Genio firmware updates use MediaTek v1 metadata in misc to choose between bootloaders and bootloaders_b. Support this ABI in SPL so systems replacing BL2 can retain their firmware update and boot-success handling.
Make selection opt-in, since trial boots need an updater and a boot-success owner to maintain the retry state. Reject corrupt metadata and failed state updates rather than silently restarting the retry budget. Signed-off-by: Carlo Caione <[email protected]> --- This is intended for systems with an updater and a boot-success owner that maintain the MediaTek v1 metadata. The option stays disabled in the standard defconfigs. It covers the firmware FIT slots; the DDR loader and SPL in boot0 remain shared. --- Changes in v2: - Rebase onto upstream/next with the SPL and dynamic MMC support merged. - Move the selector and private header under arch/arm/mach-mediatek. - Add GPL-2.0-or-later terms alongside the inherited BSD-3-Clause license. - Remove the eMMC cache guard and its EXT_CSD register definition. - Remove duplicate hardware-partition validation and add direct includes. - Clarify signed-FIT setup and software block-cache readback. - Remove the blanket download-agent restriction from the documentation. - Drop the unrelated changes to the general FIT signing example. - Link to v1: https://patch.msgid.link/20260924-ccaione-upstream-mt8390-ab-v1-1-352e05af3...@baylibre.com --- arch/arm/mach-mediatek/Kconfig | 17 ++++ arch/arm/mach-mediatek/Makefile | 1 + arch/arm/mach-mediatek/mtk_ab.c | 143 ++++++++++++++++++++++++++++++++ arch/arm/mach-mediatek/mtk_ab.h | 48 +++++++++++ arch/arm/mach-mediatek/spl_ab.c | 32 +++++++ doc/board/mediatek/mt8188-genio-evk.rst | 85 ++++++++++++++++++- 6 files changed, 323 insertions(+), 3 deletions(-) diff --git a/arch/arm/mach-mediatek/Kconfig b/arch/arm/mach-mediatek/Kconfig index e572cfe8571..b81ab29011f 100644 --- a/arch/arm/mach-mediatek/Kconfig +++ b/arch/arm/mach-mediatek/Kconfig @@ -211,6 +211,23 @@ config MTK_BROM_HEADER_INFO default "media=emmc" if TARGET_MT8516 || TARGET_MT8365 || TARGET_MT8183 || TARGET_MT8189 || TARGET_MT8195 default "lk=1" if TARGET_MT7623 +config SPL_MTK_FIRMWARE_AB + bool "MediaTek firmware A/B boot in SPL" + depends on TARGET_MT8188 && SPL_MMC && SPL_LOAD_FIT && SPL_BLK + depends on !SPL_RAW_IMAGE_SUPPORT && !SPL_LEGACY_IMAGE_FORMAT + depends on SPL_EFI_PARTITION + depends on SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION_DYNAMIC + depends on !SPL_OS_BOOT && !SPL_FS_FAT && !SPL_FS_EXT4 + depends on !SPL_FS_SQUASHFS && !SPL_RAM_SUPPORT && !SUPPORT_EMMC_BOOT + select SPL_CRC32 + select SPL_MMC_WRITE + help + Select firmware FITs from the bootloaders and bootloaders_b GPT + partitions using MediaTek A/B metadata in misc. Track trial boots + and switch slots when retries are exhausted. + + An updater and a boot-success owner must maintain the metadata. + config MTK_GENIO_BOOT_IMAGES bool "Build MediaTek firmware images" depends on TARGET_MT8188 && SPL diff --git a/arch/arm/mach-mediatek/Makefile b/arch/arm/mach-mediatek/Makefile index 35f748a70d6..20073c12d1f 100644 --- a/arch/arm/mach-mediatek/Makefile +++ b/arch/arm/mach-mediatek/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_ARM64) += armv8-mem-map.o obj-y += cpu.o obj-$(CONFIG_MTK_TZ_MOVABLE) += tzcfg.o obj-$(CONFIG_XPL_BUILD) += spl.o +obj-$(CONFIG_$(PHASE_)MTK_FIRMWARE_AB) += spl_ab.o mtk_ab.o obj-$(CONFIG_TARGET_MT7622) += mt7622/ obj-$(CONFIG_TARGET_MT7623) += mt7623/ diff --git a/arch/arm/mach-mediatek/mtk_ab.c b/arch/arm/mach-mediatek/mtk_ab.c new file mode 100644 index 00000000000..b28013bbcb6 --- /dev/null +++ b/arch/arm/mach-mediatek/mtk_ab.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-or-later AND BSD-3-Clause +/* + * Copyright (c) 2023, Mediatek Inc. All rights reserved. + * Copyright (c) 2026 BayLibre SAS + * + * Disk format and valid-record transitions follow MediaTek's TF-A mtk_ab.c. + */ + +#include <blk.h> +#include <malloc.h> +#include <memalign.h> +#include <part.h> +#include <stdio.h> +#include <asm/byteorder.h> +#include <linux/build_bug.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/log2.h> +#include <linux/string.h> +#include <u-boot/crc.h> + +#include "mtk_ab.h" + +#define MTK_AB_MISC_INDEX 2 + +static_assert(sizeof(struct mtk_ab_control) == 28); +static_assert(offsetof(struct mtk_ab_control, padding) == 11); +static_assert(offsetof(struct mtk_ab_control, reserved) == 12); +static_assert(offsetof(struct mtk_ab_control, crc32) == 24); + +static u32 mtk_ab_crc(const struct mtk_ab_control *ctrl) +{ + return crc32(0, (const void *)ctrl, offsetof(struct mtk_ab_control, crc32)); +} + +static int mtk_ab_layout(struct blk_desc *desc, struct disk_partition *parts, + int *numbers) +{ + static const char * const names[] = { "bootloaders", "bootloaders_b", "misc" }; + int i; + + part_init(desc); + if (desc->part_type != PART_TYPE_EFI) + return -EPROTONOSUPPORT; + + for (i = 0; i < ARRAY_SIZE(names); i++) { + numbers[i] = part_get_info_by_name(desc, names[i], &parts[i]); + if (numbers[i] < 0) + return numbers[i]; + } + + /* The metadata read-modify-write needs one block in misc. */ + if (!parts[MTK_AB_MISC_INDEX].start || !parts[MTK_AB_MISC_INDEX].size || + parts[MTK_AB_MISC_INDEX].start >= desc->lba) + return -EINVAL; + + return 0; +} + +int mtk_ab_select(struct blk_desc *desc, unsigned int *slot) +{ + struct disk_partition parts[3]; + struct mtk_ab_control *ctrl; + unsigned int selected; + int numbers[3], ret; + void *block, *verify; + bool changed = false; + + if (!desc || !slot || !desc->lba || desc->hwpart || desc->blksz < 512 || + desc->blksz > 4096 || !is_power_of_2(desc->blksz) || + desc->blksz % ARCH_DMA_MINALIGN) + return -EINVAL; + + /* Locate all required partitions before updating the metadata. */ + ret = mtk_ab_layout(desc, parts, numbers); + if (ret) + return ret; + + block = malloc_cache_aligned(2 * desc->blksz); + if (!block) + return -ENOMEM; + verify = block + desc->blksz; + ctrl = block; + + ret = -EIO; + blkcache_invalidate(desc->uclass_id, desc->devnum); + if (blk_dread(desc, parts[MTK_AB_MISC_INDEX].start, 1, block) != 1) + goto out; + + if (!memchr_inv(ctrl, 0, sizeof(*ctrl)) || + !memchr_inv(ctrl, 0xff, sizeof(*ctrl))) { + puts("MediaTek A/B: initializing blank metadata\n"); + memset(ctrl, 0, sizeof(*ctrl)); + ctrl->magic = cpu_to_le32(MTK_AB_MAGIC); + ctrl->version = MTK_AB_VERSION; + ctrl->tries[0] = 1; + ctrl->tries[1] = 1; + selected = 0; + changed = true; + } else { + ret = -EINVAL; + selected = le32_to_cpu(ctrl->slot); + if (le32_to_cpu(ctrl->magic) != MTK_AB_MAGIC || + ctrl->version != MTK_AB_VERSION || selected > 1 || + ctrl->tries[0] > MTK_AB_MAX_TRIES || + ctrl->tries[1] > MTK_AB_MAX_TRIES) + goto out; + ret = -EBADMSG; + if (le32_to_cpu(ctrl->crc32) != mtk_ab_crc(ctrl)) + goto out; + + if (ctrl->tries[selected] == MTK_AB_MAX_TRIES) { + selected ^= 1; + ret = -ENOENT; + if (ctrl->tries[selected] == MTK_AB_MAX_TRIES) + goto out; + ctrl->slot = cpu_to_le32(selected); + changed = true; + } + if (ctrl->tries[selected]) { + ctrl->tries[selected]++; + changed = true; + } + } + + if (changed) { + ctrl->crc32 = cpu_to_le32(mtk_ab_crc(ctrl)); + ret = -EIO; + if (blk_dwrite(desc, parts[MTK_AB_MISC_INDEX].start, 1, block) != 1) + goto out; + /* Do not verify a write against U-Boot's cached copy. */ + blkcache_invalidate(desc->uclass_id, desc->devnum); + if (blk_dread(desc, parts[MTK_AB_MISC_INDEX].start, 1, verify) != 1 || + memcmp(block, verify, desc->blksz)) + goto out; + } + + *slot = selected; + ret = numbers[selected]; +out: + free(block); + return ret; +} diff --git a/arch/arm/mach-mediatek/mtk_ab.h b/arch/arm/mach-mediatek/mtk_ab.h new file mode 100644 index 00000000000..f5e9749e80c --- /dev/null +++ b/arch/arm/mach-mediatek/mtk_ab.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later AND BSD-3-Clause */ +/* + * Copyright (c) 2023, Mediatek Inc. All rights reserved. + * Copyright (c) 2026 BayLibre SAS + */ + +#ifndef __MTK_AB_H +#define __MTK_AB_H + +#include <linux/types.h> + +struct blk_desc; + +#define MTK_AB_MAGIC 0x544f494d +#define MTK_AB_VERSION 1 +#define MTK_AB_MAX_TRIES 9 + +/* MediaTek v1 disk ABI, including the original AArch64 structure padding. */ +struct mtk_ab_control { + __le32 slot; + __le32 magic; + u8 version; + u8 tries[2]; + u8 padding; + u8 reserved[12]; + __le32 crc32; +}; + +/** + * mtk_ab_select() - Select and account for a MediaTek firmware slot + * @desc: Block device containing the GPT user area + * @slot: Returns the selected slot (0 for A, 1 for B), only on success + * + * Resolve bootloaders, bootloaders_b and misc by name, validate the v1 record + * at the start of misc, and persist any required retry update before returning. + * The caller must provide a valid, non-overlapping GPT layout. + * A zero counter denotes success; counters 1..8 consume a trial and 9 is + * exhausted. Only an all-zero or all-0xff record is initialized automatically. + * Writes preserve the rest of the block and are verified by reading back + * with U-Boot's block cache invalidated. This single-copy format does not + * provide power-fail atomicity. + * + * Return: Positive GPT partition number to boot, or a negative error. The + * caller must stop booting on error; a failed write may have changed storage. + */ +int mtk_ab_select(struct blk_desc *desc, unsigned int *slot); + +#endif diff --git a/arch/arm/mach-mediatek/spl_ab.c b/arch/arm/mach-mediatek/spl_ab.c new file mode 100644 index 00000000000..adf9726131c --- /dev/null +++ b/arch/arm/mach-mediatek/spl_ab.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (c) 2026 BayLibre SAS */ + +#include <mmc.h> +#include <spl.h> +#include <stdio.h> +#include <linux/errno.h> + +#include "mtk_ab.h" + +int spl_mmc_boot_partition(struct mmc *mmc, const u32 boot_device) +{ + struct blk_desc *desc = mmc_get_blk_desc(mmc); + unsigned int slot; + int ret; + + if (IS_SD(mmc)) { + ret = -ENODEV; + goto error; + } + + ret = mtk_ab_select(desc, &slot); + if (ret < 0) + goto error; + + printf("MediaTek A/B: slot %c, partition %d\n", 'A' + slot, ret); + return ret; + +error: + printf("MediaTek A/B: selection failed (%d)\n", ret); + return ret; +} diff --git a/doc/board/mediatek/mt8188-genio-evk.rst b/doc/board/mediatek/mt8188-genio-evk.rst index e36e8faeb48..ea1966c21ff 100644 --- a/doc/board/mediatek/mt8188-genio-evk.rst +++ b/doc/board/mediatek/mt8188-genio-evk.rst @@ -36,7 +36,8 @@ followed by U-Boot SPL. The DDR loader initializes DRAM, copies the fixed ``CONFIG_MTK_GENIO_DDR_LOADER_SIZE`` to ``CONFIG_SPL_TEXT_BASE`` and enters SPL at EL3 with exceptions masked. -SPL reads a FIT image from partition 1 of the eMMC user area. The FIT contains +By default, SPL reads a FIT image from partition 1 of the eMMC user area. +Optional firmware A/B selection is described below. The FIT contains Arm Trusted Firmware-A (BL31), OP-TEE (BL32), U-Boot proper (BL33) and the U-Boot control devicetree. It is an ordinary, directly parseable FIT whose first word is the FDT magic; the complete FIT container must not be compressed. @@ -252,12 +253,12 @@ Image Destination =================== ================================================ The standard Genio 510 and Genio 700 partition layouts name GPT partition 1 -``bootloaders``. SPL selects the partition by number through +``bootloaders``. By default, SPL selects the partition by number through ``CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_PARTITION``; it does not locate it by name. Writing an invalid image to eMMC boot0 can make the board unbootable. Preserve the platform recovery path and any backup bootloader partition while testing. -Selection of a backup partition is not implemented by this SPL configuration. +Selection of a backup partition requires the optional firmware A/B support. Using Genio Tools ~~~~~~~~~~~~~~~~~ @@ -279,3 +280,81 @@ Genio Tools uses the default bootstrap from the image directory. Keep ``bootloaders_b`` unchanged until the new images have been tested. Add ``--dry-run`` to check the selected files and partitions without accessing the board. + +Firmware A/B in SPL +------------------ + +``CONFIG_SPL_MTK_FIRMWARE_AB`` enables A/B selection for the firmware loaded +by SPL from eMMC. It is disabled by default. Enable it only with an updater +that activates firmware slots and a service that confirms successful boots. +After selecting the board defconfig, enable it with:: + + $ scripts/config --file "${KBUILD_OUTPUT}/.config" \ + --disable SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION \ + --enable SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION_DYNAMIC \ + --disable SPL_RAW_IMAGE_SUPPORT \ + --disable SPL_LEGACY_IMAGE_FORMAT \ + --enable SPL_MTK_FIRMWARE_AB + $ make olddefconfig + +Install a complete ``bootloaders.img`` FIT in each of the ``bootloaders`` +(slot A) and ``bootloaders_b`` (slot B) GPT partitions. The ``misc`` partition +stores the selected slot and boot-attempt counters. When migrating from BL2, +replace the FIP images in both slots with FIT images. To verify signed +firmware, also follow `Signed bootloader FIT`_; A/B selection does not enable +signature verification. Both slot images must then be signed with a key +trusted by the installed SPL. + +The DDR loader and SPL in boot0 are shared by both firmware slots. + +With SPL installed in the standard RITY eMMC layout, the boot flow is:: + + BootROM + | + v + +---------------------------+ +---------------------------+ + | boot0 | | boot1 | + | DDR loader -> SPL | | U-Boot environment | + | shared by slots A and B | | not a firmware slot | + +-------------+-------------+ +---------------------------+ + | + v + +-------------------------------------------------------------+ + | eMMC user area (GPT partitions) | + | | + | +-----------------------------------------------------+ | + | | misc | | + | | Selected firmware slot and boot-attempt counters | | + | +-------------------------+---------------------------+ | + | | | + | select A or B | + | +--------------+--------------+ | + | v v | + | +-----------------------+ +-------------------------+ | + | | bootloaders (slot A) | | bootloaders_b (slot B) | | + | | FIT: BL31, BL32, BL33 | | FIT: BL31, BL32, BL33 | | + | +-----------------------+ +-------------------------+ | + +-------------------------------------------------------------+ + +Firmware updates and rollback +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use an updater and boot-success service compatible with MediaTek firmware +A/B metadata: + +1. Write and verify the new FIT in the inactive slot. +2. Activate that slot for trial booting. +3. After booting and completing the required health checks, mark the booted + slot successful. + +A newly activated trial slot has eight boot attempts. Without confirmation, +even a working image eventually exhausts its attempts. SPL then selects the +other slot on the following boot, provided that slot has attempts remaining +or was previously marked successful. Blank metadata initially selects slot A. + +A failed FIT load or verification stops the current boot. Configure a watchdog +or another reset mechanism if unattended retries are required. Keep a working +download/recovery path for exhausted slots or damaged metadata, including +damage caused by an interrupted metadata update. A slot already marked +successful is no longer subject to trial retries; a later failure of that +image requires recovery. --- base-commit: bd2f5794b7e24c62042abb1921f9c2039b920e02 change-id: 20260924-ccaione-upstream-mt8390-ab-631b4d4537f7 Best regards, -- Carlo Caione <[email protected]>
