diff --git a/agent/Makefile b/agent/Makefile index d6de9cc..bbba356 100644 --- a/agent/Makefile +++ b/agent/Makefile @@ -105,8 +105,22 @@ else ifeq ($(SOC),hi3516cv610) WDT_BASE = 0x12030000 CRG_BASE = 0x12010000 SYSCTRL_REBOOT = 0x12020004 +else ifeq ($(SOC),hi3519v101) + # V3A generation (3519v101 family: hi3519v101, hi3516av200) — Cortex-A7 + # with V3-era peripheral addresses (UART 0x12100000, WDT 0x12080000) + # but DDR at 0x80000000 like cv500-family. Memory map per + # qemu-hisilicon hi3519v101_soc. + UART_BASE = 0x12100000 + UART_CLOCK = 24000000 + LOAD_ADDR = 0x81000000 + FLASH_MEM = 0x14000000 + FMC_BASE = 0x10000000 + RAM_BASE = 0x80000000 + WDT_BASE = 0x12080000 + CRG_BASE = 0x12010000 + SYSCTRL_REBOOT = 0x12020004 else - $(error Unknown SOC: $(SOC). Supported: hi3516ev300 hi3516ev200 gk7205v200 gk7205v300 hi3516cv300 hi3516cv500 hi3518ev200 hi3516cv610) + $(error Unknown SOC: $(SOC). Supported: hi3516ev300 hi3516ev200 gk7205v200 gk7205v300 hi3516cv300 hi3516cv500 hi3518ev200 hi3516cv610 hi3519v101) endif # Per-SoC CPU. V3 chips (cv300) are ARM926EJ-S (ARMv5TEJ); diff --git a/agent/main.c b/agent/main.c index a5968a3..a5f14c0 100644 --- a/agent/main.c +++ b/agent/main.c @@ -131,10 +131,10 @@ static void handle_info(void) { resp[0] = flash_info.jedec_id[0]; resp[1] = flash_info.jedec_id[1]; resp[2] = flash_info.jedec_id[2]; - resp[3] = 0; + resp[3] = flash_info.flash_type; /* was reserved padding */ write_le32(&resp[4], flash_info.size); write_le32(&resp[8], RAM_BASE); - write_le32(&resp[12], 0x10000); /* 64KB sector */ + write_le32(&resp[12], flash_info.sector_size); /* NOR=64K, NAND=128K */ write_le32(&resp[16], AGENT_VERSION); write_le32(&resp[20], AGENT_CAPS); proto_send(RSP_INFO, resp, 24); diff --git a/agent/spi_flash.c b/agent/spi_flash.c index e80a870..44f645a 100644 --- a/agent/spi_flash.c +++ b/agent/spi_flash.c @@ -18,21 +18,51 @@ #define FMC_INT 0x18 #define FMC_INT_CLR 0x20 #define FMC_CMD 0x24 +#define FMC_ADDRH 0x28 #define FMC_ADDRL 0x2C #define FMC_OP_CFG 0x30 #define FMC_DATA_NUM 0x38 #define FMC_OP 0x3C +#define FMC_DMA_LEN 0x40 +#define FMC_DMA_SADDR_D0 0x4C +#define FMC_DMA_SADDR_OOB 0x5C +#define FMC_OP_CTRL 0x68 #define FMC_STATUS 0xAC #define FMC_VERSION 0xBC /* FMC_CFG bits */ #define FMC_CFG_OP_MODE_NORMAL (1 << 0) #define FMC_CFG_FLASH_SEL_NOR (0 << 1) +#define FMC_CFG_FLASH_SEL_NAND (1 << 1) +#define FMC_CFG_FLASH_SEL_MASK (3 << 1) /* FMC_OP_CFG bits */ #define OP_CFG_OEN_EN (1 << 13) +#define OP_CFG_FM_CS(n) ((n) << 11) #define OP_CFG_CS(n) ((n) << 11) #define OP_CFG_ADDR_NUM(n) ((n) << 4) /* 3 or 4 byte address */ +#define OP_CFG_DUMMY_NUM(n) ((n) & 0xF) /* dummy bytes between address and data */ +#define OP_CFG_MEM_IF_TYPE(t) (((t) & 0x7) << 7) + +/* FMC_OP_CTRL bits — used by the NAND-aware page-read/program flow. + * Unlike FMC_OP (which fits classic NOR-style cmd+addr+data), OP_CTRL + * lets the FMC sequence PAGE_READ + READ_FROM_CACHE (or PROGRAM_LOAD + + * PROGRAM_EXECUTE) internally — including the chip's post-address + * dummy timing — and stream a full 2 KiB page via DMA. */ +#define OP_CTRL_RD_OPCODE(c) (((c) & 0xff) << 16) +#define OP_CTRL_WR_OPCODE(c) (((c) & 0xff) << 8) +#define OP_CTRL_RD_OP_SEL(o) (((o) & 0x3) << 4) +#define OP_CTRL_DMA_OP(t) ((t) << 2) +#define OP_CTRL_RW_OP(r) ((r) << 1) +#define OP_CTRL_DMA_OP_READY 1 +#define RD_OP_READ_ALL_PAGE 0 +#define OP_TYPE_DMA 0 +#define OP_TYPE_REG 1 +#define RW_OP_READ 0 +#define RW_OP_WRITE 1 + +/* FMC_INT bits */ +#define FMC_INT_OP_DONE (1 << 0) /* FMC_OP bits */ #define FMC_OP_REG_OP_START (1 << 0) @@ -50,10 +80,36 @@ #define SPI_CMD_SECTOR_ERASE 0xD8 #define SPI_CMD_CHIP_ERASE 0xC7 -/* SPI status bits */ +/* SPI NOR status bits */ #define SPI_STATUS_WIP (1 << 0) #define SPI_STATUS_WEL (1 << 1) +/* SPI NAND commands (MX35LF1GE4AB / generic SPI NAND) */ +#define SPI_CMD_NAND_GET_FEATURES 0x0F +#define SPI_CMD_NAND_SET_FEATURE 0x1F +#define SPI_CMD_NAND_PAGE_READ 0x13 /* row addr -> chip cache */ +#define SPI_CMD_NAND_READ_CACHE 0x03 /* col addr -> read cached page */ +#define SPI_CMD_NAND_PROGRAM_LOAD 0x02 /* col addr -> load data, RESETS cache */ +#define SPI_CMD_NAND_PROGRAM_RAND 0x84 /* col addr -> load data, keeps cache */ +#define SPI_CMD_NAND_PROGRAM_EXEC 0x10 /* row addr -> commit cache to NAND */ +#define SPI_CMD_NAND_BLOCK_ERASE 0xD8 /* row addr -> erase 128 KiB block */ + +/* SPI NAND feature register addresses */ +#define NAND_FEATURE_PROTECT 0xA0 +#define NAND_FEATURE_OTP 0xB0 /* bit 4 = ECC_E (on-chip ECC enable) */ +#define NAND_FEATURE_STATUS 0xC0 /* OIP, WEL, E_FAIL, P_FAIL, ECC_S */ + +/* SPI NAND status bits (feature 0xC0) */ +#define NAND_STATUS_OIP (1 << 0) /* Operation In Progress */ +#define NAND_STATUS_WEL (1 << 1) /* Write Enable Latch */ +#define NAND_STATUS_E_FAIL (1 << 2) /* Erase Fail */ +#define NAND_STATUS_P_FAIL (1 << 3) /* Program Fail */ + +/* NAND geometry — currently only MX35LF1GE4AB (1Gbit) is recognized. + * On-chip ECC is enabled by default; reads return ECC-corrected data. */ +#define NAND_PAGE_SIZE 2048 +#define NAND_BLOCK_SIZE (64 * NAND_PAGE_SIZE) /* 128 KiB */ + /* CRG register for FMC clock — CRG_BASE is per-SoC (set via -DCRG_BASE=...) */ #define REG_FMC_CRG (*(volatile uint32_t *)(CRG_BASE + 0x0144)) #define FMC_CLK_ENABLE (1 << 1) @@ -204,6 +260,55 @@ void flash_read_id(uint8_t id[3]) { id[2] = (data >> 16) & 0xFF; } +/* Cached flash type from last flash_init — used by flash_read to dispatch. */ +static uint8_t current_flash_type = FLASH_TYPE_NOR; + +/* Read a single byte from a SPI NAND feature register (GET_FEATURES 0x0F). */ +static uint8_t nand_get_feature(uint8_t addr) { + fmc_reg(FMC_INT_CLR) = 0xFF; + fmc_reg(FMC_CMD) = SPI_CMD_NAND_GET_FEATURES; + fmc_reg(FMC_ADDRL) = addr; + fmc_reg(FMC_OP_CFG) = OP_CFG_OEN_EN | OP_CFG_CS(0) | OP_CFG_ADDR_NUM(1); + fmc_reg(FMC_DATA_NUM) = 1; + fmc_reg(FMC_OP) = FMC_OP_CMD1_EN | FMC_OP_ADDR_EN | FMC_OP_READ_DATA | FMC_OP_REG_OP_START; + fmc_wait_ready(); + return *(volatile uint8_t *)(FLASH_MEM); +} + +/* Write a single byte to a SPI NAND feature register (SET_FEATURE 0x1F). */ +static void nand_set_feature(uint8_t addr, uint8_t val) { + *(volatile uint8_t *)(FLASH_MEM) = val; + fmc_reg(FMC_INT_CLR) = 0xFF; + fmc_reg(FMC_CMD) = SPI_CMD_NAND_SET_FEATURE; + fmc_reg(FMC_ADDRL) = addr; + fmc_reg(FMC_OP_CFG) = OP_CFG_OEN_EN | OP_CFG_CS(0) | OP_CFG_ADDR_NUM(1); + fmc_reg(FMC_DATA_NUM) = 1; + fmc_reg(FMC_OP) = FMC_OP_CMD1_EN | FMC_OP_ADDR_EN | FMC_OP_WRITE_DATA | FMC_OP_REG_OP_START; + fmc_wait_ready(); +} + +/* Issue WRITE_ENABLE (0x06) — sets WEL in status. Required before erase/program. */ +static void nand_write_enable(void) { + fmc_reg(FMC_INT_CLR) = 0xFF; + fmc_reg(FMC_CMD) = SPI_CMD_WRITE_ENABLE; + fmc_reg(FMC_OP_CFG) = OP_CFG_OEN_EN | OP_CFG_CS(0); + fmc_reg(FMC_OP) = FMC_OP_CMD1_EN | FMC_OP_REG_OP_START; + fmc_wait_ready(); +} + +/* Identify SPI NAND chip from JEDEC ID. Returns 1 if recognized, 0 otherwise. + * Currently only MX35LF1GE4AB (Macronix, c2 12, 1Gbit / 128MB). The agent's + * flash_read_id reads bytes [0..2] of an 8-byte fetch; some SPI NAND chips + * return the manufacturer ID with a leading dummy byte, so we accept the ID + * shifted by one position too. */ +static int nand_identify(const uint8_t id[3]) { + /* Direct: id[0]=0xC2 id[1]=0x12 */ + if (id[0] == 0xC2 && id[1] == 0x12) return 1; + /* Shifted by 1 (dummy byte at id[0]): id[1]=0xC2 id[2]=0x12 */ + if (id[1] == 0xC2 && id[2] == 0x12) return 1; + return 0; +} + int flash_init(flash_info_t *info) { /* Ensure FMC clock is enabled and not in reset */ uint32_t crg = REG_FMC_CRG; @@ -223,17 +328,178 @@ int flash_init(flash_info_t *info) { /* Read JEDEC ID (requires normal mode) */ fmc_enter_normal(); flash_read_id(info->jedec_id); + + if (nand_identify(info->jedec_id)) { + /* SPI NAND path. No flash_unlock / fmc_enter_boot — NAND has no + * memory-mapped boot mode and uses different protection (BP bits + * via SET_FEATURE 0xA0 instead of write-status-register). */ + info->flash_type = FLASH_TYPE_NAND; + info->size = 128u * 1024u * 1024u; /* MX35LF1GE4AB = 128 MiB */ + info->sector_size = NAND_BLOCK_SIZE; /* 128 KiB erase block */ + info->page_size = NAND_PAGE_SIZE; /* 2 KiB read/program page */ + current_flash_type = FLASH_TYPE_NAND; + + /* Clear block-protection bits (BP0..BP3 + BRWD) in feature 0xA0 so + * subsequent erase/program commands aren't rejected. Most SPI + * NAND chips (MX35LF, GD5F, W25N, ...) ship with all blocks + * locked; this is the equivalent of NOR's flash_unlock. */ + nand_set_feature(NAND_FEATURE_PROTECT, 0x00); + return 0; + } + + /* SPI NOR path */ fmc_enter_boot(); + info->flash_type = FLASH_TYPE_NOR; info->size = detect_size(info->jedec_id[2]); info->sector_size = 0x10000; /* 64KB */ info->page_size = 256; + current_flash_type = FLASH_TYPE_NOR; return (info->jedec_id[0] != 0x00 && info->jedec_id[0] != 0xFF) ? 0 : -1; } +/* Wait for OIP=0 in the SPI NAND status feature. Returns the final status + * byte so callers can check E_FAIL / P_FAIL. Returns 0xFF on timeout + * (callers will see this as "all flags set" and treat it as failure). */ +static uint8_t nand_wait_oip(void) { + for (uint32_t i = 0; i < 10000000; i++) { + uint8_t status = nand_get_feature(NAND_FEATURE_STATUS); + if (!(status & NAND_STATUS_OIP)) return status; + proto_drain_fifo(); /* keep host-side UART happy during long ops */ + } + return 0xFF; +} + +/* Erase one 128 KiB block. `row` is the page index of any page within the + * block (block boundary = row & ~63). Returns 0 on success, -1 on E_FAIL. */ +static int nand_erase_block(uint32_t row) { + nand_write_enable(); + + fmc_reg(FMC_INT_CLR) = 0xFF; + fmc_reg(FMC_CMD) = SPI_CMD_NAND_BLOCK_ERASE; + fmc_reg(FMC_ADDRL) = row; + fmc_reg(FMC_OP_CFG) = OP_CFG_OEN_EN | OP_CFG_CS(0) | OP_CFG_ADDR_NUM(3); + fmc_reg(FMC_OP) = FMC_OP_CMD1_EN | FMC_OP_ADDR_EN | FMC_OP_REG_OP_START; + fmc_wait_ready(); + + uint8_t status = nand_wait_oip(); + return (status & NAND_STATUS_E_FAIL) ? -1 : 0; +} + +/* Program up to one full 2 KiB page. `row` is the page index, `column` is + * the byte offset within the page (typically 0 for full-page writes). + * Loads data into the chip's cache via PROGRAM_LOAD (0x02 first chunk, + * 0x84 random-load for subsequent chunks so we don't reset the cache), + * then commits with PROGRAM_EXECUTE. Returns 0 on success, -1 on + * P_FAIL. On-chip ECC computes spare-area bytes transparently. */ +static int nand_program_page(uint32_t row, uint32_t column, + const uint8_t *data, uint32_t len) { + nand_write_enable(); + + /* Chunk PROGRAM_LOAD into the FMC's 256-byte I/O buffer. The first + * chunk uses 0x02 (resets cache to all 0xFF before loading); subsequent + * chunks use 0x84 (random load — preserves earlier chunks). */ + volatile uint8_t *iobuf = (volatile uint8_t *)(FLASH_MEM); + uint32_t off = 0; + int first_chunk = 1; + while (off < len) { + uint32_t chunk = (len - off > 256) ? 256 : (len - off); + for (uint32_t i = 0; i < chunk; i++) + iobuf[i] = data[off + i]; + + fmc_reg(FMC_INT_CLR) = 0xFF; + fmc_reg(FMC_CMD) = first_chunk ? SPI_CMD_NAND_PROGRAM_LOAD + : SPI_CMD_NAND_PROGRAM_RAND; + fmc_reg(FMC_ADDRL) = column + off; + fmc_reg(FMC_OP_CFG) = OP_CFG_OEN_EN | OP_CFG_CS(0) | OP_CFG_ADDR_NUM(2); + fmc_reg(FMC_DATA_NUM) = chunk; + fmc_reg(FMC_OP) = FMC_OP_CMD1_EN | FMC_OP_ADDR_EN | FMC_OP_WRITE_DATA | FMC_OP_REG_OP_START; + fmc_wait_ready(); + + first_chunk = 0; + off += chunk; + } + + /* Commit cache to NAND array. */ + fmc_reg(FMC_INT_CLR) = 0xFF; + fmc_reg(FMC_CMD) = SPI_CMD_NAND_PROGRAM_EXEC; + fmc_reg(FMC_ADDRL) = row; + fmc_reg(FMC_OP_CFG) = OP_CFG_OEN_EN | OP_CFG_CS(0) | OP_CFG_ADDR_NUM(3); + fmc_reg(FMC_OP) = FMC_OP_CMD1_EN | FMC_OP_ADDR_EN | FMC_OP_REG_OP_START; + fmc_wait_ready(); + + uint8_t status = nand_wait_oip(); + return (status & NAND_STATUS_P_FAIL) ? -1 : 0; +} + +/* Read up to NAND_PAGE_SIZE bytes from a NAND page (data area only). + * row = page index (0 .. flash_size/page_size - 1) + * column = byte offset within the 2 KiB data area (0 .. NAND_PAGE_SIZE-1) + * On-chip ECC is left at its power-on default (enabled on MX35LF*) so the + * returned bytes are ECC-corrected. + * + * Implementation: PAGE_READ (loads page → chip cache) → wait OIP → + * chunked READ_FROM_CACHE. The FMC captures the chip's 8-cycle dummy as + * byte 0 of its I/O buffer (always 0x00 since the chip drives the dummy + * line low), so real chip data starts at iobuf[1]. We compensate by + * requesting `chunk + 1` bytes per fetch and copying iobuf[1..chunk]. */ +static void nand_read(uint32_t row, uint32_t column, + uint8_t *buf, uint32_t len) { + /* 1) PAGE_READ: load page from array into chip cache. */ + fmc_reg(FMC_INT_CLR) = 0xFF; + fmc_reg(FMC_CMD) = SPI_CMD_NAND_PAGE_READ; + fmc_reg(FMC_ADDRL) = row; + fmc_reg(FMC_OP_CFG) = OP_CFG_OEN_EN | OP_CFG_CS(0) | OP_CFG_ADDR_NUM(3); + fmc_reg(FMC_OP) = FMC_OP_CMD1_EN | FMC_OP_ADDR_EN | FMC_OP_REG_OP_START; + fmc_wait_ready(); + + /* 2) Wait for OIP=0 — chip finishes ECC correction and signals ready. */ + nand_wait_oip(); + + /* 3) READ_FROM_CACHE: pull data from cache via column addressing. + * FMC stores the post-address dummy byte as iobuf[0] (always 0x00), + * so real data lives at iobuf[1..N]. We request (chunk + 1) bytes + * per fetch and skip iobuf[0]; max useful chunk is 255 since iobuf + * is 256 bytes. */ + volatile uint8_t *iobuf = (volatile uint8_t *)(FLASH_MEM); + uint32_t off = 0; + while (off < len) { + uint32_t chunk = (len - off > 255) ? 255 : (len - off); + fmc_reg(FMC_INT_CLR) = 0xFF; + fmc_reg(FMC_CMD) = SPI_CMD_NAND_READ_CACHE; + fmc_reg(FMC_ADDRL) = column + off; + fmc_reg(FMC_DATA_NUM) = chunk + 1; /* +1 to capture the dummy byte */ + fmc_reg(FMC_OP_CFG) = OP_CFG_OEN_EN | OP_CFG_CS(0) + | OP_CFG_ADDR_NUM(2) + | OP_CFG_DUMMY_NUM(0); /* dummy is implicit in iobuf[0] */ + fmc_reg(FMC_OP) = FMC_OP_CMD1_EN | FMC_OP_ADDR_EN | FMC_OP_READ_DATA | FMC_OP_REG_OP_START; + fmc_wait_ready(); + for (uint32_t i = 0; i < chunk; i++) + buf[off + i] = iobuf[i + 1]; /* skip iobuf[0] = dummy */ + off += chunk; + } +} + void flash_read(uint32_t addr, uint8_t *buf, uint32_t len) { - /* Use register-based reads (normal mode) instead of memory window. + if (current_flash_type == FLASH_TYPE_NAND) { + /* NAND: convert byte offset to (page, column) and read page-by-page + * via PAGE_READ + READ_FROM_CACHE. No mode-switching — NAND lives + * entirely in normal mode. */ + while (len > 0) { + uint32_t row = addr / NAND_PAGE_SIZE; + uint32_t col = addr % NAND_PAGE_SIZE; + uint32_t in_page = NAND_PAGE_SIZE - col; + uint32_t chunk = (len < in_page) ? len : in_page; + nand_read(row, col, buf, chunk); + buf += chunk; + addr += chunk; + len -= chunk; + } + return; + } + + /* NOR: register-based reads (normal mode) instead of memory window. * The boot mode memory window wraps at 1MB on some SoCs. */ fmc_enter_normal(); volatile uint8_t *iobuf = (volatile uint8_t *)(FLASH_MEM); @@ -273,6 +539,11 @@ uint8_t flash_read_status(void) { } int flash_erase_sector(uint32_t addr) { + if (current_flash_type == FLASH_TYPE_NAND) { + /* `addr` is the byte offset of any page within the block. + * Convert to a row (page index); chip rounds down to block. */ + return nand_erase_block(addr / NAND_PAGE_SIZE); + } fmc_enter_normal(); /* Disable hardware write protect — must be done every time because @@ -299,6 +570,18 @@ int flash_erase_sector(uint32_t addr) { } int flash_write_page(uint32_t addr, const uint8_t *data, uint32_t len) { + if (current_flash_type == FLASH_TYPE_NAND) { + /* For NAND, "page" is 2 KiB and writes can span the full page in + * one command. Caller may still pass smaller chunks (256 B), + * which we accept — but each call is one PROGRAM cycle, so + * efficiency is best at full-page boundaries. */ + uint32_t row = addr / NAND_PAGE_SIZE; + uint32_t column = addr % NAND_PAGE_SIZE; + if (len > NAND_PAGE_SIZE - column) + len = NAND_PAGE_SIZE - column; + return nand_program_page(row, column, data, len); + } + if (len > 256) len = 256; fmc_enter_normal(); diff --git a/agent/spi_flash.h b/agent/spi_flash.h index c049177..9cbc1b2 100644 --- a/agent/spi_flash.h +++ b/agent/spi_flash.h @@ -1,8 +1,10 @@ /* - * HiSilicon FMC SPI NOR flash driver. + * HiSilicon FMC SPI flash driver — supports SPI NOR + SPI NAND. * - * Uses the FMC (Flash Memory Controller) register interface for - * erase/write, and memory-mapped read window for fast reads. + * NOR: register-based reads via FMC normal mode (faster than memory + * window when window wraps at 1MB on some SoCs). + * NAND: PAGE_READ → READ_FROM_CACHE flow with on-chip ECC enabled. + * Currently read-only (erase/write are NOR-only). */ #ifndef SPI_FLASH_H @@ -28,12 +30,17 @@ /* FMC register access */ #define fmc_reg(off) (*(volatile uint32_t *)(FMC_BASE + (off))) +/* Flash type */ +#define FLASH_TYPE_NOR 0 +#define FLASH_TYPE_NAND 1 + /* Flash info */ typedef struct { uint8_t jedec_id[3]; /* Manufacturer + device ID */ - uint32_t size; /* Total flash size in bytes */ - uint32_t sector_size; /* Erase sector size (typically 64KB) */ - uint32_t page_size; /* Program page size (typically 256B) */ + uint32_t size; /* Total flash size in bytes (data only, no OOB) */ + uint32_t sector_size; /* Erase unit (NOR: 64KB sector, NAND: 128KB block) */ + uint32_t page_size; /* Read/program unit (NOR: 256B, NAND: 2KB) */ + uint8_t flash_type; /* FLASH_TYPE_NOR or FLASH_TYPE_NAND */ } flash_info_t; /* Initialize flash controller, detect flash chip */ diff --git a/src/defib/agent/client.py b/src/defib/agent/client.py index c47838d..f81e691 100644 --- a/src/defib/agent/client.py +++ b/src/defib/agent/client.py @@ -115,6 +115,8 @@ def get_agent_binary(chip: str) -> Path | None: "hi3516cv500": "hi3516cv500", "hi3516av300": "hi3516cv500", # cv500-family, same memory map "hi3516dv300": "hi3516cv500", # cv500-family, same memory map + "hi3519v101": "hi3519v101", + "hi3516av200": "hi3519v101", # 3519v101 family, same memory map "hi3516cv610": "hi3516cv610", "hi3518ev200": "hi3518ev200", } diff --git a/src/defib/cli/app.py b/src/defib/cli/app.py index c6df023..20662c6 100644 --- a/src/defib/cli/app.py +++ b/src/defib/cli/app.py @@ -933,11 +933,16 @@ async def _agent_upload_async(chip: str, port: str, output: str) -> None: if output == "human": console.print("Downloading U-Boot for SPL...") cached_fw = download_firmware(chip) - spl_data = cached_fw.read_bytes()[:profile.spl_max_size] + # Pass the full U-Boot binary as spl_override; _send_spl detects the + # actual SPL/u-boot boundary via LZMA/gzip signature scan and slices + # accordingly. Pre-truncating to profile.spl_max_size hides the real + # boundary on chips where the OpenIPC SPL is larger than the HiTool + # reference (e.g. hi3516av200's SVB-enabled SPL is 0x6800, not 0x4F00). + spl_data = cached_fw.read_bytes() if output == "human": console.print(f"Agent: [cyan]{agent_path.name}[/cyan] ({len(agent_data)} bytes)") - console.print(f"SPL: {len(spl_data)} bytes (from OpenIPC U-Boot)") + console.print(f"SPL: full U-Boot ({len(spl_data)} bytes — boundary auto-detected)") console.print("\n[yellow]Power-cycle the camera now![/yellow]\n") transport = await SerialTransport.create(port) @@ -1073,7 +1078,10 @@ async def _agent_flash_async( if output == "human": console.print("Downloading U-Boot for SPL...") cached_fw = download_firmware(chip) - spl_data = cached_fw.read_bytes()[:profile.spl_max_size] + # Pass full u-boot; _send_spl detects the actual SPL boundary so chips + # where OpenIPC SPL is larger than HiTool's profile_max (e.g. av200's + # SVB-enabled SPL = 0x6800 vs profile_max = 0x4F00) get the right size. + spl_data = cached_fw.read_bytes() if output == "human": console.print(f"Firmware: [cyan]{fw_path.name}[/cyan] ({len(firmware)} bytes, CRC {fw_crc:#010x})")