diff options
Diffstat (limited to 'boot')
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 ?: ∅ + + /* 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, ®ion, 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, ×tamp)) + 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, ×tamp); + 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, ×tamp); + 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(×tamp)); +} 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, ®, 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, ®, 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); |