Skip to content

mini-boot: ship a recovery image that fits the bootrom SRAM window - #7

Merged
openipc-ai merged 2 commits into
masterfrom
fix/uart-recovery-image
Aug 25, 2026
Merged

mini-boot: ship a recovery image that fits the bootrom SRAM window#7
openipc-ai merged 2 commits into
masterfrom
fix/uart-recovery-image

Conversation

@openipc-ai

@openipc-ai openipc-ai commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Closes #5. Fixes OpenIPC/firmware#2299.

Contains #6 (this branched from it), so the diff here is #6 + the recovery image. Merging this closes both; merging #6 first and then this also works.

The problem #6 left open

#6 cut the mini-boot prelude from 19376 to 17408 and made the size impossible to regress. The boot ROM's window is 14336, so that image still cannot be uploaded over UART, and the camera in OpenIPC/firmware#2299 stays dead.

One image can't serve both paths, so build two

The prelude has to shed 3072 bytes. DDR training is by far the largest thing in it — and it is the one part a bench recovery can safely do without: the static DDR setup from reg_info still runs, and the image only has to survive a single decompression into RAM with an operator present.

The flashed image keeps training untouched. That is what makes RAM work across silicon and temperature spread, and dropping it there would trade a recovery defect for an intermittent field one.

variant prelude target contains DDR training boots in QEMU
u-boot-<soc>-universal.bin (flashed) 17408 14336 yes yes
u-boot-<soc>-recovery.bin (UART) 11264 14336 no yes, 3072 bytes of margin

--gc-sections does the removal on its own once CONFIG_DDR_TRAINING_V2 is off — no object list to keep in sync.

A trap this turned up

start.S guarded the bl start_ddr_training with CONFIG_DDR_TRAINING_V2. But the function it calls is itself guarded internally, and also does this:

	/*the value should config after trainning, or
	 it will cause chip compatibility problems*/
	writel(0x401, DDR_DMC_BASE_REG0 + 0x28);

— deliberately outside the #ifdef. Guarding both meant a training-less build silently lost that write. The call site is now unconditional, which changes nothing for the flashed image (the macro is defined there) and keeps the write in the recovery one. Verified in the disassembly, where the whole function reduces to exactly that store:

80701970 <start_ddr_training>:
80701970:	ldr	r3, [pc, #8]	; 0x20111000   <- DDR_DMC_BASE_REG0
80701974:	ldr	r2, [pc, #8]	; 0x00000401
80701978:	str	r2, [r3, #40]	; 0x28
8070197c:	bx	lr

Gating

The size ASSERT now comes from the Makefile via --defsym, so the two variants can carry different limits — the recovery image is held to 0x3800 for real, the flashed one keeps ratcheting. Linking with no --defsym is an error rather than a default, so neither variant can quietly lose its gate:

mini-boot.lds:68: undefined symbol `MINI_BOOT_SIZE_CEILING' referenced in expression

CI builds, size-checks, boots and publishes both images. The recovery check does not ratchet:

- u-boot-hi3518ev200-recovery.bin: prelude [11264/14336 bytes]
-- margin: 3072 bytes
- u-boot-hi3518ev200-universal.bin: prelude [17408/14336 bytes]
::warning ... use the recovery image for that. Tracking: #5

No defib change needed

defib's payload scan starts at 0x4000, so on the recovery image it finds nothing and falls back to the profile's 0x3B00 — exactly the SRAM window, which covers the whole 11264-byte SPL and stops precisely at 0x04014000. Checked against defib's own _detect_spl_size:

hi3518ev200  recovery   detected=15104  capped=15104  chunks=15  truncated=no
hi3518ev200  universal  detected=17408  capped=15104  chunks=15  truncated=YES

So existing installs work as-is. Recovery flow is unchanged apart from which file you point at:

defib burn -c hi3518ev200 -p /dev/ttyUSB0 -f u-boot-hi3518ev200-recovery.bin

…then flash u-boot-<soc>-universal.bin to NOR from the U-Boot prompt, so the camera runs with full DDR training.

Verification

Vendor arm-hisiv510 toolchain (the one build.yml downloads), both SoCs, both variants, in qemu-hisilicon's hi3516cv200 machine — the same model the existing qemu_smoke job uses. All four reach a U-Boot prompt. The recovery image's output is byte-identical to the flashed one's:

System startup
Uncompress.........Ok
System startup
U-Boot 2010.06 (Aug 25 2026 - 06:42:32)
Check Flash Memory Controller v100 ... Found
SPI Nor(cs 0) ID: 0xef 0x40 0x17

Uncompress.........Ok is the line that matters: DDR came up from reg_info alone and the LZMA payload decompressed into it.

Gates exercised in both directions — recovery build forced past its ceiling fails the link, an artifact with the payload pushed past 14336 fails the CI check, and the shipped sizes pass.

Not validated on real hardware — I have neither SoC on a bench. The reporter in OpenIPC/firmware#2299 has a blank hi3518ev200 and a working UART setup, so this wants their confirmation before it is treated as proven. QEMU does not model DDR timing, so it cannot tell us that a training-less DDR init holds on real silicon — that is the one claim here resting on reasoning rather than measurement.

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

qodo-free-for-open-source-projects Bot commented Aug 25, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Ship SRAM-safe UART recovery mini-boot images

🐞 Bug fix ✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Adds UART recovery images without DDR training to fit boot ROM SRAM.
• Preserves compatibility-critical DMC setup and full training in flashed images.
• Enforces size, QEMU boot, and publication gates for both variants.
Diagram

graph TD
  SRC["Mini-boot sources"] --> BUILD["Variant build"] --> UNI["Universal image"] --> GATE{"Size gates"} --> QEMU["QEMU smoke"] --> PUB["Firmware release"]
  BUILD --> REC["Recovery image"] --> GATE
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use isolated variant build directories
  • ➕ Avoids cleaning shared objects between variants
  • ➕ Supports safer parallel builds and explicit variant outputs
  • ➖ Requires broader changes to the legacy compressed build system
  • ➖ Adds migration risk to boot-critical tooling
2. Keep DDR training in one universal image
  • ➕ Maintains one artifact and identical DDR behavior on every path
  • ➕ Avoids a hardware-sensitive training-free recovery mode
  • ➖ Cannot meet the boot ROM SRAM limit at the current code size
  • ➖ Leaves blank devices unrecoverable over UART

Recommendation: Keep the two-image strategy: the recovery artifact meets the immutable UART SRAM constraint while the flashed artifact retains robust DDR training. The explicit clean between builds is an acceptable low-risk compromise for this legacy build system; isolated output directories would be a worthwhile follow-up if parallel variant builds become necessary.

Files changed (8) +265 / -53

Enhancement (2) +35 / -3
MakefileAdd a dedicated UART recovery image target +11/-0

Add a dedicated UART recovery image target

• Adds 'mini-boot-recovery.bin', cleaning shared compressed objects before rebuilding with 'RECOVERY=1'. This creates the size-constrained variant without changing the existing universal target.

Makefile

MakefileConfigure size-optimized universal and recovery builds +24/-3

Configure size-optimized universal and recovery builds

• Introduces variant-specific output names, recovery flags, and linker ceilings. It switches to size optimization and section garbage collection so disabling DDR training removes unused code automatically.

arch/arm/cpu/hi3518ev200/compressed/Makefile

Bug fix (2) +54 / -15
mini-boot.ldsAlign the payload and enforce variant size ceilings +40/-9

Align the payload and enforce variant size ceilings

• Collects per-function and per-data sections, aligns the compressed payload to a 1 KiB recovery boundary, and exposes its start offset. A required linker symbol now enforces the selected prelude ceiling at link time.

arch/arm/cpu/hi3518ev200/compressed/mini-boot.lds

start.SPreserve post-training DMC configuration in recovery builds +14/-6

Preserve post-training DMC configuration in recovery builds

• Calls 'start_ddr_training' unconditionally in both startup paths. Training remains internally conditional, while the compatibility-required DMC register write still executes when recovery builds disable training.

arch/arm/cpu/hi3518ev200/start.S

Other (4) +176 / -35
build.ymlBuild, validate, boot, and publish both mini-boot variants +146/-35

Build, validate, boot, and publish both mini-boot variants

• CI now produces universal and recovery artifacts for each SoC, checks both against partition limits, and validates their exact compressed-payload boundaries. It hard-fails recovery SRAM overflows, ratchets universal growth, smoke-tests both variants in QEMU, and publishes both artifacts.

.github/workflows/build.yml

hi3516cv200.hDisable DDR training only for hi3516cv200 recovery images +10/-0

Disable DDR training only for hi3516cv200 recovery images

• Undefines 'CONFIG_DDR_TRAINING_V2' when the mini-boot recovery flag is active. Normal flashed builds retain full DDR training.

include/configs/hi3516cv200.h

hi3518ev200.hDisable DDR training only for hi3518ev200 recovery images +10/-0

Disable DDR training only for hi3518ev200 recovery images

• Undefines 'CONFIG_DDR_TRAINING_V2' when the mini-boot recovery flag is active. Normal flashed builds retain full DDR training.

include/configs/hi3518ev200.h

hi3518ev201.hDisable DDR training only for hi3518ev201 recovery images +10/-0

Disable DDR training only for hi3518ev201 recovery images

• Undefines 'CONFIG_DDR_TRAINING_V2' when the mini-boot recovery flag is active. Normal flashed builds retain full DDR training.

include/configs/hi3518ev201.h

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

qodo-free-for-open-source-projects Bot commented Aug 25, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Variant objects contaminate universal builds ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
RECOVERY=1 changes compilation flags, but the recovery and universal mini-boot variants share
object names and only the recovery target cleans before rebuilding them. Consequently, a universal
build after mini-boot-recovery.bin can reuse recovery-configured objects, silently disabling full
DDR training in the flashed image and undermining reliability across hardware and temperature
variation.
Code

Makefile[R443-446]

+		CROSS_COMPILE=$(CROSS_COMPILE) clean
+	$(MAKE) -C $(TOPDIR)/arch/$(ARCH)/cpu/$(CPU)/compressed \
+		CROSS_COMPILE=$(CROSS_COMPILE) RECOVERY=1 \
+		BINIMAGE=$(TOPDIR)/u-boot.bin TOPDIR=$(TOPDIR)
Evidence
Compliance rule 3 requires preserving the DDR initialization needed for successful boot. The
recovery flag is added to CFLAGS and disables DDR training through the board headers, while both
variants use the same compressed object names; the recovery target cleans and rebuilds those objects
with RECOVERY=1, but the normal universal target invokes the same directory without cleaning or
otherwise forcing recompilation. Because Make does not track compiler flag changes, and the recovery
clean removes the normal ELF/tmp while leaving the newly rebuilt recovery objects available, the
next normal build can relink those recovery-configured objects under the looser universal size
ceiling with CONFIG_DDR_TRAINING_V2 still disabled.

Reduce mini-boot prelude size without removing required recovery behavior
Makefile[431-446]
arch/arm/cpu/hi3518ev200/compressed/Makefile[26-35]
arch/arm/cpu/hi3518ev200/compressed/Makefile[28-47]
arch/arm/cpu/hi3518ev200/compressed/Makefile[51-64]
arch/arm/cpu/hi3518ev200/compressed/Makefile[151-158]
include/configs/hi3518ev200.h[362-364]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Recovery and universal mini-boot builds use different preprocessor flags but share object filenames, so building `mini-boot.bin` after `mini-boot-recovery.bin` can reuse objects compiled with `RECOVERY=1` and produce a flashed universal image without full DDR training.
## Issue Context
The recovery target cleans before compiling its variant, while the universal target invokes the same compressed directory without a corresponding clean or separate object directory. Make does not track compiler flag changes when determining whether shared object targets are current, and the fix should remain safe when both top-level targets are requested together or built in parallel.
## Fix Focus Areas
- Makefile[431-446]
- arch/arm/cpu/hi3518ev200/compressed/Makefile[26-64]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can hide the parts of a finding you never read, like the evidence or the agent prompt

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@openipc-ai
openipc-ai changed the base branch from fix/mini-boot-fits-bootrom-sram-window to master August 25, 2026 06:51
Comment thread Makefile Outdated
@openipc-ai openipc-ai closed this Aug 25, 2026
@openipc-ai openipc-ai reopened this Aug 25, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 9ba991f

OpenIPC/firmware#2299 is a hi3518ev200 that cannot be revived. Its
mini-boot prelude is 17408 bytes and the boot ROM's SRAM window is
14336, so the UART upload runs off the end of SRAM into the boot ROM's
own stack and the chip stops answering. The previous commit cut the
prelude as far as compiler flags can and gated it, but 3072 bytes over
is still 3072 bytes over: that image cannot be uploaded, and the
reporter's camera stays dead.

Build a second image for the recovery path instead of trying to make
one image serve both. DDR training is by far the largest thing in the
prelude, and it is the one part a bench recovery can safely do without:
the static DDR setup from reg_info still runs, and the image only has
to survive a single decompression into RAM with an operator present.
The flashed image keeps training untouched -- that is what makes RAM
work across silicon and temperature spread, and dropping it there would
trade a recovery defect for an intermittent field one.

    variant     prelude   target   boots in QEMU
    universal     17408    14336   yes
    recovery      11264    14336   yes, 3072 bytes of margin

Both were built with the vendor arm-hisiv510 toolchain for both SoCs
and booted to a U-Boot prompt in qemu-hisilicon's hi3516cv200 machine.
The recovery image's output is identical to the flashed one's, including
"Uncompress.........Ok" -- DDR came up from reg_info alone and the LZMA
payload decompressed into it.

Turning training off exposed a trap worth naming: start.S guarded the
`bl start_ddr_training` with CONFIG_DDR_TRAINING_V2, but the function
it calls is itself guarded internally and *also* performs the
post-training DMC write the vendor comments as required for chip
compatibility. Guarding both meant a training-less build silently lost
that write. The call site is now unconditional, which changes nothing
for the flashed image and keeps the write in the recovery one --
verified in the disassembly, where start_ddr_training reduces to
exactly that single store.

The size ASSERT now comes from the Makefile via --defsym so the two
variants can carry different limits: the recovery image is held to
0x3800 for real, the flashed one keeps ratcheting against its current
size. Linking with no --defsym is an error rather than a default, so
neither variant can quietly lose its gate. CI checks and boots both,
and publishes both.

No change is needed in defib: its payload scan starts at 0x4000, so on
the recovery image it finds nothing and falls back to the profile's
0x3B00 -- exactly the SRAM window, which covers the whole 11264-byte
SPL. Existing installs work as-is.

Not validated on real hardware -- I have neither SoC on a bench.
Review raised that the two variants share object file names and are
compiled with different flags, which make does not track, so building
one after the other could relink the other's objects -- and in the
direction that matters that is silent: the flashed image would come out
with DDR training compiled out and still pass its looser ceiling.

It does not currently happen, because this Makefile leaves no objects
behind between invocations and recompiles all 19 every time; the exact
sequence produces a correct image today. But that is an accident of how
make treats these files, not a property anyone declared, and one
.PRECIOUS or a directly-requested .o would undo it silently. The failure
it guards against is an unreliable flashed bootloader, which is not
something to leave resting on an accident.

Record the variant the objects belong to and wipe them when it changes,
so build order cannot matter whoever invokes the directory. Verified in
both orders: universal -> recovery -> universal, and recovery ->
universal, each giving 17408 with training and 11264 without.

This also subsumes the explicit clean the top-level recovery target was
doing, which only protected the one call path that went through it.
@openipc-ai
openipc-ai force-pushed the fix/uart-recovery-image branch from f9c86c0 to 8072c10 Compare August 25, 2026 07:19
@openipc-ai
openipc-ai merged commit cc70f96 into master Aug 25, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant