Repository navigation
hi3520dv200 agent: complete hardware verification once power-cycle relay is available #97
Description
Context
#96 lands the bare-metal flash agent for hi3520dv200 (V1-era HiSilicon DVR/NVR SoC, Cortex-A9, MX25L25635E 32 MiB NOR on CS1). The PR is verified end-to-end for the parts that don't need many iterations:
- ✅ Agent boots via the standard fastboot SPL handoff
- ✅ HISFC350 driver detects the chip on CS1
- ✅ Macronix EAR-banking exposes the full 32 MiB (lower and upper halves return distinct real data)
- ✅ Selfupdate works (cache-aware trampoline + cache-flush around copy)
- ✅ INFO v3 `flash_mem` lets the host stop hardcoding `0x14000000`
Everything else needs a power-cycle relay so we can iterate without burning a manual reset for every guess. Once the relay arrives, the work below should be picked up.
TODO (in order)
1. Stock-firmware backup
Before any write/erase test:
```
defib agent read -p
```
Verify the dump matches a fresh boot (CRC over the same range twice). Store the dump in the repo's `dumps/` (or wherever the project keeps stock backups).
2. High-baud UART (the big blocker)
The bootrom hands UART running off a slow ~2 MHz reference clock (IBRD=1, FBRD=5 → 115200 baud). Anything above ~125 kbaud needs IBRD < 1, which is illegal — `uart_set_baud` rejects it (safety check), and the agent stays at 115200.
Vendor U-Boot `board/godarm/board.c` does:
```c
reg = readl(CRG_REG_BASE + 0xe4);
reg &= ~UART_CKSEL_APB; /* clear bit 13 */
writel(reg, CRG_REG_BASE + 0xe4);
```
to switch UART onto APB clock, then assumes `CONFIG_PL011_CLOCK = CFG_CLK_BUS/4 = 99 MHz` for divisor calculation.
In this PR I implemented the same switch (gated by `UART_CKSEL_REG` / `UART_CKSEL_BIT` Makefile defines) and tried both 99 MHz (vendor U-Boot value) and 198 MHz (`busclk/2`, the value vendor Linux `mach-godarm/core.c uart_clk_init` uses) — neither produced clean 115200 from the agent. Empirical measurements suggested actual rate around 760 kbaud at host=115200 sampling, which doesn't map cleanly to any standard PLL divider.
With a relay, iterate quickly:
- (a) Read CRG state via `defib agent read -a 0x20030000 -s 4096` to dump the full PLL/divider config; correlate against vendor's `get_bus_clk()` formula in `include/configs/hi3520d.h`.
- (b) Build with various `UART_CLOCK` values (24M, 54M, 99M, 198M, 396M, 792M); for each, check whether the agent talks at 115200 after CKSEL clear.
- (c) Once the right value is found, re-enable `UART_CKSEL_REG` in the Makefile, drop the IBRD safety bypass, and confirm `set_baud(921600)` works.
- (d) Optional: PMU-based runtime calibration (transmit known sequence in PL011 loopback mode, time with CCNT) so the agent self-derives UART_CLOCK without a hardcoded constant.
3. Write / erase / scan verification
After (1) and (2):
- Erase a known-empty sector (likely `0xFE0000` or anywhere in the upper half), read back, confirm 0xFF.
- Write a small known pattern (e.g. 256 B of incrementing bytes), read back, confirm match.
- Re-flash the original sector contents, verify chip back to stock.
- Run `defib agent scan -p $PORT` over the full 32 MiB and confirm reasonable per-sector classification.
4. Bank-switch boundary cases
Make sure operations that span the 16 MiB boundary work cleanly:
- A flash_read of length 1 MiB starting at offset `0x0FF8000` (crosses the boundary by 32 KiB).
- A CRC32 over the full chip — should match the stock dump's CRC.
- A flash_write_page of a page that lies right on `0x1000000` (first page of upper half).
5. True 4-byte mode investigation (optional, low priority)
EN4B (0xB7) was tried and rejected by the chip even though the controller's `GLOBAL_CONFIG.ADDR_MODE_4B` latched. EAR-banking is a fine workaround for now, but figuring out why the chip ignores EN4B might unlock a small simplification (no per-operation bank-switch overhead). Theories: needs WREN before EN4B, needs reset (RSTEN+RST 0x66/0x99) first, or chip is in a sticky state from boot. Not worth chasing until everything else is solid.
6. Profile JSON
The existing `src/defib/profiles/data/hi3520dv200.json` already has the right `ADDRESS`/`SRAMLIMIT`/etc. for the boot-protocol path. After hardware verification, update its description / tested-flag to mark it as agent-supported.
Hardware pin notes (for whoever picks this up)
- Board on `/dev/ttyUSB3` (FT232R, ID `A5069RR4`)
- Vendor SDK U-Boot used as SPL: `/home/dima/work/hi3520dv200_sdk/Hi3520D_SDK_V2.0.5.1/package/image_glibc/u-boot_hi3520d.bin` (228344 B). Cached as `u-boot-hi3520dv200-universal.bin` in defib's firmware cache.
- Bootrom enters serial-boot mode only on cold reset and only when host floods 0xAA before flash boot — defib's manual-mode handshake handles this when the board is power-cycled while defib is already listening.
- Board has valid stock firmware in NOR, so power-on always boots into vendor U-Boot. defib catches the bootrom on the next cold reset by detecting silence-after-activity and starting to flood 0xAA.
Activity
agent: add hi3520dv200 + HISFC350 SPI flash driver (#96)
widgetii commentedon May 12, 2026
Update: erase + write verified on real hardware (2026-05-12)
PR #96 is merged. Erase + write end-to-end test on hi3520dv200 / MX25L25635E passes bit-perfect:
0x1A10000(upper bank — exercises WREAR-based bank switching)0xDEAB7E4E(= 64 KiB of0xFF) ✓0xFF(no overwrite spillover) ✓0xAD4BA3BE✓0x1B94A56B(matches U-Boot reference) ✓So step 3 of this issue's roadmap is complete for upper bank.
Bring-up note: the agent was loaded via U-Boot's
loady 0x81000000 + go 0x81000000(no power-cycle catch needed when the board is already at the U-Boot prompt). That's much faster than the bootrom-flood approach for dev iteration.Still pending (waiting on the relay):
dumps/hi3520dv200-mx25l25635e-cs1-32mib-postjffs2cleanmarkers-20260512.bin(32 MiB, CRC320x86E6DCB9). Note: vendor Linux briefly booted during a transient boot in this session and JFFS2 wrote cleanmarkers to MiBs 26/27/30 of theapppartition — the dump captures this post-cleanmarker state. Not a defect; the agent's read is bit-perfect against U-Boot'ssf read.Hardening item (not currently tracked): if a stray byte stream from a host serial collision triggers the agent to interpret garbage as a valid
CMD_FLASH_PROGRAM/CMD_FLASH_STREAMpacket (probability is low but nonzero with a valid CRC32 collision), the agent would write to flash. A reasonable defensive measure would be to require an explicit "arm" command before any flash-modifying op, or to gate flash writes behind a flag set byCMD_INFO. Worth opening a separate issue.widgetii commentedon May 16, 2026
Session writeup (2026-05-16)
Spent a day on this against the actual board. Quick summary so the next attempt doesn't repeat dead ends.
What was actually fixed (separate from #97's TODO #2)
defib agent upload -c hi3520dv200was silently failing with "Agent not responding" before any high-baud work could even start. Root cause: the user-cached~/.cache/defib/firmware/u-boot-hi3520dv200-universal.binwas the vendor SDK U-Boot (u-boot_hi3520d.bin, 228344 B). defib's_send_splchops its first 0x2300 (8960) bytes and uploads them as SPL — the first 8960 bytes of vendor U-Boot are start.S vectors + a partial reset handler, not a self-contained mini-boot. Bootrom loads them, they crash partway through init, agent at 0x81000000 never gets clean DDR + control transfer.OpenIPC's published U-Boot for hi3520dv200 (154116 B at
https://github.com/OpenIPC/firmware/releases/download/latest/u-boot-hi3520dv200-universal.bin) has a proper mini-boot in its first ~14 KB and works end-to-end. One-line fix is to addhi3520dv200toAVAILABLE_FIRMWAREinsrc/defib/firmware.pyso defib auto-downloads it on first use. After that:Holding that one-liner unstaged until #97 TODO #2 lands so it can go in one PR.
High-baud UART (TODO #2) — empirical findings
I instrumented the agent extensively and probed the chip from inside it. Captures, sweep results, and reasoning are in kaeru
hi3520dv200-uart-cksel-bit14-of-crg-e4-is-a-noop,hi3520dv200-vendor-lowlevel-init-table-extracted-not-the-answer, andhi3520dv200-pmu-calibration-confirms-bit14-effect-but-no-readable-output.Confirmed (empirically true on this board):
agent/Makefileof "bit 13" is wrong — vendor'sarch/arm/include/asm/arch-hi3520d/platform.hhas#define UART_CKSEL_APB (1 << 14), and direct probe confirms clearing it changes the clock).lowlevel_initregister table doesn't help. Extracted it from the OpenIPC U-Boot binary at offset 0x40 (__blank_zonepost-patched, 101 entries × 16-byte{addr, val, count, flags}). Bootrom already applies it — CRG values at agent startup exactly match the table. And the table does NOT touch CRG+0xE4.Doesn't work (the actual blocker):
*(volatile uint32_t *)0x20080020returns IBRD=0 (and FBRD=1), but the UART demonstrably operates at 115200 — which mathematically requires IBRD=1/FBRD=5 at 2 MHz reference. Either the read shadows different storage from what the baud generator uses, or there's an undocumented pre-divider that's only bypassed when bit-14 is cleared. The MMU mapping is DEVICE (uncached) so it isn't a cache issue.board_initdoes ONLY the bit-14 clear — nothing else UART-related, per the full source at https://github.com/OpenIPC/u-boot-hi3520dv200/blob/master/board/godarm/board.c. So either vendor U-Boot is also silently broken at 115200 afterboard_init(no one tested), or there's a setup step I can't find that lives in mask-ROM init or somewhere outside the visible source.Could-not-rule-out (the reason not to close):
UART_CKSEL_APB = (1 << 14)is unconditional in the source — strong hint it's intended to work — so maybe it does on other dies and the failure on mine is silicon-spin variance.mach-godarm/core.cuart_clk_init(the Linux 3.4 BSP function the Makefile comment cites as "busclk=396 MHz / 2 = 198 MHz") isn't on GitHub. It probably has the additional register write that we're missing.board_initUART to ground-truth what the actual baud / divisor / clock is in the working configuration.Repro for the next attempter
u-boot-hi3520dv200-universal.bininto the firmware cache, OR patchAVAILABLE_FIRMWAREto includehi3520dv200. Without this, agent upload fails before you can probe anything.defib agent upload. The harness I used (PMU CCNT init + UART register access + cloud-relay-driven re-upload between iterations) is in/tmp/probe_*.con my workstation and the structure is reproducible from the kaeru notes.board_init(CKSEL bit-14 clear) andserial_pl01x_setbrg(IBRD/FBRD programming) on a board where 115200 is empirically achieved post-clear? If anyone has a HiSilicon Linux 3.4 BSP source or can capture a vendor U-Boot boot with a logic analyzer, that's the unblock.Leaving #97 open per @widgetii — TODO #2 (the high-baud UART) is the genuine remaining blocker, and the rest of the TODOs (stock backup, full scan, profile flag) are all unblocked by the agent now working at 115200.
widgetii commentedon Oct 15, 2026
Related data point: hi3536dv100 (Xiongmai NBD8010S-KL-V2, "Hi3536E")
PR #150 ports the agent to a DVR/NVR SoC on the rig that has a power relay. Notes on what does and doesn't transfer to this issue:
CONFIG_PL011_CLOCK = 24000000; vendorboard_initsets the "uart clk from XTAL OSC 24M" bit in CRG51+0xCC).SET_BAUD 921600works with the bootrom divisor rescale, ~86 KB/s sustained. So the CKSEL puzzle on hi3520dv200 is specific to the V1-era (godarm) clock tree. Nothing here explains the IBRD/FBRD readback paradox.0x81000000and the stock U-Boot booted from flash. The cause was a board DDR table mismatch: OpenIPC SPL 222 entries, stock 185. The fix is ahi3536dv100:nbd8010svariant carrying the board's own SPL (SPL_BLOB, 0x5840 bytes).burnnow honoursSPL_BLOBtoo.hi3536dv100: flash agent, and the NBD8010S board's own SPL as a varia…