Skip to content

recovery/session: blast proactive handshake magic before power-cycle#109

Merged
widgetii merged 1 commit into
masterfrom
recovery-blast-before-power-cycle
Jun 3, 2026
Merged

recovery/session: blast proactive handshake magic before power-cycle#109
widgetii merged 1 commit into
masterfrom
recovery-blast-before-power-cycle

Conversation

@widgetii
Copy link
Copy Markdown
Member

@widgetii widgetii commented Jun 3, 2026

Summary

  • Proactive-blast handshakes (HiSiliconV500, HiSiliconCV6xx) require magic to be on the wire before the bootrom's UART listen window opens (~tens of ms after reset release). The old non-frame-blast path ran power_cycle → drain_until_silent(0.5s/5s) → handshake_task, losing the race even on healthy boards.
  • Reproduced on a Vectis-bridged hi3516cv608: defib burn --power-cycle silently missed the window; the ~/git/burn/burn reference (open serial → immediately blast → user power-cycles manually) succeeded on the same hardware.
  • Fix in src/defib/recovery/session.py: for non-frame-blast with programmatic power, spawn the handshake task first, yield 50 ms so a few magic frames land on the wire, then trigger reset. Drop drain_until_silent on this path — it was eating the catch window even when ordering was right.
  • Frame-blast (HiSiliconStandard 0x20→0xAA) keeps the existing reset-first order — it's reactive on bootrom markers, not a blaster.

Test plan

  • uv run pytest tests/test_recovery_session.py tests/test_handshake_resilience.py tests/test_autoboot.py — 25/25 pass
  • uv run ruff check src/defib/recovery/session.py clean
  • uv run mypy src/defib/recovery/session.py --ignore-missing-imports clean
  • End-to-end against live hi3516cv608 over rfc2217://127.0.0.1:35240 with VectisController: log order proves the fix — Starting HiSilicon CV6xx handshake emits before the Vectis SET-CONTROL DTR/RTS pulse, then BootROM handshake complete fires.
  • Full defib burn -c hi3516cv608 -f <blob> -p rfc2217://... once a CV608 composite is available (OpenIPC has no prebuilt; handshake stage verified independently above).

Proactive-blast handshakes (HiSiliconV500, HiSiliconCV6xx) require the
host's magic stream to already be on the wire when the bootrom's UART
listen window opens (~tens of ms after reset release); otherwise the
bootrom gives up and jumps to flash boot. The old non-frame-blast path
ran `power_cycle -> drain_until_silent(0.5s/5s) -> handshake_task`,
which lost the race even on healthy boards. Reproduced on a
Vectis-bridged hi3516cv608: `defib burn --power-cycle` silently missed
the window, while `~/git/burn/burn` (open serial, immediately blast,
expect manual reset) succeeded on the same hardware.

Restructure the non-frame-blast power+handshake block to spawn the
handshake task first, yield 50 ms so a few magic frames hit the wire,
then trigger reset. Drop `drain_until_silent` on this path — it was
eating the catch window even when ordering was right. Frame-blast
(HiSiliconStandard 0x20->0xAA) keeps the existing reset-first order
because it is reactive on bootrom markers.

End-to-end verified through `RecoverySession.run` against a live
hi3516cv608 over rfc2217://127.0.0.1:35240 with `VectisController`:
the "Starting HiSilicon CV6xx handshake" log emits before the Vectis
SET-CONTROL DTR/RTS pulse, and `BootROM handshake complete` fires.
All 25 tests in test_recovery_session.py, test_handshake_resilience.py
and test_autoboot.py still pass.
@widgetii widgetii merged commit a76d54f into master Jun 3, 2026
13 checks passed
@widgetii widgetii deleted the recovery-blast-before-power-cycle branch June 3, 2026 12:42
widgetii added a commit that referenced this pull request Jun 3, 2026
## Summary

Adds end-to-end flash agent support for the HiSilicon CV6xx family
(hi3516cv608, hi3516cv610, hi3516dv500, hi3519dv500), verified live on a
Vectis-bridged hi3516cv608.

CV6xx fastboot is structurally different from HiSilicon Standard: a
single
composite (GSL + DDR tables + U-Boot-shaped section) gets uploaded and
the
bootrom parses the 1024-byte U-Boot header to decide where to jump. That
changed the whole agent-bring-up shape, and also surfaced two stale
V3/V4
addresses in the existing cv610 stanza that turn out to never have been
exercised on real CV6xx silicon.

## What's in the change set

- **`HiSiliconCV6xx.send_firmware(..., uboot_override=)`** + new module
  helper **`wrap_cv6xx_payload(header_source, payload)`** — clones the
  1024-byte U-Boot header from a known-good composite and patches the
  length field at byte 36 so the bootrom validates and jumps to the
  wrapped payload.
- **`_agent_upload_async`** in `cli/app.py` dispatches on protocol via
  `find_protocol(chip)`. HiSiliconStandard path unchanged; CV6xx takes a
  new `-f/--file` (composite for GSL + DDR), wraps the agent, and runs
handshake → GSL → DDR → wrapped-agent. Uses the spawn-handshake-before-
power-cycle ordering from #109 so it works with Vectis `--power-cycle`.
- **`src/defib/agent/client.py`** — `hi3516cv608` → `hi3516cv610` agent
  alias, mirroring the existing same-family aliases.
- **`agent/Makefile`** hi3516cv610 stanza, fixing several latent V3/V4
  copy-paste errors:
  - `LOAD_ADDR` `0x41000000` → `0x41000400` so the agent's link-time
    addresses match where the bootrom actually jumps after the
    U-Boot-header offset (`load_addr + 0x400`).
  - `CRG_BASE` `0x12010000` → `0x11010000`, `WDT_BASE` and
    `SYSCTRL_REBOOT` likewise moved into `0x11xxxxxx` (matches the UART
    address and the kernel's `earlycon=pl011,0x11040000` arg).
  - `FLASH_MEM` `0x14000000` → `0x0F000000` (FMC MMIO window on CV6xx).
  - New `FMC_CRG_OFFSET=0x3F40` + `FMC_CRG_CLK_BIT=4` because the FMC
clock-gate register moved from `CRG+0x144` bit1 to `CRG+0x3F40` bit4.
  - New `SPI_PIN_BASE=0x10260000` + `SPI_PINS_CV6XX=1` because the SPI
    pinctrl block moved from `0x100C0000` and the per-pin register
    offsets + pad-drive values changed.
- **`agent/spi_flash.c`** uses the new `FMC_CRG_OFFSET` /
`FMC_CRG_CLK_BIT`
  and `SPI_PIN_BASE` / `SPI_PINS_CV6XX` macros, falling back to V3/V4
  defaults when not defined.

Source of truth for the CV6xx pinmux + FMC sequencing is the dimerr
u-boot
fork: `drivers/mtd/fmc_hi3516cv610.c::hi3516cv610_spi_io_config()`.

## Test plan

- [x] `uv run pytest tests/ -x -v --ignore=tests/fuzz` — 68/68 pass
- [x] `uv run ruff check src/` and `uv run mypy src/defib/` clean
- [x] `make -C agent test HOST_CC=gcc` — 5412/5412 agent C tests pass
- [x] `make -C agent SOC=hi3516cv610` — builds `agent-hi3516cv610.bin`
      (18 KiB)
- [x] End-to-end on live hi3516cv608 over Vectis
      (`rfc2217://127.0.0.1:35240` with `DEFIB_POWER_TYPE=vectis`):

      ```
      defib agent upload -c hi3516cv608 -f boot-hi3516cv608-nor.bin \
        -p rfc2217://127.0.0.1:35240 --power-cycle
      → Agent ready!
        RAM: 0x40000000
        Flash: 16384KB
        JEDEC: a14018  (FM25Q128A 16 MiB)
      ```

      Flash MMIO read from `0x0F000000` returns the burned U-Boot blob
      byte-for-byte, confirming the SPI path works end-to-end.

---------

Co-authored-by: Dmitry Ilyin <widgetii@users.noreply.github.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