From 5689a62fa81eb846a069a0bef3518a0bfc0b8e17 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:21:27 +0000 Subject: [PATCH 1/5] openipc: common/openipc.c as on openipc-hisilicon, with the generic recovery The file is meant to be the same on every branch. Take openipc-hisilicon's, together with the recovery change proposed there (OpenIPC/u-boot recovery/wrapper-payload), byte for byte: - script.txt on the card or stick still runs first; - otherwise every *.img in its root that is a valid uImage whose load address and size fit the flash, on an erase boundary, gets its payload written there -- never the wrapper header -- U-Boot at 0 included; - an image already on the flash is skipped, and the boot goes on. The NAND gate is CONFIG_FMC_SPI_NAND again, as there; the next commit drops this branch's NAND images, which used CONFIG_ENV_IS_IN_NAND. Under qemu-hisilicon xm530 from an SD card, two boots: a kernel image is written at 0x50000, a bad data CRC and a non-uImage are skipped, U-Boot and then the kernel are found already on the flash, and the saved environment loads. --- common/openipc.c | 447 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 361 insertions(+), 86 deletions(-) diff --git a/common/openipc.c b/common/openipc.c index 00c363de3dbf..e6775475099d 100644 --- a/common/openipc.c +++ b/common/openipc.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #ifdef CONFIG_USB #include #endif @@ -20,7 +22,7 @@ #ifdef CONFIG_ENV_IS_IN_NAND #include #endif -#ifdef CONFIG_ENV_IS_IN_NAND +#ifdef CONFIG_FMC_SPI_NAND #include #include #include @@ -29,8 +31,9 @@ #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#" @@ -40,14 +43,14 @@ #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 +#ifdef CONFIG_FMC_SPI_NAND #define UBIFS_SB_MAGIC 0x06101831 static int check_ubifs(void *buf) { @@ -76,7 +79,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; @@ -91,7 +94,7 @@ static int check_kernel(void *buf) { return 0; } -#ifndef CONFIG_ENV_IS_IN_NAND +#ifndef CONFIG_FMC_SPI_NAND static uint64_t get_squashfs_size(void *buf) { uint32_t magic = get_unaligned_be32(buf); @@ -118,10 +121,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; @@ -135,9 +147,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; @@ -156,8 +169,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)) { @@ -183,21 +208,53 @@ static int scan_spi_device(struct spi_flash *flash, ulong *kernel_off, ulong *ro return 0; } -#endif /* !CONFIG_ENV_IS_IN_NAND */ +#endif /* !CONFIG_FMC_SPI_NAND */ #if (defined(CONFIG_MMC) || defined(CONFIG_USB_STORAGE)) && defined(CONFIG_FS_FAT) #define RECOVERY_KERNEL 0 #define RECOVERY_ROOTFS 1 +static const char *recovery_if, *recovery_part; + +/* 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) { - if (!fs_set_blk_dev(ifname, "0:1", FS_TYPE_FAT)) + 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)) + } + if (!fs_set_blk_dev(ifname, "0", FS_TYPE_FAT)) { + recovery_part = "0"; return 0; + } return -1; } -#ifdef CONFIG_ENV_IS_IN_NAND +/* + * 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); +} + +#ifdef CONFIG_FMC_SPI_NAND static int recovery_ensure_volume(const char *name, const char *size) { struct ubi_volume_desc *desc; char cmd[64]; @@ -252,14 +309,14 @@ static int recovery_write(const char *name, int type) { ulong size; int ret; - if (!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 = fs_size(name, &fsize); + ret = rfs_size(name, &fsize); if (ret || fsize == 0) { printf("Recovery: failed to stat %s\n", name); return -1; @@ -267,7 +324,7 @@ static int recovery_write(const char *name, int type) { size = fsize; } else { /* Kernel: uImage or FIT, written to the kernel volume */ - ret = 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; @@ -283,7 +340,7 @@ static int recovery_write(const char *name, int type) { } } - ret = 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; @@ -338,50 +395,128 @@ static int recovery_write(const char *name, int type) { #else static struct spi_flash *recovery_flash; -static int recovery_write(const char *name, int type) { - struct image_header hdr; - loff_t actread; - ulong addr, size, erase_len; - int ret; +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 = 1; - if (!fs_exists(name)) + if (!buf) return 0; - - ret = fs_read(name, (ulong)&hdr, 0, sizeof(hdr), &actread); - if (ret || actread != sizeof(hdr)) { - printf("Recovery: failed to read %s\n", name); - return -1; + 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, (const u8 *)data + off, n); } + free(buf); + return same; +} - if (image_check_magic(&hdr)) { - addr = image_get_load(&hdr); - size = sizeof(hdr) + image_get_data_size(&hdr); - } else if (type == RECOVERY_KERNEL && fdt_magic(&hdr) == FDT_MAGIC && - fdt_check_header(&hdr) == 0) { - const char *str = env_get("kernaddr"); +/* + * 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_img(const char *name, loff_t size) { + struct image_header hdr; + loff_t actread; + ulong addr, len, sect = recovery_flash->sector_size; - addr = str ? hextoul(str, NULL) : OPENIPC_KERNADDR; - size = fdt_totalsize(&hdr); - } else { - printf("Recovery: no valid uImage/FIT header in %s\n", name); - return -1; + 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; + } + addr = image_get_load(&hdr); + len = image_get_data_size(&hdr); + /* 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 (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; + } + /* in RAM below the stack and the relocated U-Boot, with room */ + if (len > gd->start_addr_sp - SZ_1M - CONFIG_SYS_LOAD_ADDR) { + 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; } - ret = fs_read(name, CONFIG_SYS_LOAD_ADDR, 0, size, &actread); - if (ret || actread != size) { - printf("Recovery: failed to read %s\n", name); + 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; +} - erase_len = (size + recovery_flash->sector_size - 1) & - ~(recovery_flash->sector_size - 1); - spi_flash_erase(recovery_flash, addr, erase_len); - spi_flash_write(recovery_flash, addr, size, (void *)CONFIG_SYS_LOAD_ADDR); - - 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 @@ -392,16 +527,16 @@ static int recovery_run_script(const char *name) { int ret; char cmd[32]; - if (!fs_exists(name)) + if (!rfs_exists(name)) return 0; - ret = fs_size(name, &size); + ret = rfs_size(name, &size); if (ret || size == 0) { printf("Recovery: failed to stat %s\n", name); return 1; } - ret = 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; @@ -422,45 +557,28 @@ static int recovery_run_script(const char *name) { } static int recovery_try(void) { +#ifdef CONFIG_FMC_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; -#ifndef CONFIG_ENV_IS_IN_NAND - /* 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 CONFIG_FMC_SPI_NAND /* Rootfs. On NAND it carries the kernel (/boot), its volume is sized * to the image and rootfs_data takes the rest. */ -#ifdef CONFIG_ENV_IS_IN_NAND 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 CONFIG_FMC_SPI_NAND recovery_flash = spi_flash_probe(CONFIG_SF_DEFAULT_BUS, CONFIG_SF_DEFAULT_CS, CONFIG_SF_DEFAULT_SPEED, CONFIG_SF_DEFAULT_MODE); @@ -483,7 +601,7 @@ int firmware_recovery(void) { #endif fs_close(); -#ifndef CONFIG_ENV_IS_IN_NAND +#ifndef CONFIG_FMC_SPI_NAND spi_flash_free(recovery_flash); #endif @@ -495,8 +613,77 @@ int firmware_recovery(void) { } #endif +#if !defined(CONFIG_FMC_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)) + return 0; + return rootfs_off == kernel_off + ALIGN(image_get_image_size(&hdr), 0x10000); +} +#endif + int firmware_scan(void) { -#ifdef CONFIG_ENV_IS_IN_NAND +#ifdef CONFIG_FMC_SPI_NAND u8 buf[64]; int ret; @@ -559,13 +746,101 @@ 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) : CONFIG_ENV_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", "sf probe 0; sf erase ${kernaddr} ${fwsize}; " + "sf write ${baseaddr} ${kernaddr} ${filesize}"); + } + } +#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; + + /* + * 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(); + env_set("bootfail", NULL); + return 0; + } #endif spi_flash_free(flash); From 045e6f6360f43f74034225ffac81ff4c06c5b8f2 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:21:27 +0000 Subject: [PATCH 2/5] xm: drop the SPI NAND images No XM530/XM550 board with SPI NAND has turned up, the OpenIPC XM530 kernel has no SPI NAND driver, and the images rested on a guess at how the boot ROM starts from NAND that only qemu-hisilicon's model, built on the same guess, ever ran. Remove the xmsfc_nand.c driver and its hook, openipc/nand_config, the NAND build in build.sh and CONFIG_SYS_MAX_NAND_DEVICE. build.sh builds u-boot--nor.bin for xm510, xm530 and xm550 only (all three build). CI, on the openipc branch, needs the matching change (ci/xm-nor-only). --- CLAUDE.md | 50 +--- build.sh | 23 +- drivers/mtd/nand/raw/Kconfig | 9 - drivers/mtd/nand/raw/Makefile | 1 - drivers/mtd/nand/raw/xmsfc_nand.c | 431 ------------------------------ include/configs/xm530.h | 3 - openipc/nand_config | 26 -- 7 files changed, 14 insertions(+), 529 deletions(-) delete mode 100644 drivers/mtd/nand/raw/xmsfc_nand.c delete mode 100644 openipc/nand_config diff --git a/CLAUDE.md b/CLAUDE.md index e37177571f31..b9177c1675b9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,16 +42,14 @@ git diff --diff-filter=M v2022.10 HEAD # the mainline files we patched | SoC: PLL and DDR bring-up, timer, reset, DRAM size, SDHCI glue | `arch/arm/mach-xm/` (`ARCH_XM`; `TARGET_XM510`/`XM530`/`XM550`; register map in `include/mach/xm.h`) | | Boards: `board_init`, `misc_init_r` → `openipc_helper()`, `board_eth_init` with the PHY reset / MAC glue | `board/xiongmai/{xm510,xm530}/`. XM550 uses the xm530 board with `XM_DDR3` | | Memory map, console, timer | `include/configs/{xm510,xm530}.h`, each ending in `#include "openipc-common.h"` | -| SPI NAND on the v2 controller (XM530/550): raw NAND, full-ID table, on-die ECC status through `ecc.read_page` | `drivers/mtd/nand/raw/xmsfc_nand.c` (`NAND_XMSFC`) | | SPI NOR controller, v1 (XM510) and v2 (XM530/550), non-DM | `drivers/mtd/spi/xmsfc.c`. It provides `spi_flash_probe()` and fills `mtd->_read/_write/_erase`, so mainline's non-DM `spi_flash_*()` wrappers, `sf` and env-in-SPI-flash work without the SPI and spi-nor layers | -| OpenIPC boot layer | `common/openipc.c` and `include/openipc.h` (copied from `openipc-hisilicon`, NAND gate rekeyed to `CONFIG_ENV_IS_IN_NAND`), `include/configs/openipc-common.h` (XM-only rewrite, same env commands) | +| OpenIPC boot layer | `common/openipc.c` and `include/openipc.h` (identical to `openipc-hisilicon`'s), `include/configs/openipc-common.h` (XM-only rewrite, same env commands) | | Kconfig fragments and the build | `openipc/`, `build.sh` | **Patched mainline files**, each small: - `arch/arm/{Kconfig,Makefile}`: the `ARCH_XM` hook. - `common/{Kconfig,Makefile}`: `OPENIPC`, `PRODUCT_SOC`, `PRODUCT_SOCMODEL`. -- `drivers/mtd/nand/raw/{Kconfig,Makefile}`: the `NAND_XMSFC` hook. - `drivers/mtd/spi/{Kconfig,Makefile}`, `cmd/Kconfig` (`CMD_SF`) and `env/Kconfig` (`ENV_IS_IN_SPI_FLASH`): accept `XMSFC_SPI_NOR`. - `drivers/net/Kconfig`: `NETDEVICES` selects `DM_ETH` only when `DM` is on. @@ -96,33 +94,11 @@ position-independent fixups. The only RAM before DDR is a 4 KiB SRAM at driver restores it (`xmsfc_bus_mode()`) after every v2 operation, and only ever runs after relocation, from DDR. -## SPI NAND (XM530/XM550): built on a guess +## No SPI NAND -Xiongmai never shipped a NAND U-Boot from this SDK. There is a -`CONFIG_SYS_USE_SPI_NAND` block in the vendor `xm530.h`, but no board target, -and nothing documents how the boot ROM starts from SPI NAND. - -**What the NAND image assumes:** - -- the controller's window serves the NAND page data linearly, as in NOR - bus-read mode; -- U-Boot is linked at 0x60001000 (the vendor's NAND `TEXT_BASE`), behind a - 4 KiB header; -- the header's first word is `ldr pc, [pc, #-4]` followed by that address, so - it reaches U-Boot whether the ROM starts at the window or the 0x08000000 - alias (`build.sh`). - -**What it carries:** - -- layout `768k(boot),256k(env),-(ubi)`; -- the environment at 0xc0000; -- the kernel at `/boot/uImage` inside the UBIFS `rootfs` volume; -- the same recovery as on `openipc-hisilicon` NAND. - -Only qemu-hisilicon's NAND model, which implements the same guess, has run -it. The OpenIPC XM530 kernel has no SPI NAND driver either, so a NAND image -loads and starts the kernel, which then cannot mount its root. Treat NAND as -unverified on silicon, and say so wherever it is offered. +There are only NOR images. No XM530/XM550 board with SPI NAND has turned +up, and the OpenIPC XM530 kernel has no SPI NAND driver. `openipc.c`'s NAND +paths (`CONFIG_FMC_SPI_NAND`) are never built here. ## Build @@ -131,10 +107,10 @@ bash build.sh # xm510 xm530 xm550; continues past failures, exits bash build.sh xm530 # one or more SoCs (CROSS_COMPILE defaults to arm-linux-gnueabi-) ``` -- Output: `output/u-boot--nor.bin`, plus `-nand.bin` for xm530/xm550, - built out of tree in `output/build--/`. +- Output: `output/u-boot--nor.bin`, built out of tree in + `output/build--nor/`. - `.config` is `openipc/openipc_config`, then `openipc/_config`, then - `openipc/nand_config` for NAND images, then `olddefconfig`. A later line + `olddefconfig`. A later line wins. The "override" warnings are that layering. - There are no `configs/*_defconfig` for these targets. - The host toolchain must match CI (Ubuntu `gcc-arm-linux-gnueabi`). A Docker @@ -162,12 +138,10 @@ with the reset vector at the window or alias, exactly as silicon does. bootloaders leave it, with per-block locks on. `xmsfc_unprotect()` must clear it before writes land. -- **NAND:** `-global xm-sfc.nand-file=`, with no `flash-file`. - - The raw image is pages of 2048 + 64 bytes, U-Boot at page 0, and - `ubinize`'s output at 1 MiB. - - It must reach the prompt, save the env, mount the rootfs and start the - kernel. - - From an erased NAND, TFTP recovery must create the UBI volumes and boot. +- **SD recovery:** a FAT card (`-drive if=sd,file=,format=raw`) with + `*.img` files: each valid uImage whose load address and size fit the flash + has its payload written there unless it is already on the flash; the + boot then goes on. **What qemu does not check:** DDR training values, the v2 bus-read restore and PHY/MDIO timing are acked instantly. A change to those needs a real board. diff --git a/build.sh b/build.sh index 34f7a462453b..1860d1fb25e2 100755 --- a/build.sh +++ b/build.sh @@ -31,39 +31,20 @@ build_xm() { builddir=${BOOT_SRCDIR}/output/build-${soc}-${kind} make_opts="-C ${BOOT_SRCDIR} O=${builddir} -j$(nproc)" fragments="openipc/openipc_config openipc/${soc}_config" - [ "${kind}" = nand ] && fragments="${fragments} openipc/nand_config" rm -rf "${builddir}" mkconfig "${builddir}" ${fragments} && \ make ${make_opts} olddefconfig /dev/null && \ make ${make_opts} &2 return 1 ;; diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig index a83de6db7ada..ce67d1abde25 100644 --- a/drivers/mtd/nand/raw/Kconfig +++ b/drivers/mtd/nand/raw/Kconfig @@ -446,15 +446,6 @@ config NAND_MXIC help This selects the Macronix raw NAND controller driver. -config NAND_XMSFC - bool "Xiongmai xmsfc v2 SPI NAND" - depends on ARCH_XM && XM_SFC_V2 - select SYS_NAND_SELF_INIT - help - SPI NAND behind the XM530/XM550 flash controller, as a raw NAND - device: the controller runs the SPI NAND commands, the part's - on-die ECC corrects. - config NAND_ZYNQ bool "Support for Zynq Nand controller" select SPL_SYS_NAND_SELF_INIT diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile index 6aa79e17385f..a398aa9d8864 100644 --- a/drivers/mtd/nand/raw/Makefile +++ b/drivers/mtd/nand/raw/Makefile @@ -77,7 +77,6 @@ obj-$(CONFIG_NAND_PLAT) += nand_plat.o obj-$(CONFIG_NAND_SUNXI) += sunxi_nand.o obj-$(CONFIG_NAND_MXIC) += mxic_nand.o obj-$(CONFIG_NAND_ZYNQ) += zynq_nand.o -obj-$(CONFIG_NAND_XMSFC) += xmsfc_nand.o obj-$(CONFIG_NAND_STM32_FMC2) += stm32_fmc2_nand.o obj-$(CONFIG_CORTINA_NAND) += cortina_nand.o obj-$(CONFIG_ROCKCHIP_NAND) += rockchip_nfc.o diff --git a/drivers/mtd/nand/raw/xmsfc_nand.c b/drivers/mtd/nand/raw/xmsfc_nand.c deleted file mode 100644 index 5cebc1936840..000000000000 --- a/drivers/mtd/nand/raw/xmsfc_nand.c +++ /dev/null @@ -1,431 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * SPI NAND on the Xiongmai XM530/XM550 flash controller (xmsfc v2). - * - * The controller runs SPI NAND commands itself through four operations: - * OP1 an addressed command without data (page read to the chip's cache, - * program execute, block erase, the row in ADDR), OP2 a bare command - * (reset), OP3 a read into the 4 KiB RAM at 0x100F6000 or R_SR_ID, OP4 a - * write out of the RAM or SRW. A page and its spare area pass through the - * RAM whole, at their column offsets. - * - * From the vendor U-Boot 2014.04 drivers/mtd/nand/xmsfc_v2_nand.c, as a - * legacy raw NAND driver: cmdfunc instead of the vendor's byte-by-byte - * cmd_ctrl, a full-ID table instead of a patch to nand_base.c, and the - * part's on-die ECC status (feature C0) reported through ecc.read_page. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define V2_CTRL (XM_SFC_BASE + 0x00) -#define V2_CFG1 (XM_SFC_BASE + 0x04) -#define V2_CFG2 (XM_SFC_BASE + 0x08) -#define V2_ADDR (XM_SFC_BASE + 0x0C) -#define V2_OP (XM_SFC_BASE + 0x10) -#define V2_CMD (XM_SFC_BASE + 0x14) -#define V2_SRW (XM_SFC_BASE + 0x18) -#define V2_RW_NUM (XM_SFC_BASE + 0x1C) -#define V2_TDEL (XM_SFC_BASE + 0x28) -#define V2_ST (XM_SFC_BASE + 0x2C) -#define V2_R_SR_ID (XM_SFC_BASE + 0x30) -#define V2_RAM_SIZE 0x1000 - -/* - * CFG1: bit 0 WREN first, bit 1 the reset delay, bit 2 address, bit 3 - * dummy byte. CFG2: bit 0 data through the RAM, bit 1 poll until ready. - */ -#define CFG1_WREN BIT(0) -#define CFG1_TDEL BIT(1) -#define CFG1_ADDR BIT(2) -#define CFG1_DUMMY BIT(3) -#define CFG2_RAM BIT(0) -#define CFG2_POLL BIT(1) - -#define SPINAND_GET_FEATURE 0x0F -#define SPINAND_SET_FEATURE 0x1F -#define SPINAND_PROG_EXEC 0x10 -#define SPINAND_PAGE_READ 0x13 -#define SPINAND_PROG_LOAD 0x02 -#define SPINAND_READ_CACHE 0x03 -#define SPINAND_READ_ID 0x9F -#define SPINAND_BLOCK_ERASE 0xD8 -#define SPINAND_RESET 0xFF - -#define FEAT_PROT 0xA0 -#define FEAT_STATUS 0xC0 -#define STATUS_OIP BIT(0) -#define STATUS_E_FAIL BIT(2) -#define STATUS_P_FAIL BIT(3) -#define STATUS_ECC_MASK (3 << 4) -#define STATUS_ECC_FIXED BIT(4) - -/* The NAND bus: pads 93, 94 and 96-99 (NOR uses 9-14) */ -static const u8 xmsfc_nand_pads[] = { 93, 94, 96, 97, 98, 99 }; - -/* Parts in the vendor table (xmsfc_nand_params.c), by their full ID */ -static struct nand_flash_dev xmsfc_nand_ids[] = { - { "W25N01GV", { .id = { 0xef, 0xaa, 0x21 } }, - SZ_2K, SZ_128, SZ_128K, 0, 3, 64 }, - { "W25N02GV", { .id = { 0xef, 0xab, 0x21 } }, - SZ_2K, SZ_256, SZ_128K, 0, 3, 64 }, - { "GD5F1GQ4U", { .id = { 0xc8, 0xd1 } }, - SZ_2K, SZ_128, SZ_128K, 0, 2, 128 }, - { NULL } -}; - -struct xmsfc_nand { - struct nand_chip chip; - u8 buf[V2_RAM_SIZE]; /* the page and its spare area */ - unsigned int pos; /* read_byte/read_buf/write_buf position */ - int page; /* page of the pending program */ - u8 id[8]; - u8 status; /* feature C0 after the last page read */ - unsigned int last_cmd; -}; - -static struct xmsfc_nand xmsfc_nand; - -/* - * Run one controller operation; a controller that never finishes is - * reported, not waited on forever - */ -static int xmsfc_run(u32 op) -{ - ulong start = get_timer(0); - - writel(op, V2_OP); - while (!(readl(V2_ST) & BIT(op - 1))) { - if (get_timer(start) > 100) { - printf("xmsfc-nand: operation %u timed out\n", op); - return -ETIMEDOUT; - } - } - return 0; -} - -static void xmsfc_bus_mode(void) -{ - writel(CFG1_ADDR, V2_CFG1); - writel(CFG2_POLL, V2_CFG2); -} - -static u8 xmsfc_get_feature(u8 reg) -{ - writel(CFG1_ADDR, V2_CFG1); - writel(0, V2_CFG2); - writel(SPINAND_GET_FEATURE, V2_CMD); - writel(reg, V2_ADDR); - writel(0, V2_RW_NUM); - xmsfc_run(3); - return readl(V2_R_SR_ID) & 0xff; -} - -static void xmsfc_set_feature(u8 reg, u8 val) -{ - writel(CFG1_ADDR, V2_CFG1); - writel(0, V2_CFG2); - writel(SPINAND_SET_FEATURE, V2_CMD); - writel(reg, V2_ADDR); - writel(val, V2_SRW); - writel(0, V2_RW_NUM); - xmsfc_run(4); -} - -/* - * Wait for the part to finish (feature C0 OIP clear), at most 100 ms -- - * a block erase takes a few - */ -static int xmsfc_wait_ready(void) -{ - ulong start = get_timer(0); - - do { - if (!(xmsfc_get_feature(FEAT_STATUS) & STATUS_OIP)) - return 0; - udelay(1); - } while (get_timer(start) < 100); - printf("xmsfc-nand: still busy after 100 ms\n"); - return -ETIMEDOUT; -} - -/* An addressed command without data: page read, program execute, erase */ -static int xmsfc_row_cmd(u8 cmd, u32 row, u32 cfg1) -{ - int ret; - - writel(cfg1, V2_CFG1); - writel(CFG2_POLL, V2_CFG2); - writel(cmd, V2_CMD); - writel(row, V2_ADDR); - ret = xmsfc_run(1); - return ret ? ret : xmsfc_wait_ready(); -} - -static void xmsfc_read_page(struct mtd_info *mtd, int page) -{ - struct xmsfc_nand *host = &xmsfc_nand; - const unsigned int len = mtd->writesize + mtd->oobsize; - - if (xmsfc_row_cmd(SPINAND_PAGE_READ, page, CFG1_ADDR)) { - /* Report the page as uncorrectable rather than stale data */ - memset(host->buf, 0xff, len); - host->status = STATUS_ECC_MASK; - xmsfc_bus_mode(); - return; - } - host->status = xmsfc_get_feature(FEAT_STATUS); - - writel(CFG1_ADDR | CFG1_DUMMY, V2_CFG1); - writel(CFG2_RAM, V2_CFG2); - writel(SPINAND_READ_CACHE, V2_CMD); - writel(0, V2_ADDR); - writel(len - 1, V2_RW_NUM); - if (xmsfc_run(3)) { - memset(host->buf, 0xff, len); - host->status = STATUS_ECC_MASK; - } else { - memcpy_fromio(host->buf, (void *)XM_SFC_RAM, len); - } - xmsfc_bus_mode(); -} - -static int xmsfc_program_page(struct mtd_info *mtd, int page) -{ - struct xmsfc_nand *host = &xmsfc_nand; - const unsigned int len = mtd->writesize + mtd->oobsize; - unsigned int i; - u8 status; - - /* Unprotect, as the vendor does before every program and erase */ - xmsfc_set_feature(FEAT_PROT, 0); - - for (i = 0; i < len; i += 4) - writel(get_unaligned_le32(host->buf + i), XM_SFC_RAM + i); - writel(CFG1_WREN | CFG1_ADDR, V2_CFG1); - writel(CFG2_RAM, V2_CFG2); - writel(SPINAND_PROG_LOAD, V2_CMD); - writel(0, V2_ADDR); - writel(len - 1, V2_RW_NUM); - if (xmsfc_run(4) || - xmsfc_row_cmd(SPINAND_PROG_EXEC, page, CFG1_ADDR)) { - xmsfc_bus_mode(); - return NAND_STATUS_FAIL; - } - status = xmsfc_get_feature(FEAT_STATUS); - xmsfc_bus_mode(); - return status & STATUS_P_FAIL ? NAND_STATUS_FAIL : 0; -} - -static int xmsfc_erase_block(int page) -{ - u8 status; - - xmsfc_set_feature(FEAT_PROT, 0); - if (xmsfc_row_cmd(SPINAND_BLOCK_ERASE, page, CFG1_WREN | CFG1_ADDR)) { - xmsfc_bus_mode(); - return NAND_STATUS_FAIL; - } - status = xmsfc_get_feature(FEAT_STATUS); - xmsfc_bus_mode(); - return status & STATUS_E_FAIL ? NAND_STATUS_FAIL : 0; -} - -static void xmsfc_read_id(void) -{ - struct xmsfc_nand *host = &xmsfc_nand; - u32 id; - - /* 9F, one address (dummy) byte, three ID bytes: first byte highest */ - writel(CFG1_ADDR, V2_CFG1); - writel(0, V2_CFG2); - writel(SPINAND_READ_ID, V2_CMD); - writel(0, V2_ADDR); - writel(2, V2_RW_NUM); - memset(host->id, 0, sizeof(host->id)); - if (xmsfc_run(3)) { - /* No ID: nand_scan_ident() then finds no chip */ - xmsfc_bus_mode(); - return; - } - id = readl(V2_R_SR_ID); - host->id[0] = id >> 16; - host->id[1] = id >> 8; - host->id[2] = id; - xmsfc_bus_mode(); -} - -static void xmsfc_reset(void) -{ - writel(CFG1_TDEL, V2_CFG1); - writel(0, V2_CFG2); - writel(0x19700, V2_TDEL); - writel(SPINAND_RESET, V2_CMD); - xmsfc_run(2); - xmsfc_wait_ready(); - xmsfc_bus_mode(); -} - -static int xmsfc_status; - -static void xmsfc_cmdfunc(struct mtd_info *mtd, unsigned int command, - int column, int page) -{ - struct xmsfc_nand *host = &xmsfc_nand; - - host->last_cmd = command; - switch (command) { - case NAND_CMD_RESET: - xmsfc_reset(); - break; - case NAND_CMD_READID: - xmsfc_read_id(); - host->pos = 0; - break; - case NAND_CMD_PARAM: - /* No ONFI parameter page on SPI NAND */ - memset(host->buf, 0, 256); - host->pos = 0; - break; - case NAND_CMD_READ0: - xmsfc_read_page(mtd, page); - host->pos = column; - break; - case NAND_CMD_READOOB: - xmsfc_read_page(mtd, page); - host->pos = mtd->writesize + column; - break; - case NAND_CMD_SEQIN: - memset(host->buf, 0xff, mtd->writesize + mtd->oobsize); - host->page = page; - host->pos = column; - break; - case NAND_CMD_PAGEPROG: - xmsfc_status = xmsfc_program_page(mtd, host->page); - break; - case NAND_CMD_ERASE1: - xmsfc_status = xmsfc_erase_block(page); - break; - case NAND_CMD_ERASE2: - case NAND_CMD_STATUS: - break; - default: - printf("xmsfc-nand: command 0x%x not supported\n", command); - break; - } -} - -static u8 xmsfc_read_byte(struct mtd_info *mtd) -{ - struct xmsfc_nand *host = &xmsfc_nand; - - switch (host->last_cmd) { - case NAND_CMD_READID: - return host->pos < sizeof(host->id) ? host->id[host->pos++] : 0; - case NAND_CMD_STATUS: - /* Write protect off, ready, and the last program/erase result */ - return NAND_STATUS_WP | NAND_STATUS_READY | xmsfc_status; - default: - return host->pos < sizeof(host->buf) ? host->buf[host->pos++] : 0xff; - } -} - -static void xmsfc_read_buf(struct mtd_info *mtd, u8 *buf, int len) -{ - struct xmsfc_nand *host = &xmsfc_nand; - - len = min_t(int, len, sizeof(host->buf) - host->pos); - memcpy(buf, host->buf + host->pos, len); - host->pos += len; -} - -static void xmsfc_write_buf(struct mtd_info *mtd, const u8 *buf, int len) -{ - struct xmsfc_nand *host = &xmsfc_nand; - - len = min_t(int, len, sizeof(host->buf) - host->pos); - memcpy(host->buf + host->pos, buf, len); - host->pos += len; -} - -static int xmsfc_waitfunc(struct mtd_info *mtd, struct nand_chip *chip) -{ - return NAND_STATUS_WP | NAND_STATUS_READY | xmsfc_status; -} - -static void xmsfc_select_chip(struct mtd_info *mtd, int cs) -{ -} - -/* The part corrects on die; report what feature C0 said about the page */ -static int xmsfc_ecc_read_page(struct mtd_info *mtd, struct nand_chip *chip, - u8 *buf, int oob_required, int page) -{ - struct xmsfc_nand *host = &xmsfc_nand; - - chip->read_buf(mtd, buf, mtd->writesize); - if (oob_required) - chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); - - switch (host->status & STATUS_ECC_MASK) { - case 0: - return 0; - case STATUS_ECC_FIXED: - mtd->ecc_stats.corrected++; - return 1; - default: - mtd->ecc_stats.failed++; - return 0; - } -} - -static int xmsfc_ecc_write_page(struct mtd_info *mtd, struct nand_chip *chip, - const u8 *buf, int oob_required, int page) -{ - chip->write_buf(mtd, buf, mtd->writesize); - if (oob_required) - chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); - return 0; -} - -void board_nand_init(void) -{ - struct nand_chip *chip = &xmsfc_nand.chip; - struct mtd_info *mtd = nand_to_mtd(chip); - int i; - - for (i = 0; i < ARRAY_SIZE(xmsfc_nand_pads); i++) - writel(XM_GPIO_DRIVE_8MA | XM_GPIO_MUX2_EN, - XM_GPIO_PAD(xmsfc_nand_pads[i])); - writel(0x00, V2_CTRL); /* NAND */ - - chip->cmdfunc = xmsfc_cmdfunc; - chip->read_byte = xmsfc_read_byte; - chip->read_buf = xmsfc_read_buf; - chip->write_buf = xmsfc_write_buf; - chip->waitfunc = xmsfc_waitfunc; - chip->select_chip = xmsfc_select_chip; - chip->options = NAND_NO_SUBPAGE_WRITE; - - if (nand_scan_ident(mtd, 1, xmsfc_nand_ids)) - return; - - chip->ecc.mode = NAND_ECC_HW; - chip->ecc.size = mtd->writesize; - chip->ecc.bytes = 0; - chip->ecc.strength = 1; - chip->ecc.read_page = xmsfc_ecc_read_page; - chip->ecc.write_page = xmsfc_ecc_write_page; - - if (nand_scan_tail(mtd)) - return; - nand_register(0, mtd); -} diff --git a/include/configs/xm530.h b/include/configs/xm530.h index 3ee82dea5361..579123382012 100644 --- a/include/configs/xm530.h +++ b/include/configs/xm530.h @@ -26,9 +26,6 @@ #define CONFIG_SYS_TIMER_COUNTER XM_TIMER_VALUE #define CONFIG_SYS_TIMER_COUNTS_DOWN -/* SPI NAND images (openipc/nand_config) */ -#define CONFIG_SYS_MAX_NAND_DEVICE 1 - #include "openipc-common.h" #endif diff --git a/openipc/nand_config b/openipc/nand_config deleted file mode 100644 index 3662092db64c..000000000000 --- a/openipc/nand_config +++ /dev/null @@ -1,26 +0,0 @@ -# SPI NAND (XM530/XM550): appended after the SoC fragment for NAND images. -# The same controller drives NOR or NAND, so the NOR driver goes. -# CONFIG_XMSFC_SPI_NOR is not set -# CONFIG_CMD_SF is not set -# CONFIG_ENV_IS_IN_SPI_FLASH is not set -# Linked where the vendor links a NAND U-Boot: the v2 window, behind a -# 4 KiB header (build.sh) whose first word jumps here. -CONFIG_SYS_TEXT_BASE=0x60001000 -CONFIG_MTD=y -CONFIG_MTD_RAW_NAND=y -CONFIG_NAND_XMSFC=y -CONFIG_CMD_NAND=y -CONFIG_CMD_NAND_TRIMFFS=y -CONFIG_ENV_IS_IN_NAND=y -CONFIG_ENV_OFFSET=0xc0000 -CONFIG_ENV_SIZE=0x40000 -CONFIG_ENV_SECT_SIZE=0x20000 -# 768 KiB boot partition, less the 4 KiB header build.sh puts in front -CONFIG_BOARD_SIZE_LIMIT=782336 -CONFIG_MTD_PARTITIONS=y -CONFIG_CMD_MTDPARTS=y -CONFIG_MTD_UBI=y -CONFIG_CMD_UBI=y -CONFIG_CMD_UBIFS=y -CONFIG_RBTREE=y -CONFIG_LZO=y From 8798a41d016838fd363cc99d3cd93a7978b465f0 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:40:51 +0000 Subject: [PATCH 3/5] openipc: recovery: bound script.txt, refuse empty images, report a failed script - script.txt was read to CONFIG_SYS_LOAD_ADDR whatever its size; one larger than the free RAM below the stack would have run over U-Boot. It now gets the same bound as the images (recovery_ram_room()). - a uImage wrapper with a data size of 0 passed every check and was then read with length 0, which the FAT read takes as "the rest of the file", past the RAM bound. It is skipped now. - a script that fails is reported as failed. It still ends recovery: the card author chose a script over the images. Under qemu-hisilicon gk7102: a 0-length wrapper in front of 2 MiB is "has no data, skipped", and the rest of the card recovers as before. Reported by Qodo on #44, #45 and #46. --- common/openipc.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/common/openipc.c b/common/openipc.c index e6775475099d..c9df50841b35 100644 --- a/common/openipc.c +++ b/common/openipc.c @@ -418,6 +418,12 @@ static int recovery_flash_holds(ulong addr, const void *data, ulong len) { * 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. */ +/* 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; +} + static int recovery_write_img(const char *name, loff_t size) { struct image_header hdr; loff_t actread; @@ -432,6 +438,11 @@ static int recovery_write_img(const char *name, loff_t size) { } 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; + } /* 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); @@ -447,8 +458,7 @@ static int recovery_write_img(const char *name, loff_t size) { name, addr, sect); return 0; } - /* in RAM below the stack and the relocated U-Boot, with room */ - if (len > gd->start_addr_sp - SZ_1M - CONFIG_SYS_LOAD_ADDR) { + if (len > recovery_ram_room()) { printf("Recovery: %s (%lu bytes) does not fit the free RAM, skipped\n", name, len); return 0; @@ -535,6 +545,10 @@ static int recovery_run_script(const char *name) { 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 = rfs_read(name, CONFIG_SYS_LOAD_ADDR, 0, size, &actread); if (ret || actread != size) { @@ -551,7 +565,10 @@ 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; } From 1cc7a1511aa37616a6cd439efd2ddcc738650c80 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:40:51 +0000 Subject: [PATCH 4/5] openipc_config: no nand_config to mention Reported by Qodo on #46. --- openipc/openipc_config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openipc/openipc_config b/openipc/openipc_config index 1c63f86c636b..426d66b32381 100644 --- a/openipc/openipc_config +++ b/openipc/openipc_config @@ -1,6 +1,6 @@ # OpenIPC base configuration for the Xiongmai targets. build.sh appends the -# per-SoC fragment (and nand_config for NAND images) and runs olddefconfig, -# so a later line overrides an earlier one. +# per-SoC fragment and runs olddefconfig, so a later line overrides an +# earlier one. CONFIG_ARM=y CONFIG_ARCH_XM=y CONFIG_SYS_THUMB_BUILD=y From 37f44bc67644a752a7f4b3b2f88978f82c99c081 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:57:14 +0000 Subject: [PATCH 5/5] openipc: recovery_ram_room() outside the NOR-only part script.txt is read on SPI NAND builds too, so its RAM bound has to be there as well; the helper sat in the NOR-only section and every NOR+NAND HiSilicon build failed to link (undefined reference to recovery_ram_room). --- common/openipc.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/common/openipc.c b/common/openipc.c index c9df50841b35..c2e4fa7fafb4 100644 --- a/common/openipc.c +++ b/common/openipc.c @@ -254,6 +254,12 @@ static int rfs_read(const char *name, ulong addr, loff_t offset, loff_t len, 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 CONFIG_FMC_SPI_NAND static int recovery_ensure_volume(const char *name, const char *size) { struct ubi_volume_desc *desc; @@ -418,12 +424,6 @@ static int recovery_flash_holds(ulong addr, const void *data, ulong len) { * 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. */ -/* 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; -} - static int recovery_write_img(const char *name, loff_t size) { struct image_header hdr; loff_t actread;