Skip to content

profiles: cap hi3518ev200's SPL upload at its 16 KB SRAM ceiling - #126

Merged
openipc-ai merged 1 commit into
masterfrom
fix/hi3518ev200-spl-sram-ceiling
Aug 24, 2026
Merged

profiles: cap hi3518ev200's SPL upload at its 16 KB SRAM ceiling#126
openipc-ai merged 1 commit into
masterfrom
fix/hi3518ev200-spl-sram-ceiling

Conversation

@openipc-ai

Copy link
Copy Markdown
Contributor

Symptom

Reported in OpenIPC/firmware#2299 — a user recovering a blank-NOR Hi3518EV200 gets:

Loaded profile: hi3518ev200
Using sendFrameForStart handshake for hi3518ev200
Sending 135452 bytes of firmware...
  Sending DDR step ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:04
INFO:defib.protocol.hisilicon_standard:SPL boundary detected (LZMA) at 0x4800 (18432 bytes); profile default was 0x3B00 (15104 bytes)
Recovery failed: Failed to send SPL
  Sending DDR step ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:04
⠸ Sending SPL      ━━━━━━━━━━━━━━━━━━━━━━━╺━━━━━━  78% 0:00:07
Failed: Failed to send SPL

Handshake and DDR init are fine; SPL dies at 78%, and the retry dies at the same 78%.

Cause

u-boot-hi3518ev200-universal.bin (135452 B, latest) has its LZMA payload at file offset
0x4BB0, so _detect_spl_size returns 0x4800 — 18 chunks of 1 KB.

spl_address 0x04010500
SRAM end 0x04014000
window 0x3B00 (15104 B, 15 chunks)
detected 0x4800 (18432 B, 18 chunks)
overflow 3328 B

Chunks 1–14 fit (last byte at 0x04013D00). Chunk 15 writes 0x04013D00..0x04014100 and crosses
the ceiling into the bootrom's own stack/state. 14/18 = 77.8% — the reported 78%. The bootrom
then answers 0x20 instead of 0xAA and re-enters boot mode, which is why the retry stops at
exactly the same place instead of somewhere random.

This is the same fault as hi3520dv200 in #78: same spl_address, same chunk, same 0x04014000
boundary. That chip got SRAMLIMIT; ev200 was never given the field.

Ruled out: no >=12-byte 0xFF runs anywhere below 0x4800 in this binary, so the
cv500-family _zero_long_ff_runs quirk is not involved.

Fix

Add "SRAMLIMIT": "0x3B00" to hi3518ev200.json.

0x04010500 + 0x3B00 == 0x04014000 exactly, and 0x3B00 is also this chip's HiTool FILELEN,
so the capped upload is byte-for-byte what the vendor tool sends for an ev200.

Scope

Only ev200. SRAMLIMIT is not "FILELEN as a ceiling" — FILELEN is HiTool's reference SPL
size, which on several chips is well below the real SRAM ceiling:

chip detected FILELEN hardware
hi3516av200 0x6800 0x4F00 passes (overshoots FILELEN by 6400 B)
hi3516cv300 0x5400 0x4F00 passes (overshoots by 1280 B)
hi3516ev300 0x4400 0x6000 passes
gk7205v300 0x4400 0x6000 passes
hi3518ev200 0x4800 0x3B00 fails at chunk 15

Setting SRAMLIMIT = FILELEN fleet-wide would truncate av200 and cv300, which are known-good.
Only chips with a measured ceiling get the field.

Test plan

  • pytest — 733 passed, 2 skipped
  • New regression tests: profile wires spl_sram_limit == 0x3B00; spl_address + limit == 0x04014000; the real 0x4BB0 layout detects 0x4800 uncapped and 0x3B00 capped
  • Real hardware — no ev200 in the lab. The firmware#2299 reporter has the board and has
    offered to test; this should not merge until that comes back.

OpenIPC/firmware#2299 reported `defib burn -c hi3518ev200` dying at 78%
with "Failed to send SPL", reproducibly, on both attempts of the same run.

u-boot-hi3518ev200-universal.bin (135452 B, `latest`) carries its LZMA
payload at file offset 0x4BB0, so _detect_spl_size returns 0x4800 —
18 chunks. ev200's SPL window starts at 0x04010500 and its SRAM ends at
0x04014000, so chunks 1-14 land inside SRAM (up to 0x04013D00) and chunk
15 crosses the ceiling: 14/18 is the 78% the reporter saw. Past the
ceiling the bootrom's own stack/state is overwritten, it answers 0x20
instead of 0xAA and re-enters boot mode, which is why the retry stalls at
exactly the same place rather than somewhere random.

Identical signature to the hi3520dv200 fault fixed in #78 — same
spl_address, same chunk, same 0x04014000 boundary — and that chip already
carries SRAMLIMIT for it. ev200 was simply never given the field.

0x3B00 is not a guess: 0x04010500 + 0x3B00 lands exactly on 0x04014000,
and it is also what this chip's HiTool FILELEN uploads, so the capped
transfer is byte-for-byte what the vendor tool sends.

Deliberately scoped to ev200. SRAMLIMIT is not "FILELEN as a ceiling" —
hi3516av200 (detected 0x6800 vs FILELEN 0x4F00) and hi3516cv300 (0x5400
vs 0x4F00) both overshoot their FILELEN and pass on real hardware, so
their SRAM is genuinely larger. Only chips with a measured ceiling get
the field.

Not yet confirmed on ev200 hardware — there is no ev200 in the lab. The
reporter has the board and can test the capped path.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Cap Hi3518EV200 SPL upload at 16KB SRAM ceiling via SRAMLIMIT

🐞 Bug fix 🧪 Tests 🕐 10-20 Minutes

Grey Divider

AI Description

• Add SRAMLIMIT to hi3518ev200 profile to prevent SPL upload overrunning SRAM.
• Add regression tests ensuring boundary detection is capped to the SRAM limit.
Diagram

graph TD
  A["defib burn"] --> B["profiles/loader.load_profile"] --> C(["hi3518ev200.json"])
  B --> D["SoCProfile.spl_sram_limit"] --> E["HiSiliconStandard._detect_spl_size"] --> F["HiSiliconStandard._send_spl"] --> G{{"BootROM SRAM window"}}

  subgraph Legend
    direction LR
    _code["Code module"] ~~~ _data(["Profile data"]) ~~~ _ext{{"Hardware/ROM"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Derive SRAMLIMIT globally from FILELEN
  • ➕ No new per-chip field to maintain
  • ➕ Consistent behavior across all profiles
  • ➖ Incorrect for chips where detected SPL legitimately exceeds FILELEN (would truncate known-good uploads)
  • ➖ Conflates HiTool reference size with actual SRAM ceiling
2. Auto-compute ceiling from a chip SRAM map table
  • ➕ Less reliance on per-profile tuning once the map is correct
  • ➕ Could catch missing SRAMLIMIT cases automatically
  • ➖ Requires maintaining a separate authoritative hardware database
  • ➖ Higher risk of wrong map entries affecting many chips at once

Recommendation: Keep the current per-chip SRAMLIMIT approach. It fixes the specific ev200 bootrom-overwrite failure without changing semantics for profiles where FILELEN is not a hard ceiling, and it matches observed vendor-tool behavior for this SoC.

Files changed (2) +25 / -1

Bug fix (1) +1 / -1
hi3518ev200.jsonAdd SRAMLIMIT=0x3B00 to cap SPL upload within SRAM window +1/-1

Add SRAMLIMIT=0x3B00 to cap SPL upload within SRAM window

• Adds the optional SRAMLIMIT field to the hi3518ev200 profile, setting a hard ceiling of 0x3B00 bytes for SPL uploads. This prevents auto-detected SPL boundaries (e.g., LZMA at 0x4800) from overrunning the 0x04014000 SRAM end and corrupting boot ROM state.

src/defib/profiles/data/hi3518ev200.json

Tests (1) +24 / -0
test_protocol_standard.pyAdd regression coverage for hi3518ev200 SRAM limit and boundary capping +24/-0

Add regression coverage for hi3518ev200 SRAM limit and boundary capping

• Adds tests that verify the profile loader wires SRAMLIMIT into spl_sram_limit and that spl_address + limit hits 0x04014000. Adds a boundary-detection test reproducing the real OpenIPC layout (LZMA at 0x4BB0 -> 0x4800) and asserting capping behavior when sram_limit is set.

tests/test_protocol_standard.py

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Tip of the day
💡 Did you know, you can switch off images and animations for a plain-text comment

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@openipc-ai
openipc-ai merged commit 6a5c364 into master Aug 24, 2026
13 checks passed
@openipc-ai
openipc-ai deleted the fix/hi3518ev200-spl-sram-ceiling branch August 24, 2026 17:48
openipc-ai added a commit that referenced this pull request Aug 24, 2026
Follow-up to #126, which fixed the same defect on hi3518ev200.

## Why cv200 has it too

The two profiles are the same silicon window — only `PRESTEP0` differs:

| | hi3518ev200 | hi3516cv200 |
|---|---|---|
| `ADDRESS` | `0x04013000` / `0x04010500` / `0x81000000` | identical |
| `FILELEN` | `0x0040` / `0x3b00` | identical |
| SRAM ceiling | `0x04010500 + 0x3B00 = 0x04014000` | identical |
| shipped image | 135452 B, LZMA at `0x4BB0` → detects `0x4800` | 135432
B, LZMA at `0x4BB0` → detects `0x4800` |
| overrun | 3328 B | 3328 B |

Both are built by the same `build.sh` in OpenIPC/u-boot-hi3516cv200
(`SOCS="hi3518ev200 hi3516cv200"`).

## Evidence

The ev200 half was confirmed on real hardware in OpenIPC/firmware#2299 —
chunks 1-14 ACK on
attempt 1/32, chunk 15 targets `0x04013D00..0x04014100`, the bootrom
stops answering and all 32
retries time out. With #126's cap the same board completes both stages
with no retries at all.

cv200 puts identical numbers through the identical code path. I have no
cv200 to burn, so that
inference plus the profile diff is the evidence here.

## What this does and doesn't fix

Does: stops defib writing past the bootrom's stack mid-upload.

Doesn't: make cv200 recoverable. The prelude these builds produce is
19376 B against a 15104 B
window, so even a correctly-capped SPL is missing 4272 bytes of live
code and won't execute —
tracked upstream as OpenIPC/u-boot-hi3516cv200#5. This is the difference
between a clean failure
and a wedged chip.

## Test plan

- [x] `pytest` — 734 passed, 2 skipped
- [x] Regression test asserting `spl_sram_limit == 0x3B00` and
`spl_address + limit == 0x04014000`
- [ ] cv200 hardware — none available; ev200 sibling verified in
firmware#2299
openipc-ai added a commit that referenced this pull request Aug 24, 2026
…128)

## The problem

Two places tell users to install from PyPI:

- `README.md` — `uv tool install defib` / `pipx install defib`
- `web/index.html:621` — the fallback shown when the browser build
refuses a frame-blast SoC

```console
$ curl -s -o /dev/null -w '%{http_code}\n' https://pypi.org/pypi/defib/json
404
```

There is no `defib` package on PyPI, so both commands fail.

## Why it matters

OpenIPC/firmware#2299 is a user recovering a bricked hi3518ev200 over
UART. hi3518ev200 is a
frame-blast chip, so the web UI showed them precisely the broken line.
They got there in the end,
but when #126 landed and they were asked to reinstall and retry, the new
copy went somewhere other
than the `defib` on their `PATH` — an active virtualenv shadowed
`~/.local/bin` — and they reported
two more rounds of results from the old build against a bug that was
already fixed.

## The change

```bash
uv tool install git+https://github.com/OpenIPC/defib
# or
pipx install git+https://github.com/OpenIPC/defib
```

plus a `uvx` one-liner in both places, which needs no install and cannot
be shadowed by an active
virtualenv:

```bash
uvx --from git+https://github.com/OpenIPC/defib defib burn -c <chip> -p /dev/ttyUSB0 -t
```

## Not in scope

Publishing to PyPI would also fix this and is probably worth doing — it
needs a maintainer with the
account, so the docs fix goes first.

## Test plan

- [x] `uv tool install git+https://github.com/OpenIPC/defib` — installs,
`defib list-chips` prints 123 chips
- [x] `uvx --from git+https://github.com/OpenIPC/defib defib list-chips`
— works with nothing installed
- [x] `node --check` over all three `<script>` blocks in
`web/index.html` — parses
- [x] Evaluated the edited template literal; renders as

      ```
      uvx --from git+https://github.com/OpenIPC/defib \
        defib burn -c hi3518ev200 -p /dev/ttyUSB0 -t
      ```
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