diff options
| author | Tom Rini <[email protected]> | 2026-02-10 15:54:09 -0600 |
|---|---|---|
| committer | Tom Rini <[email protected]> | 2026-02-10 15:54:09 -0600 |
| commit | 6f7fe31bf6d934f0240da74f06a5b42689a2ac7f (patch) | |
| tree | 01b4f552f2a58a49f9a2115f5ab695396f0ec0a2 | |
| parent | af3b0be9cb0d891a0d23df6b8247dedac5a1b809 (diff) | |
| parent | a672456d4b2bd216dcfe9d09fb5fbce2b013735a (diff) | |
Merge patch series "am335x: failsafe bootloader update"
Rasmus Villemoes <[email protected]> says:
Make it possible to update the (whole) bootloader on am335x robustly,
i.e. so that a power failure or random OOM killing of the update
process or other interruptions do not result in a bricked board.
The order the trial bits gets set is somewhat odd, but is clearly what
happens, and somebody else trying to reverse engineer the ROM code has
observed the same thing: See the TracingVectors.ods in
https://github.com/sjgallagher2/am335xbootrom .
Link: https://lore.kernel.org/r/[email protected]
| -rw-r--r-- | arch/arm/mach-omap2/Kconfig | 37 | ||||
| -rw-r--r-- | arch/arm/mach-omap2/boot-common.c | 31 | ||||
| -rw-r--r-- | doc/board/ti/am335x_evm.rst | 80 |
3 files changed, 148 insertions, 0 deletions
diff --git a/arch/arm/mach-omap2/Kconfig b/arch/arm/mach-omap2/Kconfig index c07dd68e6ce..1e989ac48ac 100644 --- a/arch/arm/mach-omap2/Kconfig +++ b/arch/arm/mach-omap2/Kconfig @@ -156,6 +156,43 @@ config SYS_DEFAULT_LPDDR2_TIMINGS endchoice +config SPL_AM33XX_MMCSD_MULTIPLE + bool "Support multiple locations of next boot phase" + depends on AM33XX + depends on SPL_SYS_MMCSD_RAW_MODE + depends on SYS_MMCSD_RAW_MODE_U_BOOT_USE_SECTOR + help + The boot ROM on the am33xx looks for the first stage + bootloader at several hard-coded offsets in the mmc device + (0, 128K, 256K, 384K) and uses the first location which has + a valid header. This can be used to implement failsafe + update of that first stage (SPL). But in order for the + update of the whole bootloader to be failsafe, SPL must load + U-Boot proper from a location dependent on where SPL itself + was loaded from. This option allows you to specify four + different offsets corresponding to the different places + where SPL could have been loaded from. + +if SPL_AM33XX_MMCSD_MULTIPLE + +config SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR_0K + hex "Address on the MMC to load U-Boot from when SPL was loaded from offset 0K" + default SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR + +config SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR_128K + hex "Address on the MMC to load U-Boot from when SPL was loaded from offset 128K" + default SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR + +config SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR_256K + hex "Address on the MMC to load U-Boot from when SPL was loaded from offset 256K" + default SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR + +config SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR_384K + hex "Address on the MMC to load U-Boot from when SPL was loaded from offset 384K" + default SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR + +endif + source "arch/arm/mach-omap2/omap3/Kconfig" source "arch/arm/mach-omap2/omap5/Kconfig" diff --git a/arch/arm/mach-omap2/boot-common.c b/arch/arm/mach-omap2/boot-common.c index 95b44c8b1e5..88fa59feaf1 100644 --- a/arch/arm/mach-omap2/boot-common.c +++ b/arch/arm/mach-omap2/boot-common.c @@ -318,3 +318,34 @@ static void tee_image_process(ulong tee_image, size_t tee_size) } U_BOOT_FIT_LOADABLE_HANDLER(IH_TYPE_TEE, tee_image_process); #endif + +#ifdef CONFIG_SPL_AM33XX_MMCSD_MULTIPLE + +#define AM335X_TRACE_VECTOR2 0x4030CE44 + +unsigned long arch_spl_mmc_get_uboot_raw_sector(struct mmc *mmc, unsigned long raw_sect) +{ + u32 bits = *(u32 *)AM335X_TRACE_VECTOR2; + + bits &= 0xf000; + + /* + * The ROM code sets the "trial bit 3", bit 15, first, when + * attempting offset 0, then "trial bit 2", bit 14, when + * attempting offset 128K, and so on. If the tracing vector + * has completely unexpected contents, fall back to the + * raw_sect we were given. + */ + switch (bits) { + case 0x8000: raw_sect = CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR_0K; break; + case 0xc000: raw_sect = CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR_128K; break; + case 0xe000: raw_sect = CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR_256K; break; + case 0xf000: raw_sect = CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR_384K; break; + default: + printf("Warning: Unexpected trial bits 0x%04x in trace vector 2, falling back to 0x%lx\n", + bits, raw_sect); + } + + return raw_sect; +} +#endif diff --git a/doc/board/ti/am335x_evm.rst b/doc/board/ti/am335x_evm.rst index b14ba41917e..904881b4146 100644 --- a/doc/board/ti/am335x_evm.rst +++ b/doc/board/ti/am335x_evm.rst @@ -481,3 +481,83 @@ bind with it: misc 0 [ + ] ti-musb-wrapper | |-- usb@47400000 usb 0 [ ] ti-musb-peripheral | | |-- usb@47401000 usb 0 [ ] ti-musb-host | | `-- usb@47401800 + +Failsafe bootloader update +-------------------------- + +As indicated above, the ROM code on the AM335x supports loading SPL +from one of several different locations. It looks at offsets 0, +128KiB, 256KiB and 384KiB (sectors 0, 0x100, 0x200, 0x300) for a +sector containing a valid "TOC structure" (see the reference manual +for details). + +This can be used to implement a scheme for updating SPL which is +robust against power failure or other interruptions: Suppose we store +copies of SPL (wrapped in the "MLO" image, which is what includes that +TOC structure) at offsets 128KiB and 256KiB, and let us refer to those +two locations as slot 1 and slot 2. + +The whole procedure maintains the invariant that at any time, at +least one of the slots contains a complete and valid MLO image. In +order to update SPL: + +(1) Determine a slot X containing a valid image (by having a proper + TOC structure in the first sector). Designate the other + slot Y. Since the TOC is always the same 512 bytes (see section + 26.1.11 in the reference manual), checking for a valid image can + be done using something like + +.. code-block:: bash + + if cmp -s -n 512 MLO /path/to/SPL-1 ; then + X=1 + Y=2 + elif cmp -s -n 512 MLO /path/to/SPL-2 ; then + X=2 + Y=1 + else + # invariant broken, fatal error, refuse update... + fi + +(2) Ensure Y will be deemed invalid by the ROM code by writing all + zeroes to the first sector of Y, for example using + +.. code-block:: bash + + dd if=/dev/zero of=/path/to/SPL-Y bs=512 count=1 conv=fsync + +(3) Write everything but the first sector of the MLO image to slot Y: + +.. code-block:: bash + + dd if=MLO of=/path/to/SPL-Y bs=512 skip=1 seek=1 conv=fsync + +(4) Write the TOC structure to slot Y: + +.. code-block:: bash + + dd if=MLO of=/path/to/SPL-Y bs=512 count=1 conv=fsync + +(5) Repeat steps (2)--(4) for slot X. + +Now, this procedure only accounts for safely updating SPL. If U-Boot +proper is only stored in a single location, there is no way to update +that which is safe against powercut during the update. However, by +selecting the configuration option CONFIG_SPL_AM33XX_MMCSD_MULTIPLE, +you can tell SPL to load U-Boot proper from a location which depends on +where SPL itself was loaded from. Hence, one can for example put +copies of u-boot.img at offsets 512KiB and 1536KiB, and set + +:: + + CONFIG_SPL_AM33XX_MMCSD_MULTIPLE=y + CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR_128K=0x400 + CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR_256K=0xc00 + +and amend the step (3) above by + + Write U-Boot proper to the location corresponding to slot Y: + +.. code-block:: bash + + dd if=u-boot.img of=/path/to/U-BOOT-Y bs=512 conv=fsync |
