Skip to content

install: improve NOR install reliability and rootfs_data handling - #140

Merged
openipc-ai merged 4 commits into
OpenIPC:masterfrom
ArthurKoba:fix/nor-unlock-install
Sep 22, 2026
Merged

openipc-ai merged 4 commits into
OpenIPC:masterfrom
ArthurKoba:fix/nor-unlock-install

Conversation

@ArthurKoba

@ArthurKoba ArthurKoba commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

This PR originally started as a small fix for SPI NOR write protection encountered during defib install.

On the tested Hi3516EV200 board, U-Boot detected the SPI NOR normally, but persistent writes could fail with:

ERROR: The DMA write area was locked.

Running sf lock 0 after sf probe cleared the protection and allowed the same erase/write operation to succeed, so the initial version of the PR added that unlock step before persistent NOR operations.

While testing and reviewing that change further, two related installer issues became visible. Rather than opening separate PRs for changes that affect the same NOR installation path, I kept them together and extended the original PR with two follow-up commits.

The first follow-up addresses command synchronization and TFTP staging reliability.

The original unlock path depended too much on returned text: an incomplete shell response or a failed download command could be interpreted as success. Command execution now preserves completion/status information, shell commands must return to the expected U-Boot prompt, and probe/unlock failures stop the install before persistent writes.

The same testing also exposed an intermittent case where TFTP reported a completed transfer but the data already present in RAM had an incorrect CRC before anything was written to flash.

install now verifies TFTP-staged data in RAM before erase/write. If verification fails, it retries the same file once by default. For host TFTP, the retry falls back to 512-byte blocks. The retry count is kept in TFTP_RAM_VERIFY_RETRIES rather than being hardcoded into the control flow.

If the retry also fails, the install stops before modifying flash.

This verified staging path is used for the TFTP payloads handled by install, including U-Boot, kernel, NOR rootfs and the extracted UBIFS payload used by NAND/UBI installs. The initial boot-ROM/SPL/U-Boot upload remains a separate transport path.

The second follow-up came from checking clean-install behavior around rootfs_data.

Generic NOR installs previously preserved the existing persistent overlay, while stock-U-Boot migration paths already had their own cleanup behavior. This PR keeps those defaults unchanged, but adds an explicit:

--wipe-rootfs-data

option for cases where the operator wants a clean persistent overlay.

The explicit wipe goes through the same NOR unlock path, erases the rootfs_data region and verifies the erased contents by CRC. It is rejected for NAND and for contradictory use together with --skip-stage rootfs-data.

So the final PR keeps the original SPI NOR unlock fix as its base, while incorporating the additional reliability and cleanup changes that were discovered while testing that fix in the complete installation flow.

Verification

The final three-commit series was tested from a clean Linux/WSL checkout:

93 targeted tests passed
884 full Python tests passed, 3 skipped
16 fuzz tests passed
ruff: clean
mypy: clean (77 source files)
agent C tests: 5412/5412 passed
git diff --check: clean
working tree: clean

The NOR installation path was also tested on physical Hi3516EV200 / 8 MiB SPI NOR hardware.

Kernel-only and full NOR installs completed successfully with automatic SPI NOR unlock. U-Boot, kernel and rootfs writes passed CRC verification, and the existing factory MAC address was preserved.

The intermittent TFTP corruption condition was observed on hardware during development. The retry/fallback behavior is covered deterministically by regression tests: the first RAM CRC is forced to fail, the same file is requested again using the conservative block-size fallback, and no flash erase is allowed until RAM verification succeeds.

Issue `sf lock 0` after a successful NOR probe and before any selected persistent NOR stage. This matches the established OpenIPC flashing sequence and handles boards where erase/write is rejected with a locked DMA/write area.

Keep older U-Boot variants compatible by warning and continuing when the lock subcommand is unavailable; explicit unlock failures still stop the install before destructive writes.

Add regression coverage for successful unlock, unsupported legacy `sf` syntax, and hard unlock failure ordering.

Hardware verified on a GARUS Hi3516EV200 board with 8 MiB SPI NOR: both a kernel-only install and a full install run executed the unlock successfully; U-Boot, kernel, and rootfs writes passed CRC readback and the factory MAC was preserved.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Unlock SPI NOR before persistent install writes

🐞 Bug fix 🧪 Tests 🕐 10-20 Minutes

Grey Divider

AI Description

• Unlocks SPI NOR after probing and before selected persistent install stages.
• Preserves legacy U-Boot compatibility by warning when sf lock is unavailable.
• Aborts explicit unlock failures and tests ordering, fallback, and failure behavior.
Diagram

graph TD
  B["Probe SPI NOR"] --> C{"Persistent stage?"}
  C -- "Yes" --> E["Unlock SPI NOR"] --> F{"Unlock result?"}
  F -- "Success" --> G["Persistent write"]
  F -- "Unsupported" --> H["Warn and continue"] --> G
  F -- "Error" --> I["Abort install"]
  C -- "No" --> J["Skip unlock"]
Loading
High-Level Assessment

The centralized unlock immediately after a successful probe is the preferred approach because it covers every persistent NOR stage before destructive operations begin. Unlocking within each stage was considered but would duplicate logic and risk missing future write paths; treating unsupported legacy syntax separately also preserves compatibility without masking explicit flash errors.

Files changed (2) +189 / -0

Bug fix (1) +24 / -0
orchestrator.pyUnlock probed SPI NOR before persistent stages +24/-0

Unlock probed SPI NOR before persistent stages

• Issues 'sf lock 0' whenever selected stages can modify persistent NOR storage. Unsupported legacy syntax produces a warning and continues, while explicit unlock errors close the transport and stop installation before any persistent write.

src/defib/install/orchestrator.py

Tests (1) +165 / -0
test_install_nor_unlock.pyCover SPI NOR unlock ordering and failure handling +165/-0

Cover SPI NOR unlock ordering and failure handling

• Adds an env-only installer harness with mocked recovery, transport, and U-Boot responses. Verifies unlock ordering, compatibility with unsupported 'sf lock' syntax, hard-failure abortion, write suppression, and transport cleanup.

tests/test_install_nor_unlock.py

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

qodo-free-for-open-source-projects Bot commented Sep 17, 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. Installs proceed without confirmed unlock ✓ Resolved 🐞 Bug ☼ Reliability
Description
run_install reduces sf lock 0 to response text and treats every non-marker response as success,
even though shell timeouts return partial text and download mode discards its ok=False status.
When the command times out or the protocol reports an error without a recognized textual marker,
subsequent erase and write stages run without a confirmed unlock and the human interface can falsely
report that protection was cleared.
Code

src/defib/install/orchestrator.py[R663-664]

+            unlock_resp = await _cmd("sf lock 0", timeout=5.0)
+            unlock_text = unlock_resp.lower()
Evidence
Shell send_command returns its buffer when the deadline expires rather than raising, while the
download client returns False for protocol errors and timeouts. _cmd discards that download
status, and the new unlock block accepts any response for which the textual flash-error helper finds
no known marker.

src/defib/install/orchestrator.py[558-589]
src/defib/install/orchestrator.py[661-683]
src/defib/flashdump.py[299-305]
src/defib/protocol/download_cmd.py[119-138]
src/defib/install/layout.py[67-96]

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

## Issue description
The new unlock path infers success only from response text, while shell command timeouts return incomplete text and download-command failures lose their boolean status. Preserve command completion and protocol status so unsupported syntax can still warn and continue, but timeouts and explicit failures stop before persistent writes.
## Fix Focus Areas
- src/defib/install/orchestrator.py[558-589]
- src/defib/install/orchestrator.py[661-683]
- src/defib/flashdump.py[299-305]
## Recommended Fix
Add a status-preserving or strict command execution path for the unlock operation. Require shell mode to observe the expected prompt, retain the download client's boolean result, classify unsupported textual responses separately, and call `close_and_fail` for timeouts or non-unsupported protocol failures before any persistent NOR operation.

ⓘ 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 route each action level your way: inline, summary, both, or drop

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/defib/install/orchestrator.py Outdated
Hardware testing exposed two independent reliability failures around persistent
installs: incomplete U-Boot command responses could advance the state machine,
and a completed TFTP transfer could occasionally leave corrupt RAM contents.

Require prompt completion for ordinary shell commands, preserve the explicit
download-command result, reject a failed sf probe, and keep reset on its own
promptless control-flow path.

Verify every TFTP-staged payload before persistent writes by checking the
reported transfer size and RAM CRC. Keep retry policy in the named
TFTP_RAM_VERIFY_RETRIES constant, defaulting to one retry. When verification
fails and another attempt is available, emit an operator-facing warning naming
the next attempt and the exact TFTP file being fetched again. Host TFTP switches
future RFC2348 negotiation to 512-byte blocks after the first failed
verification; pod TFTP retries without host-side blocksize control.

Apply the same verified staging helper to U-Boot, kernel, NOR rootfs, and the
extracted UBIFS payload on NAND/UBI installs. Phase-1 boot-ROM/SPL/U-Boot upload
is a separate transport and is intentionally outside this TFTP policy.

Regression coverage deterministically corrupts the first RAM CRC and proves
that the same file is fetched a second time, the warning reports Attempt 2, no
flash erase happens before the second CRC succeeds, and the host fallback caps
TFTP blocks at 512 bytes. Persistent corruption still fails closed before any
flash write. Hardware verification remains on this test branch before folding
the result into the existing PR.
Generic NOR installs currently preserve rootfs_data, while registered stock
U-Boot migrations erase it as part of their migration flow. Keep both existing
defaults unchanged.

Add --wipe-rootfs-data as an explicit destructive option for operators who want
a clean persistent overlay during an otherwise normal install. The flag is
independent of --stage selection: requesting it always performs the NOR-tail
erase and the existing readback/CRC verification before the install continues.

Reject the option on NAND, where this NOR rootfs_data layout does not apply, and
reject the contradictory combination with --skip-stage rootfs-data before any
device access. Treat an explicit wipe as a persistent operation in the
vendor-chainload partial-install guard and in the SPI NOR unlock gate, so a wipe
cannot bypass the protection clearing added by the preceding install hardening.

Document the preserved defaults and the opt-in wipe, and add focused coverage
for the legacy generic rootfs-data stage behavior, explicit wipe execution,
unlock/erase/verification ordering, NAND rejection, and conflicting CLI intent.

Whether a full generic install should eventually wipe rootfs_data by default is
a separate policy decision because changing that default would destroy existing
overlay data. This commit deliberately does not make that behavior change.
@ArthurKoba ArthurKoba changed the title install: unlock SPI NOR before persistent writes install: harden NOR flashing and add explicit rootfs_data wipe Sep 18, 2026
@ArthurKoba ArthurKoba changed the title install: harden NOR flashing and add explicit rootfs_data wipe install: improve NOR install reliability and rootfs_data handling Sep 18, 2026
ArthurKoba added a commit to ArthurKoba/openipc-defib that referenced this pull request Sep 21, 2026
Confine strict U-Boot prompt synchronization to the install path so restore and
dump-flash keep their historical lenient command contract. Preserve shell and
download-command completion status inside install, make actual sf probe failures
fatal again, and allow expected printenv misses to reach the existing rescue-MAC
and verification logic.

Keep the TFTP retry fallback scoped to one verification attempt, scale RAM CRC
timeouts for large payloads, and preserve NAND compatibility when crc32 is not
available. Re-probe and re-unlock NOR after the stock-migration environment
reset before saveenv.

Align --wipe-rootfs-data with exact stage selection, cover the vendor-chainload
destructive guard, and add regressions for prompt timeouts, missing ethaddr,
real U-Boot probe failures, temporary TFTP block-size fallback, and post-reset
NOR unlock ordering.

This commit is developed on an isolated child branch of PR OpenIPC#140; it does not
modify the PR head until the fixes pass review and validation.
Follow up on review of the existing three-commit NOR install series without
rewriting its history.

Keep flashdump.send_command backward-compatible by making strict prompt
completion opt-in to install. Restore and dump-flash therefore retain their
lenient prompt semantics, while install preserves explicit shell/download
completion status and fails closed where persistent writes depend on it.

Reject real SPI probe failures such as "Failed to initialize SPI flash",
centralize sf lock result classification, and re-probe/re-unlock after the
in-session U-Boot reset before saveenv writes.

Treat optional printenv misses as semantic absence rather than transport
failure, preserving the generic rescue-MAC path and explicit environment
verification in download-command mode.

Share one U-Boot TFTP command sequencer between lenient and strict callers
without sharing transport policy. Scope the 512-byte host fallback to the retry
that needs it, restore normal negotiation after success, scale CRC timeout with
payload size, and retry CRC timeout/incomplete output. NAND targets whose older
U-Boot genuinely lacks crc32 retain compatibility with a single warning and
TFTP completion/size checks; timeouts, malformed checksums, and mismatches remain
fatal.

Make --wipe-rootfs-data consistent with exact --stage plans and cover its
stock-U-Boot chainload guard. Remove unreachable verification code and compute
the expected RAM CRC inside the verifier.

Add regression coverage for the review findings: strict-vs-legacy prompt
behavior, promptless reset, dump CRC capability probing, real sf probe failure,
shell/download result contracts, optional environment reads, shared TFTP
fallback classification and scope, CRC retry modes, NAND without crc32,
post-reset SPI reinitialization, and rootfs_data destructive-path guards.
@openipc-ai
openipc-ai merged commit 8d4d89b into OpenIPC:master Sep 22, 2026
13 checks passed
@ArthurKoba
ArthurKoba deleted the fix/nor-unlock-install branch September 22, 2026 09:32
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