You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
## Summary
- Adds support for **hi3520dv200** (V1-era HiSilicon DVR/NVR SoC,
Cortex-A9, Macronix MX25L25635E 32 MiB NOR on CS1) to the bare-metal
flash agent.
- Introduces a **HISFC350** SPI flash driver (\`spi_flash_hisfc350.c\`)
selectable via \`SPI_DRIVER=hisfc350\` in the per-SoC Makefile stanza.
Default stays FMC100 for every other supported SoC.
- Fixes a long-standing **selfupdate cache-coherency bug** that affected
ARMv7-A in general (the trampoline copy never cleaned D-cache, so the BX
target read stale memory; survived on Cortex-A7 by coincidence,
deterministically broken on Cortex-A9).
- Bumps INFO response to v3 with a \`flash_mem\` field so the host stops
hardcoding \`0x14000000\` (hi3520dv200 has FLASH_MEM at \`0x58000000\`).
## What's new
| File | Change |
|---|---|
| \`agent/spi_flash_hisfc350.c\` (new) | NOR-only HISFC350 driver.
Probes both CS lines. EAR-based bank-switching for >16 MiB chips. |
| \`agent/main.c\` | INFO v3 (+\`flash_mem\`); cache-aware selfupdate +
trampoline; widened \`addr_readable\`/\`io_region\` for V1 peripheral
block. |
| \`agent/uart.c\` | Preserve bootrom IBRD/FBRD (works on chips where
bootrom UART runs off a slow reference); divisor-scaling \`set_baud\`;
bounded FIFO drain; IBRD safety check. |
| \`agent/Makefile\` | \`SPI_DRIVER\` selector; hi3520dv200 SoC stanza.
|
| \`agent/README.md\` | Update SoC table; document HISFC350 vs FMC100. |
| \`src/defib/agent/client.py\` | Parse \`flash_mem\` from INFO v3+;
expose \`client.flash_mem\`; add hi3520dv200 to \`chip_to_agent\`. |
| \`src/defib/cli/app.py\` | Use \`client.flash_mem\` instead of
hardcoded \`0x14000000\` in \`agent install\`, \`agent read\`,
flash-doctor. |
## Why HISFC350 instead of EN4B for 32 MiB chips
EN4B (true 4-byte mode, 0xB7) was tried first on the MX25L25635E. The
HISFC350's \`GLOBAL_CONFIG.ADDR_MODE_4B\` bit latched cleanly, but reads
at offsets ≥16 MiB returned a fixed repeating pattern instead of real
data — the chip never actually entered 4-byte mode, for reasons we
haven't figured out. Switching to **WREAR (0xC5) + EAR-banking** (3-byte
addressing with a 25th-bit selector) makes both halves accessible. The
vendor U-Boot driver \`hisfc350_spi_mx25l25635e.c\` follows the same
approach.
## Why selfupdate needed cache fixes
The old trampoline did a byte-copy then \`BX r3\`. On ARMv7-A with
write-back D-cache:
1. Byte writes go to D-cache, not memory.
2. I-fetch at the destination misses or hits stale lines.
3. Result: the BX jumps into garbage (or, when binaries happen to be
byte-identical, into the right code "by accident").
This worked on the existing Cortex-A7 boards because we rarely deployed
a binary with diverging early-startup bytes. On Cortex-A9 (hi3520dv200)
it broke deterministically. Fix:
- The trampoline now walks the destination range invalidating I-cache
and cleaning D-cache per line (DCCMVAU + ICIMVAU + DSB + ISB + BX).
- \`handle_selfupdate\` also cleans the trampoline location itself
before calling the function pointer — without this, the trampoline bytes
themselves are in D-cache and the I-fetch reads stale memory.
## Verified locally
- \`make -C agent test HOST_CC=gcc\` — 5406 / 5406 unit tests pass.
- \`uv run pytest tests/ -x -q --ignore=tests/fuzz\` — 490 pass.
- \`uv run ruff check src/ tests/\` — clean.
- \`uv run mypy src/defib/ --ignore-missing-imports\` — clean.
- All 10 supported SoCs build: hi3516ev300, hi3516ev200, gk7205v200,
gk7205v300, hi3516cv300, hi3516cv500, hi3518ev200, hi3516cv610,
hi3519v101, hi3520dv200.
## Verified on real hardware (hi3520dv200 on /dev/ttyUSB3, manual
power-cycle)
- Agent uploads via \`defib agent upload\`, sends READY, reports \`JEDEC
c22019 / 32768 KB / sector 64 KB / RAM 0x80000000 / FLASH_MEM 0x58000000
/ agent v3\`.
- Reads from the lower 16 MiB return real vendor U-Boot bytes; reads
from the upper 16 MiB return DIFFERENT real data (bank-switching works
end to end).
- Selfupdate verified end-to-end (no power-cycle needed for subsequent
agent updates).
## Known limitations / follow-ups
Tracked separately in [#97] (issue opened in this PR sequence). Briefly:
- High baud (\`set_baud\` >115200) is **rejected** by the IBRD safety
check on hi3520dv200 because the bootrom hands UART running off a ~2 MHz
reference. Vendor U-Boot \`board.c\` clears bit 13 of \`CRG+0xE4\` to
switch UART onto APB; we tried that but the resulting UART rate didn't
match any guess at the actual APB clock. Need to identify the real clock
empirically (probably needs a power-cycle relay so we can iterate
without manual intervention).
- Stock-firmware dump and write/erase tests deferred until high-baud
works (32 MiB at 115200 ≈ 54 min round-trip).
## Test plan
- [ ] On a power-cycle relay-equipped rig: run \`defib agent upload -c
hi3520dv200 -p \$PORT\`, confirm JEDEC + 32 MiB.
- [ ] Dump full 32 MiB to a file, verify CRC, store as stock-firmware
backup.
- [ ] Erase a known-empty sector (e.g. \`0xFE0000\`), verify it reads as
0xFF.
- [ ] Write a small known pattern, read back, verify CRC matches.
- [ ] Re-flash the original sector contents, verify chip is back to
stock.
- [ ] Test scan over the full 32 MiB.
🤖 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>
0 commit comments