diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec41f8f..a303a7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,3 +61,15 @@ jobs: cd test idf.py set-target esp32s3 idf.py build + + - name: Assert K_ISR_SAFE helpers are IRAM-resident (issue #53) + # A K_ISR_SAFE (IRAM_ATTR) function that reaches a flash-resident + # helper cache-faults when called from an IRAM ISR during a flash + # op. The host suite cannot see this; verify symbol placement on + # the target ELF. OBJDUMP is set explicitly so the check does not + # depend on `file` being present in the image. + shell: bash + run: | + . /opt/esp/idf/export.sh + OBJDUMP=xtensa-esp32s3-elf-objdump \ + tools/check_iram_symbols.sh test/build/boreas_test.elf diff --git a/components/zkernel/src/k_event.c b/components/zkernel/src/k_event.c index ca16c2f..b3aa932 100644 --- a/components/zkernel/src/k_event.c +++ b/components/zkernel/src/k_event.c @@ -29,7 +29,13 @@ struct z_event_waiter { bool woken; /* a post/set targeted this waiter */ }; -static uint32_t z_event_match(uint32_t current, uint32_t mask, bool all) +/* K_ISR_SAFE (IRAM): called on both ISR-safe paths -- the post family + * (z_event_post_internal) and the wait family's fast path + * (z_event_wait_internal). A flash-resident helper here would fault with + * a cache-access error when reached from an IRAM ISR during a concurrent + * flash op (the z_sem_pop_waiter class, issue #53). Pure arithmetic, no + * FreeRTOS calls -- safe in IRAM. */ +static uint32_t K_ISR_SAFE z_event_match(uint32_t current, uint32_t mask, bool all) { uint32_t hit = current & mask; diff --git a/components/zkernel/src/k_sem.c b/components/zkernel/src/k_sem.c index 7f6847f..666add9 100644 --- a/components/zkernel/src/k_sem.c +++ b/components/zkernel/src/k_sem.c @@ -44,8 +44,14 @@ int k_sem_init(struct k_sem *sem, unsigned int initial_count, unsigned int limit /* Pop the wake target: highest cached priority, FIFO among equals * (upstream wakes the highest-priority waiter). Caller holds the lock. - * Plain code over the caller-owned list -- no FreeRTOS calls. */ -static struct z_sem_waiter *z_sem_pop_waiter(struct k_sem *sem) + * Plain code over the caller-owned list -- no FreeRTOS calls. + * + * K_ISR_SAFE (IRAM): on k_sem_give's hot path, which is ISR-safe and may + * run while the flash cache is disabled. A flash-resident helper here + * would fault with a cache-access error when the give comes from an IRAM + * ISR during a concurrent flash op (issue #53). k_sem_reset is + * flash-resident, but flash code calling an IRAM helper is fine. */ +static K_ISR_SAFE struct z_sem_waiter *z_sem_pop_waiter(struct k_sem *sem) { struct z_sem_waiter *best = NULL; sys_dnode_t *n; diff --git a/tools/check_iram_symbols.sh b/tools/check_iram_symbols.sh new file mode 100755 index 0000000..b56a226 --- /dev/null +++ b/tools/check_iram_symbols.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 Intercreate +# +# Assert that K_ISR_SAFE helpers land in IRAM (.iram0.text), not in a +# flash-mapped text section. A K_ISR_SAFE (IRAM_ATTR) function that calls +# a flash-resident helper faults with a cache-access error when reached +# from an IRAM ISR during a flash-cache-disabled window (issue #53). +# +# The host (linux) build cannot observe this -- it is a target-only +# memory-placement property -- so this guard runs against a real target +# ELF. Section names (.iram0.text vs .flash.text/.text) are consistent +# across Xtensa and RISC-V targets, so the check is ISA-agnostic. +# +# The symbol set is DERIVED from source: every function carrying the +# K_ISR_SAFE attribute in components/*/src/*.c must be IRAM-resident, so a +# newly-added ISR-safe helper is covered automatically with no edit here. +# A symbol the compiler inlined into its IRAM caller has no out-of-line +# symbol and is reported as "skip" (safe). Override the derived set with +# IRAM_SYMBOLS="a b c" if you ever need to. +# +# Usage: tools/check_iram_symbols.sh [src-root] +# OBJDUMP=-objdump override toolchain autodetect +# IRAM_SYMBOLS="sym1 sym2" override the derived symbol set + +set -euo pipefail + +ELF="${1:?usage: check_iram_symbols.sh [src-root]}" +SRC_ROOT="${2:-$(cd "$(dirname "$0")/.." && pwd)}" + +# Derive the K_ISR_SAFE function set from source. A definition line +# carries the attribute and opens a parameter list; the function name is +# the identifier just before '('. Drop comment lines and the macro +# #define (the only other places the token appears). +derive_symbols() { + grep -hE 'K_ISR_SAFE' "$SRC_ROOT"/components/*/src/*.c | + grep -vE '^[[:space:]]*([*]|/[*]|//|#)' | + grep -E '\(' | + sed -E 's/.*[^A-Za-z0-9_]([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*\(.*/\1/' | + sort -u +} + +SYMBOLS="${IRAM_SYMBOLS:-$(derive_symbols)}" +if [ -z "${SYMBOLS//[[:space:]]/}" ]; then + echo "error: no K_ISR_SAFE symbols found under $SRC_ROOT/components" >&2 + exit 2 +fi + +# Pick the toolchain objdump if not supplied. +if [ -z "${OBJDUMP:-}" ]; then + case "$(file -b "$ELF")" in + *RISC-V*) OBJDUMP=riscv32-esp-elf-objdump ;; + *Tensilica* | *Xtensa*) OBJDUMP="xtensa-${IDF_TARGET:-esp32s3}-elf-objdump" ;; + *) + echo "error: cannot determine toolchain for $ELF (set OBJDUMP=)" >&2 + exit 2 + ;; + esac +fi +command -v "$OBJDUMP" >/dev/null || { + echo "error: $OBJDUMP not on PATH (run . \$IDF_PATH/export.sh)" >&2 + exit 2 +} + +# One symbol-table dump, reused per symbol. +SYMTAB="$("$OBJDUMP" -t "$ELF")" + +fail=0 +checked=0 +while IFS= read -r sym; do + [ -n "$sym" ] || continue + checked=$((checked + 1)) + + # A symbol-table entry's line names exactly one section. + line="$(printf '%s\n' "$SYMTAB" | grep -E "[[:space:]]${sym}\$" || true)" + + if [ -z "$line" ]; then + # Inlined into its IRAM caller (no out-of-line symbol) or not + # linked into this image -- both safe. Report, don't fail. + echo "skip $sym (no out-of-line symbol -- inlined or not linked)" + elif [[ "$line" == *".iram0.text"* ]]; then + echo "ok $sym -> .iram0.text" + else + sec="$(printf '%s\n' "$line" | awk '{print $(NF-1)}')" + echo "FAIL $sym -> ${sec} (expected .iram0.text; flash-resident would cache-fault from an IRAM ISR)" + fail=1 + fi +done <<<"$SYMBOLS" + +echo "---" +echo "checked $checked K_ISR_SAFE symbol(s) in $(basename "$ELF")" +exit "$fail"