/* * 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 */ /* * identity/main.c — PROVE the unprivileged-safe identity getters (review item 4 / L2). * * A thread's identity getters — tk_thread_self/id/kernel and tk_kernel_self/name/owned_cores — read * TK_PRIVILEGED_DATA (the instance struct, g_core_kernel, the TCB pools). Under full lockdown * (TK_CONFIG_UNPRIV_THREADS + MPU/PMP isolation) a DIRECT read of that from an UNPRIVILEGED thread * faults. item 4 makes each getter a thin wrapper: called from a running thread it traps via the * syscall path so the kernel reads privileged and hands the value back; called from setup (privileged, * pre-start) it reads directly. This demo exercises BOTH contexts: * * 1. SETUP (main(), privileged, pre-start): the kernel getters read DIRECTLY. This also proves the * wrapper does NOT trap at setup — on ARM a setup-time SVC would misfire as the first-thread * LAUNCH, and on RISC-V the trap vector isn't installed yet, so setup MUST read directly. * 2. RUNTIME (worker, UNPRIVILEGED): all six getters cross the syscall trap. If item 4 holds they * return real values and the worker prints "item 4 holds"; a regression to a direct privileged * read faults instead, halting before that line ever prints (its absence is the failure signal). * * make BOARD=nrf52832_pca10040 flash # ARMv7-M: watch the debug UART, 115200 8N1 * make BOARD=qemu_riscv32_virt run # RV32 U-mode under qemu * * Needs an MPU/PMP arch with the unprivileged split (ARM v7m/v8m, RISC-V) + the board's isolation * linker zones. The Makefile turns on full lockdown; the guard below fails the build otherwise. A * single kernel instance (the default "kernel", owning every core) — the getters resolve against it. */ #include #include "tk_kernel.h" /* TK_THREAD_STACK / tk_thread_* / the identity getters / TK_FEAT_ISOLATION */ #include "tk_time.h" #include "tk_board.h" #include "tk_console.h" #if !TK_FEAT_ISOLATION #error "identity needs full lockdown — build it via its Makefile (UNPRIV + MPU/PMP isolation)." #endif /* Isolatable stack: power-of-two, aligned to its own size, in the .thread_stacks MPU/PMP zone. */ TK_THREAD_STACK(worker_stack, 2048); /* Compact hex printer (tk_print_u32 is decimal only) — handles/masks read clearer in hex. */ static void print_hex32(uint32_t v) { static const char hexd[] = "0123456789abcdef"; char buf[11]; int i; buf[0] = '0'; buf[1] = 'x'; for (i = 0; i < 8; i++) { buf[2 + i] = hexd[(v >> ((7 - i) * 4)) & 0xFu]; } buf[10] = '\0'; tk_print(buf); } static void* worker(void* arg) { (void)arg; /* Every call below reads privileged kernel state — from this unprivileged thread each one traps * into the kernel (item 4). A regression to a direct read would fault right here. */ tk_thread_t* self = tk_thread_self(); uint32_t id = tk_thread_id(self); tk_kernel_t* kself = tk_kernel_self(); tk_kernel_t* kowner = tk_thread_kernel(self); const char* kname = tk_kernel_name(kself); uint32_t cores = tk_kernel_owned_cores(kself); tk_console_lock(); tk_print("\n[worker | UNPRIVILEGED] identity via the syscall trap:\n"); tk_print(" tk_thread_self() = "); print_hex32((uint32_t)(uintptr_t)self); tk_print("\n"); tk_print(" tk_thread_id(self) = "); tk_print_u32(id); tk_print("\n"); tk_print(" tk_kernel_self() = "); print_hex32((uint32_t)(uintptr_t)kself); tk_print("\n"); tk_print(" tk_thread_kernel(self) = "); print_hex32((uint32_t)(uintptr_t)kowner); tk_print((kowner == kself) ? " (== kernel_self => placement verified)\n" : " (!! MISMATCH)\n"); tk_print(" tk_kernel_name(kself) = "); tk_print((kname != NULL) ? kname : "(null)"); tk_print("\n"); tk_print(" tk_kernel_owned_cores() = "); print_hex32(cores); tk_print("\n"); tk_print("\n[worker] all six getters returned WITHOUT a fault => item 4 holds. Looping.\n"); tk_console_unlock(); for (;;) { tk_sleep_ms(1000u); } return 0; } int main(void) { tk_board_init(); tk_console_init(TK_UART_CH_DEBUG); tk_print("\nidentity: unprivileged-safe identity getters (review item 4).\n"); tk_kernel_init(); tk_time_init(); /* SETUP context (privileged, pre-start): the SAME kernel getters, read DIRECTLY — no trap. On ARM a * setup-time SVC would be taken as the first-thread launch; on RISC-V the trap vector isn't wired * yet. That this prints (rather than misfiring) is the direct-path half of the contract. */ { tk_kernel_t* k = tk_kernel_self(); tk_print("[setup | privileged | direct read] before start:\n"); tk_print(" tk_kernel_self() = "); print_hex32((uint32_t)(uintptr_t)k); tk_print("\n"); tk_print(" tk_kernel_name(k) = "); tk_print(tk_kernel_name(k)); tk_print("\n"); tk_print(" tk_kernel_owned_cores() = "); print_hex32(tk_kernel_owned_cores(k)); tk_print("\n"); } /* Isolation-posture contract (review QC3-2/3): the arch capability (compile-time) beside what this * build actually enforces (compile-time macro == runtime getter). On this MPU/PMP lockdown build: * CAPABLE=1, LEVEL=1 (THREAD). On an M0+/C2000 build the capability would read 0 (no HW boundary). */ tk_print("[caps] TK_ARCH_ISOLATION_CAPABLE="); tk_print_u32((uint32_t)TK_ARCH_ISOLATION_CAPABLE); tk_print(" isolation level="); tk_print_u32((uint32_t)tk_kernel_isolation()); tk_print(" (compile-time TK_ISOLATION_LEVEL="); tk_print_u32((uint32_t)TK_ISOLATION_LEVEL); tk_print(", 0=none 1=thread)\n"); tk_thread_create(worker, 0, worker_stack, sizeof worker_stack, TK_PRIO_MIN + 1u); tk_kernel_start(); return 0; }