get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/1.2/patches/2225603/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2225603,
    "url": "http://patchwork.ozlabs.org/api/1.2/patches/2225603/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/uboot/patch/6818abdc90f2ad2fbf8c5fdb0e97aa5ccc577007.1776762022.git.vjardin@free.fr/",
    "project": {
        "id": 18,
        "url": "http://patchwork.ozlabs.org/api/1.2/projects/18/?format=api",
        "name": "U-Boot",
        "link_name": "uboot",
        "list_id": "u-boot.lists.denx.de",
        "list_email": "u-boot@lists.denx.de",
        "web_url": null,
        "scm_url": null,
        "webscm_url": null,
        "list_archive_url": "",
        "list_archive_url_format": "",
        "commit_url_format": ""
    },
    "msgid": "<6818abdc90f2ad2fbf8c5fdb0e97aa5ccc577007.1776762022.git.vjardin@free.fr>",
    "list_archive_url": null,
    "date": "2026-04-21T09:04:57",
    "name": "[v2,3/5] board: freebox: nbx10g: add emmcboot for dual-bank eMMC boot",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "13554a440f266443f4dc049733fb31c0a6a610c7",
    "submitter": {
        "id": 89131,
        "url": "http://patchwork.ozlabs.org/api/1.2/people/89131/?format=api",
        "name": "Vincent Jardin",
        "email": "vjardin@free.fr"
    },
    "delegate": {
        "id": 1696,
        "url": "http://patchwork.ozlabs.org/api/1.2/users/1696/?format=api",
        "username": "stroese",
        "first_name": "Stefan",
        "last_name": "Roese",
        "email": "sr@denx.de"
    },
    "mbox": "http://patchwork.ozlabs.org/project/uboot/patch/6818abdc90f2ad2fbf8c5fdb0e97aa5ccc577007.1776762022.git.vjardin@free.fr/mbox/",
    "series": [
        {
            "id": 500764,
            "url": "http://patchwork.ozlabs.org/api/1.2/series/500764/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/uboot/list/?series=500764",
            "date": "2026-04-21T09:04:54",
            "name": "NBX10G: Marvell Armada 8040 Nodebox 10G board support",
            "version": 2,
            "mbox": "http://patchwork.ozlabs.org/series/500764/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/2225603/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/2225603/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<u-boot-bounces@lists.denx.de>",
        "X-Original-To": "incoming@patchwork.ozlabs.org",
        "Delivered-To": "patchwork-incoming@legolas.ozlabs.org",
        "Authentication-Results": [
            "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=free.fr header.i=@free.fr header.a=rsa-sha256\n header.s=smtp-20201208 header.b=c0cHWon0;\n\tdkim-atps=neutral",
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de\n (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de;\n envelope-from=u-boot-bounces@lists.denx.de; receiver=patchwork.ozlabs.org)",
            "phobos.denx.de;\n dmarc=pass (p=quarantine dis=none) header.from=free.fr",
            "phobos.denx.de;\n spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de",
            "phobos.denx.de;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=free.fr header.i=@free.fr header.b=\"c0cHWon0\";\n\tdkim-atps=neutral",
            "phobos.denx.de;\n dmarc=pass (p=quarantine dis=none) header.from=free.fr",
            "phobos.denx.de; spf=pass smtp.mailfrom=vjardin@free.fr"
        ],
        "Received": [
            "from phobos.denx.de (phobos.denx.de\n [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g0Gfb45Ztz1yCv\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 21 Apr 2026 19:05:43 +1000 (AEST)",
            "from h2850616.stratoserver.net (localhost [IPv6:::1])\n\tby phobos.denx.de (Postfix) with ESMTP id F238E84435;\n\tTue, 21 Apr 2026 11:05:30 +0200 (CEST)",
            "by phobos.denx.de (Postfix, from userid 109)\n id 9421F84390; Tue, 21 Apr 2026 11:05:30 +0200 (CEST)",
            "from smtp6-g21.free.fr (smtp6-g21.free.fr [212.27.42.6])\n (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits))\n (No client certificate requested)\n by phobos.denx.de (Postfix) with ESMTPS id 2793884445\n for <u-boot@lists.denx.de>; Tue, 21 Apr 2026 11:05:28 +0200 (CEST)",
            "from y14 (unknown [IPv6:2a01:e0a:2af:22b0:6ee8:ae48:54aa:4eff])\n (Authenticated sender: vjardin@free.fr)\n by smtp6-g21.free.fr (Postfix) with ESMTPSA id E05B6780503;\n Tue, 21 Apr 2026 11:05:22 +0200 (CEST)"
        ],
        "X-Spam-Checker-Version": "SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de",
        "X-Spam-Level": "",
        "X-Spam-Status": "No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED,\n DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FROM,\n RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL,\n SPF_HELO_PASS,SPF_PASS autolearn=ham autolearn_force=no version=3.4.2",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=free.fr;\n s=smtp-20201208; t=1776762327;\n bh=y7pjBrDL45HhF7UTbDvRku7Ls31M/0A3FziUvQ8vZ1o=;\n h=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n b=c0cHWon0P6lqpU5pdCbn7ZCHlFPhVPy80fNFSDI2NbKNXH3IWilb+C2bNdzsaMsXT\n vwcSD4/oWiOWmmmSJQ4RpKokNQerGPl0XdDtsIGkbGhcbNqE1BRX66IeJlXSayM7NQ\n yshiIz9skEcgzTufuPHH0TNkkd1qFky/yCvna7pQ7zrfMqhy0AhDxLJg5ewlMoPgZT\n 5rh9/jmGycg+tMJ+AfSpUn92Q8l98/XmR+Mna8vZqY52g3h/JJJHon54cTAW81MF7l\n pkZiaZjlHXBEq/9XL80ZSawEVD/UOFcZT+vAGk9LVLY+iKL1DieKHs/n1dIrbvrf96\n kvyZ3rl4DZLeg==",
        "From": "Vincent Jardin <vjardin@free.fr>",
        "To": "u-boot@lists.denx.de",
        "Cc": "Stefan Roese <stefan.roese@mailbox.org>, Tom Rini <trini@konsulko.com>,\n Vincent Jardin <vjardin@free.fr>",
        "Subject": "[PATCH v2 3/5] board: freebox: nbx10g: add emmcboot for dual-bank\n eMMC boot",
        "Date": "Tue, 21 Apr 2026 11:04:57 +0200",
        "Message-ID": "\n <6818abdc90f2ad2fbf8c5fdb0e97aa5ccc577007.1776762022.git.vjardin@free.fr>",
        "X-Mailer": "git-send-email 2.53.0",
        "In-Reply-To": "<cover.1776762022.git.vjardin@free.fr>",
        "References": "<cover.1776762022.git.vjardin@free.fr>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "X-BeenThere": "u-boot@lists.denx.de",
        "X-Mailman-Version": "2.1.39",
        "Precedence": "list",
        "List-Id": "U-Boot discussion <u-boot.lists.denx.de>",
        "List-Unsubscribe": "<https://lists.denx.de/options/u-boot>,\n <mailto:u-boot-request@lists.denx.de?subject=unsubscribe>",
        "List-Archive": "<https://lists.denx.de/pipermail/u-boot/>",
        "List-Post": "<mailto:u-boot@lists.denx.de>",
        "List-Help": "<mailto:u-boot-request@lists.denx.de?subject=help>",
        "List-Subscribe": "<https://lists.denx.de/listinfo/u-boot>,\n <mailto:u-boot-request@lists.denx.de?subject=subscribe>",
        "Errors-To": "u-boot-bounces@lists.denx.de",
        "Sender": "\"U-Boot\" <u-boot-bounces@lists.denx.de>",
        "X-Virus-Scanned": "clamav-milter 0.103.8 at phobos.denx.de",
        "X-Virus-Status": "Clean"
    },
    "content": "Add the emmcboot command as board-specific support for the Nodebox 10G.\n\nThis is a legacy boot format that has been in production on this board\nfor many years so it cannot change anymore. It implements a dual-bank\nboot system for reliable firmware updates:\n\n- Bank0: Stable/fallback boot image\n- Bank1: Newer/test boot image with reboot tracking\n\nThe boot order depends on the nrboot counter stored in eMMC:\n- Healthy state (counter < 4): Try Bank1 first, then Bank0\n- Degraded state (counter >= 4): Try Bank0 first, then Bank1\n\nEach bank stores an image tag with CRC32 validation. The counter uses\na bit-counting scheme for wear leveling and tracks consecutive failed\nboots to trigger automatic fallback.\n\nSigned-off-by: Vincent Jardin <vjardin@free.fr>\n---\n board/freebox/nbx10g/Kconfig        |  53 +++++\n board/freebox/nbx10g/Makefile       |   1 +\n board/freebox/nbx10g/nbx_emmcboot.c | 357 ++++++++++++++++++++++++++++\n board/freebox/nbx10g/nbx_imagetag.h |  78 ++++++\n board/freebox/nbx10g/nbx_nrboot.h   |  34 +++\n 5 files changed, 523 insertions(+)\n create mode 100644 board/freebox/nbx10g/nbx_emmcboot.c\n create mode 100644 board/freebox/nbx10g/nbx_imagetag.h\n create mode 100644 board/freebox/nbx10g/nbx_nrboot.h",
    "diff": "diff --git a/board/freebox/nbx10g/Kconfig b/board/freebox/nbx10g/Kconfig\nindex 18a169761b7..d21153eae75 100644\n--- a/board/freebox/nbx10g/Kconfig\n+++ b/board/freebox/nbx10g/Kconfig\n@@ -9,4 +9,57 @@ config SYS_VENDOR\n config SYS_CONFIG_NAME\n \tdefault \"nbx10g\"\n \n+config CMD_NBX_EMMCBOOT\n+\tbool \"emmcboot command\"\n+\tdepends on MMC_SDHCI_XENON\n+\thelp\n+\t  Enable the emmcboot command for dual-bank boot from eMMC.\n+\t  This is a legacy boot format used on this board for many years.\n+\t  It implements a boot system with two image banks and automatic\n+\t  fallback on boot failures. The boot order depends on a reboot\n+\t  tracking counter (nrboot):\n+\t  - If healthy: try Bank1 (newer) first, then Bank0 (stable)\n+\t  - If degraded (>= 4 failures): try Bank0 first, then Bank1\n+\n+\t  Requires image_addr and fdt_addr environment variables to be set.\n+\n+if CMD_NBX_EMMCBOOT\n+\n+config NBX_MMC_PART_NRBOOT_OFFSET\n+\thex \"NRBoot counter offset in eMMC\"\n+\tdefault 0x802000\n+\thelp\n+\t  Byte offset in eMMC where the reboot tracking counter is stored.\n+\t  Default: 0x802000 (8MB + 8KB)\n+\n+config NBX_MMC_PART_BANK0_OFFSET\n+\thex \"Bank0 image offset in eMMC\"\n+\tdefault 0x804000\n+\thelp\n+\t  Byte offset in eMMC where the stable (Bank0) boot image starts.\n+\t  Default: 0x804000 (8MB + 16KB)\n+\n+config NBX_MMC_PART_BANK0_SIZE\n+\thex \"Bank0 image maximum size\"\n+\tdefault 0x10000000\n+\thelp\n+\t  Maximum size of the Bank0 boot image.\n+\t  Default: 0x10000000 (256MB)\n+\n+config NBX_MMC_PART_BANK1_OFFSET\n+\thex \"Bank1 image offset in eMMC\"\n+\tdefault 0x10804000\n+\thelp\n+\t  Byte offset in eMMC where the newer (Bank1) boot image starts.\n+\t  Default: 0x10804000 (264MB + 16KB)\n+\n+config NBX_MMC_PART_BANK1_SIZE\n+\thex \"Bank1 image maximum size\"\n+\tdefault 0x10000000\n+\thelp\n+\t  Maximum size of the Bank1 boot image.\n+\t  Default: 0x10000000 (256MB)\n+\n+endif\n+\n endif\ndiff --git a/board/freebox/nbx10g/Makefile b/board/freebox/nbx10g/Makefile\nindex bf83bdf63ee..a3b3d3a1fe3 100644\n--- a/board/freebox/nbx10g/Makefile\n+++ b/board/freebox/nbx10g/Makefile\n@@ -1,3 +1,4 @@\n # SPDX-License-Identifier: GPL-2.0+\n \n obj-y\t:= board.o\n+obj-$(CONFIG_CMD_NBX_EMMCBOOT)\t+= nbx_emmcboot.o\ndiff --git a/board/freebox/nbx10g/nbx_emmcboot.c b/board/freebox/nbx10g/nbx_emmcboot.c\nnew file mode 100644\nindex 00000000000..0bea96fadd9\n--- /dev/null\n+++ b/board/freebox/nbx10g/nbx_emmcboot.c\n@@ -0,0 +1,357 @@\n+// SPDX-License-Identifier: GPL-2.0+\n+/*\n+ * Nodebox 10G dual-bank eMMC boot command with automatic fallback\n+ *\n+ * Copyright (C) 2026 Free Mobile, Freebox\n+ *\n+ * This implements a dual-bank boot system with automatic fallback:\n+ * - Bank0: Stable/fallback boot image\n+ * - Bank1: Newer/test boot image\n+ *\n+ * The boot order depends on the reboot tracking counter (nrboot):\n+ * - If healthy: try Bank1 first, then Bank0\n+ * - If degraded (>= 4 failures): try Bank0 first, then Bank1\n+ */\n+\n+#include <command.h>\n+#include <env.h>\n+#include <mmc.h>\n+#include <malloc.h>\n+#include <memalign.h>\n+#include <vsprintf.h>\n+#include <u-boot/crc.h>\n+#include <u-boot/schedule.h>\n+#include <asm/byteorder.h>\n+#include <linux/errno.h>\n+#include \"nbx_imagetag.h\"\n+#include \"nbx_nrboot.h\"\n+\n+/* Partition offsets defined in Kconfig (CONFIG_NBX_MMC_PART_*) */\n+\n+/* Image Tag Functions */\n+\n+static int mvebu_imagetag_check(struct mvebu_image_tag *tag,\n+\t\t\t\tunsigned long maxsize, const char *name)\n+{\n+\tif (be32_to_cpu(tag->magic) != MVEBU_IMAGE_TAG_MAGIC) {\n+\t\tif (name)\n+\t\t\tprintf(\"%s: invalid TAG magic: %.8x\\n\", name,\n+\t\t\t       be32_to_cpu(tag->magic));\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (be32_to_cpu(tag->version) != MVEBU_IMAGE_TAG_VERSION) {\n+\t\tif (name)\n+\t\t\tprintf(\"%s: invalid TAG version: %.8x\\n\", name,\n+\t\t\t       be32_to_cpu(tag->version));\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (be32_to_cpu(tag->total_size) < sizeof(*tag)) {\n+\t\tif (name)\n+\t\t\tprintf(\"%s: tag size is too small!\\n\", name);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (be32_to_cpu(tag->total_size) > maxsize) {\n+\t\tif (name)\n+\t\t\tprintf(\"%s: tag size is too big!\\n\", name);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (be32_to_cpu(tag->device_tree_offset) < sizeof(*tag) ||\n+\t    be32_to_cpu(tag->device_tree_offset) +\n+\t    be32_to_cpu(tag->device_tree_size) > maxsize) {\n+\t\tif (name)\n+\t\t\tprintf(\"%s: bogus device tree offset/size!\\n\", name);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (be32_to_cpu(tag->kernel_offset) < sizeof(*tag) ||\n+\t    be32_to_cpu(tag->kernel_offset) +\n+\t    be32_to_cpu(tag->kernel_size) > maxsize) {\n+\t\tif (name)\n+\t\t\tprintf(\"%s: bogus kernel offset/size!\\n\", name);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (be32_to_cpu(tag->rootfs_offset) < sizeof(*tag) ||\n+\t    be32_to_cpu(tag->rootfs_offset) +\n+\t    be32_to_cpu(tag->rootfs_size) > maxsize) {\n+\t\tif (name)\n+\t\t\tprintf(\"%s: bogus rootfs offset/size!\\n\", name);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (name) {\n+\t\t/*\n+\t\t * Ensure null-termination within the 32-byte fields\n+\t\t * before printing to avoid displaying garbage.\n+\t\t */\n+\t\ttag->image_name[sizeof(tag->image_name) - 1] = '\\0';\n+\t\ttag->build_date[sizeof(tag->build_date) - 1] = '\\0';\n+\t\ttag->build_user[sizeof(tag->build_user) - 1] = '\\0';\n+\n+\t\tprintf(\"%s: Found valid tag: %s / %s / %s\\n\", name,\n+\t\t       tag->image_name, tag->build_date, tag->build_user);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int mvebu_imagetag_crc(struct mvebu_image_tag *tag, const char *name)\n+{\n+\tu32 crc = ~0;\n+\n+\tcrc = crc32(crc, ((unsigned char *)tag) + 4,\n+\t\t    be32_to_cpu(tag->total_size) - 4);\n+\n+\tif (be32_to_cpu(tag->crc) != crc) {\n+\t\tif (name)\n+\t\t\tprintf(\"%s: invalid tag CRC!\\n\", name);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* NRBoot (Reboot Tracking) Functions */\n+\n+struct mvebu_nrboot {\n+\tu16 nrboot;\n+\tu16 nrsuccess;\n+};\n+\n+#define MVEBU_MAX_FAILURE\t4\n+\n+static int mvebu_count_bits(u16 val)\n+{\n+\tint i, found = 0;\n+\n+\tfor (i = 0; i < 16; i++) {\n+\t\tif (val & (1 << i))\n+\t\t\tfound++;\n+\t}\n+\treturn found;\n+}\n+\n+int mvebu_check_nrboot(struct mmc *mmc, unsigned long offset)\n+{\n+\tstruct blk_desc *bd = mmc_get_blk_desc(mmc);\n+\tstruct mvebu_nrboot *nr;\n+\tuint blk_start = ALIGN(offset, bd->blksz) / bd->blksz;\n+\tuint blk_cnt = ALIGN(sizeof(*nr), bd->blksz) / bd->blksz;\n+\tuint n;\n+\n+\tALLOC_CACHE_ALIGN_BUFFER(char, buf, blk_cnt * bd->blksz);\n+\tnr = (void *)buf;\n+\n+\tn = blk_dread(bd, blk_start, blk_cnt, buf);\n+\tif (n != blk_cnt)\n+\t\treturn 0;\n+\n+\tprintf(\" - nr.nrboot = %04x\\n\", nr->nrboot);\n+\tprintf(\" - nr.nrsuccess = %04x\\n\", nr->nrsuccess);\n+\n+\t/* Sanity check on values */\n+\tif (mvebu_count_bits(~nr->nrboot + 1) <= 1 &&\n+\t    mvebu_count_bits(~nr->nrsuccess + 1) <= 1) {\n+\t\tint boot, success;\n+\n+\t\tboot = 16 - mvebu_count_bits(nr->nrboot);\n+\t\tsuccess = 16 - mvebu_count_bits(nr->nrsuccess);\n+\n+\t\tprintf(\" - Nrboot: %d / Nrsuccess: %d\\n\", boot, success);\n+\n+\t\tif (boot == 16 || boot < success ||\n+\t\t    boot - success >= MVEBU_MAX_FAILURE) {\n+\t\t\tprintf(\" - Nrboot exceeded\\n\");\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\t/* Increment boot attempt counter */\n+\t\tboot++;\n+\t\tnr->nrboot = ~((1 << boot) - 1);\n+\n+\t\tprintf(\" - Setting Nrboot to %d\\n\", boot);\n+\n+\t\tn = blk_dwrite(bd, blk_start, blk_cnt, buf);\n+\t\tif (n != blk_cnt)\n+\t\t\treturn 0;\n+\n+\t\treturn 1;\n+\t}\n+\n+\tprintf(\" - Invalid NR values\\n\");\n+\n+\treturn 0;\n+}\n+\n+/* emmcboot Command */\n+\n+static void mvebu_try_emmcboot(struct mmc *mmc, unsigned long offset,\n+\t\t\t       unsigned long maxsize, const char *bank)\n+{\n+\tstruct blk_desc *bd = mmc_get_blk_desc(mmc);\n+\tstruct mvebu_image_tag *tag;\n+\tulong image_addr = 0;\n+\tulong fdt_addr = 0;\n+\tulong tag_addr;\n+\tuint tag_blk_start = ALIGN(offset, bd->blksz) / bd->blksz;\n+\tuint tag_blk_cnt = ALIGN(sizeof(*tag), bd->blksz) / bd->blksz;\n+\tuint n;\n+\n+\tALLOC_CACHE_ALIGN_BUFFER(char, tag_buf, tag_blk_cnt * bd->blksz);\n+\ttag = (void *)tag_buf;\n+\n+\tschedule();\n+\n+\tprintf(\"## Trying %s boot...\\n\", bank);\n+\n+\t/* Load tag header */\n+\tn = blk_dread(bd, tag_blk_start, tag_blk_cnt, tag_buf);\n+\tif (n != tag_blk_cnt) {\n+\t\tprintf(\"%s: failed to read tag header\\n\", bank);\n+\t\treturn;\n+\t}\n+\n+\tif (mvebu_imagetag_check(tag, maxsize, bank) != 0)\n+\t\treturn;\n+\n+\tif (tag->rootfs_size != 0) {\n+\t\tprintf(\"%s: rootfs in tag not supported\\n\", bank);\n+\t\treturn;\n+\t}\n+\n+\t/* Get image and device tree load addresses from environment */\n+\timage_addr = env_get_ulong(\"image_addr\", 16, 0);\n+\tif (!image_addr) {\n+\t\tputs(\"emmcboot needs image_addr\\n\");\n+\t\treturn;\n+\t}\n+\n+\tfdt_addr = env_get_ulong(\"fdt_addr\", 16, 0);\n+\tif (!fdt_addr) {\n+\t\tputs(\"emmcboot needs fdt_addr\\n\");\n+\t\treturn;\n+\t}\n+\n+\ttag_addr = image_addr;\n+\n+\t/* Load full image, temporarily reuse image_addr for this */\n+\t{\n+\t\tuint data_blk_start = ALIGN(offset, bd->blksz) / bd->blksz;\n+\t\tuint data_blk_cnt = ALIGN(mvebu_imagetag_total_size(tag),\n+\t\t\t\t\t  bd->blksz) / bd->blksz;\n+\n+\t\tn = blk_dread(bd, data_blk_start, data_blk_cnt, (void *)tag_addr);\n+\t\tif (n != data_blk_cnt) {\n+\t\t\tprintf(\"%s: failed to read full image\\n\", bank);\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tif (mvebu_imagetag_crc((void *)tag_addr, bank) != 0)\n+\t\t\treturn;\n+\t}\n+\n+\tschedule();\n+\n+\t/* Copy image and device tree to the right addresses */\n+\t/* We assume that image_addr + tag_size < fdt_addr */\n+\t{\n+\t\ttag = (void *)tag_addr;\n+\t\tmemcpy((void *)fdt_addr,\n+\t\t       ((void *)tag_addr) + mvebu_imagetag_device_tree_offset(tag),\n+\t\t       mvebu_imagetag_device_tree_size(tag));\n+\t\tmemmove((void *)image_addr,\n+\t\t\t((void *)tag_addr) + mvebu_imagetag_kernel_offset(tag),\n+\t\t\tmvebu_imagetag_kernel_size(tag));\n+\t}\n+\n+\tschedule();\n+\n+\t/* Set bootargs and boot */\n+\t{\n+\t\tchar bootargs[256];\n+\t\tchar *console_env;\n+\n+\t\tconsole_env = env_get(\"console\");\n+\t\tif (console_env)\n+\t\t\tsnprintf(bootargs, sizeof(bootargs), \"%s bank=%s\",\n+\t\t\t\t console_env, bank);\n+\t\telse\n+\t\t\tsnprintf(bootargs, sizeof(bootargs), \"bank=%s\", bank);\n+\n+\t\tenv_set(\"bootargs\", bootargs);\n+\n+\t\tprintf(\"## Booting kernel from %s...\\n\", bank);\n+\t\tprintf(\"   Image addr: 0x%lx\\n\", image_addr);\n+\t\tprintf(\"   FDT addr:   0x%lx\\n\", fdt_addr);\n+\n+\t\t/* Build and run booti command */\n+\t\t{\n+\t\t\tchar cmd[128];\n+\n+\t\t\tsnprintf(cmd, sizeof(cmd), \"booti 0x%lx - 0x%lx\",\n+\t\t\t\t image_addr, fdt_addr);\n+\t\t\trun_command(cmd, 0);\n+\t\t}\n+\t}\n+\n+\tprintf(\"## %s boot failed\\n\", bank);\n+}\n+\n+static int do_emmcboot(struct cmd_tbl *cmdtp, int flag, int argc,\n+\t\t       char *const argv[])\n+{\n+\tint dev;\n+\tstruct mmc *mmc;\n+\n+\tdev = 0;\n+\tif (argc >= 2)\n+\t\tdev = dectoul(argv[1], NULL);\n+\n+\tmmc = find_mmc_device(dev);\n+\tif (!mmc) {\n+\t\tprintf(\"No MMC device %d found\\n\", dev);\n+\t\treturn CMD_RET_FAILURE;\n+\t}\n+\n+\tif (mmc_init(mmc)) {\n+\t\tputs(\"MMC init failed\\n\");\n+\t\treturn CMD_RET_FAILURE;\n+\t}\n+\n+\t/* Switch to partition 0 (user data area) */\n+\tif (blk_select_hwpart_devnum(UCLASS_MMC, dev, 0)) {\n+\t\tputs(\"MMC partition switch failed\\n\");\n+\t\treturn CMD_RET_FAILURE;\n+\t}\n+\n+\tif (mvebu_check_nrboot(mmc, CONFIG_NBX_MMC_PART_NRBOOT_OFFSET)) {\n+\t\t/* System is healthy: try newer bank first */\n+\t\tmvebu_try_emmcboot(mmc, CONFIG_NBX_MMC_PART_BANK1_OFFSET,\n+\t\t\t\t   CONFIG_NBX_MMC_PART_BANK1_SIZE, \"bank1\");\n+\t\tmvebu_try_emmcboot(mmc, CONFIG_NBX_MMC_PART_BANK0_OFFSET,\n+\t\t\t\t   CONFIG_NBX_MMC_PART_BANK0_SIZE, \"bank0\");\n+\t} else {\n+\t\t/* System is degraded: use stable bank first */\n+\t\tmvebu_try_emmcboot(mmc, CONFIG_NBX_MMC_PART_BANK0_OFFSET,\n+\t\t\t\t   CONFIG_NBX_MMC_PART_BANK0_SIZE, \"bank0\");\n+\t\tmvebu_try_emmcboot(mmc, CONFIG_NBX_MMC_PART_BANK1_OFFSET,\n+\t\t\t\t   CONFIG_NBX_MMC_PART_BANK1_SIZE, \"bank1\");\n+\t}\n+\n+\tputs(\"emmcboot: all boot attempts failed\\n\");\n+\treturn CMD_RET_FAILURE;\n+}\n+\n+U_BOOT_CMD(\n+\temmcboot, 2, 0, do_emmcboot,\n+\t\"boot from MVEBU eMMC image banks\",\n+\t\"[dev]\\n\"\n+\t\"    - Boot from eMMC device <dev> (default 0)\\n\"\n+\t\"    - Requires image_addr and fdt_addr environment variables\\n\"\n+\t\"    - Uses dual-bank boot with automatic fallback\\n\"\n+\t\"    - Bank selection based on reboot tracking (nrboot)\"\n+);\ndiff --git a/board/freebox/nbx10g/nbx_imagetag.h b/board/freebox/nbx10g/nbx_imagetag.h\nnew file mode 100644\nindex 00000000000..999293dd58a\n--- /dev/null\n+++ b/board/freebox/nbx10g/nbx_imagetag.h\n@@ -0,0 +1,78 @@\n+/* SPDX-License-Identifier: GPL-2.0+ */\n+/*\n+ * MVEBU Image Tag header\n+ *\n+ * Copyright (C) 2026 Free Mobile, Freebox\n+ */\n+\n+#ifndef __MVEBU_IMAGETAG_H\n+#define __MVEBU_IMAGETAG_H\n+\n+#include <linux/types.h>\n+\n+#define MVEBU_IMAGE_TAG_MAGIC\t0x8d7c90bc\n+#define MVEBU_IMAGE_TAG_VERSION\t1\n+\n+/**\n+ * struct mvebu_image_tag - MVEBU boot image tag structure\n+ *\n+ * All multi-byte fields are stored in big-endian format.\n+ */\n+struct mvebu_image_tag {\n+\tu32 crc;\t\t\t/* CRC32-LE checksum (from offset 4) */\n+\tu32 magic;\t\t\t/* Magic: 0x8d7c90bc */\n+\tu32 version;\t\t\t/* Version: 1 */\n+\tu32 total_size;\t\t\t/* Total image size including tag */\n+\tu32 flags;\t\t\t/* Feature flags (reserved) */\n+\n+\tu32 device_tree_offset;\t\t/* Offset from tag start to DTB */\n+\tu32 device_tree_size;\t\t/* DTB size in bytes */\n+\n+\tu32 kernel_offset;\t\t/* Offset from tag start to kernel */\n+\tu32 kernel_size;\t\t/* Kernel size in bytes */\n+\n+\tu32 rootfs_offset;\t\t/* Offset from tag start to rootfs */\n+\tu32 rootfs_size;\t\t/* Rootfs size (must be 0) */\n+\n+\tchar image_name[32];\t\t/* Image name (null-terminated) */\n+\tchar build_user[32];\t\t/* Build user info */\n+\tchar build_date[32];\t\t/* Build date info */\n+};\n+\n+/* Accessor functions for big-endian fields */\n+static inline u32 mvebu_imagetag_device_tree_offset(struct mvebu_image_tag *tag)\n+{\n+\treturn be32_to_cpu(tag->device_tree_offset);\n+}\n+\n+static inline u32 mvebu_imagetag_device_tree_size(struct mvebu_image_tag *tag)\n+{\n+\treturn be32_to_cpu(tag->device_tree_size);\n+}\n+\n+static inline u32 mvebu_imagetag_kernel_offset(struct mvebu_image_tag *tag)\n+{\n+\treturn be32_to_cpu(tag->kernel_offset);\n+}\n+\n+static inline u32 mvebu_imagetag_kernel_size(struct mvebu_image_tag *tag)\n+{\n+\treturn be32_to_cpu(tag->kernel_size);\n+}\n+\n+static inline u32 mvebu_imagetag_rootfs_offset(struct mvebu_image_tag *tag)\n+{\n+\treturn be32_to_cpu(tag->rootfs_offset);\n+}\n+\n+static inline u32 mvebu_imagetag_rootfs_size(struct mvebu_image_tag *tag)\n+{\n+\treturn be32_to_cpu(tag->rootfs_size);\n+}\n+\n+static inline u32 mvebu_imagetag_total_size(struct mvebu_image_tag *tag)\n+{\n+\treturn be32_to_cpu(tag->total_size);\n+}\n+\n+#endif /* __MVEBU_IMAGETAG_H */\ndiff --git a/board/freebox/nbx10g/nbx_nrboot.h b/board/freebox/nbx10g/nbx_nrboot.h\nnew file mode 100644\nindex 00000000000..91c9fb2e57b\n--- /dev/null\n+++ b/board/freebox/nbx10g/nbx_nrboot.h\n@@ -0,0 +1,34 @@\n+/* SPDX-License-Identifier: GPL-2.0+ */\n+/*\n+ * MVEBU NRBoot (Number of Reboots) tracking header\n+ *\n+ * Copyright (C) 2026 Free Mobile, Freebox\n+ */\n+\n+#ifndef __MVEBU_NRBOOT_H\n+#define __MVEBU_NRBOOT_H\n+\n+#include <mmc.h>\n+\n+/**\n+ * mvebu_check_nrboot() - Check and update reboot tracking counter\n+ * @mmc: MMC device\n+ * @offset: Byte offset in MMC where nrboot data is stored\n+ *\n+ * This function reads the reboot tracking counter, checks if we've\n+ * exceeded the maximum number of failed boots (4), and updates the\n+ * counter for the current boot attempt.\n+ *\n+ * The counter uses a bit-field encoding:\n+ * - nrboot: Running count of boot attempts\n+ * - nrsuccess: Count of successful boots\n+ *\n+ * If boot - success >= MAX_FAILURE (4), the system is considered\n+ * degraded and should use the fallback boot bank.\n+ *\n+ * Return: 1 if system is healthy (try newer bank first),\n+ *         0 if system is degraded (use stable bank first)\n+ */\n+int mvebu_check_nrboot(struct mmc *mmc, unsigned long offset);\n+\n+#endif /* __MVEBU_NRBOOT_H */\n",
    "prefixes": [
        "v2",
        "3/5"
    ]
}