From d8214236544f8be9f9355302fd475fbaeea1e6eb Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Thu, 22 Oct 2026 08:52:06 +0000 Subject: [PATCH 1/2] gk710x: publish U-Boot in CPU order, like every other file The release U-Boot was in chip order -- every 32-bit word swapped, as the chip holds it -- so that a programmer could write it as it is, while OpenIPC/firmware's uImage and rootfs are in CPU order. A programmer image made by concatenating the three put U-Boot on the chip right and the kernel and rootfs unswapped, and the camera found no kernel (a GK7102C dump shows exactly that). One order for everything published: gk710x-image.py writes CPU order, which tftp, mmc and usb load and sf write or SD recovery put on flash as they are. A programmer or qemu flash-file image is the whole flash laid out from those files and then word-swapped as one, e.g. with objcopy --reverse-bytes=4 (the openipc branch's README shows how). gk_sflash.c no longer swaps on read: through the driver the flash reads as it was written, in CPU order. It still recognises a chip-order image written at 0 by its swapped magic and swaps it back, so a programmer image fed to sf write or SD recovery lands right as well. Under qemu-hisilicon: - gk7102: an 8 MiB programmer image built with dd + objcopy from the CPU-order U-Boot, uImage and rootfs boots to login, and the second boot loads the saved environment; - gk7102 and gk7102s (a GK7102C flash dump): with only U-Boot on the chip, SD recovery writes the CPU-order kernel and rootfs images; the flash then reads back byte for byte as the three files, both boots reach login, and on the second the U-Boot, kernel and rootfs images are all "already on the flash"; - gk7102: the programmer image itself, as an SD recovery image at 0, lands on the chip byte for byte (env and the jffs2 overlay aside, which the boot then writes) and boots. --- CLAUDE.md | 55 +++++++++++++++-------------------- drivers/mtd/spi/gk_sflash.c | 58 ++++++++++--------------------------- openipc/gk710x-image.py | 13 ++++----- 3 files changed, 44 insertions(+), 82 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f37ea7350711..6c78d023d384 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -108,35 +108,27 @@ The kernel still needs the blob (see "Hand-off to the kernel"). `0x60004004` (128 vs 64 MiB). `dram_init()` reads the same word back. - **Byte order.** The flash controller packs bytes MSB first. U-Boot, the ROM and the vendor kernel all move whole little-endian words through it, so - the chip holds every 32-bit word byte-swapped, and a camera dump reads as - big-endian ("GK7101" in a hex editor). - - `output/u-boot--nor.bin` is **in chip order**: one file for every - way it gets onto a camera. - - A programmer writes it as it is; qemu takes it as `flash-file=` as it - is. - - `sf write` at offset 0 of a buffer starting with the byte-swapped magic - swaps it back first. The vendor U-Boot does this (`check_magic_number()` - in its `gk7101_sflash.c`), and so does `gk_sflash.c`. `run ub` from - either bootloader therefore writes exactly the bytes a programmer - would. A CPU-order image (the SDK's prebuilts) is written unchanged. - - `gk_sflash.c` swaps the boot image's span on read as well (the image - found at offset 0 at probe, or the one last written there). Every - caller (`sf read`, SD recovery's "already on the flash" check) sees - the boot image as the file. Everything past it is the controller's - plain round trip. - - A whole-chip dump in chip order (a programmer's) restores with one - `sf write` at 0: a write at 0 that starts with the swapped magic is - swapped over its whole length. `sf update` splits it into sectors and - only the boot image comes out right. `sf read` of the whole chip is - not a dump either: the boot image in chip order, the rest in CPU - order. - - Exception: the first release (2026-10-18) wrote CPU-order images and - its `gk_sflash.c` has no swap check. `run ub` on a camera running it - would write a chip-order image swapped twice and brick it. Upgrade - those cameras with a programmer. - - Anything else written to flash (kernel, rootfs, environment) is in CPU - order through the drivers; only a raw dump or programmer image of the - whole chip is swapped. + the chip holds every 32-bit word byte-swapped, everywhere: boot image, + env, kernel, rootfs. A programmer's dump reads as big-endian ("GK7101" in + a hex editor, "sqsh" for a squashfs). + - **Every published file is in CPU order**: `u-boot--nor.bin`, as + `gk710x-image.py` writes it, like OpenIPC/firmware's uImage and rootfs. + They load by tftp, mmc or usb and go to flash with `sf write` or SD + recovery as they are. Through the drivers the flash reads and writes in + CPU order; `gk_sflash.c` never swaps a read. + - **Programmer and qemu `flash-file=` images are in chip order**, the + whole image: lay the CPU-order files out at their offsets, then swap + every word, `objcopy -I binary -O binary --reverse-bytes=4 full.bin + programmer.bin` (the openipc branch's README has the full recipe). The + same command turns a programmer's dump back into CPU order. Writing + the files with a programmer as they are puts them on the chip + unswapped: the ROM finds no boot header, U-Boot no kernel. + - A programmer image written through the driver anyway (`sf write`, SD + recovery) is recognised by the swapped magic at 0 and swapped back: the + boot image however the caller splits it, the whole write when one write + covers more. `sf update` of a whole chip-order image therefore only gets + the boot image right. The vendor U-Boot's flash write does the same + (`check_magic_number()` in its `gk7101_sflash.c`). - `gk_sflash.c` keeps the word mapping for unaligned offsets as well: whole words only, writes padded with `0xff`. @@ -272,9 +264,8 @@ bash build.sh gk7102 # one or more SoCs (CROSS_COMPILE defaults to arm-li The emulator is widgetii/qemu-hisilicon, machines `gk7101` and `gk7102` (`docs/goke-gk710x.md` there). `-M gk7102,flash-file=` does the boot -ROM's job from the header, so it boots exactly the flashed bytes. The -release image goes in as it is; firmware parts written behind it (uImage, -rootfs) must be word-swapped, since the chip holds them that way. +ROM's job from the header, so it boots exactly the flashed bytes: a +programmer image (the whole file word-swapped, see "Byte order"). - Guest `reset` re-enters the U-Boot qemu loaded at start-up, not what is in flash now. To check a newly written bootloader, start qemu again on the diff --git a/drivers/mtd/spi/gk_sflash.c b/drivers/mtd/spi/gk_sflash.c index 6a3b36839ba7..f6c0e602969b 100644 --- a/drivers/mtd/spi/gk_sflash.c +++ b/drivers/mtd/spi/gk_sflash.c @@ -160,28 +160,22 @@ static int sf_program(u32 addr, const u8 *buf, unsigned int words) #define GK_BOOT_MAGIC 0x474b3731 /* - * The boot ROM reads the boot image as raw chip bytes, while everything - * read or written through the controller is packed MSB first: a 32-bit - * word goes to the chip byte-swapped and comes back swapped again. For the - * kernel, the rootfs and the environment that round trip is invisible. - * For the boot image it is not: a programmer writes it in the chip's - * order, and the vendor U-Boot's flash write recognises such an image by - * its byte-swapped magic and swaps it back (check_magic_number() in its - * gk7101_sflash.c). + * The controller packs bytes MSB first: every 32-bit word goes to the chip + * byte-swapped and comes back swapped again, so through this driver the + * flash reads and writes in CPU order, the order of every published file + * (U-Boot, kernel, rootfs) and of what tftp, mmc or usb load. The chip + * itself, which the boot ROM and a programmer see, holds every word + * swapped; a programmer image is the files laid out and every word + * swapped, and a dump swapped back is in CPU order. * - * This driver does the same, both ways, for every caller: from offset 0 - * to sf_swap_end -- header table plus the U-Boot length the header gives - * -- data is swapped on write and on read, so the boot image reads back - * as the file a programmer writes, byte for byte. The window is the image - * on the flash at probe, and a write at 0 moves it: one that starts with - * the swapped magic opens it as long as that image, however the caller - * splits the rest; any other write at 0 closes it. - * - * A write at 0 that starts with the swapped magic and runs past the boot - * image -- a programmer's whole-chip dump in one sf write, or recovery - * image -- is chip order throughout, and is swapped over its whole - * length. Split into sectors (sf update), only its first sector is seen - * at 0, so only the boot image is swapped back. + * A programmer image may still be written through here (sf write, SD + * recovery). A write at 0 that starts with the swapped boot magic is + * taken for one and swapped back: over sf_swap_end -- header table plus + * the U-Boot length the header gives -- however the caller splits the + * rest, and over its whole length when that one write is longer (a + * whole-chip image). Any other write at 0 closes the window. The vendor + * U-Boot's flash write does the same (check_magic_number() in its + * gk7101_sflash.c). Reads are never swapped. */ static u32 sf_swap_end; @@ -239,10 +233,6 @@ static int gk_sf_read(struct mtd_info *mtd, loff_t from, size_t len, u32 w = readl(SF_DATA); unsigned int n = min_t(size_t, len, 4 - skip); - if (addr < sf_swap_end) - w = swab32(w); - addr += 4; - if (!skip && n == 4) { put_unaligned_le32(w, buf); } else { @@ -320,21 +310,6 @@ static int gk_sf_erase(struct mtd_info *mtd, struct erase_info *instr) static struct spi_flash gk_flash; static int gk_announced; -/* The swap window over the boot image already on the flash */ -static void sf_swap_probe(u32 size) -{ - u8 hdr[SZ_2K]; - size_t n; - unsigned int i; - - sf_swap_end = 0; - gk_sf_read(&gk_flash.mtd, 0, sizeof(hdr), &n, hdr); - for (i = 0; i < sizeof(hdr); i += 4) - put_unaligned_le32(swab32(get_unaligned_le32(hdr + i)), - hdr + i); - sf_swap_end = min(sf_boot_len(hdr, sizeof(hdr)), size); -} - struct spi_flash *spi_flash_probe(unsigned int bus, unsigned int cs, unsigned int max_hz, unsigned int spi_mode) { @@ -376,9 +351,6 @@ struct spi_flash *spi_flash_probe(unsigned int bus, unsigned int cs, mtd->_write = gk_sf_write; mtd->_erase = gk_sf_erase; mtd->priv = flash; - /* Once: later writes at 0 keep the window up to date */ - if (!gk_announced) - sf_swap_probe(size); /* Once: the environment and common/openipc.c probe again and again */ if (!gk_announced++) diff --git a/openipc/gk710x-image.py b/openipc/gk710x-image.py index dd0369518f32..f2af31556fe1 100755 --- a/openipc/gk710x-image.py +++ b/openipc/gk710x-image.py @@ -21,11 +21,11 @@ words ("version:", "ddrtest:") and the table ("setup:", "rdcom:", "delay:"), as the header of the vendor image it came from has them. -The flash controller packs bytes MSB first, so the chip holds every 32-bit -word byte-swapped (drivers/mtd/spi/gk_sflash.c). The output is in that -chip order: a programmer writes it as it is, qemu takes it as flash, and -sf write -- the vendor U-Boot's and this one's -- recognises the swapped -magic at offset 0 and swaps it back. +The output is in CPU order, like every other published file: tftp, mmc or +usb load it and sf write puts it at 0. The chip holds every 32-bit word +byte-swapped (drivers/mtd/spi/gk_sflash.c), so a programmer, or qemu's +flash-file, needs every word swapped (the openipc branch's README shows +how). """ import re import struct @@ -78,8 +78,7 @@ def main(): sys.exit('%s: %d bytes, %d over the %d bytes before the environment' % (dst, len(image), len(image) - limit, limit)) with open(dst, 'wb') as f: - f.write(b''.join(image[i:i + 4][::-1] - for i in range(0, len(image), 4))) + f.write(image) if __name__ == '__main__': From 11c6a8d10e2f47fa0c96f76dba53457eebc10659 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Thu, 22 Oct 2026 09:06:30 +0000 Subject: [PATCH 2/2] gk_sflash: carry the swap window only over writes in a row After a chip-order image was written at 0, the window stayed open for any later write below its end. A CPU-order sf update in the same session whose first sector was unchanged skipped it, so nothing at 0 closed the window, and its later changed sectors were written swapped. A write that does not start at 0 now keeps the window only if it starts where the previous write ended -- the pieces of one image, as sf update and SD recovery write them. Under qemu-hisilicon gk7102: chip-order sf write at 0, then a CPU-order sf update of a U-Boot differing at 0x20000, read back with the file's CRC (before: a different one); the same with a saveenv in between; and a chip-order sf update alone lands as the CPU-order file. A programmer image still boots to login twice. Reported by Qodo on #50. --- drivers/mtd/spi/gk_sflash.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/spi/gk_sflash.c b/drivers/mtd/spi/gk_sflash.c index f6c0e602969b..58ee6a784003 100644 --- a/drivers/mtd/spi/gk_sflash.c +++ b/drivers/mtd/spi/gk_sflash.c @@ -173,11 +173,15 @@ static int sf_program(u32 addr, const u8 *buf, unsigned int words) * taken for one and swapped back: over sf_swap_end -- header table plus * the U-Boot length the header gives -- however the caller splits the * rest, and over its whole length when that one write is longer (a - * whole-chip image). Any other write at 0 closes the window. The vendor - * U-Boot's flash write does the same (check_magic_number() in its - * gk7101_sflash.c). Reads are never swapped. + * whole-chip image). Any other write at 0 closes the window, and so does + * a write elsewhere that does not start where the last one ended: the + * window carries over only the pieces of one image written in a row, so a + * later sf update whose unchanged first sector is skipped does not inherit + * it. The vendor U-Boot's flash write does the same swap + * (check_magic_number() in its gk7101_sflash.c). Reads are never swapped. */ static u32 sf_swap_end; +static loff_t sf_swap_next; /* where the last write ended */ /* The length of a chip-order boot image starting at buf, or 0 */ static u32 sf_boot_len(const u_char *buf, size_t len) @@ -207,8 +211,11 @@ static u32 sf_boot_len(const u_char *buf, size_t len) static void sf_swap_detect(loff_t to, size_t len, const u_char *buf) { - if (to) + if (to) { + if (to != sf_swap_next) + sf_swap_end = 0; return; + } sf_swap_end = sf_boot_len(buf, len); if (sf_swap_end) printf("SF: boot image in the chip's byte order, swapping it back\n"); @@ -265,6 +272,7 @@ static int gk_sf_write(struct mtd_info *mtd, loff_t to, size_t len, if (to + len > mtd->size) return -EINVAL; sf_swap_detect(to, len, buf); + sf_swap_next = to + len; end = !to && sf_swap_end ? max_t(u32, sf_swap_end, len) : sf_swap_end; *retlen = 0; while (len) {