Skip to content

hi3520dv200 agent: complete hardware verification once power-cycle relay is available #97

Open

Description

@widgetii

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 $PORT -a 0x58000000 -s 32MB -o dumps/hi3520dv200-stock-$(date +%Y%m%d-%H%M%S).bin
```
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

  1. widgetii commented on May 12, 2026

    @widgetii
    MemberAuthor

    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:

    • Test sector: 0x1A10000 (upper bank — exercises WREAR-based bank switching)
    • Erase: post-erase CRC32 = 0xDEAB7E4E (= 64 KiB of 0xFF) ✓
    • Write: 1024-byte known pattern, read-back bit-identical ✓
    • Sector tail integrity: post-pattern bytes still 0xFF (no overwrite spillover) ✓
    • Restore: erase + write-back original 64 KiB → CRC matches snapshot 0xAD4BA3BE ✓
    • Cross-bank sanity after upper-bank ops: CRC32 over first 64K of low bank = 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):

    • Step 1: stock firmware backup — done as a side effect of this session, saved as dumps/hi3520dv200-mx25l25635e-cs1-32mib-postjffs2cleanmarkers-20260512.bin (32 MiB, CRC32 0x86E6DCB9). Note: vendor Linux briefly booted during a transient boot in this session and JFFS2 wrote cleanmarkers to MiBs 26/27/30 of the app partition — the dump captures this post-cleanmarker state. Not a defect; the agent's read is bit-perfect against U-Boot's sf read.
    • Step 2: high-baud — still blocked on identifying the post-CKSEL UART clock value.
    • Step 4: bank-switch boundary cases (1 MiB read crossing 0x0FF8000, full-chip CRC32, page write at 0x1000000) — read paths verified, write paths not yet tested across the boundary.

    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_STREAM packet (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 by CMD_INFO. Worth opening a separate issue.

  2. widgetii commented on May 16, 2026

    @widgetii
    MemberAuthor

    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 hi3520dv200 was 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.bin was the vendor SDK U-Boot (u-boot_hi3520d.bin, 228344 B). defib's _send_spl chops 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 add hi3520dv200 to AVAILABLE_FIRMWARE in src/defib/firmware.py so defib auto-downloads it on first use. After that:

    $ defib agent upload -c hi3520dv200 -p /dev/ttyUSB1
    Agent ready!
      RAM: 0x80000000
      Flash: 32768KB
    
    $ defib agent info -p /dev/ttyUSB1
      JEDEC ID:    c22019      (Macronix MX25L25635E)
      Flash size:  32768 KB
      Capabilities: flash_stream, sector_bitmap, page_skip, set_baud, reboot, selfupdate, scan, membw
    

    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, and hi3520dv200-pmu-calibration-confirms-bit14-effect-but-no-readable-output.

    Confirmed (empirically true on this board):

    • Bit 14 of CRG+0xE4 IS the UART CKSEL (the docstring claim in agent/Makefile of "bit 13" is wrong — vendor's arch/arm/include/asm/arch-hi3520d/platform.h has #define UART_CKSEL_APB (1 << 14), and direct probe confirms clearing it changes the clock).
    • Bootrom-handed UART runs at ~2 MHz reference clock, IBRD=1, FBRD=5 → 115200. Verified by direct register dump emitted from the agent BEFORE any modification.
    • Clearing bit 14 makes UART ~36.85× faster at preserved divisor. PMU CCNT measurement: 58072 cyc/byte at baseline (= 10 bit-times at 115200, CPU ≈ 669 MHz) vs 1576 cyc/byte after the clear = ~4.245 Mbaud. UART is alive — sniffing at 3 Mbaud post-clear shows structured bytes (24 KB in 5s, repeated patterns).
    • Vendor's lowlevel_init register table doesn't help. Extracted it from the OpenIPC U-Boot binary at offset 0x40 (__blank_zone post-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.
    • No other CRG register affects UART0. Brute-forced offsets 0x40..0xFC (write 0, observe whether UART breaks) — only CRG+0xE4 changes UART state.

    Doesn't work (the actual blocker):

    • No IBRD/FBRD combination I tested produces readable 115200 after the CKSEL clear. Swept IBRD ∈ {1..600} (covers clocks 1.84 MHz .. 1.1 GHz, well past the expected 73 MHz computed from the ratio), tried FBRD ∈ {0, 15, 32, 47, 60} at the suspect IBRD values, tested all of vendor U-Boot's likely candidates (24.75, 41.25, 49.5, 54, 66, 82.5, 99, 110, 132, 165, 198, 247.5, 330, 396 MHz). Nothing decoded at 115200 host.
    • The IBRD/FBRD readback paradox. From inside the agent, *(volatile uint32_t *)0x20080020 returns 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.
    • Vendor board_init does 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 after board_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):

    • Different silicon revision. Hardware-tested only on this one board (cloud-relay-controlled, FT232R on /dev/ttyUSB1). The bit-14 mechanism may be revision-specific. Vendor's 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.
    • Missing reference source. HiSilicon mach-godarm/core.c uart_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.
    • No logic-analyzer capture of vendor U-Boot post-board_init UART to ground-truth what the actual baud / divisor / clock is in the working configuration.

    Repro for the next attempter

    1. Get OpenIPC u-boot-hi3520dv200-universal.bin into the firmware cache, OR patch AVAILABLE_FIRMWARE to include hi3520dv200. Without this, agent upload fails before you can probe anything.
    2. Build a bare-metal probe agent that runs after defib agent upload. The harness I used (PMU CCNT init + UART register access + cloud-relay-driven re-upload between iterations) is in /tmp/probe_*.c on my workstation and the structure is reproducible from the kaeru notes.
    3. The data point we're missing: what extra writes does vendor U-Boot do between board_init (CKSEL bit-14 clear) and serial_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.

  3. widgetii commented on Oct 15, 2026

    @widgetii
    MemberAuthor

    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:

    • High baud is not a problem there. hi3536dv100's PL011 runs off the 24 MHz XTAL (CONFIG_PL011_CLOCK = 24000000; vendor board_init sets the "uart clk from XTAL OSC 24M" bit in CRG51 +0xCC). SET_BAUD 921600 works 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.
    • Same false-success trap as before. With the OpenIPC SPL, every bootrom stage was ACKed, but the board reset on the jump to 0x81000000 and the stock U-Boot booted from flash. The cause was a board DDR table mismatch: OpenIPC SPL 222 entries, stock 185. The fix is a hi3536dv100:nbd8010s variant carrying the board's own SPL (SPL_BLOB, 0x5840 bytes). burn now honours SPL_BLOB too.
    • Long single-stream reads at 921600 still drop one COBS frame in ~10k (seq-gap error). 256 KiB chunks with device-side CRC32 were clean, so the same approach should apply to this issue's step 4 full-chip checks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions