summaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
Diffstat (limited to 'boot')
-rw-r--r--boot/Kconfig2030
-rw-r--r--boot/Makefile77
-rw-r--r--boot/android_ab.c442
-rw-r--r--boot/boot_fit.c79
-rw-r--r--boot/bootdev-uclass.c952
-rw-r--r--boot/bootflow.c1011
-rw-r--r--boot/bootflow_internal.h54
-rw-r--r--boot/bootflow_menu.c331
-rw-r--r--boot/bootm.c1269
-rw-r--r--boot/bootm_os.c603
-rw-r--r--boot/bootmeth-uclass.c486
-rw-r--r--boot/bootmeth_android.c627
-rw-r--r--boot/bootmeth_android.h29
-rw-r--r--boot/bootmeth_cros.c491
-rw-r--r--boot/bootmeth_cros.h197
-rw-r--r--boot/bootmeth_efi.c334
-rw-r--r--boot/bootmeth_efi_mgr.c124
-rw-r--r--boot/bootmeth_extlinux.c267
-rw-r--r--boot/bootmeth_pxe.c202
-rw-r--r--boot/bootmeth_qfw.c102
-rw-r--r--boot/bootmeth_rauc.c436
-rw-r--r--boot/bootmeth_sandbox.c69
-rw-r--r--boot/bootmeth_script.c272
-rw-r--r--boot/bootretry.c59
-rw-r--r--boot/bootstd-uclass.c236
-rw-r--r--boot/cedit.c888
-rw-r--r--boot/common_fit.c85
-rw-r--r--boot/expo.c355
-rw-r--r--boot/expo_build.c480
-rw-r--r--boot/expo_build_cb.c245
-rw-r--r--boot/fdt_region.c702
-rw-r--r--boot/fdt_simplefb.c119
-rw-r--r--boot/fdt_support.c2270
-rw-r--r--boot/image-android-dt.c156
-rw-r--r--boot/image-android.c906
-rw-r--r--boot/image-board.c1124
-rw-r--r--boot/image-cipher.c175
-rw-r--r--boot/image-fdt.c722
-rw-r--r--boot/image-fit-sig.c560
-rw-r--r--boot/image-fit.c2523
-rw-r--r--boot/image-host.c27
-rw-r--r--boot/image-pre-load.c426
-rw-r--r--boot/image-sig.c111
-rw-r--r--boot/image.c761
-rw-r--r--boot/prog_boot.c51
-rw-r--r--boot/pxe_utils.c1653
-rw-r--r--boot/scene.c1213
-rw-r--r--boot/scene_internal.h442
-rw-r--r--boot/scene_menu.c624
-rw-r--r--boot/scene_textedit.c61
-rw-r--r--boot/scene_textline.c237
-rw-r--r--boot/upl_common.c60
-rw-r--r--boot/upl_common.h24
-rw-r--r--boot/upl_read.c588
-rw-r--r--boot/upl_write.c622
-rw-r--r--boot/vbe.c118
-rw-r--r--boot/vbe_abrec.c83
-rw-r--r--boot/vbe_abrec.h115
-rw-r--r--boot/vbe_abrec_fw.c276
-rw-r--r--boot/vbe_common.c381
-rw-r--r--boot/vbe_common.h180
-rw-r--r--boot/vbe_request.c232
-rw-r--r--boot/vbe_simple.c170
-rw-r--r--boot/vbe_simple.h59
-rw-r--r--boot/vbe_simple_fw.c160
-rw-r--r--boot/vbe_simple_os.c111
66 files changed, 30874 insertions, 0 deletions
diff --git a/boot/Kconfig b/boot/Kconfig
new file mode 100644
index 00000000000..2ff6f003738
--- /dev/null
+++ b/boot/Kconfig
@@ -0,0 +1,2030 @@
+menu "Boot options"
+
+source "lib/efi_loader/Kconfig"
+
+menu "Boot images"
+
+config ANDROID_BOOT_IMAGE
+ bool "Android Boot Images"
+ default y if FASTBOOT
+ help
+ This enables support for booting images which use the Android
+ image format header.
+
+config ANDROID_BOOT_IMAGE_IGNORE_BLOB_ADDR
+ bool "Android Boot Image ignore addr"
+ default n
+ help
+ This ignore kernel/ramdisk load addr specified in androidboot header.
+
+ There is a concern on exposing the whole memory to image loading is
+ dangerous. Also, since it's not always possible to change the load
+ addr by repacking the boot.img (mainly due to AVB signature mismatch),
+ we need a way to use kernel_addr_r and ramdisk_addr_r.
+
+config TIMESTAMP
+ bool "Show image date and time when displaying image information"
+ default y if CMD_DATE
+ help
+ When CONFIG_TIMESTAMP is selected, the timestamp (date and time) of
+ an image is printed by image commands like bootm or iminfo. This
+ is shown as 'Timestamp: xxx' and 'Created: xxx'. If this option is
+ enabled, then U-Boot requires FITs to have a timestamp. If a FIT is
+ loaded that does not, the message 'Wrong FIT format: no timestamp'
+ is shown.
+
+config BUTTON_CMD
+ bool "Support for running a command if a button is held during boot"
+ depends on CMDLINE
+ depends on BUTTON
+ help
+ For many embedded devices it's useful to enter a special flashing mode
+ such as fastboot mode when a button is held during boot. This option
+ allows arbitrary commands to be assigned to specific buttons. These will
+ be run after "preboot" if the button is held. Configuration is done via
+ the environment variables "button_cmd_N_name" and "button_cmd_N" where n is
+ the button number (starting from 0). e.g:
+
+ "button_cmd_0_name=vol_down"
+ "button_cmd_0=fastboot usb 0"
+
+menuconfig FIT
+ bool "Flattened Image Tree (FIT)"
+ select HASH
+ select MD5
+ select SHA1
+ imply SHA256
+ help
+ This option allows you to boot the new uImage structure,
+ Flattened Image Tree. FIT is formally a FDT, which can include
+ images of various types (kernel, FDT blob, ramdisk, etc.)
+ in a single blob. To boot this new uImage structure,
+ pass the address of the blob to the "bootm" command.
+ FIT is very flexible, supporting compression, multiple images,
+ multiple configurations, verification through hashing and also
+ verified boot (secure boot using RSA).
+
+if FIT
+
+config FIT_EXTERNAL_OFFSET
+ hex "FIT external data offset"
+ default 0x0
+ help
+ This specifies a data offset in fit image.
+ The offset is from data payload offset to the beginning of
+ fit image header. When specifies a offset, specific data
+ could be put in the hole between data payload and fit image
+ header, such as CSF data on i.MX platform.
+
+config FIT_FULL_CHECK
+ bool "Do a full check of the FIT before using it"
+ default y
+ help
+ Enable this do a full check of the FIT to make sure it is valid. This
+ helps to protect against carefully crafted FITs which take advantage
+ of bugs or omissions in the code. This includes a bad structure,
+ multiple root nodes and the like.
+
+config FIT_SIGNATURE
+ bool "Enable signature verification of FIT uImages"
+ depends on DM
+ select HASH
+ imply RSA
+ imply RSA_VERIFY
+ select IMAGE_SIGN_INFO
+ select FIT_FULL_CHECK
+ help
+ This option enables signature verification of FIT uImages,
+ using a hash signed and verified using RSA. If
+ CONFIG_SHA_PROG_HW_ACCEL is defined, i.e support for progressive
+ hashing is available using hardware, then the RSA library will use
+ it. See doc/usage/fit/signature.rst for more details.
+
+ WARNING: When relying on signed FIT images with a required signature
+ check the legacy image format is disabled by default, so that
+ unsigned images cannot be loaded. If a board needs the legacy image
+ format support in this case, enable it using
+ CONFIG_LEGACY_IMAGE_FORMAT.
+
+config FIT_SIGNATURE_MAX_SIZE
+ hex "Max size of signed FIT structures"
+ depends on FIT_SIGNATURE
+ default 0x10000000
+ help
+ This option sets a max size in bytes for verified FIT uImages.
+ A sane value of 256MB protects corrupted DTB structures from overlapping
+ device memory. Assure this size does not extend past expected storage
+ space.
+
+config FIT_RSASSA_PSS
+ bool "Support rsassa-pss signature scheme of FIT image contents"
+ depends on FIT_SIGNATURE
+ help
+ Enable this to support the pss padding algorithm as described
+ in the rfc8017 (https://tools.ietf.org/html/rfc8017).
+
+config FIT_CIPHER
+ bool "Enable ciphering data in a FIT uImages"
+ depends on DM
+ select AES
+ help
+ Enable the feature of data ciphering/unciphering in the tool mkimage
+ and in the u-boot support of the FIT image.
+
+config FIT_VERBOSE
+ bool "Show verbose messages when FIT images fail"
+ help
+ Generally a system will have valid FIT images so debug messages
+ are a waste of code space. If you are debugging your images then
+ you can enable this option to get more verbose information about
+ failures.
+
+config FIT_BEST_MATCH
+ bool "Select the best match for the kernel device tree"
+ help
+ When no configuration is explicitly selected, default to the
+ one whose fdt's compatibility field best matches that of
+ U-Boot itself. A match is considered "best" if it matches the
+ most specific compatibility entry of U-Boot's fdt's root node.
+ The order of entries in the configuration's fdt is ignored.
+
+config FIT_IMAGE_POST_PROCESS
+ bool "Enable post-processing of FIT artifacts after loading by U-Boot"
+ depends on SOCFPGA_SECURE_VAB_AUTH
+ help
+ Allows doing any sort of manipulation to blobs after they got extracted
+ from FIT images like stripping off headers or modifying the size of the
+ blob, verification, authentication, decryption etc. in a platform or
+ board specific way. In order to use this feature a platform or board-
+ specific implementation of board_fit_image_post_process() must be
+ provided. Also, anything done during this post-processing step would
+ need to be comprehended in how the images were prepared before being
+ injected into the FIT creation (i.e. the blobs would have been pre-
+ processed before being added to the FIT image).
+
+config FIT_PRINT
+ bool "Support FIT printing"
+ default y
+ help
+ Support printing the content of the fitImage in a verbose manner.
+
+config SPL_FIT
+ bool "Support Flattened Image Tree within SPL"
+ depends on SPL
+ select SPL_HASH
+ select SPL_OF_LIBFDT
+
+config VPL_FIT
+ bool "Support Flattened Image Tree within VPL"
+ depends on VPL
+ select VPL_HASH
+ select VPL_OF_LIBFDT
+
+config TPL_FIT
+ bool "Support Flattened Image Tree within TPL"
+ depends on TPL
+ select TPL_HASH
+ select TPL_OF_LIBFDT
+
+config SPL_FIT_PRINT
+ bool "Support FIT printing within SPL"
+ depends on SPL_FIT
+ help
+ Support printing the content of the fitImage in a verbose manner in SPL.
+
+config SPL_FIT_FULL_CHECK
+ bool "Do a full check of the FIT before using it"
+ depends on SPL_FIT
+ help
+ Enable this do a full check of the FIT to make sure it is valid. This
+ helps to protect against carefully crafted FITs which take advantage
+ of bugs or omissions in the code. This includes a bad structure,
+ multiple root nodes and the like.
+
+config SPL_FIT_SIGNATURE
+ bool "Enable signature verification of FIT firmware within SPL"
+ depends on SPL_DM
+ depends on SPL_LOAD_FIT || SPL_LOAD_FIT_FULL
+ select FIT_SIGNATURE
+ select SPL_FIT
+ select SPL_CRYPTO
+ select SPL_HASH
+ imply SPL_RSA
+ imply SPL_RSA_VERIFY
+ select SPL_IMAGE_SIGN_INFO
+ select SPL_FIT_FULL_CHECK
+
+config SPL_FIT_SIGNATURE_MAX_SIZE
+ hex "Max size of signed FIT structures in SPL"
+ depends on SPL_FIT_SIGNATURE
+ default 0x10000000
+ help
+ This option sets a max size in bytes for verified FIT uImages.
+ A sane value of 256MB protects corrupted DTB structures from overlapping
+ device memory. Assure this size does not extend past expected storage
+ space.
+
+config SPL_FIT_RSASSA_PSS
+ bool "Support rsassa-pss signature scheme of FIT image contents in SPL"
+ depends on SPL_FIT_SIGNATURE
+ help
+ Enable this to support the pss padding algorithm as described
+ in the rfc8017 (https://tools.ietf.org/html/rfc8017) in SPL.
+
+config SPL_LOAD_FIT
+ bool "Enable SPL loading U-Boot as a FIT (basic fitImage features)"
+ depends on SPL
+ select SPL_FIT
+ help
+ Normally with the SPL framework a legacy image is generated as part
+ of the build. This contains U-Boot along with information as to
+ where it should be loaded. This option instead enables generation
+ of a FIT (Flat Image Tree) which provides more flexibility. In
+ particular it can handle selecting from multiple device tree
+ and passing the correct one to U-Boot.
+
+ This path has the following limitations:
+
+ 1. "loadables" images, other than FDTs, which do not have a "load"
+ property will not be loaded. This limitation also applies to FPGA
+ images with the correct "compatible" string.
+ 2. For FPGA images, the supported "compatible" list may be found in
+ https://fitspec.osfw.foundation/.
+ 3. FDTs are only loaded for images with an "os" property of "u-boot".
+ "linux" images are also supported with Falcon boot mode.
+
+config SPL_LOAD_FIT_ADDRESS
+ hex "load address of fit image"
+ depends on SPL_LOAD_FIT
+ default 0x44000000 if ARCH_IMX8M
+ default 0x0
+ help
+ Specify the load address of the fit image that will be loaded
+ by SPL.
+
+config SPL_LOAD_FIT_APPLY_OVERLAY
+ bool "Enable SPL applying DT overlays from FIT"
+ depends on SPL_LOAD_FIT
+ select OF_LIBFDT_OVERLAY
+ help
+ The device tree is loaded from the FIT image. Allow the SPL to
+ also load device-tree overlays from the FIT image an apply them
+ over the device tree.
+
+config SPL_LOAD_FIT_APPLY_OVERLAY_BUF_SZ
+ depends on SPL_LOAD_FIT_APPLY_OVERLAY
+ default 0x10000
+ hex "size of temporary buffer used to load the overlays"
+ help
+ The size of the area where the overlays will be loaded and
+ uncompress. Must be at least as large as biggest overlay
+ (uncompressed)
+
+config SPL_LOAD_FIT_FULL
+ bool "Enable SPL loading U-Boot as a FIT (full fitImage features)"
+ select SPL_FIT
+ help
+ Normally with the SPL framework a legacy image is generated as part
+ of the build. This contains U-Boot along with information as to
+ where it should be loaded. This option instead enables generation
+ of a FIT (Flat Image Tree) which provides more flexibility. In
+ particular it can handle selecting from multiple device tree
+ and passing the correct one to U-Boot.
+
+config TPL_LOAD_FIT
+ bool "Enable TPL loading U-Boot as a FIT (basic fitImage features)"
+ depends on TPL
+ select TPL_FIT
+ help
+ Normally with the SPL framework a legacy image is generated as part
+ of the build. This contains U-Boot along with information as to
+ where it should be loaded. This option instead enables generation
+ of a FIT (Flat Image Tree) which provides more flexibility. In
+ particular it can handle selecting from multiple device tree
+ and passing the correct one to U-Boot.
+
+ This path has the following limitations:
+
+ 1. "loadables" images, other than FDTs, which do not have a "load"
+ property will not be loaded. This limitation also applies to FPGA
+ images with the correct "compatible" string.
+ 2. For FPGA images, the supported "compatible" list is in the
+ doc/uImage.FIT/source_file_format.txt.
+ 3. FDTs are only loaded for images with an "os" property of "u-boot".
+ "linux" images are also supported with Falcon boot mode.
+
+config SPL_FIT_IMAGE_POST_PROCESS
+ bool "Enable post-processing of FIT artifacts after loading by the SPL"
+ depends on SPL_LOAD_FIT
+ default y if TI_SECURE_DEVICE
+ help
+ Allows doing any sort of manipulation to blobs after they got extracted
+ from the U-Boot FIT image like stripping off headers or modifying the
+ size of the blob, verification, authentication, decryption etc. in a
+ platform or board specific way. In order to use this feature a platform
+ or board-specific implementation of board_fit_image_post_process() must
+ be provided. Also, anything done during this post-processing step would
+ need to be comprehended in how the images were prepared before being
+ injected into the FIT creation (i.e. the blobs would have been pre-
+ processed before being added to the FIT image).
+
+if VPL
+
+config VPL_FIT
+ bool "Support Flattened Image Tree within VPL"
+ depends on VPL
+ default y
+ select VPL_HASH
+ select VPL_OF_LIBFDT
+
+config VPL_LOAD_FIT
+ bool "Enable VPL loading U-Boot as a FIT (basic fitImage features)"
+ select VPL_FIT
+ default y
+
+config VPL_LOAD_FIT_FULL
+ bool "Enable SPL loading U-Boot as a FIT (full fitImage features)"
+ select VPL_FIT
+ help
+ Normally with the SPL framework a legacy image is generated as part
+ of the build. This contains U-Boot along with information as to
+ where it should be loaded. This option instead enables generation
+ of a FIT (Flat Image Tree) which provides more flexibility. In
+ particular it can handle selecting from multiple device tree
+ and passing the correct one to U-Boot.
+
+config VPL_FIT_PRINT
+ bool "Support FIT printing within VPL"
+ depends on VPL_FIT
+ default y
+ help
+ Support printing the content of the fitImage in a verbose manner in VPL.
+
+config VPL_FIT_FULL_CHECK
+ bool "Do a full check of the FIT before using it"
+ default y
+ help
+ Enable this do a full check of the FIT to make sure it is valid. This
+ helps to protect against carefully crafted FITs which take advantage
+ of bugs or omissions in the code. This includes a bad structure,
+ multiple root nodes and the like.
+
+config VPL_FIT_SIGNATURE
+ bool "Enable signature verification of FIT firmware within VPL"
+ depends on VPL_DM
+ default y
+ select FIT_SIGNATURE
+ select VPL_FIT
+ select VPL_CRYPTO
+ select VPL_HASH
+ imply VPL_RSA
+ imply VPL_RSA_VERIFY
+ select VPL_IMAGE_SIGN_INFO
+ select VPL_FIT_FULL_CHECK
+
+config VPL_FIT_SIGNATURE_MAX_SIZE
+ hex "Max size of signed FIT structures in VPL"
+ depends on VPL_FIT_SIGNATURE
+ default 0x10000000
+ help
+ This option sets a max size in bytes for verified FIT uImages.
+ A sane value of 256MB protects corrupted DTB structures from overlapping
+ device memory. Assure this size does not extend past expected storage
+ space.
+
+endif # VPL
+
+endif # FIT
+
+config PXE_UTILS
+ bool
+ select MENU
+ help
+ Utilities for parsing PXE file formats.
+
+config BOOT_DEFAULTS_FEATURES
+ bool
+ select SUPPORT_RAW_INITRD
+ select ENV_VARS_UBOOT_CONFIG
+ imply USB_STORAGE
+ imply EFI_PARTITION
+ imply ISO_PARTITION
+
+config BOOT_DEFAULTS_CMDS
+ bool
+ imply USE_BOOTCOMMAND
+ select CMD_ENV_EXISTS
+ select CMD_EXT2
+ select CMD_EXT4
+ select CMD_FAT
+ select CMD_FS_GENERIC
+ select CMD_PART if PARTITIONS
+ select CMD_DHCP if CMD_NET
+ select CMD_PING if CMD_NET
+ select CMD_PXE if CMD_NET
+ select CMD_BOOTI if ARM64
+ select CMD_BOOTZ if ARM && !ARM64
+ imply CMD_MII if NET
+
+config BOOT_DEFAULTS
+ bool # Common defaults for standard boot and distroboot
+ select BOOT_DEFAULTS_FEATURES
+ select BOOT_DEFAULTS_CMDS if CMDLINE
+ help
+ These are not required but are commonly needed to support a good
+ selection of booting methods. Enable this to improve the capability
+ of U-Boot to boot various images. Currently much functionality is
+ tied to enabling the command that exercises it.
+
+menuconfig BOOTSTD
+ bool "Standard boot"
+ default y if BLK
+ depends on DM && OF_CONTROL
+ help
+ U-Boot supports a standard way of locating something to boot,
+ typically an Operating System such as Linux, provided by a distro such
+ as Arch Linux or Debian. Enable this to support iterating through
+ available bootdevs and using bootmeths to find bootflows suitable for
+ booting.
+
+ Standard boot is not a standard way of booting, just a framework
+ within U-Boot for supporting all the different ways that exist.
+
+ Terminology:
+
+ - bootdev - a device which can hold a distro (e.g. MMC)
+ - bootmeth - a method to scan a bootdev to find bootflows (owned by
+ U-Boot)
+ - bootflow - a description of how to boot (owned by the distro)
+
+if BOOTSTD
+
+config SPL_BOOTSTD
+ bool "Standard boot support in SPL"
+ depends on SPL && SPL_DM && SPL_OF_CONTROL && SPL_BLK
+ default y if VPL
+ help
+ This enables standard boot in SPL. This is needed so that VBE
+ (Verified Boot for Embedded) can be used, since it depends on standard
+ boot. It is enabled by default since the main purpose of VPL is to
+ handle the firmware part of VBE.
+
+config VPL_BOOTSTD
+ bool "Standard boot support in VPL"
+ depends on VPL && VPL_DM && VPL_OF_CONTROL && VPL_BLK
+ default y
+ help
+ This enables standard boot in SPL. This is needed so that VBE
+ (Verified Boot for Embedded) can be used, since it depends on standard
+ boot. It is enabled by default since the main purpose of VPL is to
+ handle the firmware part of VBE.
+
+config BOOTSTD_FULL
+ bool "Enhanced features for standard boot"
+ default y if SANDBOX
+ imply BOOTSTD_DEFAULTS
+ help
+ This enables various useful features for standard boot, which are not
+ essential for operation:
+
+ - bootdev, bootmeth commands
+ - extra features in the bootflow command
+ - support for selecting the ordering of bootmeths ("bootmeth order")
+ - support for selecting the ordering of bootdevs using the Device Tree
+ as well as the "boot_targets" environment variable
+
+config BOOTSTD_DEFAULTS
+ bool "Select some common defaults for standard boot"
+ depends on BOOTSTD
+ select BOOT_DEFAULTS
+ select BOOTMETH_DISTRO
+ help
+ These are not required but are commonly needed to support a good
+ selection of booting methods. Enable this to improve the capability
+ of U-Boot to boot various images.
+
+config BOOTSTD_BOOTCOMMAND
+ bool "Use bootstd to boot"
+ default y if !DISTRO_DEFAULTS
+ help
+ Enable this to select a default boot-command suitable for booting
+ with standard boot. This can be overridden by the board if needed,
+ but the default command should be enough for most boards which use
+ standard boot.
+
+ For now this is only selected if distro boot is NOT used, since
+ standard boot does not support all of the features of distro boot
+ yet.
+
+config BOOTSTD_MENU
+ bool "Provide a menu of available bootflows for standard boot"
+ depends on BOOTSTD_FULL && EXPO
+ default y
+ help
+ Provide a menu of available bootflows and related options.
+
+config BOOTSTD_PROG
+ bool "Use programmatic boot"
+ depends on !CMDLINE
+ default y
+ help
+ Enable this to provide a board_run_command() function which can boot
+ a system without using commands. If the boot fails, then U-Boot will
+ panic.
+
+ Note: This currently has many limitations and is not a useful booting
+ solution. Future work will eventually make this a viable option.
+
+config BOOTMETH_GLOBAL
+ bool
+ help
+ Add support for global bootmeths. This feature is used by VBE and
+ EFI bootmgr, since they take full control over which bootdevs are
+ selected to boot.
+
+config BOOTMETH_ANDROID
+ bool "Bootdev support for Android"
+ depends on X86 || ARM || SANDBOX
+ depends on CMDLINE
+ select ANDROID_BOOT_IMAGE
+ select CMD_BCB
+ imply CMD_FASTBOOT
+ imply FASTBOOT if !NET_LWIP
+ select PARTITION_TYPE_GUID
+ select PARTITION_UUIDS
+ help
+ Enables support for booting Android using bootstd. Android requires
+ multiple partitions (misc, boot, vbmeta, ...) in storage for booting.
+
+ Note that only MMC bootdevs are supported at present. This is caused
+ by AVB being limited to MMC devices only.
+
+config BOOTMETH_CROS
+ bool "Bootdev support for Chromium OS"
+ depends on X86 || ARM || SANDBOX
+ default y if !ARM
+ select EFI_PARTITION
+ select PARTITION_TYPE_GUID
+ select PARTITION_UUIDS
+ help
+ Enables support for booting Chromium OS using bootdevs. This uses the
+ kernel A slot and obtains the kernel command line from the parameters
+ provided there.
+
+ Note that only x86 devices are supported at present.
+
+config BOOTMETH_EXTLINUX
+ bool "Bootdev support for extlinux boot"
+ select PXE_UTILS
+ default y
+ help
+ Enables support for extlinux boot using bootdevs. This makes the
+ bootdevs look for a 'extlinux/extlinux.conf' on each filesystem
+ they scan.
+
+ The specification for this file is here:
+
+ https://uapi-group.org/specifications/specs/boot_loader_specification/
+
+ This provides a way to try out standard boot on an existing boot flow.
+
+config BOOTMETH_EXTLINUX_PXE
+ bool "Bootdev support for extlinux boot over network"
+ depends on CMD_PXE && CMD_NET && DM_ETH
+ default y
+ help
+ Enables support for extlinux boot using bootdevs. This makes the
+ bootdevs look for a 'extlinux/extlinux.conf' on the tftp server.
+
+ The specification for this file is here:
+
+ https://uapi-group.org/specifications/specs/boot_loader_specification/
+
+ This provides a way to try out standard boot on an existing boot flow.
+
+config BOOTMETH_EFILOADER
+ bool "Bootdev support for EFI boot"
+ depends on EFI_BINARY_EXEC
+ imply CMD_TFTPBOOT if CMD_NET
+ default y
+ help
+ Enables support for EFI boot using bootdevs. This makes the
+ bootdevs look for a 'boot<arch>.efi' on each filesystem
+ they scan. The resulting file is booted after enabling U-Boot's
+ EFI loader support.
+
+ The <arch> depends on the architecture of the board:
+
+ aa64 - aarch64 (ARM 64-bit)
+ arm - ARM 32-bit
+ ia32 - x86 32-bit
+ x64 - x86 64-bit
+ riscv32 - RISC-V 32-bit
+ riscv64 - RISC-V 64-bit
+
+ This provides a way to try out standard boot on an existing boot flow.
+
+config BOOTMETH_EFI_BOOTMGR
+ bool "Bootdev support for EFI boot manager"
+ depends on EFI_BOOTMGR
+ select BOOTMETH_GLOBAL
+ default y
+ help
+ Enable booting via the UEFI boot manager. Based on the EFI variables
+ the EFI binary to be launched is determined. To set the EFI variables
+ use the eficonfig command.
+
+config BOOTMETH_QFW
+ bool "Boot method using QEMU parameters"
+ depends on QFW
+ default y
+ help
+ Use QEMU parameters -kernel, -initrd, -append to determine the kernel,
+ initial RAM disk, and kernel command line parameters to boot an
+ operating system. U-Boot's control device-tree is passed to the kernel.
+
+config BOOTMETH_VBE
+ bool "Bootdev support for Verified Boot for Embedded"
+ depends on FIT
+ default y
+ select BOOTMETH_GLOBAL
+ select EVENT
+ help
+ Enables support for VBE boot. This is a standard boot method which
+ supports selection of various firmware components, selection of an OS to
+ boot as well as updating these using fwupd.
+
+config BOOTMETH_DISTRO
+ bool # Options needed to boot any distro
+ select BOOTMETH_SCRIPT if CMDLINE # E.g. Armbian uses scripts
+ select BOOTMETH_EXTLINUX # E.g. Debian uses these
+ select BOOTMETH_EXTLINUX_PXE if CMD_PXE && CMD_NET && DM_ETH
+ select BOOTMETH_EFILOADER if EFI_BINARY_EXEC # E.g. Ubuntu uses this
+
+config SPL_BOOTMETH_VBE
+ bool "Bootdev support for Verified Boot for Embedded (SPL)"
+ depends on SPL && FIT
+ select EVENT
+ default y if VPL
+ help
+ Enables support for VBE boot. This is a standard boot method which
+ supports selection of various firmware components, selection of an OS to
+ boot as well as updating these using fwupd.
+
+config VPL_BOOTMETH_VBE
+ bool "Bootdev support for Verified Boot for Embedded (VPL)"
+ depends on VPL && FIT
+ select EVENT
+ default y
+ help
+ Enables support for VBE boot. This is a standard boot method which
+ supports selection of various firmware components, selection of an OS to
+ boot as well as updating these using fwupd.
+
+config TPL_BOOTMETH_VBE
+ bool "Bootdev support for Verified Boot for Embedded (TPL)"
+ depends on TPL
+ default y
+ help
+ Enables support for VBE boot. This is a standard boot method which
+ supports selection of various firmware components, seleciton of an OS to
+ boot as well as updating these using fwupd.
+
+if BOOTMETH_VBE
+
+config BOOTMETH_VBE_REQUEST
+ bool "Support for serving VBE OS requests"
+ default y
+ help
+ Enables support for looking that the requests made by the
+ Operating System being booted. These requests result in additions to
+ the device tree /chosen node, added during the device tree fixup
+ phase.
+
+config SPL_BOOTMETH_VBE_REQUEST
+ bool "Support for serving VBE OS requests (SPL)"
+ depends on SPL
+ help
+ Enables support for looking that the requests made by the
+ Operating System being booted. These requests result in additions to
+ the device tree /chosen node, added during the device tree fixup
+ phase.
+
+ This is only useful if you are booting an OS direct from SPL.
+
+config BOOTMETH_VBE_SIMPLE
+ bool "Bootdev support for VBE 'simple' method"
+ default y
+ imply SPL_CRC8 if SPL
+ imply VPL_CRC8 if VPL
+ help
+ Enables support for VBE 'simple' boot. This allows updating a single
+ firmware image in boot media such as MMC. It does not support any sort
+ of rollback, recovery or A/B boot.
+
+config BOOTMETH_VBE_ABREC
+ bool "Bootdev support for VBE 'a/b/recovery' method"
+ imply SPL_CRC8
+ imply VPL_CRC8
+ help
+ Enables support for VBE 'abrec' boot. This allows updating one of an
+ A or B firmware image in boot media such as MMC. The new firmware is
+ tried and if it boots, it is copied to the other image, so that both
+ A and B have the same version. If neither firmware image passes the
+ verification step, a recovery image is booted. This method will
+ eventually provide rollback protection as well.
+
+if BOOTMETH_VBE_SIMPLE
+
+config BOOTMETH_VBE_SIMPLE_OS
+ bool "Bootdev support for VBE 'simple' method OS phase"
+ default y
+ help
+ Enables support for the OS parts of VBE 'simple' boot. This includes
+ fixing up the device tree with the required VBE information, ready
+ for booting into the OS. This option is only enabled for U-Boot
+ proper, since it is the phase where device tree fixups happen.
+
+config SPL_BOOTMETH_VBE_SIMPLE
+ bool "Bootdev support for VBE 'simple' method (SPL)"
+ depends on SPL
+ default y if VPL
+ help
+ Enables support for VBE 'simple' boot. This allows updating a single
+ firmware image in boot media such as MMC. It does not support any sort
+ of rollback, recovery or A/B boot.
+
+config VPL_BOOTMETH_VBE_SIMPLE
+ bool "Bootdev support for VBE 'simple' method (VPL)"
+ depends on VPL
+ default y
+ help
+ Enables support for VBE 'simple' boot. This allows updating a single
+ firmware image in boot media such as MMC. It does not support any sort
+ of rollback, recovery or A/B boot.
+
+config SPL_BOOTMETH_VBE_SIMPLE_FW
+ bool "Bootdev support for VBE 'simple' method firmware phase (SPL)"
+ depends on VPL
+ default y
+ help
+ Enables support for the firmware parts of VBE 'simple' boot. This
+ includes an SPL loader which locates the correct U-Boot to boot into.
+ This option should really only be enabled for VPL, since it is the
+ phase where the SPL + U-Boot decision should be made. But for now,
+ SPL does its own FIT-configuration selection.
+
+config VPL_BOOTMETH_VBE_SIMPLE_FW
+ bool "Bootdev support for VBE 'simple' method firmware phase (VPL)"
+ depends on VPL
+ default y
+ help
+ Enables support for the firmware parts of VBE 'simple' boot. This
+ includes an SPL loader which locates the correct SPL to boot into.
+ This option enabled for VPL, since it is the phase where the SPL
+ decision is made.
+
+config TPL_BOOTMETH_VBE_SIMPLE_FW
+ bool "Bootdev support for VBE 'simple' method firmware phase (TPL)"
+ depends on VPL
+ default y
+ help
+ Enables support for the firmware parts of VBE 'simple' boot, in TPL.
+ TPL loads a FIT containing the VPL binary and a suitable devicetree.
+
+endif # BOOTMETH_VBE_SIMPLE
+
+if BOOTMETH_VBE_ABREC
+
+config SPL_BOOTMETH_VBE_ABREC
+ bool "Bootdev support for VBE 'abrec' method (SPL)"
+ depends on SPL
+ default y if VPL
+ help
+ Enables support for VBE 'abrec' boot. The SPL part of this
+ implementation simply loads U-Boot from the image selected by the
+ VPL phase.
+
+config TPL_BOOTMETH_VBE_ABREC
+ bool "Bootdev support for VBE 'abrec' method (TPL)"
+ depends on TPL
+ select TPL_FIT
+ default y
+ help
+ Enables support for VBE 'abrec' boot. The TPL part of this
+ implementation simply jumps to VPL after device init is completed.
+
+config VPL_BOOTMETH_VBE_ABREC
+ bool "Bootdev support for VBE 'abrec' method (VPL)"
+ depends on VPL
+ default y
+ help
+ Enables support for VBE 'abrec' boot. The VPL part of this
+ implementation selects which SPL to use (A, B or recovery) and then
+ boots into SPL.
+
+config SPL_BOOTMETH_VBE_ABREC_FW
+ bool "Bootdev support for VBE 'abrec' method firmware phase (SPL)"
+ depends on SPL
+ default y if VPL
+ help
+ Enables support for VBE 'abrec' boot. The SPL part of this
+ implementation simply loads U-Boot from the image selected by the
+ VPL phase.
+
+config TPL_BOOTMETH_VBE_ABREC_FW
+ bool "Bootdev support for VBE 'abrec' method firmware phase (TPL)"
+ depends on TPL
+ default y if VPL
+ help
+ Enables support for VBE 'abrec' boot. The TPL part of this
+ implementation simply jumps to VPL after device init is completed.
+
+config VPL_BOOTMETH_VBE_ABREC_FW
+ bool "Bootdev support for VBE 'abrec' method firmware phase (VPL)"
+ depends on VPL
+ default y
+ help
+ Enables support for VBE 'abrec' boot. The VPL part of this
+ implementation selects which SPL to use (A, B or recovery) and then
+ boots into SPL.
+
+endif # BOOTMETH_VBE_ABREC
+
+endif # BOOTMETH_VBE
+
+config EXPO
+ bool "Support for expos - groups of scenes displaying a UI"
+ depends on VIDEO
+ default y if BOOTMETH_VBE
+ help
+ An expo is a way of presenting and collecting information from the
+ user. It consists of a collection of 'scenes' of which only one is
+ presented at a time. An expo is typically used to show a boot menu
+ and allow settings to be changed.
+
+ The expo can be presented in graphics form using a vidconsole, or in
+ text form on a serial console.
+
+config BOOTMETH_RAUC
+ bool "Bootdev support for RAUC A/B systems"
+ depends on CMDLINE
+ select BOOTMETH_GLOBAL
+ select HUSH_PARSER
+ help
+ Enables support for booting RAUC A/B systems from MMC devices. This
+ makes the bootdevs look for a 'boot.scr.uimg' or 'boot.scr' in the
+ respective boot partitions, describing how to boot the distro.
+
+if BOOTMETH_RAUC
+
+config BOOTMETH_RAUC_BOOT_ORDER
+ string "RAUC boot order"
+ default "A B"
+ help
+ Sets the default boot order. This must be list of space-separated
+ strings naming the individual boot slots. Each entry in this string
+ should correspond to an existing slot on the target's flash device.
+
+config BOOTMETH_RAUC_PARTITIONS
+ string "RAUC boot and root partitions indexes"
+ default "1,2 3,4"
+ help
+ Sets the partition indexes of boot and root slots. This must be a list
+ of comma-separated pair values, which in turn are separated by spaces.
+ The first value in pair is for the boot partition and second for the
+ root partition.
+
+config BOOTMETH_RAUC_DEFAULT_TRIES
+ int "RAUC slot default tries"
+ default 3
+ help
+ Sets how many times a slot should be tried booting, before considering
+ it to be bad.
+
+config BOOTMETH_RAUC_RESET_ALL_ZERO_TRIES
+ bool "Reset slot tries when all RAUC slots have zero tries left"
+ default y
+ help
+ When all slots have zero tries left or no valid slot was found, reset
+ to the default boot order set by BOOTMETH_RAUC_BOOT_ORDER and set the
+ slot tries to their default value specified by
+ BOOTMETH_RAUC_DEFAULT_TRIES.
+
+ This prevents a system from remaining in an unbootable state, after
+ all slot tries were decremented to zero.
+
+endif # BOOTMETH_RAUC
+
+config BOOTMETH_SANDBOX
+ def_bool y
+ depends on SANDBOX
+ help
+ This is a sandbox bootmeth driver used for testing. It always returns
+ -ENOTSUPP when attempting to boot.
+
+config BOOTMETH_SCRIPT
+ bool "Bootdev support for U-Boot scripts"
+ default y if BOOTSTD_FULL
+ depends on CMDLINE
+ select HUSH_PARSER
+ help
+ Enables support for booting a distro via a U-Boot script. This makes
+ the bootdevs look for a 'boot/boot.scr' file which can be used to
+ boot the distro.
+
+ This provides a way to try out standard boot on an existing boot flow.
+ It is not enabled by default to save space.
+
+config UPL
+ bool "upl - Universal Payload Specification"
+ imply CMD_UPL
+ imply UPL_READ
+ imply UPL_WRITE
+ imply SPL_UPL if SPL
+ help
+ Provides support for UPL payloads and handoff information. U-Boot
+ supports generating and accepting handoff information. The mkimage
+ tool will eventually support creating payloads.
+
+if UPL
+
+config UPL_READ
+ bool "upl - Support reading a Universal Payload handoff"
+ help
+ Provides support for decoding a UPL-format payload into a C structure
+ which can be used elsewhere in U-Boot. This is just the reading
+ implementation, useful for trying it out. See UPL_IN for how
+ to tell U-Boot to actually read it on startup and use it for memory
+ and device information, etc.
+
+config UPL_WRITE
+ bool "upl - Support writing a Universal Payload handoff"
+ help
+ Provides support for encoding a UPL-format payload from a C structure
+ so it can be passed to another program. This is just the writing
+ implementation, useful for trying it out. See SPL_UPL_OUT
+ for how to tell U-Boot SPL to actually write it before jumping to
+ the next phase.
+
+config UPL_IN
+ bool "upl - Read the UPL handoff on startup"
+ select UPL_READ
+ help
+ Read an SPL handoff when U-Boot starts and use it to provide
+ devices, memory layout, etc. required by U-Boot. This allows U-Boot
+ to function as a payload in the meaning of the specification.
+
+if SPL
+
+config SPL_UPL
+ bool "Write a UPL handoff in SPL"
+ imply SPL_UPL_OUT
+ help
+ This tells SPL to write a UPL handoff and pass it to the next phase
+ (e.g. to U-Boot or another program which SPL loads and runs). THis
+ provides information to help that program run correctly and
+ efficiently on the machine.
+
+config SPL_UPL_WRITE
+ bool # upl - Support writing a Universal Payload handoff in SPL
+ select SPL_BLOBLIST
+ help
+ Provides support for encoding a UPL-format payload from a C structure
+ so it can be passed to another program. This is just the writing
+ implementation, useful for trying it out.
+
+config SPL_UPL_OUT
+ bool "upl - Support writing a Universal Payload handoff in SPL"
+ select SPL_UPL_WRITE
+ help
+ Provides support for encoding a UPL-format payload and passing it to
+ the next firmware phase. This allows U-Boot SPL to function as
+ Platform Init in the meaning of the specification.
+
+endif # SPL
+
+endif # UPL
+
+endif # BOOTSTD
+
+config LEGACY_IMAGE_FORMAT
+ bool "Enable support for the legacy image format"
+ default y if !FIT_SIGNATURE && !TI_SECURE_DEVICE
+ help
+ This option enables the legacy image format. It is enabled by
+ default for backward compatibility, unless FIT_SIGNATURE is
+ set where it is disabled so that unsigned images cannot be
+ loaded. If a board needs the legacy image format support in this
+ case, enable it here.
+
+config MEASURED_BOOT
+ bool "Measure boot images and configuration when booting without EFI"
+ depends on HASH && TPM_V2
+ select SHA1
+ select SHA256
+ select SHA384
+ select SHA512
+ help
+ This option enables measurement of the boot process when booting
+ without UEFI . Measurement involves creating cryptographic hashes
+ of the binary images that are booting and storing them in the TPM.
+ In addition, a log of these hashes is stored in memory for the OS
+ to verify the booted images and configuration. Enable this if the
+ OS has configured some memory area for the event log and you intend
+ to use some attestation tools on your system.
+
+if MEASURED_BOOT
+ config MEASURE_DEVICETREE
+ bool "Measure the devicetree image"
+ default y if MEASURED_BOOT
+ help
+ On some platforms, the Device Tree is not static as it may contain
+ random MAC addresses or other such data that changes each boot.
+ Therefore, it should not be measured into the TPM. In that case,
+ disable the measurement here.
+
+ config MEASURE_IGNORE_LOG
+ bool "Ignore the existing event log"
+ help
+ On platforms that use an event log memory region that persists
+ through system resets and are the first stage bootloader, then
+ this option should be enabled to ignore any existing data in the
+ event log memory region.
+endif # MEASURED_BOOT
+
+config SYS_BOOTM_LEN
+ hex "Maximum size of a decompresed OS image"
+ depends on CMD_BOOTM || CMD_BOOTI || CMD_BOOTZ || \
+ LEGACY_IMAGE_FORMAT || SPL_LEGACY_IMAGE_FORMAT
+ default 0x4000000 if PPC || ARM64
+ default 0x1000000 if X86 || ARCH_MX6 || ARCH_MX7
+ default 0x800000
+ help
+ This is the maximum size of the buffer that is used to decompress the OS
+ image in to if attempting to boot a compressed image.
+
+config SUPPORT_RAW_INITRD
+ bool "Enable raw initrd images"
+ help
+ Note, defining the SUPPORT_RAW_INITRD allows user to supply
+ kernel with raw initrd images. The syntax is slightly different, the
+ address of the initrd must be augmented by it's size, in the following
+ format: "<initrd address>:<initrd size>".
+
+config CHROMEOS
+ bool "Support booting Chrome OS"
+ help
+ Chrome OS requires U-Boot to set up a table indicating the boot mode
+ (e.g. Developer mode) and a few other things. Enable this if you are
+ booting on a Chromebook to avoid getting an error about an invalid
+ firmware ID.
+
+config CHROMEOS_VBOOT
+ bool "Support Chrome OS verified boot"
+ help
+ This is intended to enable the full Chrome OS verified boot support
+ in U-Boot. It is not actually implemented in the U-Boot source code
+ at present, so this option is always set to 'n'. It allows
+ distinguishing between booting Chrome OS in a basic way (developer
+ mode) and a full boot.
+
+config SYS_RAMBOOT
+ bool
+
+config RAMBOOT_PBL
+ bool "Freescale PBL(pre-boot loader) image format support"
+ select SYS_RAMBOOT if PPC
+ help
+ Some SoCs use PBL to load RCW and/or pre-initialization instructions.
+ For more details refer to doc/README.pblimage
+
+choice
+ prompt "Freescale PBL (or predecessor) load location"
+ depends on RAMBOOT_PBL || ((TARGET_P1010RDB_PA || TARGET_P1010RDB_PB \
+ || TARGET_P1020RDB_PC || TARGET_P1020RDB_PD || TARGET_P2020RDB) \
+ && !CMD_NAND) || (TARGET_TURRIS_1X && SYS_MPC85XX_NO_RESETVEC)
+
+config SDCARD
+ bool "Freescale PBL (or similar) is found on SD card"
+
+config SPIFLASH
+ bool "Freescale PBL (or similar) is found on SPI flash"
+
+config NO_PBL
+ bool "Freescale PBL (or similar) is not used in this case"
+
+endchoice
+
+config FSL_FIXED_MMC_LOCATION
+ bool "PBL MMC is at a fixed location"
+ depends on SDCARD && !RAMBOOT_PBL
+
+config ESDHC_HC_BLK_ADDR
+ def_bool y
+ depends on FSL_FIXED_MMC_LOCATION && (ARCH_BSC9131 || ARCH_BSC9132 || ARCH_P1010)
+ help
+ In High Capacity SD Cards (> 2 GBytes), the 32-bit source address and
+ code length of these soc specify the memory address in block address
+ format. Block length is fixed to 512 bytes as per the SD High
+ Capacity specification.
+
+config SYS_FSL_PBL_PBI
+ string "PBI(pre-boot instructions) commands for the PBL image"
+ depends on RAMBOOT_PBL
+ help
+ PBI commands can be used to configure SoC before it starts the execution.
+ Please refer doc/README.pblimage for more details.
+
+config SYS_FSL_PBL_RCW
+ string "Aadditional RCW (Power on reset configuration) for the PBL image"
+ depends on RAMBOOT_PBL
+ help
+ Enables addition of RCW (Power on reset configuration) in built image.
+ Please refer doc/README.pblimage for more details.
+
+config SYS_BOOT_RAMDISK_HIGH
+ depends on CMD_BOOTM || CMD_BOOTI || CMD_BOOTZ
+ depends on !(NIOS2 || SANDBOX || SH || XTENSA)
+ def_bool y
+ select LMB
+ help
+ Enable initrd_high functionality. If defined then the initrd_high
+ feature is enabled and the boot* ramdisk subcommand is enabled.
+
+endmenu # Boot images
+
+config DISTRO_DEFAULTS
+ bool "(deprecated) Script-based booting of Linux distributions"
+ select CMDLINE
+ select BOOT_DEFAULTS
+ select AUTO_COMPLETE
+ select CMDLINE_EDITING
+ select CMD_SYSBOOT
+ select HUSH_PARSER
+ select SYS_LONGHELP
+ help
+ Note: These scripts have been replaced by Standard Boot. Do not use
+ them on new boards. See 'Migrating from distro_boot' at
+ doc/develop/bootstd.rst
+
+ Select this to enable various options and commands which are suitable
+ for building u-boot for booting general purpose Linux distributions.
+
+menu "Boot timing"
+
+config BOOTSTAGE
+ bool "Boot timing and reporting"
+ help
+ Enable recording of boot time while booting. To use it, insert
+ calls to bootstage_mark() with a suitable BOOTSTAGE_ID from
+ bootstage.h. Only a single entry is recorded for each ID. You can
+ give the entry a name with bootstage_mark_name(). You can also
+ record elapsed time in a particular stage using bootstage_start()
+ before starting and bootstage_accum() when finished. Bootstage will
+ add up all the accumulated time and report it.
+
+ Normally, IDs are defined in bootstage.h but a small number of
+ additional 'user' IDs can be used by passing BOOTSTAGE_ID_ALLOC
+ as the ID.
+
+ Calls to show_boot_progress() will also result in log entries but
+ these will not have names.
+
+config SPL_BOOTSTAGE
+ bool "Boot timing and reported in SPL"
+ depends on BOOTSTAGE && SPL
+ help
+ Enable recording of boot time in SPL. To make this visible to U-Boot
+ proper, enable BOOTSTAGE_STASH as well. This will stash the timing
+ information when SPL finishes and load it when U-Boot proper starts
+ up.
+
+config TPL_BOOTSTAGE
+ bool "Boot timing and reported in TPL"
+ depends on BOOTSTAGE && TPL
+ help
+ Enable recording of boot time in SPL. To make this visible to U-Boot
+ proper, enable BOOTSTAGE_STASH as well. This will stash the timing
+ information when TPL finishes and load it when U-Boot proper starts
+ up.
+
+config BOOTSTAGE_REPORT
+ bool "Display a detailed boot timing report before booting the OS"
+ depends on BOOTSTAGE
+ help
+ Enable output of a boot time report just before the OS is booted.
+ This shows how long it took U-Boot to go through each stage of the
+ boot process. The report looks something like this:
+
+ Timer summary in microseconds:
+ Mark Elapsed Stage
+ 0 0 reset
+ 3,575,678 3,575,678 board_init_f start
+ 3,575,695 17 arch_cpu_init A9
+ 3,575,777 82 arch_cpu_init done
+ 3,659,598 83,821 board_init_r start
+ 3,910,375 250,777 main_loop
+ 29,916,167 26,005,792 bootm_start
+ 30,361,327 445,160 start_kernel
+
+config BOOTSTAGE_RECORD_COUNT
+ int "Number of boot stage records to store"
+ depends on BOOTSTAGE
+ default 50
+ help
+ This is the size of the bootstage record list and is the maximum
+ number of bootstage records that can be recorded.
+
+config SPL_BOOTSTAGE_RECORD_COUNT
+ int "Number of boot stage records to store for SPL"
+ depends on SPL_BOOTSTAGE
+ default 5
+ help
+ This is the size of the bootstage record list and is the maximum
+ number of bootstage records that can be recorded.
+
+config TPL_BOOTSTAGE_RECORD_COUNT
+ int "Number of boot stage records to store for TPL"
+ depends on TPL_BOOTSTAGE
+ default 5
+ help
+ This is the size of the bootstage record list and is the maximum
+ number of bootstage records that can be recorded.
+
+config BOOTSTAGE_FDT
+ bool "Store boot timing information in the OS device tree"
+ depends on BOOTSTAGE
+ help
+ Stash the bootstage information in the FDT. A root 'bootstage'
+ node is created with each bootstage id as a child. Each child
+ has a 'name' property and either 'mark' containing the
+ mark time in microseconds, or 'accum' containing the
+ accumulated time for that bootstage id in microseconds.
+ For example:
+
+ bootstage {
+ 154 {
+ name = "board_init_f";
+ mark = <3575678>;
+ };
+ 170 {
+ name = "lcd";
+ accum = <33482>;
+ };
+ };
+
+ Code in the Linux kernel can find this in /proc/devicetree.
+
+config BOOTSTAGE_STASH
+ bool "Stash the boot timing information in memory before booting OS"
+ depends on BOOTSTAGE
+ help
+ Some OSes do not support device tree. Bootstage can instead write
+ the boot timing information in a binary format at a given address.
+ This happens through a call to bootstage_stash(), typically in
+ the CPU's cleanup_before_linux() function. You can use the
+ 'bootstage stash' and 'bootstage unstash' commands to do this on
+ the command line.
+
+config BOOTSTAGE_STASH_ADDR
+ hex "Address to stash boot timing information"
+ depends on BOOTSTAGE_STASH
+ default 0xC3000000 if STM32MP13X || STM32MP15X
+ default 0x87000000 if STM32MP25X
+ default 0x0 if SANDBOX
+ help
+ Provide an address which will not be overwritten by the OS when it
+ starts, so that it can read this information when ready.
+
+config BOOTSTAGE_STASH_SIZE
+ hex "Size of boot timing stash region"
+ depends on BOOTSTAGE_STASH
+ default 0x1000
+ help
+ This should be large enough to hold the bootstage stash. A value of
+ 4096 (4KiB) is normally plenty.
+
+config SHOW_BOOT_PROGRESS
+ bool "Show boot progress in a board-specific manner"
+ help
+ Defining this option allows to add some board-specific code (calling
+ a user-provided function show_boot_progress(int) that enables you to
+ show the system's boot progress on some display (for example, some
+ LEDs) on your board. At the moment, the following checkpoints are
+ implemented:
+
+ Legacy uImage format:
+
+ Arg Where When
+ 1 common/cmd_bootm.c before attempting to boot an image
+ -1 common/cmd_bootm.c Image header has bad magic number
+ 2 common/cmd_bootm.c Image header has correct magic number
+ -2 common/cmd_bootm.c Image header has bad checksum
+ 3 common/cmd_bootm.c Image header has correct checksum
+ -3 common/cmd_bootm.c Image data has bad checksum
+ 4 common/cmd_bootm.c Image data has correct checksum
+ -4 common/cmd_bootm.c Image is for unsupported architecture
+ 5 common/cmd_bootm.c Architecture check OK
+ -5 common/cmd_bootm.c Wrong Image Type (not kernel, multi)
+ 6 common/cmd_bootm.c Image Type check OK
+ -6 common/cmd_bootm.c gunzip uncompression error
+ -7 common/cmd_bootm.c Unimplemented compression type
+ 7 common/cmd_bootm.c Uncompression OK
+ 8 common/cmd_bootm.c No uncompress/copy overwrite error
+ -9 common/cmd_bootm.c Unsupported OS (not Linux, BSD, VxWorks, QNX)
+
+ 9 common/image.c Start initial ramdisk verification
+ -10 common/image.c Ramdisk header has bad magic number
+ -11 common/image.c Ramdisk header has bad checksum
+ 10 common/image.c Ramdisk header is OK
+ -12 common/image.c Ramdisk data has bad checksum
+ 11 common/image.c Ramdisk data has correct checksum
+ 12 common/image.c Ramdisk verification complete, start loading
+ -13 common/image.c Wrong Image Type (not PPC Linux ramdisk)
+ 13 common/image.c Start multifile image verification
+ 14 common/image.c No initial ramdisk, no multifile, continue.
+
+ 15 arch/<arch>/lib/bootm.c All preparation done, transferring control to OS
+
+ -30 arch/powerpc/lib/board.c Fatal error, hang the system
+ -31 post/post.c POST test failed, detected by post_output_backlog()
+ -32 post/post.c POST test failed, detected by post_run_single()
+
+ 34 common/cmd_doc.c before loading a Image from a DOC device
+ -35 common/cmd_doc.c Bad usage of "doc" command
+ 35 common/cmd_doc.c correct usage of "doc" command
+ -36 common/cmd_doc.c No boot device
+ 36 common/cmd_doc.c correct boot device
+ -37 common/cmd_doc.c Unknown Chip ID on boot device
+ 37 common/cmd_doc.c correct chip ID found, device available
+ -38 common/cmd_doc.c Read Error on boot device
+ 38 common/cmd_doc.c reading Image header from DOC device OK
+ -39 common/cmd_doc.c Image header has bad magic number
+ 39 common/cmd_doc.c Image header has correct magic number
+ -40 common/cmd_doc.c Error reading Image from DOC device
+ 40 common/cmd_doc.c Image header has correct magic number
+ 41 common/cmd_ide.c before loading a Image from a IDE device
+ -42 common/cmd_ide.c Bad usage of "ide" command
+ 42 common/cmd_ide.c correct usage of "ide" command
+ -43 common/cmd_ide.c No boot device
+ 43 common/cmd_ide.c boot device found
+ -44 common/cmd_ide.c Device not available
+ 44 common/cmd_ide.c Device available
+ -45 common/cmd_ide.c wrong partition selected
+ 45 common/cmd_ide.c partition selected
+ -46 common/cmd_ide.c Unknown partition table
+ 46 common/cmd_ide.c valid partition table found
+ -47 common/cmd_ide.c Invalid partition type
+ 47 common/cmd_ide.c correct partition type
+ -48 common/cmd_ide.c Error reading Image Header on boot device
+ 48 common/cmd_ide.c reading Image Header from IDE device OK
+ -49 common/cmd_ide.c Image header has bad magic number
+ 49 common/cmd_ide.c Image header has correct magic number
+ -50 common/cmd_ide.c Image header has bad checksum
+ 50 common/cmd_ide.c Image header has correct checksum
+ -51 common/cmd_ide.c Error reading Image from IDE device
+ 51 common/cmd_ide.c reading Image from IDE device OK
+ 52 common/cmd_nand.c before loading a Image from a NAND device
+ -53 common/cmd_nand.c Bad usage of "nand" command
+ 53 common/cmd_nand.c correct usage of "nand" command
+ -54 common/cmd_nand.c No boot device
+ 54 common/cmd_nand.c boot device found
+ -55 common/cmd_nand.c Unknown Chip ID on boot device
+ 55 common/cmd_nand.c correct chip ID found, device available
+ -56 common/cmd_nand.c Error reading Image Header on boot device
+ 56 common/cmd_nand.c reading Image Header from NAND device OK
+ -57 common/cmd_nand.c Image header has bad magic number
+ 57 common/cmd_nand.c Image header has correct magic number
+ -58 common/cmd_nand.c Error reading Image from NAND device
+ 58 common/cmd_nand.c reading Image from NAND device OK
+
+ -60 common/env_common.c Environment has a bad CRC, using default
+
+ 64 net/eth.c starting with Ethernet configuration.
+ -64 net/eth.c no Ethernet found.
+ 65 net/eth.c Ethernet found.
+
+ -80 common/cmd_net.c usage wrong
+ 80 common/cmd_net.c before calling net_loop()
+ -81 common/cmd_net.c some error in net_loop() occurred
+ 81 common/cmd_net.c net_loop() back without error
+ -82 common/cmd_net.c size == 0 (File with size 0 loaded)
+ 82 common/cmd_net.c trying automatic boot
+ 83 common/cmd_net.c running "source" command
+ -83 common/cmd_net.c some error in automatic boot or "source" command
+ 84 common/cmd_net.c end without errors
+
+ FIT uImage format:
+
+ Arg Where When
+ 100 common/cmd_bootm.c Kernel FIT Image has correct format
+ -100 common/cmd_bootm.c Kernel FIT Image has incorrect format
+ 101 common/cmd_bootm.c No Kernel subimage unit name, using configuration
+ -101 common/cmd_bootm.c Can't get configuration for kernel subimage
+ 102 common/cmd_bootm.c Kernel unit name specified
+ -103 common/cmd_bootm.c Can't get kernel subimage node offset
+ 103 common/cmd_bootm.c Found configuration node
+ 104 common/cmd_bootm.c Got kernel subimage node offset
+ -104 common/cmd_bootm.c Kernel subimage hash verification failed
+ 105 common/cmd_bootm.c Kernel subimage hash verification OK
+ -105 common/cmd_bootm.c Kernel subimage is for unsupported architecture
+ 106 common/cmd_bootm.c Architecture check OK
+ -106 common/cmd_bootm.c Kernel subimage has wrong type
+ 107 common/cmd_bootm.c Kernel subimage type OK
+ -107 common/cmd_bootm.c Can't get kernel subimage data/size
+ 108 common/cmd_bootm.c Got kernel subimage data/size
+ -108 common/cmd_bootm.c Wrong image type (not legacy, FIT)
+ -109 common/cmd_bootm.c Can't get kernel subimage type
+ -110 common/cmd_bootm.c Can't get kernel subimage comp
+ -111 common/cmd_bootm.c Can't get kernel subimage os
+ -112 common/cmd_bootm.c Can't get kernel subimage load address
+ -113 common/cmd_bootm.c Image uncompress/copy overwrite error
+
+ 120 common/image.c Start initial ramdisk verification
+ -120 common/image.c Ramdisk FIT image has incorrect format
+ 121 common/image.c Ramdisk FIT image has correct format
+ 122 common/image.c No ramdisk subimage unit name, using configuration
+ -122 common/image.c Can't get configuration for ramdisk subimage
+ 123 common/image.c Ramdisk unit name specified
+ -124 common/image.c Can't get ramdisk subimage node offset
+ 125 common/image.c Got ramdisk subimage node offset
+ -125 common/image.c Ramdisk subimage hash verification failed
+ 126 common/image.c Ramdisk subimage hash verification OK
+ -126 common/image.c Ramdisk subimage for unsupported architecture
+ 127 common/image.c Architecture check OK
+ -127 common/image.c Can't get ramdisk subimage data/size
+ 128 common/image.c Got ramdisk subimage data/size
+ 129 common/image.c Can't get ramdisk load address
+ -129 common/image.c Got ramdisk load address
+
+ -130 common/cmd_doc.c Incorrect FIT image format
+ 131 common/cmd_doc.c FIT image format OK
+
+ -140 common/cmd_ide.c Incorrect FIT image format
+ 141 common/cmd_ide.c FIT image format OK
+
+ -150 common/cmd_nand.c Incorrect FIT image format
+ 151 common/cmd_nand.c FIT image format OK
+
+config SPL_SHOW_BOOT_PROGRESS
+ bool "Show boot progress in a board-specific manner in SPL"
+ depends on SPL
+ help
+ Defining this option allows to add some board-specific code (calling
+ a user-provided function show_boot_progress(int) that enables you to
+ show the system's boot progress on some display (for example, some
+ LEDs) on your board. For details see SHOW_BOOT_PROGRESS.
+
+endmenu
+
+menu "Boot media"
+
+config NOR_BOOT
+ bool "Support for booting from NOR flash"
+ depends on NOR
+ help
+ Enabling this will make a U-Boot binary that is capable of being
+ booted via NOR. In this case we will enable certain pinmux early
+ as the ROM only partially sets up pinmux. We also default to using
+ NOR for environment.
+
+config NAND_BOOT
+ bool "Support for booting from NAND flash"
+ imply MTD_RAW_NAND
+ help
+ Enabling this will make a U-Boot binary that is capable of being
+ booted via NAND flash. This is not a must, some SoCs need this,
+ some not.
+
+config QSPI_BOOT
+ bool "Support for booting from QSPI flash"
+ help
+ Enabling this will make a U-Boot binary that is capable of being
+ booted via QSPI flash. This is not a must, some SoCs need this,
+ some not.
+
+config SATA_BOOT
+ bool "Support for booting from SATA"
+ help
+ Enabling this will make a U-Boot binary that is capable of being
+ booted via SATA. This is not a must, some SoCs need this,
+ some not.
+
+config SD_BOOT
+ bool "Support for booting from SD/EMMC"
+ help
+ Enabling this will make a U-Boot binary that is capable of being
+ booted via SD/EMMC. This is not a must, some SoCs need this,
+ some not.
+
+config SD_BOOT_QSPI
+ bool "Support for booting from SD/EMMC and enable QSPI"
+ help
+ Enabling this will make a U-Boot binary that is capable of being
+ booted via SD/EMMC while enabling QSPI on the platform as well. This
+ is not a must, some SoCs need this, some not.
+
+config SPI_BOOT
+ bool "Support for booting from SPI flash"
+ help
+ Enabling this will make a U-Boot binary that is capable of being
+ booted via SPI flash. This is not a must, some SoCs need this,
+ some not.
+
+endmenu
+
+menu "Autoboot options"
+
+config AUTOBOOT
+ bool "Autoboot"
+ depends on CMDLINE
+ default y
+ help
+ This enables the autoboot. See doc/README.autoboot for detail.
+
+if AUTOBOOT
+
+config BOOTDELAY
+ int "delay in seconds before automatically booting"
+ default 2
+ help
+ Delay before automatically running bootcmd;
+ set to 0 to autoboot with no delay, but you can stop it by key input.
+ set to -1 to disable autoboot.
+ set to -2 to autoboot with no delay and not check for abort
+
+ If this value is >= 0 then it is also used for the default delay
+ before starting the default entry in bootmenu. If it is < 0 then
+ a default value of 10s is used.
+
+ See doc/README.autoboot for details.
+
+config AUTOBOOT_KEYED
+ bool "Stop autobooting via specific input key / string"
+ help
+ This option enables stopping (aborting) of the automatic
+ boot feature only by issuing a specific input key or
+ string. If not enabled, any input key will abort the
+ U-Boot automatic booting process and bring the device
+ to the U-Boot prompt for user input.
+
+if AUTOBOOT_KEYED
+
+config AUTOBOOT_FLUSH_STDIN
+ bool "Enable flushing stdin before starting to read the password"
+ depends on !SANDBOX
+ help
+ When this option is enabled stdin buffer will be flushed before
+ starting to read the password.
+ This can't be enabled for the sandbox as flushing stdin would
+ break the autoboot unit tests.
+
+config AUTOBOOT_PROMPT
+ string "Autoboot stop prompt"
+ default "Autoboot in %d seconds\\n"
+ help
+ This string is displayed before the boot delay selected by
+ CONFIG_BOOTDELAY starts. If it is not defined there is no
+ output indicating that autoboot is in progress.
+
+ Note that this define is used as the (only) argument to a
+ printf() call, so it may contain '%' format specifications,
+ provided that it also includes, separated by commas exactly
+ like in a printf statement, the required arguments. It is
+ the responsibility of the user to select only such arguments
+ that are valid in the given context.
+
+config AUTOBOOT_ENCRYPTION
+ bool "Enable encryption in autoboot stopping"
+ help
+ This option allows a string to be entered into U-Boot to stop the
+ autoboot.
+ The behavior depends whether CONFIG_CRYPT_PW from lib is enabled
+ or not.
+ In case CONFIG_CRYPT_PW is enabled, the string will be forwarded
+ to the crypt-based functionality and be compared against the
+ string in the environment variable 'bootstopkeycrypt'.
+ In case CONFIG_CRYPT_PW is disabled the string itself is hashed
+ and compared against the hash in the environment variable
+ 'bootstopkeysha256'.
+ If it matches in either case then boot stops and
+ a command-line prompt is presented.
+ This provides a way to ship a secure production device which can also
+ be accessed at the U-Boot command line.
+
+config AUTOBOOT_SHA256_FALLBACK
+ bool "Allow fallback from crypt-hashed password to sha256"
+ depends on AUTOBOOT_ENCRYPTION && CRYPT_PW
+ help
+ This option adds support to fall back from crypt-hashed
+ passwords to checking a SHA256 hashed password in case the
+ 'bootstopusesha256' environment variable is set to 'true'.
+
+config AUTOBOOT_DELAY_STR
+ string "Delay autobooting via specific input key / string"
+ depends on !AUTOBOOT_ENCRYPTION
+ help
+ This option delays the automatic boot feature by issuing
+ a specific input key or string. If CONFIG_AUTOBOOT_DELAY_STR
+ or the environment variable "bootdelaykey" is specified
+ and this string is received from console input before
+ autoboot starts booting, U-Boot gives a command prompt. The
+ U-Boot prompt will time out if CONFIG_BOOT_RETRY_TIME is
+ used, otherwise it never times out.
+
+config AUTOBOOT_STOP_STR
+ string "Stop autobooting via specific input key / string"
+ depends on !AUTOBOOT_ENCRYPTION
+ help
+ This option enables stopping (aborting) of the automatic
+ boot feature only by issuing a specific input key or
+ string. If CONFIG_AUTOBOOT_STOP_STR or the environment
+ variable "bootstopkey" is specified and this string is
+ received from console input before autoboot starts booting,
+ U-Boot gives a command prompt. The U-Boot prompt never
+ times out, even if CONFIG_BOOT_RETRY_TIME is used.
+
+config AUTOBOOT_KEYED_CTRLC
+ bool "Enable Ctrl-C autoboot interruption"
+ depends on !AUTOBOOT_ENCRYPTION
+ help
+ This option allows for the boot sequence to be interrupted
+ by ctrl-c, in addition to the "bootdelaykey" and "bootstopkey".
+ Setting this variable provides an escape sequence from the
+ limited "password" strings.
+
+config AUTOBOOT_NEVER_TIMEOUT
+ bool "Make the password entry never time-out"
+ depends on AUTOBOOT_ENCRYPTION && CRYPT_PW
+ help
+ This option removes the timeout from the password entry
+ when the user first presses the <Enter> key before entering
+ any other character.
+
+config AUTOBOOT_STOP_STR_ENABLE
+ bool "Enable fixed string to stop autobooting"
+ depends on AUTOBOOT_ENCRYPTION
+ help
+ This option enables the feature to add a fixed stop
+ string that is defined at compile time.
+ In every case it will be tried to load the stop
+ string from the environment.
+ In case this is enabled and there is no stop string
+ in the environment, this will be used as default value.
+
+config AUTOBOOT_STOP_STR_CRYPT
+ string "Stop autobooting via crypt-hashed password"
+ depends on AUTOBOOT_STOP_STR_ENABLE && CRYPT_PW
+ help
+ This option adds the feature to only stop the autobooting,
+ and therefore boot into the U-Boot prompt, when the input
+ string / password matches a values that is hashed via
+ one of the supported crypt-style password hashing options
+ and saved in the environment variable "bootstopkeycrypt".
+
+config AUTOBOOT_STOP_STR_SHA256
+ string "Stop autobooting via SHA256 hashed password"
+ depends on AUTOBOOT_STOP_STR_ENABLE
+ help
+ This option adds the feature to only stop the autobooting,
+ and therefore boot into the U-Boot prompt, when the input
+ string / password matches a values that is encrypted via
+ a SHA256 hash and saved in the environment variable
+ "bootstopkeysha256". If the value in that variable
+ includes a ":", the portion prior to the ":" will be treated
+ as a salt value.
+
+endif # AUTOBOOT_KEYED
+
+if !AUTOBOOT_KEYED
+
+config AUTOBOOT_USE_MENUKEY
+ bool "Allow a specify key to run a menu from the environment"
+ help
+ If a specific key is pressed to stop autoboot, then the commands in
+ the environment variable 'menucmd' are executed before boot starts.
+
+config AUTOBOOT_MENUKEY
+ int "ASCII value of boot key to show a menu"
+ default 0
+ depends on AUTOBOOT_USE_MENUKEY
+ help
+ If this key is pressed to stop autoboot, then the commands in the
+ environment variable 'menucmd' will be executed before boot starts.
+ For example, 33 means "!" in ASCII, so pressing ! at boot would take
+ this action.
+
+endif
+
+endif # AUTOBOOT
+
+config AUTOBOOT_MENU_SHOW
+ bool "Show a menu on boot"
+ depends on CMD_BOOTMENU
+ help
+ This enables the boot menu, controlled by environment variables
+ defined by the board. The menu starts after running the 'preboot'
+ environmnent variable (if enabled) and before handling the boot delay.
+ See doc/usage/cmd/bootmenu.rst for more details.
+
+config BOOTMENU_DISABLE_UBOOT_CONSOLE
+ bool "Disallow bootmenu to enter the U-Boot console"
+ depends on AUTOBOOT_MENU_SHOW
+ help
+ If this option is enabled, user can not enter the U-Boot console from
+ bootmenu. It increases the system security.
+
+config BOOT_RETRY
+ bool "Boot retry feature"
+ help
+ Allow for having the U-Boot command prompt time out and attempt
+ to boot again. If the environment variable "bootretry" is found then
+ its value is used, otherwise the retry timeout is
+ CONFIG_BOOT_RETRY_TIME. CONFIG_BOOT_RETRY_MIN is optional and
+ defaults to CONFIG_BOOT_RETRY_TIME. All times are in seconds.
+
+config BOOT_RETRY_TIME
+ int "Timeout in seconds before attempting to boot again"
+ depends on BOOT_RETRY
+ help
+ Time in seconds before the U-Boot prompt will timeout and boot will
+ be attempted again.
+
+config BOOT_RETRY_MIN
+ int "Minimum timeout in seconds for 'bootretry'"
+ depends on BOOT_RETRY
+ default BOOT_RETRY_TIME
+ help
+ The minimum time in seconds that "bootretry" can be set to.
+
+config RESET_TO_RETRY
+ bool "Reset the board to retry autoboot"
+ depends on BOOT_RETRY
+ help
+ After the countdown timed out, the board will be reset to restart
+ again.
+
+config RETRY_BOOTCMD
+ bool "Run bootcmd on retry"
+ depends on BOOT_RETRY && HUSH_PARSER && !RESET_TO_RETRY
+ help
+ If this option is enabled, the "bootcmd" will be run after the
+ countdown times out.
+
+endmenu
+
+menu "Image support"
+
+config IMAGE_PRE_LOAD
+ bool "Image pre-load support"
+ help
+ Enable an image pre-load stage in the SPL.
+ This pre-load stage allows to do some manipulation
+ or check (for example signature check) on an image
+ before launching it.
+
+config SPL_IMAGE_PRE_LOAD
+ bool "Image pre-load support within SPL"
+ depends on SPL && IMAGE_PRE_LOAD
+ help
+ Enable an image pre-load stage in the SPL.
+ This pre-load stage allows to do some manipulation
+ or check (for example signature check) on an image
+ before launching it.
+
+config IMAGE_PRE_LOAD_SIG
+ bool "Image pre-load signature support"
+ depends on IMAGE_PRE_LOAD
+ select FIT_SIGNATURE
+ select RSA
+ select RSA_VERIFY_WITH_PKEY
+ help
+ Enable signature check support in the pre-load stage.
+ For this feature a very simple header is added before
+ the image with few fields:
+ - a magic
+ - the image size
+ - the signature
+ All other information (header size, type of signature,
+ ...) are provided in the node /image/pre-load/sig of
+ u-boot.
+
+config SPL_IMAGE_PRE_LOAD_SIG
+ bool "Image pre-load signature support witin SPL"
+ depends on SPL_IMAGE_PRE_LOAD && IMAGE_PRE_LOAD_SIG
+ select SPL_FIT_SIGNATURE
+ select SPL_RSA
+ select SPL_RSA_VERIFY_WITH_PKEY
+ help
+ Enable signature check support in the pre-load stage in the SPL.
+ For this feature a very simple header is added before
+ the image with few fields:
+ - a magic
+ - the image size
+ - the signature
+ All other information (header size, type of signature,
+ ...) are provided in the node /image/pre-load/sig of
+ u-boot.
+
+endmenu
+
+if OF_LIBFDT
+
+menu "Devicetree fixup"
+
+config OF_ENV_SETUP
+ bool "Run a command from environment to set up device tree before boot"
+ depends on CMD_FDT
+ help
+ This causes U-Boot to run a command from the environment variable
+ fdt_fixup before booting into the operating system, which can use the
+ fdt command to modify the device tree. The device tree is then passed
+ to the OS.
+
+config OF_BOARD_SETUP
+ bool "Set up board-specific details in device tree before boot"
+ help
+ This causes U-Boot to call ft_board_setup() before booting into
+ the Operating System. This function can set up various
+ board-specific information in the device tree for use by the OS.
+ The device tree is then passed to the OS.
+
+config OF_BOARD_SETUP_EXTENDED
+ bool "Set up latest board-specific details in device tree before boot"
+ imply OF_BOARD_SETUP
+ help
+ This causes U-Boot to call ft_board_setup_ex() before booting into
+ the Operating System. Similar function as ft_board_setup(). However,
+ its modifications are not overwritten by other system changes and are
+ applied to the device tree as the very last step before boot.
+ The device tree is then passed to the OS.
+
+config OF_SYSTEM_SETUP
+ bool "Set up system-specific details in device tree before boot"
+ help
+ This causes U-Boot to call ft_system_setup() before booting into
+ the Operating System. This function can set up various
+ system-specific information in the device tree for use by the OS.
+ The device tree is then passed to the OS.
+
+config OF_STDOUT_VIA_ALIAS
+ bool "Update the device-tree stdout alias from U-Boot"
+ help
+ This uses U-Boot's serial alias from the aliases node to update
+ the device tree passed to the OS. The "linux,stdout-path" property
+ in the chosen node is set to point to the correct serial node.
+ This option currently references CONFIG_CONS_INDEX, which is
+ incorrect when used with device tree as this option does not
+ exist / should not be used.
+
+config FDT_FIXUP_PARTITIONS
+ bool "Overwrite MTD partitions in DTS through defined in 'mtdparts'"
+ help
+ Allow overwriting defined partitions in the device tree blob
+ using partition info defined in the 'mtdparts' environment
+ variable.
+
+config FDT_SIMPLEFB
+ bool "FDT tools for simplefb support"
+ depends on VIDEO
+ help
+ Enable the fdt tools to manage the simple fb nodes in device tree.
+ These functions can be used by board to indicate to the OS
+ the presence of the simple frame buffer with associated reserved
+ memory
+
+config ARCH_FIXUP_FDT_MEMORY
+ bool "Enable fdt_fixup_memory_banks() call"
+ default y
+ help
+ Enable FDT memory map syncup before OS boot. This feature can be
+ used for booting OS with different memory setup where the part of
+ the memory location should be used for different purpose.
+
+endmenu
+
+endif # OF_LIBFDT
+
+config USE_BOOTARGS
+ bool "Enable boot arguments"
+ help
+ Provide boot arguments to bootm command. Boot arguments are specified
+ in CONFIG_BOOTARGS option. Enable this option to be able to specify
+ CONFIG_BOOTARGS string. If this option is disabled, CONFIG_BOOTARGS
+ will be undefined and won't take any space in U-Boot image.
+
+config BOOTARGS
+ string "Boot arguments"
+ depends on USE_BOOTARGS && !ENV_USE_DEFAULT_ENV_TEXT_FILE
+ help
+ This can be used to pass arguments to the bootm command. The value of
+ CONFIG_BOOTARGS goes into the environment value "bootargs". Note that
+ this value will also override the "chosen" node in FDT blob.
+
+config BOOTARGS_SUBST
+ bool "Support substituting strings in boot arguments"
+ help
+ This allows substituting string values in the boot arguments. These
+ are applied after the commandline has been built.
+
+ One use for this is to insert the root-disk UUID into the command
+ line where bootargs contains "root=${uuid}"
+
+ setenv bootargs "console= root=${uuid}"
+ # Set the 'uuid' environment variable
+ part uuid mmc 2:2 uuid
+
+ # Command-line substitution will put the real uuid into the
+ # kernel command line
+ bootm
+
+config USE_BOOTCOMMAND
+ bool "Enable a default value for bootcmd"
+ depends on CMDLINE
+ help
+ Provide a default value for the bootcmd entry in the environment. If
+ autoboot is enabled this is what will be run automatically. Enable
+ this option to be able to specify CONFIG_BOOTCOMMAND as a string. If
+ this option is disabled, CONFIG_BOOTCOMMAND will be undefined and
+ won't take any space in U-Boot image.
+
+config BOOTCOMMAND
+ string "bootcmd value"
+ depends on USE_BOOTCOMMAND && !ENV_USE_DEFAULT_ENV_TEXT_FILE
+ default "bootflow scan -lb" if BOOTSTD_DEFAULTS && CMD_BOOTFLOW_FULL
+ default "bootflow scan" if BOOTSTD_DEFAULTS && !CMD_BOOTFLOW_FULL
+ default "run distro_bootcmd" if !BOOTSTD_BOOTCOMMAND && DISTRO_DEFAULTS
+ help
+ This is the string of commands that will be used as bootcmd and if
+ AUTOBOOT is set, automatically run.
+
+config USE_PREBOOT
+ bool "Enable preboot"
+ depends on CMDLINE
+ help
+ When this option is enabled, the existence of the environment
+ variable "preboot" will be checked immediately before starting the
+ CONFIG_BOOTDELAY countdown and/or running the auto-boot command resp.
+ entering interactive mode.
+
+ This feature is especially useful when "preboot" is automatically
+ generated or modified. For example, the boot code can modify the
+ "preboot" when a user holds down a certain combination of keys.
+
+config PREBOOT
+ string "preboot default value"
+ depends on USE_PREBOOT && !ENV_USE_DEFAULT_ENV_TEXT_FILE
+ default "usb start" if USB_KEYBOARD
+ default ""
+ help
+ This is the default of "preboot" environment variable.
+
+config PREBOOT_DEFINED
+ bool
+ default y if PREBOOT != ""
+
+config DEFAULT_FDT_FILE
+ string "Default fdt file"
+ help
+ This option is used to set the default fdt file to boot OS.
+
+config SAVE_PREV_BL_FDT_ADDR
+ depends on ARM
+ bool "Saves fdt address, passed by the previous bootloader, to env var"
+ help
+ When u-boot is used as a chain-loaded bootloader (replacing OS kernel),
+ enable this option to save fdt address, passed by the
+ previous bootloader for future use.
+ Address is saved to `prevbl_fdt_addr` environment variable.
+
+ If no fdt was provided by previous bootloader, no env variables
+ will be created.
+
+config SAVE_PREV_BL_INITRAMFS_START_ADDR
+ depends on ARM
+ bool "Saves initramfs address, passed by the previous bootloader, to env var"
+ help
+ When u-boot is used as a chain-loaded bootloader(replacing OS kernel),
+ enable this option to save initramfs address, passed by the
+ previous bootloader for future use.
+ Address is saved to `prevbl_initrd_start_addr` environment variable.
+
+ If no initramfs was provided by previous bootloader, no env variables
+ will be created.
+
+menu "Configuration editor"
+
+config CEDIT
+ bool "Configuration editor"
+ depends on EXPO
+ help
+ Provides a way to deal with board configuration and present it to
+ the user for adjustment.
+
+ This is intended to provide both graphical and text-based user
+ interfaces, but only graphical is support at present.
+
+endmenu # Configuration editor
+
+endmenu # Booting
diff --git a/boot/Makefile b/boot/Makefile
new file mode 100644
index 00000000000..3da6f7a0914
--- /dev/null
+++ b/boot/Makefile
@@ -0,0 +1,77 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# (C) Copyright 2004-2006
+# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+
+ifndef CONFIG_XPL_BUILD
+
+obj-$(CONFIG_BOOT_RETRY) += bootretry.o
+obj-$(CONFIG_CMD_BOOTM) += bootm.o bootm_os.o
+obj-$(CONFIG_CMD_BOOTZ) += bootm.o bootm_os.o
+obj-$(CONFIG_CMD_BOOTI) += bootm.o bootm_os.o
+
+obj-$(CONFIG_PXE_UTILS) += pxe_utils.o
+
+endif
+
+obj-y += image.o image-board.o
+
+obj-$(CONFIG_ANDROID_AB) += android_ab.o
+obj-$(CONFIG_ANDROID_BOOT_IMAGE) += image-android.o image-android-dt.o
+
+obj-$(CONFIG_$(PHASE_)BOOTSTD) += bootdev-uclass.o
+obj-$(CONFIG_$(PHASE_)BOOTSTD) += bootflow.o
+obj-$(CONFIG_$(PHASE_)BOOTSTD) += bootmeth-uclass.o
+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_)BOOTMETH_EXTLINUX) += bootmeth_extlinux.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_EXTLINUX_PXE) += bootmeth_pxe.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_EFILOADER) += bootmeth_efi.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_CROS) += bootm.o bootm_os.o bootmeth_cros.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_QFW) += bootmeth_qfw.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_RAUC) += bootmeth_rauc.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_SANDBOX) += bootmeth_sandbox.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_SCRIPT) += bootmeth_script.o
+obj-$(CONFIG_$(PHASE_)CEDIT) += cedit.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_EFI_BOOTMGR) += bootmeth_efi_mgr.o
+
+obj-$(CONFIG_$(PHASE_)OF_LIBFDT) += fdt_support.o
+obj-$(CONFIG_$(PHASE_)FDT_SIMPLEFB) += fdt_simplefb.o
+
+obj-$(CONFIG_$(PHASE_)UPL) += upl_common.o
+obj-$(CONFIG_$(PHASE_)UPL_READ) += upl_read.o
+obj-$(CONFIG_$(PHASE_)UPL_WRITE) += upl_write.o
+
+obj-$(CONFIG_$(PHASE_)OF_LIBFDT) += image-fdt.o
+obj-$(CONFIG_$(PHASE_)FIT_SIGNATURE) += fdt_region.o
+obj-$(CONFIG_$(PHASE_)FIT) += image-fit.o
+obj-$(CONFIG_$(PHASE_)MULTI_DTB_FIT) += boot_fit.o common_fit.o
+obj-$(CONFIG_$(PHASE_)IMAGE_PRE_LOAD) += image-pre-load.o
+obj-$(CONFIG_$(PHASE_)IMAGE_SIGN_INFO) += image-sig.o
+obj-$(CONFIG_$(PHASE_)FIT_SIGNATURE) += image-fit-sig.o
+obj-$(CONFIG_$(PHASE_)FIT_CIPHER) += image-cipher.o
+
+obj-$(CONFIG_CMD_ADTIMG) += image-android-dt.o
+
+obj-$(CONFIG_$(PHASE_)LOAD_FIT) += common_fit.o
+
+obj-$(CONFIG_$(PHASE_)EXPO) += expo.o scene.o expo_build.o
+obj-$(CONFIG_$(PHASE_)EXPO) += scene_menu.o scene_textline.o scene_textedit.o
+ifdef CONFIG_COREBOOT_SYSINFO
+obj-$(CONFIG_$(PHASE_)EXPO) += expo_build_cb.o
+endif
+
+obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE) += vbe.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_REQUEST) += vbe_request.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o vbe_common.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_SIMPLE_FW) += vbe_simple_fw.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_SIMPLE_OS) += vbe_simple_os.o
+
+obj-$(CONFIG_$(PHASE_)BOOTMETH_ANDROID) += bootmeth_android.o
+
+obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_ABREC) += vbe_abrec.o vbe_common.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_ABREC_FW) += vbe_abrec_fw.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_ABREC_OS) += vbe_abrec_os.o
diff --git a/boot/android_ab.c b/boot/android_ab.c
new file mode 100644
index 00000000000..13e82dbcb7f
--- /dev/null
+++ b/boot/android_ab.c
@@ -0,0 +1,442 @@
+// SPDX-License-Identifier: BSD-2-Clause
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ */
+#include <android_ab.h>
+#include <android_bootloader_message.h>
+#include <blk.h>
+#include <log.h>
+#include <malloc.h>
+#include <part.h>
+#include <memalign.h>
+#include <linux/err.h>
+#include <u-boot/crc.h>
+
+/**
+ * ab_control_compute_crc() - Compute the CRC32 of the bootloader control.
+ *
+ * @abc: Bootloader control block
+ *
+ * Only the bytes up to the crc32_le field are considered for the CRC-32
+ * calculation.
+ *
+ * Return: crc32 sum
+ */
+static uint32_t ab_control_compute_crc(struct bootloader_control *abc)
+{
+ return crc32(0, (void *)abc, offsetof(typeof(*abc), crc32_le));
+}
+
+/**
+ * ab_control_default() - Initialize bootloader_control to the default value.
+ *
+ * @abc: Bootloader control block
+ *
+ * It allows us to boot all slots in order from the first one. This value
+ * should be used when the bootloader message is corrupted, but not when
+ * a valid message indicates that all slots are unbootable.
+ *
+ * Return: 0 on success and a negative on error
+ */
+static int ab_control_default(struct bootloader_control *abc)
+{
+ int i;
+ const struct slot_metadata metadata = {
+ .priority = 15,
+ .tries_remaining = 7,
+ .successful_boot = 0,
+ .verity_corrupted = 0,
+ .reserved = 0
+ };
+
+ if (!abc)
+ return -EFAULT;
+
+ memcpy(abc->slot_suffix, "_a\0\0", 4);
+ abc->magic = BOOT_CTRL_MAGIC;
+ abc->version = BOOT_CTRL_VERSION;
+ abc->nb_slot = NUM_SLOTS;
+ memset(abc->reserved0, 0, sizeof(abc->reserved0));
+ for (i = 0; i < abc->nb_slot; ++i)
+ abc->slot_info[i] = metadata;
+
+ memset(abc->reserved1, 0, sizeof(abc->reserved1));
+ abc->crc32_le = ab_control_compute_crc(abc);
+
+ return 0;
+}
+
+/**
+ * ab_control_create_from_disk() - Load the boot_control from disk into memory.
+ *
+ * @dev_desc: Device where to read the boot_control struct from
+ * @part_info: Partition in 'dev_desc' where to read from, normally
+ * the "misc" partition should be used
+ * @abc: pointer to pointer to bootloader_control data
+ * @offset: boot_control struct offset
+ *
+ * This function allocates and returns an integer number of disk blocks,
+ * based on the block size of the passed device to help performing a
+ * read-modify-write operation on the boot_control struct.
+ * The boot_control struct offset (2 KiB) must be a multiple of the device
+ * block size, for simplicity.
+ *
+ * Return: 0 on success and a negative on error
+ */
+static int ab_control_create_from_disk(struct blk_desc *dev_desc,
+ const struct disk_partition *part_info,
+ struct bootloader_control **abc,
+ ulong offset)
+{
+ ulong abc_offset, abc_blocks, ret;
+
+ abc_offset = offset +
+ offsetof(struct bootloader_message_ab, slot_suffix);
+ if (abc_offset % part_info->blksz) {
+ log_err("ANDROID: Boot control block not block aligned.\n");
+ return -EINVAL;
+ }
+ abc_offset /= part_info->blksz;
+
+ abc_blocks = DIV_ROUND_UP(sizeof(struct bootloader_control),
+ part_info->blksz);
+ if (abc_offset + abc_blocks > part_info->size) {
+ log_err("ANDROID: boot control partition too small. Need at least %lu blocks but have " LBAF " blocks.\n",
+ abc_offset + abc_blocks, part_info->size);
+ return -EINVAL;
+ }
+ *abc = malloc_cache_aligned(abc_blocks * part_info->blksz);
+ if (!*abc)
+ return -ENOMEM;
+
+ ret = blk_dread(dev_desc, part_info->start + abc_offset, abc_blocks,
+ *abc);
+ if (IS_ERR_VALUE(ret)) {
+ log_err("ANDROID: Could not read from boot ctrl partition\n");
+ free(*abc);
+ return -EIO;
+ }
+
+ log_debug("ANDROID: Loaded ABC, %lu blocks\n", abc_blocks);
+
+ return 0;
+}
+
+/**
+ * ab_control_store() - Store the loaded boot_control block.
+ *
+ * @dev_desc: Device where we should write the boot_control struct
+ * @part_info: Partition on the 'dev_desc' where to write
+ * @abc Pointer to the boot control struct and the extra bytes after
+ * it up to the nearest block boundary
+ * @offset: boot_control struct offset
+ *
+ * Store back to the same location it was read from with
+ * ab_control_create_from_misc().
+ *
+ * Return: 0 on success and a negative on error
+ */
+static int ab_control_store(struct blk_desc *dev_desc,
+ const struct disk_partition *part_info,
+ struct bootloader_control *abc, ulong offset)
+{
+ ulong abc_offset, abc_blocks, ret;
+
+ if (offset % part_info->blksz) {
+ log_err("ANDROID: offset not block aligned\n");
+ return -EINVAL;
+ }
+
+ abc_offset = (offset +
+ offsetof(struct bootloader_message_ab, slot_suffix)) /
+ part_info->blksz;
+ abc_blocks = DIV_ROUND_UP(sizeof(struct bootloader_control),
+ part_info->blksz);
+ ret = blk_dwrite(dev_desc, part_info->start + abc_offset, abc_blocks,
+ abc);
+ if (IS_ERR_VALUE(ret)) {
+ log_err("ANDROID: Could not write back the misc partition\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/**
+ * ab_compare_slots() - Compare two slots.
+ *
+ * @a: The first bootable slot metadata
+ * @b: The second bootable slot metadata
+ *
+ * The function determines slot which is should we boot from among the two.
+ *
+ * Return: Negative if the slot "a" is better, positive of the slot "b" is
+ * better or 0 if they are equally good.
+ */
+static int ab_compare_slots(const struct slot_metadata *a,
+ const struct slot_metadata *b)
+{
+ /* Higher priority is better */
+ if (a->priority != b->priority)
+ return b->priority - a->priority;
+
+ /* Higher successful_boot value is better, in case of same priority */
+ if (a->successful_boot != b->successful_boot)
+ return b->successful_boot - a->successful_boot;
+
+ /* Higher tries_remaining is better to ensure round-robin */
+ if (a->tries_remaining != b->tries_remaining)
+ return b->tries_remaining - a->tries_remaining;
+
+ return 0;
+}
+
+int ab_select_slot(struct blk_desc *dev_desc, struct disk_partition *part_info,
+ bool dec_tries)
+{
+ struct bootloader_control *abc = NULL;
+ struct bootloader_control *backup_abc = NULL;
+ u32 crc32_le;
+ int slot, i, ret;
+ bool store_needed = false;
+ bool valid_backup = false;
+ char slot_suffix[4];
+
+ ret = ab_control_create_from_disk(dev_desc, part_info, &abc, 0);
+ if (ret < 0) {
+ /*
+ * This condition represents an actual problem with the code or
+ * the board setup, like an invalid partition information.
+ * Signal a repair mode and do not try to boot from either slot.
+ */
+ return ret;
+ }
+
+ if (CONFIG_ANDROID_AB_BACKUP_OFFSET) {
+ ret = ab_control_create_from_disk(dev_desc, part_info, &backup_abc,
+ CONFIG_ANDROID_AB_BACKUP_OFFSET);
+ if (ret < 0) {
+ free(abc);
+ return ret;
+ }
+ }
+
+ crc32_le = ab_control_compute_crc(abc);
+ if (abc->crc32_le != crc32_le) {
+ log_err("ANDROID: Invalid CRC-32 (expected %.8x, found %.8x),",
+ crc32_le, abc->crc32_le);
+ if (CONFIG_ANDROID_AB_BACKUP_OFFSET) {
+ crc32_le = ab_control_compute_crc(backup_abc);
+ if (backup_abc->crc32_le != crc32_le) {
+ log_err(" ANDROID: Invalid backup CRC-32 ");
+ log_err("(expected %.8x, found %.8x),",
+ crc32_le, backup_abc->crc32_le);
+ } else {
+ valid_backup = true;
+ log_info(" copying A/B metadata from backup.\n");
+ memcpy(abc, backup_abc, sizeof(*abc));
+ }
+ }
+
+ if (!valid_backup) {
+ log_err(" re-initializing A/B metadata.\n");
+ ret = ab_control_default(abc);
+ if (ret < 0) {
+ if (CONFIG_ANDROID_AB_BACKUP_OFFSET)
+ free(backup_abc);
+ free(abc);
+ return -ENODATA;
+ }
+ }
+ store_needed = true;
+ }
+
+ if (abc->magic != BOOT_CTRL_MAGIC) {
+ log_err("ANDROID: Unknown A/B metadata: %.8x\n", abc->magic);
+ if (CONFIG_ANDROID_AB_BACKUP_OFFSET)
+ free(backup_abc);
+ free(abc);
+ return -ENODATA;
+ }
+
+ if (abc->version > BOOT_CTRL_VERSION) {
+ log_err("ANDROID: Unsupported A/B metadata version: %.8x\n",
+ abc->version);
+ if (CONFIG_ANDROID_AB_BACKUP_OFFSET)
+ free(backup_abc);
+ free(abc);
+ return -ENODATA;
+ }
+
+ /*
+ * At this point a valid boot control metadata is stored in abc,
+ * followed by other reserved data in the same block. We select a with
+ * the higher priority slot that
+ * - is not marked as corrupted and
+ * - either has tries_remaining > 0 or successful_boot is true.
+ * If the selected slot has a false successful_boot, we also decrement
+ * the tries_remaining until it eventually becomes unbootable because
+ * tries_remaining reaches 0. This mechanism produces a bootloader
+ * induced rollback, typically right after a failed update.
+ */
+
+ /* Safety check: limit the number of slots. */
+ if (abc->nb_slot > ARRAY_SIZE(abc->slot_info)) {
+ abc->nb_slot = ARRAY_SIZE(abc->slot_info);
+ store_needed = true;
+ }
+
+ slot = -1;
+ for (i = 0; i < abc->nb_slot; ++i) {
+ if (abc->slot_info[i].verity_corrupted ||
+ !abc->slot_info[i].tries_remaining) {
+ log_debug("ANDROID: unbootable slot %d tries: %d, ",
+ i, abc->slot_info[i].tries_remaining);
+ log_debug("corrupt: %d\n",
+ abc->slot_info[i].verity_corrupted);
+ continue;
+ }
+ log_debug("ANDROID: bootable slot %d pri: %d, tries: %d, ",
+ i, abc->slot_info[i].priority,
+ abc->slot_info[i].tries_remaining);
+ log_debug("corrupt: %d, successful: %d\n",
+ abc->slot_info[i].verity_corrupted,
+ abc->slot_info[i].successful_boot);
+
+ if (slot < 0 ||
+ ab_compare_slots(&abc->slot_info[i],
+ &abc->slot_info[slot]) < 0) {
+ slot = i;
+ }
+ }
+
+ if (slot >= 0 && !abc->slot_info[slot].successful_boot) {
+ log_err("ANDROID: Attempting slot %c, tries remaining %d\n",
+ BOOT_SLOT_NAME(slot),
+ abc->slot_info[slot].tries_remaining);
+ if (dec_tries) {
+ abc->slot_info[slot].tries_remaining--;
+ store_needed = true;
+ }
+ }
+
+ if (slot >= 0) {
+ /*
+ * Legacy user-space requires this field to be set in the BCB.
+ * Newer releases load this slot suffix from the command line
+ * or the device tree.
+ */
+ memset(slot_suffix, 0, sizeof(slot_suffix));
+ slot_suffix[0] = '_';
+ slot_suffix[1] = BOOT_SLOT_NAME(slot);
+ if (memcmp(abc->slot_suffix, slot_suffix,
+ sizeof(slot_suffix))) {
+ memcpy(abc->slot_suffix, slot_suffix,
+ sizeof(slot_suffix));
+ store_needed = true;
+ }
+ }
+
+ if (store_needed) {
+ abc->crc32_le = ab_control_compute_crc(abc);
+ ret = ab_control_store(dev_desc, part_info, abc, 0);
+ if (ret < 0) {
+ if (CONFIG_ANDROID_AB_BACKUP_OFFSET)
+ free(backup_abc);
+ free(abc);
+ return ret;
+ }
+ }
+
+ if (CONFIG_ANDROID_AB_BACKUP_OFFSET) {
+ /*
+ * If the backup doesn't match the primary, write the primary
+ * to the backup offset
+ */
+ if (memcmp(backup_abc, abc, sizeof(*abc)) != 0) {
+ ret = ab_control_store(dev_desc, part_info, abc,
+ CONFIG_ANDROID_AB_BACKUP_OFFSET);
+ if (ret < 0) {
+ free(backup_abc);
+ free(abc);
+ return ret;
+ }
+ }
+ free(backup_abc);
+ }
+
+ free(abc);
+
+ if (slot < 0)
+ return -EINVAL;
+
+ return slot;
+}
+
+int ab_dump_abc(struct blk_desc *dev_desc, struct disk_partition *part_info)
+{
+ struct bootloader_control *abc;
+ u32 crc32_le;
+ int i, ret;
+ struct slot_metadata *slot;
+
+ if (!dev_desc || !part_info) {
+ log_err("ANDROID: Empty device descriptor or partition info\n");
+ return -EINVAL;
+ }
+
+ ret = ab_control_create_from_disk(dev_desc, part_info, &abc, 0);
+ if (ret < 0) {
+ log_err("ANDROID: Cannot create bcb from disk %d\n", ret);
+ return ret;
+ }
+
+ if (abc->magic != BOOT_CTRL_MAGIC) {
+ log_err("ANDROID: Unknown A/B metadata: %.8x\n", abc->magic);
+ ret = -ENODATA;
+ goto error;
+ }
+
+ if (abc->version > BOOT_CTRL_VERSION) {
+ log_err("ANDROID: Unsupported A/B metadata version: %.8x\n",
+ abc->version);
+ ret = -ENODATA;
+ goto error;
+ }
+
+ if (abc->nb_slot > ARRAY_SIZE(abc->slot_info)) {
+ log_err("ANDROID: Wrong number of slots %u, expected %zu\n",
+ abc->nb_slot, ARRAY_SIZE(abc->slot_info));
+ ret = -ENODATA;
+ goto error;
+ }
+
+ printf("Bootloader Control: [%s]\n", part_info->name);
+ printf("Active Slot: %s\n", abc->slot_suffix);
+ printf("Magic Number: 0x%x\n", abc->magic);
+ printf("Version: %u\n", abc->version);
+ printf("Number of Slots: %u\n", abc->nb_slot);
+ printf("Recovery Tries Remaining: %u\n", abc->recovery_tries_remaining);
+
+ printf("CRC: 0x%.8x", abc->crc32_le);
+
+ crc32_le = ab_control_compute_crc(abc);
+ if (abc->crc32_le != crc32_le)
+ printf(" (Invalid, Expected: 0x%.8x)\n", crc32_le);
+ else
+ printf(" (Valid)\n");
+
+ for (i = 0; i < abc->nb_slot; ++i) {
+ slot = &abc->slot_info[i];
+ printf("\nSlot[%d] Metadata:\n", i);
+ printf("\t- Priority: %u\n", slot->priority);
+ printf("\t- Tries Remaining: %u\n", slot->tries_remaining);
+ printf("\t- Successful Boot: %u\n", slot->successful_boot);
+ printf("\t- Verity Corrupted: %u\n", slot->verity_corrupted);
+ }
+
+error:
+ free(abc);
+
+ return ret;
+}
diff --git a/boot/boot_fit.c b/boot/boot_fit.c
new file mode 100644
index 00000000000..4dcaf95c6ae
--- /dev/null
+++ b/boot/boot_fit.c
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2017
+ * Texas Instruments, <www.ti.com>
+ *
+ * Franklin S Cooper Jr. <fcooper@ti.com>
+ */
+
+#include <boot_fit.h>
+#include <errno.h>
+#include <image.h>
+#include <log.h>
+#include <linux/libfdt.h>
+
+static int fdt_offset(const void *fit)
+{
+ int images, node, fdt_len, fdt_node, fdt_offset;
+ const char *fdt_name;
+
+ node = fit_find_config_node(fit);
+ if (node < 0)
+ return node;
+
+ images = fdt_path_offset(fit, FIT_IMAGES_PATH);
+ if (images < 0) {
+ debug("%s: Cannot find /images node: %d\n", __func__, images);
+ return -EINVAL;
+ }
+
+ fdt_name = fdt_getprop(fit, node, FIT_FDT_PROP, &fdt_len);
+ if (!fdt_name) {
+ debug("%s: Cannot find fdt name property: %d\n",
+ __func__, fdt_len);
+ return -EINVAL;
+ }
+
+ fdt_node = fdt_subnode_offset(fit, images, fdt_name);
+ if (fdt_node < 0) {
+ debug("%s: Cannot find fdt node '%s': %d\n",
+ __func__, fdt_name, fdt_node);
+ return -EINVAL;
+ }
+
+ fdt_offset = fdt_getprop_u32(fit, fdt_node, "data-offset");
+
+ if (fdt_offset == FDT_ERROR)
+ return -ENOENT;
+
+ fdt_len = fdt_getprop_u32(fit, fdt_node, "data-size");
+
+ if (fdt_len < 0)
+ return fdt_len;
+
+ return fdt_offset;
+}
+
+void *locate_dtb_in_fit(const void *fit)
+{
+ struct legacy_img_hdr *header;
+ int size;
+ int ret;
+
+ size = fdt_totalsize(fit);
+ size = (size + 3) & ~3;
+
+ header = (struct legacy_img_hdr *)fit;
+
+ if (image_get_magic(header) != FDT_MAGIC) {
+ debug("No FIT image appended to U-Boot\n");
+ return NULL;
+ }
+
+ ret = fdt_offset(fit);
+
+ if (ret < 0)
+ return NULL;
+ else
+ return (void *)fit+size+ret;
+}
diff --git a/boot/bootdev-uclass.c b/boot/bootdev-uclass.c
new file mode 100644
index 00000000000..3f8dc2c3c4e
--- /dev/null
+++ b/boot/bootdev-uclass.c
@@ -0,0 +1,952 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <dm.h>
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <bootstd.h>
+#include <fs.h>
+#include <log.h>
+#include <malloc.h>
+#include <part.h>
+#include <sort.h>
+#include <spl.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <dm/uclass-internal.h>
+
+enum {
+ /*
+ * Set some sort of limit on the number of partitions a bootdev can
+ * have. Note that for disks this limits the partitions numbers that
+ * are scanned to 1..MAX_BOOTFLOWS_PER_BOOTDEV
+ */
+ MAX_PART_PER_BOOTDEV = 30,
+
+ /* Maximum supported length of the "boot_targets" env string */
+ BOOT_TARGETS_MAX_LEN = 100,
+};
+
+int bootdev_first_bootflow(struct udevice *dev, struct bootflow **bflowp)
+{
+ struct bootstd_priv *std;
+ struct bootflow *bflow;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return log_msg_ret("bff", ret);
+
+ bflow = alist_getw(&std->bootflows, 0, struct bootflow);
+ if (!bflow)
+ return -ENOENT;
+ *bflowp = bflow;
+
+ return 0;
+}
+
+int bootdev_next_bootflow(struct bootflow **bflowp)
+{
+ struct bootstd_priv *std;
+ struct bootflow *bflow;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return log_msg_ret("bff", ret);
+
+ bflow = alist_nextw(&std->bootflows, *bflowp);
+ if (!bflow)
+ return -ENOENT;
+ *bflowp = bflow;
+
+ return 0;
+}
+
+int bootdev_bind(struct udevice *parent, const char *drv_name, const char *name,
+ struct udevice **devp)
+{
+ struct udevice *dev;
+ char dev_name[30];
+ char *str;
+ int ret;
+
+ snprintf(dev_name, sizeof(dev_name), "%s.%s", parent->name, name);
+ str = strdup(dev_name);
+ if (!str)
+ return -ENOMEM;
+ ret = device_bind_driver(parent, drv_name, str, &dev);
+ if (ret)
+ return ret;
+ device_set_name_alloced(dev);
+ *devp = dev;
+
+ return 0;
+}
+
+int bootdev_find_in_blk(struct udevice *dev, struct udevice *blk,
+ struct bootflow_iter *iter, struct bootflow *bflow)
+{
+ struct bootmeth_uc_plat *plat = dev_get_uclass_plat(bflow->method);
+ bool allow_any_part = plat->flags & BOOTMETHF_ANY_PART;
+ struct blk_desc *desc = dev_get_uclass_plat(blk);
+ struct disk_partition info;
+ char partstr[20];
+ char name[60];
+ int ret;
+
+ /* Sanity check */
+ if (iter->part >= MAX_PART_PER_BOOTDEV)
+ return log_msg_ret("max", -ESHUTDOWN);
+
+ bflow->blk = blk;
+ if (iter->part)
+ snprintf(partstr, sizeof(partstr), "part_%x", iter->part);
+ else
+ strcpy(partstr, "whole");
+ snprintf(name, sizeof(name), "%s.%s", dev->name, partstr);
+ bflow->name = strdup(name);
+ if (!bflow->name)
+ return log_msg_ret("name", -ENOMEM);
+
+ bflow->part = iter->part;
+
+ ret = bootmeth_check(bflow->method, iter);
+ if (ret)
+ return log_msg_ret("check", ret);
+
+ /*
+ * partition numbers start at 0 so this cannot succeed, but it can tell
+ * us whether there is valid media there
+ */
+ ret = part_get_info(desc, iter->part, &info);
+ log_debug("part_get_info() returned %d\n", ret);
+ if (!iter->part && ret == -ENOENT)
+ ret = 0;
+
+ /*
+ * This error indicates the media is not present. Otherwise we just
+ * blindly scan the next partition. We could be more intelligent here
+ * and check which partition numbers actually exist.
+ */
+ if (ret == -EOPNOTSUPP)
+ ret = -ESHUTDOWN;
+ else
+ bflow->state = BOOTFLOWST_MEDIA;
+ if (ret && !allow_any_part) {
+ /* allow partition 1 to be missing */
+ if (iter->part == 1) {
+ iter->max_part = 3;
+ ret = -ENOENT;
+ }
+
+ return log_msg_ret("part", ret);
+ }
+
+ /*
+ * Currently we don't get the number of partitions, so just
+ * assume a large number
+ */
+ iter->max_part = MAX_PART_PER_BOOTDEV;
+
+ if (iter->flags & BOOTFLOWIF_SINGLE_PARTITION) {
+ /* a particular partition was specified, scan it without checking */
+ } else if (!iter->part) {
+ /* This is the whole disk, check if we have bootable partitions */
+ iter->first_bootable = part_get_bootable(desc);
+ log_debug("checking bootable=%d\n", iter->first_bootable);
+ } else if (allow_any_part) {
+ /*
+ * allow any partition to be scanned, by skipping any checks
+ * for filesystems or partition contents on this disk
+ */
+
+ /* if there are bootable partitions, scan only those */
+ } else if ((iter->flags & BOOTFLOWIF_ONLY_BOOTABLE) &&
+ iter->first_bootable >= 0 &&
+ (iter->first_bootable ? !info.bootable : iter->part != 1)) {
+ log_debug("Skipping non-bootable partition %d\n", iter->part);
+ return log_msg_ret("boot", -EINVAL);
+ } else {
+ ret = fs_set_blk_dev_with_part(desc, bflow->part);
+ bflow->state = BOOTFLOWST_PART;
+ if (ret)
+ return log_msg_ret("fs", ret);
+
+ log_debug("%s: Found partition %x type %x fstype %d\n",
+ blk->name, bflow->part,
+ IS_ENABLED(CONFIG_DOS_PARTITION) ?
+ disk_partition_sys_ind(&info) : 0,
+ ret ? -1 : fs_get_type());
+ bflow->blk = blk;
+ bflow->state = BOOTFLOWST_FS;
+ }
+
+ log_debug("method %s\n", bflow->method->name);
+ ret = bootmeth_read_bootflow(bflow->method, bflow);
+ if (ret)
+ return log_msg_ret("method", ret);
+
+ return 0;
+}
+
+void bootdev_list(bool probe)
+{
+ struct udevice *dev;
+ int ret;
+ int i;
+
+ printf("Seq Probed Status Uclass Name\n");
+ printf("--- ------ ------ -------- ------------------\n");
+ if (probe)
+ ret = uclass_first_device_check(UCLASS_BOOTDEV, &dev);
+ else
+ ret = uclass_find_first_device(UCLASS_BOOTDEV, &dev);
+ for (i = 0; dev; i++) {
+ printf("%3x [ %c ] %6s %-9.9s %s\n", dev_seq(dev),
+ device_active(dev) ? '+' : ' ',
+ ret ? simple_itoa(-ret) : "OK",
+ dev_get_uclass_name(dev_get_parent(dev)), dev->name);
+ if (probe) {
+ ret = uclass_next_device_check(&dev);
+ } else {
+ uclass_find_next_device(&dev);
+ ret = 0;
+ }
+ }
+ printf("--- ------ ------ -------- ------------------\n");
+ printf("(%d bootdev%s)\n", i, i != 1 ? "s" : "");
+}
+
+int bootdev_setup_for_dev(struct udevice *parent, const char *drv_name)
+{
+ struct udevice *bdev;
+ int ret;
+
+ ret = device_find_first_child_by_uclass(parent, UCLASS_BOOTDEV,
+ &bdev);
+ if (ret) {
+ if (ret != -ENODEV) {
+ log_debug("Cannot access bootdev device\n");
+ return ret;
+ }
+
+ ret = bootdev_bind(parent, drv_name, "bootdev", &bdev);
+ if (ret) {
+ log_debug("Cannot create bootdev device\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int bootdev_get_suffix_start(struct udevice *dev, const char *suffix)
+{
+ int len, slen;
+
+ len = strlen(dev->name);
+ slen = strlen(suffix);
+ if (len > slen && !strcmp(suffix, dev->name + len - slen))
+ return len - slen;
+
+ return len;
+}
+
+int bootdev_setup_for_sibling_blk(struct udevice *blk, const char *drv_name)
+{
+ struct udevice *parent, *dev;
+ char dev_name[50];
+ int ret, len;
+
+ len = bootdev_get_suffix_start(blk, ".blk");
+ if (xpl_phase() < PHASE_BOARD_R) {
+ strlcpy(dev_name, blk->name, sizeof(dev_name) - 5);
+ strcat(dev_name, ".sib");
+ } else {
+ snprintf(dev_name, sizeof(dev_name), "%.*s.%s", len, blk->name,
+ "bootdev");
+ }
+
+ parent = dev_get_parent(blk);
+ ret = device_find_child_by_name(parent, dev_name, &dev);
+ if (ret) {
+ char *str;
+
+ if (ret != -ENODEV) {
+ log_debug("Cannot access bootdev device\n");
+ return ret;
+ }
+ str = strdup(dev_name);
+ if (!str)
+ return -ENOMEM;
+
+ ret = device_bind_driver(parent, drv_name, str, &dev);
+ if (ret) {
+ log_debug("Cannot create bootdev device\n");
+ return ret;
+ }
+ device_set_name_alloced(dev);
+ }
+
+ return 0;
+}
+
+int bootdev_get_sibling_blk(struct udevice *dev, struct udevice **blkp)
+{
+ struct udevice *parent = dev_get_parent(dev);
+ struct udevice *blk;
+ int ret, len;
+
+ if (device_get_uclass_id(dev) != UCLASS_BOOTDEV)
+ return -EINVAL;
+
+ /*
+ * This should always work if bootdev_setup_for_sibling_blk() was used
+ */
+ len = bootdev_get_suffix_start(dev, ".bootdev");
+ ret = device_find_child_by_namelen(parent, dev->name, len, &blk);
+ if (ret) {
+ char dev_name[50];
+
+ snprintf(dev_name, sizeof(dev_name), "%.*s.blk", len,
+ dev->name);
+ ret = device_find_child_by_name(parent, dev_name, &blk);
+ if (ret)
+ return log_msg_ret("find", ret);
+ }
+ ret = device_probe(blk);
+ if (ret)
+ return log_msg_ret("act", ret);
+ *blkp = blk;
+
+ return 0;
+}
+
+int bootdev_get_from_blk(struct udevice *blk, struct udevice **bootdevp)
+{
+ struct udevice *parent = dev_get_parent(blk);
+ struct udevice *bootdev;
+ char dev_name[50];
+ int ret, len;
+
+ if (device_get_uclass_id(blk) != UCLASS_BLK)
+ return -EINVAL;
+
+ /* This should always work if bootdev_setup_for_sibling_blk() was used */
+ len = bootdev_get_suffix_start(blk, ".blk");
+ snprintf(dev_name, sizeof(dev_name), "%.*s.%s", len, blk->name,
+ "bootdev");
+ ret = device_find_child_by_name(parent, dev_name, &bootdev);
+ if (ret)
+ return log_msg_ret("find", ret);
+ *bootdevp = bootdev;
+
+ return 0;
+}
+
+int bootdev_unbind_dev(struct udevice *parent)
+{
+ struct udevice *dev;
+ int ret;
+
+ ret = device_find_first_child_by_uclass(parent, UCLASS_BOOTDEV, &dev);
+ if (!ret) {
+ ret = device_remove(dev, DM_REMOVE_NORMAL);
+ if (ret)
+ return log_msg_ret("rem", ret);
+ ret = device_unbind(dev);
+ if (ret)
+ return log_msg_ret("unb", ret);
+ }
+
+ return 0;
+}
+
+/**
+ * label_to_uclass() - Convert a label to a uclass and sequence number
+ *
+ * @label: Label to look up (e.g. "mmc1" or "mmc0")
+ * @seqp: Returns the sequence number, or -1 if none
+ * @method_flagsp: If non-NULL, returns any flags implied by the label
+ * (enum bootflow_meth_flags_t), 0 if none
+ * Returns: sequence number on success, -EPFNOSUPPORT is the uclass is not
+ * known, other -ve error code on other error
+ */
+static int label_to_uclass(const char *label, int *seqp, int *method_flagsp)
+{
+ int seq, len, method_flags;
+ enum uclass_id id;
+ const char *end;
+
+ method_flags = 0;
+ seq = trailing_strtoln_end(label, NULL, &end);
+ len = end - label;
+ if (!len)
+ return -EINVAL;
+ id = uclass_get_by_namelen(label, len);
+ log_debug("find %s: seq=%d, id=%d/%s\n", label, seq, id,
+ uclass_get_name(id));
+ if (id == UCLASS_INVALID) {
+ /* try some special cases */
+ if (IS_ENABLED(CONFIG_BOOTDEV_SPI_FLASH) &&
+ !strncmp("spi", label, len)) {
+ id = UCLASS_SPI_FLASH;
+ } else if (IS_ENABLED(CONFIG_BOOTDEV_ETH) &&
+ !strncmp("pxe", label, len)) {
+ id = UCLASS_ETH;
+ method_flags |= BOOTFLOW_METHF_PXE_ONLY;
+ } else if (IS_ENABLED(CONFIG_BOOTDEV_ETH) &&
+ !strncmp("dhcp", label, len)) {
+ id = UCLASS_ETH;
+ method_flags |= BOOTFLOW_METHF_DHCP_ONLY;
+ } else {
+ return -EPFNOSUPPORT;
+ }
+ }
+ if (id == UCLASS_USB)
+ id = UCLASS_MASS_STORAGE;
+ *seqp = seq;
+ if (method_flagsp)
+ *method_flagsp = method_flags;
+
+ return id;
+}
+
+int bootdev_find_by_label(const char *label, struct udevice **devp,
+ int *method_flagsp)
+{
+ int seq, ret, method_flags = 0;
+ struct udevice *media;
+ struct uclass *uc;
+ enum uclass_id id;
+
+ if (!CONFIG_IS_ENABLED(BLK))
+ return -ENOSYS;
+
+ ret = label_to_uclass(label, &seq, &method_flags);
+ if (ret < 0)
+ return log_msg_ret("uc", ret);
+ id = ret;
+
+ /* Iterate through devices in the media uclass (e.g. UCLASS_MMC) */
+ uclass_id_foreach_dev(id, media, uc) {
+ struct udevice *bdev, *blk;
+ int ret;
+
+ /* if there is no seq, match anything */
+ if (seq != -1 && dev_seq(media) != seq) {
+ log_debug("- skip, media seq=%d\n", dev_seq(media));
+ continue;
+ }
+
+ ret = device_find_first_child_by_uclass(media, UCLASS_BOOTDEV,
+ &bdev);
+ if (ret) {
+ log_debug("- looking via blk, seq=%d, id=%d\n", seq,
+ id);
+ ret = blk_find_device(id, seq, &blk);
+ if (!ret) {
+ log_debug("- get from blk %s\n", blk->name);
+ ret = bootdev_get_from_blk(blk, &bdev);
+ }
+ }
+ if (!ret) {
+ log_debug("- found %s\n", bdev->name);
+ *devp = bdev;
+
+ /*
+ * if no sequence number was provided, we must scan all
+ * bootdevs for this media uclass
+ */
+ if (seq == -1)
+ method_flags |= BOOTFLOW_METHF_SINGLE_UCLASS;
+ if (method_flagsp)
+ *method_flagsp = method_flags;
+ log_debug("method flags %x\n", method_flags);
+ return 0;
+ }
+ log_debug("- no device in %s\n", media->name);
+ }
+
+ return -ENOENT;
+}
+
+int bootdev_find_by_any(const char *name, struct udevice **devp,
+ int *method_flagsp)
+{
+ struct udevice *dev;
+ int method_flags = 0;
+ int ret = -ENODEV, seq;
+ char *endp;
+
+ seq = simple_strtol(name, &endp, 16);
+
+ /* Select by name, label or number */
+ if (*endp) {
+ ret = uclass_get_device_by_name(UCLASS_BOOTDEV, name, &dev);
+ if (ret == -ENODEV) {
+ ret = bootdev_find_by_label(name, &dev, &method_flags);
+ if (ret) {
+ printf("Cannot find bootdev '%s' (err=%d)\n",
+ name, ret);
+ return log_msg_ret("lab", ret);
+ }
+ ret = device_probe(dev);
+ }
+ if (ret) {
+ printf("Cannot probe bootdev '%s' (err=%d)\n", name,
+ ret);
+ return log_msg_ret("pro", ret);
+ }
+ } else if (IS_ENABLED(CONFIG_BOOTSTD_FULL)) {
+ ret = uclass_get_device_by_seq(UCLASS_BOOTDEV, seq, &dev);
+ method_flags |= BOOTFLOW_METHF_SINGLE_DEV;
+ }
+ if (ret) {
+ printf("Cannot find '%s' (err=%d)\n", name, ret);
+ return ret;
+ }
+
+ *devp = dev;
+ if (method_flagsp)
+ *method_flagsp = method_flags;
+
+ return 0;
+}
+
+int bootdev_hunt_and_find_by_label(const char *label, struct udevice **devp,
+ int *method_flagsp)
+{
+ int ret;
+
+ ret = bootdev_hunt(label, false);
+ if (ret)
+ return log_msg_ret("scn", ret);
+ ret = bootdev_find_by_label(label, devp, method_flagsp);
+ if (ret)
+ return log_msg_ret("fnd", ret);
+
+ return 0;
+}
+
+static int default_get_bootflow(struct udevice *dev, struct bootflow_iter *iter,
+ struct bootflow *bflow)
+{
+ struct udevice *blk;
+ int ret;
+
+ ret = bootdev_get_sibling_blk(dev, &blk);
+ log_debug("sibling_blk ret=%d, blk=%s\n", ret,
+ ret ? "(none)" : blk->name);
+ /*
+ * If there is no media, indicate that no more partitions should be
+ * checked
+ */
+ if (ret == -EOPNOTSUPP)
+ ret = -ESHUTDOWN;
+ if (ret)
+ return log_msg_ret("blk", ret);
+ assert(blk);
+ ret = bootdev_find_in_blk(dev, blk, iter, bflow);
+ if (ret)
+ return log_msg_ret("find", ret);
+
+ return 0;
+}
+
+int bootdev_get_bootflow(struct udevice *dev, struct bootflow_iter *iter,
+ struct bootflow *bflow)
+{
+ const struct bootdev_ops *ops = bootdev_get_ops(dev);
+
+ log_debug("->get_bootflow %s,%x=%p\n", dev->name, iter->part,
+ ops->get_bootflow);
+ bootflow_init(bflow, dev, iter->method);
+ if (!ops->get_bootflow)
+ return default_get_bootflow(dev, iter, bflow);
+
+ return ops->get_bootflow(dev, iter, bflow);
+}
+
+int bootdev_next_label(struct bootflow_iter *iter, struct udevice **devp,
+ int *method_flagsp)
+{
+ struct udevice *dev;
+
+ log_debug("next\n");
+ if (iter->cur_label >= 0 && !iter->labels[iter->cur_label])
+ return log_msg_ret("fil", -ENODEV);
+
+ for (dev = NULL; !dev && iter->labels[++iter->cur_label];) {
+ const char *label = iter->labels[iter->cur_label];
+ int ret;
+
+ log_debug("Scanning: %s\n", label);
+ ret = bootdev_hunt_and_find_by_label(label, &dev,
+ method_flagsp);
+ if (iter->flags & BOOTFLOWIF_SHOW) {
+ if (ret == -EPFNOSUPPORT) {
+ log_warning("Unknown uclass '%s' in label\n",
+ label);
+ } else if (ret == -ENOENT) {
+ /*
+ * looking for, e.g. 'scsi0' should find
+ * something if SCSI is present
+ */
+ if (!trailing_strtol(label)) {
+ log_warning("No bootdevs for '%s'\n",
+ label);
+ }
+ }
+ }
+
+ }
+
+ if (!dev)
+ return log_msg_ret("fin", -ENODEV);
+ *devp = dev;
+
+ return 0;
+}
+
+int bootdev_next_prio(struct bootflow_iter *iter, struct udevice **devp)
+{
+ struct udevice *dev = *devp;
+ bool found;
+ int ret;
+
+ /* find the next device with this priority */
+ *devp = NULL;
+ log_debug("next prio %d: dev=%p/%s\n", iter->cur_prio, dev,
+ dev ? dev->name : "none");
+ found = false;
+ do {
+ /*
+ * Don't probe devices here since they may not be of the
+ * required priority
+ */
+ if (!dev)
+ uclass_find_first_device(UCLASS_BOOTDEV, &dev);
+ else
+ uclass_find_next_device(&dev);
+ found = false;
+
+ /* scan for the next device with the correct priority */
+ while (dev) {
+ struct bootdev_uc_plat *plat;
+
+ plat = dev_get_uclass_plat(dev);
+ log_debug("- %s: %d, want %d\n", dev->name, plat->prio,
+ iter->cur_prio);
+ if (plat->prio == iter->cur_prio)
+ break;
+ uclass_find_next_device(&dev);
+ }
+
+ /* none found for this priority, so move to the next */
+ if (!dev) {
+ log_debug("None found at prio %d, moving to %d\n",
+ iter->cur_prio, iter->cur_prio + 1);
+ if (++iter->cur_prio == BOOTDEVP_COUNT)
+ return log_msg_ret("fin", -ENODEV);
+
+ if (iter->flags & BOOTFLOWIF_HUNT) {
+ /* hunt to find new bootdevs */
+ ret = bootdev_hunt_prio(iter->cur_prio,
+ iter->flags &
+ BOOTFLOWIF_SHOW);
+ log_debug("- bootdev_hunt_prio() ret %d\n",
+ ret);
+ if (ret)
+ return log_msg_ret("hun", ret);
+ }
+ } else {
+ ret = device_probe(dev);
+ if (ret)
+ log_debug("Device '%s' failed to probe\n",
+ dev->name);
+ else
+ found = true;
+ }
+ } while (!found);
+
+ *devp = dev;
+
+ return 0;
+}
+
+int bootdev_setup_iter(struct bootflow_iter *iter, const char *label,
+ struct udevice **devp, int *method_flagsp)
+{
+ struct udevice *bootstd, *dev = NULL;
+ bool show = iter->flags & BOOTFLOWIF_SHOW;
+ int method_flags;
+ char buf[32];
+ int ret;
+
+ if (label) {
+ const char *end = strchr(label, ':');
+
+ if (end) {
+ size_t len = (size_t)(end - label);
+ const char *part = end + 1;
+
+ if (len + 1 > sizeof(buf)) {
+ log_err("label \"%s\" is way too long\n", label);
+ return -EINVAL;
+ }
+
+ memcpy(buf, label, len);
+ buf[len] = '\0';
+ label = buf;
+
+ unsigned long tmp;
+
+ if (strict_strtoul(part, 0, &tmp)) {
+ log_err("Invalid partition number: %s\n", part);
+ return -EINVAL;
+ }
+
+ iter->flags |= BOOTFLOWIF_SINGLE_PARTITION;
+ iter->part = tmp;
+ }
+ }
+
+ ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd);
+ if (ret) {
+ log_err("Missing bootstd device\n");
+ return log_msg_ret("std", ret);
+ }
+
+ /* hunt for any pre-scan devices */
+ if (iter->flags & BOOTFLOWIF_HUNT) {
+ ret = bootdev_hunt_prio(BOOTDEVP_1_PRE_SCAN, show);
+ log_debug("- bootdev_hunt_prio() ret %d\n", ret);
+ if (ret)
+ return log_msg_ret("pre", ret);
+ }
+
+ /* Handle scanning a single device */
+ if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && label) {
+ if (iter->flags & BOOTFLOWIF_HUNT) {
+ ret = bootdev_hunt(label, show);
+ if (ret)
+ return log_msg_ret("hun", ret);
+ }
+ ret = bootdev_find_by_any(label, &dev, &method_flags);
+ if (ret)
+ return log_msg_ret("lab", ret);
+
+ log_debug("method_flags: %x\n", method_flags);
+ if (method_flags & BOOTFLOW_METHF_SINGLE_UCLASS)
+ iter->flags |= BOOTFLOWIF_SINGLE_UCLASS;
+ else if (method_flags & BOOTFLOW_METHF_SINGLE_DEV)
+ iter->flags |= BOOTFLOWIF_SINGLE_DEV;
+ else
+ iter->flags |= BOOTFLOWIF_SINGLE_MEDIA;
+ log_debug("Selected label: %s, flags %x\n", label, iter->flags);
+ } else {
+ bool ok;
+
+ /* This either returns a non-empty list or NULL */
+ iter->labels = bootstd_get_bootdev_order(bootstd, &ok);
+ if (!ok)
+ return log_msg_ret("ord", -ENOMEM);
+ log_debug("setup labels %p\n", iter->labels);
+ if (iter->labels) {
+ iter->cur_label = -1;
+ ret = bootdev_next_label(iter, &dev, &method_flags);
+ } else {
+ ret = bootdev_next_prio(iter, &dev);
+ method_flags = 0;
+ }
+ if (!dev)
+ return log_msg_ret("fin", -ENOENT);
+ log_debug("Selected bootdev: %s\n", dev->name);
+ }
+
+ ret = device_probe(dev);
+ if (ret)
+ return log_msg_ret("probe", ret);
+ if (method_flagsp)
+ *method_flagsp = method_flags;
+ *devp = dev;
+
+ return 0;
+}
+
+static int bootdev_hunt_drv(struct bootdev_hunter *info, uint seq, bool show)
+{
+ const char *name = uclass_get_name(info->uclass);
+ struct bootstd_priv *std;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return log_msg_ret("std", ret);
+
+ if (!(std->hunters_used & BIT(seq))) {
+ if (show)
+ printf("Hunting with: %s\n",
+ uclass_get_name(info->uclass));
+ log_debug("Hunting with: %s\n", name);
+ if (info->hunt) {
+ ret = info->hunt(info, show);
+ log_debug(" - hunt result %d\n", ret);
+ if (ret && ret != -ENOENT)
+ return ret;
+ }
+ std->hunters_used |= BIT(seq);
+ }
+
+ return 0;
+}
+
+int bootdev_hunt(const char *spec, bool show)
+{
+ struct bootdev_hunter *start;
+ const char *end;
+ int n_ent, i;
+ int result;
+ size_t len;
+
+ start = ll_entry_start(struct bootdev_hunter, bootdev_hunter);
+ n_ent = ll_entry_count(struct bootdev_hunter, bootdev_hunter);
+ result = 0;
+
+ len = SIZE_MAX;
+ if (spec) {
+ trailing_strtoln_end(spec, NULL, &end);
+ len = end - spec;
+ }
+
+ for (i = 0; i < n_ent; i++) {
+ struct bootdev_hunter *info = start + i;
+ const char *name = uclass_get_name(info->uclass);
+ int ret;
+
+ log_debug("looking at %.*s for %s\n",
+ (int)max(strlen(name), len), spec, name);
+ if (spec && strncmp(spec, name, max(strlen(name), len))) {
+ if (info->uclass != UCLASS_ETH ||
+ (strcmp("dhcp", spec) && strcmp("pxe", spec)))
+ continue;
+ }
+ ret = bootdev_hunt_drv(info, i, show);
+ if (ret)
+ result = ret;
+ }
+
+ return result;
+}
+
+int bootdev_unhunt(enum uclass_id id)
+{
+ struct bootdev_hunter *start;
+ int n_ent, i;
+
+ start = ll_entry_start(struct bootdev_hunter, bootdev_hunter);
+ n_ent = ll_entry_count(struct bootdev_hunter, bootdev_hunter);
+ for (i = 0; i < n_ent; i++) {
+ struct bootdev_hunter *info = start + i;
+
+ if (info->uclass == id) {
+ struct bootstd_priv *std;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return log_msg_ret("std", ret);
+ if (!(std->hunters_used & BIT(i)))
+ return -EALREADY;
+ std->hunters_used &= ~BIT(i);
+ return 0;
+ }
+ }
+
+ return -ENOENT;
+}
+
+int bootdev_hunt_prio(enum bootdev_prio_t prio, bool show)
+{
+ struct bootdev_hunter *start;
+ int n_ent, i;
+ int result;
+
+ start = ll_entry_start(struct bootdev_hunter, bootdev_hunter);
+ n_ent = ll_entry_count(struct bootdev_hunter, bootdev_hunter);
+ result = 0;
+
+ log_debug("Hunting for priority %d\n", prio);
+ for (i = 0; i < n_ent; i++) {
+ struct bootdev_hunter *info = start + i;
+ int ret;
+
+ if (prio != info->prio)
+ continue;
+ ret = bootdev_hunt_drv(info, i, show);
+ log_debug("bootdev_hunt_drv() return %d\n", ret);
+ if (ret && ret != -ENOENT)
+ result = ret;
+ }
+ log_debug("exit %d\n", result);
+
+ return result;
+}
+
+void bootdev_list_hunters(struct bootstd_priv *std)
+{
+ struct bootdev_hunter *orig, *start;
+ int n_ent, i;
+
+ orig = ll_entry_start(struct bootdev_hunter, bootdev_hunter);
+ n_ent = ll_entry_count(struct bootdev_hunter, bootdev_hunter);
+
+ /*
+ * workaround for strange bug in clang-12 which sees all the below data
+ * as zeroes. Any access of start seems to fix it, such as
+ *
+ * printf("%p", start);
+ *
+ * Use memcpy() to force the correct behaviour.
+ */
+ memcpy(&start, &orig, sizeof(orig));
+ printf("%4s %4s %-15s %s\n", "Prio", "Used", "Uclass", "Hunter");
+ printf("%4s %4s %-15s %s\n", "----", "----", "---------------", "---------------");
+ for (i = 0; i < n_ent; i++) {
+ struct bootdev_hunter *info = start + i;
+
+ printf("%4d %4s %-15s %s\n", info->prio,
+ std->hunters_used & BIT(i) ? "*" : "",
+ uclass_get_name(info->uclass),
+ info->drv ? info->drv->name : "(none)");
+ }
+
+ printf("(total hunters: %d)\n", n_ent);
+}
+
+static int bootdev_pre_unbind(struct udevice *dev)
+{
+ int ret;
+
+ ret = bootstd_clear_bootflows_for_bootdev(dev);
+ if (ret)
+ return log_msg_ret("bun", ret);
+
+ return 0;
+}
+
+UCLASS_DRIVER(bootdev) = {
+ .id = UCLASS_BOOTDEV,
+ .name = "bootdev",
+ .flags = DM_UC_FLAG_SEQ_ALIAS,
+ .per_device_plat_auto = sizeof(struct bootdev_uc_plat),
+ .pre_unbind = bootdev_pre_unbind,
+};
diff --git a/boot/bootflow.c b/boot/bootflow.c
new file mode 100644
index 00000000000..d79f303486d
--- /dev/null
+++ b/boot/bootflow.c
@@ -0,0 +1,1011 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <bootstd.h>
+#include <dm.h>
+#include <env_internal.h>
+#include <malloc.h>
+#include <serial.h>
+#include <dm/device-internal.h>
+#include <dm/uclass-internal.h>
+
+/* error codes used to signal running out of things */
+enum {
+ BF_NO_MORE_PARTS = -ESHUTDOWN,
+ BF_NO_MORE_DEVICES = -ENODEV,
+};
+
+static const char *const bootflow_img[BFI_COUNT - BFI_FIRST] = {
+ "extlinux_cfg",
+ "logo",
+ "efi",
+ "cmdline",
+};
+
+/**
+ * bootflow_state - name for each state
+ *
+ * See enum bootflow_state_t for what each of these means
+ */
+static const char *const bootflow_state[BOOTFLOWST_COUNT] = {
+ "base",
+ "media",
+ "part",
+ "fs",
+ "file",
+ "ready",
+};
+
+const char *bootflow_state_get_name(enum bootflow_state_t state)
+{
+ /* This doesn't need to be a useful name, since it will never occur */
+ if (state < 0 || state >= BOOTFLOWST_COUNT)
+ return "?";
+
+ return bootflow_state[state];
+}
+
+int bootflow_first_glob(struct bootflow **bflowp)
+{
+ struct bootstd_priv *std;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return ret;
+
+ if (!std->bootflows.count)
+ return -ENOENT;
+
+ *bflowp = alist_getw(&std->bootflows, 0, struct bootflow);
+
+ return 0;
+}
+
+int bootflow_next_glob(struct bootflow **bflowp)
+{
+ struct bootstd_priv *std;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return ret;
+
+ *bflowp = alist_nextw(&std->bootflows, *bflowp);
+ if (!*bflowp)
+ return -ENOENT;
+
+ return 0;
+}
+
+void bootflow_iter_init(struct bootflow_iter *iter, int flags)
+{
+ memset(iter, '\0', sizeof(*iter));
+ iter->first_glob_method = -1;
+ iter->flags = flags;
+
+ /* remember the first bootdevs we see */
+ iter->max_devs = BOOTFLOW_MAX_USED_DEVS;
+}
+
+void bootflow_iter_uninit(struct bootflow_iter *iter)
+{
+ free(iter->method_order);
+}
+
+int bootflow_iter_drop_bootmeth(struct bootflow_iter *iter,
+ const struct udevice *bmeth)
+{
+ /* We only support disabling the current bootmeth */
+ if (bmeth != iter->method || iter->cur_method >= iter->num_methods ||
+ iter->method_order[iter->cur_method] != bmeth)
+ return -EINVAL;
+
+ memmove(&iter->method_order[iter->cur_method],
+ &iter->method_order[iter->cur_method + 1],
+ (iter->num_methods - iter->cur_method - 1) * sizeof(void *));
+
+ iter->num_methods--;
+
+ return 0;
+}
+
+/**
+ * bootflow_iter_set_dev() - switch to the next bootdev when iterating
+ *
+ * This sets iter->dev, records the device in the dev_used[] list and shows a
+ * message if required
+ *
+ * @iter: Iterator to update
+ * @dev: Bootdev to use, or NULL if there are no more
+ */
+static void bootflow_iter_set_dev(struct bootflow_iter *iter,
+ struct udevice *dev, int method_flags)
+{
+ struct bootmeth_uc_plat *ucp = dev_get_uclass_plat(iter->method);
+
+ log_debug("iter: Setting dev to %s, flags %x\n",
+ dev ? dev->name : "(none)", method_flags);
+ iter->dev = dev;
+ iter->method_flags = method_flags;
+
+ if (IS_ENABLED(CONFIG_BOOTSTD_FULL)) {
+ /* record the device for later */
+ if (dev && iter->num_devs < iter->max_devs)
+ iter->dev_used[iter->num_devs++] = dev;
+
+ if ((iter->flags & (BOOTFLOWIF_SHOW | BOOTFLOWIF_SINGLE_DEV)) ==
+ BOOTFLOWIF_SHOW) {
+ if (dev)
+ printf("Scanning bootdev '%s':\n", dev->name);
+ else if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) &&
+ ucp->flags & BOOTMETHF_GLOBAL)
+ printf("Scanning global bootmeth '%s':\n",
+ iter->method->name);
+ else
+ printf("No more bootdevs\n");
+ }
+ }
+}
+
+/**
+ * scan_next_in_uclass() - Scan for the next bootdev in the same media uclass
+ *
+ * Move through the following bootdevs until we find another in this media
+ * uclass, or run out
+ *
+ * @devp: On entry, the device to check, on exit the new device, or NULL if
+ * there is none
+ */
+static void scan_next_in_uclass(struct udevice **devp)
+{
+ struct udevice *dev = *devp;
+ enum uclass_id cur_id = device_get_uclass_id(dev->parent);
+
+ do {
+ uclass_find_next_device(&dev);
+ } while (dev && cur_id != device_get_uclass_id(dev->parent));
+
+ *devp = dev;
+}
+
+/**
+ * iter_incr() - Move to the next item (method, part, bootdev)
+ *
+ * Return: 0 if OK, BF_NO_MORE_DEVICES if there are no more bootdevs
+ */
+static int iter_incr(struct bootflow_iter *iter)
+{
+ struct udevice *dev;
+ bool inc_dev = true;
+ bool global;
+ int ret;
+
+ log_debug("entry: err=%d\n", iter->err);
+ global = iter->doing_global;
+
+ if (iter->err == BF_NO_MORE_DEVICES)
+ return BF_NO_MORE_DEVICES;
+
+ if (iter->err != BF_NO_MORE_PARTS) {
+ /* Get the next boothmethod */
+ if (++iter->cur_method < iter->num_methods) {
+ iter->method = iter->method_order[iter->cur_method];
+ return 0;
+ }
+
+ /*
+ * If we have finished scanning the global bootmeths, start the
+ * normal bootdev scan
+ */
+ if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && global) {
+ iter->num_methods = iter->first_glob_method;
+ iter->doing_global = false;
+
+ /*
+ * Don't move to the next dev as we haven't tried this
+ * one yet!
+ */
+ inc_dev = false;
+ }
+ }
+
+ if (iter->flags & BOOTFLOWIF_SINGLE_PARTITION)
+ return BF_NO_MORE_DEVICES;
+
+ /* No more bootmeths; start at the first one, and... */
+ iter->cur_method = 0;
+ iter->method = iter->method_order[iter->cur_method];
+
+ if (iter->err != BF_NO_MORE_PARTS) {
+ /* ...select next partition */
+ if (++iter->part <= iter->max_part)
+ return 0;
+ }
+
+ /* No more partitions; start at the first one and... */
+ iter->part = 0;
+
+ /*
+ * Note: as far as we know, there is no partition table on the next
+ * bootdev, so set max_part to 0 until we discover otherwise. See
+ * bootdev_find_in_blk() for where this is set.
+ */
+ iter->max_part = 0;
+
+ /* ...select next bootdev */
+ if (iter->flags & BOOTFLOWIF_SINGLE_DEV) {
+ ret = -ENOENT;
+ } else {
+ int method_flags = 0;
+
+ ret = 0;
+ dev = iter->dev;
+ log_debug("inc_dev=%d\n", inc_dev);
+ if (!inc_dev) {
+ ret = bootdev_setup_iter(iter, NULL, &dev,
+ &method_flags);
+ } else if (IS_ENABLED(CONFIG_BOOTSTD_FULL) &&
+ (iter->flags & BOOTFLOWIF_SINGLE_UCLASS)) {
+ scan_next_in_uclass(&dev);
+ if (!dev) {
+ log_debug("finished uclass %s\n",
+ dev_get_uclass_name(dev));
+ ret = -ENODEV;
+ }
+ } else if (IS_ENABLED(CONFIG_BOOTSTD_FULL) &&
+ iter->flags & BOOTFLOWIF_SINGLE_MEDIA) {
+ log_debug("next in single\n");
+ do {
+ /*
+ * Move to the next bootdev child of this media
+ * device. This ensures that we cover all the
+ * available SCSI IDs and LUNs.
+ */
+ device_find_next_child(&dev);
+ log_debug("- next %s\n",
+ dev ? dev->name : "(none)");
+ } while (dev && device_get_uclass_id(dev) !=
+ UCLASS_BOOTDEV);
+ if (!dev) {
+ log_debug("finished uclass %s\n",
+ dev_get_uclass_name(dev));
+ ret = -ENODEV;
+ }
+ } else {
+ log_debug("labels %p\n", iter->labels);
+ if (iter->labels) {
+ /*
+ * when the label is "mmc" we want to scan all
+ * mmc bootdevs, not just the first. See
+ * bootdev_find_by_label() where this flag is
+ * set up
+ */
+ if (iter->method_flags &
+ BOOTFLOW_METHF_SINGLE_UCLASS) {
+ scan_next_in_uclass(&dev);
+ log_debug("looking for next device %s: %s\n",
+ iter->dev->name,
+ dev ? dev->name : "<none>");
+ method_flags = BOOTFLOW_METHF_SINGLE_UCLASS;
+ } else {
+ dev = NULL;
+ }
+ if (!dev) {
+ log_debug("looking at next label\n");
+ ret = bootdev_next_label(iter, &dev,
+ &method_flags);
+ }
+ } else {
+ ret = bootdev_next_prio(iter, &dev);
+ }
+ }
+ log_debug("ret=%d, dev=%p %s\n", ret, dev,
+ dev ? dev->name : "none");
+ if (ret) {
+ bootflow_iter_set_dev(iter, NULL, 0);
+ } else {
+ /*
+ * Probe the bootdev. This does not probe any attached
+ * block device, since they are siblings
+ */
+ ret = device_probe(dev);
+ log_debug("probe %s %d\n", dev->name, ret);
+ if (!log_msg_ret("probe", ret))
+ bootflow_iter_set_dev(iter, dev, method_flags);
+ }
+ }
+
+ /* if there are no more bootdevs, give up */
+ if (ret)
+ return log_msg_ret("incr", BF_NO_MORE_DEVICES);
+
+ return 0;
+}
+
+/**
+ * bootflow_check() - Check if a bootflow can be obtained
+ *
+ * @iter: Provides part, bootmeth to use
+ * @bflow: Bootflow to update on success
+ * Return: 0 if OK, -ENOSYS if there is no bootflow support on this device,
+ * BF_NO_MORE_PARTS if there are no more partitions on bootdev
+ */
+static int bootflow_check(struct bootflow_iter *iter, struct bootflow *bflow)
+{
+ struct udevice *dev;
+ int ret;
+
+ if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && iter->doing_global) {
+ bootflow_iter_set_dev(iter, NULL, 0);
+ ret = bootmeth_get_bootflow(iter->method, bflow);
+ if (ret)
+ return log_msg_ret("glob", ret);
+
+ return 0;
+ }
+
+ dev = iter->dev;
+ ret = bootdev_get_bootflow(dev, iter, bflow);
+
+ /* If we got a valid bootflow, return it */
+ if (!ret) {
+ log_debug("Bootdev '%s' part %d method '%s': Found bootflow\n",
+ dev->name, iter->part, iter->method->name);
+ return 0;
+ }
+
+ /* Unless there is nothing more to try, move to the next device */
+ if (ret != BF_NO_MORE_PARTS && ret != -ENOSYS) {
+ log_debug("Bootdev '%s' part %d method '%s': Error %d\n",
+ dev->name, iter->part, iter->method->name, ret);
+ /*
+ * For 'all' we return all bootflows, even
+ * those with errors
+ */
+ if (iter->flags & BOOTFLOWIF_ALL)
+ return log_msg_ret("all", ret);
+ }
+
+ return log_msg_ret("check", ret);
+}
+
+int bootflow_scan_first(struct udevice *dev, const char *label,
+ struct bootflow_iter *iter, int flags,
+ struct bootflow *bflow)
+{
+ int ret;
+
+ if (dev || label)
+ flags |= BOOTFLOWIF_SKIP_GLOBAL;
+ bootflow_iter_init(iter, flags);
+
+ /*
+ * Set up the ordering of bootmeths. This sets iter->doing_global and
+ * iter->first_glob_method if we are starting with the global bootmeths
+ */
+ ret = bootmeth_setup_iter_order(iter, !(flags & BOOTFLOWIF_SKIP_GLOBAL));
+ if (ret)
+ return log_msg_ret("obmeth", -ENODEV);
+
+ /* Find the first bootmeth (there must be at least one!) */
+ iter->method = iter->method_order[iter->cur_method];
+
+ if (!IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) || !iter->doing_global) {
+ struct udevice *dev = NULL;
+ int method_flags;
+
+ ret = bootdev_setup_iter(iter, label, &dev, &method_flags);
+ if (ret)
+ return log_msg_ret("obdev", -ENODEV);
+
+ bootflow_iter_set_dev(iter, dev, method_flags);
+ }
+
+ ret = bootflow_check(iter, bflow);
+ if (ret) {
+ log_debug("check - ret=%d\n", ret);
+ if (ret != BF_NO_MORE_PARTS && ret != -ENOSYS) {
+ if (iter->flags & BOOTFLOWIF_ALL)
+ return log_msg_ret("all", ret);
+ }
+ iter->err = ret;
+ ret = bootflow_scan_next(iter, bflow);
+ if (ret)
+ return log_msg_ret("get", ret);
+ }
+
+ return 0;
+}
+
+int bootflow_scan_next(struct bootflow_iter *iter, struct bootflow *bflow)
+{
+ int ret;
+
+ do {
+ ret = iter_incr(iter);
+ log_debug("iter_incr: ret=%d\n", ret);
+ if (ret == BF_NO_MORE_DEVICES)
+ return log_msg_ret("done", ret);
+
+ if (!ret) {
+ ret = bootflow_check(iter, bflow);
+ log_debug("check - ret=%d\n", ret);
+ if (!ret)
+ return 0;
+ iter->err = ret;
+ if (ret != BF_NO_MORE_PARTS && ret != -ENOSYS) {
+ if (iter->flags & BOOTFLOWIF_ALL)
+ return log_msg_ret("all", ret);
+ }
+ } else {
+ log_debug("incr failed, err=%d\n", ret);
+ iter->err = ret;
+ }
+
+ } while (1);
+}
+
+void bootflow_init(struct bootflow *bflow, struct udevice *bootdev,
+ struct udevice *meth)
+{
+ memset(bflow, '\0', sizeof(*bflow));
+ bflow->dev = bootdev;
+ bflow->method = meth;
+ bflow->state = BOOTFLOWST_BASE;
+ alist_init_struct(&bflow->images, struct bootflow_img);
+}
+
+void bootflow_free(struct bootflow *bflow)
+{
+ struct bootflow_img *img;
+
+ free(bflow->name);
+ free(bflow->subdir);
+ free(bflow->fname);
+ if (!(bflow->flags & BOOTFLOWF_STATIC_BUF))
+ free(bflow->buf);
+ free(bflow->os_name);
+ free(bflow->fdt_fname);
+ free(bflow->bootmeth_priv);
+
+ alist_for_each(img, &bflow->images)
+ free(img->fname);
+ alist_empty(&bflow->images);
+}
+
+void bootflow_remove(struct bootflow *bflow)
+{
+ bootflow_free(bflow);
+}
+
+#if CONFIG_IS_ENABLED(BOOTSTD_FULL)
+int bootflow_read_all(struct bootflow *bflow)
+{
+ int ret;
+
+ if (bflow->state != BOOTFLOWST_READY)
+ return log_msg_ret("rd", -EPROTO);
+
+ ret = bootmeth_read_all(bflow->method, bflow);
+ if (ret)
+ return log_msg_ret("rd2", ret);
+
+ return 0;
+}
+#endif /* BOOTSTD_FULL */
+
+int bootflow_boot(struct bootflow *bflow)
+{
+ int ret;
+
+ if (bflow->state != BOOTFLOWST_READY)
+ return log_msg_ret("load", -EPROTO);
+
+ ret = bootmeth_boot(bflow->method, bflow);
+ if (ret)
+ return log_msg_ret("boot", ret);
+
+ /*
+ * internal error, should not get here since we should have booted
+ * something or returned an error
+ */
+
+ return log_msg_ret("end", -EFAULT);
+}
+
+int bootflow_run_boot(struct bootflow_iter *iter, struct bootflow *bflow)
+{
+ int ret;
+
+ printf("** Booting bootflow '%s' with %s\n", bflow->name,
+ bflow->method->name);
+ if (IS_ENABLED(CONFIG_OF_HAS_PRIOR_STAGE) &&
+ (bflow->flags & BOOTFLOWF_USE_PRIOR_FDT))
+ printf("Using prior-stage device tree\n");
+ ret = bootflow_boot(bflow);
+ if (!IS_ENABLED(CONFIG_BOOTSTD_FULL)) {
+ printf("Boot failed (err=%d)\n", ret);
+ return ret;
+ }
+
+ switch (ret) {
+ case -EPROTO:
+ printf("Bootflow not loaded (state '%s')\n",
+ bootflow_state_get_name(bflow->state));
+ break;
+ case -ENOSYS:
+ printf("Boot method '%s' not supported\n", bflow->method->name);
+ break;
+ case -ENOTSUPP:
+ /* Disable this bootflow for this iteration */
+ if (iter) {
+ int ret2;
+
+ ret2 = bootflow_iter_drop_bootmeth(iter, bflow->method);
+ if (!ret2) {
+ printf("Boot method '%s' failed and will not be retried\n",
+ bflow->method->name);
+ }
+ }
+
+ break;
+ default:
+ printf("Boot failed (err=%d)\n", ret);
+ break;
+ }
+
+ return ret;
+}
+
+int bootflow_iter_check_blk(const struct bootflow_iter *iter)
+{
+ const struct udevice *media = dev_get_parent(iter->dev);
+ enum uclass_id id = device_get_uclass_id(media);
+
+ log_debug("uclass %d: %s\n", id, uclass_get_name(id));
+ if (id != UCLASS_ETH && id != UCLASS_BOOTSTD && id != UCLASS_QFW)
+ return 0;
+
+ return -ENOTSUPP;
+}
+
+int bootflow_iter_check_mmc(const struct bootflow_iter *iter)
+{
+ const struct udevice *media = dev_get_parent(iter->dev);
+ enum uclass_id id = device_get_uclass_id(media);
+
+ log_debug("uclass %d: %s\n", id, uclass_get_name(id));
+ if (id == UCLASS_MMC)
+ return 0;
+
+ return -ENOTSUPP;
+}
+
+int bootflow_iter_check_sf(const struct bootflow_iter *iter)
+{
+ const struct udevice *media = dev_get_parent(iter->dev);
+ enum uclass_id id = device_get_uclass_id(media);
+
+ log_debug("uclass %d: %s\n", id, uclass_get_name(id));
+ if (id == UCLASS_SPI_FLASH)
+ return 0;
+
+ return -ENOTSUPP;
+}
+
+int bootflow_iter_check_net(const struct bootflow_iter *iter)
+{
+ const struct udevice *media = dev_get_parent(iter->dev);
+ enum uclass_id id = device_get_uclass_id(media);
+
+ log_debug("uclass %d: %s\n", id, uclass_get_name(id));
+ if (id == UCLASS_ETH)
+ return 0;
+
+ return -ENOTSUPP;
+}
+
+int bootflow_iter_check_system(const struct bootflow_iter *iter)
+{
+ const struct udevice *media = dev_get_parent(iter->dev);
+ enum uclass_id id = device_get_uclass_id(media);
+
+ log_debug("uclass %d: %s\n", id, uclass_get_name(id));
+ if (id == UCLASS_BOOTSTD)
+ return 0;
+
+ return -ENOTSUPP;
+}
+
+/**
+ * bootflow_cmdline_set() - Set the command line for a bootflow
+ *
+ * @value: New command-line string
+ * Returns 0 if OK, -ENOENT if no current bootflow, -ENOMEM if out of memory
+ */
+int bootflow_cmdline_set(struct bootflow *bflow, const char *value)
+{
+ char *cmdline = NULL;
+
+ if (value) {
+ cmdline = strdup(value);
+ if (!cmdline)
+ return -ENOMEM;
+ }
+
+ free(bflow->cmdline);
+ bflow->cmdline = cmdline;
+
+ return 0;
+}
+
+#ifdef CONFIG_BOOTSTD_FULL
+/**
+ * on_bootargs() - Update the cmdline of a bootflow
+ */
+static int on_bootargs(const char *name, const char *value, enum env_op op,
+ int flags)
+{
+ struct bootstd_priv *std;
+ struct bootflow *bflow;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return 0;
+ bflow = std->cur_bootflow;
+ if (!bflow)
+ return 0;
+
+ switch (op) {
+ case env_op_create:
+ case env_op_overwrite:
+ ret = bootflow_cmdline_set(bflow, value);
+ if (ret && ret != ENOENT)
+ return 1;
+ return 0;
+ case env_op_delete:
+ bootflow_cmdline_set(bflow, NULL);
+ fallthrough;
+ default:
+ return 0;
+ }
+}
+U_BOOT_ENV_CALLBACK(bootargs, on_bootargs);
+#endif
+
+/**
+ * copy_in() - Copy a string into a cmdline buffer
+ *
+ * @buf: Buffer to copy into
+ * @end: End of buffer (pointer to char after the end)
+ * @arg: String to copy from
+ * @len: Number of chars to copy from @arg (note that this is not usually the
+ * sane as strlen(arg) since the string may contain following arguments)
+ * @new_val: Value to put after arg, or BOOTFLOWCL_EMPTY to use an empty value
+ * with no '=' sign
+ * Returns: Number of chars written to @buf
+ */
+static int copy_in(char *buf, char *end, const char *arg, int len,
+ const char *new_val)
+{
+ char *to = buf;
+
+ /* copy the arg name */
+ if (to + len >= end)
+ return -E2BIG;
+ memcpy(to, arg, len);
+ to += len;
+
+ if (new_val == BOOTFLOWCL_EMPTY) {
+ /* no value */
+ } else {
+ bool need_quote = strchr(new_val, ' ');
+ len = strlen(new_val);
+
+ /* need space for value, equals sign and maybe two quotes */
+ if (to + 1 + (need_quote ? 2 : 0) + len >= end)
+ return -E2BIG;
+ *to++ = '=';
+ if (need_quote)
+ *to++ = '"';
+ memcpy(to, new_val, len);
+ to += len;
+ if (need_quote)
+ *to++ = '"';
+ }
+
+ return to - buf;
+}
+
+int cmdline_set_arg(char *buf, int maxlen, const char *cmdline,
+ const char *set_arg, const char *new_val, int *posp)
+{
+ bool found_arg = false;
+ const char *from;
+ char *to, *end;
+ int set_arg_len;
+ char empty = '\0';
+ int ret;
+
+ from = cmdline ?: &empty;
+
+ /* check if the value has quotes inside */
+ if (new_val && new_val != BOOTFLOWCL_EMPTY && strchr(new_val, '"'))
+ return -EBADF;
+
+ set_arg_len = strlen(set_arg);
+ for (to = buf, end = buf + maxlen; *from;) {
+ const char *val, *arg_end, *val_end, *p;
+ bool in_quote;
+
+ if (to >= end)
+ return -E2BIG;
+ while (*from == ' ')
+ from++;
+ if (!*from)
+ break;
+
+ /* find the end of this arg */
+ val = NULL;
+ arg_end = NULL;
+ val_end = NULL;
+ in_quote = false;
+ for (p = from;; p++) {
+ if (in_quote) {
+ if (!*p)
+ return -EINVAL;
+ if (*p == '"')
+ in_quote = false;
+ continue;
+ }
+ if (*p == '=' && !arg_end) {
+ arg_end = p;
+ val = p + 1;
+ } else if (*p == '"') {
+ in_quote = true;
+ } else if (!*p || *p == ' ') {
+ val_end = p;
+ if (!arg_end)
+ arg_end = p;
+ break;
+ }
+ }
+ /*
+ * At this point val_end points to the end of the value, or the
+ * last char after the arg name, if there is no label.
+ * arg_end is the char after the arg name
+ * val points to the value, or NULL if there is none
+ * char after the value.
+ *
+ * fred=1234
+ * ^ ^^ ^
+ * from || |
+ * / \ \
+ * arg_end val val_end
+ */
+ log_debug("from %s arg_end %ld val %ld val_end %ld\n", from,
+ (long)(arg_end - from), (long)(val - from),
+ (long)(val_end - from));
+
+ if (to != buf) {
+ if (to >= end)
+ return -E2BIG;
+ *to++ = ' ';
+ }
+
+ /* if this is the target arg, update it */
+ if (arg_end - from == set_arg_len &&
+ !strncmp(from, set_arg, set_arg_len)) {
+ if (!buf) {
+ bool has_quote = val_end[-1] == '"';
+
+ /*
+ * exclude any start/end quotes from
+ * calculations
+ */
+ if (!val)
+ val = val_end;
+ *posp = val - cmdline + has_quote;
+ return val_end - val - 2 * has_quote;
+ }
+ found_arg = true;
+ if (!new_val) {
+ /* delete this arg */
+ from = val_end + (*val_end == ' ');
+ log_debug("delete from: %s\n", from);
+ if (to != buf)
+ to--; /* drop the space we added */
+ continue;
+ }
+
+ ret = copy_in(to, end, from, arg_end - from, new_val);
+ if (ret < 0)
+ return ret;
+ to += ret;
+
+ /* if not the target arg, copy it unchanged */
+ } else if (to) {
+ int len;
+
+ len = val_end - from;
+ if (to + len >= end)
+ return -E2BIG;
+ memcpy(to, from, len);
+ to += len;
+ }
+ from = val_end;
+ }
+
+ /* If we didn't find the arg, add it */
+ if (!found_arg) {
+ /* trying to delete something that is not there */
+ if (!new_val || !buf)
+ return -ENOENT;
+ if (to >= end)
+ return -E2BIG;
+
+ /* add a space to separate it from the previous arg */
+ if (to != buf && to[-1] != ' ')
+ *to++ = ' ';
+ ret = copy_in(to, end, set_arg, set_arg_len, new_val);
+ log_debug("ret=%d, to: %s buf: %s\n", ret, to, buf);
+ if (ret < 0)
+ return ret;
+ to += ret;
+ }
+
+ /* delete any trailing space */
+ if (to > buf && to[-1] == ' ')
+ to--;
+
+ if (to >= end)
+ return -E2BIG;
+ *to++ = '\0';
+
+ return to - buf;
+}
+
+int bootflow_cmdline_set_arg(struct bootflow *bflow, const char *set_arg,
+ const char *new_val, bool set_env)
+{
+ char buf[2048];
+ char *cmd = NULL;
+ int ret;
+
+ ret = cmdline_set_arg(buf, sizeof(buf), bflow->cmdline, set_arg,
+ new_val, NULL);
+ if (ret < 0)
+ return ret;
+
+ ret = bootflow_cmdline_set(bflow, buf);
+ if (*buf) {
+ cmd = strdup(buf);
+ if (!cmd)
+ return -ENOMEM;
+ }
+ free(bflow->cmdline);
+ bflow->cmdline = cmd;
+
+ if (set_env) {
+ ret = env_set("bootargs", bflow->cmdline);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int cmdline_get_arg(const char *cmdline, const char *arg, int *posp)
+{
+ int ret;
+
+ ret = cmdline_set_arg(NULL, 1, cmdline, arg, NULL, posp);
+
+ return ret;
+}
+
+int bootflow_cmdline_get_arg(struct bootflow *bflow, const char *arg,
+ const char **val)
+{
+ int ret;
+ int pos;
+
+ ret = cmdline_get_arg(bflow->cmdline, arg, &pos);
+ if (ret < 0)
+ return ret;
+ *val = bflow->cmdline + pos;
+
+ return ret;
+}
+
+int bootflow_cmdline_auto(struct bootflow *bflow, const char *arg)
+{
+ struct serial_device_info info;
+ char buf[50];
+ int ret;
+
+ ret = serial_getinfo(gd->cur_serial_dev, &info);
+ if (ret)
+ return ret;
+
+ *buf = '\0';
+ if (!strcmp("earlycon", arg) && info.type == SERIAL_CHIP_16550_COMPATIBLE) {
+ snprintf(buf, sizeof(buf),
+ "uart8250,%s,%#lx,%dn8",
+ info.addr_space == SERIAL_ADDRESS_SPACE_IO ? "io" :
+ "mmio", info.addr, info.baudrate);
+ } else if (!strcmp("earlycon", arg) && info.type == SERIAL_CHIP_PL01X) {
+ snprintf(buf, sizeof(buf),
+ "pl011,mmio32,%#lx,%dn8", info.addr,
+ info.baudrate);
+ } else if (!strcmp("console", arg) && info.type == SERIAL_CHIP_16550_COMPATIBLE) {
+ snprintf(buf, sizeof(buf),
+ "ttyS0,%dn8", info.baudrate);
+ }
+
+ if (!*buf) {
+ printf("Unknown param '%s'\n", arg);
+ return -ENOENT;
+ }
+
+ ret = bootflow_cmdline_set_arg(bflow, arg, buf, true);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+const char *bootflow_img_type_name(enum bootflow_img_t type)
+{
+ const char *name;
+
+ if (type >= BFI_FIRST && type < BFI_COUNT)
+ name = bootflow_img[type - BFI_FIRST];
+ else
+ name = genimg_get_type_short_name(type);
+
+ return name;
+}
+
+struct bootflow_img *bootflow_img_add(struct bootflow *bflow, const char *fname,
+ enum bootflow_img_t type, ulong addr,
+ ulong size)
+{
+ struct bootflow_img img, *ptr;
+
+ memset(&img, '\0', sizeof(struct bootflow_img));
+ img.fname = strdup(fname);
+ if (!img.fname)
+ return NULL;
+
+ img.type = type;
+ img.addr = addr;
+ img.size = size;
+ ptr = alist_add(&bflow->images, img);
+ if (!ptr)
+ return NULL;
+
+ return ptr;
+}
+
+int bootflow_get_seq(const struct bootflow *bflow)
+{
+ struct bootstd_priv *std;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return ret;
+
+ return alist_calc_index(&std->bootflows, bflow);
+}
diff --git a/boot/bootflow_internal.h b/boot/bootflow_internal.h
new file mode 100644
index 00000000000..4cdb6966a7b
--- /dev/null
+++ b/boot/bootflow_internal.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Internal header file for bootflow
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __BOOTFLOW_INTERNAL_H
+#define __BOOTFLOW_INTERNAL_H
+
+/* expo IDs for elements of the bootflow menu */
+enum {
+ START,
+
+ /* strings */
+ STR_PROMPT1A,
+ STR_PROMPT1B,
+ STR_PROMPT2,
+ STR_AUTOBOOT,
+ STR_MENU_TITLE,
+ STR_POINTER,
+
+ /* scene */
+ MAIN,
+
+ /* objects */
+ OBJ_U_BOOT_LOGO,
+ OBJ_BOX,
+ OBJ_MENU,
+ OBJ_PROMPT1A,
+ OBJ_PROMPT1B,
+ OBJ_PROMPT2,
+ OBJ_MENU_TITLE,
+ OBJ_POINTER,
+ OBJ_AUTOBOOT,
+
+ /* strings for menu items */
+ STR_LABEL = 100,
+ STR_DESC = 200,
+ STR_KEY = 300,
+
+ /* menu items / components (bootflow number is added to these) */
+ ITEM = 400,
+ ITEM_LABEL = 500,
+ ITEM_DESC = 600,
+ ITEM_KEY = 700,
+ ITEM_PREVIEW = 800,
+
+ /* left margin for the main menu */
+ MARGIN_LEFT = 100,
+};
+
+#endif /* __BOOTFLOW_INTERNAL_H */
diff --git a/boot/bootflow_menu.c b/boot/bootflow_menu.c
new file mode 100644
index 00000000000..4a442e16850
--- /dev/null
+++ b/boot/bootflow_menu.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Provide a menu of available bootflows and related options
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <bootstd.h>
+#include <cli.h>
+#include <dm.h>
+#include <expo.h>
+#include <malloc.h>
+#include <menu.h>
+#include <video_console.h>
+#include <watchdog.h>
+#include <linux/delay.h>
+#include "bootflow_internal.h"
+
+/**
+ * struct menu_priv - information about the menu
+ *
+ * @num_bootflows: Number of bootflows in the menu
+ * @last_bootdev: bootdev of the last bootflow added to the menu, NULL if none
+ */
+struct menu_priv {
+ int num_bootflows;
+ struct udevice *last_bootdev;
+};
+
+int bootflow_menu_new(struct expo **expp)
+{
+ struct scene_obj_menu *menu;
+ struct menu_priv *priv;
+ struct scene *scn;
+ struct expo *exp;
+ bool use_font;
+ void *logo;
+ int ret;
+
+ priv = calloc(1, sizeof(*priv));
+ if (!priv)
+ return log_msg_ret("prv", -ENOMEM);
+
+ ret = expo_new("bootflows", priv, &exp);
+ if (ret)
+ return log_msg_ret("exp", ret);
+
+ ret = scene_new(exp, "main", MAIN, &scn);
+ if (ret < 0)
+ return log_msg_ret("scn", ret);
+
+ ret = scene_box(scn, "box", OBJ_BOX, 2, NULL);
+ if (ret < 0)
+ return log_msg_ret("bmb", ret);
+ ret |= scene_obj_set_bbox(scn, OBJ_BOX, 30, 90, 1366 - 30, 720);
+
+ ret = scene_menu(scn, "main", OBJ_MENU, &menu);
+ ret |= scene_obj_set_pos(scn, OBJ_MENU, MARGIN_LEFT, 100);
+ ret |= scene_txt_str(scn, "title", OBJ_MENU_TITLE, STR_MENU_TITLE,
+ "U-Boot - Boot Menu", NULL);
+ ret |= scene_obj_set_bbox(scn, OBJ_MENU_TITLE, 0, 32,
+ SCENEOB_DISPLAY_MAX, 30);
+ ret |= scene_obj_set_halign(scn, OBJ_MENU_TITLE, SCENEOA_CENTRE);
+
+ logo = video_get_u_boot_logo();
+ if (logo) {
+ ret |= scene_img(scn, "ulogo", OBJ_U_BOOT_LOGO, logo, NULL);
+ ret |= scene_obj_set_pos(scn, OBJ_U_BOOT_LOGO, 1165, 100);
+ }
+
+ ret |= scene_txt_str(scn, "prompt1a", OBJ_PROMPT1A, STR_PROMPT1A,
+ "Use the \x18 and \x19 keys to select which entry is highlighted.",
+ NULL);
+ ret |= scene_txt_str(scn, "prompt1b", OBJ_PROMPT1B, STR_PROMPT1B,
+ "Use the UP and DOWN keys to select which entry is highlighted.",
+ NULL);
+ ret |= scene_txt_str(scn, "prompt2", OBJ_PROMPT2, STR_PROMPT2,
+ "Press enter to boot the selected OS, 'e' to edit the commands "
+ "before booting or 'c' for a command-line. ESC to return to "
+ "previous menu", NULL);
+ ret |= scene_txt_str(scn, "autoboot", OBJ_AUTOBOOT, STR_AUTOBOOT,
+ "The highlighted entry will be executed automatically in %ds.",
+ NULL);
+ ret |= scene_obj_set_bbox(scn, OBJ_PROMPT1A, 0, 590,
+ SCENEOB_DISPLAY_MAX, 30);
+ ret |= scene_obj_set_bbox(scn, OBJ_PROMPT1B, 0, 620,
+ SCENEOB_DISPLAY_MAX, 30);
+ ret |= scene_obj_set_bbox(scn, OBJ_PROMPT2, 100, 650,
+ 1366 - 100, 700);
+ ret |= scene_obj_set_bbox(scn, OBJ_AUTOBOOT, 0, 720,
+ SCENEOB_DISPLAY_MAX, 750);
+ ret |= scene_obj_set_halign(scn, OBJ_PROMPT1A, SCENEOA_CENTRE);
+ ret |= scene_obj_set_halign(scn, OBJ_PROMPT1B, SCENEOA_CENTRE);
+ ret |= scene_obj_set_halign(scn, OBJ_PROMPT2, SCENEOA_CENTRE);
+ ret |= scene_obj_set_valign(scn, OBJ_PROMPT2, SCENEOA_CENTRE);
+ ret |= scene_obj_set_halign(scn, OBJ_AUTOBOOT, SCENEOA_CENTRE);
+
+ use_font = IS_ENABLED(CONFIG_CONSOLE_TRUETYPE);
+ scene_obj_set_hide(scn, OBJ_PROMPT1A, use_font);
+ scene_obj_set_hide(scn, OBJ_PROMPT1B, !use_font);
+ scene_obj_set_hide(scn, OBJ_AUTOBOOT, use_font);
+
+ ret |= scene_txt_str(scn, "cur_item", OBJ_POINTER, STR_POINTER, ">",
+ NULL);
+ ret |= scene_menu_set_pointer(scn, OBJ_MENU, OBJ_POINTER);
+ if (ret < 0)
+ return log_msg_ret("new", -EINVAL);
+
+ exp->show_highlight = true;
+
+ *expp = exp;
+
+ return 0;
+}
+
+int bootflow_menu_add(struct expo *exp, struct bootflow *bflow, int seq,
+ struct scene **scnp)
+{
+ struct menu_priv *priv = exp->priv;
+ char str[2], *label, *key;
+ struct udevice *media;
+ struct scene *scn;
+ const char *name;
+ uint preview_id;
+ uint scene_id;
+ bool add_gap;
+ int ret;
+
+ ret = expo_first_scene_id(exp);
+ if (ret < 0)
+ return log_msg_ret("scn", ret);
+ scene_id = ret;
+ scn = expo_lookup_scene_id(exp, scene_id);
+
+ *str = seq < 10 ? '0' + seq : 'A' + seq - 10;
+ str[1] = '\0';
+ key = strdup(str);
+ if (!key)
+ return log_msg_ret("key", -ENOMEM);
+
+ media = dev_get_parent(bflow->dev);
+ if (device_get_uclass_id(media) == UCLASS_MASS_STORAGE)
+ name = "usb";
+ else
+ name = media->name;
+ label = strdup(name);
+
+ if (!label) {
+ free(key);
+ return log_msg_ret("nam", -ENOMEM);
+ }
+
+ add_gap = priv->last_bootdev != bflow->dev;
+
+ /* disable this gap for now, since it looks a little ugly */
+ add_gap = false;
+ priv->last_bootdev = bflow->dev;
+
+ ret = expo_str(exp, "prompt", STR_POINTER, ">");
+ ret |= scene_txt_str(scn, "label", ITEM_LABEL + seq,
+ STR_LABEL + seq, label, NULL);
+ ret |= scene_txt_str(scn, "desc", ITEM_DESC + seq, STR_DESC + seq,
+ bflow->os_name ? bflow->os_name :
+ bflow->name, NULL);
+ ret |= scene_txt_str(scn, "key", ITEM_KEY + seq, STR_KEY + seq, key,
+ NULL);
+ preview_id = 0;
+ if (bflow->logo) {
+ preview_id = ITEM_PREVIEW + seq;
+ ret |= scene_img(scn, "preview", preview_id,
+ bflow->logo, NULL);
+ }
+ ret |= scene_menuitem(scn, OBJ_MENU, "item", ITEM + seq,
+ ITEM_KEY + seq, ITEM_LABEL + seq,
+ ITEM_DESC + seq, preview_id,
+ add_gap ? SCENEMIF_GAP_BEFORE : 0,
+ NULL);
+
+ if (ret < 0)
+ return log_msg_ret("itm", -EINVAL);
+ priv->num_bootflows++;
+ *scnp = scn;
+
+ return 0;
+}
+
+int bootflow_menu_add_all(struct expo *exp)
+{
+ struct bootflow *bflow;
+ struct scene *scn;
+ int ret, i;
+
+ for (ret = bootflow_first_glob(&bflow), i = 0; !ret && i < 36;
+ ret = bootflow_next_glob(&bflow), i++) {
+ struct bootmeth_uc_plat *ucp;
+
+ if (bflow->state != BOOTFLOWST_READY)
+ continue;
+
+ /* No media to show for BOOTMETHF_GLOBAL bootmeths */
+ ucp = dev_get_uclass_plat(bflow->method);
+ if (ucp->flags & BOOTMETHF_GLOBAL)
+ continue;
+
+ ret = bootflow_menu_add(exp, bflow, i, &scn);
+ if (ret)
+ return log_msg_ret("bao", ret);
+ }
+
+ return 0;
+}
+
+int bootflow_menu_setup(struct bootstd_priv *std, bool text_mode,
+ struct expo **expp)
+{
+ struct udevice *dev;
+ struct expo *exp;
+ int ret;
+
+ ret = bootflow_menu_new(&exp);
+ if (ret)
+ return log_msg_ret("bmn", ret);
+
+ /* For now we only support a video console */
+ ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
+ if (ret)
+ return log_msg_ret("vid", ret);
+ ret = expo_set_display(exp, dev);
+ if (ret)
+ return log_msg_ret("dis", ret);
+
+ ret = expo_set_scene_id(exp, MAIN);
+ if (ret)
+ return log_msg_ret("scn", ret);
+
+ if (text_mode)
+ expo_set_text_mode(exp, text_mode);
+
+ *expp = exp;
+
+ return 0;
+}
+
+int bootflow_menu_start(struct bootstd_priv *std, bool text_mode,
+ struct expo **expp)
+{
+ struct scene *scn;
+ struct expo *exp;
+ uint scene_id;
+ int ret;
+
+ ret = bootflow_menu_setup(std, text_mode, &exp);
+ if (ret)
+ return log_msg_ret("bmd", ret);
+
+ ret = bootflow_menu_add_all(exp);
+ if (ret)
+ return log_msg_ret("bma", ret);
+
+ if (ofnode_valid(std->theme)) {
+ ret = expo_apply_theme(exp, std->theme);
+ if (ret)
+ return log_msg_ret("thm", ret);
+ }
+
+ ret = expo_calc_dims(exp);
+ if (ret)
+ return log_msg_ret("bmd", ret);
+
+ ret = expo_first_scene_id(exp);
+ if (ret < 0)
+ return log_msg_ret("scn", ret);
+ scene_id = ret;
+ scn = expo_lookup_scene_id(exp, scene_id);
+
+ scene_set_highlight_id(scn, OBJ_MENU);
+
+ ret = scene_arrange(scn);
+ if (ret)
+ return log_msg_ret("arr", ret);
+
+ *expp = exp;
+
+ return 0;
+}
+
+int bootflow_menu_poll(struct expo *exp, int *seqp)
+{
+ struct bootflow *sel_bflow;
+ struct expo_action act;
+ struct scene *scn;
+ int item, ret;
+
+ sel_bflow = NULL;
+
+ scn = expo_lookup_scene_id(exp, exp->scene_id);
+
+ item = scene_menu_get_cur_item(scn, OBJ_MENU);
+ *seqp = item > 0 ? item - ITEM : -1;
+
+ ret = expo_poll(exp, &act);
+ if (ret)
+ return log_msg_ret("bmp", ret);
+
+ switch (act.type) {
+ case EXPOACT_SELECT:
+ *seqp = act.select.id - ITEM;
+ break;
+ case EXPOACT_POINT_ITEM: {
+ struct scene *scn = expo_lookup_scene_id(exp, MAIN);
+
+ if (!scn)
+ return log_msg_ret("bms", -ENOENT);
+ ret = scene_menu_select_item(scn, OBJ_MENU, act.select.id);
+ if (ret)
+ return log_msg_ret("bmp", ret);
+ return -ERESTART;
+ }
+ case EXPOACT_QUIT:
+ return -EPIPE;
+ default:
+ return -EAGAIN;
+ }
+
+ return 0;
+}
diff --git a/boot/bootm.c b/boot/bootm.c
new file mode 100644
index 00000000000..4bdca22ea8c
--- /dev/null
+++ b/boot/bootm.c
@@ -0,0 +1,1269 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2000-2009
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ */
+
+#ifndef USE_HOSTCC
+#include <bootm.h>
+#include <bootstage.h>
+#include <cli.h>
+#include <command.h>
+#include <cpu_func.h>
+#include <env.h>
+#include <errno.h>
+#include <fdt_support.h>
+#include <irq_func.h>
+#include <lmb.h>
+#include <log.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <net.h>
+#include <asm/cache.h>
+#include <asm/global_data.h>
+#include <asm/io.h>
+#include <linux/sizes.h>
+#include <tpm-v2.h>
+#include <tpm_tcg2.h>
+#if defined(CONFIG_CMD_USB)
+#include <usb.h>
+#endif
+#else
+#include "mkimage.h"
+#endif
+
+#include <bootm.h>
+#include <image.h>
+#include <u-boot/zlib.h>
+
+#define MAX_CMDLINE_SIZE SZ_4K
+
+#define IH_INITRD_ARCH IH_ARCH_DEFAULT
+
+#ifndef USE_HOSTCC
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct bootm_headers images; /* pointers to os/initrd/fdt images */
+
+__weak void board_quiesce_devices(void)
+{
+}
+
+#if CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT)
+/**
+ * image_get_kernel - verify legacy format kernel image
+ * @img_addr: in RAM address of the legacy format image to be verified
+ * @verify: data CRC verification flag
+ *
+ * image_get_kernel() verifies legacy image integrity and returns pointer to
+ * legacy image header if image verification was completed successfully.
+ *
+ * returns:
+ * pointer to a legacy image header if valid image was found
+ * otherwise return NULL
+ */
+static struct legacy_img_hdr *image_get_kernel(ulong img_addr, int verify)
+{
+ struct legacy_img_hdr *hdr = (struct legacy_img_hdr *)img_addr;
+
+ if (!image_check_magic(hdr)) {
+ puts("Bad Magic Number\n");
+ bootstage_error(BOOTSTAGE_ID_CHECK_MAGIC);
+ return NULL;
+ }
+ bootstage_mark(BOOTSTAGE_ID_CHECK_HEADER);
+
+ if (!image_check_hcrc(hdr)) {
+ puts("Bad Header Checksum\n");
+ bootstage_error(BOOTSTAGE_ID_CHECK_HEADER);
+ return NULL;
+ }
+
+ bootstage_mark(BOOTSTAGE_ID_CHECK_CHECKSUM);
+ image_print_contents(hdr);
+
+ if (verify) {
+ puts(" Verifying Checksum ... ");
+ if (!image_check_dcrc(hdr)) {
+ printf("Bad Data CRC\n");
+ bootstage_error(BOOTSTAGE_ID_CHECK_CHECKSUM);
+ return NULL;
+ }
+ puts("OK\n");
+ }
+ bootstage_mark(BOOTSTAGE_ID_CHECK_ARCH);
+
+ if (!image_check_target_arch(hdr)) {
+ printf("Unsupported Architecture 0x%x\n", image_get_arch(hdr));
+ bootstage_error(BOOTSTAGE_ID_CHECK_ARCH);
+ return NULL;
+ }
+ return hdr;
+}
+#endif
+
+/**
+ * boot_get_kernel() - find kernel image
+ *
+ * @addr_fit: first argument to bootm: address, fit configuration, etc.
+ * @os_data: pointer to a ulong variable, will hold os data start address
+ * @os_len: pointer to a ulong variable, will hold os data length
+ * address and length, otherwise NULL
+ * pointer to image header if valid image was found, plus kernel start
+ * @kernp: image header if valid image was found, otherwise NULL
+ *
+ * boot_get_kernel() tries to find a kernel image, verifies its integrity
+ * and locates kernel data.
+ *
+ * Return: 0 on success, -ve on error. -EPROTOTYPE means that the image is in
+ * a wrong or unsupported format
+ */
+static int boot_get_kernel(const char *addr_fit, struct bootm_headers *images,
+ ulong *os_data, ulong *os_len, const void **kernp)
+{
+#if CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT)
+ struct legacy_img_hdr *hdr;
+#endif
+ ulong img_addr;
+ const void *buf;
+ const char *fit_uname_config = NULL, *fit_uname_kernel = NULL;
+#if CONFIG_IS_ENABLED(FIT)
+ int os_noffset;
+#endif
+
+#ifdef CONFIG_ANDROID_BOOT_IMAGE
+ const void *boot_img;
+ const void *vendor_boot_img;
+#endif
+ img_addr = genimg_get_kernel_addr_fit(addr_fit, &fit_uname_config,
+ &fit_uname_kernel);
+
+ if (IS_ENABLED(CONFIG_CMD_BOOTM_PRE_LOAD))
+ img_addr += image_load_offset;
+
+ bootstage_mark(BOOTSTAGE_ID_CHECK_MAGIC);
+
+ /* check image type, for FIT images get FIT kernel node */
+ *os_data = *os_len = 0;
+ buf = map_sysmem(img_addr, 0);
+ switch (genimg_get_format(buf)) {
+#if CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT)
+ case IMAGE_FORMAT_LEGACY:
+ printf("## Booting kernel from Legacy Image at %08lx ...\n",
+ img_addr);
+ hdr = image_get_kernel(img_addr, images->verify);
+ if (!hdr)
+ return -EINVAL;
+ bootstage_mark(BOOTSTAGE_ID_CHECK_IMAGETYPE);
+
+ /* get os_data and os_len */
+ switch (image_get_type(hdr)) {
+ case IH_TYPE_KERNEL:
+ case IH_TYPE_KERNEL_NOLOAD:
+ *os_data = image_get_data(hdr);
+ *os_len = image_get_data_size(hdr);
+ break;
+ case IH_TYPE_MULTI:
+ image_multi_getimg(hdr, 0, os_data, os_len);
+ break;
+ case IH_TYPE_STANDALONE:
+ *os_data = image_get_data(hdr);
+ *os_len = image_get_data_size(hdr);
+ break;
+ default:
+ bootstage_error(BOOTSTAGE_ID_CHECK_IMAGETYPE);
+ return -EPROTOTYPE;
+ }
+
+ /*
+ * copy image header to allow for image overwrites during
+ * kernel decompression.
+ */
+ memmove(&images->legacy_hdr_os_copy, hdr,
+ sizeof(struct legacy_img_hdr));
+
+ /* save pointer to image header */
+ images->legacy_hdr_os = hdr;
+
+ images->legacy_hdr_valid = 1;
+ bootstage_mark(BOOTSTAGE_ID_DECOMP_IMAGE);
+ break;
+#endif
+#if CONFIG_IS_ENABLED(FIT)
+ case IMAGE_FORMAT_FIT:
+ os_noffset = fit_image_load(images, img_addr,
+ &fit_uname_kernel, &fit_uname_config,
+ IH_ARCH_DEFAULT, IH_TYPE_KERNEL,
+ BOOTSTAGE_ID_FIT_KERNEL_START,
+ FIT_LOAD_IGNORED, os_data, os_len);
+ if (os_noffset < 0)
+ return -ENOENT;
+
+ images->fit_hdr_os = map_sysmem(img_addr, 0);
+ images->fit_uname_os = fit_uname_kernel;
+ images->fit_uname_cfg = fit_uname_config;
+ images->fit_noffset_os = os_noffset;
+ break;
+#endif
+#ifdef CONFIG_ANDROID_BOOT_IMAGE
+ case IMAGE_FORMAT_ANDROID: {
+ int ret;
+
+ boot_img = buf;
+ vendor_boot_img = NULL;
+ if (IS_ENABLED(CONFIG_CMD_ABOOTIMG)) {
+ boot_img = map_sysmem(get_abootimg_addr(), 0);
+ vendor_boot_img = map_sysmem(get_avendor_bootimg_addr(), 0);
+ }
+ printf("## Booting Android Image at 0x%08lx ...\n", img_addr);
+ ret = android_image_get_kernel(boot_img, vendor_boot_img,
+ images->verify, os_data, os_len);
+ if (IS_ENABLED(CONFIG_CMD_ABOOTIMG)) {
+ unmap_sysmem(vendor_boot_img);
+ unmap_sysmem(boot_img);
+ }
+ if (ret)
+ return ret;
+ break;
+ }
+#endif
+ default:
+ bootstage_error(BOOTSTAGE_ID_CHECK_IMAGETYPE);
+ return -EPROTOTYPE;
+ }
+
+ debug(" kernel data at 0x%08lx, len = 0x%08lx (%ld)\n",
+ *os_data, *os_len, *os_len);
+ *kernp = buf;
+
+ return 0;
+}
+
+static int bootm_start(void)
+{
+ memset((void *)&images, 0, sizeof(images));
+ images.verify = env_get_yesno("verify");
+
+ bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");
+ images.state = BOOTM_STATE_START;
+
+ return 0;
+}
+
+static ulong bootm_data_addr(const char *addr_str)
+{
+ ulong addr;
+
+ if (addr_str)
+ addr = hextoul(addr_str, NULL);
+ else
+ addr = image_load_addr;
+
+ return addr;
+}
+
+/**
+ * bootm_pre_load() - Handle the pre-load processing
+ *
+ * This can be used to do a full signature check of the image, for example.
+ * It calls image_pre_load() with the data address of the image to check.
+ *
+ * @addr_str: String containing load address in hex, or NULL to use
+ * image_load_addr
+ * Return: 0 if OK, CMD_RET_FAILURE on failure
+ */
+static int bootm_pre_load(const char *addr_str)
+{
+ ulong data_addr = bootm_data_addr(addr_str);
+ int ret = 0;
+
+ if (IS_ENABLED(CONFIG_CMD_BOOTM_PRE_LOAD))
+ ret = image_pre_load(data_addr);
+
+ if (ret)
+ ret = CMD_RET_FAILURE;
+
+ return ret;
+}
+
+/**
+ * bootm_find_os(): Find the OS to boot
+ *
+ * @cmd_name: Command name that started this boot, e.g. "bootm"
+ * @addr_fit: Address and/or FIT specifier (first arg of bootm command)
+ * Return: 0 on success, -ve on error
+ */
+static int bootm_find_os(const char *cmd_name, const char *addr_fit)
+{
+ const void *os_hdr;
+#ifdef CONFIG_ANDROID_BOOT_IMAGE
+ const void *vendor_boot_img;
+ const void *boot_img;
+#endif
+ bool ep_found = false;
+ int ret;
+
+ /* get kernel image header, start address and length */
+ ret = boot_get_kernel(addr_fit, &images, &images.os.image_start,
+ &images.os.image_len, &os_hdr);
+ if (ret) {
+ if (ret == -EPROTOTYPE)
+ printf("Wrong Image Type for %s command\n", cmd_name);
+
+ printf("ERROR %dE: can't get kernel image!\n", ret);
+ return 1;
+ }
+
+ /* get image parameters */
+ switch (genimg_get_format(os_hdr)) {
+#if CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT)
+ case IMAGE_FORMAT_LEGACY:
+ images.os.type = image_get_type(os_hdr);
+ images.os.comp = image_get_comp(os_hdr);
+ images.os.os = image_get_os(os_hdr);
+
+ images.os.end = image_get_image_end(os_hdr);
+ images.os.load = image_get_load(os_hdr);
+ images.os.arch = image_get_arch(os_hdr);
+ break;
+#endif
+#if CONFIG_IS_ENABLED(FIT)
+ case IMAGE_FORMAT_FIT:
+ if (fit_image_get_type(images.fit_hdr_os,
+ images.fit_noffset_os,
+ &images.os.type)) {
+ puts("Can't get image type!\n");
+ bootstage_error(BOOTSTAGE_ID_FIT_TYPE);
+ return 1;
+ }
+
+ if (fit_image_get_comp(images.fit_hdr_os,
+ images.fit_noffset_os,
+ &images.os.comp)) {
+ puts("Can't get image compression!\n");
+ bootstage_error(BOOTSTAGE_ID_FIT_COMPRESSION);
+ return 1;
+ }
+
+ if (fit_image_get_os(images.fit_hdr_os, images.fit_noffset_os,
+ &images.os.os)) {
+ puts("Can't get image OS!\n");
+ bootstage_error(BOOTSTAGE_ID_FIT_OS);
+ return 1;
+ }
+
+ if (fit_image_get_arch(images.fit_hdr_os,
+ images.fit_noffset_os,
+ &images.os.arch)) {
+ puts("Can't get image ARCH!\n");
+ return 1;
+ }
+
+ images.os.end = fit_get_end(images.fit_hdr_os);
+
+ if (fit_image_get_load(images.fit_hdr_os, images.fit_noffset_os,
+ &images.os.load)) {
+ puts("Can't get image load address!\n");
+ bootstage_error(BOOTSTAGE_ID_FIT_LOADADDR);
+ return 1;
+ }
+ break;
+#endif
+#ifdef CONFIG_ANDROID_BOOT_IMAGE
+ case IMAGE_FORMAT_ANDROID:
+ boot_img = os_hdr;
+ vendor_boot_img = NULL;
+ if (IS_ENABLED(CONFIG_CMD_ABOOTIMG)) {
+ boot_img = map_sysmem(get_abootimg_addr(), 0);
+ vendor_boot_img = map_sysmem(get_avendor_bootimg_addr(), 0);
+ }
+ images.os.type = IH_TYPE_KERNEL;
+ images.os.comp = android_image_get_kcomp(boot_img, vendor_boot_img);
+ images.os.os = IH_OS_LINUX;
+ images.os.end = android_image_get_end(boot_img, vendor_boot_img);
+ images.os.load = android_image_get_kload(boot_img, vendor_boot_img);
+ images.ep = images.os.load;
+ ep_found = true;
+ if (IS_ENABLED(CONFIG_CMD_ABOOTIMG)) {
+ unmap_sysmem(vendor_boot_img);
+ unmap_sysmem(boot_img);
+ }
+ break;
+#endif
+ default:
+ puts("ERROR: unknown image format type!\n");
+ return 1;
+ }
+
+ /* If we have a valid setup.bin, we will use that for entry (x86) */
+ if (images.os.arch == IH_ARCH_I386 ||
+ images.os.arch == IH_ARCH_X86_64) {
+ ulong len;
+
+ ret = boot_get_setup(&images, IH_ARCH_I386, &images.ep, &len);
+ if (ret < 0 && ret != -ENOENT) {
+ puts("Could not find a valid setup.bin for x86\n");
+ return 1;
+ }
+ /* Kernel entry point is the setup.bin */
+ } else if (images.legacy_hdr_valid) {
+ images.ep = image_get_ep(&images.legacy_hdr_os_copy);
+#if CONFIG_IS_ENABLED(FIT)
+ } else if (images.fit_uname_os) {
+ int ret;
+
+ ret = fit_image_get_entry(images.fit_hdr_os,
+ images.fit_noffset_os, &images.ep);
+ if (ret) {
+ puts("Can't get entry point property!\n");
+ return 1;
+ }
+#endif
+ } else if (!ep_found) {
+ puts("Could not find kernel entry point!\n");
+ return 1;
+ }
+
+ if (images.os.type == IH_TYPE_KERNEL_NOLOAD) {
+ images.os.load = images.os.image_start;
+ images.ep += images.os.image_start;
+ }
+
+ images.os.start = map_to_sysmem(os_hdr);
+
+ return 0;
+}
+
+/**
+ * check_overlap() - Check if an image overlaps the OS
+ *
+ * @name: Name of image to check (used to print error)
+ * @base: Base address of image
+ * @end: End address of image (+1)
+ * @os_start: Start of OS
+ * @os_size: Size of OS in bytes
+ * Return: 0 if OK, -EXDEV if the image overlaps the OS
+ */
+static int check_overlap(const char *name, ulong base, ulong end,
+ ulong os_start, ulong os_size)
+{
+ ulong os_end;
+
+ if (!base)
+ return 0;
+ os_end = os_start + os_size;
+
+ if ((base >= os_start && base < os_end) ||
+ (end > os_start && end <= os_end) ||
+ (base < os_start && end >= os_end)) {
+ printf("ERROR: %s image overlaps OS image (OS=%lx..%lx)\n",
+ name, os_start, os_end);
+
+ return -EXDEV;
+ }
+
+ return 0;
+}
+
+int bootm_find_images(ulong img_addr, const char *conf_ramdisk,
+ const char *conf_fdt, ulong start, ulong size)
+{
+ const char *select = conf_ramdisk;
+ char addr_str[17];
+ void *buf;
+ int ret;
+
+ if (IS_ENABLED(CONFIG_ANDROID_BOOT_IMAGE)) {
+ /* Look for an Android boot image */
+ buf = map_sysmem(images.os.start, 0);
+ if (buf && genimg_get_format(buf) == IMAGE_FORMAT_ANDROID) {
+ strcpy(addr_str, simple_xtoa(img_addr));
+ select = addr_str;
+ }
+ }
+
+ if (conf_ramdisk)
+ select = conf_ramdisk;
+
+ /* find ramdisk */
+ ret = boot_get_ramdisk(select, &images, IH_INITRD_ARCH,
+ &images.rd_start, &images.rd_end);
+ if (ret) {
+ puts("Ramdisk image is corrupt or invalid\n");
+ return 1;
+ }
+
+ /* check if ramdisk overlaps OS image */
+ if (check_overlap("RD", images.rd_start, images.rd_end, start, size))
+ return 1;
+
+ if (CONFIG_IS_ENABLED(OF_LIBFDT)) {
+ buf = map_sysmem(img_addr, 0);
+
+ /* find flattened device tree */
+ ret = boot_get_fdt(buf, conf_fdt, IH_ARCH_DEFAULT, &images,
+ &images.ft_addr, &images.ft_len);
+ if (ret) {
+ puts("Could not find a valid device tree\n");
+ return 1;
+ }
+
+ /* check if FDT overlaps OS image */
+ if (check_overlap("FDT", map_to_sysmem(images.ft_addr),
+ images.ft_len, start, size))
+ return 1;
+
+ if (IS_ENABLED(CONFIG_CMD_FDT))
+ set_working_fdt_addr(map_to_sysmem(images.ft_addr));
+ }
+
+#if CONFIG_IS_ENABLED(FIT)
+ if (IS_ENABLED(CONFIG_FPGA)) {
+ /* find bitstreams */
+ ret = boot_get_fpga(&images);
+ if (ret) {
+ printf("FPGA image is corrupted or invalid\n");
+ return 1;
+ }
+ }
+
+ /* find all of the loadables */
+ ret = boot_get_loadable(&images);
+ if (ret) {
+ printf("Loadable(s) is corrupt or invalid\n");
+ return 1;
+ }
+#endif
+
+ return 0;
+}
+
+static int bootm_find_other(ulong img_addr, const char *conf_ramdisk,
+ const char *conf_fdt)
+{
+ if ((images.os.type == IH_TYPE_KERNEL ||
+ images.os.type == IH_TYPE_KERNEL_NOLOAD ||
+ images.os.type == IH_TYPE_MULTI) &&
+ (images.os.os == IH_OS_LINUX || images.os.os == IH_OS_VXWORKS ||
+ images.os.os == IH_OS_EFI || images.os.os == IH_OS_TEE ||
+ images.os.os == IH_OS_ELF)) {
+ return bootm_find_images(img_addr, conf_ramdisk, conf_fdt, 0,
+ 0);
+ }
+
+ return 0;
+}
+#endif /* USE_HOSTC */
+
+#if !defined(USE_HOSTCC) || defined(CONFIG_FIT_SIGNATURE)
+/**
+ * handle_decomp_error() - display a decompression error
+ *
+ * This function tries to produce a useful message. In the case where the
+ * uncompressed size is the same as the available space, we can assume that
+ * the image is too large for the buffer.
+ *
+ * @comp_type: Compression type being used (IH_COMP_...)
+ * @uncomp_size: Number of bytes uncompressed
+ * @buf_size: Number of bytes the decompresion buffer was
+ * @ret: errno error code received from compression library
+ * Return: Appropriate BOOTM_ERR_ error code
+ */
+static int handle_decomp_error(int comp_type, size_t uncomp_size,
+ size_t buf_size, int ret)
+{
+ const char *name = genimg_get_comp_name(comp_type);
+
+ /* ENOSYS means unimplemented compression type, don't reset. */
+ if (ret == -ENOSYS)
+ return BOOTM_ERR_UNIMPLEMENTED;
+
+ if ((comp_type == IH_COMP_GZIP && ret == Z_BUF_ERROR) ||
+ uncomp_size >= buf_size)
+ printf("Image too large: increase CONFIG_SYS_BOOTM_LEN\n");
+ else
+ printf("%s: uncompress error %d\n", name, ret);
+
+ /*
+ * The decompression routines are now safe, so will not write beyond
+ * their bounds. Probably it is not necessary to reset, but maintain
+ * the current behaviour for now.
+ */
+ printf("Must RESET board to recover\n");
+#ifndef USE_HOSTCC
+ bootstage_error(BOOTSTAGE_ID_DECOMP_IMAGE);
+#endif
+
+ return BOOTM_ERR_RESET;
+}
+#endif
+
+#ifndef USE_HOSTCC
+static int bootm_load_os(struct bootm_headers *images, int boot_progress)
+{
+ struct image_info os = images->os;
+ ulong load = os.load;
+ ulong load_end;
+ ulong blob_start = os.start;
+ ulong blob_end = os.end;
+ ulong image_start = os.image_start;
+ ulong image_len = os.image_len;
+ ulong flush_start = ALIGN_DOWN(load, ARCH_DMA_MINALIGN);
+ bool no_overlap;
+ void *load_buf, *image_buf;
+ int err;
+
+ /*
+ * For a "noload" compressed kernel we need to allocate a buffer large
+ * enough to decompress in to and use that as the load address now.
+ * Assume that the kernel compression is at most a factor of 4 since
+ * zstd almost achieves that.
+ * Use an alignment of 2MB since this might help arm64
+ */
+ if (os.type == IH_TYPE_KERNEL_NOLOAD && os.comp != IH_COMP_NONE) {
+ ulong req_size = ALIGN(image_len * 4, SZ_1M);
+ phys_addr_t addr;
+
+ err = lmb_alloc_mem(LMB_MEM_ALLOC_ANY, SZ_2M, &addr,
+ req_size, LMB_NONE);
+ if (err)
+ return 1;
+
+ load = (ulong)addr;
+ os.load = (ulong)addr;
+ images->ep = (ulong)addr;
+ debug("Allocated %lx bytes at %lx for kernel (size %lx) decompression\n",
+ req_size, load, image_len);
+ }
+
+ load_buf = map_sysmem(load, 0);
+ image_buf = map_sysmem(os.image_start, image_len);
+ err = image_decomp(os.comp, load, os.image_start, os.type,
+ load_buf, image_buf, image_len,
+ CONFIG_SYS_BOOTM_LEN, &load_end);
+ if (err) {
+ err = handle_decomp_error(os.comp, load_end - load,
+ CONFIG_SYS_BOOTM_LEN, err);
+ bootstage_error(BOOTSTAGE_ID_DECOMP_IMAGE);
+ return err;
+ }
+ /* We need the decompressed image size in the next steps */
+ images->os.image_len = load_end - load;
+
+ flush_cache(flush_start, ALIGN(load_end, ARCH_DMA_MINALIGN) - flush_start);
+
+ debug(" kernel loaded at 0x%08lx, end = 0x%08lx\n", load, load_end);
+ bootstage_mark(BOOTSTAGE_ID_KERNEL_LOADED);
+
+ no_overlap = (os.comp == IH_COMP_NONE && load == image_start);
+
+ if (!no_overlap && load < blob_end && load_end > blob_start) {
+ debug("images.os.start = 0x%lX, images.os.end = 0x%lx\n",
+ blob_start, blob_end);
+ debug("images.os.load = 0x%lx, load_end = 0x%lx\n", load,
+ load_end);
+
+ /* Check what type of image this is. */
+ if (images->legacy_hdr_valid) {
+ if (image_get_type(&images->legacy_hdr_os_copy)
+ == IH_TYPE_MULTI)
+ puts("WARNING: legacy format multi component image overwritten\n");
+ return BOOTM_ERR_OVERLAP;
+ } else {
+ puts("ERROR: new format image overwritten - must RESET the board to recover\n");
+ bootstage_error(BOOTSTAGE_ID_OVERWRITTEN);
+ return BOOTM_ERR_RESET;
+ }
+ }
+
+ if (IS_ENABLED(CONFIG_CMD_BOOTI) && images->os.arch == IH_ARCH_ARM64 &&
+ images->os.os == IH_OS_LINUX) {
+ ulong relocated_addr;
+ ulong image_size;
+ int ret;
+
+ ret = booti_setup(load, &relocated_addr, &image_size, false);
+ if (ret) {
+ printf("Failed to prep arm64 kernel (err=%d)\n", ret);
+ return BOOTM_ERR_RESET;
+ }
+
+ /* Handle BOOTM_STATE_LOADOS */
+ if (relocated_addr != load) {
+ printf("Moving Image from 0x%lx to 0x%lx, end=0x%lx\n",
+ load, relocated_addr,
+ relocated_addr + image_size);
+ memmove((void *)relocated_addr, load_buf, image_size);
+ }
+
+ images->ep = relocated_addr;
+ images->os.start = relocated_addr;
+ images->os.end = relocated_addr + image_size;
+ }
+
+ if (CONFIG_IS_ENABLED(LMB)) {
+ phys_addr_t load;
+
+ load = (phys_addr_t)images->os.load;
+ err = lmb_alloc_mem(LMB_MEM_ALLOC_ADDR, 0, &load,
+ (load_end - images->os.load), LMB_NONE);
+ if (err) {
+ log_err("Unable to allocate memory %#lx for loading OS\n",
+ images->os.load);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * bootm_disable_interrupts() - Disable interrupts in preparation for load/boot
+ *
+ * Return: interrupt flag (0 if interrupts were disabled, non-zero if they were
+ * enabled)
+ */
+ulong bootm_disable_interrupts(void)
+{
+ ulong iflag;
+
+ /*
+ * We have reached the point of no return: we are going to
+ * overwrite all exception vector code, so we cannot easily
+ * recover from any failures any more...
+ */
+ iflag = disable_interrupts();
+#ifdef CONFIG_NETCONSOLE
+ /* Stop the ethernet stack if NetConsole could have left it up */
+ eth_halt();
+#endif
+
+ return iflag;
+}
+
+#define CONSOLE_ARG "console="
+#define NULL_CONSOLE (CONSOLE_ARG "ttynull")
+#define CONSOLE_ARG_SIZE sizeof(NULL_CONSOLE)
+
+/**
+ * fixup_silent_linux() - Handle silencing the linux boot if required
+ *
+ * This uses the silent_linux envvar to control whether to add/set a "console="
+ * parameter to the command line
+ *
+ * @buf: Buffer containing the string to process
+ * @maxlen: Maximum length of buffer
+ * Return: 0 if OK, -ENOSPC if @maxlen is too small
+ */
+static int fixup_silent_linux(char *buf, int maxlen)
+{
+ int want_silent;
+ char *cmdline;
+ int size;
+
+ /*
+ * Move the input string to the end of buffer. The output string will be
+ * built up at the start.
+ */
+ size = strlen(buf) + 1;
+ if (size * 2 > maxlen)
+ return -ENOSPC;
+ cmdline = buf + maxlen - size;
+ memmove(cmdline, buf, size);
+ /*
+ * Only fix cmdline when requested. The environment variable can be:
+ *
+ * no - we never fixup
+ * yes - we always fixup
+ * unset - we rely on the console silent flag
+ */
+ want_silent = env_get_yesno("silent_linux");
+ if (want_silent == 0)
+ return 0;
+ else if (want_silent == -1 && !(gd->flags & GD_FLG_SILENT))
+ return 0;
+
+ debug("before silent fix-up: %s\n", cmdline);
+ if (*cmdline) {
+ char *start = strstr(cmdline, CONSOLE_ARG);
+
+ /* Check space for maximum possible new command line */
+ if (size + CONSOLE_ARG_SIZE > maxlen)
+ return -ENOSPC;
+
+ if (start) {
+ char *end = strchr(start, ' ');
+ int start_bytes;
+
+ start_bytes = start - cmdline;
+ strncpy(buf, cmdline, start_bytes);
+ strncpy(buf + start_bytes, NULL_CONSOLE, CONSOLE_ARG_SIZE);
+ if (end)
+ strcpy(buf + start_bytes + CONSOLE_ARG_SIZE - 1, end);
+ else
+ buf[start_bytes + CONSOLE_ARG_SIZE] = '\0';
+ } else {
+ sprintf(buf, "%s %s", cmdline, NULL_CONSOLE);
+ }
+ if (buf + strlen(buf) >= cmdline)
+ return -ENOSPC;
+ } else {
+ if (maxlen < CONSOLE_ARG_SIZE)
+ return -ENOSPC;
+ strcpy(buf, NULL_CONSOLE);
+ }
+ debug("after silent fix-up: %s\n", buf);
+
+ return 0;
+}
+
+/**
+ * process_subst() - Handle substitution of ${...} fields in the environment
+ *
+ * Handle variable substitution in the provided buffer
+ *
+ * @buf: Buffer containing the string to process
+ * @maxlen: Maximum length of buffer
+ * Return: 0 if OK, -ENOSPC if @maxlen is too small
+ */
+static int process_subst(char *buf, int maxlen)
+{
+ char *cmdline;
+ int size;
+ int ret;
+
+ /* Move to end of buffer */
+ size = strlen(buf) + 1;
+ cmdline = buf + maxlen - size;
+ if (buf + size > cmdline)
+ return -ENOSPC;
+ memmove(cmdline, buf, size);
+
+ ret = cli_simple_process_macros(cmdline, buf, cmdline - buf);
+
+ return ret;
+}
+
+int bootm_process_cmdline(char *buf, int maxlen, int flags)
+{
+ int ret;
+
+ /* Check config first to enable compiler to eliminate code */
+ if (IS_ENABLED(CONFIG_SILENT_CONSOLE) &&
+ !IS_ENABLED(CONFIG_SILENT_U_BOOT_ONLY) &&
+ (flags & BOOTM_CL_SILENT)) {
+ ret = fixup_silent_linux(buf, maxlen);
+ if (ret)
+ return log_msg_ret("silent", ret);
+ }
+ if (IS_ENABLED(CONFIG_BOOTARGS_SUBST) && IS_ENABLED(CONFIG_CMDLINE) &&
+ (flags & BOOTM_CL_SUBST)) {
+ ret = process_subst(buf, maxlen);
+ if (ret)
+ return log_msg_ret("subst", ret);
+ }
+
+ return 0;
+}
+
+int bootm_process_cmdline_env(int flags)
+{
+ const int maxlen = MAX_CMDLINE_SIZE;
+ bool do_silent;
+ const char *env;
+ char *buf;
+ int ret;
+
+ /* First check if any action is needed */
+ do_silent = IS_ENABLED(CONFIG_SILENT_CONSOLE) &&
+ !IS_ENABLED(CONFIG_SILENT_U_BOOT_ONLY) && (flags & BOOTM_CL_SILENT);
+ if (!do_silent && !IS_ENABLED(CONFIG_BOOTARGS_SUBST))
+ return 0;
+
+ env = env_get("bootargs");
+ if (env && strlen(env) >= maxlen)
+ return -E2BIG;
+ buf = malloc(maxlen);
+ if (!buf)
+ return -ENOMEM;
+ if (env)
+ strcpy(buf, env);
+ else
+ *buf = '\0';
+ ret = bootm_process_cmdline(buf, maxlen, flags);
+ if (!ret) {
+ ret = env_set("bootargs", buf);
+
+ /*
+ * If buf is "" and bootargs does not exist, this will produce
+ * an error trying to delete bootargs. Ignore it
+ */
+ if (ret == -ENOENT)
+ ret = 0;
+ }
+ free(buf);
+ if (ret)
+ return log_msg_ret("env", ret);
+
+ return 0;
+}
+
+int bootm_measure(struct bootm_headers *images)
+{
+ int ret = 0;
+
+ /* Skip measurement if EFI is going to do it */
+ if (images->os.os == IH_OS_EFI &&
+ IS_ENABLED(CONFIG_EFI_TCG2_PROTOCOL) &&
+ IS_ENABLED(CONFIG_BOOTM_EFI))
+ return ret;
+
+ if (IS_ENABLED(CONFIG_MEASURED_BOOT)) {
+ struct tcg2_event_log elog;
+ struct udevice *dev;
+ void *initrd_buf;
+ void *image_buf;
+ const char *s;
+ u32 rd_len;
+ bool ign;
+
+ elog.log_size = 0;
+ ign = IS_ENABLED(CONFIG_MEASURE_IGNORE_LOG);
+ ret = tcg2_measurement_init(&dev, &elog, ign);
+ if (ret)
+ return ret;
+
+ image_buf = map_sysmem(images->os.image_start,
+ images->os.image_len);
+ ret = tcg2_measure_data(dev, &elog, 8, images->os.image_len,
+ image_buf, EV_COMPACT_HASH,
+ strlen("linux") + 1, (u8 *)"linux");
+ if (ret)
+ goto unmap_image;
+
+ rd_len = images->rd_end - images->rd_start;
+ initrd_buf = map_sysmem(images->rd_start, rd_len);
+ ret = tcg2_measure_data(dev, &elog, 9, rd_len, initrd_buf,
+ EV_COMPACT_HASH, strlen("initrd") + 1,
+ (u8 *)"initrd");
+ if (ret)
+ goto unmap_initrd;
+
+ if (IS_ENABLED(CONFIG_MEASURE_DEVICETREE)) {
+ ret = tcg2_measure_data(dev, &elog, 1, images->ft_len,
+ (u8 *)images->ft_addr,
+ EV_TABLE_OF_DEVICES,
+ strlen("dts") + 1,
+ (u8 *)"dts");
+ if (ret)
+ goto unmap_initrd;
+ }
+
+ s = env_get("bootargs");
+ if (!s)
+ s = "";
+ ret = tcg2_measure_data(dev, &elog, 1, strlen(s) + 1, (u8 *)s,
+ EV_PLATFORM_CONFIG_FLAGS,
+ strlen(s) + 1, (u8 *)s);
+
+unmap_initrd:
+ unmap_sysmem(initrd_buf);
+
+unmap_image:
+ unmap_sysmem(image_buf);
+ tcg2_measurement_term(dev, &elog, ret != 0);
+ }
+
+ return ret;
+}
+
+int bootm_run_states(struct bootm_info *bmi, int states)
+{
+ struct bootm_headers *images = bmi->images;
+ boot_os_fn *boot_fn;
+ ulong iflag = 0;
+ int ret = 0, need_boot_fn;
+
+ images->state |= states;
+
+ /*
+ * Work through the states and see how far we get. We stop on
+ * any error.
+ */
+ if (states & BOOTM_STATE_START)
+ ret = bootm_start();
+
+ if (!ret && (states & BOOTM_STATE_PRE_LOAD))
+ ret = bootm_pre_load(bmi->addr_img);
+
+ if (!ret && (states & BOOTM_STATE_FINDOS))
+ ret = bootm_find_os(bmi->cmd_name, bmi->addr_img);
+
+ if (!ret && (states & BOOTM_STATE_FINDOTHER)) {
+ ulong img_addr;
+
+ img_addr = bmi->addr_img ? hextoul(bmi->addr_img, NULL)
+ : image_load_addr;
+ ret = bootm_find_other(img_addr, bmi->conf_ramdisk,
+ bmi->conf_fdt);
+ }
+
+ if (IS_ENABLED(CONFIG_MEASURED_BOOT) && !ret &&
+ (states & BOOTM_STATE_MEASURE))
+ bootm_measure(images);
+
+ /* Load the OS */
+ if (!ret && (states & BOOTM_STATE_LOADOS)) {
+ iflag = bootm_disable_interrupts();
+ ret = bootm_load_os(images, 0);
+ if (ret && ret != BOOTM_ERR_OVERLAP)
+ goto err;
+ else if (ret == BOOTM_ERR_OVERLAP)
+ ret = 0;
+ }
+
+ /* Relocate the ramdisk */
+#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
+ if (!ret && (states & BOOTM_STATE_RAMDISK)) {
+ ulong rd_len = images->rd_end - images->rd_start;
+
+ ret = boot_ramdisk_high(images->rd_start, rd_len,
+ &images->initrd_start,
+ &images->initrd_end);
+ if (!ret) {
+ env_set_hex("initrd_start", images->initrd_start);
+ env_set_hex("initrd_end", images->initrd_end);
+ }
+ }
+#endif
+#if CONFIG_IS_ENABLED(OF_LIBFDT) && CONFIG_IS_ENABLED(LMB)
+ if (!ret && (states & BOOTM_STATE_FDT)) {
+ boot_fdt_add_mem_rsv_regions(images->ft_addr);
+ ret = boot_relocate_fdt(&images->ft_addr, &images->ft_len);
+ }
+#endif
+
+ /* From now on, we need the OS boot function */
+ if (ret)
+ return ret;
+ boot_fn = bootm_os_get_boot_func(images->os.os);
+ need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |
+ BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |
+ BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
+ if (boot_fn == NULL && need_boot_fn) {
+ if (iflag)
+ enable_interrupts();
+ printf("ERROR: booting os '%s' (%d) is not supported\n",
+ genimg_get_os_name(images->os.os), images->os.os);
+ bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
+ return 1;
+ }
+
+ /* Call various other states that are not generally used */
+ if (!ret && (states & BOOTM_STATE_OS_CMDLINE))
+ ret = boot_fn(BOOTM_STATE_OS_CMDLINE, bmi);
+ if (!ret && (states & BOOTM_STATE_OS_BD_T))
+ ret = boot_fn(BOOTM_STATE_OS_BD_T, bmi);
+ if (!ret && (states & BOOTM_STATE_OS_PREP)) {
+ int flags = 0;
+ /* For Linux OS do all substitutions at console processing */
+ if (images->os.os == IH_OS_LINUX)
+ flags = BOOTM_CL_ALL;
+ ret = bootm_process_cmdline_env(flags);
+ if (ret) {
+ printf("Cmdline setup failed (err=%d)\n", ret);
+ ret = CMD_RET_FAILURE;
+ goto err;
+ }
+ ret = boot_fn(BOOTM_STATE_OS_PREP, bmi);
+ }
+
+#ifdef CONFIG_TRACE
+ /* Pretend to run the OS, then run a user command */
+ if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {
+ char *cmd_list = env_get("fakegocmd");
+
+ ret = boot_selected_os(BOOTM_STATE_OS_FAKE_GO, bmi, boot_fn);
+ if (!ret && cmd_list)
+ ret = run_command_list(cmd_list, -1, 0);
+ }
+#endif
+
+ /* Check for unsupported subcommand. */
+ if (ret) {
+ printf("subcommand failed (err=%d)\n", ret);
+ return ret;
+ }
+
+ /* Now run the OS! We hope this doesn't return */
+ if (!ret && (states & BOOTM_STATE_OS_GO))
+ ret = boot_selected_os(BOOTM_STATE_OS_GO, bmi, boot_fn);
+
+ /* Deal with any fallout */
+err:
+ if (iflag)
+ enable_interrupts();
+
+ if (ret == BOOTM_ERR_UNIMPLEMENTED) {
+ bootstage_error(BOOTSTAGE_ID_DECOMP_UNIMPL);
+ } else if (ret == BOOTM_ERR_RESET) {
+ printf("Resetting the board...\n");
+ reset_cpu();
+ }
+
+ return ret;
+}
+
+int boot_run(struct bootm_info *bmi, const char *cmd, int extra_states)
+{
+ int states;
+
+ bmi->cmd_name = cmd;
+ states = BOOTM_STATE_MEASURE | BOOTM_STATE_OS_PREP |
+ BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO;
+ if (IS_ENABLED(CONFIG_SYS_BOOT_RAMDISK_HIGH))
+ states |= BOOTM_STATE_RAMDISK;
+ states |= extra_states;
+
+ return bootm_run_states(bmi, states);
+}
+
+int bootm_run(struct bootm_info *bmi)
+{
+ return boot_run(bmi, "bootm", BOOTM_STATE_START | BOOTM_STATE_FINDOS |
+ BOOTM_STATE_PRE_LOAD | BOOTM_STATE_FINDOTHER |
+ BOOTM_STATE_LOADOS);
+}
+
+int bootz_run(struct bootm_info *bmi)
+{
+ return boot_run(bmi, "bootz", 0);
+}
+
+int booti_run(struct bootm_info *bmi)
+{
+ return boot_run(bmi, "booti", 0);
+}
+
+int bootm_boot_start(ulong addr, const char *cmdline)
+{
+ char addr_str[30];
+ struct bootm_info bmi;
+ int states;
+ int ret;
+
+ states = BOOTM_STATE_START | BOOTM_STATE_FINDOS | BOOTM_STATE_PRE_LOAD |
+ BOOTM_STATE_FINDOTHER | BOOTM_STATE_LOADOS |
+ BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
+ BOOTM_STATE_OS_GO;
+ if (IS_ENABLED(CONFIG_SYS_BOOT_RAMDISK_HIGH))
+ states |= BOOTM_STATE_RAMDISK;
+ if (IS_ENABLED(CONFIG_PPC) || IS_ENABLED(CONFIG_MIPS))
+ states |= BOOTM_STATE_OS_CMDLINE;
+ images.state |= states;
+
+ snprintf(addr_str, sizeof(addr_str), "%lx", addr);
+
+ ret = env_set("bootargs", cmdline);
+ if (ret) {
+ printf("Failed to set cmdline\n");
+ return ret;
+ }
+ bootm_init(&bmi);
+ bmi.addr_img = addr_str;
+ bmi.cmd_name = "bootm";
+ ret = bootm_run_states(&bmi, states);
+
+ return ret;
+}
+
+void bootm_init(struct bootm_info *bmi)
+{
+ memset(bmi, '\0', sizeof(struct bootm_info));
+ bmi->boot_progress = true;
+ bmi->images = &images;
+}
+
+/**
+ * switch_to_non_secure_mode() - switch to non-secure mode
+ *
+ * This routine is overridden by architectures requiring this feature.
+ */
+void __weak switch_to_non_secure_mode(void)
+{
+}
+
+#else /* USE_HOSTCC */
+
+#if defined(CONFIG_FIT_SIGNATURE)
+static int bootm_host_load_image(const void *fit, int req_image_type,
+ int cfg_noffset)
+{
+ const char *fit_uname_config = NULL;
+ ulong data, len;
+ struct bootm_headers images;
+ int noffset;
+ ulong load_end, buf_size;
+ uint8_t image_type;
+ uint8_t image_comp;
+ void *load_buf;
+ int ret;
+
+ fit_uname_config = fdt_get_name(fit, cfg_noffset, NULL);
+ memset(&images, '\0', sizeof(images));
+ images.verify = 1;
+ noffset = fit_image_load(&images, (ulong)fit,
+ NULL, &fit_uname_config,
+ IH_ARCH_DEFAULT, req_image_type, -1,
+ FIT_LOAD_IGNORED, &data, &len);
+ if (noffset < 0)
+ return noffset;
+ if (fit_image_get_type(fit, noffset, &image_type)) {
+ puts("Can't get image type!\n");
+ return -EINVAL;
+ }
+
+ if (fit_image_get_comp(fit, noffset, &image_comp))
+ image_comp = IH_COMP_NONE;
+
+ /* Allow the image to expand by a factor of 4, should be safe */
+ buf_size = (1 << 20) + len * 4;
+ load_buf = malloc(buf_size);
+ ret = image_decomp(image_comp, 0, data, image_type, load_buf,
+ (void *)data, len, buf_size, &load_end);
+ free(load_buf);
+
+ if (ret) {
+ ret = handle_decomp_error(image_comp, load_end - 0, buf_size, ret);
+ if (ret != BOOTM_ERR_UNIMPLEMENTED)
+ return ret;
+ }
+
+ return 0;
+}
+
+int bootm_host_load_images(const void *fit, int cfg_noffset)
+{
+ static uint8_t image_types[] = {
+ IH_TYPE_KERNEL,
+ IH_TYPE_FLATDT,
+ IH_TYPE_RAMDISK,
+ };
+ int err = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(image_types); i++) {
+ int ret;
+
+ ret = bootm_host_load_image(fit, image_types[i], cfg_noffset);
+ if (!err && ret && ret != -ENOENT)
+ err = ret;
+ }
+
+ /* Return the first error we found */
+ return err;
+}
+#endif
+
+#endif /* ndef USE_HOSTCC */
diff --git a/boot/bootm_os.c b/boot/bootm_os.c
new file mode 100644
index 00000000000..88f7c183867
--- /dev/null
+++ b/boot/bootm_os.c
@@ -0,0 +1,603 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2000-2009
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ */
+
+#include <bootm.h>
+#include <bootstage.h>
+#include <cpu_func.h>
+#include <efi_loader.h>
+#include <elf.h>
+#include <env.h>
+#include <fdt_support.h>
+#include <image.h>
+#include <lmb.h>
+#include <log.h>
+#include <asm/global_data.h>
+#include <linux/libfdt.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <vxworks.h>
+#include <tee/optee.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+static int do_bootm_standalone(int flag, struct bootm_info *bmi)
+{
+ struct bootm_headers *images = bmi->images;
+ int (*appl)(int, char *const[]);
+
+ if (!env_get_autostart()) {
+ env_set_hex("filesize", images->os.image_len);
+ return 0;
+ }
+ appl = (int (*)(int, char * const []))images->ep;
+ appl(bmi->argc, bmi->argv);
+ return 0;
+}
+
+/*******************************************************************/
+/* OS booting routines */
+/*******************************************************************/
+
+#if defined(CONFIG_BOOTM_NETBSD) || defined(CONFIG_BOOTM_PLAN9)
+static void copy_args(char *dest, int argc, char *const argv[], char delim)
+{
+ int i;
+
+ for (i = 0; i < argc; i++) {
+ if (i > 0)
+ *dest++ = delim;
+ strcpy(dest, argv[i]);
+ dest += strlen(argv[i]);
+ }
+}
+#endif
+
+static void __maybe_unused fit_unsupported_reset(const char *msg)
+{
+ if (CONFIG_IS_ENABLED(FIT_VERBOSE)) {
+ printf("! FIT images not supported for '%s' - must reset board to recover!\n",
+ msg);
+ }
+}
+
+#ifdef CONFIG_BOOTM_NETBSD
+static int do_bootm_netbsd(int flag, struct bootm_info *bmi)
+{
+ struct bootm_headers *images = bmi->images;
+ void (*loader)(struct bd_info *bd, struct legacy_img_hdr *hdr,
+ char *console, char *cmdline);
+ struct legacy_img_hdr *os_hdr, *hdr;
+ ulong kernel_data, kernel_len;
+ char *cmdline;
+
+ if (flag != BOOTM_STATE_OS_GO)
+ return 0;
+
+#if defined(CONFIG_FIT)
+ if (!images->legacy_hdr_valid) {
+ fit_unsupported_reset("NetBSD");
+ return 1;
+ }
+#endif
+ hdr = images->legacy_hdr_os;
+
+ /*
+ * Booting a (NetBSD) kernel image
+ *
+ * This process is pretty similar to a standalone application:
+ * The (first part of an multi-) image must be a stage-2 loader,
+ * which in turn is responsible for loading & invoking the actual
+ * kernel. The only differences are the parameters being passed:
+ * besides the board info strucure, the loader expects a command
+ * line, the name of the console device, and (optionally) the
+ * address of the original image header.
+ */
+ os_hdr = NULL;
+ if (image_check_type(&images->legacy_hdr_os_copy, IH_TYPE_MULTI)) {
+ image_multi_getimg(hdr, 1, &kernel_data, &kernel_len);
+ if (kernel_len)
+ os_hdr = hdr;
+ }
+
+ if (bmi->argc > 0) {
+ ulong len;
+ int i;
+
+ for (i = 0, len = 0; i < bmi->argc; i += 1)
+ len += strlen(bmi->argv[i]) + 1;
+ cmdline = malloc(len);
+ copy_args(cmdline, bmi->argc, bmi->argv, ' ');
+ } else {
+ cmdline = env_get("bootargs");
+ if (cmdline == NULL)
+ cmdline = "";
+ }
+
+ loader = (void (*)(struct bd_info *, struct legacy_img_hdr *, char *, char *))images->ep;
+
+ printf("## Transferring control to NetBSD stage-2 loader (at address %08lx) ...\n",
+ (ulong)loader);
+
+ bootstage_mark(BOOTSTAGE_ID_RUN_OS);
+
+ /*
+ * NetBSD Stage-2 Loader Parameters:
+ * arg[0]: pointer to board info data
+ * arg[1]: image load address
+ * arg[2]: char pointer to the console device to use
+ * arg[3]: char pointer to the boot arguments
+ */
+ (*loader)(gd->bd, os_hdr, "", cmdline);
+
+ return 1;
+}
+#endif /* CONFIG_BOOTM_NETBSD*/
+
+#ifdef CONFIG_BOOTM_RTEMS
+static int do_bootm_rtems(int flag, struct bootm_info *bmi)
+{
+ struct bootm_headers *images = bmi->images;
+ void (*entry_point)(struct bd_info *);
+
+ if (flag != BOOTM_STATE_OS_GO)
+ return 0;
+
+#if defined(CONFIG_FIT)
+ if (!images->legacy_hdr_valid) {
+ fit_unsupported_reset("RTEMS");
+ return 1;
+ }
+#endif
+
+ entry_point = (void (*)(struct bd_info *))images->ep;
+
+ printf("## Transferring control to RTEMS (at address %08lx) ...\n",
+ (ulong)entry_point);
+
+ bootstage_mark(BOOTSTAGE_ID_RUN_OS);
+
+ /*
+ * RTEMS Parameters:
+ * r3: ptr to board info data
+ */
+ (*entry_point)(gd->bd);
+
+ return 1;
+}
+#endif /* CONFIG_BOOTM_RTEMS */
+
+#if defined(CONFIG_BOOTM_OSE)
+static int do_bootm_ose(int flag, struct bootm_info *bmi)
+{
+ struct bootm_headers *images = bmi->images;
+ void (*entry_point)(void);
+
+ if (flag != BOOTM_STATE_OS_GO)
+ return 0;
+
+#if defined(CONFIG_FIT)
+ if (!images->legacy_hdr_valid) {
+ fit_unsupported_reset("OSE");
+ return 1;
+ }
+#endif
+
+ entry_point = (void (*)(void))images->ep;
+
+ printf("## Transferring control to OSE (at address %08lx) ...\n",
+ (ulong)entry_point);
+
+ bootstage_mark(BOOTSTAGE_ID_RUN_OS);
+
+ /*
+ * OSE Parameters:
+ * None
+ */
+ (*entry_point)();
+
+ return 1;
+}
+#endif /* CONFIG_BOOTM_OSE */
+
+#if defined(CONFIG_BOOTM_PLAN9)
+static int do_bootm_plan9(int flag, struct bootm_info *bmi)
+{
+ struct bootm_headers *images = bmi->images;
+ void (*entry_point)(void);
+ char *s;
+
+ if (flag != BOOTM_STATE_OS_GO)
+ return 0;
+
+#if defined(CONFIG_FIT)
+ if (!images->legacy_hdr_valid) {
+ fit_unsupported_reset("Plan 9");
+ return 1;
+ }
+#endif
+
+ /* See README.plan9 */
+ s = env_get("confaddr");
+ if (s != NULL) {
+ char *confaddr = (char *)hextoul(s, NULL);
+
+ if (bmi->argc) {
+ copy_args(confaddr, bmi->argc, bmi->argv, '\n');
+ } else {
+ s = env_get("bootargs");
+ if (s != NULL)
+ strcpy(confaddr, s);
+ }
+ }
+
+ entry_point = (void (*)(void))images->ep;
+
+ printf("## Transferring control to Plan 9 (at address %08lx) ...\n",
+ (ulong)entry_point);
+
+ bootstage_mark(BOOTSTAGE_ID_RUN_OS);
+
+ /*
+ * Plan 9 Parameters:
+ * None
+ */
+ (*entry_point)();
+
+ return 1;
+}
+#endif /* CONFIG_BOOTM_PLAN9 */
+
+#if defined(CONFIG_BOOTM_VXWORKS) && \
+ (defined(CONFIG_PPC) || defined(CONFIG_ARM))
+
+static void do_bootvx_fdt(struct bootm_headers *images)
+{
+#if defined(CONFIG_OF_LIBFDT)
+ int ret;
+ char *bootline;
+ ulong of_size = images->ft_len;
+ char **of_flat_tree = &images->ft_addr;
+
+ if (*of_flat_tree) {
+ boot_fdt_add_mem_rsv_regions(*of_flat_tree);
+
+ ret = boot_relocate_fdt(of_flat_tree, &of_size);
+ if (ret)
+ return;
+
+ /* Update ethernet nodes */
+ fdt_fixup_ethernet(*of_flat_tree);
+
+ ret = fdt_add_subnode(*of_flat_tree, 0, "chosen");
+ if ((ret >= 0 || ret == -FDT_ERR_EXISTS)) {
+ bootline = env_get("bootargs");
+ if (bootline) {
+ ret = fdt_find_and_setprop(*of_flat_tree,
+ "/chosen", "bootargs",
+ bootline,
+ strlen(bootline) + 1, 1);
+ if (ret < 0) {
+ printf("## ERROR: %s : %s\n", __func__,
+ fdt_strerror(ret));
+ return;
+ }
+ }
+ } else {
+ printf("## ERROR: %s : %s\n", __func__,
+ fdt_strerror(ret));
+ return;
+ }
+ }
+#endif
+
+ boot_prep_vxworks(images);
+
+ bootstage_mark(BOOTSTAGE_ID_RUN_OS);
+
+#if defined(CONFIG_OF_LIBFDT)
+ printf("## Starting vxWorks at 0x%08lx, device tree at 0x%08lx ...\n",
+ (ulong)images->ep, (ulong)*of_flat_tree);
+#else
+ printf("## Starting vxWorks at 0x%08lx\n", (ulong)images->ep);
+#endif
+ flush();
+
+ boot_jump_vxworks(images);
+
+ puts("## vxWorks terminated\n");
+}
+
+static int do_bootm_vxworks_legacy(int flag, struct bootm_info *bmi)
+{
+ struct bootm_headers *images = bmi->images;
+
+ if (flag != BOOTM_STATE_OS_GO)
+ return 0;
+
+ do_bootvx_fdt(images);
+
+ return 1;
+}
+
+int do_bootm_vxworks(int flag, struct bootm_info *bmi)
+{
+ char *bootargs;
+ int pos;
+ unsigned long vxflags;
+ bool std_dtb = false;
+
+ /* get bootargs env */
+ bootargs = env_get("bootargs");
+
+ if (bootargs != NULL) {
+ for (pos = 0; pos < strlen(bootargs); pos++) {
+ /* find f=0xnumber flag */
+ if ((bootargs[pos] == '=') && (pos >= 1) &&
+ (bootargs[pos - 1] == 'f')) {
+ vxflags = hextoul(&bootargs[pos + 1], NULL);
+ if (vxflags & VXWORKS_SYSFLG_STD_DTB)
+ std_dtb = true;
+ }
+ }
+ }
+
+ if (std_dtb) {
+ if (flag & BOOTM_STATE_OS_PREP)
+ printf(" Using standard DTB\n");
+ return do_bootm_linux(flag, bmi);
+ } else {
+ if (flag & BOOTM_STATE_OS_PREP)
+ printf(" !!! WARNING !!! Using legacy DTB\n");
+ return do_bootm_vxworks_legacy(flag, bmi);
+ }
+}
+#endif
+
+#if defined(CONFIG_CMD_ELF)
+static int do_bootm_qnxelf(int flag, struct bootm_info *bmi)
+{
+ struct bootm_headers *images = bmi->images;
+ char *local_args[2];
+ char str[16];
+ int dcache;
+
+ if (flag != BOOTM_STATE_OS_GO)
+ return 0;
+
+#if defined(CONFIG_FIT)
+ if (!images->legacy_hdr_valid) {
+ fit_unsupported_reset("QNX");
+ return 1;
+ }
+#endif
+
+ sprintf(str, "%lx", images->ep); /* write entry-point into string */
+ local_args[0] = bmi->argv[0];
+ local_args[1] = str; /* and provide it via the arguments */
+
+ /*
+ * QNX images require the data cache is disabled.
+ */
+ dcache = dcache_status();
+ if (dcache)
+ dcache_disable();
+
+ do_bootelf(NULL, 0, 2, local_args);
+
+ if (dcache)
+ dcache_enable();
+
+ return 1;
+}
+#endif
+
+#if defined(CONFIG_BOOTM_ELF)
+static int do_bootm_elf(int flag, struct bootm_info *bmi)
+{
+ Bootelf_flags flags = { .autostart = 1 };
+
+ if (flag != BOOTM_STATE_OS_GO)
+ return 0;
+
+ /*
+ * Required per RISC-V boot protocol:
+ * a0(argc) = hartid of the current core
+ * a1(argv) = address of the devicetree in memory
+ * https://www.kernel.org/doc/html/latest/arch/riscv/boot.html#register-state
+ */
+#if defined(CONFIG_RISCV)
+ bmi->argc = gd->arch.boot_hart;
+ bmi->argv = (char **)bmi->images->ft_addr;
+#endif
+
+ bootelf(bmi->images->ep, flags, bmi->argc, bmi->argv);
+
+ return 1;
+}
+#endif
+
+#ifdef CONFIG_INTEGRITY
+static int do_bootm_integrity(int flag, struct bootm_info *bmi)
+{
+ struct bootm_headers *images = bmi->images;
+ void (*entry_point)(void);
+
+ if (flag != BOOTM_STATE_OS_GO)
+ return 0;
+
+#if defined(CONFIG_FIT)
+ if (!images->legacy_hdr_valid) {
+ fit_unsupported_reset("INTEGRITY");
+ return 1;
+ }
+#endif
+
+ entry_point = (void (*)(void))images->ep;
+
+ printf("## Transferring control to INTEGRITY (at address %08lx) ...\n",
+ (ulong)entry_point);
+
+ bootstage_mark(BOOTSTAGE_ID_RUN_OS);
+
+ /*
+ * INTEGRITY Parameters:
+ * None
+ */
+ (*entry_point)();
+
+ return 1;
+}
+#endif
+
+#ifdef CONFIG_BOOTM_OPENRTOS
+static int do_bootm_openrtos(int flag, struct bootm_info *bmi)
+{
+ struct bootm_headers *images = bmi->images;
+ void (*entry_point)(void);
+
+ if (flag != BOOTM_STATE_OS_GO)
+ return 0;
+
+ entry_point = (void (*)(void))images->ep;
+
+ printf("## Transferring control to OpenRTOS (at address %08lx) ...\n",
+ (ulong)entry_point);
+
+ bootstage_mark(BOOTSTAGE_ID_RUN_OS);
+
+ /*
+ * OpenRTOS Parameters:
+ * None
+ */
+ (*entry_point)();
+
+ return 1;
+}
+#endif
+
+#ifdef CONFIG_BOOTM_OPTEE
+static int do_bootm_tee(int flag, struct bootm_info *bmi)
+{
+ struct bootm_headers *images = bmi->images;
+ int ret;
+
+ /* Validate OPTEE header */
+ ret = optee_verify_bootm_image(images->os.image_start,
+ images->os.load,
+ images->os.image_len);
+ if (ret)
+ return ret;
+
+ /* From here we can run the regular linux boot path */
+ return do_bootm_linux(flag, bmi);
+}
+#endif
+
+#ifdef CONFIG_BOOTM_EFI
+static int do_bootm_efi(int flag, struct bootm_info *bmi)
+{
+ struct bootm_headers *images = bmi->images;
+ int ret;
+ void *image_buf;
+
+ if (flag != BOOTM_STATE_OS_GO)
+ return 0;
+
+ /* We expect to return */
+ images->os.type = IH_TYPE_STANDALONE;
+
+ image_buf = map_sysmem(images->os.image_start, images->os.image_len);
+
+ /* Run EFI image */
+ printf("## Transferring control to EFI (at address %08lx) ...\n",
+ images->os.image_start);
+ bootstage_mark(BOOTSTAGE_ID_RUN_OS);
+
+ ret = efi_binary_run(image_buf, images->os.image_len,
+ images->ft_len
+ ? images->ft_addr : EFI_FDT_USE_INTERNAL,
+ (void *)images->initrd_start,
+ (size_t)(images->initrd_end - images->initrd_start));
+
+ return ret;
+}
+#endif
+
+static boot_os_fn *boot_os[] = {
+ [IH_OS_U_BOOT] = do_bootm_standalone,
+#ifdef CONFIG_BOOTM_LINUX
+ [IH_OS_LINUX] = do_bootm_linux,
+#endif
+#ifdef CONFIG_BOOTM_NETBSD
+ [IH_OS_NETBSD] = do_bootm_netbsd,
+#endif
+#ifdef CONFIG_BOOTM_RTEMS
+ [IH_OS_RTEMS] = do_bootm_rtems,
+#endif
+#if defined(CONFIG_BOOTM_OSE)
+ [IH_OS_OSE] = do_bootm_ose,
+#endif
+#if defined(CONFIG_BOOTM_PLAN9)
+ [IH_OS_PLAN9] = do_bootm_plan9,
+#endif
+#if defined(CONFIG_BOOTM_VXWORKS) && \
+ (defined(CONFIG_PPC) || defined(CONFIG_ARM) || defined(CONFIG_RISCV))
+ [IH_OS_VXWORKS] = do_bootm_vxworks,
+#endif
+#if defined(CONFIG_CMD_ELF)
+ [IH_OS_QNX] = do_bootm_qnxelf,
+#endif
+#ifdef CONFIG_INTEGRITY
+ [IH_OS_INTEGRITY] = do_bootm_integrity,
+#endif
+#ifdef CONFIG_BOOTM_OPENRTOS
+ [IH_OS_OPENRTOS] = do_bootm_openrtos,
+#endif
+#ifdef CONFIG_BOOTM_OPTEE
+ [IH_OS_TEE] = do_bootm_tee,
+#endif
+#ifdef CONFIG_BOOTM_EFI
+ [IH_OS_EFI] = do_bootm_efi,
+#endif
+#if defined(CONFIG_BOOTM_ELF)
+ [IH_OS_ELF] = do_bootm_elf,
+#endif
+};
+
+/* Allow for arch specific config before we boot */
+__weak void arch_preboot_os(void)
+{
+ /* please define platform specific arch_preboot_os() */
+}
+
+/* Allow for board specific config before we boot */
+__weak void board_preboot_os(void)
+{
+ /* please define board specific board_preboot_os() */
+}
+
+int boot_selected_os(int state, struct bootm_info *bmi, boot_os_fn *boot_fn)
+{
+ arch_preboot_os();
+ board_preboot_os();
+
+ boot_fn(state, bmi);
+
+ /* Stand-alone may return when 'autostart' is 'no' */
+ if (bmi->images->os.type == IH_TYPE_STANDALONE ||
+ IS_ENABLED(CONFIG_SANDBOX) ||
+ state == BOOTM_STATE_OS_FAKE_GO) /* We expect to return */
+ return 0;
+ bootstage_error(BOOTSTAGE_ID_BOOT_OS_RETURNED);
+ debug("\n## Control returned to monitor - resetting...\n");
+
+ return BOOTM_ERR_RESET;
+}
+
+boot_os_fn *bootm_os_get_boot_func(int os)
+{
+ return boot_os[os];
+}
diff --git a/boot/bootmeth-uclass.c b/boot/bootmeth-uclass.c
new file mode 100644
index 00000000000..188f6ea1895
--- /dev/null
+++ b/boot/bootmeth-uclass.c
@@ -0,0 +1,486 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <alist.h>
+#include <blk.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <bootstd.h>
+#include <dm.h>
+#include <dm/device-internal.h>
+#include <env_internal.h>
+#include <fs.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <dm/uclass-internal.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+int bootmeth_get_state_desc(struct udevice *dev, char *buf, int maxsize)
+{
+ const struct bootmeth_ops *ops = bootmeth_get_ops(dev);
+
+ if (!ops->get_state_desc)
+ return -ENOSYS;
+
+ return ops->get_state_desc(dev, buf, maxsize);
+}
+
+int bootmeth_check(struct udevice *dev, struct bootflow_iter *iter)
+{
+ const struct bootmeth_ops *ops = bootmeth_get_ops(dev);
+
+ if (!ops->check)
+ return 0;
+
+ return ops->check(dev, iter);
+}
+
+int bootmeth_read_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+ const struct bootmeth_ops *ops = bootmeth_get_ops(dev);
+
+ if (!ops->read_bootflow)
+ return -ENOSYS;
+
+ return ops->read_bootflow(dev, bflow);
+}
+
+int bootmeth_set_bootflow(struct udevice *dev, struct bootflow *bflow,
+ char *buf, int size)
+{
+ const struct bootmeth_ops *ops = bootmeth_get_ops(dev);
+
+ if (!ops->set_bootflow)
+ return -ENOSYS;
+
+ return ops->set_bootflow(dev, bflow, buf, size);
+}
+
+#if CONFIG_IS_ENABLED(BOOTSTD_FULL)
+int bootmeth_read_all(struct udevice *dev, struct bootflow *bflow)
+{
+ const struct bootmeth_ops *ops = bootmeth_get_ops(dev);
+
+ if (!ops->read_all)
+ return -ENOSYS;
+
+ return ops->read_all(dev, bflow);
+}
+#endif /* BOOTSTD_FULL */
+
+int bootmeth_boot(struct udevice *dev, struct bootflow *bflow)
+{
+ const struct bootmeth_ops *ops = bootmeth_get_ops(dev);
+
+ if (!ops->boot)
+ return -ENOSYS;
+
+ return ops->boot(dev, bflow);
+}
+
+int bootmeth_read_file(struct udevice *dev, struct bootflow *bflow,
+ const char *file_path, ulong addr,
+ enum bootflow_img_t type, ulong *sizep)
+{
+ const struct bootmeth_ops *ops = bootmeth_get_ops(dev);
+
+ if (!ops->read_file)
+ return -ENOSYS;
+
+ return ops->read_file(dev, bflow, file_path, addr, type, sizep);
+}
+
+int bootmeth_get_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+ const struct bootmeth_ops *ops = bootmeth_get_ops(dev);
+
+ if (!ops->read_bootflow)
+ return -ENOSYS;
+ bootflow_init(bflow, NULL, dev);
+
+ return ops->read_bootflow(dev, bflow);
+}
+
+int bootmeth_setup_iter_order(struct bootflow_iter *iter, bool include_global)
+{
+ struct bootstd_priv *std;
+ struct udevice **order;
+ int count;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return ret;
+
+ /* Create an array large enough */
+ count = std->bootmeth_count ? std->bootmeth_count :
+ uclass_id_count(UCLASS_BOOTMETH);
+ if (!count)
+ return log_msg_ret("count", -ENOENT);
+
+ order = calloc(count, sizeof(struct udevice *));
+ if (!order)
+ return log_msg_ret("order", -ENOMEM);
+
+ /* If we have an ordering, copy it */
+ if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && std->bootmeth_count) {
+ int i;
+
+ /*
+ * We don't support skipping global bootmeths. Instead, the user
+ * should omit them from the ordering
+ */
+ if (!include_global) {
+ ret = log_msg_ret("glob", -EPERM);
+ goto err_order;
+ }
+ memcpy(order, std->bootmeth_order,
+ count * sizeof(struct udevice *));
+
+ if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL)) {
+ for (i = 0; i < count; i++) {
+ struct udevice *dev = order[i];
+ struct bootmeth_uc_plat *ucp;
+ bool is_global;
+
+ ret = device_probe(dev);
+ if (ret) {
+ ret = log_msg_ret("probe", ret);
+ goto err_order;
+ }
+
+ ucp = dev_get_uclass_plat(dev);
+ is_global = ucp->flags &
+ BOOTMETHF_GLOBAL;
+ if (is_global) {
+ iter->first_glob_method = i;
+ break;
+ }
+ }
+ }
+ } else {
+ struct udevice *dev;
+ int i, upto, pass;
+
+ /*
+ * Do two passes, one to find the normal bootmeths and another
+ * to find the global ones, if required, The global ones go at
+ * the end.
+ */
+ for (pass = 0, upto = 0; pass < 1 + include_global; pass++) {
+ if (pass)
+ iter->first_glob_method = upto;
+ /*
+ * Get a list of bootmethods, in seq order (i.e. using
+ * aliases). There may be gaps so try to count up high
+ * enough to find them all.
+ */
+ for (i = 0; upto < count && i < 20 + count * 2; i++) {
+ struct bootmeth_uc_plat *ucp;
+ bool is_global;
+
+ ret = uclass_get_device_by_seq(UCLASS_BOOTMETH,
+ i, &dev);
+ if (ret)
+ continue;
+ ucp = dev_get_uclass_plat(dev);
+ is_global =
+ IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) &&
+ (ucp->flags & BOOTMETHF_GLOBAL);
+ if (pass ? is_global : !is_global)
+ order[upto++] = dev;
+ }
+ }
+ count = upto;
+ }
+ if (!count) {
+ ret = log_msg_ret("count2", -ENOENT);
+ goto err_order;
+ }
+
+ if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && include_global &&
+ iter->first_glob_method != -1 && iter->first_glob_method != count) {
+ iter->cur_method = iter->first_glob_method;
+ iter->doing_global = true;
+ }
+ iter->method_order = order;
+ iter->num_methods = count;
+
+ return 0;
+
+err_order:
+ free(order);
+ return ret;
+}
+
+int bootmeth_set_order(const char *order_str)
+{
+ struct bootstd_priv *std;
+ struct udevice **order;
+ int count, ret, i, len;
+ const char *s, *p;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return ret;
+
+ if (!order_str) {
+ free(std->bootmeth_order);
+ std->bootmeth_order = NULL;
+ std->bootmeth_count = 0;
+ return 0;
+ }
+
+ /* Create an array large enough */
+ count = uclass_id_count(UCLASS_BOOTMETH);
+ if (!count)
+ return log_msg_ret("count", -ENOENT);
+
+ order = calloc(count + 1, sizeof(struct udevice *));
+ if (!order)
+ return log_msg_ret("order", -ENOMEM);
+
+ for (i = 0, s = order_str; *s && i < count; s = p + (*p == ' '), i++) {
+ struct udevice *dev;
+
+ p = strchrnul(s, ' ');
+ len = p - s;
+ ret = uclass_find_device_by_namelen(UCLASS_BOOTMETH, s, len,
+ &dev);
+ if (ret) {
+ printf("Unknown bootmeth '%.*s'\n", len, s);
+ free(order);
+ return ret;
+ }
+ order[i] = dev;
+ }
+ order[i] = NULL;
+ free(std->bootmeth_order);
+ std->bootmeth_order = order;
+ std->bootmeth_count = i;
+
+ return 0;
+}
+
+int bootmeth_set_property(const char *name, const char *property, const char *value)
+{
+ int ret;
+ int len;
+ struct udevice *dev;
+ const struct bootmeth_ops *ops;
+
+ len = strlen(name);
+
+ ret = uclass_find_device_by_namelen(UCLASS_BOOTMETH, name, len,
+ &dev);
+ if (ret) {
+ printf("Unknown bootmeth '%s'\n", name);
+ return ret;
+ }
+
+ ops = bootmeth_get_ops(dev);
+ if (!ops->set_property) {
+ printf("set_property not found\n");
+ return -ENODEV;
+ }
+
+ return ops->set_property(dev, property, value);
+}
+
+int bootmeth_setup_fs(struct bootflow *bflow, struct blk_desc *desc)
+{
+ int ret;
+
+ if (desc) {
+ ret = fs_set_blk_dev_with_part(desc, bflow->part);
+ if (ret)
+ return log_msg_ret("set", ret);
+ } else if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && bflow->fs_type) {
+ fs_set_type(bflow->fs_type);
+ }
+
+ return 0;
+}
+
+int bootmeth_try_file(struct bootflow *bflow, struct blk_desc *desc,
+ const char *prefix, const char *fname)
+{
+ char path[200];
+ loff_t size;
+ int ret, ret2;
+
+ snprintf(path, sizeof(path), "%s%s", prefix ? prefix : "", fname);
+ log_debug("trying: %s\n", path);
+
+ free(bflow->fname);
+ bflow->fname = strdup(path);
+ if (!bflow->fname)
+ return log_msg_ret("name", -ENOMEM);
+
+ if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && bflow->fs_type)
+ fs_set_type(bflow->fs_type);
+
+ ret = fs_size(path, &size);
+ log_debug(" %s - err=%d\n", path, ret);
+
+ /* Sadly FS closes the file after fs_size() so we must redo this */
+ ret2 = bootmeth_setup_fs(bflow, desc);
+ if (ret2)
+ return log_msg_ret("fs", ret2);
+
+ if (ret)
+ return log_msg_ret("size", ret);
+
+ bflow->size = size;
+ bflow->state = BOOTFLOWST_FILE;
+
+ return 0;
+}
+
+int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align,
+ enum bootflow_img_t type)
+{
+ struct blk_desc *desc = NULL;
+ void *buf;
+ uint size;
+ int ret;
+
+ size = bflow->size;
+ log_debug(" - script file size %x\n", size);
+ if (size > size_limit)
+ return log_msg_ret("chk", -E2BIG);
+
+ ret = fs_read_alloc(bflow->fname, bflow->size, align, &buf);
+ if (ret)
+ return log_msg_ret("all", ret);
+
+ bflow->state = BOOTFLOWST_READY;
+ bflow->buf = buf;
+
+ if (bflow->blk)
+ desc = dev_get_uclass_plat(bflow->blk);
+
+ if (!bootflow_img_add(bflow, bflow->fname, type, map_to_sysmem(buf),
+ size))
+ return log_msg_ret("bai", -ENOMEM);
+
+ return 0;
+}
+
+int bootmeth_alloc_other(struct bootflow *bflow, const char *fname,
+ enum bootflow_img_t type, void **bufp, uint *sizep)
+{
+ struct blk_desc *desc = NULL;
+ char path[200];
+ loff_t size;
+ void *buf;
+ int ret;
+
+ snprintf(path, sizeof(path), "%s%s", bflow->subdir, fname);
+ log_debug("trying: %s\n", path);
+
+ if (bflow->blk)
+ desc = dev_get_uclass_plat(bflow->blk);
+
+ ret = bootmeth_setup_fs(bflow, desc);
+ if (ret)
+ return log_msg_ret("fs", ret);
+
+ ret = fs_size(path, &size);
+ log_debug(" %s - err=%d\n", path, ret);
+
+ ret = bootmeth_setup_fs(bflow, desc);
+ if (ret)
+ return log_msg_ret("fs", ret);
+
+ ret = fs_read_alloc(path, size, 0, &buf);
+ if (ret)
+ return log_msg_ret("all", ret);
+
+ if (!bootflow_img_add(bflow, bflow->fname, type, map_to_sysmem(buf),
+ size))
+ return log_msg_ret("boi", -ENOMEM);
+
+ *bufp = buf;
+ *sizep = size;
+
+ return 0;
+}
+
+int bootmeth_common_read_file(struct udevice *dev, struct bootflow *bflow,
+ const char *file_path, ulong addr,
+ enum bootflow_img_t type, ulong *sizep)
+{
+ struct blk_desc *desc = NULL;
+ loff_t len_read;
+ loff_t size;
+ int ret;
+
+ if (bflow->blk)
+ desc = dev_get_uclass_plat(bflow->blk);
+
+ ret = bootmeth_setup_fs(bflow, desc);
+ if (ret)
+ return log_msg_ret("fs", ret);
+
+ ret = fs_size(file_path, &size);
+ if (ret)
+ return log_msg_ret("size", ret);
+ if (size > *sizep)
+ return log_msg_ret("spc", -ENOSPC);
+
+ ret = bootmeth_setup_fs(bflow, desc);
+ if (ret)
+ return log_msg_ret("fs", ret);
+
+ ret = fs_read(file_path, addr, 0, 0, &len_read);
+ if (ret)
+ return ret;
+ *sizep = len_read;
+
+ if (!bootflow_img_add(bflow, bflow->fname, type, addr, size))
+ return log_msg_ret("bci", -ENOMEM);
+
+ return 0;
+}
+
+#ifdef CONFIG_BOOTSTD_FULL
+/**
+ * on_bootmeths() - Update the bootmeth order
+ *
+ * This will check for a valid list of bootmeths and only apply it if valid.
+ */
+static int on_bootmeths(const char *name, const char *value, enum env_op op,
+ int flags)
+{
+ int ret;
+
+ switch (op) {
+ case env_op_create:
+ case env_op_overwrite:
+ ret = bootmeth_set_order(value);
+ if (ret)
+ return 1;
+ return 0;
+ case env_op_delete:
+ bootmeth_set_order(NULL);
+ fallthrough;
+ default:
+ return 0;
+ }
+}
+U_BOOT_ENV_CALLBACK(bootmeths, on_bootmeths);
+#endif /* CONFIG_BOOTSTD_FULL */
+
+UCLASS_DRIVER(bootmeth) = {
+ .id = UCLASS_BOOTMETH,
+ .name = "bootmeth",
+ .flags = DM_UC_FLAG_SEQ_ALIAS,
+ .per_device_plat_auto = sizeof(struct bootmeth_uc_plat),
+};
diff --git a/boot/bootmeth_android.c b/boot/bootmeth_android.c
new file mode 100644
index 00000000000..8c2bde10e17
--- /dev/null
+++ b/boot/bootmeth_android.c
@@ -0,0 +1,627 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootmeth for Android
+ *
+ * Copyright (C) 2024 BayLibre, SAS
+ * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
+ */
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <android_ab.h>
+#include <android_image.h>
+#if CONFIG_IS_ENABLED(AVB_VERIFY)
+#include <avb_verify.h>
+#endif
+#include <bcb.h>
+#include <blk.h>
+#include <bootflow.h>
+#include <bootm.h>
+#include <bootmeth.h>
+#include <dm.h>
+#include <env.h>
+#include <image.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <part.h>
+#include <version.h>
+#include "bootmeth_android.h"
+
+#define BCB_FIELD_COMMAND_SZ 32
+#define BCB_PART_NAME "misc"
+#define BOOT_PART_NAME "boot"
+#define VENDOR_BOOT_PART_NAME "vendor_boot"
+#define SLOT_LEN 2
+
+/**
+ * struct android_priv - Private data
+ *
+ * This is read from the disk and recorded for use when the full Android
+ * kernel must be loaded and booted
+ *
+ * @boot_mode: Requested boot mode (normal, recovery, bootloader)
+ * @slot: Nul-terminated partition slot suffix read from BCB ("a\0" or "b\0")
+ * @header_version: Android boot image header version
+ */
+struct android_priv {
+ enum android_boot_mode boot_mode;
+ char *slot;
+ u32 header_version;
+ u32 boot_img_size;
+ u32 vendor_boot_img_size;
+};
+
+static int android_check(struct udevice *dev, struct bootflow_iter *iter)
+{
+ /* This only works on mmc devices */
+ if (bootflow_iter_check_mmc(iter))
+ return log_msg_ret("mmc", -ENOTSUPP);
+
+ /*
+ * This only works on whole devices, as multiple
+ * partitions are needed to boot Android
+ */
+ if (iter->part != 0)
+ return log_msg_ret("mmc part", -ENOTSUPP);
+
+ return 0;
+}
+
+static int scan_boot_part(struct udevice *blk, struct android_priv *priv)
+{
+ struct blk_desc *desc = dev_get_uclass_plat(blk);
+ struct disk_partition partition;
+ char partname[PART_NAME_LEN];
+ ulong num_blks, bufsz;
+ char *buf;
+ int ret;
+
+ if (priv->slot)
+ sprintf(partname, BOOT_PART_NAME "_%s", priv->slot);
+ else
+ sprintf(partname, BOOT_PART_NAME);
+
+ ret = part_get_info_by_name(desc, partname, &partition);
+ if (ret < 0)
+ return log_msg_ret("part info", ret);
+
+ num_blks = DIV_ROUND_UP(sizeof(struct andr_boot_img_hdr_v0), desc->blksz);
+ bufsz = num_blks * desc->blksz;
+ buf = malloc(bufsz);
+ if (!buf)
+ return log_msg_ret("buf", -ENOMEM);
+
+ ret = blk_read(blk, partition.start, num_blks, buf);
+ if (ret != num_blks) {
+ free(buf);
+ return log_msg_ret("part read", -EIO);
+ }
+
+ if (!is_android_boot_image_header(buf)) {
+ free(buf);
+ return log_msg_ret("header", -ENOENT);
+ }
+
+ if (!android_image_get_bootimg_size(buf, &priv->boot_img_size)) {
+ free(buf);
+ return log_msg_ret("get bootimg size", -EINVAL);
+ }
+
+ priv->header_version = ((struct andr_boot_img_hdr_v0 *)buf)->header_version;
+
+ free(buf);
+
+ return 0;
+}
+
+static int scan_vendor_boot_part(struct udevice *blk, struct android_priv *priv)
+{
+ struct blk_desc *desc = dev_get_uclass_plat(blk);
+ struct disk_partition partition;
+ char partname[PART_NAME_LEN];
+ ulong num_blks, bufsz;
+ char *buf;
+ int ret;
+
+ if (priv->slot)
+ sprintf(partname, VENDOR_BOOT_PART_NAME "_%s", priv->slot);
+ else
+ sprintf(partname, VENDOR_BOOT_PART_NAME);
+
+ ret = part_get_info_by_name(desc, partname, &partition);
+ if (ret < 0)
+ return log_msg_ret("part info", ret);
+
+ num_blks = DIV_ROUND_UP(sizeof(struct andr_vnd_boot_img_hdr), desc->blksz);
+ bufsz = num_blks * desc->blksz;
+ buf = malloc(bufsz);
+ if (!buf)
+ return log_msg_ret("buf", -ENOMEM);
+
+ ret = blk_read(blk, partition.start, num_blks, buf);
+ if (ret != num_blks) {
+ free(buf);
+ return log_msg_ret("part read", -EIO);
+ }
+
+ if (!is_android_vendor_boot_image_header(buf)) {
+ free(buf);
+ return log_msg_ret("header", -ENOENT);
+ }
+
+ if (!android_image_get_vendor_bootimg_size(buf, &priv->vendor_boot_img_size)) {
+ free(buf);
+ return log_msg_ret("get vendor bootimg size", -EINVAL);
+ }
+
+ free(buf);
+
+ return 0;
+}
+
+static int android_read_slot_from_bcb(struct bootflow *bflow, bool decrement)
+{
+ struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
+ struct android_priv *priv = bflow->bootmeth_priv;
+ struct disk_partition misc;
+ char slot_suffix[3];
+ int ret;
+
+ if (!CONFIG_IS_ENABLED(ANDROID_AB)) {
+ priv->slot = NULL;
+ return 0;
+ }
+
+ ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc);
+ if (ret < 0)
+ return log_msg_ret("part", ret);
+
+ ret = ab_select_slot(desc, &misc, decrement);
+ if (ret < 0)
+ return log_msg_ret("slot", ret);
+
+ priv->slot = malloc(SLOT_LEN);
+ priv->slot[0] = BOOT_SLOT_NAME(ret);
+ priv->slot[1] = '\0';
+
+ sprintf(slot_suffix, "_%s", priv->slot);
+ ret = bootflow_cmdline_set_arg(bflow, "androidboot.slot_suffix",
+ slot_suffix, false);
+ if (ret < 0)
+ return log_msg_ret("cmdl", ret);
+
+ return 0;
+}
+
+static int configure_serialno(struct bootflow *bflow)
+{
+ char *serialno = env_get("serial#");
+
+ if (!serialno)
+ return log_msg_ret("serial", -ENOENT);
+
+ return bootflow_cmdline_set_arg(bflow, "androidboot.serialno", serialno, false);
+}
+
+static int configure_bootloader_version(struct bootflow *bflow)
+{
+ return bootflow_cmdline_set_arg(bflow, "androidboot.bootloader",
+ PLAIN_VERSION, false);
+}
+
+static int android_read_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+ struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
+ struct disk_partition misc;
+ struct android_priv *priv;
+ char command[BCB_FIELD_COMMAND_SZ];
+ int ret;
+
+ bflow->state = BOOTFLOWST_MEDIA;
+
+ /*
+ * bcb_find_partition_and_load() will print errors to stdout
+ * if BCB_PART_NAME is not found. To avoid that, check if the
+ * partition exists first.
+ */
+ ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc);
+ if (ret < 0)
+ return log_msg_ret("part", ret);
+
+ ret = bcb_find_partition_and_load("mmc", desc->devnum, BCB_PART_NAME);
+ if (ret < 0)
+ return log_msg_ret("bcb load", ret);
+
+ ret = bcb_get(BCB_FIELD_COMMAND, command, sizeof(command));
+ if (ret < 0)
+ return log_msg_ret("bcb read", ret);
+
+ priv = malloc(sizeof(struct android_priv));
+ if (!priv)
+ return log_msg_ret("buf", -ENOMEM);
+
+ if (!strcmp("bootonce-bootloader", command)) {
+ priv->boot_mode = ANDROID_BOOT_MODE_BOOTLOADER;
+ bflow->os_name = strdup("Android (bootloader)");
+ } else if (!strcmp("boot-fastboot", command)) {
+ priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY;
+ bflow->os_name = strdup("Android (fastbootd)");
+ } else if (!strcmp("boot-recovery", command)) {
+ priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY;
+ bflow->os_name = strdup("Android (recovery)");
+ } else {
+ priv->boot_mode = ANDROID_BOOT_MODE_NORMAL;
+ bflow->os_name = strdup("Android");
+ }
+ if (!bflow->os_name)
+ return log_msg_ret("os", -ENOMEM);
+
+ if (priv->boot_mode == ANDROID_BOOT_MODE_BOOTLOADER) {
+ /* Clear BCB */
+ memset(command, 0, sizeof(command));
+ ret = bcb_set(BCB_FIELD_COMMAND, command);
+ if (ret < 0) {
+ free(priv);
+ return log_msg_ret("bcb set", ret);
+ }
+ ret = bcb_store();
+ if (ret < 0) {
+ free(priv);
+ return log_msg_ret("bcb store", ret);
+ }
+
+ bflow->bootmeth_priv = priv;
+ bflow->state = BOOTFLOWST_READY;
+ return 0;
+ }
+
+ bflow->bootmeth_priv = priv;
+
+ /* For recovery and normal boot, we need to scan the partitions */
+ ret = android_read_slot_from_bcb(bflow, false);
+ if (ret < 0) {
+ log_err("read slot: %d", ret);
+ goto free_priv;
+ }
+
+ ret = scan_boot_part(bflow->blk, priv);
+ if (ret < 0) {
+ log_debug("scan boot failed: err=%d\n", ret);
+ goto free_priv;
+ }
+
+ if (priv->header_version >= 3) {
+ ret = scan_vendor_boot_part(bflow->blk, priv);
+ if (ret < 0) {
+ log_debug("scan vendor_boot failed: err=%d\n", ret);
+ goto free_priv;
+ }
+ }
+
+ /*
+ * Ignoring return code for the following configurations:
+ * these are not mandatory for booting.
+ */
+ configure_serialno(bflow);
+ configure_bootloader_version(bflow);
+
+ if (priv->boot_mode == ANDROID_BOOT_MODE_NORMAL && priv->slot) {
+ ret = bootflow_cmdline_set_arg(bflow, "androidboot.force_normal_boot",
+ "1", false);
+ if (ret < 0) {
+ log_debug("normal_boot %d", ret);
+ goto free_priv;
+ }
+ }
+
+ bflow->state = BOOTFLOWST_READY;
+
+ return 0;
+
+ free_priv:
+ free(priv);
+ bflow->bootmeth_priv = NULL;
+ return ret;
+}
+
+static int android_read_file(struct udevice *dev, struct bootflow *bflow,
+ const char *file_path, ulong addr,
+ enum bootflow_img_t type, ulong *sizep)
+{
+ /*
+ * Reading individual files is not supported since we only
+ * operate on whole mmc devices (because we require multiple partitions)
+ */
+ return log_msg_ret("Unsupported", -ENOSYS);
+}
+
+/**
+ * read_slotted_partition() - Read a partition by appending a slot suffix
+ *
+ * Most modern Android devices use Seamless Updates, where each partition
+ * is duplicated. For example, the boot partition has boot_a and boot_b.
+ * For more information, see:
+ * https://source.android.com/docs/core/ota/ab
+ * https://source.android.com/docs/core/ota/ab/ab_implement
+ *
+ * @blk: Block device to read
+ * @name: Partition name to read
+ * @slot: Nul-terminated slot suffixed to partition name ("a\0" or "b\0")
+ * @image_size: Image size in bytes used when reading the partition
+ * @addr: Address where the partition content is loaded into
+ * Return: 0 if OK, negative errno on failure.
+ */
+static int read_slotted_partition(struct blk_desc *desc, const char *const name,
+ const char slot[2], ulong image_size, ulong addr)
+{
+ struct disk_partition partition;
+ char partname[PART_NAME_LEN];
+ size_t partname_len;
+ ulong num_blks = DIV_ROUND_UP(image_size, desc->blksz);
+ int ret;
+ u32 n;
+
+ /*
+ * Ensure name fits in partname.
+ * For A/B, it should be <name>_<slot>\0
+ * For non A/B, it should be <name>\0
+ */
+ if (CONFIG_IS_ENABLED(ANDROID_AB))
+ partname_len = PART_NAME_LEN - 2 - 1;
+ else
+ partname_len = PART_NAME_LEN - 1;
+
+ if (strlen(name) > partname_len)
+ return log_msg_ret("name too long", -EINVAL);
+
+ if (slot)
+ sprintf(partname, "%s_%s", name, slot);
+ else
+ sprintf(partname, "%s", name);
+
+ ret = part_get_info_by_name(desc, partname, &partition);
+ if (ret < 0)
+ return log_msg_ret("part", ret);
+
+ n = blk_dread(desc, partition.start, num_blks, map_sysmem(addr, 0));
+ if (n < num_blks)
+ return log_msg_ret("part read", -EIO);
+
+ return 0;
+}
+
+#if CONFIG_IS_ENABLED(AVB_VERIFY)
+static int avb_append_commandline_arg(struct bootflow *bflow, char *arg)
+{
+ char *key = strsep(&arg, "=");
+ char *value = arg;
+ int ret;
+
+ ret = bootflow_cmdline_set_arg(bflow, key, value, false);
+ if (ret < 0)
+ return log_msg_ret("avb cmdline", ret);
+
+ return 0;
+}
+
+static int avb_append_commandline(struct bootflow *bflow, char *cmdline)
+{
+ char *arg = strsep(&cmdline, " ");
+ int ret;
+
+ while (arg) {
+ ret = avb_append_commandline_arg(bflow, arg);
+ if (ret < 0)
+ return ret;
+
+ arg = strsep(&cmdline, " ");
+ }
+
+ return 0;
+}
+
+static int run_avb_verification(struct bootflow *bflow)
+{
+ struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
+ struct android_priv *priv = bflow->bootmeth_priv;
+ const char * const requested_partitions[] = {"boot", "vendor_boot", NULL};
+ struct AvbOps *avb_ops;
+ AvbSlotVerifyResult result;
+ AvbSlotVerifyData *out_data;
+ enum avb_boot_state boot_state;
+ char *extra_args;
+ char slot_suffix[3] = "";
+ bool unlocked = false;
+ int ret;
+
+ avb_ops = avb_ops_alloc(desc->devnum);
+ if (!avb_ops)
+ return log_msg_ret("avb ops", -ENOMEM);
+
+ if (priv->slot)
+ sprintf(slot_suffix, "_%s", priv->slot);
+
+ ret = avb_ops->read_is_device_unlocked(avb_ops, &unlocked);
+ if (ret != AVB_IO_RESULT_OK)
+ return log_msg_ret("avb lock", -EIO);
+
+ result = avb_slot_verify(avb_ops,
+ requested_partitions,
+ slot_suffix,
+ unlocked,
+ AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
+ &out_data);
+
+ if (!unlocked) {
+ /* When device is locked, we only accept AVB_SLOT_VERIFY_RESULT_OK */
+ if (result != AVB_SLOT_VERIFY_RESULT_OK) {
+ printf("Verification failed, reason: %s\n",
+ str_avb_slot_error(result));
+ if (out_data)
+ avb_slot_verify_data_free(out_data);
+ return log_msg_ret("avb verify", -EIO);
+ }
+ boot_state = AVB_GREEN;
+ } else {
+ /* When device is unlocked, we also accept verification errors */
+ if (result != AVB_SLOT_VERIFY_RESULT_OK &&
+ result != AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION) {
+ printf("Unlocked verification failed, reason: %s\n",
+ str_avb_slot_error(result));
+ if (out_data)
+ avb_slot_verify_data_free(out_data);
+ return log_msg_ret("avb verify unlocked", -EIO);
+ }
+ boot_state = AVB_ORANGE;
+ }
+
+ extra_args = avb_set_state(avb_ops, boot_state);
+ if (extra_args) {
+ /* extra_args will be modified after this. This is fine */
+ ret = avb_append_commandline_arg(bflow, extra_args);
+ if (ret < 0)
+ goto free_out_data;
+ }
+
+ if (result == AVB_SLOT_VERIFY_RESULT_OK) {
+ ret = avb_append_commandline(bflow, out_data->cmdline);
+ if (ret < 0)
+ goto free_out_data;
+ }
+
+ return 0;
+
+ free_out_data:
+ if (out_data)
+ avb_slot_verify_data_free(out_data);
+
+ return log_msg_ret("avb cmdline", ret);
+}
+#else
+static int run_avb_verification(struct bootflow *bflow)
+{
+ int ret;
+
+ /* When AVB is unsupported, pass ORANGE state */
+ ret = bootflow_cmdline_set_arg(bflow,
+ "androidboot.verifiedbootstate",
+ "orange", false);
+ if (ret < 0)
+ return log_msg_ret("avb cmdline", ret);
+
+ return 0;
+}
+#endif /* AVB_VERIFY */
+
+static int boot_android_normal(struct bootflow *bflow)
+{
+ struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
+ struct android_priv *priv = bflow->bootmeth_priv;
+ int ret;
+ ulong loadaddr = env_get_hex("loadaddr", 0);
+ ulong vloadaddr = env_get_hex("vendor_boot_comp_addr_r", 0);
+
+ ret = run_avb_verification(bflow);
+ if (ret < 0)
+ return log_msg_ret("avb", ret);
+
+ /* Read slot once more to decrement counter from BCB */
+ ret = android_read_slot_from_bcb(bflow, true);
+ if (ret < 0)
+ return log_msg_ret("read slot", ret);
+
+ ret = read_slotted_partition(desc, "boot", priv->slot, priv->boot_img_size,
+ loadaddr);
+ if (ret < 0)
+ return log_msg_ret("read boot", ret);
+
+ if (priv->header_version >= 3) {
+ ret = read_slotted_partition(desc, "vendor_boot", priv->slot,
+ priv->vendor_boot_img_size, vloadaddr);
+ if (ret < 0)
+ return log_msg_ret("read vendor_boot", ret);
+ set_avendor_bootimg_addr(vloadaddr);
+ }
+ set_abootimg_addr(loadaddr);
+
+ if (priv->slot)
+ free(priv->slot);
+
+ ret = bootm_boot_start(loadaddr, bflow->cmdline);
+
+ return log_msg_ret("boot", ret);
+}
+
+static int boot_android_recovery(struct bootflow *bflow)
+{
+ int ret;
+
+ ret = boot_android_normal(bflow);
+
+ return log_msg_ret("boot", ret);
+}
+
+static int boot_android_bootloader(struct bootflow *bflow)
+{
+ int ret;
+
+ ret = run_command("fastboot usb 0", 0);
+ do_reset(NULL, 0, 0, NULL);
+
+ return log_msg_ret("boot", ret);
+}
+
+static int android_boot(struct udevice *dev, struct bootflow *bflow)
+{
+ struct android_priv *priv = bflow->bootmeth_priv;
+ int ret;
+
+ switch (priv->boot_mode) {
+ case ANDROID_BOOT_MODE_NORMAL:
+ ret = boot_android_normal(bflow);
+ break;
+ case ANDROID_BOOT_MODE_RECOVERY:
+ ret = boot_android_recovery(bflow);
+ break;
+ case ANDROID_BOOT_MODE_BOOTLOADER:
+ ret = boot_android_bootloader(bflow);
+ break;
+ default:
+ printf("ANDROID: Unknown boot mode %d. Running fastboot...\n",
+ priv->boot_mode);
+ boot_android_bootloader(bflow);
+ /* Tell we failed to boot since boot mode is unknown */
+ ret = -EFAULT;
+ }
+
+ return ret;
+}
+
+static int android_bootmeth_bind(struct udevice *dev)
+{
+ struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ plat->desc = "Android boot";
+ plat->flags = BOOTMETHF_ANY_PART;
+
+ return 0;
+}
+
+static struct bootmeth_ops android_bootmeth_ops = {
+ .check = android_check,
+ .read_bootflow = android_read_bootflow,
+ .read_file = android_read_file,
+ .boot = android_boot,
+};
+
+static const struct udevice_id android_bootmeth_ids[] = {
+ { .compatible = "u-boot,android" },
+ { }
+};
+
+U_BOOT_DRIVER(bootmeth_android) = {
+ .name = "bootmeth_android",
+ .id = UCLASS_BOOTMETH,
+ .of_match = android_bootmeth_ids,
+ .ops = &android_bootmeth_ops,
+ .bind = android_bootmeth_bind,
+};
diff --git a/boot/bootmeth_android.h b/boot/bootmeth_android.h
new file mode 100644
index 00000000000..a57fad9f011
--- /dev/null
+++ b/boot/bootmeth_android.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Bootmethod for Android
+ *
+ * Copyright (C) 2024 BayLibre, SAS
+ * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
+ */
+
+enum android_boot_mode {
+ ANDROID_BOOT_MODE_NORMAL = 0,
+
+ /*
+ * Android "recovery" is a special boot mode that uses another ramdisk.
+ * It can be used to "factory reset" a board or to flash logical partitions
+ * It operates in 2 modes: adb or fastbootd
+ * To enter recovery from Android, we can do:
+ * $ adb reboot recovery
+ * $ adb reboot fastboot
+ */
+ ANDROID_BOOT_MODE_RECOVERY,
+
+ /*
+ * Android "bootloader" is for accessing/reflashing physical partitions
+ * Typically, this will launch a fastboot process in U-Boot.
+ * To enter "bootloader" from Android, we can do:
+ * $ adb reboot bootloader
+ */
+ ANDROID_BOOT_MODE_BOOTLOADER,
+};
diff --git a/boot/bootmeth_cros.c b/boot/bootmeth_cros.c
new file mode 100644
index 00000000000..c7b862e512a
--- /dev/null
+++ b/boot/bootmeth_cros.c
@@ -0,0 +1,491 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootmethod for ChromiumOS
+ *
+ * Copyright 2023 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <blk.h>
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootm.h>
+#include <bootmeth.h>
+#include <display_options.h>
+#include <dm.h>
+#include <efi.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <part.h>
+#include <linux/sizes.h>
+#include "bootmeth_cros.h"
+
+static const efi_guid_t cros_kern_type = PARTITION_CROS_KERNEL;
+
+/*
+ * Layout of the ChromeOS kernel
+ *
+ * Partitions 2 and 4 contain kernels with type GUID_CROS_KERNEL
+ *
+ * Contents are:
+ *
+ * Offset Contents
+ * 0 struct vb2_keyblock
+ * m struct vb2_kernel_preamble
+ * m + n kernel buffer
+ *
+ * m is keyblock->keyblock_size
+ * n is preamble->preamble_size
+ *
+ * The kernel buffer itself consists of various parts:
+ *
+ * Offset Contents
+ * m + n kernel image (Flat vmlinux binary or FIT)
+ * b - 8KB Command line text
+ * b - 4KB X86 setup block (struct boot_params, extends for about 16KB)
+ * b X86 bootloader (continuation of setup block)
+ * b + 16KB X86 setup block (copy, used for hold data pointed to)
+ *
+ * b is m + n + preamble->bootloader_address - preamble->body_load_address
+ *
+ * Useful metadata extends from b - 8KB through to b + 32 KB
+ */
+
+enum {
+ PROBE_SIZE = SZ_4K, /* initial bytes read from partition */
+
+ X86_SETUP_OFFSET = -0x1000, /* setup offset relative to base */
+ CMDLINE_OFFSET = -0x2000, /* cmdline offset relative to base */
+ X86_KERNEL_OFFSET = 0x4000, /* kernel offset relative to base */
+};
+
+/**
+ * struct cros_priv - Private data
+ *
+ * This is read from the disk and recorded for use when the full kernel must
+ * be loaded and booted
+ *
+ * @body_offset: Offset of kernel body from start of partition (in bytes)
+ * @body_size: Size of kernel body in bytes
+ * @part_start: Block offset of selected partition from the start of the disk
+ * @body_load_address: Nominal load address for kernel body
+ * @bootloader_address: Address of bootloader, after body is loaded at
+ * body_load_address
+ * @bootloader_size: Size of bootloader in bytes
+ * @info_buf: Buffer containing ChromiumOS info
+ */
+struct cros_priv {
+ ulong body_offset;
+ ulong body_size;
+ lbaint_t part_start;
+ ulong body_load_address;
+ ulong bootloader_address;
+ ulong bootloader_size;
+ void *info_buf;
+};
+
+static int cros_check(struct udevice *dev, struct bootflow_iter *iter)
+{
+ /* This only works on block and network devices */
+ if (bootflow_iter_check_blk(iter))
+ return log_msg_ret("blk", -ENOTSUPP);
+
+ return 0;
+}
+
+static int copy_cmdline(const char *from, const char *uuid, char **bufp)
+{
+ const int maxlen = 2048;
+ char buf[maxlen];
+ char *cmd, *to, *end;
+ int len;
+
+ /* Allow space for cmdline + UUID */
+ len = strnlen(from, sizeof(buf));
+ if (len >= maxlen)
+ return -E2BIG;
+
+ log_debug("uuid %d %s\n", uuid ? (int)strlen(uuid) : 0, uuid);
+ for (to = buf, end = buf + maxlen - UUID_STR_LEN - 1; *from; from++) {
+ if (to >= end)
+ return -E2BIG;
+ if (from[0] == '%' && from[1] == 'U' && uuid &&
+ strlen(uuid) == UUID_STR_LEN) {
+ strcpy(to, uuid);
+ to += UUID_STR_LEN;
+ from++;
+ } else {
+ *to++ = *from;
+ }
+ }
+ *to = '\0';
+ len = to - buf;
+ cmd = strdup(buf);
+ if (!cmd)
+ return -ENOMEM;
+ free(*bufp);
+ *bufp = cmd;
+
+ return 0;
+}
+
+/**
+ * scan_part() - Scan a kernel partition to see if has a ChromeOS header
+ *
+ * This reads the first PROBE_SIZE of a partition, loookng for
+ * VB2_KEYBLOCK_MAGIC
+ *
+ * @blk: Block device to scan
+ * @partnum: Partition number to scan
+ * @info: Please to put partition info
+ * @hdrp: Return allocated keyblock header on success
+ */
+static int scan_part(struct udevice *blk, int partnum,
+ struct disk_partition *info, struct vb2_keyblock **hdrp)
+{
+ struct blk_desc *desc = dev_get_uclass_plat(blk);
+ struct vb2_keyblock *hdr;
+ efi_guid_t type;
+ ulong num_blks;
+ int ret;
+
+ if (!partnum)
+ return log_msg_ret("efi", -ENOENT);
+
+ ret = part_get_info(desc, partnum, info);
+ if (ret)
+ return log_msg_ret("part", ret);
+
+ /* Check for kernel partition type */
+ log_debug("part %x: type=%s\n", partnum, info->type_guid);
+ if (uuid_str_to_bin(info->type_guid, type.b, UUID_STR_FORMAT_GUID))
+ return log_msg_ret("typ", -EINVAL);
+
+ if (guidcmp(&cros_kern_type, &type))
+ return log_msg_ret("typ", -ENOEXEC);
+
+ /* Make a buffer for the header information */
+ num_blks = PROBE_SIZE >> desc->log2blksz;
+ log_debug("Reading header, blk=%s, start=%lx, blocks=%lx\n",
+ blk->name, (ulong)info->start, num_blks);
+ hdr = memalign(SZ_1K, PROBE_SIZE);
+ if (!hdr)
+ return log_msg_ret("hdr", -ENOMEM);
+ ret = blk_read(blk, info->start, num_blks, hdr);
+ if (ret != num_blks) {
+ free(hdr);
+ return log_msg_ret("inf", -EIO);
+ }
+
+ if (memcmp(VB2_KEYBLOCK_MAGIC, hdr->magic, VB2_KEYBLOCK_MAGIC_SIZE)) {
+ free(hdr);
+ log_debug("no magic\n");
+ return -ENOENT;
+ }
+
+ *hdrp = hdr;
+
+ return 0;
+}
+
+/**
+ * cros_read_buf() - Read information into a buf and parse it
+ *
+ * @bflow: Bootflow to update
+ * @buf: Buffer to use
+ * @size: Size of buffer and number of bytes to read thereinto
+ * @start: Start offset to read from on disk
+ * @before_base: Number of bytes to read before the bootloader base
+ * @uuid: UUID string if supported, else NULL
+ * Return: 0 if OK, -ENOMEM if out of memory, -EIO on read failure
+ */
+static int cros_read_buf(struct bootflow *bflow, void *buf, ulong size,
+ loff_t start, ulong before_base, const char *uuid)
+{
+ struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
+ ulong base, setup, cmdline, kern_base;
+ ulong num_blks;
+ int ret;
+
+ num_blks = size >> desc->log2blksz;
+ log_debug("Reading info to %lx, blk=%s, size=%lx, blocks=%lx\n",
+ (ulong)map_to_sysmem(buf), bflow->blk->name, size, num_blks);
+ ret = blk_read(bflow->blk, start, num_blks, buf);
+ if (ret != num_blks)
+ return log_msg_ret("inf", -EIO);
+ base = map_to_sysmem(buf) + before_base;
+
+ setup = base + X86_SETUP_OFFSET;
+ cmdline = base + CMDLINE_OFFSET;
+ kern_base = base + X86_KERNEL_OFFSET;
+ log_debug("base %lx setup %lx cmdline %lx kern_base %lx\n", base,
+ setup, cmdline, kern_base);
+
+#ifdef CONFIG_X86
+ const char *version;
+
+ version = zimage_get_kernel_version(map_sysmem(setup, 0),
+ map_sysmem(kern_base, 0));
+ log_debug("version %s\n", version);
+ if (version)
+ bflow->name = strdup(version);
+#endif
+ if (!bflow->name)
+ bflow->name = strdup("ChromeOS");
+ if (!bflow->name)
+ return log_msg_ret("nam", -ENOMEM);
+ bflow->os_name = strdup("ChromeOS");
+ if (!bflow->os_name)
+ return log_msg_ret("os", -ENOMEM);
+
+ ret = copy_cmdline(map_sysmem(cmdline, 0), uuid, &bflow->cmdline);
+ if (ret)
+ return log_msg_ret("cmd", ret);
+
+ if (!bootflow_img_add(bflow, "setup",
+ (enum bootflow_img_t)IH_TYPE_X86_SETUP,
+ setup, 0x3000))
+ return log_msg_ret("cri", -ENOMEM);
+
+ bflow->x86_setup = map_sysmem(setup, 0);
+
+ if (!bootflow_img_add(bflow, "cmdline", BFI_CMDLINE, cmdline, 0x1000))
+ return log_msg_ret("crc", -ENOMEM);
+
+ return 0;
+}
+
+/**
+ * cros_read_info() - Read information and fill out the bootflow
+ *
+ * @bflow: Bootflow to update
+ * @uuid: UUID string if supported, else NULL
+ * @preamble: Kernel preamble information
+ * Return: 0 if OK, -ENOMEM if out of memory, -EIO on read failure
+ */
+static int cros_read_info(struct bootflow *bflow, const char *uuid,
+ const struct vb2_kernel_preamble *preamble)
+{
+ struct cros_priv *priv = bflow->bootmeth_priv;
+ struct udevice *blk = bflow->blk;
+ struct blk_desc *desc = dev_get_uclass_plat(blk);
+ ulong offset, size, before_base;
+ void *buf;
+ int ret;
+
+ log_debug("Kernel preamble at %lx, version major %x, minor %x\n",
+ (ulong)map_to_sysmem(preamble),
+ preamble->header_version_major,
+ preamble->header_version_minor);
+
+ log_debug(" - load_address %lx, bl_addr %lx, bl_size %lx\n",
+ (ulong)preamble->body_load_address,
+ (ulong)preamble->bootloader_address,
+ (ulong)preamble->bootloader_size);
+
+ priv->body_size = preamble->body_signature.data_size;
+ priv->body_load_address = preamble->body_load_address;
+ priv->bootloader_address = preamble->bootloader_address;
+ priv->bootloader_size = preamble->bootloader_size;
+ log_debug("Kernel body at %lx size %lx\n", priv->body_offset,
+ priv->body_size);
+
+ /* Work out how many bytes to read before the bootloader base */
+ before_base = -CMDLINE_OFFSET;
+
+ /* Read the cmdline through to the end of the bootloader */
+ size = priv->bootloader_size + before_base;
+ offset = priv->body_offset +
+ (priv->bootloader_address - priv->body_load_address) +
+ CMDLINE_OFFSET;
+ buf = malloc(size);
+ if (!buf)
+ return log_msg_ret("buf", -ENOMEM);
+
+ ret = cros_read_buf(bflow, buf, size,
+ priv->part_start + (offset >> desc->log2blksz),
+ before_base, uuid);
+ if (ret) {
+ /* Clear this since the buffer is invalid */
+ bflow->x86_setup = NULL;
+ free(buf);
+ return log_msg_ret("pro", ret);
+ }
+ priv->info_buf = buf;
+
+ if (!bootflow_img_add(bflow, "kernel",
+ (enum bootflow_img_t)IH_TYPE_KERNEL, 0,
+ priv->body_size))
+ return log_msg_ret("crk", -ENOMEM);
+
+ return 0;
+}
+
+static int cros_read_kernel(struct bootflow *bflow)
+{
+ struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
+ struct cros_priv *priv = bflow->bootmeth_priv;
+ ulong base, setup;
+ ulong num_blks;
+ void *buf;
+ int ret;
+
+ bflow->size = priv->body_size;
+
+ buf = memalign(SZ_1K, priv->body_size);
+ if (!buf)
+ return log_msg_ret("buf", -ENOMEM);
+
+ /* Check that the header is not smaller than permitted */
+ if (priv->body_offset < PROBE_SIZE)
+ return log_msg_ret("san", EFAULT);
+
+ /* Read kernel body */
+ num_blks = priv->body_size >> desc->log2blksz;
+ log_debug("Reading body to %lx, blk=%s, size=%lx, blocks=%lx\n",
+ (ulong)map_to_sysmem(buf), bflow->blk->name, priv->body_size,
+ num_blks);
+ ret = blk_read(bflow->blk,
+ priv->part_start + (priv->body_offset >> desc->log2blksz),
+ num_blks, buf);
+ if (ret != num_blks)
+ return log_msg_ret("inf", -EIO);
+ base = map_to_sysmem(buf) + priv->bootloader_address -
+ priv->body_load_address;
+ setup = base + X86_SETUP_OFFSET;
+
+ bflow->buf = buf;
+ bflow->x86_setup = map_sysmem(setup, 0);
+
+ return 0;
+}
+
+static int cros_read_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+ const struct vb2_kernel_preamble *preamble;
+ struct disk_partition info;
+ struct vb2_keyblock *hdr;
+ const char *uuid = NULL;
+ struct cros_priv *priv;
+ int ret;
+
+ log_debug("starting, part=%x\n", bflow->part);
+
+ /* Check for kernel partitions */
+ ret = scan_part(bflow->blk, bflow->part, &info, &hdr);
+ if (ret) {
+ log_debug("- scan failed: err=%d\n", ret);
+ return log_msg_ret("scan", ret);
+ }
+
+ priv = malloc(sizeof(struct cros_priv));
+ if (!priv) {
+ free(hdr);
+ return log_msg_ret("buf", -ENOMEM);
+ }
+ bflow->bootmeth_priv = priv;
+
+ log_debug("Selected partition %d, header at %lx\n", bflow->part,
+ (ulong)map_to_sysmem(hdr));
+
+ /* Grab a few things from the preamble */
+ preamble = (void *)hdr + hdr->keyblock_size;
+ priv->body_offset = hdr->keyblock_size + preamble->preamble_size;
+ priv->part_start = info.start;
+
+ /* Now read everything we can learn about kernel */
+#if CONFIG_IS_ENABLED(PARTITION_UUIDS)
+ uuid = info.uuid;
+#endif
+ ret = cros_read_info(bflow, uuid, preamble);
+ preamble = NULL;
+ free(hdr);
+ if (ret) {
+ free(priv->info_buf);
+ free(priv);
+ return log_msg_ret("inf", ret);
+ }
+ bflow->size = priv->body_size;
+ bflow->state = BOOTFLOWST_READY;
+
+ return 0;
+}
+
+static int cros_read_file(struct udevice *dev, struct bootflow *bflow,
+ const char *file_path, ulong addr,
+ enum bootflow_img_t type, ulong *sizep)
+{
+ return -ENOSYS;
+}
+
+#if CONFIG_IS_ENABLED(BOOTSTD_FULL)
+static int cros_read_all(struct udevice *dev, struct bootflow *bflow)
+{
+ int ret;
+
+ if (bflow->buf)
+ return log_msg_ret("ld", -EALREADY);
+ ret = cros_read_kernel(bflow);
+ if (ret)
+ return log_msg_ret("rd", ret);
+
+ return 0;
+}
+#endif /* BOOTSTD_FULL */
+
+static int cros_boot(struct udevice *dev, struct bootflow *bflow)
+{
+ int ret;
+
+ if (!bflow->buf) {
+ ret = cros_read_kernel(bflow);
+ if (ret)
+ return log_msg_ret("rd", ret);
+ }
+
+ if (IS_ENABLED(CONFIG_X86)) {
+ ret = zboot_run(map_to_sysmem(bflow->buf), bflow->size, 0, 0,
+ map_to_sysmem(bflow->x86_setup),
+ bflow->cmdline);
+ } else {
+ ret = bootm_boot_start(map_to_sysmem(bflow->buf),
+ bflow->cmdline);
+ }
+
+ return log_msg_ret("go", ret);
+}
+
+static int cros_bootmeth_bind(struct udevice *dev)
+{
+ struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ plat->desc = "ChromiumOS boot";
+ plat->flags = BOOTMETHF_ANY_PART;
+
+ return 0;
+}
+
+static struct bootmeth_ops cros_bootmeth_ops = {
+ .check = cros_check,
+ .read_bootflow = cros_read_bootflow,
+ .read_file = cros_read_file,
+ .boot = cros_boot,
+#if CONFIG_IS_ENABLED(BOOTSTD_FULL)
+ .read_all = cros_read_all,
+#endif /* BOOTSTD_FULL */
+};
+
+static const struct udevice_id cros_bootmeth_ids[] = {
+ { .compatible = "u-boot,cros" },
+ { }
+};
+
+U_BOOT_DRIVER(bootmeth_cros) = {
+ .name = "bootmeth_cros",
+ .id = UCLASS_BOOTMETH,
+ .of_match = cros_bootmeth_ids,
+ .ops = &cros_bootmeth_ops,
+ .bind = cros_bootmeth_bind,
+};
diff --git a/boot/bootmeth_cros.h b/boot/bootmeth_cros.h
new file mode 100644
index 00000000000..8e3038571d1
--- /dev/null
+++ b/boot/bootmeth_cros.h
@@ -0,0 +1,197 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Structures used by the ChromiumOS bootmeth
+ *
+ * See docs at:
+ * https://www.chromium.org/chromium-os/chromiumos-design-docs/verified-boot-data-structures/
+ *
+ * Original code at:
+ * https://chromium.googlesource.com/chromiumos/platform/vboot_reference/+/refs/heads/main/firmware/2lib/include/2struct.h
+ *
+ * Code taken from vboot_reference commit 5b8596ce file 2struct.h
+ *
+ * Copyright 2023 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __BOOTMETH_CROS_H
+#define __BOOTMETH_CROS_H
+
+/* Signature data (a secure hash, possibly signed) */
+struct vb2_signature {
+ /* Offset of signature data from start of this struct */
+ uint32_t sig_offset;
+ uint32_t reserved0;
+
+ /* Size of signature data in bytes */
+ uint32_t sig_size;
+ uint32_t reserved1;
+
+ /* Size of the data block which was signed in bytes */
+ uint32_t data_size;
+ uint32_t reserved2;
+} __attribute__((packed));
+
+#define EXPECTED_VB2_SIGNATURE_SIZE 24
+
+/* Packed public key data */
+struct vb2_packed_key {
+ /* Offset of key data from start of this struct */
+ uint32_t key_offset;
+ uint32_t reserved0;
+
+ /* Size of key data in bytes (NOT strength of key in bits) */
+ uint32_t key_size;
+ uint32_t reserved1;
+
+ /* Signature algorithm used by the key (enum vb2_crypto_algorithm) */
+ uint32_t algorithm;
+ uint32_t reserved2;
+
+ /* Key version */
+ uint32_t key_version;
+ uint32_t reserved3;
+
+ /* TODO: when redoing this struct, add a text description of the key */
+} __attribute__((packed));
+
+#define EXPECTED_VB2_PACKED_KEY_SIZE 32
+
+#define VB2_KEYBLOCK_MAGIC "CHROMEOS"
+#define VB2_KEYBLOCK_MAGIC_SIZE 8
+
+/*
+ * Keyblock, containing the public key used to sign some other chunk of data.
+ *
+ * This should be followed by:
+ * 1) The data_key key data, pointed to by data_key.key_offset.
+ * 2) The checksum data for (vb2_keyblock + data_key data), pointed to
+ * by keyblock_checksum.sig_offset.
+ * 3) The signature data for (vb2_keyblock + data_key data), pointed to
+ * by keyblock_signature.sig_offset.
+ */
+struct vb2_keyblock {
+ /* Magic number */
+ uint8_t magic[VB2_KEYBLOCK_MAGIC_SIZE];
+
+ /* Version of this header format */
+ uint32_t header_version_major;
+ uint32_t header_version_minor;
+
+ /*
+ * Length of this entire keyblock, including keys, signatures, and
+ * padding, in bytes
+ */
+ uint32_t keyblock_size;
+ uint32_t reserved0;
+
+ /*
+ * Signature for this keyblock (header + data pointed to by data_key)
+ * For use with signed data keys
+ */
+ struct vb2_signature keyblock_signature;
+
+ /*
+ * SHA-512 hash for this keyblock (header + data pointed to by
+ * data_key) For use with unsigned data keys.
+ *
+ * Only supported for kernel keyblocks, not firmware keyblocks.
+ */
+ struct vb2_signature keyblock_hash;
+
+ /* Flags for key (VB2_KEYBLOCK_FLAG_*) */
+ uint32_t keyblock_flags;
+ uint32_t reserved1;
+
+ /* Key to verify the chunk of data */
+ struct vb2_packed_key data_key;
+} __attribute__((packed));
+
+#define EXPECTED_VB2_KEYBLOCK_SIZE 112
+
+/*
+ * Preamble block for kernel, version 2.2
+ *
+ * This should be followed by:
+ * 1) The signature data for the kernel body, pointed to by
+ * body_signature.sig_offset.
+ * 2) The signature data for (vb2_kernel_preamble + body signature data),
+ * pointed to by preamble_signature.sig_offset.
+ * 3) The 16-bit vmlinuz header, which is used for reconstruction of
+ * vmlinuz image.
+ */
+struct vb2_kernel_preamble {
+ /*
+ * Size of this preamble, including keys, signatures, vmlinuz header,
+ * and padding, in bytes
+ */
+ uint32_t preamble_size;
+ uint32_t reserved0;
+
+ /* Signature for this preamble (header + body signature) */
+ struct vb2_signature preamble_signature;
+
+ /* Version of this header format */
+ uint32_t header_version_major;
+ uint32_t header_version_minor;
+
+ /* Kernel version */
+ uint32_t kernel_version;
+ uint32_t reserved1;
+
+ /* Load address for kernel body */
+ uint64_t body_load_address;
+ /* TODO (vboot 2.1): we never used that */
+
+ /* Address of bootloader, after body is loaded at body_load_address */
+ uint64_t bootloader_address;
+ /* TODO (vboot 2.1): should be a 32-bit offset */
+
+ /* Size of bootloader in bytes */
+ uint32_t bootloader_size;
+ uint32_t reserved2;
+
+ /* Signature for the kernel body */
+ struct vb2_signature body_signature;
+
+ /*
+ * TODO (vboot 2.1): fields for kernel offset and size. Right now the
+ * size is implicitly the same as the size of data signed by the body
+ * signature, and the offset is implicitly at the end of the preamble.
+ * But that forces us to pad the preamble to 64KB rather than just
+ * having a tiny preamble and an offset field.
+ */
+
+ /*
+ * Fields added in header version 2.1. You must verify the header
+ * version before reading these fields!
+ */
+
+ /*
+ * Address of 16-bit header for vmlinuz reassembly. Readers should
+ * return 0 for header version < 2.1.
+ */
+ uint64_t vmlinuz_header_address;
+
+ /* Size of 16-bit header for vmlinuz in bytes. Readers should return 0
+ for header version < 2.1 */
+ uint32_t vmlinuz_header_size;
+ uint32_t reserved3;
+
+ /*
+ * Fields added in header version 2.2. You must verify the header
+ * version before reading these fields!
+ */
+
+ /*
+ * Flags; see VB2_KERNEL_PREAMBLE_*. Readers should return 0 for
+ * header version < 2.2. Flags field is currently defined as:
+ * [31:2] - Reserved (for future use)
+ * [1:0] - Kernel image type (0b00 - CrOS,
+ * 0b01 - bootimg,
+ * 0b10 - multiboot)
+ */
+ uint32_t flags;
+} __attribute__((packed));
+
+#endif /* __BOOTMETH_CROS_H */
diff --git a/boot/bootmeth_efi.c b/boot/bootmeth_efi.c
new file mode 100644
index 00000000000..0af23df3a4a
--- /dev/null
+++ b/boot/bootmeth_efi.c
@@ -0,0 +1,334 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootmethod for distro boot via EFI
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <command.h>
+#include <dm.h>
+#include <efi.h>
+#include <efi_loader.h>
+#include <env.h>
+#include <fs.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <mmc.h>
+#include <net.h>
+#include <pxe_utils.h>
+#include <linux/sizes.h>
+
+#define EFI_DIRNAME "/EFI/BOOT/"
+
+static int get_efi_pxe_vci(char *str, int max_len)
+{
+ int ret;
+
+ ret = efi_get_pxe_arch();
+ if (ret < 0)
+ return ret;
+
+ snprintf(str, max_len, "PXEClient:Arch:%05x:UNDI:003000", ret);
+
+ return 0;
+}
+
+/**
+ * bootmeth_uses_network() - check if the media device is Ethernet
+ *
+ * @bflow: Bootflow to check
+ * Returns: true if the media device is Ethernet, else false
+ */
+static bool bootmeth_uses_network(struct bootflow *bflow)
+{
+ const struct udevice *media = dev_get_parent(bflow->dev);
+
+ return IS_ENABLED(CONFIG_CMD_DHCP) &&
+ device_get_uclass_id(media) == UCLASS_ETH;
+}
+
+static int efiload_read_file(struct bootflow *bflow, ulong addr)
+{
+ struct blk_desc *desc = NULL;
+ ulong size;
+ int ret;
+
+ if (bflow->blk)
+ desc = dev_get_uclass_plat(bflow->blk);
+
+ size = SZ_1G;
+ ret = bootmeth_common_read_file(bflow->method, bflow, bflow->fname,
+ addr, BFI_EFI, &size);
+ if (ret)
+ return log_msg_ret("rdf", ret);
+ bflow->buf = map_sysmem(addr, bflow->size);
+
+ return 0;
+}
+
+static int distro_efi_check(struct udevice *dev, struct bootflow_iter *iter)
+{
+ /* This only works on block and network devices */
+ if (bootflow_iter_check_blk(iter) && bootflow_iter_check_net(iter))
+ return log_msg_ret("blk", -ENOTSUPP);
+
+ /* This works on block devices and network devices */
+ if (iter->method_flags & BOOTFLOW_METHF_PXE_ONLY)
+ return log_msg_ret("pxe", -ENOTSUPP);
+
+ return 0;
+}
+
+/*
+ * distro_efi_try_bootflow_files() - Check that files are present
+ *
+ * This reads any FDT file and checks whether the bootflow file is present, for
+ * later reading. We avoid reading the bootflow now, since it is likely large,
+ * it may take a long time and we want to avoid needing to allocate memory for
+ * it
+ *
+ * @dev: bootmeth device to use
+ * @bflow: bootflow to update
+ */
+static int distro_efi_try_bootflow_files(struct udevice *dev,
+ struct bootflow *bflow)
+{
+ struct blk_desc *desc = NULL;
+ ulong fdt_addr, size;
+ char fname[256];
+ int ret, seq;
+
+ /* We require a partition table */
+ if (!bflow->part) {
+ log_debug("no partitions\n");
+ return -ENOENT;
+ }
+
+ strcpy(fname, EFI_DIRNAME);
+ strcat(fname, efi_get_basename());
+
+ if (bflow->blk)
+ desc = dev_get_uclass_plat(bflow->blk);
+ ret = bootmeth_try_file(bflow, desc, NULL, fname);
+ if (ret) {
+ log_debug("File '%s' not found\n", fname);
+ return log_msg_ret("try", ret);
+ }
+
+ /* Since we can access the file, let's call it ready */
+ bflow->state = BOOTFLOWST_READY;
+
+ fdt_addr = env_get_hex("fdt_addr_r", 0);
+
+ /* try the various available names */
+ ret = -ENOENT;
+ *fname = '\0';
+ for (seq = 0; ret == -ENOENT; seq++) {
+ ret = efi_get_distro_fdt_name(fname, sizeof(fname), seq);
+ if (ret == -EALREADY)
+ bflow->flags = BOOTFLOWF_USE_PRIOR_FDT;
+ if (!ret) {
+ /* Limit FDT files to 4MB */
+ size = SZ_4M;
+ ret = bootmeth_common_read_file(dev, bflow, fname,
+ fdt_addr, (enum bootflow_img_t)IH_TYPE_FLATDT,
+ &size);
+ }
+ }
+
+ if (*fname) {
+ bflow->fdt_fname = strdup(fname);
+ if (!bflow->fdt_fname)
+ return log_msg_ret("fil", -ENOMEM);
+ }
+
+ if (!ret) {
+ bflow->fdt_size = size;
+ bflow->fdt_addr = fdt_addr;
+
+ /*
+ * TODO: Apply extension overlay
+ *
+ * Here we need to load and apply the extension overlay. This is
+ * not implemented. See do_extension_apply(). The extension
+ * stuff needs an implementation in boot/extension.c so it is
+ * separate from the command code. Really the extension stuff
+ * should use the device tree and a uclass / driver interface
+ * rather than implementing its own list
+ */
+ } else {
+ log_debug("No device tree available\n");
+ bflow->flags |= BOOTFLOWF_USE_BUILTIN_FDT;
+ }
+
+ return 0;
+}
+
+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;
+ int ret, arch, size;
+ ulong addr, fdt_addr;
+ char str[36];
+
+ ret = get_efi_pxe_vci(str, sizeof(str));
+ if (ret)
+ return log_msg_ret("vci", ret);
+ ret = efi_get_pxe_arch();
+ if (ret < 0)
+ return log_msg_ret("arc", ret);
+ arch = ret;
+
+ ret = env_set("bootp_vci", str);
+ if (ret)
+ return log_msg_ret("vcs", ret);
+ ret = env_set_hex("bootp_arch", arch);
+ if (ret)
+ return log_msg_ret("ars", ret);
+
+ /* figure out the load address */
+ addr_str = env_get("kernel_addr_r");
+ addr = addr_str ? hextoul(addr_str, NULL) : image_load_addr;
+
+ /* clear any previous bootfile */
+ env_set("bootfile", NULL);
+
+ /* read the kernel */
+ ret = dhcp_run(addr, NULL, true);
+ if (ret)
+ return log_msg_ret("dhc", ret);
+
+ size = env_get_hex("filesize", -1);
+ if (size <= 0)
+ return log_msg_ret("sz", -EINVAL);
+ bflow->size = size;
+ bflow->buf = map_sysmem(addr, size);
+
+ /* bootfile should be setup by dhcp */
+ bootfile_name = env_get("bootfile");
+ if (!bootfile_name)
+ return log_msg_ret("bootfile_name", ret);
+ bflow->fname = strdup(bootfile_name);
+ if (!bflow->fname)
+ return log_msg_ret("fi0", -ENOMEM);
+
+ /* read the DT file also */
+ fdt_addr_str = env_get("fdt_addr_r");
+ if (!fdt_addr_str)
+ return log_msg_ret("fdt", -EINVAL);
+ fdt_addr = hextoul(fdt_addr_str, NULL);
+ sprintf(file_addr, "%lx", fdt_addr);
+
+ /* We only allow the first prefix with PXE */
+ ret = efi_get_distro_fdt_name(fname, sizeof(fname), 0);
+ if (ret)
+ return log_msg_ret("nam", ret);
+
+ bflow->fdt_fname = strdup(fname);
+ if (!bflow->fdt_fname)
+ return log_msg_ret("fil", -ENOMEM);
+
+ if (!do_tftpb(&cmdtp, 0, 3, tftp_argv)) {
+ bflow->fdt_size = env_get_hex("filesize", 0);
+ bflow->fdt_addr = fdt_addr;
+ } else {
+ log_debug("No device tree available\n");
+ bflow->flags |= BOOTFLOWF_USE_BUILTIN_FDT;
+ }
+
+ bflow->state = BOOTFLOWST_READY;
+
+ return 0;
+}
+
+static int distro_efi_read_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+ int ret;
+
+ log_debug("dev='%s', part=%d\n", bflow->dev->name, bflow->part);
+
+ /*
+ * bootmeth_efi doesn't allocate any buffer neither for blk nor net device
+ * set flag to avoid freeing static buffer.
+ */
+ bflow->flags |= BOOTFLOWF_STATIC_BUF;
+
+ if (bootmeth_uses_network(bflow)) {
+ /* we only support reading from one device, so ignore 'dev' */
+ ret = distro_efi_read_bootflow_net(bflow);
+ if (ret)
+ return log_msg_ret("net", ret);
+ } else {
+ ret = distro_efi_try_bootflow_files(dev, bflow);
+ if (ret)
+ return log_msg_ret("blk", ret);
+ }
+
+ return 0;
+}
+
+static int distro_efi_boot(struct udevice *dev, struct bootflow *bflow)
+{
+ ulong kernel, fdt;
+ int ret;
+
+ log_debug("distro EFI boot\n");
+ kernel = env_get_hex("kernel_addr_r", 0);
+ if (!bootmeth_uses_network(bflow)) {
+ ret = efiload_read_file(bflow, kernel);
+ if (ret)
+ return log_msg_ret("read", ret);
+
+ /*
+ * use the provided device tree if not using the built-in fdt
+ */
+ if (bflow->flags & ~BOOTFLOWF_USE_BUILTIN_FDT)
+ fdt = bflow->fdt_addr;
+
+ }
+
+ if (efi_bootflow_run(bflow))
+ return log_msg_ret("run", -EINVAL);
+
+ return 0;
+}
+
+static int distro_bootmeth_efi_bind(struct udevice *dev)
+{
+ struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ?
+ "EFI boot from an .efi file" : "EFI";
+
+ return 0;
+}
+
+static struct bootmeth_ops distro_efi_bootmeth_ops = {
+ .check = distro_efi_check,
+ .read_bootflow = distro_efi_read_bootflow,
+ .read_file = bootmeth_common_read_file,
+ .boot = distro_efi_boot,
+};
+
+static const struct udevice_id distro_efi_bootmeth_ids[] = {
+ { .compatible = "u-boot,distro-efi" },
+ { }
+};
+
+/* Put a number before 'efi' to provide a default ordering */
+U_BOOT_DRIVER(bootmeth_4efi) = {
+ .name = "bootmeth_efi",
+ .id = UCLASS_BOOTMETH,
+ .of_match = distro_efi_bootmeth_ids,
+ .ops = &distro_efi_bootmeth_ops,
+ .bind = distro_bootmeth_efi_bind,
+};
diff --git a/boot/bootmeth_efi_mgr.c b/boot/bootmeth_efi_mgr.c
new file mode 100644
index 00000000000..42b8863815e
--- /dev/null
+++ b/boot/bootmeth_efi_mgr.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootmethod for EFI boot manager
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <command.h>
+#include <dm.h>
+#include <efi_loader.h>
+#include <efi_variable.h>
+#include <malloc.h>
+
+/**
+ * struct efi_mgr_priv - private info for the efi-mgr driver
+ *
+ * @fake_bootflow: Fake a valid bootflow for testing
+ */
+struct efi_mgr_priv {
+ bool fake_dev;
+};
+
+void sandbox_set_fake_efi_mgr_dev(struct udevice *dev, bool fake_dev)
+{
+ struct efi_mgr_priv *priv = dev_get_priv(dev);
+
+ priv->fake_dev = fake_dev;
+}
+
+static int efi_mgr_check(struct udevice *dev, struct bootflow_iter *iter)
+{
+ int ret;
+
+ /* Must be an bootstd device */
+ ret = bootflow_iter_check_system(iter);
+ if (ret)
+ return log_msg_ret("net", ret);
+
+ return 0;
+}
+
+static int efi_mgr_read_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+ struct efi_mgr_priv *priv = dev_get_priv(dev);
+ efi_status_t ret;
+ efi_uintn_t size;
+ u16 *bootorder;
+
+ if (priv->fake_dev) {
+ bflow->state = BOOTFLOWST_READY;
+ return 0;
+ }
+
+ ret = efi_init_obj_list();
+ if (ret)
+ return log_msg_ret("init", ret);
+
+ /* Enable this method if the "BootOrder" UEFI exists. */
+ bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid,
+ &size);
+ if (bootorder) {
+ free(bootorder);
+ bflow->state = BOOTFLOWST_READY;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int efi_mgr_read_file(struct udevice *dev, struct bootflow *bflow,
+ const char *file_path, ulong addr,
+ enum bootflow_img_t type, ulong *sizep)
+{
+ /* Files are loaded by the 'bootefi bootmgr' command */
+
+ return -ENOSYS;
+}
+
+static int efi_mgr_boot(struct udevice *dev, struct bootflow *bflow)
+{
+ int ret;
+
+ /* Booting is handled by the 'bootefi bootmgr' command */
+ ret = efi_bootmgr_run(EFI_FDT_USE_INTERNAL);
+
+ return 0;
+}
+
+static int bootmeth_efi_mgr_bind(struct udevice *dev)
+{
+ struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ plat->desc = "EFI bootmgr flow";
+ plat->flags = BOOTMETHF_GLOBAL;
+
+ return 0;
+}
+
+static struct bootmeth_ops efi_mgr_bootmeth_ops = {
+ .check = efi_mgr_check,
+ .read_bootflow = efi_mgr_read_bootflow,
+ .read_file = efi_mgr_read_file,
+ .boot = efi_mgr_boot,
+};
+
+static const struct udevice_id efi_mgr_bootmeth_ids[] = {
+ { .compatible = "u-boot,efi-bootmgr" },
+ { }
+};
+
+U_BOOT_DRIVER(bootmeth_3efi_mgr) = {
+ .name = "bootmeth_efi_mgr",
+ .id = UCLASS_BOOTMETH,
+ .of_match = efi_mgr_bootmeth_ids,
+ .ops = &efi_mgr_bootmeth_ops,
+ .bind = bootmeth_efi_mgr_bind,
+ .priv_auto = sizeof(struct efi_mgr_priv),
+};
diff --git a/boot/bootmeth_extlinux.c b/boot/bootmeth_extlinux.c
new file mode 100644
index 00000000000..921d721a27b
--- /dev/null
+++ b/boot/bootmeth_extlinux.c
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootmethod for extlinux boot from a block device
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <asm/cache.h>
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <bootstd.h>
+#include <command.h>
+#include <dm.h>
+#include <extlinux.h>
+#include <fs.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <mmc.h>
+#include <pxe_utils.h>
+
+struct extlinux_plat {
+ bool use_fallback;
+};
+
+enum extlinux_option_type {
+ EO_FALLBACK,
+ EO_INVALID
+};
+
+struct extlinux_option {
+ char *name;
+ enum extlinux_option_type option;
+};
+
+static const struct extlinux_option options[] = {
+ {"fallback", EO_FALLBACK},
+ {NULL, EO_INVALID}
+};
+
+static enum extlinux_option_type get_option(const char *option)
+{
+ int i = 0;
+
+ while (options[i].name) {
+ if (!strcmp(options[i].name, option))
+ return options[i].option;
+
+ i++;
+ }
+
+ return EO_INVALID;
+};
+
+static int extlinux_get_state_desc(struct udevice *dev, char *buf, int maxsize)
+{
+ if (IS_ENABLED(CONFIG_SANDBOX)) {
+ int len;
+
+ len = snprintf(buf, maxsize, "OK");
+
+ return len + 1 < maxsize ? 0 : -ENOSPC;
+ }
+
+ return 0;
+}
+
+static int extlinux_getfile(struct pxe_context *ctx, const char *file_path,
+ char *file_addr, enum bootflow_img_t type,
+ ulong *sizep)
+{
+ struct extlinux_info *info = ctx->userdata;
+ ulong addr;
+ int ret;
+
+ addr = simple_strtoul(file_addr, NULL, 16);
+
+ /* Allow up to 1GB */
+ *sizep = 1 << 30;
+ ret = bootmeth_read_file(info->dev, info->bflow, file_path, addr,
+ type, sizep);
+ if (ret)
+ return log_msg_ret("read", ret);
+
+ return 0;
+}
+
+static int extlinux_check(struct udevice *dev, struct bootflow_iter *iter)
+{
+ int ret;
+
+ /* This only works on block devices */
+ ret = bootflow_iter_check_blk(iter);
+ if (ret)
+ return log_msg_ret("blk", ret);
+
+ return 0;
+}
+
+/**
+ * extlinux_fill_info() - Decode the extlinux file to find out its info
+ *
+ * @bflow: Bootflow to process
+ * @return 0 if OK, -ve on error
+ */
+static int extlinux_fill_info(struct bootflow *bflow)
+{
+ struct membuf mb;
+ char line[200];
+ char *data;
+ int len;
+
+ log_debug("parsing bflow file size %x\n", bflow->size);
+ membuf_init(&mb, bflow->buf, bflow->size);
+ membuf_putraw(&mb, bflow->size, true, &data);
+ while (len = membuf_readline(&mb, line, sizeof(line) - 1, ' ', true), len) {
+ char *tok, *p = line;
+
+ tok = strsep(&p, " ");
+ if (p) {
+ if (!strcmp("label", tok)) {
+ bflow->os_name = strdup(p);
+ if (!bflow->os_name)
+ return log_msg_ret("os", -ENOMEM);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int extlinux_read_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+ struct blk_desc *desc;
+ const char *const *prefixes;
+ struct udevice *bootstd;
+ const char *prefix;
+ loff_t size;
+ int ret, i;
+
+ ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd);
+ if (ret)
+ return log_msg_ret("std", ret);
+
+ /* If a block device, we require a partition table */
+ if (bflow->blk && !bflow->part)
+ return -ENOENT;
+
+ prefixes = bootstd_get_prefixes(bootstd);
+ i = 0;
+ desc = bflow->blk ? dev_get_uclass_plat(bflow->blk) : NULL;
+ do {
+ prefix = prefixes ? prefixes[i] : NULL;
+
+ ret = bootmeth_try_file(bflow, desc, prefix, EXTLINUX_FNAME);
+ } while (ret && prefixes && prefixes[++i]);
+ if (ret)
+ return log_msg_ret("try", ret);
+ size = bflow->size;
+
+ ret = bootmeth_alloc_file(bflow, 0x10000, ARCH_DMA_MINALIGN,
+ BFI_EXTLINUX_CFG);
+ if (ret)
+ return log_msg_ret("read", ret);
+
+ ret = extlinux_fill_info(bflow);
+ if (ret)
+ return log_msg_ret("inf", ret);
+
+ return 0;
+}
+
+static int extlinux_boot(struct udevice *dev, struct bootflow *bflow)
+{
+ struct cmd_tbl cmdtp = {}; /* dummy */
+ struct pxe_context ctx;
+ struct extlinux_info info;
+ struct extlinux_plat *plat;
+ ulong addr;
+ int ret;
+
+ addr = map_to_sysmem(bflow->buf);
+ info.dev = dev;
+ info.bflow = bflow;
+
+ plat = dev_get_plat(dev);
+
+ ret = pxe_setup_ctx(&ctx, &cmdtp, extlinux_getfile, &info, true,
+ bflow->fname, false, plat->use_fallback);
+ if (ret)
+ return log_msg_ret("ctx", -EINVAL);
+
+ ret = pxe_process(&ctx, addr, false);
+ if (ret)
+ return log_msg_ret("bread", -EINVAL);
+
+ return 0;
+}
+
+static int extlinux_set_property(struct udevice *dev, const char *property, const char *value)
+{
+ struct extlinux_plat *plat;
+ static enum extlinux_option_type option;
+
+ plat = dev_get_plat(dev);
+
+ option = get_option(property);
+ if (option == EO_INVALID) {
+ printf("Invalid option\n");
+ return -EINVAL;
+ }
+
+ switch (option) {
+ case EO_FALLBACK:
+ if (!strcmp(value, "1")) {
+ plat->use_fallback = true;
+ } else if (!strcmp(value, "0")) {
+ plat->use_fallback = false;
+ } else {
+ printf("Unexpected value '%s'\n", value);
+ return -EINVAL;
+ }
+ break;
+ default:
+ printf("Unrecognised property '%s'\n", property);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int extlinux_bootmeth_bind(struct udevice *dev)
+{
+ struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ?
+ "Extlinux boot from a block device" : "extlinux";
+
+ return 0;
+}
+
+static struct bootmeth_ops extlinux_bootmeth_ops = {
+ .get_state_desc = extlinux_get_state_desc,
+ .check = extlinux_check,
+ .read_bootflow = extlinux_read_bootflow,
+ .read_file = bootmeth_common_read_file,
+ .boot = extlinux_boot,
+ .set_property = extlinux_set_property,
+};
+
+static const struct udevice_id extlinux_bootmeth_ids[] = {
+ { .compatible = "u-boot,extlinux" },
+ { }
+};
+
+/* Put a number before 'extlinux' to provide a default ordering */
+U_BOOT_DRIVER(bootmeth_1extlinux) = {
+ .name = "bootmeth_extlinux",
+ .id = UCLASS_BOOTMETH,
+ .of_match = extlinux_bootmeth_ids,
+ .ops = &extlinux_bootmeth_ops,
+ .bind = extlinux_bootmeth_bind,
+ .plat_auto = sizeof(struct extlinux_plat)
+};
diff --git a/boot/bootmeth_pxe.c b/boot/bootmeth_pxe.c
new file mode 100644
index 00000000000..faa8d729b15
--- /dev/null
+++ b/boot/bootmeth_pxe.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootmethod for extlinux boot using PXE (network boot)
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <command.h>
+#include <dm.h>
+#include <env.h>
+#include <extlinux.h>
+#include <fs.h>
+#include <log.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <mmc.h>
+#include <net.h>
+#include <pxe_utils.h>
+
+static int extlinux_pxe_getfile(struct pxe_context *ctx, const char *file_path,
+ char *file_addr, enum bootflow_img_t type,
+ ulong *sizep)
+{
+ struct extlinux_info *info = ctx->userdata;
+ ulong addr;
+ int ret;
+
+ addr = simple_strtoul(file_addr, NULL, 16);
+
+ /* Allow up to 1GB */
+ *sizep = 1 << 30;
+ ret = bootmeth_read_file(info->dev, info->bflow, file_path, addr,
+ type, sizep);
+ if (ret)
+ return log_msg_ret("read", ret);
+
+ return 0;
+}
+
+static int extlinux_pxe_check(struct udevice *dev, struct bootflow_iter *iter)
+{
+ int ret;
+
+ /* This only works on network devices */
+ ret = bootflow_iter_check_net(iter);
+ if (ret)
+ return log_msg_ret("net", ret);
+
+ if (iter->method_flags & BOOTFLOW_METHF_DHCP_ONLY)
+ return log_msg_ret("dhcp", -ENOTSUPP);
+
+ return 0;
+}
+
+static int extlinux_pxe_read_bootflow(struct udevice *dev,
+ struct bootflow *bflow)
+{
+ const char *addr_str;
+ char fname[200];
+ char *bootdir;
+ ulong addr;
+ ulong size;
+ char *buf;
+ int ret;
+
+ addr_str = env_get("pxefile_addr_r");
+ if (!addr_str)
+ return log_msg_ret("pxeb", -EPERM);
+ addr = simple_strtoul(addr_str, NULL, 16);
+
+ ret = dhcp_run(addr, NULL, false);
+ if (ret)
+ return log_msg_ret("dhc", ret);
+
+ log_debug("calling pxe_get()\n");
+ ret = pxe_get(addr, &bootdir, &size, false);
+ log_debug("pxe_get() returned %d\n", ret);
+ if (ret)
+ return log_msg_ret("pxeb", ret);
+ bflow->size = size;
+
+ /* Use the directory of the dhcp bootdir as our subdir, if provided */
+ if (bootdir) {
+ const char *last_slash;
+ int path_len;
+
+ last_slash = strrchr(bootdir, '/');
+ if (last_slash) {
+ path_len = (last_slash - bootdir) + 1;
+ bflow->subdir = malloc(path_len + 1);
+ memcpy(bflow->subdir, bootdir, path_len);
+ bflow->subdir[path_len] = '\0';
+ }
+ }
+ snprintf(fname, sizeof(fname), "%s%s",
+ bflow->subdir ? bflow->subdir : "", EXTLINUX_FNAME);
+
+ bflow->fname = strdup(fname);
+ if (!bflow->fname)
+ return log_msg_ret("name", -ENOMEM);
+
+ bflow->state = BOOTFLOWST_READY;
+
+ /* Allocate the buffer, including the \0 byte added by get_pxe_file() */
+ buf = malloc(size + 1);
+ if (!buf)
+ return log_msg_ret("buf", -ENOMEM);
+ memcpy(buf, map_sysmem(addr, 0), size + 1);
+ bflow->buf = buf;
+
+ return 0;
+}
+
+static int extlinux_pxe_read_file(struct udevice *dev, struct bootflow *bflow,
+ const char *file_path, ulong addr,
+ enum bootflow_img_t type, ulong *sizep)
+{
+ char *tftp_argv[] = {"tftp", NULL, NULL, NULL};
+ struct pxe_context *ctx = dev_get_priv(dev);
+ char file_addr[17];
+ ulong size;
+ int ret;
+
+ sprintf(file_addr, "%lx", addr);
+ tftp_argv[1] = file_addr;
+ tftp_argv[2] = (void *)file_path;
+
+ if (do_tftpb(ctx->cmdtp, 0, 3, tftp_argv))
+ return -ENOENT;
+ ret = pxe_get_file_size(&size);
+ if (ret)
+ return log_msg_ret("tftp", ret);
+ if (size > *sizep)
+ return log_msg_ret("spc", -ENOSPC);
+ *sizep = size;
+
+ if (!bootflow_img_add(bflow, file_path, type, addr, size))
+ return log_msg_ret("pxi", -ENOMEM);
+
+ return 0;
+}
+
+static int extlinux_pxe_boot(struct udevice *dev, struct bootflow *bflow)
+{
+ struct pxe_context *ctx = dev_get_priv(dev);
+ struct cmd_tbl cmdtp = {}; /* dummy */
+ struct extlinux_info info;
+ ulong addr;
+ int ret;
+
+ addr = map_to_sysmem(bflow->buf);
+ info.dev = dev;
+ info.bflow = bflow;
+ info.cmdtp = &cmdtp;
+ ret = pxe_setup_ctx(ctx, &cmdtp, extlinux_pxe_getfile, &info, false,
+ bflow->subdir, false, false);
+ if (ret)
+ return log_msg_ret("ctx", -EINVAL);
+
+ ret = pxe_process(ctx, addr, false);
+ if (ret)
+ return log_msg_ret("bread", -EINVAL);
+
+ return 0;
+}
+
+static int extlinux_bootmeth_pxe_bind(struct udevice *dev)
+{
+ struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ?
+ "PXE boot from a network device" : "PXE";
+
+ return 0;
+}
+
+static struct bootmeth_ops extlinux_bootmeth_pxe_ops = {
+ .check = extlinux_pxe_check,
+ .read_bootflow = extlinux_pxe_read_bootflow,
+ .read_file = extlinux_pxe_read_file,
+ .boot = extlinux_pxe_boot,
+};
+
+static const struct udevice_id extlinux_bootmeth_pxe_ids[] = {
+ { .compatible = "u-boot,extlinux-pxe" },
+ { }
+};
+
+U_BOOT_DRIVER(bootmeth_zpxe) = {
+ .name = "bootmeth_pxe",
+ .id = UCLASS_BOOTMETH,
+ .of_match = extlinux_bootmeth_pxe_ids,
+ .ops = &extlinux_bootmeth_pxe_ops,
+ .bind = extlinux_bootmeth_pxe_bind,
+ .priv_auto = sizeof(struct pxe_context),
+};
diff --git a/boot/bootmeth_qfw.c b/boot/bootmeth_qfw.c
new file mode 100644
index 00000000000..028c2481583
--- /dev/null
+++ b/boot/bootmeth_qfw.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootmethod for QEMU qfw
+ *
+ * Copyright 2023 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <command.h>
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <env.h>
+#include <qfw.h>
+#include <dm.h>
+
+static int qfw_check(struct udevice *dev, struct bootflow_iter *iter)
+{
+ const struct udevice *media = dev_get_parent(iter->dev);
+ enum uclass_id id = device_get_uclass_id(media);
+
+ log_debug("media=%s\n", media->name);
+ if (id == UCLASS_QFW)
+ return 0;
+
+ return -ENOTSUPP;
+}
+
+static int qfw_read_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+ struct udevice *qfw_dev = dev_get_parent(bflow->dev);
+ ulong load, initrd;
+ int ret;
+
+ load = env_get_hex("kernel_addr_r", 0);
+ initrd = env_get_hex("ramdisk_addr_r", 0);
+ log_debug("setup kernel %s %lx %lx\n", qfw_dev->name, load, initrd);
+ bflow->name = strdup("qfw");
+ if (!bflow->name)
+ return log_msg_ret("name", -ENOMEM);
+
+ ret = qemu_fwcfg_setup_kernel(qfw_dev, load, initrd);
+ log_debug("setup kernel result %d\n", ret);
+ if (ret)
+ return log_msg_ret("cmd", -EIO);
+
+ bflow->state = BOOTFLOWST_READY;
+
+ return 0;
+}
+
+static int qfw_read_file(struct udevice *dev, struct bootflow *bflow,
+ const char *file_path, ulong addr,
+ enum bootflow_img_t type, ulong *sizep)
+{
+ return -ENOSYS;
+}
+
+static int qfw_boot(struct udevice *dev, struct bootflow *bflow)
+{
+ int ret;
+
+ ret = run_command("booti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdtcontroladdr}",
+ 0);
+ if (ret) {
+ ret = run_command("bootz ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} "
+ "${fdtcontroladdr}", 0);
+ }
+
+ return ret ? -EIO : 0;
+}
+
+static int qfw_bootmeth_bind(struct udevice *dev)
+{
+ struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ plat->desc = "QEMU boot using firmware interface";
+
+ return 0;
+}
+
+static struct bootmeth_ops qfw_bootmeth_ops = {
+ .check = qfw_check,
+ .read_bootflow = qfw_read_bootflow,
+ .read_file = qfw_read_file,
+ .boot = qfw_boot,
+};
+
+static const struct udevice_id qfw_bootmeth_ids[] = {
+ { .compatible = "u-boot,qfw-bootmeth" },
+ { }
+};
+
+U_BOOT_DRIVER(bootmeth_qfw) = {
+ .name = "bootmeth_qfw",
+ .id = UCLASS_BOOTMETH,
+ .of_match = qfw_bootmeth_ids,
+ .ops = &qfw_bootmeth_ops,
+ .bind = qfw_bootmeth_bind,
+};
diff --git a/boot/bootmeth_rauc.c b/boot/bootmeth_rauc.c
new file mode 100644
index 00000000000..cc6180221ed
--- /dev/null
+++ b/boot/bootmeth_rauc.c
@@ -0,0 +1,436 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootmethod for distro boot with RAUC
+ *
+ * Copyright 2025 PHYTEC Messtechnik GmbH
+ * Written by Martin Schwan <m.schwan@phytec.de>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <blk.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <bootstd.h>
+#include <dm.h>
+#include <env.h>
+#include <fs.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <string.h>
+#include <asm/cache.h>
+
+/* Length of env var "BOOT_*_LEFT" */
+#define BOOT_LEFT_LEN (5 + 32 + 5)
+
+static const char * const script_names[] = { "boot.scr", "boot.scr.uimg", NULL };
+
+/**
+ * struct distro_rauc_slot - Slot information
+ *
+ * A slot describes the unit of a bootable system consisting of one or multiple
+ * partitions. This usually includes a root filesystem, kernel and potentially other
+ * files, like device trees and boot scripts for that particular distribution.
+ *
+ * @name The slot name
+ * @boot_part The boot partition number on disk
+ * @root_part The root partition number on disk
+ */
+struct distro_rauc_slot {
+ char *name;
+ int boot_part;
+ int root_part;
+};
+
+/**
+ * struct distro_rauc_priv - Private data
+ *
+ * @slots All slots of the device in default order
+ * @boot_order String of the current boot order containing the active slot names
+ */
+struct distro_rauc_priv {
+ struct distro_rauc_slot **slots;
+};
+
+static struct distro_rauc_slot *get_slot(struct distro_rauc_priv *priv,
+ const char *slot_name)
+{
+ int i;
+
+ for (i = 0; priv->slots[i]->name; i++) {
+ if (!strcmp(priv->slots[i]->name, slot_name))
+ return priv->slots[i];
+ }
+
+ return NULL;
+}
+
+static int distro_rauc_check(struct udevice *dev, struct bootflow_iter *iter)
+{
+ /*
+ * This distro only works on whole MMC devices, as multiple partitions
+ * are needed for an A/B system.
+ */
+ if (bootflow_iter_check_mmc(iter))
+ return log_msg_ret("mmc", -EOPNOTSUPP);
+ if (iter->part)
+ return log_msg_ret("part", -EOPNOTSUPP);
+
+ return 0;
+}
+
+static int distro_rauc_scan_parts(struct bootflow *bflow)
+{
+ struct blk_desc *desc;
+ struct distro_rauc_priv *priv;
+ char *boot_order;
+ const char **boot_order_list;
+ int ret;
+ int i;
+
+ if (bflow->blk)
+ desc = dev_get_uclass_plat(bflow->blk);
+
+ priv = bflow->bootmeth_priv;
+ if (!priv || !priv->slots)
+ return log_msg_ret("priv", -EINVAL);
+
+ boot_order = env_get("BOOT_ORDER");
+ boot_order_list = str_to_list(boot_order);
+ for (i = 0; boot_order_list[i]; i++) {
+ const struct distro_rauc_slot *slot;
+
+ slot = get_slot(priv, boot_order_list[i]);
+ if (!slot)
+ return log_msg_ret("slot", -EINVAL);
+ if (desc) {
+ ret = fs_set_blk_dev_with_part(desc, slot->boot_part);
+ if (ret)
+ return log_msg_ret("part", ret);
+ fs_close();
+ ret = fs_set_blk_dev_with_part(desc, slot->root_part);
+ if (ret)
+ return log_msg_ret("part", ret);
+ fs_close();
+ }
+ }
+ str_free_list(boot_order_list);
+
+ return 0;
+}
+
+static int distro_rauc_read_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+ struct distro_rauc_priv *priv;
+ int ret;
+ char *slot;
+ int i;
+ char *partitions;
+ char *boot_order;
+ const char *default_boot_order;
+ const char **default_boot_order_list;
+ char *boot_order_copy;
+ char boot_left[BOOT_LEFT_LEN];
+ char *parts;
+
+ /* Get RAUC variables or set their default values */
+ boot_order = env_get("BOOT_ORDER");
+ if (!boot_order) {
+ log_debug("BOOT_ORDER did not exist yet, setting default value\n");
+ if (env_set("BOOT_ORDER", CONFIG_BOOTMETH_RAUC_BOOT_ORDER))
+ return log_msg_ret("env", -EPERM);
+ boot_order = CONFIG_BOOTMETH_RAUC_BOOT_ORDER;
+ }
+ default_boot_order = CONFIG_BOOTMETH_RAUC_BOOT_ORDER;
+ default_boot_order_list = str_to_list(default_boot_order);
+ for (i = 0; default_boot_order_list[i]; i++) {
+ sprintf(boot_left, "BOOT_%s_LEFT", default_boot_order_list[i]);
+ if (!env_get(boot_left)) {
+ log_debug("%s did not exist yet, setting default value\n",
+ boot_left);
+ if (env_set_ulong(boot_left, CONFIG_BOOTMETH_RAUC_DEFAULT_TRIES))
+ return log_msg_ret("env", -EPERM);
+ }
+ }
+ str_free_list(default_boot_order_list);
+
+ priv = calloc(1, sizeof(struct distro_rauc_priv));
+ if (!priv)
+ return log_msg_ret("buf", -ENOMEM);
+ priv->slots = calloc(1, sizeof(struct distro_rauc_slot));
+
+ /* Copy default boot_order, so we can leave the original unmodified */
+ boot_order_copy = strdup(default_boot_order);
+ partitions = strdup(CONFIG_BOOTMETH_RAUC_PARTITIONS);
+
+ for (i = 1;
+ (parts = strsep(&partitions, " ")) &&
+ (slot = strsep(&boot_order_copy, " "));
+ i++) {
+ struct distro_rauc_slot *s;
+ struct distro_rauc_slot **new_slots;
+
+ s = calloc(1, sizeof(struct distro_rauc_slot));
+ s->name = strdup(slot);
+ s->boot_part = simple_strtoul(strsep(&parts, ","), NULL, 10);
+ s->root_part = simple_strtoul(strsep(&parts, ","), NULL, 10);
+ new_slots = realloc(priv->slots, (i + 1) *
+ sizeof(struct distro_rauc_slot));
+ if (!new_slots)
+ return log_msg_ret("buf", -ENOMEM);
+ priv->slots = new_slots;
+ priv->slots[i - 1] = s;
+ priv->slots[i] = NULL;
+ }
+
+ bflow->bootmeth_priv = priv;
+
+ ret = distro_rauc_scan_parts(bflow);
+ if (ret < 0) {
+ for (i = 0; priv->slots[i]->name; i++) {
+ free(priv->slots[i]->name);
+ free(priv->slots[i]);
+ }
+ free(priv);
+ free(boot_order_copy);
+ bflow->bootmeth_priv = NULL;
+ return ret;
+ }
+
+ bflow->state = BOOTFLOWST_READY;
+
+ return 0;
+}
+
+static int distro_rauc_read_file(struct udevice *dev, struct bootflow *bflow,
+ const char *file_path, ulong addr,
+ enum bootflow_img_t type, ulong *sizep)
+{
+ /*
+ * Reading individual files is not supported since we only operate on
+ * whole MMC devices (because we require multiple partitions).
+ */
+ return log_msg_ret("Unsupported", -ENOSYS);
+}
+
+static int distro_rauc_load_boot_script(struct bootflow *bflow,
+ const struct distro_rauc_slot *slot)
+{
+ struct blk_desc *desc;
+ struct distro_rauc_priv *priv;
+ struct udevice *bootstd;
+ const char *const *prefixes;
+ int ret;
+ int i;
+ int j;
+
+ ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd);
+ if (ret)
+ return log_msg_ret("std", ret);
+ prefixes = bootstd_get_prefixes(bootstd);
+
+ desc = dev_get_uclass_plat(bflow->blk);
+ priv = bflow->bootmeth_priv;
+ if (!priv || !priv->slots)
+ return log_msg_ret("priv", -EINVAL);
+
+ bflow->part = slot->boot_part;
+ if (!bflow->part)
+ return log_msg_ret("part", -ENOENT);
+
+ ret = bootmeth_setup_fs(bflow, desc);
+ if (ret)
+ return log_msg_ret("set", ret);
+
+ for (i = 0; prefixes[i] && bflow->state != BOOTFLOWST_FILE; i++) {
+ for (j = 0; script_names[j] && bflow->state != BOOTFLOWST_FILE; j++) {
+ if (!bootmeth_try_file(bflow, desc, prefixes[i], script_names[j])) {
+ log_debug("Found file '%s%s' in %s.part_%x\n",
+ prefixes[i], script_names[j],
+ bflow->dev->name, bflow->part);
+ bflow->subdir = strdup(prefixes[i]);
+ }
+ }
+ }
+ if (bflow->state != BOOTFLOWST_FILE)
+ return log_msg_ret("file", -ENOENT);
+
+ ret = bootmeth_alloc_file(bflow, 0x10000, ARCH_DMA_MINALIGN,
+ (enum bootflow_img_t)IH_TYPE_SCRIPT);
+ if (ret)
+ return log_msg_ret("read", ret);
+
+ return 0;
+}
+
+static int find_active_slot(char **slot_name, ulong *slot_tries)
+{
+ ulong tries;
+ char boot_left[BOOT_LEFT_LEN];
+ char *boot_order;
+ const char **boot_order_list;
+ bool slot_found = false;
+ int ret;
+ int i;
+
+ boot_order = env_get("BOOT_ORDER");
+ if (!boot_order)
+ return log_msg_ret("env", -ENOENT);
+ boot_order_list = str_to_list(boot_order);
+ for (i = 0; boot_order_list[i] && !slot_found; i++) {
+ sprintf(boot_left, "BOOT_%s_LEFT", boot_order_list[i]);
+ tries = env_get_ulong(boot_left, 10, ULONG_MAX);
+ if (tries == ULONG_MAX)
+ return log_msg_ret("env", -ENOENT);
+
+ if (tries) {
+ ret = env_set_ulong(boot_left, tries - 1);
+ if (ret)
+ return log_msg_ret("env", ret);
+ *slot_name = strdup(boot_order_list[i]);
+ *slot_tries = tries;
+ slot_found = true;
+ }
+ }
+ str_free_list(boot_order_list);
+
+ if (!slot_found) {
+ if (IS_ENABLED(CONFIG_BOOTMETH_RAUC_RESET_ALL_ZERO_TRIES)) {
+ log_warning("WARNING: No valid slot found\n");
+ log_info("INFO: Resetting boot order and all slot tries\n");
+ boot_order_list = str_to_list(CONFIG_BOOTMETH_RAUC_BOOT_ORDER);
+ for (i = 0; boot_order_list[i]; i++) {
+ sprintf(boot_left, "BOOT_%s_LEFT", boot_order_list[i]);
+ ret = env_set_ulong(boot_left, CONFIG_BOOTMETH_RAUC_DEFAULT_TRIES);
+ if (ret)
+ return log_msg_ret("env", ret);
+ }
+ str_free_list(boot_order_list);
+ ret = env_save();
+ if (ret)
+ return log_msg_ret("env", ret);
+ do_reset(NULL, 0, 0, NULL);
+ }
+ log_err("ERROR: No valid slot found\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int distro_rauc_boot(struct udevice *dev, struct bootflow *bflow)
+{
+ struct blk_desc *desc;
+ struct distro_rauc_priv *priv;
+ const struct distro_rauc_slot *slot;
+ char *boot_order;
+ const char **boot_order_list;
+ char *active_slot;
+ ulong active_slot_tries;
+ char raucargs[64];
+ char boot_left[BOOT_LEFT_LEN];
+ ulong addr;
+ int ret = 0;
+ int i;
+
+ desc = dev_get_uclass_plat(bflow->blk);
+ if (desc->uclass_id != UCLASS_MMC)
+ return log_msg_ret("blk", -EINVAL);
+ priv = bflow->bootmeth_priv;
+
+ /* Device info variables */
+ ret = env_set("devtype", blk_get_devtype(bflow->blk));
+ if (ret)
+ return log_msg_ret("env", ret);
+
+ ret = env_set_hex("devnum", desc->devnum);
+ if (ret)
+ return log_msg_ret("env", ret);
+
+ /* Find active, valid slot */
+ ret = find_active_slot(&active_slot, &active_slot_tries);
+ if (ret)
+ return log_msg_ret("env", ret);
+
+ /* Kernel command line arguments */
+ sprintf(raucargs, "rauc.slot=%s", active_slot);
+ ret = env_set("raucargs", raucargs);
+ if (ret)
+ return log_msg_ret("env", ret);
+
+ /* Active slot info */
+ slot = get_slot(priv, active_slot);
+ if (!slot)
+ return log_msg_ret("env", -ENOENT);
+ ret = env_set_hex("distro_bootpart", slot->boot_part);
+ if (ret)
+ return log_msg_ret("env", ret);
+ ret = env_set_hex("distro_rootpart", slot->root_part);
+ if (ret)
+ return log_msg_ret("env", ret);
+ ret = env_save();
+ if (ret)
+ return log_msg_ret("env", ret);
+
+ /* Load distro boot script */
+ ret = distro_rauc_load_boot_script(bflow, slot);
+ if (ret)
+ return log_msg_ret("load", ret);
+
+ log_info("INFO: Booting slot %s, %lu of %d tries left\n",
+ active_slot, active_slot_tries, CONFIG_BOOTMETH_RAUC_DEFAULT_TRIES);
+
+ log_debug("devtype: %s\n", env_get("devtype"));
+ log_debug("devnum: %s\n", env_get("devnum"));
+ log_debug("distro_bootpart: %s\n", env_get("distro_bootpart"));
+ log_debug("distro_rootpart: %s\n", env_get("distro_rootpart"));
+ log_debug("raucargs: %s\n", env_get("raucargs"));
+ boot_order = env_get("BOOT_ORDER");
+ if (!boot_order)
+ return log_msg_ret("env", -EPERM);
+ log_debug("BOOT_ORDER: %s\n", boot_order);
+ boot_order_list = str_to_list(boot_order);
+ for (i = 0; boot_order_list[i]; i++) {
+ sprintf(boot_left, "BOOT_%s_LEFT", boot_order_list[i]);
+ log_debug("%s: %s\n", boot_left, env_get(boot_left));
+ }
+ str_free_list(boot_order_list);
+
+ /* Run distro boot script */
+ addr = map_to_sysmem(bflow->buf);
+ ret = cmd_source_script(addr, NULL, NULL);
+ if (ret)
+ return log_msg_ret("boot", ret);
+
+ return 0;
+}
+
+static int distro_rauc_bootmeth_bind(struct udevice *dev)
+{
+ struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ plat->desc = "RAUC distro boot from MMC";
+ plat->flags = BOOTMETHF_GLOBAL;
+
+ return 0;
+}
+
+static struct bootmeth_ops distro_rauc_bootmeth_ops = {
+ .check = distro_rauc_check,
+ .read_bootflow = distro_rauc_read_bootflow,
+ .read_file = distro_rauc_read_file,
+ .boot = distro_rauc_boot,
+};
+
+static const struct udevice_id distro_rauc_bootmeth_ids[] = {
+ { .compatible = "u-boot,distro-rauc" },
+ { }
+};
+
+U_BOOT_DRIVER(bootmeth_rauc) = {
+ .name = "bootmeth_rauc",
+ .id = UCLASS_BOOTMETH,
+ .of_match = distro_rauc_bootmeth_ids,
+ .ops = &distro_rauc_bootmeth_ops,
+ .bind = distro_rauc_bootmeth_bind,
+};
diff --git a/boot/bootmeth_sandbox.c b/boot/bootmeth_sandbox.c
new file mode 100644
index 00000000000..92ba2e3f050
--- /dev/null
+++ b/boot/bootmeth_sandbox.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootmethod for sandbox testing
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <dm.h>
+
+static int sandbox_check(struct udevice *dev, struct bootflow_iter *iter)
+{
+ return 0;
+}
+
+static int sandbox_read_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+ /* pretend we are ready */
+ bflow->state = BOOTFLOWST_READY;
+
+ return 0;
+}
+
+static int sandbox_read_file(struct udevice *dev, struct bootflow *bflow,
+ const char *file_path, ulong addr,
+ enum bootflow_img_t type, ulong *sizep)
+{
+ return -ENOSYS;
+}
+
+static int sandbox_boot(struct udevice *dev, struct bootflow *bflow)
+{
+ /* always fail: see bootflow_iter_disable() */
+ return -ENOTSUPP;
+}
+
+static int sandbox_bootmeth_bind(struct udevice *dev)
+{
+ struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ plat->desc = "Sandbox boot for testing";
+
+ return 0;
+}
+
+static struct bootmeth_ops sandbox_bootmeth_ops = {
+ .check = sandbox_check,
+ .read_bootflow = sandbox_read_bootflow,
+ .read_file = sandbox_read_file,
+ .boot = sandbox_boot,
+};
+
+static const struct udevice_id sandbox_bootmeth_ids[] = {
+ { .compatible = "u-boot,sandbox-bootmeth" },
+ { }
+};
+
+U_BOOT_DRIVER(bootmeth_sandbox) = {
+ .name = "bootmeth_sandbox",
+ .id = UCLASS_BOOTMETH,
+ .of_match = sandbox_bootmeth_ids,
+ .ops = &sandbox_bootmeth_ops,
+ .bind = sandbox_bootmeth_bind,
+};
diff --git a/boot/bootmeth_script.c b/boot/bootmeth_script.c
new file mode 100644
index 00000000000..020cb8a7aec
--- /dev/null
+++ b/boot/bootmeth_script.c
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootmethod for booting via a U-Boot script
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <blk.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <bootstd.h>
+#include <dm.h>
+#include <env.h>
+#include <fs.h>
+#include <image.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <net.h>
+
+#define SCRIPT_FNAME1 "boot.scr.uimg"
+#define SCRIPT_FNAME2 "boot.scr"
+
+static int script_check(struct udevice *dev, struct bootflow_iter *iter)
+{
+ /* This works on block devices, network devices and SPI Flash */
+ if (iter->method_flags & BOOTFLOW_METHF_PXE_ONLY)
+ return log_msg_ret("pxe", -ENOTSUPP);
+
+ return 0;
+}
+
+/**
+ * script_fill_info() - Decode the U-Boot script to find out distro info
+ *
+ * @bflow: Bootflow to process
+ * @return 0 if OK, -ve on error
+ */
+static int script_fill_info(struct bootflow *bflow)
+{
+ char *name = NULL;
+ char *data;
+ uint len;
+ int ret;
+
+ log_debug("parsing bflow file size %x\n", bflow->size);
+
+ ret = image_locate_script(bflow->buf, bflow->size, NULL, NULL, &data, &len);
+ if (!ret) {
+ if (strstr(data, "armbianEnv"))
+ name = "Armbian";
+ }
+
+ if (name) {
+ bflow->os_name = strdup(name);
+ if (!bflow->os_name)
+ return log_msg_ret("os", -ENOMEM);
+ }
+
+ return 0;
+}
+
+static int script_read_bootflow_file(struct udevice *bootstd,
+ struct bootflow *bflow)
+{
+ struct blk_desc *desc = NULL;
+ const char *const *prefixes;
+ const char *prefix;
+ int ret, i;
+
+ ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd);
+ if (ret)
+ return log_msg_ret("std", ret);
+
+ if (bflow->blk) {
+ /* We require a partition table */
+ if (!bflow->part)
+ return -ENOENT;
+ desc = dev_get_uclass_plat(bflow->blk);
+ }
+
+ prefixes = bootstd_get_prefixes(bootstd);
+ i = 0;
+ do {
+ prefix = prefixes ? prefixes[i] : NULL;
+
+ ret = bootmeth_try_file(bflow, desc, prefix, SCRIPT_FNAME1);
+ if (ret)
+ ret = bootmeth_try_file(bflow, desc, prefix,
+ SCRIPT_FNAME2);
+ } while (ret && prefixes && prefixes[++i]);
+ if (ret)
+ return log_msg_ret("try", ret);
+
+ bflow->subdir = strdup(prefix ? prefix : "");
+ if (!bflow->subdir)
+ return log_msg_ret("prefix", -ENOMEM);
+
+ ret = bootmeth_alloc_file(bflow, 0x10000, ARCH_DMA_MINALIGN,
+ (enum bootflow_img_t)IH_TYPE_SCRIPT);
+ if (ret)
+ return log_msg_ret("read", ret);
+
+ ret = script_fill_info(bflow);
+ if (ret)
+ return log_msg_ret("inf", ret);
+
+ ret = bootmeth_alloc_other(bflow, "boot.bmp", BFI_LOGO,
+ &bflow->logo, &bflow->logo_size);
+ /* ignore error */
+
+ return 0;
+}
+
+static int script_read_bootflow_net(struct bootflow *bflow)
+{
+ const char *addr_str;
+ int size, ret;
+ char *fname;
+ ulong addr;
+
+ /* figure out the load address */
+ addr_str = env_get("scriptaddr");
+ addr = addr_str ? hextoul(addr_str, NULL) : image_load_addr;
+
+ fname = env_get("boot_script_dhcp");
+ if (!fname)
+ return log_msg_ret("dhc", -EINVAL);
+
+ ret = dhcp_run(addr, fname, true);
+ if (ret)
+ return log_msg_ret("dhc", ret);
+
+ size = env_get_hex("filesize", 0);
+ if (!size)
+ return log_msg_ret("sz", -EINVAL);
+
+ bflow->buf = malloc(size + 1);
+ if (!bflow->buf)
+ return log_msg_ret("buf", -ENOMEM);
+ memcpy(bflow->buf, map_sysmem(addr, size), size);
+ bflow->buf[size] = '\0';
+ bflow->size = size;
+ bflow->state = BOOTFLOWST_READY;
+
+ return 0;
+}
+
+static int script_read_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+ const struct udevice *media = dev_get_parent(bflow->dev);
+ struct udevice *bootstd;
+ int ret;
+
+ ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd);
+ if (ret)
+ return log_msg_ret("std", ret);
+
+ if (IS_ENABLED(CONFIG_CMD_DHCP) &&
+ device_get_uclass_id(media) == UCLASS_ETH) {
+ /* we only support reading from one device, so ignore 'dev' */
+ ret = script_read_bootflow_net(bflow);
+ if (ret)
+ return log_msg_ret("net", ret);
+ } else {
+ ret = script_read_bootflow_file(bootstd, bflow);
+ if (ret)
+ return log_msg_ret("blk", ret);
+ }
+
+ return 0;
+}
+
+static int script_set_bootflow(struct udevice *dev, struct bootflow *bflow,
+ char *buf, int size)
+{
+ buf[size] = '\0';
+ bflow->buf = buf;
+ bflow->size = size;
+ bflow->state = BOOTFLOWST_READY;
+
+ return 0;
+}
+
+static int script_boot(struct udevice *dev, struct bootflow *bflow)
+{
+ struct blk_desc *desc;
+ ulong addr;
+ int ret = 0;
+
+ if (bflow->blk) {
+ desc = dev_get_uclass_plat(bflow->blk);
+ if (desc->uclass_id == UCLASS_USB) {
+ ret = env_set("devtype", "usb");
+ } else {
+ /*
+ * If the uclass is AHCI, but the driver is ATA
+ * (not scsi), set devtype to sata
+ */
+ if (IS_ENABLED(CONFIG_SATA) &&
+ desc->uclass_id == UCLASS_AHCI)
+ ret = env_set("devtype", "sata");
+ else
+ ret = env_set("devtype", blk_get_devtype(bflow->blk));
+ }
+ if (!ret)
+ ret = env_set_hex("devnum", desc->devnum);
+ if (!ret)
+ ret = env_set_hex("distro_bootpart", bflow->part);
+ if (!ret)
+ ret = env_set("prefix", bflow->subdir);
+ if (!ret && IS_ENABLED(CONFIG_ARCH_SUNXI) &&
+ !strcmp("mmc", blk_get_devtype(bflow->blk)))
+ ret = env_set_hex("mmc_bootdev", desc->devnum);
+ } else {
+ const struct udevice *media = dev_get_parent(bflow->dev);
+
+ ret = env_set("devtype",
+ uclass_get_name(device_get_uclass_id(media)));
+ if (!ret)
+ ret = env_set_hex("devnum", dev_seq(media));
+ }
+ if (ret)
+ return log_msg_ret("env", ret);
+
+ log_debug("devtype: %s\n", env_get("devtype"));
+ log_debug("devnum: %s\n", env_get("devnum"));
+ log_debug("distro_bootpart: %s\n", env_get("distro_bootpart"));
+ log_debug("prefix: %s\n", env_get("prefix"));
+ log_debug("mmc_bootdev: %s\n", env_get("mmc_bootdev"));
+
+ addr = map_to_sysmem(bflow->buf);
+ ret = cmd_source_script(addr, NULL, NULL);
+ if (ret)
+ return log_msg_ret("boot", ret);
+
+ return 0;
+}
+
+static int script_bootmeth_bind(struct udevice *dev)
+{
+ struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ?
+ "Script boot from a block device" : "script";
+
+ return 0;
+}
+
+static struct bootmeth_ops script_bootmeth_ops = {
+ .check = script_check,
+ .read_bootflow = script_read_bootflow,
+ .set_bootflow = script_set_bootflow,
+ .read_file = bootmeth_common_read_file,
+ .boot = script_boot,
+};
+
+static const struct udevice_id script_bootmeth_ids[] = {
+ { .compatible = "u-boot,script" },
+ { }
+};
+
+/* Put a number before 'script' to provide a default ordering */
+U_BOOT_DRIVER(bootmeth_2script) = {
+ .name = "bootmeth_script",
+ .id = UCLASS_BOOTMETH,
+ .of_match = script_bootmeth_ids,
+ .ops = &script_bootmeth_ops,
+ .bind = script_bootmeth_bind,
+};
diff --git a/boot/bootretry.c b/boot/bootretry.c
new file mode 100644
index 00000000000..a60767eaa2e
--- /dev/null
+++ b/boot/bootretry.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2000
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ */
+
+#include <stdio.h>
+#include <bootretry.h>
+#include <cli.h>
+#include <env.h>
+#include <errno.h>
+#include <time.h>
+#include <vsprintf.h>
+#include <watchdog.h>
+
+static uint64_t endtime; /* must be set, default is instant timeout */
+static int retry_time = -1; /* -1 so can call readline before main_loop */
+
+/***************************************************************************
+ * initialize command line timeout
+ */
+void bootretry_init_cmd_timeout(void)
+{
+ char *s = env_get("bootretry");
+
+ if (s != NULL)
+ retry_time = (int)simple_strtol(s, NULL, 10);
+ else
+ retry_time = CONFIG_BOOT_RETRY_TIME;
+
+ if (retry_time >= 0 && retry_time < CONFIG_BOOT_RETRY_MIN)
+ retry_time = CONFIG_BOOT_RETRY_MIN;
+}
+
+/***************************************************************************
+ * reset command line timeout to retry_time seconds
+ */
+void bootretry_reset_cmd_timeout(void)
+{
+ /* Parse changes to bootretry */
+ bootretry_init_cmd_timeout();
+ endtime = endtick(retry_time);
+}
+
+int bootretry_tstc_timeout(void)
+{
+ while (!tstc()) { /* while no incoming data */
+ if (retry_time >= 0 && get_ticks() > endtime)
+ return -ETIMEDOUT;
+ schedule();
+ }
+
+ return 0;
+}
+
+void bootretry_dont_retry(void)
+{
+ retry_time = -1;
+}
diff --git a/boot/bootstd-uclass.c b/boot/bootstd-uclass.c
new file mode 100644
index 00000000000..8c0fd4e63c3
--- /dev/null
+++ b/boot/bootstd-uclass.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Uclass implementation for standard boot
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <alist.h>
+#include <bootflow.h>
+#include <bootstd.h>
+#include <dm.h>
+#include <env.h>
+#include <log.h>
+#include <malloc.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <dm/read.h>
+#include <dm/uclass-internal.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/* These are used if filename-prefixes is not present */
+const char *const default_prefixes[] = {"/", "/boot/", NULL};
+
+static int bootstd_of_to_plat(struct udevice *dev)
+{
+ struct bootstd_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ if (IS_ENABLED(CONFIG_BOOTSTD_FULL)) {
+ /* Don't check errors since livetree and flattree are different */
+ ret = dev_read_string_list(dev, "filename-prefixes",
+ &priv->prefixes);
+ dev_read_string_list(dev, "bootdev-order",
+ &priv->bootdev_order);
+
+ priv->theme = ofnode_find_subnode(dev_ofnode(dev), "theme");
+ }
+
+ return 0;
+}
+
+static void bootstd_clear_glob_(struct bootstd_priv *priv)
+{
+ struct bootflow *bflow;
+
+ alist_for_each(bflow, &priv->bootflows)
+ bootflow_remove(bflow);
+ alist_empty(&priv->bootflows);
+}
+
+void bootstd_clear_glob(void)
+{
+ struct bootstd_priv *std;
+
+ if (bootstd_get_priv(&std))
+ return;
+
+ bootstd_clear_glob_(std);
+}
+
+int bootstd_add_bootflow(struct bootflow *bflow)
+{
+ struct bootstd_priv *std;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return ret;
+
+ ret = std->bootflows.count;
+ bflow = alist_add(&std->bootflows, *bflow);
+ if (!bflow)
+ return log_msg_ret("bf2", -ENOMEM);
+
+ return ret;
+}
+
+int bootstd_clear_bootflows_for_bootdev(struct udevice *dev)
+{
+ struct bootstd_priv *std = bootstd_try_priv();
+ struct bootflow *from, *to;
+
+ /* if bootstd does not exist we cannot have any bootflows */
+ if (!std)
+ return 0;
+
+ /* Drop any bootflows that mention this dev */
+ alist_for_each_filter(from, to, &std->bootflows) {
+ if (from->dev == dev)
+ bootflow_remove(from);
+ else
+ *to++ = *from;
+ }
+ alist_update_end(&std->bootflows, to);
+
+ return 0;
+}
+
+static int bootstd_remove(struct udevice *dev)
+{
+ struct bootstd_priv *priv = dev_get_priv(dev);
+
+ free(priv->prefixes);
+ free(priv->bootdev_order);
+ bootstd_clear_glob_(priv);
+
+ return 0;
+}
+
+const char *const *const bootstd_get_bootdev_order(struct udevice *dev,
+ bool *okp)
+{
+ struct bootstd_priv *std = dev_get_priv(dev);
+ const char *targets = env_get("boot_targets");
+
+ *okp = true;
+ log_debug("- targets %s %p\n", targets, std->bootdev_order);
+ if (targets && *targets) {
+ str_free_list(std->env_order);
+ std->env_order = str_to_list(targets);
+ if (!std->env_order) {
+ *okp = false;
+ return NULL;
+ }
+ return std->env_order;
+ }
+
+ return std->bootdev_order;
+}
+
+const char *const *const bootstd_get_prefixes(struct udevice *dev)
+{
+ struct bootstd_priv *std = dev_get_priv(dev);
+
+ return std->prefixes ? std->prefixes : default_prefixes;
+}
+
+struct bootstd_priv *bootstd_try_priv(void)
+{
+ struct udevice *dev;
+
+ dev = uclass_try_first_device(UCLASS_BOOTSTD);
+ if (!dev || !device_active(dev))
+ return NULL;
+
+ return dev_get_priv(dev);
+}
+
+int bootstd_get_priv(struct bootstd_priv **stdp)
+{
+ struct udevice *dev;
+ int ret;
+
+ ret = uclass_first_device_err(UCLASS_BOOTSTD, &dev);
+ if (ret)
+ return ret;
+ *stdp = dev_get_priv(dev);
+
+ return 0;
+}
+
+static int bootstd_probe(struct udevice *dev)
+{
+ struct bootstd_priv *std = dev_get_priv(dev);
+
+ alist_init_struct(&std->bootflows, struct bootflow);
+
+ return 0;
+}
+
+/* For now, bind the bootmethod device if none are found in the devicetree */
+int dm_scan_other(bool pre_reloc_only)
+{
+ struct driver *drv = ll_entry_start(struct driver, driver);
+ const int n_ents = ll_entry_count(struct driver, driver);
+ struct udevice *dev, *bootstd;
+ int i, ret;
+
+ /* These are not needed before relocation */
+ if (!(gd->flags & GD_FLG_RELOC))
+ return 0;
+
+ /* Create a bootstd device if needed */
+ uclass_find_first_device(UCLASS_BOOTSTD, &bootstd);
+ if (!bootstd) {
+ ret = device_bind_driver(gd->dm_root, "bootstd_drv", "bootstd",
+ &bootstd);
+ if (ret)
+ return log_msg_ret("bootstd", ret);
+ }
+
+ /* If there are no bootmeth devices, create them */
+ uclass_find_first_device(UCLASS_BOOTMETH, &dev);
+ if (dev)
+ return 0;
+
+ for (i = 0; i < n_ents; i++, drv++) {
+ if (drv->id == UCLASS_BOOTMETH) {
+ const char *name = drv->name;
+
+ if (!strncmp("bootmeth_", name, 9))
+ name += 9;
+ ret = device_bind(bootstd, drv, name, 0, ofnode_null(),
+ &dev);
+ if (ret)
+ return log_msg_ret("meth", ret);
+ }
+ }
+
+ return 0;
+}
+
+static const struct udevice_id bootstd_ids[] = {
+ { .compatible = "u-boot,boot-std" },
+ { }
+};
+
+U_BOOT_DRIVER(bootstd_drv) = {
+ .id = UCLASS_BOOTSTD,
+ .name = "bootstd_drv",
+ .of_to_plat = bootstd_of_to_plat,
+ .probe = bootstd_probe,
+ .remove = bootstd_remove,
+ .of_match = bootstd_ids,
+ .priv_auto = sizeof(struct bootstd_priv),
+};
+
+UCLASS_DRIVER(bootstd) = {
+ .id = UCLASS_BOOTSTD,
+ .name = "bootstd",
+#if CONFIG_IS_ENABLED(OF_REAL)
+ .post_bind = dm_scan_fdt_dev,
+#endif
+};
diff --git a/boot/cedit.c b/boot/cedit.c
new file mode 100644
index 00000000000..56dc7c6af15
--- /dev/null
+++ b/boot/cedit.c
@@ -0,0 +1,888 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Implementation of configuration editor
+ *
+ * Copyright 2023 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY LOGC_EXPO
+
+#include <abuf.h>
+#include <cedit.h>
+#include <cli.h>
+#include <dm.h>
+#include <env.h>
+#include <expo.h>
+#include <malloc.h>
+#include <menu.h>
+#include <rtc.h>
+#include <video.h>
+#include <linux/delay.h>
+#include "scene_internal.h"
+#include <u-boot/schedule.h>
+
+enum {
+ CMOS_MAX_BITS = 2048,
+ CMOS_MAX_BYTES = CMOS_MAX_BITS / 8,
+};
+
+#define CMOS_BYTE(bit) ((bit) / 8)
+#define CMOS_BIT(bit) ((bit) % 8)
+
+/**
+ * struct cedit_iter_priv - private data for cedit operations
+ *
+ * @buf: Buffer to use when writing settings to the devicetree
+ * @node: Node to read from when reading settings from devicetree
+ * @verbose: true to show writing to environment variables
+ * @mask: Mask bits for the CMOS RAM. If a bit is set the byte containing it
+ * will be written
+ * @value: Value bits for CMOS RAM. This is the actual value written
+ * @dev: RTC device to write to
+ */
+struct cedit_iter_priv {
+ struct abuf *buf;
+ ofnode node;
+ bool verbose;
+ u8 *mask;
+ u8 *value;
+ struct udevice *dev;
+};
+
+int cedit_arange(struct expo *exp, struct video_priv *vpriv, uint scene_id)
+{
+ struct expo_arrange_info arr;
+ struct scene_obj_txt *txt;
+ struct scene_obj *obj;
+ struct scene *scn;
+ int y, ret;
+
+ scn = expo_lookup_scene_id(exp, scene_id);
+ if (!scn)
+ return log_msg_ret("scn", -ENOENT);
+
+ txt = scene_obj_find_by_name(scn, "prompt");
+ if (txt)
+ scene_obj_set_pos(scn, txt->obj.id, 0, vpriv->ysize - 50);
+
+ txt = scene_obj_find_by_name(scn, "title");
+ if (txt)
+ scene_obj_set_pos(scn, txt->obj.id, 200, 10);
+
+ memset(&arr, '\0', sizeof(arr));
+ ret = scene_calc_arrange(scn, &arr);
+ if (ret < 0)
+ return log_msg_ret("arr", ret);
+
+ y = 100;
+ list_for_each_entry(obj, &scn->obj_head, sibling) {
+ switch (obj->type) {
+ case SCENEOBJT_NONE:
+ case SCENEOBJT_IMAGE:
+ case SCENEOBJT_TEXT:
+ case SCENEOBJT_BOX:
+ case SCENEOBJT_TEXTEDIT:
+ break;
+ case SCENEOBJT_MENU:
+ scene_obj_set_pos(scn, obj->id, 50, y);
+ scene_menu_arrange(scn, &arr,
+ (struct scene_obj_menu *)obj);
+ y += 50;
+ break;
+ case SCENEOBJT_TEXTLINE:
+ scene_obj_set_pos(scn, obj->id, 50, y);
+ scene_textline_arrange(scn, &arr,
+ (struct scene_obj_textline *)obj);
+ y += 50;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int cedit_prepare(struct expo *exp, struct udevice *vid_dev,
+ struct scene **scnp)
+{
+ struct udevice *dev = vid_dev;
+ struct video_priv *vid_priv;
+ struct scene *scn;
+ uint scene_id;
+ int ret;
+
+ /* For now we only support a video console */
+ ret = expo_set_display(exp, dev);
+ if (ret)
+ return log_msg_ret("dis", ret);
+
+ ret = expo_first_scene_id(exp);
+ if (ret < 0)
+ return log_msg_ret("scn", ret);
+ scene_id = ret;
+
+ ret = expo_set_scene_id(exp, scene_id);
+ if (ret)
+ return log_msg_ret("sid", ret);
+
+ exp->popup = true;
+ exp->show_highlight = true;
+
+ /* This is not supported for now */
+ if (0)
+ expo_set_text_mode(exp, true);
+
+ vid_priv = dev_get_uclass_priv(dev);
+
+ scn = expo_lookup_scene_id(exp, scene_id);
+ scene_highlight_first(scn);
+
+ cedit_arange(exp, vid_priv, scene_id);
+
+ ret = expo_calc_dims(exp);
+ if (ret)
+ return log_msg_ret("dim", ret);
+
+ *scnp = scn;
+
+ return scene_id;
+}
+
+int cedit_do_action(struct expo *exp, struct scene *scn,
+ struct video_priv *vid_priv, struct expo_action *act)
+{
+ int ret;
+
+ switch (act->type) {
+ case EXPOACT_NONE:
+ return -EAGAIN;
+ case EXPOACT_POINT_ITEM:
+ ret = scene_menu_select_item(scn, scn->highlight_id,
+ act->select.id);
+ if (ret)
+ return log_msg_ret("cdp", ret);
+ break;
+ case EXPOACT_POINT_OBJ:
+ scene_set_highlight_id(scn, act->select.id);
+ cedit_arange(exp, vid_priv, scn->id);
+ break;
+ case EXPOACT_OPEN:
+ scene_set_open(scn, act->select.id, true);
+ cedit_arange(exp, vid_priv, scn->id);
+ switch (scn->highlight_id) {
+ case EXPOID_SAVE:
+ exp->done = true;
+ exp->save = true;
+ break;
+ case EXPOID_DISCARD:
+ exp->done = true;
+ break;
+ }
+ break;
+ case EXPOACT_CLOSE:
+ scene_set_open(scn, act->select.id, false);
+ cedit_arange(exp, vid_priv, scn->id);
+ break;
+ case EXPOACT_SELECT:
+ scene_set_open(scn, scn->highlight_id, false);
+ cedit_arange(exp, vid_priv, scn->id);
+ break;
+ case EXPOACT_QUIT:
+ log_debug("quitting\n");
+ exp->done = true;
+ break;
+ }
+
+ return 0;
+}
+
+int cedit_run(struct expo *exp)
+{
+ struct video_priv *vid_priv;
+ struct udevice *dev;
+ struct scene *scn;
+ uint scene_id;
+ int ret;
+
+ ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
+ if (ret)
+ return log_msg_ret("vid", ret);
+ vid_priv = dev_get_uclass_priv(dev);
+
+ ret = cedit_prepare(exp, dev, &scn);
+ if (ret < 0)
+ return log_msg_ret("prep", ret);
+ scene_id = ret;
+
+ exp->done = false;
+ exp->save = false;
+ do {
+ struct expo_action act;
+
+ ret = expo_render(exp);
+ if (ret)
+ return log_msg_ret("cer", ret);
+
+ ret = expo_poll(exp, &act);
+ if (!ret)
+ cedit_do_action(exp, scn, vid_priv, &act);
+ else if (ret != -EAGAIN)
+ return log_msg_ret("cep", ret);
+ } while (!exp->done);
+
+ if (ret)
+ return log_msg_ret("end", ret);
+ if (!exp->save)
+ return -EACCES;
+
+ return 0;
+}
+
+static int check_space(int ret, struct abuf *buf)
+{
+ if (ret == -FDT_ERR_NOSPACE) {
+ if (!abuf_realloc_inc(buf, CEDIT_SIZE_INC))
+ return log_msg_ret("spc", -ENOMEM);
+ ret = fdt_resize(abuf_data(buf), abuf_data(buf),
+ abuf_size(buf));
+ if (ret)
+ return log_msg_ret("res", -EFAULT);
+ }
+
+ return 0;
+}
+
+/**
+ * get_cur_menuitem_text() - Get the text of the currently selected item
+ *
+ * Looks up the object for the current item, finds text object for it and looks
+ * up the string for that text
+ *
+ * @menu: Menu to look at
+ * @strp: Returns a pointer to the next
+ * Return: 0 if OK, -ENOENT if something was not found
+ */
+static int get_cur_menuitem_text(const struct scene_obj_menu *menu,
+ const char **strp)
+{
+ struct scene *scn = menu->obj.scene;
+ const struct scene_menitem *mi;
+ const struct scene_obj_txt *txt;
+ const char *str;
+
+ mi = scene_menuitem_find(menu, menu->cur_item_id);
+ if (!mi)
+ return log_msg_ret("mi", -ENOENT);
+
+ txt = scene_obj_find(scn, mi->label_id, SCENEOBJT_TEXT);
+ if (!txt)
+ return log_msg_ret("txt", -ENOENT);
+
+ str = expo_get_str(scn->expo, txt->gen.str_id);
+ if (!str)
+ return log_msg_ret("str", -ENOENT);
+ *strp = str;
+
+ return 0;
+}
+
+/**
+ * get_cur_menuitem_val() - Get the value of a menu's current item
+ *
+ * Obtains the value of the current item in the menu. If no value, then
+ * enumerates the items of a menu (0, 1, 2) and returns the sequence number of
+ * the currently selected item. If the first item is selected, this returns 0;
+ * if the second, 1; etc.
+ *
+ * @menu: Menu to check
+ * @valp: Returns current-item value / sequence number
+ * Return: 0 on success, else -ve error value
+ */
+static int get_cur_menuitem_val(const struct scene_obj_menu *menu, int *valp)
+{
+ const struct scene_menitem *mi;
+ int seq;
+
+ seq = 0;
+ list_for_each_entry(mi, &menu->item_head, sibling) {
+ if (mi->id == menu->cur_item_id) {
+ *valp = mi->value == INT_MAX ? seq : mi->value;
+ return 0;
+ }
+ seq++;
+ }
+
+ return log_msg_ret("nf", -ENOENT);
+}
+
+/**
+ * write_dt_string() - Write a string to the devicetree, expanding if needed
+ *
+ * If this fails, it tries again after expanding the devicetree a little
+ *
+ * @buf: Buffer containing the devicetree
+ * @name: Property name to use
+ * @str: String value
+ * Return: 0 if OK, -EFAULT if something went horribly wrong
+ */
+static int write_dt_string(struct abuf *buf, const char *name, const char *str)
+{
+ int ret, i;
+
+ ret = -EAGAIN;
+ for (i = 0; ret && i < 2; i++) {
+ ret = fdt_property_string(abuf_data(buf), name, str);
+ if (!i) {
+ ret = check_space(ret, buf);
+ if (ret)
+ return log_msg_ret("rs2", -ENOMEM);
+ }
+ }
+
+ /* this should not happen */
+ if (ret)
+ return log_msg_ret("str", -EFAULT);
+
+ return 0;
+}
+
+/**
+ * write_dt_u32() - Write an int to the devicetree, expanding if needed
+ *
+ * If this fails, it tries again after expanding the devicetree a little
+ *
+ * @buf: Buffer containing the devicetree
+ * @name: Property name to use
+ * @lva: Integer value
+ * Return: 0 if OK, -EFAULT if something went horribly wrong
+ */
+static int write_dt_u32(struct abuf *buf, const char *name, uint val)
+{
+ int ret, i;
+
+ /* write the text of the current item */
+ ret = -EAGAIN;
+ for (i = 0; ret && i < 2; i++) {
+ ret = fdt_property_u32(abuf_data(buf), name, val);
+ if (!i) {
+ ret = check_space(ret, buf);
+ if (ret)
+ return log_msg_ret("rs2", -ENOMEM);
+ }
+ }
+
+ /* this should not happen */
+ if (ret)
+ return log_msg_ret("str", -EFAULT);
+
+ return 0;
+}
+
+static int h_write_settings(struct scene_obj *obj, void *vpriv)
+{
+ struct cedit_iter_priv *priv = vpriv;
+ struct abuf *buf = priv->buf;
+ int ret;
+
+ switch (obj->type) {
+ case SCENEOBJT_NONE:
+ case SCENEOBJT_IMAGE:
+ case SCENEOBJT_TEXT:
+ case SCENEOBJT_BOX:
+ case SCENEOBJT_TEXTEDIT:
+ break;
+ case SCENEOBJT_TEXTLINE: {
+ const struct scene_obj_textline *tline;
+
+ tline = (struct scene_obj_textline *)obj;
+ ret = write_dt_string(buf, obj->name, abuf_data(&tline->buf));
+ if (ret)
+ return log_msg_ret("wr2", ret);
+ break;
+ }
+ case SCENEOBJT_MENU: {
+ const struct scene_obj_menu *menu;
+ const char *str;
+ char name[80];
+ int val;
+
+ /* write the ID of the current item */
+ menu = (struct scene_obj_menu *)obj;
+ ret = write_dt_u32(buf, obj->name, menu->cur_item_id);
+ if (ret)
+ return log_msg_ret("wrt", ret);
+
+ snprintf(name, sizeof(name), "%s-value", obj->name);
+ ret = get_cur_menuitem_val(menu, &val);
+ if (ret < 0)
+ return log_msg_ret("cur", ret);
+ ret = write_dt_u32(buf, name, val);
+ if (ret)
+ return log_msg_ret("wr2", ret);
+
+ ret = get_cur_menuitem_text(menu, &str);
+ if (ret)
+ return log_msg_ret("mis", ret);
+
+ /* write the text of the current item */
+ snprintf(name, sizeof(name), "%s-str", obj->name);
+ ret = write_dt_string(buf, name, str);
+ if (ret)
+ return log_msg_ret("wr2", ret);
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int cedit_write_settings(struct expo *exp, struct abuf *buf)
+{
+ struct cedit_iter_priv priv;
+ void *fdt;
+ int ret;
+
+ if (!abuf_init_size(buf, CEDIT_SIZE_INC))
+ return log_msg_ret("buf", -ENOMEM);
+
+ fdt = abuf_data(buf);
+ ret = fdt_create(fdt, abuf_size(buf));
+ if (!ret)
+ ret = fdt_finish_reservemap(fdt);
+ if (!ret)
+ ret = fdt_begin_node(fdt, "");
+ if (!ret)
+ ret = fdt_begin_node(fdt, CEDIT_NODE_NAME);
+ if (ret) {
+ log_debug("Failed to start FDT (err=%d)\n", ret);
+ return log_msg_ret("sta", -EINVAL);
+ }
+
+ /* write out the items */
+ priv.buf = buf;
+ ret = expo_iter_scene_objs(exp, h_write_settings, &priv);
+ if (ret) {
+ log_debug("Failed to write settings (err=%d)\n", ret);
+ return log_msg_ret("set", ret);
+ }
+
+ ret = fdt_end_node(fdt);
+ if (!ret)
+ ret = fdt_end_node(fdt);
+ if (!ret)
+ ret = fdt_finish(fdt);
+ if (ret) {
+ log_debug("Failed to finish FDT (err=%d)\n", ret);
+ return log_msg_ret("fin", -EINVAL);
+ }
+
+ return 0;
+}
+
+static int h_read_settings(struct scene_obj *obj, void *vpriv)
+{
+ struct cedit_iter_priv *priv = vpriv;
+ ofnode node = priv->node;
+
+ switch (obj->type) {
+ case SCENEOBJT_NONE:
+ case SCENEOBJT_IMAGE:
+ case SCENEOBJT_TEXT:
+ case SCENEOBJT_BOX:
+ case SCENEOBJT_TEXTEDIT:
+ break;
+ case SCENEOBJT_TEXTLINE: {
+ const struct scene_obj_textline *tline;
+ const char *val;
+ int len;
+
+ tline = (struct scene_obj_textline *)obj;
+
+ val = ofnode_read_prop(node, obj->name, &len);
+ if (len >= tline->max_chars)
+ return log_msg_ret("str", -ENOSPC);
+ strcpy(abuf_data(&tline->buf), val);
+ break;
+ }
+ case SCENEOBJT_MENU: {
+ struct scene_obj_menu *menu;
+ uint val;
+
+ if (ofnode_read_u32(node, obj->name, &val))
+ return log_msg_ret("rd", -ENOENT);
+ menu = (struct scene_obj_menu *)obj;
+ menu->cur_item_id = val;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int cedit_read_settings(struct expo *exp, oftree tree)
+{
+ struct cedit_iter_priv priv;
+ ofnode root, node;
+ int ret;
+
+ root = oftree_root(tree);
+ if (!ofnode_valid(root))
+ return log_msg_ret("roo", -ENOENT);
+ node = ofnode_find_subnode(root, CEDIT_NODE_NAME);
+ if (!ofnode_valid(node))
+ return log_msg_ret("pat", -ENOENT);
+
+ /* read in the items */
+ priv.node = node;
+ ret = expo_iter_scene_objs(exp, h_read_settings, &priv);
+ if (ret) {
+ log_debug("Failed to read settings (err=%d)\n", ret);
+ return log_msg_ret("set", ret);
+ }
+
+ return 0;
+}
+
+static int h_write_settings_env(struct scene_obj *obj, void *vpriv)
+{
+ const struct scene_obj_menu *menu;
+ struct cedit_iter_priv *priv = vpriv;
+ char name[80], var[60];
+ const char *str;
+ int val, ret;
+
+ if (obj->id < EXPOID_BASE_ID)
+ return 0;
+
+ snprintf(var, sizeof(var), "c.%s", obj->name);
+
+ switch (obj->type) {
+ case SCENEOBJT_NONE:
+ case SCENEOBJT_IMAGE:
+ case SCENEOBJT_TEXT:
+ case SCENEOBJT_BOX:
+ case SCENEOBJT_TEXTEDIT:
+ break;
+ case SCENEOBJT_MENU:
+ menu = (struct scene_obj_menu *)obj;
+ val = menu->cur_item_id;
+
+ if (priv->verbose)
+ printf("%s=%d\n", var, val);
+
+ ret = env_set_ulong(var, val);
+ if (ret)
+ return log_msg_ret("set", ret);
+
+ ret = get_cur_menuitem_text(menu, &str);
+ if (ret)
+ return log_msg_ret("mis", ret);
+
+ snprintf(name, sizeof(name), "c.%s-str", obj->name);
+ if (priv->verbose)
+ printf("%s=%s\n", name, str);
+
+ ret = env_set(name, str);
+ if (ret)
+ return log_msg_ret("st2", ret);
+
+ ret = get_cur_menuitem_val(menu, &val);
+ if (ret < 0)
+ return log_msg_ret("cur", ret);
+ snprintf(name, sizeof(name), "c.%s-value", obj->name);
+ if (priv->verbose)
+ printf("%s=%d\n", name, val);
+
+ break;
+ case SCENEOBJT_TEXTLINE: {
+ const struct scene_obj_textline *tline;
+
+ tline = (struct scene_obj_textline *)obj;
+ str = abuf_data(&tline->buf);
+ ret = env_set(var, str);
+ if (ret)
+ return log_msg_ret("set", ret);
+
+ if (priv->verbose)
+ printf("%s=%s\n", var, str);
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int cedit_write_settings_env(struct expo *exp, bool verbose)
+{
+ struct cedit_iter_priv priv;
+ int ret;
+
+ /* write out the items */
+ priv.verbose = verbose;
+ ret = expo_iter_scene_objs(exp, h_write_settings_env, &priv);
+ if (ret) {
+ log_debug("Failed to write settings to env (err=%d)\n", ret);
+ return log_msg_ret("set", ret);
+ }
+
+ return 0;
+}
+
+static int h_read_settings_env(struct scene_obj *obj, void *vpriv)
+{
+ struct cedit_iter_priv *priv = vpriv;
+ struct scene_obj_menu *menu;
+ char var[60];
+ int val;
+
+ if (obj->id < EXPOID_BASE_ID)
+ return 0;
+
+ snprintf(var, sizeof(var), "c.%s", obj->name);
+
+ switch (obj->type) {
+ case SCENEOBJT_NONE:
+ case SCENEOBJT_IMAGE:
+ case SCENEOBJT_TEXT:
+ case SCENEOBJT_BOX:
+ case SCENEOBJT_TEXTEDIT:
+ break;
+ case SCENEOBJT_MENU:
+ menu = (struct scene_obj_menu *)obj;
+ val = env_get_ulong(var, 10, 0);
+ if (priv->verbose)
+ printf("%s=%d\n", var, val);
+ if (!val)
+ return log_msg_ret("get", -ENOENT);
+
+ /*
+ * note that no validation is done here, to make sure the ID is
+ * valid and actually points to a menu item
+ */
+ menu->cur_item_id = val;
+ break;
+ case SCENEOBJT_TEXTLINE: {
+ const struct scene_obj_textline *tline;
+ const char *value;
+
+ tline = (struct scene_obj_textline *)obj;
+ value = env_get(var);
+ if (value && strlen(value) >= tline->max_chars)
+ return log_msg_ret("str", -ENOSPC);
+ if (!value)
+ value = "";
+ if (priv->verbose)
+ printf("%s=%s\n", var, value);
+ strcpy(abuf_data(&tline->buf), value);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int cedit_read_settings_env(struct expo *exp, bool verbose)
+{
+ struct cedit_iter_priv priv;
+ int ret;
+
+ /* write out the items */
+ priv.verbose = verbose;
+ ret = expo_iter_scene_objs(exp, h_read_settings_env, &priv);
+ if (ret) {
+ log_debug("Failed to read settings from env (err=%d)\n", ret);
+ return log_msg_ret("set", ret);
+ }
+
+ return 0;
+}
+
+static int h_write_settings_cmos(struct scene_obj *obj, void *vpriv)
+{
+ const struct scene_obj_menu *menu;
+ struct cedit_iter_priv *priv = vpriv;
+ int val, ret;
+ uint i;
+
+ if (obj->type != SCENEOBJT_MENU || obj->id < EXPOID_BASE_ID)
+ return 0;
+
+ menu = (struct scene_obj_menu *)obj;
+ val = menu->cur_item_id;
+
+ ret = get_cur_menuitem_val(menu, &val);
+ if (ret < 0)
+ return log_msg_ret("cur", ret);
+ log_debug("%s: val=%d\n", menu->obj.name, val);
+
+ /* figure out where to place this item */
+ if (!obj->bit_length)
+ return log_msg_ret("len", -EINVAL);
+ if (obj->start_bit + obj->bit_length > CMOS_MAX_BITS)
+ return log_msg_ret("bit", -E2BIG);
+
+ for (i = 0; i < obj->bit_length; i++, val >>= 1) {
+ uint bitnum = obj->start_bit + i;
+
+ priv->mask[CMOS_BYTE(bitnum)] |= 1 << CMOS_BIT(bitnum);
+ if (val & 1)
+ priv->value[CMOS_BYTE(bitnum)] |= BIT(CMOS_BIT(bitnum));
+ log_debug("bit %x %x %x\n", bitnum,
+ priv->mask[CMOS_BYTE(bitnum)],
+ priv->value[CMOS_BYTE(bitnum)]);
+ }
+
+ return 0;
+}
+
+int cedit_write_settings_cmos(struct expo *exp, struct udevice *dev,
+ bool verbose)
+{
+ struct cedit_iter_priv priv;
+ int ret, i, count, first, last;
+
+ /* write out the items */
+ priv.mask = calloc(1, CMOS_MAX_BYTES);
+ if (!priv.mask)
+ return log_msg_ret("mas", -ENOMEM);
+ priv.value = calloc(1, CMOS_MAX_BYTES);
+ if (!priv.value) {
+ free(priv.mask);
+ return log_msg_ret("val", -ENOMEM);
+ }
+
+ ret = expo_iter_scene_objs(exp, h_write_settings_cmos, &priv);
+ if (ret) {
+ log_debug("Failed to write CMOS (err=%d)\n", ret);
+ ret = log_msg_ret("set", ret);
+ goto done;
+ }
+
+ /* write the data to the RTC */
+ log_debug("Writing CMOS\n");
+ first = CMOS_MAX_BYTES;
+ last = -1;
+ for (i = 0, count = 0; i < CMOS_MAX_BYTES; i++) {
+ if (priv.mask[i]) {
+ log_debug("Write byte %x: %x\n", i, priv.value[i]);
+ ret = rtc_write8(dev, i, priv.value[i]);
+ if (ret) {
+ ret = log_msg_ret("wri", ret);
+ goto done;
+ }
+ count++;
+ first = min(first, i);
+ last = max(last, i);
+ }
+ }
+ if (verbose) {
+ printf("Write %d bytes from offset %x to %x\n", count, first,
+ last);
+ }
+
+done:
+ free(priv.mask);
+ free(priv.value);
+ return ret;
+}
+
+static int h_read_settings_cmos(struct scene_obj *obj, void *vpriv)
+{
+ struct cedit_iter_priv *priv = vpriv;
+ const struct scene_menitem *mi;
+ struct scene_obj_menu *menu;
+ int val, ret;
+ uint i;
+
+ if (obj->type != SCENEOBJT_MENU || obj->id < EXPOID_BASE_ID)
+ return 0;
+
+ menu = (struct scene_obj_menu *)obj;
+
+ /* figure out where to place this item */
+ if (!obj->bit_length)
+ return log_msg_ret("len", -EINVAL);
+ if (obj->start_bit + obj->bit_length > CMOS_MAX_BITS)
+ return log_msg_ret("bit", -E2BIG);
+
+ val = 0;
+ for (i = 0; i < obj->bit_length; i++) {
+ uint bitnum = obj->start_bit + i;
+ uint offset = CMOS_BYTE(bitnum);
+
+ /* read the byte if not already read */
+ if (!priv->mask[offset]) {
+ ret = rtc_read8(priv->dev, offset);
+ if (ret < 0)
+ return log_msg_ret("rea", ret);
+ priv->value[offset] = ret;
+
+ /* mark it as read */
+ priv->mask[offset] = 0xff;
+ }
+
+ if (priv->value[offset] & BIT(CMOS_BIT(bitnum)))
+ val |= BIT(i);
+ log_debug("bit %x %x\n", bitnum, val);
+ }
+
+ /* update the current item */
+ log_debug("look for menuitem value %d in menu %d\n", val, menu->obj.id);
+ mi = scene_menuitem_find_val(menu, val);
+ if (!mi)
+ return log_msg_ret("seq", -ENOENT);
+
+ menu->cur_item_id = mi->id;
+ log_debug("Update menu %d cur_item_id %d\n", menu->obj.id, mi->id);
+
+ return 0;
+}
+
+int cedit_read_settings_cmos(struct expo *exp, struct udevice *dev,
+ bool verbose)
+{
+ struct cedit_iter_priv priv;
+ int ret, i, count, first, last;
+
+ /* read in the items */
+ priv.mask = calloc(1, CMOS_MAX_BYTES);
+ if (!priv.mask)
+ return log_msg_ret("mas", -ENOMEM);
+ priv.value = calloc(1, CMOS_MAX_BYTES);
+ if (!priv.value) {
+ free(priv.mask);
+ return log_msg_ret("val", -ENOMEM);
+ }
+ priv.dev = dev;
+
+ ret = expo_iter_scene_objs(exp, h_read_settings_cmos, &priv);
+ if (ret) {
+ log_debug("Failed to read CMOS (err=%d)\n", ret);
+ ret = log_msg_ret("set", ret);
+ goto done;
+ }
+
+ /* indicate what bytes were read from the RTC */
+ first = CMOS_MAX_BYTES;
+ last = -1;
+ for (i = 0, count = 0; i < CMOS_MAX_BYTES; i++) {
+ if (priv.mask[i]) {
+ log_debug("Read byte %x: %x\n", i, priv.value[i]);
+ count++;
+ first = min(first, i);
+ last = max(last, i);
+ }
+ }
+ if (verbose) {
+ printf("Read %d bytes from offset %x to %x\n", count, first,
+ last);
+ }
+
+done:
+ free(priv.mask);
+ free(priv.value);
+ return ret;
+}
diff --git a/boot/common_fit.c b/boot/common_fit.c
new file mode 100644
index 00000000000..a2f9b8d83c3
--- /dev/null
+++ b/boot/common_fit.c
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2016 Google, Inc
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <errno.h>
+#include <image.h>
+#include <log.h>
+#include <linux/libfdt.h>
+
+ulong fdt_getprop_u32(const void *fdt, int node, const char *prop)
+{
+ const u32 *cell;
+ int len;
+
+ cell = fdt_getprop(fdt, node, prop, &len);
+ if (!cell || len != sizeof(*cell))
+ return FDT_ERROR;
+
+ return fdt32_to_cpu(*cell);
+}
+
+__weak int board_fit_config_name_match(const char *name)
+{
+ return -EINVAL;
+}
+
+/*
+ * Iterate over all /configurations subnodes and call a platform specific
+ * function to find the matching configuration.
+ * Returns the node offset or a negative error number.
+ */
+int fit_find_config_node(const void *fdt)
+{
+ const char *name;
+ int conf, node, len;
+ const char *dflt_conf_name;
+ const char *dflt_conf_desc = NULL;
+ int dflt_conf_node = -ENOENT;
+
+ conf = fdt_path_offset(fdt, FIT_CONFS_PATH);
+ if (conf < 0) {
+ debug("%s: Cannot find /configurations node: %d\n", __func__,
+ conf);
+ return -EINVAL;
+ }
+
+ dflt_conf_name = fdt_getprop(fdt, conf, "default", &len);
+
+ for (node = fdt_first_subnode(fdt, conf);
+ node >= 0;
+ node = fdt_next_subnode(fdt, node)) {
+ name = fdt_getprop(fdt, node, "description", &len);
+ if (!name) {
+#ifdef CONFIG_SPL_LIBCOMMON_SUPPORT
+ printf("%s: Missing FDT description in DTB\n",
+ __func__);
+#endif
+ return -EINVAL;
+ }
+
+ if (dflt_conf_name) {
+ const char *node_name = fdt_get_name(fdt, node, NULL);
+ if (strcmp(dflt_conf_name, node_name) == 0) {
+ dflt_conf_node = node;
+ dflt_conf_desc = name;
+ }
+ }
+
+ if (board_fit_config_name_match(name))
+ continue;
+
+ debug("Selecting config '%s'\n", name);
+
+ return node;
+ }
+
+ if (dflt_conf_node != -ENOENT) {
+ debug("Selecting default config '%s'\n", dflt_conf_desc);
+ return dflt_conf_node;
+ }
+
+ return -ENOENT;
+}
diff --git a/boot/expo.c b/boot/expo.c
new file mode 100644
index 00000000000..94413acd381
--- /dev/null
+++ b/boot/expo.c
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Implementation of a expo, a collection of scenes providing menu options
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY LOGC_EXPO
+
+#include <dm.h>
+#include <expo.h>
+#include <log.h>
+#include <malloc.h>
+#include <menu.h>
+#include <video.h>
+#include <watchdog.h>
+#include <linux/delay.h>
+#include "scene_internal.h"
+
+int expo_new(const char *name, void *priv, struct expo **expp)
+{
+ struct expo *exp;
+
+ exp = calloc(1, sizeof(struct expo));
+ if (!exp)
+ return log_msg_ret("expo", -ENOMEM);
+ exp->name = strdup(name);
+ if (!exp->name) {
+ free(exp);
+ return log_msg_ret("name", -ENOMEM);
+ }
+ exp->priv = priv;
+ INIT_LIST_HEAD(&exp->scene_head);
+ INIT_LIST_HEAD(&exp->str_head);
+ exp->next_id = EXPOID_BASE_ID;
+ cli_ch_init(&exp->cch);
+
+ *expp = exp;
+
+ return 0;
+}
+
+static void estr_destroy(struct expo_string *estr)
+{
+ free(estr);
+}
+
+void expo_destroy(struct expo *exp)
+{
+ struct scene *scn, *next;
+ struct expo_string *estr, *enext;
+
+ list_for_each_entry_safe(scn, next, &exp->scene_head, sibling)
+ scene_destroy(scn);
+
+ list_for_each_entry_safe(estr, enext, &exp->str_head, sibling)
+ estr_destroy(estr);
+
+ free(exp->name);
+ free(exp);
+}
+
+uint resolve_id(struct expo *exp, uint id)
+{
+ log_debug("resolve id %d\n", id);
+ if (!id)
+ id = exp->next_id++;
+ else if (id >= exp->next_id)
+ exp->next_id = id + 1;
+
+ return id;
+}
+
+void expo_set_dynamic_start(struct expo *exp, uint dyn_start)
+{
+ exp->next_id = dyn_start;
+}
+
+int expo_str(struct expo *exp, const char *name, uint id, const char *str)
+{
+ struct expo_string *estr;
+
+ estr = calloc(1, sizeof(struct expo_string));
+ if (!estr)
+ return log_msg_ret("obj", -ENOMEM);
+
+ estr->id = resolve_id(exp, id);
+ abuf_init_const(&estr->buf, str, strlen(str) + 1);
+ list_add_tail(&estr->sibling, &exp->str_head);
+
+ return estr->id;
+}
+
+const char *expo_get_str(struct expo *exp, uint id)
+{
+ struct expo_string *estr;
+
+ list_for_each_entry(estr, &exp->str_head, sibling) {
+ if (estr->id == id)
+ return estr->buf.data;
+ }
+
+ return NULL;
+}
+
+int expo_edit_str(struct expo *exp, uint id, struct abuf *orig,
+ struct abuf **copyp)
+{
+ struct expo_string *estr;
+ struct abuf old;
+
+ list_for_each_entry(estr, &exp->str_head, sibling) {
+ if (estr->id == id) {
+ old = estr->buf;
+ if (!abuf_copy(&old, &estr->buf))
+ return -ENOMEM;
+ *copyp = &estr->buf;
+ if (orig)
+ *orig = old;
+ return 0;
+ }
+ }
+
+ return -ENOENT;
+}
+
+int expo_set_display(struct expo *exp, struct udevice *dev)
+{
+ struct udevice *cons;
+ int ret;
+
+ ret = device_find_first_child_by_uclass(dev, UCLASS_VIDEO_CONSOLE,
+ &cons);
+ if (ret)
+ return log_msg_ret("con", ret);
+
+ exp->display = dev;
+ exp->cons = cons;
+
+ return 0;
+}
+
+int expo_calc_dims(struct expo *exp)
+{
+ struct scene *scn;
+ int ret;
+
+ if (!exp->cons)
+ return log_msg_ret("dim", -ENOTSUPP);
+
+ list_for_each_entry(scn, &exp->scene_head, sibling) {
+ /*
+ * Do the menus last so that all the menus' text objects
+ * are dimensioned
+ */
+ ret = scene_calc_dims(scn, false);
+ if (ret)
+ return log_msg_ret("scn", ret);
+ ret = scene_calc_dims(scn, true);
+ if (ret)
+ return log_msg_ret("scn", ret);
+ }
+
+ return 0;
+}
+
+void expo_set_text_mode(struct expo *exp, bool text_mode)
+{
+ exp->text_mode = text_mode;
+}
+
+struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id)
+{
+ struct scene *scn;
+
+ list_for_each_entry(scn, &exp->scene_head, sibling) {
+ if (scn->id == scene_id)
+ return scn;
+ }
+
+ return NULL;
+}
+
+int expo_set_scene_id(struct expo *exp, uint scene_id)
+{
+ struct scene *scn;
+ int ret;
+
+ scn = expo_lookup_scene_id(exp, scene_id);
+ if (!scn)
+ return log_msg_ret("id", -ENOENT);
+ ret = scene_arrange(scn);
+ if (ret)
+ return log_msg_ret("arr", ret);
+
+ exp->scene_id = scene_id;
+
+ return 0;
+}
+
+int expo_first_scene_id(struct expo *exp)
+{
+ struct scene *scn;
+
+ if (list_empty(&exp->scene_head))
+ return -ENOENT;
+
+ scn = list_first_entry(&exp->scene_head, struct scene, sibling);
+
+ return scn->id;
+}
+
+int expo_render(struct expo *exp)
+{
+ struct udevice *dev = exp->display;
+ struct video_priv *vid_priv = dev_get_uclass_priv(dev);
+ struct scene *scn = NULL;
+ enum colour_idx back;
+ u32 colour;
+ int ret;
+
+ back = vid_priv->white_on_black ? VID_BLACK : VID_WHITE;
+ colour = video_index_to_colour(vid_priv, back);
+ ret = video_fill(dev, colour);
+ if (ret)
+ return log_msg_ret("fill", ret);
+
+ if (exp->scene_id) {
+ scn = expo_lookup_scene_id(exp, exp->scene_id);
+ if (!scn)
+ return log_msg_ret("scn", -ENOENT);
+
+ ret = scene_render(scn);
+ if (ret)
+ return log_msg_ret("ren", ret);
+ }
+
+ video_sync(dev, true);
+
+ return scn ? 0 : -ECHILD;
+}
+
+int expo_send_key(struct expo *exp, int key)
+{
+ struct scene *scn = NULL;
+
+ if (exp->scene_id) {
+ int ret;
+
+ scn = expo_lookup_scene_id(exp, exp->scene_id);
+ if (!scn)
+ return log_msg_ret("scn", -ENOENT);
+
+ ret = scene_send_key(scn, key, &exp->action);
+ if (ret)
+ return log_msg_ret("key", ret);
+
+ /* arrange it to get any changes */
+ ret = scene_arrange(scn);
+ if (ret)
+ return log_msg_ret("arr", ret);
+ }
+
+ return scn ? 0 : -ECHILD;
+}
+
+int expo_action_get(struct expo *exp, struct expo_action *act)
+{
+ *act = exp->action;
+ exp->action.type = EXPOACT_NONE;
+
+ return act->type == EXPOACT_NONE ? -EAGAIN : 0;
+}
+
+int expo_apply_theme(struct expo *exp, ofnode node)
+{
+ struct scene *scn;
+ struct expo_theme *theme = &exp->theme;
+ bool white_on_black;
+ int ret;
+
+ log_debug("Applying theme %s\n", ofnode_get_name(node));
+
+ memset(theme, '\0', sizeof(struct expo_theme));
+ ofnode_read_u32(node, "font-size", &theme->font_size);
+ ofnode_read_u32(node, "menu-inset", &theme->menu_inset);
+ ofnode_read_u32(node, "menuitem-gap-y", &theme->menuitem_gap_y);
+ ofnode_read_u32(node, "menu-title-margin-x",
+ &theme->menu_title_margin_x);
+ white_on_black = ofnode_read_bool(node, "white-on-black");
+ if (exp->display)
+ video_set_white_on_black(exp->display, white_on_black);
+
+ list_for_each_entry(scn, &exp->scene_head, sibling) {
+ ret = scene_apply_theme(scn, theme);
+ if (ret)
+ return log_msg_ret("app", ret);
+ }
+
+ return 0;
+}
+
+int expo_iter_scene_objs(struct expo *exp, expo_scene_obj_iterator iter,
+ void *priv)
+{
+ struct scene *scn;
+ int ret;
+
+ list_for_each_entry(scn, &exp->scene_head, sibling) {
+ ret = scene_iter_objs(scn, iter, priv);
+ if (ret)
+ return log_msg_ret("wr", ret);
+ }
+
+ return 0;
+}
+
+int expo_poll(struct expo *exp, struct expo_action *act)
+{
+ int ichar, key, ret;
+
+ ichar = cli_ch_process(&exp->cch, 0);
+ if (!ichar) {
+ int i;
+
+ for (i = 0; i < 10 && !ichar && !tstc(); i++) {
+ schedule();
+ mdelay(2);
+ ichar = cli_ch_process(&exp->cch, -ETIMEDOUT);
+ }
+ while (!ichar && tstc()) {
+ ichar = getchar();
+ ichar = cli_ch_process(&exp->cch, ichar);
+ }
+ }
+
+ key = 0;
+ if (ichar) {
+ key = bootmenu_conv_key(ichar);
+ if (key == BKEY_NONE || key >= BKEY_FIRST_EXTRA)
+ key = ichar;
+ }
+ if (!key)
+ return -EAGAIN;
+
+ ret = expo_send_key(exp, key);
+ if (ret)
+ return log_msg_ret("epk", ret);
+ ret = expo_action_get(exp, act);
+ if (ret)
+ return log_msg_ret("eag", ret);
+
+ return 0;
+}
diff --git a/boot/expo_build.c b/boot/expo_build.c
new file mode 100644
index 00000000000..d97347e1725
--- /dev/null
+++ b/boot/expo_build.c
@@ -0,0 +1,480 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Building an expo from an FDT description
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY LOGC_EXPO
+
+#include <expo.h>
+#include <fdtdec.h>
+#include <log.h>
+#include <malloc.h>
+#include <dm/ofnode.h>
+#include <linux/libfdt.h>
+
+/**
+ * struct build_info - Information to use when building
+ *
+ * @str_for_id: String for each ID in use, NULL if empty. The string is NULL
+ * if there is nothing for this ID. Since ID 0 is never used, the first
+ * element of this array is always NULL
+ * @str_count: Number of entries in @str_for_id
+ * @err_node: Node being processed (for error reporting)
+ * @err_prop: Property being processed (for error reporting)
+ */
+struct build_info {
+ const char **str_for_id;
+ int str_count;
+ ofnode err_node;
+ const char *err_prop;
+};
+
+/**
+ * add_txt_str - Add a string or lookup its ID, then add to expo
+ *
+ * @info: Build information
+ * @node: Node describing scene
+ * @scn: Scene to add to
+ * @find_name: Name to look for (e.g. "title"). This will find a property called
+ * "title" if it exists, else will look up the string for "title-id"
+ * Return: ID of added string, or -ve on error
+ */
+int add_txt_str(struct build_info *info, ofnode node, struct scene *scn,
+ const char *find_name, uint obj_id)
+{
+ const char *text;
+ int ret;
+
+ info->err_prop = find_name;
+ text = ofnode_read_string(node, find_name);
+ if (!text) {
+ char name[40];
+ u32 id;
+
+ snprintf(name, sizeof(name), "%s-id", find_name);
+ ret = ofnode_read_u32(node, name, &id);
+ if (ret)
+ return log_msg_ret("id", -ENOENT);
+
+ if (id >= info->str_count)
+ return log_msg_ret("id", -E2BIG);
+ text = info->str_for_id[id];
+ if (!text)
+ return log_msg_ret("id", -EINVAL);
+ }
+
+ ret = scene_txt_str(scn, find_name, obj_id, 0, text, NULL);
+ if (ret < 0)
+ return log_msg_ret("add", ret);
+
+ return ret;
+}
+
+/**
+ * add_txt_str_list - Add a list string or lookup its ID, then add to expo
+ *
+ * @info: Build information
+ * @node: Node describing scene
+ * @scn: Scene to add to
+ * @find_name: Name to look for (e.g. "title"). This will find a string-list
+ * property called "title" if it exists, else will look up the string in the
+ * "title-id" string list.
+ * Return: ID of added string, or -ve on error
+ */
+int add_txt_str_list(struct build_info *info, ofnode node, struct scene *scn,
+ const char *find_name, int index, uint obj_id)
+{
+ const char *text;
+ int ret;
+
+ ret = ofnode_read_string_index(node, find_name, index, &text);
+ if (ret) {
+ char name[40];
+ u32 id;
+
+ snprintf(name, sizeof(name), "%s-id", find_name);
+ ret = ofnode_read_u32_index(node, name, index, &id);
+ if (ret)
+ return log_msg_ret("id", -ENOENT);
+
+ if (id >= info->str_count)
+ return log_msg_ret("id", -E2BIG);
+ text = info->str_for_id[id];
+ if (!text)
+ return log_msg_ret("id", -EINVAL);
+ }
+
+ ret = scene_txt_str(scn, find_name, obj_id, 0, text, NULL);
+ if (ret < 0)
+ return log_msg_ret("add", ret);
+
+ return ret;
+}
+
+/*
+ * build_element() - Handle creating a text object from a label
+ *
+ * Look up a property called @label or @label-id and create a string for it
+ */
+int build_element(void *ldtb, int node, const char *label)
+{
+ return 0;
+}
+
+/**
+ * read_strings() - Read in the list of strings
+ *
+ * Read the strings into an ID-indexed list, so they can be used for building
+ * an expo. The strings are in a /strings node and each has its own subnode
+ * containing the ID and the string itself:
+ *
+ * example {
+ * id = <123>;
+ * value = "This is a test";
+ * };
+ *
+ * Future work may add support for unicode and multiple languages
+ *
+ * @info: Build information
+ * @root: Root node to read from
+ * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
+ * error
+ */
+static int read_strings(struct build_info *info, ofnode root)
+{
+ ofnode strings, node;
+
+ strings = ofnode_find_subnode(root, "strings");
+ if (!ofnode_valid(strings))
+ return log_msg_ret("str", -EINVAL);
+
+ ofnode_for_each_subnode(node, strings) {
+ const char *val;
+ int ret;
+ u32 id;
+
+ info->err_node = node;
+ ret = ofnode_read_u32(node, "id", &id);
+ if (ret)
+ return log_msg_ret("id", -ENOENT);
+ val = ofnode_read_string(node, "value");
+ if (!val)
+ return log_msg_ret("val", -EINVAL);
+
+ if (id >= info->str_count) {
+ int new_count = info->str_count + 20;
+ void *new_arr;
+
+ new_arr = realloc(info->str_for_id,
+ new_count * sizeof(char *));
+ if (!new_arr)
+ return log_msg_ret("id", -ENOMEM);
+ memset(new_arr + info->str_count, '\0',
+ (new_count - info->str_count) * sizeof(char *));
+ info->str_for_id = new_arr;
+ info->str_count = new_count;
+ }
+
+ info->str_for_id[id] = val;
+ }
+
+ return 0;
+}
+
+/**
+ * list_strings() - List the available strings with their IDs
+ *
+ * @info: Build information
+ */
+static void list_strings(struct build_info *info)
+{
+ int i;
+
+ for (i = 0; i < info->str_count; i++) {
+ if (info->str_for_id[i])
+ printf("%3d %s\n", i, info->str_for_id[i]);
+ }
+}
+
+/**
+ * menu_build() - Build a menu and add it to a scene
+ *
+ * See doc/develop/expo.rst for a description of the format
+ *
+ * @info: Build information
+ * @node: Node containing the menu description
+ * @scn: Scene to add the menu to
+ * @id: ID for the menu
+ * @objp: Returns the object pointer
+ * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
+ * error, -ENOENT if there is a references to a non-existent string
+ */
+static int menu_build(struct build_info *info, ofnode node, struct scene *scn,
+ uint id, struct scene_obj **objp)
+{
+ const u32 *item_ids, *item_values;
+ struct scene_obj_menu *menu;
+ int ret, size, i, num_items;
+ uint title_id, menu_id;
+ const char *name;
+
+ name = ofnode_get_name(node);
+
+ ret = scene_menu(scn, name, id, &menu);
+ if (ret < 0)
+ return log_msg_ret("men", ret);
+ menu_id = ret;
+
+ /* Set the title */
+ ret = add_txt_str(info, node, scn, "title", 0);
+ if (ret < 0)
+ return log_msg_ret("tit", ret);
+ title_id = ret;
+ ret = scene_menu_set_title(scn, menu_id, title_id);
+ if (ret)
+ return log_msg_ret("set", ret);
+
+ item_ids = ofnode_read_prop(node, "item-id", &size);
+ if (!item_ids)
+ return log_msg_ret("itm", -EINVAL);
+ if (!size || size % sizeof(u32))
+ return log_msg_ret("isz", -EINVAL);
+ num_items = size / sizeof(u32);
+
+ item_values = ofnode_read_prop(node, "item-value", &size);
+ if (item_values) {
+ if (size != num_items * sizeof(u32))
+ return log_msg_ret("vsz", -EINVAL);
+ }
+
+ for (i = 0; i < num_items; i++) {
+ struct scene_menitem *item;
+ uint label, key, desc;
+
+ ret = add_txt_str_list(info, node, scn, "item-label", i, 0);
+ if (ret < 0 && ret != -ENOENT)
+ return log_msg_ret("lab", ret);
+ label = max(0, ret);
+
+ ret = add_txt_str_list(info, node, scn, "key-label", i, 0);
+ if (ret < 0 && ret != -ENOENT)
+ return log_msg_ret("key", ret);
+ key = max(0, ret);
+
+ ret = add_txt_str_list(info, node, scn, "desc-label", i, 0);
+ if (ret < 0 && ret != -ENOENT)
+ return log_msg_ret("lab", ret);
+ desc = max(0, ret);
+
+ ret = scene_menuitem(scn, menu_id, simple_xtoa(i),
+ fdt32_to_cpu(item_ids[i]), key, label,
+ desc, 0, 0, &item);
+ if (ret < 0)
+ return log_msg_ret("mi", ret);
+ if (item_values)
+ item->value = fdt32_to_cpu(item_values[i]);
+ }
+ *objp = &menu->obj;
+
+ return 0;
+}
+
+static int textline_build(struct build_info *info, ofnode node,
+ struct scene *scn, uint id, struct scene_obj **objp)
+{
+ struct scene_obj_textline *ted;
+ uint ted_id, edit_id;
+ const char *name;
+ u32 max_chars;
+ int ret;
+
+ name = ofnode_get_name(node);
+
+ info->err_prop = "max-chars";
+ ret = ofnode_read_u32(node, "max-chars", &max_chars);
+ if (ret)
+ return log_msg_ret("max", -ENOENT);
+
+ ret = scene_textline(scn, name, id, max_chars, &ted);
+ if (ret < 0)
+ return log_msg_ret("ted", ret);
+ ted_id = ret;
+
+ /* Set the title */
+ ret = add_txt_str(info, node, scn, "title", 0);
+ if (ret < 0)
+ return log_msg_ret("tit", ret);
+ ted->label_id = ret;
+
+ /* Setup the editor */
+ info->err_prop = "edit-id";
+ ret = ofnode_read_u32(node, "edit-id", &id);
+ if (ret)
+ return log_msg_ret("id", -ENOENT);
+ edit_id = ret;
+
+ ret = scene_txt_str(scn, "edit", edit_id, 0, abuf_data(&ted->buf),
+ NULL);
+ if (ret < 0)
+ return log_msg_ret("add", ret);
+ ted->edit_id = ret;
+
+ return 0;
+}
+
+/**
+ * obj_build() - Build an expo object and add it to a scene
+ *
+ * See doc/develop/expo.rst for a description of the format
+ *
+ * @info: Build information
+ * @node: Node containing the object description
+ * @scn: Scene to add the object to
+ * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
+ * error, -ENOENT if there is a references to a non-existent string
+ */
+static int obj_build(struct build_info *info, ofnode node, struct scene *scn)
+{
+ struct scene_obj *obj;
+ const char *type;
+ u32 id, val;
+ int ret;
+
+ log_debug("- object %s\n", ofnode_get_name(node));
+ ret = ofnode_read_u32(node, "id", &id);
+ if (ret)
+ return log_msg_ret("id", -ENOENT);
+
+ type = ofnode_read_string(node, "type");
+ if (!type)
+ return log_msg_ret("typ", -EINVAL);
+
+ if (!strcmp("menu", type))
+ ret = menu_build(info, node, scn, id, &obj);
+ else if (!strcmp("textline", type))
+ ret = textline_build(info, node, scn, id, &obj);
+ else
+ ret = -EOPNOTSUPP;
+ if (ret)
+ return log_msg_ret("bld", ret);
+
+ if (!ofnode_read_u32(node, "start-bit", &val))
+ obj->start_bit = val;
+ if (!ofnode_read_u32(node, "bit-length", &val))
+ obj->bit_length = val;
+
+ return 0;
+}
+
+/**
+ * scene_build() - Build a scene and all its objects
+ *
+ * See doc/develop/expo.rst for a description of the format
+ *
+ * @info: Build information
+ * @node: Node containing the scene description
+ * @scn: Scene to add the object to
+ * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
+ * error, -ENOENT if there is a references to a non-existent string
+ */
+static int scene_build(struct build_info *info, ofnode scn_node,
+ struct expo *exp)
+{
+ const char *name;
+ struct scene *scn;
+ uint id, title_id;
+ ofnode node;
+ int ret;
+
+ info->err_node = scn_node;
+ name = ofnode_get_name(scn_node);
+ log_debug("Building scene %s\n", name);
+ ret = ofnode_read_u32(scn_node, "id", &id);
+ if (ret)
+ return log_msg_ret("id", -ENOENT);
+
+ ret = scene_new(exp, name, id, &scn);
+ if (ret < 0)
+ return log_msg_ret("scn", ret);
+
+ ret = add_txt_str(info, scn_node, scn, "title", 0);
+ if (ret < 0)
+ return log_msg_ret("tit", ret);
+ title_id = ret;
+ scn->title_id = title_id;
+
+ ret = add_txt_str(info, scn_node, scn, "prompt", 0);
+ if (ret < 0)
+ return log_msg_ret("pr", ret);
+
+ ofnode_for_each_subnode(node, scn_node) {
+ info->err_node = node;
+ ret = obj_build(info, node, scn);
+ if (ret < 0)
+ return log_msg_ret("mit", ret);
+ }
+
+ return 0;
+}
+
+static int build_it(struct build_info *info, ofnode root, struct expo **expp)
+{
+ ofnode scenes, node;
+ struct expo *exp;
+ u32 dyn_start;
+ int ret;
+
+ ret = read_strings(info, root);
+ if (ret)
+ return log_msg_ret("str", ret);
+ if (_DEBUG)
+ list_strings(info);
+ info->err_node = root;
+
+ ret = expo_new("name", NULL, &exp);
+ if (ret)
+ return log_msg_ret("exp", ret);
+
+ if (!ofnode_read_u32(root, "dynamic-start", &dyn_start))
+ expo_set_dynamic_start(exp, dyn_start);
+
+ scenes = ofnode_find_subnode(root, "scenes");
+ if (!ofnode_valid(scenes))
+ return log_msg_ret("sno", -EINVAL);
+
+ ofnode_for_each_subnode(node, scenes) {
+ ret = scene_build(info, node, exp);
+ if (ret < 0)
+ return log_msg_ret("scn", ret);
+ }
+ *expp = exp;
+
+ return 0;
+}
+
+int expo_build(ofnode root, struct expo **expp)
+{
+ struct build_info info;
+ struct expo *exp;
+ int ret;
+
+ memset(&info, '\0', sizeof(info));
+ ret = build_it(&info, root, &exp);
+ if (ret) {
+ char buf[120];
+ int node_ret;
+
+ node_ret = ofnode_get_path(info.err_node, buf, sizeof(buf));
+ log_warning("Build failed at node %s, property %s\n",
+ node_ret ? ofnode_get_name(info.err_node) : buf,
+ info.err_prop);
+
+ return log_msg_ret("bui", ret);
+ }
+ *expp = exp;
+
+ return 0;
+}
diff --git a/boot/expo_build_cb.c b/boot/expo_build_cb.c
new file mode 100644
index 00000000000..442ad760e79
--- /dev/null
+++ b/boot/expo_build_cb.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Building an expo from an FDT description
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY LOGC_EXPO
+
+#include <cedit.h>
+#include <ctype.h>
+#include <errno.h>
+#include <expo.h>
+#include <log.h>
+#include <malloc.h>
+#include <vsprintf.h>
+#include <asm/cb_sysinfo.h>
+
+/**
+ * struct build_info - Information to use when building
+ */
+struct build_info {
+ const struct cb_cmos_option_table *tab;
+ struct cedit_priv *priv;
+};
+
+/**
+ * convert_to_title() - Convert text to 'title' format and allocate a string
+ *
+ * Converts "this_is_a_test" to "This is a test" so it looks better
+ *
+ * @text: Text to convert
+ * Return: Allocated string, or NULL if out of memory
+ */
+static char *convert_to_title(const char *text)
+{
+ int len = strlen(text);
+ char *buf, *s;
+
+ buf = malloc(len + 1);
+ if (!buf)
+ return NULL;
+
+ for (s = buf; *text; s++, text++) {
+ if (s == buf)
+ *s = toupper(*text);
+ else if (*text == '_')
+ *s = ' ';
+ else
+ *s = *text;
+ }
+ *s = '\0';
+
+ return buf;
+}
+
+/**
+ * menu_build() - Build a menu and add it to a scene
+ *
+ * See doc/developer/expo.rst for a description of the format
+ *
+ * @info: Build information
+ * @entry: CMOS entry to build a menu for
+ * @scn: Scene to add the menu to
+ * @objp: Returns the object pointer
+ * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
+ * error, -ENOENT if there is a references to a non-existent string
+ */
+static int menu_build(struct build_info *info,
+ const struct cb_cmos_entries *entry, struct scene *scn,
+ struct scene_obj **objp)
+{
+ struct scene_obj_menu *menu;
+ const void *ptr, *end;
+ uint menu_id;
+ char *title;
+ int ret, i;
+
+ ret = scene_menu(scn, entry->name, 0, &menu);
+ if (ret < 0)
+ return log_msg_ret("men", ret);
+ menu_id = ret;
+
+ title = convert_to_title(entry->name);
+ if (!title)
+ return log_msg_ret("con", -ENOMEM);
+
+ /* Set the title */
+ ret = scene_txt_str(scn, "title", 0, 0, title, NULL);
+ if (ret < 0)
+ return log_msg_ret("tit", ret);
+ menu->title_id = ret;
+
+ end = (void *)info->tab + info->tab->size;
+ for (ptr = (void *)info->tab + info->tab->header_length, i = 0;
+ ptr < end; i++) {
+ const struct cb_cmos_enums *enums = ptr;
+ struct scene_menitem *item;
+ uint label;
+
+ ptr += enums->size;
+ if (enums->tag != CB_TAG_OPTION_ENUM ||
+ enums->config_id != entry->config_id)
+ continue;
+
+ ret = scene_txt_str(scn, enums->text, 0, 0, enums->text, NULL);
+ if (ret < 0)
+ return log_msg_ret("tit", ret);
+ label = ret;
+
+ ret = scene_menuitem(scn, menu_id, simple_xtoa(i), 0, 0, label,
+ 0, 0, 0, &item);
+ if (ret < 0)
+ return log_msg_ret("mi", ret);
+ item->value = enums->value;
+ }
+ *objp = &menu->obj;
+
+ return 0;
+}
+
+/**
+ * scene_build() - Build a scene and all its objects
+ *
+ * See doc/developer/expo.rst for a description of the format
+ *
+ * @info: Build information
+ * @scn: Scene to add the object to
+ * Returns: 0 if OK, -ENOMEM if out of memory, -EINVAL if there is a format
+ * error, -ENOENT if there is a references to a non-existent string
+ */
+static int scene_build(struct build_info *info, struct expo *exp)
+{
+ struct scene_obj_menu *menu;
+ const void *ptr, *end;
+ struct scene_obj *obj;
+ struct scene *scn;
+ uint label, menu_id;
+ int ret;
+
+ ret = scene_new(exp, "cmos", 0, &scn);
+ if (ret < 0)
+ return log_msg_ret("scn", ret);
+
+ ret = scene_txt_str(scn, "title", 0, 0, "CMOS RAM settings", NULL);
+ if (ret < 0)
+ return log_msg_ret("add", ret);
+ scn->title_id = ret;
+
+ ret = scene_txt_str(scn, "prompt", 0, 0,
+ "UP and DOWN to choose, ENTER to select", NULL);
+ if (ret < 0)
+ return log_msg_ret("add", ret);
+
+ end = (void *)info->tab + info->tab->size;
+ for (ptr = (void *)info->tab + info->tab->header_length; ptr < end;) {
+ const struct cb_cmos_entries *entry;
+ const struct cb_record *rec = ptr;
+
+ entry = ptr;
+ ptr += rec->size;
+ if (rec->tag != CB_TAG_OPTION)
+ continue;
+ switch (entry->config) {
+ case 'e':
+ ret = menu_build(info, entry, scn, &obj);
+ break;
+ default:
+ continue;
+ }
+ if (ret < 0)
+ return log_msg_ret("add", ret);
+
+ obj->start_bit = entry->bit;
+ obj->bit_length = entry->length;
+ }
+
+ ret = scene_menu(scn, "save", EXPOID_SAVE, &menu);
+ if (ret < 0)
+ return log_msg_ret("men", ret);
+ menu_id = ret;
+
+ ret = scene_txt_str(scn, "save", 0, 0, "Save and exit", NULL);
+ if (ret < 0)
+ return log_msg_ret("sav", ret);
+ label = ret;
+ ret = scene_menuitem(scn, menu_id, "save", 0, 0, label,
+ 0, 0, 0, NULL);
+ if (ret < 0)
+ return log_msg_ret("mi", ret);
+
+ ret = scene_menu(scn, "nosave", EXPOID_DISCARD, &menu);
+ if (ret < 0)
+ return log_msg_ret("men", ret);
+ menu_id = ret;
+
+ ret = scene_txt_str(scn, "nosave", 0, 0, "Exit without saving", NULL);
+ if (ret < 0)
+ return log_msg_ret("nos", ret);
+ label = ret;
+ ret = scene_menuitem(scn, menu_id, "exit", 0, 0, label,
+ 0, 0, 0, NULL);
+ if (ret < 0)
+ return log_msg_ret("mi", ret);
+
+ return 0;
+}
+
+static int build_it(struct build_info *info, struct expo **expp)
+{
+ struct expo *exp;
+ int ret;
+
+ ret = expo_new("coreboot", NULL, &exp);
+ if (ret)
+ return log_msg_ret("exp", ret);
+ expo_set_dynamic_start(exp, EXPOID_BASE_ID);
+
+ ret = scene_build(info, exp);
+ if (ret < 0)
+ return log_msg_ret("scn", ret);
+
+ *expp = exp;
+
+ return 0;
+}
+
+int cb_expo_build(struct expo **expp)
+{
+ struct build_info info;
+ struct expo *exp;
+ int ret;
+
+ info.tab = lib_sysinfo.option_table;
+ if (!info.tab)
+ return log_msg_ret("tab", -ENOENT);
+
+ ret = build_it(&info, &exp);
+ if (ret)
+ return log_msg_ret("bui", ret);
+ *expp = exp;
+
+ return 0;
+}
diff --git a/boot/fdt_region.c b/boot/fdt_region.c
new file mode 100644
index 00000000000..295ea08ac91
--- /dev/null
+++ b/boot/fdt_region.c
@@ -0,0 +1,702 @@
+// SPDX-License-Identifier: GPL-2.0+ OR BSD-2-Clause
+/*
+ * libfdt - Flat Device Tree manipulation
+ * Copyright (C) 2013 Google, Inc
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <fdt_support.h>
+#include <linux/libfdt_env.h>
+#include <fdt_region.h>
+
+#ifndef USE_HOSTCC
+#include <fdt.h>
+#include <linux/libfdt.h>
+#else
+#include "fdt_host.h"
+#endif
+
+#define FDT_MAX_DEPTH 32
+
+static int str_in_list(const char *str, char * const list[], int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++)
+ if (!strcmp(list[i], str))
+ return 1;
+
+ return 0;
+}
+
+int fdt_find_regions(const void *fdt, char * const inc[], int inc_count,
+ char * const exc_prop[], int exc_prop_count,
+ struct fdt_region region[], int max_regions,
+ char *path, int path_len, int add_string_tab)
+{
+ int stack[FDT_MAX_DEPTH] = { 0 };
+ char *end;
+ int nextoffset = 0;
+ uint32_t tag;
+ int count = 0;
+ int start = -1;
+ int depth = -1;
+ int want = 0;
+ int base = fdt_off_dt_struct(fdt);
+ bool expect_end = false;
+
+ end = path;
+ *end = '\0';
+ do {
+ const struct fdt_property *prop;
+ const char *name;
+ const char *str;
+ int include = 0;
+ int stop_at = 0;
+ int offset;
+ int len;
+
+ offset = nextoffset;
+ tag = fdt_next_tag(fdt, offset, &nextoffset);
+ stop_at = nextoffset;
+
+ /* If we see two root nodes, something is wrong */
+ if (expect_end && tag != FDT_END)
+ return -FDT_ERR_BADLAYOUT;
+
+ switch (tag) {
+ case FDT_PROP:
+ include = want >= 2;
+ stop_at = offset;
+ prop = fdt_get_property_by_offset(fdt, offset, NULL);
+ str = fdt_string(fdt, fdt32_to_cpu(prop->nameoff));
+ if (!str)
+ return -FDT_ERR_BADSTRUCTURE;
+ if (str_in_list(str, exc_prop, exc_prop_count))
+ include = 0;
+ break;
+
+ case FDT_NOP:
+ include = want >= 2;
+ stop_at = offset;
+ break;
+
+ case FDT_BEGIN_NODE:
+ depth++;
+ if (depth == FDT_MAX_DEPTH)
+ return -FDT_ERR_BADSTRUCTURE;
+ name = fdt_get_name(fdt, offset, &len);
+
+ /* The root node must have an empty name */
+ if (!depth && *name)
+ return -FDT_ERR_BADLAYOUT;
+ if (end - path + 2 + len >= path_len)
+ return -FDT_ERR_NOSPACE;
+ if (end != path + 1)
+ *end++ = '/';
+ strcpy(end, name);
+ end += len;
+ stack[depth] = want;
+ if (want == 1)
+ stop_at = offset;
+ if (str_in_list(path, inc, inc_count))
+ want = 2;
+ else if (want)
+ want--;
+ else
+ stop_at = offset;
+ include = want;
+ break;
+
+ case FDT_END_NODE:
+ /* Depth must never go below -1 */
+ if (depth < 0)
+ return -FDT_ERR_BADSTRUCTURE;
+ include = want;
+ want = stack[depth--];
+ while (end > path && *--end != '/')
+ ;
+ *end = '\0';
+ if (depth == -1)
+ expect_end = true;
+ break;
+
+ case FDT_END:
+ include = 1;
+ break;
+ }
+
+ if (include && start == -1) {
+ /* Should we merge with previous? */
+ if (count && count <= max_regions &&
+ offset == region[count - 1].offset +
+ region[count - 1].size - base)
+ start = region[--count].offset - base;
+ else
+ start = offset;
+ }
+
+ if (!include && start != -1) {
+ if (count < max_regions) {
+ region[count].offset = base + start;
+ region[count].size = stop_at - start;
+ }
+ count++;
+ start = -1;
+ }
+ } while (tag != FDT_END);
+
+ if (nextoffset != fdt_size_dt_struct(fdt))
+ return -FDT_ERR_BADLAYOUT;
+
+ /* Add a region for the END tag and the string table */
+ if (count < max_regions) {
+ region[count].offset = base + start;
+ region[count].size = nextoffset - start;
+ if (add_string_tab)
+ region[count].size += fdt_size_dt_strings(fdt);
+ }
+ count++;
+
+ return count;
+}
+
+/**
+ * fdt_add_region() - Add a new region to our list
+ * @info: State information
+ * @offset: Start offset of region
+ * @size: Size of region
+ *
+ * The region is added if there is space, but in any case we increment the
+ * count. If permitted, and the new region overlaps the last one, we merge
+ * them.
+ */
+static int fdt_add_region(struct fdt_region_state *info, int offset, int size)
+{
+ struct fdt_region *reg;
+
+ reg = info->region ? &info->region[info->count - 1] : NULL;
+ if (info->can_merge && info->count &&
+ info->count <= info->max_regions &&
+ reg && offset <= reg->offset + reg->size) {
+ reg->size = offset + size - reg->offset;
+ } else if (info->count++ < info->max_regions) {
+ if (reg) {
+ reg++;
+ reg->offset = offset;
+ reg->size = size;
+ if (!(offset - fdt_off_dt_struct(info->fdt)))
+ info->have_node = true;
+ }
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int region_list_contains_offset(struct fdt_region_state *info,
+ const void *fdt, int target)
+{
+ struct fdt_region *reg;
+ int num;
+
+ target += fdt_off_dt_struct(fdt);
+ for (reg = info->region, num = 0; num < info->count; reg++, num++) {
+ if (target >= reg->offset && target < reg->offset + reg->size)
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * fdt_add_alias_regions() - Add regions covering the aliases that we want
+ *
+ * The /aliases node is not automatically included by fdtgrep unless the
+ * command-line arguments cause to be included (or not excluded). However
+ * aliases are special in that we generally want to include those which
+ * reference a node that fdtgrep includes.
+ *
+ * In fact we want to include only aliases for those nodes still included in
+ * the fdt, and drop the other aliases since they point to nodes that will not
+ * be present.
+ *
+ * This function scans the aliases and adds regions for those which we want
+ * to keep.
+ *
+ * @fdt: Device tree to scan
+ * @region: List of regions
+ * @count: Number of regions in the list so far (i.e. starting point for this
+ * function)
+ * @max_regions: Maximum number of regions in @region list
+ * @info: Place to put the region state
+ * Return: number of regions after processing, or -FDT_ERR_NOSPACE if we did
+ * not have enough room in the regions table for the regions we wanted to add.
+ */
+int fdt_add_alias_regions(const void *fdt, struct fdt_region *region, int count,
+ int max_regions, struct fdt_region_state *info)
+{
+ int base = fdt_off_dt_struct(fdt);
+ int node, node_end, offset;
+ int did_alias_header;
+
+ node = fdt_subnode_offset(fdt, 0, "aliases");
+ if (node < 0)
+ return -FDT_ERR_NOTFOUND;
+
+ /*
+ * Find the next node so that we know where the /aliases node ends. We
+ * need special handling if /aliases is the last node.
+ */
+ node_end = fdt_next_subnode(fdt, node);
+ if (node_end == -FDT_ERR_NOTFOUND)
+ /* Move back to the FDT_END_NODE tag of '/' */
+ node_end = fdt_size_dt_struct(fdt) - sizeof(fdt32_t) * 2;
+ else if (node_end < 0) /* other error */
+ return node_end;
+ node_end -= sizeof(fdt32_t); /* Move to FDT_END_NODE tag of /aliases */
+
+ did_alias_header = 0;
+ info->region = region;
+ info->count = count;
+ info->can_merge = 0;
+ info->max_regions = max_regions;
+
+ for (offset = fdt_first_property_offset(fdt, node);
+ offset >= 0;
+ offset = fdt_next_property_offset(fdt, offset)) {
+ const struct fdt_property *prop;
+ const char *name;
+ int target, next;
+
+ prop = fdt_get_property_by_offset(fdt, offset, NULL);
+ name = fdt_string(fdt, fdt32_to_cpu(prop->nameoff));
+ target = fdt_path_offset(fdt, name);
+ if (!region_list_contains_offset(info, fdt, target))
+ continue;
+ next = fdt_next_property_offset(fdt, offset);
+ if (next < 0)
+ next = node_end;
+
+ if (!did_alias_header) {
+ fdt_add_region(info, base + node, 12);
+ did_alias_header = 1;
+ }
+ fdt_add_region(info, base + offset, next - offset);
+ }
+
+ /* Add the FDT_END_NODE tag */
+ if (did_alias_header)
+ fdt_add_region(info, base + node_end, sizeof(fdt32_t));
+
+ return info->count < max_regions ? info->count : -FDT_ERR_NOSPACE;
+}
+
+/**
+ * fdt_include_supernodes() - Include supernodes required by this node
+ * @info: State information
+ * @depth: Current stack depth
+ *
+ * When we decided to include a node or property which is not at the top
+ * level, this function forces the inclusion of higher level nodes. For
+ * example, given this tree:
+ *
+ * / {
+ * testing {
+ * }
+ * }
+ *
+ * If we decide to include testing then we need the root node to have a valid
+ * tree. This function adds those regions.
+ */
+static int fdt_include_supernodes(struct fdt_region_state *info, int depth)
+{
+ int base = fdt_off_dt_struct(info->fdt);
+ int start, stop_at;
+ int i;
+
+ /*
+ * Work down the stack looking for supernodes that we didn't include.
+ * The algortihm here is actually pretty simple, since we know that
+ * no previous subnode had to include these nodes, or if it did, we
+ * marked them as included (on the stack) already.
+ */
+ for (i = 0; i <= depth; i++) {
+ if (!info->stack[i].included) {
+ start = info->stack[i].offset;
+
+ /* Add the FDT_BEGIN_NODE tag of this supernode */
+ fdt_next_tag(info->fdt, start, &stop_at);
+ if (fdt_add_region(info, base + start, stop_at - start))
+ return -1;
+
+ /* Remember that this supernode is now included */
+ info->stack[i].included = 1;
+ info->can_merge = 1;
+ }
+
+ /* Force (later) generation of the FDT_END_NODE tag */
+ if (!info->stack[i].want)
+ info->stack[i].want = WANT_NODES_ONLY;
+ }
+
+ return 0;
+}
+
+/*
+ * Tracks the progress through the device tree. Everything fdt_next_region() is
+ * called it picks up at the same state as last time, looks at info->start and
+ * decides what region to add next
+ */
+enum {
+ FDT_DONE_NOTHING, /* Starting */
+ FDT_DONE_MEM_RSVMAP, /* Completed mem_rsvmap region */
+ FDT_DONE_STRUCT, /* Completed struct region */
+ FDT_DONE_EMPTY, /* Completed checking for empty struct region */
+ FDT_DONE_END, /* Completed the FDT_END tag */
+ FDT_DONE_STRINGS, /* Completed the strings */
+ FDT_DONE_ALL, /* All done */
+};
+
+int fdt_first_region(const void *fdt,
+ int (*h_include)(void *priv, const void *fdt, int offset,
+ int type, const char *data, int size),
+ void *priv, struct fdt_region *region,
+ char *path, int path_len, int flags,
+ struct fdt_region_state *info)
+{
+ struct fdt_region_ptrs *p = &info->ptrs;
+
+ /* Set up our state */
+ info->fdt = fdt;
+ info->can_merge = 1;
+ info->max_regions = 1;
+ info->start = -1;
+ info->have_node = false;
+ p->want = WANT_NOTHING;
+ p->end = path;
+ *p->end = '\0';
+ p->nextoffset = 0;
+ p->depth = -1;
+ p->done = FDT_DONE_NOTHING;
+
+ return fdt_next_region(fdt, h_include, priv, region,
+ path, path_len, flags, info);
+}
+
+/***********************************************************************
+ *
+ * Theory of operation
+ *
+ * Note: in this description 'included' means that a node (or other part
+ * of the tree) should be included in the region list, i.e. it will have
+ * a region which covers its part of the tree.
+ *
+ * This function maintains some state from the last time it is called.
+ * It checks the next part of the tree that it is supposed to look at
+ * (p.nextoffset) to see if that should be included or not. When it
+ * finds something to include, it sets info->start to its offset. This
+ * marks the start of the region we want to include.
+ *
+ * Once info->start is set to the start (i.e. not -1), we continue
+ * scanning until we find something that we don't want included. This
+ * will be the end of a region. At this point we can close off the
+ * region and add it to the list. So we do so, and reset info->start
+ * to -1.
+ *
+ * One complication here is that we want to merge regions. So when we
+ * come to add another region later, we may in fact merge it with the
+ * previous one if one ends where the other starts.
+ *
+ * The function fdt_add_region() will return -1 if it fails to add the
+ * region, because we already have a region ready to be returned, and
+ * the new one cannot be merged in with it. In this case, we must return
+ * the region we found, and wait for another call to this function.
+ * When it comes, we will repeat the processing of the tag and again
+ * try to add a region. This time it will succeed.
+ *
+ * The current state of the pointers (stack, offset, etc.) is maintained
+ * in a ptrs member. At the start of every loop iteration we make a copy
+ * of it. The copy is then updated as the tag is processed. Only if we
+ * get to the end of the loop iteration (and successfully call
+ * fdt_add_region() if we need to) can we commit the changes we have
+ * made to these pointers. For example, if we see an FDT_END_NODE tag,
+ * we will decrement the depth value. But if we need to add a region
+ * for this tag (let's say because the previous tag is included and this
+ * FDT_END_NODE tag is not included) then we will only commit the result
+ * if we were able to add the region. That allows us to retry again next
+ * time.
+ *
+ * We keep track of a variable called 'want' which tells us what we want
+ * to include when there is no specific information provided by the
+ * h_include function for a particular property. This basically handles
+ * the inclusion of properties which are pulled in by virtue of the node
+ * they are in. So if you include a node, its properties are also
+ * included. In this case 'want' will be WANT_NODES_AND_PROPS. The
+ * FDT_REG_DIRECT_SUBNODES feature also makes use of 'want'. While we
+ * are inside the subnode, 'want' will be set to WANT_NODES_ONLY, so
+ * that only the subnode's FDT_BEGIN_NODE and FDT_END_NODE tags will be
+ * included, and properties will be skipped. If WANT_NOTHING is
+ * selected, then we will just rely on what the h_include() function
+ * tells us.
+ *
+ * Using 'want' we work out 'include', which tells us whether this
+ * current tag should be included or not. As you can imagine, if the
+ * value of 'include' changes, that means we are on a boundary between
+ * nodes to include and nodes to exclude. At this point we either close
+ * off a previous region and add it to the list, or mark the start of a
+ * new region.
+ *
+ * Apart from the nodes, we have mem_rsvmap, the FDT_END tag and the
+ * string list. Each of these dealt with as a whole (i.e. we create a
+ * region for each if it is to be included). For mem_rsvmap we don't
+ * allow it to merge with the first struct region. For the stringlist,
+ * we don't allow it to merge with the last struct region (which
+ * contains at minimum the FDT_END tag).
+ *
+ *********************************************************************/
+
+int fdt_next_region(const void *fdt,
+ int (*h_include)(void *priv, const void *fdt, int offset,
+ int type, const char *data, int size),
+ void *priv, struct fdt_region *region,
+ char *path, int path_len, int flags,
+ struct fdt_region_state *info)
+{
+ int base = fdt_off_dt_struct(fdt);
+ int last_node = 0;
+ const char *str;
+
+ info->region = region;
+ info->count = 0;
+ if (info->ptrs.done < FDT_DONE_MEM_RSVMAP &&
+ (flags & FDT_REG_ADD_MEM_RSVMAP)) {
+ /* Add the memory reserve map into its own region */
+ if (fdt_add_region(info, fdt_off_mem_rsvmap(fdt),
+ fdt_off_dt_struct(fdt) -
+ fdt_off_mem_rsvmap(fdt)))
+ return 0;
+ info->can_merge = 0; /* Don't allow merging with this */
+ info->ptrs.done = FDT_DONE_MEM_RSVMAP;
+ }
+
+ /*
+ * Work through the tags one by one, deciding whether each needs to
+ * be included or not. We set the variable 'include' to indicate our
+ * decision. 'want' is used to track what we want to include - it
+ * allows us to pick up all the properties (and/or subnode tags) of
+ * a node.
+ */
+ while (info->ptrs.done < FDT_DONE_STRUCT) {
+ const struct fdt_property *prop;
+ struct fdt_region_ptrs p;
+ const char *name;
+ int include = 0;
+ int stop_at = 0;
+ uint32_t tag;
+ int offset;
+ int val;
+ int len;
+
+ /*
+ * Make a copy of our pointers. If we make it to the end of
+ * this block then we will commit them back to info->ptrs.
+ * Otherwise we can try again from the same starting state
+ * next time we are called.
+ */
+ p = info->ptrs;
+
+ /*
+ * Find the tag, and the offset of the next one. If we need to
+ * stop including tags, then by default we stop *after*
+ * including the current tag
+ */
+ offset = p.nextoffset;
+ tag = fdt_next_tag(fdt, offset, &p.nextoffset);
+ stop_at = p.nextoffset;
+
+ switch (tag) {
+ case FDT_PROP:
+ stop_at = offset;
+ prop = fdt_get_property_by_offset(fdt, offset, NULL);
+ str = fdt_string(fdt, fdt32_to_cpu(prop->nameoff));
+ val = h_include(priv, fdt, last_node, FDT_IS_PROP, str,
+ strlen(str) + 1);
+ if (val == -1) {
+ include = p.want >= WANT_NODES_AND_PROPS;
+ } else {
+ include = val;
+ /*
+ * Make sure we include the } for this block.
+ * It might be more correct to have this done
+ * by the call to fdt_include_supernodes() in
+ * the case where it adds the node we are
+ * currently in, but this is equivalent.
+ */
+ if ((flags & FDT_REG_SUPERNODES) && val &&
+ !p.want)
+ p.want = WANT_NODES_ONLY;
+ }
+
+ /* Value grepping is not yet supported */
+ break;
+
+ case FDT_NOP:
+ include = p.want >= WANT_NODES_AND_PROPS;
+ stop_at = offset;
+ break;
+
+ case FDT_BEGIN_NODE:
+ last_node = offset;
+ p.depth++;
+ if (p.depth == FDT_MAX_DEPTH)
+ return -FDT_ERR_BADSTRUCTURE;
+ name = fdt_get_name(fdt, offset, &len);
+ if (p.end - path + 2 + len >= path_len)
+ return -FDT_ERR_NOSPACE;
+
+ /* Build the full path of this node */
+ if (p.end != path + 1)
+ *p.end++ = '/';
+ strcpy(p.end, name);
+ p.end += len;
+ info->stack[p.depth].want = p.want;
+ info->stack[p.depth].offset = offset;
+
+ /*
+ * If we are not intending to include this node unless
+ * it matches, make sure we stop *before* its tag.
+ */
+ if (p.want == WANT_NODES_ONLY ||
+ !(flags & (FDT_REG_DIRECT_SUBNODES |
+ FDT_REG_ALL_SUBNODES))) {
+ stop_at = offset;
+ p.want = WANT_NOTHING;
+ }
+ val = h_include(priv, fdt, offset, FDT_IS_NODE, path,
+ p.end - path + 1);
+
+ /* Include this if requested */
+ if (val) {
+ p.want = (flags & FDT_REG_ALL_SUBNODES) ?
+ WANT_ALL_NODES_AND_PROPS :
+ WANT_NODES_AND_PROPS;
+ }
+
+ /* If not requested, decay our 'p.want' value */
+ else if (p.want) {
+ if (p.want != WANT_ALL_NODES_AND_PROPS)
+ p.want--;
+
+ /* Not including this tag, so stop now */
+ } else {
+ stop_at = offset;
+ }
+
+ /*
+ * Decide whether to include this tag, and update our
+ * stack with the state for this node
+ */
+ include = p.want;
+ info->stack[p.depth].included = include;
+ break;
+
+ case FDT_END_NODE:
+ include = p.want;
+ if (p.depth < 0)
+ return -FDT_ERR_BADSTRUCTURE;
+
+ /*
+ * If we don't want this node, stop right away, unless
+ * we are including subnodes
+ */
+ if (!p.want && !(flags & FDT_REG_DIRECT_SUBNODES))
+ stop_at = offset;
+ p.want = info->stack[p.depth].want;
+ p.depth--;
+ while (p.end > path && *--p.end != '/')
+ ;
+ *p.end = '\0';
+ break;
+
+ case FDT_END:
+ /* We always include the end tag */
+ include = 1;
+ p.done = FDT_DONE_STRUCT;
+ break;
+ }
+
+ /* If this tag is to be included, mark it as region start */
+ if (include && info->start == -1) {
+ /* Include any supernodes required by this one */
+ if (flags & FDT_REG_SUPERNODES) {
+ if (fdt_include_supernodes(info, p.depth))
+ return 0;
+ }
+ info->start = offset;
+ }
+
+ /*
+ * If this tag is not to be included, finish up the current
+ * region.
+ */
+ if (!include && info->start != -1) {
+ if (!info->start)
+ info->have_node = true;
+ if (fdt_add_region(info, base + info->start,
+ stop_at - info->start))
+ return 0;
+ info->start = -1;
+ info->can_merge = 1;
+ }
+
+ /* If we have made it this far, we can commit our pointers */
+ info->ptrs = p;
+ }
+
+ if (info->ptrs.done < FDT_DONE_EMPTY) {
+ /*
+ * Handle a special case where no nodes have been written. Write
+ * the first { so we have at least something, since
+ * FDT_REG_SUPERNODES means that a valid tree is required. A
+ * tree with no nodes is not valid
+ */
+ if ((flags & FDT_REG_SUPERNODES) && !info->have_node &&
+ info->start) {
+ /* Output the FDT_BEGIN_NODE and the empty name */
+ if (fdt_add_region(info, base, 8))
+ return 0;
+ }
+ info->ptrs.done++;
+ }
+
+ /* Add a region for the END tag and a separate one for string table */
+ if (info->ptrs.done < FDT_DONE_END) {
+ if (info->ptrs.nextoffset != fdt_size_dt_struct(fdt))
+ return -FDT_ERR_BADSTRUCTURE;
+
+ /* Output the } before the end tag to finish it off */
+ if (info->start == fdt_size_dt_struct(fdt) - 4)
+ info->start -= 4;
+
+ if (fdt_add_region(info, base + info->start,
+ info->ptrs.nextoffset - info->start))
+ return 0;
+ info->ptrs.done++;
+ }
+ if (info->ptrs.done < FDT_DONE_STRINGS) {
+ if (flags & FDT_REG_ADD_STRING_TAB) {
+ info->can_merge = 0;
+ if (fdt_off_dt_strings(fdt) <
+ base + info->ptrs.nextoffset)
+ return -FDT_ERR_BADLAYOUT;
+ if (fdt_add_region(info, fdt_off_dt_strings(fdt),
+ fdt_size_dt_strings(fdt)))
+ return 0;
+ }
+ info->ptrs.done++;
+ }
+
+ return info->count > 0 ? 0 : -FDT_ERR_NOTFOUND;
+}
diff --git a/boot/fdt_simplefb.c b/boot/fdt_simplefb.c
new file mode 100644
index 00000000000..5822131767d
--- /dev/null
+++ b/boot/fdt_simplefb.c
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Simplefb device tree support
+ *
+ * (C) Copyright 2015
+ * Stephen Warren <swarren@wwwdotorg.org>
+ */
+
+#include <dm.h>
+#include <fdt_support.h>
+#include <asm/global_data.h>
+#include <linux/libfdt.h>
+#include <video.h>
+#include <spl.h>
+#include <bloblist.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+static int fdt_simplefb_configure_node(void *blob, int off)
+{
+ int xsize, ysize;
+ int bpix; /* log2 of bits per pixel */
+ const char *name;
+ ulong fb_base;
+ struct video_uc_plat *plat;
+ struct video_priv *uc_priv;
+ struct udevice *dev;
+ int ret;
+
+ if (IS_ENABLED(CONFIG_SPL_VIDEO_HANDOFF) && xpl_phase() > PHASE_SPL) {
+ struct video_handoff *ho;
+
+ ho = bloblist_find(BLOBLISTT_U_BOOT_VIDEO, sizeof(*ho));
+ if (!ho)
+ return log_msg_ret("Missing video bloblist", -ENOENT);
+
+ xsize = ho->xsize;
+ ysize = ho->ysize;
+ bpix = ho->bpix;
+ fb_base = ho->fb;
+ } else {
+ ret = uclass_first_device_err(UCLASS_VIDEO, &dev);
+ if (ret)
+ return ret;
+ uc_priv = dev_get_uclass_priv(dev);
+ plat = dev_get_uclass_plat(dev);
+ xsize = uc_priv->xsize;
+ ysize = uc_priv->ysize;
+ bpix = uc_priv->bpix;
+ fb_base = plat->base;
+ }
+
+ switch (bpix) {
+ case 4: /* VIDEO_BPP16 */
+ name = "r5g6b5";
+ break;
+ case 5: /* VIDEO_BPP32 */
+ name = "a8r8g8b8";
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return fdt_setup_simplefb_node(blob, off, fb_base, xsize, ysize,
+ xsize * (1 << bpix) / 8, name);
+}
+
+int fdt_simplefb_add_node(void *blob)
+{
+ static const char compat[] = "simple-framebuffer";
+ static const char disabled[] = "disabled";
+ int off, ret;
+
+ off = fdt_add_subnode(blob, 0, "framebuffer");
+ if (off < 0)
+ return -1;
+
+ ret = fdt_setprop(blob, off, "status", disabled, sizeof(disabled));
+ if (ret < 0)
+ return -1;
+
+ ret = fdt_setprop(blob, off, "compatible", compat, sizeof(compat));
+ if (ret < 0)
+ return -1;
+
+ return fdt_simplefb_configure_node(blob, off);
+}
+
+/**
+ * fdt_simplefb_enable_existing_node() - enable simple-framebuffer DT node
+ *
+ * @blob: device-tree
+ * Return: 0 on success, non-zero otherwise
+ */
+static int fdt_simplefb_enable_existing_node(void *blob)
+{
+ int off;
+
+ off = fdt_node_offset_by_compatible(blob, -1, "simple-framebuffer");
+ if (off < 0)
+ return -1;
+
+ return fdt_simplefb_configure_node(blob, off);
+}
+
+int fdt_simplefb_enable_and_mem_rsv(void *blob)
+{
+ int ret;
+
+ /* nothing to do when video is not active */
+ if (!video_is_active())
+ return 0;
+
+ ret = fdt_simplefb_enable_existing_node(blob);
+ if (ret)
+ return ret;
+
+ return fdt_add_fb_mem_rsv(blob);
+}
diff --git a/boot/fdt_support.c b/boot/fdt_support.c
new file mode 100644
index 00000000000..b7331bb76b3
--- /dev/null
+++ b/boot/fdt_support.c
@@ -0,0 +1,2270 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2007
+ * Gerald Van Baren, Custom IDEAS, vanbaren@cideas.com
+ *
+ * Copyright 2010-2011 Freescale Semiconductor, Inc.
+ */
+
+#include <dm.h>
+#include <abuf.h>
+#include <env.h>
+#include <log.h>
+#include <mapmem.h>
+#include <net.h>
+#include <rng.h>
+#include <stdio_dev.h>
+#include <dm/device_compat.h>
+#include <dm/ofnode.h>
+#include <linux/ctype.h>
+#include <linux/types.h>
+#include <linux/sizes.h>
+#include <asm/global_data.h>
+#include <asm/unaligned.h>
+#include <linux/libfdt.h>
+#include <fdt_support.h>
+#include <exports.h>
+#include <fdtdec.h>
+#include <version.h>
+#include <video.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/**
+ * fdt_getprop_u32_default_node - Return a node's property or a default
+ *
+ * @fdt: ptr to device tree
+ * @off: offset of node
+ * @cell: cell offset in property
+ * @prop: property name
+ * @dflt: default value if the property isn't found
+ *
+ * Convenience function to return a node's property or a default value if
+ * the property doesn't exist.
+ */
+u32 fdt_getprop_u32_default_node(const void *fdt, int off, int cell,
+ const char *prop, const u32 dflt)
+{
+ const fdt32_t *val;
+ int len;
+
+ val = fdt_getprop(fdt, off, prop, &len);
+
+ /* Check if property exists */
+ if (!val)
+ return dflt;
+
+ /* Check if property is long enough */
+ if (len < ((cell + 1) * sizeof(uint32_t)))
+ return dflt;
+
+ return fdt32_to_cpu(*val);
+}
+
+/**
+ * fdt_getprop_u32_default - Find a node and return it's property or a default
+ *
+ * @fdt: ptr to device tree
+ * @path: path of node
+ * @prop: property name
+ * @dflt: default value if the property isn't found
+ *
+ * Convenience function to find a node and return it's property or a
+ * default value if it doesn't exist.
+ */
+u32 fdt_getprop_u32_default(const void *fdt, const char *path,
+ const char *prop, const u32 dflt)
+{
+ int off;
+
+ off = fdt_path_offset(fdt, path);
+ if (off < 0)
+ return dflt;
+
+ return fdt_getprop_u32_default_node(fdt, off, 0, prop, dflt);
+}
+
+/**
+ * fdt_find_and_setprop: Find a node and set it's property
+ *
+ * @fdt: ptr to device tree
+ * @node: path of node
+ * @prop: property name
+ * @val: ptr to new value
+ * @len: length of new property value
+ * @create: flag to create the property if it doesn't exist
+ *
+ * Convenience function to directly set a property given the path to the node.
+ */
+int fdt_find_and_setprop(void *fdt, const char *node, const char *prop,
+ const void *val, int len, int create)
+{
+ int nodeoff = fdt_path_offset(fdt, node);
+
+ if (nodeoff < 0)
+ return nodeoff;
+
+ if ((!create) && (fdt_get_property(fdt, nodeoff, prop, NULL) == NULL))
+ return 0; /* create flag not set; so exit quietly */
+
+ return fdt_setprop(fdt, nodeoff, prop, val, len);
+}
+
+/**
+ * fdt_find_or_add_subnode() - find or possibly add a subnode of a given node
+ *
+ * @fdt: pointer to the device tree blob
+ * @parentoffset: structure block offset of a node
+ * @name: name of the subnode to locate
+ *
+ * fdt_subnode_offset() finds a subnode of the node with a given name.
+ * If the subnode does not exist, it will be created.
+ */
+int fdt_find_or_add_subnode(void *fdt, int parentoffset, const char *name)
+{
+ int offset;
+
+ offset = fdt_subnode_offset(fdt, parentoffset, name);
+
+ if (offset == -FDT_ERR_NOTFOUND)
+ offset = fdt_add_subnode(fdt, parentoffset, name);
+
+ if (offset < 0)
+ printf("%s: %s: %s\n", __func__, name, fdt_strerror(offset));
+
+ return offset;
+}
+
+#if defined(CONFIG_OF_STDOUT_VIA_ALIAS) && defined(CONFIG_CONS_INDEX)
+static int fdt_fixup_stdout(void *fdt, int chosenoff)
+{
+ int err;
+ int aliasoff;
+ char sername[9] = { 0 };
+ const void *path;
+ int len;
+ char tmp[256]; /* long enough */
+
+ sprintf(sername, "serial%d", CONFIG_CONS_INDEX - 1);
+
+ aliasoff = fdt_path_offset(fdt, "/aliases");
+ if (aliasoff < 0) {
+ err = aliasoff;
+ goto noalias;
+ }
+
+ path = fdt_getprop(fdt, aliasoff, sername, &len);
+ if (!path) {
+ err = len;
+ goto noalias;
+ }
+
+ /* fdt_setprop may break "path" so we copy it to tmp buffer */
+ memcpy(tmp, path, len);
+
+ err = fdt_setprop(fdt, chosenoff, "linux,stdout-path", tmp, len);
+ if (err < 0)
+ printf("WARNING: could not set linux,stdout-path %s.\n",
+ fdt_strerror(err));
+
+ return err;
+
+noalias:
+ printf("WARNING: %s: could not read %s alias: %s\n",
+ __func__, sername, fdt_strerror(err));
+
+ return 0;
+}
+#else
+static int fdt_fixup_stdout(void *fdt, int chosenoff)
+{
+ return 0;
+}
+#endif
+
+static inline int fdt_setprop_uxx(void *fdt, int nodeoffset, const char *name,
+ uint64_t val, int is_u64)
+{
+ if (is_u64)
+ return fdt_setprop_u64(fdt, nodeoffset, name, val);
+ else
+ return fdt_setprop_u32(fdt, nodeoffset, name, (uint32_t)val);
+}
+
+int fdt_root(void *fdt)
+{
+ char *serial;
+ int err;
+
+ err = fdt_check_header(fdt);
+ if (err < 0) {
+ printf("fdt_root: %s\n", fdt_strerror(err));
+ return err;
+ }
+
+ serial = env_get("serial#");
+ if (serial) {
+ err = fdt_setprop(fdt, 0, "serial-number", serial,
+ strlen(serial) + 1);
+
+ if (err < 0) {
+ printf("WARNING: could not set serial-number %s.\n",
+ fdt_strerror(err));
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+int fdt_initrd(void *fdt, ulong initrd_start, ulong initrd_end)
+{
+ int nodeoffset;
+ int err, j, total;
+ int is_u64;
+ uint64_t addr, size;
+
+ /* find or create "/chosen" node. */
+ nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
+ if (nodeoffset < 0)
+ return nodeoffset;
+
+ /*
+ * Although we didn't setup an initrd, there could be a stale
+ * initrd setting from the previous boot firmware in the live
+ * device tree. So, make sure there is no setting left if we
+ * don't want an initrd.
+ */
+ if (initrd_start == initrd_end) {
+ fdt_delprop(fdt, nodeoffset, "linux,initrd-start");
+ fdt_delprop(fdt, nodeoffset, "linux,initrd-end");
+
+ return 0;
+ }
+
+ total = fdt_num_mem_rsv(fdt);
+
+ /*
+ * Look for an existing entry and update it. If we don't find
+ * the entry, we will j be the next available slot.
+ */
+ for (j = 0; j < total; j++) {
+ err = fdt_get_mem_rsv(fdt, j, &addr, &size);
+ if (addr == initrd_start) {
+ fdt_del_mem_rsv(fdt, j);
+ break;
+ }
+ }
+
+ err = fdt_add_mem_rsv(fdt, initrd_start, initrd_end - initrd_start);
+ if (err < 0) {
+ printf("fdt_initrd: %s\n", fdt_strerror(err));
+ return err;
+ }
+
+ is_u64 = (fdt_address_cells(fdt, 0) == 2);
+
+ err = fdt_setprop_uxx(fdt, nodeoffset, "linux,initrd-start",
+ (uint64_t)initrd_start, is_u64);
+
+ if (err < 0) {
+ printf("WARNING: could not set linux,initrd-start %s.\n",
+ fdt_strerror(err));
+ return err;
+ }
+
+ err = fdt_setprop_uxx(fdt, nodeoffset, "linux,initrd-end",
+ (uint64_t)initrd_end, is_u64);
+
+ if (err < 0) {
+ printf("WARNING: could not set linux,initrd-end %s.\n",
+ fdt_strerror(err));
+
+ return err;
+ }
+
+ return 0;
+}
+
+int fdt_kaslrseed(void *fdt, bool overwrite)
+{
+ int len, err, nodeoffset;
+ struct udevice *dev;
+ const u64 *orig;
+ u64 data = 0;
+
+ err = fdt_check_header(fdt);
+ if (err < 0)
+ return err;
+
+ /* find or create "/chosen" node. */
+ nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
+ if (nodeoffset < 0)
+ return nodeoffset;
+
+ /* return without error if we are not overwriting and existing non-zero node */
+ orig = fdt_getprop(fdt, nodeoffset, "kaslr-seed", &len);
+ if (orig && len == sizeof(*orig))
+ data = fdt64_to_cpu(*orig);
+ if (data && !overwrite) {
+ debug("not overwriting existing kaslr-seed\n");
+ return 0;
+ }
+ err = uclass_get_device(UCLASS_RNG, 0, &dev);
+ if (err) {
+ printf("No RNG device\n");
+ return err;
+ }
+ err = dm_rng_read(dev, &data, sizeof(data));
+ if (err) {
+ dev_err(dev, "dm_rng_read failed: %d\n", err);
+ return err;
+ }
+ err = fdt_setprop(fdt, nodeoffset, "kaslr-seed", &data, sizeof(data));
+ if (err < 0)
+ printf("WARNING: could not set kaslr-seed %s.\n", fdt_strerror(err));
+
+ return err;
+}
+
+/**
+ * board_fdt_chosen_bootargs - boards may override this function to use
+ * alternative kernel command line arguments
+ */
+__weak const char *board_fdt_chosen_bootargs(const struct fdt_property *fdt_ba)
+{
+ return env_get("bootargs");
+}
+
+int fdt_chosen(void *fdt)
+{
+ struct abuf buf = {};
+ int nodeoffset;
+ int err;
+ const char *str; /* used to set string properties */
+
+ err = fdt_check_header(fdt);
+ if (err < 0) {
+ printf("fdt_chosen: %s\n", fdt_strerror(err));
+ return err;
+ }
+
+ /* find or create "/chosen" node. */
+ nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
+ if (nodeoffset < 0)
+ return nodeoffset;
+
+ /* if DM_RNG enabled automatically inject kaslr-seed node unless:
+ * CONFIG_MEASURED_BOOT enabled: as dt modifications break measured boot
+ * CONFIG_ARMV8_SEC_FIRMWARE_SUPPORT enabled: as that implementation does not use dm yet
+ */
+ if (IS_ENABLED(CONFIG_DM_RNG) &&
+ !IS_ENABLED(CONFIG_MEASURED_BOOT) &&
+ !IS_ENABLED(CONFIG_ARMV8_SEC_FIRMWARE_SUPPORT))
+ fdt_kaslrseed(fdt, false);
+
+ if (IS_ENABLED(CONFIG_BOARD_RNG_SEED) && !board_rng_seed(&buf)) {
+ err = fdt_setprop(fdt, nodeoffset, "rng-seed",
+ abuf_data(&buf), abuf_size(&buf));
+ abuf_uninit(&buf);
+ if (err < 0) {
+ printf("WARNING: could not set rng-seed %s.\n",
+ fdt_strerror(err));
+ return err;
+ }
+ }
+
+ str = board_fdt_chosen_bootargs(fdt_get_property(fdt, nodeoffset,
+ "bootargs", NULL));
+
+ if (str) {
+ err = fdt_setprop(fdt, nodeoffset, "bootargs", str,
+ strlen(str) + 1);
+ if (err < 0) {
+ printf("WARNING: could not set bootargs %s.\n",
+ fdt_strerror(err));
+ return err;
+ }
+ }
+
+ /* add u-boot version */
+ err = fdt_setprop(fdt, nodeoffset, "u-boot,version", PLAIN_VERSION,
+ strlen(PLAIN_VERSION) + 1);
+ if (err < 0) {
+ printf("WARNING: could not set u-boot,version %s.\n",
+ fdt_strerror(err));
+ return err;
+ }
+
+ return fdt_fixup_stdout(fdt, nodeoffset);
+}
+
+void do_fixup_by_path(void *fdt, const char *path, const char *prop,
+ const void *val, int len, int create)
+{
+#if defined(DEBUG)
+ int i;
+ debug("Updating property '%s/%s' = ", path, prop);
+ for (i = 0; i < len; i++)
+ debug(" %.2x", *(u8*)(val+i));
+ debug("\n");
+#endif
+ int rc = fdt_find_and_setprop(fdt, path, prop, val, len, create);
+ if (rc)
+ printf("Unable to update property %s:%s, err=%s\n",
+ path, prop, fdt_strerror(rc));
+}
+
+void do_fixup_by_path_u32(void *fdt, const char *path, const char *prop,
+ u32 val, int create)
+{
+ fdt32_t tmp = cpu_to_fdt32(val);
+ do_fixup_by_path(fdt, path, prop, &tmp, sizeof(tmp), create);
+}
+
+void do_fixup_by_prop(void *fdt,
+ const char *pname, const void *pval, int plen,
+ const char *prop, const void *val, int len,
+ int create)
+{
+ int off;
+#if defined(DEBUG)
+ int i;
+ debug("Updating property '%s' = ", prop);
+ for (i = 0; i < len; i++)
+ debug(" %.2x", *(u8*)(val+i));
+ debug("\n");
+#endif
+ off = fdt_node_offset_by_prop_value(fdt, -1, pname, pval, plen);
+ while (off != -FDT_ERR_NOTFOUND) {
+ if (create || (fdt_get_property(fdt, off, prop, NULL) != NULL))
+ fdt_setprop(fdt, off, prop, val, len);
+ off = fdt_node_offset_by_prop_value(fdt, off, pname, pval, plen);
+ }
+}
+
+void do_fixup_by_prop_u32(void *fdt,
+ const char *pname, const void *pval, int plen,
+ const char *prop, u32 val, int create)
+{
+ fdt32_t tmp = cpu_to_fdt32(val);
+ do_fixup_by_prop(fdt, pname, pval, plen, prop, &tmp, 4, create);
+}
+
+void do_fixup_by_compat(void *fdt, const char *compat,
+ const char *prop, const void *val, int len, int create)
+{
+ int off = -1;
+#if defined(DEBUG)
+ int i;
+ debug("Updating property '%s' = ", prop);
+ for (i = 0; i < len; i++)
+ debug(" %.2x", *(u8*)(val+i));
+ debug("\n");
+#endif
+ fdt_for_each_node_by_compatible(off, fdt, -1, compat)
+ if (create || (fdt_get_property(fdt, off, prop, NULL) != NULL))
+ fdt_setprop(fdt, off, prop, val, len);
+}
+
+void do_fixup_by_compat_u32(void *fdt, const char *compat,
+ const char *prop, u32 val, int create)
+{
+ fdt32_t tmp = cpu_to_fdt32(val);
+ do_fixup_by_compat(fdt, compat, prop, &tmp, 4, create);
+}
+
+/*
+ * fdt_pack_reg - pack address and size array into the "reg"-suitable stream
+ */
+static int fdt_pack_reg(const void *fdt, void *buf, u64 *address, u64 *size,
+ int n)
+{
+ int i;
+ int address_cells = fdt_address_cells(fdt, 0);
+ int size_cells = fdt_size_cells(fdt, 0);
+ char *p = buf;
+
+ for (i = 0; i < n; i++) {
+ if (address_cells == 2)
+ put_unaligned_be64(address[i], p);
+ else
+ *(fdt32_t *)p = cpu_to_fdt32(address[i]);
+ p += 4 * address_cells;
+
+ if (size_cells == 2)
+ put_unaligned_be64(size[i], p);
+ else
+ *(fdt32_t *)p = cpu_to_fdt32(size[i]);
+ p += 4 * size_cells;
+ }
+
+ return p - (char *)buf;
+}
+
+#ifdef CONFIG_ARCH_FIXUP_FDT_MEMORY
+#if CONFIG_NR_DRAM_BANKS > 4
+#define MEMORY_BANKS_MAX CONFIG_NR_DRAM_BANKS
+#else
+#define MEMORY_BANKS_MAX 4
+#endif
+
+/**
+ * fdt_fixup_memory_banks - Update DT memory node
+ * @blob: Pointer to DT blob
+ * @start: Pointer to memory start addresses array
+ * @size: Pointer to memory sizes array
+ * @banks: Number of memory banks
+ *
+ * Return: 0 on success, negative value on failure
+ *
+ * Based on the passed number of banks and arrays, the function is able to
+ * update existing DT memory nodes to match run time detected/changed memory
+ * configuration. Implementation is handling one specific case with only one
+ * memory node where multiple tuples could be added/updated.
+ * The case where multiple memory nodes with a single tuple (base, size) are
+ * used, this function is only updating the first memory node without removing
+ * others.
+ */
+int fdt_fixup_memory_banks(void *blob, u64 start[], u64 size[], int banks)
+{
+ int err, nodeoffset;
+ int len, i;
+ u8 tmp[MEMORY_BANKS_MAX * 16]; /* Up to 64-bit address + 64-bit size */
+
+ if (banks > MEMORY_BANKS_MAX) {
+ printf("%s: num banks %d exceeds hardcoded limit %d."
+ " Recompile with higher MEMORY_BANKS_MAX?\n",
+ __FUNCTION__, banks, MEMORY_BANKS_MAX);
+ return -1;
+ }
+
+ err = fdt_check_header(blob);
+ if (err < 0) {
+ printf("%s: %s\n", __FUNCTION__, fdt_strerror(err));
+ return err;
+ }
+
+ /* find or create "/memory" node. */
+ nodeoffset = fdt_find_or_add_subnode(blob, 0, "memory");
+ if (nodeoffset < 0)
+ return nodeoffset;
+
+ err = fdt_setprop(blob, nodeoffset, "device_type", "memory",
+ sizeof("memory"));
+ if (err < 0) {
+ printf("WARNING: could not set %s %s.\n", "device_type",
+ fdt_strerror(err));
+ return err;
+ }
+
+ for (i = 0; i < banks; i++) {
+ if (start[i] == 0 && size[i] == 0)
+ break;
+ }
+
+ banks = i;
+
+ if (!banks)
+ return 0;
+
+ len = fdt_pack_reg(blob, tmp, start, size, banks);
+
+ err = fdt_setprop(blob, nodeoffset, "reg", tmp, len);
+ if (err < 0) {
+ printf("WARNING: could not set %s %s.\n",
+ "reg", fdt_strerror(err));
+ return err;
+ }
+ return 0;
+}
+
+int fdt_set_usable_memory(void *blob, u64 start[], u64 size[], int areas)
+{
+ int err, nodeoffset;
+ int len;
+ u8 tmp[8 * 16]; /* Up to 64-bit address + 64-bit size */
+
+ if (areas > 8) {
+ printf("%s: num areas %d exceeds hardcoded limit %d\n",
+ __func__, areas, 8);
+ return -1;
+ }
+
+ err = fdt_check_header(blob);
+ if (err < 0) {
+ printf("%s: %s\n", __func__, fdt_strerror(err));
+ return err;
+ }
+
+ /* find or create "/memory" node. */
+ nodeoffset = fdt_find_or_add_subnode(blob, 0, "memory");
+ if (nodeoffset < 0)
+ return nodeoffset;
+
+ len = fdt_pack_reg(blob, tmp, start, size, areas);
+
+ err = fdt_setprop(blob, nodeoffset, "linux,usable-memory", tmp, len);
+ if (err < 0) {
+ printf("WARNING: could not set %s %s.\n",
+ "reg", fdt_strerror(err));
+ return err;
+ }
+
+ return 0;
+}
+#endif
+
+int fdt_fixup_memory(void *blob, u64 start, u64 size)
+{
+ return fdt_fixup_memory_banks(blob, &start, &size, 1);
+}
+
+void fdt_fixup_ethernet(void *fdt)
+{
+ int i = 0, j, prop;
+ char *tmp, *end;
+ char mac[16];
+ const char *path;
+ unsigned char mac_addr[ARP_HLEN];
+ int offset;
+#ifdef FDT_SEQ_MACADDR_FROM_ENV
+ int nodeoff;
+ const struct fdt_property *fdt_prop;
+#endif
+
+ if (fdt_path_offset(fdt, "/aliases") < 0)
+ return;
+
+ /* Cycle through all aliases */
+ for (prop = 0; ; prop++) {
+ const char *name;
+
+ /* FDT might have been edited, recompute the offset */
+ offset = fdt_first_property_offset(fdt,
+ fdt_path_offset(fdt, "/aliases"));
+ /* Select property number 'prop' */
+ for (j = 0; j < prop; j++)
+ offset = fdt_next_property_offset(fdt, offset);
+
+ if (offset < 0)
+ break;
+
+ path = fdt_getprop_by_offset(fdt, offset, &name, NULL);
+ if (!strncmp(name, "ethernet", 8)) {
+ /* Treat plain "ethernet" same as "ethernet0". */
+ if (!strcmp(name, "ethernet")
+#ifdef FDT_SEQ_MACADDR_FROM_ENV
+ || !strcmp(name, "ethernet0")
+#endif
+ )
+ i = 0;
+#ifndef FDT_SEQ_MACADDR_FROM_ENV
+ else
+ i = trailing_strtol(name);
+#endif
+ if (i != -1) {
+ if (i == 0)
+ strcpy(mac, "ethaddr");
+ else
+ sprintf(mac, "eth%daddr", i);
+ } else {
+ continue;
+ }
+#ifdef FDT_SEQ_MACADDR_FROM_ENV
+ nodeoff = fdt_path_offset(fdt, path);
+ fdt_prop = fdt_get_property(fdt, nodeoff, "status",
+ NULL);
+ if (fdt_prop && !strcmp(fdt_prop->data, "disabled"))
+ continue;
+ i++;
+#endif
+ tmp = env_get(mac);
+ if (!tmp)
+ continue;
+
+ for (j = 0; j < 6; j++) {
+ mac_addr[j] = tmp ?
+ hextoul(tmp, &end) : 0;
+ if (tmp)
+ tmp = (*end) ? end + 1 : end;
+ }
+
+ do_fixup_by_path(fdt, path, "mac-address",
+ &mac_addr, 6, 0);
+ do_fixup_by_path(fdt, path, "local-mac-address",
+ &mac_addr, 6, 1);
+ }
+ }
+}
+
+int fdt_record_loadable(void *blob, u32 index, const char *name,
+ uintptr_t load_addr, u32 size, uintptr_t entry_point,
+ const char *type, const char *os, const char *arch)
+{
+ int err, node;
+
+ err = fdt_check_header(blob);
+ if (err < 0) {
+ printf("%s: %s\n", __func__, fdt_strerror(err));
+ return err;
+ }
+
+ /* find or create "/fit-images" node */
+ node = fdt_find_or_add_subnode(blob, 0, "fit-images");
+ if (node < 0)
+ return node;
+
+ /* find or create "/fit-images/<name>" node */
+ node = fdt_find_or_add_subnode(blob, node, name);
+ if (node < 0)
+ return node;
+
+ fdt_setprop_u64(blob, node, "load", load_addr);
+ if (entry_point != -1)
+ fdt_setprop_u64(blob, node, "entry", entry_point);
+ fdt_setprop_u32(blob, node, "size", size);
+ if (type)
+ fdt_setprop_string(blob, node, "type", type);
+ if (os)
+ fdt_setprop_string(blob, node, "os", os);
+ if (arch)
+ fdt_setprop_string(blob, node, "arch", arch);
+
+ return node;
+}
+
+int fdt_shrink_to_minimum(void *blob, uint extrasize)
+{
+ int i;
+ uint64_t addr, size;
+ int total, ret;
+ uint actualsize;
+ int fdt_memrsv = 0;
+
+ if (!blob)
+ return 0;
+
+ total = fdt_num_mem_rsv(blob);
+ for (i = 0; i < total; i++) {
+ fdt_get_mem_rsv(blob, i, &addr, &size);
+ if (addr == (uintptr_t)blob) {
+ fdt_del_mem_rsv(blob, i);
+ fdt_memrsv = 1;
+ break;
+ }
+ }
+
+ /*
+ * Calculate the actual size of the fdt
+ * plus the size needed for 5 fdt_add_mem_rsv, one
+ * for the fdt itself and 4 for a possible initrd
+ * ((initrd-start + initrd-end) * 2 (name & value))
+ */
+ actualsize = fdt_off_dt_strings(blob) +
+ fdt_size_dt_strings(blob) + 5 * sizeof(struct fdt_reserve_entry);
+
+ actualsize += extrasize;
+ /* Make it so the fdt ends on a page boundary */
+ actualsize = ALIGN(actualsize + ((uintptr_t)blob & 0xfff), 0x1000);
+ actualsize = actualsize - ((uintptr_t)blob & 0xfff);
+
+ /* Change the fdt header to reflect the correct size */
+ fdt_set_totalsize(blob, actualsize);
+
+ if (fdt_memrsv) {
+ /* Add the new reservation */
+ ret = fdt_add_mem_rsv(blob, map_to_sysmem(blob), actualsize);
+ if (ret < 0)
+ return ret;
+ }
+
+ return actualsize;
+}
+
+/**
+ * fdt_delete_disabled_nodes: Delete all nodes with status == "disabled"
+ *
+ * @blob: ptr to device tree
+ */
+int fdt_delete_disabled_nodes(void *blob)
+{
+ while (1) {
+ int ret, offset;
+
+ offset = fdt_node_offset_by_prop_value(blob, -1, "status",
+ "disabled", 9);
+ if (offset < 0)
+ break;
+
+ ret = fdt_del_node(blob, offset);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PCI
+#define CFG_SYS_PCI_NR_INBOUND_WIN 4
+
+#define FDT_PCI_PREFETCH (0x40000000)
+#define FDT_PCI_MEM32 (0x02000000)
+#define FDT_PCI_IO (0x01000000)
+#define FDT_PCI_MEM64 (0x03000000)
+
+int fdt_pci_dma_ranges(void *blob, int phb_off, struct pci_controller *hose) {
+
+ int addrcell, sizecell, len, r;
+ u32 *dma_range;
+ /* sized based on pci addr cells, size-cells, & address-cells */
+ u32 dma_ranges[(3 + 2 + 2) * CFG_SYS_PCI_NR_INBOUND_WIN];
+
+ addrcell = fdt_getprop_u32_default(blob, "/", "#address-cells", 1);
+ sizecell = fdt_getprop_u32_default(blob, "/", "#size-cells", 1);
+
+ dma_range = &dma_ranges[0];
+ for (r = 0; r < hose->region_count; r++) {
+ u64 bus_start, phys_start, size;
+
+ /* skip if !PCI_REGION_SYS_MEMORY */
+ if (!(hose->regions[r].flags & PCI_REGION_SYS_MEMORY))
+ continue;
+
+ bus_start = (u64)hose->regions[r].bus_start;
+ phys_start = (u64)hose->regions[r].phys_start;
+ size = (u64)hose->regions[r].size;
+
+ dma_range[0] = 0;
+ if (size >= 0x100000000ull)
+ dma_range[0] |= cpu_to_fdt32(FDT_PCI_MEM64);
+ else
+ dma_range[0] |= cpu_to_fdt32(FDT_PCI_MEM32);
+ if (hose->regions[r].flags & PCI_REGION_PREFETCH)
+ dma_range[0] |= cpu_to_fdt32(FDT_PCI_PREFETCH);
+#ifdef CONFIG_SYS_PCI_64BIT
+ dma_range[1] = cpu_to_fdt32(bus_start >> 32);
+#else
+ dma_range[1] = 0;
+#endif
+ dma_range[2] = cpu_to_fdt32(bus_start & 0xffffffff);
+
+ if (addrcell == 2) {
+ dma_range[3] = cpu_to_fdt32(phys_start >> 32);
+ dma_range[4] = cpu_to_fdt32(phys_start & 0xffffffff);
+ } else {
+ dma_range[3] = cpu_to_fdt32(phys_start & 0xffffffff);
+ }
+
+ if (sizecell == 2) {
+ dma_range[3 + addrcell + 0] =
+ cpu_to_fdt32(size >> 32);
+ dma_range[3 + addrcell + 1] =
+ cpu_to_fdt32(size & 0xffffffff);
+ } else {
+ dma_range[3 + addrcell + 0] =
+ cpu_to_fdt32(size & 0xffffffff);
+ }
+
+ dma_range += (3 + addrcell + sizecell);
+ }
+
+ len = dma_range - &dma_ranges[0];
+ if (len)
+ fdt_setprop(blob, phb_off, "dma-ranges", &dma_ranges[0], len*4);
+
+ return 0;
+}
+#endif
+
+int fdt_increase_size(void *fdt, int add_len)
+{
+ int newlen;
+
+ newlen = fdt_totalsize(fdt) + add_len;
+
+ /* Open in place with a new len */
+ return fdt_open_into(fdt, fdt, newlen);
+}
+
+#ifdef CONFIG_FDT_FIXUP_PARTITIONS
+#include <jffs2/load_kernel.h>
+#include <mtd_node.h>
+
+static int fdt_del_subnodes(const void *blob, int parent_offset)
+{
+ int off, ndepth;
+ int ret;
+
+ for (ndepth = 0, off = fdt_next_node(blob, parent_offset, &ndepth);
+ (off >= 0) && (ndepth > 0);
+ off = fdt_next_node(blob, off, &ndepth)) {
+ if (ndepth == 1) {
+ debug("delete %s: offset: %x\n",
+ fdt_get_name(blob, off, 0), off);
+ ret = fdt_del_node((void *)blob, off);
+ if (ret < 0) {
+ printf("Can't delete node: %s\n",
+ fdt_strerror(ret));
+ return ret;
+ } else {
+ ndepth = 0;
+ off = parent_offset;
+ }
+ }
+ }
+ return 0;
+}
+
+static int fdt_del_partitions(void *blob, int parent_offset)
+{
+ const void *prop;
+ int ndepth = 0;
+ int off;
+ int ret;
+
+ off = fdt_next_node(blob, parent_offset, &ndepth);
+ if (off > 0 && ndepth == 1) {
+ prop = fdt_getprop(blob, off, "label", NULL);
+ if (prop == NULL) {
+ /*
+ * Could not find label property, nand {}; node?
+ * Check subnode, delete partitions there if any.
+ */
+ return fdt_del_partitions(blob, off);
+ } else {
+ ret = fdt_del_subnodes(blob, parent_offset);
+ if (ret < 0) {
+ printf("Can't remove subnodes: %s\n",
+ fdt_strerror(ret));
+ return ret;
+ }
+ }
+ }
+ return 0;
+}
+
+static int fdt_node_set_part_info(void *blob, int parent_offset,
+ struct mtd_device *dev)
+{
+ struct list_head *pentry;
+ struct part_info *part;
+ int off, ndepth = 0;
+ int part_num, ret;
+ int sizecell;
+ char buf[64];
+
+ ret = fdt_del_partitions(blob, parent_offset);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Check if size/address is 1 or 2 cells.
+ * We assume #address-cells and #size-cells have same value.
+ */
+ sizecell = fdt_getprop_u32_default_node(blob, parent_offset,
+ 0, "#size-cells", 1);
+
+ /*
+ * Check if it is nand {}; subnode, adjust
+ * the offset in this case
+ */
+ off = fdt_next_node(blob, parent_offset, &ndepth);
+ if (off > 0 && ndepth == 1)
+ parent_offset = off;
+
+ part_num = 0;
+ list_for_each_prev(pentry, &dev->parts) {
+ int newoff;
+
+ part = list_entry(pentry, struct part_info, link);
+
+ debug("%2d: %-20s0x%08llx\t0x%08llx\t%d\n",
+ part_num, part->name, part->size,
+ part->offset, part->mask_flags);
+
+ sprintf(buf, "partition@%llx", part->offset);
+add_sub:
+ ret = fdt_add_subnode(blob, parent_offset, buf);
+ if (ret == -FDT_ERR_NOSPACE) {
+ ret = fdt_increase_size(blob, 512);
+ if (!ret)
+ goto add_sub;
+ else
+ goto err_size;
+ } else if (ret < 0) {
+ printf("Can't add partition node: %s\n",
+ fdt_strerror(ret));
+ return ret;
+ }
+ newoff = ret;
+
+ /* Check MTD_WRITEABLE_CMD flag */
+ if (part->mask_flags & 1) {
+add_ro:
+ ret = fdt_setprop(blob, newoff, "read_only", NULL, 0);
+ if (ret == -FDT_ERR_NOSPACE) {
+ ret = fdt_increase_size(blob, 512);
+ if (!ret)
+ goto add_ro;
+ else
+ goto err_size;
+ } else if (ret < 0)
+ goto err_prop;
+ }
+
+add_reg:
+ if (sizecell == 2) {
+ ret = fdt_setprop_u64(blob, newoff,
+ "reg", part->offset);
+ if (!ret)
+ ret = fdt_appendprop_u64(blob, newoff,
+ "reg", part->size);
+ } else {
+ ret = fdt_setprop_u32(blob, newoff,
+ "reg", part->offset);
+ if (!ret)
+ ret = fdt_appendprop_u32(blob, newoff,
+ "reg", part->size);
+ }
+
+ if (ret == -FDT_ERR_NOSPACE) {
+ ret = fdt_increase_size(blob, 512);
+ if (!ret)
+ goto add_reg;
+ else
+ goto err_size;
+ } else if (ret < 0)
+ goto err_prop;
+
+add_label:
+ ret = fdt_setprop_string(blob, newoff, "label", part->name);
+ if (ret == -FDT_ERR_NOSPACE) {
+ ret = fdt_increase_size(blob, 512);
+ if (!ret)
+ goto add_label;
+ else
+ goto err_size;
+ } else if (ret < 0)
+ goto err_prop;
+
+ part_num++;
+ }
+ return 0;
+err_size:
+ printf("Can't increase blob size: %s\n", fdt_strerror(ret));
+ return ret;
+err_prop:
+ printf("Can't add property: %s\n", fdt_strerror(ret));
+ return ret;
+}
+
+/*
+ * Update partitions in nor/nand nodes using info from
+ * mtdparts environment variable. The nodes to update are
+ * specified by node_info structure which contains mtd device
+ * type and compatible string: E. g. the board code in
+ * ft_board_setup() could use:
+ *
+ * struct node_info nodes[] = {
+ * { "fsl,mpc5121-nfc", MTD_DEV_TYPE_NAND, },
+ * { "cfi-flash", MTD_DEV_TYPE_NOR, },
+ * };
+ *
+ * fdt_fixup_mtdparts(blob, nodes, ARRAY_SIZE(nodes));
+ */
+void fdt_fixup_mtdparts(void *blob, const struct node_info *node_info,
+ int node_info_size)
+{
+ struct mtd_device *dev;
+ int i, idx;
+ int noff, parts;
+ bool inited = false;
+
+ for (i = 0; i < node_info_size; i++) {
+ idx = 0;
+
+ fdt_for_each_node_by_compatible(noff, blob, -1,
+ node_info[i].compat) {
+ const char *prop;
+
+ prop = fdt_getprop(blob, noff, "status", NULL);
+ if (prop && !strcmp(prop, "disabled"))
+ continue;
+
+ debug("%s: %s, mtd dev type %d\n",
+ fdt_get_name(blob, noff, 0),
+ node_info[i].compat, node_info[i].type);
+
+ if (!inited) {
+ if (mtdparts_init() != 0)
+ return;
+ inited = true;
+ }
+
+ dev = device_find(node_info[i].type, idx++);
+ if (dev) {
+ parts = fdt_subnode_offset(blob, noff,
+ "partitions");
+ if (parts < 0)
+ parts = noff;
+
+ if (fdt_node_set_part_info(blob, parts, dev))
+ return; /* return on error */
+ }
+ }
+ }
+}
+#endif
+
+int fdt_copy_fixed_partitions(void *blob)
+{
+ ofnode node, subnode;
+ int off, suboff, res;
+ char path[256];
+ int address_cells, size_cells;
+ u8 i, j, child_count;
+
+ node = ofnode_by_compatible(ofnode_null(), "fixed-partitions");
+ while (ofnode_valid(node)) {
+ /* copy the U-Boot fixed partition */
+ address_cells = ofnode_read_simple_addr_cells(node);
+ size_cells = ofnode_read_simple_size_cells(node);
+
+ res = ofnode_get_path(ofnode_get_parent(node), path, sizeof(path));
+ if (res)
+ return res;
+
+ off = fdt_path_offset(blob, path);
+ if (off < 0)
+ return -ENODEV;
+
+ off = fdt_find_or_add_subnode(blob, off, "partitions");
+ res = fdt_setprop_string(blob, off, "compatible", "fixed-partitions");
+ if (res)
+ return res;
+
+ res = fdt_setprop_u32(blob, off, "#address-cells", address_cells);
+ if (res)
+ return res;
+
+ res = fdt_setprop_u32(blob, off, "#size-cells", size_cells);
+ if (res)
+ return res;
+
+ /*
+ * parse partition in reverse order as fdt_find_or_add_subnode() only
+ * insert the new node after the parent's properties
+ */
+ child_count = ofnode_get_child_count(node);
+ for (i = child_count; i > 0 ; i--) {
+ subnode = ofnode_first_subnode(node);
+ if (!ofnode_valid(subnode))
+ break;
+
+ for (j = 0; (j < i - 1); j++)
+ subnode = ofnode_next_subnode(subnode);
+
+ if (!ofnode_valid(subnode))
+ break;
+
+ const u32 *reg;
+ int len;
+
+ suboff = fdt_find_or_add_subnode(blob, off, ofnode_get_name(subnode));
+ res = fdt_setprop_string(blob, suboff, "label",
+ ofnode_read_string(subnode, "label"));
+ if (res)
+ return res;
+
+ reg = ofnode_get_property(subnode, "reg", &len);
+ res = fdt_setprop(blob, suboff, "reg", reg, len);
+ if (res)
+ return res;
+ }
+
+ /* go to next fixed-partitions node */
+ node = ofnode_by_compatible(node, "fixed-partitions");
+ }
+
+ return 0;
+}
+
+void fdt_del_node_and_alias(void *blob, const char *alias)
+{
+ int off = fdt_path_offset(blob, alias);
+
+ if (off < 0)
+ return;
+
+ fdt_del_node(blob, off);
+
+ off = fdt_path_offset(blob, "/aliases");
+ fdt_delprop(blob, off, alias);
+}
+
+/* Max address size we deal with */
+#define OF_MAX_ADDR_CELLS 4
+#define OF_CHECK_COUNTS(na, ns) ((na) > 0 && (na) <= OF_MAX_ADDR_CELLS && \
+ (ns) > 0)
+
+/* Debug utility */
+#ifdef DEBUG
+static void of_dump_addr(const char *s, const fdt32_t *addr, int na)
+{
+ printf("%s", s);
+ while(na--)
+ printf(" %08x", *(addr++));
+ printf("\n");
+}
+#else
+static void of_dump_addr(const char *s, const fdt32_t *addr, int na) { }
+#endif
+
+/**
+ * struct of_bus - Callbacks for bus specific translators
+ * @name: A string used to identify this bus in debug output.
+ * @addresses: The name of the DT property from which addresses are
+ * to be read, typically "reg".
+ * @match: Return non-zero if the node whose parent is at
+ * parentoffset in the FDT blob corresponds to a bus
+ * of this type, otherwise return zero. If NULL a match
+ * is assumed.
+ * @count_cells:Count how many cells (be32 values) a node whose parent
+ * is at parentoffset in the FDT blob will require to
+ * represent its address (written to *addrc) & size
+ * (written to *sizec).
+ * @map: Map the address addr from the address space of this
+ * bus to that of its parent, making use of the ranges
+ * read from DT to an array at range. na and ns are the
+ * number of cells (be32 values) used to hold and address
+ * or size, respectively, for this bus. pna is the number
+ * of cells used to hold an address for the parent bus.
+ * Returns the address in the address space of the parent
+ * bus.
+ * @translate: Update the value of the address cells at addr within an
+ * FDT by adding offset to it. na specifies the number of
+ * cells used to hold the address being translated. Returns
+ * zero on success, non-zero on error.
+ *
+ * Each bus type will include a struct of_bus in the of_busses array,
+ * providing implementations of some or all of the functions used to
+ * match the bus & handle address translation for its children.
+ */
+struct of_bus {
+ const char *name;
+ const char *addresses;
+ int (*match)(const void *blob, int parentoffset);
+ void (*count_cells)(const void *blob, int parentoffset,
+ int *addrc, int *sizec);
+ u64 (*map)(fdt32_t *addr, const fdt32_t *range,
+ int na, int ns, int pna);
+ int (*translate)(fdt32_t *addr, u64 offset, int na);
+};
+
+/* Default translator (generic bus) */
+void fdt_support_default_count_cells(const void *blob, int parentoffset,
+ int *addrc, int *sizec)
+{
+ const fdt32_t *prop;
+
+ if (addrc)
+ *addrc = fdt_address_cells(blob, parentoffset);
+
+ if (sizec) {
+ prop = fdt_getprop(blob, parentoffset, "#size-cells", NULL);
+ if (prop)
+ *sizec = be32_to_cpup(prop);
+ else
+ *sizec = 1;
+ }
+}
+
+static u64 of_bus_default_map(fdt32_t *addr, const fdt32_t *range,
+ int na, int ns, int pna)
+{
+ u64 cp, s, da;
+
+ cp = fdt_read_number(range, na);
+ s = fdt_read_number(range + na + pna, ns);
+ da = fdt_read_number(addr, na);
+
+ debug("OF: default map, cp=%llx, s=%llx, da=%llx\n", cp, s, da);
+
+ if (da < cp || da >= (cp + s))
+ return OF_BAD_ADDR;
+ return da - cp;
+}
+
+static int of_bus_default_translate(fdt32_t *addr, u64 offset, int na)
+{
+ u64 a = fdt_read_number(addr, na);
+ memset(addr, 0, na * 4);
+ a += offset;
+ if (na > 1)
+ addr[na - 2] = cpu_to_fdt32(a >> 32);
+ addr[na - 1] = cpu_to_fdt32(a & 0xffffffffu);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF_ISA_BUS
+
+/* ISA bus translator */
+static int of_bus_isa_match(const void *blob, int parentoffset)
+{
+ const char *name;
+
+ name = fdt_get_name(blob, parentoffset, NULL);
+ if (!name)
+ return 0;
+
+ return !strcmp(name, "isa");
+}
+
+static void of_bus_isa_count_cells(const void *blob, int parentoffset,
+ int *addrc, int *sizec)
+{
+ if (addrc)
+ *addrc = 2;
+ if (sizec)
+ *sizec = 1;
+}
+
+static u64 of_bus_isa_map(fdt32_t *addr, const fdt32_t *range,
+ int na, int ns, int pna)
+{
+ u64 cp, s, da;
+
+ /* Check address type match */
+ if ((addr[0] ^ range[0]) & cpu_to_be32(1))
+ return OF_BAD_ADDR;
+
+ cp = fdt_read_number(range + 1, na - 1);
+ s = fdt_read_number(range + na + pna, ns);
+ da = fdt_read_number(addr + 1, na - 1);
+
+ debug("OF: ISA map, cp=%llx, s=%llx, da=%llx\n", cp, s, da);
+
+ if (da < cp || da >= (cp + s))
+ return OF_BAD_ADDR;
+ return da - cp;
+}
+
+static int of_bus_isa_translate(fdt32_t *addr, u64 offset, int na)
+{
+ return of_bus_default_translate(addr + 1, offset, na - 1);
+}
+
+#endif /* CONFIG_OF_ISA_BUS */
+
+/* Array of bus specific translators */
+static struct of_bus of_busses[] = {
+#ifdef CONFIG_OF_ISA_BUS
+ /* ISA */
+ {
+ .name = "isa",
+ .addresses = "reg",
+ .match = of_bus_isa_match,
+ .count_cells = of_bus_isa_count_cells,
+ .map = of_bus_isa_map,
+ .translate = of_bus_isa_translate,
+ },
+#endif /* CONFIG_OF_ISA_BUS */
+ /* Default */
+ {
+ .name = "default",
+ .addresses = "reg",
+ .count_cells = fdt_support_default_count_cells,
+ .map = of_bus_default_map,
+ .translate = of_bus_default_translate,
+ },
+};
+
+static struct of_bus *of_match_bus(const void *blob, int parentoffset)
+{
+ struct of_bus *bus;
+
+ if (ARRAY_SIZE(of_busses) == 1)
+ return of_busses;
+
+ for (bus = of_busses; bus; bus++) {
+ if (!bus->match || bus->match(blob, parentoffset))
+ return bus;
+ }
+
+ /*
+ * We should always have matched the default bus at least, since
+ * it has a NULL match field. If we didn't then it somehow isn't
+ * in the of_busses array or something equally catastrophic has
+ * gone wrong.
+ */
+ assert(0);
+ return NULL;
+}
+
+static int of_translate_one(const void *blob, int parent, struct of_bus *bus,
+ struct of_bus *pbus, fdt32_t *addr,
+ int na, int ns, int pna, const char *rprop)
+{
+ const fdt32_t *ranges;
+ int rlen;
+ int rone;
+ u64 offset = OF_BAD_ADDR;
+
+ /* Normally, an absence of a "ranges" property means we are
+ * crossing a non-translatable boundary, and thus the addresses
+ * below the current not cannot be converted to CPU physical ones.
+ * Unfortunately, while this is very clear in the spec, it's not
+ * what Apple understood, and they do have things like /uni-n or
+ * /ht nodes with no "ranges" property and a lot of perfectly
+ * useable mapped devices below them. Thus we treat the absence of
+ * "ranges" as equivalent to an empty "ranges" property which means
+ * a 1:1 translation at that level. It's up to the caller not to try
+ * to translate addresses that aren't supposed to be translated in
+ * the first place. --BenH.
+ */
+ ranges = fdt_getprop(blob, parent, rprop, &rlen);
+ if (ranges == NULL || rlen == 0) {
+ offset = fdt_read_number(addr, na);
+ memset(addr, 0, pna * 4);
+ debug("OF: no ranges, 1:1 translation\n");
+ goto finish;
+ }
+
+ debug("OF: walking ranges...\n");
+
+ /* Now walk through the ranges */
+ rlen /= 4;
+ rone = na + pna + ns;
+ for (; rlen >= rone; rlen -= rone, ranges += rone) {
+ offset = bus->map(addr, ranges, na, ns, pna);
+ if (offset != OF_BAD_ADDR)
+ break;
+ }
+ if (offset == OF_BAD_ADDR) {
+ debug("OF: not found !\n");
+ return 1;
+ }
+ memcpy(addr, ranges + na, 4 * pna);
+
+ finish:
+ of_dump_addr("OF: parent translation for:", addr, pna);
+ debug("OF: with offset: %llu\n", offset);
+
+ /* Translate it into parent bus space */
+ return pbus->translate(addr, offset, pna);
+}
+
+/*
+ * Translate an address from the device-tree into a CPU physical address,
+ * this walks up the tree and applies the various bus mappings on the
+ * way.
+ *
+ * Note: We consider that crossing any level with #size-cells == 0 to mean
+ * that translation is impossible (that is we are not dealing with a value
+ * that can be mapped to a cpu physical address). This is not really specified
+ * that way, but this is traditionally the way IBM at least do things
+ */
+static u64 __of_translate_address(const void *blob, int node_offset,
+ const fdt32_t *in_addr, const char *rprop)
+{
+ int parent;
+ struct of_bus *bus, *pbus;
+ fdt32_t addr[OF_MAX_ADDR_CELLS];
+ int na, ns, pna, pns;
+ u64 result = OF_BAD_ADDR;
+
+ debug("OF: ** translation for device %s **\n",
+ fdt_get_name(blob, node_offset, NULL));
+
+ /* Get parent & match bus type */
+ parent = fdt_parent_offset(blob, node_offset);
+ if (parent < 0)
+ goto bail;
+ bus = of_match_bus(blob, parent);
+
+ /* Cound address cells & copy address locally */
+ bus->count_cells(blob, parent, &na, &ns);
+ if (!OF_CHECK_COUNTS(na, ns)) {
+ printf("%s: Bad cell count for %s\n", __FUNCTION__,
+ fdt_get_name(blob, node_offset, NULL));
+ goto bail;
+ }
+ memcpy(addr, in_addr, na * 4);
+
+ debug("OF: bus is %s (na=%d, ns=%d) on %s\n",
+ bus->name, na, ns, fdt_get_name(blob, parent, NULL));
+ of_dump_addr("OF: translating address:", addr, na);
+
+ /* Translate */
+ for (;;) {
+ /* Switch to parent bus */
+ node_offset = parent;
+ parent = fdt_parent_offset(blob, node_offset);
+
+ /* If root, we have finished */
+ if (parent < 0) {
+ debug("OF: reached root node\n");
+ result = fdt_read_number(addr, na);
+ break;
+ }
+
+ /* Get new parent bus and counts */
+ pbus = of_match_bus(blob, parent);
+ pbus->count_cells(blob, parent, &pna, &pns);
+ if (!OF_CHECK_COUNTS(pna, pns)) {
+ printf("%s: Bad cell count for %s\n", __FUNCTION__,
+ fdt_get_name(blob, node_offset, NULL));
+ break;
+ }
+
+ debug("OF: parent bus is %s (na=%d, ns=%d) on %s\n",
+ pbus->name, pna, pns, fdt_get_name(blob, parent, NULL));
+
+ /* Apply bus translation */
+ if (of_translate_one(blob, node_offset, bus, pbus,
+ addr, na, ns, pna, rprop))
+ break;
+
+ /* Complete the move up one level */
+ na = pna;
+ ns = pns;
+ bus = pbus;
+
+ of_dump_addr("OF: one level translation:", addr, na);
+ }
+ bail:
+
+ return result;
+}
+
+u64 fdt_translate_address(const void *blob, int node_offset,
+ const fdt32_t *in_addr)
+{
+ return __of_translate_address(blob, node_offset, in_addr, "ranges");
+}
+
+u64 fdt_translate_dma_address(const void *blob, int node_offset,
+ const fdt32_t *in_addr)
+{
+ return __of_translate_address(blob, node_offset, in_addr, "dma-ranges");
+}
+
+int fdt_get_dma_range(const void *blob, int node, phys_addr_t *cpu,
+ dma_addr_t *bus, u64 *size)
+{
+ bool found_dma_ranges = false;
+ struct of_bus *bus_node;
+ const fdt32_t *ranges;
+ int na, ns, pna, pns;
+ int parent = node;
+ int ret = 0;
+ int len;
+
+ /* Find the closest dma-ranges property */
+ while (parent >= 0) {
+ ranges = fdt_getprop(blob, parent, "dma-ranges", &len);
+
+ /* Ignore empty ranges, they imply no translation required */
+ if (ranges && len > 0)
+ break;
+
+ /* Once we find 'dma-ranges', then a missing one is an error */
+ if (found_dma_ranges && !ranges) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (ranges)
+ found_dma_ranges = true;
+
+ parent = fdt_parent_offset(blob, parent);
+ }
+
+ if (!ranges || parent < 0) {
+ debug("no dma-ranges found for node %s\n",
+ fdt_get_name(blob, node, NULL));
+ ret = -ENOENT;
+ goto out;
+ }
+
+ /* switch to that node */
+ node = parent;
+ parent = fdt_parent_offset(blob, node);
+ if (parent < 0) {
+ printf("Found dma-ranges in root node, shouldn't happen\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Get the address sizes both for the bus and its parent */
+ bus_node = of_match_bus(blob, node);
+ bus_node->count_cells(blob, node, &na, &ns);
+ if (!OF_CHECK_COUNTS(na, ns)) {
+ printf("%s: Bad cell count for %s\n", __FUNCTION__,
+ fdt_get_name(blob, node, NULL));
+ return -EINVAL;
+ goto out;
+ }
+
+ bus_node = of_match_bus(blob, parent);
+ bus_node->count_cells(blob, parent, &pna, &pns);
+ if (!OF_CHECK_COUNTS(pna, pns)) {
+ printf("%s: Bad cell count for %s\n", __FUNCTION__,
+ fdt_get_name(blob, parent, NULL));
+ return -EINVAL;
+ goto out;
+ }
+
+ *bus = fdt_read_number(ranges, na);
+ *cpu = fdt_translate_dma_address(blob, node, ranges + na);
+ *size = fdt_read_number(ranges + na + pna, ns);
+out:
+ return ret;
+}
+
+/**
+ * fdt_node_offset_by_compat_reg: Find a node that matches compatible and
+ * who's reg property matches a physical cpu address
+ *
+ * @blob: ptr to device tree
+ * @compat: compatible string to match
+ * @compat_off: property name
+ *
+ */
+int fdt_node_offset_by_compat_reg(void *blob, const char *compat,
+ phys_addr_t compat_off)
+{
+ int len, off;
+
+ fdt_for_each_node_by_compatible(off, blob, -1, compat) {
+ const fdt32_t *reg = fdt_getprop(blob, off, "reg", &len);
+ if (reg && compat_off == fdt_translate_address(blob, off, reg))
+ return off;
+ }
+
+ return -FDT_ERR_NOTFOUND;
+}
+
+static int vnode_offset_by_pathf(void *blob, const char *fmt, va_list ap)
+{
+ char path[512];
+ int len;
+
+ len = vsnprintf(path, sizeof(path), fmt, ap);
+ if (len < 0 || len + 1 > sizeof(path))
+ return -FDT_ERR_NOSPACE;
+
+ return fdt_path_offset(blob, path);
+}
+
+/**
+ * fdt_node_offset_by_pathf: Find node offset by sprintf formatted path
+ *
+ * @blob: ptr to device tree
+ * @fmt: path format
+ * @ap: vsnprintf arguments
+ */
+int fdt_node_offset_by_pathf(void *blob, const char *fmt, ...)
+{
+ va_list ap;
+ int res;
+
+ va_start(ap, fmt);
+ res = vnode_offset_by_pathf(blob, fmt, ap);
+ va_end(ap);
+
+ return res;
+}
+
+/*
+ * fdt_set_phandle: Create a phandle property for the given node
+ *
+ * @fdt: ptr to device tree
+ * @nodeoffset: node to update
+ * @phandle: phandle value to set (must be unique)
+ */
+int fdt_set_phandle(void *fdt, int nodeoffset, uint32_t phandle)
+{
+ int ret;
+
+#ifdef DEBUG
+ int off = fdt_node_offset_by_phandle(fdt, phandle);
+
+ if ((off >= 0) && (off != nodeoffset)) {
+ char buf[64];
+
+ fdt_get_path(fdt, nodeoffset, buf, sizeof(buf));
+ printf("Trying to update node %s with phandle %u ",
+ buf, phandle);
+
+ fdt_get_path(fdt, off, buf, sizeof(buf));
+ printf("that already exists in node %s.\n", buf);
+ return -FDT_ERR_BADPHANDLE;
+ }
+#endif
+
+ ret = fdt_setprop_cell(fdt, nodeoffset, "phandle", phandle);
+
+ return ret;
+}
+
+/*
+ * fdt_create_phandle: Get or create a phandle property for the given node
+ *
+ * @fdt: ptr to device tree
+ * @nodeoffset: node to update
+ */
+unsigned int fdt_create_phandle(void *fdt, int nodeoffset)
+{
+ /* see if there is a phandle already */
+ uint32_t phandle = fdt_get_phandle(fdt, nodeoffset);
+
+ /* if we got 0, means no phandle so create one */
+ if (phandle == 0) {
+ int ret;
+
+ ret = fdt_generate_phandle(fdt, &phandle);
+ if (ret < 0) {
+ printf("Can't generate phandle: %s\n",
+ fdt_strerror(ret));
+ return 0;
+ }
+
+ ret = fdt_set_phandle(fdt, nodeoffset, phandle);
+ if (ret < 0) {
+ printf("Can't set phandle %u: %s\n", phandle,
+ fdt_strerror(ret));
+ return 0;
+ }
+ }
+
+ return phandle;
+}
+
+/**
+ * fdt_create_phandle_by_compatible: Get or create a phandle for first node with
+ * given compatible
+ *
+ * @fdt: ptr to device tree
+ * @compat: node's compatible string
+ */
+unsigned int fdt_create_phandle_by_compatible(void *fdt, const char *compat)
+{
+ int offset = fdt_node_offset_by_compatible(fdt, -1, compat);
+
+ if (offset < 0) {
+ printf("Can't find node with compatible \"%s\": %s\n", compat,
+ fdt_strerror(offset));
+ return 0;
+ }
+
+ return fdt_create_phandle(fdt, offset);
+}
+
+/**
+ * fdt_create_phandle_by_pathf: Get or create a phandle for node given by
+ * sprintf-formatted path
+ *
+ * @fdt: ptr to device tree
+ * @fmt, ...: path format string and arguments to pass to sprintf
+ */
+unsigned int fdt_create_phandle_by_pathf(void *fdt, const char *fmt, ...)
+{
+ va_list ap;
+ int offset;
+
+ va_start(ap, fmt);
+ offset = vnode_offset_by_pathf(fdt, fmt, ap);
+ va_end(ap);
+
+ if (offset < 0) {
+ printf("Can't find node by given path: %s\n",
+ fdt_strerror(offset));
+ return 0;
+ }
+
+ return fdt_create_phandle(fdt, offset);
+}
+
+/*
+ * fdt_set_node_status: Set status for the given node
+ *
+ * @fdt: ptr to device tree
+ * @nodeoffset: node to update
+ * @status: FDT_STATUS_OKAY, FDT_STATUS_DISABLED, FDT_STATUS_FAIL
+ */
+int fdt_set_node_status(void *fdt, int nodeoffset, enum fdt_status status)
+{
+ int ret = 0;
+
+ if (nodeoffset < 0)
+ return nodeoffset;
+
+ switch (status) {
+ case FDT_STATUS_OKAY:
+ ret = fdt_setprop_string(fdt, nodeoffset, "status", "okay");
+ break;
+ case FDT_STATUS_DISABLED:
+ ret = fdt_setprop_string(fdt, nodeoffset, "status", "disabled");
+ break;
+ case FDT_STATUS_FAIL:
+ ret = fdt_setprop_string(fdt, nodeoffset, "status", "fail");
+ break;
+ default:
+ printf("Invalid fdt status: %x\n", status);
+ ret = -1;
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * fdt_set_status_by_alias: Set status for the given node given an alias
+ *
+ * @fdt: ptr to device tree
+ * @alias: alias of node to update
+ * @status: FDT_STATUS_OKAY, FDT_STATUS_DISABLED, FDT_STATUS_FAIL
+ */
+int fdt_set_status_by_alias(void *fdt, const char* alias,
+ enum fdt_status status)
+{
+ int offset = fdt_path_offset(fdt, alias);
+
+ return fdt_set_node_status(fdt, offset, status);
+}
+
+/**
+ * fdt_set_status_by_compatible: Set node status for first node with given
+ * compatible
+ *
+ * @fdt: ptr to device tree
+ * @compat: node's compatible string
+ * @status: FDT_STATUS_OKAY, FDT_STATUS_DISABLED, FDT_STATUS_FAIL
+ */
+int fdt_set_status_by_compatible(void *fdt, const char *compat,
+ enum fdt_status status)
+{
+ int offset = fdt_node_offset_by_compatible(fdt, -1, compat);
+
+ if (offset < 0)
+ return offset;
+
+ return fdt_set_node_status(fdt, offset, status);
+}
+
+/**
+ * fdt_set_status_by_pathf: Set node status for node given by sprintf-formatted
+ * path
+ *
+ * @fdt: ptr to device tree
+ * @status: FDT_STATUS_OKAY, FDT_STATUS_DISABLED, FDT_STATUS_FAIL
+ * @fmt, ...: path format string and arguments to pass to sprintf
+ */
+int fdt_set_status_by_pathf(void *fdt, enum fdt_status status, const char *fmt,
+ ...)
+{
+ va_list ap;
+ int offset;
+
+ va_start(ap, fmt);
+ offset = vnode_offset_by_pathf(fdt, fmt, ap);
+ va_end(ap);
+
+ if (offset < 0)
+ return offset;
+
+ return fdt_set_node_status(fdt, offset, status);
+}
+
+/*
+ * Verify the physical address of device tree node for a given alias
+ *
+ * This function locates the device tree node of a given alias, and then
+ * verifies that the physical address of that device matches the given
+ * parameter. It displays a message if there is a mismatch.
+ *
+ * Returns 1 on success, 0 on failure
+ */
+int fdt_verify_alias_address(void *fdt, int anode, const char *alias, u64 addr)
+{
+ const char *path;
+ const fdt32_t *reg;
+ int node, len;
+ u64 dt_addr;
+
+ path = fdt_getprop(fdt, anode, alias, NULL);
+ if (!path) {
+ /* If there's no such alias, then it's not a failure */
+ return 1;
+ }
+
+ node = fdt_path_offset(fdt, path);
+ if (node < 0) {
+ printf("Warning: device tree alias '%s' points to invalid "
+ "node %s.\n", alias, path);
+ return 0;
+ }
+
+ reg = fdt_getprop(fdt, node, "reg", &len);
+ if (!reg) {
+ printf("Warning: device tree node '%s' has no address.\n",
+ path);
+ return 0;
+ }
+
+ dt_addr = fdt_translate_address(fdt, node, reg);
+ if (addr != dt_addr) {
+ printf("Warning: U-Boot configured device %s at address %llu,\n"
+ "but the device tree has it address %llx.\n",
+ alias, addr, dt_addr);
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * Returns the base address of an SOC or PCI node
+ */
+u64 fdt_get_base_address(const void *fdt, int node)
+{
+ int size;
+ const fdt32_t *prop;
+
+ prop = fdt_getprop(fdt, node, "reg", &size);
+
+ return prop ? fdt_translate_address(fdt, node, prop) : OF_BAD_ADDR;
+}
+
+/*
+ * Read a property of size <prop_len>. Currently only supports 1 or 2 cells,
+ * or 3 cells specially for a PCI address.
+ */
+static int fdt_read_prop(const fdt32_t *prop, int prop_len, int cell_off,
+ uint64_t *val, int cells)
+{
+ const fdt32_t *prop32;
+ const unaligned_fdt64_t *prop64;
+
+ if ((cell_off + cells) > prop_len)
+ return -FDT_ERR_NOSPACE;
+
+ prop32 = &prop[cell_off];
+
+ /*
+ * Special handling for PCI address in PCI bus <ranges>
+ *
+ * PCI child address is made up of 3 cells. Advance the cell offset
+ * by 1 so that the PCI child address can be correctly read.
+ */
+ if (cells == 3)
+ cell_off += 1;
+ prop64 = (const fdt64_t *)&prop[cell_off];
+
+ switch (cells) {
+ case 1:
+ *val = fdt32_to_cpu(*prop32);
+ break;
+ case 2:
+ case 3:
+ *val = fdt64_to_cpu(*prop64);
+ break;
+ default:
+ return -FDT_ERR_NOSPACE;
+ }
+
+ return 0;
+}
+
+/**
+ * fdt_read_range - Read a node's n'th range property
+ *
+ * @fdt: ptr to device tree
+ * @node: offset of node
+ * @n: range index
+ * @child_addr: pointer to storage for the "child address" field
+ * @addr: pointer to storage for the CPU view translated physical start
+ * @len: pointer to storage for the range length
+ *
+ * Convenience function that reads and interprets a specific range out of
+ * a number of the "ranges" property array.
+ */
+int fdt_read_range(void *fdt, int node, int n, uint64_t *child_addr,
+ uint64_t *addr, uint64_t *len)
+{
+ int pnode = fdt_parent_offset(fdt, node);
+ const fdt32_t *ranges;
+ int pacells;
+ int acells;
+ int scells;
+ int ranges_len;
+ int cell = 0;
+ int r = 0;
+
+ /*
+ * The "ranges" property is an array of
+ * { <child address> <parent address> <size in child address space> }
+ *
+ * All 3 elements can span a diffent number of cells. Fetch their size.
+ */
+ pacells = fdt_getprop_u32_default_node(fdt, pnode, 0, "#address-cells", 1);
+ acells = fdt_getprop_u32_default_node(fdt, node, 0, "#address-cells", 1);
+ scells = fdt_getprop_u32_default_node(fdt, node, 0, "#size-cells", 1);
+
+ /* Now try to get the ranges property */
+ ranges = fdt_getprop(fdt, node, "ranges", &ranges_len);
+ if (!ranges)
+ return -FDT_ERR_NOTFOUND;
+ ranges_len /= sizeof(uint32_t);
+
+ /* Jump to the n'th entry */
+ cell = n * (pacells + acells + scells);
+
+ /* Read <child address> */
+ if (child_addr) {
+ r = fdt_read_prop(ranges, ranges_len, cell, child_addr,
+ acells);
+ if (r)
+ return r;
+ }
+ cell += acells;
+
+ /* Read <parent address> */
+ if (addr)
+ *addr = fdt_translate_address(fdt, node, ranges + cell);
+ cell += pacells;
+
+ /* Read <size in child address space> */
+ if (len) {
+ r = fdt_read_prop(ranges, ranges_len, cell, len, scells);
+ if (r)
+ return r;
+ }
+
+ return 0;
+}
+
+/**
+ * fdt_setup_simplefb_node - Fill and enable a simplefb node
+ *
+ * @fdt: ptr to device tree
+ * @node: offset of the simplefb node
+ * @base_address: framebuffer base address
+ * @width: width in pixels
+ * @height: height in pixels
+ * @stride: bytes per line
+ * @format: pixel format string
+ *
+ * Convenience function to fill and enable a simplefb node.
+ */
+int fdt_setup_simplefb_node(void *fdt, int node, u64 base_address, u32 width,
+ u32 height, u32 stride, const char *format)
+{
+ char name[32];
+ fdt32_t cells[4];
+ int i, addrc, sizec, ret;
+
+ fdt_support_default_count_cells(fdt, fdt_parent_offset(fdt, node),
+ &addrc, &sizec);
+ i = 0;
+ if (addrc == 2)
+ cells[i++] = cpu_to_fdt32(base_address >> 32);
+ cells[i++] = cpu_to_fdt32(base_address);
+ if (sizec == 2)
+ cells[i++] = 0;
+ cells[i++] = cpu_to_fdt32(height * stride);
+
+ ret = fdt_setprop(fdt, node, "reg", cells, sizeof(cells[0]) * i);
+ if (ret < 0)
+ return ret;
+
+ snprintf(name, sizeof(name), "framebuffer@%llx", base_address);
+ ret = fdt_set_name(fdt, node, name);
+ if (ret < 0)
+ return ret;
+
+ ret = fdt_setprop_u32(fdt, node, "width", width);
+ if (ret < 0)
+ return ret;
+
+ ret = fdt_setprop_u32(fdt, node, "height", height);
+ if (ret < 0)
+ return ret;
+
+ ret = fdt_setprop_u32(fdt, node, "stride", stride);
+ if (ret < 0)
+ return ret;
+
+ ret = fdt_setprop_string(fdt, node, "format", format);
+ if (ret < 0)
+ return ret;
+
+ ret = fdt_setprop_string(fdt, node, "status", "okay");
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+#if CONFIG_IS_ENABLED(VIDEO)
+int fdt_add_fb_mem_rsv(void *blob)
+{
+ struct fdt_memory mem;
+
+ /* nothing to do when the frame buffer is not defined */
+ if (gd->video_bottom == gd->video_top)
+ return 0;
+
+ /* reserved with no-map tag the video buffer */
+ mem.start = gd->video_bottom;
+ mem.end = gd->video_top - 1;
+
+ return fdtdec_add_reserved_memory(blob, "framebuffer", &mem, NULL, 0, NULL,
+ FDTDEC_RESERVED_MEMORY_NO_MAP);
+}
+#endif
+
+/*
+ * Update native-mode in display-timings from display environment variable.
+ * The node to update are specified by path.
+ */
+int fdt_fixup_display(void *blob, const char *path, const char *display)
+{
+ int off, toff;
+
+ if (!display || !path)
+ return -FDT_ERR_NOTFOUND;
+
+ toff = fdt_path_offset(blob, path);
+ if (toff >= 0)
+ toff = fdt_subnode_offset(blob, toff, "display-timings");
+ if (toff < 0)
+ return toff;
+
+ for (off = fdt_first_subnode(blob, toff);
+ off >= 0;
+ off = fdt_next_subnode(blob, off)) {
+ uint32_t h = fdt_get_phandle(blob, off);
+ debug("%s:0x%x\n", fdt_get_name(blob, off, NULL),
+ fdt32_to_cpu(h));
+ if (strcasecmp(fdt_get_name(blob, off, NULL), display) == 0)
+ return fdt_setprop_u32(blob, toff, "native-mode", h);
+ }
+ return toff;
+}
+
+#ifdef CONFIG_OF_LIBFDT_OVERLAY
+/**
+ * fdt_overlay_apply_verbose - Apply an overlay with verbose error reporting
+ *
+ * @fdt: ptr to device tree
+ * @fdto: ptr to device tree overlay
+ *
+ * Convenience function to apply an overlay and display helpful messages
+ * in the case of an error
+ */
+int fdt_overlay_apply_verbose(void *fdt, void *fdto)
+{
+ int err;
+ bool has_symbols;
+
+ err = fdt_path_offset(fdt, "/__symbols__");
+ has_symbols = err >= 0;
+
+ err = fdt_overlay_apply(fdt, fdto);
+ if (err < 0) {
+ printf("failed on fdt_overlay_apply(): %s\n",
+ fdt_strerror(err));
+ if (!has_symbols) {
+ printf("base fdt does not have a /__symbols__ node\n");
+ printf("make sure you've compiled with -@\n");
+ }
+ }
+ return err;
+}
+#endif
+
+/**
+ * fdt_valid() - Check if an FDT is valid. If not, change it to NULL
+ *
+ * @blobp: Pointer to FDT pointer
+ * Return: 1 if OK, 0 if bad (in which case *blobp is set to NULL)
+ */
+int fdt_valid(struct fdt_header **blobp)
+{
+ const void *blob = *blobp;
+ int err;
+
+ if (!blob) {
+ printf("The address of the fdt is invalid (NULL).\n");
+ return 0;
+ }
+
+ err = fdt_check_header(blob);
+ if (err == 0)
+ return 1; /* valid */
+
+ if (err < 0) {
+ printf("libfdt fdt_check_header(): %s", fdt_strerror(err));
+ /*
+ * Be more informative on bad version.
+ */
+ if (err == -FDT_ERR_BADVERSION) {
+ if (fdt_version(blob) <
+ FDT_FIRST_SUPPORTED_VERSION) {
+ printf(" - too old, fdt %d < %d",
+ fdt_version(blob),
+ FDT_FIRST_SUPPORTED_VERSION);
+ }
+ if (fdt_last_comp_version(blob) >
+ FDT_LAST_SUPPORTED_VERSION) {
+ printf(" - too new, fdt %d > %d",
+ fdt_version(blob),
+ FDT_LAST_SUPPORTED_VERSION);
+ }
+ }
+ printf("\n");
+ *blobp = NULL;
+ return 0;
+ }
+ return 1;
+}
+
+int fdt_fixup_pmem_region(void *fdt, u64 pmem_start, u64 pmem_size)
+{
+ char node_name[32];
+ int nodeoffset, len;
+ int err;
+ u8 tmp[4 * 16]; /* Up to 64-bit address + 64-bit size */
+
+ if (!IS_ALIGNED(pmem_start, SZ_2M) ||
+ !IS_ALIGNED(pmem_start + pmem_size, SZ_2M)) {
+ printf("Start and end address must be 2MiB aligned\n");
+ return -1;
+ }
+
+ snprintf(node_name, sizeof(node_name), "pmem@%llx", pmem_start);
+ nodeoffset = fdt_find_or_add_subnode(fdt, 0, node_name);
+ if (nodeoffset < 0)
+ return nodeoffset;
+
+ err = fdt_setprop_string(fdt, nodeoffset, "compatible", "pmem-region");
+ if (err)
+ return err;
+ err = fdt_setprop_empty(fdt, nodeoffset, "volatile");
+ if (err)
+ return err;
+
+ len = fdt_pack_reg(fdt, tmp, &pmem_start, &pmem_size, 1);
+ err = fdt_setprop(fdt, nodeoffset, "reg", tmp, len);
+ if (err < 0) {
+ printf("WARNING: could not set pmem %s %s.\n", "reg",
+ fdt_strerror(err));
+ return err;
+ }
+
+ return 0;
+}
diff --git a/boot/image-android-dt.c b/boot/image-android-dt.c
new file mode 100644
index 00000000000..653835cea76
--- /dev/null
+++ b/boot/image-android-dt.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2018 Linaro Ltd.
+ * Sam Protsenko <semen.protsenko@linaro.org>
+ */
+
+#include <image-android-dt.h>
+#include <dt_table.h>
+#include <linux/libfdt.h>
+#include <mapmem.h>
+
+/**
+ * Check if image header is correct.
+ *
+ * @param hdr_addr Start address of DT image
+ * Return: true if header is correct or false if header is incorrect
+ */
+bool android_dt_check_header(ulong hdr_addr)
+{
+ const struct dt_table_header *hdr;
+ u32 magic;
+
+ hdr = map_sysmem(hdr_addr, sizeof(*hdr));
+ magic = fdt32_to_cpu(hdr->magic);
+ unmap_sysmem(hdr);
+
+ return magic == DT_TABLE_MAGIC;
+}
+
+/**
+ * Get the address of FDT (dtb or dtbo) in memory by its index in image.
+ *
+ * @param hdr_addr Start address of DT image
+ * @param index Index of desired FDT in image (starting from 0)
+ * @param[out] addr If not NULL, will contain address to specified FDT
+ * @param[out] size If not NULL, will contain size of specified FDT
+ *
+ * Return: true on success or false on error
+ */
+bool android_dt_get_fdt_by_index(ulong hdr_addr, u32 index, ulong *addr,
+ u32 *size)
+{
+ const struct dt_table_header *hdr;
+ const struct dt_table_entry *e;
+ u32 entry_count, entries_offset, entry_size;
+ ulong e_addr;
+ u32 dt_offset, dt_size;
+
+ hdr = map_sysmem(hdr_addr, sizeof(*hdr));
+ entry_count = fdt32_to_cpu(hdr->dt_entry_count);
+ entries_offset = fdt32_to_cpu(hdr->dt_entries_offset);
+ entry_size = fdt32_to_cpu(hdr->dt_entry_size);
+ unmap_sysmem(hdr);
+
+ if (index >= entry_count) {
+ printf("Error: index >= dt_entry_count (%u >= %u)\n", index,
+ entry_count);
+ return false;
+ }
+
+ e_addr = hdr_addr + entries_offset + index * entry_size;
+ e = map_sysmem(e_addr, sizeof(*e));
+ dt_offset = fdt32_to_cpu(e->dt_offset);
+ dt_size = fdt32_to_cpu(e->dt_size);
+ unmap_sysmem(e);
+
+ if (addr)
+ *addr = hdr_addr + dt_offset;
+ if (size)
+ *size = dt_size;
+
+ return true;
+}
+
+#if !defined(CONFIG_XPL_BUILD)
+static void android_dt_print_fdt_info(const struct fdt_header *fdt)
+{
+ u32 fdt_size;
+ int root_node_off;
+ const char *compatible;
+
+ root_node_off = fdt_path_offset(fdt, "/");
+ if (root_node_off < 0) {
+ printf("Error: Root node not found\n");
+ return;
+ }
+
+ fdt_size = fdt_totalsize(fdt);
+ compatible = fdt_getprop(fdt, root_node_off, "compatible",
+ NULL);
+
+ printf(" (FDT)size = %d\n", fdt_size);
+ printf(" (FDT)compatible = %s\n",
+ compatible ? compatible : "(unknown)");
+}
+
+/**
+ * Print information about DT image structure.
+ *
+ * @param hdr_addr Start address of DT image
+ */
+void android_dt_print_contents(ulong hdr_addr)
+{
+ const struct dt_table_header *hdr;
+ u32 entry_count, entries_offset, entry_size;
+ u32 i;
+
+ hdr = map_sysmem(hdr_addr, sizeof(*hdr));
+ entry_count = fdt32_to_cpu(hdr->dt_entry_count);
+ entries_offset = fdt32_to_cpu(hdr->dt_entries_offset);
+ entry_size = fdt32_to_cpu(hdr->dt_entry_size);
+
+ /* Print image header info */
+ printf("dt_table_header:\n");
+ printf(" magic = %08x\n", fdt32_to_cpu(hdr->magic));
+ printf(" total_size = %d\n", fdt32_to_cpu(hdr->total_size));
+ printf(" header_size = %d\n", fdt32_to_cpu(hdr->header_size));
+ printf(" dt_entry_size = %d\n", entry_size);
+ printf(" dt_entry_count = %d\n", entry_count);
+ printf(" dt_entries_offset = %d\n", entries_offset);
+ printf(" page_size = %d\n", fdt32_to_cpu(hdr->page_size));
+ printf(" version = %d\n", fdt32_to_cpu(hdr->version));
+
+ unmap_sysmem(hdr);
+
+ /* Print image entries info */
+ for (i = 0; i < entry_count; ++i) {
+ const ulong e_addr = hdr_addr + entries_offset + i * entry_size;
+ const struct dt_table_entry *e;
+ const struct fdt_header *fdt;
+ u32 dt_offset, dt_size;
+ u32 j;
+
+ e = map_sysmem(e_addr, sizeof(*e));
+ dt_offset = fdt32_to_cpu(e->dt_offset);
+ dt_size = fdt32_to_cpu(e->dt_size);
+
+ printf("dt_table_entry[%d]:\n", i);
+ printf(" dt_size = %d\n", dt_size);
+ printf(" dt_offset = %d\n", dt_offset);
+ printf(" id = %08x\n", fdt32_to_cpu(e->id));
+ printf(" rev = %08x\n", fdt32_to_cpu(e->rev));
+ for (j = 0; j < 4; ++j) {
+ printf(" custom[%d] = %08x\n", j,
+ fdt32_to_cpu(e->custom[j]));
+ }
+
+ unmap_sysmem(e);
+
+ /* Print FDT info for this entry */
+ fdt = map_sysmem(hdr_addr + dt_offset, sizeof(*fdt));
+ android_dt_print_fdt_info(fdt);
+ unmap_sysmem(fdt);
+ }
+}
+#endif
diff --git a/boot/image-android.c b/boot/image-android.c
new file mode 100644
index 00000000000..1cd2060bb3f
--- /dev/null
+++ b/boot/image-android.c
@@ -0,0 +1,906 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2011 Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+ */
+
+#include <env.h>
+#include <image.h>
+#include <image-android-dt.h>
+#include <android_image.h>
+#include <malloc.h>
+#include <errno.h>
+#include <asm/unaligned.h>
+#include <mapmem.h>
+#include <linux/libfdt.h>
+
+#define ANDROID_IMAGE_DEFAULT_KERNEL_ADDR 0x10008000
+#define ANDROID_IMAGE_DEFAULT_RAMDISK_ADDR 0x11000000
+
+static char andr_tmp_str[ANDR_BOOT_ARGS_SIZE + 1];
+
+static ulong checksum(const unsigned char *buffer, ulong size)
+{
+ ulong sum = 0;
+
+ for (ulong i = 0; i < size; i++)
+ sum += buffer[i];
+ return sum;
+}
+
+static bool is_trailer_present(ulong bootconfig_end_addr)
+{
+ return !strncmp((char *)(bootconfig_end_addr - BOOTCONFIG_MAGIC_SIZE),
+ BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_SIZE);
+}
+
+static ulong add_trailer(ulong bootconfig_start_addr, ulong bootconfig_size)
+{
+ ulong end;
+ ulong sum;
+
+ if (!bootconfig_start_addr)
+ return -1;
+ if (!bootconfig_size)
+ return 0;
+
+ end = bootconfig_start_addr + bootconfig_size;
+ if (is_trailer_present(end))
+ return 0;
+
+ memcpy((void *)(end), &bootconfig_size, BOOTCONFIG_SIZE_SIZE);
+ sum = checksum((unsigned char *)bootconfig_start_addr, bootconfig_size);
+ memcpy((void *)(end + BOOTCONFIG_SIZE_SIZE), &sum,
+ BOOTCONFIG_CHECKSUM_SIZE);
+ memcpy((void *)(end + BOOTCONFIG_SIZE_SIZE + BOOTCONFIG_CHECKSUM_SIZE),
+ BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_SIZE);
+
+ return BOOTCONFIG_TRAILER_SIZE;
+}
+
+__weak ulong get_avendor_bootimg_addr(void)
+{
+ return -1;
+}
+
+static void android_boot_image_v3_v4_parse_hdr(const struct andr_boot_img_hdr_v3 *hdr,
+ struct andr_image_data *data)
+{
+ ulong end;
+
+ data->kcmdline = hdr->cmdline;
+ data->header_version = hdr->header_version;
+
+ /*
+ * The header takes a full page, the remaining components are aligned
+ * on page boundary.
+ */
+ end = (ulong)hdr;
+ end += ANDR_GKI_PAGE_SIZE;
+ data->kernel_ptr = end;
+ data->kernel_size = hdr->kernel_size;
+ end += ALIGN(hdr->kernel_size, ANDR_GKI_PAGE_SIZE);
+ data->ramdisk_ptr = end;
+ data->ramdisk_size = hdr->ramdisk_size;
+ data->boot_ramdisk_size = hdr->ramdisk_size;
+ end += ALIGN(hdr->ramdisk_size, ANDR_GKI_PAGE_SIZE);
+
+ if (hdr->header_version > 3)
+ end += ALIGN(hdr->signature_size, ANDR_GKI_PAGE_SIZE);
+
+ data->boot_img_total_size = end - (ulong)hdr;
+}
+
+static void android_vendor_boot_image_v3_v4_parse_hdr(const struct andr_vnd_boot_img_hdr
+ *hdr, struct andr_image_data *data)
+{
+ ulong end;
+
+ /*
+ * The header takes a full page, the remaining components are aligned
+ * on page boundary.
+ */
+ data->kcmdline_extra = hdr->cmdline;
+ data->tags_addr = hdr->tags_addr;
+ data->image_name = hdr->name;
+ data->kernel_addr = hdr->kernel_addr;
+ data->ramdisk_addr = hdr->ramdisk_addr;
+ data->dtb_load_addr = hdr->dtb_addr;
+ data->bootconfig_size = hdr->bootconfig_size;
+ end = (ulong)hdr;
+ end += hdr->page_size;
+ if (hdr->vendor_ramdisk_size) {
+ data->vendor_ramdisk_ptr = end;
+ data->vendor_ramdisk_size = hdr->vendor_ramdisk_size;
+ data->ramdisk_size += hdr->vendor_ramdisk_size;
+ end += ALIGN(hdr->vendor_ramdisk_size, hdr->page_size);
+ }
+
+ data->dtb_ptr = end;
+ data->dtb_size = hdr->dtb_size;
+
+ end += ALIGN(hdr->dtb_size, hdr->page_size);
+ end += ALIGN(hdr->vendor_ramdisk_table_size, hdr->page_size);
+ data->bootconfig_addr = end;
+ if (hdr->bootconfig_size) {
+ data->bootconfig_size += add_trailer(data->bootconfig_addr,
+ data->bootconfig_size);
+ data->ramdisk_size += data->bootconfig_size;
+ }
+ end += ALIGN(data->bootconfig_size, hdr->page_size);
+ data->vendor_boot_img_total_size = end - (ulong)hdr;
+}
+
+static void android_boot_image_v0_v1_v2_parse_hdr(const struct andr_boot_img_hdr_v0 *hdr,
+ struct andr_image_data *data)
+{
+ ulong end;
+
+ data->image_name = hdr->name;
+ data->kcmdline = hdr->cmdline;
+ data->kernel_addr = hdr->kernel_addr;
+ data->ramdisk_addr = hdr->ramdisk_addr;
+ data->header_version = hdr->header_version;
+ data->dtb_load_addr = hdr->dtb_addr;
+
+ end = (ulong)hdr;
+
+ /*
+ * The header takes a full page, the remaining components are aligned
+ * on page boundary
+ */
+
+ end += hdr->page_size;
+
+ data->kernel_ptr = end;
+ data->kernel_size = hdr->kernel_size;
+ end += ALIGN(hdr->kernel_size, hdr->page_size);
+
+ data->ramdisk_ptr = end;
+ data->ramdisk_size = hdr->ramdisk_size;
+ end += ALIGN(hdr->ramdisk_size, hdr->page_size);
+
+ data->second_ptr = end;
+ data->second_size = hdr->second_size;
+ end += ALIGN(hdr->second_size, hdr->page_size);
+
+ if (hdr->header_version >= 1) {
+ data->recovery_dtbo_ptr = end;
+ data->recovery_dtbo_size = hdr->recovery_dtbo_size;
+ end += ALIGN(hdr->recovery_dtbo_size, hdr->page_size);
+ }
+
+ if (hdr->header_version >= 2) {
+ data->dtb_ptr = end;
+ data->dtb_size = hdr->dtb_size;
+ end += ALIGN(hdr->dtb_size, hdr->page_size);
+ }
+
+ data->boot_img_total_size = end - (ulong)hdr;
+}
+
+bool android_image_get_bootimg_size(const void *hdr, u32 *boot_img_size)
+{
+ struct andr_image_data data;
+
+ if (!hdr || !boot_img_size) {
+ printf("hdr or boot_img_size can't be NULL\n");
+ return false;
+ }
+
+ if (!is_android_boot_image_header(hdr)) {
+ printf("Incorrect boot image header\n");
+ return false;
+ }
+
+ if (((struct andr_boot_img_hdr_v0 *)hdr)->header_version <= 2)
+ android_boot_image_v0_v1_v2_parse_hdr(hdr, &data);
+ else
+ android_boot_image_v3_v4_parse_hdr(hdr, &data);
+
+ *boot_img_size = data.boot_img_total_size;
+
+ return true;
+}
+
+bool android_image_get_vendor_bootimg_size(const void *hdr, u32 *vendor_boot_img_size)
+{
+ struct andr_image_data data;
+
+ if (!hdr || !vendor_boot_img_size) {
+ printf("hdr or vendor_boot_img_size can't be NULL\n");
+ return false;
+ }
+
+ if (!is_android_vendor_boot_image_header(hdr)) {
+ printf("Incorrect vendor boot image header\n");
+ return false;
+ }
+
+ android_vendor_boot_image_v3_v4_parse_hdr(hdr, &data);
+
+ *vendor_boot_img_size = data.vendor_boot_img_total_size;
+
+ return true;
+}
+
+bool android_image_get_data(const void *boot_hdr, const void *vendor_boot_hdr,
+ struct andr_image_data *data)
+{
+ if (!boot_hdr || !data) {
+ printf("boot_hdr or data params can't be NULL\n");
+ return false;
+ }
+
+ if (!is_android_boot_image_header(boot_hdr)) {
+ printf("Incorrect boot image header\n");
+ return false;
+ }
+
+ if (((struct andr_boot_img_hdr_v0 *)boot_hdr)->header_version > 2) {
+ if (!vendor_boot_hdr) {
+ printf("For boot header v3+ vendor boot image has to be provided\n");
+ return false;
+ }
+ if (!is_android_vendor_boot_image_header(vendor_boot_hdr)) {
+ printf("Incorrect vendor boot image header\n");
+ return false;
+ }
+ android_boot_image_v3_v4_parse_hdr(boot_hdr, data);
+ android_vendor_boot_image_v3_v4_parse_hdr(vendor_boot_hdr, data);
+ } else {
+ android_boot_image_v0_v1_v2_parse_hdr(boot_hdr, data);
+ }
+
+ return true;
+}
+
+static ulong android_image_get_kernel_addr(struct andr_image_data *img_data,
+ ulong comp)
+{
+ /*
+ * All the Android tools that generate a boot.img use this
+ * address as the default.
+ *
+ * Even though it doesn't really make a lot of sense, and it
+ * might be valid on some platforms, we treat that adress as
+ * the default value for this field, and try to execute the
+ * kernel in place in such a case.
+ *
+ * Otherwise, we will return the actual value set by the user.
+ */
+ if (img_data->kernel_addr == ANDROID_IMAGE_DEFAULT_KERNEL_ADDR ||
+ IS_ENABLED(CONFIG_ANDROID_BOOT_IMAGE_IGNORE_BLOB_ADDR)) {
+ if (comp == IH_COMP_NONE)
+ return img_data->kernel_ptr;
+ return env_get_ulong("kernel_addr_r", 16, 0);
+ }
+
+ /*
+ * abootimg creates images where all load addresses are 0
+ * and we need to fix them.
+ */
+ if (img_data->kernel_addr == 0 && img_data->ramdisk_addr == 0)
+ return env_get_ulong("kernel_addr_r", 16, 0);
+
+ return img_data->kernel_addr;
+}
+
+/**
+ * android_image_get_kernel() - processes kernel part of Android boot images
+ * @hdr: Pointer to boot image header, which is at the start
+ * of the image.
+ * @vendor_boot_img: Pointer to vendor boot image header, which is at the
+ * start of the image.
+ * @verify: Checksum verification flag. Currently unimplemented.
+ * @os_data: Pointer to a ulong variable, will hold os data start
+ * address.
+ * @os_len: Pointer to a ulong variable, will hold os data length.
+ *
+ * This function returns the os image's start address and length. Also,
+ * it appends the kernel command line to the bootargs env variable.
+ *
+ * Return: Zero, os start address and length on success,
+ * otherwise on failure.
+ */
+int android_image_get_kernel(const void *hdr,
+ const void *vendor_boot_img, int verify,
+ ulong *os_data, ulong *os_len)
+{
+ struct andr_image_data img_data = {0};
+ ulong kernel_addr;
+ const struct legacy_img_hdr *ihdr;
+ ulong comp;
+
+ if (!android_image_get_data(hdr, vendor_boot_img, &img_data))
+ return -EINVAL;
+
+ comp = android_image_get_kcomp(hdr, vendor_boot_img);
+
+ kernel_addr = android_image_get_kernel_addr(&img_data, comp);
+ ihdr = (const struct legacy_img_hdr *)img_data.kernel_ptr;
+
+ /*
+ * Not all Android tools use the id field for signing the image with
+ * sha1 (or anything) so we don't check it. It is not obvious that the
+ * string is null terminated so we take care of this.
+ */
+ strlcpy(andr_tmp_str, img_data.image_name, ANDR_BOOT_NAME_SIZE);
+ andr_tmp_str[ANDR_BOOT_NAME_SIZE] = '\0';
+ if (strlen(andr_tmp_str))
+ printf("Android's image name: %s\n", andr_tmp_str);
+
+ printf("Kernel load addr 0x%08lx size %u KiB\n",
+ kernel_addr, DIV_ROUND_UP(img_data.kernel_size, 1024));
+
+ int len = 0;
+ char *bootargs = env_get("bootargs");
+
+ if (bootargs)
+ len += strlen(bootargs);
+
+ if (img_data.kcmdline && *img_data.kcmdline) {
+ printf("Kernel command line: %s\n", img_data.kcmdline);
+ len += strlen(img_data.kcmdline) + (len ? 1 : 0); /* +1 for extra space */
+ }
+
+ if (img_data.kcmdline_extra && *img_data.kcmdline_extra) {
+ printf("Kernel extra command line: %s\n", img_data.kcmdline_extra);
+ len += strlen(img_data.kcmdline_extra) + (len ? 1 : 0); /* +1 for extra space */
+ }
+
+ char *newbootargs = malloc(len + 1); /* +1 for the '\0' */
+ if (!newbootargs) {
+ puts("Error: malloc in android_image_get_kernel failed!\n");
+ return -ENOMEM;
+ }
+ *newbootargs = '\0'; /* set to Null in case no components below are present */
+
+ if (bootargs)
+ strcpy(newbootargs, bootargs);
+
+ if (img_data.kcmdline && *img_data.kcmdline) {
+ if (*newbootargs) /* If there is something in newbootargs, a space is needed */
+ strcat(newbootargs, " ");
+ strcat(newbootargs, img_data.kcmdline);
+ }
+
+ if (img_data.kcmdline_extra && *img_data.kcmdline_extra) {
+ if (*newbootargs) /* If there is something in newbootargs, a space is needed */
+ strcat(newbootargs, " ");
+ strcat(newbootargs, img_data.kcmdline_extra);
+ }
+
+ env_set("bootargs", newbootargs);
+ free(newbootargs);
+
+ if (os_data) {
+ if (image_get_magic(ihdr) == IH_MAGIC) {
+ *os_data = image_get_data(ihdr);
+ } else {
+ *os_data = img_data.kernel_ptr;
+ }
+ }
+ if (os_len) {
+ if (image_get_magic(ihdr) == IH_MAGIC)
+ *os_len = image_get_data_size(ihdr);
+ else
+ *os_len = img_data.kernel_size;
+ }
+ return 0;
+}
+
+bool is_android_vendor_boot_image_header(const void *vendor_boot_img)
+{
+ return !memcmp(VENDOR_BOOT_MAGIC, vendor_boot_img, ANDR_VENDOR_BOOT_MAGIC_SIZE);
+}
+
+bool is_android_boot_image_header(const void *hdr)
+{
+ return !memcmp(ANDR_BOOT_MAGIC, hdr, ANDR_BOOT_MAGIC_SIZE);
+}
+
+ulong android_image_get_end(const struct andr_boot_img_hdr_v0 *hdr,
+ const void *vendor_boot_img)
+{
+ struct andr_image_data img_data;
+
+ if (!android_image_get_data(hdr, vendor_boot_img, &img_data))
+ return -EINVAL;
+
+ if (img_data.header_version > 2)
+ return 0;
+
+ return img_data.boot_img_total_size;
+}
+
+ulong android_image_get_kload(const void *hdr,
+ const void *vendor_boot_img)
+{
+ struct andr_image_data img_data;
+ ulong comp;
+
+ if (!android_image_get_data(hdr, vendor_boot_img, &img_data))
+ return -EINVAL;
+
+ comp = android_image_get_kcomp(hdr, vendor_boot_img);
+
+ return android_image_get_kernel_addr(&img_data, comp);
+}
+
+ulong android_image_get_kcomp(const void *hdr,
+ const void *vendor_boot_img)
+{
+ struct andr_image_data img_data;
+ const void *p;
+
+ if (!android_image_get_data(hdr, vendor_boot_img, &img_data))
+ return -EINVAL;
+
+ p = (const void *)img_data.kernel_ptr;
+ if (image_get_magic((struct legacy_img_hdr *)p) == IH_MAGIC)
+ return image_get_comp((struct legacy_img_hdr *)p);
+ else if (get_unaligned_le32(p) == LZ4F_MAGIC)
+ return IH_COMP_LZ4;
+ else
+ return image_decomp_type(p, sizeof(u32));
+}
+
+int android_image_get_ramdisk(const void *hdr, const void *vendor_boot_img,
+ ulong *rd_data, ulong *rd_len)
+{
+ struct andr_image_data img_data = {0};
+ ulong ramdisk_ptr;
+
+ if (!android_image_get_data(hdr, vendor_boot_img, &img_data))
+ return -EINVAL;
+
+ if (!img_data.ramdisk_size)
+ return -ENOENT;
+ /*
+ * Android tools can generate a boot.img with default load address
+ * or 0, even though it doesn't really make a lot of sense, and it
+ * might be valid on some platforms, we treat that address as
+ * the default value for this field, and try to pass ramdisk
+ * in place if possible.
+ */
+ if (img_data.header_version > 2) {
+ /* Ramdisk can't be used in-place, copy it to ramdisk_addr_r */
+ if (img_data.ramdisk_addr == ANDROID_IMAGE_DEFAULT_RAMDISK_ADDR ||
+ IS_ENABLED(CONFIG_ANDROID_BOOT_IMAGE_IGNORE_BLOB_ADDR)) {
+ ramdisk_ptr = env_get_ulong("ramdisk_addr_r", 16, 0);
+ if (!ramdisk_ptr) {
+ printf("Invalid ramdisk_addr_r to copy ramdisk into\n");
+ return -EINVAL;
+ }
+ } else {
+ ramdisk_ptr = img_data.ramdisk_addr;
+ }
+ *rd_data = ramdisk_ptr;
+ memcpy((void *)(ramdisk_ptr), (void *)img_data.vendor_ramdisk_ptr,
+ img_data.vendor_ramdisk_size);
+ ramdisk_ptr += img_data.vendor_ramdisk_size;
+ memcpy((void *)(ramdisk_ptr), (void *)img_data.ramdisk_ptr,
+ img_data.boot_ramdisk_size);
+ ramdisk_ptr += img_data.boot_ramdisk_size;
+ if (img_data.bootconfig_size) {
+ memcpy((void *)
+ (ramdisk_ptr), (void *)img_data.bootconfig_addr,
+ img_data.bootconfig_size);
+ }
+ } else {
+ /* Ramdisk can be used in-place, use current ptr */
+ if (img_data.ramdisk_addr == 0 ||
+ img_data.ramdisk_addr == ANDROID_IMAGE_DEFAULT_RAMDISK_ADDR ||
+ img_data.ramdisk_addr == img_data.kernel_addr ||
+ IS_ENABLED(CONFIG_ANDROID_BOOT_IMAGE_IGNORE_BLOB_ADDR)) {
+ *rd_data = img_data.ramdisk_ptr;
+ } else {
+ ramdisk_ptr = img_data.ramdisk_addr;
+ *rd_data = ramdisk_ptr;
+ memcpy((void *)(ramdisk_ptr), (void *)img_data.ramdisk_ptr,
+ img_data.ramdisk_size);
+ }
+ }
+
+ printf("RAM disk load addr 0x%08lx size %u KiB\n",
+ *rd_data, DIV_ROUND_UP(img_data.ramdisk_size, 1024));
+
+ *rd_len = img_data.ramdisk_size;
+ return 0;
+}
+
+int android_image_get_second(const void *hdr, ulong *second_data, ulong *second_len)
+{
+ struct andr_image_data img_data;
+
+ if (!android_image_get_data(hdr, NULL, &img_data))
+ return -EINVAL;
+
+ if (img_data.header_version > 2) {
+ printf("Second stage bootloader is only supported for boot image version <= 2\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (!img_data.second_size) {
+ *second_data = *second_len = 0;
+ return -1;
+ }
+
+ *second_data = img_data.second_ptr;
+
+ printf("second address is 0x%lx\n",*second_data);
+
+ *second_len = img_data.second_size;
+ return 0;
+}
+
+/**
+ * android_image_get_dtbo() - Get address and size of recovery DTBO image.
+ * @hdr_addr: Boot image header address
+ * @addr: If not NULL, will contain address of recovery DTBO image
+ * @size: If not NULL, will contain size of recovery DTBO image
+ *
+ * Get the address and size of DTBO image in "Recovery DTBO" area of Android
+ * Boot Image in RAM. The format of this image is Android DTBO (see
+ * corresponding "DTB/DTBO Partitions" AOSP documentation for details). Once
+ * the address is obtained from this function, one can use 'adtimg' U-Boot
+ * command or android_dt_*() functions to extract desired DTBO blob.
+ *
+ * This DTBO (included in boot image) is only needed for non-A/B devices, and it
+ * only can be found in recovery image. On A/B devices we can always rely on
+ * "dtbo" partition. See "Including DTBO in Recovery for Non-A/B Devices" in
+ * AOSP documentation for details.
+ *
+ * Return: true on success or false on error.
+ */
+bool android_image_get_dtbo(ulong hdr_addr, ulong *addr, u32 *size)
+{
+ const struct andr_boot_img_hdr_v0 *hdr;
+ ulong dtbo_img_addr;
+ bool ret = true;
+
+ hdr = map_sysmem(hdr_addr, sizeof(*hdr));
+ if (!is_android_boot_image_header(hdr)) {
+ printf("Error: Boot Image header is incorrect\n");
+ ret = false;
+ goto exit;
+ }
+
+ if (hdr->header_version != 1 && hdr->header_version != 2) {
+ printf("Error: header version must be >= 1 and <= 2 to get dtbo\n");
+ ret = false;
+ goto exit;
+ }
+
+ if (hdr->recovery_dtbo_size == 0) {
+ printf("Error: recovery_dtbo_size is 0\n");
+ ret = false;
+ goto exit;
+ }
+
+ /* Calculate the address of DTB area in boot image */
+ dtbo_img_addr = hdr_addr;
+ dtbo_img_addr += hdr->page_size;
+ dtbo_img_addr += ALIGN(hdr->kernel_size, hdr->page_size);
+ dtbo_img_addr += ALIGN(hdr->ramdisk_size, hdr->page_size);
+ dtbo_img_addr += ALIGN(hdr->second_size, hdr->page_size);
+
+ if (addr)
+ *addr = dtbo_img_addr;
+ if (size)
+ *size = hdr->recovery_dtbo_size;
+
+exit:
+ unmap_sysmem(hdr);
+ return ret;
+}
+
+/**
+ * android_image_get_dtb_img_addr() - Get the address of DTB area in boot image.
+ * @hdr_addr: Boot image header address
+ * @vhdr_addr: Vendor Boot image header address
+ * @addr: Will contain the address of DTB area in boot image
+ *
+ * Return: true on success or false on fail.
+ */
+static bool android_image_get_dtb_img_addr(ulong hdr_addr, ulong vhdr_addr, ulong *addr)
+{
+ const struct andr_boot_img_hdr_v0 *hdr;
+ const struct andr_vnd_boot_img_hdr *v_hdr;
+ ulong dtb_img_addr;
+ bool ret = true;
+
+ hdr = map_sysmem(hdr_addr, sizeof(*hdr));
+ if (!is_android_boot_image_header(hdr)) {
+ printf("Error: Boot Image header is incorrect\n");
+ ret = false;
+ goto exit;
+ }
+
+ if (hdr->header_version < 2) {
+ printf("Error: header_version must be >= 2 to get dtb\n");
+ ret = false;
+ goto exit;
+ }
+
+ if (hdr->header_version == 2) {
+ if (!hdr->dtb_size) {
+ printf("Error: dtb_size is 0\n");
+ ret = false;
+ goto exit;
+ }
+ /* Calculate the address of DTB area in boot image */
+ dtb_img_addr = hdr_addr;
+ dtb_img_addr += hdr->page_size;
+ dtb_img_addr += ALIGN(hdr->kernel_size, hdr->page_size);
+ dtb_img_addr += ALIGN(hdr->ramdisk_size, hdr->page_size);
+ dtb_img_addr += ALIGN(hdr->second_size, hdr->page_size);
+ dtb_img_addr += ALIGN(hdr->recovery_dtbo_size, hdr->page_size);
+
+ *addr = dtb_img_addr;
+ }
+
+ if (hdr->header_version > 2) {
+ v_hdr = map_sysmem(vhdr_addr, sizeof(*v_hdr));
+ if (!v_hdr->dtb_size) {
+ printf("Error: dtb_size is 0\n");
+ ret = false;
+ unmap_sysmem(v_hdr);
+ goto exit;
+ }
+ /* Calculate the address of DTB area in boot image */
+ dtb_img_addr = vhdr_addr;
+ dtb_img_addr += v_hdr->page_size;
+ if (v_hdr->vendor_ramdisk_size)
+ dtb_img_addr += ALIGN(v_hdr->vendor_ramdisk_size, v_hdr->page_size);
+ *addr = dtb_img_addr;
+ unmap_sysmem(v_hdr);
+ goto exit;
+ }
+exit:
+ unmap_sysmem(hdr);
+ return ret;
+}
+
+/**
+ * android_image_get_dtb_by_index() - Get address and size of blob in DTB area.
+ * @hdr_addr: Boot image header address
+ * @vendor_boot_img: Pointer to vendor boot image header, which is at the start of the image.
+ * @index: Index of desired DTB in DTB area (starting from 0)
+ * @addr: If not NULL, will contain address to specified DTB
+ * @size: If not NULL, will contain size of specified DTB
+ *
+ * Get the address and size of DTB blob by its index in DTB area of Android
+ * Boot Image in RAM.
+ *
+ * Return: true on success or false on error.
+ */
+bool android_image_get_dtb_by_index(ulong hdr_addr, ulong vendor_boot_img,
+ u32 index, ulong *addr, u32 *size)
+{
+ struct andr_image_data img_data;
+ const struct andr_boot_img_hdr_v0 *hdr;
+ const struct andr_vnd_boot_img_hdr *vhdr = NULL;
+
+ hdr = map_sysmem(hdr_addr, sizeof(*hdr));
+ if (vendor_boot_img != -1)
+ vhdr = map_sysmem(vendor_boot_img, sizeof(*vhdr));
+ if (!android_image_get_data(hdr, vhdr, &img_data)) {
+ if (vendor_boot_img != -1)
+ unmap_sysmem(vhdr);
+ unmap_sysmem(hdr);
+ return false;
+ }
+ if (vendor_boot_img != -1)
+ unmap_sysmem(vhdr);
+ unmap_sysmem(hdr);
+
+ ulong dtb_img_addr; /* address of DTB part in boot image */
+ u32 dtb_img_size; /* size of DTB payload in boot image */
+ ulong dtb_addr; /* address of DTB blob with specified index */
+ u32 i; /* index iterator */
+
+ if (!android_image_get_dtb_img_addr(hdr_addr, vendor_boot_img,
+ &dtb_img_addr))
+ return false;
+
+ /* Check if DTB area of boot image is in DTBO format */
+ if (android_dt_check_header(dtb_img_addr)) {
+ return android_dt_get_fdt_by_index(dtb_img_addr, index, addr,
+ size);
+ }
+
+ /* Find out the address of DTB with specified index in concat blobs */
+ dtb_img_size = img_data.dtb_size;
+ i = 0;
+ dtb_addr = dtb_img_addr;
+ while (dtb_addr < dtb_img_addr + dtb_img_size) {
+ const struct fdt_header *fdt;
+ u32 dtb_size;
+
+ fdt = map_sysmem(dtb_addr, sizeof(*fdt));
+ if (fdt_check_header(fdt) != 0) {
+ unmap_sysmem(fdt);
+ printf("Error: Invalid FDT header for index %u\n", i);
+ return false;
+ }
+
+ dtb_size = fdt_totalsize(fdt);
+ unmap_sysmem(fdt);
+
+ if (i == index) {
+ if (size)
+ *size = dtb_size;
+ if (addr)
+ *addr = dtb_addr;
+ return true;
+ }
+
+ dtb_addr += dtb_size;
+ ++i;
+ }
+
+ printf("Error: Index is out of bounds (%u/%u)\n", index, i);
+ return false;
+}
+
+#if !defined(CONFIG_XPL_BUILD)
+/**
+ * android_print_contents - prints out the contents of the Android format image
+ * @hdr: pointer to the Android format image header
+ *
+ * android_print_contents() formats a multi line Android image contents
+ * description.
+ * The routine prints out Android image properties
+ *
+ * returns:
+ * no returned results
+ */
+void android_print_contents(const struct andr_boot_img_hdr_v0 *hdr)
+{
+ if (hdr->header_version >= 3) {
+ printf("Content print is not supported for boot image header version > 2");
+ return;
+ }
+ const char * const p = IMAGE_INDENT_STRING;
+ /* os_version = ver << 11 | lvl */
+ u32 os_ver = hdr->os_version >> 11;
+ u32 os_lvl = hdr->os_version & ((1U << 11) - 1);
+
+ printf("%skernel size: %x\n", p, hdr->kernel_size);
+ printf("%skernel address: %x\n", p, hdr->kernel_addr);
+ printf("%sramdisk size: %x\n", p, hdr->ramdisk_size);
+ printf("%sramdisk address: %x\n", p, hdr->ramdisk_addr);
+ printf("%ssecond size: %x\n", p, hdr->second_size);
+ printf("%ssecond address: %x\n", p, hdr->second_addr);
+ printf("%stags address: %x\n", p, hdr->tags_addr);
+ printf("%spage size: %x\n", p, hdr->page_size);
+ /* ver = A << 14 | B << 7 | C (7 bits for each of A, B, C)
+ * lvl = ((Y - 2000) & 127) << 4 | M (7 bits for Y, 4 bits for M) */
+ printf("%sos_version: %x (ver: %u.%u.%u, level: %u.%u)\n",
+ p, hdr->os_version,
+ (os_ver >> 7) & 0x7F, (os_ver >> 14) & 0x7F, os_ver & 0x7F,
+ (os_lvl >> 4) + 2000, os_lvl & 0x0F);
+ printf("%sname: %s\n", p, hdr->name);
+ printf("%scmdline: %s\n", p, hdr->cmdline);
+ printf("%sheader_version: %d\n", p, hdr->header_version);
+
+ if (hdr->header_version >= 1) {
+ printf("%srecovery dtbo size: %x\n", p,
+ hdr->recovery_dtbo_size);
+ printf("%srecovery dtbo offset: %llx\n", p,
+ hdr->recovery_dtbo_offset);
+ printf("%sheader size: %x\n", p,
+ hdr->header_size);
+ }
+
+ if (hdr->header_version == 2) {
+ printf("%sdtb size: %x\n", p, hdr->dtb_size);
+ printf("%sdtb addr: %llx\n", p, hdr->dtb_addr);
+ }
+}
+
+/**
+ * android_image_print_dtb_info - Print info for one DTB blob in DTB area.
+ * @fdt: DTB header
+ * @index: Number of DTB blob in DTB area.
+ *
+ * Return: true on success or false on error.
+ */
+static bool android_image_print_dtb_info(const struct fdt_header *fdt,
+ u32 index)
+{
+ int root_node_off;
+ u32 fdt_size;
+ const char *model;
+ const char *compatible;
+
+ root_node_off = fdt_path_offset(fdt, "/");
+ if (root_node_off < 0) {
+ printf("Error: Root node not found\n");
+ return false;
+ }
+
+ fdt_size = fdt_totalsize(fdt);
+ compatible = fdt_getprop(fdt, root_node_off, "compatible",
+ NULL);
+ model = fdt_getprop(fdt, root_node_off, "model", NULL);
+
+ printf(" - DTB #%u:\n", index);
+ printf(" (DTB)size = %d\n", fdt_size);
+ printf(" (DTB)model = %s\n", model ? model : "(unknown)");
+ printf(" (DTB)compatible = %s\n",
+ compatible ? compatible : "(unknown)");
+
+ return true;
+}
+
+/**
+ * android_image_print_dtb_contents() - Print info for DTB blobs in DTB area.
+ * @hdr_addr: Boot image header address
+ *
+ * DTB payload in Android Boot Image v2+ can be in one of following formats:
+ * 1. Concatenated DTB blobs
+ * 2. Android DTBO format (see CONFIG_CMD_ADTIMG for details)
+ *
+ * This function does next:
+ * 1. Prints out the format used in DTB area
+ * 2. Iterates over all DTB blobs in DTB area and prints out the info for
+ * each blob.
+ *
+ * Return: true on success or false on error.
+ */
+bool android_image_print_dtb_contents(ulong hdr_addr)
+{
+ const struct andr_boot_img_hdr_v0 *hdr;
+ bool res;
+ ulong dtb_img_addr; /* address of DTB part in boot image */
+ u32 dtb_img_size; /* size of DTB payload in boot image */
+ ulong dtb_addr; /* address of DTB blob with specified index */
+ u32 i; /* index iterator */
+
+ res = android_image_get_dtb_img_addr(hdr_addr, 0, &dtb_img_addr);
+ if (!res)
+ return false;
+
+ /* Check if DTB area of boot image is in DTBO format */
+ if (android_dt_check_header(dtb_img_addr)) {
+ printf("## DTB area contents (DTBO format):\n");
+ android_dt_print_contents(dtb_img_addr);
+ return true;
+ }
+
+ printf("## DTB area contents (concat format):\n");
+
+ /* Iterate over concatenated DTB blobs */
+ hdr = map_sysmem(hdr_addr, sizeof(*hdr));
+ dtb_img_size = hdr->dtb_size;
+ unmap_sysmem(hdr);
+ i = 0;
+ dtb_addr = dtb_img_addr;
+ while (dtb_addr < dtb_img_addr + dtb_img_size) {
+ const struct fdt_header *fdt;
+ u32 dtb_size;
+
+ fdt = map_sysmem(dtb_addr, sizeof(*fdt));
+ if (fdt_check_header(fdt) != 0) {
+ unmap_sysmem(fdt);
+ printf("Error: Invalid FDT header for index %u\n", i);
+ return false;
+ }
+
+ res = android_image_print_dtb_info(fdt, i);
+ if (!res) {
+ unmap_sysmem(fdt);
+ return false;
+ }
+
+ dtb_size = fdt_totalsize(fdt);
+ unmap_sysmem(fdt);
+ dtb_addr += dtb_size;
+ ++i;
+ }
+
+ return true;
+}
+#endif
diff --git a/boot/image-board.c b/boot/image-board.c
new file mode 100644
index 00000000000..005d60caf5c
--- /dev/null
+++ b/boot/image-board.c
@@ -0,0 +1,1124 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Image code used by boards (and not host tools)
+ *
+ * (C) Copyright 2008 Semihalf
+ *
+ * (C) Copyright 2000-2006
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ */
+
+#include <config.h>
+#include <bootstage.h>
+#include <cpu_func.h>
+#include <display_options.h>
+#include <env.h>
+#include <fpga.h>
+#include <image.h>
+#include <init.h>
+#include <lmb.h>
+#include <log.h>
+#include <mapmem.h>
+#include <rtc.h>
+#include <watchdog.h>
+#include <asm/cache.h>
+#include <asm/global_data.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/**
+ * image_get_ramdisk - get and verify ramdisk image
+ * @rd_addr: ramdisk image start address
+ * @arch: expected ramdisk architecture
+ * @verify: checksum verification flag
+ *
+ * image_get_ramdisk() returns a pointer to the verified ramdisk image
+ * header. Routine receives image start address and expected architecture
+ * flag. Verification done covers data and header integrity and os/type/arch
+ * fields checking.
+ *
+ * returns:
+ * pointer to a ramdisk image header, if image was found and valid
+ * otherwise, return NULL
+ */
+static const struct legacy_img_hdr *image_get_ramdisk(ulong rd_addr, u8 arch,
+ int verify)
+{
+ const struct legacy_img_hdr *rd_hdr = (const struct legacy_img_hdr *)rd_addr;
+
+ if (!image_check_magic(rd_hdr)) {
+ puts("Bad Magic Number\n");
+ bootstage_error(BOOTSTAGE_ID_RD_MAGIC);
+ return NULL;
+ }
+
+ if (!image_check_hcrc(rd_hdr)) {
+ puts("Bad Header Checksum\n");
+ bootstage_error(BOOTSTAGE_ID_RD_HDR_CHECKSUM);
+ return NULL;
+ }
+
+ bootstage_mark(BOOTSTAGE_ID_RD_MAGIC);
+ image_print_contents(rd_hdr);
+
+ if (verify) {
+ puts(" Verifying Checksum ... ");
+ if (!image_check_dcrc(rd_hdr)) {
+ puts("Bad Data CRC\n");
+ bootstage_error(BOOTSTAGE_ID_RD_CHECKSUM);
+ return NULL;
+ }
+ puts("OK\n");
+ }
+
+ bootstage_mark(BOOTSTAGE_ID_RD_HDR_CHECKSUM);
+
+ if (!image_check_os(rd_hdr, IH_OS_LINUX) ||
+ !image_check_arch(rd_hdr, arch) ||
+ !image_check_type(rd_hdr, IH_TYPE_RAMDISK)) {
+ printf("No Linux %s Ramdisk Image\n",
+ genimg_get_arch_name(arch));
+ bootstage_error(BOOTSTAGE_ID_RAMDISK);
+ return NULL;
+ }
+
+ return rd_hdr;
+}
+
+/*****************************************************************************/
+/* Shared dual-format routines */
+/*****************************************************************************/
+ulong image_load_addr = CONFIG_SYS_LOAD_ADDR; /* Default Load Address */
+ulong image_save_addr; /* Default Save Address */
+ulong image_save_size; /* Default Save Size (in bytes) */
+
+static int on_loadaddr(const char *name, const char *value, enum env_op op,
+ int flags)
+{
+ switch (op) {
+ case env_op_create:
+ case env_op_overwrite:
+ image_load_addr = hextoul(value, NULL);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+U_BOOT_ENV_CALLBACK(loadaddr, on_loadaddr);
+
+phys_addr_t env_get_bootm_low(void)
+{
+ char *s = env_get("bootm_low");
+
+ if (s)
+ return simple_strtoull(s, NULL, 16);
+
+#if defined(CFG_SYS_SDRAM_BASE)
+ return CFG_SYS_SDRAM_BASE;
+#elif defined(CONFIG_ARM) || defined(CONFIG_MICROBLAZE) || defined(CONFIG_RISCV)
+ return gd->bd->bi_dram[0].start;
+#else
+ return 0;
+#endif
+}
+
+phys_size_t env_get_bootm_size(void)
+{
+ phys_addr_t start, low;
+ phys_size_t size;
+ char *s = env_get("bootm_size");
+
+ if (s)
+ return simple_strtoull(s, NULL, 16);
+
+ start = gd->ram_base;
+ size = gd->ram_size;
+
+ if (start + size > gd->ram_top)
+ size = gd->ram_top - start;
+
+ s = env_get("bootm_low");
+ if (s)
+ low = simple_strtoull(s, NULL, 16);
+ else
+ low = start;
+
+ return size - (low - start);
+}
+
+phys_size_t env_get_bootm_mapsize(void)
+{
+ char *s = env_get("bootm_mapsize");
+
+ if (s)
+ return simple_strtoull(s, NULL, 16);
+
+#if defined(CFG_SYS_BOOTMAPSZ)
+ return CFG_SYS_BOOTMAPSZ;
+#else
+ return env_get_bootm_size();
+#endif
+}
+
+void memmove_wd(void *to, void *from, size_t len, ulong chunksz)
+{
+ if (to == from)
+ return;
+
+ if (IS_ENABLED(CONFIG_HW_WATCHDOG) || IS_ENABLED(CONFIG_WATCHDOG)) {
+ if (to > from) {
+ from += len;
+ to += len;
+ }
+ while (len > 0) {
+ size_t tail = (len > chunksz) ? chunksz : len;
+
+ schedule();
+ if (to > from) {
+ to -= tail;
+ from -= tail;
+ }
+ memmove(to, from, tail);
+ if (to < from) {
+ to += tail;
+ from += tail;
+ }
+ len -= tail;
+ }
+ } else {
+ memmove(to, from, len);
+ }
+}
+
+ulong genimg_get_kernel_addr_fit(const char *const img_addr,
+ const char **fit_uname_config,
+ const char **fit_uname_kernel)
+{
+ ulong kernel_addr;
+
+ /* find out kernel image address */
+ if (!img_addr) {
+ kernel_addr = image_load_addr;
+ debug("* kernel: default image load address = 0x%08lx\n",
+ image_load_addr);
+ } else if (CONFIG_IS_ENABLED(FIT) &&
+ fit_parse_conf(img_addr, image_load_addr, &kernel_addr,
+ fit_uname_config)) {
+ debug("* kernel: config '%s' from image at 0x%08lx\n",
+ *fit_uname_config, kernel_addr);
+ } else if (CONFIG_IS_ENABLED(FIT) &&
+ fit_parse_subimage(img_addr, image_load_addr, &kernel_addr,
+ fit_uname_kernel)) {
+ debug("* kernel: subimage '%s' from image at 0x%08lx\n",
+ *fit_uname_kernel, kernel_addr);
+ } else {
+ kernel_addr = hextoul(img_addr, NULL);
+ debug("* kernel: cmdline image address = 0x%08lx\n",
+ kernel_addr);
+ }
+
+ return kernel_addr;
+}
+
+/**
+ * genimg_get_kernel_addr() is the simple version of
+ * genimg_get_kernel_addr_fit(). It ignores those return FIT strings
+ */
+ulong genimg_get_kernel_addr(char * const img_addr)
+{
+ const char *fit_uname_config = NULL;
+ const char *fit_uname_kernel = NULL;
+
+ return genimg_get_kernel_addr_fit(img_addr, &fit_uname_config,
+ &fit_uname_kernel);
+}
+
+/**
+ * genimg_get_format - get image format type
+ * @img_addr: image start address
+ *
+ * genimg_get_format() checks whether provided address points to a valid
+ * legacy or FIT image.
+ *
+ * New uImage format and FDT blob are based on a libfdt. FDT blob
+ * may be passed directly or embedded in a FIT image. In both situations
+ * genimg_get_format() must be able to dectect libfdt header.
+ *
+ * returns:
+ * image format type or IMAGE_FORMAT_INVALID if no image is present
+ */
+int genimg_get_format(const void *img_addr)
+{
+ if (CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT)) {
+ const struct legacy_img_hdr *hdr;
+
+ hdr = (const struct legacy_img_hdr *)img_addr;
+ if (image_check_magic(hdr))
+ return IMAGE_FORMAT_LEGACY;
+ }
+ if (CONFIG_IS_ENABLED(FIT) || CONFIG_IS_ENABLED(OF_LIBFDT)) {
+ if (!fdt_check_header(img_addr))
+ return IMAGE_FORMAT_FIT;
+ }
+ if (IS_ENABLED(CONFIG_ANDROID_BOOT_IMAGE) &&
+ is_android_boot_image_header(img_addr))
+ return IMAGE_FORMAT_ANDROID;
+
+ return IMAGE_FORMAT_INVALID;
+}
+
+/**
+ * fit_has_config - check if there is a valid FIT configuration
+ * @images: pointer to the bootm command headers structure
+ *
+ * fit_has_config() checks if there is a FIT configuration in use
+ * (if FTI support is present).
+ *
+ * returns:
+ * 0, no FIT support or no configuration found
+ * 1, configuration found
+ */
+int genimg_has_config(struct bootm_headers *images)
+{
+ if (CONFIG_IS_ENABLED(FIT) && images->fit_uname_cfg)
+ return 1;
+
+ return 0;
+}
+
+/**
+ * select_ramdisk() - Select and locate the ramdisk to use
+ *
+ * @images: pointer to the bootm images structure
+ * @select: name of ramdisk to select, or hex address, NULL for any
+ * @arch: expected ramdisk architecture
+ * @rd_datap: pointer to a ulong variable, will hold ramdisk pointer
+ * @rd_lenp: pointer to a ulong variable, will hold ramdisk length
+ * Return: 0 if OK, -ENOPKG if no ramdisk (but an error should not be reported),
+ * other -ve value on other error
+ */
+static int select_ramdisk(struct bootm_headers *images, const char *select, u8 arch,
+ ulong *rd_datap, ulong *rd_lenp)
+{
+ const char *fit_uname_config;
+ const char *fit_uname_ramdisk;
+ bool done_select = !select;
+ bool done = false;
+ int rd_noffset;
+ ulong rd_addr = 0;
+ char *buf;
+
+ if (CONFIG_IS_ENABLED(FIT)) {
+ fit_uname_config = images->fit_uname_cfg;
+ fit_uname_ramdisk = NULL;
+
+ if (select) {
+ ulong default_addr;
+ /*
+ * If the init ramdisk comes from the FIT image and
+ * the FIT image address is omitted in the command
+ * line argument, try to use os FIT image address or
+ * default load address.
+ */
+ if (images->fit_uname_os)
+ default_addr = (ulong)images->fit_hdr_os;
+ else
+ default_addr = image_load_addr;
+
+ if (fit_parse_conf(select, default_addr, &rd_addr,
+ &fit_uname_config)) {
+ debug("* ramdisk: config '%s' from image at 0x%08lx\n",
+ fit_uname_config, rd_addr);
+ done_select = true;
+ } else if (fit_parse_subimage(select, default_addr,
+ &rd_addr,
+ &fit_uname_ramdisk)) {
+ debug("* ramdisk: subimage '%s' from image at 0x%08lx\n",
+ fit_uname_ramdisk, rd_addr);
+ done_select = true;
+ }
+ }
+ }
+ if (!done_select) {
+ rd_addr = hextoul(select, NULL);
+ debug("* ramdisk: cmdline image address = 0x%08lx\n", rd_addr);
+ }
+ if (CONFIG_IS_ENABLED(FIT) && !select) {
+ /* use FIT configuration provided in first bootm
+ * command argument. If the property is not defined,
+ * quit silently (with -ENOPKG)
+ */
+ rd_addr = map_to_sysmem(images->fit_hdr_os);
+ rd_noffset = fit_get_node_from_config(images, FIT_RAMDISK_PROP,
+ rd_addr);
+ if (rd_noffset == -ENOENT)
+ return -ENOPKG;
+ else if (rd_noffset < 0)
+ return rd_noffset;
+ }
+
+ /*
+ * Check if there is an initrd image at the
+ * address provided in the second bootm argument
+ * check image type, for FIT images get FIT node.
+ */
+ buf = map_sysmem(rd_addr, 0);
+ switch (genimg_get_format(buf)) {
+ case IMAGE_FORMAT_LEGACY:
+ if (CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT)) {
+ const struct legacy_img_hdr *rd_hdr;
+
+ printf("## Loading init Ramdisk from Legacy Image at %08lx ...\n",
+ rd_addr);
+
+ bootstage_mark(BOOTSTAGE_ID_CHECK_RAMDISK);
+ rd_hdr = image_get_ramdisk(rd_addr, arch,
+ images->verify);
+
+ if (!rd_hdr)
+ return -ENOENT;
+
+ *rd_datap = image_get_data(rd_hdr);
+ *rd_lenp = image_get_data_size(rd_hdr);
+ done = true;
+ }
+ break;
+ case IMAGE_FORMAT_FIT:
+ if (CONFIG_IS_ENABLED(FIT)) {
+ rd_noffset = fit_image_load(images, rd_addr,
+ &fit_uname_ramdisk,
+ &fit_uname_config,
+ arch, IH_TYPE_RAMDISK,
+ BOOTSTAGE_ID_FIT_RD_START,
+ FIT_LOAD_OPTIONAL_NON_ZERO,
+ rd_datap, rd_lenp);
+ if (rd_noffset < 0)
+ return rd_noffset;
+
+ images->fit_hdr_rd = map_sysmem(rd_addr, 0);
+ images->fit_uname_rd = fit_uname_ramdisk;
+ images->fit_noffset_rd = rd_noffset;
+ done = true;
+ }
+ break;
+ case IMAGE_FORMAT_ANDROID:
+ if (IS_ENABLED(CONFIG_ANDROID_BOOT_IMAGE)) {
+ int ret;
+ if (IS_ENABLED(CONFIG_CMD_ABOOTIMG)) {
+ ulong boot_img = get_abootimg_addr();
+ ulong init_boot_img = get_ainit_bootimg_addr();
+ void *vendor_boot_img = map_sysmem(get_avendor_bootimg_addr(), 0);
+ void *ramdisk_img;
+
+ if (init_boot_img == -1)
+ ramdisk_img = map_sysmem(boot_img, 0);
+ else
+ ramdisk_img = map_sysmem(init_boot_img, 0);
+
+ ret = android_image_get_ramdisk(ramdisk_img, vendor_boot_img,
+ rd_datap, rd_lenp);
+ unmap_sysmem(vendor_boot_img);
+ unmap_sysmem(ramdisk_img);
+ } else {
+ void *ptr = map_sysmem(images->os.start, 0);
+
+ ret = android_image_get_ramdisk(ptr, NULL, rd_datap, rd_lenp);
+ unmap_sysmem(ptr);
+ }
+
+ if (ret == -ENOENT)
+ return -ENOPKG;
+ else if (ret)
+ return ret;
+ done = true;
+ }
+ break;
+ }
+
+ if (!done) {
+ if (IS_ENABLED(CONFIG_SUPPORT_RAW_INITRD)) {
+ char *end = NULL;
+
+ if (select)
+ end = strchr(select, ':');
+ if (end) {
+ *rd_lenp = hextoul(++end, NULL);
+ *rd_datap = rd_addr;
+ done = true;
+ }
+ }
+
+ if (!done) {
+ puts("Wrong Ramdisk Image Format\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+int boot_get_ramdisk(char const *select, struct bootm_headers *images,
+ uint arch, ulong *rd_start, ulong *rd_end)
+{
+ ulong rd_data, rd_len;
+
+ *rd_start = 0;
+ *rd_end = 0;
+
+ /*
+ * Look for a '-' which indicates to ignore the
+ * ramdisk argument
+ */
+ if (select && strcmp(select, "-") == 0) {
+ debug("## Skipping init Ramdisk\n");
+ rd_len = 0;
+ rd_data = 0;
+ } else if (select || genimg_has_config(images)) {
+ int ret;
+
+ ret = select_ramdisk(images, select, arch, &rd_data, &rd_len);
+ if (ret == -ENOPKG)
+ return 0;
+ else if (ret)
+ return ret;
+ } else if (images->legacy_hdr_valid &&
+ image_check_type(&images->legacy_hdr_os_copy,
+ IH_TYPE_MULTI)) {
+ /*
+ * Now check if we have a legacy mult-component image,
+ * get second entry data start address and len.
+ */
+ bootstage_mark(BOOTSTAGE_ID_RAMDISK);
+ printf("## Loading init Ramdisk from multi component Legacy Image at %08lx ...\n",
+ (ulong)images->legacy_hdr_os);
+
+ image_multi_getimg(images->legacy_hdr_os, 1, &rd_data, &rd_len);
+ } else {
+ /*
+ * no initrd image
+ */
+ bootstage_mark(BOOTSTAGE_ID_NO_RAMDISK);
+ rd_len = 0;
+ rd_data = 0;
+ }
+
+ if (!rd_data) {
+ debug("## No init Ramdisk\n");
+ } else {
+ *rd_start = rd_data;
+ *rd_end = rd_data + rd_len;
+ }
+ debug(" ramdisk start = 0x%08lx, ramdisk end = 0x%08lx\n",
+ *rd_start, *rd_end);
+
+ return 0;
+}
+
+/**
+ * boot_ramdisk_high - relocate init ramdisk
+ * @rd_data: ramdisk data start address
+ * @rd_len: ramdisk data length
+ * @initrd_start: pointer to a ulong variable, will hold final init ramdisk
+ * start address (after possible relocation)
+ * @initrd_end: pointer to a ulong variable, will hold final init ramdisk
+ * end address (after possible relocation)
+ *
+ * boot_ramdisk_high() takes a relocation hint from "initrd_high" environment
+ * variable and if requested ramdisk data is moved to a specified location.
+ *
+ * Initrd_start and initrd_end are set to final (after relocation) ramdisk
+ * start/end addresses if ramdisk image start and len were provided,
+ * otherwise set initrd_start and initrd_end set to zeros.
+ *
+ * returns:
+ * 0 - success
+ * -1 - failure
+ */
+int boot_ramdisk_high(ulong rd_data, ulong rd_len, ulong *initrd_start,
+ ulong *initrd_end)
+{
+ int err;
+ char *s;
+ phys_addr_t initrd_high;
+ int initrd_copy_to_ram = 1;
+
+ s = env_get("initrd_high");
+ if (s) {
+ /* a value of "no" or a similar string will act like 0,
+ * turning the "load high" feature off. This is intentional.
+ */
+ initrd_high = hextoul(s, NULL);
+ if (initrd_high == ~0)
+ initrd_copy_to_ram = 0;
+ } else {
+ initrd_high = env_get_bootm_mapsize() + env_get_bootm_low();
+ }
+
+ debug("## initrd_high = 0x%llx, copy_to_ram = %d\n",
+ (u64)initrd_high, initrd_copy_to_ram);
+
+ if (rd_data) {
+ if (!initrd_copy_to_ram) { /* zero-copy ramdisk support */
+ phys_addr_t initrd_addr;
+
+ debug(" in-place initrd\n");
+ *initrd_start = rd_data;
+ *initrd_end = rd_data + rd_len;
+ initrd_addr = (phys_addr_t)rd_data;
+ err = lmb_alloc_mem(LMB_MEM_ALLOC_ADDR, 0, &initrd_addr,
+ rd_len, LMB_NONE);
+ if (err) {
+ puts("in-place initrd alloc failed\n");
+ goto error;
+ }
+ } else {
+ enum lmb_mem_type type = initrd_high ?
+ LMB_MEM_ALLOC_MAX : LMB_MEM_ALLOC_ANY;
+
+ err = lmb_alloc_mem(type, 0x1000, &initrd_high, rd_len,
+ LMB_NONE);
+ if (err) {
+ puts("ramdisk - allocation error\n");
+ goto error;
+ }
+
+ *initrd_start = (ulong)initrd_high;
+ bootstage_mark(BOOTSTAGE_ID_COPY_RAMDISK);
+
+ *initrd_end = *initrd_start + rd_len;
+ printf(" Loading Ramdisk to %08lx, end %08lx ... ",
+ *initrd_start, *initrd_end);
+
+ memmove_wd((void *)*initrd_start,
+ (void *)rd_data, rd_len, CHUNKSZ);
+
+ /*
+ * Ensure the image is flushed to memory to handle
+ * AMP boot scenarios in which we might not be
+ * HW cache coherent
+ */
+ if (IS_ENABLED(CONFIG_MP)) {
+ flush_cache((unsigned long)*initrd_start,
+ ALIGN(rd_len, ARCH_DMA_MINALIGN));
+ }
+ puts("OK\n");
+ }
+ } else {
+ *initrd_start = 0;
+ *initrd_end = 0;
+ }
+ debug(" ramdisk load start = 0x%08lx, ramdisk load end = 0x%08lx\n",
+ *initrd_start, *initrd_end);
+
+ return 0;
+
+error:
+ return -1;
+}
+
+int boot_get_setup(struct bootm_headers *images, u8 arch,
+ ulong *setup_start, ulong *setup_len)
+{
+ if (!CONFIG_IS_ENABLED(FIT))
+ return -ENOENT;
+
+ return boot_get_setup_fit(images, arch, setup_start, setup_len);
+}
+
+int boot_get_fpga(struct bootm_headers *images)
+{
+ ulong tmp_img_addr, img_data, img_len;
+ void *buf;
+ int conf_noffset;
+ int fit_img_result;
+ const char *uname, *name, *compatible;
+ int err;
+ int devnum = 0; /* TODO support multi fpga platforms */
+ int flags = 0;
+
+ if (!IS_ENABLED(CONFIG_FPGA))
+ return -ENOSYS;
+
+ /* Check to see if the images struct has a FIT configuration */
+ if (!genimg_has_config(images)) {
+ debug("## FIT configuration was not specified\n");
+ return 0;
+ }
+
+ /*
+ * Obtain the os FIT header from the images struct
+ */
+ tmp_img_addr = map_to_sysmem(images->fit_hdr_os);
+ buf = map_sysmem(tmp_img_addr, 0);
+ /*
+ * Check image type. For FIT images get FIT node
+ * and attempt to locate a generic binary.
+ */
+ switch (genimg_get_format(buf)) {
+ case IMAGE_FORMAT_FIT:
+ conf_noffset = fit_conf_get_node(buf, images->fit_uname_cfg);
+
+ uname = fdt_stringlist_get(buf, conf_noffset, FIT_FPGA_PROP, 0,
+ NULL);
+ if (!uname) {
+ debug("## FPGA image is not specified\n");
+ return 0;
+ }
+ fit_img_result = fit_image_load(images,
+ tmp_img_addr,
+ (const char **)&uname,
+ &images->fit_uname_cfg,
+ IH_ARCH_DEFAULT,
+ IH_TYPE_FPGA,
+ BOOTSTAGE_ID_FPGA_INIT,
+ FIT_LOAD_OPTIONAL_NON_ZERO,
+ &img_data, &img_len);
+
+ debug("FPGA image (%s) loaded to 0x%lx/size 0x%lx\n",
+ uname, img_data, img_len);
+
+ if (fit_img_result < 0) {
+ /* Something went wrong! */
+ return fit_img_result;
+ }
+
+ conf_noffset = fit_image_get_node(buf, uname);
+ compatible = fdt_getprop(buf, conf_noffset, "compatible", NULL);
+ if (!compatible) {
+ printf("'fpga' image without 'compatible' property\n");
+ } else {
+ if (CONFIG_IS_ENABLED(FPGA_LOAD_SECURE))
+ flags = fpga_compatible2flag(devnum, compatible);
+ }
+
+ if (!fpga_is_partial_data(devnum, img_len)) {
+ name = "full";
+ err = fpga_loadbitstream(devnum, (char *)img_data,
+ img_len, BIT_FULL);
+ if (err)
+ err = fpga_load(devnum, (const void *)img_data,
+ img_len, BIT_FULL, flags);
+ } else {
+ name = "partial";
+ err = fpga_loadbitstream(devnum, (char *)img_data,
+ img_len, BIT_PARTIAL);
+ if (err)
+ err = fpga_load(devnum, (const void *)img_data,
+ img_len, BIT_PARTIAL, flags);
+ }
+
+ if (err)
+ return err;
+
+ printf(" Programming %s bitstream... OK\n", name);
+ break;
+ default:
+ printf("The given image format is not supported (corrupt?)\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+static void fit_loadable_process(u8 img_type,
+ ulong img_data,
+ ulong img_len)
+{
+ int i;
+ const unsigned int count =
+ ll_entry_count(struct fit_loadable_tbl, fit_loadable);
+ struct fit_loadable_tbl *fit_loadable_handler =
+ ll_entry_start(struct fit_loadable_tbl, fit_loadable);
+ /* For each loadable handler */
+ for (i = 0; i < count; i++, fit_loadable_handler++)
+ /* matching this type */
+ if (fit_loadable_handler->type == img_type)
+ /* call that handler with this image data */
+ fit_loadable_handler->handler(img_data, img_len);
+}
+
+int boot_get_loadable(struct bootm_headers *images)
+{
+ /*
+ * These variables are used to hold the current image location
+ * in system memory.
+ */
+ ulong tmp_img_addr;
+ /*
+ * These two variables are requirements for fit_image_load, but
+ * their values are not used
+ */
+ ulong img_data, img_len;
+ void *buf;
+ int loadables_index;
+ int conf_noffset;
+ int fit_img_result;
+ const char *uname;
+ u8 img_type;
+
+ /* Check to see if the images struct has a FIT configuration */
+ if (!genimg_has_config(images)) {
+ debug("## FIT configuration was not specified\n");
+ return 0;
+ }
+
+ /*
+ * Obtain the os FIT header from the images struct
+ */
+ tmp_img_addr = map_to_sysmem(images->fit_hdr_os);
+ buf = map_sysmem(tmp_img_addr, 0);
+ /*
+ * Check image type. For FIT images get FIT node
+ * and attempt to locate a generic binary.
+ */
+ switch (genimg_get_format(buf)) {
+ case IMAGE_FORMAT_FIT:
+ conf_noffset = fit_conf_get_node(buf, images->fit_uname_cfg);
+
+ for (loadables_index = 0;
+ uname = fdt_stringlist_get(buf, conf_noffset,
+ FIT_LOADABLE_PROP,
+ loadables_index, NULL), uname;
+ loadables_index++) {
+ fit_img_result = fit_image_load(images, tmp_img_addr,
+ &uname,
+ &images->fit_uname_cfg,
+ IH_ARCH_DEFAULT,
+ IH_TYPE_LOADABLE,
+ BOOTSTAGE_ID_FIT_LOADABLE_START,
+ FIT_LOAD_OPTIONAL_NON_ZERO,
+ &img_data, &img_len);
+ if (fit_img_result < 0) {
+ /* Something went wrong! */
+ return fit_img_result;
+ }
+
+ fit_img_result = fit_image_get_node(buf, uname);
+ if (fit_img_result < 0) {
+ /* Something went wrong! */
+ return fit_img_result;
+ }
+ fit_img_result = fit_image_get_type(buf,
+ fit_img_result,
+ &img_type);
+ if (fit_img_result < 0) {
+ /* Something went wrong! */
+ return fit_img_result;
+ }
+
+ fit_loadable_process(img_type, img_data, img_len);
+ }
+ break;
+ default:
+ printf("The given image format is not supported (corrupt?)\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * boot_get_cmdline - allocate and initialize kernel cmdline
+ * @cmd_start: pointer to a ulong variable, will hold cmdline start
+ * @cmd_end: pointer to a ulong variable, will hold cmdline end
+ *
+ * This allocates space for kernel command line below
+ * BOOTMAPSZ + env_get_bootm_low() address. If "bootargs" U-Boot environment
+ * variable is present its contents is copied to allocated kernel
+ * command line.
+ *
+ * returns:
+ * 0 - success
+ * -1 - failure
+ */
+int boot_get_cmdline(ulong *cmd_start, ulong *cmd_end)
+{
+ int barg, err;
+ char *cmdline;
+ char *s;
+ phys_addr_t addr;
+
+ /*
+ * Help the compiler detect that this function is only called when
+ * CONFIG_SYS_BOOT_GET_CMDLINE is enabled
+ */
+ if (!IS_ENABLED(CONFIG_SYS_BOOT_GET_CMDLINE))
+ return 0;
+
+ barg = IF_ENABLED_INT(CONFIG_SYS_BOOT_GET_CMDLINE, CONFIG_SYS_BARGSIZE);
+ addr = env_get_bootm_mapsize() + env_get_bootm_low();
+
+ err = lmb_alloc_mem(LMB_MEM_ALLOC_MAX, 0xf, &addr, barg, LMB_NONE);
+ if (err)
+ return -1;
+
+ cmdline = (char *)(uintptr_t)addr;
+
+ s = env_get("bootargs");
+ if (!s)
+ s = "";
+
+ strcpy(cmdline, s);
+
+ *cmd_start = (ulong)cmdline;
+ *cmd_end = *cmd_start + strlen(cmdline);
+
+ debug("## cmdline at 0x%08lx ... 0x%08lx\n", *cmd_start, *cmd_end);
+
+ return 0;
+}
+
+/**
+ * boot_get_kbd - allocate and initialize kernel copy of board info
+ * @kbd: double pointer to board info data
+ *
+ * boot_get_kbd() allocates space for kernel copy of board info data below
+ * BOOTMAPSZ + env_get_bootm_low() address and kernel board info is initialized
+ * with the current u-boot board info data.
+ *
+ * returns:
+ * 0 - success
+ * -1 - failure
+ */
+int boot_get_kbd(struct bd_info **kbd)
+{
+ int err;
+ phys_addr_t addr;
+
+ addr = env_get_bootm_mapsize() + env_get_bootm_low();
+ err = lmb_alloc_mem(LMB_MEM_ALLOC_MAX, 0xf, &addr,
+ sizeof(struct bd_info), LMB_NONE);
+ if (err)
+ return -1;
+
+ *kbd = (struct bd_info *)(uintptr_t)addr;
+ **kbd = *gd->bd;
+
+ debug("## kernel board info at 0x%08lx\n", (ulong)*kbd);
+
+ if (_DEBUG && IS_ENABLED(CONFIG_CMD_BDI))
+ do_bdinfo(NULL, 0, 0, NULL);
+
+ return 0;
+}
+
+int image_setup_linux(struct bootm_headers *images)
+{
+ ulong of_size = images->ft_len;
+ char **of_flat_tree = &images->ft_addr;
+ int ret;
+
+ /* This function cannot be called without lmb support */
+ if (!CONFIG_IS_ENABLED(LMB))
+ return -EFAULT;
+ if (CONFIG_IS_ENABLED(OF_LIBFDT))
+ boot_fdt_add_mem_rsv_regions(*of_flat_tree);
+
+ if (IS_ENABLED(CONFIG_SYS_BOOT_GET_CMDLINE)) {
+ ret = boot_get_cmdline(&images->cmdline_start,
+ &images->cmdline_end);
+ if (ret) {
+ puts("ERROR with allocation of cmdline\n");
+ return ret;
+ }
+ }
+
+ if (CONFIG_IS_ENABLED(OF_LIBFDT)) {
+ ret = boot_relocate_fdt(of_flat_tree, &of_size);
+ if (ret)
+ return ret;
+ }
+
+ if (CONFIG_IS_ENABLED(OF_LIBFDT) && of_size) {
+ ret = image_setup_libfdt(images, *of_flat_tree, true);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+void genimg_print_size(uint32_t size)
+{
+ printf("%d Bytes = ", size);
+ print_size(size, "\n");
+}
+
+void genimg_print_time(time_t timestamp)
+{
+ struct rtc_time tm;
+
+ rtc_to_tm(timestamp, &tm);
+ printf("%4d-%02d-%02d %2d:%02d:%02d UTC\n",
+ tm.tm_year, tm.tm_mon, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+}
+
+/**
+ * get_default_image() - Return default property from /images
+ *
+ * Return: Pointer to value of default property (or NULL)
+ */
+static const char *get_default_image(const void *fit)
+{
+ int images_noffset;
+
+ images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH);
+ if (images_noffset < 0)
+ return NULL;
+
+ return fdt_getprop(fit, images_noffset, FIT_DEFAULT_PROP, NULL);
+}
+
+int image_locate_script(void *buf, int size, const char *fit_uname,
+ const char *confname, char **datap, uint *lenp)
+{
+ const struct legacy_img_hdr *hdr;
+ const void *fit_data;
+ const void *fit_hdr;
+ size_t fit_len;
+ int noffset;
+ int verify;
+ ulong len;
+ u32 *data;
+
+ verify = env_get_yesno("verify");
+
+ switch (genimg_get_format(buf)) {
+ case IMAGE_FORMAT_LEGACY:
+ if (!IS_ENABLED(CONFIG_LEGACY_IMAGE_FORMAT)) {
+ goto exit_image_format;
+ } else {
+ hdr = buf;
+
+ if (!image_check_magic(hdr)) {
+ puts("Bad magic number\n");
+ return 1;
+ }
+
+ if (!image_check_hcrc(hdr)) {
+ puts("Bad header crc\n");
+ return 1;
+ }
+
+ if (verify) {
+ if (!image_check_dcrc(hdr)) {
+ puts("Bad data crc\n");
+ return 1;
+ }
+ }
+
+ if (!image_check_type(hdr, IH_TYPE_SCRIPT)) {
+ puts("Bad image type\n");
+ return 1;
+ }
+
+ /* get length of script */
+ data = (u32 *)image_get_data(hdr);
+
+ len = uimage_to_cpu(*data);
+ if (!len) {
+ puts("Empty Script\n");
+ return 1;
+ }
+
+ /*
+ * scripts are just multi-image files with one
+ * component, so seek past the zero-terminated sequence
+ * of image lengths to get to the actual image data
+ */
+ while (*data++);
+ }
+ break;
+ case IMAGE_FORMAT_FIT:
+ if (!IS_ENABLED(CONFIG_FIT)) {
+ goto exit_image_format;
+ } else {
+ fit_hdr = buf;
+ if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) {
+ puts("Bad FIT image format\n");
+ return 1;
+ }
+
+ if (!fit_uname) {
+ /* If confname is empty, use the default */
+ if (confname && *confname)
+ noffset = fit_conf_get_node(fit_hdr, confname);
+ else
+ noffset = fit_conf_get_node(fit_hdr, NULL);
+ if (noffset < 0) {
+ if (!confname)
+ goto fallback;
+ printf("Could not find config %s\n", confname);
+ return 1;
+ }
+
+ if (verify && fit_config_verify(fit_hdr, noffset))
+ return 1;
+
+ noffset = fit_conf_get_prop_node(fit_hdr,
+ noffset,
+ FIT_SCRIPT_PROP,
+ IH_PHASE_NONE);
+ if (noffset < 0) {
+ if (!confname)
+ goto fallback;
+ printf("Could not find script in %s\n", confname);
+ return 1;
+ }
+ } else {
+fallback:
+ if (!fit_uname || !*fit_uname)
+ fit_uname = get_default_image(fit_hdr);
+ if (!fit_uname) {
+ puts("No FIT subimage unit name\n");
+ return 1;
+ }
+
+ /* get script component image node offset */
+ noffset = fit_image_get_node(fit_hdr, fit_uname);
+ if (noffset < 0) {
+ printf("Can't find '%s' FIT subimage\n",
+ fit_uname);
+ return 1;
+ }
+ }
+
+ if (!fit_image_check_type(fit_hdr, noffset,
+ IH_TYPE_SCRIPT)) {
+ puts("Not a image image\n");
+ return 1;
+ }
+
+ /* verify integrity */
+ if (verify && !fit_image_verify(fit_hdr, noffset)) {
+ puts("Bad Data Hash\n");
+ return 1;
+ }
+
+ /* get script subimage data address and length */
+ if (fit_image_get_data(fit_hdr, noffset, &fit_data,
+ &fit_len)) {
+ puts("Could not find script subimage data\n");
+ return 1;
+ }
+
+ data = (u32 *)fit_data;
+ len = (ulong)fit_len;
+ }
+ break;
+ default:
+ goto exit_image_format;
+ }
+
+ *datap = (char *)data;
+ *lenp = len;
+
+ return 0;
+
+exit_image_format:
+ puts("Wrong image format for \"source\" command\n");
+ return -EPERM;
+}
diff --git a/boot/image-cipher.c b/boot/image-cipher.c
new file mode 100644
index 00000000000..9d389f26cea
--- /dev/null
+++ b/boot/image-cipher.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2019, Softathome
+ */
+
+#ifdef USE_HOSTCC
+#include "mkimage.h"
+#include <time.h>
+#else
+#include <malloc.h>
+#include <asm/global_data.h>
+DECLARE_GLOBAL_DATA_PTR;
+#endif /* !USE_HOSdTCC*/
+#include <image.h>
+#include <uboot_aes.h>
+#include <u-boot/aes.h>
+
+struct cipher_algo cipher_algos[] = {
+ {
+ .name = "aes128",
+ .key_len = AES128_KEY_LENGTH,
+ .iv_len = AES_BLOCK_LENGTH,
+#if IMAGE_ENABLE_ENCRYPT
+ .calculate_type = EVP_aes_128_cbc,
+#endif
+ .encrypt = image_aes_encrypt,
+ .decrypt = image_aes_decrypt,
+ .add_cipher_data = image_aes_add_cipher_data
+ },
+ {
+ .name = "aes192",
+ .key_len = AES192_KEY_LENGTH,
+ .iv_len = AES_BLOCK_LENGTH,
+#if IMAGE_ENABLE_ENCRYPT
+ .calculate_type = EVP_aes_192_cbc,
+#endif
+ .encrypt = image_aes_encrypt,
+ .decrypt = image_aes_decrypt,
+ .add_cipher_data = image_aes_add_cipher_data
+ },
+ {
+ .name = "aes256",
+ .key_len = AES256_KEY_LENGTH,
+ .iv_len = AES_BLOCK_LENGTH,
+#if IMAGE_ENABLE_ENCRYPT
+ .calculate_type = EVP_aes_256_cbc,
+#endif
+ .encrypt = image_aes_encrypt,
+ .decrypt = image_aes_decrypt,
+ .add_cipher_data = image_aes_add_cipher_data
+ }
+};
+
+struct cipher_algo *image_get_cipher_algo(const char *full_name)
+{
+ int i;
+ const char *name;
+
+ for (i = 0; i < ARRAY_SIZE(cipher_algos); i++) {
+ name = cipher_algos[i].name;
+ if (!strncmp(name, full_name, strlen(name)))
+ return &cipher_algos[i];
+ }
+
+ return NULL;
+}
+
+static int fit_image_setup_decrypt(struct image_cipher_info *info,
+ const void *fit, int image_noffset,
+ int cipher_noffset)
+{
+ const void *fdt = gd_fdt_blob();
+ const char *node_name;
+ char node_path[128];
+ int noffset;
+ char *algo_name;
+ int ret;
+
+ node_name = fit_get_name(fit, image_noffset, NULL);
+ if (!node_name) {
+ printf("Can't get node name\n");
+ return -1;
+ }
+
+ if (fit_image_cipher_get_algo(fit, cipher_noffset, &algo_name)) {
+ printf("Can't get algo name for cipher '%s' in image '%s'\n",
+ node_name, node_name);
+ return -1;
+ }
+
+ info->keyname = fdt_getprop(fit, cipher_noffset, FIT_KEY_HINT, NULL);
+ if (!info->keyname) {
+ printf("Can't get key name\n");
+ return -1;
+ }
+
+ info->iv = fdt_getprop(fit, cipher_noffset, "iv", NULL);
+ info->ivname = fdt_getprop(fit, cipher_noffset, "iv-name-hint", NULL);
+
+ if (!info->iv && !info->ivname) {
+ printf("Can't get IV or IV name\n");
+ return -1;
+ }
+
+ info->fit = fit;
+ info->node_noffset = image_noffset;
+ info->name = algo_name;
+ info->cipher = image_get_cipher_algo(algo_name);
+ if (!info->cipher) {
+ printf("Can't get cipher\n");
+ return -1;
+ }
+
+ ret = fit_image_get_data_size_unciphered(fit, image_noffset,
+ &info->size_unciphered);
+ if (ret) {
+ printf("Can't get size of unciphered data\n");
+ return -1;
+ }
+
+ /*
+ * Search the cipher node in the u-boot fdt
+ * the path should be: /cipher/key-<algo>-<key>-<iv>
+ */
+ if (info->ivname)
+ snprintf(node_path, sizeof(node_path), "/%s/key-%s-%s-%s",
+ FIT_CIPHER_NODENAME, algo_name, info->keyname, info->ivname);
+ else
+ snprintf(node_path, sizeof(node_path), "/%s/key-%s-%s",
+ FIT_CIPHER_NODENAME, algo_name, info->keyname);
+
+ noffset = fdt_path_offset(fdt, node_path);
+ if (noffset < 0) {
+ printf("Can't found cipher node offset\n");
+ return -1;
+ }
+
+ /* read key */
+ info->key = fdt_getprop(fdt, noffset, "key", NULL);
+ if (!info->key) {
+ printf("Can't get key in cipher node '%s'\n", node_path);
+ return -1;
+ }
+
+ /* read iv */
+ if (!info->iv) {
+ info->iv = fdt_getprop(fdt, noffset, "iv", NULL);
+ if (!info->iv) {
+ printf("Can't get IV in cipher node '%s'\n", node_path);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int fit_image_decrypt_data(const void *fit,
+ int image_noffset, int cipher_noffset,
+ const void *data_ciphered, size_t size_ciphered,
+ void **data_unciphered, size_t *size_unciphered)
+{
+ struct image_cipher_info info;
+ int ret;
+
+ ret = fit_image_setup_decrypt(&info, fit, image_noffset,
+ cipher_noffset);
+ if (ret < 0)
+ goto out;
+
+ ret = info.cipher->decrypt(&info, data_ciphered, size_ciphered,
+ data_unciphered, size_unciphered);
+
+ out:
+ return ret;
+}
diff --git a/boot/image-fdt.c b/boot/image-fdt.c
new file mode 100644
index 00000000000..3f0ac54f76f
--- /dev/null
+++ b/boot/image-fdt.c
@@ -0,0 +1,722 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2013, Google Inc.
+ *
+ * (C) Copyright 2008 Semihalf
+ *
+ * (C) Copyright 2000-2006
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ */
+
+#include <command.h>
+#include <fdt_support.h>
+#include <fdtdec.h>
+#include <efi.h>
+#include <env.h>
+#include <errno.h>
+#include <image.h>
+#include <lmb.h>
+#include <log.h>
+#include <malloc.h>
+#include <asm/global_data.h>
+#include <linux/libfdt.h>
+#include <mapmem.h>
+#include <asm/io.h>
+#include <dm/ofnode.h>
+#include <tee/optee.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+static void fdt_error(const char *msg)
+{
+ puts("ERROR: ");
+ puts(msg);
+ puts(" - must RESET the board to recover.\n");
+}
+
+#if CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT)
+static const struct legacy_img_hdr *image_get_fdt(ulong fdt_addr)
+{
+ const struct legacy_img_hdr *fdt_hdr = map_sysmem(fdt_addr, 0);
+
+ image_print_contents(fdt_hdr);
+
+ puts(" Verifying Checksum ... ");
+ if (!image_check_hcrc(fdt_hdr)) {
+ fdt_error("fdt header checksum invalid");
+ return NULL;
+ }
+
+ if (!image_check_dcrc(fdt_hdr)) {
+ fdt_error("fdt checksum invalid");
+ return NULL;
+ }
+ puts("OK\n");
+
+ if (!image_check_type(fdt_hdr, IH_TYPE_FLATDT)) {
+ fdt_error("uImage is not a fdt");
+ return NULL;
+ }
+ if (image_get_comp(fdt_hdr) != IH_COMP_NONE) {
+ fdt_error("uImage is compressed");
+ return NULL;
+ }
+ if (fdt_check_header((void *)image_get_data(fdt_hdr)) != 0) {
+ fdt_error("uImage data is not a fdt");
+ return NULL;
+ }
+ return fdt_hdr;
+}
+#endif
+
+static void boot_fdt_reserve_region(u64 addr, u64 size, u32 flags)
+{
+ long ret;
+ phys_addr_t rsv_addr;
+
+ rsv_addr = (phys_addr_t)addr;
+ ret = lmb_alloc_mem(LMB_MEM_ALLOC_ADDR, 0, &rsv_addr, size, flags);
+ if (!ret) {
+ debug(" reserving fdt memory region: addr=%llx size=%llx flags=%x\n",
+ (unsigned long long)addr,
+ (unsigned long long)size, flags);
+ } else if (ret != -EEXIST && ret != -EINVAL) {
+ puts("ERROR: reserving fdt memory region failed ");
+ printf("(addr=%llx size=%llx flags=%x)\n",
+ (unsigned long long)addr,
+ (unsigned long long)size, flags);
+ }
+}
+
+/**
+ * boot_fdt_add_mem_rsv_regions - Mark the memreserve and reserved-memory
+ * sections as unusable
+ * @fdt_blob: pointer to fdt blob base address
+ *
+ * Adds the and reserved-memorymemreserve regions in the dtb to the lmb block.
+ * Adding the memreserve regions prevents u-boot from using them to store the
+ * initrd or the fdt blob.
+ */
+void boot_fdt_add_mem_rsv_regions(void *fdt_blob)
+{
+ uint64_t addr, size;
+ int i, total, ret;
+ int nodeoffset, subnode;
+ struct fdt_resource res;
+ u32 flags;
+
+ if (fdt_check_header(fdt_blob) != 0)
+ return;
+
+ /* process memreserve sections */
+ total = fdt_num_mem_rsv(fdt_blob);
+ for (i = 0; i < total; i++) {
+ if (fdt_get_mem_rsv(fdt_blob, i, &addr, &size) != 0)
+ continue;
+ boot_fdt_reserve_region(addr, size, LMB_NOOVERWRITE);
+ }
+
+ /* process reserved-memory */
+ nodeoffset = fdt_subnode_offset(fdt_blob, 0, "reserved-memory");
+ if (nodeoffset >= 0) {
+ subnode = fdt_first_subnode(fdt_blob, nodeoffset);
+ while (subnode >= 0) {
+ /* check if this subnode has a reg property */
+ ret = fdt_get_resource(fdt_blob, subnode, "reg", 0,
+ &res);
+ if (!ret && fdtdec_get_is_enabled(fdt_blob, subnode)) {
+ flags = LMB_NOOVERWRITE;
+ if (fdtdec_get_bool(fdt_blob, subnode,
+ "no-map"))
+ flags = LMB_NOMAP;
+ addr = res.start;
+ size = res.end - res.start + 1;
+ boot_fdt_reserve_region(addr, size, flags);
+ }
+
+ subnode = fdt_next_subnode(fdt_blob, subnode);
+ }
+ }
+}
+
+/**
+ * boot_relocate_fdt - relocate flat device tree
+ * @of_flat_tree: pointer to a char* variable, will hold fdt start address
+ * @of_size: pointer to a ulong variable, will hold fdt length
+ *
+ * boot_relocate_fdt() allocates a region of memory within the bootmap and
+ * relocates the of_flat_tree into that region, even if the fdt is already in
+ * the bootmap. It also expands the size of the fdt by CONFIG_SYS_FDT_PAD
+ * bytes.
+ *
+ * of_flat_tree and of_size are set to final (after relocation) values
+ *
+ * returns:
+ * 0 - success
+ * 1 - failure
+ */
+int boot_relocate_fdt(char **of_flat_tree, ulong *of_size)
+{
+ u64 start, size, usable, low, mapsize;
+ void *fdt_blob = *of_flat_tree;
+ void *of_start = NULL;
+ char *fdt_high;
+ ulong of_len = 0;
+ int bank;
+ int err;
+ int disable_relocation = 0;
+ phys_addr_t addr;
+
+ /* nothing to do */
+ if (*of_size == 0)
+ return 0;
+
+ if (fdt_check_header(fdt_blob) != 0) {
+ fdt_error("image is not a fdt");
+ goto error;
+ }
+
+ /* position on a 4K boundary before the alloc_current */
+ /* Pad the FDT by a specified amount */
+ of_len = *of_size + CONFIG_SYS_FDT_PAD;
+
+ /* If fdt_high is set use it to select the relocation address */
+ fdt_high = env_get("fdt_high");
+ if (fdt_high) {
+ ulong high_addr = hextoul(fdt_high, NULL);
+
+ if (high_addr == ~0UL) {
+ /* All ones means use fdt in place */
+ of_start = fdt_blob;
+ addr = map_to_sysmem(fdt_blob);
+ err = lmb_alloc_mem(LMB_MEM_ALLOC_ADDR, 0, &addr,
+ of_len, LMB_NONE);
+ if (err) {
+ printf("Failed to reserve memory for fdt at %#llx\n",
+ (u64)addr);
+ goto error;
+ }
+
+ disable_relocation = 1;
+ } else {
+ enum lmb_mem_type type = high_addr ?
+ LMB_MEM_ALLOC_MAX : LMB_MEM_ALLOC_ANY;
+
+ addr = high_addr;
+ err = lmb_alloc_mem(type, 0x1000, &addr, of_len,
+ LMB_NONE);
+ if (err) {
+ puts("Failed to allocate memory for Device Tree relocation\n");
+ goto error;
+ }
+ of_start = map_sysmem(addr, of_len);
+ }
+ } else {
+ mapsize = env_get_bootm_mapsize();
+ low = env_get_bootm_low();
+ of_start = NULL;
+
+ for (bank = 0; bank < CONFIG_NR_DRAM_BANKS; bank++) {
+ start = gd->bd->bi_dram[bank].start;
+ size = gd->bd->bi_dram[bank].size;
+
+ /* DRAM bank addresses are too low, skip it. */
+ if (start + size < low)
+ continue;
+
+ /*
+ * At least part of this DRAM bank is usable, try
+ * using the DRAM bank up to 'usable' address limit
+ * for LMB allocation.
+ */
+ usable = min(start + size, low + mapsize);
+ addr = usable;
+ err = lmb_alloc_mem(LMB_MEM_ALLOC_MAX, 0x1000,
+ &addr, of_len, LMB_NONE);
+ if (!err) {
+ of_start = map_sysmem(addr, of_len);
+ /* Allocation succeeded, use this block. */
+ if (of_start)
+ break;
+ }
+
+ /*
+ * Reduce the mapping size in the next bank
+ * by the size of attempt in current bank.
+ */
+ mapsize -= usable - max(start, low);
+ if (!mapsize)
+ break;
+ }
+ }
+
+ if (of_start == NULL) {
+ puts("device tree - allocation error\n");
+ goto error;
+ }
+
+ if (disable_relocation) {
+ /*
+ * We assume there is space after the existing fdt to use
+ * for padding
+ */
+ fdt_set_totalsize(of_start, of_len);
+ printf(" Using Device Tree in place at %p, end %p\n",
+ of_start, of_start + of_len - 1);
+ } else {
+ debug("## device tree at %p ... %p (len=%ld [0x%lX])\n",
+ fdt_blob, fdt_blob + *of_size - 1, of_len, of_len);
+
+ printf(" Loading Device Tree to %p, end %p ... ",
+ of_start, of_start + of_len - 1);
+
+ err = fdt_open_into(fdt_blob, of_start, of_len);
+ if (err != 0) {
+ fdt_error("fdt move failed");
+ goto error;
+ }
+ puts("OK\n");
+ }
+
+ *of_flat_tree = of_start;
+ *of_size = of_len;
+
+ if (IS_ENABLED(CONFIG_CMD_FDT))
+ set_working_fdt_addr(map_to_sysmem(*of_flat_tree));
+ return 0;
+
+error:
+ return 1;
+}
+
+/**
+ * select_fdt() - Select and locate the FDT to use
+ *
+ * @images: pointer to the bootm images structure
+ * @select: name of FDT to select, or NULL for any
+ * @arch: expected FDT architecture
+ * @fdt_addrp: pointer to a ulong variable, will hold FDT pointer
+ * Return: 0 if OK, -ENOPKG if no FDT (but an error should not be reported),
+ * other -ve value on other error
+ */
+
+static int select_fdt(struct bootm_headers *images, const char *select, u8 arch,
+ ulong *fdt_addrp)
+{
+ const char *buf;
+ ulong fdt_addr;
+
+#if CONFIG_IS_ENABLED(FIT)
+ const char *fit_uname_config = images->fit_uname_cfg;
+ const char *fit_uname_fdt = NULL;
+ ulong default_addr;
+ int fdt_noffset;
+
+ if (select) {
+ /*
+ * If the FDT blob comes from the FIT image and the
+ * FIT image address is omitted in the command line
+ * argument, try to use ramdisk or os FIT image
+ * address or default load address.
+ */
+ if (images->fit_uname_rd)
+ default_addr = (ulong)images->fit_hdr_rd;
+ else if (images->fit_uname_os)
+ default_addr = (ulong)images->fit_hdr_os;
+ else
+ default_addr = image_load_addr;
+
+ if (fit_parse_conf(select, default_addr, &fdt_addr,
+ &fit_uname_config)) {
+ debug("* fdt: config '%s' from image at 0x%08lx\n",
+ fit_uname_config, fdt_addr);
+ } else if (fit_parse_subimage(select, default_addr, &fdt_addr,
+ &fit_uname_fdt)) {
+ debug("* fdt: subimage '%s' from image at 0x%08lx\n",
+ fit_uname_fdt, fdt_addr);
+ } else
+#endif
+ {
+ fdt_addr = hextoul(select, NULL);
+ debug("* fdt: cmdline image address = 0x%08lx\n",
+ fdt_addr);
+ }
+#if CONFIG_IS_ENABLED(FIT)
+ } else {
+ /* use FIT configuration provided in first bootm
+ * command argument
+ */
+ fdt_addr = map_to_sysmem(images->fit_hdr_os);
+ fdt_noffset = fit_get_node_from_config(images, FIT_FDT_PROP,
+ fdt_addr);
+ if (fdt_noffset == -ENOENT)
+ return -ENOPKG;
+ else if (fdt_noffset < 0)
+ return fdt_noffset;
+ }
+#endif
+ debug("## Checking for 'FDT'/'FDT Image' at %08lx\n",
+ fdt_addr);
+
+ /*
+ * Check if there is an FDT image at the
+ * address provided in the second bootm argument
+ * check image type, for FIT images get a FIT node.
+ */
+ buf = map_sysmem(fdt_addr, 0);
+ switch (genimg_get_format(buf)) {
+#if CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT)
+ case IMAGE_FORMAT_LEGACY: {
+ const struct legacy_img_hdr *fdt_hdr;
+ ulong load, load_end;
+ ulong image_start, image_data, image_end;
+
+ /* verify fdt_addr points to a valid image header */
+ printf("## Flattened Device Tree from Legacy Image at %08lx\n",
+ fdt_addr);
+ fdt_hdr = image_get_fdt(fdt_addr);
+ if (!fdt_hdr)
+ return -ENOPKG;
+
+ /*
+ * move image data to the load address,
+ * make sure we don't overwrite initial image
+ */
+ image_start = (ulong)fdt_hdr;
+ image_data = (ulong)image_get_data(fdt_hdr);
+ image_end = image_get_image_end(fdt_hdr);
+
+ load = image_get_load(fdt_hdr);
+ load_end = load + image_get_data_size(fdt_hdr);
+
+ if (load == image_start ||
+ load == image_data) {
+ fdt_addr = load;
+ break;
+ }
+
+ if ((load < image_end) && (load_end > image_start)) {
+ fdt_error("fdt overwritten");
+ return -EFAULT;
+ }
+
+ debug(" Loading FDT from 0x%08lx to 0x%08lx\n",
+ image_data, load);
+
+ memmove((void *)load,
+ (void *)image_data,
+ image_get_data_size(fdt_hdr));
+
+ fdt_addr = load;
+ break;
+ }
+#endif
+ case IMAGE_FORMAT_FIT:
+ /*
+ * This case will catch both: new uImage format
+ * (libfdt based) and raw FDT blob (also libfdt
+ * based).
+ */
+#if CONFIG_IS_ENABLED(FIT)
+ /* check FDT blob vs FIT blob */
+ if (!fit_check_format(buf, IMAGE_SIZE_INVAL)) {
+ ulong load, len;
+
+ fdt_noffset = boot_get_fdt_fit(images, fdt_addr,
+ &fit_uname_fdt,
+ &fit_uname_config,
+ arch, &load, &len);
+
+ if (fdt_noffset < 0)
+ return -ENOENT;
+
+ images->fit_hdr_fdt = map_sysmem(fdt_addr, 0);
+ images->fit_uname_fdt = fit_uname_fdt;
+ images->fit_noffset_fdt = fdt_noffset;
+ fdt_addr = load;
+
+ break;
+ } else
+#endif
+ {
+ /*
+ * FDT blob
+ */
+ debug("* fdt: raw FDT blob\n");
+ printf("## Flattened Device Tree blob at %08lx\n",
+ (long)fdt_addr);
+ }
+ break;
+ default:
+ puts("ERROR: Did not find a cmdline Flattened Device Tree\n");
+ return -ENOENT;
+ }
+ *fdt_addrp = fdt_addr;
+
+ return 0;
+}
+
+int boot_get_fdt(void *buf, const char *select, uint arch,
+ struct bootm_headers *images, char **of_flat_tree,
+ ulong *of_size)
+{
+ char *fdt_blob = NULL;
+ ulong fdt_addr;
+
+ *of_flat_tree = NULL;
+ *of_size = 0;
+
+ if (select || genimg_has_config(images)) {
+ int ret;
+
+ ret = select_fdt(images, select, arch, &fdt_addr);
+ if (ret == -ENOPKG)
+ goto no_fdt;
+ else if (ret)
+ return 1;
+ printf(" Booting using the fdt blob at %#08lx\n", fdt_addr);
+ fdt_blob = map_sysmem(fdt_addr, 0);
+ } else if (images->legacy_hdr_valid &&
+ image_check_type(&images->legacy_hdr_os_copy,
+ IH_TYPE_MULTI)) {
+ ulong fdt_data, fdt_len;
+
+ /*
+ * Now check if we have a legacy multi-component image,
+ * get second entry data start address and len.
+ */
+ printf("## Flattened Device Tree from multi component Image at %08lX\n",
+ (ulong)images->legacy_hdr_os);
+
+ image_multi_getimg(images->legacy_hdr_os, 2, &fdt_data,
+ &fdt_len);
+ if (fdt_len) {
+ fdt_blob = (char *)fdt_data;
+ printf(" Booting using the fdt at 0x%p\n", fdt_blob);
+
+ if (fdt_check_header(fdt_blob) != 0) {
+ fdt_error("image is not a fdt");
+ goto error;
+ }
+
+ if (fdt_totalsize(fdt_blob) != fdt_len) {
+ fdt_error("fdt size != image size");
+ goto error;
+ }
+ } else {
+ debug("## No Flattened Device Tree\n");
+ goto no_fdt;
+ }
+#ifdef CONFIG_ANDROID_BOOT_IMAGE
+ } else if (genimg_get_format(buf) == IMAGE_FORMAT_ANDROID) {
+ void *hdr = buf;
+ ulong fdt_data, fdt_len;
+ u32 fdt_size, dtb_idx;
+ /*
+ * Firstly check if this android boot image has dtb field.
+ */
+ dtb_idx = (u32)env_get_ulong("adtb_idx", 10, 0);
+ if (android_image_get_dtb_by_index((ulong)hdr, get_avendor_bootimg_addr(),
+ dtb_idx, &fdt_addr, &fdt_size)) {
+ fdt_blob = (char *)map_sysmem(fdt_addr, 0);
+ if (fdt_check_header(fdt_blob))
+ goto no_fdt;
+
+ debug("## Using FDT in Android image dtb area with idx %u\n", dtb_idx);
+ } else if (!android_image_get_second(hdr, &fdt_data, &fdt_len) &&
+ !fdt_check_header((char *)fdt_data)) {
+ fdt_blob = (char *)fdt_data;
+ if (fdt_totalsize(fdt_blob) != fdt_len)
+ goto error;
+
+ debug("## Using FDT in Android image second area\n");
+ } else {
+ fdt_addr = env_get_hex("fdtaddr", 0);
+ if (!fdt_addr)
+ goto no_fdt;
+
+ fdt_blob = map_sysmem(fdt_addr, 0);
+ if (fdt_check_header(fdt_blob))
+ goto no_fdt;
+
+ debug("## Using FDT at ${fdtaddr}=Ox%lx\n", fdt_addr);
+ }
+#endif
+ } else {
+ debug("## No Flattened Device Tree\n");
+ goto no_fdt;
+ }
+
+ *of_flat_tree = fdt_blob;
+ *of_size = fdt_totalsize(fdt_blob);
+ debug(" of_flat_tree at 0x%08lx size 0x%08lx\n",
+ (ulong)*of_flat_tree, *of_size);
+
+ return 0;
+
+no_fdt:
+ debug("Continuing to boot without FDT\n");
+ return 0;
+error:
+ return 1;
+}
+
+/*
+ * Verify the device tree.
+ *
+ * This function is called after all device tree fix-ups have been enacted,
+ * so that the final device tree can be verified. The definition of "verified"
+ * is up to the specific implementation. However, it generally means that the
+ * addresses of some of the devices in the device tree are compared with the
+ * actual addresses at which U-Boot has placed them.
+ *
+ * Returns 1 on success, 0 on failure. If 0 is returned, U-Boot will halt the
+ * boot process.
+ */
+__weak int ft_verify_fdt(void *fdt)
+{
+ return 1;
+}
+
+__weak int arch_fixup_fdt(void *blob)
+{
+ return 0;
+}
+
+int image_setup_libfdt(struct bootm_headers *images, void *blob, bool lmb)
+{
+ ulong *initrd_start = &images->initrd_start;
+ ulong *initrd_end = &images->initrd_end;
+ bool skip_board_fixup = false;
+ int ret, fdt_ret, of_size;
+
+ if (IS_ENABLED(CONFIG_OF_ENV_SETUP)) {
+ const char *fdt_fixup;
+
+ fdt_fixup = env_get("fdt_fixup");
+ if (fdt_fixup) {
+ set_working_fdt_addr(map_to_sysmem(blob));
+ ret = run_command_list(fdt_fixup, -1, 0);
+ if (ret)
+ printf("WARNING: fdt_fixup command returned %d\n",
+ ret);
+ }
+ }
+
+ ret = -EPERM;
+
+ if (fdt_root(blob) < 0) {
+ printf("ERROR: root node setup failed\n");
+ goto err;
+ }
+ if (fdt_chosen(blob) < 0) {
+ printf("ERROR: /chosen node create failed\n");
+ goto err;
+ }
+ if (arch_fixup_fdt(blob) < 0) {
+ printf("ERROR: arch-specific fdt fixup failed\n");
+ goto err;
+ }
+
+ fdt_ret = optee_copy_fdt_nodes(blob);
+ if (fdt_ret) {
+ printf("ERROR: transfer of optee nodes to new fdt failed: %s\n",
+ fdt_strerror(fdt_ret));
+ goto err;
+ }
+
+ /* Store name of configuration node as u-boot,bootconf in /chosen node */
+ if (images->fit_uname_cfg)
+ fdt_find_and_setprop(blob, "/chosen", "u-boot,bootconf",
+ images->fit_uname_cfg,
+ strlen(images->fit_uname_cfg) + 1, 1);
+
+ /* Update ethernet nodes */
+ fdt_fixup_ethernet(blob);
+#if IS_ENABLED(CONFIG_CMD_PSTORE)
+ /* Append PStore configuration */
+ fdt_fixup_pstore(blob);
+#endif
+ if (IS_ENABLED(CONFIG_OF_BOARD_SETUP)) {
+ skip_board_fixup = (env_get_ulong("skip_board_fixup", 10, 0) == 1);
+
+ if (skip_board_fixup)
+ printf("skip all board fdt fixup\n");
+ }
+
+ if (IS_ENABLED(CONFIG_OF_BOARD_SETUP) && !skip_board_fixup) {
+ fdt_ret = ft_board_setup(blob, gd->bd);
+ if (fdt_ret) {
+ printf("ERROR: board-specific fdt fixup failed: %s\n",
+ fdt_strerror(fdt_ret));
+ goto err;
+ }
+ }
+ if (IS_ENABLED(CONFIG_OF_SYSTEM_SETUP)) {
+ fdt_ret = ft_system_setup(blob, gd->bd);
+ if (fdt_ret) {
+ printf("ERROR: system-specific fdt fixup failed: %s\n",
+ fdt_strerror(fdt_ret));
+ goto err;
+ }
+ }
+
+ if (fdt_initrd(blob, *initrd_start, *initrd_end))
+ goto err;
+
+ if (!ft_verify_fdt(blob))
+ goto err;
+
+ if (CONFIG_IS_ENABLED(BLKMAP) && CONFIG_IS_ENABLED(EFI_LOADER)) {
+ fdt_ret = fdt_efi_pmem_setup(blob);
+ if (fdt_ret)
+ goto err;
+ }
+
+ /* after here we are using a livetree */
+ if (!of_live_active() && CONFIG_IS_ENABLED(EVENT)) {
+ struct event_ft_fixup fixup;
+
+ fixup.tree = oftree_from_fdt(blob);
+ fixup.images = images;
+ if (oftree_valid(fixup.tree)) {
+ ret = event_notify(EVT_FT_FIXUP, &fixup, sizeof(fixup));
+ if (ret) {
+ printf("ERROR: fdt fixup event failed: %d\n",
+ ret);
+ goto err;
+ }
+ }
+ }
+
+ /* Delete the old LMB reservation */
+ if (CONFIG_IS_ENABLED(LMB) && lmb)
+ lmb_free(map_to_sysmem(blob), fdt_totalsize(blob), LMB_NONE);
+
+ ret = fdt_shrink_to_minimum(blob, 0);
+ if (ret < 0)
+ goto err;
+ of_size = ret;
+
+ /* Create a new LMB reservation */
+ if (CONFIG_IS_ENABLED(LMB) && lmb) {
+ phys_addr_t fdt_addr;
+
+ fdt_addr = map_to_sysmem(blob);
+ ret = lmb_alloc_mem(LMB_MEM_ALLOC_ADDR, 0, &fdt_addr,
+ of_size, LMB_NONE);
+ if (ret) {
+ printf("Failed to reserve memory for the fdt at %#llx\n",
+ (u64)fdt_addr);
+ }
+ }
+
+ if (IS_ENABLED(CONFIG_OF_BOARD_SETUP_EXTENDED) && !skip_board_fixup)
+ ft_board_setup_ex(blob, gd->bd);
+
+ return 0;
+err:
+ printf(" - must RESET the board to recover.\n\n");
+
+ return ret;
+}
diff --git a/boot/image-fit-sig.c b/boot/image-fit-sig.c
new file mode 100644
index 00000000000..f23e9d5d0b0
--- /dev/null
+++ b/boot/image-fit-sig.c
@@ -0,0 +1,560 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2013, Google Inc.
+ */
+
+#ifdef USE_HOSTCC
+#include "mkimage.h"
+#include <time.h>
+#else
+#include <log.h>
+#include <malloc.h>
+#include <asm/global_data.h>
+DECLARE_GLOBAL_DATA_PTR;
+#endif /* !USE_HOSTCC*/
+#include <fdt_region.h>
+#include <image.h>
+#include <u-boot/rsa.h>
+#include <u-boot/hash-checksum.h>
+
+#define IMAGE_MAX_HASHED_NODES 100
+
+/**
+ * fit_region_make_list() - Make a list of image regions
+ *
+ * Given a list of fdt_regions, create a list of image_regions. This is a
+ * simple conversion routine since the FDT and image code use different
+ * structures.
+ *
+ * @fit: FIT image
+ * @fdt_regions: Pointer to FDT regions
+ * @count: Number of FDT regions
+ * @region: Pointer to image regions, which must hold @count records. If
+ * region is NULL, then (except for an SPL build) the array will be
+ * allocated.
+ * @return: Pointer to image regions
+ */
+struct image_region *fit_region_make_list(const void *fit,
+ struct fdt_region *fdt_regions,
+ int count,
+ struct image_region *region)
+{
+ int i;
+
+ debug("Hash regions:\n");
+ debug("%10s %10s\n", "Offset", "Size");
+
+ /*
+ * Use malloc() except in SPL (to save code size). In SPL the caller
+ * must allocate the array.
+ */
+ if (!IS_ENABLED(CONFIG_XPL_BUILD) && !region)
+ region = calloc(sizeof(*region), count);
+ if (!region)
+ return NULL;
+ for (i = 0; i < count; i++) {
+ debug("%10x %10x\n", fdt_regions[i].offset,
+ fdt_regions[i].size);
+ region[i].data = fit + fdt_regions[i].offset;
+ region[i].size = fdt_regions[i].size;
+ }
+
+ return region;
+}
+
+static int fit_image_setup_verify(struct image_sign_info *info,
+ const void *fit, int noffset,
+ const void *key_blob, int required_keynode,
+ char **err_msgp)
+{
+ const char *algo_name;
+ const char *padding_name;
+
+ if (fdt_totalsize(fit) > CONFIG_VAL(FIT_SIGNATURE_MAX_SIZE)) {
+ *err_msgp = "Total size too large";
+ return 1;
+ }
+ if (fit_image_hash_get_algo(fit, noffset, &algo_name)) {
+ *err_msgp = "Can't get hash algo property";
+ return -1;
+ }
+
+ padding_name = fdt_getprop(fit, noffset, "padding", NULL);
+ if (!padding_name)
+ padding_name = RSA_DEFAULT_PADDING_NAME;
+
+ memset(info, '\0', sizeof(*info));
+ info->keyname = fdt_getprop(fit, noffset, FIT_KEY_HINT, NULL);
+ info->fit = fit;
+ info->node_offset = noffset;
+ info->name = algo_name;
+ info->checksum = image_get_checksum_algo(algo_name);
+ info->crypto = image_get_crypto_algo(algo_name);
+ info->padding = image_get_padding_algo(padding_name);
+ info->fdt_blob = key_blob;
+ info->required_keynode = required_keynode;
+ printf("%s:%s", algo_name, info->keyname);
+
+ if (!info->checksum || !info->crypto) {
+ *err_msgp = "Unknown signature algorithm";
+ return -1;
+ }
+
+ return 0;
+}
+
+int fit_image_check_sig(const void *fit, int noffset, const void *data,
+ size_t size, const void *key_blob, int required_keynode,
+ char **err_msgp)
+{
+ struct image_sign_info info;
+ struct image_region region;
+ uint8_t *fit_value;
+ int fit_value_len;
+
+ *err_msgp = NULL;
+ if (fit_image_setup_verify(&info, fit, noffset, key_blob,
+ required_keynode, err_msgp))
+ return -1;
+
+ if (fit_image_hash_get_value(fit, noffset, &fit_value,
+ &fit_value_len)) {
+ *err_msgp = "Can't get hash value property";
+ return -1;
+ }
+
+ region.data = data;
+ region.size = size;
+
+ if (info.crypto->verify(&info, &region, 1, fit_value, fit_value_len)) {
+ *err_msgp = "Verification failed";
+ return -1;
+ }
+
+ return 0;
+}
+
+static int fit_image_verify_sig(const void *fit, int image_noffset,
+ const char *data, size_t size,
+ const void *key_blob, int key_offset)
+{
+ int noffset;
+ char *err_msg = "";
+ int verified = 0;
+ int ret;
+
+ /* Process all hash subnodes of the component image node */
+ fdt_for_each_subnode(noffset, fit, image_noffset) {
+ const char *name = fit_get_name(fit, noffset, NULL);
+
+ /*
+ * We don't support this since libfdt considers names with the
+ * name root but different @ suffix to be equal
+ */
+ if (strchr(name, '@')) {
+ err_msg = "Node name contains @";
+ goto error;
+ }
+ if (!strncmp(name, FIT_SIG_NODENAME,
+ strlen(FIT_SIG_NODENAME))) {
+ ret = fit_image_check_sig(fit, noffset, data, size,
+ key_blob, -1, &err_msg);
+ if (ret) {
+ puts("- ");
+ } else {
+ puts("+ ");
+ verified = 1;
+ break;
+ }
+ }
+ }
+
+ if (noffset == -FDT_ERR_TRUNCATED || noffset == -FDT_ERR_BADSTRUCTURE) {
+ err_msg = "Corrupted or truncated tree";
+ goto error;
+ }
+
+ return verified ? 0 : -EPERM;
+
+error:
+ printf(" error!\n%s for '%s' hash node in '%s' image node\n",
+ err_msg, fit_get_name(fit, noffset, NULL),
+ fit_get_name(fit, image_noffset, NULL));
+ return -1;
+}
+
+int fit_image_verify_required_sigs(const void *fit, int image_noffset,
+ const char *data, size_t size,
+ const void *key_blob, int *no_sigsp)
+{
+ int verify_count = 0;
+ int noffset;
+ int key_node;
+
+#ifdef USE_HOSTCC
+ if (!key_blob)
+ return 0;
+#endif
+
+ /* Work out what we need to verify */
+ *no_sigsp = 1;
+ key_node = fdt_subnode_offset(key_blob, 0, FIT_SIG_NODENAME);
+ if (key_node < 0) {
+ debug("%s: No signature node found: %s\n", __func__,
+ fdt_strerror(key_node));
+ return 0;
+ }
+
+ fdt_for_each_subnode(noffset, key_blob, key_node) {
+ const char *required;
+ int ret;
+
+ required = fdt_getprop(key_blob, noffset, FIT_KEY_REQUIRED,
+ NULL);
+ if (!required || strcmp(required, "image"))
+ continue;
+ ret = fit_image_verify_sig(fit, image_noffset, data, size,
+ key_blob, noffset);
+ if (ret) {
+ printf("Failed to verify required signature '%s'\n",
+ fit_get_name(key_blob, noffset, NULL));
+ return ret;
+ }
+ verify_count++;
+ }
+
+ if (verify_count)
+ *no_sigsp = 0;
+
+ return 0;
+}
+
+/**
+ * fit_config_check_sig() - Check the signature of a config
+ *
+ * Here we are looking at a particular signature that needs verification (here
+ * signature-1):
+ *
+ * configurations {
+ * default = "conf-1";
+ * conf-1 {
+ * kernel = "kernel-1";
+ * fdt = "fdt-1";
+ * signature-1 {
+ * algo = "sha1,rsa2048";
+ * value = <...conf 1 signature...>;
+ * };
+ * };
+ *
+ * @fit: FIT to check
+ * @noffset: Offset of the signature node being checked (e.g.
+ * /configurations/conf-1/signature-1)
+ * @conf_noffset: Offset of configuration node (e.g. /configurations/conf-1)
+ * @key_blob: Blob containing the keys to check against
+ * @required_keynode: Offset in @key_blob of the required key node,
+ * if any. If this is given, then the configuration wil not
+ * pass verification unless that key is used. If this is
+ * -1 then any signature will do.
+ * @err_msgp: In the event of an error, this will be pointed to a
+ * help error string to display to the user.
+ * Return: 0 if all verified ok, <0 on error
+ */
+static int fit_config_check_sig(const void *fit, int noffset, int conf_noffset,
+ const void *key_blob, int required_keynode,
+ char **err_msgp)
+{
+ static char * const exc_prop[] = {
+ FIT_DATA_PROP,
+ FIT_DATA_SIZE_PROP,
+ FIT_DATA_POSITION_PROP,
+ FIT_DATA_OFFSET_PROP,
+ };
+
+ const char *prop, *end, *name;
+ struct image_sign_info info;
+ const uint32_t *strings;
+ const char *config_name;
+ uint8_t *fit_value;
+ int fit_value_len;
+ bool found_config;
+ int max_regions;
+ int i, prop_len;
+ char path[200];
+ int count;
+
+ config_name = fit_get_name(fit, conf_noffset, NULL);
+ debug("%s: fdt=%p, conf='%s', sig='%s'\n", __func__, key_blob,
+ fit_get_name(fit, noffset, NULL),
+ fit_get_name(key_blob, required_keynode, NULL));
+ *err_msgp = NULL;
+ if (fit_image_setup_verify(&info, fit, noffset, key_blob,
+ required_keynode, err_msgp))
+ return -1;
+
+ if (fit_image_hash_get_value(fit, noffset, &fit_value,
+ &fit_value_len)) {
+ *err_msgp = "Can't get hash value property";
+ return -1;
+ }
+
+ /* Count the number of strings in the property */
+ prop = fdt_getprop(fit, noffset, "hashed-nodes", &prop_len);
+ end = prop ? prop + prop_len : prop;
+ for (name = prop, count = 0; name < end; name++)
+ if (!*name)
+ count++;
+ if (!count) {
+ *err_msgp = "Can't get hashed-nodes property";
+ return -1;
+ }
+
+ if (prop && prop_len > 0 && prop[prop_len - 1] != '\0') {
+ *err_msgp = "hashed-nodes property must be null-terminated";
+ return -1;
+ }
+
+ /* Add a sanity check here since we are using the stack */
+ if (count > IMAGE_MAX_HASHED_NODES) {
+ *err_msgp = "Number of hashed nodes exceeds maximum";
+ return -1;
+ }
+
+ /* Create a list of node names from those strings */
+ char *node_inc[count];
+
+ debug("Hash nodes (%d):\n", count);
+ found_config = false;
+ for (name = prop, i = 0; name < end; name += strlen(name) + 1, i++) {
+ debug(" '%s'\n", name);
+ node_inc[i] = (char *)name;
+ if (!strncmp(FIT_CONFS_PATH, name, strlen(FIT_CONFS_PATH)) &&
+ name[sizeof(FIT_CONFS_PATH) - 1] == '/' &&
+ !strcmp(name + sizeof(FIT_CONFS_PATH), config_name)) {
+ debug(" (found config node %s)", config_name);
+ found_config = true;
+ }
+ }
+ if (!found_config) {
+ *err_msgp = "Selected config not in hashed nodes";
+ return -1;
+ }
+
+ /*
+ * Each node can generate one region for each sub-node. Allow for
+ * 7 sub-nodes (hash-1, signature-1, etc.) and some extra.
+ */
+ max_regions = 20 + count * 7;
+ struct fdt_region fdt_regions[max_regions];
+
+ /* Get a list of regions to hash */
+ count = fdt_find_regions(fit, node_inc, count,
+ exc_prop, ARRAY_SIZE(exc_prop),
+ fdt_regions, max_regions - 1,
+ path, sizeof(path), 0);
+ if (count < 0) {
+ *err_msgp = "Failed to hash configuration";
+ return -1;
+ }
+ if (count == 0) {
+ *err_msgp = "No data to hash";
+ return -1;
+ }
+ if (count >= max_regions - 1) {
+ *err_msgp = "Too many hash regions";
+ return -1;
+ }
+
+ /* Add the strings */
+ strings = fdt_getprop(fit, noffset, "hashed-strings", NULL);
+ if (strings) {
+ /*
+ * The strings region offset must be a static 0x0.
+ * This is set in tool/image-host.c
+ */
+ fdt_regions[count].offset = fdt_off_dt_strings(fit);
+ fdt_regions[count].size = fdt32_to_cpu(strings[1]);
+ count++;
+ }
+
+ /* Allocate the region list on the stack */
+ struct image_region region[count];
+
+ fit_region_make_list(fit, fdt_regions, count, region);
+ if (info.crypto->verify(&info, region, count, fit_value,
+ fit_value_len)) {
+ *err_msgp = "Verification failed";
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * fit_config_verify_key() - Verify that a configuration is signed with a key
+ *
+ * Here we are looking at a particular configuration that needs verification:
+ *
+ * configurations {
+ * default = "conf-1";
+ * conf-1 {
+ * kernel = "kernel-1";
+ * fdt = "fdt-1";
+ * signature-1 {
+ * algo = "sha1,rsa2048";
+ * value = <...conf 1 signature...>;
+ * };
+ * };
+ *
+ * We must check each of the signature subnodes of conf-1. Hopefully one of them
+ * will match the key at key_offset.
+ *
+ * @fit: FIT to check
+ * @conf_noffset: Offset of the configuration node to check (e.g.
+ * /configurations/conf-1)
+ * @key_blob: Blob containing the keys to check against
+ * @key_offset: Offset of the key to check within @key_blob
+ * @return 0 if OK, -EPERM if any signatures did not verify, or the
+ * configuration node has an invalid name
+ */
+static int fit_config_verify_key(const void *fit, int conf_noffset,
+ const void *key_blob, int key_offset)
+{
+ int noffset;
+ char *err_msg = "No 'signature' subnode found";
+ int verified = 0;
+ int ret;
+
+ /* Process all hash subnodes of the component conf node */
+ fdt_for_each_subnode(noffset, fit, conf_noffset) {
+ const char *name = fit_get_name(fit, noffset, NULL);
+
+ if (!strncmp(name, FIT_SIG_NODENAME,
+ strlen(FIT_SIG_NODENAME))) {
+ ret = fit_config_check_sig(fit, noffset, conf_noffset,
+ key_blob, key_offset,
+ &err_msg);
+ if (ret) {
+ puts("- ");
+ } else {
+ puts("+ ");
+ verified = 1;
+ break;
+ }
+ }
+ }
+
+ if (noffset == -FDT_ERR_TRUNCATED || noffset == -FDT_ERR_BADSTRUCTURE) {
+ err_msg = "Corrupted or truncated tree";
+ goto error;
+ }
+
+ if (verified)
+ return 0;
+
+error:
+ printf(" error!\n%s for '%s' hash node in '%s' config node\n",
+ err_msg, fit_get_name(fit, noffset, NULL),
+ fit_get_name(fit, conf_noffset, NULL));
+ return -EPERM;
+}
+
+/**
+ * fit_config_verify_required_keys() - verify any required signatures for config
+ *
+ * This looks through all the signatures we expect and verifies that at least
+ * all the required ones are valid signatures for the configuration
+ *
+ * @fit: FIT to check
+ * @conf_noffset: Offset of the configuration node to check (e.g.
+ * /configurations/conf-1)
+ * @key_blob: Blob containing the keys to check against
+ * @return 0 if OK, -EPERM if any signatures did not verify, or the
+ * configuration node has an invalid name
+ */
+static int fit_config_verify_required_keys(const void *fit, int conf_noffset,
+ const void *key_blob)
+{
+ const char *name = fit_get_name(fit, conf_noffset, NULL);
+ int noffset;
+ int key_node;
+ int verified = 0;
+ int reqd_sigs = 0;
+ bool reqd_policy_all = true;
+ const char *reqd_mode;
+
+#ifdef USE_HOSTCC
+ if (!key_blob)
+ return 0;
+#endif
+
+ /*
+ * We don't support this since libfdt considers names with the
+ * name root but different @ suffix to be equal
+ */
+ if (strchr(name, '@')) {
+ printf("Configuration node '%s' contains '@'\n", name);
+ return -EPERM;
+ }
+
+ /* Work out what we need to verify */
+ key_node = fdt_subnode_offset(key_blob, 0, FIT_SIG_NODENAME);
+ if (key_node < 0) {
+ debug("%s: No signature node found: %s\n", __func__,
+ fdt_strerror(key_node));
+ return 0;
+ }
+
+ /* Get required-mode policy property from DTB */
+ reqd_mode = fdt_getprop(key_blob, key_node, "required-mode", NULL);
+ if (reqd_mode && !strcmp(reqd_mode, "any"))
+ reqd_policy_all = false;
+
+ debug("%s: required-mode policy set to '%s'\n", __func__,
+ reqd_policy_all ? "all" : "any");
+
+ /*
+ * The algorithm here is a little convoluted due to how we want it to
+ * work. Here we work through each of the signature nodes in the
+ * public-key area. These are in the U-Boot control devicetree. Each
+ * node was created by signing a configuration, so we check if it is
+ * 'required' and if so, request that it be verified.
+ */
+ fdt_for_each_subnode(noffset, key_blob, key_node) {
+ const char *required;
+ int ret;
+
+ required = fdt_getprop(key_blob, noffset, FIT_KEY_REQUIRED,
+ NULL);
+ if (!required || strcmp(required, "conf"))
+ continue;
+
+ reqd_sigs++;
+
+ ret = fit_config_verify_key(fit, conf_noffset, key_blob,
+ noffset);
+ if (ret) {
+ if (reqd_policy_all) {
+ printf("Failed to verify required signature '%s'\n",
+ fit_get_name(key_blob, noffset, NULL));
+ return ret;
+ }
+ } else {
+ verified++;
+ if (!reqd_policy_all)
+ break;
+ }
+ }
+
+ if (reqd_sigs && !verified) {
+ printf("Failed to verify 'any' of the required signature(s)\n");
+ return -EPERM;
+ }
+
+ return 0;
+}
+
+int fit_config_verify(const void *fit, int conf_noffset)
+{
+ return fit_config_verify_required_keys(fit, conf_noffset,
+ gd_fdt_blob());
+}
diff --git a/boot/image-fit.c b/boot/image-fit.c
new file mode 100644
index 00000000000..41ab1f552b0
--- /dev/null
+++ b/boot/image-fit.c
@@ -0,0 +1,2523 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2013, Google Inc.
+ *
+ * (C) Copyright 2008 Semihalf
+ *
+ * (C) Copyright 2000-2006
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ */
+
+#define LOG_CATEGORY LOGC_BOOT
+
+#ifdef USE_HOSTCC
+#include "mkimage.h"
+#include <time.h>
+#include <linux/libfdt.h>
+#include <u-boot/crc.h>
+#include <linux/kconfig.h>
+#else
+#include <linux/compiler.h>
+#include <linux/sizes.h>
+#include <errno.h>
+#include <log.h>
+#include <mapmem.h>
+#include <asm/io.h>
+#include <malloc.h>
+#include <memalign.h>
+#include <asm/global_data.h>
+#ifdef CONFIG_DM_HASH
+#include <dm.h>
+#include <u-boot/hash.h>
+#endif
+DECLARE_GLOBAL_DATA_PTR;
+#endif /* !USE_HOSTCC*/
+
+#include <bootm.h>
+#include <image.h>
+#include <bootstage.h>
+#include <upl.h>
+#include <u-boot/crc.h>
+
+/*****************************************************************************/
+/* New uImage format routines */
+/*****************************************************************************/
+#ifndef USE_HOSTCC
+static int fit_parse_spec(const char *spec, char sepc, ulong addr_curr,
+ ulong *addr, const char **name)
+{
+ const char *sep;
+
+ *addr = addr_curr;
+ *name = NULL;
+
+ sep = strchr(spec, sepc);
+ if (sep) {
+ if (sep - spec > 0)
+ *addr = hextoul(spec, NULL);
+
+ *name = sep + 1;
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * fit_parse_conf - parse FIT configuration spec
+ * @spec: input string, containing configuration spec
+ * @add_curr: current image address (to be used as a possible default)
+ * @addr: pointer to a ulong variable, will hold FIT image address of a given
+ * configuration
+ * @conf_name double pointer to a char, will hold pointer to a configuration
+ * unit name
+ *
+ * fit_parse_conf() expects configuration spec in the form of [<addr>]#<conf>,
+ * where <addr> is a FIT image address that contains configuration
+ * with a <conf> unit name.
+ *
+ * Address part is optional, and if omitted default add_curr will
+ * be used instead.
+ *
+ * returns:
+ * 1 if spec is a valid configuration string,
+ * addr and conf_name are set accordingly
+ * 0 otherwise
+ */
+int fit_parse_conf(const char *spec, ulong addr_curr,
+ ulong *addr, const char **conf_name)
+{
+ return fit_parse_spec(spec, '#', addr_curr, addr, conf_name);
+}
+
+/**
+ * fit_parse_subimage - parse FIT subimage spec
+ * @spec: input string, containing subimage spec
+ * @add_curr: current image address (to be used as a possible default)
+ * @addr: pointer to a ulong variable, will hold FIT image address of a given
+ * subimage
+ * @image_name: double pointer to a char, will hold pointer to a subimage name
+ *
+ * fit_parse_subimage() expects subimage spec in the form of
+ * [<addr>]:<subimage>, where <addr> is a FIT image address that contains
+ * subimage with a <subimg> unit name.
+ *
+ * Address part is optional, and if omitted default add_curr will
+ * be used instead.
+ *
+ * returns:
+ * 1 if spec is a valid subimage string,
+ * addr and image_name are set accordingly
+ * 0 otherwise
+ */
+int fit_parse_subimage(const char *spec, ulong addr_curr,
+ ulong *addr, const char **image_name)
+{
+ return fit_parse_spec(spec, ':', addr_curr, addr, image_name);
+}
+#endif /* !USE_HOSTCC */
+
+#ifdef USE_HOSTCC
+/* Host tools use these implementations for Cipher and Signature support */
+static void *host_blob;
+
+void image_set_host_blob(void *blob)
+{
+ host_blob = blob;
+}
+
+void *image_get_host_blob(void)
+{
+ return host_blob;
+}
+#endif /* USE_HOSTCC */
+
+static void fit_get_debug(const void *fit, int noffset,
+ char *prop_name, int err)
+{
+ debug("Can't get '%s' property from FIT 0x%08lx, node: offset %d, name %s (%s)\n",
+ prop_name, (ulong)fit, noffset, fit_get_name(fit, noffset, NULL),
+ fdt_strerror(err));
+}
+
+/**
+ * fit_get_subimage_count - get component (sub-image) count
+ * @fit: pointer to the FIT format image header
+ * @images_noffset: offset of images node
+ *
+ * returns:
+ * number of image components
+ */
+int fit_get_subimage_count(const void *fit, int images_noffset)
+{
+ int noffset;
+ int ndepth;
+ int count = 0;
+
+ /* Process its subnodes, print out component images details */
+ for (ndepth = 0, count = 0,
+ noffset = fdt_next_node(fit, images_noffset, &ndepth);
+ (noffset >= 0) && (ndepth > 0);
+ noffset = fdt_next_node(fit, noffset, &ndepth)) {
+ if (ndepth == 1) {
+ count++;
+ }
+ }
+
+ return count;
+}
+
+/**
+ * fit_image_print_data() - prints out the hash node details
+ * @fit: pointer to the FIT format image header
+ * @noffset: offset of the hash node
+ * @p: pointer to prefix string
+ * @type: Type of information to print ("hash" or "sign")
+ *
+ * fit_image_print_data() lists properties for the processed hash node
+ *
+ * This function avoid using puts() since it prints a newline on the host
+ * but does not in U-Boot.
+ *
+ * returns:
+ * no returned results
+ */
+static void fit_image_print_data(const void *fit, int noffset, const char *p,
+ const char *type)
+{
+ const char *keyname;
+ uint8_t *value;
+ int value_len;
+ const char *algo;
+ const char *padding;
+ bool required;
+ int ret, i;
+
+ debug("%s %s node: '%s'\n", p, type,
+ fit_get_name(fit, noffset, NULL));
+ printf("%s %s algo: ", p, type);
+ if (fit_image_hash_get_algo(fit, noffset, &algo)) {
+ printf("invalid/unsupported\n");
+ return;
+ }
+ printf("%s", algo);
+ keyname = fdt_getprop(fit, noffset, FIT_KEY_HINT, NULL);
+ required = fdt_getprop(fit, noffset, FIT_KEY_REQUIRED, NULL) != NULL;
+ if (keyname)
+ printf(":%s", keyname);
+ if (required)
+ printf(" (required)");
+ printf("\n");
+
+ padding = fdt_getprop(fit, noffset, "padding", NULL);
+ if (padding)
+ printf("%s %s padding: %s\n", p, type, padding);
+
+ ret = fit_image_hash_get_value(fit, noffset, &value,
+ &value_len);
+ printf("%s %s value: ", p, type);
+ if (ret) {
+ printf("unavailable\n");
+ } else {
+ for (i = 0; i < value_len; i++)
+ printf("%02x", value[i]);
+ printf("\n");
+ }
+
+ debug("%s %s len: %d\n", p, type, value_len);
+
+ /* Signatures have a time stamp */
+ if (IMAGE_ENABLE_TIMESTAMP && keyname) {
+ time_t timestamp;
+
+ printf("%s Timestamp: ", p);
+ if (fit_get_timestamp(fit, noffset, &timestamp))
+ printf("unavailable\n");
+ else
+ genimg_print_time(timestamp);
+ }
+}
+
+/**
+ * fit_image_print_verification_data() - prints out the hash/signature details
+ * @fit: pointer to the FIT format image header
+ * @noffset: offset of the hash or signature node
+ * @p: pointer to prefix string
+ *
+ * This lists properties for the processed hash node
+ *
+ * returns:
+ * no returned results
+ */
+static void fit_image_print_verification_data(const void *fit, int noffset,
+ const char *p)
+{
+ const char *name;
+
+ /*
+ * Check subnode name, must be equal to "hash" or "signature".
+ * Multiple hash/signature nodes require unique unit node
+ * names, e.g. hash-1, hash-2, signature-1, signature-2, etc.
+ */
+ name = fit_get_name(fit, noffset, NULL);
+ if (!strncmp(name, FIT_HASH_NODENAME, strlen(FIT_HASH_NODENAME))) {
+ fit_image_print_data(fit, noffset, p, "Hash");
+ } else if (!strncmp(name, FIT_SIG_NODENAME,
+ strlen(FIT_SIG_NODENAME))) {
+ fit_image_print_data(fit, noffset, p, "Sign");
+ }
+}
+
+/**
+ * fit_conf_print - prints out the FIT configuration details
+ * @fit: pointer to the FIT format image header
+ * @noffset: offset of the configuration node
+ * @p: pointer to prefix string
+ *
+ * fit_conf_print() lists all mandatory properties for the processed
+ * configuration node.
+ *
+ * returns:
+ * no returned results
+ */
+static void fit_conf_print(const void *fit, int noffset, const char *p)
+{
+ char *desc;
+ const char *uname;
+ int ret;
+ int fdt_index, loadables_index;
+ int ndepth;
+
+ /* Mandatory properties */
+ ret = fit_get_desc(fit, noffset, &desc);
+ printf("%s Description: ", p);
+ if (ret)
+ printf("unavailable\n");
+ else
+ printf("%s\n", desc);
+
+ uname = fdt_getprop(fit, noffset, FIT_KERNEL_PROP, NULL);
+ printf("%s Kernel: ", p);
+ if (!uname)
+ printf("unavailable\n");
+ else
+ printf("%s\n", uname);
+
+ /* Optional properties */
+ uname = fdt_getprop(fit, noffset, FIT_RAMDISK_PROP, NULL);
+ if (uname)
+ printf("%s Init Ramdisk: %s\n", p, uname);
+
+ uname = fdt_getprop(fit, noffset, FIT_FIRMWARE_PROP, NULL);
+ if (uname)
+ printf("%s Firmware: %s\n", p, uname);
+
+ for (fdt_index = 0;
+ uname = fdt_stringlist_get(fit, noffset, FIT_FDT_PROP,
+ fdt_index, NULL), uname;
+ fdt_index++) {
+ if (fdt_index == 0)
+ printf("%s FDT: ", p);
+ else
+ printf("%s ", p);
+ printf("%s\n", uname);
+ }
+
+ uname = fdt_getprop(fit, noffset, FIT_FPGA_PROP, NULL);
+ if (uname)
+ printf("%s FPGA: %s\n", p, uname);
+
+ /* Print out all of the specified loadables */
+ for (loadables_index = 0;
+ uname = fdt_stringlist_get(fit, noffset, FIT_LOADABLE_PROP,
+ loadables_index, NULL), uname;
+ loadables_index++) {
+ if (loadables_index == 0) {
+ printf("%s Loadables: ", p);
+ } else {
+ printf("%s ", p);
+ }
+ printf("%s\n", uname);
+ }
+
+ /* Process all hash subnodes of the component configuration node */
+ for (ndepth = 0, noffset = fdt_next_node(fit, noffset, &ndepth);
+ (noffset >= 0) && (ndepth > 0);
+ noffset = fdt_next_node(fit, noffset, &ndepth)) {
+ if (ndepth == 1) {
+ /* Direct child node of the component configuration node */
+ fit_image_print_verification_data(fit, noffset, p);
+ }
+ }
+}
+
+/**
+ * fit_print_contents - prints out the contents of the FIT format image
+ * @fit: pointer to the FIT format image header
+ * @p: pointer to prefix string
+ *
+ * fit_print_contents() formats a multi line FIT image contents description.
+ * The routine prints out FIT image properties (root node level) followed by
+ * the details of each component image.
+ *
+ * returns:
+ * no returned results
+ */
+void fit_print_contents(const void *fit)
+{
+ char *desc;
+ char *uname;
+ int images_noffset;
+ int confs_noffset;
+ int noffset;
+ int ndepth;
+ int count = 0;
+ int ret;
+ const char *p;
+ time_t timestamp;
+
+ if (!CONFIG_IS_ENABLED(FIT_PRINT))
+ return;
+
+ /* Indent string is defined in header image.h */
+ p = IMAGE_INDENT_STRING;
+
+ /* Root node properties */
+ ret = fit_get_desc(fit, 0, &desc);
+ printf("%sFIT description: ", p);
+ if (ret)
+ printf("unavailable\n");
+ else
+ printf("%s\n", desc);
+
+ if (IMAGE_ENABLE_TIMESTAMP) {
+ ret = fit_get_timestamp(fit, 0, &timestamp);
+ printf("%sCreated: ", p);
+ if (ret)
+ printf("unavailable\n");
+ else
+ genimg_print_time(timestamp);
+ }
+
+ /* Find images parent node offset */
+ images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH);
+ if (images_noffset < 0) {
+ printf("Can't find images parent node '%s' (%s)\n",
+ FIT_IMAGES_PATH, fdt_strerror(images_noffset));
+ return;
+ }
+
+ /* Process its subnodes, print out component images details */
+ for (ndepth = 0, count = 0,
+ noffset = fdt_next_node(fit, images_noffset, &ndepth);
+ (noffset >= 0) && (ndepth > 0);
+ noffset = fdt_next_node(fit, noffset, &ndepth)) {
+ if (ndepth == 1) {
+ /*
+ * Direct child node of the images parent node,
+ * i.e. component image node.
+ */
+ printf("%s Image %u (%s)\n", p, count++,
+ fit_get_name(fit, noffset, NULL));
+
+ fit_image_print(fit, noffset, p);
+ }
+ }
+
+ /* Find configurations parent node offset */
+ confs_noffset = fdt_path_offset(fit, FIT_CONFS_PATH);
+ if (confs_noffset < 0) {
+ debug("Can't get configurations parent node '%s' (%s)\n",
+ FIT_CONFS_PATH, fdt_strerror(confs_noffset));
+ return;
+ }
+
+ /* get default configuration unit name from default property */
+ uname = (char *)fdt_getprop(fit, noffset, FIT_DEFAULT_PROP, NULL);
+ if (uname)
+ printf("%s Default Configuration: '%s'\n", p, uname);
+
+ /* Process its subnodes, print out configurations details */
+ for (ndepth = 0, count = 0,
+ noffset = fdt_next_node(fit, confs_noffset, &ndepth);
+ (noffset >= 0) && (ndepth > 0);
+ noffset = fdt_next_node(fit, noffset, &ndepth)) {
+ if (ndepth == 1) {
+ /*
+ * Direct child node of the configurations parent node,
+ * i.e. configuration node.
+ */
+ printf("%s Configuration %u (%s)\n", p, count++,
+ fit_get_name(fit, noffset, NULL));
+
+ fit_conf_print(fit, noffset, p);
+ }
+ }
+}
+
+/**
+ * fit_image_print - prints out the FIT component image details
+ * @fit: pointer to the FIT format image header
+ * @image_noffset: offset of the component image node
+ * @p: pointer to prefix string
+ *
+ * fit_image_print() lists all mandatory properties for the processed component
+ * image. If present, hash nodes are printed out as well. Load
+ * address for images of type firmware is also printed out. Since the load
+ * address is not mandatory for firmware images, it will be output as
+ * "unavailable" when not present.
+ *
+ * returns:
+ * no returned results
+ */
+void fit_image_print(const void *fit, int image_noffset, const char *p)
+{
+ char *desc;
+ uint8_t type, arch, os, comp = IH_COMP_NONE;
+ size_t size;
+ ulong load, entry;
+ const void *data;
+ int noffset;
+ int ndepth;
+ int ret;
+
+ if (!CONFIG_IS_ENABLED(FIT_PRINT))
+ return;
+
+ /* Mandatory properties */
+ ret = fit_get_desc(fit, image_noffset, &desc);
+ printf("%s Description: ", p);
+ if (ret)
+ printf("unavailable\n");
+ else
+ printf("%s\n", desc);
+
+ if (IMAGE_ENABLE_TIMESTAMP) {
+ time_t timestamp;
+
+ ret = fit_get_timestamp(fit, 0, &timestamp);
+ printf("%s Created: ", p);
+ if (ret)
+ printf("unavailable\n");
+ else
+ genimg_print_time(timestamp);
+ }
+
+ fit_image_get_type(fit, image_noffset, &type);
+ printf("%s Type: %s\n", p, genimg_get_type_name(type));
+
+ fit_image_get_comp(fit, image_noffset, &comp);
+ printf("%s Compression: %s\n", p, genimg_get_comp_name(comp));
+
+ ret = fit_image_get_data(fit, image_noffset, &data, &size);
+
+ if (!tools_build()) {
+ printf("%s Data Start: ", p);
+ if (ret) {
+ printf("unavailable\n");
+ } else {
+ void *vdata = (void *)data;
+
+ printf("0x%08lx\n", (ulong)map_to_sysmem(vdata));
+ }
+ }
+
+ printf("%s Data Size: ", p);
+ if (ret)
+ printf("unavailable\n");
+ else
+ genimg_print_size(size);
+
+ /* Remaining, type dependent properties */
+ if ((type == IH_TYPE_KERNEL) || (type == IH_TYPE_STANDALONE) ||
+ (type == IH_TYPE_RAMDISK) || (type == IH_TYPE_FIRMWARE) ||
+ (type == IH_TYPE_FLATDT)) {
+ fit_image_get_arch(fit, image_noffset, &arch);
+ printf("%s Architecture: %s\n", p, genimg_get_arch_name(arch));
+ }
+
+ if ((type == IH_TYPE_KERNEL) || (type == IH_TYPE_RAMDISK) ||
+ (type == IH_TYPE_FIRMWARE)) {
+ fit_image_get_os(fit, image_noffset, &os);
+ printf("%s OS: %s\n", p, genimg_get_os_name(os));
+ }
+
+ if ((type == IH_TYPE_KERNEL) || (type == IH_TYPE_STANDALONE) ||
+ (type == IH_TYPE_FIRMWARE) || (type == IH_TYPE_RAMDISK) ||
+ (type == IH_TYPE_FPGA)) {
+ ret = fit_image_get_load(fit, image_noffset, &load);
+ printf("%s Load Address: ", p);
+ if (ret)
+ printf("unavailable\n");
+ else
+ printf("0x%08lx\n", load);
+ }
+
+ /* optional load address for FDT */
+ if (type == IH_TYPE_FLATDT && !fit_image_get_load(fit, image_noffset, &load))
+ printf("%s Load Address: 0x%08lx\n", p, load);
+
+ if ((type == IH_TYPE_KERNEL) || (type == IH_TYPE_STANDALONE) ||
+ (type == IH_TYPE_RAMDISK)) {
+ ret = fit_image_get_entry(fit, image_noffset, &entry);
+ printf("%s Entry Point: ", p);
+ if (ret)
+ printf("unavailable\n");
+ else
+ printf("0x%08lx\n", entry);
+ }
+
+ /* Process all hash subnodes of the component image node */
+ for (ndepth = 0, noffset = fdt_next_node(fit, image_noffset, &ndepth);
+ (noffset >= 0) && (ndepth > 0);
+ noffset = fdt_next_node(fit, noffset, &ndepth)) {
+ if (ndepth == 1) {
+ /* Direct child node of the component image node */
+ fit_image_print_verification_data(fit, noffset, p);
+ }
+ }
+}
+
+/**
+ * fit_get_desc - get node description property
+ * @fit: pointer to the FIT format image header
+ * @noffset: node offset
+ * @desc: double pointer to the char, will hold pointer to the description
+ *
+ * fit_get_desc() reads description property from a given node, if
+ * description is found pointer to it is returned in third call argument.
+ *
+ * returns:
+ * 0, on success
+ * -1, on failure
+ */
+int fit_get_desc(const void *fit, int noffset, char **desc)
+{
+ int len;
+
+ *desc = (char *)fdt_getprop(fit, noffset, FIT_DESC_PROP, &len);
+ if (*desc == NULL) {
+ fit_get_debug(fit, noffset, FIT_DESC_PROP, len);
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * fit_get_timestamp - get node timestamp property
+ * @fit: pointer to the FIT format image header
+ * @noffset: node offset
+ * @timestamp: pointer to the time_t, will hold read timestamp
+ *
+ * fit_get_timestamp() reads timestamp property from given node, if timestamp
+ * is found and has a correct size its value is returned in third call
+ * argument.
+ *
+ * returns:
+ * 0, on success
+ * -1, on property read failure
+ * -2, on wrong timestamp size
+ */
+int fit_get_timestamp(const void *fit, int noffset, time_t *timestamp)
+{
+ int len;
+ const void *data;
+
+ data = fdt_getprop(fit, noffset, FIT_TIMESTAMP_PROP, &len);
+ if (data == NULL) {
+ fit_get_debug(fit, noffset, FIT_TIMESTAMP_PROP, len);
+ return -1;
+ }
+ if (len != sizeof(uint32_t)) {
+ debug("FIT timestamp with incorrect size of (%u)\n", len);
+ return -2;
+ }
+
+ *timestamp = uimage_to_cpu(*((uint32_t *)data));
+ return 0;
+}
+
+/**
+ * fit_image_get_node - get node offset for component image of a given unit name
+ * @fit: pointer to the FIT format image header
+ * @image_uname: component image node unit name
+ *
+ * fit_image_get_node() finds a component image (within the '/images'
+ * node) of a provided unit name. If image is found its node offset is
+ * returned to the caller.
+ *
+ * returns:
+ * image node offset when found (>=0)
+ * negative number on failure (FDT_ERR_* code)
+ */
+int fit_image_get_node(const void *fit, const char *image_uname)
+{
+ int noffset, images_noffset;
+
+ images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH);
+ if (images_noffset < 0) {
+ debug("Can't find images parent node '%s' (%s)\n",
+ FIT_IMAGES_PATH, fdt_strerror(images_noffset));
+ return images_noffset;
+ }
+
+ noffset = fdt_subnode_offset(fit, images_noffset, image_uname);
+ if (noffset < 0) {
+ debug("Can't get node offset for image unit name: '%s' (%s)\n",
+ image_uname, fdt_strerror(noffset));
+ }
+
+ return noffset;
+}
+
+/**
+ * fit_image_get_os - get os id for a given component image node
+ * @fit: pointer to the FIT format image header
+ * @noffset: component image node offset
+ * @os: pointer to the uint8_t, will hold os numeric id
+ *
+ * fit_image_get_os() finds os property in a given component image node.
+ * If the property is found, its (string) value is translated to the numeric
+ * id which is returned to the caller.
+ *
+ * returns:
+ * 0, on success
+ * -1, on failure
+ */
+int fit_image_get_os(const void *fit, int noffset, uint8_t *os)
+{
+ int len;
+ const void *data;
+
+ /* Get OS name from property data */
+ data = fdt_getprop(fit, noffset, FIT_OS_PROP, &len);
+ if (data == NULL) {
+ fit_get_debug(fit, noffset, FIT_OS_PROP, len);
+ *os = -1;
+ return -1;
+ }
+
+ /* Translate OS name to id */
+ *os = genimg_get_os_id(data);
+ return 0;
+}
+
+/**
+ * fit_image_get_arch - get arch id for a given component image node
+ * @fit: pointer to the FIT format image header
+ * @noffset: component image node offset
+ * @arch: pointer to the uint8_t, will hold arch numeric id
+ *
+ * fit_image_get_arch() finds arch property in a given component image node.
+ * If the property is found, its (string) value is translated to the numeric
+ * id which is returned to the caller.
+ *
+ * returns:
+ * 0, on success
+ * -1, on failure
+ */
+int fit_image_get_arch(const void *fit, int noffset, uint8_t *arch)
+{
+ int len;
+ const void *data;
+
+ /* Get architecture name from property data */
+ data = fdt_getprop(fit, noffset, FIT_ARCH_PROP, &len);
+ if (data == NULL) {
+ fit_get_debug(fit, noffset, FIT_ARCH_PROP, len);
+ *arch = -1;
+ return -1;
+ }
+
+ /* Translate architecture name to id */
+ *arch = genimg_get_arch_id(data);
+ return 0;
+}
+
+/**
+ * fit_image_get_type - get type id for a given component image node
+ * @fit: pointer to the FIT format image header
+ * @noffset: component image node offset
+ * @type: pointer to the uint8_t, will hold type numeric id
+ *
+ * fit_image_get_type() finds type property in a given component image node.
+ * If the property is found, its (string) value is translated to the numeric
+ * id which is returned to the caller.
+ *
+ * returns:
+ * 0, on success
+ * -1, on failure
+ */
+int fit_image_get_type(const void *fit, int noffset, uint8_t *type)
+{
+ int len;
+ const void *data;
+
+ /* Get image type name from property data */
+ data = fdt_getprop(fit, noffset, FIT_TYPE_PROP, &len);
+ if (data == NULL) {
+ fit_get_debug(fit, noffset, FIT_TYPE_PROP, len);
+ *type = -1;
+ return -1;
+ }
+
+ /* Translate image type name to id */
+ *type = genimg_get_type_id(data);
+ return 0;
+}
+
+/**
+ * fit_image_get_comp - get comp id for a given component image node
+ * @fit: pointer to the FIT format image header
+ * @noffset: component image node offset
+ * @comp: pointer to the uint8_t, will hold comp numeric id
+ *
+ * fit_image_get_comp() finds comp property in a given component image node.
+ * If the property is found, its (string) value is translated to the numeric
+ * id which is returned to the caller.
+ *
+ * returns:
+ * 0, on success
+ * -1, on failure
+ */
+int fit_image_get_comp(const void *fit, int noffset, uint8_t *comp)
+{
+ int len;
+ const void *data;
+
+ /* Get compression name from property data */
+ data = fdt_getprop(fit, noffset, FIT_COMP_PROP, &len);
+ if (data == NULL) {
+ fit_get_debug(fit, noffset, FIT_COMP_PROP, len);
+ return -1;
+ }
+
+ /* Translate compression name to id */
+ *comp = genimg_get_comp_id(data);
+ return 0;
+}
+
+/**
+ * fit_image_get_phase() - get the phase for a configuration node
+ * @fit: pointer to the FIT format image header
+ * @offset: configuration-node offset
+ * @phasep: returns the phase
+ *
+ * Finds the phase property in a given configuration node. If the property is
+ * found, its (string) value is translated to the numeric id which is returned
+ * to the caller.
+ *
+ * Returns: 0 on success, -ENOENT if missing, -EINVAL for invalid value
+ */
+int fit_image_get_phase(const void *fit, int offset, enum image_phase_t *phasep)
+{
+ const void *data;
+ int len, ret;
+
+ /* Get phase name from property data */
+ data = fdt_getprop(fit, offset, FIT_PHASE_PROP, &len);
+ if (!data) {
+ fit_get_debug(fit, offset, FIT_PHASE_PROP, len);
+ *phasep = 0;
+ return -ENOENT;
+ }
+
+ /* Translate phase name to id */
+ ret = genimg_get_phase_id(data);
+ if (ret < 0)
+ return ret;
+ *phasep = ret;
+
+ return 0;
+}
+
+static int fit_image_get_address(const void *fit, int noffset, char *name,
+ ulong *load)
+{
+ int len, cell_len;
+ const fdt32_t *cell;
+ uint64_t load64 = 0;
+
+ cell = fdt_getprop(fit, noffset, name, &len);
+ if (cell == NULL) {
+ fit_get_debug(fit, noffset, name, len);
+ return -1;
+ }
+
+ cell_len = len >> 2;
+ /* Use load64 to avoid compiling warning for 32-bit target */
+ while (cell_len--) {
+ load64 = (load64 << 32) | uimage_to_cpu(*cell);
+ cell++;
+ }
+
+ if (len > sizeof(ulong) && (uint32_t)(load64 >> 32)) {
+ printf("Unsupported %s address size\n", name);
+ return -1;
+ }
+
+ *load = (ulong)load64;
+
+ return 0;
+}
+/**
+ * fit_image_get_load() - get load addr property for given component image node
+ * @fit: pointer to the FIT format image header
+ * @noffset: component image node offset
+ * @load: pointer to the uint32_t, will hold load address
+ *
+ * fit_image_get_load() finds load address property in a given component
+ * image node. If the property is found, its value is returned to the caller.
+ *
+ * returns:
+ * 0, on success
+ * -1, on failure
+ */
+int fit_image_get_load(const void *fit, int noffset, ulong *load)
+{
+ return fit_image_get_address(fit, noffset, FIT_LOAD_PROP, load);
+}
+
+/**
+ * fit_image_get_entry() - get entry point address property
+ * @fit: pointer to the FIT format image header
+ * @noffset: component image node offset
+ * @entry: pointer to the uint32_t, will hold entry point address
+ *
+ * This gets the entry point address property for a given component image
+ * node.
+ *
+ * fit_image_get_entry() finds entry point address property in a given
+ * component image node. If the property is found, its value is returned
+ * to the caller.
+ *
+ * returns:
+ * 0, on success
+ * -1, on failure
+ */
+int fit_image_get_entry(const void *fit, int noffset, ulong *entry)
+{
+ return fit_image_get_address(fit, noffset, FIT_ENTRY_PROP, entry);
+}
+
+/**
+ * fit_image_get_emb_data - get data property and its size for a given component image node
+ * @fit: pointer to the FIT format image header
+ * @noffset: component image node offset
+ * @data: double pointer to void, will hold data property's data address
+ * @size: pointer to size_t, will hold data property's data size
+ *
+ * fit_image_get_emb_data() finds data property in a given component image node.
+ * If the property is found its data start address and size are returned to
+ * the caller.
+ *
+ * returns:
+ * 0, on success
+ * -1, on failure
+ */
+int fit_image_get_emb_data(const void *fit, int noffset, const void **data,
+ size_t *size)
+{
+ int len;
+
+ *data = fdt_getprop(fit, noffset, FIT_DATA_PROP, &len);
+ if (*data == NULL) {
+ fit_get_debug(fit, noffset, FIT_DATA_PROP, len);
+ *size = 0;
+ return -1;
+ }
+
+ *size = len;
+ return 0;
+}
+
+/**
+ * Get 'data-offset' property from a given image node.
+ *
+ * @fit: pointer to the FIT image header
+ * @noffset: component image node offset
+ * @data_offset: holds the data-offset property
+ *
+ * returns:
+ * 0, on success
+ * -ENOENT if the property could not be found
+ */
+int fit_image_get_data_offset(const void *fit, int noffset, int *data_offset)
+{
+ const fdt32_t *val;
+
+ val = fdt_getprop(fit, noffset, FIT_DATA_OFFSET_PROP, NULL);
+ if (!val)
+ return -ENOENT;
+
+ *data_offset = fdt32_to_cpu(*val);
+
+ return 0;
+}
+
+/**
+ * Get 'data-position' property from a given image node.
+ *
+ * @fit: pointer to the FIT image header
+ * @noffset: component image node offset
+ * @data_position: holds the data-position property
+ *
+ * returns:
+ * 0, on success
+ * -ENOENT if the property could not be found
+ */
+int fit_image_get_data_position(const void *fit, int noffset,
+ int *data_position)
+{
+ const fdt32_t *val;
+
+ val = fdt_getprop(fit, noffset, FIT_DATA_POSITION_PROP, NULL);
+ if (!val)
+ return -ENOENT;
+
+ *data_position = fdt32_to_cpu(*val);
+
+ return 0;
+}
+
+/**
+ * Get 'data-size' property from a given image node.
+ *
+ * @fit: pointer to the FIT image header
+ * @noffset: component image node offset
+ * @data_size: holds the data-size property
+ *
+ * returns:
+ * 0, on success
+ * -ENOENT if the property could not be found
+ */
+int fit_image_get_data_size(const void *fit, int noffset, int *data_size)
+{
+ const fdt32_t *val;
+
+ val = fdt_getprop(fit, noffset, FIT_DATA_SIZE_PROP, NULL);
+ if (!val)
+ return -ENOENT;
+
+ *data_size = fdt32_to_cpu(*val);
+
+ return 0;
+}
+
+/**
+ * Get 'data-size-unciphered' property from a given image node.
+ *
+ * @fit: pointer to the FIT image header
+ * @noffset: component image node offset
+ * @data_size: holds the data-size property
+ *
+ * returns:
+ * 0, on success
+ * -ENOENT if the property could not be found
+ */
+int fit_image_get_data_size_unciphered(const void *fit, int noffset,
+ size_t *data_size)
+{
+ const fdt32_t *val;
+
+ val = fdt_getprop(fit, noffset, "data-size-unciphered", NULL);
+ if (!val)
+ return -ENOENT;
+
+ *data_size = (size_t)fdt32_to_cpu(*val);
+
+ return 0;
+}
+
+/**
+ * fit_image_get_data - get data and its size including
+ * both embedded and external data
+ * @fit: pointer to the FIT format image header
+ * @noffset: component image node offset
+ * @data: double pointer to void, will hold data property's data address
+ * @size: pointer to size_t, will hold data property's data size
+ *
+ * fit_image_get_data() finds data and its size including
+ * both embedded and external data. If the property is found
+ * its data start address and size are returned to the caller.
+ *
+ * returns:
+ * 0, on success
+ * otherwise, on failure
+ */
+int fit_image_get_data(const void *fit, int noffset, const void **data,
+ size_t *size)
+{
+ bool external_data = false;
+ int offset;
+ int len;
+ int ret;
+
+ if (!fit_image_get_data_position(fit, noffset, &offset)) {
+ external_data = true;
+ } else if (!fit_image_get_data_offset(fit, noffset, &offset)) {
+ external_data = true;
+ /*
+ * For FIT with external data, figure out where
+ * the external images start. This is the base
+ * for the data-offset properties in each image.
+ */
+ offset += ((fdt_totalsize(fit) + 3) & ~3);
+ }
+
+ if (external_data) {
+ debug("External Data\n");
+ ret = fit_image_get_data_size(fit, noffset, &len);
+ if (!ret) {
+ *data = fit + offset;
+ *size = len;
+ }
+ } else {
+ ret = fit_image_get_emb_data(fit, noffset, data, size);
+ }
+
+ return ret;
+}
+
+/**
+ * fit_image_hash_get_algo - get hash algorithm name
+ * @fit: pointer to the FIT format image header
+ * @noffset: hash node offset
+ * @algo: double pointer to char, will hold pointer to the algorithm name
+ *
+ * fit_image_hash_get_algo() finds hash algorithm property in a given hash node.
+ * If the property is found its data start address is returned to the caller.
+ *
+ * returns:
+ * 0, on success
+ * -1, on failure
+ */
+int fit_image_hash_get_algo(const void *fit, int noffset, const char **algo)
+{
+ int len;
+
+ *algo = (const char *)fdt_getprop(fit, noffset, FIT_ALGO_PROP, &len);
+ if (*algo == NULL) {
+ fit_get_debug(fit, noffset, FIT_ALGO_PROP, len);
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * fit_image_hash_get_value - get hash value and length
+ * @fit: pointer to the FIT format image header
+ * @noffset: hash node offset
+ * @value: double pointer to uint8_t, will hold address of a hash value data
+ * @value_len: pointer to an int, will hold hash data length
+ *
+ * fit_image_hash_get_value() finds hash value property in a given hash node.
+ * If the property is found its data start address and size are returned to
+ * the caller.
+ *
+ * returns:
+ * 0, on success
+ * -1, on failure
+ */
+int fit_image_hash_get_value(const void *fit, int noffset, uint8_t **value,
+ int *value_len)
+{
+ int len;
+
+ *value = (uint8_t *)fdt_getprop(fit, noffset, FIT_VALUE_PROP, &len);
+ if (*value == NULL) {
+ fit_get_debug(fit, noffset, FIT_VALUE_PROP, len);
+ *value_len = 0;
+ return -1;
+ }
+
+ *value_len = len;
+ return 0;
+}
+
+/**
+ * fit_image_hash_get_ignore - get hash ignore flag
+ * @fit: pointer to the FIT format image header
+ * @noffset: hash node offset
+ * @ignore: pointer to an int, will hold hash ignore flag
+ *
+ * fit_image_hash_get_ignore() finds hash ignore property in a given hash node.
+ * If the property is found and non-zero, the hash algorithm is not verified by
+ * u-boot automatically.
+ *
+ * returns:
+ * 0, on ignore not found
+ * value, on ignore found
+ */
+static int fit_image_hash_get_ignore(const void *fit, int noffset, int *ignore)
+{
+ int len;
+ int *value;
+
+ value = (int *)fdt_getprop(fit, noffset, FIT_IGNORE_PROP, &len);
+ if (value == NULL || len != sizeof(int))
+ *ignore = 0;
+ else
+ *ignore = *value;
+
+ return 0;
+}
+
+/**
+ * fit_image_cipher_get_algo - get cipher algorithm name
+ * @fit: pointer to the FIT format image header
+ * @noffset: cipher node offset
+ * @algo: double pointer to char, will hold pointer to the algorithm name
+ *
+ * fit_image_cipher_get_algo() finds cipher algorithm property in a given
+ * cipher node. If the property is found its data start address is returned
+ * to the caller.
+ *
+ * returns:
+ * 0, on success
+ * -1, on failure
+ */
+int fit_image_cipher_get_algo(const void *fit, int noffset, char **algo)
+{
+ int len;
+
+ *algo = (char *)fdt_getprop(fit, noffset, FIT_ALGO_PROP, &len);
+ if (!*algo) {
+ fit_get_debug(fit, noffset, FIT_ALGO_PROP, len);
+ return -1;
+ }
+
+ return 0;
+}
+
+ulong fit_get_end(const void *fit)
+{
+ return map_to_sysmem((void *)(fit + fdt_totalsize(fit)));
+}
+
+/**
+ * fit_set_timestamp - set node timestamp property
+ * @fit: pointer to the FIT format image header
+ * @noffset: node offset
+ * @timestamp: timestamp value to be set
+ *
+ * fit_set_timestamp() attempts to set timestamp property in the requested
+ * node and returns operation status to the caller.
+ *
+ * returns:
+ * 0, on success
+ * -ENOSPC if no space in device tree, -1 for other error
+ */
+int fit_set_timestamp(void *fit, int noffset, time_t timestamp)
+{
+ uint32_t t;
+ int ret;
+
+ t = cpu_to_uimage(timestamp);
+ ret = fdt_setprop(fit, noffset, FIT_TIMESTAMP_PROP, &t,
+ sizeof(uint32_t));
+ if (ret) {
+ debug("Can't set '%s' property for '%s' node (%s)\n",
+ FIT_TIMESTAMP_PROP, fit_get_name(fit, noffset, NULL),
+ fdt_strerror(ret));
+ return ret == -FDT_ERR_NOSPACE ? -ENOSPC : -1;
+ }
+
+ return 0;
+}
+
+/**
+ * calculate_hash - calculate and return hash for provided input data
+ * @data: pointer to the input data
+ * @data_len: data length
+ * @name: requested hash algorithm name
+ * @value: pointer to the char, will hold hash value data (caller must
+ * allocate enough free space)
+ * value_len: length of the calculated hash
+ *
+ * calculate_hash() computes input data hash according to the requested
+ * algorithm.
+ * Resulting hash value is placed in caller provided 'value' buffer, length
+ * of the calculated hash is returned via value_len pointer argument.
+ *
+ * returns:
+ * 0, on success
+ * -1, when algo is unsupported
+ */
+int calculate_hash(const void *data, int data_len, const char *name,
+ uint8_t *value, int *value_len)
+{
+#if !defined(USE_HOSTCC) && defined(CONFIG_DM_HASH)
+ int rc;
+ enum HASH_ALGO hash_algo;
+ struct udevice *dev;
+
+ rc = uclass_get_device(UCLASS_HASH, 0, &dev);
+ if (rc) {
+ debug("failed to get hash device, rc=%d\n", rc);
+ return -1;
+ }
+
+ hash_algo = hash_algo_lookup_by_name(name);
+ if (hash_algo == HASH_ALGO_INVALID) {
+ debug("Unsupported hash algorithm\n");
+ return -1;
+ };
+
+ rc = hash_digest_wd(dev, hash_algo, data, data_len, value, CHUNKSZ);
+ if (rc) {
+ debug("failed to get hash value, rc=%d\n", rc);
+ return -1;
+ }
+
+ *value_len = hash_algo_digest_size(hash_algo);
+#else
+ struct hash_algo *algo;
+ int ret;
+
+ ret = hash_lookup_algo(name, &algo);
+ if (ret < 0) {
+ debug("Unsupported hash alogrithm\n");
+ return -1;
+ }
+
+ algo->hash_func_ws(data, data_len, value, algo->chunk_size);
+ *value_len = algo->digest_size;
+#endif
+
+ return 0;
+}
+
+static int fit_image_check_hash(const void *fit, int noffset, const void *data,
+ size_t size, char **err_msgp)
+{
+ ALLOC_CACHE_ALIGN_BUFFER(uint8_t, value, FIT_MAX_HASH_LEN);
+ int value_len;
+ const char *algo;
+ uint8_t *fit_value;
+ int fit_value_len;
+ int ignore;
+
+ *err_msgp = NULL;
+
+ if (fit_image_hash_get_algo(fit, noffset, &algo)) {
+ *err_msgp = "Can't get hash algo property";
+ return -1;
+ }
+ printf("%s", algo);
+
+ if (!tools_build()) {
+ fit_image_hash_get_ignore(fit, noffset, &ignore);
+ if (ignore) {
+ printf("-skipped ");
+ return 0;
+ }
+ }
+
+ if (fit_image_hash_get_value(fit, noffset, &fit_value,
+ &fit_value_len)) {
+ *err_msgp = "Can't get hash value property";
+ return -1;
+ }
+
+ if (calculate_hash(data, size, algo, value, &value_len)) {
+ *err_msgp = "Unsupported hash algorithm";
+ return -1;
+ }
+
+ if (value_len != fit_value_len) {
+ *err_msgp = "Bad hash value len";
+ return -1;
+ } else if (memcmp(value, fit_value, value_len) != 0) {
+ *err_msgp = "Bad hash value";
+ return -1;
+ }
+
+ return 0;
+}
+
+int fit_image_verify_with_data(const void *fit, int image_noffset,
+ const void *key_blob, const void *data,
+ size_t size)
+{
+ int noffset = 0;
+ char *err_msg = "";
+ int verify_all = 1;
+ int ret;
+
+ /* Verify all required signatures */
+ if (FIT_IMAGE_ENABLE_VERIFY &&
+ fit_image_verify_required_sigs(fit, image_noffset, data, size,
+ key_blob, &verify_all)) {
+ err_msg = "Unable to verify required signature";
+ goto error;
+ }
+
+ /* Process all hash subnodes of the component image node */
+ fdt_for_each_subnode(noffset, fit, image_noffset) {
+ const char *name = fit_get_name(fit, noffset, NULL);
+
+ /*
+ * Check subnode name, must be equal to "hash".
+ * Multiple hash nodes require unique unit node
+ * names, e.g. hash-1, hash-2, etc.
+ */
+ if (!strncmp(name, FIT_HASH_NODENAME,
+ strlen(FIT_HASH_NODENAME))) {
+ if (fit_image_check_hash(fit, noffset, data, size,
+ &err_msg))
+ goto error;
+ puts("+ ");
+ } else if (FIT_IMAGE_ENABLE_VERIFY && verify_all &&
+ !strncmp(name, FIT_SIG_NODENAME,
+ strlen(FIT_SIG_NODENAME))) {
+ ret = fit_image_check_sig(fit, noffset, data, size,
+ gd_fdt_blob(), -1, &err_msg);
+
+ /*
+ * Show an indication on failure, but do not return
+ * an error. Only keys marked 'required' can cause
+ * an image validation failure. See the call to
+ * fit_image_verify_required_sigs() above.
+ */
+ if (ret)
+ puts("- ");
+ else
+ puts("+ ");
+ }
+ }
+
+ if (noffset == -FDT_ERR_TRUNCATED || noffset == -FDT_ERR_BADSTRUCTURE) {
+ err_msg = "Corrupted or truncated tree";
+ goto error;
+ }
+
+ return 1;
+
+error:
+ printf(" error!\n%s for '%s' hash node in '%s' image node\n",
+ err_msg, fit_get_name(fit, noffset, NULL),
+ fit_get_name(fit, image_noffset, NULL));
+ return 0;
+}
+
+/**
+ * fit_image_verify - verify data integrity
+ * @fit: pointer to the FIT format image header
+ * @image_noffset: component image node offset
+ *
+ * fit_image_verify() goes over component image hash nodes,
+ * re-calculates each data hash and compares with the value stored in hash
+ * node.
+ *
+ * returns:
+ * 1, if all hashes are valid
+ * 0, otherwise (or on error)
+ */
+int fit_image_verify(const void *fit, int image_noffset)
+{
+ const char *name = fit_get_name(fit, image_noffset, NULL);
+ const void *data;
+ size_t size;
+ char *err_msg = "";
+
+ if (IS_ENABLED(CONFIG_FIT_SIGNATURE) && strchr(name, '@')) {
+ /*
+ * We don't support this since libfdt considers names with the
+ * name root but different @ suffix to be equal
+ */
+ err_msg = "Node name contains @";
+ goto err;
+ }
+ /* Get image data and data length */
+ if (fit_image_get_data(fit, image_noffset, &data, &size)) {
+ err_msg = "Can't get image data/size";
+ goto err;
+ }
+
+ return fit_image_verify_with_data(fit, image_noffset, gd_fdt_blob(),
+ data, size);
+
+err:
+ printf("error!\n%s in '%s' image node\n", err_msg,
+ fit_get_name(fit, image_noffset, NULL));
+ return 0;
+}
+
+/**
+ * fit_all_image_verify - verify data integrity for all images
+ * @fit: pointer to the FIT format image header
+ *
+ * fit_all_image_verify() goes over all images in the FIT and
+ * for every images checks if all it's hashes are valid.
+ *
+ * returns:
+ * 1, if all hashes of all images are valid
+ * 0, otherwise (or on error)
+ */
+int fit_all_image_verify(const void *fit)
+{
+ int images_noffset;
+ int noffset;
+ int ndepth;
+ int count;
+
+ /* Find images parent node offset */
+ images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH);
+ if (images_noffset < 0) {
+ printf("Can't find images parent node '%s' (%s)\n",
+ FIT_IMAGES_PATH, fdt_strerror(images_noffset));
+ return 0;
+ }
+
+ /* Process all image subnodes, check hashes for each */
+ printf("## Checking hash(es) for FIT Image at %08lx ...\n",
+ (ulong)fit);
+ for (ndepth = 0, count = 0,
+ noffset = fdt_next_node(fit, images_noffset, &ndepth);
+ (noffset >= 0) && (ndepth > 0);
+ noffset = fdt_next_node(fit, noffset, &ndepth)) {
+ if (ndepth == 1) {
+ /*
+ * Direct child node of the images parent node,
+ * i.e. component image node.
+ */
+ printf(" Hash(es) for Image %u (%s): ", count,
+ fit_get_name(fit, noffset, NULL));
+ count++;
+
+ if (!fit_image_verify(fit, noffset))
+ return 0;
+ printf("\n");
+ }
+ }
+ return 1;
+}
+
+static int fit_image_uncipher(const void *fit, int image_noffset,
+ void **data, size_t *size)
+{
+ int cipher_noffset, ret;
+ void *dst;
+ size_t size_dst;
+
+ cipher_noffset = fdt_subnode_offset(fit, image_noffset,
+ FIT_CIPHER_NODENAME);
+ if (cipher_noffset < 0)
+ return 0;
+
+ ret = fit_image_decrypt_data(fit, image_noffset, cipher_noffset,
+ *data, *size, &dst, &size_dst);
+ if (ret)
+ goto out;
+
+ *data = dst;
+ *size = size_dst;
+
+ out:
+ return ret;
+}
+
+/**
+ * fit_image_check_os - check whether image node is of a given os type
+ * @fit: pointer to the FIT format image header
+ * @noffset: component image node offset
+ * @os: requested image os
+ *
+ * fit_image_check_os() reads image os property and compares its numeric
+ * id with the requested os. Comparison result is returned to the caller.
+ *
+ * returns:
+ * 1 if image is of given os type
+ * 0 otherwise (or on error)
+ */
+int fit_image_check_os(const void *fit, int noffset, uint8_t os)
+{
+ uint8_t image_os;
+
+ if (fit_image_get_os(fit, noffset, &image_os))
+ return 0;
+ return (os == image_os);
+}
+
+/**
+ * fit_image_check_arch - check whether image node is of a given arch
+ * @fit: pointer to the FIT format image header
+ * @noffset: component image node offset
+ * @arch: requested imagearch
+ *
+ * fit_image_check_arch() reads image arch property and compares its numeric
+ * id with the requested arch. Comparison result is returned to the caller.
+ *
+ * returns:
+ * 1 if image is of given arch
+ * 0 otherwise (or on error)
+ */
+int fit_image_check_arch(const void *fit, int noffset, uint8_t arch)
+{
+ uint8_t image_arch;
+ int aarch32_support = 0;
+
+ /* Let's assume that sandbox can load any architecture */
+ if (IS_ENABLED(CONFIG_SANDBOX))
+ return true;
+
+ if (IS_ENABLED(CONFIG_ARM64_SUPPORT_AARCH32))
+ aarch32_support = 1;
+
+ if (fit_image_get_arch(fit, noffset, &image_arch))
+ return 0;
+ return (arch == image_arch) ||
+ (arch == IH_ARCH_I386 && image_arch == IH_ARCH_X86_64) ||
+ (arch == IH_ARCH_ARM64 && image_arch == IH_ARCH_ARM &&
+ aarch32_support);
+}
+
+/**
+ * fit_image_check_type - check whether image node is of a given type
+ * @fit: pointer to the FIT format image header
+ * @noffset: component image node offset
+ * @type: requested image type
+ *
+ * fit_image_check_type() reads image type property and compares its numeric
+ * id with the requested type. Comparison result is returned to the caller.
+ *
+ * returns:
+ * 1 if image is of given type
+ * 0 otherwise (or on error)
+ */
+int fit_image_check_type(const void *fit, int noffset, uint8_t type)
+{
+ uint8_t image_type;
+
+ if (fit_image_get_type(fit, noffset, &image_type))
+ return 0;
+ return (type == image_type);
+}
+
+/**
+ * fit_image_check_comp - check whether image node uses given compression
+ * @fit: pointer to the FIT format image header
+ * @noffset: component image node offset
+ * @comp: requested image compression type
+ *
+ * fit_image_check_comp() reads image compression property and compares its
+ * numeric id with the requested compression type. Comparison result is
+ * returned to the caller.
+ *
+ * returns:
+ * 1 if image uses requested compression
+ * 0 otherwise (or on error)
+ */
+int fit_image_check_comp(const void *fit, int noffset, uint8_t comp)
+{
+ uint8_t image_comp;
+
+ if (fit_image_get_comp(fit, noffset, &image_comp))
+ return 0;
+ return (comp == image_comp);
+}
+
+/**
+ * fdt_check_no_at() - Check for nodes whose names contain '@'
+ *
+ * This checks the parent node and all subnodes recursively
+ *
+ * @fit: FIT to check
+ * @parent: Parent node to check
+ * Return: 0 if OK, -EADDRNOTAVAIL is a node has a name containing '@'
+ */
+static int fdt_check_no_at(const void *fit, int parent)
+{
+ const char *name;
+ int node;
+ int ret;
+
+ name = fdt_get_name(fit, parent, NULL);
+ if (!name || strchr(name, '@'))
+ return -EADDRNOTAVAIL;
+
+ fdt_for_each_subnode(node, fit, parent) {
+ ret = fdt_check_no_at(fit, node);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int fit_check_format(const void *fit, ulong size)
+{
+ int ret;
+
+ /* A FIT image must be a valid FDT */
+ ret = fdt_check_header(fit);
+ if (ret) {
+ log_debug("Wrong FIT format: not a flattened device tree (err=%d)\n",
+ ret);
+ return -ENOEXEC;
+ }
+
+ if (CONFIG_IS_ENABLED(FIT_FULL_CHECK)) {
+ /*
+ * If we are not given the size, make do wtih calculating it.
+ * This is not as secure, so we should consider a flag to
+ * control this.
+ */
+ if (size == IMAGE_SIZE_INVAL)
+ size = fdt_totalsize(fit);
+ ret = fdt_check_full(fit, size);
+ if (ret)
+ ret = -EINVAL;
+
+ /*
+ * U-Boot stopped using unit addressed in 2017. Since libfdt
+ * can match nodes ignoring any unit address, signature
+ * verification can see the wrong node if one is inserted with
+ * the same name as a valid node but with a unit address
+ * attached. Protect against this by disallowing unit addresses.
+ */
+ if (!ret && CONFIG_IS_ENABLED(FIT_SIGNATURE)) {
+ ret = fdt_check_no_at(fit, 0);
+
+ if (ret) {
+ log_debug("FIT check error %d\n", ret);
+ return ret;
+ }
+ }
+ if (ret) {
+ log_debug("FIT check error %d\n", ret);
+ return ret;
+ }
+ }
+
+ /* mandatory / node 'description' property */
+ if (!fdt_getprop(fit, 0, FIT_DESC_PROP, NULL)) {
+ log_debug("Wrong FIT format: no description\n");
+ return -ENOMSG;
+ }
+
+ if (IMAGE_ENABLE_TIMESTAMP) {
+ /* mandatory / node 'timestamp' property */
+ if (!fdt_getprop(fit, 0, FIT_TIMESTAMP_PROP, NULL)) {
+ log_debug("Wrong FIT format: no timestamp\n");
+ return -EBADMSG;
+ }
+ }
+
+ /* mandatory subimages parent '/images' node */
+ if (fdt_path_offset(fit, FIT_IMAGES_PATH) < 0) {
+ log_debug("Wrong FIT format: no images parent node\n");
+ return -ENOENT;
+ }
+
+ return 0;
+}
+
+int fit_conf_find_compat(const void *fit, const void *fdt)
+{
+ int ndepth = 0;
+ int noffset, confs_noffset, images_noffset;
+ const void *fdt_compat;
+ int fdt_compat_len;
+ int best_match_offset = 0;
+ int best_match_pos = 0;
+
+ confs_noffset = fdt_path_offset(fit, FIT_CONFS_PATH);
+ images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH);
+ if (confs_noffset < 0 || images_noffset < 0) {
+ debug("Can't find configurations or images nodes.\n");
+ return -EINVAL;
+ }
+
+ fdt_compat = fdt_getprop(fdt, 0, "compatible", &fdt_compat_len);
+ if (!fdt_compat) {
+ debug("Fdt for comparison has no \"compatible\" property.\n");
+ return -ENXIO;
+ }
+
+ /*
+ * Loop over the configurations in the FIT image.
+ */
+ for (noffset = fdt_next_node(fit, confs_noffset, &ndepth);
+ (noffset >= 0) && (ndepth > 0);
+ noffset = fdt_next_node(fit, noffset, &ndepth)) {
+ const void *fdt;
+ const char *kfdt_name;
+ int kfdt_noffset, compat_noffset;
+ const char *cur_fdt_compat;
+ int len;
+ size_t sz;
+ int i;
+
+ if (ndepth > 1)
+ continue;
+
+ /* If there's a compat property in the config node, use that. */
+ if (fdt_getprop(fit, noffset, "compatible", NULL)) {
+ fdt = fit; /* search in FIT image */
+ compat_noffset = noffset; /* search under config node */
+ } else { /* Otherwise extract it from the kernel FDT. */
+ kfdt_name = fdt_getprop(fit, noffset, "fdt", &len);
+ if (!kfdt_name) {
+ debug("No fdt property found.\n");
+ continue;
+ }
+ kfdt_noffset = fdt_subnode_offset(fit, images_noffset,
+ kfdt_name);
+ if (kfdt_noffset < 0) {
+ debug("No image node named \"%s\" found.\n",
+ kfdt_name);
+ continue;
+ }
+
+ if (!fit_image_check_comp(fit, kfdt_noffset,
+ IH_COMP_NONE)) {
+ debug("Can't extract compat from \"%s\" "
+ "(compressed)\n", kfdt_name);
+ continue;
+ }
+
+ /* search in this config's kernel FDT */
+ if (fit_image_get_data(fit, kfdt_noffset, &fdt, &sz)) {
+ debug("Failed to get fdt \"%s\".\n", kfdt_name);
+ continue;
+ }
+
+ compat_noffset = 0; /* search kFDT under root node */
+ }
+
+ len = fdt_compat_len;
+ cur_fdt_compat = fdt_compat;
+ /*
+ * Look for a match for each U-Boot compatibility string in
+ * turn in the compat string property.
+ */
+ for (i = 0; len > 0 &&
+ (!best_match_offset || best_match_pos > i); i++) {
+ int cur_len = strlen(cur_fdt_compat) + 1;
+
+ if (!fdt_node_check_compatible(fdt, compat_noffset,
+ cur_fdt_compat)) {
+ best_match_offset = noffset;
+ best_match_pos = i;
+ break;
+ }
+ len -= cur_len;
+ cur_fdt_compat += cur_len;
+ }
+ }
+ if (!best_match_offset) {
+ debug("No match found.\n");
+ return -ENOENT;
+ }
+
+ return best_match_offset;
+}
+
+int fit_conf_get_node(const void *fit, const char *conf_uname)
+{
+ int noffset, confs_noffset;
+ int len;
+ const char *s;
+ char *conf_uname_copy = NULL;
+
+ confs_noffset = fdt_path_offset(fit, FIT_CONFS_PATH);
+ if (confs_noffset < 0) {
+ debug("Can't find configurations parent node '%s' (%s)\n",
+ FIT_CONFS_PATH, fdt_strerror(confs_noffset));
+ return confs_noffset;
+ }
+
+ if (conf_uname == NULL) {
+ /* get configuration unit name from the default property */
+ debug("No configuration specified, trying default...\n");
+ if (!tools_build() && IS_ENABLED(CONFIG_MULTI_DTB_FIT)) {
+ noffset = fit_find_config_node(fit);
+ if (noffset < 0)
+ return noffset;
+ conf_uname = fdt_get_name(fit, noffset, NULL);
+ } else {
+ conf_uname = (char *)fdt_getprop(fit, confs_noffset,
+ FIT_DEFAULT_PROP, &len);
+ if (conf_uname == NULL) {
+ fit_get_debug(fit, confs_noffset, FIT_DEFAULT_PROP,
+ len);
+ return len;
+ }
+ }
+ debug("Found default configuration: '%s'\n", conf_uname);
+ }
+
+ s = strchr(conf_uname, '#');
+ if (s) {
+ len = s - conf_uname;
+ conf_uname_copy = malloc(len + 1);
+ if (!conf_uname_copy) {
+ debug("Can't allocate uname copy: '%s'\n",
+ conf_uname);
+ return -ENOMEM;
+ }
+ memcpy(conf_uname_copy, conf_uname, len);
+ conf_uname_copy[len] = '\0';
+ conf_uname = conf_uname_copy;
+ }
+
+ noffset = fdt_subnode_offset(fit, confs_noffset, conf_uname);
+ if (noffset < 0) {
+ debug("Can't get node offset for configuration unit name: '%s' (%s)\n",
+ conf_uname, fdt_strerror(noffset));
+ }
+
+ free(conf_uname_copy);
+
+ return noffset;
+}
+
+int fit_conf_get_prop_node_count(const void *fit, int noffset,
+ const char *prop_name)
+{
+ return fdt_stringlist_count(fit, noffset, prop_name);
+}
+
+int fit_conf_get_prop_node_index(const void *fit, int noffset,
+ const char *prop_name, int index)
+{
+ const char *uname;
+ int len;
+
+ /* get kernel image unit name from configuration kernel property */
+ uname = fdt_stringlist_get(fit, noffset, prop_name, index, &len);
+ if (uname == NULL)
+ return len;
+
+ return fit_image_get_node(fit, uname);
+}
+
+int fit_conf_get_prop_node(const void *fit, int noffset, const char *prop_name,
+ enum image_phase_t sel_phase)
+{
+ int i, count;
+
+ if (sel_phase == IH_PHASE_NONE)
+ return fit_conf_get_prop_node_index(fit, noffset, prop_name, 0);
+
+ count = fit_conf_get_prop_node_count(fit, noffset, prop_name);
+ if (count < 0)
+ return count;
+ log_debug("looking for %s (%s, image-count %d):\n", prop_name,
+ genimg_get_phase_name(image_ph_phase(sel_phase)), count);
+
+ /* check each image in the list */
+ for (i = 0; i < count; i++) {
+ enum image_phase_t phase = IH_PHASE_NONE;
+ int ret, node;
+
+ node = fit_conf_get_prop_node_index(fit, noffset, prop_name, i);
+ ret = fit_image_get_phase(fit, node, &phase);
+ log_debug("- %s (%s): ", fdt_get_name(fit, node, NULL),
+ genimg_get_phase_name(phase));
+
+ /* if the image is for any phase, let's use it */
+ if (ret == -ENOENT || phase == sel_phase) {
+ log_debug("found\n");
+ return node;
+ } else if (ret < 0) {
+ log_debug("err=%d\n", ret);
+ return ret;
+ }
+ log_debug("no match\n");
+ }
+ log_debug("- not found\n");
+
+ return -ENOENT;
+}
+
+static int fit_get_data_tail(const void *fit, int noffset,
+ const void **data, size_t *size)
+{
+ char *desc;
+
+ if (noffset < 0)
+ return noffset;
+
+ if (!fit_image_verify(fit, noffset))
+ return -EINVAL;
+
+ if (fit_image_get_data(fit, noffset, data, size))
+ return -ENOENT;
+
+ if (!fit_get_desc(fit, noffset, &desc))
+ printf("%s\n", desc);
+
+ return 0;
+}
+
+int fit_get_data_node(const void *fit, const char *image_uname,
+ const void **data, size_t *size)
+{
+ int noffset = fit_image_get_node(fit, image_uname);
+
+ return fit_get_data_tail(fit, noffset, data, size);
+}
+
+int fit_get_data_conf_prop(const void *fit, const char *prop_name,
+ const void **data, size_t *size)
+{
+ int noffset = fit_conf_get_node(fit, NULL);
+
+ noffset = fit_conf_get_prop_node(fit, noffset, prop_name,
+ IH_PHASE_NONE);
+ return fit_get_data_tail(fit, noffset, data, size);
+}
+
+static int fit_image_select(const void *fit, int rd_noffset, int verify)
+{
+ fit_image_print(fit, rd_noffset, " ");
+
+ if (verify) {
+ puts(" Verifying Hash Integrity ... ");
+ if (!fit_image_verify(fit, rd_noffset)) {
+ puts("Bad Data Hash\n");
+ return -EACCES;
+ }
+ puts("OK\n");
+ }
+
+ return 0;
+}
+
+int fit_get_node_from_config(struct bootm_headers *images,
+ const char *prop_name, ulong addr)
+{
+ int cfg_noffset;
+ void *fit_hdr;
+ int noffset;
+
+ debug("* %s: using config '%s' from image at 0x%08lx\n",
+ prop_name, images->fit_uname_cfg, addr);
+
+ /* Check whether configuration has this property defined */
+ fit_hdr = map_sysmem(addr, 0);
+ cfg_noffset = fit_conf_get_node(fit_hdr, images->fit_uname_cfg);
+ if (cfg_noffset < 0) {
+ debug("* %s: no such config\n", prop_name);
+ return -EINVAL;
+ }
+
+ noffset = fit_conf_get_prop_node(fit_hdr, cfg_noffset, prop_name,
+ IH_PHASE_NONE);
+ if (noffset < 0) {
+ debug("* %s: no '%s' in config\n", prop_name, prop_name);
+ return -ENOENT;
+ }
+
+ return noffset;
+}
+
+/**
+ * fit_get_image_type_property() - get property name for sel_phase
+ *
+ * Return: the properly name where we expect to find the image in the
+ * config node
+ */
+static const char *fit_get_image_type_property(int ph_type)
+{
+ int type = image_ph_type(ph_type);
+
+ /*
+ * This is sort-of available in the uimage_type[] table in image.c
+ * but we don't have access to the short name, and "fdt" is different
+ * anyway. So let's just keep it here.
+ */
+ switch (type) {
+ case IH_TYPE_FLATDT:
+ return FIT_FDT_PROP;
+ case IH_TYPE_KERNEL:
+ return FIT_KERNEL_PROP;
+ case IH_TYPE_FIRMWARE:
+ return FIT_FIRMWARE_PROP;
+ case IH_TYPE_RAMDISK:
+ return FIT_RAMDISK_PROP;
+ case IH_TYPE_X86_SETUP:
+ return FIT_SETUP_PROP;
+ case IH_TYPE_LOADABLE:
+ return FIT_LOADABLE_PROP;
+ case IH_TYPE_FPGA:
+ return FIT_FPGA_PROP;
+ case IH_TYPE_STANDALONE:
+ return FIT_STANDALONE_PROP;
+ }
+
+ return "unknown";
+}
+
+int fit_image_load(struct bootm_headers *images, ulong addr,
+ const char **fit_unamep, const char **fit_uname_configp,
+ int arch, int ph_type, int bootstage_id,
+ enum fit_load_op load_op, ulong *datap, ulong *lenp)
+{
+ int image_type = image_ph_type(ph_type);
+ int cfg_noffset, noffset;
+ const char *fit_uname;
+ const char *fit_uname_config;
+ const char *fit_base_uname_config;
+ const void *fit;
+ void *buf;
+ void *loadbuf;
+ size_t size;
+ int type_ok, os_ok;
+ ulong load, load_end, data, len;
+ uint8_t os, comp;
+ const char *prop_name;
+ int ret;
+
+ fit = map_sysmem(addr, 0);
+ fit_uname = fit_unamep ? *fit_unamep : NULL;
+ fit_uname_config = fit_uname_configp ? *fit_uname_configp : NULL;
+ fit_base_uname_config = NULL;
+ prop_name = fit_get_image_type_property(ph_type);
+ printf("## Loading %s (%s) from FIT Image at %08lx ...\n",
+ prop_name, genimg_get_phase_name(image_ph_phase(ph_type)), addr);
+
+ bootstage_mark(bootstage_id + BOOTSTAGE_SUB_FORMAT);
+ ret = fit_check_format(fit, IMAGE_SIZE_INVAL);
+ if (ret) {
+ printf("Bad FIT %s image format! (err=%d)\n", prop_name, ret);
+ if (CONFIG_IS_ENABLED(FIT_SIGNATURE) && ret == -EADDRNOTAVAIL)
+ printf("Signature checking prevents use of unit addresses (@) in nodes\n");
+ bootstage_error(bootstage_id + BOOTSTAGE_SUB_FORMAT);
+ return ret;
+ }
+ bootstage_mark(bootstage_id + BOOTSTAGE_SUB_FORMAT_OK);
+ if (fit_uname) {
+ /* get FIT component image node offset */
+ bootstage_mark(bootstage_id + BOOTSTAGE_SUB_UNIT_NAME);
+ noffset = fit_image_get_node(fit, fit_uname);
+ } else {
+ /*
+ * no image node unit name, try to get config
+ * node first. If config unit node name is NULL
+ * fit_conf_get_node() will try to find default config node
+ */
+ bootstage_mark(bootstage_id + BOOTSTAGE_SUB_NO_UNIT_NAME);
+ ret = -ENXIO;
+ if (IS_ENABLED(CONFIG_FIT_BEST_MATCH) && !fit_uname_config)
+ ret = fit_conf_find_compat(fit, gd_fdt_blob());
+ if (ret < 0 && ret != -EINVAL)
+ ret = fit_conf_get_node(fit, fit_uname_config);
+ if (ret < 0) {
+ puts("Could not find configuration node\n");
+ bootstage_error(bootstage_id +
+ BOOTSTAGE_SUB_NO_UNIT_NAME);
+ return -ENOENT;
+ }
+ cfg_noffset = ret;
+
+ fit_base_uname_config = fdt_get_name(fit, cfg_noffset, NULL);
+ printf(" Using '%s' configuration\n", fit_base_uname_config);
+ /* Remember this config */
+ if (image_type == IH_TYPE_KERNEL)
+ images->fit_uname_cfg = fit_base_uname_config;
+
+ if (FIT_IMAGE_ENABLE_VERIFY && images->verify) {
+ puts(" Verifying Hash Integrity ... ");
+ if (fit_config_verify(fit, cfg_noffset)) {
+ puts("Bad Data Hash\n");
+ bootstage_error(bootstage_id +
+ BOOTSTAGE_SUB_HASH);
+ return -EACCES;
+ }
+ puts("OK\n");
+ }
+
+ bootstage_mark(BOOTSTAGE_ID_FIT_CONFIG);
+
+ noffset = fit_conf_get_prop_node(fit, cfg_noffset, prop_name,
+ image_ph_phase(ph_type));
+ fit_uname = fit_get_name(fit, noffset, NULL);
+ }
+ if (noffset < 0) {
+ printf("Could not find subimage node type '%s'\n", prop_name);
+ bootstage_error(bootstage_id + BOOTSTAGE_SUB_SUBNODE);
+ return -ENOENT;
+ }
+
+ printf(" Trying '%s' %s subimage\n", fit_uname, prop_name);
+
+ ret = fit_image_select(fit, noffset, images->verify);
+ if (ret) {
+ bootstage_error(bootstage_id + BOOTSTAGE_SUB_HASH);
+ return ret;
+ }
+
+ bootstage_mark(bootstage_id + BOOTSTAGE_SUB_CHECK_ARCH);
+ if (!tools_build() && IS_ENABLED(CONFIG_SANDBOX)) {
+ if (!fit_image_check_target_arch(fit, noffset)) {
+ puts("Unsupported Architecture\n");
+ bootstage_error(bootstage_id + BOOTSTAGE_SUB_CHECK_ARCH);
+ return -ENOEXEC;
+ }
+ }
+
+#ifndef USE_HOSTCC
+ {
+ uint8_t os_arch;
+
+ fit_image_get_arch(fit, noffset, &os_arch);
+ images->os.arch = os_arch;
+ }
+#endif
+
+ bootstage_mark(bootstage_id + BOOTSTAGE_SUB_CHECK_ALL);
+ type_ok = fit_image_check_type(fit, noffset, image_type) ||
+ fit_image_check_type(fit, noffset, IH_TYPE_FIRMWARE) ||
+ fit_image_check_type(fit, noffset, IH_TYPE_TEE) ||
+ fit_image_check_type(fit, noffset, IH_TYPE_TFA_BL31) ||
+ (image_type == IH_TYPE_KERNEL &&
+ fit_image_check_type(fit, noffset, IH_TYPE_KERNEL_NOLOAD));
+
+ os_ok = image_type == IH_TYPE_FLATDT ||
+ image_type == IH_TYPE_FPGA ||
+ fit_image_check_os(fit, noffset, IH_OS_LINUX) ||
+ fit_image_check_os(fit, noffset, IH_OS_U_BOOT) ||
+ fit_image_check_os(fit, noffset, IH_OS_TEE) ||
+ fit_image_check_os(fit, noffset, IH_OS_OPENRTOS) ||
+ fit_image_check_os(fit, noffset, IH_OS_EFI) ||
+ fit_image_check_os(fit, noffset, IH_OS_VXWORKS) ||
+ fit_image_check_os(fit, noffset, IH_OS_ELF);
+
+ /*
+ * If either of the checks fail, we should report an error, but
+ * if the image type is coming from the "loadables" field, we
+ * don't care what it is
+ */
+ if ((!type_ok || !os_ok) && image_type != IH_TYPE_LOADABLE) {
+ fit_image_get_os(fit, noffset, &os);
+ printf("No %s %s %s Image\n",
+ genimg_get_os_name(os),
+ genimg_get_arch_name(arch),
+ genimg_get_type_name(image_type));
+ bootstage_error(bootstage_id + BOOTSTAGE_SUB_CHECK_ALL);
+ return -EIO;
+ }
+
+ bootstage_mark(bootstage_id + BOOTSTAGE_SUB_CHECK_ALL_OK);
+
+ /* get image data address and length */
+ if (fit_image_get_data(fit, noffset, (const void **)&buf, &size)) {
+ printf("Could not find %s subimage data!\n", prop_name);
+ bootstage_error(bootstage_id + BOOTSTAGE_SUB_GET_DATA);
+ return -ENOENT;
+ }
+
+ /* Decrypt data before uncompress/move */
+ if (IS_ENABLED(CONFIG_FIT_CIPHER) && IMAGE_ENABLE_DECRYPT) {
+ puts(" Decrypting Data ... ");
+ if (fit_image_uncipher(fit, noffset, &buf, &size)) {
+ puts("Error\n");
+ return -EACCES;
+ }
+ puts("OK\n");
+ }
+
+ /* perform any post-processing on the image data */
+ if (!tools_build() && IS_ENABLED(CONFIG_FIT_IMAGE_POST_PROCESS))
+ board_fit_image_post_process(fit, noffset, &buf, &size);
+
+ len = (ulong)size;
+
+ bootstage_mark(bootstage_id + BOOTSTAGE_SUB_GET_DATA_OK);
+
+ data = map_to_sysmem(buf);
+ load = data;
+ if (load_op == FIT_LOAD_IGNORED) {
+ log_debug("load_op: not loading\n");
+ /* Don't load */
+ } else if (fit_image_get_load(fit, noffset, &load)) {
+ if (load_op == FIT_LOAD_REQUIRED) {
+ printf("Can't get %s subimage load address!\n",
+ prop_name);
+ bootstage_error(bootstage_id + BOOTSTAGE_SUB_LOAD);
+ return -EBADF;
+ }
+ } else if (load_op != FIT_LOAD_OPTIONAL_NON_ZERO || load) {
+ ulong image_start, image_end;
+
+ /*
+ * move image data to the load address,
+ * make sure we don't overwrite initial image
+ */
+ image_start = addr;
+ image_end = addr + fit_get_size(fit);
+
+ load_end = load + len;
+ if (image_type != IH_TYPE_KERNEL &&
+ load < image_end && load_end > image_start) {
+ printf("Error: %s overwritten\n", prop_name);
+ return -EXDEV;
+ }
+
+ printf(" Loading %s from 0x%08lx to 0x%08lx\n",
+ prop_name, data, load);
+ } else {
+ load = data; /* No load address specified */
+ }
+
+ comp = IH_COMP_NONE;
+ loadbuf = buf;
+ /* Kernel images get decompressed later in bootm_load_os(). */
+ if (!fit_image_get_comp(fit, noffset, &comp) &&
+ comp != IH_COMP_NONE &&
+ load_op != FIT_LOAD_IGNORED &&
+ !(image_type == IH_TYPE_KERNEL ||
+ image_type == IH_TYPE_KERNEL_NOLOAD ||
+ image_type == IH_TYPE_RAMDISK)) {
+ ulong max_decomp_len = len * 20;
+
+ log_debug("decompressing image\n");
+ if (load == data) {
+ loadbuf = malloc(max_decomp_len);
+ load = map_to_sysmem(loadbuf);
+ } else {
+ loadbuf = map_sysmem(load, max_decomp_len);
+ }
+ if (image_decomp(comp, load, data, image_type,
+ loadbuf, buf, len, max_decomp_len, &load_end)) {
+ printf("Error decompressing %s\n", prop_name);
+
+ return -ENOEXEC;
+ }
+ len = load_end - load;
+ } else if (load != data) {
+ log_debug("copying\n");
+ loadbuf = map_sysmem(load, len);
+ memcpy(loadbuf, buf, len);
+ }
+
+ if (image_type == IH_TYPE_RAMDISK && comp != IH_COMP_NONE)
+ puts("WARNING: 'compression' nodes for ramdisks are deprecated,"
+ " please fix your .its file!\n");
+
+ /* verify that image data is a proper FDT blob */
+ if (load_op != FIT_LOAD_IGNORED && image_type == IH_TYPE_FLATDT &&
+ fdt_check_header(loadbuf)) {
+ puts("Subimage data is not a FDT\n");
+ return -ENOEXEC;
+ }
+
+ bootstage_mark(bootstage_id + BOOTSTAGE_SUB_LOAD);
+
+ upl_add_image(fit, noffset, load, len);
+
+ *datap = load;
+ *lenp = len;
+ if (fit_unamep)
+ *fit_unamep = (char *)fit_uname;
+ if (fit_uname_configp)
+ *fit_uname_configp = (char *)(fit_uname_config ? :
+ fit_base_uname_config);
+
+ return noffset;
+}
+
+int boot_get_setup_fit(struct bootm_headers *images, uint8_t arch,
+ ulong *setup_start, ulong *setup_len)
+{
+ int noffset;
+ ulong addr;
+ ulong len;
+ int ret;
+
+ addr = map_to_sysmem(images->fit_hdr_os);
+ noffset = fit_get_node_from_config(images, FIT_SETUP_PROP, addr);
+ if (noffset < 0)
+ return noffset;
+
+ ret = fit_image_load(images, addr, NULL, NULL, arch,
+ IH_TYPE_X86_SETUP, BOOTSTAGE_ID_FIT_SETUP_START,
+ FIT_LOAD_REQUIRED, setup_start, &len);
+
+ return ret;
+}
+
+#ifndef USE_HOSTCC
+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 fdt_noffset, cfg_noffset, count;
+ const void *fit;
+ const char *fit_uname = NULL;
+ const char *fit_uname_config = NULL;
+ char *fit_uname_config_copy = NULL;
+ char *next_config = NULL;
+ ulong load, len;
+#ifdef CONFIG_OF_LIBFDT_OVERLAY
+ ulong ovload, ovlen, ovcopylen;
+ const char *uconfig;
+ const char *uname;
+ /*
+ * of_flat_tree is storing the void * returned by map_sysmem, then its
+ * address is passed to boot_relocate_fdt which expects a char ** and it
+ * is then cast into a ulong. Setting its type to void * would require
+ * to cast its address to char ** when passing it to boot_relocate_fdt.
+ * Instead, let's be lazy and use void *.
+ */
+ char *of_flat_tree;
+ void *base, *ov, *ovcopy = NULL;
+ int i, err, noffset, ov_noffset;
+#endif
+
+ fit_uname = fit_unamep ? *fit_unamep : NULL;
+
+ if (fit_uname_configp && *fit_uname_configp) {
+ fit_uname_config_copy = strdup(*fit_uname_configp);
+ if (!fit_uname_config_copy)
+ return -ENOMEM;
+
+ next_config = strchr(fit_uname_config_copy, '#');
+ if (next_config)
+ *next_config++ = '\0';
+ if (next_config - 1 > fit_uname_config_copy)
+ fit_uname_config = fit_uname_config_copy;
+ }
+
+ fdt_noffset = fit_image_load(images,
+ addr, &fit_uname, &fit_uname_config,
+ arch, IH_TYPE_FLATDT,
+ BOOTSTAGE_ID_FIT_FDT_START,
+ FIT_LOAD_OPTIONAL, &load, &len);
+
+ if (fdt_noffset < 0)
+ goto out;
+
+ debug("fit_uname=%s, fit_uname_config=%s\n",
+ fit_uname ? fit_uname : "<NULL>",
+ fit_uname_config ? fit_uname_config : "<NULL>");
+
+ fit = map_sysmem(addr, 0);
+
+ cfg_noffset = fit_conf_get_node(fit, fit_uname_config);
+
+ /* single blob, or error just return as well */
+ count = fit_conf_get_prop_node_count(fit, cfg_noffset, FIT_FDT_PROP);
+ if (count <= 1 && !next_config)
+ goto out;
+
+ /* we need to apply overlays */
+
+#ifdef CONFIG_OF_LIBFDT_OVERLAY
+ /* Relocate FDT so resizing does not overwrite other data in FIT. */
+ of_flat_tree = map_sysmem(load, len);
+ len = ALIGN(fdt_totalsize(load), SZ_4K);
+ err = boot_relocate_fdt(&of_flat_tree, &len);
+ if (err) {
+ printf("Required FDT relocation for applying DTOs failed: %d\n",
+ err);
+ fdt_noffset = -EBADF;
+ goto out;
+ }
+
+ load = (ulong)of_flat_tree;
+
+ /* apply extra configs in FIT first, followed by args */
+ for (i = 1; ; i++) {
+ if (i < count) {
+ noffset = fit_conf_get_prop_node_index(fit, cfg_noffset,
+ FIT_FDT_PROP, i);
+ uname = fit_get_name(fit, noffset, NULL);
+ uconfig = NULL;
+ } else {
+ if (!next_config)
+ break;
+ uconfig = next_config;
+ next_config = strchr(next_config, '#');
+ if (next_config)
+ *next_config++ = '\0';
+ uname = NULL;
+
+ /*
+ * fit_image_load() would load the first FDT from the
+ * extra config only when uconfig is specified.
+ * Check if the extra config contains multiple FDTs and
+ * if so, load them.
+ */
+ cfg_noffset = fit_conf_get_node(fit, uconfig);
+
+ i = 0;
+ count = fit_conf_get_prop_node_count(fit, cfg_noffset,
+ FIT_FDT_PROP);
+ }
+
+ debug("%d: using uname=%s uconfig=%s\n", i, uname, uconfig);
+
+ ov_noffset = fit_image_load(images,
+ addr, &uname, &uconfig,
+ arch, IH_TYPE_FLATDT,
+ BOOTSTAGE_ID_FIT_FDT_START,
+ FIT_LOAD_IGNORED, &ovload, &ovlen);
+ if (ov_noffset < 0) {
+ printf("load of %s failed\n", uname);
+ continue;
+ }
+ debug("%s loaded at 0x%08lx len=0x%08lx\n",
+ uname, ovload, ovlen);
+ ov = map_sysmem(ovload, ovlen);
+
+ ovcopylen = ALIGN(fdt_totalsize(ov), SZ_4K);
+ ovcopy = malloc(ovcopylen);
+ if (!ovcopy) {
+ printf("failed to duplicate DTO before application\n");
+ fdt_noffset = -ENOMEM;
+ goto out;
+ }
+
+ err = fdt_open_into(ov, ovcopy, ovcopylen);
+ if (err < 0) {
+ printf("failed on fdt_open_into for DTO\n");
+ fdt_noffset = err;
+ goto out;
+ }
+
+ base = map_sysmem(load, len + ovlen);
+ err = fdt_open_into(base, base, len + ovlen);
+ if (err < 0) {
+ printf("failed on fdt_open_into\n");
+ fdt_noffset = err;
+ goto out;
+ }
+
+ /* the verbose method prints out messages on error */
+ err = fdt_overlay_apply_verbose(base, ovcopy);
+ if (err < 0) {
+ fdt_noffset = err;
+ goto out;
+ }
+ fdt_pack(base);
+ len = fdt_totalsize(base);
+ }
+#else
+ printf("config with overlays but CONFIG_OF_LIBFDT_OVERLAY not set\n");
+ fdt_noffset = -EBADF;
+#endif
+
+out:
+ if (datap)
+ *datap = load;
+ if (lenp)
+ *lenp = len;
+ if (fit_unamep)
+ *fit_unamep = fit_uname;
+ if (fit_uname_configp)
+ *fit_uname_configp = fit_uname_config;
+
+#ifdef CONFIG_OF_LIBFDT_OVERLAY
+ free(ovcopy);
+#endif
+ free(fit_uname_config_copy);
+ return fdt_noffset;
+}
+#endif
diff --git a/boot/image-host.c b/boot/image-host.c
new file mode 100644
index 00000000000..20a9521948b
--- /dev/null
+++ b/boot/image-host.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Image code used by host tools (and not boards)
+ *
+ * (C) Copyright 2008 Semihalf
+ *
+ * (C) Copyright 2000-2006
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ */
+
+#include <time.h>
+
+void memmove_wd(void *to, void *from, size_t len, ulong chunksz)
+{
+ memmove(to, from, len);
+}
+
+void genimg_print_size(uint32_t size)
+{
+ printf("%d Bytes = %.2f KiB = %.2f MiB\n", size, (double)size / 1.024e3,
+ (double)size / 1.048576e6);
+}
+
+void genimg_print_time(time_t timestamp)
+{
+ printf("%s", ctime(&timestamp));
+}
diff --git a/boot/image-pre-load.c b/boot/image-pre-load.c
new file mode 100644
index 00000000000..2f851ebb28c
--- /dev/null
+++ b/boot/image-pre-load.c
@@ -0,0 +1,426 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Philippe Reynes <philippe.reynes@softathome.com>
+ */
+
+#ifdef USE_HOSTCC
+#include "mkimage.h"
+#else
+#include <asm/global_data.h>
+#include <env.h>
+#include <mapmem.h>
+DECLARE_GLOBAL_DATA_PTR;
+#endif /* !USE_HOSTCC*/
+
+#include <image.h>
+#include <u-boot/sha256.h>
+
+#ifdef USE_HOSTCC
+/* Define compat stuff for use in tools. */
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+#endif
+
+/*
+ * Offset of the image
+ *
+ * This value is used to skip the header before really launching the image
+ */
+ulong image_load_offset;
+
+#ifdef USE_HOSTCC
+/* Host tools use these implementations to setup information related to the
+ * pre-load signatures
+ */
+static struct image_sig_info *host_info;
+
+#define log_info(fmt, args...) printf(fmt, ##args)
+#define log_err(fmt, args...) printf(fmt, ##args)
+
+void image_pre_load_sig_set_info(struct image_sig_info *info)
+{
+ host_info = info;
+}
+
+/*
+ * This function sets a pointer to information for the signature check.
+ * It expects that host_info has been initially provision by the host
+ * application.
+ *
+ * return:
+ * < 0 => an error has occurred
+ * 0 => OK
+ */
+static int image_pre_load_sig_setup(struct image_sig_info *info)
+{
+ if (!info) {
+ log_err("ERROR: info is NULL\n");
+ return -EINVAL;
+ }
+
+ if (!host_info) {
+ log_err("ERROR: host_info is NULL\n");
+ log_err("ERROR: Set it with image_pre_load_sig_set_info()\n");
+ return -EINVAL;
+ }
+
+ memcpy(info, host_info, sizeof(struct image_sig_info));
+
+ return 0;
+}
+#else
+/*
+ * This function gathers information about the signature check
+ * that could be done before launching the image.
+ *
+ * return:
+ * < 0 => an error has occurred
+ * 0 => OK
+ * 1 => no setup
+ */
+static int image_pre_load_sig_setup(struct image_sig_info *info)
+{
+ const void *algo_name, *padding_name, *key, *mandatory;
+ const u32 *sig_size;
+ int key_len;
+ int node, ret = 0;
+ char *sig_info_path = NULL;
+
+ if (!info) {
+ log_err("ERROR: info is NULL for image pre-load sig check\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ memset(info, 0, sizeof(*info));
+
+ sig_info_path = env_get("pre_load_sig_info_path");
+ if (!sig_info_path)
+ sig_info_path = IMAGE_PRE_LOAD_PATH;
+
+ node = fdt_path_offset(gd_fdt_blob(), sig_info_path);
+ if (node < 0) {
+ log_info("INFO: no info for image pre-load sig check\n");
+ ret = 1;
+ goto out;
+ }
+
+ algo_name = fdt_getprop(gd_fdt_blob(), node,
+ IMAGE_PRE_LOAD_PROP_ALGO_NAME, NULL);
+ if (!algo_name) {
+ printf("ERROR: no algo_name for image pre-load sig check\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ padding_name = fdt_getprop(gd_fdt_blob(), node,
+ IMAGE_PRE_LOAD_PROP_PADDING_NAME, NULL);
+ if (!padding_name) {
+ log_info("INFO: no padding_name provided, so using pkcs-1.5\n");
+ padding_name = "pkcs-1.5";
+ }
+
+ sig_size = fdt_getprop(gd_fdt_blob(), node,
+ IMAGE_PRE_LOAD_PROP_SIG_SIZE, NULL);
+ if (!sig_size) {
+ log_err("ERROR: no signature-size for image pre-load sig check\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ key = fdt_getprop(gd_fdt_blob(), node,
+ IMAGE_PRE_LOAD_PROP_PUBLIC_KEY, &key_len);
+ if (!key) {
+ log_err("ERROR: no key for image pre-load sig check\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ info->algo_name = (char *)algo_name;
+ info->padding_name = (char *)padding_name;
+ info->key = (uint8_t *)key;
+ info->key_len = key_len;
+ info->sig_size = fdt32_to_cpu(*sig_size);
+
+ mandatory = fdt_getprop(gd_fdt_blob(), node,
+ IMAGE_PRE_LOAD_PROP_MANDATORY, NULL);
+ if (mandatory && !strcmp((char *)mandatory, "yes"))
+ info->mandatory = 1;
+
+ /* Compute signature information */
+ info->sig_info.name = info->algo_name;
+ info->sig_info.padding = image_get_padding_algo(info->padding_name);
+ info->sig_info.checksum = image_get_checksum_algo(info->sig_info.name);
+ info->sig_info.crypto = image_get_crypto_algo(info->sig_info.name);
+ info->sig_info.key = info->key;
+ info->sig_info.keylen = info->key_len;
+
+ out:
+ return ret;
+}
+#endif /* !USE_HOSTCC */
+
+static int image_pre_load_sig_get_magic(ulong addr, u32 *magic)
+{
+ struct sig_header_s *sig_header;
+ int ret = 0;
+
+ sig_header = (struct sig_header_s *)map_sysmem(addr, SIG_HEADER_LEN);
+ if (!sig_header) {
+ log_err("ERROR: can't map first header\n");
+ ret = -EFAULT;
+ goto out;
+ }
+
+ *magic = fdt32_to_cpu(sig_header->magic);
+
+ unmap_sysmem(sig_header);
+
+ out:
+ return ret;
+}
+
+static int image_pre_load_sig_get_header_size(ulong addr, u32 *header_size)
+{
+ struct sig_header_s *sig_header;
+ int ret = 0;
+
+ sig_header = (struct sig_header_s *)map_sysmem(addr, SIG_HEADER_LEN);
+ if (!sig_header) {
+ log_err("ERROR: can't map first header\n");
+ ret = -EFAULT;
+ goto out;
+ }
+
+ *header_size = fdt32_to_cpu(sig_header->header_size);
+
+ unmap_sysmem(sig_header);
+
+ out:
+ return ret;
+}
+
+/*
+ * return:
+ * < 0 => no magic and magic mandatory (or error when reading magic)
+ * 0 => magic found
+ * 1 => magic NOT found
+ */
+static int image_pre_load_sig_check_magic(struct image_sig_info *info, ulong addr)
+{
+ u32 magic;
+ int ret = 1;
+
+ ret = image_pre_load_sig_get_magic(addr, &magic);
+ if (ret < 0)
+ goto out;
+
+ if (magic != IMAGE_PRE_LOAD_SIG_MAGIC) {
+ if (info->mandatory) {
+ log_err("ERROR: signature is mandatory\n");
+ ret = -EINVAL;
+ goto out;
+ }
+ ret = 1;
+ goto out;
+ }
+
+ ret = 0; /* magic found */
+
+ out:
+ return ret;
+}
+
+static int image_pre_load_sig_check_header_sig(struct image_sig_info *info, ulong addr)
+{
+ void *header;
+ struct image_region reg;
+ u32 sig_len;
+ u8 *sig;
+ int ret = 0;
+
+ /* Only map header of the header and its signature */
+ header = (void *)map_sysmem(addr, SIG_HEADER_LEN + info->sig_size);
+ if (!header) {
+ log_err("ERROR: can't map header\n");
+ ret = -EFAULT;
+ goto out;
+ }
+
+ reg.data = header;
+ reg.size = SIG_HEADER_LEN;
+
+ sig = (uint8_t *)header + SIG_HEADER_LEN;
+ sig_len = info->sig_size;
+
+ ret = info->sig_info.crypto->verify(&info->sig_info, &reg, 1, sig, sig_len);
+ if (ret) {
+ log_err("ERROR: header signature check has failed (err=%d)\n", ret);
+ ret = -EINVAL;
+ goto out_unmap;
+ }
+
+ out_unmap:
+ unmap_sysmem(header);
+
+ out:
+ return ret;
+}
+
+static int image_pre_load_sig_check_img_sig_sha256(struct image_sig_info *info, ulong addr)
+{
+ struct sig_header_s *sig_header;
+ u32 header_size, offset_img_sig;
+ void *header;
+ u8 sha256_img_sig[SHA256_SUM_LEN];
+ int ret = 0;
+
+ sig_header = (struct sig_header_s *)map_sysmem(addr, SIG_HEADER_LEN);
+ if (!sig_header) {
+ log_err("ERROR: can't map first header\n");
+ ret = -EFAULT;
+ goto out;
+ }
+
+ header_size = fdt32_to_cpu(sig_header->header_size);
+ offset_img_sig = fdt32_to_cpu(sig_header->offset_img_sig);
+
+ header = (void *)map_sysmem(addr, header_size);
+ if (!header) {
+ log_err("ERROR: can't map header\n");
+ ret = -EFAULT;
+ goto out_sig_header;
+ }
+
+ sha256_csum_wd(header + offset_img_sig, info->sig_size,
+ sha256_img_sig, CHUNKSZ_SHA256);
+
+ ret = memcmp(sig_header->sha256_img_sig, sha256_img_sig, SHA256_SUM_LEN);
+ if (ret) {
+ log_err("ERROR: sha256 of image signature is invalid\n");
+ ret = -EFAULT;
+ goto out_header;
+ }
+
+ out_header:
+ unmap_sysmem(header);
+ out_sig_header:
+ unmap_sysmem(sig_header);
+ out:
+ return ret;
+}
+
+static int image_pre_load_sig_check_img_sig(struct image_sig_info *info, ulong addr)
+{
+ struct sig_header_s *sig_header;
+ u32 header_size, image_size, offset_img_sig;
+ void *image;
+ struct image_region reg;
+ u32 sig_len;
+ u8 *sig;
+ int ret = 0;
+
+ sig_header = (struct sig_header_s *)map_sysmem(addr, SIG_HEADER_LEN);
+ if (!sig_header) {
+ log_err("ERROR: can't map first header\n");
+ ret = -EFAULT;
+ goto out;
+ }
+
+ header_size = fdt32_to_cpu(sig_header->header_size);
+ image_size = fdt32_to_cpu(sig_header->image_size);
+ offset_img_sig = fdt32_to_cpu(sig_header->offset_img_sig);
+
+ unmap_sysmem(sig_header);
+
+ image = (void *)map_sysmem(addr, header_size + image_size);
+ if (!image) {
+ log_err("ERROR: can't map full image\n");
+ ret = -EFAULT;
+ goto out;
+ }
+
+ reg.data = image + header_size;
+ reg.size = image_size;
+
+ sig = (uint8_t *)image + offset_img_sig;
+ sig_len = info->sig_size;
+
+ ret = info->sig_info.crypto->verify(&info->sig_info, &reg, 1, sig, sig_len);
+ if (ret) {
+ log_err("ERROR: signature check has failed (err=%d)\n", ret);
+ ret = -EINVAL;
+ goto out_unmap_image;
+ }
+
+ log_info("INFO: signature check has succeed\n");
+
+ out_unmap_image:
+ unmap_sysmem(image);
+
+ out:
+ return ret;
+}
+
+int image_pre_load_sig(ulong addr)
+{
+ struct image_sig_info info;
+ int ret;
+
+ ret = image_pre_load_sig_setup(&info);
+ if (ret < 0)
+ goto out;
+ if (ret > 0) {
+ ret = 0;
+ goto out;
+ }
+
+ ret = image_pre_load_sig_check_magic(&info, addr);
+ if (ret < 0)
+ goto out;
+ if (ret > 0) {
+ ret = 0;
+ goto out;
+ }
+
+ /* Check the signature of the signature header */
+ ret = image_pre_load_sig_check_header_sig(&info, addr);
+ if (ret < 0)
+ goto out;
+
+ /* Check sha256 of the image signature */
+ ret = image_pre_load_sig_check_img_sig_sha256(&info, addr);
+ if (ret < 0)
+ goto out;
+
+ /* Check the image signature */
+ ret = image_pre_load_sig_check_img_sig(&info, addr);
+ if (!ret) {
+ u32 header_size;
+
+ ret = image_pre_load_sig_get_header_size(addr, &header_size);
+ if (ret) {
+ log_err("%s: can't get header size\n", __func__);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ image_load_offset += header_size;
+ }
+
+ out:
+ return ret;
+}
+
+int image_pre_load(ulong addr)
+{
+ int ret = 0;
+
+ image_load_offset = 0;
+
+ if (CONFIG_IS_ENABLED(IMAGE_PRE_LOAD_SIG))
+ ret = image_pre_load_sig(addr);
+
+ return ret;
+}
diff --git a/boot/image-sig.c b/boot/image-sig.c
new file mode 100644
index 00000000000..6bc74866eae
--- /dev/null
+++ b/boot/image-sig.c
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2013, Google Inc.
+ */
+
+#include <log.h>
+#include <malloc.h>
+#include <asm/global_data.h>
+DECLARE_GLOBAL_DATA_PTR;
+#include <image.h>
+#include <relocate.h>
+#include <u-boot/ecdsa.h>
+#include <u-boot/rsa.h>
+#include <u-boot/hash-checksum.h>
+
+#define IMAGE_MAX_HASHED_NODES 100
+
+struct checksum_algo checksum_algos[] = {
+#if CONFIG_IS_ENABLED(SHA1)
+ {
+ .name = "sha1",
+ .checksum_len = SHA1_SUM_LEN,
+ .der_len = SHA1_DER_LEN,
+ .der_prefix = sha1_der_prefix,
+ .calculate = hash_calculate,
+ },
+#endif
+#if CONFIG_IS_ENABLED(SHA256)
+ {
+ .name = "sha256",
+ .checksum_len = SHA256_SUM_LEN,
+ .der_len = SHA256_DER_LEN,
+ .der_prefix = sha256_der_prefix,
+ .calculate = hash_calculate,
+ },
+#endif
+#if CONFIG_IS_ENABLED(SHA384)
+ {
+ .name = "sha384",
+ .checksum_len = SHA384_SUM_LEN,
+ .der_len = SHA384_DER_LEN,
+ .der_prefix = sha384_der_prefix,
+ .calculate = hash_calculate,
+ },
+#endif
+#if CONFIG_IS_ENABLED(SHA512)
+ {
+ .name = "sha512",
+ .checksum_len = SHA512_SUM_LEN,
+ .der_len = SHA512_DER_LEN,
+ .der_prefix = sha512_der_prefix,
+ .calculate = hash_calculate,
+ },
+#endif
+
+};
+
+struct checksum_algo *image_get_checksum_algo(const char *full_name)
+{
+ int i;
+ const char *name;
+
+ for (i = 0; i < ARRAY_SIZE(checksum_algos); i++) {
+ name = checksum_algos[i].name;
+ /* Make sure names match and next char is a comma */
+ if (!strncmp(name, full_name, strlen(name)) &&
+ full_name[strlen(name)] == ',')
+ return &checksum_algos[i];
+ }
+
+ return NULL;
+}
+
+struct crypto_algo *image_get_crypto_algo(const char *full_name)
+{
+ struct crypto_algo *crypto, *end;
+ const char *name;
+
+ /* Move name to after the comma */
+ name = strchr(full_name, ',');
+ if (!name)
+ return NULL;
+ name += 1;
+
+ crypto = ll_entry_start(struct crypto_algo, cryptos);
+ end = ll_entry_end(struct crypto_algo, cryptos);
+ for (; crypto < end; crypto++) {
+ if (!strcmp(crypto->name, name))
+ return crypto;
+ }
+
+ /* Not found */
+ return NULL;
+}
+
+struct padding_algo *image_get_padding_algo(const char *name)
+{
+ struct padding_algo *padding, *end;
+
+ if (!name)
+ return NULL;
+
+ padding = ll_entry_start(struct padding_algo, paddings);
+ end = ll_entry_end(struct padding_algo, paddings);
+ for (; padding < end; padding++) {
+ if (!strcmp(padding->name, name))
+ return padding;
+ }
+
+ return NULL;
+}
diff --git a/boot/image.c b/boot/image.c
new file mode 100644
index 00000000000..abac2c7034b
--- /dev/null
+++ b/boot/image.c
@@ -0,0 +1,761 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2008 Semihalf
+ *
+ * (C) Copyright 2000-2006
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ */
+
+#ifndef USE_HOSTCC
+#include <env.h>
+#include <display_options.h>
+#include <init.h>
+#include <lmb.h>
+#include <log.h>
+#include <malloc.h>
+#include <u-boot/crc.h>
+
+#ifdef CONFIG_SHOW_BOOT_PROGRESS
+#include <status_led.h>
+#endif
+
+#if CONFIG_IS_ENABLED(FIT) || CONFIG_IS_ENABLED(OF_LIBFDT)
+#include <linux/libfdt.h>
+#include <fdt_support.h>
+#endif
+
+#include <asm/global_data.h>
+#include <linux/errno.h>
+#include <asm/io.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/* Set this if we have less than 4 MB of malloc() space */
+#if CONFIG_SYS_MALLOC_LEN < (4096 * 1024)
+#define CONSERVE_MEMORY true
+#else
+#define CONSERVE_MEMORY false
+#endif
+
+#else /* USE_HOSTCC */
+#include "mkimage.h"
+#include <linux/kconfig.h>
+#include <u-boot/md5.h>
+#include <time.h>
+
+#ifndef __maybe_unused
+# define __maybe_unused /* unimplemented */
+#endif
+
+#define CONSERVE_MEMORY false
+
+#endif /* !USE_HOSTCC*/
+
+#include <abuf.h>
+#include <bzlib.h>
+#include <display_options.h>
+#include <gzip.h>
+#include <image.h>
+#include <imximage.h>
+#include <relocate.h>
+#include <linux/lzo.h>
+#include <linux/zstd.h>
+#include <lzma/LzmaTypes.h>
+#include <lzma/LzmaDec.h>
+#include <lzma/LzmaTools.h>
+#include <u-boot/crc.h>
+#include <u-boot/lz4.h>
+
+static const table_entry_t uimage_arch[] = {
+ { IH_ARCH_INVALID, "invalid", "Invalid ARCH", },
+ { IH_ARCH_ALPHA, "alpha", "Alpha", },
+ { IH_ARCH_ARM, "arm", "ARM", },
+ { IH_ARCH_I386, "x86", "Intel x86", },
+ { IH_ARCH_IA64, "ia64", "IA64", },
+ { IH_ARCH_M68K, "m68k", "M68K", },
+ { IH_ARCH_MICROBLAZE, "microblaze", "MicroBlaze", },
+ { IH_ARCH_MIPS, "mips", "MIPS", },
+ { IH_ARCH_MIPS64, "mips64", "MIPS 64 Bit", },
+ { IH_ARCH_NIOS2, "nios2", "NIOS II", },
+ { IH_ARCH_PPC, "powerpc", "PowerPC", },
+ { IH_ARCH_PPC, "ppc", "PowerPC", },
+ { IH_ARCH_S390, "s390", "IBM S390", },
+ { IH_ARCH_SH, "sh", "SuperH", },
+ { IH_ARCH_SPARC, "sparc", "SPARC", },
+ { IH_ARCH_SPARC64, "sparc64", "SPARC 64 Bit", },
+ { IH_ARCH_BLACKFIN, "blackfin", "Blackfin", },
+ { IH_ARCH_AVR32, "avr32", "AVR32", },
+ { IH_ARCH_NDS32, "nds32", "NDS32", },
+ { IH_ARCH_OPENRISC, "or1k", "OpenRISC 1000",},
+ { IH_ARCH_SANDBOX, "sandbox", "Sandbox", },
+ { IH_ARCH_ARM64, "arm64", "AArch64", },
+ { IH_ARCH_ARC, "arc", "ARC", },
+ { IH_ARCH_X86_64, "x86_64", "AMD x86_64", },
+ { IH_ARCH_XTENSA, "xtensa", "Xtensa", },
+ { IH_ARCH_RISCV, "riscv", "RISC-V", },
+ { -1, "", "", },
+};
+
+static const table_entry_t uimage_os[] = {
+ { IH_OS_INVALID, "invalid", "Invalid OS", },
+ { IH_OS_ARM_TRUSTED_FIRMWARE, "arm-trusted-firmware", "ARM Trusted Firmware" },
+ { IH_OS_LINUX, "linux", "Linux", },
+ { IH_OS_NETBSD, "netbsd", "NetBSD", },
+ { IH_OS_OSE, "ose", "Enea OSE", },
+ { IH_OS_PLAN9, "plan9", "Plan 9", },
+ { IH_OS_RTEMS, "rtems", "RTEMS", },
+ { IH_OS_TEE, "tee", "Trusted Execution Environment" },
+ { IH_OS_U_BOOT, "u-boot", "U-Boot", },
+ { IH_OS_VXWORKS, "vxworks", "VxWorks", },
+#if defined(CONFIG_CMD_ELF) || defined(USE_HOSTCC)
+ { IH_OS_QNX, "qnx", "QNX", },
+#endif
+#if defined(CONFIG_INTEGRITY) || defined(USE_HOSTCC)
+ { IH_OS_INTEGRITY,"integrity", "INTEGRITY", },
+#endif
+#ifdef USE_HOSTCC
+ { IH_OS_4_4BSD, "4_4bsd", "4_4BSD", },
+ { IH_OS_DELL, "dell", "Dell", },
+ { IH_OS_ESIX, "esix", "Esix", },
+ { IH_OS_FREEBSD, "freebsd", "FreeBSD", },
+ { IH_OS_IRIX, "irix", "Irix", },
+ { IH_OS_NCR, "ncr", "NCR", },
+ { IH_OS_OPENBSD, "openbsd", "OpenBSD", },
+ { IH_OS_PSOS, "psos", "pSOS", },
+ { IH_OS_SCO, "sco", "SCO", },
+ { IH_OS_SOLARIS, "solaris", "Solaris", },
+ { IH_OS_SVR4, "svr4", "SVR4", },
+#endif
+#if defined(CONFIG_BOOTM_OPENRTOS) || defined(USE_HOSTCC)
+ { IH_OS_OPENRTOS, "openrtos", "OpenRTOS", },
+#endif
+ { IH_OS_OPENSBI, "opensbi", "RISC-V OpenSBI", },
+ { IH_OS_EFI, "efi", "EFI Firmware" },
+#ifdef CONFIG_BOOTM_ELF
+ { IH_OS_ELF, "elf", "ELF Image" },
+#endif
+
+ { -1, "", "", },
+};
+
+static const table_entry_t uimage_type[] = {
+ { IH_TYPE_AISIMAGE, "aisimage", "Davinci AIS image",},
+ { IH_TYPE_FILESYSTEM, "filesystem", "Filesystem Image", },
+ { IH_TYPE_FIRMWARE, "firmware", "Firmware", },
+ { IH_TYPE_FLATDT, "flat_dt", "Flat Device Tree", },
+ { IH_TYPE_GPIMAGE, "gpimage", "TI Keystone SPL Image",},
+ { IH_TYPE_KERNEL, "kernel", "Kernel Image", },
+ { IH_TYPE_KERNEL_NOLOAD, "kernel_noload", "Kernel Image (no loading done)", },
+ { IH_TYPE_KWBIMAGE, "kwbimage", "Kirkwood Boot Image",},
+ { IH_TYPE_IMXIMAGE, "imximage", "Freescale i.MX Boot Image",},
+ { IH_TYPE_IMX8IMAGE, "imx8image", "NXP i.MX8 Boot Image",},
+ { IH_TYPE_IMX8MIMAGE, "imx8mimage", "NXP i.MX8M Boot Image",},
+ { IH_TYPE_INVALID, "invalid", "Invalid Image", },
+ { IH_TYPE_MULTI, "multi", "Multi-File Image", },
+ { IH_TYPE_OMAPIMAGE, "omapimage", "TI OMAP SPL With GP CH",},
+ { IH_TYPE_PBLIMAGE, "pblimage", "Freescale PBL Boot Image",},
+ { IH_TYPE_RAMDISK, "ramdisk", "RAMDisk Image", },
+ { IH_TYPE_SCRIPT, "script", "Script", },
+ { IH_TYPE_SOCFPGAIMAGE, "socfpgaimage", "Altera SoCFPGA CV/AV preloader",},
+ { IH_TYPE_SOCFPGAIMAGE_V1, "socfpgaimage_v1", "Altera SoCFPGA A10 preloader",},
+ { IH_TYPE_STANDALONE, "standalone", "Standalone Program", },
+ { IH_TYPE_UBLIMAGE, "ublimage", "Davinci UBL image",},
+ { IH_TYPE_MXSIMAGE, "mxsimage", "Freescale MXS Boot Image",},
+ { IH_TYPE_ATMELIMAGE, "atmelimage", "ATMEL ROM-Boot Image",},
+ { IH_TYPE_X86_SETUP, "x86_setup", "x86 setup.bin", },
+ { IH_TYPE_LPC32XXIMAGE, "lpc32xximage", "LPC32XX Boot Image", },
+ { IH_TYPE_RKIMAGE, "rkimage", "Rockchip Boot Image" },
+ { IH_TYPE_RKSD, "rksd", "Rockchip SD Boot Image" },
+ { IH_TYPE_RKSPI, "rkspi", "Rockchip SPI Boot Image" },
+ { IH_TYPE_VYBRIDIMAGE, "vybridimage", "Vybrid Boot Image", },
+ { IH_TYPE_ZYNQIMAGE, "zynqimage", "Xilinx Zynq Boot Image" },
+ { IH_TYPE_ZYNQMPIMAGE, "zynqmpimage", "Xilinx ZynqMP Boot Image" },
+ { IH_TYPE_ZYNQMPBIF, "zynqmpbif", "Xilinx ZynqMP Boot Image (bif)" },
+ { IH_TYPE_FPGA, "fpga", "FPGA Image" },
+ { IH_TYPE_TEE, "tee", "Trusted Execution Environment Image",},
+ { IH_TYPE_FIRMWARE_IVT, "firmware_ivt", "Firmware with HABv4 IVT" },
+ { IH_TYPE_PMMC, "pmmc", "TI Power Management Micro-Controller Firmware",},
+ { IH_TYPE_STM32IMAGE, "stm32image", "STMicroelectronics STM32 Image" },
+ { IH_TYPE_MTKIMAGE, "mtk_image", "MediaTek BootROM loadable Image" },
+ { IH_TYPE_COPRO, "copro", "Coprocessor Image"},
+ { IH_TYPE_SUNXI_EGON, "sunxi_egon", "Allwinner eGON Boot Image" },
+ { IH_TYPE_SUNXI_TOC0, "sunxi_toc0", "Allwinner TOC0 Boot Image" },
+ { IH_TYPE_FDT_LEGACY, "fdt_legacy", "legacy Image with Flat Device Tree ", },
+ { IH_TYPE_RENESAS_SPKG, "spkgimage", "Renesas SPKG Image" },
+ { IH_TYPE_STARFIVE_SPL, "sfspl", "StarFive SPL Image" },
+ { IH_TYPE_TFA_BL31, "tfa-bl31", "TFA BL31 Image", },
+ { IH_TYPE_STM32IMAGE_V2, "stm32imagev2", "STMicroelectronics STM32 Image V2.0" },
+ { -1, "", "", },
+};
+
+static const table_entry_t uimage_comp[] = {
+ { IH_COMP_NONE, "none", "uncompressed", },
+ { IH_COMP_BZIP2, "bzip2", "bzip2 compressed", },
+ { IH_COMP_GZIP, "gzip", "gzip compressed", },
+ { IH_COMP_LZMA, "lzma", "lzma compressed", },
+ { IH_COMP_LZO, "lzo", "lzo compressed", },
+ { IH_COMP_LZ4, "lz4", "lz4 compressed", },
+ { IH_COMP_ZSTD, "zstd", "zstd compressed", },
+ { -1, "", "", },
+};
+
+static const table_entry_t uimage_phase[] = {
+ { IH_PHASE_NONE, "none", "any", },
+ { IH_PHASE_U_BOOT, "u-boot", "U-Boot phase", },
+ { IH_PHASE_SPL, "spl", "SPL Phase", },
+ { -1, "", "", },
+};
+
+struct table_info {
+ const char *desc;
+ int count;
+ const table_entry_t *table;
+};
+
+static const struct comp_magic_map image_comp[] = {
+ { IH_COMP_BZIP2, "bzip2", {0x42, 0x5a},},
+ { IH_COMP_GZIP, "gzip", {0x1f, 0x8b},},
+ { IH_COMP_LZMA, "lzma", {0x5d, 0x00},},
+ { IH_COMP_LZO, "lzo", {0x89, 0x4c},},
+ { IH_COMP_LZ4, "lz4", {0x04, 0x22},},
+ { IH_COMP_ZSTD, "zstd", {0x28, 0xb5},},
+ { IH_COMP_NONE, "none", {}, },
+};
+
+static const struct table_info table_info[IH_COUNT] = {
+ { "architecture", IH_ARCH_COUNT, uimage_arch },
+ { "compression", IH_COMP_COUNT, uimage_comp },
+ { "operating system", IH_OS_COUNT, uimage_os },
+ { "image type", IH_TYPE_COUNT, uimage_type },
+ { "phase", IH_PHASE_COUNT, uimage_phase },
+};
+
+/*****************************************************************************/
+/* Legacy format routines */
+/*****************************************************************************/
+int image_check_hcrc(const struct legacy_img_hdr *hdr)
+{
+ ulong hcrc;
+ ulong len = image_get_header_size();
+ struct legacy_img_hdr header;
+
+ /* Copy header so we can blank CRC field for re-calculation */
+ memmove(&header, (char *)hdr, image_get_header_size());
+ image_set_hcrc(&header, 0);
+
+ hcrc = crc32(0, (unsigned char *)&header, len);
+
+ return (hcrc == image_get_hcrc(hdr));
+}
+
+int image_check_dcrc(const struct legacy_img_hdr *hdr)
+{
+ ulong data = image_get_data(hdr);
+ ulong len = image_get_data_size(hdr);
+ ulong dcrc = crc32_wd(0, (unsigned char *)data, len, CHUNKSZ_CRC32);
+
+ return (dcrc == image_get_dcrc(hdr));
+}
+
+/**
+ * image_multi_count - get component (sub-image) count
+ * @hdr: pointer to the header of the multi component image
+ *
+ * image_multi_count() returns number of components in a multi
+ * component image.
+ *
+ * Note: no checking of the image type is done, caller must pass
+ * a valid multi component image.
+ *
+ * returns:
+ * number of components
+ */
+ulong image_multi_count(const struct legacy_img_hdr *hdr)
+{
+ ulong i, count = 0;
+ uint32_t *size;
+
+ /* get start of the image payload, which in case of multi
+ * component images that points to a table of component sizes */
+ size = (uint32_t *)image_get_data(hdr);
+
+ /* count non empty slots */
+ for (i = 0; size[i]; ++i)
+ count++;
+
+ return count;
+}
+
+/**
+ * image_multi_getimg - get component data address and size
+ * @hdr: pointer to the header of the multi component image
+ * @idx: index of the requested component
+ * @data: pointer to a ulong variable, will hold component data address
+ * @len: pointer to a ulong variable, will hold component size
+ *
+ * image_multi_getimg() returns size and data address for the requested
+ * component in a multi component image.
+ *
+ * Note: no checking of the image type is done, caller must pass
+ * a valid multi component image.
+ *
+ * returns:
+ * data address and size of the component, if idx is valid
+ * 0 in data and len, if idx is out of range
+ */
+void image_multi_getimg(const struct legacy_img_hdr *hdr, ulong idx,
+ ulong *data, ulong *len)
+{
+ int i;
+ uint32_t *size;
+ ulong offset, count, img_data;
+
+ /* get number of component */
+ count = image_multi_count(hdr);
+
+ /* get start of the image payload, which in case of multi
+ * component images that points to a table of component sizes */
+ size = (uint32_t *)image_get_data(hdr);
+
+ /* get address of the proper component data start, which means
+ * skipping sizes table (add 1 for last, null entry) */
+ img_data = image_get_data(hdr) + (count + 1) * sizeof(uint32_t);
+
+ if (idx < count) {
+ *len = uimage_to_cpu(size[idx]);
+ offset = 0;
+
+ /* go over all indices preceding requested component idx */
+ for (i = 0; i < idx; i++) {
+ /* add up i-th component size, rounding up to 4 bytes */
+ offset += (uimage_to_cpu(size[i]) + 3) & ~3 ;
+ }
+
+ /* calculate idx-th component data address */
+ *data = img_data + offset;
+ } else {
+ *len = 0;
+ *data = 0;
+ }
+}
+
+static void image_print_type(const struct legacy_img_hdr *hdr)
+{
+ const char __maybe_unused *os, *arch, *type, *comp;
+
+ os = genimg_get_os_name(image_get_os(hdr));
+ arch = genimg_get_arch_name(image_get_arch(hdr));
+ type = genimg_get_type_name(image_get_type(hdr));
+ comp = genimg_get_comp_name(image_get_comp(hdr));
+
+ printf("%s %s %s (%s)\n", arch, os, type, comp);
+}
+
+/**
+ * image_print_contents - prints out the contents of the legacy format image
+ * @ptr: pointer to the legacy format image header
+ * @p: pointer to prefix string
+ *
+ * image_print_contents() formats a multi line legacy image contents description.
+ * The routine prints out all header fields followed by the size/offset data
+ * for MULTI/SCRIPT images.
+ *
+ * returns:
+ * no returned results
+ */
+void image_print_contents(const void *ptr)
+{
+ const struct legacy_img_hdr *hdr = (const struct legacy_img_hdr *)ptr;
+ const char __maybe_unused *p;
+
+ p = IMAGE_INDENT_STRING;
+ printf("%sImage Name: %.*s\n", p, IH_NMLEN, image_get_name(hdr));
+ if (IMAGE_ENABLE_TIMESTAMP) {
+ printf("%sCreated: ", p);
+ genimg_print_time((time_t)image_get_time(hdr));
+ }
+ printf("%sImage Type: ", p);
+ image_print_type(hdr);
+ printf("%sData Size: ", p);
+ genimg_print_size(image_get_data_size(hdr));
+ printf("%sLoad Address: %08x\n", p, image_get_load(hdr));
+ printf("%sEntry Point: %08x\n", p, image_get_ep(hdr));
+
+ if (image_check_type(hdr, IH_TYPE_MULTI) ||
+ image_check_type(hdr, IH_TYPE_SCRIPT)) {
+ int i;
+ ulong data, len;
+ ulong count = image_multi_count(hdr);
+
+ printf("%sContents:\n", p);
+ for (i = 0; i < count; i++) {
+ image_multi_getimg(hdr, i, &data, &len);
+
+ printf("%s Image %d: ", p, i);
+ genimg_print_size(len);
+
+ if (image_check_type(hdr, IH_TYPE_SCRIPT) && i > 0) {
+ /*
+ * the user may need to know offsets
+ * if planning to do something with
+ * multiple files
+ */
+ printf("%s Offset = 0x%08lx\n", p, data);
+ }
+ }
+ } else if (image_check_type(hdr, IH_TYPE_FIRMWARE_IVT)) {
+ printf("HAB Blocks: 0x%08x 0x0000 0x%08x\n",
+ image_get_load(hdr) - image_get_header_size(),
+ (int)(image_get_size(hdr) + image_get_header_size()
+ + sizeof(flash_header_v2_t) - 0x2060));
+ }
+}
+
+/**
+ * print_decomp_msg() - Print a suitable decompression/loading message
+ *
+ * @type: OS type (IH_OS_...)
+ * @comp_type: Compression type being used (IH_COMP_...)
+ * @is_xip: true if the load address matches the image start
+ * @load: Load address for printing
+ */
+static void print_decomp_msg(int comp_type, int type, bool is_xip,
+ ulong load)
+{
+ const char *name = genimg_get_type_name(type);
+
+ /* Shows "Loading Kernel Image" for example */
+ if (comp_type == IH_COMP_NONE)
+ printf(" %s %s", is_xip ? "XIP" : "Loading", name);
+ else
+ printf(" Uncompressing %s", name);
+
+ printf(" to %lx\n", load);
+}
+
+int image_decomp_type(const unsigned char *buf, ulong len)
+{
+ const struct comp_magic_map *cmagic = image_comp;
+
+ if (len < 2)
+ return -EINVAL;
+
+ for (; cmagic->comp_id > 0; cmagic++) {
+ if (!memcmp(buf, cmagic->magic, 2))
+ break;
+ }
+
+ return cmagic->comp_id;
+}
+
+int image_decomp(int comp, ulong load, ulong image_start, int type,
+ void *load_buf, void *image_buf, ulong image_len,
+ uint unc_len, ulong *load_end)
+{
+ int ret = -ENOSYS;
+
+ *load_end = load;
+ print_decomp_msg(comp, type, load == image_start, load);
+
+ /*
+ * Load the image to the right place, decompressing if needed. After
+ * this, image_len will be set to the number of uncompressed bytes
+ * loaded, ret will be non-zero on error.
+ */
+ switch (comp) {
+ case IH_COMP_NONE:
+ ret = 0;
+ if (load == image_start)
+ break;
+ if (image_len <= unc_len)
+ memmove_wd(load_buf, image_buf, image_len, CHUNKSZ);
+ else
+ ret = -ENOSPC;
+ break;
+ case IH_COMP_GZIP:
+ if (!tools_build() && CONFIG_IS_ENABLED(GZIP))
+ ret = gunzip(load_buf, unc_len, image_buf, &image_len);
+ break;
+ case IH_COMP_BZIP2:
+ if (!tools_build() && CONFIG_IS_ENABLED(BZIP2)) {
+ uint size = unc_len;
+
+ /*
+ * If we've got less than 4 MB of malloc() space,
+ * use slower decompression algorithm which requires
+ * at most 2300 KB of memory.
+ */
+ ret = BZ2_bzBuffToBuffDecompress(load_buf, &size,
+ image_buf, image_len, CONSERVE_MEMORY, 0);
+ image_len = size;
+ }
+ break;
+ case IH_COMP_LZMA:
+ if (!tools_build() && CONFIG_IS_ENABLED(LZMA)) {
+ SizeT lzma_len = unc_len;
+
+ ret = lzmaBuffToBuffDecompress(load_buf, &lzma_len,
+ image_buf, image_len);
+ image_len = lzma_len;
+ }
+ break;
+ case IH_COMP_LZO:
+ if (!tools_build() && CONFIG_IS_ENABLED(LZO)) {
+ size_t size = unc_len;
+
+ ret = lzop_decompress(image_buf, image_len, load_buf, &size);
+ image_len = size;
+ }
+ break;
+ case IH_COMP_LZ4:
+ if (!tools_build() && CONFIG_IS_ENABLED(LZ4)) {
+ size_t size = unc_len;
+
+ ret = ulz4fn(image_buf, image_len, load_buf, &size);
+ image_len = size;
+ }
+ break;
+ case IH_COMP_ZSTD:
+ if (!tools_build() && CONFIG_IS_ENABLED(ZSTD)) {
+ struct abuf in, out;
+
+ abuf_init_set(&in, image_buf, image_len);
+ abuf_init_set(&out, load_buf, unc_len);
+ ret = zstd_decompress(&in, &out);
+ if (ret >= 0) {
+ image_len = ret;
+ ret = 0;
+ }
+ }
+ break;
+ }
+ if (ret == -ENOSYS) {
+ printf("Unimplemented compression type %d\n", comp);
+ return ret;
+ }
+
+ *load_end = load + image_len;
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+const table_entry_t *get_table_entry(const table_entry_t *table, int id)
+{
+ for (; table->id >= 0; ++table) {
+ if (table->id == id)
+ return table;
+ }
+ return NULL;
+}
+
+static const char *unknown_msg(enum ih_category category)
+{
+ static const char unknown_str[] = "Unknown ";
+ static char msg[30];
+
+ strcpy(msg, unknown_str);
+ strncat(msg, table_info[category].desc,
+ sizeof(msg) - sizeof(unknown_str));
+
+ return msg;
+}
+
+/**
+ * genimg_get_cat_name - translate entry id to long name
+ * @category: category to look up (enum ih_category)
+ * @id: entry id to be translated
+ *
+ * This will scan the translation table trying to find the entry that matches
+ * the given id.
+ *
+ * Return: long entry name if translation succeeds; error string on failure
+ */
+const char *genimg_get_cat_name(enum ih_category category, uint id)
+{
+ const table_entry_t *entry;
+
+ entry = get_table_entry(table_info[category].table, id);
+ if (!entry)
+ return unknown_msg(category);
+ return entry->lname;
+}
+
+/**
+ * genimg_get_cat_short_name - translate entry id to short name
+ * @category: category to look up (enum ih_category)
+ * @id: entry id to be translated
+ *
+ * This will scan the translation table trying to find the entry that matches
+ * the given id.
+ *
+ * Return: short entry name if translation succeeds; error string on failure
+ */
+const char *genimg_get_cat_short_name(enum ih_category category, uint id)
+{
+ const table_entry_t *entry;
+
+ entry = get_table_entry(table_info[category].table, id);
+ if (!entry)
+ return unknown_msg(category);
+ return entry->sname;
+}
+
+int genimg_get_cat_count(enum ih_category category)
+{
+ return table_info[category].count;
+}
+
+const char *genimg_get_cat_desc(enum ih_category category)
+{
+ return table_info[category].desc;
+}
+
+/**
+ * genimg_cat_has_id - check whether category has entry id
+ * @category: category to look up (enum ih_category)
+ * @id: entry id to be checked
+ *
+ * This will scan the translation table trying to find the entry that matches
+ * the given id.
+ *
+ * Return: true if category has entry id; false if not
+ */
+bool genimg_cat_has_id(enum ih_category category, uint id)
+{
+ if (get_table_entry(table_info[category].table, id))
+ return true;
+
+ return false;
+}
+
+/**
+ * get_table_entry_name - translate entry id to long name
+ * @table: pointer to a translation table for entries of a specific type
+ * @msg: message to be returned when translation fails
+ * @id: entry id to be translated
+ *
+ * get_table_entry_name() will go over translation table trying to find
+ * entry that matches given id. If matching entry is found, its long
+ * name is returned to the caller.
+ *
+ * returns:
+ * long entry name if translation succeeds
+ * msg otherwise
+ */
+char *get_table_entry_name(const table_entry_t *table, char *msg, int id)
+{
+ table = get_table_entry(table, id);
+ if (!table)
+ return msg;
+ return table->lname;
+}
+
+const char *genimg_get_os_name(uint8_t os)
+{
+ return (get_table_entry_name(uimage_os, "Unknown OS", os));
+}
+
+const char *genimg_get_arch_name(uint8_t arch)
+{
+ return (get_table_entry_name(uimage_arch, "Unknown Architecture",
+ arch));
+}
+
+const char *genimg_get_type_name(uint8_t type)
+{
+ return (get_table_entry_name(uimage_type, "Unknown Image", type));
+}
+
+const char *genimg_get_comp_name(uint8_t comp)
+{
+ return (get_table_entry_name(uimage_comp, "Unknown Compression",
+ comp));
+}
+
+const char *genimg_get_phase_name(enum image_phase_t phase)
+{
+ return get_table_entry_name(uimage_phase, "Unknown Phase", phase);
+}
+
+static const char *genimg_get_short_name(const table_entry_t *table, int val)
+{
+ table = get_table_entry(table, val);
+ if (!table)
+ return "unknown";
+ return table->sname;
+}
+
+const char *genimg_get_type_short_name(uint8_t type)
+{
+ return genimg_get_short_name(uimage_type, type);
+}
+
+const char *genimg_get_comp_short_name(uint8_t comp)
+{
+ return genimg_get_short_name(uimage_comp, comp);
+}
+
+const char *genimg_get_os_short_name(uint8_t os)
+{
+ return genimg_get_short_name(uimage_os, os);
+}
+
+const char *genimg_get_arch_short_name(uint8_t arch)
+{
+ return genimg_get_short_name(uimage_arch, arch);
+}
+
+/**
+ * get_table_entry_id - translate short entry name to id
+ * @table: pointer to a translation table for entries of a specific type
+ * @table_name: to be used in case of error
+ * @name: entry short name to be translated
+ *
+ * get_table_entry_id() will go over translation table trying to find
+ * entry that matches given short name. If matching entry is found,
+ * its id returned to the caller.
+ *
+ * returns:
+ * entry id if translation succeeds
+ * -1 otherwise
+ */
+int get_table_entry_id(const table_entry_t *table,
+ const char *table_name, const char *name)
+{
+ const table_entry_t *t;
+
+ for (t = table; t->id >= 0; ++t) {
+ if (t->sname && !strcasecmp(t->sname, name))
+ return t->id;
+ }
+ debug("Invalid %s Type: %s\n", table_name, name);
+
+ return -1;
+}
+
+int genimg_get_os_id(const char *name)
+{
+ return (get_table_entry_id(uimage_os, "OS", name));
+}
+
+int genimg_get_arch_id(const char *name)
+{
+ return (get_table_entry_id(uimage_arch, "CPU", name));
+}
+
+int genimg_get_type_id(const char *name)
+{
+ return (get_table_entry_id(uimage_type, "Image", name));
+}
+
+int genimg_get_comp_id(const char *name)
+{
+ return (get_table_entry_id(uimage_comp, "Compression", name));
+}
+
+int genimg_get_phase_id(const char *name)
+{
+ return get_table_entry_id(uimage_phase, "Phase", name);
+}
diff --git a/boot/prog_boot.c b/boot/prog_boot.c
new file mode 100644
index 00000000000..045554b93db
--- /dev/null
+++ b/boot/prog_boot.c
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <bootflow.h>
+#include <bootstd.h>
+#include <command.h>
+#include <dm.h>
+
+/*
+ * show_bootmeths() - List available bootmeths
+ *
+ * We could refactor this to use do_bootmeth_list() if more detail (or ordering)
+ * are needed
+ */
+static void show_bootmeths(void)
+{
+ struct udevice *dev;
+ struct uclass *uc;
+
+ printf("Bootmeths: ");
+ uclass_id_foreach_dev(UCLASS_BOOTMETH, dev, uc)
+ printf(" %s", dev->name);
+ printf("\n");
+}
+
+int bootstd_prog_boot(void)
+{
+ struct bootflow_iter iter;
+ struct bootflow bflow;
+ int ret, flags, i;
+
+ printf("Programmatic boot starting\n");
+ show_bootmeths();
+ flags = BOOTFLOWIF_HUNT | BOOTFLOWIF_SHOW | BOOTFLOWIF_SKIP_GLOBAL;
+
+ bootstd_clear_glob();
+ for (i = 0, ret = bootflow_scan_first(NULL, NULL, &iter, flags, &bflow);
+ i < 1000 && ret != -ENODEV;
+ i++, ret = bootflow_scan_next(&iter, &bflow)) {
+ if (!bflow.err)
+ bootflow_run_boot(&iter, &bflow);
+ bootflow_free(&bflow);
+ }
+
+ return -EFAULT;
+}
diff --git a/boot/pxe_utils.c b/boot/pxe_utils.c
new file mode 100644
index 00000000000..eb4d7723481
--- /dev/null
+++ b/boot/pxe_utils.c
@@ -0,0 +1,1653 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2010-2011 Calxeda, Inc.
+ * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
+ */
+
+#define LOG_CATEGORY LOGC_BOOT
+
+#include <bootflow.h>
+#include <command.h>
+#include <dm.h>
+#include <env.h>
+#include <image.h>
+#include <log.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <net.h>
+#include <fdt_support.h>
+#include <video.h>
+#include <linux/libfdt.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <errno.h>
+#include <linux/list.h>
+
+#include <rng.h>
+
+#include <splash.h>
+#include <asm/io.h>
+
+#include "menu.h"
+#include "cli.h"
+
+#include "pxe_utils.h"
+
+#define MAX_TFTP_PATH_LEN 512
+
+int pxe_get_file_size(ulong *sizep)
+{
+ const char *val;
+
+ val = from_env("filesize");
+ if (!val)
+ return -ENOENT;
+
+ if (strict_strtoul(val, 16, sizep) < 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+/**
+ * format_mac_pxe() - obtain a MAC address in the PXE format
+ *
+ * This produces a MAC-address string in the format for the current ethernet
+ * device:
+ *
+ * 01-aa-bb-cc-dd-ee-ff
+ *
+ * where aa-ff is the MAC address in hex
+ *
+ * @outbuf: Buffer to write string to
+ * @outbuf_len: length of buffer
+ * Return: 1 if OK, -ENOSPC if buffer is too small, -ENOENT is there is no
+ * current ethernet device
+ */
+int format_mac_pxe(char *outbuf, size_t outbuf_len)
+{
+ uchar ethaddr[6];
+
+ if (outbuf_len < 21) {
+ printf("outbuf is too small (%zd < 21)\n", outbuf_len);
+ return -ENOSPC;
+ }
+
+ if (!eth_env_get_enetaddr_by_index("eth", eth_get_dev_index(), ethaddr))
+ return -ENOENT;
+
+ sprintf(outbuf, "01-%02x-%02x-%02x-%02x-%02x-%02x",
+ ethaddr[0], ethaddr[1], ethaddr[2],
+ ethaddr[3], ethaddr[4], ethaddr[5]);
+
+ return 1;
+}
+
+/**
+ * get_relfile() - read a file relative to the PXE file
+ *
+ * As in pxelinux, paths to files referenced from files we retrieve are
+ * relative to the location of bootfile. get_relfile takes such a path and
+ * joins it with the bootfile path to get the full path to the target file. If
+ * the bootfile path is NULL, we use file_path as is.
+ *
+ * @ctx: PXE context
+ * @file_path: File path to read (relative to the PXE file)
+ * @file_addr: Address to load file to
+ * @filesizep: If not NULL, returns the file size in bytes
+ * Returns 1 for success, or < 0 on error
+ */
+static int get_relfile(struct pxe_context *ctx, const char *file_path,
+ unsigned long file_addr, enum bootflow_img_t type,
+ ulong *filesizep)
+{
+ size_t path_len;
+ char relfile[MAX_TFTP_PATH_LEN + 1];
+ char addr_buf[18];
+ ulong size;
+ int ret;
+
+ if (file_path[0] == '/' && ctx->allow_abs_path)
+ *relfile = '\0';
+ else
+ strncpy(relfile, ctx->bootdir, MAX_TFTP_PATH_LEN);
+
+ path_len = strlen(file_path) + strlen(relfile);
+
+ if (path_len > MAX_TFTP_PATH_LEN) {
+ printf("Base path too long (%s%s)\n", relfile, file_path);
+
+ return -ENAMETOOLONG;
+ }
+
+ strcat(relfile, file_path);
+
+ printf("Retrieving file: %s\n", relfile);
+
+ sprintf(addr_buf, "%lx", file_addr);
+
+ ret = ctx->getfile(ctx, relfile, addr_buf, type, &size);
+ if (ret < 0)
+ return log_msg_ret("get", ret);
+ if (filesizep)
+ *filesizep = size;
+
+ return 1;
+}
+
+int get_pxe_file(struct pxe_context *ctx, const char *file_path,
+ ulong file_addr)
+{
+ ulong size;
+ int err;
+ char *buf;
+
+ err = get_relfile(ctx, file_path, file_addr, BFI_EXTLINUX_CFG,
+ &size);
+ if (err < 0)
+ return err;
+
+ buf = map_sysmem(file_addr + size, 1);
+ *buf = '\0';
+ unmap_sysmem(buf);
+
+ return 1;
+}
+
+#define PXELINUX_DIR "pxelinux.cfg/"
+
+/**
+ * get_pxelinux_path() - Get a file in the pxelinux.cfg/ directory
+ *
+ * @ctx: PXE context
+ * @file: Filename to process (relative to pxelinux.cfg/)
+ * Returns 1 for success, -ENAMETOOLONG if the resulting path is too long.
+ * or other value < 0 on other error
+ */
+int get_pxelinux_path(struct pxe_context *ctx, const char *file,
+ unsigned long pxefile_addr_r)
+{
+ size_t base_len = strlen(PXELINUX_DIR);
+ char path[MAX_TFTP_PATH_LEN + 1];
+
+ if (base_len + strlen(file) > MAX_TFTP_PATH_LEN) {
+ printf("path (%s%s) too long, skipping\n",
+ PXELINUX_DIR, file);
+ return -ENAMETOOLONG;
+ }
+
+ sprintf(path, PXELINUX_DIR "%s", file);
+
+ return get_pxe_file(ctx, path, pxefile_addr_r);
+}
+
+/**
+ * get_relfile_envaddr() - read a file to an address in an env var
+ *
+ * Wrapper to make it easier to store the file at file_path in the location
+ * specified by envaddr_name. file_path will be joined to the bootfile path,
+ * if any is specified.
+ *
+ * @ctx: PXE context
+ * @file_path: File path to read (relative to the PXE file)
+ * @envaddr_name: Name of environment variable which contains the address to
+ * load to
+ * @type: File type
+ * @filesizep: Returns the file size in bytes
+ * Returns 1 on success, -ENOENT if @envaddr_name does not exist as an
+ * environment variable, -EINVAL if its format is not valid hex, or other
+ * value < 0 on other error
+ */
+static int get_relfile_envaddr(struct pxe_context *ctx, const char *file_path,
+ const char *envaddr_name,
+ enum bootflow_img_t type, ulong *filesizep)
+{
+ unsigned long file_addr;
+ char *envaddr;
+
+ envaddr = from_env(envaddr_name);
+ if (!envaddr)
+ return -ENOENT;
+
+ if (strict_strtoul(envaddr, 16, &file_addr) < 0)
+ return -EINVAL;
+
+ return get_relfile(ctx, file_path, file_addr, type, filesizep);
+}
+
+/**
+ * label_create() - crate a new PXE label
+ *
+ * Allocates memory for and initializes a pxe_label. This uses malloc, so the
+ * result must be free()'d to reclaim the memory.
+ *
+ * Returns a pointer to the label, or NULL if out of memory
+ */
+static struct pxe_label *label_create(void)
+{
+ struct pxe_label *label;
+
+ label = malloc(sizeof(struct pxe_label));
+ if (!label)
+ return NULL;
+
+ memset(label, 0, sizeof(struct pxe_label));
+
+ return label;
+}
+
+/**
+ * label_destroy() - free the memory used by a pxe_label
+ *
+ * This frees @label itself as well as memory used by its name,
+ * kernel, config, append, initrd, fdt, fdtdir and fdtoverlay members, if
+ * they're non-NULL.
+ *
+ * So - be sure to only use dynamically allocated memory for the members of
+ * the pxe_label struct, unless you want to clean it up first. These are
+ * currently only created by the pxe file parsing code.
+ *
+ * @label: Label to free
+ */
+static void label_destroy(struct pxe_label *label)
+{
+ free(label->name);
+ free(label->kernel_label);
+ free(label->kernel);
+ free(label->config);
+ free(label->append);
+ free(label->initrd);
+ free(label->fdt);
+ free(label->fdtdir);
+ free(label->fdtoverlays);
+ free(label);
+}
+
+/**
+ * label_print() - Print a label and its string members if they're defined
+ *
+ * This is passed as a callback to the menu code for displaying each
+ * menu entry.
+ *
+ * @data: Label to print (is cast to struct pxe_label *)
+ */
+static void label_print(void *data)
+{
+ struct pxe_label *label = data;
+ const char *c = label->menu ? label->menu : label->name;
+
+ printf("%s:\t%s\n", label->num, c);
+}
+
+/**
+ * label_localboot() - Boot a label that specified 'localboot'
+ *
+ * This requires that the 'localcmd' environment variable is defined. Its
+ * contents will be executed as U-Boot commands. If the label specified an
+ * 'append' line, its contents will be used to overwrite the contents of the
+ * 'bootargs' environment variable prior to running 'localcmd'.
+ *
+ * @label: Label to process
+ * Returns 1 on success or < 0 on error
+ */
+static int label_localboot(struct pxe_label *label)
+{
+ char *localcmd;
+
+ localcmd = from_env("localcmd");
+ if (!localcmd)
+ return -ENOENT;
+
+ if (label->append) {
+ char bootargs[CONFIG_SYS_CBSIZE];
+
+ cli_simple_process_macros(label->append, bootargs,
+ sizeof(bootargs));
+ env_set("bootargs", bootargs);
+ }
+
+ debug("running: %s\n", localcmd);
+
+ return run_command_list(localcmd, strlen(localcmd), 0);
+}
+
+/*
+ * label_boot_kaslrseed generate kaslrseed from hw rng
+ */
+
+static void label_boot_kaslrseed(void)
+{
+#if CONFIG_IS_ENABLED(DM_RNG)
+ ulong fdt_addr;
+ struct fdt_header *working_fdt;
+ int err;
+
+ /* Get the main fdt and map it */
+ fdt_addr = hextoul(env_get("fdt_addr_r"), NULL);
+ working_fdt = map_sysmem(fdt_addr, 0);
+ err = fdt_check_header(working_fdt);
+ if (err)
+ return;
+
+ /* add extra size for holding kaslr-seed */
+ /* err is new fdt size, 0 or negtive */
+ err = fdt_shrink_to_minimum(working_fdt, 512);
+ if (err <= 0)
+ return;
+
+ fdt_kaslrseed(working_fdt, true);
+#endif
+ return;
+}
+
+/**
+ * label_boot_fdtoverlay() - Loads fdt overlays specified in 'fdtoverlays'
+ * or 'devicetree-overlay'
+ *
+ * @ctx: PXE context
+ * @label: Label to process
+ */
+#ifdef CONFIG_OF_LIBFDT_OVERLAY
+static void label_boot_fdtoverlay(struct pxe_context *ctx,
+ struct pxe_label *label)
+{
+ char *fdtoverlay = label->fdtoverlays;
+ struct fdt_header *working_fdt;
+ char *fdtoverlay_addr_env;
+ ulong fdtoverlay_addr;
+ ulong fdt_addr;
+ int err;
+
+ /* Get the main fdt and map it */
+ fdt_addr = hextoul(env_get("fdt_addr_r"), NULL);
+ working_fdt = map_sysmem(fdt_addr, 0);
+ err = fdt_check_header(working_fdt);
+ if (err)
+ return;
+
+ /* Get the specific overlay loading address */
+ fdtoverlay_addr_env = env_get("fdtoverlay_addr_r");
+ if (!fdtoverlay_addr_env) {
+ printf("Invalid fdtoverlay_addr_r for loading overlays\n");
+ return;
+ }
+
+ fdtoverlay_addr = hextoul(fdtoverlay_addr_env, NULL);
+
+ /* Cycle over the overlay files and apply them in order */
+ do {
+ struct fdt_header *blob;
+ char *overlayfile;
+ char *end;
+ int len;
+
+ /* Drop leading spaces */
+ while (*fdtoverlay == ' ')
+ ++fdtoverlay;
+
+ /* Copy a single filename if multiple provided */
+ end = strstr(fdtoverlay, " ");
+ if (end) {
+ len = (int)(end - fdtoverlay);
+ overlayfile = malloc(len + 1);
+ strncpy(overlayfile, fdtoverlay, len);
+ overlayfile[len] = '\0';
+ } else
+ overlayfile = fdtoverlay;
+
+ if (!strlen(overlayfile))
+ goto skip_overlay;
+
+ /* Load overlay file */
+ err = get_relfile_envaddr(ctx, overlayfile, "fdtoverlay_addr_r",
+ (enum bootflow_img_t)IH_TYPE_FLATDT,
+ NULL);
+ if (err < 0) {
+ printf("Failed loading overlay %s\n", overlayfile);
+ goto skip_overlay;
+ }
+
+ /* Resize main fdt */
+ fdt_shrink_to_minimum(working_fdt, 8192);
+
+ blob = map_sysmem(fdtoverlay_addr, 0);
+ err = fdt_check_header(blob);
+ if (err) {
+ printf("Invalid overlay %s, skipping\n",
+ overlayfile);
+ goto skip_overlay;
+ }
+
+ err = fdt_overlay_apply_verbose(working_fdt, blob);
+ if (err) {
+ printf("Failed to apply overlay %s, skipping\n",
+ overlayfile);
+ goto skip_overlay;
+ }
+
+skip_overlay:
+ if (end)
+ free(overlayfile);
+ } while ((fdtoverlay = strstr(fdtoverlay, " ")));
+}
+#endif
+
+/**
+ * label_boot() - Boot according to the contents of a pxe_label
+ *
+ * If we can't boot for any reason, we return. A successful boot never
+ * returns.
+ *
+ * The kernel will be stored in the location given by the 'kernel_addr_r'
+ * environment variable.
+ *
+ * If the label specifies an initrd file, it will be stored in the location
+ * given by the 'ramdisk_addr_r' environment variable.
+ *
+ * If the label specifies an 'append' line, its contents will overwrite that
+ * of the 'bootargs' environment variable.
+ *
+ * @ctx: PXE context
+ * @label: Label to process
+ * Returns does not return on success, otherwise returns 0 if a localboot
+ * label was processed, or 1 on error
+ */
+static int label_boot(struct pxe_context *ctx, struct pxe_label *label)
+{
+ char *bootm_argv[] = { "bootm", NULL, NULL, NULL, NULL };
+ char *zboot_argv[] = { "zboot", NULL, "0", NULL, NULL };
+ char *kernel_addr = NULL;
+ char *initrd_addr_str = NULL;
+ char initrd_filesize[10];
+ char initrd_str[28];
+ char mac_str[29] = "";
+ char ip_str[68] = "";
+ char *fit_addr = NULL;
+ int bootm_argc = 2;
+ int zboot_argc = 3;
+ int len = 0;
+ ulong kernel_addr_r;
+ void *buf;
+
+ label_print(label);
+
+ label->attempted = 1;
+
+ if (label->localboot) {
+ if (label->localboot_val >= 0)
+ label_localboot(label);
+ return 0;
+ }
+
+ if (!label->kernel) {
+ printf("No kernel given, skipping %s\n",
+ label->name);
+ return 1;
+ }
+
+ if (get_relfile_envaddr(ctx, label->kernel, "kernel_addr_r",
+ (enum bootflow_img_t)IH_TYPE_KERNEL, NULL)
+ < 0) {
+ printf("Skipping %s for failure retrieving kernel\n",
+ label->name);
+ return 1;
+ }
+
+ kernel_addr = env_get("kernel_addr_r");
+ /* for FIT, append the configuration identifier */
+ if (label->config) {
+ int len = strlen(kernel_addr) + strlen(label->config) + 1;
+
+ fit_addr = malloc(len);
+ if (!fit_addr) {
+ printf("malloc fail (FIT address)\n");
+ return 1;
+ }
+ snprintf(fit_addr, len, "%s%s", kernel_addr, label->config);
+ kernel_addr = fit_addr;
+ }
+
+ /* For FIT, the label can be identical to kernel one */
+ if (label->initrd && !strcmp(label->kernel_label, label->initrd)) {
+ initrd_addr_str = kernel_addr;
+ } else if (label->initrd) {
+ ulong size;
+ if (get_relfile_envaddr(ctx, label->initrd, "ramdisk_addr_r",
+ (enum bootflow_img_t)IH_TYPE_RAMDISK,
+ &size) < 0) {
+ printf("Skipping %s for failure retrieving initrd\n",
+ label->name);
+ goto cleanup;
+ }
+ strcpy(initrd_filesize, simple_xtoa(size));
+ initrd_addr_str = env_get("ramdisk_addr_r");
+ size = snprintf(initrd_str, sizeof(initrd_str), "%s:%lx",
+ initrd_addr_str, size);
+ if (size >= sizeof(initrd_str))
+ goto cleanup;
+ }
+
+ if (label->ipappend & 0x1) {
+ sprintf(ip_str, " ip=%s:%s:%s:%s",
+ env_get("ipaddr"), env_get("serverip"),
+ env_get("gatewayip"), env_get("netmask"));
+ }
+
+ if (IS_ENABLED(CONFIG_CMD_NET)) {
+ if (label->ipappend & 0x2) {
+ int err;
+
+ strcpy(mac_str, " BOOTIF=");
+ err = format_mac_pxe(mac_str + 8, sizeof(mac_str) - 8);
+ if (err < 0)
+ mac_str[0] = '\0';
+ }
+ }
+
+ if ((label->ipappend & 0x3) || label->append) {
+ char bootargs[CONFIG_SYS_CBSIZE] = "";
+ char finalbootargs[CONFIG_SYS_CBSIZE];
+
+ if (strlen(label->append ?: "") +
+ strlen(ip_str) + strlen(mac_str) + 1 > sizeof(bootargs)) {
+ printf("bootarg overflow %zd+%zd+%zd+1 > %zd\n",
+ strlen(label->append ?: ""),
+ strlen(ip_str), strlen(mac_str),
+ sizeof(bootargs));
+ goto cleanup;
+ }
+
+ if (label->append)
+ strncpy(bootargs, label->append, sizeof(bootargs));
+
+ strcat(bootargs, ip_str);
+ strcat(bootargs, mac_str);
+
+ cli_simple_process_macros(bootargs, finalbootargs,
+ sizeof(finalbootargs));
+ env_set("bootargs", finalbootargs);
+ printf("append: %s\n", finalbootargs);
+ }
+
+ /*
+ * fdt usage is optional:
+ * It handles the following scenarios.
+ *
+ * Scenario 1: If fdt_addr_r specified and "fdt" or "fdtdir" label is
+ * defined in pxe file, retrieve fdt blob from server. Pass fdt_addr_r to
+ * bootm, and adjust argc appropriately.
+ *
+ * If retrieve fails and no exact fdt blob is specified in pxe file with
+ * "fdt" label, try Scenario 2.
+ *
+ * Scenario 2: If there is an fdt_addr specified, pass it along to
+ * bootm, and adjust argc appropriately.
+ *
+ * Scenario 3: If there is an fdtcontroladdr specified, pass it along to
+ * bootm, and adjust argc appropriately, unless the image type is fitImage.
+ *
+ * Scenario 4: fdt blob is not available.
+ */
+ bootm_argv[3] = env_get("fdt_addr_r");
+
+ /* For FIT, the label can be identical to kernel one */
+ if (label->fdt && !strcmp(label->kernel_label, label->fdt)) {
+ bootm_argv[3] = kernel_addr;
+ /* if fdt label is defined then get fdt from server */
+ } else if (bootm_argv[3]) {
+ char *fdtfile = NULL;
+ char *fdtfilefree = NULL;
+
+ if (label->fdt) {
+ if (IS_ENABLED(CONFIG_SUPPORT_PASSING_ATAGS)) {
+ if (strcmp("-", label->fdt))
+ fdtfile = label->fdt;
+ } else {
+ fdtfile = label->fdt;
+ }
+ } else if (label->fdtdir) {
+ char *f1, *f2, *f3, *f4, *slash;
+
+ f1 = env_get("fdtfile");
+ if (f1) {
+ f2 = "";
+ f3 = "";
+ f4 = "";
+ } else {
+ /*
+ * For complex cases where this code doesn't
+ * generate the correct filename, the board
+ * code should set $fdtfile during early boot,
+ * or the boot scripts should set $fdtfile
+ * before invoking "pxe" or "sysboot".
+ */
+ f1 = env_get("soc");
+ f2 = "-";
+ f3 = env_get("board");
+ f4 = ".dtb";
+ if (!f1) {
+ f1 = "";
+ f2 = "";
+ }
+ if (!f3) {
+ f2 = "";
+ f3 = "";
+ }
+ }
+
+ len = strlen(label->fdtdir);
+ if (!len)
+ slash = "./";
+ else if (label->fdtdir[len - 1] != '/')
+ slash = "/";
+ else
+ slash = "";
+
+ len = strlen(label->fdtdir) + strlen(slash) +
+ strlen(f1) + strlen(f2) + strlen(f3) +
+ strlen(f4) + 1;
+ fdtfilefree = malloc(len);
+ if (!fdtfilefree) {
+ printf("malloc fail (FDT filename)\n");
+ goto cleanup;
+ }
+
+ snprintf(fdtfilefree, len, "%s%s%s%s%s%s",
+ label->fdtdir, slash, f1, f2, f3, f4);
+ fdtfile = fdtfilefree;
+ }
+
+ if (fdtfile) {
+ int err = get_relfile_envaddr(ctx, fdtfile,
+ "fdt_addr_r",
+ (enum bootflow_img_t)IH_TYPE_FLATDT, NULL);
+
+ free(fdtfilefree);
+ if (err < 0) {
+ bootm_argv[3] = NULL;
+
+ if (label->fdt) {
+ printf("Skipping %s for failure retrieving FDT\n",
+ label->name);
+ goto cleanup;
+ }
+
+ if (label->fdtdir) {
+ printf("Skipping fdtdir %s for failure retrieving dts\n",
+ label->fdtdir);
+ }
+ }
+
+ if (label->kaslrseed)
+ label_boot_kaslrseed();
+
+#ifdef CONFIG_OF_LIBFDT_OVERLAY
+ if (label->fdtoverlays)
+ label_boot_fdtoverlay(ctx, label);
+#endif
+ } else {
+ bootm_argv[3] = NULL;
+ }
+ }
+
+ bootm_argv[1] = kernel_addr;
+ zboot_argv[1] = kernel_addr;
+
+ if (initrd_addr_str) {
+ bootm_argv[2] = initrd_str;
+ bootm_argc = 3;
+
+ zboot_argv[3] = initrd_addr_str;
+ zboot_argv[4] = initrd_filesize;
+ zboot_argc = 5;
+ }
+
+ if (!bootm_argv[3]) {
+ if (IS_ENABLED(CONFIG_SUPPORT_PASSING_ATAGS)) {
+ if (strcmp("-", label->fdt))
+ bootm_argv[3] = env_get("fdt_addr");
+ } else {
+ bootm_argv[3] = env_get("fdt_addr");
+ }
+ }
+
+ kernel_addr_r = genimg_get_kernel_addr(kernel_addr);
+ buf = map_sysmem(kernel_addr_r, 0);
+
+ if (!bootm_argv[3] && genimg_get_format(buf) != IMAGE_FORMAT_FIT) {
+ if (IS_ENABLED(CONFIG_SUPPORT_PASSING_ATAGS)) {
+ if (strcmp("-", label->fdt))
+ bootm_argv[3] = env_get("fdtcontroladdr");
+ } else {
+ bootm_argv[3] = env_get("fdtcontroladdr");
+ }
+ }
+
+ if (bootm_argv[3]) {
+ if (!bootm_argv[2])
+ bootm_argv[2] = "-";
+ bootm_argc = 4;
+ }
+
+ /* Try bootm for legacy and FIT format image */
+ if (genimg_get_format(buf) != IMAGE_FORMAT_INVALID &&
+ IS_ENABLED(CONFIG_CMD_BOOTM)) {
+ log_debug("using bootm\n");
+ do_bootm(ctx->cmdtp, 0, bootm_argc, bootm_argv);
+ /* Try booting an AArch64 Linux kernel image */
+ } else if (IS_ENABLED(CONFIG_CMD_BOOTI)) {
+ log_debug("using booti\n");
+ do_booti(ctx->cmdtp, 0, bootm_argc, bootm_argv);
+ /* Try booting a Image */
+ } else if (IS_ENABLED(CONFIG_CMD_BOOTZ)) {
+ log_debug("using bootz\n");
+ do_bootz(ctx->cmdtp, 0, bootm_argc, bootm_argv);
+ /* Try booting an x86_64 Linux kernel image */
+ } else if (IS_ENABLED(CONFIG_CMD_ZBOOT)) {
+ log_debug("using zboot\n");
+ do_zboot_parent(ctx->cmdtp, 0, zboot_argc, zboot_argv, NULL);
+ }
+
+ unmap_sysmem(buf);
+
+cleanup:
+ free(fit_addr);
+
+ return 1;
+}
+
+/** enum token_type - Tokens for the pxe file parser */
+enum token_type {
+ T_EOL,
+ T_STRING,
+ T_EOF,
+ T_MENU,
+ T_TITLE,
+ T_TIMEOUT,
+ T_LABEL,
+ T_KERNEL,
+ T_LINUX,
+ T_APPEND,
+ T_INITRD,
+ T_LOCALBOOT,
+ T_DEFAULT,
+ T_PROMPT,
+ T_INCLUDE,
+ T_FDT,
+ T_FDTDIR,
+ T_FDTOVERLAYS,
+ T_ONTIMEOUT,
+ T_IPAPPEND,
+ T_BACKGROUND,
+ T_KASLRSEED,
+ T_FALLBACK,
+ T_INVALID
+};
+
+/** struct token - token - given by a value and a type */
+struct token {
+ char *val;
+ enum token_type type;
+};
+
+/* Keywords recognized */
+static const struct token keywords[] = {
+ {"menu", T_MENU},
+ {"title", T_TITLE},
+ {"timeout", T_TIMEOUT},
+ {"default", T_DEFAULT},
+ {"prompt", T_PROMPT},
+ {"label", T_LABEL},
+ {"kernel", T_KERNEL},
+ {"linux", T_LINUX},
+ {"localboot", T_LOCALBOOT},
+ {"append", T_APPEND},
+ {"initrd", T_INITRD},
+ {"include", T_INCLUDE},
+ {"devicetree", T_FDT},
+ {"fdt", T_FDT},
+ {"devicetreedir", T_FDTDIR},
+ {"fdtdir", T_FDTDIR},
+ {"fdtoverlays", T_FDTOVERLAYS},
+ {"devicetree-overlay", T_FDTOVERLAYS},
+ {"ontimeout", T_ONTIMEOUT,},
+ {"ipappend", T_IPAPPEND,},
+ {"background", T_BACKGROUND,},
+ {"kaslrseed", T_KASLRSEED,},
+ {"fallback", T_FALLBACK,},
+ {NULL, T_INVALID}
+};
+
+/**
+ * enum lex_state - lexer state
+ *
+ * Since pxe(linux) files don't have a token to identify the start of a
+ * literal, we have to keep track of when we're in a state where a literal is
+ * expected vs when we're in a state a keyword is expected.
+ */
+enum lex_state {
+ L_NORMAL = 0,
+ L_KEYWORD,
+ L_SLITERAL
+};
+
+/**
+ * get_string() - retrieves a string from *p and stores it as a token in *t.
+ *
+ * This is used for scanning both string literals and keywords.
+ *
+ * Characters from *p are copied into t-val until a character equal to
+ * delim is found, or a NUL byte is reached. If delim has the special value of
+ * ' ', any whitespace character will be used as a delimiter.
+ *
+ * If lower is unequal to 0, uppercase characters will be converted to
+ * lowercase in the result. This is useful to make keywords case
+ * insensitive.
+ *
+ * The location of *p is updated to point to the first character after the end
+ * of the token - the ending delimiter.
+ *
+ * Memory for t->val is allocated using malloc and must be free()'d to reclaim
+ * it.
+ *
+ * @p: Points to a pointer to the current position in the input being processed.
+ * Updated to point at the first character after the current token
+ * @t: Pointers to a token to fill in
+ * @delim: Delimiter character to look for, either newline or space
+ * @lower: true to convert the string to lower case when storing
+ * Returns the new value of t->val, on success, NULL if out of memory
+ */
+static char *get_string(char **p, struct token *t, char delim, int lower)
+{
+ char *b, *e;
+ size_t len, i;
+
+ /*
+ * b and e both start at the beginning of the input stream.
+ *
+ * e is incremented until we find the ending delimiter, or a NUL byte
+ * is reached. Then, we take e - b to find the length of the token.
+ */
+ b = *p;
+ e = *p;
+ while (*e) {
+ if ((delim == ' ' && isspace(*e)) || delim == *e)
+ break;
+ e++;
+ }
+
+ len = e - b;
+
+ /*
+ * Allocate memory to hold the string, and copy it in, converting
+ * characters to lowercase if lower is != 0.
+ */
+ t->val = malloc(len + 1);
+ if (!t->val)
+ return NULL;
+
+ for (i = 0; i < len; i++, b++) {
+ if (lower)
+ t->val[i] = tolower(*b);
+ else
+ t->val[i] = *b;
+ }
+
+ t->val[len] = '\0';
+
+ /* Update *p so the caller knows where to continue scanning */
+ *p = e;
+ t->type = T_STRING;
+
+ return t->val;
+}
+
+/**
+ * get_keyword() - Populate a keyword token with a type and value
+ *
+ * Updates the ->type field based on the keyword string in @val
+ * @t: Token to populate
+ */
+static void get_keyword(struct token *t)
+{
+ int i;
+
+ for (i = 0; keywords[i].val; i++) {
+ if (!strcmp(t->val, keywords[i].val)) {
+ t->type = keywords[i].type;
+ break;
+ }
+ }
+}
+
+/**
+ * get_token() - Get the next token
+ *
+ * We have to keep track of which state we're in to know if we're looking to get
+ * a string literal or a keyword.
+ *
+ * @p: Points to a pointer to the current position in the input being processed.
+ * Updated to point at the first character after the current token
+ */
+static void get_token(char **p, struct token *t, enum lex_state state)
+{
+ char *c = *p;
+
+ t->type = T_INVALID;
+
+ /* eat non EOL whitespace */
+ while (isblank(*c))
+ c++;
+
+ /*
+ * eat comments. note that string literals can't begin with #, but
+ * can contain a # after their first character.
+ */
+ if (*c == '#') {
+ while (*c && *c != '\n')
+ c++;
+ }
+
+ if (*c == '\n') {
+ t->type = T_EOL;
+ c++;
+ } else if (*c == '\0') {
+ t->type = T_EOF;
+ c++;
+ } else if (state == L_SLITERAL) {
+ get_string(&c, t, '\n', 0);
+ } else if (state == L_KEYWORD) {
+ /*
+ * when we expect a keyword, we first get the next string
+ * token delimited by whitespace, and then check if it
+ * matches a keyword in our keyword list. if it does, it's
+ * converted to a keyword token of the appropriate type, and
+ * if not, it remains a string token.
+ */
+ get_string(&c, t, ' ', 1);
+ get_keyword(t);
+ }
+
+ *p = c;
+}
+
+/**
+ * eol_or_eof() - Find end of line
+ *
+ * Increment *c until we get to the end of the current line, or EOF
+ *
+ * @c: Points to a pointer to the current position in the input being processed.
+ * Updated to point at the first character after the current token
+ */
+static void eol_or_eof(char **c)
+{
+ while (**c && **c != '\n')
+ (*c)++;
+}
+
+/*
+ * All of these parse_* functions share some common behavior.
+ *
+ * They finish with *c pointing after the token they parse, and return 1 on
+ * success, or < 0 on error.
+ */
+
+/*
+ * Parse a string literal and store a pointer it at *dst. String literals
+ * terminate at the end of the line.
+ */
+static int parse_sliteral(char **c, char **dst)
+{
+ struct token t;
+ char *s = *c;
+
+ get_token(c, &t, L_SLITERAL);
+
+ if (t.type != T_STRING) {
+ printf("Expected string literal: %.*s\n", (int)(*c - s), s);
+ return -EINVAL;
+ }
+
+ *dst = t.val;
+
+ return 1;
+}
+
+/*
+ * Parse a base 10 (unsigned) integer and store it at *dst.
+ */
+static int parse_integer(char **c, int *dst)
+{
+ struct token t;
+ char *s = *c;
+
+ get_token(c, &t, L_SLITERAL);
+ if (t.type != T_STRING) {
+ printf("Expected string: %.*s\n", (int)(*c - s), s);
+ return -EINVAL;
+ }
+
+ *dst = simple_strtol(t.val, NULL, 10);
+
+ free(t.val);
+
+ return 1;
+}
+
+static int parse_pxefile_top(struct pxe_context *ctx, char *p, ulong base,
+ struct pxe_menu *cfg, int nest_level);
+
+/*
+ * Parse an include statement, and retrieve and parse the file it mentions.
+ *
+ * base should point to a location where it's safe to store the file, and
+ * nest_level should indicate how many nested includes have occurred. For this
+ * include, nest_level has already been incremented and doesn't need to be
+ * incremented here.
+ */
+static int handle_include(struct pxe_context *ctx, char **c, unsigned long base,
+ struct pxe_menu *cfg, int nest_level)
+{
+ char *include_path;
+ char *s = *c;
+ int err;
+ char *buf;
+ int ret;
+
+ err = parse_sliteral(c, &include_path);
+ if (err < 0) {
+ printf("Expected include path: %.*s\n", (int)(*c - s), s);
+ return err;
+ }
+
+ err = get_pxe_file(ctx, include_path, base);
+ if (err < 0) {
+ printf("Couldn't retrieve %s\n", include_path);
+ return err;
+ }
+
+ buf = map_sysmem(base, 0);
+ ret = parse_pxefile_top(ctx, buf, base, cfg, nest_level);
+ unmap_sysmem(buf);
+
+ return ret;
+}
+
+/*
+ * Parse lines that begin with 'menu'.
+ *
+ * base and nest are provided to handle the 'menu include' case.
+ *
+ * base should point to a location where it's safe to store the included file.
+ *
+ * nest_level should be 1 when parsing the top level pxe file, 2 when parsing
+ * a file it includes, 3 when parsing a file included by that file, and so on.
+ */
+static int parse_menu(struct pxe_context *ctx, char **c, struct pxe_menu *cfg,
+ unsigned long base, int nest_level)
+{
+ struct token t;
+ char *s = *c;
+ int err = 0;
+
+ get_token(c, &t, L_KEYWORD);
+
+ switch (t.type) {
+ case T_TITLE:
+ err = parse_sliteral(c, &cfg->title);
+
+ break;
+
+ case T_INCLUDE:
+ err = handle_include(ctx, c, base, cfg, nest_level + 1);
+ break;
+
+ case T_BACKGROUND:
+ err = parse_sliteral(c, &cfg->bmp);
+ break;
+
+ default:
+ printf("Ignoring malformed menu command: %.*s\n",
+ (int)(*c - s), s);
+ }
+ if (err < 0)
+ return err;
+
+ eol_or_eof(c);
+
+ return 1;
+}
+
+/*
+ * Handles parsing a 'menu line' when we're parsing a label.
+ */
+static int parse_label_menu(char **c, struct pxe_menu *cfg,
+ struct pxe_label *label)
+{
+ struct token t;
+ char *s;
+
+ s = *c;
+
+ get_token(c, &t, L_KEYWORD);
+
+ switch (t.type) {
+ case T_DEFAULT:
+ if (!cfg->default_label)
+ cfg->default_label = strdup(label->name);
+
+ if (!cfg->default_label)
+ return -ENOMEM;
+
+ break;
+ case T_LABEL:
+ parse_sliteral(c, &label->menu);
+ break;
+ default:
+ printf("Ignoring malformed menu command: %.*s\n",
+ (int)(*c - s), s);
+ }
+
+ eol_or_eof(c);
+
+ return 0;
+}
+
+/*
+ * Handles parsing a 'kernel' label.
+ * expecting "filename" or "<fit_filename>#cfg"
+ */
+static int parse_label_kernel(char **c, struct pxe_label *label)
+{
+ char *s;
+ int err;
+
+ err = parse_sliteral(c, &label->kernel);
+ if (err < 0)
+ return err;
+
+ /* copy the kernel label to compare with FDT / INITRD when FIT is used */
+ label->kernel_label = strdup(label->kernel);
+ if (!label->kernel_label)
+ return -ENOMEM;
+
+ s = strstr(label->kernel, "#");
+ if (!s)
+ return 1;
+
+ label->config = strdup(s);
+ if (!label->config)
+ return -ENOMEM;
+
+ *s = 0;
+
+ return 1;
+}
+
+/*
+ * Parses a label and adds it to the list of labels for a menu.
+ *
+ * A label ends when we either get to the end of a file, or
+ * get some input we otherwise don't have a handler defined
+ * for.
+ *
+ */
+static int parse_label(char **c, struct pxe_menu *cfg)
+{
+ struct token t;
+ int len;
+ char *s = *c;
+ struct pxe_label *label;
+ int err;
+
+ label = label_create();
+ if (!label)
+ return -ENOMEM;
+
+ err = parse_sliteral(c, &label->name);
+ if (err < 0) {
+ printf("Expected label name: %.*s\n", (int)(*c - s), s);
+ label_destroy(label);
+ return -EINVAL;
+ }
+
+ list_add_tail(&label->list, &cfg->labels);
+
+ while (1) {
+ s = *c;
+ get_token(c, &t, L_KEYWORD);
+
+ err = 0;
+ switch (t.type) {
+ case T_MENU:
+ err = parse_label_menu(c, cfg, label);
+ break;
+
+ case T_KERNEL:
+ case T_LINUX:
+ err = parse_label_kernel(c, label);
+ break;
+
+ case T_APPEND:
+ err = parse_sliteral(c, &label->append);
+ if (label->initrd)
+ break;
+ s = strstr(label->append, "initrd=");
+ if (!s)
+ break;
+ s += 7;
+ len = (int)(strchr(s, ' ') - s);
+ label->initrd = malloc(len + 1);
+ strncpy(label->initrd, s, len);
+ label->initrd[len] = '\0';
+
+ break;
+
+ case T_INITRD:
+ if (!label->initrd)
+ err = parse_sliteral(c, &label->initrd);
+ break;
+
+ case T_FDT:
+ if (!label->fdt)
+ err = parse_sliteral(c, &label->fdt);
+ break;
+
+ case T_FDTDIR:
+ if (!label->fdtdir)
+ err = parse_sliteral(c, &label->fdtdir);
+ break;
+
+ case T_FDTOVERLAYS:
+ if (!label->fdtoverlays)
+ err = parse_sliteral(c, &label->fdtoverlays);
+ break;
+
+ case T_LOCALBOOT:
+ label->localboot = 1;
+ err = parse_integer(c, &label->localboot_val);
+ break;
+
+ case T_IPAPPEND:
+ err = parse_integer(c, &label->ipappend);
+ break;
+
+ case T_KASLRSEED:
+ label->kaslrseed = 1;
+ break;
+
+ case T_EOL:
+ break;
+
+ default:
+ /*
+ * put the token back! we don't want it - it's the end
+ * of a label and whatever token this is, it's
+ * something for the menu level context to handle.
+ */
+ *c = s;
+ return 1;
+ }
+
+ if (err < 0)
+ return err;
+ }
+}
+
+/*
+ * This 16 comes from the limit pxelinux imposes on nested includes.
+ *
+ * There is no reason at all we couldn't do more, but some limit helps prevent
+ * infinite (until crash occurs) recursion if a file tries to include itself.
+ */
+#define MAX_NEST_LEVEL 16
+
+/*
+ * Entry point for parsing a menu file. nest_level indicates how many times
+ * we've nested in includes. It will be 1 for the top level menu file.
+ *
+ * Returns 1 on success, < 0 on error.
+ */
+static int parse_pxefile_top(struct pxe_context *ctx, char *p, unsigned long base,
+ struct pxe_menu *cfg, int nest_level)
+{
+ struct token t;
+ char *s, *b, *label_name;
+ int err;
+
+ b = p;
+
+ if (nest_level > MAX_NEST_LEVEL) {
+ printf("Maximum nesting (%d) exceeded\n", MAX_NEST_LEVEL);
+ return -EMLINK;
+ }
+
+ while (1) {
+ s = p;
+
+ get_token(&p, &t, L_KEYWORD);
+
+ err = 0;
+ switch (t.type) {
+ case T_MENU:
+ cfg->prompt = 1;
+ err = parse_menu(ctx, &p, cfg,
+ base + ALIGN(strlen(b) + 1, 4),
+ nest_level);
+ break;
+
+ case T_TIMEOUT:
+ err = parse_integer(&p, &cfg->timeout);
+ break;
+
+ case T_LABEL:
+ err = parse_label(&p, cfg);
+ break;
+
+ case T_DEFAULT:
+ case T_ONTIMEOUT:
+ err = parse_sliteral(&p, &label_name);
+
+ if (err >= 0 && label_name) {
+ if (cfg->default_label)
+ free(cfg->default_label);
+
+ cfg->default_label = label_name;
+ }
+
+ break;
+
+ case T_FALLBACK:
+ err = parse_sliteral(&p, &label_name);
+
+ if (err >= 0 && label_name) {
+ if (cfg->fallback_label)
+ free(cfg->fallback_label);
+
+ cfg->fallback_label = label_name;
+ }
+
+ break;
+
+ case T_INCLUDE:
+ err = handle_include(ctx, &p,
+ base + ALIGN(strlen(b), 4), cfg,
+ nest_level + 1);
+ break;
+
+ case T_PROMPT:
+ err = parse_integer(&p, &cfg->prompt);
+ // Do not fail if prompt configuration is undefined
+ if (err < 0)
+ eol_or_eof(&p);
+ break;
+
+ case T_EOL:
+ break;
+
+ case T_EOF:
+ return 1;
+
+ default:
+ printf("Ignoring unknown command: %.*s\n",
+ (int)(p - s), s);
+ eol_or_eof(&p);
+ }
+
+ if (err < 0)
+ return err;
+ }
+}
+
+/*
+ */
+void destroy_pxe_menu(struct pxe_menu *cfg)
+{
+ struct list_head *pos, *n;
+ struct pxe_label *label;
+
+ free(cfg->title);
+ free(cfg->default_label);
+ free(cfg->fallback_label);
+
+ list_for_each_safe(pos, n, &cfg->labels) {
+ label = list_entry(pos, struct pxe_label, list);
+
+ label_destroy(label);
+ }
+
+ free(cfg);
+}
+
+struct pxe_menu *parse_pxefile(struct pxe_context *ctx, unsigned long menucfg)
+{
+ struct pxe_menu *cfg;
+ char *buf;
+ int r;
+
+ cfg = malloc(sizeof(struct pxe_menu));
+ if (!cfg)
+ return NULL;
+
+ memset(cfg, 0, sizeof(struct pxe_menu));
+
+ INIT_LIST_HEAD(&cfg->labels);
+
+ buf = map_sysmem(menucfg, 0);
+ r = parse_pxefile_top(ctx, buf, menucfg, cfg, 1);
+
+ if (ctx->use_fallback) {
+ if (cfg->fallback_label) {
+ printf("Setting use of fallback\n");
+ cfg->default_label = cfg->fallback_label;
+ } else {
+ printf("Selected fallback option, but not set\n");
+ }
+ }
+
+ unmap_sysmem(buf);
+ if (r < 0) {
+ destroy_pxe_menu(cfg);
+ return NULL;
+ }
+
+ return cfg;
+}
+
+/*
+ * Converts a pxe_menu struct into a menu struct for use with U-Boot's generic
+ * menu code.
+ */
+static struct menu *pxe_menu_to_menu(struct pxe_menu *cfg)
+{
+ struct pxe_label *label;
+ struct list_head *pos;
+ struct menu *m;
+ char *label_override;
+ int err;
+ int i = 1;
+ char *default_num = NULL;
+ char *override_num = NULL;
+
+ /*
+ * Create a menu and add items for all the labels.
+ */
+ m = menu_create(cfg->title, DIV_ROUND_UP(cfg->timeout, 10),
+ cfg->prompt, NULL, label_print, NULL, NULL, NULL);
+ if (!m)
+ return NULL;
+
+ label_override = env_get("pxe_label_override");
+
+ list_for_each(pos, &cfg->labels) {
+ label = list_entry(pos, struct pxe_label, list);
+
+ sprintf(label->num, "%d", i++);
+ if (menu_item_add(m, label->num, label) != 1) {
+ menu_destroy(m);
+ return NULL;
+ }
+ if (cfg->default_label &&
+ (strcmp(label->name, cfg->default_label) == 0))
+ default_num = label->num;
+ if (label_override && !strcmp(label->name, label_override))
+ override_num = label->num;
+ }
+
+ if (label_override) {
+ if (override_num)
+ default_num = override_num;
+ else
+ printf("Missing override pxe label: %s\n",
+ label_override);
+ }
+
+ /*
+ * After we've created items for each label in the menu, set the
+ * menu's default label if one was specified.
+ */
+ if (default_num) {
+ err = menu_default_set(m, default_num);
+ if (err != 1) {
+ if (err != -ENOENT) {
+ menu_destroy(m);
+ return NULL;
+ }
+
+ printf("Missing default: %s\n", cfg->default_label);
+ }
+ }
+
+ return m;
+}
+
+/*
+ * Try to boot any labels we have yet to attempt to boot.
+ */
+static void boot_unattempted_labels(struct pxe_context *ctx,
+ struct pxe_menu *cfg)
+{
+ struct list_head *pos;
+ struct pxe_label *label;
+
+ list_for_each(pos, &cfg->labels) {
+ label = list_entry(pos, struct pxe_label, list);
+
+ if (!label->attempted)
+ label_boot(ctx, label);
+ }
+}
+
+void handle_pxe_menu(struct pxe_context *ctx, struct pxe_menu *cfg)
+{
+ void *choice;
+ struct menu *m;
+ int err;
+
+ if (IS_ENABLED(CONFIG_CMD_BMP)) {
+ /* display BMP if available */
+ if (cfg->bmp) {
+ if (get_relfile(ctx, cfg->bmp, image_load_addr,
+ BFI_LOGO, NULL)) {
+#if defined(CONFIG_VIDEO)
+ struct udevice *dev;
+
+ err = uclass_first_device_err(UCLASS_VIDEO, &dev);
+ if (!err)
+ video_clear(dev);
+#endif
+ bmp_display(image_load_addr,
+ BMP_ALIGN_CENTER, BMP_ALIGN_CENTER);
+ } else {
+ printf("Skipping background bmp %s for failure\n",
+ cfg->bmp);
+ }
+ }
+ }
+
+ m = pxe_menu_to_menu(cfg);
+ if (!m)
+ return;
+
+ err = menu_get_choice(m, &choice);
+ menu_destroy(m);
+
+ /*
+ * err == 1 means we got a choice back from menu_get_choice.
+ *
+ * err == -ENOENT if the menu was setup to select the default but no
+ * default was set. in that case, we should continue trying to boot
+ * labels that haven't been attempted yet.
+ *
+ * otherwise, the user interrupted or there was some other error and
+ * we give up.
+ */
+
+ if (err == 1) {
+ err = label_boot(ctx, choice);
+ if (!err)
+ return;
+ } else if (err != -ENOENT) {
+ return;
+ }
+
+ boot_unattempted_labels(ctx, cfg);
+}
+
+int pxe_setup_ctx(struct pxe_context *ctx, struct cmd_tbl *cmdtp,
+ pxe_getfile_func getfile, void *userdata,
+ bool allow_abs_path, const char *bootfile, bool use_ipv6,
+ bool use_fallback)
+{
+ const char *last_slash;
+ size_t path_len = 0;
+
+ memset(ctx, '\0', sizeof(*ctx));
+ ctx->cmdtp = cmdtp;
+ ctx->getfile = getfile;
+ ctx->userdata = userdata;
+ ctx->allow_abs_path = allow_abs_path;
+ ctx->use_ipv6 = use_ipv6;
+ ctx->use_fallback = use_fallback;
+
+ /* figure out the boot directory, if there is one */
+ if (bootfile && strlen(bootfile) >= MAX_TFTP_PATH_LEN)
+ return -ENOSPC;
+ ctx->bootdir = strdup(bootfile ? bootfile : "");
+ if (!ctx->bootdir)
+ return -ENOMEM;
+
+ if (bootfile) {
+ last_slash = strrchr(bootfile, '/');
+ if (last_slash)
+ path_len = (last_slash - bootfile) + 1;
+ }
+ ctx->bootdir[path_len] = '\0';
+
+ return 0;
+}
+
+void pxe_destroy_ctx(struct pxe_context *ctx)
+{
+ free(ctx->bootdir);
+}
+
+int pxe_process(struct pxe_context *ctx, ulong pxefile_addr_r, bool prompt)
+{
+ struct pxe_menu *cfg;
+
+ cfg = parse_pxefile(ctx, pxefile_addr_r);
+ if (!cfg) {
+ printf("Error parsing config file\n");
+ return 1;
+ }
+
+ if (prompt)
+ cfg->prompt = 1;
+
+ handle_pxe_menu(ctx, cfg);
+
+ destroy_pxe_menu(cfg);
+
+ return 0;
+}
diff --git a/boot/scene.c b/boot/scene.c
new file mode 100644
index 00000000000..fa8f540bfb0
--- /dev/null
+++ b/boot/scene.c
@@ -0,0 +1,1213 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Implementation of a scene, a collection of text/image/menu items in an expo
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY LOGC_EXPO
+
+#include <alist.h>
+#include <dm.h>
+#include <expo.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <menu.h>
+#include <video.h>
+#include <video_console.h>
+#include <linux/input.h>
+#include "scene_internal.h"
+
+int scene_new(struct expo *exp, const char *name, uint id, struct scene **scnp)
+{
+ struct scene *scn;
+
+ scn = calloc(1, sizeof(struct scene));
+ if (!scn)
+ return log_msg_ret("expo", -ENOMEM);
+ scn->name = strdup(name);
+ if (!scn->name) {
+ free(scn);
+ return log_msg_ret("name", -ENOMEM);
+ }
+
+ if (!abuf_init_size(&scn->buf, EXPO_MAX_CHARS + 1)) {
+ free(scn->name);
+ free(scn);
+ return log_msg_ret("buf", -ENOMEM);
+ }
+ abuf_init(&scn->entry_save);
+
+ INIT_LIST_HEAD(&scn->obj_head);
+ scn->id = resolve_id(exp, id);
+ scn->expo = exp;
+ list_add_tail(&scn->sibling, &exp->scene_head);
+
+ *scnp = scn;
+
+ return scn->id;
+}
+
+void scene_obj_destroy(struct scene_obj *obj)
+{
+ if (obj->type == SCENEOBJT_MENU)
+ scene_menu_destroy((struct scene_obj_menu *)obj);
+ free(obj->name);
+ free(obj);
+}
+
+void scene_destroy(struct scene *scn)
+{
+ struct scene_obj *obj, *next;
+
+ list_for_each_entry_safe(obj, next, &scn->obj_head, sibling)
+ scene_obj_destroy(obj);
+
+ abuf_uninit(&scn->entry_save);
+ abuf_uninit(&scn->buf);
+ free(scn->name);
+ free(scn);
+}
+
+int scene_obj_count(struct scene *scn)
+{
+ return list_count_nodes(&scn->obj_head);
+}
+
+void *scene_obj_find(const struct scene *scn, uint id, enum scene_obj_t type)
+{
+ struct scene_obj *obj;
+
+ list_for_each_entry(obj, &scn->obj_head, sibling) {
+ if (obj->id == id &&
+ (type == SCENEOBJT_NONE || obj->type == type))
+ return obj;
+ }
+
+ return NULL;
+}
+
+void *scene_obj_find_by_name(struct scene *scn, const char *name)
+{
+ struct scene_obj *obj;
+
+ list_for_each_entry(obj, &scn->obj_head, sibling) {
+ if (!strcmp(name, obj->name))
+ return obj;
+ }
+
+ return NULL;
+}
+
+int scene_obj_add(struct scene *scn, const char *name, uint id,
+ enum scene_obj_t type, uint size, struct scene_obj **objp)
+{
+ struct scene_obj *obj;
+
+ obj = calloc(1, size);
+ if (!obj)
+ return log_msg_ret("obj", -ENOMEM);
+ obj->name = strdup(name);
+ if (!obj->name) {
+ free(obj);
+ return log_msg_ret("name", -ENOMEM);
+ }
+
+ obj->id = resolve_id(scn->expo, id);
+ obj->scene = scn;
+ obj->type = type;
+ list_add_tail(&obj->sibling, &scn->obj_head);
+ *objp = obj;
+
+ return obj->id;
+}
+
+int scene_img(struct scene *scn, const char *name, uint id, char *data,
+ struct scene_obj_img **imgp)
+{
+ struct scene_obj_img *img;
+ int ret;
+
+ ret = scene_obj_add(scn, name, id, SCENEOBJT_IMAGE,
+ sizeof(struct scene_obj_img),
+ (struct scene_obj **)&img);
+ if (ret < 0)
+ return log_msg_ret("obj", ret);
+
+ img->data = data;
+
+ if (imgp)
+ *imgp = img;
+
+ return img->obj.id;
+}
+
+int scene_txt_generic_init(struct expo *exp, struct scene_txt_generic *gen,
+ const char *name, uint str_id, const char *str)
+{
+ int ret;
+
+ if (str) {
+ ret = expo_str(exp, name, str_id, str);
+ if (ret < 0)
+ return log_msg_ret("str", ret);
+ if (str_id && ret != str_id)
+ return log_msg_ret("id", -EEXIST);
+ str_id = ret;
+ } else {
+ ret = resolve_id(exp, str_id);
+ if (ret < 0)
+ return log_msg_ret("nst", ret);
+ if (str_id && ret != str_id)
+ return log_msg_ret("nid", -EEXIST);
+ }
+
+ gen->str_id = str_id;
+ alist_init_struct(&gen->lines, struct vidconsole_mline);
+
+ return 0;
+}
+
+int scene_txt(struct scene *scn, const char *name, uint id, uint str_id,
+ struct scene_obj_txt **txtp)
+{
+ struct scene_obj_txt *txt;
+ int ret;
+
+ ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXT,
+ sizeof(struct scene_obj_txt),
+ (struct scene_obj **)&txt);
+ if (ret < 0)
+ return log_msg_ret("obj", ret);
+
+ ret = scene_txt_generic_init(scn->expo, &txt->gen, name, str_id, NULL);
+ if (ret)
+ return log_msg_ret("stg", ret);
+ if (txtp)
+ *txtp = txt;
+
+ return txt->obj.id;
+}
+
+int scene_txt_str(struct scene *scn, const char *name, uint id, uint str_id,
+ const char *str, struct scene_obj_txt **txtp)
+{
+ struct scene_obj_txt *txt;
+ int ret;
+
+ ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXT,
+ sizeof(struct scene_obj_txt),
+ (struct scene_obj **)&txt);
+ if (ret < 0)
+ return log_msg_ret("obj", ret);
+
+ ret = scene_txt_generic_init(scn->expo, &txt->gen, name, str_id, str);
+ if (ret)
+ return log_msg_ret("tsg", ret);
+ if (txtp)
+ *txtp = txt;
+
+ return txt->obj.id;
+}
+
+int scene_box(struct scene *scn, const char *name, uint id, uint width,
+ struct scene_obj_box **boxp)
+{
+ struct scene_obj_box *box;
+ int ret;
+
+ ret = scene_obj_add(scn, name, id, SCENEOBJT_BOX,
+ sizeof(struct scene_obj_box),
+ (struct scene_obj **)&box);
+ if (ret < 0)
+ return log_msg_ret("obj", ret);
+
+ box->width = width;
+
+ if (boxp)
+ *boxp = box;
+
+ return box->obj.id;
+}
+
+int scene_txt_set_font(struct scene *scn, uint id, const char *font_name,
+ uint font_size)
+{
+ struct scene_obj_txt *txt;
+
+ txt = scene_obj_find(scn, id, SCENEOBJT_TEXT);
+ if (!txt)
+ return log_msg_ret("find", -ENOENT);
+ txt->gen.font_name = font_name;
+ txt->gen.font_size = font_size;
+
+ return 0;
+}
+
+int scene_obj_set_pos(struct scene *scn, uint id, int x, int y)
+{
+ struct scene_obj *obj;
+ int w, h;
+
+ obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+ if (!obj)
+ return log_msg_ret("find", -ENOENT);
+ w = obj->bbox.x1 - obj->bbox.x0;
+ h = obj->bbox.y1 - obj->bbox.y0;
+ obj->bbox.x0 = x;
+ obj->bbox.y0 = y;
+ obj->bbox.x1 = obj->bbox.x0 + w;
+ obj->bbox.y1 = obj->bbox.y0 + h;
+
+ return 0;
+}
+
+int scene_obj_set_size(struct scene *scn, uint id, int w, int h)
+{
+ struct scene_obj *obj;
+
+ obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+ if (!obj)
+ return log_msg_ret("find", -ENOENT);
+ obj->bbox.x1 = obj->bbox.x0 + w;
+ obj->bbox.y1 = obj->bbox.y0 + h;
+ obj->flags |= SCENEOF_SIZE_VALID;
+
+ return 0;
+}
+
+int scene_obj_set_width(struct scene *scn, uint id, int w)
+{
+ struct scene_obj *obj;
+
+ obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+ if (!obj)
+ return log_msg_ret("find", -ENOENT);
+ obj->bbox.x1 = obj->bbox.x0 + w;
+
+ return 0;
+}
+
+int scene_obj_set_bbox(struct scene *scn, uint id, int x0, int y0, int x1,
+ int y1)
+{
+ struct scene_obj *obj;
+
+ obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+ if (!obj)
+ return log_msg_ret("find", -ENOENT);
+ obj->bbox.x0 = x0;
+ obj->bbox.y0 = y0;
+ obj->bbox.x1 = x1;
+ obj->bbox.y1 = y1;
+ obj->flags |= SCENEOF_SIZE_VALID;
+
+ return 0;
+}
+
+int scene_obj_set_halign(struct scene *scn, uint id, enum scene_obj_align aln)
+{
+ struct scene_obj *obj;
+
+ obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+ if (!obj)
+ return log_msg_ret("osh", -ENOENT);
+ obj->horiz = aln;
+
+ return 0;
+}
+
+int scene_obj_set_valign(struct scene *scn, uint id, enum scene_obj_align aln)
+{
+ struct scene_obj *obj;
+
+ obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+ if (!obj)
+ return log_msg_ret("osv", -ENOENT);
+ obj->vert = aln;
+
+ return 0;
+}
+
+int scene_obj_set_hide(struct scene *scn, uint id, bool hide)
+{
+ int ret;
+
+ ret = scene_obj_flag_clrset(scn, id, SCENEOF_HIDE,
+ hide ? SCENEOF_HIDE : 0);
+ if (ret)
+ return log_msg_ret("flg", ret);
+
+ return 0;
+}
+
+int scene_obj_flag_clrset(struct scene *scn, uint id, uint clr, uint set)
+{
+ struct scene_obj *obj;
+
+ obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+ if (!obj)
+ return log_msg_ret("find", -ENOENT);
+ obj->flags &= ~clr;
+ obj->flags |= set;
+
+ return 0;
+}
+
+static void handle_alignment(enum scene_obj_align horiz,
+ enum scene_obj_align vert,
+ struct scene_obj_bbox *bbox,
+ struct scene_obj_dims *dims,
+ int xsize, int ysize,
+ struct scene_obj_offset *offset)
+{
+ int width, height;
+
+ if (bbox->x1 == SCENEOB_DISPLAY_MAX)
+ bbox->x1 = xsize ?: 1280;
+ if (bbox->y1 == SCENEOB_DISPLAY_MAX)
+ bbox->y1 = ysize ?: 1024;
+
+ width = bbox->x1 - bbox->x0;
+ height = bbox->y1 - bbox->y0;
+
+ switch (horiz) {
+ case SCENEOA_CENTRE:
+ offset->xofs = (width - dims->x) / 2;
+ break;
+ case SCENEOA_RIGHT:
+ offset->xofs = width - dims->x;
+ break;
+ case SCENEOA_LEFT:
+ offset->xofs = 0;
+ break;
+ }
+
+ switch (vert) {
+ case SCENEOA_CENTRE:
+ offset->yofs = (height - dims->y) / 2;
+ break;
+ case SCENEOA_BOTTOM:
+ offset->yofs = height - dims->y;
+ break;
+ case SCENEOA_TOP:
+ default:
+ offset->yofs = 0;
+ break;
+ }
+}
+
+int scene_obj_get_hw(struct scene *scn, uint id, int *widthp)
+{
+ struct scene_obj *obj;
+
+ obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+ if (!obj)
+ return log_msg_ret("find", -ENOENT);
+
+ switch (obj->type) {
+ case SCENEOBJT_NONE:
+ case SCENEOBJT_MENU:
+ case SCENEOBJT_TEXTLINE:
+ case SCENEOBJT_BOX:
+ break;
+ case SCENEOBJT_IMAGE: {
+ struct scene_obj_img *img = (struct scene_obj_img *)obj;
+ ulong width, height;
+ uint bpix;
+
+ video_bmp_get_info(img->data, &width, &height, &bpix);
+ if (widthp)
+ *widthp = width;
+ return height;
+ }
+ case SCENEOBJT_TEXT:
+ case SCENEOBJT_TEXTEDIT: {
+ struct scene_txt_generic *gen;
+ struct expo *exp = scn->expo;
+ struct vidconsole_bbox bbox;
+ int len, ret, limit;
+ const char *str;
+
+ if (obj->type == SCENEOBJT_TEXT)
+ gen = &((struct scene_obj_txt *)obj)->gen;
+ else
+ gen = &((struct scene_obj_txtedit *)obj)->gen;
+
+ str = expo_get_str(exp, gen->str_id);
+ if (!str)
+ return log_msg_ret("str", -ENOENT);
+ len = strlen(str);
+
+ /* if there is no console, make it up */
+ if (!exp->cons) {
+ if (widthp)
+ *widthp = 8 * len;
+ return 16;
+ }
+
+ limit = obj->flags & SCENEOF_SIZE_VALID ?
+ obj->bbox.x1 - obj->bbox.x0 : -1;
+
+ ret = vidconsole_measure(scn->expo->cons, gen->font_name,
+ gen->font_size, str, limit, &bbox,
+ &gen->lines);
+ if (ret)
+ return log_msg_ret("mea", ret);
+ if (widthp)
+ *widthp = bbox.x1;
+
+ return bbox.y1;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * scene_render_background() - Render the background for an object
+ *
+ * @obj: Object to render
+ * @box_only: true to show a box around the object, but keep the normal
+ * background colour inside
+ * @cur_item: true to render the background only for the current menu item
+ */
+static void scene_render_background(struct scene_obj *obj, bool box_only,
+ bool cur_item)
+{
+ struct vidconsole_bbox bbox[SCENEBB_count], *sel;
+ struct expo *exp = obj->scene->expo;
+ const struct expo_theme *theme = &exp->theme;
+ struct udevice *dev = exp->display;
+ struct video_priv *vid_priv;
+ struct udevice *cons = exp->cons;
+ struct vidconsole_colour old;
+ enum colour_idx fore, back;
+ uint inset = theme->menu_inset;
+
+ vid_priv = dev_get_uclass_priv(dev);
+ /* draw a background for the object */
+ if (vid_priv->white_on_black) {
+ fore = VID_DARK_GREY;
+ back = VID_WHITE;
+ } else {
+ fore = VID_LIGHT_GRAY;
+ back = VID_BLACK;
+ }
+
+ /* see if this object wants to render a background */
+ if (scene_obj_calc_bbox(obj, bbox))
+ return;
+
+ sel = cur_item ? &bbox[SCENEBB_curitem] : &bbox[SCENEBB_label];
+ if (!sel->valid)
+ return;
+
+ vidconsole_push_colour(cons, fore, back, &old);
+ video_fill_part(dev, sel->x0 - inset, sel->y0 - inset,
+ sel->x1 + inset, sel->y1 + inset,
+ vid_priv->colour_fg);
+ vidconsole_pop_colour(cons, &old);
+ if (box_only) {
+ video_fill_part(dev, sel->x0, sel->y0, sel->x1, sel->y1,
+ vid_priv->colour_bg);
+ }
+}
+
+static int scene_txt_render(struct expo *exp, struct udevice *dev,
+ struct udevice *cons, struct scene_obj *obj,
+ struct scene_txt_generic *gen, int x, int y,
+ int menu_inset)
+{
+ const struct vidconsole_mline *mline, *last;
+ struct video_priv *vid_priv;
+ struct vidconsole_colour old;
+ enum colour_idx fore, back;
+ struct scene_obj_dims dims;
+ struct scene_obj_bbox bbox;
+ const char *str;
+ int ret;
+
+ if (!cons)
+ return -ENOTSUPP;
+
+ if (gen->font_name || gen->font_size) {
+ ret = vidconsole_select_font(cons, gen->font_name,
+ gen->font_size);
+ } else {
+ ret = vidconsole_select_font(cons, NULL, 0);
+ }
+ if (ret && ret != -ENOSYS)
+ return log_msg_ret("font", ret);
+ str = expo_get_str(exp, gen->str_id);
+ if (!str)
+ return 0;
+
+ vid_priv = dev_get_uclass_priv(dev);
+ if (vid_priv->white_on_black) {
+ fore = VID_BLACK;
+ back = VID_WHITE;
+ } else {
+ fore = VID_LIGHT_GRAY;
+ back = VID_BLACK;
+ }
+
+ if (obj->flags & SCENEOF_POINT) {
+ int inset;
+
+ inset = exp->popup ? menu_inset : 0;
+ vidconsole_push_colour(cons, fore, back, &old);
+ video_fill_part(dev, x - inset, y,
+ obj->bbox.x1, obj->bbox.y1,
+ vid_priv->colour_bg);
+ }
+
+ mline = alist_get(&gen->lines, 0, typeof(*mline));
+ last = alist_get(&gen->lines, gen->lines.count - 1, typeof(*mline));
+ if (mline)
+ dims.y = last->bbox.y1 - mline->bbox.y0;
+ bbox.y0 = obj->bbox.y0;
+ bbox.y1 = obj->bbox.y1;
+
+ if (!mline) {
+ vidconsole_set_cursor_pos(cons, x, y);
+ vidconsole_put_string(cons, str);
+ }
+
+ alist_for_each(mline, &gen->lines) {
+ struct scene_obj_offset offset;
+
+ bbox.x0 = obj->bbox.x0;
+ bbox.x1 = obj->bbox.x1;
+ dims.x = mline->bbox.x1 - mline->bbox.x0;
+ handle_alignment(obj->horiz, obj->vert, &bbox, &dims,
+ obj->bbox.x1 - obj->bbox.x0,
+ obj->bbox.y1 - obj->bbox.y0, &offset);
+
+ x = obj->bbox.x0 + offset.xofs;
+ y = obj->bbox.y0 + offset.yofs + mline->bbox.y0;
+ if (y > bbox.y1)
+ break; /* clip this line and any following */
+ vidconsole_set_cursor_pos(cons, x, y);
+ vidconsole_put_stringn(cons, str + mline->start, mline->len);
+ }
+ if (obj->flags & SCENEOF_POINT)
+ vidconsole_pop_colour(cons, &old);
+
+ return 0;
+}
+
+/**
+ * scene_obj_render() - Render an object
+ *
+ * @obj: Object to render
+ * @text_mode: true to use text mode
+ * Return: 0 if OK, -ve on error
+ */
+static int scene_obj_render(struct scene_obj *obj, bool text_mode)
+{
+ struct scene *scn = obj->scene;
+ struct expo *exp = scn->expo;
+ const struct expo_theme *theme = &exp->theme;
+ struct udevice *dev = exp->display;
+ struct udevice *cons = text_mode ? NULL : exp->cons;
+ struct video_priv *vid_priv;
+ int x, y, ret;
+
+ y = obj->bbox.y0;
+ x = obj->bbox.x0 + obj->ofs.xofs;
+ vid_priv = dev_get_uclass_priv(dev);
+
+ switch (obj->type) {
+ case SCENEOBJT_NONE:
+ break;
+ case SCENEOBJT_IMAGE: {
+ struct scene_obj_img *img = (struct scene_obj_img *)obj;
+
+ if (!cons)
+ return -ENOTSUPP;
+ ret = video_bmp_display(dev, map_to_sysmem(img->data), x, y,
+ true);
+ if (ret < 0)
+ return log_msg_ret("img", ret);
+ break;
+ }
+ case SCENEOBJT_TEXT: {
+ struct scene_obj_txt *txt = (struct scene_obj_txt *)obj;
+
+ ret = scene_txt_render(exp, dev, cons, obj, &txt->gen, x, y,
+ theme->menu_inset);
+ break;
+ }
+ case SCENEOBJT_MENU: {
+ struct scene_obj_menu *menu = (struct scene_obj_menu *)obj;
+
+ if (exp->popup) {
+ if (obj->flags & SCENEOF_OPEN) {
+ if (!cons)
+ return -ENOTSUPP;
+
+ /* draw a background behind the menu items */
+ scene_render_background(obj, false, false);
+ }
+ } else if (exp->show_highlight) {
+ /* do nothing */
+ }
+
+ /*
+ * With a vidconsole, the text and item pointer are rendered as
+ * normal objects so we don't need to do anything here. The menu
+ * simply controls where they are positioned.
+ */
+ if (cons)
+ return -ENOTSUPP;
+
+ ret = scene_menu_display(menu);
+ if (ret < 0)
+ return log_msg_ret("img", ret);
+
+ break;
+ }
+ case SCENEOBJT_TEXTLINE:
+ if (obj->flags & SCENEOF_OPEN)
+ scene_render_background(obj, true, false);
+ break;
+ case SCENEOBJT_BOX: {
+ struct scene_obj_box *box = (struct scene_obj_box *)obj;
+
+ video_draw_box(dev, obj->bbox.x0, obj->bbox.y0, obj->bbox.x1,
+ obj->bbox.y1, box->width, vid_priv->colour_fg);
+ break;
+ }
+ case SCENEOBJT_TEXTEDIT: {
+ struct scene_obj_txtedit *ted = (struct scene_obj_txtedit *)obj;
+
+ ret = scene_txt_render(exp, dev, cons, obj, &ted->gen, x, y,
+ theme->menu_inset);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int scene_calc_arrange(struct scene *scn, struct expo_arrange_info *arr)
+{
+ struct scene_obj *obj;
+
+ arr->label_width = 0;
+ list_for_each_entry(obj, &scn->obj_head, sibling) {
+ uint label_id = 0;
+ int width;
+
+ switch (obj->type) {
+ case SCENEOBJT_NONE:
+ case SCENEOBJT_IMAGE:
+ case SCENEOBJT_TEXT:
+ case SCENEOBJT_BOX:
+ case SCENEOBJT_TEXTEDIT:
+ break;
+ case SCENEOBJT_MENU: {
+ struct scene_obj_menu *menu;
+
+ menu = (struct scene_obj_menu *)obj,
+ label_id = menu->title_id;
+ break;
+ }
+ case SCENEOBJT_TEXTLINE: {
+ struct scene_obj_textline *tline;
+
+ tline = (struct scene_obj_textline *)obj,
+ label_id = tline->label_id;
+ break;
+ }
+ }
+
+ if (label_id) {
+ int ret;
+
+ ret = scene_obj_get_hw(scn, label_id, &width);
+ if (ret < 0)
+ return log_msg_ret("hei", ret);
+ arr->label_width = max(arr->label_width, width);
+ }
+ }
+
+ return 0;
+}
+
+int scene_arrange(struct scene *scn)
+{
+ struct expo_arrange_info arr;
+ int xsize = 0, ysize = 0;
+ struct scene_obj *obj;
+ struct udevice *dev;
+ int ret;
+
+ dev = scn->expo->display;
+ if (dev) {
+ struct video_priv *priv = dev_get_uclass_priv(dev);
+
+ xsize = priv->xsize;
+ ysize = priv->ysize;
+ }
+
+ ret = scene_calc_arrange(scn, &arr);
+ if (ret < 0)
+ return log_msg_ret("arr", ret);
+
+ list_for_each_entry(obj, &scn->obj_head, sibling) {
+ handle_alignment(obj->horiz, obj->vert, &obj->bbox, &obj->dims,
+ xsize, ysize, &obj->ofs);
+
+ switch (obj->type) {
+ case SCENEOBJT_NONE:
+ case SCENEOBJT_IMAGE:
+ case SCENEOBJT_TEXT:
+ case SCENEOBJT_BOX:
+ case SCENEOBJT_TEXTEDIT:
+ break;
+ case SCENEOBJT_MENU: {
+ struct scene_obj_menu *menu;
+
+ menu = (struct scene_obj_menu *)obj,
+ ret = scene_menu_arrange(scn, &arr, menu);
+ if (ret)
+ return log_msg_ret("arr", ret);
+ break;
+ }
+ case SCENEOBJT_TEXTLINE: {
+ struct scene_obj_textline *tline;
+
+ tline = (struct scene_obj_textline *)obj,
+ ret = scene_textline_arrange(scn, &arr, tline);
+ if (ret)
+ return log_msg_ret("arr", ret);
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int scene_render_deps(struct scene *scn, uint id)
+{
+ struct scene_obj *obj;
+ int ret;
+
+ if (!id)
+ return 0;
+ obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+ if (!obj)
+ return log_msg_ret("obj", -ENOENT);
+
+ if (!(obj->flags & SCENEOF_HIDE)) {
+ ret = scene_obj_render(obj, false);
+ if (ret && ret != -ENOTSUPP)
+ return log_msg_ret("ren", ret);
+
+ switch (obj->type) {
+ case SCENEOBJT_NONE:
+ case SCENEOBJT_IMAGE:
+ case SCENEOBJT_TEXT:
+ case SCENEOBJT_BOX:
+ case SCENEOBJT_TEXTEDIT:
+ break;
+ case SCENEOBJT_MENU:
+ scene_menu_render_deps(scn,
+ (struct scene_obj_menu *)obj);
+ break;
+ case SCENEOBJT_TEXTLINE:
+ scene_textline_render_deps(scn,
+ (struct scene_obj_textline *)obj);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int scene_render(struct scene *scn)
+{
+ struct expo *exp = scn->expo;
+ struct scene_obj *obj;
+ int ret;
+
+ list_for_each_entry(obj, &scn->obj_head, sibling) {
+ if (!(obj->flags & SCENEOF_HIDE)) {
+ ret = scene_obj_render(obj, exp->text_mode);
+ if (ret && ret != -ENOTSUPP)
+ return log_msg_ret("ren", ret);
+ }
+ }
+
+ /* render any highlighted object on top of the others */
+ if (scn->highlight_id && !exp->text_mode) {
+ ret = scene_render_deps(scn, scn->highlight_id);
+ if (ret && ret != -ENOTSUPP)
+ return log_msg_ret("dep", ret);
+ }
+
+ return 0;
+}
+
+/**
+ * send_key_obj() - Handle a keypress for moving between objects
+ *
+ * @scn: Scene to receive the key
+ * @key: Key to send (KEYCODE_UP)
+ * @event: Returns resulting event from this keypress
+ * Returns: 0 if OK, -ve on error
+ */
+static void send_key_obj(struct scene *scn, struct scene_obj *obj, int key,
+ struct expo_action *event)
+{
+ switch (key) {
+ case BKEY_UP:
+ while (obj != list_first_entry(&scn->obj_head, struct scene_obj,
+ sibling)) {
+ obj = list_entry(obj->sibling.prev,
+ struct scene_obj, sibling);
+ if (scene_obj_can_highlight(obj)) {
+ event->type = EXPOACT_POINT_OBJ;
+ event->select.id = obj->id;
+ log_debug("up to obj %d\n", event->select.id);
+ break;
+ }
+ }
+ break;
+ case BKEY_DOWN:
+ while (!list_is_last(&obj->sibling, &scn->obj_head)) {
+ obj = list_entry(obj->sibling.next, struct scene_obj,
+ sibling);
+ if (scene_obj_can_highlight(obj)) {
+ event->type = EXPOACT_POINT_OBJ;
+ event->select.id = obj->id;
+ log_debug("down to obj %d\n", event->select.id);
+ break;
+ }
+ }
+ break;
+ case BKEY_SELECT:
+ if (scene_obj_can_highlight(obj)) {
+ event->type = EXPOACT_OPEN;
+ event->select.id = obj->id;
+ log_debug("open obj %d\n", event->select.id);
+ }
+ break;
+ case BKEY_QUIT:
+ event->type = EXPOACT_QUIT;
+ log_debug("obj quit\n");
+ break;
+ }
+}
+
+int scene_send_key(struct scene *scn, int key, struct expo_action *event)
+{
+ struct scene_obj *obj;
+ int ret;
+
+ event->type = EXPOACT_NONE;
+
+ /*
+ * In 'popup' mode, arrow keys move betwen objects, unless a menu is
+ * opened
+ */
+ if (scn->expo->popup) {
+ obj = NULL;
+ if (scn->highlight_id) {
+ obj = scene_obj_find(scn, scn->highlight_id,
+ SCENEOBJT_NONE);
+ }
+ if (!obj)
+ return 0;
+
+ if (!(obj->flags & SCENEOF_OPEN)) {
+ send_key_obj(scn, obj, key, event);
+ return 0;
+ }
+
+ switch (obj->type) {
+ case SCENEOBJT_NONE:
+ case SCENEOBJT_IMAGE:
+ case SCENEOBJT_TEXT:
+ case SCENEOBJT_BOX:
+ break;
+ case SCENEOBJT_MENU: {
+ struct scene_obj_menu *menu;
+
+ menu = (struct scene_obj_menu *)obj,
+ ret = scene_menu_send_key(scn, menu, key, event);
+ if (ret)
+ return log_msg_ret("key", ret);
+ break;
+ }
+ case SCENEOBJT_TEXTLINE: {
+ struct scene_obj_textline *tline;
+
+ tline = (struct scene_obj_textline *)obj,
+ ret = scene_textline_send_key(scn, tline, key, event);
+ if (ret)
+ return log_msg_ret("key", ret);
+ break;
+ }
+ case SCENEOBJT_TEXTEDIT:
+ /* TODO(sjg@chromium.org): Implement this */
+ break;
+ }
+ return 0;
+ }
+
+ list_for_each_entry(obj, &scn->obj_head, sibling) {
+ if (obj->type == SCENEOBJT_MENU) {
+ struct scene_obj_menu *menu;
+
+ menu = (struct scene_obj_menu *)obj,
+ ret = scene_menu_send_key(scn, menu, key, event);
+ if (ret)
+ return log_msg_ret("key", ret);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int scene_obj_calc_bbox(struct scene_obj *obj, struct vidconsole_bbox bbox[])
+{
+ switch (obj->type) {
+ case SCENEOBJT_NONE:
+ case SCENEOBJT_IMAGE:
+ case SCENEOBJT_TEXT:
+ case SCENEOBJT_BOX:
+ case SCENEOBJT_TEXTEDIT:
+ return -ENOSYS;
+ case SCENEOBJT_MENU: {
+ struct scene_obj_menu *menu = (struct scene_obj_menu *)obj;
+
+ scene_menu_calc_bbox(menu, bbox);
+ break;
+ }
+ case SCENEOBJT_TEXTLINE: {
+ struct scene_obj_textline *tline;
+
+ tline = (struct scene_obj_textline *)obj;
+ scene_textline_calc_bbox(tline, &bbox[SCENEBB_all],
+ &bbox[SCENEBB_label]);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int scene_calc_dims(struct scene *scn, bool do_menus)
+{
+ struct scene_obj *obj;
+ int ret;
+
+ list_for_each_entry(obj, &scn->obj_head, sibling) {
+ switch (obj->type) {
+ case SCENEOBJT_NONE:
+ case SCENEOBJT_TEXT:
+ case SCENEOBJT_BOX:
+ case SCENEOBJT_TEXTEDIT:
+ case SCENEOBJT_IMAGE: {
+ int width;
+
+ if (!do_menus) {
+ ret = scene_obj_get_hw(scn, obj->id, &width);
+ if (ret < 0)
+ return log_msg_ret("get", ret);
+ obj->dims.x = width;
+ obj->dims.y = ret;
+ if (!(obj->flags & SCENEOF_SIZE_VALID)) {
+ obj->bbox.x1 = obj->bbox.x0 + width;
+ obj->bbox.y1 = obj->bbox.y0 + ret;
+ obj->flags |= SCENEOF_SIZE_VALID;
+ }
+ }
+ break;
+ }
+ case SCENEOBJT_MENU: {
+ struct scene_obj_menu *menu;
+
+ if (do_menus) {
+ menu = (struct scene_obj_menu *)obj;
+
+ ret = scene_menu_calc_dims(menu);
+ if (ret)
+ return log_msg_ret("men", ret);
+ }
+ break;
+ }
+ case SCENEOBJT_TEXTLINE: {
+ struct scene_obj_textline *tline;
+
+ tline = (struct scene_obj_textline *)obj;
+ ret = scene_textline_calc_dims(tline);
+ if (ret)
+ return log_msg_ret("men", ret);
+
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int scene_apply_theme(struct scene *scn, struct expo_theme *theme)
+{
+ struct scene_obj *obj;
+ int ret;
+
+ /* Avoid error-checking optional items */
+ scene_txt_set_font(scn, scn->title_id, NULL, theme->font_size);
+
+ list_for_each_entry(obj, &scn->obj_head, sibling) {
+ switch (obj->type) {
+ case SCENEOBJT_NONE:
+ case SCENEOBJT_IMAGE:
+ case SCENEOBJT_MENU:
+ case SCENEOBJT_BOX:
+ case SCENEOBJT_TEXTLINE:
+ break;
+ case SCENEOBJT_TEXTEDIT:
+ scene_txted_set_font(scn, obj->id, NULL,
+ theme->font_size);
+ break;
+ case SCENEOBJT_TEXT:
+ scene_txt_set_font(scn, obj->id, NULL,
+ theme->font_size);
+ break;
+ }
+ }
+
+ ret = scene_arrange(scn);
+ if (ret)
+ return log_msg_ret("arr", ret);
+
+ return 0;
+}
+
+void scene_set_highlight_id(struct scene *scn, uint id)
+{
+ scn->highlight_id = id;
+}
+
+void scene_highlight_first(struct scene *scn)
+{
+ struct scene_obj *obj;
+
+ list_for_each_entry(obj, &scn->obj_head, sibling) {
+ if (scene_obj_can_highlight(obj)) {
+ scene_set_highlight_id(scn, obj->id);
+ return;
+ }
+ }
+}
+
+static int scene_obj_open(struct scene *scn, struct scene_obj *obj)
+{
+ int ret;
+
+ switch (obj->type) {
+ case SCENEOBJT_NONE:
+ case SCENEOBJT_IMAGE:
+ case SCENEOBJT_MENU:
+ case SCENEOBJT_TEXT:
+ case SCENEOBJT_BOX:
+ case SCENEOBJT_TEXTEDIT:
+ break;
+ case SCENEOBJT_TEXTLINE:
+ ret = scene_textline_open(scn,
+ (struct scene_obj_textline *)obj);
+ if (ret)
+ return log_msg_ret("op", ret);
+ break;
+ }
+
+ return 0;
+}
+
+int scene_set_open(struct scene *scn, uint id, bool open)
+{
+ struct scene_obj *obj;
+ int ret;
+
+ obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+ if (!obj)
+ return log_msg_ret("find", -ENOENT);
+
+ if (open) {
+ ret = scene_obj_open(scn, obj);
+ if (ret)
+ return log_msg_ret("op", ret);
+ }
+
+ ret = scene_obj_flag_clrset(scn, id, SCENEOF_OPEN,
+ open ? SCENEOF_OPEN : 0);
+ if (ret)
+ return log_msg_ret("flg", ret);
+
+ return 0;
+}
+
+int scene_iter_objs(struct scene *scn, expo_scene_obj_iterator iter,
+ void *priv)
+{
+ struct scene_obj *obj;
+
+ list_for_each_entry(obj, &scn->obj_head, sibling) {
+ int ret;
+
+ ret = iter(obj, priv);
+ if (ret)
+ return log_msg_ret("itr", ret);
+ }
+
+ return 0;
+}
+
+int scene_bbox_join(const struct vidconsole_bbox *src, int inset,
+ struct vidconsole_bbox *dst)
+{
+ if (dst->valid) {
+ dst->x0 = min(dst->x0, src->x0 - inset);
+ dst->y0 = min(dst->y0, src->y0);
+ dst->x1 = max(dst->x1, src->x1 + inset);
+ dst->y1 = max(dst->y1, src->y1);
+ } else {
+ dst->x0 = src->x0 - inset;
+ dst->y0 = src->y0;
+ dst->x1 = src->x1 + inset;
+ dst->y1 = src->y1;
+ dst->valid = true;
+ }
+
+ return 0;
+}
+
+int scene_bbox_union(struct scene *scn, uint id, int inset,
+ struct vidconsole_bbox *bbox)
+{
+ struct scene_obj *obj;
+ struct vidconsole_bbox local;
+
+ if (!id)
+ return 0;
+ obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
+ if (!obj)
+ return log_msg_ret("obj", -ENOENT);
+ local.x0 = obj->bbox.x0;
+ local.y0 = obj->bbox.y0;
+ local.x1 = obj->bbox.x1;
+ local.y1 = obj->bbox.y1;
+ local.valid = true;
+ scene_bbox_join(&local, inset, bbox);
+
+ return 0;
+}
diff --git a/boot/scene_internal.h b/boot/scene_internal.h
new file mode 100644
index 00000000000..95927472875
--- /dev/null
+++ b/boot/scene_internal.h
@@ -0,0 +1,442 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Internal header file for scenes
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __SCENE_INTERNAL_H
+#define __SCENE_INTERNAL_H
+
+#include <linux/types.h>
+
+struct expo;
+struct expo_action;
+struct expo_arrange_info;
+struct expo_theme;
+struct scene_obj;
+struct scene_obj_menu;
+struct scene_obj_textline;
+struct scene_obj_txtedit;
+struct scene_txt_generic;
+struct vidconsole_bbox;
+
+enum scene_obj_t;
+
+typedef int (*expo_scene_obj_iterator)(struct scene_obj *obj, void *priv);
+
+/**
+ * enum scene_bbox_t - Parts of an object which can have a bounding box
+ *
+ * Objects can provide any or all of these bounding boxes
+ *
+ * @SCENEBB_label: Menu-item label
+ * @SCENEBB_key: Menu-item key label
+ * @SCENEBB_desc: Menu-item Description
+ * @SCENEBB_curitem: Current item (pointed to)
+ * @SCENEBB_all: All the above objects combined
+ */
+enum scene_bbox_t {
+ SCENEBB_label,
+ SCENEBB_key,
+ SCENEBB_desc,
+ SCENEBB_curitem,
+ SCENEBB_all,
+
+ SCENEBB_count,
+};
+
+/**
+ * expo_lookup_scene_id() - Look up a scene ID
+ *
+ * @exp: Expo to use
+ * @id: scene ID to look up
+ * Returns: Scene for that ID, or NULL if none
+ */
+struct scene *expo_lookup_scene_id(struct expo *exp, uint scene_id);
+
+/**
+ * resolve_id() - Automatically allocate an ID if needed
+ *
+ * @exp: Expo to use
+ * @id: ID to use, or 0 to auto-allocate one
+ * Returns: Either @id, or the auto-allocated ID
+ */
+uint resolve_id(struct expo *exp, uint id);
+
+/**
+ * scene_obj_find() - Find an object in a scene
+ *
+ * Note that @type is used to restrict the search when the object type is known.
+ * If any type is acceptable, set @type to SCENEOBJT_NONE
+ *
+ * @scn: Scene to search
+ * @id: ID of object to find
+ * @type: Type of the object, or SCENEOBJT_NONE to match any type
+ * Returns: Object found, or NULL if not found
+ */
+void *scene_obj_find(const struct scene *scn, uint id, enum scene_obj_t type);
+
+/**
+ * scene_obj_find_by_name() - Find an object in a scene by name
+ *
+ * @scn: Scene to search
+ * @name: Name to search for
+ */
+void *scene_obj_find_by_name(struct scene *scn, const char *name);
+
+/**
+ * scene_obj_add() - Add a new object to a scene
+ *
+ * @scn: Scene to update
+ * @name: Name to use (this is allocated by this call)
+ * @id: ID to use for the new object (0 to allocate one)
+ * @type: Type of object to add
+ * @size: Size to allocate for the object, in bytes
+ * @objp: Returns a pointer to the new object (must not be NULL)
+ * Returns: ID number for the object (generally @id), or -ve on error
+ */
+int scene_obj_add(struct scene *scn, const char *name, uint id,
+ enum scene_obj_t type, uint size, struct scene_obj **objp);
+
+/**
+ * scene_obj_flag_clrset() - Adjust object flags
+ *
+ * @scn: Scene to update
+ * @id: ID of object to update
+ * @clr: Bits to clear in the object's flags
+ * @set: Bits to set in the object's flags
+ * Returns 0 if OK, -ENOENT if the object was not found
+ */
+int scene_obj_flag_clrset(struct scene *scn, uint id, uint clr, uint set);
+
+/**
+ * scene_calc_dims() - Calculate the dimensions of the scene objects
+ *
+ * Updates the width and height of all objects based on their contents
+ *
+ * @scn: Scene to update
+ * @do_menus: true to calculate only menus, false to calculate everything else
+ * Returns 0 if OK, -ENOTSUPP if there is no graphical console
+ */
+int scene_calc_dims(struct scene *scn, bool do_menus);
+
+/**
+ * scene_menu_arrange() - Set the position of things in the menu
+ *
+ * This updates any items associated with a menu to make sure they are
+ * positioned correctly relative to the menu. It also selects the first item
+ * if not already done
+ *
+ * @scn: Scene to update
+ * @arr: Arrangement information
+ * @menu: Menu to process
+ * Returns: 0 if OK, -ve on error
+ */
+int scene_menu_arrange(struct scene *scn, struct expo_arrange_info *arr,
+ struct scene_obj_menu *menu);
+
+/**
+ * scene_textline_arrange() - Set the position of things in a textline
+ *
+ * This updates any items associated with a textline to make sure they are
+ * positioned correctly relative to the textline.
+ *
+ * @scn: Scene to update
+ * @arr: Arrangement information
+ * @tline: textline to process
+ * Returns: 0 if OK, -ve on error
+ */
+int scene_textline_arrange(struct scene *scn, struct expo_arrange_info *arr,
+ struct scene_obj_textline *tline);
+
+/**
+ * scene_apply_theme() - Apply a theme to a scene
+ *
+ * @scn: Scene to update
+ * @theme: Theme to apply
+ * Returns: 0 if OK, -ve on error
+ */
+int scene_apply_theme(struct scene *scn, struct expo_theme *theme);
+
+/**
+ * scene_menu_send_key() - Send a key to a menu for processing
+ *
+ * @scn: Scene to use
+ * @menu: Menu to use
+ * @key: Key code to send (KEY_...)
+ * @event: Place to put any event which is generated by the key
+ * Returns: 0 if OK, -ENOTTY if there is no current menu item, other -ve on other
+ * error
+ */
+int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
+ struct expo_action *event);
+
+/**
+ * scene_textline_send_key() - Send a key to a textline for processing
+ *
+ * @scn: Scene to use
+ * @tline: textline to use
+ * @key: Key code to send (KEY_...)
+ * @event: Place to put any event which is generated by the key
+ * Returns: 0 if OK (always)
+ */
+int scene_textline_send_key(struct scene *scn, struct scene_obj_textline *tline,
+ int key, struct expo_action *event);
+
+/**
+ * scene_menu_destroy() - Destroy a menu in a scene
+ *
+ * @scn: Scene to destroy
+ */
+void scene_menu_destroy(struct scene_obj_menu *menu);
+
+/**
+ * scene_menu_display() - Display a menu as text
+ *
+ * @menu: Menu to display
+ * Returns: 0 if OK, -ENOENT if @id is invalid
+ */
+int scene_menu_display(struct scene_obj_menu *menu);
+
+/**
+ * scene_destroy() - Destroy a scene and all its memory
+ *
+ * @scn: Scene to destroy
+ */
+void scene_destroy(struct scene *scn);
+
+/**
+ * scene_render() - Render a scene
+ *
+ * This is called from expo_render()
+ *
+ * @scn: Scene to render
+ * Returns: 0 if OK, -ve on error
+ */
+int scene_render(struct scene *scn);
+
+/**
+ * scene_send_key() - set a keypress to a scene
+ *
+ * @scn: Scene to receive the key
+ * @key: Key to send (KEYCODE_UP)
+ * @event: Returns resulting event from this keypress
+ * Returns: 0 if OK, -ve on error
+ */
+int scene_send_key(struct scene *scn, int key, struct expo_action *event);
+
+/**
+ * scene_render_deps() - Render an object and its dependencies
+ *
+ * @scn: Scene to render
+ * @id: Object ID to render (or 0 for none)
+ * Returns: 0 if OK, -ve on error
+ */
+int scene_render_deps(struct scene *scn, uint id);
+
+/**
+ * scene_menu_render_deps() - Render a menu and its dependencies
+ *
+ * Renders the menu and all of its attached objects
+ *
+ * @scn: Scene to render
+ * @menu: Menu to render
+ * Returns: 0 if OK, -ve on error
+ */
+int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu);
+
+/**
+ * scene_textline_render_deps() - Render a textline and its dependencies
+ *
+ * Renders the textline and all of its attached objects
+ *
+ * @scn: Scene to render
+ * @tline: textline to render
+ * Returns: 0 if OK, -ve on error
+ */
+int scene_textline_render_deps(struct scene *scn,
+ struct scene_obj_textline *tline);
+
+/**
+ * scene_menu_calc_dims() - Calculate the dimensions of a menu
+ *
+ * Updates the width and height of the menu based on its contents
+ *
+ * @menu: Menu to update
+ * Returns 0 if OK, -ENOTSUPP if there is no graphical console
+ */
+int scene_menu_calc_dims(struct scene_obj_menu *menu);
+
+/**
+ * scene_iter_objs() - Iterate through all scene objects
+ *
+ * @scn: Scene to process
+ * @iter: Iterator to call on each object
+ * @priv: Private data to pass to the iterator, in addition to the object
+ * Return: 0 if OK, -ve on error
+ */
+int scene_iter_objs(struct scene *scn, expo_scene_obj_iterator iter,
+ void *priv);
+
+/**
+ * expo_iter_scene_objects() - Iterate through all scene objects
+ *
+ * @exp: Expo to process
+ * @iter: Iterator to call on each object
+ * @priv: Private data to pass to the iterator, in addition to the object
+ * Return: 0 if OK, -ve on error
+ */
+int expo_iter_scene_objs(struct expo *exp, expo_scene_obj_iterator iter,
+ void *priv);
+
+/**
+ * scene_menuitem_find() - Find the menu item for an ID
+ *
+ * Looks up the menu to find the item with the given ID
+ *
+ * @menu: Menu to check
+ * @id: ID to look for
+ * Return: Menu item, or NULL if not found
+ */
+struct scene_menitem *scene_menuitem_find(const struct scene_obj_menu *menu,
+ int id);
+
+/**
+ * scene_menuitem_find_seq() - Find the menu item at a sequential position
+ *
+ * This numbers the items from 0 and returns the seq'th one
+ *
+ * @menu: Menu to check
+ * @seq: Sequence number to look for
+ * Return: menu item if found, else NULL
+ */
+struct scene_menitem *scene_menuitem_find_seq(const struct scene_obj_menu *menu,
+ uint seq);
+
+/**
+ * scene_menuitem_find_val() - Find the menu item with a given value
+ *
+ * @menu: Menu to check
+ * @find_val: Value to look for
+ * Return: menu item if found, else NULL
+ */
+struct scene_menitem *scene_menuitem_find_val(const struct scene_obj_menu *menu,
+ int val);
+
+/**
+ * scene_bbox_join() - update bouding box with a given src box
+ *
+ * Updates @dst so that it encompasses the bounding box @src
+ *
+ * @src: Input bounding box
+ * @inset: Amount of inset to use for width
+ * @dst: Bounding box to update
+ * Return: 0 if OK, -ve on error
+ */
+int scene_bbox_join(const struct vidconsole_bbox *src, int inset,
+ struct vidconsole_bbox *dst);
+
+/**
+ * scene_bbox_union() - update bouding box with the demensions of an object
+ *
+ * Updates @bbox so that it encompasses the bounding box of object @id
+ *
+ * @snd: Scene containing object
+ * @id: Object id
+ * @inset: Amount of inset to use for width
+ * @bbox: Bounding box to update
+ * Return: 0 if OK, -ve on error
+ */
+int scene_bbox_union(struct scene *scn, uint id, int inset,
+ struct vidconsole_bbox *bbox);
+
+/**
+ * scene_textline_calc_dims() - Calculate the dimensions of a textline
+ *
+ * Updates the width and height of the textline based on its contents
+ *
+ * @tline: Textline to update
+ * Returns 0 if OK, -ENOTSUPP if there is no graphical console
+ */
+int scene_textline_calc_dims(struct scene_obj_textline *tline);
+
+/**
+ * scene_menu_calc_bbox() - Calculate bounding boxes for the menu
+ *
+ * @menu: Menu to process
+ * @bbox: List of bounding box to fill in
+ * Return: 0 if OK, -ve on error
+ */
+void scene_menu_calc_bbox(struct scene_obj_menu *menu,
+ struct vidconsole_bbox *bbox);
+
+/**
+ * scene_textline_calc_bbox() - Calculate bounding box for the textline
+ *
+ * @textline: Menu to process
+ * @bbox: Returns bounding box of textline including prompt
+ * @edit_bbox: Returns bounding box of editable part
+ * Return: 0 if OK, -ve on error
+ */
+void scene_textline_calc_bbox(struct scene_obj_textline *menu,
+ struct vidconsole_bbox *bbox,
+ struct vidconsole_bbox *label_bbox);
+
+/**
+ * scene_obj_calc_bbox() - Calculate bounding boxes for an object
+ *
+ * @obj: Object to process
+ * @bbox: Returns bounding boxes for object
+ * Return: 0 if OK, -ve on error
+ */
+int scene_obj_calc_bbox(struct scene_obj *obj, struct vidconsole_bbox *bbox);
+
+/**
+ * scene_textline_open() - Open a textline object
+ *
+ * Set up the text editor ready for use
+ *
+ * @scn: Scene containing the textline
+ * @tline: textline object
+ * Return: 0 if OK, -ve on error
+ */
+int scene_textline_open(struct scene *scn, struct scene_obj_textline *tline);
+
+/**
+ * scene_textline_close() - Close a textline object
+ *
+ * Close out the text editor after use
+ *
+ * @scn: Scene containing the textline
+ * @tline: textline object
+ * Return: 0 if OK, -ve on error
+ */
+int scene_textline_close(struct scene *scn, struct scene_obj_textline *tline);
+
+/**
+ * scene_calc_arrange() - Calculate sizes needed to arrange a scene
+ *
+ * Checks the size of some objects and stores this info to help with a later
+ * scene arrangement
+ *
+ * @scn: Scene to check
+ * @arr: Place to put scene-arrangement info
+ * Returns: 0 if OK, -ve on error
+ */
+int scene_calc_arrange(struct scene *scn, struct expo_arrange_info *arr);
+
+/**
+ * scene_txt_generic_init() - Set up the generic part of a text object
+ *
+ * @exp: Expo containing the object
+ * @gen: Generic text info
+ * @name: Object name
+ * @str_id: String ID for the text
+ * @str: Initial text string for the object, or NULL to just use str_id
+ */
+int scene_txt_generic_init(struct expo *exp, struct scene_txt_generic *gen,
+ const char *name, uint str_id, const char *str);
+
+#endif /* __SCENE_INTERNAL_H */
diff --git a/boot/scene_menu.c b/boot/scene_menu.c
new file mode 100644
index 00000000000..23404172093
--- /dev/null
+++ b/boot/scene_menu.c
@@ -0,0 +1,624 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Implementation of a menu in a scene
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY LOGC_EXPO
+
+#include <dm.h>
+#include <expo.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <menu.h>
+#include <video.h>
+#include <video_console.h>
+#include <linux/input.h>
+#include "scene_internal.h"
+
+static void scene_menuitem_destroy(struct scene_menitem *item)
+{
+ free(item->name);
+ free(item);
+}
+
+void scene_menu_destroy(struct scene_obj_menu *menu)
+{
+ struct scene_menitem *item, *next;
+
+ list_for_each_entry_safe(item, next, &menu->item_head, sibling)
+ scene_menuitem_destroy(item);
+}
+
+struct scene_menitem *scene_menuitem_find(const struct scene_obj_menu *menu,
+ int id)
+{
+ struct scene_menitem *item;
+
+ list_for_each_entry(item, &menu->item_head, sibling) {
+ if (item->id == id)
+ return item;
+ }
+
+ return NULL;
+}
+
+struct scene_menitem *scene_menuitem_find_seq(const struct scene_obj_menu *menu,
+ uint seq)
+{
+ struct scene_menitem *item;
+ uint i;
+
+ i = 0;
+ list_for_each_entry(item, &menu->item_head, sibling) {
+ if (i == seq)
+ return item;
+ i++;
+ }
+
+ return NULL;
+}
+
+struct scene_menitem *scene_menuitem_find_val(const struct scene_obj_menu *menu,
+ int val)
+{
+ struct scene_menitem *item;
+ uint i;
+
+ i = 0;
+ list_for_each_entry(item, &menu->item_head, sibling) {
+ if (item->value == INT_MAX ? val == i : item->value == val)
+ return item;
+ i++;
+ }
+
+ return NULL;
+}
+
+/**
+ * update_pointers() - Update the pointer object and handle highlights
+ *
+ * @menu: Menu to update
+ * @id: ID of menu item to select/deselect
+ * @point: true if @id is being selected, false if it is being deselected
+ */
+static int update_pointers(struct scene_obj_menu *menu, uint id, bool point)
+{
+ struct scene *scn = menu->obj.scene;
+ const bool stack = scn->expo->show_highlight;
+ const struct scene_menitem *item;
+ int ret;
+
+ item = scene_menuitem_find(menu, id);
+ if (!item)
+ return log_msg_ret("itm", -ENOENT);
+
+ /* adjust the pointer object to point to the selected item */
+ if (menu->pointer_id && item && point) {
+ struct scene_obj *label;
+
+ label = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
+
+ ret = scene_obj_set_pos(scn, menu->pointer_id,
+ menu->obj.bbox.x0 + 200, label->bbox.y0);
+ if (ret < 0)
+ return log_msg_ret("ptr", ret);
+ }
+
+ if (stack) {
+ uint id;
+ int val;
+
+ point &= scn->highlight_id == menu->obj.id;
+ val = point ? SCENEOF_POINT : 0;
+ id = item->desc_id;
+ if (!id)
+ id = item->label_id;
+ if (!id)
+ id = item->key_id;
+ scene_obj_flag_clrset(scn, id, SCENEOF_POINT, val);
+ }
+
+ return 0;
+}
+
+/**
+ * menu_point_to_item() - Point to a particular menu item
+ *
+ * Sets the currently pointed-to / highlighted menu item
+ */
+static int menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
+{
+ int ret;
+
+ if (menu->cur_item_id) {
+ ret = update_pointers(menu, menu->cur_item_id, false);
+ if (ret)
+ return log_msg_ret("mpi", ret);
+ }
+ menu->cur_item_id = item_id;
+ ret = update_pointers(menu, item_id, true);
+ if (ret)
+ return log_msg_ret("mpu", ret);
+
+ return 0;
+}
+
+void scene_menu_calc_bbox(struct scene_obj_menu *menu,
+ struct vidconsole_bbox *bbox)
+{
+ const struct expo_theme *theme = &menu->obj.scene->expo->theme;
+ const struct scene_menitem *item;
+ int inset = theme->menu_inset;
+ int i;
+
+ for (i = 0; i < SCENEBB_count; i++)
+ bbox[i].valid = false;
+
+ scene_bbox_union(menu->obj.scene, menu->title_id, 0,
+ &bbox[SCENEBB_all]);
+
+ list_for_each_entry(item, &menu->item_head, sibling) {
+ struct vidconsole_bbox local;
+
+ local.valid = false;
+ scene_bbox_union(menu->obj.scene, item->label_id, inset,
+ &local);
+ scene_bbox_union(menu->obj.scene, item->key_id, 0, &local);
+ scene_bbox_union(menu->obj.scene, item->desc_id, 0, &local);
+ scene_bbox_union(menu->obj.scene, item->preview_id, 0, &local);
+
+ scene_bbox_join(&local, 0, &bbox[SCENEBB_all]);
+
+ /* Get the bounding box of all individual fields */
+ scene_bbox_union(menu->obj.scene, item->label_id, inset,
+ &bbox[SCENEBB_label]);
+ scene_bbox_union(menu->obj.scene, item->key_id, inset,
+ &bbox[SCENEBB_key]);
+ scene_bbox_union(menu->obj.scene, item->desc_id, inset,
+ &bbox[SCENEBB_desc]);
+
+ if (menu->cur_item_id == item->id)
+ scene_bbox_join(&local, 0, &bbox[SCENEBB_curitem]);
+ }
+
+ /*
+ * subtract the final menuitem's gap to keep the inset the same top and
+ * bottom
+ */
+ bbox[SCENEBB_label].y1 -= theme->menuitem_gap_y;
+}
+
+int scene_menu_calc_dims(struct scene_obj_menu *menu)
+{
+ struct vidconsole_bbox bbox[SCENEBB_count], *cur;
+ const struct scene_menitem *item;
+
+ scene_menu_calc_bbox(menu, bbox);
+
+ /* Make all field types the same width */
+ list_for_each_entry(item, &menu->item_head, sibling) {
+ cur = &bbox[SCENEBB_label];
+ if (cur->valid)
+ scene_obj_set_width(menu->obj.scene, item->label_id,
+ cur->x1 - cur->x0);
+ cur = &bbox[SCENEBB_key];
+ if (cur->valid)
+ scene_obj_set_width(menu->obj.scene, item->key_id,
+ cur->x1 - cur->x0);
+ cur = &bbox[SCENEBB_desc];
+ if (cur->valid)
+ scene_obj_set_width(menu->obj.scene, item->desc_id,
+ cur->x1 - cur->x0);
+ }
+
+ cur = &bbox[SCENEBB_all];
+ if (cur->valid) {
+ menu->obj.dims.x = cur->x1 - cur->x0;
+ menu->obj.dims.y = cur->y1 - cur->y0;
+
+ menu->obj.bbox.x1 = cur->x1;
+ menu->obj.bbox.y1 = cur->y1;
+ }
+
+ return 0;
+}
+
+int scene_menu_arrange(struct scene *scn, struct expo_arrange_info *arr,
+ struct scene_obj_menu *menu)
+{
+ const bool open = menu->obj.flags & SCENEOF_OPEN;
+ struct expo *exp = scn->expo;
+ const bool stack = exp->popup;
+ const struct expo_theme *theme = &exp->theme;
+ struct scene_menitem *item;
+ uint sel_id;
+ int x, y;
+ int ret;
+
+ x = menu->obj.bbox.x0;
+ y = menu->obj.bbox.y0;
+ if (menu->title_id) {
+ int width;
+
+ ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.bbox.x0, y);
+ if (ret < 0)
+ return log_msg_ret("tit", ret);
+
+ ret = scene_obj_get_hw(scn, menu->title_id, &width);
+ if (ret < 0)
+ return log_msg_ret("hei", ret);
+
+ if (stack)
+ x += arr->label_width + theme->menu_title_margin_x;
+ else
+ y += ret * 2;
+ }
+
+ /*
+ * Currently everything is hard-coded to particular columns so this
+ * won't work on small displays and looks strange if the font size is
+ * small. This can be updated once text measuring is supported in
+ * vidconsole
+ */
+ sel_id = menu->cur_item_id;
+ list_for_each_entry(item, &menu->item_head, sibling) {
+ bool selected;
+ int height;
+
+ ret = scene_obj_get_hw(scn, item->label_id, NULL);
+ if (ret < 0)
+ return log_msg_ret("get", ret);
+ height = ret;
+
+ if (item->flags & SCENEMIF_GAP_BEFORE)
+ y += height;
+
+ /* select an item if not done already */
+ if (!sel_id)
+ sel_id = item->id;
+
+ selected = sel_id == item->id;
+
+ /*
+ * Put the label on the left, then leave a space for the
+ * pointer, then the key and the description
+ */
+ ret = scene_obj_set_pos(scn, item->label_id,
+ x + theme->menu_inset, y);
+ if (ret < 0)
+ return log_msg_ret("nam", ret);
+ scene_obj_set_hide(scn, item->label_id,
+ stack && !open && !selected);
+
+ if (item->key_id) {
+ ret = scene_obj_set_pos(scn, item->key_id, x + 230, y);
+ if (ret < 0)
+ return log_msg_ret("key", ret);
+ }
+
+ if (item->desc_id) {
+ ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y);
+ if (ret < 0)
+ return log_msg_ret("des", ret);
+ }
+
+ if (item->preview_id) {
+ bool hide;
+
+ /*
+ * put all previews on top of each other, on the right
+ * size of the display
+ */
+ ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
+ if (ret < 0)
+ return log_msg_ret("prev", ret);
+
+ hide = menu->cur_item_id != item->id;
+ ret = scene_obj_set_hide(scn, item->preview_id, hide);
+ if (ret < 0)
+ return log_msg_ret("hid", ret);
+ }
+
+ if (!stack || open)
+ y += height + theme->menuitem_gap_y;
+ }
+
+ if (sel_id)
+ menu_point_to_item(menu, sel_id);
+ menu->obj.bbox.x1 = menu->obj.bbox.x0 + menu->obj.dims.x;
+ menu->obj.bbox.y1 = menu->obj.bbox.y0 + menu->obj.dims.y;
+ menu->obj.flags |= SCENEOF_SIZE_VALID;
+
+ return 0;
+}
+
+int scene_menu(struct scene *scn, const char *name, uint id,
+ struct scene_obj_menu **menup)
+{
+ struct scene_obj_menu *menu;
+ int ret;
+
+ ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
+ sizeof(struct scene_obj_menu),
+ (struct scene_obj **)&menu);
+ if (ret < 0)
+ return log_msg_ret("obj", -ENOMEM);
+
+ if (menup)
+ *menup = menu;
+ INIT_LIST_HEAD(&menu->item_head);
+
+ return menu->obj.id;
+}
+
+static struct scene_menitem *scene_menu_find_key(struct scene *scn,
+ struct scene_obj_menu *menu,
+ int key)
+{
+ struct scene_menitem *item;
+
+ list_for_each_entry(item, &menu->item_head, sibling) {
+ if (item->key_id) {
+ struct scene_obj_txt *txt;
+ const char *str;
+
+ txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
+ if (txt) {
+ str = expo_get_str(scn->expo, txt->gen.str_id);
+ if (str && *str == key)
+ return item;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
+ struct expo_action *event)
+{
+ const bool open = menu->obj.flags & SCENEOF_OPEN;
+ struct scene_menitem *item, *cur, *key_item;
+
+ cur = NULL;
+ key_item = NULL;
+
+ if (!list_empty(&menu->item_head)) {
+ list_for_each_entry(item, &menu->item_head, sibling) {
+ /* select an item if not done already */
+ if (menu->cur_item_id == item->id) {
+ cur = item;
+ break;
+ }
+ }
+ }
+
+ if (!cur)
+ return -ENOTTY;
+
+ switch (key) {
+ case BKEY_UP:
+ if (item != list_first_entry(&menu->item_head,
+ struct scene_menitem, sibling)) {
+ item = list_entry(item->sibling.prev,
+ struct scene_menitem, sibling);
+ event->type = EXPOACT_POINT_ITEM;
+ event->select.id = item->id;
+ log_debug("up to item %d\n", event->select.id);
+ }
+ break;
+ case BKEY_DOWN:
+ if (!list_is_last(&item->sibling, &menu->item_head)) {
+ item = list_entry(item->sibling.next,
+ struct scene_menitem, sibling);
+ event->type = EXPOACT_POINT_ITEM;
+ event->select.id = item->id;
+ log_debug("down to item %d\n", event->select.id);
+ }
+ break;
+ case BKEY_SELECT:
+ event->type = EXPOACT_SELECT;
+ event->select.id = item->id;
+ log_debug("select item %d\n", event->select.id);
+ break;
+ case BKEY_QUIT:
+ if (scn->expo->popup && open) {
+ event->type = EXPOACT_CLOSE;
+ event->select.id = menu->obj.id;
+ } else {
+ event->type = EXPOACT_QUIT;
+ log_debug("menu quit\n");
+ }
+ break;
+ case '0'...'9':
+ key_item = scene_menu_find_key(scn, menu, key);
+ if (key_item) {
+ event->type = EXPOACT_SELECT;
+ event->select.id = key_item->id;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
+ uint key_id, uint label_id, uint desc_id, uint preview_id,
+ uint flags, struct scene_menitem **itemp)
+{
+ struct scene_obj_menu *menu;
+ struct scene_menitem *item;
+
+ menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
+ if (!menu)
+ return log_msg_ret("find", -ENOENT);
+
+ /* Check that the text ID is valid */
+ if (!scene_obj_find(scn, label_id, SCENEOBJT_TEXT))
+ return log_msg_ret("txt", -EINVAL);
+
+ item = calloc(1, sizeof(struct scene_menitem));
+ if (!item)
+ return log_msg_ret("item", -ENOMEM);
+ item->name = strdup(name);
+ if (!item->name) {
+ free(item);
+ return log_msg_ret("name", -ENOMEM);
+ }
+
+ item->id = resolve_id(scn->expo, id);
+ item->key_id = key_id;
+ item->label_id = label_id;
+ item->desc_id = desc_id;
+ item->preview_id = preview_id;
+ item->flags = flags;
+ item->value = INT_MAX;
+ list_add_tail(&item->sibling, &menu->item_head);
+
+ if (itemp)
+ *itemp = item;
+
+ return item->id;
+}
+
+int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
+{
+ struct scene_obj_menu *menu;
+ struct scene_obj_txt *txt;
+
+ menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
+ if (!menu)
+ return log_msg_ret("menu", -ENOENT);
+
+ /* Check that the ID is valid */
+ if (title_id) {
+ txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
+ if (!txt)
+ return log_msg_ret("txt", -EINVAL);
+ }
+
+ menu->title_id = title_id;
+
+ return 0;
+}
+
+int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
+{
+ struct scene_obj_menu *menu;
+ struct scene_obj *obj;
+
+ menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
+ if (!menu)
+ return log_msg_ret("menu", -ENOENT);
+
+ /* Check that the ID is valid */
+ if (pointer_id) {
+ obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
+ if (!obj)
+ return log_msg_ret("obj", -EINVAL);
+ }
+
+ menu->pointer_id = pointer_id;
+
+ return 0;
+}
+
+int scene_menu_select_item(struct scene *scn, uint id, uint cur_item_id)
+{
+ struct scene_obj_menu *menu;
+ int ret;
+
+ menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
+ if (!menu)
+ return log_msg_ret("menu", -ENOENT);
+
+ ret = menu_point_to_item(menu, cur_item_id);
+ if (ret)
+ return log_msg_ret("msi", ret);
+
+ return 0;
+}
+
+int scene_menu_get_cur_item(struct scene *scn, uint id)
+{
+ struct scene_obj_menu *menu;
+
+ menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
+ if (!menu)
+ return log_msg_ret("menu", -ENOENT);
+
+ return menu->cur_item_id;
+}
+
+int scene_menu_display(struct scene_obj_menu *menu)
+{
+ struct scene *scn = menu->obj.scene;
+ struct scene_obj_txt *pointer;
+ struct expo *exp = scn->expo;
+ struct scene_menitem *item;
+ const char *pstr;
+
+ printf("U-Boot : Boot Menu\n\n");
+ if (menu->title_id) {
+ struct scene_obj_txt *txt;
+ const char *str;
+
+ txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
+ if (!txt)
+ return log_msg_ret("txt", -EINVAL);
+
+ str = expo_get_str(exp, txt->gen.str_id);
+ printf("%s\n\n", str ? str : "");
+ }
+
+ if (list_empty(&menu->item_head))
+ return 0;
+
+ pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
+ if (pointer)
+ pstr = expo_get_str(scn->expo, pointer->gen.str_id);
+
+ list_for_each_entry(item, &menu->item_head, sibling) {
+ struct scene_obj_txt *key = NULL, *label = NULL;
+ struct scene_obj_txt *desc = NULL;
+ const char *kstr = NULL, *lstr = NULL, *dstr = NULL;
+
+ key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
+ if (key)
+ kstr = expo_get_str(exp, key->gen.str_id);
+
+ label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
+ if (label)
+ lstr = expo_get_str(exp, label->gen.str_id);
+
+ desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
+ if (desc)
+ dstr = expo_get_str(exp, desc->gen.str_id);
+
+ printf("%3s %3s %-10s %s\n",
+ pointer && menu->cur_item_id == item->id ? pstr : "",
+ kstr, lstr, dstr);
+ }
+
+ return -ENOTSUPP;
+}
+
+int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu)
+{
+ struct scene_menitem *item;
+
+ scene_render_deps(scn, menu->title_id);
+ scene_render_deps(scn, menu->cur_item_id);
+ scene_render_deps(scn, menu->pointer_id);
+
+ list_for_each_entry(item, &menu->item_head, sibling) {
+ scene_render_deps(scn, item->key_id);
+ scene_render_deps(scn, item->label_id);
+ scene_render_deps(scn, item->desc_id);
+ }
+
+ return 0;
+}
diff --git a/boot/scene_textedit.c b/boot/scene_textedit.c
new file mode 100644
index 00000000000..8242eb39806
--- /dev/null
+++ b/boot/scene_textedit.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Implementation of a menu in a scene
+ *
+ * Copyright 2025 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY LOGC_EXPO
+
+#include <expo.h>
+#include <log.h>
+#include <linux/err.h>
+#include <linux/sizes.h>
+#include "scene_internal.h"
+
+enum {
+ INITIAL_SIZE = SZ_4K,
+};
+
+int scene_texted(struct scene *scn, const char *name, uint id, uint str_id,
+ struct scene_obj_txtedit **teditp)
+{
+ struct scene_obj_txtedit *ted;
+ char *buf;
+ int ret;
+
+ ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXTEDIT,
+ sizeof(struct scene_obj_txtedit),
+ (struct scene_obj **)&ted);
+ if (ret < 0)
+ return log_msg_ret("obj", ret);
+
+ abuf_init(&ted->buf);
+ if (!abuf_realloc(&ted->buf, INITIAL_SIZE))
+ return log_msg_ret("buf", -ENOMEM);
+ buf = abuf_data(&ted->buf);
+ *buf = '\0';
+
+ ret = scene_txt_generic_init(scn->expo, &ted->gen, name, str_id, buf);
+ if (ret)
+ return log_msg_ret("teg", ret);
+ if (teditp)
+ *teditp = ted;
+
+ return ted->obj.id;
+}
+
+int scene_txted_set_font(struct scene *scn, uint id, const char *font_name,
+ uint font_size)
+{
+ struct scene_obj_txtedit *ted;
+
+ ted = scene_obj_find(scn, id, SCENEOBJT_TEXTEDIT);
+ if (!ted)
+ return log_msg_ret("find", -ENOENT);
+ ted->gen.font_name = font_name;
+ ted->gen.font_size = font_size;
+
+ return 0;
+}
diff --git a/boot/scene_textline.c b/boot/scene_textline.c
new file mode 100644
index 00000000000..7bc35a997dc
--- /dev/null
+++ b/boot/scene_textline.c
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Implementation of a menu in a scene
+ *
+ * Copyright 2023 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY LOGC_EXPO
+
+#include <expo.h>
+#include <menu.h>
+#include <log.h>
+#include <video_console.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include "scene_internal.h"
+
+int scene_textline(struct scene *scn, const char *name, uint id, uint max_chars,
+ struct scene_obj_textline **tlinep)
+{
+ struct scene_obj_textline *tline;
+ char *buf;
+ int ret;
+
+ if (max_chars >= EXPO_MAX_CHARS)
+ return log_msg_ret("chr", -E2BIG);
+
+ ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXTLINE,
+ sizeof(struct scene_obj_textline),
+ (struct scene_obj **)&tline);
+ if (ret < 0)
+ return log_msg_ret("obj", -ENOMEM);
+ if (!abuf_init_size(&tline->buf, max_chars + 1))
+ return log_msg_ret("buf", -ENOMEM);
+ buf = abuf_data(&tline->buf);
+ *buf = '\0';
+ tline->pos = max_chars;
+ tline->max_chars = max_chars;
+
+ if (tlinep)
+ *tlinep = tline;
+
+ return tline->obj.id;
+}
+
+void scene_textline_calc_bbox(struct scene_obj_textline *tline,
+ struct vidconsole_bbox *bbox,
+ struct vidconsole_bbox *edit_bbox)
+{
+ const struct expo_theme *theme = &tline->obj.scene->expo->theme;
+
+ bbox->valid = false;
+ scene_bbox_union(tline->obj.scene, tline->label_id, 0, bbox);
+ scene_bbox_union(tline->obj.scene, tline->edit_id, 0, bbox);
+
+ edit_bbox->valid = false;
+ scene_bbox_union(tline->obj.scene, tline->edit_id, theme->menu_inset,
+ edit_bbox);
+}
+
+int scene_textline_calc_dims(struct scene_obj_textline *tline)
+{
+ struct scene_obj *obj = &tline->obj;
+ struct scene *scn = obj->scene;
+ struct vidconsole_bbox bbox;
+ struct scene_obj_txt *txt;
+ int ret;
+
+ txt = scene_obj_find(scn, tline->edit_id, SCENEOBJT_NONE);
+ if (!txt)
+ return log_msg_ret("dim", -ENOENT);
+
+ ret = vidconsole_nominal(scn->expo->cons, txt->gen.font_name,
+ txt->gen.font_size, tline->max_chars, &bbox);
+ if (ret)
+ return log_msg_ret("nom", ret);
+
+ if (bbox.valid) {
+ obj->dims.x = bbox.x1 - bbox.x0;
+ obj->dims.y = bbox.y1 - bbox.y0;
+ if (!(obj->flags & SCENEOF_SIZE_VALID)) {
+ obj->bbox.x1 = obj->bbox.x0 + obj->dims.x;
+ obj->bbox.y1 = obj->bbox.y0 + obj->dims.y;
+ obj->flags |= SCENEOF_SIZE_VALID;
+ }
+ scene_obj_set_size(scn, tline->edit_id,
+ obj->bbox.x1 - obj->bbox.x0,
+ obj->bbox.y1 - obj->bbox.y0);
+ }
+
+ return 0;
+}
+
+int scene_textline_arrange(struct scene *scn, struct expo_arrange_info *arr,
+ struct scene_obj_textline *tline)
+{
+ const bool open = tline->obj.flags & SCENEOF_OPEN;
+ bool point;
+ int x, y;
+ int ret;
+
+ x = tline->obj.bbox.x0;
+ y = tline->obj.bbox.y0;
+ if (tline->label_id) {
+ ret = scene_obj_set_pos(scn, tline->label_id,
+ tline->obj.bbox.x0, y);
+ if (ret < 0)
+ return log_msg_ret("tit", ret);
+
+ ret = scene_obj_set_pos(scn, tline->edit_id,
+ tline->obj.bbox.x0 + 200, y);
+ if (ret < 0)
+ return log_msg_ret("tit", ret);
+
+ ret = scene_obj_get_hw(scn, tline->label_id, NULL);
+ if (ret < 0)
+ return log_msg_ret("hei", ret);
+
+ y += ret * 2;
+ }
+
+ point = scn->highlight_id == tline->obj.id;
+ point &= !open;
+ scene_obj_flag_clrset(scn, tline->edit_id, SCENEOF_POINT,
+ point ? SCENEOF_POINT : 0);
+
+ return 0;
+}
+
+int scene_textline_send_key(struct scene *scn, struct scene_obj_textline *tline,
+ int key, struct expo_action *event)
+{
+ const bool open = tline->obj.flags & SCENEOF_OPEN;
+
+ log_debug("key=%d\n", key);
+ switch (key) {
+ case BKEY_QUIT:
+ if (open) {
+ event->type = EXPOACT_CLOSE;
+ event->select.id = tline->obj.id;
+
+ /* Copy the backup text from the scene buffer */
+ memcpy(abuf_data(&tline->buf), abuf_data(&scn->buf),
+ abuf_size(&scn->buf));
+ } else {
+ event->type = EXPOACT_QUIT;
+ log_debug("menu quit\n");
+ }
+ break;
+ case BKEY_SELECT:
+ if (!open)
+ break;
+ event->type = EXPOACT_CLOSE;
+ event->select.id = tline->obj.id;
+ key = '\n';
+ fallthrough;
+ default: {
+ struct udevice *cons = scn->expo->cons;
+ int ret;
+
+ ret = vidconsole_entry_restore(cons, &scn->entry_save);
+ if (ret)
+ return log_msg_ret("sav", ret);
+ ret = cread_line_process_ch(&scn->cls, key);
+ ret = vidconsole_entry_save(cons, &scn->entry_save);
+ if (ret)
+ return log_msg_ret("sav", ret);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int scene_textline_render_deps(struct scene *scn,
+ struct scene_obj_textline *tline)
+{
+ const bool open = tline->obj.flags & SCENEOF_OPEN;
+ struct udevice *cons = scn->expo->cons;
+ struct scene_obj_txt *txt;
+ int ret;
+
+ scene_render_deps(scn, tline->label_id);
+ scene_render_deps(scn, tline->edit_id);
+
+ /* show the vidconsole cursor if open */
+ if (open) {
+ /* get the position within the field */
+ txt = scene_obj_find(scn, tline->edit_id, SCENEOBJT_NONE);
+ if (!txt)
+ return log_msg_ret("cur", -ENOENT);
+
+ if (txt->gen.font_name || txt->gen.font_size) {
+ ret = vidconsole_select_font(cons,
+ txt->gen.font_name,
+ txt->gen.font_size);
+ } else {
+ ret = vidconsole_select_font(cons, NULL, 0);
+ }
+
+ ret = vidconsole_entry_restore(cons, &scn->entry_save);
+ if (ret)
+ return log_msg_ret("sav", ret);
+
+ vidconsole_set_cursor_visible(cons, true, txt->obj.bbox.x0,
+ txt->obj.bbox.y0, scn->cls.num);
+ }
+
+ return 0;
+}
+
+int scene_textline_open(struct scene *scn, struct scene_obj_textline *tline)
+{
+ struct udevice *cons = scn->expo->cons;
+ struct scene_obj_txt *txt;
+ int ret;
+
+ /* Copy the text into the scene buffer in case the edit is cancelled */
+ memcpy(abuf_data(&scn->buf), abuf_data(&tline->buf),
+ abuf_size(&scn->buf));
+
+ /* get the position of the editable */
+ txt = scene_obj_find(scn, tline->edit_id, SCENEOBJT_NONE);
+ if (!txt)
+ return log_msg_ret("cur", -ENOENT);
+
+ vidconsole_set_cursor_pos(cons, txt->obj.bbox.x0, txt->obj.bbox.y0);
+ vidconsole_entry_start(cons);
+ cli_cread_init(&scn->cls, abuf_data(&tline->buf), tline->max_chars);
+ scn->cls.insert = true;
+ ret = vidconsole_entry_save(cons, &scn->entry_save);
+ if (ret)
+ return log_msg_ret("sav", ret);
+
+ return 0;
+}
diff --git a/boot/upl_common.c b/boot/upl_common.c
new file mode 100644
index 00000000000..3924423abd5
--- /dev/null
+++ b/boot/upl_common.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * UPL handoff command functions
+ *
+ * Copyright 2024 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <string.h>
+#include <upl.h>
+
+/* Names of bootmodes */
+const char *const bootmode_names[UPLBM_COUNT] = {
+ [UPLBM_FULL] = "full",
+ [UPLBM_MINIMAL] = "minimal",
+ [UPLBM_FAST] = "fast",
+ [UPLBM_DIAG] = "diag",
+ [UPLBM_DEFAULT] = "default",
+ [UPLBM_S2] = "s2",
+ [UPLBM_S3] = "s3",
+ [UPLBM_S4] = "s4",
+ [UPLBM_S5] = "s5",
+ [UPLBM_FACTORY] = "factory",
+ [UPLBM_FLASH] = "flash",
+ [UPLBM_RECOVERY] = "recovery",
+};
+
+/* Names of memory usages */
+const char *const usage_names[UPLUS_COUNT] = {
+ [UPLUS_ACPI_RECLAIM] = "acpi-reclaim",
+ [UPLUS_ACPI_NVS] = "acpi-nvs",
+ [UPLUS_BOOT_CODE] = "boot-code",
+ [UPLUS_BOOT_DATA] = "boot-data",
+ [UPLUS_RUNTIME_CODE] = "runtime-code",
+ [UPLUS_RUNTIME_DATA] = "runtime-data",
+};
+
+/* Names of access types */
+const char *const access_types[UPLUS_COUNT] = {
+ [UPLAT_MMIO] = "mmio",
+ [UPLAT_IO] = "io",
+};
+
+/* Names of graphics formats */
+const char *const graphics_formats[UPLUS_COUNT] = {
+ [UPLGF_ARGB32] = "a8r8g8b8",
+ [UPLGF_ABGR32] = "a8b8g8r8",
+ [UPLGF_ABGR64] = "a16b16g16r16",
+};
+
+void upl_init(struct upl *upl)
+{
+ memset(upl, '\0', sizeof(struct upl));
+ alist_init_struct(&upl->image, struct upl_image);
+ alist_init_struct(&upl->mem, struct upl_mem);
+ alist_init_struct(&upl->memmap, struct upl_memmap);
+ alist_init_struct(&upl->memres, struct upl_memres);
+}
diff --git a/boot/upl_common.h b/boot/upl_common.h
new file mode 100644
index 00000000000..cc517dc1de9
--- /dev/null
+++ b/boot/upl_common.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * UPL handoff command functions
+ *
+ * Copyright 2024 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __UPL_COMMON_H
+#define __UPL_COMMON_H
+
+/* Names of bootmodes */
+extern const char *const bootmode_names[UPLBM_COUNT];
+
+/* Names of memory usages */
+extern const char *const usage_names[UPLUS_COUNT];
+
+/* Names of access types */
+extern const char *const access_types[UPLUS_COUNT];
+
+/* Names of graphics formats */
+extern const char *const graphics_formats[UPLUS_COUNT];
+
+#endif /* __UPL_COMMON_H */
diff --git a/boot/upl_read.c b/boot/upl_read.c
new file mode 100644
index 00000000000..be3e1d116e1
--- /dev/null
+++ b/boot/upl_read.c
@@ -0,0 +1,588 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * UPL handoff parsing
+ *
+ * Copyright 2024 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <log.h>
+#include <upl.h>
+#include <dm/ofnode.h>
+#include "upl_common.h"
+
+/**
+ * read_addr() - Read an address
+ *
+ * Reads an address in the correct format, either 32- or 64-bit
+ *
+ * @upl: UPL state
+ * @node: Node to read from
+ * @prop: Property name to read
+ * @addr: Place to put the address
+ * Return: 0 if OK, -ve on error
+ */
+static int read_addr(const struct upl *upl, ofnode node, const char *prop,
+ ulong *addrp)
+{
+ int ret;
+
+ if (upl->addr_cells == 1) {
+ u32 val;
+
+ ret = ofnode_read_u32(node, prop, &val);
+ if (!ret)
+ *addrp = val;
+ } else {
+ u64 val;
+
+ ret = ofnode_read_u64(node, prop, &val);
+ if (!ret)
+ *addrp = val;
+ }
+
+ return ret;
+}
+
+/**
+ * read_size() - Read a size
+ *
+ * Reads a size in the correct format, either 32- or 64-bit
+ *
+ * @upl: UPL state
+ * @node: Node to read from
+ * @prop: Property name to read
+ * @addr: Place to put the size
+ * Return: 0 if OK, -ve on error
+ */
+static int read_size(const struct upl *upl, ofnode node, const char *prop,
+ ulong *sizep)
+{
+ int ret;
+
+ if (upl->size_cells == 1) {
+ u32 val;
+
+ ret = ofnode_read_u32(node, prop, &val);
+ if (!ret)
+ *sizep = val;
+ } else {
+ u64 val;
+
+ ret = ofnode_read_u64(node, prop, &val);
+ if (!ret)
+ *sizep = val;
+ }
+
+ return ret;
+}
+
+/**
+ * ofnode_read_bitmask() - Read a bit mask from a string list
+ *
+ * @node: Node to read from
+ * @prop: Property name to read
+ * @names: Array of names for each bit
+ * @count: Number of array entries
+ * @value: Returns resulting bit-mask value on success
+ * Return: 0 if OK, -EINVAL if a bit number is not defined, -ENOSPC if the
+ * string is too long for the (internal) buffer, -EINVAL if no such property
+ */
+static int ofnode_read_bitmask(ofnode node, const char *prop,
+ const char *const names[], uint count,
+ uint *valuep)
+{
+ const char **list;
+ const char **strp;
+ uint val;
+ uint bit;
+ int ret;
+
+ ret = ofnode_read_string_list(node, prop, &list);
+ if (ret < 0)
+ return log_msg_ret("rea", ret);
+
+ val = 0;
+ for (strp = list; *strp; strp++) {
+ const char *str = *strp;
+ bool found = false;
+
+ for (bit = 0; bit < count; bit++) {
+ if (!strcmp(str, names[bit])) {
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ val |= BIT(bit);
+ else
+ log_warning("%s/%s: Invalid value '%s'\n",
+ ofnode_get_name(node), prop, str);
+ }
+ *valuep = val;
+
+ return 0;
+}
+
+/**
+ * ofnode_read_value() - Read a string value as an int using a lookup
+ *
+ * @node: Node to read from
+ * @prop: Property name to read
+ * @names: Array of names for each int value
+ * @count: Number of array entries
+ * @valuep: Returns int value read
+ * Return: 0 if OK, -EINVAL if a bit number is not defined, -ENOENT if the
+ * property does not exist
+ */
+static int ofnode_read_value(ofnode node, const char *prop,
+ const char *const names[], uint count,
+ uint *valuep)
+{
+ const char *str;
+ int i;
+
+ str = ofnode_read_string(node, prop);
+ if (!str)
+ return log_msg_ret("rd", -ENOENT);
+
+ for (i = 0; i < count; i++) {
+ if (!strcmp(names[i], str)) {
+ *valuep = i;
+ return 0;
+ }
+ }
+
+ log_debug("Unnamed value '%s'\n", str);
+ return log_msg_ret("val", -EINVAL);
+}
+
+static int read_uint(ofnode node, const char *prop, uint *valp)
+{
+ u32 val;
+ int ret;
+
+ ret = ofnode_read_u32(node, prop, &val);
+ if (ret)
+ return ret;
+ *valp = val;
+
+ return 0;
+}
+
+/**
+ * decode_root_props() - Decode root properties from the tree
+ *
+ * @upl: UPL state
+ * @node: Node to decode
+ * Return 0 if OK, -ve on error
+ */
+static int decode_root_props(struct upl *upl, ofnode node)
+{
+ int ret;
+
+ ret = read_uint(node, UPLP_ADDRESS_CELLS, &upl->addr_cells);
+ if (!ret)
+ ret = read_uint(node, UPLP_SIZE_CELLS, &upl->size_cells);
+ if (ret)
+ return log_msg_ret("cel", ret);
+
+ return 0;
+}
+
+/**
+ * decode_root_props() - Decode UPL parameters from the tree
+ *
+ * @upl: UPL state
+ * @node: Node to decode
+ * Return 0 if OK, -ve on error
+ */
+static int decode_upl_params(struct upl *upl, ofnode options)
+{
+ ofnode node;
+ int ret;
+
+ node = ofnode_find_subnode(options, UPLN_UPL_PARAMS);
+ if (!ofnode_valid(node))
+ return log_msg_ret("par", -EINVAL);
+ log_debug("decoding '%s'\n", ofnode_get_name(node));
+
+ ret = read_addr(upl, node, UPLP_SMBIOS, &upl->smbios);
+ if (ret)
+ return log_msg_ret("smb", ret);
+ ret = read_addr(upl, node, UPLP_ACPI, &upl->acpi);
+ if (ret)
+ return log_msg_ret("acp", ret);
+ ret = ofnode_read_bitmask(node, UPLP_BOOTMODE, bootmode_names,
+ UPLBM_COUNT, &upl->bootmode);
+ if (ret)
+ return log_msg_ret("boo", ret);
+ ret = read_uint(node, UPLP_ADDR_WIDTH, &upl->addr_width);
+ if (ret)
+ return log_msg_ret("add", ret);
+ ret = read_uint(node, UPLP_ACPI_NVS_SIZE, &upl->acpi_nvs_size);
+ if (ret)
+ return log_msg_ret("nvs", ret);
+
+ return 0;
+}
+
+/**
+ * decode_upl_images() - Decode /options/upl-image nodes
+ *
+ * @node: /options node in which to look for the node
+ * Return 0 if OK, -ve on error
+ */
+static int decode_upl_images(struct upl *upl, ofnode options)
+{
+ ofnode node, images;
+ int ret;
+
+ images = ofnode_find_subnode(options, UPLN_UPL_IMAGE);
+ if (!ofnode_valid(images))
+ return log_msg_ret("img", -EINVAL);
+ log_debug("decoding '%s'\n", ofnode_get_name(images));
+
+ ret = read_addr(upl, images, UPLP_FIT, &upl->fit);
+ if (!ret)
+ ret = read_uint(images, UPLP_CONF_OFFSET, &upl->conf_offset);
+ if (ret)
+ return log_msg_ret("cnf", ret);
+
+ ofnode_for_each_subnode(node, images) {
+ struct upl_image img;
+
+ ret = read_addr(upl, node, UPLP_LOAD, &img.load);
+ if (!ret)
+ ret = read_size(upl, node, UPLP_SIZE, &img.size);
+ if (!ret)
+ ret = read_uint(node, UPLP_OFFSET, &img.offset);
+ img.description = ofnode_read_string(node, UPLP_DESCRIPTION);
+ if (!img.description)
+ return log_msg_ret("sim", ret);
+ if (!alist_add(&upl->image, img))
+ return log_msg_ret("img", -ENOMEM);
+ }
+
+ return 0;
+}
+
+/**
+ * decode_addr_size() - Decide a set of addr/size pairs
+ *
+ * Each base/size value from the devicetree is written to the region list
+ *
+ * @upl: UPL state
+ * @buf: Bytes to decode
+ * @size: Number of bytes to decode
+ * @regions: List of regions to process (struct memregion)
+ * Returns: number of regions found, if OK, else -ve on error
+ */
+static int decode_addr_size(const struct upl *upl, const char *buf, int size,
+ struct alist *regions)
+{
+ const char *ptr, *end = buf + size;
+ int i;
+
+ alist_init_struct(regions, struct memregion);
+ ptr = buf;
+ for (i = 0; ptr < end; i++) {
+ struct memregion reg;
+
+ if (upl->addr_cells == 1)
+ reg.base = fdt32_to_cpu(*(u32 *)ptr);
+ else
+ reg.base = fdt64_to_cpu(*(u64 *)ptr);
+ ptr += upl->addr_cells * sizeof(u32);
+
+ if (upl->size_cells == 1)
+ reg.size = fdt32_to_cpu(*(u32 *)ptr);
+ else
+ reg.size = fdt64_to_cpu(*(u64 *)ptr);
+ ptr += upl->size_cells * sizeof(u32);
+ if (ptr > end)
+ return -ENOSPC;
+
+ if (!alist_add(regions, reg))
+ return log_msg_ret("reg", -ENOMEM);
+ }
+
+ return i;
+}
+
+/**
+ * node_matches_at() - Check if a node name matches "base@..."
+ *
+ * Return: true if the node name matches the base string followed by an @ sign;
+ * false otherwise
+ */
+static bool node_matches_at(ofnode node, const char *base)
+{
+ const char *name = ofnode_get_name(node);
+ int len = strlen(base);
+
+ return !strncmp(base, name, len) && name[len] == '@';
+}
+
+/**
+ * decode_upl_memory_node() - Decode a /memory node from the tree
+ *
+ * @upl: UPL state
+ * @node: Node to decode
+ * Return 0 if OK, -ve on error
+ */
+static int decode_upl_memory_node(struct upl *upl, ofnode node)
+{
+ struct upl_mem mem;
+ const char *buf;
+ int size, len;
+
+ buf = ofnode_read_prop(node, UPLP_REG, &size);
+ if (!buf) {
+ log_warning("Node '%s': Missing '%s' property\n",
+ ofnode_get_name(node), UPLP_REG);
+ return log_msg_ret("reg", -EINVAL);
+ }
+ len = decode_addr_size(upl, buf, size, &mem.region);
+ if (len < 0)
+ return log_msg_ret("buf", len);
+ mem.hotpluggable = ofnode_read_bool(node, UPLP_HOTPLUGGABLE);
+ if (!alist_add(&upl->mem, mem))
+ return log_msg_ret("mem", -ENOMEM);
+
+ return 0;
+}
+
+/**
+ * decode_upl_memmap() - Decode memory-map nodes from the tree
+ *
+ * @upl: UPL state
+ * @root: Parent node containing the /memory-map nodes
+ * Return 0 if OK, -ve on error
+ */
+static int decode_upl_memmap(struct upl *upl, ofnode root)
+{
+ ofnode node;
+
+ ofnode_for_each_subnode(node, root) {
+ struct upl_memmap memmap;
+ int size, len, ret;
+ const char *buf;
+
+ memmap.name = ofnode_get_name(node);
+ memmap.usage = 0;
+
+ buf = ofnode_read_prop(node, UPLP_REG, &size);
+ if (!buf) {
+ log_warning("Node '%s': Missing '%s' property\n",
+ ofnode_get_name(node), UPLP_REG);
+ continue;
+ }
+
+ len = decode_addr_size(upl, buf, size, &memmap.region);
+ if (len < 0)
+ return log_msg_ret("buf", len);
+ ret = ofnode_read_bitmask(node, UPLP_USAGE, usage_names,
+ UPLUS_COUNT, &memmap.usage);
+ if (ret && ret != -EINVAL) /* optional property */
+ return log_msg_ret("bit", ret);
+
+ if (!alist_add(&upl->memmap, memmap))
+ return log_msg_ret("mmp", -ENOMEM);
+ }
+
+ return 0;
+}
+
+/**
+ * decode_upl_memres() - Decode reserved-memory nodes from the tree
+ *
+ * @upl: UPL state
+ * @root: Parent node containing the reserved-memory nodes
+ * Return 0 if OK, -ve on error
+ */
+static int decode_upl_memres(struct upl *upl, ofnode root)
+{
+ ofnode node;
+
+ ofnode_for_each_subnode(node, root) {
+ struct upl_memres memres;
+ const char *buf;
+ int size, len;
+
+ log_debug("decoding '%s'\n", ofnode_get_name(node));
+ memres.name = ofnode_get_name(node);
+
+ buf = ofnode_read_prop(node, UPLP_REG, &size);
+ if (!buf) {
+ log_warning("Node '%s': Missing 'reg' property\n",
+ ofnode_get_name(node));
+ continue;
+ }
+
+ len = decode_addr_size(upl, buf, size, &memres.region);
+ if (len < 0)
+ return log_msg_ret("buf", len);
+ memres.no_map = ofnode_read_bool(node, UPLP_NO_MAP);
+
+ if (!alist_add(&upl->memres, memres))
+ return log_msg_ret("mre", -ENOMEM);
+ }
+
+ return 0;
+}
+
+/**
+ * decode_upl_serial() - Decode the serial node
+ *
+ * @upl: UPL state
+ * @root: Parent node contain node
+ * Return 0 if OK, -ve on error
+ */
+static int decode_upl_serial(struct upl *upl, ofnode node)
+{
+ struct upl_serial *ser = &upl->serial;
+ const char *buf;
+ int len, size;
+ int ret;
+
+ ser->compatible = ofnode_read_string(node, UPLP_COMPATIBLE);
+ if (!ser->compatible) {
+ log_warning("Node '%s': Missing compatible string\n",
+ ofnode_get_name(node));
+ return log_msg_ret("com", -EINVAL);
+ }
+ ret = read_uint(node, UPLP_CLOCK_FREQUENCY, &ser->clock_frequency);
+ if (!ret)
+ ret = read_uint(node, UPLP_CURRENT_SPEED, &ser->current_speed);
+ if (ret)
+ return log_msg_ret("spe", ret);
+
+ buf = ofnode_read_prop(node, UPLP_REG, &size);
+ if (!buf) {
+ log_warning("Node '%s': Missing 'reg' property\n",
+ ofnode_get_name(node));
+ return log_msg_ret("reg", -EINVAL);
+ }
+
+ len = decode_addr_size(upl, buf, sizeof(buf), &ser->reg);
+ if (len < 0)
+ return log_msg_ret("buf", len);
+
+ /* set defaults */
+ ser->reg_io_shift = UPLD_REG_IO_SHIFT;
+ ser->reg_offset = UPLD_REG_OFFSET;
+ ser->reg_io_width = UPLD_REG_IO_WIDTH;
+ read_uint(node, UPLP_REG_IO_SHIFT, &ser->reg_io_shift);
+ read_uint(node, UPLP_REG_OFFSET, &ser->reg_offset);
+ read_uint(node, UPLP_REG_IO_WIDTH, &ser->reg_io_width);
+ read_addr(upl, node, UPLP_VIRTUAL_REG, &ser->virtual_reg);
+ ret = ofnode_read_value(node, UPLP_ACCESS_TYPE, access_types,
+ ARRAY_SIZE(access_types), &ser->access_type);
+ if (ret && ret != -ENOENT)
+ return log_msg_ret("ser", ret);
+
+ return 0;
+}
+
+/**
+ * decode_upl_graphics() - Decode graphics node
+ *
+ * @upl: UPL state
+ * @root: Node to decode
+ * Return 0 if OK, -ve on error
+ */
+static int decode_upl_graphics(struct upl *upl, ofnode node)
+{
+ struct upl_graphics *gra = &upl->graphics;
+ const char *buf, *compat;
+ int len, size;
+ int ret;
+
+ compat = ofnode_read_string(node, UPLP_COMPATIBLE);
+ if (!compat) {
+ log_warning("Node '%s': Missing compatible string\n",
+ ofnode_get_name(node));
+ return log_msg_ret("com", -EINVAL);
+ }
+ if (strcmp(UPLC_GRAPHICS, compat)) {
+ log_warning("Node '%s': Ignoring compatible '%s'\n",
+ ofnode_get_name(node), compat);
+ return 0;
+ }
+
+ buf = ofnode_read_prop(node, UPLP_REG, &size);
+ if (!buf) {
+ log_warning("Node '%s': Missing 'reg' property\n",
+ ofnode_get_name(node));
+ return log_msg_ret("reg", -EINVAL);
+ }
+
+ len = decode_addr_size(upl, buf, size, &gra->reg);
+ if (len < 0)
+ return log_msg_ret("buf", len);
+
+ ret = read_uint(node, UPLP_WIDTH, &gra->width);
+ if (!ret)
+ ret = read_uint(node, UPLP_HEIGHT, &gra->height);
+ if (!ret)
+ ret = read_uint(node, UPLP_STRIDE, &gra->stride);
+ if (!ret) {
+ ret = ofnode_read_value(node, UPLP_GRAPHICS_FORMAT,
+ graphics_formats,
+ ARRAY_SIZE(graphics_formats),
+ &gra->format);
+ }
+ if (ret)
+ return log_msg_ret("pro", ret);
+
+ return 0;
+}
+
+int upl_read_handoff(struct upl *upl, oftree tree)
+{
+ ofnode root, node;
+ int ret;
+
+ if (!oftree_valid(tree))
+ return log_msg_ret("tre", -EINVAL);
+
+ root = oftree_root(tree);
+
+ upl_init(upl);
+ ret = decode_root_props(upl, root);
+ if (ret)
+ return log_msg_ret("roo", ret);
+
+ ofnode_for_each_subnode(node, root) {
+ const char *name = ofnode_get_name(node);
+
+ log_debug("decoding '%s'\n", name);
+ if (!strcmp(UPLN_OPTIONS, name)) {
+ ret = decode_upl_params(upl, node);
+ if (ret)
+ return log_msg_ret("opt", ret);
+
+ ret = decode_upl_images(upl, node);
+ } else if (node_matches_at(node, UPLN_MEMORY)) {
+ ret = decode_upl_memory_node(upl, node);
+ } else if (!strcmp(UPLN_MEMORY_MAP, name)) {
+ ret = decode_upl_memmap(upl, node);
+ } else if (!strcmp(UPLN_MEMORY_RESERVED, name)) {
+ ret = decode_upl_memres(upl, node);
+ } else if (node_matches_at(node, UPLN_SERIAL)) {
+ ret = decode_upl_serial(upl, node);
+ } else if (node_matches_at(node, UPLN_GRAPHICS)) {
+ ret = decode_upl_graphics(upl, node);
+ } else {
+ log_debug("Unknown node '%s'\n", name);
+ ret = 0;
+ }
+ if (ret)
+ return log_msg_ret("err", ret);
+ }
+
+ return 0;
+}
diff --git a/boot/upl_write.c b/boot/upl_write.c
new file mode 100644
index 00000000000..7d637c15ba0
--- /dev/null
+++ b/boot/upl_write.c
@@ -0,0 +1,622 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * UPL handoff generation
+ *
+ * Copyright 2024 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <log.h>
+#include <upl.h>
+#include <dm/ofnode.h>
+#include "upl_common.h"
+
+/**
+ * write_addr() - Write an address
+ *
+ * Writes an address in the correct format, either 32- or 64-bit
+ *
+ * @upl: UPL state
+ * @node: Node to write to
+ * @prop: Property name to write
+ * @addr: Address to write
+ * Return: 0 if OK, -ve on error
+ */
+static int write_addr(const struct upl *upl, ofnode node, const char *prop,
+ ulong addr)
+{
+ int ret;
+
+ if (upl->addr_cells == 1)
+ ret = ofnode_write_u32(node, prop, addr);
+ else
+ ret = ofnode_write_u64(node, prop, addr);
+
+ return ret;
+}
+
+/**
+ * write_size() - Write a size
+ *
+ * Writes a size in the correct format, either 32- or 64-bit
+ *
+ * @upl: UPL state
+ * @node: Node to write to
+ * @prop: Property name to write
+ * @size: Size to write
+ * Return: 0 if OK, -ve on error
+ */
+static int write_size(const struct upl *upl, ofnode node, const char *prop,
+ ulong size)
+{
+ int ret;
+
+ if (upl->size_cells == 1)
+ ret = ofnode_write_u32(node, prop, size);
+ else
+ ret = ofnode_write_u64(node, prop, size);
+
+ return ret;
+}
+
+/**
+ * ofnode_write_bitmask() - Write a bit mask as a string list
+ *
+ * @node: Node to write to
+ * @prop: Property name to write
+ * @names: Array of names for each bit
+ * @count: Number of array entries
+ * @value: Bit-mask value to write
+ * Return: 0 if OK, -EINVAL if a bit number is not defined, -ENOSPC if the
+ * string is too long for the (internal) buffer
+ */
+static int ofnode_write_bitmask(ofnode node, const char *prop,
+ const char *const names[], uint count,
+ uint value)
+{
+ char buf[128];
+ char *ptr, *end = buf + sizeof(buf);
+ uint bit;
+ int ret;
+
+ ptr = buf;
+ for (bit = 0; bit < count; bit++) {
+ if (value & BIT(bit)) {
+ const char *str = names[bit];
+ uint len;
+
+ if (!str) {
+ log_debug("Unnamed bit number %d\n", bit);
+ return log_msg_ret("bit", -EINVAL);
+ }
+ len = strlen(str) + 1;
+ if (ptr + len > end) {
+ log_debug("String array too long\n");
+ return log_msg_ret("bit", -ENOSPC);
+ }
+
+ memcpy(ptr, str, len);
+ ptr += len;
+ }
+ }
+
+ ret = ofnode_write_prop(node, prop, buf, ptr - buf, true);
+ if (ret)
+ return log_msg_ret("wri", ret);
+
+ return 0;
+}
+
+/**
+ * ofnode_write_value() - Write an int as a string value using a lookup
+ *
+ * @node: Node to write to
+ * @prop: Property name to write
+ * @names: Array of names for each int value
+ * @count: Number of array entries
+ * @value: Int value to write
+ * Return: 0 if OK, -EINVAL if a bit number is not defined, -ENOSPC if the
+ * string is too long for the (internal) buffer
+ */
+static int ofnode_write_value(ofnode node, const char *prop,
+ const char *const names[], uint count,
+ uint value)
+{
+ const char *str;
+ int ret;
+
+ if (value >= count) {
+ log_debug("Value of range %d\n", value);
+ return log_msg_ret("val", -ERANGE);
+ }
+ str = names[value];
+ if (!str) {
+ log_debug("Unnamed value %d\n", value);
+ return log_msg_ret("val", -EINVAL);
+ }
+ ret = ofnode_write_string(node, prop, str);
+ if (ret)
+ return log_msg_ret("wri", ret);
+
+ return 0;
+}
+
+/**
+ * add_root_props() - Add root properties to the tree
+ *
+ * @node: Node to add to
+ * Return 0 if OK, -ve on error
+ */
+static int add_root_props(const struct upl *upl, ofnode node)
+{
+ int ret;
+
+ ret = ofnode_write_u32(node, UPLP_ADDRESS_CELLS, upl->addr_cells);
+ if (!ret)
+ ret = ofnode_write_u32(node, UPLP_SIZE_CELLS, upl->size_cells);
+ if (ret)
+ return log_msg_ret("cel", ret);
+
+ return 0;
+}
+
+/**
+ * add_upl_params() - Add UPL parameters node
+ *
+ * @upl: UPL state
+ * @options: /options node to add to
+ * Return 0 if OK, -ve on error
+ */
+static int add_upl_params(const struct upl *upl, ofnode options)
+{
+ ofnode node;
+ int ret;
+
+ ret = ofnode_add_subnode(options, UPLN_UPL_PARAMS, &node);
+ if (ret)
+ return log_msg_ret("img", ret);
+
+ ret = write_addr(upl, node, UPLP_SMBIOS, upl->smbios);
+ if (!ret)
+ ret = write_addr(upl, node, UPLP_ACPI, upl->acpi);
+ if (!ret && upl->bootmode)
+ ret = ofnode_write_bitmask(node, UPLP_BOOTMODE, bootmode_names,
+ UPLBM_COUNT, upl->bootmode);
+ if (!ret)
+ ret = ofnode_write_u32(node, UPLP_ADDR_WIDTH, upl->addr_width);
+ if (!ret)
+ ret = ofnode_write_u32(node, UPLP_ACPI_NVS_SIZE,
+ upl->acpi_nvs_size);
+ if (ret)
+ return log_msg_ret("cnf", ret);
+
+ return 0;
+}
+
+/**
+ * add_upl_image() - Add /options/upl-image nodes and properties to the tree
+ *
+ * @upl: UPL state
+ * @node: /options node to add to
+ * Return 0 if OK, -ve on error
+ */
+static int add_upl_image(const struct upl *upl, ofnode options)
+{
+ ofnode node;
+ int ret, i;
+
+ ret = ofnode_add_subnode(options, UPLN_UPL_IMAGE, &node);
+ if (ret)
+ return log_msg_ret("img", ret);
+
+ if (upl->fit)
+ ret = ofnode_write_u32(node, UPLP_FIT, upl->fit);
+ if (!ret && upl->conf_offset)
+ ret = ofnode_write_u32(node, UPLP_CONF_OFFSET,
+ upl->conf_offset);
+ if (ret)
+ return log_msg_ret("cnf", ret);
+
+ for (i = 0; i < upl->image.count; i++) {
+ const struct upl_image *img = alist_get(&upl->image, i,
+ struct upl_image);
+ ofnode subnode;
+ char name[10];
+
+ snprintf(name, sizeof(name), UPLN_IMAGE "-%d", i + 1);
+ ret = ofnode_add_subnode(node, name, &subnode);
+ if (ret)
+ return log_msg_ret("sub", ret);
+
+ ret = write_addr(upl, subnode, UPLP_LOAD, img->load);
+ if (!ret)
+ ret = write_size(upl, subnode, UPLP_SIZE, img->size);
+ if (!ret && img->offset)
+ ret = ofnode_write_u32(subnode, UPLP_OFFSET,
+ img->offset);
+ ret = ofnode_write_string(subnode, UPLP_DESCRIPTION,
+ img->description);
+ if (ret)
+ return log_msg_ret("sim", ret);
+ }
+
+ return 0;
+}
+
+/**
+ * buffer_addr_size() - Generate a set of addr/size pairs
+ *
+ * Each base/size value from each region is written to the buffer in a suitable
+ * format to be written to the devicetree
+ *
+ * @upl: UPL state
+ * @buf: Buffer to write to
+ * @size: Buffer size
+ * @num_regions: Number of regions to process
+ * @region: List of regions to process (struct memregion)
+ * Returns: Number of bytes written, or -ENOSPC if the buffer is too small
+ */
+static int buffer_addr_size(const struct upl *upl, char *buf, int size,
+ uint num_regions, const struct alist *region)
+{
+ char *ptr, *end = buf + size;
+ int i;
+
+ ptr = buf;
+ for (i = 0; i < num_regions; i++) {
+ const struct memregion *reg = alist_get(region, i,
+ struct memregion);
+
+ if (upl->addr_cells == 1)
+ *(u32 *)ptr = cpu_to_fdt32(reg->base);
+ else
+ *(u64 *)ptr = cpu_to_fdt64(reg->base);
+ ptr += upl->addr_cells * sizeof(u32);
+
+ if (upl->size_cells == 1)
+ *(u32 *)ptr = cpu_to_fdt32(reg->size);
+ else
+ *(u64 *)ptr = cpu_to_fdt64(reg->size);
+ ptr += upl->size_cells * sizeof(u32);
+ if (ptr > end)
+ return -ENOSPC;
+ }
+
+ return ptr - buf;
+}
+
+/**
+ * add_upl_memory() - Add /memory nodes to the tree
+ *
+ * @upl: UPL state
+ * @root: Parent node to contain the new /memory nodes
+ * Return 0 if OK, -ve on error
+ */
+static int add_upl_memory(const struct upl *upl, ofnode root)
+{
+ int i;
+
+ for (i = 0; i < upl->mem.count; i++) {
+ const struct upl_mem *mem = alist_get(&upl->mem, i,
+ struct upl_mem);
+ char buf[mem->region.count * sizeof(64) * 2];
+ const struct memregion *first;
+ char name[26];
+ int ret, len;
+ ofnode node;
+
+ if (!mem->region.count) {
+ log_debug("Memory %d has no regions\n", i);
+ return log_msg_ret("reg", -EINVAL);
+ }
+ first = alist_get(&mem->region, 0, struct memregion);
+ sprintf(name, UPLN_MEMORY "@0x%lx", first->base);
+ ret = ofnode_add_subnode(root, name, &node);
+ if (ret)
+ return log_msg_ret("mem", ret);
+
+ len = buffer_addr_size(upl, buf, sizeof(buf), mem->region.count,
+ &mem->region);
+ if (len < 0)
+ return log_msg_ret("buf", len);
+
+ ret = ofnode_write_prop(node, UPLP_REG, buf, len, true);
+ if (!ret && mem->hotpluggable)
+ ret = ofnode_write_bool(node, UPLP_HOTPLUGGABLE,
+ mem->hotpluggable);
+ if (ret)
+ return log_msg_ret("lst", ret);
+ }
+
+ return 0;
+}
+
+/**
+ * add_upl_memmap() - Add memory-map nodes to the tree
+ *
+ * @upl: UPL state
+ * @root: Parent node to contain the new /memory-map node and its subnodes
+ * Return 0 if OK, -ve on error
+ */
+static int add_upl_memmap(const struct upl *upl, ofnode root)
+{
+ ofnode mem_node;
+ int i, ret;
+
+ if (!upl->memmap.count)
+ return 0;
+ ret = ofnode_add_subnode(root, UPLN_MEMORY_MAP, &mem_node);
+ if (ret)
+ return log_msg_ret("img", ret);
+
+ for (i = 0; i < upl->memmap.count; i++) {
+ const struct upl_memmap *memmap = alist_get(&upl->memmap, i,
+ struct upl_memmap);
+ char buf[memmap->region.count * sizeof(64) * 2];
+ const struct memregion *first;
+ char name[26];
+ int ret, len;
+ ofnode node;
+
+ if (!memmap->region.count) {
+ log_debug("Memory %d has no regions\n", i);
+ return log_msg_ret("reg", -EINVAL);
+ }
+ first = alist_get(&memmap->region, 0, struct memregion);
+ sprintf(name, "%s@0x%lx", memmap->name, first->base);
+ ret = ofnode_add_subnode(mem_node, name, &node);
+ if (ret)
+ return log_msg_ret("memmap", ret);
+
+ len = buffer_addr_size(upl, buf, sizeof(buf),
+ memmap->region.count, &memmap->region);
+ if (len < 0)
+ return log_msg_ret("buf", len);
+ ret = ofnode_write_prop(node, UPLP_REG, buf, len, true);
+ if (!ret && memmap->usage)
+ ret = ofnode_write_bitmask(node, UPLP_USAGE,
+ usage_names,
+ UPLUS_COUNT, memmap->usage);
+ if (ret)
+ return log_msg_ret("lst", ret);
+ }
+
+ return 0;
+}
+
+/**
+ * add_upl_memres() - Add /memory-reserved nodes to the tree
+ *
+ * @upl: UPL state
+ * @root: Parent node to contain the new node
+ * Return 0 if OK, -ve on error
+ */
+static int add_upl_memres(const struct upl *upl, ofnode root,
+ bool skip_existing)
+{
+ ofnode mem_node;
+ int i, ret;
+
+ if (!upl->memmap.count)
+ return 0;
+ ret = ofnode_add_subnode(root, UPLN_MEMORY_RESERVED, &mem_node);
+ if (ret) {
+ if (skip_existing && ret == -EEXIST)
+ return 0;
+ return log_msg_ret("img", ret);
+ }
+
+ for (i = 0; i < upl->memres.count; i++) {
+ const struct upl_memres *memres = alist_get(&upl->memres, i,
+ struct upl_memres);
+ char buf[memres->region.count * sizeof(64) * 2];
+ const struct memregion *first;
+ char name[26];
+ int ret, len;
+ ofnode node;
+
+ if (!memres->region.count) {
+ log_debug("Memory %d has no regions\n", i);
+ return log_msg_ret("reg", -EINVAL);
+ }
+ first = alist_get(&memres->region, 0, struct memregion);
+ sprintf(name, "%s@0x%lx", memres->name, first->base);
+ ret = ofnode_add_subnode(mem_node, name, &node);
+ if (ret)
+ return log_msg_ret("memres", ret);
+
+ len = buffer_addr_size(upl, buf, sizeof(buf),
+ memres->region.count, &memres->region);
+ ret = ofnode_write_prop(node, UPLP_REG, buf, len, true);
+ if (!ret && memres->no_map)
+ ret = ofnode_write_bool(node, UPLP_NO_MAP,
+ memres->no_map);
+ if (ret)
+ return log_msg_ret("lst", ret);
+ }
+
+ return 0;
+}
+
+/**
+ * add_upl_serial() - Add serial node
+ *
+ * @upl: UPL state
+ * @root: Parent node to contain the new node
+ * Return 0 if OK, -ve on error
+ */
+static int add_upl_serial(const struct upl *upl, ofnode root,
+ bool skip_existing)
+{
+ const struct upl_serial *ser = &upl->serial;
+ const struct memregion *first;
+ char name[26];
+ ofnode node;
+ int ret;
+
+ if (!ser->compatible || skip_existing)
+ return 0;
+ if (!ser->reg.count)
+ return log_msg_ret("ser", -EINVAL);
+ first = alist_get(&ser->reg, 0, struct memregion);
+ sprintf(name, UPLN_SERIAL "@0x%lx", first->base);
+ ret = ofnode_add_subnode(root, name, &node);
+ if (ret)
+ return log_msg_ret("img", ret);
+ ret = ofnode_write_string(node, UPLP_COMPATIBLE, ser->compatible);
+ if (!ret)
+ ret = ofnode_write_u32(node, UPLP_CLOCK_FREQUENCY,
+ ser->clock_frequency);
+ if (!ret)
+ ret = ofnode_write_u32(node, UPLP_CURRENT_SPEED,
+ ser->current_speed);
+ if (!ret) {
+ char buf[16];
+ int len;
+
+ len = buffer_addr_size(upl, buf, sizeof(buf), 1, &ser->reg);
+ if (len < 0)
+ return log_msg_ret("buf", len);
+
+ ret = ofnode_write_prop(node, UPLP_REG, buf, len, true);
+ }
+ if (!ret && ser->reg_io_shift != UPLD_REG_IO_SHIFT)
+ ret = ofnode_write_u32(node, UPLP_REG_IO_SHIFT,
+ ser->reg_io_shift);
+ if (!ret && ser->reg_offset != UPLD_REG_OFFSET)
+ ret = ofnode_write_u32(node, UPLP_REG_OFFSET, ser->reg_offset);
+ if (!ret && ser->reg_io_width != UPLD_REG_IO_WIDTH)
+ ret = ofnode_write_u32(node, UPLP_REG_IO_WIDTH,
+ ser->reg_io_width);
+ if (!ret && ser->virtual_reg)
+ ret = write_addr(upl, node, UPLP_VIRTUAL_REG, ser->virtual_reg);
+ if (!ret) {
+ ret = ofnode_write_value(node, UPLP_ACCESS_TYPE, access_types,
+ ARRAY_SIZE(access_types),
+ ser->access_type);
+ }
+ if (ret)
+ return log_msg_ret("ser", ret);
+
+ return 0;
+}
+
+/**
+ * add_upl_graphics() - Add graphics node
+ *
+ * @upl: UPL state
+ * @root: Parent node to contain the new node
+ * Return 0 if OK, -ve on error
+ */
+static int add_upl_graphics(const struct upl *upl, ofnode root)
+{
+ const struct upl_graphics *gra = &upl->graphics;
+ const struct memregion *first;
+ char name[36];
+ ofnode node;
+ int ret;
+
+ if (!gra->reg.count)
+ return log_msg_ret("gra", -ENOENT);
+ first = alist_get(&gra->reg, 0, struct memregion);
+ sprintf(name, UPLN_GRAPHICS "@0x%lx", first->base);
+ ret = ofnode_add_subnode(root, name, &node);
+ if (ret)
+ return log_msg_ret("gra", ret);
+
+ ret = ofnode_write_string(node, UPLP_COMPATIBLE, UPLC_GRAPHICS);
+ if (!ret) {
+ char buf[16];
+ int len;
+
+ len = buffer_addr_size(upl, buf, sizeof(buf), 1, &gra->reg);
+ if (len < 0)
+ return log_msg_ret("buf", len);
+
+ ret = ofnode_write_prop(node, UPLP_REG, buf, len, true);
+ }
+ if (!ret)
+ ret = ofnode_write_u32(node, UPLP_WIDTH, gra->width);
+ if (!ret)
+ ret = ofnode_write_u32(node, UPLP_HEIGHT, gra->height);
+ if (!ret)
+ ret = ofnode_write_u32(node, UPLP_STRIDE, gra->stride);
+ if (!ret) {
+ ret = ofnode_write_value(node, UPLP_GRAPHICS_FORMAT,
+ graphics_formats,
+ ARRAY_SIZE(graphics_formats),
+ gra->format);
+ }
+ if (ret)
+ return log_msg_ret("pro", ret);
+
+ return 0;
+}
+
+int upl_write_handoff(const struct upl *upl, ofnode root, bool skip_existing)
+{
+ ofnode options;
+ int ret;
+
+ ret = add_root_props(upl, root);
+ if (ret)
+ return log_msg_ret("ad1", ret);
+ ret = ofnode_add_subnode(root, UPLN_OPTIONS, &options);
+ if (ret && ret != -EEXIST)
+ return log_msg_ret("opt", -EINVAL);
+
+ ret = add_upl_params(upl, options);
+ if (ret)
+ return log_msg_ret("ad1", ret);
+
+ ret = add_upl_image(upl, options);
+ if (ret)
+ return log_msg_ret("ad2", ret);
+
+ ret = add_upl_memory(upl, root);
+ if (ret)
+ return log_msg_ret("ad3", ret);
+
+ ret = add_upl_memmap(upl, root);
+ if (ret)
+ return log_msg_ret("ad4", ret);
+
+ ret = add_upl_memres(upl, root, skip_existing);
+ if (ret)
+ return log_msg_ret("ad5", ret);
+
+ ret = add_upl_serial(upl, root, skip_existing);
+ if (ret)
+ return log_msg_ret("ad6", ret);
+
+ ret = add_upl_graphics(upl, root);
+ if (ret && ret != -ENOENT)
+ return log_msg_ret("ad6", ret);
+
+ return 0;
+}
+
+int upl_create_handoff_tree(const struct upl *upl, oftree *treep)
+{
+ ofnode root;
+ oftree tree;
+ int ret;
+
+ ret = oftree_new(&tree);
+ if (ret)
+ return log_msg_ret("new", ret);
+
+ root = oftree_root(tree);
+ if (!ofnode_valid(root))
+ return log_msg_ret("roo", -EINVAL);
+
+ ret = upl_write_handoff(upl, root, false);
+ if (ret)
+ return log_msg_ret("wr", ret);
+
+ *treep = tree;
+
+ return 0;
+}
diff --git a/boot/vbe.c b/boot/vbe.c
new file mode 100644
index 00000000000..00673de7ee2
--- /dev/null
+++ b/boot/vbe.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Verified Boot for Embedded (VBE) access functions
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <bootmeth.h>
+#include <bootstd.h>
+#include <dm.h>
+#include <image.h>
+#include <vbe.h>
+#include <dm/uclass-internal.h>
+
+/**
+ * is_vbe() - Check if a device is a VBE method
+ *
+ * @dev: Device to check
+ * @return true if this is a VBE bootmth device, else false
+ */
+static bool is_vbe(struct udevice *dev)
+{
+ return !strncmp("vbe", dev->driver->name, 3);
+}
+
+int vbe_find_next_device(struct udevice **devp)
+{
+ for (uclass_find_next_device(devp);
+ *devp;
+ uclass_find_next_device(devp)) {
+ if (is_vbe(*devp))
+ return 0;
+ }
+
+ return 0;
+}
+
+int vbe_find_first_device(struct udevice **devp)
+{
+ uclass_find_first_device(UCLASS_BOOTMETH, devp);
+ if (!*devp || is_vbe(*devp))
+ return 0;
+
+ return vbe_find_next_device(devp);
+}
+
+int vbe_list(void)
+{
+ struct bootstd_priv *std;
+ struct udevice *dev;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return ret;
+
+ printf("%3s %-3s %-15s %-15s %s\n", "#", "Sel", "Device", "Driver",
+ "Description");
+ printf("%3s %-3s %-15s %-15s %s\n", "---", "---", "--------------",
+ "--------------", "-----------");
+ for (ret = vbe_find_first_device(&dev); dev;
+ ret = vbe_find_next_device(&dev)) {
+ const struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ printf("%3d %-3s %-15s %-15s %s\n", dev_seq(dev),
+ std->vbe_bootmeth == dev ? "*" : "", dev->name,
+ dev->driver->name, plat->desc);
+ }
+ printf("%3s %-3s %-15s %-15s %s\n", "---", "---", "--------------",
+ "--------------", "-----------");
+
+ return 0;
+}
+
+int vbe_select(struct udevice *dev)
+{
+ struct bootstd_priv *std;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return ret;
+ std->vbe_bootmeth = dev;
+
+ return 0;
+}
+
+int vbe_find_by_any(const char *name, struct udevice **devp)
+{
+ struct udevice *dev;
+ int ret, seq;
+ char *endp;
+
+ seq = simple_strtol(name, &endp, 16);
+
+ /* Select by name */
+ if (*endp) {
+ ret = uclass_get_device_by_name(UCLASS_BOOTMETH, name, &dev);
+ if (ret) {
+ printf("Cannot probe VBE bootmeth '%s' (err=%d)\n", name,
+ ret);
+ return ret;
+ }
+
+ /* select by number */
+ } else {
+ ret = uclass_get_device_by_seq(UCLASS_BOOTMETH, seq, &dev);
+ if (ret) {
+ printf("Cannot find '%s' (err=%d)\n", name, ret);
+ return ret;
+ }
+ }
+
+ *devp = dev;
+
+ return 0;
+}
diff --git a/boot/vbe_abrec.c b/boot/vbe_abrec.c
new file mode 100644
index 00000000000..6d0f622262d
--- /dev/null
+++ b/boot/vbe_abrec.c
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Verified Boot for Embedded (VBE) 'simple' method
+ *
+ * Copyright 2024 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY LOGC_BOOT
+
+#include <dm.h>
+#include <memalign.h>
+#include <mmc.h>
+#include <dm/ofnode.h>
+#include "vbe_abrec.h"
+
+int abrec_read_priv(ofnode node, struct abrec_priv *priv)
+{
+ memset(priv, '\0', sizeof(*priv));
+ if (ofnode_read_u32(node, "area-start", &priv->area_start) ||
+ ofnode_read_u32(node, "area-size", &priv->area_size) ||
+ ofnode_read_u32(node, "version-offset", &priv->version_offset) ||
+ ofnode_read_u32(node, "version-size", &priv->version_size) ||
+ ofnode_read_u32(node, "state-offset", &priv->state_offset) ||
+ ofnode_read_u32(node, "state-size", &priv->state_size))
+ return log_msg_ret("read", -EINVAL);
+ ofnode_read_u32(node, "skip-offset", &priv->skip_offset);
+ priv->storage = strdup(ofnode_read_string(node, "storage"));
+ if (!priv->storage)
+ return log_msg_ret("str", -EINVAL);
+
+ return 0;
+}
+
+int abrec_read_nvdata(struct abrec_priv *priv, struct udevice *blk,
+ struct abrec_state *state)
+{
+ ALLOC_CACHE_ALIGN_BUFFER(u8, buf, MMC_MAX_BLOCK_LEN);
+ const struct vbe_nvdata *nvd = (struct vbe_nvdata *)buf;
+ uint flags;
+ int ret;
+
+ ret = vbe_read_nvdata(blk, priv->area_start + priv->state_offset,
+ priv->state_size, buf);
+ if (ret == -EPERM) {
+ memset(buf, '\0', MMC_MAX_BLOCK_LEN);
+ log_warning("Starting with empty state\n");
+ } else if (ret) {
+ return log_msg_ret("nv", ret);
+ }
+
+ state->fw_vernum = nvd->fw_vernum;
+ flags = nvd->flags;
+ state->try_count = flags & VBEF_TRY_COUNT_MASK;
+ state->try_b = flags & VBEF_TRY_B;
+ state->recovery = flags & VBEF_RECOVERY;
+ state->pick = (flags & VBEF_PICK_MASK) >> VBEF_PICK_SHIFT;
+
+ return 0;
+}
+
+int abrec_read_state(struct udevice *dev, struct abrec_state *state)
+{
+ struct abrec_priv *priv = dev_get_priv(dev);
+ struct udevice *blk;
+ int ret;
+
+ ret = vbe_get_blk(priv->storage, &blk);
+ if (ret)
+ return log_msg_ret("blk", ret);
+
+ ret = vbe_read_version(blk, priv->area_start + priv->version_offset,
+ state->fw_version, MAX_VERSION_LEN);
+ if (ret)
+ return log_msg_ret("ver", ret);
+ log_debug("version=%s\n", state->fw_version);
+
+ ret = abrec_read_nvdata(priv, blk, state);
+ if (ret)
+ return log_msg_ret("nvd", ret);
+
+ return 0;
+}
diff --git a/boot/vbe_abrec.h b/boot/vbe_abrec.h
new file mode 100644
index 00000000000..63c73297351
--- /dev/null
+++ b/boot/vbe_abrec.h
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Verified Boot for Embedded (VBE) vbe-abrec common file
+ *
+ * Copyright 2024 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __VBE_ABREC_H
+#define __VBE_ABREC_H
+
+#include <vbe.h>
+#include <dm/ofnode_decl.h>
+
+#include "vbe_common.h"
+
+struct bootflow;
+struct udevice;
+
+/**
+ * struct abrec_priv - information read from the device tree
+ *
+ * @area_start: Start offset of the VBE area in the device, in bytes
+ * @area_size: Total size of the VBE area
+ * @skip_offset: Size of an initial part of the device to skip, when using
+ * area_start. This is effectively added to area_start to calculate the
+ * actual start position on the device
+ * @state_offset: Offset from area_start of the VBE state, in bytes
+ * @state_size: Size of the state information
+ * @version_offset: Offset from from area_start of the VBE version info
+ * @version_size: Size of the version info
+ * @storage: Storage device to use, in the form <uclass><devnum>, e.g. "mmc1"
+ */
+struct abrec_priv {
+ u32 area_start;
+ u32 area_size;
+ u32 skip_offset;
+ u32 state_offset;
+ u32 state_size;
+ u32 version_offset;
+ u32 version_size;
+ const char *storage;
+};
+
+/** struct abrec_state - state information read from media
+ *
+ * The state on the media is converted into this more code-friendly structure.
+ *
+ * @fw_version: Firmware version string
+ * @fw_vernum: Firmware version number
+ * @try_count: Number of times the B firmware has been tried
+ * @try_b: true to try B firmware on the next boot
+ * @recovery: true to enter recovery firmware on the next boot
+ * @try_result: Result of trying to boot with the last firmware
+ * @pick: Firmware which was chosen in this boot
+ */
+struct abrec_state {
+ char fw_version[MAX_VERSION_LEN];
+ u32 fw_vernum;
+ u8 try_count;
+ bool try_b;
+ bool recovery;
+ enum vbe_try_result try_result;
+ enum vbe_pick_t pick;
+};
+
+/**
+ * abrec_read_fw_bootflow() - Read a bootflow for firmware
+ *
+ * Locates and loads the firmware image (FIT) needed for the next phase. The FIT
+ * should ideally use external data, to reduce the amount of it that needs to be
+ * read.
+ *
+ * @bdev: bootdev device containing the firmwre
+ * @bflow: Place to put the created bootflow, on success
+ * @return 0 if OK, -ve on error
+ */
+int abrec_read_bootflow_fw(struct udevice *dev, struct bootflow *bflow);
+
+/**
+ * vbe_simple_read_state() - Read the VBE simple state information
+ *
+ * @dev: VBE bootmeth
+ * @state: Place to put the state
+ * @return 0 if OK, -ve on error
+ */
+int abrec_read_state(struct udevice *dev, struct abrec_state *state);
+
+/**
+ * abrec_read_nvdata() - Read non-volatile data from a block device
+ *
+ * Reads the ABrec VBE nvdata from a device. This function reads a single block
+ * from the device, so the nvdata cannot be larger than that.
+ *
+ * @blk: Device to read from
+ * @offset: Offset to read, in bytes
+ * @size: Number of bytes to read
+ * @buf: Buffer to hold the data
+ * Return: 0 if OK, -E2BIG if @size > block size, -EBADF if the offset is not
+ * block-aligned, -EIO if an I/O error occurred, -EPERM if the header version is
+ * incorrect, the header size is invalid or the data fails its CRC check
+ */
+int abrec_read_nvdata(struct abrec_priv *priv, struct udevice *blk,
+ struct abrec_state *state);
+
+/**
+ * abrec_read_priv() - Read info from the devicetree
+ *
+ * @node: Node to read from
+ * @priv: Information to fill in
+ * Return 0 if OK, -EINVAL if something is wrong with the devicetree node
+ */
+int abrec_read_priv(ofnode node, struct abrec_priv *priv);
+
+#endif /* __VBE_ABREC_H */
diff --git a/boot/vbe_abrec_fw.c b/boot/vbe_abrec_fw.c
new file mode 100644
index 00000000000..d52bd9ddff0
--- /dev/null
+++ b/boot/vbe_abrec_fw.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Verified Boot for Embedded (VBE) loading firmware phases
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY LOGC_BOOT
+
+#include <binman_sym.h>
+#include <bloblist.h>
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <bootstage.h>
+#include <display_options.h>
+#include <dm.h>
+#include <image.h>
+#include <log.h>
+#include <mapmem.h>
+#include <memalign.h>
+#include <mmc.h>
+#include <spl.h>
+#include <vbe.h>
+#include <dm/device-internal.h>
+#include "vbe_abrec.h"
+#include "vbe_common.h"
+
+binman_sym_declare(ulong, spl_a, image_pos);
+binman_sym_declare(ulong, spl_b, image_pos);
+binman_sym_declare(ulong, spl_recovery, image_pos);
+
+binman_sym_declare(ulong, spl_a, size);
+binman_sym_declare(ulong, spl_b, size);
+binman_sym_declare(ulong, spl_recovery, size);
+
+binman_sym_declare(ulong, u_boot_a, image_pos);
+binman_sym_declare(ulong, u_boot_b, image_pos);
+binman_sym_declare(ulong, u_boot_recovery, image_pos);
+
+binman_sym_declare(ulong, u_boot_a, size);
+binman_sym_declare(ulong, u_boot_b, size);
+binman_sym_declare(ulong, u_boot_recovery, size);
+
+binman_sym_declare(ulong, vpl, image_pos);
+binman_sym_declare(ulong, vpl, size);
+
+static const char *const pick_names[] = {"A", "B", "Recovery"};
+
+/**
+ * abrec_read_bootflow_fw() - Create a bootflow for firmware
+ *
+ * Locates and loads the firmware image (FIT) needed for the next phase. The FIT
+ * should ideally use external data, to reduce the amount of it that needs to be
+ * read.
+ *
+ * @bdev: bootdev device containing the firmwre
+ * @meth: VBE abrec bootmeth
+ * @blow: Place to put the created bootflow, on success
+ * @return 0 if OK, -ve on error
+ */
+int abrec_read_bootflow_fw(struct udevice *dev, struct bootflow *bflow)
+{
+ struct udevice *media = dev_get_parent(bflow->dev);
+ struct udevice *meth = bflow->method;
+ struct abrec_priv *priv = dev_get_priv(meth);
+ ulong len, load_addr;
+ struct udevice *blk;
+ int ret;
+
+ log_debug("media=%s\n", media->name);
+ ret = blk_get_from_parent(media, &blk);
+ if (ret)
+ return log_msg_ret("med", ret);
+
+ ret = vbe_read_fit(blk, priv->area_start + priv->skip_offset,
+ priv->area_size, NULL, &load_addr, &len, &bflow->name);
+ if (ret)
+ return log_msg_ret("vbe", ret);
+
+ /* set up the bootflow with the info we obtained */
+ bflow->blk = blk;
+ bflow->buf = map_sysmem(load_addr, len);
+ bflow->size = len;
+
+ return 0;
+}
+
+static int abrec_run_vpl(struct udevice *blk, struct spl_image_info *image,
+ struct vbe_handoff *handoff)
+{
+ uint flags, tries, prev_result;
+ struct abrec_priv priv;
+ struct abrec_state state;
+ enum vbe_pick_t pick;
+ uint try_count;
+ ulong offset, size;
+ ulong ub_offset, ub_size;
+ ofnode node;
+ int ret;
+
+ node = vbe_get_node();
+ if (!ofnode_valid(node))
+ return log_msg_ret("nod", -EINVAL);
+
+ ret = abrec_read_priv(node, &priv);
+ if (ret)
+ return log_msg_ret("pri", ret);
+
+ ret = abrec_read_nvdata(&priv, blk, &state);
+ if (ret)
+ return log_msg_ret("sta", ret);
+
+ prev_result = state.try_result;
+ try_count = state.try_count;
+
+ if (state.recovery) {
+ pick = VBEP_RECOVERY;
+
+ /* if we are trying B but ran out of tries, use A */
+ } else if ((prev_result == VBETR_TRYING) && !tries) {
+ pick = VBEP_A;
+ state.try_result = VBETR_BAD;
+
+ /* if requested, try B */
+ } else if (flags & VBEF_TRY_B) {
+ pick = VBEP_B;
+
+ /* decrement the try count if not already zero */
+ if (try_count)
+ try_count--;
+ state.try_result = VBETR_TRYING;
+ } else {
+ pick = VBEP_A;
+ }
+ state.try_count = try_count;
+
+ switch (pick) {
+ case VBEP_A:
+ offset = binman_sym(ulong, spl_a, image_pos);
+ size = binman_sym(ulong, spl_a, size);
+ ub_offset = binman_sym(ulong, u_boot_a, image_pos);
+ ub_size = binman_sym(ulong, u_boot_a, size);
+ break;
+ case VBEP_B:
+ offset = binman_sym(ulong, spl_b, image_pos);
+ size = binman_sym(ulong, spl_b, size);
+ ub_offset = binman_sym(ulong, u_boot_b, image_pos);
+ ub_size = binman_sym(ulong, u_boot_b, size);
+ break;
+ case VBEP_RECOVERY:
+ offset = binman_sym(ulong, spl_recovery, image_pos);
+ size = binman_sym(ulong, spl_recovery, size);
+ ub_offset = binman_sym(ulong, u_boot_recovery, image_pos);
+ ub_size = binman_sym(ulong, u_boot_recovery, size);
+ break;
+ }
+ log_debug("pick=%d, offset=%lx size=%lx\n", pick, offset, size);
+ log_info("VBE: Firmware pick %s at %lx\n", pick_names[pick], offset);
+
+ ret = vbe_read_fit(blk, offset, size, image, NULL, NULL, NULL);
+ if (ret)
+ return log_msg_ret("vbe", ret);
+ handoff->offset = ub_offset;
+ handoff->size = ub_size;
+ handoff->pick = pick;
+ image->load_addr = spl_get_image_text_base();
+ image->entry_point = image->load_addr;
+
+ return 0;
+}
+
+static int abrec_run_spl(struct udevice *blk, struct spl_image_info *image,
+ struct vbe_handoff *handoff)
+{
+ int ret;
+
+ log_info("VBE: Firmware pick %s at %lx\n", pick_names[handoff->pick],
+ handoff->offset);
+ ret = vbe_read_fit(blk, handoff->offset, handoff->size, image, NULL,
+ NULL, NULL);
+ if (ret)
+ return log_msg_ret("vbe", ret);
+ image->load_addr = spl_get_image_text_base();
+ image->entry_point = image->load_addr;
+
+ return 0;
+}
+
+static int abrec_load_from_image(struct spl_image_info *image,
+ struct spl_boot_device *bootdev)
+{
+ struct vbe_handoff *handoff;
+ int ret;
+
+ printf("load: %s\n", ofnode_read_string(ofnode_root(), "model"));
+ if (xpl_phase() != PHASE_VPL && xpl_phase() != PHASE_SPL &&
+ xpl_phase() != PHASE_TPL)
+ return -ENOENT;
+
+ ret = bloblist_ensure_size(BLOBLISTT_VBE, sizeof(struct vbe_handoff),
+ 0, (void **)&handoff);
+ if (ret)
+ return log_msg_ret("ro", ret);
+
+ if (USE_BOOTMETH) {
+ struct udevice *meth, *bdev;
+ struct abrec_priv *priv;
+ struct bootflow bflow;
+
+ vbe_find_first_device(&meth);
+ if (!meth)
+ return log_msg_ret("vd", -ENODEV);
+ log_debug("vbe dev %s\n", meth->name);
+ ret = device_probe(meth);
+ if (ret)
+ return log_msg_ret("probe", ret);
+
+ priv = dev_get_priv(meth);
+ log_debug("abrec %s\n", priv->storage);
+ ret = bootdev_find_by_label(priv->storage, &bdev, NULL);
+ if (ret)
+ return log_msg_ret("bd", ret);
+ log_debug("bootdev %s\n", bdev->name);
+
+ bootflow_init(&bflow, bdev, meth);
+ ret = bootmeth_read_bootflow(meth, &bflow);
+ log_debug("\nfw ret=%d\n", ret);
+ if (ret)
+ return log_msg_ret("rd", ret);
+
+ /* jump to the image */
+ image->flags = SPL_SANDBOXF_ARG_IS_BUF;
+ image->arg = bflow.buf;
+ image->size = bflow.size;
+ log_debug("Image: %s at %p size %x\n", bflow.name, bflow.buf,
+ bflow.size);
+
+ /* this is not used from now on, so free it */
+ bootflow_free(&bflow);
+ } else {
+ struct udevice *media;
+ struct udevice *blk;
+
+ ret = uclass_get_device_by_seq(UCLASS_MMC, 1, &media);
+ if (ret)
+ return log_msg_ret("vdv", ret);
+ ret = blk_get_from_parent(media, &blk);
+ if (ret)
+ return log_msg_ret("med", ret);
+
+ if (xpl_phase() == PHASE_TPL) {
+ ulong offset, size;
+
+ offset = binman_sym(ulong, vpl, image_pos);
+ size = binman_sym(ulong, vpl, size);
+ log_debug("VPL at offset %lx size %lx\n", offset, size);
+ ret = vbe_read_fit(blk, offset, size, image, NULL,
+ NULL, NULL);
+ if (ret)
+ return log_msg_ret("vbe", ret);
+ } else if (xpl_phase() == PHASE_VPL) {
+ ret = abrec_run_vpl(blk, image, handoff);
+ } else {
+ ret = abrec_run_spl(blk, image, handoff);
+ }
+ }
+
+ /* Record that VBE was used in this phase */
+ handoff->phases |= 1 << xpl_phase();
+
+ return 0;
+}
+SPL_LOAD_IMAGE_METHOD("vbe_abrec", 5, BOOT_DEVICE_VBE,
+ abrec_load_from_image);
diff --git a/boot/vbe_common.c b/boot/vbe_common.c
new file mode 100644
index 00000000000..a86986d86e9
--- /dev/null
+++ b/boot/vbe_common.c
@@ -0,0 +1,381 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Verified Boot for Embedded (VBE) common functions
+ *
+ * Copyright 2024 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <bootstage.h>
+#include <dm.h>
+#include <blk.h>
+#include <image.h>
+#include <mapmem.h>
+#include <memalign.h>
+#include <spl.h>
+#include <u-boot/crc.h>
+#include "vbe_common.h"
+
+binman_sym_declare(ulong, u_boot_vpl_nodtb, size);
+binman_sym_declare(ulong, u_boot_vpl_bss_pad, size);
+binman_sym_declare(ulong, u_boot_spl_nodtb, size);
+binman_sym_declare(ulong, u_boot_spl_bss_pad, size);
+
+int vbe_get_blk(const char *storage, struct udevice **blkp)
+{
+ struct blk_desc *desc;
+ char devname[16];
+ const char *end;
+ int devnum;
+
+ /* First figure out the block device */
+ log_debug("storage=%s\n", storage);
+ devnum = trailing_strtoln_end(storage, NULL, &end);
+ if (devnum == -1)
+ return log_msg_ret("num", -ENODEV);
+ if (end - storage >= sizeof(devname))
+ return log_msg_ret("end", -E2BIG);
+ strlcpy(devname, storage, end - storage + 1);
+ log_debug("dev=%s, %x\n", devname, devnum);
+
+ desc = blk_get_dev(devname, devnum);
+ if (!desc)
+ return log_msg_ret("get", -ENXIO);
+ *blkp = desc->bdev;
+
+ return 0;
+}
+
+int vbe_read_version(struct udevice *blk, ulong offset, char *version,
+ int max_size)
+{
+ ALLOC_CACHE_ALIGN_BUFFER(u8, buf, MMC_MAX_BLOCK_LEN);
+
+ /* we can use an assert() here since we already read only one block */
+ assert(max_size <= MMC_MAX_BLOCK_LEN);
+
+ /*
+ * we can use an assert() here since reading the wrong block will just
+ * cause an invalid version-string to be (safely) read
+ */
+ assert(!(offset & (MMC_MAX_BLOCK_LEN - 1)));
+
+ offset /= MMC_MAX_BLOCK_LEN;
+
+ if (blk_read(blk, offset, 1, buf) != 1)
+ return log_msg_ret("read", -EIO);
+ strlcpy(version, buf, max_size);
+ log_debug("version=%s\n", version);
+
+ return 0;
+}
+
+int vbe_read_nvdata(struct udevice *blk, ulong offset, ulong size, u8 *buf)
+{
+ uint hdr_ver, hdr_size, data_size, crc;
+ const struct vbe_nvdata *nvd;
+
+ /* we can use an assert() here since we already read only one block */
+ assert(size <= MMC_MAX_BLOCK_LEN);
+
+ /*
+ * We can use an assert() here since reading the wrong block will just
+ * cause invalid state to be (safely) read. If the crc passes, then we
+ * obtain invalid state and it will likely cause booting to fail.
+ *
+ * VBE relies on valid values being in U-Boot's devicetree, so this
+ * should not every be wrong on a production device.
+ */
+ assert(!(offset & (MMC_MAX_BLOCK_LEN - 1)));
+
+ if (offset & (MMC_MAX_BLOCK_LEN - 1))
+ return log_msg_ret("get", -EBADF);
+ offset /= MMC_MAX_BLOCK_LEN;
+
+ if (blk_read(blk, offset, 1, buf) != 1)
+ return log_msg_ret("read", -EIO);
+ nvd = (struct vbe_nvdata *)buf;
+ hdr_ver = (nvd->hdr & NVD_HDR_VER_MASK) >> NVD_HDR_VER_SHIFT;
+ hdr_size = (nvd->hdr & NVD_HDR_SIZE_MASK) >> NVD_HDR_SIZE_SHIFT;
+ if (hdr_ver != NVD_HDR_VER_CUR)
+ return log_msg_ret("hdr", -EPERM);
+ data_size = 1 << hdr_size;
+ if (!data_size || data_size > sizeof(*nvd))
+ return log_msg_ret("sz", -EPERM);
+
+ crc = crc8(0, buf + 1, data_size - 1);
+ if (crc != nvd->crc8)
+ return log_msg_ret("crc", -EPERM);
+
+ return 0;
+}
+
+/**
+ * h_vbe_load_read() - Handler for reading an SPL image from a FIT
+ *
+ * See spl_load_reader for the definition
+ */
+ulong h_vbe_load_read(struct spl_load_info *load, ulong off, ulong size,
+ void *buf)
+{
+ struct blk_desc *desc = load->priv;
+ lbaint_t sector = off >> desc->log2blksz;
+ lbaint_t count = size >> desc->log2blksz;
+ int ret;
+
+ log_debug("vbe read log2blksz %x offset %lx sector %lx count %lx\n",
+ desc->log2blksz, (ulong)off, (long)sector, (ulong)count);
+
+ ret = blk_dread(desc, sector, count, buf);
+ log_debug("ret=%x\n", ret);
+ if (ret < 0)
+ return ret;
+
+ return ret << desc->log2blksz;
+}
+
+int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size,
+ struct spl_image_info *image, ulong *load_addrp, ulong *lenp,
+ char **namep)
+{
+ ALLOC_CACHE_ALIGN_BUFFER(u8, sbuf, MMC_MAX_BLOCK_LEN);
+ ulong size, blknum, addr, len, load_addr, num_blks, spl_load_addr;
+ ulong aligned_size, fdt_load_addr, fdt_size;
+ const char *fit_uname, *fit_uname_config;
+ struct bootm_headers images = {};
+ enum image_phase_t phase;
+ struct blk_desc *desc;
+ int node, ret;
+ bool for_xpl;
+ void *buf;
+
+ desc = dev_get_uclass_plat(blk);
+
+ /* read in one block to find the FIT size */
+ blknum = area_offset / desc->blksz;
+ log_debug("read at %lx, blknum %lx\n", area_offset, blknum);
+ ret = blk_read(blk, blknum, 1, sbuf);
+ if (ret < 0)
+ return log_msg_ret("rd", ret);
+ else if (ret != 1)
+ return log_msg_ret("rd2", -EIO);
+
+ ret = fdt_check_header(sbuf);
+ if (ret < 0)
+ return log_msg_ret("fdt", -EINVAL);
+ size = fdt_totalsize(sbuf);
+ if (size > area_size)
+ return log_msg_ret("fdt", -E2BIG);
+ log_debug("FIT size %lx\n", size);
+ aligned_size = ALIGN(size, desc->blksz);
+
+ /*
+ * Load the FIT into the SPL memory. This is typically a FIT with
+ * external data, so this is quite small, perhaps a few KB.
+ */
+ if (IS_ENABLED(CONFIG_SANDBOX)) {
+ addr = CONFIG_VAL(TEXT_BASE);
+ buf = map_sysmem(addr, size);
+ } else {
+ buf = malloc(aligned_size);
+ if (!buf)
+ return log_msg_ret("fit", -ENOMEM);
+ addr = map_to_sysmem(buf);
+ }
+ num_blks = aligned_size / desc->blksz;
+ log_debug("read %lx, %lx blocks to %lx / %p\n", aligned_size, num_blks,
+ addr, buf);
+ ret = blk_read(blk, blknum, num_blks, buf);
+ if (ret < 0)
+ return log_msg_ret("rd3", ret);
+ else if (ret != num_blks)
+ return log_msg_ret("rd4", -EIO);
+ log_debug("check total size %x off_dt_strings %x\n", fdt_totalsize(buf),
+ fdt_off_dt_strings(buf));
+
+#if CONFIG_IS_ENABLED(SYS_MALLOC_F)
+ log_debug("malloc base %lx ptr %x limit %x top %lx\n",
+ gd->malloc_base, gd->malloc_ptr, gd->malloc_limit,
+ gd->malloc_base + gd->malloc_limit);
+#endif
+ /* figure out the phase to load */
+ phase = IS_ENABLED(CONFIG_TPL_BUILD) ? IH_PHASE_NONE :
+ IS_ENABLED(CONFIG_VPL_BUILD) ? IH_PHASE_SPL : IH_PHASE_U_BOOT;
+
+ log_debug("loading FIT\n");
+
+ if (xpl_phase() == PHASE_SPL && !IS_ENABLED(CONFIG_SANDBOX)) {
+ struct spl_load_info info;
+
+ spl_load_init(&info, h_vbe_load_read, desc, desc->blksz);
+ xpl_set_fdt_update(&info, false);
+ xpl_set_phase(&info, IH_PHASE_U_BOOT);
+ log_debug("doing SPL from %s blksz %lx log2blksz %x area_offset %lx + fdt_size %lx\n",
+ blk->name, desc->blksz, desc->log2blksz, area_offset, ALIGN(size, 4));
+ ret = spl_load_simple_fit(image, &info, area_offset, buf);
+ log_debug("spl_load_simple_fit() ret=%d\n", ret);
+
+ return ret;
+ }
+
+ /*
+ * Load the image from the FIT. We ignore any load-address information
+ * so in practice this simply locates the image in the external-data
+ * region and returns its address and size. Since we only loaded the FIT
+ * itself, only a part of the image will be present, at best.
+ */
+ fit_uname = NULL;
+ fit_uname_config = NULL;
+ ret = fit_image_load(&images, addr, &fit_uname, &fit_uname_config,
+ IH_ARCH_DEFAULT, image_ph(phase, IH_TYPE_FIRMWARE),
+ BOOTSTAGE_ID_FIT_SPL_START, FIT_LOAD_IGNORED,
+ &load_addr, &len);
+ if (ret == -ENOENT) {
+ ret = fit_image_load(&images, addr, &fit_uname,
+ &fit_uname_config, IH_ARCH_DEFAULT,
+ image_ph(phase, IH_TYPE_LOADABLE),
+ BOOTSTAGE_ID_FIT_SPL_START,
+ FIT_LOAD_IGNORED, &load_addr, &len);
+ }
+ if (ret < 0)
+ return log_msg_ret("ld", ret);
+ node = ret;
+ log_debug("load %lx size %lx\n", load_addr, len);
+
+ fdt_load_addr = 0;
+ fdt_size = 0;
+ if ((xpl_phase() == PHASE_TPL || xpl_phase() == PHASE_VPL) &&
+ !IS_ENABLED(CONFIG_SANDBOX)) {
+ /* allow use of a different image from the configuration node */
+ fit_uname = NULL;
+ ret = fit_image_load(&images, addr, &fit_uname,
+ &fit_uname_config, IH_ARCH_DEFAULT,
+ image_ph(phase, IH_TYPE_FLATDT),
+ BOOTSTAGE_ID_FIT_SPL_START,
+ FIT_LOAD_IGNORED, &fdt_load_addr,
+ &fdt_size);
+ fdt_size = ALIGN(fdt_size, desc->blksz);
+ log_debug("FDT noload to %lx size %lx\n", fdt_load_addr,
+ fdt_size);
+ }
+
+ for_xpl = !USE_BOOTMETH && CONFIG_IS_ENABLED(RELOC_LOADER);
+ if (for_xpl) {
+ image->size = len;
+ image->fdt_size = fdt_size;
+ ret = spl_reloc_prepare(image, &spl_load_addr);
+ if (ret)
+ return log_msg_ret("spl", ret);
+ }
+ if (!IS_ENABLED(CONFIG_SANDBOX))
+ image->os = IH_OS_U_BOOT;
+
+ /* For FIT external data, read in the external data */
+ log_debug("load_addr %lx len %lx addr %lx aligned_size %lx\n",
+ load_addr, len, addr, aligned_size);
+ if (load_addr + len > addr + aligned_size) {
+ ulong base, full_size, offset, extra, fdt_base, fdt_full_size;
+ ulong fdt_offset;
+ void *base_buf, *fdt_base_buf;
+
+ /* Find the start address to load from */
+ base = ALIGN_DOWN(load_addr, desc->blksz);
+
+ offset = area_offset + load_addr - addr;
+ blknum = offset / desc->blksz;
+ extra = offset % desc->blksz;
+
+ /*
+ * Get the total number of bytes to load, taking care of
+ * block alignment
+ */
+ full_size = len + extra;
+
+ /*
+ * Get the start block number, number of blocks and the address
+ * to load to, then load the blocks
+ */
+ num_blks = DIV_ROUND_UP(full_size, desc->blksz);
+ if (for_xpl)
+ base = spl_load_addr;
+ base_buf = map_sysmem(base, full_size);
+ ret = blk_read(blk, blknum, num_blks, base_buf);
+ log_debug("read foffset %lx blknum %lx full_size %lx num_blks %lx to %lx / %p: ret=%d\n",
+ offset - 0x8000, blknum, full_size, num_blks, base, base_buf,
+ ret);
+ if (ret < 0)
+ return log_msg_ret("rd", ret);
+ if (ret != num_blks)
+ return log_msg_ret("rd", -EIO);
+ if (extra && !IS_ENABLED(CONFIG_SANDBOX)) {
+ log_debug("move %p %p %lx\n", base_buf,
+ base_buf + extra, len);
+ memmove(base_buf, base_buf + extra, len);
+ }
+
+ if ((xpl_phase() == PHASE_VPL || xpl_phase() == PHASE_TPL) &&
+ !IS_ENABLED(CONFIG_SANDBOX)) {
+ image->load_addr = spl_get_image_text_base();
+ image->entry_point = image->load_addr;
+ }
+
+ /* now the FDT */
+ if (fdt_size) {
+ fdt_offset = area_offset + fdt_load_addr - addr;
+ blknum = fdt_offset / desc->blksz;
+ extra = fdt_offset % desc->blksz;
+ fdt_full_size = fdt_size + extra;
+ num_blks = DIV_ROUND_UP(fdt_full_size, desc->blksz);
+ fdt_base = ALIGN(base + len, 4);
+ fdt_base_buf = map_sysmem(fdt_base, fdt_size);
+ ret = blk_read(blk, blknum, num_blks, fdt_base_buf);
+ log_debug("fdt read foffset %lx blknum %lx full_size %lx num_blks %lx to %lx / %p: ret=%d\n",
+ fdt_offset - 0x8000, blknum, fdt_full_size, num_blks,
+ fdt_base, fdt_base_buf, ret);
+ if (ret != num_blks)
+ return log_msg_ret("rdf", -EIO);
+ if (extra) {
+ log_debug("move %p %p %lx\n", fdt_base_buf,
+ fdt_base_buf + extra, fdt_size);
+ memmove(fdt_base_buf, fdt_base_buf + extra,
+ fdt_size);
+ }
+#if CONFIG_IS_ENABLED(RELOC_LOADER)
+ image->fdt_buf = fdt_base_buf;
+
+ ulong xpl_size;
+ ulong xpl_pad;
+ ulong fdt_start;
+
+ if (xpl_phase() == PHASE_TPL) {
+ xpl_size = binman_sym(ulong, u_boot_vpl_nodtb, size);
+ xpl_pad = binman_sym(ulong, u_boot_vpl_bss_pad, size);
+ } else {
+ xpl_size = binman_sym(ulong, u_boot_spl_nodtb, size);
+ xpl_pad = binman_sym(ulong, u_boot_spl_bss_pad, size);
+ }
+ fdt_start = image->load_addr + xpl_size + xpl_pad;
+ log_debug("load_addr %lx xpl_size %lx copy-to %lx\n",
+ image->load_addr, xpl_size + xpl_pad,
+ fdt_start);
+ image->fdt_start = map_sysmem(fdt_start, fdt_size);
+#endif
+ }
+ }
+ if (load_addrp)
+ *load_addrp = load_addr;
+ if (lenp)
+ *lenp = len;
+ if (namep) {
+ *namep = strdup(fdt_get_name(buf, node, NULL));
+ if (!namep)
+ return log_msg_ret("nam", -ENOMEM);
+ }
+
+ return 0;
+}
+
+ofnode vbe_get_node(void)
+{
+ return ofnode_path("/bootstd/firmware0");
+}
diff --git a/boot/vbe_common.h b/boot/vbe_common.h
new file mode 100644
index 00000000000..493cbdc3694
--- /dev/null
+++ b/boot/vbe_common.h
@@ -0,0 +1,180 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Verified Boot for Embedded (VBE) common functions
+ *
+ * Copyright 2024 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __VBE_COMMON_H
+#define __VBE_COMMON_H
+
+#include <dm/ofnode_decl.h>
+#include <linux/bitops.h>
+#include <linux/types.h>
+
+struct spl_image_info;
+struct udevice;
+
+/*
+ * Controls whether we use a full bootmeth driver with VBE in this phase, or
+ * just access the information directly.
+ *
+ * For now VBE-simple uses the full bootmeth, but VBE-abrec does not, to reduce
+ * code size
+ */
+#define USE_BOOTMETH CONFIG_IS_ENABLED(BOOTMETH_VBE_SIMPLE)
+
+enum {
+ MAX_VERSION_LEN = 256,
+
+ NVD_HDR_VER_SHIFT = 0,
+ NVD_HDR_VER_MASK = 0xf,
+ NVD_HDR_SIZE_SHIFT = 4,
+ NVD_HDR_SIZE_MASK = 0xf << NVD_HDR_SIZE_SHIFT,
+
+ /* Firmware key-version is in the top 16 bits of fw_ver */
+ FWVER_KEY_SHIFT = 16,
+ FWVER_FW_MASK = 0xffff,
+
+ NVD_HDR_VER_CUR = 1, /* current version */
+};
+
+/**
+ * enum vbe_try_result - result of trying a firmware pick
+ *
+ * @VBETR_UNKNOWN: Unknown / invalid result
+ * @VBETR_TRYING: Firmware pick is being tried
+ * @VBETR_OK: Firmware pick is OK and can be used from now on
+ * @VBETR_BAD: Firmware pick is bad and should be removed
+ */
+enum vbe_try_result {
+ VBETR_UNKNOWN,
+ VBETR_TRYING,
+ VBETR_OK,
+ VBETR_BAD,
+};
+
+/**
+ * enum vbe_flags - flags controlling operation
+ *
+ * @VBEF_TRY_COUNT_MASK: mask for the 'try count' value
+ * @VBEF_TRY_B: Try the B slot
+ * @VBEF_RECOVERY: Use recovery slot
+ */
+enum vbe_flags {
+ VBEF_TRY_COUNT_MASK = 0x3,
+ VBEF_TRY_B = BIT(2),
+ VBEF_RECOVERY = BIT(3),
+
+ VBEF_RESULT_SHIFT = 4,
+ VBEF_RESULT_MASK = 3 << VBEF_RESULT_SHIFT,
+
+ VBEF_PICK_SHIFT = 6,
+ VBEF_PICK_MASK = 3 << VBEF_PICK_SHIFT,
+};
+
+/**
+ * struct vbe_nvdata - basic storage format for non-volatile data
+ *
+ * This is used for all VBE methods
+ *
+ * @crc8: crc8 for the entire record except @crc8 field itself
+ * @hdr: header size and version (NVD_HDR_...)
+ * @spare1: unused, must be 0
+ * @fw_vernum: version and key version (FWVER_...)
+ * @flags: Flags controlling operation (enum vbe_flags)
+ */
+struct vbe_nvdata {
+ u8 crc8;
+ u8 hdr;
+ u16 spare1;
+ u32 fw_vernum;
+ u32 flags;
+ u8 spare2[0x34];
+};
+
+/**
+ * vbe_get_blk() - Obtain the block device to use for VBE
+ *
+ * Decodes the string to produce a block device
+ *
+ * @storage: String indicating the device to use, e.g. "mmc1"
+ * @blkp: Returns associated block device, on success
+ * Return 0 if OK, -ENODEV if @storage does not end with a number, -E2BIG if
+ * the device name is more than 15 characters, -ENXIO if the block device could
+ * not be found
+ */
+int vbe_get_blk(const char *storage, struct udevice **blkp);
+
+/**
+ * vbe_read_version() - Read version-string from a block device
+ *
+ * Reads the VBE version-string from a device. This function reads a single
+ * block from the device, so the string cannot be larger than that. It uses a
+ * temporary buffer for the read, then copies in up to @size bytes
+ *
+ * @blk: Device to read from
+ * @offset: Offset to read, in bytes
+ * @version: Place to put the string
+ * @max_size: Maximum size of @version
+ * Return: 0 if OK, -E2BIG if @max_size > block size, -EBADF if the offset is
+ * not block-aligned, -EIO if an I/O error occurred
+ */
+int vbe_read_version(struct udevice *blk, ulong offset, char *version,
+ int max_size);
+
+/**
+ * vbe_read_nvdata() - Read non-volatile data from a block device
+ *
+ * Reads the VBE nvdata from a device. This function reads a single block from
+ * the device, so the nvdata cannot be larger than that.
+ *
+ * @blk: Device to read from
+ * @offset: Offset to read, in bytes
+ * @size: Number of bytes to read
+ * @buf: Buffer to hold the data
+ * Return: 0 if OK, -E2BIG if @size > block size, -EBADF if the offset is not
+ * block-aligned, -EIO if an I/O error occurred, -EPERM if the header version is
+ * incorrect, the header size is invalid or the data fails its CRC check
+ */
+int vbe_read_nvdata(struct udevice *blk, ulong offset, ulong size, u8 *buf);
+
+/**
+ * vbe_read_fit() - Read an image from a FIT
+ *
+ * This handles most of the VBE logic for reading from a FIT. It reads the FIT
+ * metadata, decides which image to load and loads it to a suitable address,
+ * ready for jumping to the next phase of VBE.
+ *
+ * This supports transition from VPL to SPL as well as SPL to U-Boot proper. For
+ * now, TPL->VPL is not supported.
+ *
+ * Both embedded and external data are supported for the FIT
+ *
+ * @blk: Block device containing FIT
+ * @area_offset: Byte offset of the VBE area in @blk containing the FIT
+ * @area_size: Size of the VBE area
+ * @image: SPL image to fill in with details of the loaded image, or NULL
+ * @load_addrp: If non-null, returns the address where the image was loaded
+ * @lenp: If non-null, returns the size of the image loaded, in bytes
+ * @namep: If non-null, returns the name of the FIT-image node that was loaded
+ * (allocated by this function)
+ * Return: 0 if OK, -EINVAL if the area does not contain an FDT (the underlying
+ * format for FIT), -E2BIG if the FIT extends past @area_size, -ENOMEM if there
+ * was not space to allocate the image-node name, other error if a read error
+ * occurred (see blk_read()), or something went wrong with the actually
+ * FIT-parsing (see fit_image_load()).
+ */
+int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size,
+ struct spl_image_info *image, ulong *load_addrp, ulong *lenp,
+ char **namep);
+
+/**
+ * vbe_get_node() - Get the node containing the VBE settings
+ *
+ * Return: VBE node (typically "/bootstd/firmware0")
+ */
+ofnode vbe_get_node(void);
+
+#endif /* __VBE_ABREC_H */
diff --git a/boot/vbe_request.c b/boot/vbe_request.c
new file mode 100644
index 00000000000..a1350c1a706
--- /dev/null
+++ b/boot/vbe_request.c
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Verified Boot for Embedded (VBE) OS request (device tree fixup) functions
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY LOGC_BOOT
+
+#include <dm.h>
+#include <event.h>
+#include <image.h>
+#include <malloc.h>
+#include <rng.h>
+#include <dm/ofnode.h>
+
+#define VBE_PREFIX "vbe,"
+#define VBE_PREFIX_LEN (sizeof(VBE_PREFIX) - 1)
+#define VBE_ERR_STR_LEN 128
+#define VBE_MAX_RAND_SIZE 256
+
+struct vbe_result {
+ int errnum;
+ char err_str[VBE_ERR_STR_LEN];
+};
+
+typedef int (*vbe_req_func)(ofnode node, struct vbe_result *result);
+
+static int handle_random_req(ofnode node, int default_size,
+ struct vbe_result *result)
+{
+ char buf[VBE_MAX_RAND_SIZE];
+ struct udevice *dev;
+ u32 size;
+ int ret;
+
+ if (!CONFIG_IS_ENABLED(DM_RNG))
+ return -ENOTSUPP;
+
+ if (ofnode_read_u32(node, "vbe,size", &size)) {
+ if (!default_size) {
+ snprintf(result->err_str, VBE_ERR_STR_LEN,
+ "Missing vbe,size property");
+ return log_msg_ret("byt", -EINVAL);
+ }
+ size = default_size;
+ }
+ if (size > VBE_MAX_RAND_SIZE) {
+ snprintf(result->err_str, VBE_ERR_STR_LEN,
+ "vbe,size %#x exceeds max size %#x", size,
+ VBE_MAX_RAND_SIZE);
+ return log_msg_ret("siz", -E2BIG);
+ }
+ ret = uclass_first_device_err(UCLASS_RNG, &dev);
+ if (ret) {
+ snprintf(result->err_str, VBE_ERR_STR_LEN,
+ "Cannot find random-number device (err=%d)", ret);
+ return log_msg_ret("wr", ret);
+ }
+ ret = dm_rng_read(dev, buf, size);
+ if (ret) {
+ snprintf(result->err_str, VBE_ERR_STR_LEN,
+ "Failed to read random-number device (err=%d)", ret);
+ return log_msg_ret("rd", ret);
+ }
+ ret = ofnode_write_prop(node, "data", buf, size, true);
+ if (ret)
+ return log_msg_ret("wr", -EINVAL);
+
+ return 0;
+}
+
+static int vbe_req_random_seed(ofnode node, struct vbe_result *result)
+{
+ return handle_random_req(node, 0, result);
+}
+
+static int vbe_req_aslr_move(ofnode node, struct vbe_result *result)
+{
+ return -ENOTSUPP;
+}
+
+static int vbe_req_aslr_rand(ofnode node, struct vbe_result *result)
+{
+ return handle_random_req(node, 4, result);
+}
+
+static int vbe_req_efi_runtime_rand(ofnode node, struct vbe_result *result)
+{
+ return handle_random_req(node, 4, result);
+}
+
+static struct vbe_req {
+ const char *compat;
+ vbe_req_func func;
+} vbe_reqs[] = {
+ /* address space layout randomization - move the OS in memory */
+ { "aslr-move", vbe_req_aslr_move },
+
+ /* provide random data for address space layout randomization */
+ { "aslr-rand", vbe_req_aslr_rand },
+
+ /* provide random data for EFI-runtime-services address */
+ { "efi-runtime-rand", vbe_req_efi_runtime_rand },
+
+ /* generate random data bytes to see the OS's rand generator */
+ { "random-rand", vbe_req_random_seed },
+
+};
+
+static int vbe_process_request(ofnode node, struct vbe_result *result)
+{
+ const char *compat, *req_name;
+ int i;
+
+ compat = ofnode_read_string(node, "compatible");
+ if (!compat)
+ return 0;
+
+ if (strlen(compat) <= VBE_PREFIX_LEN ||
+ strncmp(compat, VBE_PREFIX, VBE_PREFIX_LEN))
+ return -EINVAL;
+
+ req_name = compat + VBE_PREFIX_LEN; /* drop "vbe," prefix */
+ for (i = 0; i < ARRAY_SIZE(vbe_reqs); i++) {
+ if (!strcmp(vbe_reqs[i].compat, req_name)) {
+ int ret;
+
+ ret = vbe_reqs[i].func(node, result);
+ if (ret)
+ return log_msg_ret("req", ret);
+ return 0;
+ }
+ }
+ snprintf(result->err_str, VBE_ERR_STR_LEN, "Unknown request: %s",
+ req_name);
+
+ return -ENOTSUPP;
+}
+
+/**
+ * bootmeth_vbe_ft_fixup() - Process VBE OS requests and do device tree fixups
+ *
+ * If there are no images provided, this does nothing and returns 0.
+ *
+ * @ctx: Context for event
+ * @event: Event to process
+ * @return 0 if OK, -ve on error
+ */
+static int bootmeth_vbe_ft_fixup(void *ctx, struct event *event)
+{
+ const struct event_ft_fixup *fixup = &event->data.ft_fixup;
+ const struct bootm_headers *images = fixup->images;
+ ofnode parent, dest_parent, root, node;
+ oftree fit;
+
+ if (!images || !images->fit_hdr_os)
+ return 0;
+
+ /* Get the image node with requests in it */
+ log_debug("fit=%p, noffset=%d\n", images->fit_hdr_os,
+ images->fit_noffset_os);
+ fit = oftree_from_fdt(images->fit_hdr_os);
+ root = oftree_root(fit);
+ if (of_live_active()) {
+ log_warning("Cannot fix up live tree\n");
+ return 0;
+ }
+ if (!ofnode_valid(root))
+ return log_msg_ret("rt", -EINVAL);
+ parent = noffset_to_ofnode(root, images->fit_noffset_os);
+ if (!ofnode_valid(parent))
+ return log_msg_ret("img", -EINVAL);
+ dest_parent = oftree_path(fixup->tree, "/chosen");
+ if (!ofnode_valid(dest_parent))
+ return log_msg_ret("dst", -EINVAL);
+
+ ofnode_for_each_subnode(node, parent) {
+ const char *name = ofnode_get_name(node);
+ struct vbe_result result;
+ ofnode dest;
+ int ret;
+
+ log_debug("copy subnode: %s\n", name);
+ ret = ofnode_add_subnode(dest_parent, name, &dest);
+ if (ret && ret != -EEXIST)
+ return log_msg_ret("add", ret);
+ ret = ofnode_copy_props(dest, node);
+ if (ret)
+ return log_msg_ret("cp", ret);
+
+ *result.err_str = '\0';
+ ret = vbe_process_request(dest, &result);
+ if (ret) {
+ result.errnum = ret;
+ log_warning("Failed to process VBE request %s (err=%d)\n",
+ ofnode_get_name(dest), ret);
+ if (*result.err_str) {
+ char *msg = strdup(result.err_str);
+
+ if (!msg)
+ return log_msg_ret("msg", -ENOMEM);
+ ret = ofnode_write_string(dest, "vbe,error",
+ msg);
+ if (ret) {
+ free(msg);
+ return log_msg_ret("str", -ENOMEM);
+ }
+ }
+ if (result.errnum) {
+ ret = ofnode_write_u32(dest, "vbe,errnum",
+ result.errnum);
+ if (ret)
+ return log_msg_ret("num", -ENOMEM);
+ if (result.errnum != -ENOTSUPP)
+ return log_msg_ret("pro",
+ result.errnum);
+ if (result.errnum == -ENOTSUPP &&
+ ofnode_read_bool(dest, "vbe,required")) {
+ log_err("Cannot handle required request: %s\n",
+ ofnode_get_name(dest));
+ return log_msg_ret("req",
+ result.errnum);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+EVENT_SPY_FULL(EVT_FT_FIXUP, bootmeth_vbe_ft_fixup);
diff --git a/boot/vbe_simple.c b/boot/vbe_simple.c
new file mode 100644
index 00000000000..c6766c532f2
--- /dev/null
+++ b/boot/vbe_simple.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Verified Boot for Embedded (VBE) 'simple' method
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY LOGC_BOOT
+
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <dm.h>
+#include <log.h>
+#include <memalign.h>
+#include <mmc.h>
+#include <vbe.h>
+#include <dm/device-internal.h>
+#include <dm/ofnode.h>
+#include "vbe_simple.h"
+
+static int simple_read_nvdata(const struct simple_priv *priv,
+ struct udevice *blk, struct simple_state *state)
+{
+ ALLOC_CACHE_ALIGN_BUFFER(u8, buf, MMC_MAX_BLOCK_LEN);
+ const struct vbe_nvdata *nvd;
+ int ret;
+
+ ret = vbe_read_nvdata(blk, priv->area_start + priv->state_offset,
+ priv->state_size, buf);
+ if (ret)
+ return log_msg_ret("nv", ret);
+
+ nvd = (struct vbe_nvdata *)buf;
+ state->fw_vernum = nvd->fw_vernum;
+
+ log_debug("version=%s\n", state->fw_version);
+
+ return 0;
+}
+
+int vbe_simple_read_state(struct udevice *dev, struct simple_state *state)
+{
+ struct simple_priv *priv = dev_get_priv(dev);
+ struct udevice *blk;
+ int ret;
+
+ ret = vbe_get_blk(priv->storage, &blk);
+ if (ret)
+ return log_msg_ret("blk", ret);
+
+ ret = vbe_read_version(blk, priv->area_start + priv->version_offset,
+ state->fw_version, MAX_VERSION_LEN);
+ if (ret)
+ return log_msg_ret("ver", ret);
+
+ ret = simple_read_nvdata(priv, blk, state);
+ if (ret)
+ return log_msg_ret("nvd", ret);
+
+ return 0;
+}
+
+static int vbe_simple_get_state_desc(struct udevice *dev, char *buf,
+ int maxsize)
+{
+ struct simple_state state;
+ int ret;
+
+ ret = vbe_simple_read_state(dev, &state);
+ if (ret)
+ return log_msg_ret("read", ret);
+
+ if (maxsize < 30)
+ return -ENOSPC;
+ snprintf(buf, maxsize, "Version: %s\nVernum: %x/%x", state.fw_version,
+ state.fw_vernum >> FWVER_KEY_SHIFT,
+ state.fw_vernum & FWVER_FW_MASK);
+
+ return 0;
+}
+
+static int vbe_simple_read_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+ int ret;
+
+ if (CONFIG_IS_ENABLED(BOOTMETH_VBE_SIMPLE_FW)) {
+ if (vbe_phase() == VBE_PHASE_FIRMWARE) {
+ ret = vbe_simple_read_bootflow_fw(dev, bflow);
+ if (ret)
+ return log_msg_ret("fw", ret);
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int vbe_simple_read_file(struct udevice *dev, struct bootflow *bflow,
+ const char *file_path, ulong addr,
+ enum bootflow_img_t type, ulong *sizep)
+{
+ int ret;
+
+ if (vbe_phase() == VBE_PHASE_OS) {
+ ret = bootmeth_common_read_file(dev, bflow, file_path, addr,
+ type, sizep);
+ if (ret)
+ return log_msg_ret("os", ret);
+ }
+
+ /* To be implemented */
+ return -EINVAL;
+}
+
+static struct bootmeth_ops bootmeth_vbe_simple_ops = {
+ .get_state_desc = vbe_simple_get_state_desc,
+ .read_bootflow = vbe_simple_read_bootflow,
+ .read_file = vbe_simple_read_file,
+};
+
+static int bootmeth_vbe_simple_probe(struct udevice *dev)
+{
+ struct simple_priv *priv = dev_get_priv(dev);
+
+ memset(priv, '\0', sizeof(*priv));
+ if (dev_read_u32(dev, "area-start", &priv->area_start) ||
+ dev_read_u32(dev, "area-size", &priv->area_size) ||
+ dev_read_u32(dev, "version-offset", &priv->version_offset) ||
+ dev_read_u32(dev, "version-size", &priv->version_size) ||
+ dev_read_u32(dev, "state-offset", &priv->state_offset) ||
+ dev_read_u32(dev, "state-size", &priv->state_size))
+ return log_msg_ret("read", -EINVAL);
+ dev_read_u32(dev, "skip-offset", &priv->skip_offset);
+ priv->storage = strdup(dev_read_string(dev, "storage"));
+ if (!priv->storage)
+ return log_msg_ret("str", -EINVAL);
+
+ return 0;
+}
+
+static int bootmeth_vbe_simple_bind(struct udevice *dev)
+{
+ struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ?
+ "VBE simple" : "vbe-simple";
+ plat->flags = BOOTMETHF_GLOBAL;
+
+ return 0;
+}
+
+#if CONFIG_IS_ENABLED(OF_REAL)
+static const struct udevice_id generic_simple_vbe_simple_ids[] = {
+ { .compatible = "fwupd,vbe-simple" },
+ { }
+};
+#endif
+
+U_BOOT_DRIVER(vbe_simple) = {
+ .name = "vbe_simple",
+ .id = UCLASS_BOOTMETH,
+ .of_match = of_match_ptr(generic_simple_vbe_simple_ids),
+ .ops = &bootmeth_vbe_simple_ops,
+ .bind = bootmeth_vbe_simple_bind,
+ .probe = bootmeth_vbe_simple_probe,
+ .flags = DM_FLAG_PRE_RELOC,
+ .priv_auto = sizeof(struct simple_priv),
+};
diff --git a/boot/vbe_simple.h b/boot/vbe_simple.h
new file mode 100644
index 00000000000..dc3f70052b0
--- /dev/null
+++ b/boot/vbe_simple.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Verified Boot for Embedded (VBE) vbe-simple common file
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __VBE_SIMPLE_H
+#define __VBE_SIMPLE_H
+
+#include <linux/types.h>
+#include "vbe_common.h"
+
+/** struct simple_priv - information read from the device tree */
+struct simple_priv {
+ u32 area_start;
+ u32 area_size;
+ u32 skip_offset;
+ u32 state_offset;
+ u32 state_size;
+ u32 version_offset;
+ u32 version_size;
+ const char *storage;
+};
+
+/** struct simple_state - state information read from media
+ *
+ * @fw_version: Firmware version string
+ * @fw_vernum: Firmware version number
+ */
+struct simple_state {
+ char fw_version[MAX_VERSION_LEN];
+ u32 fw_vernum;
+};
+
+/**
+ * vbe_simple_read_fw_bootflow() - Read a bootflow for firmware
+ *
+ * Locates and loads the firmware image (FIT) needed for the next phase. The FIT
+ * should ideally use external data, to reduce the amount of it that needs to be
+ * read.
+ *
+ * @bdev: bootdev device containing the firmwre
+ * @blow: Place to put the created bootflow, on success
+ * @return 0 if OK, -ve on error
+ */
+int vbe_simple_read_bootflow_fw(struct udevice *dev, struct bootflow *bflow);
+
+/**
+ * vbe_simple_read_state() - Read the VBE simple state information
+ *
+ * @dev: VBE bootmeth
+ * @state: Place to put the state
+ * @return 0 if OK, -ve on error
+ */
+int vbe_simple_read_state(struct udevice *dev, struct simple_state *state);
+
+#endif /* __VBE_SIMPLE_H */
diff --git a/boot/vbe_simple_fw.c b/boot/vbe_simple_fw.c
new file mode 100644
index 00000000000..cb5534fc731
--- /dev/null
+++ b/boot/vbe_simple_fw.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Verified Boot for Embedded (VBE) loading firmware phases
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY LOGC_BOOT
+
+#include <binman_sym.h>
+#include <bloblist.h>
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <bootstage.h>
+#include <dm.h>
+#include <image.h>
+#include <log.h>
+#include <mapmem.h>
+#include <mmc.h>
+#include <spl.h>
+#include <vbe.h>
+#include <dm/device-internal.h>
+#include "vbe_common.h"
+#include "vbe_simple.h"
+
+#ifdef CONFIG_BOOTMETH_VBE_SIMPLE
+binman_sym_extern(ulong, vbe_a, image_pos);
+binman_sym_extern(ulong, vbe_a, size);
+#else
+binman_sym_declare(ulong, vbe_a, image_pos);
+binman_sym_declare(ulong, vbe_a, size);
+#endif
+
+binman_sym_declare(ulong, vpl, image_pos);
+binman_sym_declare(ulong, vpl, size);
+
+/**
+ * vbe_simple_read_bootflow_fw() - Create a bootflow for firmware
+ *
+ * Locates and loads the firmware image (FIT) needed for the next phase. The FIT
+ * should ideally use external data, to reduce the amount of it that needs to be
+ * read.
+ *
+ * @bdev: bootdev device containing the firmwre
+ * @meth: VBE simple bootmeth
+ * @blow: Place to put the created bootflow, on success
+ * @return 0 if OK, -ve on error
+ */
+int vbe_simple_read_bootflow_fw(struct udevice *dev, struct bootflow *bflow)
+{
+ struct udevice *media = dev_get_parent(bflow->dev);
+ struct udevice *meth = bflow->method;
+ struct simple_priv *priv = dev_get_priv(meth);
+ ulong len, load_addr;
+ struct udevice *blk;
+ int ret;
+
+ log_debug("media=%s\n", media->name);
+ ret = blk_get_from_parent(media, &blk);
+ if (ret)
+ return log_msg_ret("med", ret);
+ log_debug("blk=%s\n", blk->name);
+
+ ret = vbe_read_fit(blk, priv->area_start + priv->skip_offset,
+ priv->area_size, NULL, &load_addr, &len,
+ &bflow->name);
+ if (ret)
+ return log_msg_ret("vbe", ret);
+
+ /* set up the bootflow with the info we obtained */
+ bflow->blk = blk;
+ bflow->buf = map_sysmem(load_addr, len);
+ bflow->size = len;
+
+ return 0;
+}
+
+static int simple_load_from_image(struct spl_image_info *image,
+ struct spl_boot_device *bootdev)
+{
+ struct vbe_handoff *handoff;
+ int ret;
+
+ if (xpl_phase() != PHASE_VPL && xpl_phase() != PHASE_SPL &&
+ xpl_phase() != PHASE_TPL)
+ return -ENOENT;
+
+ ret = bloblist_ensure_size(BLOBLISTT_VBE, sizeof(struct vbe_handoff),
+ 0, (void **)&handoff);
+ if (ret)
+ return log_msg_ret("ro", ret);
+
+ if (USE_BOOTMETH) {
+ struct udevice *meth, *bdev;
+ struct simple_priv *priv;
+ struct bootflow bflow;
+
+ vbe_find_first_device(&meth);
+ if (!meth)
+ return log_msg_ret("vd", -ENODEV);
+ log_debug("vbe dev %s\n", meth->name);
+ ret = device_probe(meth);
+ if (ret)
+ return log_msg_ret("probe", ret);
+
+ priv = dev_get_priv(meth);
+ log_debug("simple %s\n", priv->storage);
+ ret = bootdev_find_by_label(priv->storage, &bdev, NULL);
+ if (ret)
+ return log_msg_ret("bd", ret);
+ log_debug("bootdev %s\n", bdev->name);
+
+ bootflow_init(&bflow, bdev, meth);
+ ret = bootmeth_read_bootflow(meth, &bflow);
+ log_debug("\nfw ret=%d\n", ret);
+ if (ret)
+ return log_msg_ret("rd", ret);
+
+ /* jump to the image */
+ image->flags = SPL_SANDBOXF_ARG_IS_BUF;
+ image->arg = bflow.buf;
+ image->size = bflow.size;
+ log_debug("Image: %s at %p size %x\n", bflow.name, bflow.buf,
+ bflow.size);
+
+ /* this is not used from now on, so free it */
+ bootflow_free(&bflow);
+ } else {
+ struct udevice *media, *blk;
+ ulong offset, size;
+
+ ret = uclass_get_device_by_seq(UCLASS_MMC, 1, &media);
+ if (ret)
+ return log_msg_ret("vdv", ret);
+ ret = blk_get_from_parent(media, &blk);
+ if (ret)
+ return log_msg_ret("med", ret);
+ if (xpl_phase() == PHASE_TPL) {
+ offset = binman_sym(ulong, vpl, image_pos);
+ size = binman_sym(ulong, vpl, size);
+ } else {
+ offset = binman_sym(ulong, vbe_a, image_pos);
+ size = binman_sym(ulong, vbe_a, size);
+ printf("offset=%lx\n", offset);
+ }
+
+ ret = vbe_read_fit(blk, offset, size, image, NULL, NULL, NULL);
+ if (ret)
+ return log_msg_ret("vbe", ret);
+ }
+
+ /* Record that VBE was used in this phase */
+ handoff->phases |= 1 << xpl_phase();
+
+ return 0;
+}
+SPL_LOAD_IMAGE_METHOD("vbe_simple", 5, BOOT_DEVICE_VBE,
+ simple_load_from_image);
diff --git a/boot/vbe_simple_os.c b/boot/vbe_simple_os.c
new file mode 100644
index 00000000000..b4126d8d2d0
--- /dev/null
+++ b/boot/vbe_simple_os.c
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Verified Boot for Embedded (VBE) loading firmware phases
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY LOGC_BOOT
+
+#include <dm.h>
+#include <bootflow.h>
+#include <vbe.h>
+#include <version_string.h>
+#include <dm/device-internal.h>
+#include "vbe_simple.h"
+
+int vbe_simple_fixup_node(ofnode node, struct simple_state *state)
+{
+ const char *version, *str;
+ int ret;
+
+ version = strdup(state->fw_version);
+ if (!version)
+ return log_msg_ret("dup", -ENOMEM);
+
+ ret = ofnode_write_string(node, "cur-version", version);
+ if (ret)
+ return log_msg_ret("ver", ret);
+ ret = ofnode_write_u32(node, "cur-vernum", state->fw_vernum);
+ if (ret)
+ return log_msg_ret("num", ret);
+
+ /* Drop the 'U-Boot ' at the start */
+ str = version_string;
+ if (!strncmp("U-Boot ", str, 7))
+ str += 7;
+ ret = ofnode_write_string(node, "bootloader-version", str);
+ if (ret)
+ return log_msg_ret("bl", ret);
+
+ return 0;
+}
+
+/**
+ * bootmeth_vbe_simple_ft_fixup() - Write out all VBE simple data to the DT
+ *
+ * @ctx: Context for event
+ * @event: Event to process
+ * @return 0 if OK, -ve on error
+ */
+static int bootmeth_vbe_simple_ft_fixup(void *ctx, struct event *event)
+{
+ oftree tree = event->data.ft_fixup.tree;
+ struct udevice *dev;
+
+ /*
+ * Ideally we would have driver model support for fixups, but that does
+ * not exist yet. It is a step too far to try to do this before VBE is
+ * in place.
+ */
+ for (vbe_find_first_device(&dev); dev; vbe_find_next_device(&dev)) {
+ struct simple_state state;
+ ofnode node, subnode, chosen;
+ int ret;
+
+ if (strcmp("vbe_simple", dev->driver->name))
+ continue;
+
+ /* Check if there is a node to fix up, adding if not */
+ chosen = oftree_path(tree, "/chosen");
+ if (!ofnode_valid(chosen))
+ continue;
+
+ ret = device_probe(dev);
+ if (ret) {
+ /*
+ * This should become an error when VBE is updated to
+ * only bind this device when a node exists
+ */
+ log_debug("VBE device '%s' failed to probe (err=%d)",
+ dev->name, ret);
+ return 0;
+ }
+
+ ret = ofnode_add_subnode(chosen, "fwupd", &node);
+ if (ret && ret != -EEXIST)
+ return log_msg_ret("fwu", ret);
+
+ ret = ofnode_add_subnode(node, dev->name, &subnode);
+ if (ret && ret != -EEXIST)
+ return log_msg_ret("dev", ret);
+
+ /* Copy over the vbe properties for fwupd */
+ log_debug("Fixing up: %s\n", dev->name);
+ ret = ofnode_copy_props(subnode, dev_ofnode(dev));
+ if (ret)
+ return log_msg_ret("cp", ret);
+
+ ret = vbe_simple_read_state(dev, &state);
+ if (ret)
+ return log_msg_ret("read", ret);
+
+ ret = vbe_simple_fixup_node(subnode, &state);
+ if (ret)
+ return log_msg_ret("fix", ret);
+ }
+
+ return 0;
+}
+EVENT_SPY_FULL(EVT_FT_FIXUP, bootmeth_vbe_simple_ft_fixup);