A pool ending exactly at 0x100000000 (e.g. 0xC0000000 + 0x40000000,
the classic top-half 32-bit MMIO layout) was a working configuration:
the old base + length - 1 initialization wrapped to 0xFFFFFFFF in
32-bit arithmetic. The overflow guard from the exclusive-limit fix
rejected such pools with base + length > 0xFFFFFFFF, aborting
enumeration - and the FSP caller discards the return value, so the
platform would boot with no PCI BARs programmed.
The limit fields cannot hold the exclusive end 0x100000000 while
32-bit, so widen mem_limit and mem_pf_limit (and the limit
parameters of pci_enum_next_aligned32 and pci_align_check_up, plus
the local in pci_program_bar) to 64-bit, and reject only pools whose
end is above the 32-bit space. The initialization now casts to
64-bit before the addition so the sum cannot wrap.
The T10xx PCIe setup initializes the same struct with the old
inclusive base + length - 1 form; align it to the exclusive
semantics the allocator enforces, or the last byte of the configured
pool is unusable.
New unit-pci-4gib build of the existing test file with the MMIO
pool [0xC0000000, 0x100000000): pci_enum_do() must accept the pool
and map a 1 MB BAR at the pool base. Fails on the old guard.
NVMCMD_ERASE (0x02) is the NVMCTRL row erase: one command erases a
256-byte row (4 pages). The erase loop advanced the address by one
page per iteration, so it issued four row-erase commands against the
same row - the second through fourth with a non-row-aligned address -
quadrupling erase time and wear on every flash erase.
Stride the loop by FLASH_ROW_SIZE (4 * FLASH_PAGESIZE). The row
containing a sub-row request is erased once, as the command
granularity requires.
Unit test updated to row semantics: a sub-row request ends on the
containing row, a two-row range advances to the second row, and an
exact row erases once with no extra row. All three fail on the
page-stride loop.
hal_flash_erase() used the length decrement as the unbraced body of the
NVMREADY wait loop. With the peripheral idle (NVMREADY set) the wait
body never ran, the length never shrank, and the outer loop re-erased
the first page of the range forever; whatever the wait duration, the
number of decrements tracked wait-loop iterations instead of completed
erases, and the address was never advanced, so later pages of the
requested range were never erased.
Brace the ready wait, and after a completed erase advance the address
by FLASH_PAGESIZE and decrement the length once, as the sibling
P1021 multi-block erase loop does (F-11034).
unit-samr21-erase-advance extracts the real function and register
macros and runs it against a host NVMCTRL window with NVMREADY preset
(an idle peripheral): a 128-byte range must end with page 0x1040
programmed, a 256-byte range with page 0xC0, and a single 64-byte
erase must complete. Pre-fix all three cases hang in the re-erase loop
and fail on the tcase timeout.
Verification:
- Built: arm-none-eabi-gcc -fsyntax-only -Wall -Wextra hal/samr21.c:
clean.
- Tested: unit-samr21-erase-advance 3/3; pre-fix all three timed out
(10 s tcase limit).
- Pitfalls: single-page erases and page-aligned ranges behave as
before; a non-page-multiple len erases the final partial page's
page, unchanged from the pre-existing decrement semantics.
- Style: cstyle-check.sh FMT diff on hal/samr21.c byte-identical to
the pre-change file; the new test trips only the uncrustify
START_TEST brace class the sibling unit tests trip.
- Unverified: no SAMR21 board execution.
- Message: F-11036: prefix, no co-author trailers.
hal_flash_write() in hal/hifive1.c selected the page path and clamped
the partial-page length from the original total len instead of the
bytes still remaining (len - j). A page-aligned multi-page write that
ended in a partial page therefore took the full-page branch on the
last iteration: it read past the end of the caller's buffer and
programmed a full 256-byte page where only the remaining bytes were
requested, clobbering flash past the update range.
Compute remaining = len - j at the top of the loop and use it for both
the branch test and the rel_len clamp; j still advances only by the
bytes actually consumed (256 on the full-page path, rel_len on the
partial path).
unit-hifive1-flash-write runs the real extracted function against a
mock fespi model (FLASH_BASE points at a flash image buffer,
fespi_write_address/fespi_sw_tx program into it, and the RMW path
reads the image back through FLASH_BASE as on hardware). The
regression case is a 356-byte aligned write: the last page's tail must
stay erased, which fails pre-fix (the over-read bytes are programmed
instead).
Verification:
- Built: riscv-none-elf-gcc 15.2 -fsyntax-only -Wall with ARCH_RISCV:
clean.
- Tested: unit-hifive1-flash-write 3/3; pre-fix the 356-byte case
wrote non-erased bytes past offset 356 of the flash image.
Unaligned single-page RMW and exact-full-page cases unchanged.
- Pitfalls: relative (sub-FLASH_BASE) addresses are accepted as-is by
the function, which is what the test passes so the 32-bit address
parameter never carries a 64-bit host pointer.
- Style: cstyle-check.sh flag count on hal/hifive1.c unchanged from
the pre-change file; the new test trips only the uncrustify
pointer-alignment class the sibling unit tests trip.
- Message: F-11035: prefix, no co-author trailers.
- Unverified: no HiFive1 board execution.
ext_flash_erase() decremented the remaining length each iteration but
never advanced the address, so the derived page (address / page_size) was identical
every pass: the first block of the range was re-erased for the whole
loop and every later block was left intact (the caller then programmed
an update image into un-erased NAND). The sibling ext_flash_write()
loop already advanced address/pos/data and was not affected.
Advance address by block_size after each successful erase.
unit-p1021-erase-advance runs the real extracted function against
mocked ELBC register access and records the page programmed per erase
command: two blocks must hit page 0 then page 32 (16 KiB block / 512
page), and a failing command must stop the loop after one attempt.
Verification:
- Built: powerpc-linux-gnu-gcc -fsyntax-only -Wall with
TARGET_nxp_p1021: clean.
- Tested: unit-p1021-erase-advance 2/2; pre-fix the second erase
re-targeted page 0.
- Pitfalls: single-block erases (len <= block_size) behave exactly as
before; the error path is unchanged (break on hal_flash_command
failure).
- Style: cstyle-check.sh flag count on hal/nxp_p1021.c unchanged from
the pre-change file; the new test trips only the uncrustify
pointer-alignment class the sibling unit tests trip.
- Message: F-11034: prefix, no co-author trailers.
- Unverified: no P1021 board execution.
hal_flash_status_wait() returns -1 when the NOR does not settle its
status bits within the poll budget, but hal_flash_write() and
hal_flash_erase() discarded the result and returned 0 unconditionally:
a stuck program or erase reported success, and the update flow
continued as if the flash held the new image.
Capture every wait result and return it to the caller on the first
failure. No state-restore command is needed: this driver works through
the memory-mapped QPI window, where reads are plain loads and the
controller issues the read command per access, so there is no device
command state to recover.
unit-t10xx-flash-status runs the real extracted functions against a
mock QPI status model (offset 0 reports the DQ status byte: toggling
while busy, 0x44 after a program, 0x4C after an erase). A stuck
device burns the full 200 ms / 1.1 s poll budget with a no-op udelay,
so the timeout path runs in milliseconds.
Verification:
- Built: powerpc-linux-gnu-gcc -fsyntax-only -Wall with
TARGET_nxp_t1024: clean.
- Tested: unit-t10xx-flash-status 4/4; pre-fix both timeout tests got
ret == 0 from a stuck device, now -1. Success paths (write lands in
the model, erase completes) unchanged.
- Pitfalls: the first failing page/sector now aborts the rest of the
operation, which is the desired behavior (the caller aborts the
update); no callers depended on the unconditional 0.
- Style: cstyle-check.sh flag count on hal/nxp_t10xx.c unchanged from
the pre-change file; the new test trips only the uncrustify
pointer-alignment class the sibling unit tests trip.
- Message: F-11033: prefix, no co-author trailers.
- Unverified: no T10xx board execution.
wolfBoot_start() in hal/library.c ended its exit: path with an
unconditional 'return 0;', so a rejected image (bad header, hash or
signature) still made the test-lib process exit 0. main() propagates
wolfBoot_start()'s return value, so the failure was only visible in
the printed "Failure" message, which the test-library workflow had to
grep for (TODO referencing PR #625).
Return ret, which carries the wolfBoot_verify_*() error on every path
that reaches exit: with a failure. The success path never returns:
do_boot() jumps to the firmware.
The test-library workflow drops the status-rewriting workaround and
asserts the non-zero exit code directly, keeping the "Failure"
message check as a diagnostic.
Verification:
- Built: make test-lib (host, library.config, ED25519/SHA256).
- Tested: local repro of the workflow flow: corrupt the last byte of
a signed image; before the fix the process exited 0 while printing
"Failure -1", after the fix it exits 255; a valid image still
exits 0 with "Firmware Valid".
- Pitfalls: 'return ret' only changes the error paths; do_boot() does
not return on success.
- Style: the cstyle-check.sh FMT flag on hal/library.c is present on
the pre-change file as well (not introduced here).
- Message: F-9749: prefix, no co-author trailers.
The double-word fast path in hal_flash_write() on STM32G4, STM32C0
and STM32G0 was selected on 'len - i > 3' but programs an 8-byte
unit, so an aligned 4-7 byte tail read up to 4 bytes past the
caller's buffer and programmed those bytes into flash.
Require len - i >= 8 before taking the fast path. Shorter tails
fall through to the existing RMW branch, which rewrites the unit
with the out-of-range bytes read back from flash, so nothing past
len is read or programmed.
Add unit-stm32g4-write (same harness as the STM32L5/STM32U5 twins),
which fails on the 60-byte tail before the fix.
The page walk recomputed the block from the address each iteration,
losing the running cursor.
Carry the cursor again, checking for a bad block only when it rolls
over. unit-sama5d3-ext-read now emulates bad blocks.
The fman and esdhc fixup guards tested 'off != !FDT_ERR_NOTFOUND',
which is 'off != 0'. fdt_node_offset_by_compatible() returns a negative
offset when the node is absent, so the guard passed and the fixups ran
with a negative offset (fdt_setprop() rejects it, the fixups fail
silently with misleading log lines); conversely a node at struct
offset 0 (the root node) matched but was skipped. Every other guard in
the function already used '-FDT_ERR_NOTFOUND'.
Use the correct guard in both blocks and fix the esdhc status fixup's
log label, a copy-paste from the cpu fixup block.
unit-t10xx-dts-memac gains four cases driving the real
hal_dts_fixup(): a root node compatible with fsl,fman gets the clock
fixup (failed before the guard fix), child fman and esdhc nodes get
their fixups, and absent nodes are skipped cleanly.
ext_flash_write() and ext_flash_erase() discarded their arguments and
returned 0, so any update flow that reaches this HAL was told an
erase/program succeeded while the NAND contents were left untouched.
NAND page program and block erase are not implemented for this target;
report failure so callers that branch on the result can detect it
instead of continuing with a write that never happened. The read-only
boot path is unaffected.
ext_flash_read() ignored the intra-page offset of the start address (a
partial read returned bytes from the head of the page), copied sub-page
reads in 32-bit words (dropping a sub-word tail), and wrote a full NAND
page into the caller's buffer when a multi-page read ended mid-page
(overrunning it). The integrity check hashes the image in 64-byte blocks
from fw_base + offset, so any read crossing a page mid-block returned the
wrong bytes.
Walk the read page by page from the exact address: full aligned pages go
straight into the caller's buffer; partial first/last pages are staged
through the page buffer and copied from the column offset.
Proven by unit-sama5d3-ext-read, which extracts the real function and
runs it against an emulated device: unaligned starts, 1-3 byte lengths,
the 64-byte integrity-hash block pattern, page/block boundaries, and
multi-page reads with a short tail. Pre-fix 6 of the 8 checks failed.
The test file headers ran to 20-40 lines of prose before the licence,
restating the whole finding and the harness design. Cut them to a short
paragraph on what broke and a short one on how the test reaches the
code. Same for the long inline blocks in the HAL and libwolfboot
changes.
Comments only; no functional change.
DEBUG_UART=1 did not compile: uart_init() declared an unused local and
-Werror rejected it, so the target's console output could never be
enabled. Drop the local.
The documented key file is 44 bytes, but AES256-CTR wants a 32-byte key
plus a 16-byte IV; sign fails with "Error reading IV" as written. Use
48 bytes, matching the key the HAL used to carry.
Drop the internal report numbers from source, test and Makefile
comments: they mean nothing outside the tracker and do not belong in
the tree. Condense the long inline comment blocks the review flagged,
and note in the raspi3 encrypted example and its docs that CI builds
but does not boot it, so the end-to-end path has no automated
coverage.
Comments and docs only; no functional change.
The wait loop increments timeout past FLASH_TIMEOUT_TRIES on its way
out, so "timeout == FLASH_TIMEOUT_TRIES" never matched and a hung
command returned success. Test the completion flag instead.
That made the failure path reachable for the first time, and it did not
exit: the break left the inner block loop only, so the outer loop
respun the same failing command forever with pos unchanged.
NXP's qoriq-fman3 dtsi numbers the 10G memacs 0x8/0x9, and phydevs
holds the 10G port at FM1_10GEC1. Bounding the raw cell-index against
the table traded an out-of-bounds read for a silently dropped MAC
fixup on exactly the DTBs that motivated the guard. Translate instead,
and skip only indexes with no slot.
The NOR-extent test ran after the microcode table had already been
dereferenced, so a container near the top of the bank read past the
window the guard exists to enforce -- a machine check on hardware. Bound
the fixed part before the first dereference and the table before the
walk. The accept/reject outcome is unchanged; the out-of-bank read is
what goes away.
Use the file's own QE_MAX_RISC for the count bound instead of a
hand-written 2, matching the t10xx and p1021 siblings; the shared length
formula already handles count > 1.
open_kernel_image() left *sz set on the read-failure path though it
documents both outputs as 0 on failure. The p1021 FCM helpers used
32-bit accesses from flash_idx, which starts at the page column, so an
odd column made every access misaligned -- an alignment interrupt on
the guarded eLBC window on e500.
XSPI_IPCMDDONE only says the controller finished driving the sequence
on the bus. The device keeps WIP set for the ~ms the cycle takes and
ignores Write Enable while it is set, so the per-page WREN this PR
added was dropped for pages 2..N and their Page Programs with it.
The erase loops polled the same bit with inverted polarity: they
exited as soon as WIP read *set*, i.e. once the erase had started, and
would spin forever if they missed the busy window. FLASH_READY_MSK is
renamed FLASH_SR_WIP_MSK so the sense is not guessable, and both paths
now share xspi_wait_ready().
The test device models WIP, so a missing wait shows up as a rejected
Write Enable.
The block-crossing short write issued two programming commands
back-to-back. The F021 FSM rejects a command while busy, and the
read-modify-write that stages the next block reads the array while the
first program is in flight, so it stages undefined data and programs
it. The multi-block branch already waits after every command.
The test FSM now stays busy for a few polls and holds the programmed
data back until it drains, so the missing wait is observable.
The bad-block check reads flash_buf[bad_marker], which sits in the
spare region. A BC != 0 transfer loads only the requested main-area
bytes, so the marker byte is leftover FCM RAM: a stale non-0xFF value
makes ext_flash_read() declare the block bad, skip to the next erase
block and return data from the wrong address while still returning
len. BC = 0 is also the only setting that checks ECC.
Latent until the FBCR shift fix made BC take effect. read_size still
bounds the copy out of the buffer, so the F-7976 sizing fix stands.
P1021RM 12.3.30 numbers BC as bits 20-31 MSB-first, i.e. unshifted --
same convention every FPAR field macro in this file already follows
(FPAR_SP_PI at RM 17-21 is << 10, CI at RM 23-31 is unshifted). The
<< 20 put the count in reserved bits 0-19 and left BC at 0, which the
RM defines as "whole page + spare, FPAR[MS]/[CI] treated as zero": a
partial program filled the FCM buffer at offset col but then wrote the
entire page back, stale bytes included.
The test emulator decoded FBCR the same wrong way, so it could not see
this; it now decodes BC as the hardware does, and fails on the shifted
macro while still catching the original F-7976 bug.
The partial-tail handling skipped the words that hold no requested
byte. Neither part can do that: U5 only starts the 128-bit program on
the fourth word (a partial quad-word leaves FLASH_SR_WDW set and
hal_flash_wait_complete() never returns), and L5 has no 32-bit program
mode, so splitting the double word into two PG cycles programs
nothing and faults the second on alignment.
Build the full unit first, taking bytes outside [i, len) from the
current flash content as hal/stm32h5.c does, then store every word.
That drops the over-read F-9763/F-9764 targeted without ever issuing
a partial unit. L5 TZ claim widened to the 8-byte boundary to match.
hal_flash_write() looped while (i < len) but programmed whole 16-byte
units: each iteration read all four 32-bit source words unconditionally
and wrote the full quadword to flash, so a write whose length is not a
multiple of 16 read up to 12 bytes past the caller's buffer - which
the NSC update path validates for exactly len - and wrote those bytes
to flash.
Program the unit word by word: each word is written only when at least
one of its bytes is requested, and a partial final word is padded with
the erased value (0xFF) in its upper bytes so no source bytes past len
are read. The single PG/wait/clear sequence per unit is unchanged.
Test: tools/unit-tests/unit-stm32u5-write.c is the 16-byte twin of the
STM32L5 test (F-9763): it extracts the real hal_flash_write() and its
wait/clear helpers, maps the destination flash at a 32-bit host address
(the parameter is uint32_t on this 32-bit target) pre-filled with stale
data, and runs short writes; pre-fix the bytes past len landed in the
destination flash in all four partial-unit cases.
hal_flash_write() looped while (i < len) but programmed whole 8-byte
units: each iteration read both 32-bit source words (src[i>>2] and
src[(i>>2)+1]) unconditionally, so a write whose length is not a
multiple of 8 read up to 4 bytes past the caller's buffer - which the
NSC update path validates for exactly len - and wrote those bytes to
flash.
Program the unit word by word: the first word is always programmed
(it contains byte i), the second only when at least one of its bytes
is requested, and a partial final word is padded with the erased
value (0xFF) in its upper bytes so no source bytes past len are read.
The TZ non-secure claim is extended to the flash span the program
actually writes (len rounded up to the word boundary).
Test: tools/unit-tests/unit-stm32l5-write.c extracts the real
hal_flash_write() and its wait/clear helpers, maps the destination
flash at a 32-bit host address (the parameter is uint32_t on this
32-bit target) pre-filled with stale data, and runs short writes;
pre-fix the bytes past len (including un-padded upper word bytes)
landed in the destination flash.
hal_fman_init() checked only the NOR window guard and the QEF magic,
then copied fw->microcode[i].count words from fw + code_offset into
FMan instruction RAM and armed the engine. No version, count, length
self-consistency, or code-offset validation: a count of zero was
accepted as a successful upload of nothing, and a structurally
consistent blob with an out-of-image offset made the loader copy
arbitrary memory into FMan IRAM.
Add the same structural checks the T10xx qe_check_firmware() path
applies: version, count (1..2, the FMan RISC count), self-consistent
length, every code range inside the declared image, and the whole
image inside the remaining NOR bank (offset arithmetic that cannot
overflow uintptr_t, matching the existing window guard). All 64-bit,
fail closed - FMan stays unconfigured on any mismatch.
Test: tools/unit-tests/unit-t2080-fman-loader.c extracts the real
structures and hal_fman_init() and runs them against a test-owned NOR
image (FMAN slot at a nonzero bank offset, guard slack after the bank)
and an emulated FMan IRAM; pre-fix the loader accepted all four
invalid-container cases.
open_kernel_image() wrote the allocated address into *_addr before the
read, and returned from a failed Read() or undersized-image check
without clearing it; efi_main() ignored both return values and treated
any nonzero kernel_addr/update_addr as a loaded image. A kernel.img
shorter than the header (or a failing Read) with no update.img left a
nonzero, rejected buffer that wolfBoot_start() would boot from. The
file handle was also never closed and the allocated pages were leaked
on the failure paths.
Mirror the AArch64 sibling: initialize *_addr/*sz to zero, publish the
address only on full success, FreePages on read/size failure, close
the file on every path, reject non-fitting file sizes, return -1
instead of a truncated EFI_STATUS, and have efi_main() acknowledge the
reliable zero-on-failure contract.
Test: the existing F-9734 harness in
tools/unit-tests/unit-efi-x86-open-image.c now tracks FreePages/Close
and injects Read and allocation failures; pre-fix the rejected loads
published nonzero addresses, returned truncated EFI_STATUS values,
and never closed the file.
cacheSector() and programBytesToErasedFlash() filled uint32_t page
buffers with the byte-valued FLASH_BYTE_ERASED, so in the default
(non-inverted) build each 32-bit word became 0x000000FF instead of
0xFFFFFFFF. A one-byte write into an erased page then programmed
zeroes into the unrelated bytes of the same words, and the sector
cache carried the corrupted pattern into every read-modify-write.
Assign FLASH_WORD_ERASED to each word, which tracks the flag polarity
(0xFFFFFFFF / 0x00000000UL). The bytewise fills elsewhere in the file
were already correct.
Test: tools/unit-tests/unit-aurix-erased-fill.c extracts the real
functions and runs them against an emulated tc3_flash_* API; it is
built for both flag polarities. Pre-fix the default-polarity build
shows the 0x00 high bytes in all three fill cases (the inverted build
passes, masking the bug as the finding notes).
qe_check_firmware() validated the QE/FMAN microcode header for
self-consistency (magic, version, count, length == computed size,
optional CRC) but never bounded the per-microcode code_offset against
the declared image length. qe_upload_firmware() reads code_offset +
4*count bytes from the firmware start and copies them into QE IRAM
(and programs the microcode's trap table), so a structurally valid
blob with an out-of-image offset made wolfBoot copy arbitrary memory
into the engine ahead of image authentication.
Reject the firmware when any microcode's words extend past the
declared end (64-bit sum, no wrap). This protects both the QE and the
FMAN firmware paths, which share the check.
Test: tools/unit-tests/unit-t10xx-qe-firmware.c extracts the real
qe_* structures and qe_check_firmware() from hal/nxp_t10xx.c and
builds the blobs in memory; pre-fix both out-of-image variants were
accepted (one of them segfaulting the test harness while building the
blob's claimed code).
ext_flash_write() chunked the request into page-indexed FLASH_PAGE_SIZE
transfers (page 0, page 1, ...) without accounting for the start
address's offset inside the physical page. A write starting mid-page
sent a full-page Page Program across the boundary; NOR wraps the write
pointer at the page start, so the excess bytes clobbered the start of
the page.
Rewrite the loop to advance from the current address and cap each
transfer at the end of the physical page holding it, mirroring the
LS1028A (F-7978) and Zynq (F-7979) fixes. Failure-stop semantics are
unchanged.
Test: tools/unit-tests/unit-versal-ext-write.c extracts the real
QspiDev_t and ext_flash_write() from hal/versal.c (the qspi_* helpers
are file-static, so the test emulates them) and programs an emulated
NOR that models the wrap, so the pre-fix corruption is observable.