/* * Revera — a real-time kernel with hardware-enforced isolation. * Copyright (c) 2026 VIACERTO LLC. All rights reserved. * * This file is dual-licensed under the PolyForm Noncommercial License 1.0.0 OR * a commercial license from VIACERTO LLC. You may choose to use this file under * the terms of either license. * * For the full license terms, see the LICENSE and LICENSE-COMMERCIAL.md files in * the root directory of this source tree. * * SPDX-FileCopyrightText: © 2026 VIACERTO LLC * SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 OR LicenseRef-VIACERTO-Commercial */ /* * tk_config.h — compile-time configuration + capability gates. * * #ifndef-guarded DEFAULTS. A force-included profile (CFLAGS += -include * profiles/tk_profile_.h) overrides any default; this file fills the rest. Two * knob kinds: sizing (arrays → RAM) and feature (ENABLE_* → #if-gate code AND struct * fields). * * DEFAULTS = a MINIMAL-SAFE kernel: preemptive priority scheduling + the software * stack canary, with every hardware-protection, advanced-scheduling, and observability * feature OFF. So the bare drop-in "just runs" — link kernel/, add the include path, wire * the tick, watch it preempt — WITHOUT seizing the vendor's fault vectors (HardFault / * MemManage) or changing the syscall surface. Layer more on with a force-included profile: * nano — sizing floor for tiny MCUs (even smaller pools; canary off) * dev — observability: profiling + flight-recorder trace + ps/top * hardened — freedom-from-interference: MPU guard + unprivileged threads + fault-contain * full — every portable feature at once (coverage / regression builds) * * A DERIVED-capability section (bottom) ANDs each hardware-backed feature with the arch's * ARCH_HAS_* flag, so "app enables it AND the arch provides it" is resolved at COMPILE time * (TK_FEAT_*). This unifies the two gate idioms — config (app choice) × arch capability * (hardware) — into one set of derived flags. * * Included by tk_kernel.h AFTER the scheduling enums and kernel_arch.h, so this file * sees the enum values, ARCH_MIN_STACK_SIZE, ARCH_CORE_COUNT, and ARCH_HAS_*. * NOTE: sizes are ADDRESSABLE CELLS (ADR 0001): 8-bit bytes on ARM, 16-bit words on C2000. * * ── KNOB DOCUMENTATION FORMAT ─────────────────────────────────────────────────────── * Each user knob below carries a labeled block. This file is the SINGLE SOURCE OF TRUTH * for a knob's contract — example READMEs and reference docs LINK here, they never restate * it (so a change touches exactly one place). The labels are plain English; nothing external * is needed to read them, and a small script can still extract "Label : value" into a table. * * TK_CONFIG_ — . * Values : allowed values + meaning (or the range, for a sizing knob) * Default : the value this file sets when no profile/-D overrides it * Requires : arch capability / other knob / board provision this needs — and how it * fails (a hard #error, or a silent fold-to-0). Present only when it depends. * Effect : what turning it on/changing it does to the built kernel * Arch : where it applies vs. #errors / no-ops. Present only when non-trivial. * Profile : which profile(s) set it. Present only when a profile does. * Keep the field set stable — adding a field is a deliberate, repo-wide pass. */ #ifndef TK_CONFIG_H #define TK_CONFIG_H /* ── Sizing ──────────────────────────────────────────────────────────────── */ /* TK_CONFIG_MAX_THREADS — size of the static thread pool (idle threads + application threads). * Values : integer > ARCH_CORE_COUNT (one idle thread per core is reserved on a switch port) * Default : 8 * Effect : the TCB array; each slot is one createable thread. An SMP build reserves one idle * per core, so the app budget is MAX_THREADS - ARCH_CORE_COUNT. * Profile : nano = 4 */ #ifndef TK_CONFIG_MAX_THREADS #define TK_CONFIG_MAX_THREADS (8U) #endif /* TK_CONFIG_MAX_KERNELS — number of kernel INSTANCES (ADR 0002). * Values : integer >= 1 * Default : 1 (the singleton — zero extra cost) * Requires: INSTANCE isolation needs >= 2 (a fence acts BETWEEN instances) * Effect : 1 = the single default kernel. Raise to declare AMP / SMP-subset islands with * tk_kernel_define(); each extra instance is another full TCB/sync pool. */ #ifndef TK_CONFIG_MAX_KERNELS #define TK_CONFIG_MAX_KERNELS (1U) #endif /* TK_CONFIG_DEFAULT_STACK_SIZE — reference size for application thread stacks (cells). * Values : integer >= ARCH_MIN_STACK_SIZE * Default : max(512, ARCH_MIN_STACK_SIZE) — a 64-bit port whose trap frame forces a larger * ARCH_MIN_STACK_SIZE (arm_v8a: 1 KiB) carries this reference up with it * Effect : the suggested default when an app doesn't size a stack itself. */ #ifndef TK_CONFIG_DEFAULT_STACK_SIZE #define TK_CONFIG_DEFAULT_STACK_SIZE (((ARCH_MIN_STACK_SIZE) > 512U) ? (ARCH_MIN_STACK_SIZE) : 512U) #endif /* TK_CONFIG_IDLE_STACK_SIZE — per-core idle-thread stack (cells). * Values : integer >= ARCH_MIN_STACK_SIZE * Default : ARCH_MIN_STACK_SIZE * Effect : one such stack is reserved per core for its idle thread. */ #ifndef TK_CONFIG_IDLE_STACK_SIZE #define TK_CONFIG_IDLE_STACK_SIZE (ARCH_MIN_STACK_SIZE) #endif /* TK_CONFIG_KERNEL_REGION_SIZE — the fence-granular region each instance's `struct tk_kernel` occupies * under INSTANCE isolation, so a neighbour island's kernel RAM is denied by placement (D2, ADR 0004). * Values : power of two, >= sizeof(struct tk_kernel) (a static assert fails loud if the struct * outgrows it — raise this then) * Default : 4096 — fits the default pool (8 TCBs, 1 KiB idle) and satisfies both fence classes * (a 4 KiB paging page and a PMSAv7 power-of-two region) * Requires: only used when TK_FEAT_INSTANCE_ISOLATION; zero cost otherwise * Effect : aligns + sizes the per-island kernel struct so the fence can deny it wholesale. */ #ifndef TK_CONFIG_KERNEL_REGION_SIZE #define TK_CONFIG_KERNEL_REGION_SIZE (4096U) #endif /* TK_CONFIG_MAX_SEMAPHORES / _MUTEXES / _CONDS — sizes of the static sync-object pools. * Values : integer >= 0 (>= what the app creates) * Default : 8 / 8 / 4 * Effect : the tk_sem_create / tk_mutex_create / tk_cond_create pools; creation past the * count returns an error rather than allocating. * Profile : nano = 2 / 2 / 2 */ #ifndef TK_CONFIG_MAX_SEMAPHORES #define TK_CONFIG_MAX_SEMAPHORES (8U) #endif #ifndef TK_CONFIG_MAX_MUTEXES #define TK_CONFIG_MAX_MUTEXES (8U) #endif #ifndef TK_CONFIG_MAX_CONDS #define TK_CONFIG_MAX_CONDS (4U) #endif /* TK_CONFIG_QUANTUM_MS — round-robin time slice for equal-priority threads (ms). * Values : integer >= 1 * Default : 10 * Effect : how long an equal-priority thread runs before the tick rotates to its peer. */ #ifndef TK_CONFIG_QUANTUM_MS #define TK_CONFIG_QUANTUM_MS (10U) #endif /* ── Default scheduling (numeric, so a profile can set them before the enums exist) ── */ /* TK_CONFIG_SCHED_POLICY — the scheduler discipline. * Values : 0 = priority (round-robin within a level) | 1 = EDF (earliest deadline first) * Default : 0 * Requires: 1 needs TK_CONFIG_ENABLE_EDF (the deadline field + EDF selector); asserted below * Effect : which ready thread the scheduler picks. */ #ifndef TK_CONFIG_SCHED_POLICY #define TK_CONFIG_SCHED_POLICY (0U) #endif /* TK_CONFIG_PREEMPT_MODE — whether the periodic tick preempts the running thread. * Values : 0 = preemptive | 1 = cooperative (only explicit yields/blocks switch) * Default : 0 * Effect : preemptive is the norm on a real tick; cooperative suits the host port. */ #ifndef TK_CONFIG_PREEMPT_MODE #define TK_CONFIG_PREEMPT_MODE (0U) #endif /* ── Optional features (gate code + struct fields) ───────────────────────── */ /* TK_CONFIG_ENABLE_PROFILING — per-thread CPU accounting. * Values : 0 = off | 1 = on * Default : 0 * Effect : adds cpu_ticks + run_count fields to each TCB, increments them in the scheduler, and exposes * them via tk_thread_cpu_ticks() / tk_thread_run_count() (feeds ps/top). Purely software — no arch gate. * Profile : dev, full */ #ifndef TK_CONFIG_ENABLE_PROFILING #define TK_CONFIG_ENABLE_PROFILING (0U) #endif /* TK_CONFIG_ENABLE_EDF — earliest-deadline-first scheduling support. * Values : 0 = off | 1 = on * Default : 0 * Effect : adds the deadline_ms field + tk_thread_set_deadline() and makes SCHED_POLICY=1 (EDF) * selectable. Software-only. * Profile : full */ #ifndef TK_CONFIG_ENABLE_EDF #define TK_CONFIG_ENABLE_EDF (0U) #endif /* TK_CONFIG_ENABLE_AFFINITY — per-thread core pinning (SMP). * Values : 0 = off | 1 = on * Default : 0 * Requires: a multi-core build (ARCH_CORE_COUNT > 1) — enabling it on a single-core build is a hard * #error (pinning to the only core is a no-op knob; fail loud, consistent with the other gates). * Every affinity example pairs it with make SMP=2/3. * Effect : adds the per-thread core mask + tk_thread_pin(); the scheduler honours it. */ #ifndef TK_CONFIG_ENABLE_AFFINITY #define TK_CONFIG_ENABLE_AFFINITY (0U) #endif /* TK_CONFIG_ENABLE_INTROSPECT — thread names for the introspection snapshot (ps/top). * Values : 0 = off | 1 = on * Default : 0 * Effect : carries a human name per thread in the TCB (TK_TCB_HAS_NAME) so tk_thread_set_name() sticks * and the (always-available) tk_kernel_thread_snapshot() reports it; without it the snapshot's * name field is NULL. Software-only. A trace / SysView build turns the name field on too. The * fuller external ps/top region (a dedicated exported RAM window for an off-target monitor) * is a roadmap item, not this flag. * Profile : dev, full */ #ifndef TK_CONFIG_ENABLE_INTROSPECT #define TK_CONFIG_ENABLE_INTROSPECT (0U) #endif /* TK_CONFIG_ENABLE_STACK_CHECK — software stack-overflow detection. * Values : 0 = off | 1 = on * Default : 1 — the one default-ON safeguard: software-only, arch-neutral, installs NO fault * vectors, and makes a first-run stack overflow legible on any target * Effect : fills each stack with TK_CONFIG_STACK_FILL_BYTE at create; on every context switch it * verifies the outgoing thread's overflow-end canary (TK_CONFIG_STACK_CANARY_CELLS cells) * is intact and, on a breach, reports via the tk_set_stack_overflow_hook() hook (and TK_DET * when diagnostics are on). The same fill backs the tk_thread_stack_peak()/_free() high-water * reads. Detection is post-hoc (at the next switch) and arch-neutral; install a hook to act * (log / halt / reset). Independent of the MPU guard below. * Profile : nano = 0 (drops even this) */ #ifndef TK_CONFIG_ENABLE_STACK_CHECK #define TK_CONFIG_ENABLE_STACK_CHECK (1U) #endif /* TK_CONFIG_STACK_FILL_BYTE — the canary / high-water fill value. * Values : any 0x00–0xFF byte (a distinctive, uncommon value is best) * Default : 0xA5 * Requires: TK_CONFIG_ENABLE_STACK_CHECK — unused otherwise * Effect : written to every stack cell at create; a cell that no longer holds it has been touched, * which drives both the overflow canary and the high-water scan. */ #ifndef TK_CONFIG_STACK_FILL_BYTE #define TK_CONFIG_STACK_FILL_BYTE (0xA5U) #endif /* TK_CONFIG_STACK_CANARY_CELLS — size of the overflow canary at each stack's growth limit. * Values : >= 1 (addressable cells; a cell is one unsigned char, ADR 0001) * Default : 16 * Requires: TK_CONFIG_ENABLE_STACK_CHECK — unused otherwise * Effect : the zone checked on every switch, at the overflow end (the lowest addresses on a * down-growing stack, the highest on an up-growing one). Larger = fewer missed overflows, * marginally slower checks. */ #ifndef TK_CONFIG_STACK_CANARY_CELLS #define TK_CONFIG_STACK_CANARY_CELLS (16U) #endif /* TK_CONFIG_ENABLE_MPU_GUARD — hardware stack-overflow guard (installs the MPU + fault vectors). * Values : 0 = off | 1 = on * Default : 0 * Requires: ARCH_HAS_MPU — folds to 0 (no-op) on an arch without an MPU/PMP (host, arm_v6m, c2000) * Effect : an MPU no-access band below each stack faults an overflow in hardware; installs the * MemManage/HardFault path. TK_CONFIG_UNPRIV_THREADS implies this (unpriv needs the MPU), * so a demo enabling unpriv need not also set this. * Profile : hardened, full */ #ifndef TK_CONFIG_ENABLE_MPU_GUARD #define TK_CONFIG_ENABLE_MPU_GUARD (0U) #endif /* TK_CONFIG_UNPRIV_THREADS — run application threads unprivileged (user mode). * Values : 0 = off | 1 = on * Default : 0 * Requires: ARCH_HAS_UNPRIV — else a hard #error (an unprivileged level is a hardware property). * Implies the MPU guard (→ TK_FEAT_MPU_GUARD, which needs ARCH_HAS_MPU). * Arch : arm_v7m, arm_v8m, risc_v (S-mode), tricore — NOT host / arm_v6m / c2000 * Effect : app threads run in the unprivileged level and reach the kernel ONLY through the * SVC/ecall/trap syscall boundary; a direct poke at kernel RAM or a peer stack faults. * Profile : hardened, full */ #ifndef TK_CONFIG_UNPRIV_THREADS #define TK_CONFIG_UNPRIV_THREADS (0U) #endif /* TK_CONFIG_ENABLE_MPU_ISOLATION — full-lockdown per-thread + kernel-RAM MPU isolation. * Values : 0 = off | 1 = on * Default : 0 * Requires: ARCH_HAS_MPU — else a hard #error; TK_FEAT_UNPRIV (implied); AND the board must provide the * .privileged_bss / .thread_stacks linker zones + tk_arch_code_top() and declare * BOARD_HAS_MPU_ZONES (a board lacking them fails LOUD at compile time, not a cryptic link error) * Effect : places kernel RAM (.privileged_bss) and each thread's stack (.thread_stacks) in * MPU-fenced sections, so a thread touching a peer's stack or kernel RAM faults. * OFF = reduced isolation: unprivileged confinement via the SCS-unmapped map, no custom * sections (works on any MPU+UNPRIV arch without board linker changes). * Arch : the MPU arches; boards providing the zones today = nrf52832_pca10040, qemu_riscv32_virt. */ #ifndef TK_CONFIG_ENABLE_MPU_ISOLATION #define TK_CONFIG_ENABLE_MPU_ISOLATION (0U) #endif /* TK_CONFIG_FAULT_CONTAIN — recover from an unprivileged fatal fault instead of halting. * Values : 0 = off | 1 = on * Default : 0 * Requires: TK_CONFIG_UNPRIV_THREADS (asserted) + ARCH_HAS_FAULT_CONTAIN — folds to 0 on an * MPU+UNPRIV arch whose fault handler only halts (risc_v today), so it never overclaims * Effect : a fatal fault in an unprivileged thread TERMINATES just that thread and reschedules * the rest, rather than halting the system. A privileged fault still halts (fail-safe). * Profile : hardened, full */ #ifndef TK_CONFIG_FAULT_CONTAIN #define TK_CONFIG_FAULT_CONTAIN (0U) #endif /* TK_CONFIG_ENABLE_INSTANCE_ISOLATION — per-island SUPERVISOR memory fence (C3-B). * Values : 0 = off | 1 = on * Default : 0 * Requires: ARCH_HAS_SUPERVISOR_FENCE — else a hard #error; TK_CONFIG_MAX_KERNELS >= 2; * and a power-of-two TK_CONFIG_KERNEL_REGION_SIZE (all checked below) * Effect : drops the privileged MPU background so island A's KERNEL cannot reach island B's RAM — * the fence binds even privileged code. Opt in per island via tk_kernel_set_ram(). This is * the top of the TK_ISOLATION_* ladder, above THREAD. * Arch : a fence-capable arch (arm_v8m/arm_v7m by dropping PRIVDEFENA, risc_v S-mode via satp, * tricore natively, arm_v8r / arm_v7a / arm_v8a) — NOT host / arm_v6m / c2000. */ #ifndef TK_CONFIG_ENABLE_INSTANCE_ISOLATION #define TK_CONFIG_ENABLE_INSTANCE_ISOLATION (0U) #endif /* TK_CONFIG_ENABLE_WORLD — per-island security-attribution axis (TrustZone-AMP; ADR 0005). * Values : 0 = off | 1 = on * Default : 0 * Requires: TK_CONFIG_ENABLE_INSTANCE_ISOLATION — else a hard #error; AND a board that realizes the * hooks, declared with BOARD_HAS_WORLD (-D in the board make fragment) — else a hard #error * (fail-loud: a security boundary must not silently no-op). No root board declares it yet. * Effect : declares a per-island security domain — TK_WORLD_SECURE (a trusted attributor that marks * its own RAM protected system-side via board_world_protect) or TK_WORLD_NONSECURE (a * confined bus master released into a separate image via board_core_release). Lets the * KERNEL own the security setup from the portable API instead of hidden SDK boot code, on a * SoC whose secondary core has no per-core unit (e.g. MCXN947 CPU1). * Arch : arm_v8m + a system-side MPC/AHBSC board (the board supplies the strong hooks). */ #ifndef TK_CONFIG_ENABLE_WORLD #define TK_CONFIG_ENABLE_WORLD (0U) #endif /* TK_CONFIG_ENABLE_TRACE — the flight-recorder ring (last-N kernel events for a post-mortem dump). * Values : 0 = off | 1 = on * Default : 0 * Effect : records scheduling events into a RAM ring the app can dump on a fault/panic. Software-only. * Profile : dev, full */ #ifndef TK_CONFIG_ENABLE_TRACE #define TK_CONFIG_ENABLE_TRACE (0U) #endif /* TK_CONFIG_TRACE_RECORDS — depth of the flight-recorder ring. * Values : power of two (asserted when TRACE is on) * Default : 32 * Effect : how many recent events the ring holds before overwriting the oldest. * Profile : dev, full = 64 */ #ifndef TK_CONFIG_TRACE_RECORDS #define TK_CONFIG_TRACE_RECORDS (32U) #endif /* TK_CONFIG_TRACE_SCHED — how much each ring record carries. * Values : 1 = record carries the object (task handle), 12 bytes | 0 = lean 8-byte fact record * Default : 1 * Effect : trades ring RAM for detail; 0 suits the tiniest targets. */ #ifndef TK_CONFIG_TRACE_SCHED #define TK_CONFIG_TRACE_SCHED (1U) #endif /* TK_CONFIG_TRACE_TICK — instrument the periodic tick ISR in the trace/timeline. * Values : 0 = off | 1 = on * Default : 0 * Effect : OFF by default because at ~1 kHz the tick floods SystemView and buries the useful * events. Turn on only to study tick timing. */ #ifndef TK_CONFIG_TRACE_TICK #define TK_CONFIG_TRACE_TICK (0U) #endif /* TK_CONFIG_ENABLE_SYSVIEW — SEGGER SystemView live timeline (over RTT). * Values : 0 = off | 1 = on * Default : 0 * Requires: ARCH_HAS_SYSVIEW — else a hard #error. The backend needs two CPU-specific sources this * repo supplies only for Cortex-M (arm_v7m): a timestamp (DWT->CYCCNT) and the active * exception id (IPSR). ALSO needs the SEGGER SystemView/RTT library on the include/link * path + a board SEGGER config (a link-time requirement, not checkable here). * Effect : streams the scheduler timeline to the SystemView host app, no UART needed. * Arch : arm_v7m today. SystemView itself is arch-neutral (SEGGER supports R/A/RISC-V); porting = * supply the timestamp + active-IRQ sources for that arch and define ARCH_HAS_SYSVIEW. */ #ifndef TK_CONFIG_ENABLE_SYSVIEW #define TK_CONFIG_ENABLE_SYSVIEW (0U) #endif /* TK_CONFIG_ENABLE_DET — development-error trace (DET) reporting sink. * Values : 0 = off | 1 = on * Default : 0 * Effect : routes TK_DET(module, id, err) to a tk_det_set_hook() callback (observability; the cause * is ALSO returned to the caller as tk_status_t). OFF = TK_DET compiles to nothing. */ #ifndef TK_CONFIG_ENABLE_DET #define TK_CONFIG_ENABLE_DET (0U) #endif /* ── Derived (not user knobs) ────────────────────────────────────────────── */ #if (TK_CONFIG_ENABLE_TRACE != 0U) || (TK_CONFIG_ENABLE_SYSVIEW != 0U) #define TK_TRACE_ANY 1 #else #define TK_TRACE_ANY 0 #endif #if TK_TRACE_ANY || (TK_CONFIG_ENABLE_INTROSPECT != 0U) #define TK_TCB_HAS_NAME 1 #else #define TK_TCB_HAS_NAME 0 #endif /* ── Derived EFFECTIVE capabilities: config × arch ───────────────────────────── * A hardware-backed feature is live only when the app ENABLES it AND the arch PROVIDES * it (its ARCH_HAS_* flag). Resolving it here, once, means the rest of the kernel * writes a single `#if TK_FEAT_X` instead of repeating the (config && arch) pair — and a * config that asks for hardware the arch lacks simply yields 0 (compiles out), never a * broken build. Purely-software features (profiling/EDF/introspect/trace/stack-check) * have no arch gate and stay their raw TK_CONFIG_* value. */ /* The MPU machinery (init + per-thread region + MemManage/HardFault handlers) is compiled * when the app asks for the stack guard OR for unprivileged threads: unpriv confinement * REQUIRES the MPU, so enabling UNPRIV implies the guard — a demo turning on unpriv need not * also set MPU_GUARD. Either path still needs the hardware (ARCH_HAS_MPU); an arch without it * folds both to 0. This is also what keeps the minimal default (both off) from installing any * fault vectors — the drop-in stays collision-free until protection is explicitly requested. */ #if ((TK_CONFIG_ENABLE_MPU_GUARD != 0U) || (TK_CONFIG_UNPRIV_THREADS != 0U)) && defined(ARCH_HAS_MPU) #define TK_FEAT_MPU_GUARD 1 #else #define TK_FEAT_MPU_GUARD 0 #endif #if (TK_CONFIG_UNPRIV_THREADS != 0U) && defined(ARCH_HAS_UNPRIV) #define TK_FEAT_UNPRIV 1 #else #define TK_FEAT_UNPRIV 0 #endif /* Full-lockdown isolation = the kernel-RAM + per-thread MPU section placement. Requires * unprivileged threads AND the board's linker regions. OFF for the reduced-isolation build. */ #if (TK_CONFIG_ENABLE_MPU_ISOLATION != 0U) && TK_FEAT_UNPRIV && defined(ARCH_HAS_MPU) #define TK_FEAT_ISOLATION 1 #else #define TK_FEAT_ISOLATION 0 #endif /* Legible fail-loud for the THREAD tier — matching the INSTANCE_ISOLATION #error above (never a cryptic * negative-array static assert). Asking for unprivileged threads / full-lockdown MPU isolation on an arch * that cannot enforce them is a hard error. This is exactly what a TriCore/ARM isolation example does when * built for the default BOARD=host (pthread has no MPU / no unprivileged level): pass BOARD=. */ #if (TK_CONFIG_UNPRIV_THREADS != 0U) && !defined(ARCH_HAS_UNPRIV) #error "TK_CONFIG_UNPRIV_THREADS: this arch has no unprivileged execution level (no ARCH_HAS_UNPRIV). \ Use an MPU+unpriv arch (arm_v7m / arm_v8m / risc_v S-mode / tricore) — e.g. BOARD=tc275_lite_kit — or \ build without unprivileged threads. (A default BOARD=host build fails here BY DESIGN.)" #endif #if (TK_CONFIG_ENABLE_MPU_ISOLATION != 0U) && !defined(ARCH_HAS_MPU) #error "TK_CONFIG_ENABLE_MPU_ISOLATION: this arch has no MPU/PMP to fence per-thread stacks + kernel RAM \ (no ARCH_HAS_MPU). Use an MPU arch (e.g. BOARD=tc275_lite_kit), or disable MPU isolation." #endif /* Board-wiring fail-loud: full-lockdown isolation places kernel RAM + each thread's stack in the board's * .privileged_bss / .thread_stacks linker zones (and reads tk_arch_code_top()). A board whose linker script * provides them declares BOARD_HAS_MPU_ZONES (a -D in its make/board/.mk). Without it the LINK would * fail with a cryptic `undefined reference to _spriv / __mpu_ram_base`; turn that into a legible message at * compile time. Reduced isolation (TK_CONFIG_UNPRIV_THREADS WITHOUT MPU_ISOLATION) needs no zones and never * reaches here (TK_FEAT_ISOLATION is 0). */ #if TK_FEAT_ISOLATION && !defined(BOARD_HAS_MPU_ZONES) #error "TK_CONFIG_ENABLE_MPU_ISOLATION (full-lockdown) needs this board's .privileged_bss / .thread_stacks \ linker zones + tk_arch_code_top(), which it does not declare (no BOARD_HAS_MPU_ZONES). Provide the zones in the \ board linker script (see boards/nrf52832_pca10040/nrf52832_pca10040.ld) and add -DBOARD_HAS_MPU_ZONES=1 to \ make/board/.mk; or use reduced isolation (TK_CONFIG_UNPRIV_THREADS without MPU_ISOLATION), which needs \ no board linker changes." #endif /* ── Isolation capability + level (review QC3-2/3) ──────────────────────────────────────────────── * TK_FEAT_ISOLATION says whether THIS build turned isolation on; it does NOT tell an app whether the * arch could enforce a HW boundary at all. These two surface that as an explicit, degrading contract * (never silently implied) so an app or a monitor can ask "is my island / thread boundary HW-backed * here?" and get an honest per-arch answer, at compile time (#if) or at runtime (tk_kernel_isolation()). * * The ladder — a value an app can compare (>=). INSTANCE = a per-island HW memory fence that binds even * the KERNEL (the C3-B supervisor fence): island A's privileged code cannot reach island B's RAM. Below * it, inter-island "share nothing" holds only by CONVENTION (see ADR 0002 / the isolation docs). */ #define TK_ISOLATION_NONE 0 /* no HW-enforced boundary: one privilege level, shared RAM */ #define TK_ISOLATION_THREAD 1 /* HW boundary: unprivileged threads + kernel-RAM + per-thread stack */ #define TK_ISOLATION_INSTANCE 2 /* per-island HW fence binding the KERNEL too (C3-B supervisor fence) */ /* CAPABILITY (QC3-3): can this arch enforce ANY HW isolation boundary? It needs BOTH a memory-protection * unit (MPU/PMP — to fence kernel RAM + per-thread stacks) AND the unprivileged split (a thread the fence * can act against). TRUE on ARM v7m/v8m + RISC-V; FALSE on M0+ (arm_v6m), C2000, and the host — there an * island/thread boundary is a scheduling+convention partition, NOT a HW safety boundary. This is a * property of the ARCH, independent of whether a build enables isolation. */ #if defined(ARCH_HAS_MPU) && defined(ARCH_HAS_UNPRIV) #define TK_ARCH_ISOLATION_CAPABLE 1 #else #define TK_ARCH_ISOLATION_CAPABLE 0 #endif /* SUPERVISOR-FENCE CAPABILITY (C3-B): can this arch bind the PRIVILEGED level per island — i.e. fence one * island's KERNEL out of another's RAM? That needs an MPU/paging unit that acts on the privileged level * too (ARM v8-M by dropping MPU_CTRL.PRIVDEFENA, TriCore natively, RISC-V S-mode via satp). An arch that * implements it declares ARCH_HAS_SUPERVISOR_FENCE; where the MPU only binds the UNPRIVILEGED level (ARM * with PRIVDEFENA as the only mode, RISC-V M-mode PMP) this is FALSE and INSTANCE isolation fails loud. */ #if defined(ARCH_HAS_SUPERVISOR_FENCE) #define TK_ARCH_SUPERVISOR_FENCE_CAPABLE 1 #else #define TK_ARCH_SUPERVISOR_FENCE_CAPABLE 0 #endif /* INSTANCE-isolation FEATURE (the top rung): the app asked for the per-island supervisor fence AND the * arch can honour it AND there is more than one instance to fence. Requesting it on an incapable arch, or * with a single instance, fails LOUD (honest degrade — never a silent drop to a weaker boundary). */ #if (TK_CONFIG_ENABLE_INSTANCE_ISOLATION != 0U) #if !TK_ARCH_SUPERVISOR_FENCE_CAPABLE #error "TK_CONFIG_ENABLE_INSTANCE_ISOLATION: this arch cannot bind the privileged level per island \ (no ARCH_HAS_SUPERVISOR_FENCE). Use a fence-capable arch (e.g. arm_v8m), or disable INSTANCE isolation." #endif #if (TK_CONFIG_MAX_KERNELS < 2U) #error "TK_CONFIG_ENABLE_INSTANCE_ISOLATION needs TK_CONFIG_MAX_KERNELS >= 2 (a fence acts BETWEEN instances)." #endif #if (TK_CONFIG_KERNEL_REGION_SIZE & (TK_CONFIG_KERNEL_REGION_SIZE - 1U)) != 0U #error "TK_CONFIG_KERNEL_REGION_SIZE must be a power of two (the per-island kernel fence region; ADR 0004)." #endif #define TK_FEAT_INSTANCE_ISOLATION 1 #else #define TK_FEAT_INSTANCE_ISOLATION 0 #endif /* WORLD axis (ADR 0005) — the security-attribution layer on TOP of the instance fence. A SECURE island * marks its own RAM protected system-side; a NONSECURE island is confined by that mark and released into * its own image. Realized through the BOARD hooks board_world_protect / board_core_release. A security * boundary must NOT silently no-op, so — unlike a purely-portable feature — WORLD fails LOUD when the * board has not declared it can realize the hooks (BOARD_HAS_WORLD, a -D in the board make fragment). It * also requires the instance fence (the self-mark protects the SAME regions the fence deny-list names). */ #if (TK_CONFIG_ENABLE_WORLD != 0U) #if !TK_FEAT_INSTANCE_ISOLATION #error "TK_CONFIG_ENABLE_WORLD needs TK_CONFIG_ENABLE_INSTANCE_ISOLATION (the world self-mark protects \ the instance-fence regions; enable INSTANCE isolation, or disable WORLD)." #endif #if !defined(BOARD_HAS_WORLD) #error "TK_CONFIG_ENABLE_WORLD: this board does not realize the world hooks (no BOARD_HAS_WORLD). WORLD \ marks RAM at a system-side MPC/AHBSC + releases a confined secondary core — a board that can do that must \ provide strong board_world_protect/board_core_release and define BOARD_HAS_WORLD (a -D in its make \ fragment). No portable/collapse fallback: a security boundary must not silently no-op. Use such a board \ (the WORLD graft target), or disable WORLD." #endif #define TK_FEAT_WORLD 1 #else #define TK_FEAT_WORLD 0 #endif /* LEVEL (QC3-2, the degrading contract): what THIS build actually enforces — the highest active rung. * INSTANCE (supervisor fence) > THREAD (unpriv fence) > NONE. A capable arch that didn't enable a rung * still reports the level it actually reached (the boundary isn't active until enabled). */ #if TK_FEAT_INSTANCE_ISOLATION #define TK_ISOLATION_LEVEL TK_ISOLATION_INSTANCE #elif TK_FEAT_ISOLATION #define TK_ISOLATION_LEVEL TK_ISOLATION_THREAD #else #define TK_ISOLATION_LEVEL TK_ISOLATION_NONE #endif /* Containment needs MPU + unprivileged threads AND an arch whose fault handler actually WIRES it * (ARCH_HAS_FAULT_CONTAIN — calls tk_kernel_fault_contain + resumes from the fault). Requiring the * capability keeps the hardened profile from claiming containment on an MPU+UNPRIV arch whose fault * handler only halts (e.g. risc_v today): there it compiles out cleanly instead of silently overclaiming * (review H1). */ #if (TK_CONFIG_FAULT_CONTAIN != 0U) && defined(ARCH_HAS_MPU) && defined(ARCH_HAS_UNPRIV) && defined(ARCH_HAS_FAULT_CONTAIN) #define TK_FEAT_FAULT_CONTAIN 1 #else #define TK_FEAT_FAULT_CONTAIN 0 #endif /* Affinity is meaningful only under SMP (>1 core); config asks, the arch's core count decides. Enabling * it on a single-core build pins to the only core — a no-op knob — so fail LOUD rather than let it look * effective. (A non-fatal #warning isn't expressible under this project's -std=c99 -pedantic-errors, where * `#warning` is itself an error; so the honest non-silent signal is a hard #error, matching the gates above.) */ #if (TK_CONFIG_ENABLE_AFFINITY != 0U) && (ARCH_CORE_COUNT <= 1) #error "TK_CONFIG_ENABLE_AFFINITY has no effect on a single-core build (ARCH_CORE_COUNT == 1): per-thread \ core pinning needs more than one core. Build an SMP target (e.g. make SMP=2 on a multi-core board such as \ rp2040_pico_flash or tc275_lite_kit), or drop TK_CONFIG_ENABLE_AFFINITY." #endif #if (TK_CONFIG_ENABLE_AFFINITY != 0U) && (ARCH_CORE_COUNT > 1) #define TK_FEAT_AFFINITY 1 #else #define TK_FEAT_AFFINITY 0 #endif /* SysView needs the CPU-specific backend glue (timestamp + active-exception id). Fail LOUD when asked * for on an arch that has not ported it (ARCH_HAS_SYSVIEW undefined) instead of a cryptic link error — * SystemView/RTT themselves are arch-neutral, but this repo's glue is Cortex-M (arm_v7m) only today. */ #if (TK_CONFIG_ENABLE_SYSVIEW != 0U) && !defined(ARCH_HAS_SYSVIEW) #error "TK_CONFIG_ENABLE_SYSVIEW: this arch does not provide the SystemView backend glue (no \ ARCH_HAS_SYSVIEW). The backend needs a timestamp (Cortex-M DWT->CYCCNT) + the active exception id (IPSR), \ supplied only for arm_v7m today; Cortex-R/A (generic timer + GIC) and RISC-V (mcycle/mcause) are not yet \ wired. Build SysView on a Cortex-M target (e.g. BOARD=nrf52832_pca10040), or disable SysView." #endif /* ── Validation (compile-time): turn misconfiguration into a build error ───── */ #define TK_CONCAT_(a, b) a##b #define TK_CONCAT(a, b) TK_CONCAT_(a, b) #define TK_STATIC_ASSERT(cond) \ typedef char TK_CONCAT(tk_static_assert_, __LINE__)[(cond) ? 1 : -1] TK_STATIC_ASSERT(TK_CONFIG_MAX_THREADS >= 1U); TK_STATIC_ASSERT(TK_CONFIG_MAX_KERNELS >= 1U); /* Core sets live in a uint32_t owned_cores bitmask and an 8-bit per-thread affinity mask, and the * kernel builds masks with (1u << core); cap the compiled core count so those never overflow/UB * (ADR 0002 review 0001, L5). Real targets are <= 3; raise deliberately if a >8-core part appears. */ TK_STATIC_ASSERT(ARCH_CORE_COUNT >= 1U); TK_STATIC_ASSERT(ARCH_CORE_COUNT <= 8U); #if ARCH_CONTEXT_SWITCH_SUPPORTED /* A switch port reserves ARCH_CORE_COUNT TCBs for per-core idle threads → need >1 beyond them. */ TK_STATIC_ASSERT(TK_CONFIG_MAX_THREADS > ARCH_CORE_COUNT); #endif TK_STATIC_ASSERT(TK_CONFIG_IDLE_STACK_SIZE >= ARCH_MIN_STACK_SIZE); TK_STATIC_ASSERT(TK_CONFIG_DEFAULT_STACK_SIZE >= ARCH_MIN_STACK_SIZE); TK_STATIC_ASSERT(TK_CONFIG_QUANTUM_MS >= 1U); TK_STATIC_ASSERT(TK_SCHED_PRIORITY == 0); TK_STATIC_ASSERT(TK_SCHED_EDF == 1); TK_STATIC_ASSERT(TK_PREEMPTIVE == 0); TK_STATIC_ASSERT(TK_COOPERATIVE == 1); /* A feature's default selection must not outrun the feature itself. */ TK_STATIC_ASSERT((TK_CONFIG_SCHED_POLICY != 1U) || (TK_CONFIG_ENABLE_EDF != 0U)); /* Fault containment needs the isolation it relies on. */ TK_STATIC_ASSERT((TK_CONFIG_FAULT_CONTAIN == 0U) || (TK_CONFIG_UNPRIV_THREADS != 0U)); /* Full-lockdown isolation reuses the guard's per-switch MPU reprogram to re-grant each thread's * own stack region, so the guard machinery must be present. Check the DERIVED flag: isolation implies * unpriv, which implies TK_FEAT_MPU_GUARD — so this holds without separately setting MPU_GUARD. */ TK_STATIC_ASSERT((TK_CONFIG_ENABLE_MPU_ISOLATION == 0U) || (TK_FEAT_MPU_GUARD != 0U)); /* Each isolation level asserts against ITS OWN arch capability — not one blanket capability (ADR 0003). * A THREAD boundary (unprivileged threads + kernel-RAM/per-thread fencing) needs the MPU+UNPRIV split * (TK_ARCH_ISOLATION_CAPABLE). An INSTANCE boundary (the C3-B supervisor fence) binds the PRIVILEGED * level per island — it needs the privileged-binding fence (TK_ARCH_SUPERVISOR_FENCE_CAPABLE) and does * NOT require an unprivileged split at all (an all-privileged AMP island can still fence its kernel). * Keeping these separate is what lets a future fence-capable arch that lacks UNPRIV plug into the seam. */ TK_STATIC_ASSERT((TK_ISOLATION_LEVEL != TK_ISOLATION_THREAD) || (TK_ARCH_ISOLATION_CAPABLE != 0)); TK_STATIC_ASSERT((TK_ISOLATION_LEVEL != TK_ISOLATION_INSTANCE) || (TK_ARCH_SUPERVISOR_FENCE_CAPABLE != 0)); #if (TK_CONFIG_ENABLE_TRACE != 0U) TK_STATIC_ASSERT((TK_CONFIG_TRACE_RECORDS > 0U) && ((TK_CONFIG_TRACE_RECORDS & (TK_CONFIG_TRACE_RECORDS - 1U)) == 0U)); #endif #endif /* TK_CONFIG_H */