From 5d4a87a327f95b0a135a716e9eb8be0925b5ba31 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sat, 7 Nov 2026 16:58:13 +0000 Subject: [PATCH 1/7] openipc: the common OpenIPC layer, as openipc-ingenic carries it common/openipc.c, common/Kconfig and common/Makefile are openipc-ingenic's, which keep openipc-hisilicon's in step and take the SPI NAND path by OPENIPC_SPI_NAND: the split NOR layout (CONFIG_OPENIPC_NOR_SPLIT), the recovery from a FAT partition or a whole-disk FAT, the NAND scan of the UBIFS rootfs and its /boot kernel, the boot-limit failsafe and the saved environment's migrations. OPENIPC_SPI_NAND takes CONFIG_NAND_MS_SPINAND too, and the UBI marker is read from the first good eraseblock, which a corrected read (-EUCLEAN) still gives. CONFIG_OPENIPC_NAND_KERNEL_VOLUME, the kernel in a UBI volume of its own, goes. --- common/Kconfig | 18 +- common/Makefile | 2 +- common/openipc.c | 666 ++++++++++++++++++++++++++++++++++++----------- 3 files changed, 526 insertions(+), 160 deletions(-) diff --git a/common/Kconfig b/common/Kconfig index f2855a3b3264..bb5f28e56645 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -1014,14 +1014,6 @@ config OPENIPC (common/openipc.c), driven from the board's misc_init_r(), with the default environment in include/configs/openipc-common.h. -config OPENIPC_NAND_KERNEL_VOLUME - bool "NAND firmware keeps the kernel in its own UBI volume" - depends on OPENIPC - help - The layout of OpenIPC's SigmaStar NAND images: UBI volumes kernel - and rootfs, a squashfs. Without it, the kernel is a file in the - UBIFS rootfs. - config PRODUCT_SOC string "Product SoC name" default SYS_CONFIG_NAME @@ -1035,3 +1027,13 @@ config PRODUCT_SOCMODEL help Optional board/binning model exposed as 'socmodel=' in the default environment. + +config OPENIPC_NOR_SPLIT + bool "OpenIPC: recognise the split NOR layout (uImage + squashfs in one partition)" + help + A uImage at the NOR kernel offset with the root squashfs at the next + 64 KiB block after it is taken for the split layout: one "firmware" + partition the kernel divides at boot. The + kernel is told nothing about it: the fixed-slot mtdparts and root= + of a saved environment are dropped, and bootm checks the uImage CRC. + Only for boards whose kernel knows the layout. diff --git a/common/Makefile b/common/Makefile index d725fc328029..64c8c2671d08 100644 --- a/common/Makefile +++ b/common/Makefile @@ -113,4 +113,4 @@ obj-$(CONFIG_SCP03) += scp03.o obj-$(CONFIG_QFW) += qfw.o -obj-$(CONFIG_OPENIPC) += openipc.o +obj-$(CONFIG_$(SPL_TPL_)OPENIPC) += openipc.o diff --git a/common/openipc.c b/common/openipc.c index fc09372961c3..84a3a3215612 100644 --- a/common/openipc.c +++ b/common/openipc.c @@ -11,16 +11,24 @@ #include #include #include +#include +#include #ifdef CONFIG_USB #include #endif #ifdef CONFIG_MMC #include #endif +/* The SPI-NAND paths: the HiSilicon FMC's, the Ingenic SFC's, or the + * SigmaStar FSP-QSPI's */ +#if defined(CONFIG_FMC_SPI_NAND) || defined(CONFIG_JZ_SFC_NAND) || \ + defined(CONFIG_NAND_MS_SPINAND) +#define OPENIPC_SPI_NAND +#endif #ifdef CONFIG_ENV_IS_IN_NAND #include #endif -#ifdef CONFIG_ENV_IS_IN_NAND +#ifdef OPENIPC_SPI_NAND #include #include #include @@ -29,32 +37,31 @@ #include #endif -#define SCAN_FROM CONFIG_ENV_OFFSET + CONFIG_ENV_SIZE +#define SCAN_FROM (CONFIG_ENV_OFFSET + CONFIG_ENV_SIZE) #define SCAN_STEP 0x10000 +/* How far to look for the kernel, and for the rootfs past the kernel's end */ #define SCAN_LIMIT 0x400000 #define UBI_MAGIC_ASCII "UBI#" #define FDT_MAGIC 0xd00dfeed -#define SQSH_SIZE_OFF 0x28 /* bytes_used */ +#define SQSH_SIZE_OFF 0x28 /* bytes_used; 0x58 is the export table */ #define SQSH_MAGIC_BE 0x73717368 #define SQSH_MAGIC_LE 0x68737173 -static int check_squashfs(void *buf) { +static int __maybe_unused check_squashfs(void *buf) { uint32_t magic = get_unaligned_be32(buf); if (magic == SQSH_MAGIC_BE || magic == SQSH_MAGIC_LE) return 1; return 0; } -#ifdef CONFIG_ENV_IS_IN_NAND -#ifndef CONFIG_OPENIPC_NAND_KERNEL_VOLUME +#ifdef OPENIPC_SPI_NAND #define UBIFS_SB_MAGIC 0x06101831 static int check_ubifs(void *buf) { return get_unaligned_le32(buf) == UBIFS_SB_MAGIC; } -#endif /* Check for the UBI marker without attaching: attaching an empty MTD * would create a fresh UBI on flash. UBI writes an EC header to every @@ -86,7 +93,7 @@ static int ubi_marker_present(void) { } #endif -static int check_kernel(void *buf) { +static int __maybe_unused check_kernel(void *buf) { struct image_header *hdr = (struct image_header *)buf; if (image_check_magic(hdr)) { return 1; @@ -101,7 +108,7 @@ static int check_kernel(void *buf) { return 0; } -#ifndef CONFIG_ENV_IS_IN_NAND +#ifndef OPENIPC_SPI_NAND static uint64_t get_squashfs_size(void *buf) { uint32_t magic = get_unaligned_be32(buf); @@ -128,10 +135,19 @@ static int check_rootfs(void *buf, uint64_t *rootfs_size) { return 0; } +/* The kernel image's size from its header, or 0 when it has none */ +static ulong kernel_image_size(void *buf) { + if (image_check_magic((struct image_header *)buf)) + return image_get_image_size((struct image_header *)buf); + if (fdt_magic(buf) == FDT_MAGIC && fdt_check_header(buf) == 0) + return fdt_totalsize(buf); + return 0; +} + static int scan_spi_device(struct spi_flash *flash, ulong *kernel_off, ulong *rootfs_off, uint64_t *kernel_size, uint64_t *rootfs_size) { u8 *buf; - ulong offset; + ulong offset, limit; ulong erase_size; int ret; @@ -145,9 +161,10 @@ static int scan_spi_device(struct spi_flash *flash, ulong *kernel_off, ulong *ro erase_size = 0x10000; #endif - printf("Scanning 0x%x-0x%x ...\n", SCAN_FROM, SCAN_LIMIT); + limit = min_t(ulong, SCAN_FROM + SCAN_LIMIT, flash->size); + printf("Scanning 0x%x-0x%lx ...\n", SCAN_FROM, limit); - for (offset = SCAN_FROM; offset < SCAN_LIMIT && offset < flash->size; offset += SCAN_STEP) { + for (offset = SCAN_FROM; offset < limit; offset += SCAN_STEP) { ret = spi_flash_read(flash, offset, SCAN_STEP, buf); if (ret != 0) continue; @@ -166,8 +183,20 @@ static int scan_spi_device(struct spi_flash *flash, ulong *kernel_off, ulong *ro } if (!*kernel_off && check_kernel(buf)) { + ulong end = offset + ALIGN(kernel_image_size(buf), SCAN_STEP); + *kernel_off = offset; printf("Found Kernel at 0x%08lx\n", offset); + /* + * The rootfs is past the kernel: skip the image, whose contents could + * pass for one, and look as far past its end as before it. A large + * kernel (arm64) ends beyond the first window. + */ + if (end > offset) { + limit = min_t(ulong, max_t(ulong, limit, end + SCAN_LIMIT), flash->size); + offset = end - SCAN_STEP; + continue; + } } if (!*rootfs_off && check_rootfs(buf, rootfs_size)) { @@ -193,39 +222,59 @@ static int scan_spi_device(struct spi_flash *flash, ulong *kernel_off, ulong *ro return 0; } -#endif /* !CONFIG_ENV_IS_IN_NAND */ +#endif /* !OPENIPC_SPI_NAND */ #if (defined(CONFIG_MMC) || defined(CONFIG_USB_STORAGE)) && defined(CONFIG_FS_FAT) #define RECOVERY_KERNEL 0 #define RECOVERY_ROOTFS 1 -/* The medium recovery_mount() found. Every fs_*() call closes the - * filesystem when it returns, so each one has to select it again. */ static const char *recovery_if, *recovery_part; -static int recovery_select(void) { - return fs_set_blk_dev(recovery_if, recovery_part, FS_TYPE_FAT); -} - +/* The first partition, else the whole medium (a card formatted without a + * partition table); the one that worked is remembered for the calls after. */ static int recovery_mount(const char *ifname) { - static const char *const parts[] = { "0:1", "0" }; - int i; - - for (i = 0; i < ARRAY_SIZE(parts); i++) { - if (!fs_set_blk_dev(ifname, parts[i], FS_TYPE_FAT)) { - recovery_if = ifname; - recovery_part = parts[i]; - return 0; - } + if (recovery_if != ifname) { + recovery_if = ifname; + recovery_part = NULL; + } + if (recovery_part) + return fs_set_blk_dev(ifname, recovery_part, FS_TYPE_FAT) ? -1 : 0; + if (!fs_set_blk_dev(ifname, "0:1", FS_TYPE_FAT)) { + recovery_part = "0:1"; + return 0; + } + if (!fs_set_blk_dev(ifname, "0", FS_TYPE_FAT)) { + recovery_part = "0"; + return 0; } return -1; } -#ifdef CONFIG_ENV_IS_IN_NAND -#ifndef OPENIPC_KERNEL_VOLUME_SIZE -#define OPENIPC_KERNEL_VOLUME_SIZE "0x300000" -#endif +/* + * Every fs_* call ends in fs_close(), which forgets the device: the next one + * would read through no filesystem at all ("failed to read" right after + * fs_exists found the file). So each call selects the medium again. + */ +static int rfs_exists(const char *name) { + return !recovery_mount(recovery_if) && fs_exists(name); +} + +static int rfs_size(const char *name, loff_t *size) { + return recovery_mount(recovery_if) ? -1 : fs_size(name, size); +} + +static int rfs_read(const char *name, ulong addr, loff_t offset, loff_t len, + loff_t *actread) { + return recovery_mount(recovery_if) ? -1 : fs_read(name, addr, offset, len, actread); +} +/* What a file read to CONFIG_SYS_LOAD_ADDR may take: the RAM below the + * stack and the relocated U-Boot, with room */ +static ulong recovery_ram_room(void) { + return gd->start_addr_sp - SZ_1M - CONFIG_SYS_LOAD_ADDR; +} + +#ifdef OPENIPC_SPI_NAND static int recovery_ensure_volume(const char *name, const char *size) { struct ubi_volume_desc *desc; char cmd[64]; @@ -280,14 +329,14 @@ static int recovery_write(const char *name, int type) { ulong size; int ret; - if (recovery_select() || !fs_exists(name)) + if (!rfs_exists(name)) return 0; if (type == RECOVERY_ROOTFS) { /* Raw ubifs image, written into the rootfs volume */ loff_t fsize; - ret = recovery_select() ? -1 : fs_size(name, &fsize); + ret = rfs_size(name, &fsize); if (ret || fsize == 0) { printf("Recovery: failed to stat %s\n", name); return -1; @@ -295,7 +344,7 @@ static int recovery_write(const char *name, int type) { size = fsize; } else { /* Kernel: uImage or FIT, written to the kernel volume */ - ret = recovery_select() ? -1 : fs_read(name, (ulong)&hdr, 0, sizeof(hdr), &actread); + ret = rfs_read(name, (ulong)&hdr, 0, sizeof(hdr), &actread); if (ret || actread != sizeof(hdr)) { printf("Recovery: failed to read %s\n", name); return -1; @@ -311,7 +360,7 @@ static int recovery_write(const char *name, int type) { } } - ret = recovery_select() ? -1 : fs_read(name, CONFIG_SYS_LOAD_ADDR, 0, size, &actread); + ret = rfs_read(name, CONFIG_SYS_LOAD_ADDR, 0, size, &actread); if (ret || actread != size) { printf("Recovery: failed to read %s\n", name); return -1; @@ -346,7 +395,7 @@ static int recovery_write(const char *name, int type) { printf("Recovery: wrote %s (%lu bytes) to rootfs volume\n", name, size); } else { - ret = recovery_recreate_volume("kernel", OPENIPC_KERNEL_VOLUME_SIZE, false); + ret = recovery_recreate_volume("kernel", "0x300000", false); if (ret) { printf("Recovery: cannot recreate kernel volume (%d)\n", ret); return -1; @@ -366,86 +415,132 @@ static int recovery_write(const char *name, int type) { #else static struct spi_flash *recovery_flash; -/* Whether the flash at addr already holds these bytes */ -static int recovery_same(ulong addr, const u8 *data, ulong size) { +static int recovery_flash_holds(ulong addr, const void *data, ulong len) { const ulong chunk = 0x10000; u8 *buf = malloc(chunk); ulong off, n; - int same = buf != NULL; + int same = 1; - for (off = 0; same && off < size; off += n) { - n = min(chunk, size - off); + if (!buf) + return 0; + for (off = 0; same && off < len; off += n) { + n = min(len - off, chunk); same = !spi_flash_read(recovery_flash, addr + off, n, buf) && - !memcmp(buf, data + off, n); + !memcmp(buf, (const u8 *)data + off, n); } free(buf); return same; } -static ulong recovery_env(const char *name, ulong def) { - const char *str = env_get(name); - - return str ? hextoul(str, NULL) : def; -} - /* - * A uImage on the card is flashed where its header's load address says, - * without the header (mkimage -a ); a FIT goes to kernaddr. - * A card left in reflashes nothing: an image the flash already holds is - * skipped. + * One recovery image: a uImage wrapper whose load address is where in the + * flash its payload goes. Whatever the payload is -- a kernel, a rootfs, a + * whole firmware, U-Boot itself -- it is written there; the wrapper header + * never is. Returns 1 when it wrote, 0 when it skipped, -1 on a failed write. */ -static int recovery_write(const char *name, int type) { +static int recovery_write_img(const char *name, loff_t size) { struct image_header hdr; loff_t actread; - ulong addr, offset = 0, size, erase_len; - int ret; + ulong addr, len, sect = recovery_flash->sector_size; - if (recovery_select() || !fs_exists(name)) + if (size <= (loff_t)sizeof(hdr) || + rfs_read(name, (ulong)&hdr, 0, sizeof(hdr), &actread) || + actread != sizeof(hdr) || !image_check_magic(&hdr) || + !image_check_hcrc(&hdr)) { + printf("Recovery: %s is not a uImage, skipped\n", name); return 0; - - ret = recovery_select() ? -1 : fs_read(name, (ulong)&hdr, 0, sizeof(hdr), &actread); - if (ret || actread != sizeof(hdr)) { - printf("Recovery: failed to read %s\n", name); - return -1; } - - if (image_check_magic(&hdr)) { - addr = image_get_load(&hdr); - offset = sizeof(hdr); - size = image_get_data_size(&hdr); - } else if (type == RECOVERY_KERNEL && fdt_magic(&hdr) == FDT_MAGIC && - fdt_check_header(&hdr) == 0) { - addr = recovery_env("kernaddr", OPENIPC_KERNADDR); - size = fdt_totalsize(&hdr); - } else { - printf("Recovery: no valid uImage/FIT header in %s\n", name); - return -1; + addr = image_get_load(&hdr); + len = image_get_data_size(&hdr); + /* a read of length 0 would be a read of the whole file */ + if (!len) { + printf("Recovery: %s has no data, skipped\n", name); + return 0; } - - ret = recovery_select() ? -1 : fs_read(name, CONFIG_SYS_LOAD_ADDR, offset, size, &actread); - if (ret || actread != size) { - printf("Recovery: failed to read %s\n", name); - return -1; + /* in 64 bits: a size near 4 GiB would wrap a 32-bit sum */ + if ((loff_t)len > size - (loff_t)sizeof(hdr)) { + printf("Recovery: %s is shorter than its header says, skipped\n", name); + return 0; } - - if (recovery_same(addr, (u8 *)CONFIG_SYS_LOAD_ADDR, size)) { - printf("Recovery: %s already in flash at 0x%lx\n", name, addr); - return 1; + if (addr >= recovery_flash->size || len > recovery_flash->size - addr) { + printf("Recovery: %s (0x%lx + %lu bytes) is outside the flash, skipped\n", + name, addr, len); + return 0; + } + if (addr % sect) { + printf("Recovery: %s: 0x%lx is not on a %lu-byte erase boundary, skipped\n", + name, addr, sect); + return 0; + } + if (len > recovery_ram_room()) { + printf("Recovery: %s (%lu bytes) does not fit the free RAM, skipped\n", + name, len); + return 0; + } + if (rfs_read(name, CONFIG_SYS_LOAD_ADDR, sizeof(hdr), len, &actread) || + actread != len) { + printf("Recovery: failed to read %s, skipped\n", name); + return 0; + } + if (crc32(0, (const u8 *)CONFIG_SYS_LOAD_ADDR, len) != image_get_dcrc(&hdr)) { + printf("Recovery: %s: the data CRC does not match, skipped\n", name); + return 0; + } + /* A card left in the slot: nothing to do, the system boots */ + if (recovery_flash_holds(addr, (void *)CONFIG_SYS_LOAD_ADDR, len)) { + printf("Recovery: %s is already on the flash at 0x%lx\n", name, addr); + return 0; } - erase_len = (size + recovery_flash->sector_size - 1) & - ~(recovery_flash->sector_size - 1); - if (spi_flash_erase(recovery_flash, addr, erase_len) || - spi_flash_write(recovery_flash, addr, size, - (void *)CONFIG_SYS_LOAD_ADDR)) { - printf("Recovery: failed to write %s to flash at 0x%lx\n", name, addr); + printf("Recovery: writing %s (%lu bytes) at 0x%lx\n", name, len, addr); + if (spi_flash_erase(recovery_flash, addr, ALIGN(len, sect)) || + spi_flash_write(recovery_flash, addr, len, (void *)CONFIG_SYS_LOAD_ADDR)) { + printf("Recovery: writing %s failed\n", name); return -1; } + printf("Recovery: wrote %s\n", name); + return 1; +} - printf("Recovery: wrote %s (%lu bytes) to flash at 0x%lx\n", name, size, - addr); +struct recovery_file { + char name[256]; + loff_t size; +}; + +/* Every *.img in the root of the medium, in the order the directory lists + * them. The names are collected first: reading a file closes the + * filesystem under an open directory stream. */ +static int recovery_images(void) { + struct recovery_file *files = NULL, *more; + struct fs_dir_stream *dirs; + struct fs_dirent *d; + int n = 0, i, written = 0; + size_t l; + + if (recovery_mount(recovery_if)) + return 0; + dirs = fs_opendir("/"); + if (!dirs) + return 0; + while ((d = fs_readdir(dirs))) { + l = strlen(d->name); + if (d->type != FS_DT_REG || l < 5 || l >= sizeof(files->name) || + strcasecmp(d->name + l - 4, ".img")) + continue; + more = realloc(files, (n + 1) * sizeof(*files)); + if (!more) + break; + files = more; + strcpy(files[n].name, d->name); + files[n++].size = d->size; + } + fs_closedir(dirs); - return 1; + for (i = 0; i < n; i++) + if (recovery_write_img(files[i].name, files[i].size) > 0) + written++; + free(files); + return written; } #endif @@ -456,16 +551,20 @@ static int recovery_run_script(const char *name) { int ret; char cmd[32]; - if (recovery_select() || !fs_exists(name)) + if (!rfs_exists(name)) return 0; - ret = recovery_select() ? -1 : fs_size(name, &size); + ret = rfs_size(name, &size); if (ret || size == 0) { printf("Recovery: failed to stat %s\n", name); return 1; } + if ((u64)size > recovery_ram_room()) { + printf("Recovery: %s (%lld bytes) does not fit the free RAM\n", name, size); + return 1; + } - ret = recovery_select() ? -1 : fs_read(name, CONFIG_SYS_LOAD_ADDR, 0, size, &actread); + ret = rfs_read(name, CONFIG_SYS_LOAD_ADDR, 0, size, &actread); if (ret || actread != size) { printf("Recovery: failed to read %s\n", name); return 1; @@ -480,53 +579,37 @@ static int recovery_run_script(const char *name) { ret = run_command_list((char *)CONFIG_SYS_LOAD_ADDR, size, 0); } - printf("Recovery: executed %s (%lld bytes)\n", name, size); + /* The script took recovery over: its result is reported, not retried + * with the images */ + printf("Recovery: executed %s (%lld bytes)%s\n", name, size, + ret ? ", it failed" : ""); return 1; } static int recovery_try(void) { +#ifdef OPENIPC_SPI_NAND char name[64]; - int ret, found = 0; +#endif - /* script.txt takes precedence over the kernel/rootfs search */ + /* script.txt takes precedence over the image search */ if (recovery_run_script("script.txt")) return 1; -#if !defined(CONFIG_ENV_IS_IN_NAND) || \ - defined(CONFIG_OPENIPC_NAND_KERNEL_VOLUME) - /* Kernel: uImage preferred, FIT fallback */ - sprintf(name, "uImage.%s.img", CONFIG_PRODUCT_SOC); - ret = recovery_write(name, RECOVERY_KERNEL); - if (ret > 0) { - found = 1; - } else if (ret == 0) { - sprintf(name, "fitImage.%s.img", CONFIG_PRODUCT_SOC); - ret = recovery_write(name, RECOVERY_KERNEL); - if (ret > 0) - found = 1; - } -#endif - +#ifdef OPENIPC_SPI_NAND /* Rootfs. On NAND it carries the kernel (/boot), its volume is sized * to the image and rootfs_data takes the rest. */ -#if defined(CONFIG_ENV_IS_IN_NAND) && \ - !defined(CONFIG_OPENIPC_NAND_KERNEL_VOLUME) sprintf(name, "rootfs.ubifs.%s.img", CONFIG_PRODUCT_SOC); + return recovery_write(name, RECOVERY_ROOTFS) > 0; #else - sprintf(name, "rootfs.squashfs.%s.img", CONFIG_PRODUCT_SOC); + return recovery_images() > 0; #endif - ret = recovery_write(name, RECOVERY_ROOTFS); - if (ret > 0) - found = 1; - - return found; } int firmware_recovery(void) { int found = 0; -#ifndef CONFIG_ENV_IS_IN_NAND +#ifndef OPENIPC_SPI_NAND recovery_flash = spi_flash_probe(CONFIG_SF_DEFAULT_BUS, CONFIG_SF_DEFAULT_CS, CONFIG_SF_DEFAULT_SPEED, CONFIG_SF_DEFAULT_MODE); @@ -549,7 +632,7 @@ int firmware_recovery(void) { #endif fs_close(); -#ifndef CONFIG_ENV_IS_IN_NAND +#ifndef OPENIPC_SPI_NAND spi_flash_free(recovery_flash); #endif @@ -561,8 +644,129 @@ int firmware_recovery(void) { } #endif +#if !defined(OPENIPC_SPI_NAND) && defined(CONFIG_OPENIPC_NOR_SPLIT) +/* + * The split layout needs nothing from bootargs: the kernel's device tree has + * the partitions and the kernel takes the root its firmware split finds. A + * saved environment still spells out the fixed slots (mtdparts=..., root= + * /dev/mtdblock3), and an older build of this bootloader added + * openipc_nor_fit=1; drop those, in RAM only, so the command line says only + * what is true. + */ +static void nor_split_bootargs(void) +{ + static const char * const drop[] = { "mtdparts=", "root=", "openipc_nor_fit=" }; + const char *cur = env_get("bootargs"); + char buf[1024], *out = buf; + const char *p; + + if (!cur || strlen(cur) >= sizeof(buf)) + return; + for (p = cur; *p;) { + const char *end = strchr(p, ' '); + size_t len = end ? (size_t)(end - p) : strlen(p); + int keep = len > 0; + int i; + + for (i = 0; keep && i < ARRAY_SIZE(drop); i++) + if (!strncmp(p, drop[i], strlen(drop[i]))) + keep = 0; + if (keep) { + if (out != buf) + *out++ = ' '; + memcpy(out, p, len); + out += len; + } + p += len; + while (*p == ' ') + p++; + } + *out = '\0'; + if (strcmp(buf, cur)) + env_set("bootargs", buf); +} + +/* + * The split layout: a uImage at the kernel offset and the root squashfs at the + * next 64 KiB block after it, in one "firmware" partition the kernel divides + * at boot. The fixed-slot layout also starts with a uImage, but its squashfs + * sits at the slot boundary. + */ +static int nor_split_layout(struct spi_flash *flash, ulong kernel_off, ulong rootfs_off) +{ + image_header_t hdr; + + /* + * A squashfs at a fixed rootfs slot is fixed slots, even when the uImage + * before it ends in the kernel slot's last block and so looks split: a + * kernel from before the split layout needs its mtdparts and root=. A + * split image of that size loses nothing, as the kernel finds the split + * itself and ignores both. + */ + if (rootfs_off - kernel_off == 0x200000 || rootfs_off - kernel_off == 0x300000) + return 0; + if (!kernel_off || !rootfs_off || + spi_flash_read(flash, kernel_off, sizeof(hdr), &hdr) || + !image_check_magic(&hdr) || !image_check_hcrc(&hdr)) + return 0; + return rootfs_off == kernel_off + ALIGN(image_get_image_size(&hdr), 0x10000); +} + +/* + * Whether the split layout's kernel is whole: its header and its data CRC, + * read back from the flash. The scan clears bootfail for a split image, and + * a kernel with a sound header but a damaged payload would then fail bootm + * with nothing asking for recovery -- the boot-limit fallback retries the + * same image. Checked here, it is handed to recovery like a missing one. + * Without the memory to check, the kernel is taken as it is; bootm still + * checks it. + */ +static int nor_split_kernel_ok(struct spi_flash *flash, ulong kernel_off) +{ + const ulong chunk = 0x10000; + image_header_t hdr; + ulong len, off, n; + u32 crc = 0; + u8 *buf; + int ok = 1; + + if (spi_flash_read(flash, kernel_off, sizeof(hdr), &hdr) || + !image_check_hcrc(&hdr)) + return 0; + len = image_get_data_size(&hdr); + if (kernel_off + sizeof(hdr) > flash->size || + len > flash->size - kernel_off - sizeof(hdr)) + return 0; + buf = malloc_cache_aligned(chunk); + if (!buf) + return 1; + for (off = 0; ok && off < len; off += n) { + n = min(len - off, chunk); + if (spi_flash_read(flash, kernel_off + sizeof(hdr) + off, n, buf)) + ok = 0; + else + crc = crc32(crc, buf, n); + } + free(buf); + return ok && crc == image_get_dcrc(&hdr); +} + +/* + * A uImage whose header fails its CRC: bootm refuses it whatever verify + * says, so it boots nothing in either layout. Its size field cannot be + * trusted either, which is why nor_split_layout does not take it as split. + */ +static int nor_split_header_bad(struct spi_flash *flash, ulong kernel_off) +{ + image_header_t hdr; + + return kernel_off && !spi_flash_read(flash, kernel_off, sizeof(hdr), &hdr) && + image_check_magic(&hdr) && !image_check_hcrc(&hdr); +} +#endif + int firmware_scan(void) { -#ifdef CONFIG_ENV_IS_IN_NAND +#ifdef OPENIPC_SPI_NAND u8 buf[64]; int ret; @@ -581,21 +785,6 @@ int firmware_scan(void) { return 0; } -#ifdef CONFIG_OPENIPC_NAND_KERNEL_VOLUME - /* The kernel in its volume, a squashfs rootfs in its own */ - ret = ubi_volume_read("kernel", (char *)buf, sizeof(buf)); - if (ret || !check_kernel(buf)) { - printf("Firmware is absent/corrupt (kernel volume)\n"); - env_set("bootfail", "1"); - return 0; - } - ret = ubi_volume_read("rootfs", (char *)buf, sizeof(buf)); - if (ret || !check_squashfs(buf)) { - printf("Firmware is absent/corrupt (rootfs volume)\n"); - env_set("bootfail", "1"); - return 0; - } -#else /* The kernel is a file in the UBIFS rootfs (/boot/fitImage or * /boot/uImage): there is no kernel volume to check. */ ret = ubi_volume_read("rootfs", (char *)buf, sizeof(buf)); @@ -619,7 +808,6 @@ int firmware_scan(void) { env_set("bootfail", "1"); return 0; } -#endif env_set("bootfail", NULL); @@ -630,6 +818,7 @@ int firmware_scan(void) { ulong rootfs_off = 0; uint64_t kernel_size = 0; uint64_t rootfs_size = 0; + int bad_header = 0; printf("Automatic flash layout detection...\n"); @@ -641,13 +830,118 @@ int firmware_scan(void) { scan_spi_device(flash, &kernel_off, &rootfs_off, &kernel_size, &rootfs_size); -#ifndef CONFIG_TARGET_HI3516CV610_FAMILY - if (kernel_size <= 0x200000) - kernel_size = 0x200000; - if (rootfs_size < 0x500000) - rootfs_size = 0x500000; - else if (rootfs_size > 0x500000 && flash->size > 0x800000) - rootfs_size = 0xa00000; +#ifdef CONFIG_OPENIPC_NOR_SPLIT + { + /* fwwrite erases from the firmware start to the end of the chip, as + * recovery does, so no old overlay is left behind a shorter image */ + char str[32]; + const char *ka = env_get("kernaddr"); + ulong start = kernel_off ? kernel_off : + (ka ? hextoul(ka, NULL) : OPENIPC_KERNADDR); + + if (start < flash->size) { + sprintf(str, "0x%lx", (ulong)(flash->size - start)); + env_set("fwsize", str); + /* RAM only, like the rest of the scan: a saved environment keeps an + * older fwwrite that erased only the file's length */ + env_set("fwwrite", OPENIPC_FWWRITE_SPLIT_CMD); + } + /* A saved fwupd from before the split layout fetches only the pair; + * exactly that one takes the firmware file first. An edited one is + * left alone. */ + { + const char *fu = env_get("fwupd"); + + if (fu && !strcmp(fu, OPENIPC_FWUPD_LEGACY_CMD)) + env_set("fwupd", OPENIPC_FWUPD_SPLIT_CMD); + if (!env_get("fwfile")) + env_set("fwfile", OPENIPC_FW_FILE); + } + } +#endif + + /* + * Fixed slots -- the rootfs at the 2 or 3 MiB slot boundary after the + * kernel, or nothing found to say otherwise -- get the slots' sizes, so the + * rootfs partition is its whole slot and rootfs_data starts after it. A + * rootfs packed right after the kernel (a FIT and its squashfs written as + * one image) keeps the sizes found: a slot there would put the rootfs + * partition past the squashfs. + * + * The 10 MiB rootfs slot is the 3 MiB kernel slot's (the ultimate pair, + * rootfs at 0x350000; ur moves a large rootfs there before it writes). A + * rootfs over 5 MiB behind the 2 MiB kernel slot is a packed image that + * outgrew the lite slot -- hi3516cv6xx ultimate on 16 MiB, 7104k at + * 0x250000 -- and keeps the size found: rounding it up to 10 MiB moved + * rootfs_data from 0x940000 to 0xc50000, away from the settings there. + */ + if (!kernel_size || kernel_size == 0x200000 || kernel_size == 0x300000) { + if (kernel_size <= 0x200000) + kernel_size = 0x200000; + if (rootfs_size < 0x500000) + rootfs_size = 0x500000; + else if (kernel_size == 0x300000 && rootfs_size < 0xa00000 && flash->size > 0x800000) + rootfs_size = 0xa00000; + } + +#ifdef CONFIG_OPENIPC_NOR_SPLIT + if (nor_split_layout(flash, kernel_off, rootfs_off)) { + char str[32], mtds[128], mtdparts[160]; + const char *mtdids = env_get("mtdids"); + ulong kern_slot, root_slot; + int kernel_ok = nor_split_kernel_ok(flash, kernel_off); + + /* + * kernsize is what loadkernel reads, and also what uk erases; rootaddr + * and rootsize are where ur writes. Those commands install a uImage and a + * squashfs, i.e. fixed slots, so they get the fixed slots: the kernel + * slot whole (the split squashfs starts inside it, and a head left there + * would be found by the next scan before the new rootfs), the rootfs slot + * after it. The kernel slot holds the whole uImage either way. + */ + if (rootfs_off - kernel_off <= 0x200000) { + kern_slot = 0x200000; + root_slot = 0x500000; + } else if (rootfs_off - kernel_off <= 0x300000 && flash->size > 0x800000) { + kern_slot = 0x300000; + root_slot = 0xa00000; + } else { + /* No fixed-slot layout holds this kernel. */ + kern_slot = rootfs_off - kernel_off; + root_slot = flash->size - rootfs_off; + } + spi_flash_free(flash); + sprintf(str, "0x%08lx", kernel_off); + env_set("kernaddr", str); + sprintf(str, "0x%lx", kern_slot); + env_set("kernsize", str); + sprintf(str, "0x%08lx", kernel_off + kern_slot); + env_set("rootaddr", str); + sprintf(str, "0x%lx", root_slot); + env_set("rootsize", str); +#ifdef SFC + if (!mtdids || !*mtdids) + mtdids = SFC; +#endif + /* U-Boot's own idea of the flash; the kernel is not told. */ + sprintf(mtds, "%dk(u-boot),%dk(env),-(firmware)", + CONFIG_ENV_OFFSET / 1024, CONFIG_ENV_SIZE / 1024); + env_set("mtds", mtds); + sprintf(mtdparts, "%s:%s", mtdids ? mtdids : "", mtds); + env_set("mtdparts", mtdparts); + /* bootm checks the uImage's data CRC; a verify=n saved by an older + * bootloader must not skip it. RAM only, like the rest of the scan. */ + env_set("verify", NULL); + nor_split_bootargs(); + if (kernel_ok) { + env_set("bootfail", NULL); + } else { + printf("Firmware is absent/corrupt (kernel at 0x%lx fails its CRC)\n", kernel_off); + env_set("bootfail", "1"); + } + return 0; + } + bad_header = nor_split_header_bad(flash, kernel_off); #endif spi_flash_free(flash); @@ -688,9 +982,31 @@ int firmware_scan(void) { env_set("mtds", mtds); sprintf(mtdparts, "%s:%s", mtdids ? mtdids : "", mtds); env_set("mtdparts", mtdparts); +#ifdef CONFIG_OPENIPC_NOR_SPLIT + /* + * Fixed slots need root= and mtdparts= on the command line. A build of + * this bootloader that saved its environment after the scan could + * have stored a split boot's bootargs without them; put them back, in + * RAM, when both are missing. A command line that names either is the + * owner's and is left alone. + */ + { + const char *ba = env_get("bootargs"); + char buf[1024]; + + if (ba && !strstr(ba, "root=") && !strstr(ba, "mtdparts=") && + strlen(ba) + 64 < sizeof(buf)) { + snprintf(buf, sizeof(buf), "%s root=/dev/mtdblock3 mtdparts=${mtdparts}", ba); + env_set("bootargs", buf); + } + } +#endif } - if (kernel_off && rootfs_off) { + if (bad_header) { + printf("Firmware is absent/corrupt (kernel header at 0x%lx fails its CRC)\n", kernel_off); + env_set("bootfail", "1"); + } else if (kernel_off && rootfs_off) { env_set("bootfail", NULL); } else { printf("Firmware is absent/corrupt (kernel=0x%lx, rootfs=0x%lx)\n", kernel_off, rootfs_off); @@ -779,19 +1095,67 @@ static void altbootcmd_migrate(void) { env_save(); } +#if !defined(OPENIPC_SPI_NAND) && defined(CONFIG_OPENIPC_NOR_SPLIT) +/* + * fwwrite writes only a file that fits fwsize, the firmware area from + * kernaddr to the end of the chip, and the layout scan sets it. Recovery + * runs before the scan, so a script.txt that runs fwupd would find no + * fwsize on a fresh environment and write nothing. Set it from the flash + * here; the scan sets it again from what it finds. + */ +static void nor_split_fwsize(void) +{ + struct spi_flash *flash; + const char *ka = env_get("kernaddr"); + ulong start = ka ? hextoul(ka, NULL) : OPENIPC_KERNADDR; + char str[32]; + + flash = spi_flash_probe(CONFIG_SF_DEFAULT_BUS, CONFIG_SF_DEFAULT_CS, + CONFIG_SF_DEFAULT_SPEED, CONFIG_SF_DEFAULT_MODE); + if (!flash) + return; + if (start < flash->size) { + sprintf(str, "0x%lx", (ulong)(flash->size - start)); + env_set("fwsize", str); + } + spi_flash_free(flash); +} +#endif + int openipc_helper(void) { char msize[16]; sprintf(msize, "%ldM", gd->ram_size / 1024 / 1024); env_set("totalmem", msize); - firmware_recovery(); - firmware_scan(); + +#if !defined(OPENIPC_SPI_NAND) && defined(CONFIG_OPENIPC_NOR_SPLIT) + nor_split_fwsize(); +#endif + + /* Recovery checks what it runs: a board may already have set verify=n + * (so that the split scan below can clear it), and source would then run + * a wrapped script.txt without its data CRC. Drop it for recovery only + * and put it back for the boot. */ + { + const char *v = env_get("verify"); + char *saved = v ? strdup(v) : NULL; + + env_set("verify", NULL); + firmware_recovery(); + env_set("verify", saved); + free(saved); + } altbootcmd_migrate(); /* Persist the default environment on first start (or after a bad env); * only if the device env area is still blank so we never overwrite - * foreign data (e.g. a RAM-booted U-Boot on another device). */ + * foreign data (e.g. a RAM-booted U-Boot on another device). Both saves + * come before the scan: what it sets (the layout's addresses and + * mtdparts, a split boot's bootargs without root= and mtdparts=) is for + * this boot only, and a later fixed-slot image needs the defaults. */ if ((gd->flags & GD_FLG_ENV_DEFAULT) && env_crc_is_blank()) env_save(); + firmware_scan(); + return 0; } From 5b32b4c32b3db778d6c77ce66ed1d35787ff2953 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sat, 7 Nov 2026 16:58:13 +0000 Subject: [PATCH 2/7] openipc: SigmaStar SPI NAND in openipc-hisilicon's layout 768k(boot),256k(env),-(ubi): the environment at 0xC0000 (256 KiB, range 256 KiB), one UBI device from 0x100000 whose UBIFS rootfs volume holds the kernel as /boot/fitImage or /boot/uImage, rootfs_data on the rest, root=ubi0:rootfs rootfstype=ubifs ubi.mtd=2,2048; ur installs rootfs.ubifs. sized to the image, keeping rootfs_data. nand_config is openipc-hisilicon's, with UBIFS and FIT; the per-die NAND fragments go. The NOR environment takes the split layout's fwupd/fwwrite, fwfile=firmware.bin., and mtdslite resetting kernaddr, as the others. Not OpenIPC/u-boot-sigmastar's NAND layout (UBI at 0x240000, a kernel volume and a squashfs rootfs): a camera moving over takes the firmware's rootfs.ubifs too, as HiSilicon's do. qemu: ssc325de, ssc337de, ssc338q from blank through TFTP and SD recovery to login, again after saveenv and reset; ssc377 with mstar-06's Maruko kernel. --- include/configs/openipc-common.h | 166 ++++++++++++++++--------------- openipc/ifliegen_nand_config | 4 - openipc/iford_nand_config | 3 - openipc/infinity2m_nand_config | 4 - openipc/infinity6c_nand_config | 3 - openipc/nand_config | 33 +++--- 6 files changed, 109 insertions(+), 104 deletions(-) delete mode 100644 openipc/ifliegen_nand_config delete mode 100644 openipc/iford_nand_config delete mode 100644 openipc/infinity2m_nand_config delete mode 100644 openipc/infinity6c_nand_config diff --git a/include/configs/openipc-common.h b/include/configs/openipc-common.h index 6b8ca8a36899..b870f41ce958 100644 --- a/include/configs/openipc-common.h +++ b/include/configs/openipc-common.h @@ -48,7 +48,9 @@ #define OPENIPC_ROOTADDR 0x250000 #define OPENIPC_ROOTSIZE 0x500000 -#define OPENIPC_FW_FILE "firmware.${soc}" +/* the split package's image, firmware.bin.; spelled out here, as + * urfile is: U-Boot does not expand a variable inside another's value */ +#define OPENIPC_FW_FILE "firmware.bin." CONFIG_PRODUCT_SOC /* * What the kernel is told about memory: mem= on infinity3; LX_MEM and the @@ -111,79 +113,9 @@ "fwrecovery=if env exists bootfail; then run fwupd; sleep 5; reset; fi\0" \ "bootflash=run fwrecovery; setenv setargs setenv bootargs ${bootargs}; run setargs; run loadkernel; bootm ${baseaddr}; reset\0" -#ifdef CONFIG_ENV_IS_IN_NAND +#ifndef CONFIG_NAND_MS_SPINAND -/* - * SPI NAND, in the layout of OpenIPC/u-boot-sigmastar, whose saved - * environments have to keep working: the boot partitions, the environment - * and one UBI device, with the kernel and a squashfs rootfs in volumes of - * their own. "mtdparts" holds the kernel argument whole. - */ -#ifdef CONFIG_SS_INFINITY2M -/* Its PNI puts the stages further up: env in blocks 34-36, then ubi */ -#define OPENIPC_NAND_MTDPARTS "nand0:4352k(boot),384k(env),-(ubi)" -#define OPENIPC_NAND_BOOTSIZE 0x440000 -#define OPENIPC_NAND_ROOTADDR 0x4A0000 -#define OPENIPC_NAND_ROOTSIZE 0x7B60000 -#define OPENIPC_NAND_ROOTDEV "mtdblock4" -#elif defined(CONFIG_SS_IFLIEGEN) -/* The Ifliegen SDK's: CIS, BOOT and BOOT_BAK, the environment at 0x500000 */ -#define OPENIPC_NAND_MTDPARTS "nand0:1280k(cis),1920k(boot0),1920k(boot1),256k(env),-(ubi)" -#define OPENIPC_NAND_BOOTSIZE 0x500000 -#define OPENIPC_NAND_ROOTADDR 0x540000 -#define OPENIPC_NAND_ROOTSIZE 0x7AC0000 -#define OPENIPC_NAND_ROOTDEV "mtdblock6" -#elif defined(CONFIG_SS_IFORD) -/* The Iford SDK's: CIS, BOOT and its copy, the environment at 0x580000, ubi */ -#define OPENIPC_NAND_MTDPARTS "nand0:1280k(cis),2176k(boot0),2176k(boot1),256k(env),-(ubi)" -#define OPENIPC_NAND_BOOTSIZE 0x580000 -#define OPENIPC_NAND_ROOTADDR 0x5C0000 -#define OPENIPC_NAND_ROOTSIZE 0x7A40000 -#define OPENIPC_NAND_ROOTDEV "mtdblock6" -#elif defined(CONFIG_SS_I6C_FAMILY) -/* The SDK's: the CIS, BOOT and BOOT_BAK, the environment, ubi */ -#define OPENIPC_NAND_MTDPARTS "nand0:1280k(cis),1792k(boot0),1792k(boot1),256k(env),-(ubi)" -#define OPENIPC_NAND_BOOTSIZE 0x4C0000 -#define OPENIPC_NAND_ROOTADDR 0x500000 -#define OPENIPC_NAND_ROOTSIZE 0x7B00000 -#define OPENIPC_NAND_ROOTDEV "mtdblock6" -#else -#define OPENIPC_NAND_MTDPARTS "nand0:1024k(boot0),1024k(boot1),256k(env),-(ubi)" -#define OPENIPC_NAND_BOOTSIZE 0x200000 -#define OPENIPC_NAND_ROOTADDR 0x240000 -#define OPENIPC_NAND_ROOTSIZE 0x7DC0000 -#define OPENIPC_NAND_ROOTDEV "mtdblock5" -#endif -/* - * The rootfs volume as the OpenIPC kernels number it: gluebi makes each - * UBI volume an MTD after the partitions, kernel then rootfs. A kernel - * without gluebi takes extras="ubi.block=0,rootfs root=/dev/ubiblock0_1 - * rootfstype=squashfs", the later root= winning. - */ - -/* As the firmware's own volume holds it, 34 LEBs of 126976 bytes */ -#define OPENIPC_KERNEL_VOLUME_SIZE "0x400000" - -#define CONFIG_BOOTARGS "console=ttyS0,115200 panic=20 root=/dev/" OPENIPC_NAND_ROOTDEV " init=/init ubi.mtd=ubi \\${mtdparts}" OPENIPC_MEMARGS_TAIL " \\${extras}" - -/* rootfs.ubi carries the kernel volume too */ -#define OPENIPC_FWUPD "fwupd=run ur\0" - -#define OPENIPC_LOADKERNEL "ubi part ubi; ubi read ${baseaddr} kernel" -#define OPENIPC_ENV_MEDIUM \ - "ubfile=u-boot-" CONFIG_PRODUCT_SOC "-nand.bin\0" \ - "urfile=rootfs.ubi." CONFIG_PRODUCT_SOC "\0" \ - "rootaddr=" __stringify(OPENIPC_NAND_ROOTADDR) "\0" \ - "rootsize=" __stringify(OPENIPC_NAND_ROOTSIZE) "\0" \ - "ubwrite=nand erase 0x0 " __stringify(OPENIPC_NAND_BOOTSIZE) "; nand write ${baseaddr} 0x0 ${filesize}\0" \ - "ukwrite=ubi part ubi; ubi write ${baseaddr} kernel ${filesize}\0" \ - "urwrite=nand erase ${rootaddr} ${rootsize}; nand write ${baseaddr} ${rootaddr} ${filesize}\0" \ - "loadkernel=" OPENIPC_LOADKERNEL "\0" \ - "mtdids=nand0=nand0\0" \ - "mtdparts=mtdparts=" OPENIPC_NAND_MTDPARTS "\0" \ - "bootargs=" CONFIG_BOOTARGS - -#else /* !CONFIG_ENV_IS_IN_NAND */ +/* ---------- SPI NOR ---------- */ /* * The flash's MTD name in the vendor kernels: drivers/mstar/flash_isp's @@ -199,8 +131,44 @@ #define CONFIG_BOOTARGS OPENIPC_MEMARGS_HEAD "console=ttyS0,115200 panic=20 root=/dev/mtdblock3 rootfstype=squashfs init=/init mtdparts=\\${mtdparts}" OPENIPC_MEMARGS_TAIL " \\${extras}" -/* Recovery: the rootfs is fetched first so its size selects the NOR schema */ -#define OPENIPC_FWUPD "fwupd=run ur; run uk\0" +/* + * Recovery: the rootfs is fetched first so its size selects the NOR schema. + * It starts from the lite layout, not from what the scan found: on a camera + * that still holds its vendor firmware the scan reports the vendor's + * offsets, and writing OpenIPC there left the kernel write erasing the + * start of the new rootfs. + */ +#ifdef CONFIG_OPENIPC_NOR_SPLIT +/* + * The split layout: the whole firmware file, written + * where the firmware starts, when the server has one that fits the firmware + * area; else -- none, an empty one, or one too big for the chip -- the + * fixed-slot pair as before. common/openipc.c turns a saved fwupd of exactly the + * legacy form into this one. + */ +#define OPENIPC_FWUPD_SPLIT_CMD \ + "if ${source} ${baseaddr} ${fwfile} && itest ${filesize} -gt 0 && itest ${filesize} -le ${fwsize}; " \ + "then run fwwrite; else run mtdslite; run ur; run uk; fi" +#define OPENIPC_FWUPD_LEGACY_CMD "run mtdslite; run ur; run uk" +/* + * The whole firmware area (the scan sets fwsize to the end of the chip), and + * only for a file that fits it: an oversized download must not erase the + * firmware it cannot replace. + */ +#define OPENIPC_FWWRITE_SPLIT_CMD \ + "sf probe 0; if itest ${filesize} -gt 0; then if itest ${filesize} -le ${fwsize}; then " \ + "sf erase ${kernaddr} ${fwsize}; sf write ${baseaddr} ${kernaddr} ${filesize}; " \ + "else echo fwwrite: ${filesize} bytes do not fit ${fwsize} from ${kernaddr}, nothing written; fi; " \ + "else echo fwwrite: empty file, nothing written; fi" +#define OPENIPC_FWUPD \ + "fwfile=" OPENIPC_FW_FILE "\0" \ + "fwupd=" OPENIPC_FWUPD_SPLIT_CMD "\0" +#define OPENIPC_FWWRITE "fwwrite=" OPENIPC_FWWRITE_SPLIT_CMD "\0" +#else +#define OPENIPC_FWUPD "fwupd=run mtdslite; run ur; run uk\0" +#define OPENIPC_FWWRITE \ + "fwwrite=sf probe 0; sf erase ${kernaddr} ${filesize}; sf write ${baseaddr} ${kernaddr} ${filesize}\0" +#endif #define OPENIPC_LOADKERNEL "sf probe 0; sf read ${baseaddr} ${kernaddr} ${kernsize}" #define OPENIPC_ENV_MEDIUM \ @@ -210,19 +178,61 @@ "kernsize=" __stringify(OPENIPC_KERNSIZE) "\0" \ "rootaddr=" __stringify(OPENIPC_ROOTADDR) "\0" \ "rootsize=" __stringify(OPENIPC_ROOTSIZE) "\0" \ - "mtdslite=setenv kernsize 0x200000; setenv rootaddr 0x250000; setenv rootsize 0x500000\0" \ + "mtdslite=setenv kernaddr " __stringify(OPENIPC_KERNADDR) "; setenv kernsize 0x200000; setenv rootaddr 0x250000; setenv rootsize 0x500000\0" \ "mtdsultimate=setenv kernsize 0x300000; setenv rootaddr 0x350000; setenv rootsize 0xa00000\0" \ "ubwrite=sf probe 0; sf erase 0x0 ${kernaddr}; sf write ${baseaddr} 0x0 ${filesize}\0" \ "ukwrite=sf probe 0; sf erase ${kernaddr} ${kernsize}; sf write ${baseaddr} ${kernaddr} ${filesize}\0" \ "urwrite=sf probe 0; if itest ${filesize} -gt 500000; then run mtdsultimate; fi; sf erase ${rootaddr} ${rootsize}; sf write ${baseaddr} ${rootaddr} ${filesize}\0" \ - "fwwrite=sf probe 0; sf erase ${kernaddr} ${filesize}; sf write ${baseaddr} ${kernaddr} ${filesize}\0" \ + OPENIPC_FWWRITE \ "loadkernel=" OPENIPC_LOADKERNEL "\0" \ "mtdids=" SFC "\0" \ "mtds=" OPENIPC_MTDPARTS "\0" \ "mtdparts=" SFC ":" OPENIPC_MTDPARTS "\0" \ "bootargs=" CONFIG_BOOTARGS -#endif /* CONFIG_ENV_IS_IN_NAND */ +#else /* CONFIG_NAND_MS_SPINAND */ + +/* ---------- SPI NAND ---------- */ + +/* The flash's MTD name in the vendor kernels (mtdparts=nand0:...) */ +#define SFC "nand0" + +/* NAND partition list; composed the same way. */ +#define OPENIPC_NAND_MTDPARTS "768k(boot),256k(env),-(ubi)" + +/* Environment lives in the NAND 'env' partition: CONFIG_ENV_OFFSET 0xc0000, + * CONFIG_ENV_SIZE and CONFIG_ENV_RANGE 0x40000 (openipc/nand_config), as + * on openipc-hisilicon. */ + +#define CONFIG_BOOTARGS OPENIPC_MEMARGS_HEAD "console=ttyS0,115200 panic=20 init=/init root=ubi0:rootfs rootfstype=ubifs ubi.mtd=2,2048 mtdparts=\\${mtdparts}" OPENIPC_MEMARGS_TAIL " \\${extras}" + +/* + * The NAND layout is one UBI device after boot and env, holding the UBIFS + * rootfs with the kernel inside it -- /boot/fitImage, or /boot/uImage on a + * build without a FIT -- and rootfs_data on the rest. No flash is set aside + * for a kernel: the rootfs volume is sized to its image. This is the layout + * of openipc-hisilicon's NAND images. + * + * Recovery rewrites the rootfs volume only, sized to the image, with + * rootfs_data taking the rest. Only a rootfs that grew beyond what the old + * volume could give costs the data volume, as U-Boot cannot resize UBI + * volumes; else rootfs_data is preserved. + */ +#define OPENIPC_FWUPD "fwupd=run ur\0" + +#define OPENIPC_LOADKERNEL "ubi part ubi; ubifsmount ubi0:rootfs; ubifsload ${baseaddr} /boot/fitImage || ubifsload ${baseaddr} /boot/uImage" +#define OPENIPC_ENV_MEDIUM \ + "ubfile=u-boot-" CONFIG_PRODUCT_SOC "-nand.bin\0" \ + "urfile=rootfs.ubifs." CONFIG_PRODUCT_SOC "\0" \ + "ubwrite=nand erase 0x0 0xc0000; nand write ${baseaddr} 0x0 ${filesize}\0" \ + "urwrite=ubi part ubi; ubi check rootfs && ubi remove rootfs; if ubi create rootfs ${filesize} d; then ubi check rootfs; else ubi remove rootfs_data; ubi create rootfs ${filesize} d; fi; ubi check rootfs_data || ubi create rootfs_data 0 d; ubi write ${baseaddr} rootfs ${filesize}\0" \ + "loadkernel=" OPENIPC_LOADKERNEL "\0" \ + "mtdids=nand0=" SFC "\0" \ + "mtds=" OPENIPC_NAND_MTDPARTS "\0" \ + "mtdparts=" SFC ":" OPENIPC_NAND_MTDPARTS "\0" \ + "bootargs=" CONFIG_BOOTARGS + +#endif /* CONFIG_NAND_MS_SPINAND */ #define CONFIG_BOOTCOMMAND "run bootflash" diff --git a/openipc/ifliegen_nand_config b/openipc/ifliegen_nand_config deleted file mode 100644 index c0945605899c..000000000000 --- a/openipc/ifliegen_nand_config +++ /dev/null @@ -1,4 +0,0 @@ -# The Ifliegen SDK's layout (spinand.squashfs.tfa.partition.config): CIS, -# BOOT and BOOT_BAK of 0x1E0000 each, the environment at 0x500000, then ubi -CONFIG_ENV_OFFSET=0x500000 -CONFIG_MTDPARTS_DEFAULT="mtdparts=nand0:1280k(cis),1920k(boot0),1920k(boot1),256k(env),-(ubi)" diff --git a/openipc/iford_nand_config b/openipc/iford_nand_config deleted file mode 100644 index ed435a9ab0a1..000000000000 --- a/openipc/iford_nand_config +++ /dev/null @@ -1,3 +0,0 @@ -# The Iford SDK's layout: CIS, BOOT0, BOOT1, the environment, then ubi -CONFIG_ENV_OFFSET=0x580000 -CONFIG_MTDPARTS_DEFAULT="mtdparts=nand0:1280k(cis),2176k(boot0),2176k(boot1),256k(env),-(ubi)" diff --git a/openipc/infinity2m_nand_config b/openipc/infinity2m_nand_config deleted file mode 100644 index 8792d2764008..000000000000 --- a/openipc/infinity2m_nand_config +++ /dev/null @@ -1,4 +0,0 @@ -# Its PNI: the environment in blocks 34-36 -CONFIG_ENV_OFFSET=0x440000 -CONFIG_ENV_RANGE=0x60000 -CONFIG_MTDPARTS_DEFAULT="mtdparts=nand0:4352k(boot),384k(env),-(ubi)" diff --git a/openipc/infinity6c_nand_config b/openipc/infinity6c_nand_config deleted file mode 100644 index 4b79b79509a3..000000000000 --- a/openipc/infinity6c_nand_config +++ /dev/null @@ -1,3 +0,0 @@ -# The SDK's layout: the CIS, BOOT, BOOT_BAK, then the environment -CONFIG_ENV_OFFSET=0x4C0000 -CONFIG_MTDPARTS_DEFAULT="mtdparts=nand0:1280k(cis),1792k(boot0),1792k(boot1),256k(env),-(ubi)" diff --git a/openipc/nand_config b/openipc/nand_config index c00e471f2bbe..70ce2f5b1829 100644 --- a/openipc/nand_config +++ b/openipc/nand_config @@ -1,21 +1,30 @@ -# On SPI NAND, in place of the SPI NOR of openipc_config. The layout and the -# environment are OpenIPC/u-boot-sigmastar's: boot0, boot1, env at 2 MiB, ubi. +# SPI NAND on the FSP-QSPI, appended to a NOR target's fragments for its +# NAND image, as openipc-hisilicon's nand_config: the environment is in the +# NAND 'env' partition and the firmware is one UBI device +# (768k(boot),256k(env),-(ubi)) with the kernel inside the UBIFS rootfs. # CONFIG_MS_SFLASH is not set # CONFIG_CMD_SF is not set # CONFIG_ENV_IS_IN_SPI_FLASH is not set -CONFIG_ENV_IS_IN_NAND=y -CONFIG_ENV_OFFSET=0x200000 -CONFIG_ENV_SIZE=0x20000 -CONFIG_ENV_SECT_SIZE=0x20000 -CONFIG_ENV_RANGE=0x40000 CONFIG_MTD=y CONFIG_MTD_RAW_NAND=y CONFIG_NAND_MS_SPINAND=y CONFIG_CMD_NAND=y -CONFIG_MTD_UBI=y -CONFIG_CMD_UBI=y +CONFIG_CMD_NAND_TRIMFFS=y +# CONFIG_CMD_NAND_TORTURE is not set +CONFIG_ENV_IS_IN_NAND=y +CONFIG_ENV_OFFSET=0xc0000 +CONFIG_ENV_SIZE=0x40000 +CONFIG_ENV_RANGE=0x40000 +CONFIG_MTD_PARTITIONS=y CONFIG_CMD_MTDPARTS=y CONFIG_MTDIDS_DEFAULT="nand0=nand0" -CONFIG_MTDPARTS_DEFAULT="mtdparts=nand0:1024k(boot0),1024k(boot1),256k(env),-(ubi)" -# The firmware's kernel is in a UBI volume, the rootfs a squashfs -CONFIG_OPENIPC_NAND_KERNEL_VOLUME=y +CONFIG_MTDPARTS_DEFAULT="mtdparts=nand0:768k(boot),256k(env),-(ubi)" +CONFIG_CMD_UBI=y +CONFIG_CMD_UBIFS=y +CONFIG_MTD_UBI=y +CONFIG_MTD_UBI_WL_THRESHOLD=4096 +CONFIG_MTD_UBI_BEB_LIMIT=20 +# CONFIG_MTD_UBI_FASTMAP is not set +CONFIG_RBTREE=y +CONFIG_FIT=y +CONFIG_SHA1=y From dc284f26fc86519cc70867e8294cb6d9d4338115 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sat, 7 Nov 2026 16:58:13 +0000 Subject: [PATCH 3/7] sigmastar-image: every die's NAND boot stages in 768 KiB; no padding One copy of each stage, a block each where the IPL wants blocks: - infinity6, 6b0, 6e, infinity2m: SNI and PNI in block 0 (BL0PBA 1, BL1PBA 0, SNI byte 0x2E 2, where infinity6's IPL_CUST starts its search for U-Boot), IPL in 1, IPL_CUST in 2, U-Boot in 3-5. - infinity6c: the CIS in blocks 0 and 1 (its IPL reads a copy past block 0), IPL in 2, IPL_CUST in 3, U-Boot in 4-5. - Ifliegen: the CIS, IPL, IPL_CUST, TF-A's SP_MIN in blocks 0-3, U-Boot in 4-5. - Iford: its IPL takes every stage by name at any 2 KiB page, which lets the 338 KB OP-TEE in: IPL from 0x20000, IPL_CUST, OP-TEE and U-Boot each from the next page, ending near 0xB8000. NOR and NAND images end where their data does, as OpenIPC's others: no 0xFF padding to the partition. qemu: every image, NOR and NAND, two boots to the autoboot prompt with the environment kept, on its own machine. --- build.sh | 10 +- openipc/sigmastar-image.py | 316 +++++++++++++++++++------------------ 2 files changed, 167 insertions(+), 159 deletions(-) diff --git a/build.sh b/build.sh index 659d19755327..e2d17990da88 100755 --- a/build.sh +++ b/build.sh @@ -75,8 +75,7 @@ build_nand() { rm -rf "${builddir}" mkconfig "${builddir}" openipc/openipc_config openipc/${die}_config \ - openipc/${soc}_config openipc/nand_config \ - $(ls openipc/${die}_nand_config 2>/dev/null) && \ + openipc/${soc}_config openipc/nand_config && \ make ${make_opts} olddefconfig /dev/null && \ make ${make_opts} /dev/null && \ make ${make_opts} /dev/null && \ make ${make_opts} IIIIIIIBBBB32s", 0x27051956, 0, int(time.time()), @@ -93,8 +76,7 @@ def iford(args, ipl, cust): boot[0xE000:0xE000 + len(cis)] = cis boot[0x18000:0x18000 + len(cust)] = cust boot[0x1F000:0x1F000 + len(img)] = img - with open(args.out, "wb") as f: - f.write(boot) + write_image(args.out, boot) print("%s: UBOOT %u of %u bytes" % (args.out, len(img), 0x21000)) @@ -121,8 +103,7 @@ def ifado(args, ipl, cust): boot[0x8000:0x8000 + len(cis)] = cis boot[0x10000:0x10000 + len(cust)] = cust boot[0x20000:0x20000 + len(img)] = img - with open(args.out, "wb") as f: - f.write(boot) + write_image(args.out, boot) print("%s: UBOOT %u of %u bytes" % (args.out, len(img), 0x20000)) @@ -171,8 +152,7 @@ def ifliegen(args, ipl, cust): if len(data) > room: sys.exit("%s: %u bytes, it has %u" % (name, len(data), room)) boot[off:off + len(data)] = data - with open(args.out, "wb") as f: - f.write(boot) + write_image(args.out, boot) print("%s: UBOOT %u of %u bytes" % (args.out, len(img), 0x1C000)) @@ -219,91 +199,141 @@ def snigen(data, bl0, bl1, page=0, spare=0): def ifliegen_nand(args): """ - Ifliegen's SPI NAND (2048+64, 64 pages a block), as its SDK's image.mk - and spinand.squashfs.tfa.partition.config lay it out: the CIS in each - of the ten CIS blocks (page 0 the SNIB and the part's SNIF, page 1 the - PNI, then the flash list), BOOT from 0x140000 (IPL and IPL_CUST three - blocks each, a copy in each, TF-A's SP_MIN one block, UBOOT 0x100000 - with our uImage), BOOT_BAK the same from 0x320000, the environment at - 0x500000. The IPL takes its timeouts from the SNIF, so the CIS carries - a real part's: the flash list's record --sni-part (57, a W25N01GV). + Ifliegen's SPI NAND (2048+64, 64 pages a block) in OpenIPC's 768 KiB + boot area, one copy of everything: the CIS in block 0 (page 0 the SNIB + and the part's SNIF, page 1 the PNI, then the flash list, with the boot + block fields as snigenerator sets them: BL0PBA 1, no backup IPL), IPL in + block 1, IPL_CUST in 2, TF-A's SP_MIN in 3, our uImage in 4 and 5; the + environment at 0xC0000, UBI from 0x100000. The IPL takes its timeouts + from the SNIF, so the CIS carries a real part's: the flash list's record + --sni-part (57, a W25N01GV). """ - blk = 0x20000 payload = lzma.compress(read(args.uboot), format=lzma.FORMAT_XZ, check=lzma.CHECK_CRC64, preset=6) tag = ("MVX4##%-4s" % args.tag).replace(" ", "#") + "openIPC#CM_UBT2110#XVM" img = uimage(payload, tag[:32], 0x23E00000) - # name, room, data, copies in the room; flags as pnigenerator writes them - stages = (("IPL", 3 * blk, read(args.ipl), 3), - ("IPL_CUST", 3 * blk, read(args.ipl_cust), 3), - ("TF_A", blk, read(args.ifliegen_tfa), 1), - ("UBOOT", 0x100000, img, 1)) - cis_size = 10 * blk - boot_size = sum(room for _, room, _, _ in stages) - env_off = cis_size + 2 * boot_size - bl0, bl1 = cis_size // blk, (cis_size + boot_size) // blk - out = bytearray(b"\xff" * env_off) - parts = [("CIS", 0, cis_size, (0, 1, 1, 0))] - for copy, base in enumerate((cis_size, cis_size + boot_size)): - off = base - for name, room, data, copies in stages: - parts.append((name, off, room, - (0, 1, 1, 1) if copy == 0 else (1, 0, 0, 2))) - step = room // copies - if len(data) > step: - sys.exit("%s: %u bytes, it has %u" % (name, len(data), step)) - for c in range(copies): - out[off + c * step:off + c * step + len(data)] = data - off += room - parts.append(("ENV", env_off, 0x40000, (0, 1, 1, 0))) - parts.append(("ubi", env_off + 0x40000, 0x8000000 - env_off - 0x40000, - (0, 1, 1, 0))) + blk = 0x20000 + stages = (("IPL", blk, blk, read(args.ipl)), + ("IPL_CUST", 2 * blk, blk, read(args.ipl_cust)), + ("TF_A", 3 * blk, blk, read(args.ifliegen_tfa)), + ("UBOOT", 4 * blk, 2 * blk, img)) + flist = snigen(read(args.ifliegen_sni_list), 1, 0) + sni = bytearray(snigen(read(args.ifliegen_sni), 1, 0, 2048, 64)) + rec = args.sni_part * 1024 + sni[0x100:0x400] = flist[rec + 0x100:rec + 0x400] + sni = snib_fix(sni[:0x400], 1, 0) + write_image(args.out, nand_cis_boot(args, sni, flist, stages)) + + +def snib_fix(r, bl0, bl1): + """A SNIB or SNIF record with one IPL at block bl0 and its copy at bl1, + no further backups, and its CRCs again.""" + r = bytearray(r) + boot = bytes([bl0, bl1, 0, 1, 1]) + if r[0:4] == b"SNIB": + r[0x3F:0x44] = boot + r[0x127:0x12C] = boot + struct.pack_into(" size or off + size > NAND_ENV_OFF: + sys.exit("%s: %u bytes at 0x%x do not fit" % (name, len(data), off)) + parts.append((name, off, size, (0, 1, 1, 1))) + spans.append((off, off + size)) + boot[off:off + len(data)] = data + spans.sort() + for (_, end), (start, _) in zip(spans, spans[1:]): + if end > start: + sys.exit("NAND: the boot stages overlap at 0x%x" % start) + parts += [("ENV", NAND_ENV_OFF, 0x40000, (0, 1, 1, 0)), + ("UBI", NAND_UBI_OFF, 0x8000000 - NAND_UBI_OFF, (0, 1, 1, 0))] recs = b"".join(name.encode().ljust(16, b"\0") + bytes(flags) + - struct.pack(" blk: - sys.exit("Ifliegen NAND: the CIS does not fit a block") - for b in range(10): - out[b * blk:b * blk + len(cis)] = cis - with open(args.out, "wb") as f: - f.write(out) - print("%s: UBOOT %u of %u bytes" % (args.out, len(img), 0x100000)) + sys.exit("NAND: the CIS does not fit its block") + for o in (0,) + tuple(cis_copies): + boot[o:o + len(cis)] = cis + name, off, size, _ = parts[len(stages)] + print("%s: %s %u of %u bytes" % (args.out, name, len(stages[-1][3]), + size)) + return boot + + +def infinity6c_nand(args, ipl, cust, img): + """ + infinity6c's SPI NAND in OpenIPC's 768 KiB: the CIS in blocks 0 and 1 + (its IPL reads a copy past block 0; the SNI the flash list's record + --sni-part, with BL0PBA 2, no backup IPL, and its CRC32), IPL in block 2, + IPL_CUST in 3, U-Boot in 4-5. + """ + sni_list = read(args.sni_list) + sni = bytearray(sni_list[args.sni_part * 512:(args.sni_part + 1) * 512]) + sni[0x2F:0x34] = bytes((2, 0, 0, 1, 1)) + struct.pack_into(" 0x100000 or len(stages) > 0x120000: - sys.exit("Iford NAND: a stage does not fit") - boot = bytearray(b"\xff" * 0x580000) - for blk in range(10): - boot[blk * 0x20000:blk * 0x20000 + len(cis)] = cis - boot[0x140000:0x140000 + len(stages)] = stages - boot[0x260000:0x260000 + len(img)] = img - with open(args.out, "wb") as f: - f.write(boot) - print("%s: UBOOT %u of %u bytes" % (args.out, len(img), 0x100000)) + pieces = (("IPL", stages_bin[0:0x20000]), + ("IPL_CUST", stages_bin[0x60000:0x80000]), + ("OPTEE", stages_bin[0xC0000:0x120000]), ("UBOOT", img)) + stages, off = [], 0x20000 + for name, data in pieces: + data = data.rstrip(b"\xff") if name != "UBOOT" else data + stages.append((name, off, None, data)) + off += (len(data) + 0x7FF) // 0x800 * 0x800 + sni = snib_fix(cis[:0x400], 1, 0) + write_image(args.out, nand_cis_boot(args, sni, cis[0x1000:], stages)) + + +def write_image(path, boot): + """The boot area as far as it holds data: what follows is erased flash, + and the image is no bigger than what it carries, as OpenIPC's others.""" + with open(path, "wb") as f: + f.write(bytes(boot).rstrip(b"\xff")) def read(path): @@ -331,10 +361,9 @@ def main(): help="Iford NAND: IPL, IPL_CUST, OP-TEE from 0x140000") ap.add_argument("--nand", action="store_true") ap.add_argument("--pni", help="partition table, with --nand") - ap.add_argument("--pni-layout", action="store_true") ap.add_argument("--sni-list", help="flash table for infinity6c, page 2") - ap.add_argument("--nand-6c", action="store_true") ap.add_argument("--nand-i2m", action="store_true") + ap.add_argument("--nand-6c", action="store_true") ap.add_argument("--sni-part", type=int, default=1, help="infinity6c: the flash list's record for page 0") ap.add_argument("--tag", required=True, help="die in the MVX tag, 4 chars") @@ -360,65 +389,47 @@ def main(): tag = ("MVX4##%-4s" % args.tag).replace(" ", "#") + "openIPC#CM_UBT1501#XVM" img = uimage(payload, tag[:32]) + if args.nand and args.nand_6c: + return infinity6c_nand(args, ipl, cust, img) if args.nand: - pni = read(args.pni) - parts = {PNI_IPL: NAND_IPL_OFFS, PNI_IPL_CUST: NAND_CUST_OFFS, - PNI_UBOOT: NAND_UBOOT_OFFS, PNI_ENV: (NAND_BOOT_SIZE,)} - if args.pni_layout: - parts = pni_layout(pni) - if args.nand_6c: - # image/configs/general/spinand.*: three copies of each stage, - # one block apart, then U-Boot, and the same again for BOOT_BAK - ipls = [0x140000 + i * 0x20000 for i in range(3)] - custs = [0x1A0000 + i * 0x20000 for i in range(3)] - parts = {PNI_IPL: ipls + [o + 0x1C0000 for o in ipls], - PNI_IPL_CUST: custs + [o + 0x1C0000 for o in custs], - PNI_UBOOT: (0x200000, 0x3C0000), PNI_ENV: (0x4C0000,)} - size = parts[PNI_ENV][0] - uboot = parts[PNI_UBOOT] - limit = min(uboot[1] - uboot[0], 0x100000) - for what, have, room in (("SNI", len(tab), 0x800), - ("PNI", len(pni), 0x800), - ("UBOOT", len(img), limit)): - if have > room: - sys.exit("%s: %u bytes, it has %u" % (what, have, room)) - boot = bytearray(b"\xff" * size) - boot[:len(tab)] = tab - boot[0x800:0x800 + len(pni)] = pni - if args.sni_list and args.nand_6c: - sni_list = read(args.sni_list) - boot[0x1000:0x1000 + len(sni_list)] = sni_list - if args.nand_6c: - # infinity6c's IPL wants a part's own SNI in page 0 (the SDK's - # "nand probe" writes it from the list), with its boot block - # fields and CRC32, and the CIS in each of the ten CIS blocks - rec = bytearray(sni_list[args.sni_part * 512: - (args.sni_part + 1) * 512]) - rec[0x2F:0x34] = tab[0x2F:0x34] - struct.pack_into(" room: + sys.exit("NAND: %u bytes at block %u, it has %u" % + (len(data), start, room)) + boot[start * blk:start * blk + len(data)] = data + write_image(args.out, boot) + print("%s: UBOOT %u of %u bytes" % (args.out, len(img), 3 * blk)) return tab_off = GCIS_OFF if args.gcis else MXP_OFF @@ -434,8 +445,7 @@ def main(): boot[tab_off:tab_off + len(tab)] = tab boot[CUST_OFF:CUST_OFF + len(cust)] = cust boot[UBOOT_OFF:UBOOT_OFF + len(img)] = img - with open(args.out, "wb") as f: - f.write(boot) + write_image(args.out, boot) print("%s: UBOOT %u of %u bytes" % (args.out, len(img), UBOOT_SIZE)) From b2416192807f03f7f181a12dde5c062bd98d19c4 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sat, 7 Nov 2026 16:58:13 +0000 Subject: [PATCH 4/7] docs: CLAUDE.md: SPI NAND in openipc-hisilicon's layout, the dies' 768 KiB boot areas, the common layer --- CLAUDE.md | 147 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 77 insertions(+), 70 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2fd87a9e0a70..bde8ec104755 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,7 +28,7 @@ release as `openipc-hisilicon`, `openipc-xm`, `openipc-gk710x` and | Iford | `ssc029a` `ssc029b` `ssc029c` | Iford SDK IMD00V2.1.0 boot.bin, `board/iford/boot/nor/ipl` | **no silicon**; only `ssc029c` boots in qemu | | Ifado | `ssc375` | Dahua IPC-HDW1433V-A-IL2 NOR (IPL, CIS, IPL_CUST) | **no silicon**; qemu `ssc375`, to the vendor kernel's userspace | | Ifliegen | `ssc034a` `ssc034b`, NOR and SPI NAND | Ifliegen SDK ILSSV01C12 `board/ifliegen/boot` (IPL, IPL_CUST, TF-A `u-bl32.bin`, flash.nri/flash.sni, flash lists) | **no silicon**; qemu `ssc034a`/`ssc034b`, to the SDK kernel's shell | -| SPI NAND | `-nand` for every infinity2m, 6, 6b0, 6e, 6c part | published images, i2m and Maruko SDKs | **no silicon**; qemu | +| SPI NAND | `-nand` for every infinity2m, 6, 6b0, 6e, 6c part, `ssc029a`, `ssc034a/b` | the dies' NAND IPLs (published images, i2m, Maruko, Iford and Ifliegen SDKs) | **no silicon**; qemu, openipc-hisilicon's 768k/256k/UBIFS layout | OpenIPC/u-boot-sigmastar, the vendor's U-Boot 2015.01 with OpenIPC's environment, is what OpenIPC ships for the infinity6 dies today, and it @@ -155,39 +155,39 @@ IPL_CUST at `0x10000`, the uImage at `0x20000`. ### SPI NAND -`./build.sh -nand` adds `openipc/nand_config` and gives -`output/u-boot--nand.bin`, the 2 MiB of `make_boot_spinand.sh`: SNI at `0`, -PNI at `0x800`, IPL at `0x40000` and `0x80000`, IPL_CUST at `0xC0000` and -`0x100000`, the same uImage at `0x140000` and `0x1A0000`. The stages are the NOR -images' own: the published NAND images hold the same bytes, and -`openipc/-nand-{sni,pni}.bin` are the vendor's `GCIS.bin`, `PARTINFO.pni`. - -- Environment at `0x200000`, 128 KiB, in a 256 KiB range, as a saved one sits. -- UBI after it, as the firmware's `rootfs.ubi` lays it out: volumes `kernel`, - `rootfs` (a squashfs, not UBIFS), `rootfs_data`, `other`. - `CONFIG_OPENIPC_NAND_KERNEL_VOLUME` makes `common/openipc.c` check and - recover those, and not `/boot/fitImage` as on HiSilicon. -- `ms_spinand` takes geometry from the SNI, since the published images carry one - generic SNI whose ID is `0xEE`. The bad-block table stays in memory, like the - vendor's: nothing is written to the last blocks, which UBI owns. +The layout and environment of openipc-hisilicon (`include/configs/openipc-common.h`, +`openipc/nand_config`): `768k(boot),256k(env),-(ubi)`, the environment at +`0xC0000` (256 KiB, range 256 KiB), one UBI device from `0x100000` whose UBIFS +`rootfs` volume holds the kernel as `/boot/fitImage` or `/boot/uImage`, and +`rootfs_data` on the rest; `root=ubi0:rootfs rootfstype=ubifs ubi.mtd=2,2048`. +`common/openipc.c` is the common one (openipc-ingenic's, `OPENIPC_SPI_NAND` +taking `CONFIG_NAND_MS_SPINAND` too): the scan checks the UBI marker in the +first good eraseblock, the rootfs volume's UBIFS and its `/boot` kernel; `ur` +installs `rootfs.ubifs.`, sized to the image, keeping `rootfs_data`. + +`./build.sh -nand` gives `output/u-boot--nand.bin`, the boot +partition as far as it holds data (no `0xFF` padding, as the NOR images), one +copy of every stage, a stage a block: + +- infinity6, 6b0, 6e, infinity2m (the SNI's `MSTARSEMIUSFDCIS` form and the + older PNI, `(start block, blocks, type, backups)` records, a byte sum from + offset 4): block 0 the SNI (page 0) and PNI (page 1), IPL in block 1 + (BL0PBA 1, BL1PBA 0: infinity2m's IPL loses IPL_CUST with a BL1PBA set), + IPL_CUST in 2, U-Boot in 3-5. SNI byte `0x2E` is 2: infinity6's IPL_CUST + looks for U-Boot from the block after it; the other IPLs read the PNI. +- Ifliegen: block 0 the CIS (SNIB with the flash list's W25N01GV SNIF, + record 57, BL0PBA 1, one copy; the name-record PNI; the flash list), IPL, + IPL_CUST, TF-A's SP_MIN in blocks 1-3, U-Boot in 4-5. +- `ms_spinand` takes geometry from the SNI. The bad-block table stays in + memory, like the vendor's: nothing is written to the last blocks, which UBI + owns. A part whose SNI gives a spare other than 64 or 128 bytes is refused: + the core has no layout for it. - A page load holds the chip select by QSPI's burst bit, and OUT_SIZE counts one more than BDMA sends. Whole page and spare go by BDMA; the vendor sends those through the FSP, so a spare write takes a path it never does. -- No infinity6c or infinity2m NAND is published to compare against. Run - instead (qemu): `ssc377-nand` with mstar-06's Maruko kernel, the UBI - written by `run ur` over TFTP (`nand erase`/`write` through infinity6c's - software chip select), booted to `login:` with the `ubiblock` override; - `ssd202d-nand` with the i2m SDK kernel from a UBI, its UBIFS root mounted. - A part whose SNI gives a spare other than 64 or 128 bytes is refused: the - core has no layout for it. -- `root=` follows the partition count (`OPENIPC_NAND_ROOTDEV`): the published - kernels have gluebi, which makes each UBI volume an MTD after the partitions, - `kernel` then `rootfs`. So `mtdblock5` behind the default four partitions, - `mtdblock6` on infinity6c and Iford (five), `mtdblock4` on infinity2m (three). - A kernel without gluebi (Maruko's, the i2m and Iford SDKs') takes - `extras='ubi.block=0,rootfs root=/dev/ubiblock0_1 rootfstype=squashfs'` - (the later `root=` wins), or `root=ubi0:rootfs rootfstype=ubifs` for a - UBIFS rootfs. +- Not compatible with OpenIPC/u-boot-sigmastar's NAND layout (UBI at + `0x240000`, a `kernel` volume and a squashfs `rootfs`): a camera moving to + this U-Boot takes the firmware's `rootfs.ubifs` too, as HiSilicon's do. ### Iford @@ -259,13 +259,10 @@ not Iford's MCM write at bank `0x1132`), with: - SD: SD0, the board's mmc0 (`mmc_host/src/ifliegen`): PADGPIO pads from `0x0F`, card detect at `0x16`, power on PAD_FUART_RTS (`0x34`), the clock as Iford's but SC_GP_CTRL at bank `0x1133`, the host's MCG off. -- SPI NAND (`ssc034a-nand`, `ssc034b-nand`): `--ifliegen-sni` builds the CIS - in ten blocks (SNIB and SNIF, the PNI, the flash list, with snigenerator's - boot blocks 10 and 25 and CRCs), BOOT at `0x140000` (IPL and IPL_CUST three - blocks each, SP_MIN, a 1 MiB UBOOT), BOOT_BAK at `0x320000`, the - environment at `0x500000`, UBI from `0x540000`. The IPL takes its - timeouts from the SNIF: the CIS carries the flash list's W25N01GV, record - 57; the SDK's generic record has none and the IPL fails to load. +- SPI NAND (`ssc034a-nand`, `ssc034b-nand`): `--ifliegen-sni`, in OpenIPC's + 768 KiB (see "SPI NAND"). The IPL takes its timeouts from the SNIF: the CIS + carries the flash list's W25N01GV, record 57; the SDK's generic record has + none and the IPL fails to load. The SGS kernels (Iford, Ifliegen, Ifado: `drivers/sgs_common/flash/nor`) name the NOR `nor0`, not the older `NOR_FLASH`: `SFC` in @@ -278,31 +275,29 @@ write and read-back. NAND: the chain, `nand` erase/write/read against the raw file, env across reset, the SDK kernel from an OpenIPC UBI to a shell with the `ubiblock` override. -### Iford SPI NAND - -`ssc029a-nand` only: the one SDK spinand set for a part with plain Linux -(`ipc_iford.spinand...ssc029a.512.bga12_ddr4_optee`). Its `cis.bin` in the ten -CIS blocks (SNIB header, page 0; the SNIF of `flash_list-fb.sni` record 1, -W25N01JW, in place of the empty generic one; PNI, page 1), its IPL, IPL_CUST -and OP-TEE from `0x140000`, our uImage in UBOOT at `0x260000`, the environment at -`0x580000`. U-Boot runs after OP-TEE. `ms_spinand` reads the SNIB's geometry and -turns a W25N's buffer mode back on, which this IPL leaves off. qemu `ssc029a`: -the chain, `nand` erase/write/read against the raw file, env across reset. -Its `mtdparts` are `1280k(cis),2176k(boot0),2176k(boot1),256k(env),-(ubi)`: -each boot copy is IPL, IPL_CUST, OP-TEE (`0x60000` each) and UBOOT -(`0x100000`), so `env` lands at `0x580000` and `ubi` at `0x5C0000`, where -U-Boot keeps them. The SDK's 5.10 kernel, from a UBI of its `kernel` and -`rootfs.sqfs`, lists those five partitions and, without gluebi, mounts the -squashfs through the `ubiblock` override (qemu `ssc029a`). -029b and 029c have only RTOS/dual-OS NAND sets in the SDK; not built. - -### SPI NAND on infinity2m and infinity6c - -Nothing is published, so the layouts are the SDKs': infinity2m by its PNI -(stages repeated over their blocks, `writecis ... 10 0 0 5`: part record 2 in -blocks 0-8); infinity6c by Maruko's `spinand.squashfs` config, the SNI from its -flash list (record 1, GD5F1GQ4UEYIH) in all ten CIS blocks. A board with another -part needs its own record in page 0, as the SDK's `nand probe` writes it. +### SPI NAND on infinity6c and Iford + +Both read the name-record PNI, as Ifliegen does, and need more than a block a +stage allows: + +- infinity6c: its IPL reads a CIS copy past block 0, so the CIS is in blocks + 0 and 1 (the SNI the flash list's record 1, GD5F1GQ4UEYIH, BL0PBA 2, one + copy, its CRC32), IPL in block 2, IPL_CUST in 3, U-Boot in 4-5. The + generic `EE EE` SNI is refused. With the copy elsewhere (block 5) IPL_CUST + loads but reads U-Boot back failing its CRC; why, not found. +- Iford (`ssc029a-nand`, the one SDK spinand set with plain Linux, an OP-TEE + one): its IPL takes IPL_CUST, OP-TEE, then UBOOT by name, at any 2 KiB + page, which is what lets the 338 KB OP-TEE in: the CIS (the SDK cis.bin's + SNIB with W25N01JW's SNIF, BL0PBA 1, one copy, its CRCs; the PNI; its flash + list) in block 0, IPL at `0x20000`, IPL_CUST, OP-TEE and our uImage each + from the next page, U-Boot ending near `0xB8000`. Block-aligned, they would + need 1 MiB. `ms_spinand` turns a W25N's buffer mode back on, which this IPL + leaves off. 029b and 029c have only RTOS/dual-OS NAND sets in the SDK; not + built. +- A board with another part needs that part's record in page 0, as the SDK's + `nand probe` writes it: the IPLs take their timing from it. +- infinity6c's IPL reads the environment partition too and prints "env bad + CRC" for the format it expects; U-Boot's own environment is unaffected. ## Hand-off to the kernel @@ -528,13 +523,25 @@ upstream at the time). **Nothing here has run on silicon.** back to its 64 MiB values; this takes the largest row under the size (256 MiB), as for any size a table lacks. The other parts of these dies reach their prompts, and `ssd201`/`ssd202d` pass SD at `0x1410`. -- SPI NAND, on the same branch: `ssc325de`, `ssc337de` and `ssc338q` boot the - published `rootfs.ubi` to `login:`, also after `saveenv` + `reset`; `ssc30kq`, - with no firmware published, to U-Boot. On `ssc337de`: `nand` erase, write and - read against the raw file; an environment the vendor's U-Boot saved; a factory - bad block and `markbad`, surviving a reboot, UBI seeing both; an injected - uncorrectable read (-74), a corrected one and a failed erase; TFTP recovery - from blank; SD recovery from a FAT card, also on NOR (`ssc337`). +- SPI NAND, in the 768k/256k/UBIFS layout: from a blank flash, the scan + finds no UBI, `fwupd` takes `rootfs.ubifs.` over TFTP into a new + `rootfs` volume (and `rootfs_data`), the next scan finds `/boot/uImage`, + and the kernel mounts `root=ubi0:rootfs` to `login:`, again after `saveenv` + and `reset`: `ssc325de`, `ssc337de`, `ssc338q` with the published firmware's + kernel and rootfs (repacked as UBIFS, the kernel in `/boot`), `ssc377` with + mstar-06's Maruko kernel. A test UBIFS needs a journal its volume can hold + (`mkfs.ubifs -j`), or U-Boot refuses its superblock. That Maruko kernel has + no overlayfs, so `/init` leaves `/` the UBIFS rootfs and inittab remounts it + read-write; killed then, its journal is dirty and U-Boot's read-only UBIFS + cannot recover the master node, so the scan sends it to recovery, as on + HiSilicon. Earlier, in the vendor layout, on `ssc337de`: `nand` erase, + write and read against the raw file; a factory bad block and `markbad`, + surviving a reboot, UBI seeing both; an injected uncorrectable read (-74), a + corrected one and a failed erase; SD recovery from a FAT card, also on NOR + (`ssc337`). A bad first eraseblock in `ubi` still finds the marker. +- Every image, NOR and NAND, as CI's smoke test runs it (two boots to the + autoboot prompt, the second loading the environment the first saved), on + its own machine: 52 of 52. - **Not run on any machine:** media (ISP, encoders); `ssc378*` kernels; 4 KiB page NAND; the infinity2m card power pad as a real pad (PAD_TTL0, taken as already GPIO). A `ssc338q` at bond 0 (QFN88) boots U-Boot, but both the vendor's and From 9ad71b55a3fbbcbf07d57d6673e4ffaf8425bfbc Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sat, 7 Nov 2026 17:39:35 +0000 Subject: [PATCH 5/7] openipc: SigmaStar recovery checks the image's SoC, size and environment - NOR SD/USB recovery writes only the *.img files whose name carries CONFIG_PRODUCT_SOC as a word between '.' or '-': a card holding several cameras' images flashes this one's alone (uImage.ssc337.img is not ssc337de's). - NAND recovery reads rootfs.ubifs..img only if it fits the RAM below the relocated U-Boot, as the NOR image and script.txt paths do. - A saved altbootcmd of the retired form is migrated before recovery, so a script.txt that runs it gets the current one. openipc-sigmastar only for now; the other vendor branches follow. Reported by Qodo on OpenIPC/u-boot#109. --- common/openipc.c | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/common/openipc.c b/common/openipc.c index 84a3a3215612..23bcd1203c62 100644 --- a/common/openipc.c +++ b/common/openipc.c @@ -342,6 +342,11 @@ static int recovery_write(const char *name, int type) { return -1; } size = fsize; + if (size > recovery_ram_room()) { + printf("Recovery: %s (%lu bytes) does not fit the free RAM\n", name, + size); + return -1; + } } else { /* Kernel: uImage or FIT, written to the kernel volume */ ret = rfs_read(name, (ulong)&hdr, 0, sizeof(hdr), &actread); @@ -507,9 +512,25 @@ struct recovery_file { loff_t size; }; -/* Every *.img in the root of the medium, in the order the directory lists - * them. The names are collected first: reading a file closes the - * filesystem under an open directory stream. */ +/* Whether a file is this SoC's: its name carries CONFIG_PRODUCT_SOC as a + * whole word between '.' or '-' (uImage.ssc337.img, not ssc337de's), so a + * card holding several cameras' images flashes only this one's. */ +static int recovery_name_is_ours(const char *name) { + const char *soc = CONFIG_PRODUCT_SOC, *p = name; + size_t n = strlen(soc); + + while ((p = strstr(p, soc))) { + if ((p == name || p[-1] == '.' || p[-1] == '-') && + (p[n] == '.' || p[n] == '-')) + return 1; + p++; + } + return 0; +} + +/* Every *.img of this SoC in the root of the medium, in the order the + * directory lists them. The names are collected first: reading a file + * closes the filesystem under an open directory stream. */ static int recovery_images(void) { struct recovery_file *files = NULL, *more; struct fs_dir_stream *dirs; @@ -527,6 +548,11 @@ static int recovery_images(void) { if (d->type != FS_DT_REG || l < 5 || l >= sizeof(files->name) || strcasecmp(d->name + l - 4, ".img")) continue; + if (!recovery_name_is_ours(d->name)) { + printf("Recovery: %s is not for %s, skipped\n", d->name, + CONFIG_PRODUCT_SOC); + continue; + } more = realloc(files, (n + 1) * sizeof(*files)); if (!more) break; @@ -1131,6 +1157,10 @@ int openipc_helper(void) { nor_split_fwsize(); #endif + /* A saved environment's retired commands are replaced first: a + * script.txt that runs them would otherwise get the old ones */ + altbootcmd_migrate(); + /* Recovery checks what it runs: a board may already have set verify=n * (so that the split scan below can clear it), and source would then run * a wrapped script.txt without its data CRC. Drop it for recovery only @@ -1144,7 +1174,6 @@ int openipc_helper(void) { env_set("verify", saved); free(saved); } - altbootcmd_migrate(); /* Persist the default environment on first start (or after a bad env); * only if the device env area is still blank so we never overwrite From 631d8991cc13205f583e90d0649371819824551f Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sat, 7 Nov 2026 17:39:35 +0000 Subject: [PATCH 6/7] openipc: SigmaStar NAND env survives a bad block; ubwrite stays in boot - The environment is 128 KiB in the 256 KiB env partition: with CONFIG_ENV_SIZE equal to its range, one bad block there left saveenv nowhere to go and every boot on the default environment. - ubwrite writes only an image that fits the 768 KiB boot partition, checked before it erases (a failed write after the erase left no IPL), and into the partition by name, so bad blocks cannot push it into env. The firmware's fw_env.config for these cameras takes the 128 KiB size. qemu ssc337de: an oversize ub refused with the boot intact; the env block at 0xC0000 marked bad, saveenv to 0xE0000, loaded after reset. Reported by Qodo on OpenIPC/u-boot#109. --- include/configs/openipc-common.h | 7 ++++--- openipc/nand_config | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/include/configs/openipc-common.h b/include/configs/openipc-common.h index b870f41ce958..56e4e59951ab 100644 --- a/include/configs/openipc-common.h +++ b/include/configs/openipc-common.h @@ -201,8 +201,9 @@ #define OPENIPC_NAND_MTDPARTS "768k(boot),256k(env),-(ubi)" /* Environment lives in the NAND 'env' partition: CONFIG_ENV_OFFSET 0xc0000, - * CONFIG_ENV_SIZE and CONFIG_ENV_RANGE 0x40000 (openipc/nand_config), as - * on openipc-hisilicon. */ + * CONFIG_ENV_RANGE 0x40000 as on openipc-hisilicon, CONFIG_ENV_SIZE 0x20000 + * so that a bad block in the partition leaves room for it + * (openipc/nand_config). */ #define CONFIG_BOOTARGS OPENIPC_MEMARGS_HEAD "console=ttyS0,115200 panic=20 init=/init root=ubi0:rootfs rootfstype=ubifs ubi.mtd=2,2048 mtdparts=\\${mtdparts}" OPENIPC_MEMARGS_TAIL " \\${extras}" @@ -224,7 +225,7 @@ #define OPENIPC_ENV_MEDIUM \ "ubfile=u-boot-" CONFIG_PRODUCT_SOC "-nand.bin\0" \ "urfile=rootfs.ubifs." CONFIG_PRODUCT_SOC "\0" \ - "ubwrite=nand erase 0x0 0xc0000; nand write ${baseaddr} 0x0 ${filesize}\0" \ + "ubwrite=if itest ${filesize} -gt 0 && itest ${filesize} -le 0xc0000; then nand erase.part boot; nand write ${baseaddr} boot ${filesize}; else echo ubwrite: ${filesize} bytes do not fit the 768 KiB boot partition, nothing written; fi\0" \ "urwrite=ubi part ubi; ubi check rootfs && ubi remove rootfs; if ubi create rootfs ${filesize} d; then ubi check rootfs; else ubi remove rootfs_data; ubi create rootfs ${filesize} d; fi; ubi check rootfs_data || ubi create rootfs_data 0 d; ubi write ${baseaddr} rootfs ${filesize}\0" \ "loadkernel=" OPENIPC_LOADKERNEL "\0" \ "mtdids=nand0=" SFC "\0" \ diff --git a/openipc/nand_config b/openipc/nand_config index 70ce2f5b1829..a1913ff486ae 100644 --- a/openipc/nand_config +++ b/openipc/nand_config @@ -13,7 +13,8 @@ CONFIG_CMD_NAND_TRIMFFS=y # CONFIG_CMD_NAND_TORTURE is not set CONFIG_ENV_IS_IN_NAND=y CONFIG_ENV_OFFSET=0xc0000 -CONFIG_ENV_SIZE=0x40000 +# 128 KiB in the 256 KiB partition: one bad block there leaves the other +CONFIG_ENV_SIZE=0x20000 CONFIG_ENV_RANGE=0x40000 CONFIG_MTD_PARTITIONS=y CONFIG_CMD_MTDPARTS=y From d8bf7e2ac13124467a878920085e3c08fb341687 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sat, 7 Nov 2026 17:39:35 +0000 Subject: [PATCH 7/7] docs: CLAUDE.md: where SigmaStar's NAND and recovery are ahead of the other branches --- CLAUDE.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index bde8ec104755..b34caaa6cd8a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -157,7 +157,8 @@ IPL_CUST at `0x10000`, the uImage at `0x20000`. The layout and environment of openipc-hisilicon (`include/configs/openipc-common.h`, `openipc/nand_config`): `768k(boot),256k(env),-(ubi)`, the environment at -`0xC0000` (256 KiB, range 256 KiB), one UBI device from `0x100000` whose UBIFS +`0xC0000` (128 KiB in the 256 KiB range, so one bad block there leaves the +other; openipc-hisilicon's is 256 KiB in 256), one UBI device from `0x100000` whose UBIFS `rootfs` volume holds the kernel as `/boot/fitImage` or `/boot/uImage`, and `rootfs_data` on the rest; `root=ubi0:rootfs rootfstype=ubifs ubi.mtd=2,2048`. `common/openipc.c` is the common one (openipc-ingenic's, `OPENIPC_SPI_NAND` @@ -185,6 +186,15 @@ copy of every stage, a stage a block: - A page load holds the chip select by QSPI's burst bit, and OUT_SIZE counts one more than BDMA sends. Whole page and spare go by BDMA; the vendor sends those through the FSP, so a spare write takes a path it never does. +- Where this branch is ahead of the other vendor branches (Qodo on + OpenIPC/u-boot#109; they follow later): `ubwrite` writes only an image + that fits the boot partition, checked before the erase, and into the + partition, so a bad block cannot push it into `env`; recovery takes a + NAND image only if it fits the free RAM, on NOR only files whose name + carries this SoC as a word (`uImage.ssc337.img` is not `ssc337de`'s); + a saved `altbootcmd` is migrated before recovery may run it. A bad block + in blocks 1-5 still moves the stages off the blocks the PNI names: one + copy each is what 768 KiB holds. - Not compatible with OpenIPC/u-boot-sigmastar's NAND layout (UBI at `0x240000`, a `kernel` volume and a squashfs `rootfs`): a camera moving to this U-Boot takes the firmware's `rootfs.ubifs` too, as HiSilicon's do.