From 40c51eb734634b73f3a52db549c4a7a79eb48568 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:21:11 +0000 Subject: [PATCH 1/3] openipc: SD/USB recovery writes every uImage-wrapped *.img at its address Recovery from a card or stick looked for files by name (firmware.bin, the per-SoC kernel and rootfs names), had a special path for a split firmware.bin, and wrote the wrapper's header along with the payload. Make it one rule, the same on every branch: - script.txt still runs first; - otherwise every *.img in the root, in directory order, that is a valid uImage (magic, header CRC, data CRC) whose load address plus data size fit the flash, starting on an erase boundary, and that fits in RAM, has its payload -- never the wrapper header -- written at the load address. Whatever it is (U-Boot at 0, a kernel, a rootfs, a whole firmware) is the card author's call; - an image already on the flash is skipped, so a card left in the slot does not rewrite anything; the boot then goes on as usual; - the SPI NAND path (rootfs.ubifs..img) is unchanged. The split firmware.bin path and the name lookups go. Under qemu-hisilicon on openipc-gk710x (gk7102, gk7102s) and openipc-xm (xm530), with this file byte for byte: U-Boot, kernel and rootfs images written at their load addresses and booted; bad data CRC, non-uImage, past-the-end and off-boundary images skipped; a second boot finds them all already on the flash. --- common/openipc.c | 240 ++++++++++++++++++----------------------------- 1 file changed, 90 insertions(+), 150 deletions(-) diff --git a/common/openipc.c b/common/openipc.c index 1cfd11b1b734..e6775475099d 100644 --- a/common/openipc.c +++ b/common/openipc.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #ifdef CONFIG_USB #include #endif @@ -393,54 +395,6 @@ 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; - - if (!rfs_exists(name)) - return 0; - - 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; - } - - 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"); - - addr = str ? hextoul(str, NULL) : CONFIG_ENV_KERNADDR; - size = fdt_totalsize(&hdr); - } else { - printf("Recovery: no valid uImage/FIT header in %s\n", name); - return -1; - } - - 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; - } - - 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); - - return 1; -} - -#ifdef CONFIG_OPENIPC_NOR_SPLIT -/* Whether flash from addr holds data, compared 64 KiB at a time. */ static int recovery_flash_holds(ulong addr, const void *data, ulong len) { const ulong chunk = 0x10000; u8 *buf = malloc(chunk); @@ -459,101 +413,111 @@ static int recovery_flash_holds(ulong addr, const void *data, ulong len) { } /* - * A split image refused before anything is erased: said so, then treated as - * absent, so a kernel and rootfs pair on the same card is still tried. + * 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_skip(const char *name) -{ - printf("Recovery: %s skipped, trying the other recovery files\n", name); - return 0; -} - -/* - * The split layout's one image (firmware.bin., ${fwfile}, what fwupd - * fetches): a uImage, then the squashfs. Written at kernaddr, where the - * firmware partition starts on this board, with the rest of the chip erased: - * the old image's overlay must not be found by the next boot's scan or taken - * for settings. Nothing is erased until the whole file is in RAM and its - * kernel checks out, and a card left in the slot does not reflash an - * unchanged image at every boot. Returns 1 when written, 2 when already on - * the flash, 0 when absent or refused before any erase, -1 when the erase - * or the write failed. - */ -static int recovery_write_firmware(const char *name) { +static int recovery_write_img(const char *name, loff_t size) { struct image_header hdr; - loff_t size, actread; - ulong addr, len, env_end; - const char *str; + loff_t actread; + ulong addr, len, sect = recovery_flash->sector_size; - if (!name || !rfs_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; - - if (rfs_size(name, &size) || size <= (loff_t)sizeof(hdr)) { - printf("Recovery: failed to stat %s\n", name); - return recovery_skip(name); - } - if (rfs_read(name, (ulong)&hdr, 0, sizeof(hdr), &actread) || - actread != sizeof(hdr)) { - printf("Recovery: failed to read %s\n", name); - return recovery_skip(name); } - /* the data size against what follows the header, in 64 bits: added to - * the header's in a 32-bit ulong, a size near 4 GiB wrapped past this */ - if (!image_check_magic(&hdr) || !image_check_hcrc(&hdr) || - (loff_t)image_get_data_size(&hdr) > size - (loff_t)sizeof(hdr)) { - printf("Recovery: %s does not start with a uImage\n", name); - return recovery_skip(name); - } - - str = env_get("kernaddr"); - addr = str ? hextoul(str, NULL) : CONFIG_ENV_KERNADDR; - len = size; - /* never into the boot image or the environment below the firmware */ - env_end = CONFIG_ENV_OFFSET + CONFIG_ENV_SIZE; - if (addr < env_end) { - printf("Recovery: kernaddr 0x%lx is inside the boot area (firmware starts at 0x%lx)\n", - addr, env_end); - return recovery_skip(name); + 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 (%lu bytes) does not fit the flash from 0x%lx\n", - name, len, addr); - return recovery_skip(name); + printf("Recovery: %s (0x%lx + %lu bytes) is outside the flash, skipped\n", + name, addr, len); + return 0; } - /* the file in RAM below the stack and the relocated U-Boot, with room */ + 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 from 0x%lx\n", - name, len, (ulong)CONFIG_SYS_LOAD_ADDR); - return recovery_skip(name); + 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, 0, len, &actread) || actread != len) { - printf("Recovery: failed to read %s\n", name); - return recovery_skip(name); + 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 (!image_check_dcrc((struct image_header *)CONFIG_SYS_LOAD_ADDR)) { - printf("Recovery: %s: the kernel's CRC does not match\n", name); - return recovery_skip(name); + 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; } - - /* handled, not absent: the legacy pair on the same card is not written */ + /* 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 2; + return 0; } - printf("Recovery: writing %s (%lu bytes) at 0x%lx, erasing to the end of the flash\n", - name, len, addr); - if (spi_flash_erase(recovery_flash, addr, recovery_flash->size - 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; } -#endif + +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); + + for (i = 0; i < n; i++) + if (recovery_write_img(files[i].name, files[i].size) > 0) + written++; + free(files); + return written; +} #endif /* If a script.txt is present on the medium, execute it (uImage script or @@ -593,46 +557,22 @@ 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; -#if defined(CONFIG_OPENIPC_NOR_SPLIT) && !defined(CONFIG_FMC_SPI_NAND) - /* The split image, where this board takes one; else the pair below */ - ret = recovery_write_firmware(env_get("fwfile") ? env_get("fwfile") : OPENIPC_FW_FILE); - if (ret) - return ret > 0; -#endif - -#ifndef CONFIG_FMC_SPI_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_FMC_SPI_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) { From bca42fc1383ac13995760b56cb8660a4fd90af3b Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:40:49 +0000 Subject: [PATCH 2/3] 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 c2b9c7c14adfc7170b19c9330ad190f66c78148e Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:57:10 +0000 Subject: [PATCH 3/3] 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;