Skip to content

Add hi3519v101 + hi3516av200 (V3A family) to flash agent#69

Merged
widgetii merged 1 commit intomasterfrom
agent-3519v101
May 5, 2026
Merged

Add hi3519v101 + hi3516av200 (V3A family) to flash agent#69
widgetii merged 1 commit intomasterfrom
agent-3519v101

Conversation

@widgetii
Copy link
Copy Markdown
Member

@widgetii widgetii commented May 5, 2026

Summary

Fourth agent platform after ev300 (V4), cv300 (V3), and av300/dv300/cv500 (V5/cv500-family). V3A = 3519v101 + av200 — Cortex-A7 with V3-era peripheral addresses (UART 0x12100000, WDT 0x12080000) but DDR at 0x80000000 like cv500-family, per qemu-hisilicon's hi3519v101_soc.

The bootrom-protocol quirks (sendFrameForStart handshake, PRESTEP1 DDR training step, non-fatal TAILs) were already landed for defib install / defib burn in #47 + #48 + #65. This PR is just the agent build wiring plus one real protocol fix the agent-flash path was missing.

The fix: don't pre-truncate spl_override at the call site

defib agent upload / agent flash were doing:

```python
spl_data = cached_fw.read_bytes()[:profile.spl_max_size]
```

before passing to send_firmware(). When _send_spl() then scans this truncated buffer for the LZMA/gzip SPL boundary, it can't find anything past profile_max — so for chips where the OpenIPC SPL is larger than the HiTool reference (e.g. av200's SVB-enabled SPL is 0x6800, but profile_max is 0x4F00), we send 0x1900 too few bytes. The SPL never finishes its post-DDR-init code, the SPL TAIL completes with no follow-through, and the agent HEAD frame for 0x81000000 gets 0x08 rejection.

Fix: pass the full u-boot binary as spl_override. _send_spl() already handles the slicing via its detected LZMA/gzip boundary.

Verification

QEMU qemu-system-arm -M hi3519v101 -kernel agent-hi3519v101.elf:
agent boots cleanly, READY/DEFIB packet stream, no faults.

Real hi3516av200 board (/dev/ttyUSB1, MikroTik ether8):

```
upload ok=True
agent ready: ram_base=0x80000000 caps=0x7f version=2
```

The board in our lab has SPI NAND and the agent's NOR-only flash driver
returns shifted JEDEC bytes / 0-byte reads on NAND; that's a separate
larger limitation noted at the bottom.

hi3516cv300 regression (/dev/uart-IVGHP203Y-AF, MikroTik ether3):

```
agent ready: jedec=ef4018 flash=16384KiB ram=0x80000000 caps=0x7f
256 KiB @ 921600: 3.02 s = 84.9 KB/s
```

The spl_override-truncation fix changes cv300's SPL size from 0x4F00 (clamped) to 0x5400 (LZMA-detected at offset 0x54c8). Agent loads identically, throughput unchanged — the previous undersized SPL was working coincidentally because cv300's SPL pieces happened to fit in the smaller window.

```
make -C agent test HOST_CC=gcc: 5406/5406
pytest tests/ -x --ignore=tests/fuzz: 402 passed, 2 skipped
ruff & mypy: clean
```

All four agent SoCs (ev300, cv300, cv500, 3519v101) build clean.

Aliasing

Following the existing gk7205v300 → gk7205v200 shape: one binary serves the family, multiple chip names route to it.

```python
"hi3519v101": "hi3519v101",
"hi3516av200": "hi3519v101", # 3519v101 family, same memory map
```

Known limitation: SPI NAND on av200 boards

The av200 board in our lab has SPI NAND flash. The agent's flash driver (agent/spi_flash.c) supports SPI NOR only — uses the memory-mapped read window at 0x14000000 and direct FMC register commands. On a NAND board:

  • Agent loads, runs, and emits READY ✓
  • agent info returns shifted JEDEC bytes (e.g. 00c212 instead of valid c2 XX YY)
  • agent read returns 0 bytes
  • Erase/write/scan won't work

This affects all V3-and-later HiSilicon chips that ship with SPI NAND (some av200, some av300, some cv500). Adding SPI NAND support to the agent is its own piece of work. This PR ships the platform wiring; a follow-up can address NAND.

Test plan

  • QEMU -M hi3519v101: agent boots cleanly
  • Real av200 hardware: agent uploads + runs + READY
  • cv300 regression: throughput and behavior unchanged
  • All test suites green

🤖 Generated with Claude Code

Fourth agent platform after ev300 (V4), cv300 (V3), and av300/dv300/cv500
(V5/cv500-family).  V3A is 3519v101 + av200 — Cortex-A7 with V3-era
peripheral addresses (UART 0x12100000, WDT 0x12080000) but DDR at
0x80000000 like cv500-family.  Memory map per qemu-hisilicon's
hi3519v101_soc.

The bootrom-protocol quirks (sendFrameForStart handshake, PRESTEP1 DDR
training step, non-fatal TAILs) were already landed for the install/burn
flow in #47 + #48 + #65, so this is just the agent build wiring plus one
real protocol fix the agent path was missing.

## Fix: don't pre-truncate spl_override at the call site

`defib agent upload` and `agent flash` were doing
\`spl_data = uboot[:profile.spl_max_size]\` before passing to
`send_firmware()`.  When `_send_spl()` then scans this truncated buffer
for the LZMA/gzip SPL boundary, it can't find anything past
profile_max — so for chips where the OpenIPC SPL is *larger* than the
HiTool reference (e.g. av200's SVB-enabled SPL is 0x6800, profile_max
is 0x4F00), we send 0x1900 too few bytes.  The SPL never reaches its
post-DDR-init code, hangs after the SPL TAIL, and the agent HEAD
frame for 0x81000000 gets `0x08` rejection.

Fix: pass the full u-boot binary as `spl_override`.  `_send_spl()`
already handles the slicing via its detected boundary.  Verified
on real hardware:

- hi3516av200 (NAND board, on /dev/ttyUSB1, ether8): SPL detected at
  0x6800, agent uploads, runs, READY received.  Flash JEDEC reads
  byte-shifted (this board has SPI NAND; the agent's NOR-only flash
  driver is a separate, larger limitation).
- hi3516cv300 regression (on /dev/uart-IVGHP203Y-AF, ether3): SPL
  now detected at 0x5400 (was being clamped to 0x4F00 pre-fix).
  Agent loads identically, jedec=ef4018, 256 KiB read at 921600
  baud = 84.9 KB/s — same as before.

## Aliasing

Match the existing `gk7205v300 → gk7205v200` shape: one agent binary
serves the V3A family, multiple chip names route to it.  Add
`hi3519v101 → hi3519v101` (own binary) and
`hi3516av200 → hi3519v101` to `chip_to_agent`.

## Verification

QEMU `qemu-system-arm -M hi3519v101 -kernel agent-hi3519v101.elf`:
agent boots cleanly, READY/DEFIB packet stream, no faults.

Real hi3516av200 board:
\`\`\`
upload ok=True
agent ready: ram=0x80000000 caps=0x7f version=2
\`\`\`

cv300 regression (testing that the spl_override fix doesn't break what
landed in #66/#67): jedec_id, ram_base, caps, throughput unchanged.

make test HOST_CC=gcc: 5406/5406.  pytest: 402 passed, 2 skipped.
ruff & mypy clean.

## Known limitation

The av200 board in this lab has SPI NAND.  The agent's flash driver
(`agent/spi_flash.c`) supports SPI NOR only — uses memory-mapped reads
at 0x14000000 and direct FMC register commands.  On NAND, JEDEC reads
return shifted bytes and `read_memory` returns 0 bytes.  The agent
still loads, runs, and emits READY on NAND boards; just the
read/erase/write/scan operations don't work.  SPI NAND support is a
separate larger piece of work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@widgetii widgetii merged commit 429857d into master May 5, 2026
13 checks passed
@widgetii widgetii deleted the agent-3519v101 branch May 5, 2026 13:35
widgetii added a commit that referenced this pull request May 5, 2026
## Summary

The agent's flash driver was NOR-only. This PR adds full **read + erase
+ program** for SPI NAND, so `defib agent read/erase/write` works on
NAND boards (hi3516av200 we just landed in #69 ships with a Macronix
MX35LF1GE4AB, 1Gbit / 128 MiB).

## Two commits

### 1. Add SPI NAND read support to flash agent (`777538a`)

- `nand_identify` recognizes Macronix `0xc2 0x12` (MX35LF1GE4AB),
tolerating the leading dummy byte some SPI NAND chips emit during 0x9F
READ_ID.
- `flash_init` dispatches by JEDEC: NAND skips `fmc_enter_boot` (no
memory-mapped boot mode on NAND) and reports 128 MiB total / 128 KiB
block / 2 KiB page.
- `nand_read` issues PAGE_READ (0x13) → wait OIP → READ_FROM_CACHE
(0x03) chunked through the FMC's 256-byte I/O buffer.
- `flash_read` early-dispatches to NAND on chip type; NOR path
unchanged.
- `flash_info.flash_type` reported in CMD_INFO so the host can branch on
chip type.

### 2. Add SPI NAND erase + program — byte-perfect (`563cb14`)

- **Read-side fix**: the FMC captures the chip's post-address dummy byte
at `iobuf[0]` (always 0x00 because the chip drives the dummy line low)
rather than consuming it transparently. Reading `iobuf[0..N-1]` gave the
dummy byte at position 0 of every 2 KiB page — the off-by-one bug from
the read commit. Fix: request `chunk + 1` bytes, set
`OP_CFG_DUMMY_NUM(0)`, copy `iobuf[1..N]`.
- `nand_get_feature` / `nand_set_feature` for GET_FEATURES 0x0F /
SET_FEATURE 0x1F.
- `nand_write_enable` (0x06).
- `nand_erase_block`: WE → BLOCK_ERASE 0xD8 (3-byte row) → wait OIP →
check E_FAIL bit.
- `nand_program_page`: WE → PROGRAM_LOAD 0x02 (first chunk, resets
cache) → PROGRAM_LOAD_RANDOM 0x84 (rest, preserves cache) →
PROGRAM_EXECUTE 0x10 → wait OIP → check P_FAIL bit.
- `flash_init` for NAND now clears block-protect bits via SET_FEATURE
0xA0 = 0x00 (NAND equivalent of NOR's `flash_unlock`).
- `flash_erase_sector` and `flash_write_page` dispatch to NAND helpers
when type=NAND.
- NAND guard early-returns in `main.c` (added in commit 1) are removed:
erase/write/scan/flash_program/flash_stream now flow to the right path.

## Verification on real hi3516av200 (Macronix MX35LF1GE4AB)

End-to-end test cycle on a sacrificial block (flash offset `0x800000`):
**backup → erase → write 64 pages × 2 KiB pattern → read-back verify →
erase → restore backup → final verify**.

\`\`\`
ERASE block (128 KiB):   0.02 s, 100.0 % bytes are 0xFF post-erase
WRITE 64 pages × 2 KiB:  1.53 s = 83.4 KB/s, P_FAIL=0
READ-BACK verify:        byte-for-byte match (131072 B) ✓
RESTORE original:        byte-for-byte match against backup ✓
\`\`\`

Plus the 16 MiB factory-firmware dump from the earlier read-only
iteration shows real, structured content with byte-perfect strings:

| Offset | String | Significance |
|---|---|---|
| `0x0080a04` | `"U-Boot 2010.06-dirty (Apr 22 2…"` | u-boot env entry |
| `0x0200021` | `"Linux-3.18.20-hi3516av2.0…"` | Linux kernel header |
| `0x03da91d` | `"Hisilicon HI3516AV200 DEMO Board"` | exact board ID |
| `0x03de19d` | `"spi-nand@0"` | DT node confirming SPI NAND |

Throughput at 921600 baud is the same as NOR — UART is the bottleneck,
the per-page PAGE_READ→READ_FROM_CACHE overhead is negligible.

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

## Implementation table

| Concept | NOR path | NAND path |
|---|---|---|
| Read | mem-mapped at FLASH_MEM (or normal-mode 0x03 cmd) | PAGE_READ
0x13 (row addr) → wait OIP → chunked READ_FROM_CACHE 0x03 with iobuf[0]
dummy skip |
| Erase | WE → SECTOR_ERASE 0xD8 (3-byte byte addr) → poll RDSR.WIP | WE
→ BLOCK_ERASE 0xD8 (3-byte row) → poll feature 0xC0 OIP → check E_FAIL |
| Program | WE → PAGE_PROGRAM 0x02 (col addr + 1–256 B data) → poll
RDSR.WIP | WE → PROGRAM_LOAD 0x02 (first 256 B, resets cache) →
PROGRAM_LOAD_RANDOM 0x84 (rest) → PROGRAM_EXECUTE 0x10 (row) → poll OIP
→ check P_FAIL |
| Write protection | RDSR.BP_MASK clear via WRSR | SET_FEATURE 0xA0 =
0x00 (clear BP0..BP3 + BRWD) |
| Block size | 64 KiB sector | 128 KiB block |
| Page size | 256 B | 2 KiB |
| ECC | none | on-chip 4-bit/512 (chip handles transparently) |

## Out of scope (separate follow-ups)

- Bad-block management (skip blocks marked as bad in OOB byte 0 of page
0). Currently any erase/write hits all blocks; bad blocks would show as
ECC errors / write failures.
- Other SPI NAND chip IDs — currently only Macronix MX35LF1GE4AB
recognized. Winbond W25N, GigaDevice GD5F, Micron MT29F, Toshiba TC58
each need their JEDEC + geometry added.
- Quad-IO read/write (not needed at 921600 baud — UART is the
bottleneck).
- ECC mismatch reporting back to host.

## Test plan

- [x] Real hi3516av200 hardware: erase + write + byte-perfect read-back
+ restore cycle
- [x] All test suites green
🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Dmitry Ilyin <widgetii@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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