The Nodebox 10G stores firmware in a board-specific format (mvebu_image_tag) at fixed offsets in eMMC. Each image bundles a kernel and device tree under a CRC32-validated tag header.
Include a board-specific rollback support based on a reboot counter in order to track consecutive boot failures and then to boot from the fallback image. Signed-off-by: Vincent Jardin <[email protected]> --- cmd/mvebu/Kconfig | 53 +++++ cmd/mvebu/Makefile | 1 + cmd/mvebu/mvebu_emmcboot.c | 340 ++++++++++++++++++++++++++++ configs/mvebu_nbx_88f8040_defconfig | 1 + include/mvebu_imagetag.h | 82 +++++++ include/mvebu_nrboot.h | 51 +++++ 6 files changed, 528 insertions(+) create mode 100644 cmd/mvebu/mvebu_emmcboot.c create mode 100644 include/mvebu_imagetag.h create mode 100644 include/mvebu_nrboot.h diff --git a/cmd/mvebu/Kconfig b/cmd/mvebu/Kconfig index e83a9829491..ea03f581280 100644 --- a/cmd/mvebu/Kconfig +++ b/cmd/mvebu/Kconfig @@ -79,4 +79,57 @@ config CMD_MVEBU_COMPHY_RX_TRAINING help Perform COMPHY RX training sequence +config CMD_NBX_EMMCBOOT + bool "Nodebox emmcboot command" + depends on ARMADA_8K && MMC_SDHCI_XENON + help + Enable the emmcboot command for Nodebox 10G boards. The command + loads and boots firmware stored in the board-specific image format + (mvebu_image_tag) at fixed eMMC offsets. Each image bundles a + kernel and device tree under a CRC32-validated tag header. + + Two image banks are supported (Bank0 stable, Bank1 newer) with + automatic fallback based on a reboot tracking counter (nrboot). + + Requires image_addr and fdt_addr environment variables to be set. + +if CMD_NBX_EMMCBOOT + +config MVEBU_MMC_PART_NRBOOT_OFFSET + hex "NRBoot counter offset in eMMC" + default 0x802000 + help + Byte offset in eMMC where the reboot tracking counter is stored. + Default: 0x802000 (8MB + 8KB) + +config MVEBU_MMC_PART_BANK0_OFFSET + hex "Bank0 image offset in eMMC" + default 0x804000 + help + Byte offset in eMMC where the stable (Bank0) boot image starts. + Default: 0x804000 (8MB + 16KB) + +config MVEBU_MMC_PART_BANK0_SIZE + hex "Bank0 image maximum size" + default 0x10000000 + help + Maximum size of the Bank0 boot image. + Default: 0x10000000 (256MB) + +config MVEBU_MMC_PART_BANK1_OFFSET + hex "Bank1 image offset in eMMC" + default 0x10804000 + help + Byte offset in eMMC where the newer (Bank1) boot image starts. + Default: 0x10804000 (264MB + 16KB) + +config MVEBU_MMC_PART_BANK1_SIZE + hex "Bank1 image maximum size" + default 0x10000000 + help + Maximum size of the Bank1 boot image. + Default: 0x10000000 (256MB) + +endif + endmenu diff --git a/cmd/mvebu/Makefile b/cmd/mvebu/Makefile index ca96ad01d91..c096f507e26 100644 --- a/cmd/mvebu/Makefile +++ b/cmd/mvebu/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_CMD_MVEBU_BUBT) += bubt.o obj-$(CONFIG_CMD_MVEBU_COMPHY_RX_TRAINING) += comphy_rx_training.o +obj-$(CONFIG_CMD_NBX_EMMCBOOT) += mvebu_emmcboot.o diff --git a/cmd/mvebu/mvebu_emmcboot.c b/cmd/mvebu/mvebu_emmcboot.c new file mode 100644 index 00000000000..3d85f82670a --- /dev/null +++ b/cmd/mvebu/mvebu_emmcboot.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * MVEBU eMMC boot command for Nodebox 10G boot image format + * + * Copyright (C) 2026 Free Mobile, Freebox + * + * The Nodebox 10G stores firmware images in a board-specific format + * (mvebu_image_tag) at fixed offsets in eMMC. Each image bundles a + * kernel, device tree, and optional rootfs under a CRC32-validated + * tag header. + * + * Two image banks are laid out in eMMC (Bank0 stable, Bank1 newer). + * A reboot counter (nrboot) selects which bank to try first. + */ + +#include <command.h> +#include <env.h> +#include <mmc.h> +#include <malloc.h> +#include <memalign.h> +#include <vsprintf.h> +#include <u-boot/crc.h> +#include <u-boot/schedule.h> +#include <asm/byteorder.h> +#include <linux/bitops.h> +#include <mvebu_imagetag.h> +#include <mvebu_nrboot.h> + +/* Image Tag Functions */ + +static int mvebu_imagetag_check(struct mvebu_image_tag *tag, + unsigned long maxsize, const char *name) +{ + if (be32_to_cpu(tag->magic) != MVEBU_IMAGE_TAG_MAGIC) { + if (name) + printf("%s: invalid TAG magic: %.8x\n", name, + be32_to_cpu(tag->magic)); + return -1; + } + + if (be32_to_cpu(tag->version) != MVEBU_IMAGE_TAG_VERSION) { + if (name) + printf("%s: invalid TAG version: %.8x\n", name, + be32_to_cpu(tag->version)); + return -1; + } + + if (be32_to_cpu(tag->total_size) < sizeof(*tag)) { + if (name) + printf("%s: tag size is too small!\n", name); + return -1; + } + + if (be32_to_cpu(tag->total_size) > maxsize) { + if (name) + printf("%s: tag size is too big!\n", name); + return -1; + } + + if (be32_to_cpu(tag->device_tree_offset) < sizeof(*tag) || + be32_to_cpu(tag->device_tree_offset) + + be32_to_cpu(tag->device_tree_size) > maxsize) { + if (name) + printf("%s: bogus device tree offset/size!\n", name); + return -1; + } + + if (be32_to_cpu(tag->kernel_offset) < sizeof(*tag) || + be32_to_cpu(tag->kernel_offset) + + be32_to_cpu(tag->kernel_size) > maxsize) { + if (name) + printf("%s: bogus kernel offset/size!\n", name); + return -1; + } + + if (be32_to_cpu(tag->rootfs_offset) < sizeof(*tag) || + be32_to_cpu(tag->rootfs_offset) + + be32_to_cpu(tag->rootfs_size) > maxsize) { + if (name) + printf("%s: bogus rootfs offset/size!\n", name); + return -1; + } + + if (name) { + /* + * Ensure null-termination within the 32-byte fields + * before printing to avoid displaying garbage. + */ + tag->image_name[sizeof(tag->image_name) - 1] = '\0'; + tag->build_date[sizeof(tag->build_date) - 1] = '\0'; + tag->build_user[sizeof(tag->build_user) - 1] = '\0'; + + printf("%s: Found valid tag: %s / %s / %s\n", name, + tag->image_name, tag->build_date, tag->build_user); + } + + return 0; +} + +static int mvebu_imagetag_crc(struct mvebu_image_tag *tag, const char *name) +{ + u32 crc = ~0; + + crc = crc32(crc, ((unsigned char *)tag) + 4, + be32_to_cpu(tag->total_size) - 4); + + if (be32_to_cpu(tag->crc) != crc) { + if (name) + printf("%s: invalid tag CRC!\n", name); + return -1; + } + + return 0; +} + +/* NRBoot (Reboot Tracking) Functions */ + +int mvebu_check_nrboot(struct mmc *mmc, unsigned long offset) +{ + struct blk_desc *bd = mmc_get_blk_desc(mmc); + struct mvebu_nrboot *nr; + uint blk_start = ALIGN(offset, bd->blksz) / bd->blksz; + uint blk_cnt = ALIGN(sizeof(*nr), bd->blksz) / bd->blksz; + uint n; + + ALLOC_CACHE_ALIGN_BUFFER(char, buf, blk_cnt * bd->blksz); + nr = (void *)buf; + + n = blk_dread(bd, blk_start, blk_cnt, buf); + if (n != blk_cnt) + return 0; + + printf(" - nr.nrboot = %04x\n", nr->nrboot); + printf(" - nr.nrsuccess = %04x\n", nr->nrsuccess); + + /* Sanity check: values must be valid bit-field counters */ + if (generic_hweight16(~nr->nrboot + 1) <= 1 && + generic_hweight16(~nr->nrsuccess + 1) <= 1) { + int boot, success; + + boot = 16 - generic_hweight16(nr->nrboot); + success = 16 - generic_hweight16(nr->nrsuccess); + + printf(" - Nrboot: %d / Nrsuccess: %d\n", boot, success); + + if (boot == 16 || boot < success || + boot - success >= MVEBU_MAX_FAILURE) { + printf(" - Nrboot exceeded\n"); + return 0; + } + + /* Increment boot attempt counter */ + boot++; + nr->nrboot = ~((1 << boot) - 1); + + printf(" - Setting Nrboot to %d\n", boot); + + n = blk_dwrite(bd, blk_start, blk_cnt, buf); + if (n != blk_cnt) + return 0; + + return 1; + } + + printf(" - Invalid NR values\n"); + + return 0; +} + +/* emmcboot Command */ + +static int mvebu_load_image(struct blk_desc *bd, unsigned long offset, + unsigned long maxsize, ulong tag_addr, + const char *bank) +{ + struct mvebu_image_tag *tag; + uint blk_start = ALIGN(offset, bd->blksz) / bd->blksz; + uint blk_cnt; + uint n; + + ALLOC_CACHE_ALIGN_BUFFER(char, tag_buf, + ALIGN(sizeof(*tag), bd->blksz)); + tag = (void *)tag_buf; + + /* Load and validate tag header */ + blk_cnt = ALIGN(sizeof(*tag), bd->blksz) / bd->blksz; + n = blk_dread(bd, blk_start, blk_cnt, tag_buf); + if (n != blk_cnt) { + printf("%s: failed to read tag header\n", bank); + return -1; + } + + if (mvebu_imagetag_check(tag, maxsize, bank) != 0) + return -1; + + if (tag->rootfs_size != 0) { + printf("%s: rootfs in tag not supported\n", bank); + return -1; + } + + /* Load full image to tag_addr */ + blk_cnt = ALIGN(mvebu_imagetag_total_size(tag), bd->blksz) / bd->blksz; + n = blk_dread(bd, blk_start, blk_cnt, (void *)tag_addr); + if (n != blk_cnt) { + printf("%s: failed to read full image\n", bank); + return -1; + } + + if (mvebu_imagetag_crc((void *)tag_addr, bank) != 0) + return -1; + + return 0; +} + +static void mvebu_relocate_and_boot(ulong image_addr, ulong fdt_addr, + const char *bank) +{ + struct mvebu_image_tag *tag = (void *)image_addr; + char bootargs[256]; + char cmd[128]; + char *console_env; + + /* Copy DTB and kernel to their final addresses */ + memcpy((void *)fdt_addr, + ((void *)image_addr) + mvebu_imagetag_device_tree_offset(tag), + mvebu_imagetag_device_tree_size(tag)); + memmove((void *)image_addr, + ((void *)image_addr) + mvebu_imagetag_kernel_offset(tag), + mvebu_imagetag_kernel_size(tag)); + + schedule(); + + /* Set bootargs */ + console_env = env_get("console"); + if (console_env) + snprintf(bootargs, sizeof(bootargs), + "console=%s bank=%s", console_env, bank); + else + snprintf(bootargs, sizeof(bootargs), "bank=%s", bank); + + env_set("bootargs", bootargs); + + printf("## Booting kernel from %s...\n", bank); + printf(" Image addr: 0x%lx\n", image_addr); + printf(" FDT addr: 0x%lx\n", fdt_addr); + + snprintf(cmd, sizeof(cmd), "booti 0x%lx - 0x%lx", + image_addr, fdt_addr); + run_command(cmd, 0); +} + +static void mvebu_try_emmcboot(struct mmc *mmc, unsigned long offset, + unsigned long maxsize, const char *bank) +{ + struct blk_desc *bd = mmc_get_blk_desc(mmc); + ulong image_addr; + ulong fdt_addr; + + schedule(); + + printf("## Trying %s boot...\n", bank); + + /* Get load addresses from environment */ + image_addr = env_get_ulong("image_addr", 16, 0); + if (!image_addr) { + puts("emmcboot needs image_addr\n"); + return; + } + + fdt_addr = env_get_ulong("fdt_addr", 16, 0); + if (!fdt_addr) { + puts("emmcboot needs fdt_addr\n"); + return; + } + + if (mvebu_load_image(bd, offset, maxsize, image_addr, bank) != 0) + return; + + schedule(); + + mvebu_relocate_and_boot(image_addr, fdt_addr, bank); + + printf("## %s boot failed\n", bank); +} + +static int do_emmcboot(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int dev; + struct mmc *mmc; + + dev = 0; + if (argc >= 2) + dev = dectoul(argv[1], NULL); + + mmc = find_mmc_device(dev); + if (!mmc) { + printf("No MMC device %d found\n", dev); + return CMD_RET_FAILURE; + } + + if (mmc_init(mmc)) { + puts("MMC init failed\n"); + return CMD_RET_FAILURE; + } + + /* Switch to partition 0 (user data area) */ + if (blk_select_hwpart_devnum(UCLASS_MMC, dev, 0)) { + puts("MMC partition switch failed\n"); + return CMD_RET_FAILURE; + } + + if (mvebu_check_nrboot(mmc, CONFIG_MVEBU_MMC_PART_NRBOOT_OFFSET)) { + /* System is healthy: try newer bank first */ + mvebu_try_emmcboot(mmc, CONFIG_MVEBU_MMC_PART_BANK1_OFFSET, + CONFIG_MVEBU_MMC_PART_BANK1_SIZE, "bank1"); + mvebu_try_emmcboot(mmc, CONFIG_MVEBU_MMC_PART_BANK0_OFFSET, + CONFIG_MVEBU_MMC_PART_BANK0_SIZE, "bank0"); + } else { + /* System is degraded: use stable bank first */ + mvebu_try_emmcboot(mmc, CONFIG_MVEBU_MMC_PART_BANK0_OFFSET, + CONFIG_MVEBU_MMC_PART_BANK0_SIZE, "bank0"); + mvebu_try_emmcboot(mmc, CONFIG_MVEBU_MMC_PART_BANK1_OFFSET, + CONFIG_MVEBU_MMC_PART_BANK1_SIZE, "bank1"); + } + + puts("emmcboot: all boot attempts failed\n"); + + return CMD_RET_FAILURE; +} + +U_BOOT_CMD( + emmcboot, 2, 0, do_emmcboot, + "boot from Nodebox eMMC image banks", + "[dev]\n" + " - Load and boot a Nodebox image from eMMC device <dev> (default 0)\n" + " - Requires image_addr and fdt_addr environment variables\n" + " - Two banks: Bank0 (stable) and Bank1 (newer)\n" + " - Bank order selected by reboot tracking counter (nrboot)" +); diff --git a/configs/mvebu_nbx_88f8040_defconfig b/configs/mvebu_nbx_88f8040_defconfig index 975c53e9a92..2fd58e4ad64 100644 --- a/configs/mvebu_nbx_88f8040_defconfig +++ b/configs/mvebu_nbx_88f8040_defconfig @@ -40,6 +40,7 @@ CONFIG_CMD_PING=y CONFIG_CMD_CACHE=y CONFIG_CMD_TIME=y CONFIG_CMD_TIMER=y +CONFIG_CMD_NBX_EMMCBOOT=y CONFIG_CMD_EXT2=y CONFIG_CMD_EXT4=y CONFIG_CMD_EXT4_WRITE=y diff --git a/include/mvebu_imagetag.h b/include/mvebu_imagetag.h new file mode 100644 index 00000000000..d513038aaf6 --- /dev/null +++ b/include/mvebu_imagetag.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Nodebox 10G boot image tag format + * + * Copyright (C) 2026 Free Mobile, Freebox + * + * Defines the on-eMMC image layout used by Nodebox 10G boards. + * Each image carries a CRC32-validated tag header that describes + * the kernel, device tree, and rootfs components. + */ + +#ifndef __MVEBU_IMAGETAG_H +#define __MVEBU_IMAGETAG_H + +#include <linux/types.h> + +#define MVEBU_IMAGE_TAG_MAGIC 0x8d7c90bc +#define MVEBU_IMAGE_TAG_VERSION 1 + +/** + * struct mvebu_image_tag - Nodebox boot image tag stored in eMMC + * + * All multi-byte fields are stored in big-endian format. + */ +struct mvebu_image_tag { + u32 crc; /* CRC32-LE checksum (from offset 4) */ + u32 magic; /* Magic: 0x8d7c90bc */ + u32 version; /* Version: 1 */ + u32 total_size; /* Total image size including tag */ + u32 flags; /* Feature flags (reserved) */ + + u32 device_tree_offset; /* Offset from tag start to DTB */ + u32 device_tree_size; /* DTB size in bytes */ + + u32 kernel_offset; /* Offset from tag start to kernel */ + u32 kernel_size; /* Kernel size in bytes */ + + u32 rootfs_offset; /* Offset from tag start to rootfs */ + u32 rootfs_size; /* Rootfs size (must be 0) */ + + char image_name[32]; /* Image name (null-terminated) */ + char build_user[32]; /* Build user info */ + char build_date[32]; /* Build date info */ +}; + +/* Accessor functions for big-endian fields */ +static inline u32 mvebu_imagetag_device_tree_offset(struct mvebu_image_tag *tag) +{ + return be32_to_cpu(tag->device_tree_offset); +} + +static inline u32 mvebu_imagetag_device_tree_size(struct mvebu_image_tag *tag) +{ + return be32_to_cpu(tag->device_tree_size); +} + +static inline u32 mvebu_imagetag_kernel_offset(struct mvebu_image_tag *tag) +{ + return be32_to_cpu(tag->kernel_offset); +} + +static inline u32 mvebu_imagetag_kernel_size(struct mvebu_image_tag *tag) +{ + return be32_to_cpu(tag->kernel_size); +} + +static inline u32 mvebu_imagetag_rootfs_offset(struct mvebu_image_tag *tag) +{ + return be32_to_cpu(tag->rootfs_offset); +} + +static inline u32 mvebu_imagetag_rootfs_size(struct mvebu_image_tag *tag) +{ + return be32_to_cpu(tag->rootfs_size); +} + +static inline u32 mvebu_imagetag_total_size(struct mvebu_image_tag *tag) +{ + return be32_to_cpu(tag->total_size); +} + +#endif /* __MVEBU_IMAGETAG_H */ diff --git a/include/mvebu_nrboot.h b/include/mvebu_nrboot.h new file mode 100644 index 00000000000..11d59d7680f --- /dev/null +++ b/include/mvebu_nrboot.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Nodebox 10G reboot tracking counter (NRBoot) + * + * Copyright (C) 2026 Free Mobile, Freebox + * + * Part of the Nodebox eMMC image format: a small bit-field counter + * stored between the serial info and the image banks that tracks + * consecutive boot failures to select the right bank. + */ + +#ifndef __MVEBU_NRBOOT_H +#define __MVEBU_NRBOOT_H + +#include <linux/types.h> +#include <mmc.h> + +#define MVEBU_MAX_FAILURE 4 + +/** + * struct mvebu_nrboot - Reboot tracking counter stored in eMMC + * @nrboot: Bit-field counter of boot attempts (0-bits = attempt count) + * @nrsuccess: Bit-field counter of successful boots + */ +struct mvebu_nrboot { + u16 nrboot; + u16 nrsuccess; +}; + +/** + * mvebu_check_nrboot() - Check and update reboot tracking counter + * @mmc: MMC device + * @offset: Byte offset in MMC where nrboot data is stored + * + * Reads the reboot tracking counter, checks if the maximum number of + * failed boots (4) has been exceeded, and updates the counter for the + * current boot attempt. + * + * The counter uses a bit-field encoding for wear leveling: + * - nrboot: Running count of boot attempts (counted as cleared bits) + * - nrsuccess: Count of successful boots (counted as cleared bits) + * + * If boot - success >= MAX_FAILURE (4), the system is considered + * degraded and should use the fallback boot bank. + * + * Return: 1 if system is healthy (try newer bank first), + * 0 if system is degraded (use stable bank first) + */ +int mvebu_check_nrboot(struct mmc *mmc, unsigned long offset); + +#endif /* __MVEBU_NRBOOT_H */ -- 2.43.0

