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]>

Reply via email to