Add the emmcboot command as board-specific support for the Nodebox 10G. This is a legacy boot format that has been in production on this board for many years so it cannot change anymore. It implements a dual-bank boot system for reliable firmware updates:
- Bank0: Stable/fallback boot image - Bank1: Newer/test boot image with reboot tracking The boot order depends on the nrboot counter stored in eMMC: - Healthy state (counter < 4): Try Bank1 first, then Bank0 - Degraded state (counter >= 4): Try Bank0 first, then Bank1 Each bank stores an image tag with CRC32 validation. The counter uses a bit-counting scheme for wear leveling and tracks consecutive failed boots to trigger automatic fallback. Signed-off-by: Vincent Jardin <[email protected]> Reviewed-by: Stefan Roese <[email protected]> --- Changes in v3: - Move per-patch changelog below --- per U-Boot guidelines (Stefan) - Collected Reviewed-by Stefan from v2 review Changes in v2: - Moved from cmd/mvebu/ to board/freebox/nbx10g/ - Renamed Kconfig CMD_MVEBU_EMMCBOOT -> CMD_NBX_EMMCBOOT and MVEBU_MMC_PART_* -> NBX_MMC_PART_* - Replaced all `return -1` with `return -EINVAL` - Commit message now explains legacy format board/freebox/nbx10g/Kconfig | 53 +++++ board/freebox/nbx10g/Makefile | 1 + board/freebox/nbx10g/nbx_emmcboot.c | 357 ++++++++++++++++++++++++++++ board/freebox/nbx10g/nbx_imagetag.h | 78 ++++++ board/freebox/nbx10g/nbx_nrboot.h | 34 +++ 5 files changed, 523 insertions(+) create mode 100644 board/freebox/nbx10g/nbx_emmcboot.c create mode 100644 board/freebox/nbx10g/nbx_imagetag.h create mode 100644 board/freebox/nbx10g/nbx_nrboot.h diff --git a/board/freebox/nbx10g/Kconfig b/board/freebox/nbx10g/Kconfig index 18a169761b7..d21153eae75 100644 --- a/board/freebox/nbx10g/Kconfig +++ b/board/freebox/nbx10g/Kconfig @@ -9,4 +9,57 @@ config SYS_VENDOR config SYS_CONFIG_NAME default "nbx10g" +config CMD_NBX_EMMCBOOT + bool "emmcboot command" + depends on MMC_SDHCI_XENON + help + Enable the emmcboot command for dual-bank boot from eMMC. + This is a legacy boot format used on this board for many years. + It implements a boot system with two image banks and automatic + fallback on boot failures. The boot order depends on a reboot + tracking counter (nrboot): + - If healthy: try Bank1 (newer) first, then Bank0 (stable) + - If degraded (>= 4 failures): try Bank0 first, then Bank1 + + Requires image_addr and fdt_addr environment variables to be set. + +if CMD_NBX_EMMCBOOT + +config NBX_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 NBX_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 NBX_MMC_PART_BANK0_SIZE + hex "Bank0 image maximum size" + default 0x10000000 + help + Maximum size of the Bank0 boot image. + Default: 0x10000000 (256MB) + +config NBX_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 NBX_MMC_PART_BANK1_SIZE + hex "Bank1 image maximum size" + default 0x10000000 + help + Maximum size of the Bank1 boot image. + Default: 0x10000000 (256MB) + +endif + endif diff --git a/board/freebox/nbx10g/Makefile b/board/freebox/nbx10g/Makefile index bf83bdf63ee..a3b3d3a1fe3 100644 --- a/board/freebox/nbx10g/Makefile +++ b/board/freebox/nbx10g/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0+ obj-y := board.o +obj-$(CONFIG_CMD_NBX_EMMCBOOT) += nbx_emmcboot.o diff --git a/board/freebox/nbx10g/nbx_emmcboot.c b/board/freebox/nbx10g/nbx_emmcboot.c new file mode 100644 index 00000000000..0bea96fadd9 --- /dev/null +++ b/board/freebox/nbx10g/nbx_emmcboot.c @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Nodebox 10G dual-bank eMMC boot command with automatic fallback + * + * Copyright (C) 2026 Free Mobile, Freebox + * + * This implements a dual-bank boot system with automatic fallback: + * - Bank0: Stable/fallback boot image + * - Bank1: Newer/test boot image + * + * The boot order depends on the reboot tracking counter (nrboot): + * - If healthy: try Bank1 first, then Bank0 + * - If degraded (>= 4 failures): try Bank0 first, then Bank1 + */ + +#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/errno.h> +#include "nbx_imagetag.h" +#include "nbx_nrboot.h" + +/* Partition offsets defined in Kconfig (CONFIG_NBX_MMC_PART_*) */ + +/* 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 -EINVAL; + } + + 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 -EINVAL; + } + + if (be32_to_cpu(tag->total_size) < sizeof(*tag)) { + if (name) + printf("%s: tag size is too small!\n", name); + return -EINVAL; + } + + if (be32_to_cpu(tag->total_size) > maxsize) { + if (name) + printf("%s: tag size is too big!\n", name); + return -EINVAL; + } + + 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 -EINVAL; + } + + 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 -EINVAL; + } + + 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 -EINVAL; + } + + 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 -EINVAL; + } + + return 0; +} + +/* NRBoot (Reboot Tracking) Functions */ + +struct mvebu_nrboot { + u16 nrboot; + u16 nrsuccess; +}; + +#define MVEBU_MAX_FAILURE 4 + +static int mvebu_count_bits(u16 val) +{ + int i, found = 0; + + for (i = 0; i < 16; i++) { + if (val & (1 << i)) + found++; + } + return found; +} + +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 on values */ + if (mvebu_count_bits(~nr->nrboot + 1) <= 1 && + mvebu_count_bits(~nr->nrsuccess + 1) <= 1) { + int boot, success; + + boot = 16 - mvebu_count_bits(nr->nrboot); + success = 16 - mvebu_count_bits(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 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); + struct mvebu_image_tag *tag; + ulong image_addr = 0; + ulong fdt_addr = 0; + ulong tag_addr; + uint tag_blk_start = ALIGN(offset, bd->blksz) / bd->blksz; + uint tag_blk_cnt = ALIGN(sizeof(*tag), bd->blksz) / bd->blksz; + uint n; + + ALLOC_CACHE_ALIGN_BUFFER(char, tag_buf, tag_blk_cnt * bd->blksz); + tag = (void *)tag_buf; + + schedule(); + + printf("## Trying %s boot...\n", bank); + + /* Load tag header */ + n = blk_dread(bd, tag_blk_start, tag_blk_cnt, tag_buf); + if (n != tag_blk_cnt) { + printf("%s: failed to read tag header\n", bank); + return; + } + + if (mvebu_imagetag_check(tag, maxsize, bank) != 0) + return; + + if (tag->rootfs_size != 0) { + printf("%s: rootfs in tag not supported\n", bank); + return; + } + + /* Get image and device tree 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; + } + + tag_addr = image_addr; + + /* Load full image, temporarily reuse image_addr for this */ + { + uint data_blk_start = ALIGN(offset, bd->blksz) / bd->blksz; + uint data_blk_cnt = ALIGN(mvebu_imagetag_total_size(tag), + bd->blksz) / bd->blksz; + + n = blk_dread(bd, data_blk_start, data_blk_cnt, (void *)tag_addr); + if (n != data_blk_cnt) { + printf("%s: failed to read full image\n", bank); + return; + } + + if (mvebu_imagetag_crc((void *)tag_addr, bank) != 0) + return; + } + + schedule(); + + /* Copy image and device tree to the right addresses */ + /* We assume that image_addr + tag_size < fdt_addr */ + { + tag = (void *)tag_addr; + memcpy((void *)fdt_addr, + ((void *)tag_addr) + mvebu_imagetag_device_tree_offset(tag), + mvebu_imagetag_device_tree_size(tag)); + memmove((void *)image_addr, + ((void *)tag_addr) + mvebu_imagetag_kernel_offset(tag), + mvebu_imagetag_kernel_size(tag)); + } + + schedule(); + + /* Set bootargs and boot */ + { + char bootargs[256]; + char *console_env; + + console_env = env_get("console"); + if (console_env) + snprintf(bootargs, sizeof(bootargs), "%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); + + /* Build and run booti command */ + { + char cmd[128]; + + snprintf(cmd, sizeof(cmd), "booti 0x%lx - 0x%lx", + image_addr, fdt_addr); + run_command(cmd, 0); + } + } + + 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_NBX_MMC_PART_NRBOOT_OFFSET)) { + /* System is healthy: try newer bank first */ + mvebu_try_emmcboot(mmc, CONFIG_NBX_MMC_PART_BANK1_OFFSET, + CONFIG_NBX_MMC_PART_BANK1_SIZE, "bank1"); + mvebu_try_emmcboot(mmc, CONFIG_NBX_MMC_PART_BANK0_OFFSET, + CONFIG_NBX_MMC_PART_BANK0_SIZE, "bank0"); + } else { + /* System is degraded: use stable bank first */ + mvebu_try_emmcboot(mmc, CONFIG_NBX_MMC_PART_BANK0_OFFSET, + CONFIG_NBX_MMC_PART_BANK0_SIZE, "bank0"); + mvebu_try_emmcboot(mmc, CONFIG_NBX_MMC_PART_BANK1_OFFSET, + CONFIG_NBX_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 MVEBU eMMC image banks", + "[dev]\n" + " - Boot from eMMC device <dev> (default 0)\n" + " - Requires image_addr and fdt_addr environment variables\n" + " - Uses dual-bank boot with automatic fallback\n" + " - Bank selection based on reboot tracking (nrboot)" +); diff --git a/board/freebox/nbx10g/nbx_imagetag.h b/board/freebox/nbx10g/nbx_imagetag.h new file mode 100644 index 00000000000..999293dd58a --- /dev/null +++ b/board/freebox/nbx10g/nbx_imagetag.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * MVEBU Image Tag header + * + * Copyright (C) 2026 Free Mobile, Freebox + */ + +#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 - MVEBU boot image tag structure + * + * 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/board/freebox/nbx10g/nbx_nrboot.h b/board/freebox/nbx10g/nbx_nrboot.h new file mode 100644 index 00000000000..91c9fb2e57b --- /dev/null +++ b/board/freebox/nbx10g/nbx_nrboot.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * MVEBU NRBoot (Number of Reboots) tracking header + * + * Copyright (C) 2026 Free Mobile, Freebox + */ + +#ifndef __MVEBU_NRBOOT_H +#define __MVEBU_NRBOOT_H + +#include <mmc.h> + +/** + * mvebu_check_nrboot() - Check and update reboot tracking counter + * @mmc: MMC device + * @offset: Byte offset in MMC where nrboot data is stored + * + * This function reads the reboot tracking counter, checks if we've + * exceeded the maximum number of failed boots (4), and updates the + * counter for the current boot attempt. + * + * The counter uses a bit-field encoding: + * - nrboot: Running count of boot attempts + * - nrsuccess: Count of successful boots + * + * 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.53.0

