diff --git a/CLAUDE.md b/CLAUDE.md index 2fd87a9e0a70..b34caaa6cd8a 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,49 @@ 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` (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` +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. +- 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. ### Iford @@ -259,13 +269,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 +285,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 +533,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 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} #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,22 +329,27 @@ 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; } 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 = 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 +365,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 +400,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 +420,153 @@ 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; + } + /* 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; + } + 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; } - 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); + 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; +} - if (recovery_same(addr, (u8 *)CONFIG_SYS_LOAD_ADDR, size)) { - printf("Recovery: %s already in flash at 0x%lx\n", name, addr); - return 1; +struct recovery_file { + char name[256]; + loff_t size; +}; + +/* 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; +} - 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); - return -1; +/* 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; + 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; + 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; + files = more; + strcpy(files[n].name, d->name); + files[n++].size = d->size; } + fs_closedir(dirs); - printf("Recovery: wrote %s (%lu bytes) to flash at 0x%lx\n", name, size, - addr); - - 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 +577,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 +605,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 +658,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 +670,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 +811,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 +834,6 @@ int firmware_scan(void) { env_set("bootfail", "1"); return 0; } -#endif env_set("bootfail", NULL); @@ -630,6 +844,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 +856,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 +1008,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 +1121,70 @@ 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 + + /* 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 + * 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); + } + /* 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; } diff --git a/include/configs/openipc-common.h b/include/configs/openipc-common.h index 6b8ca8a36899..56e4e59951ab 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,62 @@ "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_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}" + +/* + * 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=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" \ + "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..a1913ff486ae 100644 --- a/openipc/nand_config +++ b/openipc/nand_config @@ -1,21 +1,31 @@ -# 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 +# 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 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 diff --git a/openipc/sigmastar-image.py b/openipc/sigmastar-image.py index c97e54666737..898f7c099515 100755 --- a/openipc/sigmastar-image.py +++ b/openipc/sigmastar-image.py @@ -19,12 +19,10 @@ sigmastar-image.py --ipl F --ipl-cust F (--mxp F | --gcis F) --tag TAG --uboot u-boot.bin --soc SOC --out F -On SPI NAND (--nand), the SNI (--gcis) in page 0 and the PNI (--pni) in page 1, -then make_boot_spinand.sh's 2 MiB: IPL at 0x40000 and 0x80000, IPL_CUST at -0xC0000 and 0x100000, U-Boot at 0x140000 and 0x1A0000. That is what OpenIPC -publishes, whatever the PNI says: infinity6's table has its stages further up -and its images boot regardless. Nothing is published for infinity2m, so ---pni-layout puts each stage where its SDK's PNI says. +On SPI NAND (--nand), OpenIPC's 768k(boot),256k(env),-(ubi): the SNI (--gcis, +or for infinity2m --sni-list's record) in page 0 and the PNI (--pni, its +records rewritten) in page 1 of block 0, IPL in block 1, IPL_CUST in block 2, +U-Boot in blocks 3-5, one copy each; the environment at 0xC0000. """ import argparse @@ -39,28 +37,13 @@ CUST_OFF, CUST_SIZE = 0x10000, 0xF000 UBOOT_OFF, UBOOT_SIZE = 0x20000, 0x20000 -NAND_BOOT_SIZE = 0x200000 -NAND_IPL_OFFS = (0x40000, 0x80000) -NAND_CUST_OFFS = (0xC0000, 0x100000) -NAND_UBOOT_OFFS = (0x140000, 0x1A0000) +# OpenIPC's SPI NAND: 768k(boot),256k(env),-(ubi) +NAND_ENV_OFF, NAND_UBI_OFF = 0xC0000, 0x100000 # The PNI's partition types PNI_IPL_CUST, PNI_IPL, PNI_UBOOT, PNI_ENV = 1, 3, 6, 13 -def pni_layout(pni): - """Each partition's copies, in bytes, from the IPL's own table.""" - spare, page, pages, blocks, count, unit = struct.unpack_from("<6H", pni, 4) - block = page * pages - recs = [struct.unpack_from("<4H", pni, 16 + i * 8) for i in range(count)] - parts = {} - for start, cnt, kind, backup in recs: - # a partition holds its piece once per cnt blocks, backups included - for k in range((cnt + backup) // cnt if cnt else 1): - parts.setdefault(kind, []).append((start + k * cnt) * block) - return parts - - def uimage(data, name, load=0): """A legacy uImage: ARM, OS U-Boot, type kernel, "lzma" (xz data).""" hdr = struct.pack(">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))