Skip to content

PSoC 6 parity and CY8CEVAL-062S2 support, with two core power cut fixes - #975

Open
aidangarske wants to merge 4 commits into
wolfSSL:masterfrom
aidangarske:psoc6-eval
Open

aidangarske wants to merge 4 commits into
wolfSSL:masterfrom
aidangarske:psoc6-eval

Conversation

@aidangarske

Copy link
Copy Markdown
Member

Four commits, meant to be reviewed one at a time. They're all in the same PR because I needed them for the PSoC 6 changes. The first two fix core wolfBoot (src/update_flash.c, src/libwolfboot.c) for every target; the PSoC 6 port needs both because cypsoc6-qspi.config uses FLAGS_HOME with write-once flash.

1. Keep a rollback on the original image across a power cut (core, all targets)

  • A power cut during a rollback could boot the unconfirmed image again, in every layout. In the new sweep almost every rollback cut point fails without the fix: 6918 of 7272 (default) and 8718 of 9240 (write-once internal).
  • Cause: wolfBoot_update() chose rollback by comparing BOOT and UPDATE versions before the swap, so a resumed swap read half swapped headers and ended the restored image in TESTING. A resumed final erase also always wrote TESTING, and wolfBoot_start() then fell back again from its pre-resume state.
  • The rollback decision is now taken after the swap, when UPDATE holds the image that was in BOOT. The final erase writes SUCCESS directly after a rollback, and a resumed final erase works out the direction the same way.
  • When the staging sector is the header sector, a resume takes the BOOT version from the header backup in SWAP. A header that is missing, partly written or short-read counts as an update, and version 0 is an ordinary version. With NVM_FLASH_ECC an erased backup is not read.
  • wolfBoot_start() re-reads the BOOT state after a resumed final erase, also when only its cleanup erase failed. Trailer reads in wolfBoot_update() and the final erase unlock the flash, as wolfBoot_start() already does.
  • Scope: CUSTOM_PARTITION_TRAILER keeps its recorded direction (IMG_STATE_ROLLBACK) and EXT_ENCRYPTED keeps the pre-swap comparison. ALLOW_DOWNGRADE and DELTA_UPDATES keep TESTING for a cut during the final erase.
  • unit-mock-flash.c gains a power-cut hook (clean, half or all-but-last-word cuts), write and erase failure countdowns, and erase polarity for inverted flags. New variant unit-update-flash-int-invert covers write-once trailers in internal flash.
  • New tests: power-cut sweeps of update, confirm and rollback at three sizes and from unconfirmed and confirmed originals including a maximum-size original. Also a failed-erase sweep of the rollback, a rollback to version 0, and the SWAP backup direction check.
  • tools/scripts/sim-update-powerfail-resume.sh expected the bug: its last step ("make sure it can double revert") relied on a reset in the fallback leaving v1 in TESTING, so the next boots swapped the unconfirmed v2 back in with no trigger. It now checks that v1 stays booted through the same resets. Encrypted, delta and ALLOW_DOWNGRADE builds, which keep the old decision, keep the old check. On master the new check fails for every other configuration that runs it.

2. Keep the FLAGS_HOME final swap resumable across a power cut (core, FLAGS_HOME with write-once flash)

  • With FLAGS_HOME and NVM_FLASH_WRITEONCE the UPDATE flags live in the BOOT trailer, and wolfBoot_swap_and_final_erase() erased that trailer, FINAL_FLAGS included, before writing TESTING. A power cut there left the new image booting as if confirmed, never rolling back.
  • wolfBoot_flags_home_reset_boot() now clears only the BOOT magic and state and keeps FINAL_FLAGS. After the restore, wolfBoot_flags_home_set_boot() writes the BOOT state and clears the UPDATE flags in one rewrite: TESTING after an update, SUCCESS after a rollback.
  • A torn write of the shared trailer, cut before its last word (the BOOT magic), could also retire the complete copy and lose the BOOT state. nvm_select_fresh_sector() now keeps a copy holding a whole BOOT record over one without the BOOT magic, unless that one holds FINAL_FLAGS, so a torn erase of the old copy cannot hide the resume record.
  • Each rewrite writes the spare sector before erasing the old copy. Other builds compile unchanged; the affected layouts are sim-nvm-writeonce-flags-home*.config and the PSoC 6 QSPI configuration.
  • New variants unit-update-flash-flagshome (whole-sector writes) and unit-update-flash-invert (512-byte chunks) run the suite, including the power-cut sweeps. New tests cover the update trigger cut at every operation, a torn erase that keeps the old BOOT record, and write or erase failures in both rewrites.

3. Add CY8CEVAL-062S2 support and bring the PSoC 6 port to parity

  • Moves the PSoC 6 port to the Infineon mtb-pdl-cat1 PDL; the EXT_FLASH and DUALBANK_SWAP builds did not compile on master. tools/ci/fetch-psoc6-pdl.sh fetches pinned releases into lib/.
  • cypsoc6.config covers the CY8CKIT-062S2-43012 (the default) and the CY8CEVAL-062S2 (PSOC6_BOARD=CY8CEVAL_062S2). The new cypsoc6-qspi.config puts UPDATE in QSPI; dual-bank is make arguments.
  • New: an SCB console, a TCPWM timer for BOOT_BENCHMARK, a test-app that confirms updates, and PSOC6_CM4_APP=1, which boots the verified image on the CM4.
  • The target requires NO_MPU=1 (the ROM flash NMI locked up under the wolfBoot MPU), FLAGS_INVERT=1 (flash erases to 0x00) and NVM_FLASH_WRITEONCE=1 (rewriting a row erases it first).
  • HAL: ProgramRow for erased rows and WriteRow otherwise, skipping unchanged rows (an update went from 38 s to 6 s); write errors return -1; the vector table and the PDL flash/SMIF code run from SRAM for RAM_CODE self-update.
  • Shared: a generic WOLFBOOT_BOOT_REMOTE_CORE hook in do_boot(), and the test-app builds private PDL and string objects, so a CM4 build never leaks into the bootloader.
  • tools/scripts/check-ramcode.py walks the call graph from wolfBoot_self_update through calls, literal pools, register-built constants and function-pointer data. It fails if reachable code is outside RAM or reads the flash image; the entry may only write at the image base. Its --selftest and the RAM_CODE CI jobs run it.
  • The documented QSPI debugger trigger replaces both write-once trailer copies, so it also works after a confirmed update.
  • Host tests unit-psoc6-flash, unit-psoc6-qspi and unit-psoc6-boot build against the real PDL headers with only CMSIS mocked; each of 36 HAL mutations fails a test.

4. Clock the PSoC 6 QSPI interface from the PLL

  • hal_ext_flash_init() fed the SMIF clock CLK_HF2 from CLK_PATH0 divided by 4. wolfBoot never configures that path; it only sets up the PLL on CLK_PATH1.
  • CLK_PATH0 is the FLL, left as the last loader set it. After pyOCD programming it was enabled but unlocked (CLK_FLL_STATUS 0x6), so the SMIF clock switched between the FLL and the 8 MHz IMO. Cy_SMIF_MemRead() then returned CY_SMIF_CMD_FIFO_FULL, a correct update image failed its hash, and SFDP init needed up to four 500 ms retries.
  • CLK_HF2 now comes from the PLL divided by 8, which is 12.5 MHz, the rate the locked FLL gave, so QSPI timing is unchanged. unit-psoc6-qspi checks the source and the divider; reverting either fails the test.
  • Under host CPU load, the QSPI update test failed 3 of 4 runs before and passed every run after, with SFDP succeeding first time.

@aidangarske aidangarske self-assigned this Nov 6, 2026
Copilot AI balanced review requested due to automatic review settings November 6, 2026 03:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Incomplete image-header validation can treat an unknown BOOT version as 0, misclassifying a resumed forward update as a rollback and committing SUCCESS without confirmation.

2 open issues
What changed in this PR

This PR improves wolfBoot’s power-cut recovery and expands the PSoC 6 port with CY8CEVAL-062S2 support, updated drivers, and additional boot configurations.

Changes:

  • Revises rollback detection and shared write-once trailer handling to preserve recovery state.
  • Updates PSoC 6 flash drivers, board configuration, console, timing, and CM4 application handoff.
  • Adds power-cut tests, PSoC 6 HAL tests, RAM-code checks, and CI coverage.
File Description
tools/​unit-tests/​unit-update-flash.c Adds power-cut, rollback, and trailer recovery tests.
tools/​unit-tests/​unit-psoc6-qspi.c Tests QSPI operations and erase geometry.
tools/​unit-tests/​unit-psoc6-flash.c Tests internal flash operations and console output.
tools/​unit-tests/​unit-psoc6-boot.c Tests bank mapping, vectors, timer, and CM4 handoff.
tools/​unit-tests/​unit-mock-flash.c Adds torn-operation and flash-failure simulation.
tools/​unit-tests/​Makefile Registers new flash variants and PSoC 6 tests.
tools/​unit-tests/​cmsis_stub/​core_cm0plus.h Provides host-side CMSIS register and intrinsic mocks.
tools/​scripts/​check-ramcode.py Checks reachable self-update code and data placement.
tools/​config.mk Persists new PSoC 6 configuration options.
tools/​ci/​fetch-psoc6-pdl.sh Fetches pinned Infineon libraries.
test-app/​startup_arm.c Preserves the ROM flash NMI handler on CM0+.
test-app/​Makefile Separates application HAL and PDL objects.
test-app/​ARM-psoc6.ld Expands application SRAM allocation.
test-app/​ARM-psoc6-cm4.ld Defines a separate CM4 application memory layout.
test-app/​app_psoc6.c Reports firmware version and confirms updates.
src/​update_flash.c Revises rollback detection and final-erase recovery.
src/​libwolfboot.c Preserves shared trailer records during rewrites.
src/​boot_arm.c Adds remote-core boot handoff.
include/​image.h Declares shared-trailer state operations.
include/​hal.h Declares the remote-core boot hook.
hal/​psoc6.ld Places flash dependencies and vectors in SRAM.
hal/​psoc6.h Defines board pins and hardware constants.
hal/​psoc6.c Updates flash, QSPI, console, timer, and boot support.
docs/​Targets.md Documents PSoC 6 configuration and update procedures.
config/​examples/​cypsoc6.config Updates the internal-flash configuration and layout.
config/​examples/​cypsoc6-qspi.config Adds an external-QSPI update configuration.
arch.mk Integrates updated PDL libraries and target requirements.
.gitignore Excludes fetched libraries and new test binaries.
.github/​workflows/​test-units.yml Enables PSoC 6 host tests in CI.
.github/​workflows/​test-configs.yml Adds PSoC 6 build variants.
.github/​workflows/​test-build-psoc6.yml Fetches pinned libraries and runs optional RAM-code checks.

Review effort: Balanced

Comment thread tools/scripts/check-ramcode.py Resolved
Comment thread tools/unit-tests/unit-psoc6-qspi.c Resolved
@aidangarske
aidangarske force-pushed the psoc6-eval branch 5 times, most recently from 76851ee to 41aa841 Compare November 6, 2026 21:44
@aidangarske
aidangarske marked this pull request as ready for review November 6, 2026 22:13

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #975

Scan targets checked: wolfboot-src, wolfboot-bugs
Not reviewed (no scan target covers them): .github/workflows/test-build-psoc6.yml, .github/workflows/test-configs.yml, .github/workflows/test-units.yml, .gitignore, arch.mk, config/examples/cypsoc6-qspi.config, config/examples/cypsoc6.config, config/examples/stm32c0-rsa2048.config, docs/Targets.md, hal/psoc6.h, hal/psoc6.ld, test-app/ARM-psoc6-cm4.ld, test-app/ARM-psoc6.ld, test-app/Makefile, tools/ci/fetch-psoc6-pdl.sh, tools/config.mk, tools/scripts/check-ramcode.py, tools/scripts/sim-update-powerfail-resume.sh, tools/unit-tests/Makefile, tools/unit-tests/cmsis_stub/core_cm0plus.h
Coverage: 10 of 13 in-scope changed file(s) opened by the reviewer; not opened: tools/unit-tests/unit-psoc6-boot.c, tools/unit-tests/unit-psoc6-flash.c, tools/unit-tests/unit-psoc6-qspi.c

Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)

This review was generated automatically by Fenrir. Reported findings require changes before merge.

Review tier: Max

Comment thread tools/unit-tests/unit-update-flash.c Resolved

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #975

Scan targets checked: wolfboot-src, wolfboot-bugs
Not reviewed (no scan target covers them): .github/workflows/test-build-psoc6.yml, .github/workflows/test-configs.yml, .github/workflows/test-units.yml, .gitignore, arch.mk, config/examples/cypsoc6-qspi.config, config/examples/cypsoc6.config, config/examples/stm32c0-rsa2048.config, docs/Targets.md, hal/psoc6.h, hal/psoc6.ld, test-app/ARM-psoc6-cm4.ld, test-app/ARM-psoc6.ld, test-app/Makefile, tools/ci/fetch-psoc6-pdl.sh, tools/config.mk, tools/scripts/check-ramcode.py, tools/scripts/sim-update-powerfail-resume.sh, tools/unit-tests/Makefile, tools/unit-tests/cmsis_stub/core_cm0plus.h
Coverage: 2 of 2 in-scope changed file(s) opened by the reviewer

Fenrir result: Approved ✅

No new issues found in the changed files.

Advisory only — this automated result does not count as a GitHub approval.

Review tier: Lite

@wolfSSL-Fenrir-bot
wolfSSL-Fenrir-bot dismissed their stale review November 6, 2026 22:48

Fenrir's latest completed scan found no issues; clearing the prior automated change request.

@dgarske dgarske left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

The PR works on hardware. Every documented flow passed, and the core power-cut fix held under 47 hardware resets swept across an update and a rollback. No correctness bug found. The gaps are in the documentation and the test app, plus a few low-severity code notes.

Hardware results

Test Result
Docs flow, internal flash: fetch PDL, build, flash, boot v1 Pass, output matches docs
Docs update to v2, reset, second update to v3 (documented trigger after a confirmed update) Pass
cypsoc6-qspi.config: v1 internal, v2 staged in QSPI, documented FLAGS_HOME trigger Pass
PSOC6_CM4_APP=1 PSOC6_CRYPTO=1 BOOT_BENCHMARK=1: boot v1 on CM4, update, CM4 calls wolfBoot_success() Pass, ECDSA verify 180-192 ms on the crypto block
DUALBANK_SWAP=1: v2 written at 0x10120000, no trigger Pass, bank swap and confirm
RAM_CODE=1 bootloader self-update Pass once both bootloaders set WOLFBOOT_VERSION (see gap 1)
Reset swept across the rollback (26 cuts, 40 ms steps) 26/26: always settles on v1, unconfirmed image never boots again
Reset swept across the update (21 cuts, 80 ms steps) 21/21: settles on v1 after the unconfirmed image fails to confirm

Host results

  • Unit suites touched by the PR all pass: unit-psoc6-flash, -qspi, -boot, unit-update-flash and its int-invert, invert, flagshome, delta, enc, custom-trailer, hwswap and self-update variants.
  • check-ramcode.py --selftest and the CI invocation on the RAM_CODE build pass (59 functions reachable, all in RAM).
  • Build matrix: all documented options build. Removing NO_MPU, FLAGS_INVERT or NVM_FLASH_WRITEONCE fails with a clear message, and PSOC6_CM4_APP=1 without the 1 KB header fails with a clear #error.

Gaps found acting as a new user

  1. Self-update silently discards the image. With the config default WOLFBOOT_VERSION=0 no define is passed, so the bootloader takes LIBWOLFBOOT_VERSION_HEX (0x020A0000). A self-update signed with any small version is erased with no console message and the old bootloader keeps running. The docs line for RAM_CODE=1 needs the full recipe: build the running bootloader with WOLFBOOT_VERSION=N, build the new one with a higher value, sign with that version, write to UPDATE, set the trigger. This is upstream behavior, not a PR regression, but a log line on that erase would save the next user an hour.
  2. The documented rollback cannot be exercised with the shipped test app. It confirms every version except 1, so "if version 2 does not call wolfBoot_success(), the next reset rolls back" needs a code edit to try. A make knob or a version convention (for example, versions at or above some value never confirm) would make rollback testable.
  3. A missing PDL fails with a raw compiler error. cy_device_headers.h: No such file or directory, with no pointer to tools/ci/fetch-psoc6-pdl.sh. A wildcard check in arch.mk next to the three existing $(error ...) lines would fix it.
  4. Loading commands only show the internal layout. A QSPI user must work out from the layout paragraph that v1 goes to 0x10040000.
  5. No migration note. Partition addresses change, the default signature goes from ED25519 to ECC256, CYPRESS_TARGET_LIB becomes CYPRESS_TEMPLATE_LIB, and PSOC6_CRYPTO was documented as on by default and is now off. A device in the field on the old layout cannot take an update from the new one.
  6. Only pyOCD is documented. The OpenOCD/ModusToolbox instructions and the one-step factory.bin flash were removed. Most Infineon users have ModusToolbox, so one line for its OpenOCD or Programmer would help.
  7. Only the CY8CEVAL-062S2 is listed as tested, yet the config default is the CY8CKIT-062S2-43012. Worth asking whether the default board was run on hardware.
  8. The QSPI trigger wording is hard to follow. "8 bytes below the BOOT flags" plus an inline python one-liner works, but a sentence on what the last 13 bytes are would help.
  9. Harness note, not a bug. pyocd flash resets and runs the target when it finishes, so the trigger write starts the update immediately. Pressing reset right after it interrupts the first boot of the new image, which wolfBoot correctly treats as a failed confirm and rolls back. The docs could say "wait for the confirm line before resetting".

Code review notes (no high-severity issue)

The earlier Copilot and Fenrir comments are all addressed at 650a380: register moves are tracked in check-ramcode.py, the SMIF clock comes from CLK_PATH1/8, and the short-read test now reaches wolfBoot_swap_backup_version.

  1. Equal versions decide differently on a resume. src/update_flash.c:664 returns rollback for ver == cur on a first run but not on a resume. A cut in the final erase of an equal-version rollback would then write TESTING and ping-pong again. Equal versions are refused by a normal update, so this needs debugger staging or ALLOW_DOWNGRADE. Low.
  2. ALLOW_DOWNGRADE and DELTA_UPDATES keep the old behavior. The PR states that scope, but the description's "in every layout" overstates it. Low, wording only.
  3. One trailer read in the delta branch runs with flash locked. src/update_flash.c:1758 after trailer_read_lock() at :1685. Pre-existing. Low.
  4. "Other builds compile unchanged" is not quite true. The new nvm_select_fresh_sector preference (src/libwolfboot.c:421-438) is also compiled for FLAGS_HOME && NVM_FLASH_WRITEONCE builds without a shared trailer, for example the enc flagshome unit variant. No failure found. Low.
  5. from_swap assumes the header fits in one sector (src/update_flash.c:609-627). Only matters for very small sectors with large headers. Low.
  6. The test-app still rebuilds hal/psoc6.o with CM4 flags. An incremental top-level make after it fails to link instead of producing a bad image. Pre-existing pattern. Low.

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.

4 participants