Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 7 additions & 1 deletion components/zkernel/src/k_event.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
10 changes: 8 additions & 2 deletions components/zkernel/src/k_sem.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
93 changes: 93 additions & 0 deletions tools/check_iram_symbols.sh
Original file line number Diff line number Diff line change
@@ -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 <firmware.elf> [src-root]
# OBJDUMP=<triple>-objdump override toolchain autodetect
# IRAM_SYMBOLS="sym1 sym2" override the derived symbol set

set -euo pipefail

ELF="${1:?usage: check_iram_symbols.sh <firmware.elf> [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"
Loading