Add SPI NAND read support to flash agent (read-only first cut)#70
Closed
widgetii wants to merge 1 commit intoagent-3519v101from
Closed
Add SPI NAND read support to flash agent (read-only first cut)#70widgetii wants to merge 1 commit intoagent-3519v101from
widgetii wants to merge 1 commit intoagent-3519v101from
Conversation
The agent's flash driver was NOR-only — JEDEC ID readback on a SPI NAND
chip returned shifted bytes and `read_memory` returned 0 bytes. This
adds NAND detection plus a register-based read path so `defib agent
read` works on NAND boards (e.g. hi3516av200 with Macronix
MX35LF1GE4AB, 1Gbit / 128 MiB).
Read-only for now: erase/write are NOR-specific (different opcodes,
different protection model, ECC and bad-block management). Those
handlers (CMD_ERASE, CMD_WRITE, CMD_FLASH_PROGRAM, CMD_FLASH_STREAM,
CMD_SCAN) now return ACK_FLASH_ERROR cleanly instead of silently
issuing NOR commands the chip won't honor.
## Changes
- `agent/spi_flash.h`: add `flash_type` field to `flash_info_t`
(FLASH_TYPE_NOR or FLASH_TYPE_NAND), exposed in CMD_INFO response.
- `agent/spi_flash.c`:
- `nand_identify()` recognizes Macronix `0xc2 0x12`
(MX35LF1GE4AB), tolerating the leading dummy byte some SPI NAND
chips emit during 0x9F READ_ID.
- `nand_wait_oip()` polls SPI NAND status via
GET_FEATURE 0xC0 + bit 0 (Operation In Progress).
- `nand_read()` issues PAGE_READ (0x13) → wait OIP → REG_FROM_CACHE
(0x03) with `OP_CFG_DUMMY_NUM(1)` for the 1-byte dummy SPI NAND
READ_x1 requires. On-chip ECC (default-enabled on MX35LF*) gives
us byte-perfect reads; no host-side ECC needed.
- `flash_init()` dispatches by JEDEC: NAND skips fmc_enter_boot
(NAND has no memory-mapped boot mode) and reports 128 MiB total /
128 KiB block / 2 KiB page.
- `flash_read()` early-dispatches to a per-page loop on NAND;
NOR path unchanged.
- `agent/main.c`:
- `handle_info` reports `flash_info.flash_type` in the spare byte
of the JEDEC ID slot and uses `flash_info.sector_size`
(was hardcoded 0x10000).
- `handle_flash_write`, `handle_flash_program`, `handle_flash_stream`,
`handle_erase`, `handle_scan`: early-return ACK_FLASH_ERROR on NAND
instead of issuing NOR commands the chip won't accept.
## Verification
Real hi3516av200 board (Macronix MX35LF1GE4AB SPI NAND, 128 MiB):
jedec=00c212 flash=131072 KiB block=128 KiB
64 KiB read at 921600 baud: 0.86 s = 76 KB/s
256 KiB read at 921600 baud: 3.01 s = 85.0 KB/s
1 MiB read at 921600 baud: 11.48 s = 89.2 KB/s
Same 64 KiB read across two passes: identical (no read errors)
Found "System startup" string at offset 0x175f
(real HiSilicon SDK u-boot content, not OpenIPC — board has
factory firmware, throughput and consistency confirm read works)
Throughput matches NOR path on the same baud rate — UART is the
bottleneck on both, the per-page PAGE_READ→READ_FROM_CACHE overhead
is ~negligible at 921600.
QEMU `qemu-system-arm -M hi3519v101`: agent boots clean, READY/DEFIB
packets, no faults (NAND code paths gated by chip detection — QEMU
doesn't emulate a NAND chip but the NOR path still works there).
cv300 hardware regression deferred (FTDI USB-serial glitch on that
adapter). NOR code path is logically unchanged: flash_init sets
`current_flash_type` and `flash_read` adds an early NAND check that
returns before any NOR code; the NOR loop after it is byte-identical
to before.
\`\`\`
make -C agent test HOST_CC=gcc: 5406/5406
pytest tests/ -x --ignore=tests/fuzz: 402 passed, 2 skipped
ruff & mypy: clean
make SOC=hi3516ev300: 13624 B (was 12652)
make SOC=hi3516cv300: 13844 B (was 12872, ARM926 + MMU + NAND)
make SOC=hi3516cv500: 13608 B (was 12636)
make SOC=hi3519v101: 13608 B (was 12636)
\`\`\`
## Out of scope (follow-up)
- NAND erase + program (block erase 0xD8, page program 0x02 + 0x10).
- ECC mismatch detection / reporting.
- Bad-block management (skip blocks with FF byte 0 of OOB).
- Other SPI NAND chip IDs (currently only Macronix MX35LF1GE4AB).
- Quad-IO read for higher throughput (not needed at 921600 baud).
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The agent's flash driver was NOR-only. On a SPI NAND board (e.g. the hi3516av200 we just landed in #69),
agent inforeturned shifted JEDEC bytes andagent readreturned 0 bytes because:This PR adds NAND detection plus a register-based read path so
defib agent readproduces correct content on NAND boards. Read-only for this first cut; erase/write/scan/flash_program/flash_stream now returnACK_FLASH_ERRORcleanly on NAND instead of silently issuing NOR commands the chip won't honor.Verification on real hi3516av200 (Macronix MX35LF1GE4AB, 1Gbit / 128 MiB)
```
jedec=00c212 flash=131072 KiB block=128 KiB
64 KiB read at 921600 baud: 0.86 s = 76 KB/s
256 KiB read at 921600 baud: 3.01 s = 85.0 KB/s
1 MiB read at 921600 baud: 11.48 s = 89.2 KB/s
Same 64 KiB read across two passes: identical (no read errors)
Found "System startup" string at offset 0x175f
```
Throughput matches NOR on the same baud rate — UART is the bottleneck, the per-page PAGE_READ→READ_FROM_CACHE overhead is negligible at 921600. Read consistency confirms ECC is doing its job (the on-chip 4-bit/512-byte ECC on MX35LF* is enabled by default and decoded transparently to the SPI master).
The "System startup" string at
0x175fis real HiSilicon SDK u-boot content — this lab's av200 board has factory firmware rather than OpenIPC, which is why the bytes don't match the cached OpenIPC u-boot.Implementation
FLASH_MEM(or normal-mode 0x03 cmd)PAGE_READ 0x13(row addr) → wait OIP →READ_FROM_CACHE 0x03(col addr + 1 dummy byte)fmc_enter_boot()for fast paths0x05bit 0 = WIPGET_FEATURES 0x0Fof feature0xC0bit 0 = OIP0xD8)flash_info.flash_typeis now reported in the JEDEC ID slot's spare byte ofCMD_INFOso the host can branch on chip type.Out of scope (separate follow-ups)
0xD8with row address.0x02+ PROGRAM_EXECUTE0x10with status checking.Notes / risks
flash_initadds acurrent_flash_typeassignment andflash_readadds an early NAND-dispatch check that returns before any NOR code; the NOR read loop after it is byte-identical to before. Risk is minimal but worth a note.agent-3519v101(Add hi3519v101 + hi3516av200 (V3A family) to flash agent #69) so the av200 chip wiring is in scope; once Add hi3519v101 + hi3516av200 (V3A family) to flash agent #69 merges, please rebase tomaster.```
make -C agent test HOST_CC=gcc: 5406/5406
pytest tests/ -x --ignore=tests/fuzz: 402 passed, 2 skipped
ruff & mypy: clean
make SOC=hi3516ev300/cv300/cv500/3519v101: all build
```
Test plan
🤖 Generated with Claude Code