Skip to content

Add SPI NAND read support to flash agent (read-only first cut)#70

Closed
widgetii wants to merge 1 commit intoagent-3519v101from
agent-spi-nand
Closed

Add SPI NAND read support to flash agent (read-only first cut)#70
widgetii wants to merge 1 commit intoagent-3519v101from
agent-spi-nand

Conversation

@widgetii
Copy link
Copy Markdown
Member

@widgetii widgetii commented May 5, 2026

Summary

The agent's flash driver was NOR-only. On a SPI NAND board (e.g. the hi3516av200 we just landed in #69), agent info returned shifted JEDEC bytes and agent read returned 0 bytes because:

  1. SPI NAND READ_ID emits a leading dummy byte that we weren't accounting for.
  2. NAND has no memory-mapped read window — must use PAGE_READ + READ_FROM_CACHE.
  3. NAND command sequence requires 1 dummy byte after column address (8 dummy clock cycles per the MX35LF datasheet).

This PR adds NAND detection plus a register-based read path so defib agent read produces correct content on NAND boards. Read-only for this first cut; erase/write/scan/flash_program/flash_stream now return ACK_FLASH_ERROR cleanly 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 0x175f is 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

Concept NOR path NAND path
Read mem-mapped at FLASH_MEM (or normal-mode 0x03 cmd) PAGE_READ 0x13 (row addr) → wait OIP → READ_FROM_CACHE 0x03 (col addr + 1 dummy byte)
Boot mode fmc_enter_boot() for fast paths always normal mode (NAND has no boot-mode window)
Status poll RDSR 0x05 bit 0 = WIP GET_FEATURES 0x0F of feature 0xC0 bit 0 = OIP
Block size 64 KiB sector erase (0xD8) 128 KiB block (not yet implemented)
Page size 256 B program 2 KiB page (not yet implemented)
ECC none on-chip 4-bit/512 (chip handles it)

flash_info.flash_type is now reported in the JEDEC ID slot's spare byte of CMD_INFO so the host can branch on chip type.

Out of scope (separate follow-ups)

  • NAND erase: block erase 0xD8 with row address.
  • NAND program: PROGRAM_LOAD 0x02 + PROGRAM_EXECUTE 0x10 with status checking.
  • ECC mismatch detection / reporting back to host.
  • Bad-block management (skip blocks marked as bad in OOB byte 0 of page 0).
  • Other SPI NAND chip IDs (currently only Macronix MX35LF1GE4AB recognized — Winbond W25N, GigaDevice GD5F, Micron MT29F, Toshiba TC58 etc would each need an entry).
  • Quad-IO read for higher throughput (not needed at 921600 baud — UART is the bottleneck).

Notes / risks

  • cv300 hardware regression deferred: the FTDI USB-serial adapter on the cv300 board is in an I/O-error state today and needs a physical replug. The NOR code path is logically unchanged: flash_init adds a current_flash_type assignment and flash_read adds 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.
  • This PR targets 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 to master.

```
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

  • Real av200 hardware: NAND read at 921600 baud, byte-stable across passes, real flash content
  • All test suites green
  • cv300 hardware regression (deferred — FTDI replug needed)

🤖 Generated with Claude Code

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]>
@widgetii widgetii deleted the branch agent-3519v101 May 5, 2026 13:35
@widgetii widgetii closed this May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant