From 7fc9a86d445d787eb1fd103305e6ffbd45e80738 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:15:03 +0000 Subject: [PATCH 1/8] gk_sflash: swap the boot image in the driver, on write and on read The boot ROM reads the boot image as raw chip bytes, while the controller packs every word MSB first: whatever goes through it comes back as it went in, but lands on the chip byte-swapped. The release image is in chip order, and cmd/sf.c swapped it back before sf write or sf update. Any other writer (SD recovery, spi_flash_write() from anywhere) put it on the flash swapped twice, and every reader saw the boot image in the controller's order rather than as the file. Move it into gk_sflash.c, for every caller, both ways: from offset 0 to the end of the boot image (its header table plus the U-Boot length the header gives) data is swapped on write and on read. The window is the image found at offset 0 at probe; a write at 0 that starts with the swapped magic opens it as long as that image, however the caller splits the rest, and any other write at 0 closes it. The table is walked entry by entry, as a register value of 0 is not its end. cmd/sf.c is mainline's again. Under qemu-hisilicon gk7102 and gk7102s: the image written by SD recovery at 0 is byte-for-byte the release file on the flash and boots; sf read of it gives the file's CRC32; on the next boot the "already on the flash" check matches it. --- CLAUDE.md | 5 + arch/arm/mach-gk710x/include/mach/gk710x.h | 2 - cmd/sf.c | 9 -- drivers/mtd/spi/gk_sflash.c | 109 ++++++++++++++++----- 4 files changed, 90 insertions(+), 35 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c0a5495a7ed7..e69f3a47ae34 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -117,6 +117,11 @@ The kernel still needs the blob (see "Hand-off to the kernel"). 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. - 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 diff --git a/arch/arm/mach-gk710x/include/mach/gk710x.h b/arch/arm/mach-gk710x/include/mach/gk710x.h index d55fd9c4ca24..73479c4b240f 100644 --- a/arch/arm/mach-gk710x/include/mach/gk710x.h +++ b/arch/arm/mach-gk710x/include/mach/gk710x.h @@ -149,8 +149,6 @@ #ifndef __ASSEMBLY__ void gk_gpio_config(unsigned int pin, unsigned int type); void gk_gpio_init(void); -/* drivers/mtd/spi/gk_sflash.c, for cmd/sf.c */ -void gk_sflash_boot_order(unsigned int offset, unsigned long len, void *buf); /* board/goke/gk710x/gpio_map.c */ void gk_gpio_map_apply(void); void gk_gpio_map_save(void *dst); diff --git a/cmd/sf.c b/cmd/sf.c index 5b4e7a3f7632..bd102f5af9df 100644 --- a/cmd/sf.c +++ b/cmd/sf.c @@ -16,9 +16,6 @@ #include #include #include -#ifdef CONFIG_GK_SFLASH -#include -#endif #include #include @@ -309,12 +306,6 @@ static int do_spi_flash_read_write(int argc, char *const argv[]) return 1; } -#ifdef CONFIG_GK_SFLASH - /* A Goke boot image in the flash's byte order goes in swapped back */ - if (strcmp(argv[0], "update") == 0 || strncmp(argv[0], "write", 5) == 0) - gk_sflash_boot_order(offset, len, buf); -#endif - if (strcmp(argv[0], "update") == 0) { ret = spi_flash_update(flash, offset, len, buf); } else if (strncmp(argv[0], "read", 4) == 0 || diff --git a/drivers/mtd/spi/gk_sflash.c b/drivers/mtd/spi/gk_sflash.c index bf2f056b4052..2806f87344d5 100644 --- a/drivers/mtd/spi/gk_sflash.c +++ b/drivers/mtd/spi/gk_sflash.c @@ -156,6 +156,64 @@ static int sf_program(u32 addr, const u8 *buf, unsigned int words) return sf_wait_ready(); } +/* The boot ROM header's first word, "GK71" */ +#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). + * + * 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. + */ +static u32 sf_swap_end; + +/* 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) +{ + u32 nwords, i; + + if (len < 32 || get_unaligned_le32(buf) != swab32(GK_BOOT_MAGIC)) + return 0; + /* + * Header: 7 words, then the table -- writes, + * 0xFFFFFFFE polls, 0xFFFFFFFF delays -- + * ended by a 0 word; then nwords of U-Boot. A value can be 0, so the + * table is walked entry by entry. + */ + nwords = swab32(get_unaligned_le32(buf + 8)); + for (i = 7 * 4; i + 4 <= len; ) { + u32 w = swab32(get_unaligned_le32(buf + i)); + + if (!w) + break; + i += 4 * (w == 0xfffffffe ? 3 : 2); + } + if (i + 4 > len) + return 0; + return i + 4 + 4 * nwords; +} + +static void sf_swap_detect(loff_t to, size_t len, const u_char *buf) +{ + if (to) + 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"); +} + static int gk_sf_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { @@ -175,6 +233,10 @@ 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 { @@ -190,30 +252,6 @@ static int gk_sf_read(struct mtd_info *mtd, loff_t from, size_t len, return 0; } -/* The boot ROM header's first word, "GK71" */ -#define GK_BOOT_MAGIC 0x474b3731 - -/* - * An image as a programmer writes it, every word as the chip holds it, - * starts with the boot header magic byte-swapped. Written at 0 as it is, it - * would land swapped twice. The vendor U-Boot's sf write swaps such an - * image back in RAM first, so one boot image serves a programmer and sf - * write alike; the sf command does the same through this, for the whole - * buffer at once -- sf update writes it one sector at a time. - */ -void gk_sflash_boot_order(unsigned int offset, unsigned long len, void *buf) -{ - u8 *p = buf; - size_t i; - - if (offset || len < 4 || len % 4 || - get_unaligned_le32(p) != swab32(GK_BOOT_MAGIC)) - return; - printf("SF: boot image in flash byte order, swapping it back\n"); - for (i = 0; i < len; i += 4) - put_unaligned_le32(swab32(get_unaligned_le32(p + i)), p + i); -} - /* * The controller programs whole words, so a write that starts or ends * inside one pads it with 0xff, which leaves those flash bytes as they are. @@ -224,10 +262,12 @@ static int gk_sf_write(struct mtd_info *mtd, loff_t to, size_t len, u8 page[SZ_PAGE]; u32 addr = to & ~3; unsigned int head = to & 3; + unsigned int i; int ret; if (to + len > mtd->size) return -EINVAL; + sf_swap_detect(to, len, buf); *retlen = 0; while (len) { unsigned int room = SZ_PAGE - (addr & (SZ_PAGE - 1)); @@ -236,6 +276,9 @@ static int gk_sf_write(struct mtd_info *mtd, loff_t to, size_t len, memset(page, 0xff, span); memcpy(page + head, buf, n); + for (i = 0; i < span && addr + i < sf_swap_end; i += 4) + put_unaligned_le32(swab32(get_unaligned_le32(page + i)), + page + i); ret = sf_program(addr, page, span / 4); if (ret) return ret; @@ -269,6 +312,21 @@ 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) { @@ -310,6 +368,9 @@ 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++) From 206c35141acc073bac4b927e13660aecc3020afc Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:15:31 +0000 Subject: [PATCH 2/8] net: designware: no network, rather than a crash, without a PHY On a GK7102C camera with no Ethernet, designware_initialize() finds no PHY ("Could not get PHY for dwmac.9000e000") and leaves phydev NULL. The first network command (the fwupd/recovery flow) then dereferenced it in designware_eth_init() and _dw_eth_halt(): a data abort and a reset loop. Fail eth init with -ENODEV and skip phy_shutdown() when there is no PHY. The qemu-hisilicon machines always have the PHY, so this ran only in the boots where it does nothing (gk7102 and gk7102s to login). --- drivers/net/designware.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/designware.c b/drivers/net/designware.c index ee23839033ab..23f030b47cc5 100644 --- a/drivers/net/designware.c +++ b/drivers/net/designware.c @@ -362,7 +362,8 @@ static void _dw_eth_halt(struct dw_eth_dev *priv) writel(readl(&mac_p->conf) & ~(RXENABLE | TXENABLE), &mac_p->conf); writel(readl(&dma_p->opmode) & ~(RXSTART | TXSTART), &dma_p->opmode); - phy_shutdown(priv->phydev); + if (priv->phydev) + phy_shutdown(priv->phydev); } int designware_eth_init(struct dw_eth_dev *priv, u8 *enetaddr) @@ -372,6 +373,10 @@ int designware_eth_init(struct dw_eth_dev *priv, u8 *enetaddr) unsigned int start; int ret; + /* No PHY was found at initialisation: no network, rather than a crash */ + if (!priv->phydev) + return -ENODEV; + writel(readl(&dma_p->busmode) | DMAMAC_SRST, &dma_p->busmode); /* From 4ec4067bba2e62bbc6fd23cc13c0caead73ab6b6 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:15:31 +0000 Subject: [PATCH 3/8] openipc: GK7101/GK7102 boot ROM eFuse set-up as cameras have it A GK7102 camera (rb ov9712, GD25Q128) did not start with our U-Boot. Its own boot header, and those of two more GK7102 flash dumps (vendor U-Boots of 2016 and 2017), differ from the SDK prebuilts' in the eFuse lines only: they never write 0xFF39C119 to 0x6000B100, and after starting the eFuse load (0x6000B104 = 1) they wait for it to finish (0x6000B104 == 3). Do the same in both tables. The GK7102 header is now word for word that camera's: header words and all 62 table entries. qemu-hisilicon does not model the eFuse block, so silicon has to confirm it. GK7101 follows the same SDK change; no GK7101 field dump is at hand. --- CLAUDE.md | 4 +++- openipc/gk7101.rvs | 10 +++++++--- openipc/gk7102.rvs | 10 +++++++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e69f3a47ae34..86c70a763c42 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -101,7 +101,9 @@ The kernel still needs the blob (see "Hand-off to the kernel"). - the words around the table are as in every SDK prebuilt and camera flash seen; - the table is `openipc/.rvs`, taken from the SDK's prebuilt - U-Boots, which matches what is on cameras in the field; + U-Boots, with the eFuse lines as GK7102 cameras in the field have + them (no write to `0x6000B100`; wait for `0x6000B104 == 3` after + starting the load). A GK7102 with the SDK's lines did not start; - GK7101 and GK7102 differ only in the DDR controller word at `0x60004004` (128 vs 64 MiB). `dram_init()` reads the same word back. - **Byte order.** The flash controller packs bytes MSB first. U-Boot, the diff --git a/openipc/gk7101.rvs b/openipc/gk7101.rvs index 0a931078c8b4..1f608bd87590 100644 --- a/openipc/gk7101.rvs +++ b/openipc/gk7101.rvs @@ -1,8 +1,12 @@ # GK7101 boot ROM set-up: the register writes the mask ROM makes before it # copies U-Boot into DDR -- eFuse, DDR PHY, the RCT and the DDR controller. # Taken from the header of the SDK's prebuilt uboot-gk7101-rb-ov2710_v2.2.bin -# (GK710X_LinuxSDK_v2.0.0 boot/prebuild), the table on GK710x cameras in the -# field. build.sh writes it into the boot header. +# (GK710X_LinuxSDK_v2.0.0 boot/prebuild), with the eFuse lines as GK7102 +# cameras in the field have them: the SDK images also write 0xFF39C119 to +# 0x6000B100 first and start the eFuse load at the end without waiting for +# it; the vendor U-Boots of 2016-2017 found on cameras do neither, and a +# GK7102 with the SDK's table did not start. build.sh writes it into the +# boot header. # # version: header word 1 # ddrtest:
= header words 5 and 6 @@ -13,7 +17,6 @@ version: 0x30310801 ddrtest: 0xC4000000 = 0x5A5A5A5A # -setup: 0x6000B100 = 0xFF39C119 setup: 0x60021A68 = 0x00000300 setup: 0x60021A60 = 0x00002B52 setup: 0x60021A64 = 0x00000035 @@ -75,3 +78,4 @@ rdcom: 0x60004018 = 0x01010000 setup: 0x6000401C = 0x00000210 setup: 0x60004000 = 0x00000007 setup: 0x6000B104 = 0x00000001 +rdcom: 0x6000B104 = 0x00000003 diff --git a/openipc/gk7102.rvs b/openipc/gk7102.rvs index f66dd0133b7d..7d8c696c8e97 100644 --- a/openipc/gk7102.rvs +++ b/openipc/gk7102.rvs @@ -1,8 +1,12 @@ # GK7102 boot ROM set-up: the register writes the mask ROM makes before it # copies U-Boot into DDR -- eFuse, DDR PHY, the RCT and the DDR controller. # Taken from the header of the SDK's prebuilt uboot-gk7102-rb-jxh42_v2.0.bin -# (GK710X_LinuxSDK_v2.0.0 boot/prebuild), the table on GK710x cameras in the -# field. build.sh writes it into the boot header. +# (GK710X_LinuxSDK_v2.0.0 boot/prebuild), with the eFuse lines as GK7102 +# cameras in the field have them: the SDK images also write 0xFF39C119 to +# 0x6000B100 first and start the eFuse load at the end without waiting for +# it; the vendor U-Boots of 2016-2017 found on cameras do neither, and a +# GK7102 with the SDK's table did not start. build.sh writes it into the +# boot header. # # version: header word 1 # ddrtest:
= header words 5 and 6 @@ -13,7 +17,6 @@ version: 0x30310801 ddrtest: 0xC4000000 = 0x5A5A5A5A # -setup: 0x6000B100 = 0xFF39C119 setup: 0x60021A68 = 0x00000300 setup: 0x60021A60 = 0x00002B52 setup: 0x60021A64 = 0x00000035 @@ -75,3 +78,4 @@ rdcom: 0x60004018 = 0x01010000 setup: 0x6000401C = 0x00000210 setup: 0x60004000 = 0x00000007 setup: 0x6000B104 = 0x00000001 +rdcom: 0x6000B104 = 0x00000003 From e2ffdef0b0ef13118312583652a8ccd961839a25 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:15:47 +0000 Subject: [PATCH 4/8] openipc: common/openipc.c as on openipc-hisilicon, with the generic recovery The file is meant to be the same on every branch; this one had drifted from openipc-hisilicon's. Take that one, together with the recovery change proposed there (OpenIPC/u-boot recovery/wrapper-payload), so the two are byte for byte the same: - script.txt on the card or stick still runs first; - otherwise every *.img in its root, in directory order, that is a valid uImage (magic, header CRC, data CRC) whose load address and size fit the flash, on an erase boundary, gets its payload written there -- never the wrapper header. That covers U-Boot at 0 as well; - an image already on the flash is skipped, and the boot goes on; - the split firmware.bin path and the per-name lookups are gone; the NAND path is as it was. It builds here only with CONFIG_MMC, which the next commit enables. --- common/openipc.c | 447 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 361 insertions(+), 86 deletions(-) diff --git a/common/openipc.c b/common/openipc.c index 00c363de3dbf..e6775475099d 100644 --- a/common/openipc.c +++ b/common/openipc.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #ifdef CONFIG_USB #include #endif @@ -20,7 +22,7 @@ #ifdef CONFIG_ENV_IS_IN_NAND #include #endif -#ifdef CONFIG_ENV_IS_IN_NAND +#ifdef CONFIG_FMC_SPI_NAND #include #include #include @@ -29,8 +31,9 @@ #include #endif -#define SCAN_FROM CONFIG_ENV_OFFSET + CONFIG_ENV_SIZE +#define SCAN_FROM (CONFIG_ENV_OFFSET + CONFIG_ENV_SIZE) #define SCAN_STEP 0x10000 +/* How far to look for the kernel, and for the rootfs past the kernel's end */ #define SCAN_LIMIT 0x400000 #define UBI_MAGIC_ASCII "UBI#" @@ -40,14 +43,14 @@ #define SQSH_MAGIC_BE 0x73717368 #define SQSH_MAGIC_LE 0x68737173 -static int check_squashfs(void *buf) { +static int __maybe_unused check_squashfs(void *buf) { uint32_t magic = get_unaligned_be32(buf); if (magic == SQSH_MAGIC_BE || magic == SQSH_MAGIC_LE) return 1; return 0; } -#ifdef CONFIG_ENV_IS_IN_NAND +#ifdef CONFIG_FMC_SPI_NAND #define UBIFS_SB_MAGIC 0x06101831 static int check_ubifs(void *buf) { @@ -76,7 +79,7 @@ static int ubi_marker_present(void) { } #endif -static int check_kernel(void *buf) { +static int __maybe_unused check_kernel(void *buf) { struct image_header *hdr = (struct image_header *)buf; if (image_check_magic(hdr)) { return 1; @@ -91,7 +94,7 @@ static int check_kernel(void *buf) { return 0; } -#ifndef CONFIG_ENV_IS_IN_NAND +#ifndef CONFIG_FMC_SPI_NAND static uint64_t get_squashfs_size(void *buf) { uint32_t magic = get_unaligned_be32(buf); @@ -118,10 +121,19 @@ static int check_rootfs(void *buf, uint64_t *rootfs_size) { return 0; } +/* The kernel image's size from its header, or 0 when it has none */ +static ulong kernel_image_size(void *buf) { + if (image_check_magic((struct image_header *)buf)) + return image_get_image_size((struct image_header *)buf); + if (fdt_magic(buf) == FDT_MAGIC && fdt_check_header(buf) == 0) + return fdt_totalsize(buf); + return 0; +} + static int scan_spi_device(struct spi_flash *flash, ulong *kernel_off, ulong *rootfs_off, uint64_t *kernel_size, uint64_t *rootfs_size) { u8 *buf; - ulong offset; + ulong offset, limit; ulong erase_size; int ret; @@ -135,9 +147,10 @@ static int scan_spi_device(struct spi_flash *flash, ulong *kernel_off, ulong *ro erase_size = 0x10000; #endif - printf("Scanning 0x%x-0x%x ...\n", SCAN_FROM, SCAN_LIMIT); + limit = min_t(ulong, SCAN_FROM + SCAN_LIMIT, flash->size); + printf("Scanning 0x%x-0x%lx ...\n", SCAN_FROM, limit); - for (offset = SCAN_FROM; offset < SCAN_LIMIT && offset < flash->size; offset += SCAN_STEP) { + for (offset = SCAN_FROM; offset < limit; offset += SCAN_STEP) { ret = spi_flash_read(flash, offset, SCAN_STEP, buf); if (ret != 0) continue; @@ -156,8 +169,20 @@ static int scan_spi_device(struct spi_flash *flash, ulong *kernel_off, ulong *ro } if (!*kernel_off && check_kernel(buf)) { + ulong end = offset + ALIGN(kernel_image_size(buf), SCAN_STEP); + *kernel_off = offset; printf("Found Kernel at 0x%08lx\n", offset); + /* + * The rootfs is past the kernel: skip the image, whose contents could + * pass for one, and look as far past its end as before it. A large + * kernel (arm64) ends beyond the first window. + */ + if (end > offset) { + limit = min_t(ulong, max_t(ulong, limit, end + SCAN_LIMIT), flash->size); + offset = end - SCAN_STEP; + continue; + } } if (!*rootfs_off && check_rootfs(buf, rootfs_size)) { @@ -183,21 +208,53 @@ static int scan_spi_device(struct spi_flash *flash, ulong *kernel_off, ulong *ro return 0; } -#endif /* !CONFIG_ENV_IS_IN_NAND */ +#endif /* !CONFIG_FMC_SPI_NAND */ #if (defined(CONFIG_MMC) || defined(CONFIG_USB_STORAGE)) && defined(CONFIG_FS_FAT) #define RECOVERY_KERNEL 0 #define RECOVERY_ROOTFS 1 +static const char *recovery_if, *recovery_part; + +/* The first partition, else the whole medium (a card formatted without a + * partition table); the one that worked is remembered for the calls after. */ static int recovery_mount(const char *ifname) { - if (!fs_set_blk_dev(ifname, "0:1", FS_TYPE_FAT)) + if (recovery_if != ifname) { + recovery_if = ifname; + recovery_part = NULL; + } + if (recovery_part) + return fs_set_blk_dev(ifname, recovery_part, FS_TYPE_FAT) ? -1 : 0; + if (!fs_set_blk_dev(ifname, "0:1", FS_TYPE_FAT)) { + recovery_part = "0:1"; return 0; - if (!fs_set_blk_dev(ifname, "0", FS_TYPE_FAT)) + } + if (!fs_set_blk_dev(ifname, "0", FS_TYPE_FAT)) { + recovery_part = "0"; return 0; + } return -1; } -#ifdef CONFIG_ENV_IS_IN_NAND +/* + * Every fs_* call ends in fs_close(), which forgets the device: the next one + * would read through no filesystem at all ("failed to read" right after + * fs_exists found the file). So each call selects the medium again. + */ +static int rfs_exists(const char *name) { + return !recovery_mount(recovery_if) && fs_exists(name); +} + +static int rfs_size(const char *name, loff_t *size) { + return recovery_mount(recovery_if) ? -1 : fs_size(name, size); +} + +static int rfs_read(const char *name, ulong addr, loff_t offset, loff_t len, + loff_t *actread) { + return recovery_mount(recovery_if) ? -1 : fs_read(name, addr, offset, len, actread); +} + +#ifdef CONFIG_FMC_SPI_NAND static int recovery_ensure_volume(const char *name, const char *size) { struct ubi_volume_desc *desc; char cmd[64]; @@ -252,14 +309,14 @@ static int recovery_write(const char *name, int type) { ulong size; int ret; - if (!fs_exists(name)) + if (!rfs_exists(name)) return 0; if (type == RECOVERY_ROOTFS) { /* Raw ubifs image, written into the rootfs volume */ loff_t fsize; - ret = fs_size(name, &fsize); + ret = rfs_size(name, &fsize); if (ret || fsize == 0) { printf("Recovery: failed to stat %s\n", name); return -1; @@ -267,7 +324,7 @@ static int recovery_write(const char *name, int type) { size = fsize; } else { /* Kernel: uImage or FIT, written to the kernel volume */ - ret = fs_read(name, (ulong)&hdr, 0, sizeof(hdr), &actread); + ret = rfs_read(name, (ulong)&hdr, 0, sizeof(hdr), &actread); if (ret || actread != sizeof(hdr)) { printf("Recovery: failed to read %s\n", name); return -1; @@ -283,7 +340,7 @@ static int recovery_write(const char *name, int type) { } } - ret = fs_read(name, CONFIG_SYS_LOAD_ADDR, 0, size, &actread); + ret = rfs_read(name, CONFIG_SYS_LOAD_ADDR, 0, size, &actread); if (ret || actread != size) { printf("Recovery: failed to read %s\n", name); return -1; @@ -338,50 +395,128 @@ static int recovery_write(const char *name, int type) { #else static struct spi_flash *recovery_flash; -static int recovery_write(const char *name, int type) { - struct image_header hdr; - loff_t actread; - ulong addr, size, erase_len; - int ret; +static int recovery_flash_holds(ulong addr, const void *data, ulong len) { + const ulong chunk = 0x10000; + u8 *buf = malloc(chunk); + ulong off, n; + int same = 1; - if (!fs_exists(name)) + if (!buf) return 0; - - ret = fs_read(name, (ulong)&hdr, 0, sizeof(hdr), &actread); - if (ret || actread != sizeof(hdr)) { - printf("Recovery: failed to read %s\n", name); - return -1; + for (off = 0; same && off < len; off += n) { + n = min(len - off, chunk); + same = !spi_flash_read(recovery_flash, addr + off, n, buf) && + !memcmp(buf, (const u8 *)data + off, n); } + free(buf); + return same; +} - if (image_check_magic(&hdr)) { - addr = image_get_load(&hdr); - size = sizeof(hdr) + image_get_data_size(&hdr); - } else if (type == RECOVERY_KERNEL && fdt_magic(&hdr) == FDT_MAGIC && - fdt_check_header(&hdr) == 0) { - const char *str = env_get("kernaddr"); +/* + * One recovery image: a uImage wrapper whose load address is where in the + * flash its payload goes. Whatever the payload is -- a kernel, a rootfs, a + * whole firmware, U-Boot itself -- it is written there; the wrapper header + * never is. Returns 1 when it wrote, 0 when it skipped, -1 on a failed write. + */ +static int recovery_write_img(const char *name, loff_t size) { + struct image_header hdr; + loff_t actread; + ulong addr, len, sect = recovery_flash->sector_size; - addr = str ? hextoul(str, NULL) : OPENIPC_KERNADDR; - size = fdt_totalsize(&hdr); - } else { - printf("Recovery: no valid uImage/FIT header in %s\n", name); - return -1; + if (size <= (loff_t)sizeof(hdr) || + rfs_read(name, (ulong)&hdr, 0, sizeof(hdr), &actread) || + actread != sizeof(hdr) || !image_check_magic(&hdr) || + !image_check_hcrc(&hdr)) { + printf("Recovery: %s is not a uImage, skipped\n", name); + return 0; + } + addr = image_get_load(&hdr); + len = image_get_data_size(&hdr); + /* in 64 bits: a size near 4 GiB would wrap a 32-bit sum */ + if ((loff_t)len > size - (loff_t)sizeof(hdr)) { + printf("Recovery: %s is shorter than its header says, skipped\n", name); + return 0; + } + if (addr >= recovery_flash->size || len > recovery_flash->size - addr) { + printf("Recovery: %s (0x%lx + %lu bytes) is outside the flash, skipped\n", + name, addr, len); + return 0; + } + if (addr % sect) { + printf("Recovery: %s: 0x%lx is not on a %lu-byte erase boundary, skipped\n", + name, addr, sect); + return 0; + } + /* in RAM below the stack and the relocated U-Boot, with room */ + if (len > gd->start_addr_sp - SZ_1M - CONFIG_SYS_LOAD_ADDR) { + printf("Recovery: %s (%lu bytes) does not fit the free RAM, skipped\n", + name, len); + return 0; + } + if (rfs_read(name, CONFIG_SYS_LOAD_ADDR, sizeof(hdr), len, &actread) || + actread != len) { + printf("Recovery: failed to read %s, skipped\n", name); + return 0; + } + if (crc32(0, (const u8 *)CONFIG_SYS_LOAD_ADDR, len) != image_get_dcrc(&hdr)) { + printf("Recovery: %s: the data CRC does not match, skipped\n", name); + return 0; + } + /* A card left in the slot: nothing to do, the system boots */ + if (recovery_flash_holds(addr, (void *)CONFIG_SYS_LOAD_ADDR, len)) { + printf("Recovery: %s is already on the flash at 0x%lx\n", name, addr); + return 0; } - ret = fs_read(name, CONFIG_SYS_LOAD_ADDR, 0, size, &actread); - if (ret || actread != size) { - printf("Recovery: failed to read %s\n", name); + printf("Recovery: writing %s (%lu bytes) at 0x%lx\n", name, len, addr); + if (spi_flash_erase(recovery_flash, addr, ALIGN(len, sect)) || + spi_flash_write(recovery_flash, addr, len, (void *)CONFIG_SYS_LOAD_ADDR)) { + printf("Recovery: writing %s failed\n", name); return -1; } + printf("Recovery: wrote %s\n", name); + return 1; +} - erase_len = (size + recovery_flash->sector_size - 1) & - ~(recovery_flash->sector_size - 1); - spi_flash_erase(recovery_flash, addr, erase_len); - spi_flash_write(recovery_flash, addr, size, (void *)CONFIG_SYS_LOAD_ADDR); - - printf("Recovery: wrote %s (%lu bytes) to flash at 0x%lx\n", name, size, - addr); +struct recovery_file { + char name[256]; + loff_t size; +}; + +/* Every *.img in the root of the medium, in the order the directory lists + * them. The names are collected first: reading a file closes the + * filesystem under an open directory stream. */ +static int recovery_images(void) { + struct recovery_file *files = NULL, *more; + struct fs_dir_stream *dirs; + struct fs_dirent *d; + int n = 0, i, written = 0; + size_t l; + + if (recovery_mount(recovery_if)) + return 0; + dirs = fs_opendir("/"); + if (!dirs) + return 0; + while ((d = fs_readdir(dirs))) { + l = strlen(d->name); + if (d->type != FS_DT_REG || l < 5 || l >= sizeof(files->name) || + strcasecmp(d->name + l - 4, ".img")) + continue; + more = realloc(files, (n + 1) * sizeof(*files)); + if (!more) + break; + files = more; + strcpy(files[n].name, d->name); + files[n++].size = d->size; + } + fs_closedir(dirs); - return 1; + for (i = 0; i < n; i++) + if (recovery_write_img(files[i].name, files[i].size) > 0) + written++; + free(files); + return written; } #endif @@ -392,16 +527,16 @@ static int recovery_run_script(const char *name) { int ret; char cmd[32]; - if (!fs_exists(name)) + if (!rfs_exists(name)) return 0; - ret = fs_size(name, &size); + ret = rfs_size(name, &size); if (ret || size == 0) { printf("Recovery: failed to stat %s\n", name); return 1; } - ret = fs_read(name, CONFIG_SYS_LOAD_ADDR, 0, size, &actread); + ret = rfs_read(name, CONFIG_SYS_LOAD_ADDR, 0, size, &actread); if (ret || actread != size) { printf("Recovery: failed to read %s\n", name); return 1; @@ -422,45 +557,28 @@ static int recovery_run_script(const char *name) { } static int recovery_try(void) { +#ifdef CONFIG_FMC_SPI_NAND char name[64]; - int ret, found = 0; +#endif - /* script.txt takes precedence over the kernel/rootfs search */ + /* script.txt takes precedence over the image search */ if (recovery_run_script("script.txt")) return 1; -#ifndef CONFIG_ENV_IS_IN_NAND - /* Kernel: uImage preferred, FIT fallback */ - sprintf(name, "uImage.%s.img", CONFIG_PRODUCT_SOC); - ret = recovery_write(name, RECOVERY_KERNEL); - if (ret > 0) { - found = 1; - } else if (ret == 0) { - sprintf(name, "fitImage.%s.img", CONFIG_PRODUCT_SOC); - ret = recovery_write(name, RECOVERY_KERNEL); - if (ret > 0) - found = 1; - } -#endif - +#ifdef CONFIG_FMC_SPI_NAND /* Rootfs. On NAND it carries the kernel (/boot), its volume is sized * to the image and rootfs_data takes the rest. */ -#ifdef CONFIG_ENV_IS_IN_NAND sprintf(name, "rootfs.ubifs.%s.img", CONFIG_PRODUCT_SOC); + return recovery_write(name, RECOVERY_ROOTFS) > 0; #else - sprintf(name, "rootfs.squashfs.%s.img", CONFIG_PRODUCT_SOC); + return recovery_images() > 0; #endif - ret = recovery_write(name, RECOVERY_ROOTFS); - if (ret > 0) - found = 1; - - return found; } int firmware_recovery(void) { int found = 0; -#ifndef CONFIG_ENV_IS_IN_NAND +#ifndef CONFIG_FMC_SPI_NAND recovery_flash = spi_flash_probe(CONFIG_SF_DEFAULT_BUS, CONFIG_SF_DEFAULT_CS, CONFIG_SF_DEFAULT_SPEED, CONFIG_SF_DEFAULT_MODE); @@ -483,7 +601,7 @@ int firmware_recovery(void) { #endif fs_close(); -#ifndef CONFIG_ENV_IS_IN_NAND +#ifndef CONFIG_FMC_SPI_NAND spi_flash_free(recovery_flash); #endif @@ -495,8 +613,77 @@ int firmware_recovery(void) { } #endif +#if !defined(CONFIG_FMC_SPI_NAND) && defined(CONFIG_OPENIPC_NOR_SPLIT) +/* + * The split layout needs nothing from bootargs: the kernel's device tree has + * the partitions and the kernel takes the root its firmware split finds. A + * saved environment still spells out the fixed slots (mtdparts=..., root= + * /dev/mtdblock3), and an older build of this bootloader added + * openipc_nor_fit=1; drop those, in RAM only, so the command line says only + * what is true. + */ +static void nor_split_bootargs(void) +{ + static const char * const drop[] = { "mtdparts=", "root=", "openipc_nor_fit=" }; + const char *cur = env_get("bootargs"); + char buf[1024], *out = buf; + const char *p; + + if (!cur || strlen(cur) >= sizeof(buf)) + return; + for (p = cur; *p;) { + const char *end = strchr(p, ' '); + size_t len = end ? (size_t)(end - p) : strlen(p); + int keep = len > 0; + int i; + + for (i = 0; keep && i < ARRAY_SIZE(drop); i++) + if (!strncmp(p, drop[i], strlen(drop[i]))) + keep = 0; + if (keep) { + if (out != buf) + *out++ = ' '; + memcpy(out, p, len); + out += len; + } + p += len; + while (*p == ' ') + p++; + } + *out = '\0'; + if (strcmp(buf, cur)) + env_set("bootargs", buf); +} + +/* + * The split layout: a uImage at the kernel offset and the root squashfs at the + * next 64 KiB block after it, in one "firmware" partition the kernel divides + * at boot. The fixed-slot layout also starts with a uImage, but its squashfs + * sits at the slot boundary. + */ +static int nor_split_layout(struct spi_flash *flash, ulong kernel_off, ulong rootfs_off) +{ + image_header_t hdr; + + /* + * A squashfs at a fixed rootfs slot is fixed slots, even when the uImage + * before it ends in the kernel slot's last block and so looks split: a + * kernel from before the split layout needs its mtdparts and root=. A + * split image of that size loses nothing, as the kernel finds the split + * itself and ignores both. + */ + if (rootfs_off - kernel_off == 0x200000 || rootfs_off - kernel_off == 0x300000) + return 0; + if (!kernel_off || !rootfs_off || + spi_flash_read(flash, kernel_off, sizeof(hdr), &hdr) || + !image_check_magic(&hdr)) + return 0; + return rootfs_off == kernel_off + ALIGN(image_get_image_size(&hdr), 0x10000); +} +#endif + int firmware_scan(void) { -#ifdef CONFIG_ENV_IS_IN_NAND +#ifdef CONFIG_FMC_SPI_NAND u8 buf[64]; int ret; @@ -559,13 +746,101 @@ int firmware_scan(void) { scan_spi_device(flash, &kernel_off, &rootfs_off, &kernel_size, &rootfs_size); -#ifndef CONFIG_TARGET_HI3516CV610_FAMILY - if (kernel_size <= 0x200000) - kernel_size = 0x200000; - if (rootfs_size < 0x500000) - rootfs_size = 0x500000; - else if (rootfs_size > 0x500000 && flash->size > 0x800000) - rootfs_size = 0xa00000; +#ifdef CONFIG_OPENIPC_NOR_SPLIT + { + /* fwwrite erases from the firmware start to the end of the chip, as + * recovery does, so no old overlay is left behind a shorter image */ + char str[32]; + const char *ka = env_get("kernaddr"); + ulong start = kernel_off ? kernel_off : + (ka ? hextoul(ka, NULL) : CONFIG_ENV_KERNADDR); + + if (start < flash->size) { + sprintf(str, "0x%lx", (ulong)(flash->size - start)); + env_set("fwsize", str); + /* RAM only, like the rest of the scan: a saved environment keeps an + * older fwwrite that erased only the file's length */ + env_set("fwwrite", "sf probe 0; sf erase ${kernaddr} ${fwsize}; " + "sf write ${baseaddr} ${kernaddr} ${filesize}"); + } + } +#endif + + /* + * Fixed slots -- the rootfs at the 2 or 3 MiB slot boundary after the + * kernel, or nothing found to say otherwise -- get the slots' sizes, so the + * rootfs partition is its whole slot and rootfs_data starts after it. A + * rootfs packed right after the kernel (a FIT and its squashfs written as + * one image) keeps the sizes found: a slot there would put the rootfs + * partition past the squashfs. + * + * The 10 MiB rootfs slot is the 3 MiB kernel slot's (the ultimate pair, + * rootfs at 0x350000; ur moves a large rootfs there before it writes). A + * rootfs over 5 MiB behind the 2 MiB kernel slot is a packed image that + * outgrew the lite slot -- hi3516cv6xx ultimate on 16 MiB, 7104k at + * 0x250000 -- and keeps the size found: rounding it up to 10 MiB moved + * rootfs_data from 0x940000 to 0xc50000, away from the settings there. + */ + if (!kernel_size || kernel_size == 0x200000 || kernel_size == 0x300000) { + if (kernel_size <= 0x200000) + kernel_size = 0x200000; + if (rootfs_size < 0x500000) + rootfs_size = 0x500000; + else if (kernel_size == 0x300000 && rootfs_size < 0xa00000 && flash->size > 0x800000) + rootfs_size = 0xa00000; + } + +#ifdef CONFIG_OPENIPC_NOR_SPLIT + if (nor_split_layout(flash, kernel_off, rootfs_off)) { + char str[32], mtds[128], mtdparts[160]; + const char *mtdids = env_get("mtdids"); + ulong kern_slot, root_slot; + + /* + * kernsize is what loadkernel reads, and also what uk erases; rootaddr + * and rootsize are where ur writes. Those commands install a uImage and a + * squashfs, i.e. fixed slots, so they get the fixed slots: the kernel + * slot whole (the split squashfs starts inside it, and a head left there + * would be found by the next scan before the new rootfs), the rootfs slot + * after it. The kernel slot holds the whole uImage either way. + */ + if (rootfs_off - kernel_off <= 0x200000) { + kern_slot = 0x200000; + root_slot = 0x500000; + } else if (rootfs_off - kernel_off <= 0x300000 && flash->size > 0x800000) { + kern_slot = 0x300000; + root_slot = 0xa00000; + } else { + /* No fixed-slot layout holds this kernel. */ + kern_slot = rootfs_off - kernel_off; + root_slot = flash->size - rootfs_off; + } + spi_flash_free(flash); + sprintf(str, "0x%08lx", kernel_off); + env_set("kernaddr", str); + sprintf(str, "0x%lx", kern_slot); + env_set("kernsize", str); + sprintf(str, "0x%08lx", kernel_off + kern_slot); + env_set("rootaddr", str); + sprintf(str, "0x%lx", root_slot); + env_set("rootsize", str); +#ifdef SFC + if (!mtdids || !*mtdids) + mtdids = SFC; +#endif + /* U-Boot's own idea of the flash; the kernel is not told. */ + sprintf(mtds, "%dk(u-boot),%dk(env),-(firmware)", + CONFIG_ENV_OFFSET / 1024, CONFIG_ENV_SIZE / 1024); + env_set("mtds", mtds); + sprintf(mtdparts, "%s:%s", mtdids ? mtdids : "", mtds); + env_set("mtdparts", mtdparts); + /* bootm checks the uImage's data CRC; a verify=n saved by an older + * bootloader must not skip it. RAM only, like the rest of the scan. */ + env_set("verify", NULL); + nor_split_bootargs(); + env_set("bootfail", NULL); + return 0; + } #endif spi_flash_free(flash); From 3b0b70e95e5fea0bc275bf57387d54c79436c5f9 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:15:47 +0000 Subject: [PATCH 5/8] gk710x: SD card, for recovery on cameras without Ethernet A GK7102C camera has no Ethernet, so TFTP recovery cannot bring it back; the SD card can (common/openipc.c). - arch/arm/mach-gk710x/sdhci.c: GK7101/GK7102 have a standard SDHCI at 0x60002000. The GK710xS has it at 0x90000000 in the vendor halfword layout, with registers moved and bits swapped; the accessors map every SDHCI register through a table taken from the S HAL. The version reads SDHCI_SPEC_200, and the base clock comes from the RCT SD PLL and scaler. - gpio_map.c: also set the map's SD pins and SD power pin. - openipc_config: MMC, SDHCI, DOS partitions, FAT; LTO and a few unused commands off to stay in the 256 KiB boot partition (gk7101/gk7102 231892, gk7101s 222548, gk7102s 222808 bytes). Under qemu-hisilicon gk7102 and gk7102s (the latter on a GK7102C flash dump), from an MBR + FAT32 card, two boots with the card left in: the U-Boot, kernel and rootfs images are written at their load addresses and boot to login; a bad data CRC, a non-uImage, an image past the flash end and one off an erase boundary are skipped; on the second boot all three are already on the flash and the saved environment loads. --- CLAUDE.md | 14 +- arch/arm/mach-gk710x/Kconfig | 1 + arch/arm/mach-gk710x/Makefile | 1 + arch/arm/mach-gk710x/include/mach/gk710x.h | 6 + arch/arm/mach-gk710x/sdhci.c | 296 +++++++++++++++++++++ board/goke/gk710x/gpio_map.c | 36 ++- openipc/openipc_config | 23 +- 7 files changed, 367 insertions(+), 10 deletions(-) create mode 100644 arch/arm/mach-gk710x/sdhci.c diff --git a/CLAUDE.md b/CLAUDE.md index 86c70a763c42..885bf6ca7fdf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -188,9 +188,17 @@ included. - `gk_ephy_init()` does the vendor's PHY clock (audio PLL, out on a pin), analog trim and MAC/PHY mux. - External-PHY boards (`phytype=1/2`) are not handled. -- **SD:** not enabled. The vendor U-Boot had none, the controller's base - clock is unknown, and the SD pins depend on the board map. SD recovery in - `common/openipc.c` is compiled out with `CONFIG_MMC`. +- **SD:** for recovery, the only way back for a camera without Ethernet + (`common/openipc.c`: `script.txt`, or uImage-wrapped `*.img` written at + their load address). `arch/arm/mach-gk710x/sdhci.c`: + - GK7101/GK7102: a standard SDHCI at `0x60002000`; + - GK710xS: at `0x90000000` in the vendor's halfword layout, so every + access goes through a table of where each SDHCI register and bit + sits, taken from what the S HAL does; + - the base clock comes from the RCT SD PLL and scaler. + + U-Boot sets the map's SD pins and SD power pin. MMC, FAT and LTO + together still fit the 256 KiB boot partition. - **SPI NAND:** not supported. OpenIPC's GK7102 firmware is NOR only. ## The GK710xS (GK7101S, GK7102S, GK7102C) diff --git a/arch/arm/mach-gk710x/Kconfig b/arch/arm/mach-gk710x/Kconfig index dfac3f298e73..e4feead82201 100644 --- a/arch/arm/mach-gk710x/Kconfig +++ b/arch/arm/mach-gk710x/Kconfig @@ -32,6 +32,7 @@ endchoice config GK710X_S bool + select MMC_SDHCI_IO_ACCESSORS if MMC_SDHCI help The GK710xS die: its blocks sit at the addresses the vendor drivers use, in their register layout, instead of being moved and diff --git a/arch/arm/mach-gk710x/Makefile b/arch/arm/mach-gk710x/Makefile index 46654b9be11c..c81b985957b7 100644 --- a/arch/arm/mach-gk710x/Makefile +++ b/arch/arm/mach-gk710x/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0+ obj-y += lowlevel_init.o soc.o gpio.o kernel.o +obj-$(CONFIG_MMC_SDHCI) += sdhci.o diff --git a/arch/arm/mach-gk710x/include/mach/gk710x.h b/arch/arm/mach-gk710x/include/mach/gk710x.h index 73479c4b240f..264ac5ba3684 100644 --- a/arch/arm/mach-gk710x/include/mach/gk710x.h +++ b/arch/arm/mach-gk710x/include/mach/gk710x.h @@ -20,6 +20,7 @@ #ifndef CONFIG_GK710X_S /* AHB */ +#define GK_SDIO0_BASE 0x60002000 /* SDHCI */ #define GK_DDRC_BASE 0x60004000 #define GK_GMAC_BASE 0x6000E000 #define GK_SFLASH_BASE 0x60016000 @@ -52,6 +53,8 @@ #define GK_RCT_DIV_AUDIO_PRE (GK_RCT_BASE + 0x060) #define GK_RCT_SCALER_UART (GK_RCT_BASE + 0x038) #define GK_RCT_PLL_CORE (GK_RCT_BASE + 0x0E4) +#define GK_RCT_PLL_SD (GK_RCT_BASE + 0x000) /* fields moved */ +#define GK_RCT_SCALER_SD (GK_RCT_BASE + 0x00C) #define GK_RCT_IOCTRL_MISC2 (GK_RCT_BASE + 0x200) #define GK_IOCTRL_MISC2_VAL 0x11 /* Pad drive and pull: one byte per pin, four pins per word */ @@ -70,6 +73,7 @@ */ /* AHB */ +#define GK_SDIO0_BASE 0x90000000 /* vendor layout, sdhci.c */ #define GK_DDRC_BASE 0x90002000 #define GK_GMAC_BASE 0x9000E000 /* DWMAC, registers reordered */ #define GK_SFLASH_BASE 0x90016000 @@ -100,6 +104,8 @@ #define GK_RCT_DIV_AUDIO_PST (GK_RCT_BASE + 0x084) #define GK_RCT_DIV_AUDIO_PRE (GK_RCT_BASE + 0x080) #define GK_RCT_SCALER_UART (GK_RCT_BASE + 0x028) +#define GK_RCT_PLL_SD (GK_RCT_BASE + 0x014) +#define GK_RCT_SCALER_SD (GK_RCT_BASE + 0x018) #define GK_RCT_IOCTRL_MISC2 (GK_RCT_BASE + 0x198) #define GK_IOCTRL_MISC2_VAL 0x12 /* the flash HOLD pin pulled up */ /* Pad control words count down from GPIO 55 (gk7101s_gpio.c) */ diff --git a/arch/arm/mach-gk710x/sdhci.c b/arch/arm/mach-gk710x/sdhci.c new file mode 100644 index 000000000000..60d53504f264 --- /dev/null +++ b/arch/arm/mach-gk710x/sdhci.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Goke GK710x SD controller, for SD-card recovery (common/openipc.c). + * + * Underneath it is a standard SDHCI. On the GK7101/GK7102 it sits at + * 0x60002000 as one. On the GK710xS the vendor drivers reach it with no + * HAL in between, in their own layout (arch-gk7101s/gk7101_reg.h + * REG_SDIO_*): registers in another order, 16 bits wide where SDHCI packs + * bytes, some bits permuted. That layout is exactly what the GK7101/GK7102 + * HAL translates into the SDHCI one, so the table below is that HAL's + * translation, recovered by running its hw_write/hw_read accessors of each + * width under Unicorn (the read and write maps agree), with what the S + * drivers change on top of it: the transfer-mode direction and multi-block + * bits swapped (mach-gk710xs sd.h). The timeout, reset, host, power, gap + * and wake-up registers are bytes with bit orders of their own; the same + * addresses read as words are something else. The accessors put mainline's + * SDHCI driver on top of this table. + */ + +#include +#include +#include +#include +#include +#include + +#define GK_SD_FREQ_MAX 48000000 + +/* + * The SD base clock: 24 MHz * fbdiv / (refdiv * pstdiv1 * pstdiv2) of the + * core PLL, divided by the SD scaler (the vendor kernel's get_sd_freq_hz()). + * The scaler is set here only if nothing has set it. + */ +static unsigned int gk_sd_clock(void) +{ + u32 pll = readl(GK_RCT_PLL_SD); + u32 fbdiv, pst1, pst2, ref, div; + ulong vco; + +#ifdef CONFIG_GK710X_S + fbdiv = pll & 0xfff; + pst1 = (pll >> 12) & 7; + pst2 = (pll >> 16) & 7; + ref = (pll >> 20) & 0x3f; +#else + /* The GK7101/GK7102 HAL moves the fields on the way in */ + fbdiv = (pll >> 8) & 0xfff; + pst1 = (pll >> 20) & 7; + pst2 = (pll >> 24) & 7; + ref = pll & 0x3f; +#endif + if (!fbdiv || !pst1 || !pst2 || !ref) + return GK_SD_FREQ_MAX; + vco = 24000000UL / ref * fbdiv / (pst1 * pst2); + + div = readl(GK_RCT_SCALER_SD); + if (!div) { + div = DIV_ROUND_UP(vco, GK_SD_FREQ_MAX); + writel(div, GK_RCT_SCALER_SD); + } + return vco / div; +} + +#ifdef CONFIG_GK710X_S +/* + * One vendor register: its offset and width, the SDHCI register it is, and + * for each of its bits the SDHCI bit (-1: the bit does not exist there). + * A register not listed is the identity: SDHCI offsets from 0x44 up. + */ +struct gk_sd_reg { + u8 off, width, std; + s8 bit[32]; +}; + +#define ID8 { 0, 1, 2, 3, 4, 5, 6, 7 } +#define ID16 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } +#define ID32 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, \ + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 } +#define N -1 + +static const struct gk_sd_reg gk_sd_regs[] = { + { 0x00, 32, SDHCI_DMA_ADDRESS, ID32 }, + { 0x04, 16, SDHCI_TRANSFER_MODE, + { 1, 2, 0, N, 5, 4, N, N, N, N, N, N, N, N, N, N } }, + { 0x06, 16, SDHCI_SIGNAL_ENABLE, ID16 }, + { 0x08, 16, SDHCI_SIGNAL_ENABLE + 2, + { 0, 2, 3, 4, 1, 5, 6, 7, 8, N, N, N, N, N, 14, 15 } }, + { 0x0a, 16, SDHCI_BLOCK_COUNT, ID16 }, + { 0x0c, 16, SDHCI_BLOCK_SIZE, ID16 }, + { 0x0e, 16, SDHCI_INT_ENABLE, ID16 }, + { 0x10, 16, SDHCI_INT_ENABLE + 2, + { 0, 2, 1, 3, 5, 4, 6, 7, 8, N, N, N, N, N, 14, 15 } }, + { 0x12, 16, SDHCI_INT_STATUS, + { 0, 2, 3, 1, 4, 6, 5, 7, 8, 9, N, N, N, N, N, 15 } }, + { 0x14, 16, SDHCI_INT_STATUS + 2, ID16 }, + { 0x16, 16, SDHCI_COMMAND, + { 0, 1, N, 3, 5, 4, 6, 7, 8, 9, 10, 11, 12, 13, N, N } }, + { 0x18, 8, SDHCI_TIMEOUT_CONTROL, ID8 }, + { 0x19, 8, SDHCI_SOFTWARE_RESET, { 1, 0, 2, 3, 4, 5, 6, 7 } }, + { 0x1a, 16, SDHCI_CLOCK_CONTROL, ID16 }, + { 0x1c, 32, SDHCI_RESPONSE, ID32 }, + { 0x20, 32, SDHCI_RESPONSE + 4, ID32 }, + { 0x24, 32, SDHCI_RESPONSE + 8, ID32 }, + { 0x28, 32, SDHCI_RESPONSE + 12, ID32 }, + { 0x2c, 8, SDHCI_HOST_CONTROL, { 0, 1, 3, 2, 4, 5, 6, 7 } }, + { 0x2d, 8, SDHCI_POWER_CONTROL, ID8 }, + { 0x2e, 8, SDHCI_BLOCK_GAP_CONTROL, { 0, 2, 1, 3, 7, 5, 6, 4 } }, + { 0x2f, 8, SDHCI_WAKE_UP_CONTROL, { 1, 0, 2, 3, 4, 5, 6, 7 } }, + { 0x30, 32, SDHCI_PRESENT_STATE, + { 0, 2, 1, N, N, N, N, N, 9, 10, 8, 11, N, N, N, N, + 16, 18, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, N, N, N } }, + { 0x34, 32, SDHCI_ARGUMENT, ID32 }, + { 0x38, 32, SDHCI_CAPABILITIES, + { 0, 1, 2, 3, 4, 5, N, 7, 8, 9, 10, 11, 12, 13, N, N, + 16, 17, 18, N, N, 21, 23, 22, 24, 25, 26, 27, N, N, N, N } }, + { 0x3c, 32, SDHCI_ACMD12_ERR, + { 1, 2, 3, 0, 4, N, N, 7, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N } }, + { 0x40, 32, SDHCI_BUFFER, ID32 }, +}; + +static const struct gk_sd_reg *gk_sd_find(int std) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(gk_sd_regs); i++) { + const struct gk_sd_reg *r = &gk_sd_regs[i]; + + if (std >= r->std && std < r->std + r->width / 8) + return r; + } + return NULL; +} + +/* A vendor register's value as SDHCI bits, and back */ +static u32 gk_sd_to_std(const struct gk_sd_reg *r, u32 v) +{ + u32 s = 0; + int i; + + for (i = 0; i < r->width; i++) + if (r->bit[i] >= 0 && (v & BIT(i))) + s |= BIT(r->bit[i]); + return s; +} + +static u32 gk_sd_from_std(const struct gk_sd_reg *r, u32 s) +{ + u32 v = 0; + int i; + + for (i = 0; i < r->width; i++) + if (r->bit[i] >= 0 && (s & BIT(r->bit[i]))) + v |= BIT(i); + return v; +} + +static u32 gk_sd_rd(struct sdhci_host *host, const struct gk_sd_reg *r) +{ + void *a = host->ioaddr + r->off; + + return gk_sd_to_std(r, r->width == 8 ? readb(a) : + r->width == 16 ? readw(a) : readl(a)); +} + +static void gk_sd_wr(struct sdhci_host *host, const struct gk_sd_reg *r, + u32 s) +{ + void *a = host->ioaddr + r->off; + + if (r->width == 8) + writeb(gk_sd_from_std(r, s), a); + else if (r->width == 16) + writew(gk_sd_from_std(r, s), a); + else + writel(gk_sd_from_std(r, s), a); +} + +/* + * An access of `size` bytes at SDHCI offset `reg`: whole vendor registers + * are read or written as they are; a narrower write into one is merged + * with its current value (the control registers SDHCI writes bytewise). + */ +static u32 gk_sd_read(struct sdhci_host *host, int reg, int size) +{ + u32 v = 0; + int i; + + /* No version register in this layout; the vendor clocks it as 2.0 */ + if (reg == SDHCI_HOST_VERSION) + return SDHCI_SPEC_200; + + for (i = 0; i < size; ) { + const struct gk_sd_reg *r = gk_sd_find(reg + i); + int sh; + + if (!r) { + v |= (size == 4 ? readl(host->ioaddr + reg) : + size == 2 ? readw(host->ioaddr + reg) : + readb(host->ioaddr + reg)) << (8 * i); + break; + } + sh = 8 * (reg + i - r->std); + v |= (gk_sd_rd(host, r) >> sh) << (8 * i); + i += r->width / 8 - (reg + i - r->std); + } + return size == 4 ? v : v & ((1u << (8 * size)) - 1); +} + +static void gk_sd_write(struct sdhci_host *host, u32 val, int reg, int size) +{ + int i; + + for (i = 0; i < size; ) { + const struct gk_sd_reg *r = gk_sd_find(reg + i); + int start, n; + u32 mask, s; + + if (!r) { + if (size == 4) + writel(val, host->ioaddr + reg); + else if (size == 2) + writew(val, host->ioaddr + reg); + else + writeb(val, host->ioaddr + reg); + return; + } + start = reg + i - r->std; + n = min(size - i, r->width / 8 - start); + mask = (n == 4 ? ~0u : (1u << (8 * n)) - 1) << (8 * start); + s = (val >> (8 * i)) << (8 * start) & mask; + if (n * 8 != r->width) + s |= gk_sd_rd(host, r) & ~mask; + gk_sd_wr(host, r, s); + i += n; + } +} + +static u32 gk_sd_readl(struct sdhci_host *host, int reg) +{ + return gk_sd_read(host, reg, 4); +} + +static u16 gk_sd_readw(struct sdhci_host *host, int reg) +{ + return gk_sd_read(host, reg, 2); +} + +static u8 gk_sd_readb(struct sdhci_host *host, int reg) +{ + return gk_sd_read(host, reg, 1); +} + +static void gk_sd_writel(struct sdhci_host *host, u32 val, int reg) +{ + gk_sd_write(host, val, reg, 4); +} + +static void gk_sd_writew(struct sdhci_host *host, u16 val, int reg) +{ + gk_sd_write(host, val, reg, 2); +} + +static void gk_sd_writeb(struct sdhci_host *host, u8 val, int reg) +{ + gk_sd_write(host, val, reg, 1); +} + +static const struct sdhci_ops gk_sd_ops = { + .read_l = gk_sd_readl, + .read_w = gk_sd_readw, + .read_b = gk_sd_readb, + .write_l = gk_sd_writel, + .write_w = gk_sd_writew, + .write_b = gk_sd_writeb, +}; +#endif /* CONFIG_GK710X_S */ + +int board_mmc_init(struct bd_info *bis) +{ + struct sdhci_host *host = calloc(1, sizeof(*host)); + unsigned int clk = gk_sd_clock(); + + if (!host) + return -ENOMEM; + host->name = "gk-sdhci"; + host->ioaddr = (void *)GK_SDIO0_BASE; + host->max_clk = clk; + host->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + /* The vendor layout has no power register: the slot is always on */ + host->quirks = SDHCI_QUIRK_BROKEN_VOLTAGE | SDHCI_QUIRK_NO_HISPD_BIT; +#ifdef CONFIG_GK710X_S + host->ops = &gk_sd_ops; +#endif + return add_sdhci(host, clk, 400000); +} diff --git a/board/goke/gk710x/gpio_map.c b/board/goke/gk710x/gpio_map.c index 7af2d1ad0f21..882e247def55 100644 --- a/board/goke/gk710x/gpio_map.c +++ b/board/goke/gk710x/gpio_map.c @@ -510,15 +510,35 @@ static const struct gk_gpio_map *gk_map(void) } /* - * U-Boot itself needs one pin: the internal PHY's reference clock, out on - * GPIO 48 in both layouts. The vendor U-Boot applied the whole map here, - * but the map is a guess until gpiomap is set, and the v2 map moves UART0 - * off GPIO 30/31 -- a v1 board would lose its console before anyone could - * set gpiomap=v1. The pins stay as the boot ROM left them; the kernel - * applies the full map it is handed. + * U-Boot itself needs the internal PHY's reference clock, out on GPIO 48 + * in the layouts that have it, and the SD pins. The vendor U-Boot applied + * the whole map here, but the map is a guess until gpiomap is set, and the + * v2 map moves UART0 off GPIO 30/31 -- a v1 board would lose its console + * before anyone could set gpiomap=v1. The other pins stay as the boot ROM + * left them; the kernel applies the full map it is handed. */ #define GK_EPHY_CLK_PIN 48 +/* + * The SD pin functions: card detect, CMD, CLK, DATA0-3. Their pin types + * are the same on both dies. With the map's SD power pin they are what + * U-Boot needs for SD-card recovery; on boards without SD the map has + * none of them. + */ +static bool gk_gpio_is_sd(u32 type) +{ + static const u32 sd[] = { + 0x50010f00, 0xd090181e, 0x9000001f, + 0xd0881016, 0xd0891117, 0xd08a1218, 0xd08b1319, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(sd); i++) + if (type == sd[i]) + return true; + return false; +} + void gk_gpio_map_apply(void) { const struct gk_gpio_map *m = gk_map(); @@ -527,6 +547,10 @@ void gk_gpio_map_apply(void) for (i = 0; i < m->nintphy; i++) if (m->intphy[i].pin == GK_EPHY_CLK_PIN) gk_gpio_config(m->intphy[i].pin, m->intphy[i].type); + for (i = 0; i < m->npins; i++) + if (gk_gpio_is_sd(m->pins[i].type) || + m->pins[i].pin == m->sd_power) + gk_gpio_config(m->pins[i].pin, m->pins[i].type); gk_gpio_init(); } diff --git a/openipc/openipc_config b/openipc/openipc_config index 3ddf3089b22a..bfcaea3e3297 100644 --- a/openipc/openipc_config +++ b/openipc/openipc_config @@ -78,6 +78,28 @@ CONFIG_CMD_MEMORY=y CONFIG_CMD_CRC32=y CONFIG_CMD_SLEEP=y +# SD card: recovery images and scripts (common/openipc.c); the only way +# back for a camera without Ethernet +CONFIG_MMC=y +CONFIG_MMC_SDHCI=y +# CONFIG_MMC_HW_PARTITIONING is not set +CONFIG_CMD_MMC=y +CONFIG_DOS_PARTITION=y +CONFIG_FS_FAT=y +CONFIG_CMD_FAT=y + +# Room for it in the 256 KiB boot partition, with no Thumb-2 on ARM1176: +# link-time optimisation, and no commands the OpenIPC flows have no use for +# here (these kernels take no device tree) +CONFIG_LTO=y +# CONFIG_CMD_FDT is not set +# CONFIG_CMD_ELF is not set +# CONFIG_CMD_XIMG is not set +# CONFIG_CMD_NFS is not set +# CONFIG_CMD_IMPORTENV is not set +# CONFIG_CMD_EXPORTENV is not set +# CONFIG_CMD_CONSOLE is not set + # Network: the GMAC is a DesignWare MAC, the PHY the SoC's own CONFIG_NET=y CONFIG_NETDEVICES=y @@ -87,6 +109,5 @@ CONFIG_CMD_NET=y CONFIG_CMD_TFTPBOOT=y CONFIG_CMD_DHCP=y CONFIG_CMD_PING=y -CONFIG_CMD_MII=y CONFIG_NET_RANDOM_ETHADDR=y CONFIG_LIB_RAND=y From 9e09c42f385419a06604ace11b1b351256c982c0 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:40:53 +0000 Subject: [PATCH 6/8] 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 3685ffd45af742c6bcc8f4de092b48f19b0cc23c Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:40:53 +0000 Subject: [PATCH 7/8] gk_sflash: swap a whole-chip dump written at 0 over its whole length The window swapped on write is the boot image's span. A programmer's whole-chip dump written in one go at 0 (sf write, or a recovery image at 0) is in chip order throughout, so everything past U-Boot -- env, kernel, rootfs -- landed swapped twice. A write at 0 that starts with the swapped magic is now swapped over its whole length when it is longer than the boot image, as cmd/sf.c did before. The read window stays the boot image. Under qemu-hisilicon gk7102: a recovery image holding a 16 MiB GK7102 camera dump, at 0, leaves the flash byte for byte the dump. Reported by Qodo on #45. --- drivers/mtd/spi/gk_sflash.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/spi/gk_sflash.c b/drivers/mtd/spi/gk_sflash.c index 2806f87344d5..6a3b36839ba7 100644 --- a/drivers/mtd/spi/gk_sflash.c +++ b/drivers/mtd/spi/gk_sflash.c @@ -176,6 +176,12 @@ static int sf_program(u32 addr, const u8 *buf, unsigned int words) * 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. */ static u32 sf_swap_end; @@ -263,11 +269,13 @@ static int gk_sf_write(struct mtd_info *mtd, loff_t to, size_t len, u32 addr = to & ~3; unsigned int head = to & 3; unsigned int i; + u32 end; int ret; if (to + len > mtd->size) return -EINVAL; sf_swap_detect(to, len, buf); + end = !to && sf_swap_end ? max_t(u32, sf_swap_end, len) : sf_swap_end; *retlen = 0; while (len) { unsigned int room = SZ_PAGE - (addr & (SZ_PAGE - 1)); @@ -276,7 +284,7 @@ static int gk_sf_write(struct mtd_info *mtd, loff_t to, size_t len, memset(page, 0xff, span); memcpy(page + head, buf, n); - for (i = 0; i < span && addr + i < sf_swap_end; i += 4) + for (i = 0; i < span && addr + i < end; i += 4) put_unaligned_le32(swab32(get_unaligned_le32(page + i)), page + i); ret = sf_program(addr, page, span / 4); From f1a88eb3d855da4b33813d4b724d542fab6dde6f Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Tue, 20 Oct 2026 22:40:53 +0000 Subject: [PATCH 8/8] gk710x: set the SD pins only from a map known to be the board's The SD pins came from the default map even with gpiomap unset. On the GK7101/GK7102 that default (v2) is a guess, and its SD pins are a v1 board's PHY reset, audio clock and external-PHY data lines -- v1 boards have no SD. They are now set on the GK710xS, which has one map, and on the GK7101/GK7102 once gpiomap names the board's. CLAUDE.md: what a whole-chip dump through sf write, sf update and sf read comes to. Reported by Qodo on #45. --- CLAUDE.md | 6 ++++++ board/goke/gk710x/gpio_map.c | 12 ++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 885bf6ca7fdf..f37ea7350711 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -124,6 +124,12 @@ The kernel still needs the blob (see "Hand-off to the kernel"). 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 diff --git a/board/goke/gk710x/gpio_map.c b/board/goke/gk710x/gpio_map.c index 882e247def55..7ce1d171d8f9 100644 --- a/board/goke/gk710x/gpio_map.c +++ b/board/goke/gk710x/gpio_map.c @@ -511,7 +511,7 @@ static const struct gk_gpio_map *gk_map(void) /* * U-Boot itself needs the internal PHY's reference clock, out on GPIO 48 - * in the layouts that have it, and the SD pins. The vendor U-Boot applied + * in the layouts that have it, and the SD pins where the map is certain. The vendor U-Boot applied * the whole map here, but the map is a guess until gpiomap is set, and the * v2 map moves UART0 off GPIO 30/31 -- a v1 board would lose its console * before anyone could set gpiomap=v1. The other pins stay as the boot ROM @@ -547,7 +547,15 @@ void gk_gpio_map_apply(void) for (i = 0; i < m->nintphy; i++) if (m->intphy[i].pin == GK_EPHY_CLK_PIN) gk_gpio_config(m->intphy[i].pin, m->intphy[i].type); - for (i = 0; i < m->npins; i++) + /* + * The SD pins only from a map known to be the board's: the GK710xS + * has one, the GK7101/GK7102 default is a guess until gpiomap is set, + * and the v2 SD pins are a v1 board's PHY reset, audio clock and PHY + * data lines (v1 boards have no SD). + */ + if (!IS_ENABLED(CONFIG_GK710X_S) && !env_get("gpiomap")) + m = NULL; + for (i = 0; m && i < m->npins; i++) if (gk_gpio_is_sd(m->pins[i].type) || m->pins[i].pin == m->sd_power) gk_gpio_config(m->pins[i].pin, m->pins[i].type);