agent: add hi3520dv200 + HISFC350 SPI flash driver#96
Merged
Conversation
V1-era HiSilicon SoCs (hi3520dv200, also covers hi3518ev100 / hi3535
boards by extension) ship a HISFC350 SPI flash controller with a
register layout that's incompatible with the FMC100 driver shipped
since the agent's introduction. This adds a parallel `spi_flash_hisfc350.c`
implementing the same `spi_flash.h` API, gated by `SPI_DRIVER=hisfc350` in
the per-SoC Makefile stanza so the FMC100 path stays the default for
every other supported SoC.
What's new:
- `agent/spi_flash_hisfc350.c` — NOR-only HISFC350 driver. Probes both
CS lines (vendor hi3520dv200 reference board puts the chip on CS1).
For 32 MiB chips, uses the Macronix Extended Address Register
(WREAR 0xC5) to bank-switch the high 16 MiB into the AHB read window
— true 4-byte mode (EN4B) was tried but the chip didn't actually
enter it, even though the controller's GLOBAL_CONFIG.ADDR_MODE_4B
latched. EAR-banking matches what the vendor U-Boot driver does.
- `agent/main.c`:
* `addr_readable` whitelisted the V1-era peripheral block (FMC at
0x10010000, CRG at 0x20030000, UART at 0x20080000) per-SoC via the
Makefile-supplied bases. `io_region` widened to match so 32-bit
register reads work via CMD_READ.
* INFO response bumped to v3, adds a `flash_mem` field at bytes
24..27. Lets the host stop hardcoding 0x14000000 — hi3520dv200
has FLASH_MEM at 0x58000000.
* `handle_selfupdate` now does explicit D-cache clean + I-cache
invalidate around the trampoline copy. Without this, byte writes
to 0x80000200 sat in D-cache and the function-pointer call jumped
to whatever stale memory happened to hold (typically zeros) —
the agent crashed silently.
* Trampoline (ARMv7-A only) extended with per-cache-line DCCMVAU +
ICIMVAU + DSB + ISB before the BX so the freshly-copied bytes at
LOAD_ADDR are visible to the I-fetch unit. Without these the
selfupdate path was deterministically broken on Cortex-A9 and
survived on Cortex-A7 only by coincidence (identical bytes).
- `agent/uart.c`:
* `uart_init` preserves bootrom IBRD/FBRD instead of recomputing
from a possibly-wrong UART_CLOCK constant. hi3520dv200 ships UART
on a slow ~2 MHz reference clock (IBRD=1, FBRD=5) so the bootrom's
divisors are the only known-good way to keep 115200 working.
* `uart_set_baud` scales from the captured bootrom divisor so future
set_baud calls stay clock-agnostic, and bounds the FIFO-drain wait
so a wrong baud can't permanently hang the agent.
* IBRD safety check rejects any baud that would compute IBRD<1 (which
would disable the PL011 baud generator and brick the link until
power-cycle). Today this caps hi3520dv200 at the bootrom's 115200
— see roadmap issue for the high-baud follow-up.
- `agent/Makefile`: adds `SPI_DRIVER` selector (default `fmc100`) and
the hi3520dv200 SoC stanza (Cortex-A9, FLASH_MEM at 0x58000000,
HISFC350).
- `src/defib/agent/client.py`: parses `flash_mem` from INFO v3+ and
exposes it as `client.flash_mem` so callers don't hardcode
0x14000000. Adds hi3520dv200 to `chip_to_agent`. Falls back to
0x14000000 for older agents that don't report it.
- `src/defib/cli/app.py`: `agent install`, `agent read` and the
flash-doctor recovery path now use `client.flash_mem` instead of
the hardcoded constant.
Verified locally:
- 5406 / 5406 agent C unit tests pass.
- 490 Python tests pass; ruff + mypy clean.
- All 10 supported SoCs build cleanly.
- Real hardware (hi3520dv200 board on /dev/ttyUSB3): agent uploads,
reports correct JEDEC c22019 / 32 MiB / sector 64 KiB / RAM 0x80000000,
and reads return distinct real data from both the lower and upper
16 MiB halves (confirming bank-switching works). Selfupdate also
verified end-to-end on real hardware.
Not verified yet (needs power-cycle relay): high-baud (>115200), full
chip dump, write/erase, scan. Tracking issue captures the full TODO.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vendor U-Boot's hisfc350 driver (and our own previous warm boots) leave the controller configured for DMA-mode QUAD I/O reads with BUS_CONFIG1 READ_EN=0. After a warm jump (e.g. agent uploaded via U-Boot loady + go, or a prior agent run that touched the chip), the AHB read window at FLASH_MEM returns bus garbage instead of flash content — every CRC32 and read collapses to a per-size constant. Reset to a known state at the top of flash_init: 1. Clear GLOBAL_CONFIG.ADDR_MODE_4B (controller back to 3-byte). 2. Issue EX4B (0xE9) on both CS lines to leave 4-byte chip mode (vendor U-Boot's hisfc350 enters 4-byte mode on >16 MiB chips via EN4B). 3. Reprogram BUS_CONFIG1 for AHB STD READ (0x03, READ_EN=1, no dummy, IF_TYPE=STD). The mem-mapped read window at FLASH_MEM now serves flash bytes again. Verified end-to-end on hi3520dv200 hardware: agent CRC32 over 8 ranges including the 16 MiB bank-switch boundary all match vendor U-Boot's reference, including the full-chip CRC = 0xD5300DFC. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
What's new
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:
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:
Verified locally
Verified on real hardware (hi3520dv200 on /dev/ttyUSB3, manual power-cycle)
Known limitations / follow-ups
Tracked separately in [#97] (issue opened in this PR sequence). Briefly:
Test plan
🤖 Generated with Claude Code