From a5d676c9f10472583517728ba5e405cee7692b93 Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Sun, 1 Mar 2026 13:26:59 +0100 Subject: [PATCH 01/19] implement trap handler --- include/stdbigos/csr.h | 6 ++ include/trap/trap.h | 103 +++++++++++++++++++++++++++ src/example_machine/CMakeLists.txt | 2 +- src/example_machine/entry.c | 10 +-- src/example_trap/CMakeLists.txt | 5 ++ src/example_trap/entry.c | 107 +++++++++++++++++++++++++++++ src/kernel/CMakeLists.txt | 2 +- src/lib/trap/CMakeLists.txt | 3 + src/lib/trap/trap.S | 106 ++++++++++++++++++++++++++++ src/lib/trap/trap.c | 35 ++++++++++ 10 files changed, 372 insertions(+), 7 deletions(-) create mode 100644 include/trap/trap.h create mode 100644 src/example_trap/CMakeLists.txt create mode 100644 src/example_trap/entry.c create mode 100644 src/lib/trap/CMakeLists.txt create mode 100644 src/lib/trap/trap.S create mode 100644 src/lib/trap/trap.c diff --git a/include/stdbigos/csr.h b/include/stdbigos/csr.h index 6c345a64..54f83648 100644 --- a/include/stdbigos/csr.h +++ b/include/stdbigos/csr.h @@ -73,6 +73,12 @@ static inline void wfi() { static inline void ebreak() { __asm__ volatile("ebreak" ::: "memory"); } +static inline void intr_enable() { + CSR_SET(sstatus, 1 << 1); +} +static inline void intr_disable() { + CSR_CLEAR(sstatus, 1 << 1); +} /// @} /// @} diff --git a/include/trap/trap.h b/include/trap/trap.h new file mode 100644 index 00000000..ab5f2ca7 --- /dev/null +++ b/include/trap/trap.h @@ -0,0 +1,103 @@ +#ifndef KERNEL_TRAP_H_ +#define KERNEL_TRAP_H_ + +#include +#include + +typedef enum trap_interrupt_type { + TRAP_INT_S_SOFTWARE = 1, + TRAP_INT_S_TIMER = 5, + TRAP_INT_S_EXTERNAL = 9, + TRAP_INT_COUNTER_OVERFLOW = 13, + // >=16 are for platform use +} trap_interrupt_type_t; + +typedef enum trap_exception_type { + TRAP_EXC_INSTR_ADDRESS_MISALIGNED = 0, + TRAP_EXC_INSTR_ACCESS_FAULT = 1, + TRAP_EXC_ILLEGAL_INSTR = 2, + TRAP_EXC_BREAKPOINT = 3, + TRAP_EXC_LOAD_ADDRESS_MISALIGNED = 4, + TRAP_EXC_LOAD_ACCESS_FAULT = 5, + TRAP_EXC_STORE_ADDRESS_MISALIGNED = 6, + TRAP_EXC_STORE_ACCESS_FAULT = 7, + TRAP_EXC_ENV_CALL_U = 8, + TRAP_EXC_ENV_CALL_S = 9, + TRAP_EXC_INSTR_PAGE_FAULT = 12, + TRAP_EXC_LOAD_PAGE_FAULT = 13, + TRAP_EXC_SOFTWARE_CHECK = 18, + TRAP_EXC_HARDWARE_ERROR = 19, + // 24-31 designated for custom use + // 48-63 designated for custom use + // >=64 reserved +} trap_exception_type_t; + +typedef struct trap_frame { + union { + reg_t gpr[32]; + struct { + reg_t zero; + reg_t ra; + reg_t sp; + reg_t gp; + reg_t tp; + reg_t t0; + reg_t t1; + reg_t t2; + reg_t s0; + reg_t s1; + reg_t a0; + reg_t a1; + reg_t a2; + reg_t a3; + reg_t a4; + reg_t a5; + reg_t a6; + reg_t a7; + reg_t s2; + reg_t s3; + reg_t s4; + reg_t s5; + reg_t s6; + reg_t s7; + reg_t s8; + reg_t s9; + reg_t s10; + reg_t s11; + reg_t t3; + reg_t t4; + reg_t t5; + reg_t t6; + }; + }; + reg_t sepc; + reg_t sstatus; +} trap_frame_t; + +typedef void (*pfn_trap_handler_t)(trap_frame_t* tf); +typedef void (*pfn_continuation_t)(void* user); + +static inline bool trap_is_interrupt(reg_t cause) { + return (ireg_t)cause < 0; +} + +static inline trap_interrupt_type_t trap_get_interrupt_code(reg_t cause) { + return (cause << 1) >> 1; // strip highest bit +} + +static inline trap_exception_type_t trap_get_exception_code(reg_t cause) { + return cause; +} + +error_t trap_init(pfn_trap_handler_t handler); + +[[gnu::nonnull]] +error_t trap_utils_prepare_stack_for_transition(void** stack, const trap_frame_t* tf); + +[[noreturn, gnu::nonnull(1, 3)]] +void trap_utils_jump_with_stack(void* stack, void* user, pfn_continuation_t continuation); + +[[noreturn, gnu::nonnull(1)]] +void trap_restore_with_cleanup(void* stack, void* user, pfn_continuation_t cleanup); + +#endif diff --git a/src/example_machine/CMakeLists.txt b/src/example_machine/CMakeLists.txt index cb12f40d..f54ad963 100644 --- a/src/example_machine/CMakeLists.txt +++ b/src/example_machine/CMakeLists.txt @@ -6,6 +6,6 @@ target_link_options(example_machine PRIVATE -static-pie -T ${LINKER_SCRIPT}) set_target_properties(example_machine PROPERTIES LINK_DEPENDS ${LINKER_SCRIPT}) -target_link_libraries(example_machine PRIVATE Debug relocations) +target_link_libraries(example_machine PRIVATE Debug trap relocations) ADD_QEMU_TARGET(example_machine BIOS_IMAGE) diff --git a/src/example_machine/entry.c b/src/example_machine/entry.c index 856f9e2b..f1aac37f 100644 --- a/src/example_machine/entry.c +++ b/src/example_machine/entry.c @@ -2,10 +2,10 @@ #include #include #include -#include #include #include #include +#include // NOLINTBEGIN(readability-identifier-naming) extern u8 bss_start[]; @@ -30,12 +30,12 @@ void main() { [[gnu::interrupt("machine")]] void int_handler() { reg_t cause = CSR_READ(mcause); - if (is_interrupt(cause)) { + if (trap_is_interrupt(cause)) { // interrupt - reg_t int_no = get_interrupt_code(cause); + reg_t int_no = trap_get_interrupt_code(cause); switch (int_no) { - case IntMTimer: + case 7: // M-mode timer dputs("got timer interrupt\n"); g_mtimecmp[hartid()] = *g_mtime + g_quant; break; @@ -64,7 +64,7 @@ void start() { CSR_SET(mstatus, 8); // set TIMER in mie - CSR_SET(mie, 1lu << IntMTimer); + CSR_SET(mie, 1lu << 7); main(); diff --git a/src/example_trap/CMakeLists.txt b/src/example_trap/CMakeLists.txt new file mode 100644 index 00000000..d943b4e9 --- /dev/null +++ b/src/example_trap/CMakeLists.txt @@ -0,0 +1,5 @@ +SETUP_EXECUTABLE(example_trap) + +target_link_libraries(example_trap PRIVATE startup trap Debug stdbigos) + +ADD_QEMU_TARGET(example_trap) diff --git a/src/example_trap/entry.c b/src/example_trap/entry.c new file mode 100644 index 00000000..aa190e3a --- /dev/null +++ b/src/example_trap/entry.c @@ -0,0 +1,107 @@ +#include +#include +#include +#include +#include + +void handle_exc(trap_exception_type_t exc, trap_frame_t* tf) { + [[maybe_unused]] reg_t stval = CSR_READ_RELAXED(stval); + + switch (exc) { + case TRAP_EXC_ENV_CALL_U: + intr_enable(); + tf->sepc += 4; // skip ECALL instruction + + DEBUG_PRINTF("syscall: got 0x%lx\n", tf->a0); + tf->a0 = 0xeed; + + intr_disable(); + return; + default: + // we've got reserved exception + DEBUG_PRINTF("exception: got unknown number %lu\n", (u64)exc); + return; + } +} + +void my_trap_handler(trap_frame_t* tf) { + reg_t scause = CSR_READ_RELAXED(scause); + if (trap_is_interrupt(scause)) { + DEBUG_PRINTF("got interrupt: %lu\n", (u64)trap_get_interrupt_code(scause)); + } else { + handle_exc(trap_get_exception_code(scause), tf); + } +} + +static u8 g_kernel_mode_stack[4096] __attribute__((aligned(4096))); +static u8 g_kernel_mode_stack2[4096] __attribute__((aligned(4096))); +static u8 g_user_mode_stack[4096] __attribute__((aligned(4096))); + +void user_fn() { + while (1) { + DEBUG_PRINTF("from user mode\n"); + + register reg_t a0 __asm__("a0") = 0xdeadbeef; + __asm__ volatile("ecall" : "+r"(a0)); + DEBUG_PRINTF("syscall returned: 0x%lx\n", a0); + + // try do do something bad with sp, so we know kernel will not break + __asm__ volatile("mv t0, sp\n\t" + "mv sp, zero\n\t" + "ecall\n\t" + "mv sp, t0" + : "+r"(a0) + : + : "t0"); + } +} + +typedef struct { + u32 hartid; + const void* fdt; +} kernel_state_t; + +static inline void* get_sp() { + void* sp; + __asm__("mv %0, sp" : "=r"(sp)); + return sp; +} + +void kernel_continuation(void* usr) { + // copy the state from the old stack + kernel_state_t state = *(kernel_state_t*)usr; + DEBUG_PRINTF("on new stack: hartid %d, fdt %p, sp %p\n", state.hartid, state.fdt, get_sp()); + + // from now on we can cleanup the old stack + + // now jump to user mode (also reenter our stack) + void* user_stack_top = &g_user_mode_stack[sizeof(g_user_mode_stack)]; + void* kernel_stack_top = &g_kernel_mode_stack2[sizeof(g_kernel_mode_stack2)]; + trap_frame_t tf = {0}; + + tf.sp = (reg_t)user_stack_top; + tf.sepc = (reg_t)user_fn; // setup user entry point + tf.sstatus = CSR_READ_RELAXED(sstatus); + tf.sstatus &= ~(1ull << 8); // clear previous privilage S -> sret will return to U mode + + if (trap_utils_prepare_stack_for_transition(&kernel_stack_top, &tf) != ERR_NONE) { + DEBUG_PRINTF("failed to prepare stack for transition\n"); + return; + } + + DEBUG_PRINTF("transition to U-mode\n"); + trap_restore_with_cleanup(kernel_stack_top, nullptr, nullptr); +} + +void main([[maybe_unused]] u32 hartid, [[maybe_unused]] const void* fdt) { + DEBUG_PRINTF("on old stack: hartid %d, fdt %p, sp %p\n", hartid, fdt, get_sp()); + + if (trap_init(my_trap_handler) != ERR_NONE) { + return; + } + + // migrate to a different stack + kernel_state_t state = {hartid, fdt}; + void* stack_top = &g_kernel_mode_stack[sizeof(g_kernel_mode_stack)]; + trap_utils_jump_with_stack(stack_top, &state, kernel_continuation); +} diff --git a/src/kernel/CMakeLists.txt b/src/kernel/CMakeLists.txt index e87f158e..7b0bd7c6 100644 --- a/src/kernel/CMakeLists.txt +++ b/src/kernel/CMakeLists.txt @@ -1,7 +1,7 @@ SETUP_EXECUTABLE(kernel) target_compile_definitions(kernel PRIVATE $,__DEBUG__ __LOGLVL__=3, __LOGLVL__=1>) -target_link_libraries(kernel PRIVATE Debug stdbigos) +target_link_libraries(kernel PRIVATE trap Debug stdbigos) target_link_options(kernel PRIVATE -static-pie -e kinit) diff --git a/src/lib/trap/CMakeLists.txt b/src/lib/trap/CMakeLists.txt new file mode 100644 index 00000000..001317c5 --- /dev/null +++ b/src/lib/trap/CMakeLists.txt @@ -0,0 +1,3 @@ +SETUP_LIBRARY(trap) + +target_link_libraries(trap PUBLIC stdbigos) diff --git a/src/lib/trap/trap.S b/src/lib/trap/trap.S new file mode 100644 index 00000000..97025ec2 --- /dev/null +++ b/src/lib/trap/trap.S @@ -0,0 +1,106 @@ +.text + +.global trap_entry +.global trap_restore_with_cleanup +.global trap_utils_jump_with_stack + +.equ SSTATUS_SIE, 0x00000002 // Supervisor Interrupt Enable +.equ SSTATUS_SPP, 0x00000100 // Supervisor Previous Privilege (1 = S-mode, 0 = U-mode) +.equ FRAME_SIZE, 0x110 + +// sscratch should have kernel stack pointer IIF in user-mode +// when comming from kernel-space then it should be 0 + +// the thread pointer should be at the base of the sscratch location if it is not 0 + +.align 4 +trap_entry: + csrrw sp, sscratch, sp + csrrw t0, sscratch, t0 + + // sp <- sscratch + // t0 <- old sp + // sscratch <- old t0 + bnez sp, save_regs + +from_smode: + // sp == 0 + // t0 <- old sp (kernel stack pointer) + // sscratch <- old t0 + + // someone could be stupid and have made kernel's sp not 8-byte aligned -> align down to 16-bytes + and sp, t0, -16 + +save_regs: + // sp != 0 - valid kernel stack pointer + // t0 <- old sp + // sscratch <- old t0 + + add sp, sp, -FRAME_SIZE + // save original sp, restore original t0 in sscratch and restore sscratch invariant + sd t0, 2*8(sp) + csrrw t0, sscratch, zero + + // Save everything, but sp (x2) + .irp i, 0,1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 + sd x\i, \i*8(sp) + .endr + + csrr t0, sepc + csrr t1, sstatus + + sd t0, 32*8(sp) + sd t1, 33*8(sp) + + andi t1, t1, SSTATUS_SPP + bnez t1, run_handler + +.option push +.option norelax + lla gp, __global_pointer$ +.option pop + + ld tp, FRAME_SIZE(sp) + +run_handler: + mv a0, sp + call trap_handler_trampoline + +kernel_trap_restore: + ld t0, 32*8(sp) + ld t1, 33*8(sp) + + // disable interrupts and restore sstatus and sepc + and t2, t1, ~SSTATUS_SIE + csrw sstatus, t2 + csrw sepc, t0 + + and t2, t1, SSTATUS_SPP + bnez t2, load_regs + +restore_umode: + sd tp, FRAME_SIZE(sp) + + add t0, sp, FRAME_SIZE + csrw sscratch, t0 + +load_regs: + .irp i, 1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 + ld x\i, \i*8(sp) + .endr + + ld sp, 2*8(sp) + sret + +trap_restore_with_cleanup: + mv sp, a0 + beqz a2, kernel_trap_restore + lla ra, kernel_trap_restore + mv a0, a1 + jr a2 + +trap_utils_jump_with_stack: + mv sp, a0 + mv a0, a1 + mv ra, zero + jr a2 diff --git a/src/lib/trap/trap.c b/src/lib/trap/trap.c new file mode 100644 index 00000000..08409c1b --- /dev/null +++ b/src/lib/trap/trap.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include +#include +#include +#include + +extern void trap_entry(); + +static pfn_trap_handler_t g_trap_handler = nullptr; + +void trap_handler_trampoline(trap_frame_t* tf) { + if (g_trap_handler) + g_trap_handler(tf); +} + +error_t trap_init(pfn_trap_handler_t handler) { + if (!handler) + return ERR_BAD_ARG; + + CSR_WRITE(sscratch, 0); + CSR_WRITE(stvec, trap_entry); + g_trap_handler = handler; + return ERR_NONE; +} + +error_t trap_utils_prepare_stack_for_transition(void** stack, const trap_frame_t* tf) { + u8* sp = (u8*)ALIGN_DOWN((uintptr_t)*stack, 16); + sp = sp - 16 - sizeof(*tf); // leave space for tp and trap frame + memcpy(sp, tf, sizeof(*tf)); + *stack = sp; + + return ERR_NONE; +} From 82cfd765d2e0a18a0ca910e45c719ae0cc2ce834 Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Mon, 9 Mar 2026 22:10:39 +0100 Subject: [PATCH 02/19] gcc is making more and more problems --- src/lib/stdbigos/string.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lib/stdbigos/string.c b/src/lib/stdbigos/string.c index 582178d1..03217ec0 100644 --- a/src/lib/stdbigos/string.c +++ b/src/lib/stdbigos/string.c @@ -2,6 +2,16 @@ #include #include +#if __has_attribute(externally_visible) + // make GCC's LTO not remove those definitions, because if the compiler + // generates reference to them (e.g when doing `var = {0}`), it will not be able + // to find them and will error out (will generate PLT redirection) + #define EXTERNALLY_VISIBLE [[gnu::externally_visible]] +#else + #define EXTERNALLY_VISIBLE +#endif + +EXTERNALLY_VISIBLE void* memcpy(void* restrict dest, const void* restrict src, size_t n) { u8* d = dest; const u8* s = src; @@ -24,6 +34,7 @@ void* memccpy(void* restrict dest, const void* restrict src, int ch, size_t coun return nullptr; } +EXTERNALLY_VISIBLE void* memset(void* dest, int val, size_t n) { u8* d = dest; while (n--) *d++ = val; @@ -36,6 +47,7 @@ void* memset_explicit(void* dest, int val, size_t n) { return dest; } +EXTERNALLY_VISIBLE void* memmove(void* dest, const void* src, size_t n) { u8* d = dest; const u8* s = src; From b0cc590398a6bf68b4f7670e82dc660c8fa8aa7d Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Tue, 10 Mar 2026 00:46:55 +0100 Subject: [PATCH 03/19] add docs --- docs/doxygen/groups.dox | 2 +- docs/source/pages/api/trap.rst | 7 ++ include/trap/trap.h | 104 ++++++++++++++++-- src/kernel/interrupt/trap_handler.c | 46 ++++++++ .../physical_memory/manager.c | 1 + src/lib/trap/trap.c | 15 ++- 6 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 docs/source/pages/api/trap.rst create mode 100644 src/kernel/interrupt/trap_handler.c diff --git a/docs/doxygen/groups.dox b/docs/doxygen/groups.dox index 96d779b5..4da1ff4d 100644 --- a/docs/doxygen/groups.dox +++ b/docs/doxygen/groups.dox @@ -9,8 +9,8 @@ @defgroup stdbigos @defgroup sbi @defgroup csr - @defgroup trap @defgroup types @defgroup string @defgroup bitutils +@defgroup trap Interrupt and exception handling */ diff --git a/docs/source/pages/api/trap.rst b/docs/source/pages/api/trap.rst new file mode 100644 index 00000000..dd67eb09 --- /dev/null +++ b/docs/source/pages/api/trap.rst @@ -0,0 +1,7 @@ +Trap API +======== + +:Date: 09-03-2026 + + +.. doxygengroup:: trap diff --git a/include/trap/trap.h b/include/trap/trap.h index ab5f2ca7..aa3e8aad 100644 --- a/include/trap/trap.h +++ b/include/trap/trap.h @@ -1,9 +1,18 @@ +/** + * @file trap.h + * @brief Supervisor-mode trap dispatch and context transition helpers. + */ + #ifndef KERNEL_TRAP_H_ #define KERNEL_TRAP_H_ #include #include +/** + * @ingroup trap + * @brief Encoded interrupt cause values from the RISC-V `scause` CSR. + */ typedef enum trap_interrupt_type { TRAP_INT_S_SOFTWARE = 1, TRAP_INT_S_TIMER = 5, @@ -12,6 +21,7 @@ typedef enum trap_interrupt_type { // >=16 are for platform use } trap_interrupt_type_t; +/** @brief Encoded exception cause values from the RISC-V `scause` CSR. */ typedef enum trap_exception_type { TRAP_EXC_INSTR_ADDRESS_MISALIGNED = 0, TRAP_EXC_INSTR_ACCESS_FAULT = 1, @@ -32,8 +42,18 @@ typedef enum trap_exception_type { // >=64 reserved } trap_exception_type_t; +/** + * @ingroup trap + * @brief Saved trap context. + * + * Structure representing the saved context of a trap, including general-purpose registers and relevant CSRs. + * It is also used for state transitions (e.g from S-mode to U-mode) by preparing the structure on the + * target stack and performing a trap return (see `trap_utils_prepare_stack_for_transition` and + * `trap_restore_with_cleanup`). + */ typedef struct trap_frame { union { + /** @brief General-purpose registers */ reg_t gpr[32]; struct { reg_t zero; @@ -70,33 +90,99 @@ typedef struct trap_frame { reg_t t6; }; }; + /** @brief value of `sepc` CSR */ reg_t sepc; + /** @brief value of `sstatus` CSR */ reg_t sstatus; } trap_frame_t; +/** + * @ingroup trap + * @brief Trap handler callback executed by the trap trampoline. + */ typedef void (*pfn_trap_handler_t)(trap_frame_t* tf); +/** + * @ingroup trap + * @brief Continuation callback used by stack transition helpers. + */ typedef void (*pfn_continuation_t)(void* user); -static inline bool trap_is_interrupt(reg_t cause) { - return (ireg_t)cause < 0; -} +/** + * @ingroup trap + * @brief Returns `true` if a trap cause corresponds to an interrupt. + * @param cause Raw `scause` value. + * @return `true` if the cause is an interrupt, `false` if it is an exception. + */ +bool trap_is_interrupt(reg_t cause); -static inline trap_interrupt_type_t trap_get_interrupt_code(reg_t cause) { - return (cause << 1) >> 1; // strip highest bit -} +/** + * @ingroup trap + * @brief Extracts interrupt code from `scause` by clearing the interrupt bit. + * @param cause Raw `scause` value. + * @return Interrupt code. + */ +trap_interrupt_type_t trap_get_interrupt_code(reg_t cause); -static inline trap_exception_type_t trap_get_exception_code(reg_t cause) { - return cause; -} +/** + * @ingroup trap + * @brief Returns exception code from `scause`. + * @param cause Raw `scause` value. + * @return Exception code. + */ +trap_exception_type_t trap_get_exception_code(reg_t cause); +/** + * @ingroup trap + * @brief Installs trap entry and sets kernel trap callback. + * + * @param handler Non-null C trap handler. + * + * @retval ERR_NONE - success + */ +[[gnu::nonnull]] error_t trap_init(pfn_trap_handler_t handler); +/** + * @ingroup trap + * @brief Prepares a stack so trap restore can continue from a supplied frame. + * + * @param stack In/out top of the kernel stack pointer after the transition. Updated to point to the new top after + * pushing the trap frame. + * @param tf Trap frame copied onto the target stack. + * + * @retval ERR_NONE - success + */ [[gnu::nonnull]] error_t trap_utils_prepare_stack_for_transition(void** stack, const trap_frame_t* tf); +/** + * @ingroup trap + * @brief Switches to `stack` and transfers control to `continuation(user)`. + * + * This is a helper routine for performing stack/context transitions, e.g. when + * migrating from the boot stack to a per-hart kernel stack. The `continuation` + * callback is executed after the stack switch, and receives a user-provided pointer for context. + * + * @param stack Top of the kernel stack to switch to. + * @param user User-provided pointer passed to the continuation callback. + * @param continuation Non-null callback executed after the stack switch. + */ [[noreturn, gnu::nonnull(1, 3)]] void trap_utils_jump_with_stack(void* stack, void* user, pfn_continuation_t continuation); +/** + * @ingroup trap + * @brief Restores trap context from `stack`, optionally running `cleanup(user)` first on the new stack. + * + * This is a helper routine for performing trap return with an optional cleanup step. If `cleanup` is non-null, + * it is executed on the new stack before performing the trap return to user mode. + * The `user` pointer is passed to the cleanup callback for context. + * + * @param stack Top of the kernel stack containing the trap frame to restore. + * @param user User-provided pointer passed to the cleanup callback. + * @param cleanup Optional callback executed before trap return. If null, the trap return is performed immediately + * without cleanup. + */ [[noreturn, gnu::nonnull(1)]] void trap_restore_with_cleanup(void* stack, void* user, pfn_continuation_t cleanup); diff --git a/src/kernel/interrupt/trap_handler.c b/src/kernel/interrupt/trap_handler.c new file mode 100644 index 00000000..8c7b3760 --- /dev/null +++ b/src/kernel/interrupt/trap_handler.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +static void handle_irq(trap_interrupt_type_t irq) { + switch (irq) { + case TRAP_INT_S_TIMER: + case TRAP_INT_S_SOFTWARE: + case TRAP_INT_S_EXTERNAL: + case TRAP_INT_COUNTER_OVERFLOW: return; + + default: + // we've got reserved signal + DEBUG_PRINTF("interrupt: got reserved number %lu\n", (u64)irq); + return; + } +} + +static void handle_exc(trap_exception_type_t exc, trap_frame_t* tf) { + [[maybe_unused]] reg_t stval = CSR_READ_RELAXED(stval); + + switch (exc) { + case TRAP_EXC_ENV_CALL_U: + intr_enable(); + tf->sepc += 4; // skip ECALL instruction + + // do something, registers are available in tf + + intr_disable(); + return; + default: + // we've got reserved exception + DEBUG_PRINTF("exception: got reserved number %lu\n", (u64)exc); + return; + } +} + +void kernel_trap_handler(trap_frame_t* tf) { + reg_t scause = CSR_READ_RELAXED(scause); + if (trap_is_interrupt(scause)) { + handle_irq(trap_get_interrupt_code(scause)); + } else { + handle_exc(trap_get_exception_code(scause), tf); + } +} diff --git a/src/kernel/memory_management/physical_memory/manager.c b/src/kernel/memory_management/physical_memory/manager.c index 1431d8f8..a465c06b 100644 --- a/src/kernel/memory_management/physical_memory/manager.c +++ b/src/kernel/memory_management/physical_memory/manager.c @@ -7,6 +7,7 @@ #include "allocator.h" #include "stdbigos/address.h" #include "stdbigos/error.h" +#include "stdbigos/types.h" // ========================================== // Private diff --git a/src/lib/trap/trap.c b/src/lib/trap/trap.c index 08409c1b..daac064a 100644 --- a/src/lib/trap/trap.c +++ b/src/lib/trap/trap.c @@ -16,9 +16,6 @@ void trap_handler_trampoline(trap_frame_t* tf) { } error_t trap_init(pfn_trap_handler_t handler) { - if (!handler) - return ERR_BAD_ARG; - CSR_WRITE(sscratch, 0); CSR_WRITE(stvec, trap_entry); g_trap_handler = handler; @@ -33,3 +30,15 @@ error_t trap_utils_prepare_stack_for_transition(void** stack, const trap_frame_t return ERR_NONE; } + +bool trap_is_interrupt(reg_t cause) { + return (ireg_t)cause < 0; +} + +trap_interrupt_type_t trap_get_interrupt_code(reg_t cause) { + return (cause << 1) >> 1; // strip highest bit +} + +trap_exception_type_t trap_get_exception_code(reg_t cause) { + return cause; +} From a56129d8c1f05503795c49c1f25aeaf23da64075 Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Tue, 10 Mar 2026 19:54:25 +0100 Subject: [PATCH 04/19] implement fixes --- include/trap/trap.h | 1 + src/example_machine/entry.c | 6 ++++-- src/example_trap/entry.c | 4 ++-- src/lib/trap/trap.S | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/include/trap/trap.h b/include/trap/trap.h index aa3e8aad..f12a52f8 100644 --- a/include/trap/trap.h +++ b/include/trap/trap.h @@ -35,6 +35,7 @@ typedef enum trap_exception_type { TRAP_EXC_ENV_CALL_S = 9, TRAP_EXC_INSTR_PAGE_FAULT = 12, TRAP_EXC_LOAD_PAGE_FAULT = 13, + TRAP_EXC_STORE_PAGE_FAULT = 15, TRAP_EXC_SOFTWARE_CHECK = 18, TRAP_EXC_HARDWARE_ERROR = 19, // 24-31 designated for custom use diff --git a/src/example_machine/entry.c b/src/example_machine/entry.c index f1aac37f..949684ed 100644 --- a/src/example_machine/entry.c +++ b/src/example_machine/entry.c @@ -19,6 +19,8 @@ static volatile u64* g_mtimecmp = (u64*)(g_clint_base + 0x4000); static const u64 g_quant = 50000llu; +#define INTERRUPT_TIMER_M 7 + void main() { for (u32 i = 0;; ++i) { CSR_CLEAR(mstatus, 8); // disable interrupts @@ -35,7 +37,7 @@ void int_handler() { reg_t int_no = trap_get_interrupt_code(cause); switch (int_no) { - case 7: // M-mode timer + case INTERRUPT_TIMER_M: dputs("got timer interrupt\n"); g_mtimecmp[hartid()] = *g_mtime + g_quant; break; @@ -64,7 +66,7 @@ void start() { CSR_SET(mstatus, 8); // set TIMER in mie - CSR_SET(mie, 1lu << 7); + CSR_SET(mie, 1lu << INTERRUPT_TIMER_M); main(); diff --git a/src/example_trap/entry.c b/src/example_trap/entry.c index aa190e3a..3ee69669 100644 --- a/src/example_trap/entry.c +++ b/src/example_trap/entry.c @@ -45,7 +45,7 @@ void user_fn() { __asm__ volatile("ecall" : "+r"(a0)); DEBUG_PRINTF("syscall returned: 0x%lx\n", a0); - // try do do something bad with sp, so we know kernel will not break + // try to do something bad with sp, so we know kernel will not break __asm__ volatile("mv t0, sp\n\t" "mv sp, zero\n\t" "ecall\n\t" @@ -82,7 +82,7 @@ void kernel_continuation(void* usr) { tf.sp = (reg_t)user_stack_top; tf.sepc = (reg_t)user_fn; // setup user entry point tf.sstatus = CSR_READ_RELAXED(sstatus); - tf.sstatus &= ~(1ull << 8); // clear previous privilage S -> sret will return to U mode + tf.sstatus &= ~(1ull << 8); // clear previous privilege S -> sret will return to U mode if (trap_utils_prepare_stack_for_transition(&kernel_stack_top, &tf) != ERR_NONE) { DEBUG_PRINTF("failed to prepare stack for transition\n"); diff --git a/src/lib/trap/trap.S b/src/lib/trap/trap.S index 97025ec2..585a10f7 100644 --- a/src/lib/trap/trap.S +++ b/src/lib/trap/trap.S @@ -9,7 +9,7 @@ .equ FRAME_SIZE, 0x110 // sscratch should have kernel stack pointer IIF in user-mode -// when comming from kernel-space then it should be 0 +// when coming from kernel-space then it should be 0 // the thread pointer should be at the base of the sscratch location if it is not 0 From 9359f4fb032d6151084d7ef66ae740a55b3f9a55 Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Mon, 16 Mar 2026 20:19:21 +0100 Subject: [PATCH 05/19] remove zero register from gpr array --- include/trap/trap.h | 3 +-- src/lib/trap/trap.S | 16 ++++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/include/trap/trap.h b/include/trap/trap.h index f12a52f8..95943f1a 100644 --- a/include/trap/trap.h +++ b/include/trap/trap.h @@ -55,9 +55,8 @@ typedef enum trap_exception_type { typedef struct trap_frame { union { /** @brief General-purpose registers */ - reg_t gpr[32]; + reg_t gpr[31]; struct { - reg_t zero; reg_t ra; reg_t sp; reg_t gp; diff --git a/src/lib/trap/trap.S b/src/lib/trap/trap.S index 585a10f7..ed99c4e9 100644 --- a/src/lib/trap/trap.S +++ b/src/lib/trap/trap.S @@ -42,15 +42,15 @@ save_regs: csrrw t0, sscratch, zero // Save everything, but sp (x2) - .irp i, 0,1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 - sd x\i, \i*8(sp) + .irp i, 1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 + sd x\i, (\i - 1)*8(sp) .endr csrr t0, sepc csrr t1, sstatus - sd t0, 32*8(sp) - sd t1, 33*8(sp) + sd t0, 31*8(sp) + sd t1, 32*8(sp) andi t1, t1, SSTATUS_SPP bnez t1, run_handler @@ -67,8 +67,8 @@ run_handler: call trap_handler_trampoline kernel_trap_restore: - ld t0, 32*8(sp) - ld t1, 33*8(sp) + ld t0, 31*8(sp) + ld t1, 32*8(sp) // disable interrupts and restore sstatus and sepc and t2, t1, ~SSTATUS_SIE @@ -86,10 +86,10 @@ restore_umode: load_regs: .irp i, 1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 - ld x\i, \i*8(sp) + ld x\i, (\i - 1)*8(sp) .endr - ld sp, 2*8(sp) + ld sp, (2 - 1)*8(sp) sret trap_restore_with_cleanup: From 47f0bccd0e333e6233820ab621b046e83a24d94e Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Thu, 19 Mar 2026 23:51:28 +0100 Subject: [PATCH 06/19] cleanup1 --- CMake/toolchains/riscv64-common.cmake | 2 +- docs/source/pages/api/libs/index.rst | 1 + docs/source/pages/api/libs/stdbigos/index.rst | 1 - docs/source/pages/api/libs/stdbigos/trap.rst | 5 -- docs/source/pages/api/libs/trap.rst | 5 ++ docs/source/pages/api/trap.rst | 7 -- include/stdbigos/csr.h | 21 ++--- include/stdbigos/csr_vals.h | 22 +++++ include/stdbigos/trap.h | 65 --------------- include/stdbigos/types.h | 2 + include/trap/trap.h | 43 ++++------ src/example_machine/entry.c | 4 +- src/example_trap/entry.c | 4 +- src/lib/trap/trap.S | 83 +++++++++++-------- src/lib/trap/trap.c | 16 +++- 15 files changed, 124 insertions(+), 157 deletions(-) delete mode 100644 docs/source/pages/api/libs/stdbigos/trap.rst create mode 100644 docs/source/pages/api/libs/trap.rst delete mode 100644 docs/source/pages/api/trap.rst create mode 100644 include/stdbigos/csr_vals.h delete mode 100644 include/stdbigos/trap.h diff --git a/CMake/toolchains/riscv64-common.cmake b/CMake/toolchains/riscv64-common.cmake index 88297b97..f280c1c4 100644 --- a/CMake/toolchains/riscv64-common.cmake +++ b/CMake/toolchains/riscv64-common.cmake @@ -10,7 +10,7 @@ set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_PROCESSOR riscv64) set(CMAKE_CROSSCOMPILING 1) -set(RISCV_ARCH "rv64ima_zicsr_zifencei" CACHE STRING "RISC-V instruction set") +set(RISCV_ARCH "rv64imac_zicsr_zifencei" CACHE STRING "RISC-V instruction set") set(RISCV_ABI "lp64" CACHE STRING "RISC-V abi") set(RISCV_CMODEL "medany" CACHE STRING "RISC-V cmodel") diff --git a/docs/source/pages/api/libs/index.rst b/docs/source/pages/api/libs/index.rst index 4de83c84..63f999cc 100644 --- a/docs/source/pages/api/libs/index.rst +++ b/docs/source/pages/api/libs/index.rst @@ -6,4 +6,5 @@ Bigos Libraries :maxdepth: 1 dt + trap stdbigos/index diff --git a/docs/source/pages/api/libs/stdbigos/index.rst b/docs/source/pages/api/libs/stdbigos/index.rst index 66e694b3..94ba6004 100644 --- a/docs/source/pages/api/libs/stdbigos/index.rst +++ b/docs/source/pages/api/libs/stdbigos/index.rst @@ -7,7 +7,6 @@ Stdbigos sbi csr - trap types string bitutils diff --git a/docs/source/pages/api/libs/stdbigos/trap.rst b/docs/source/pages/api/libs/stdbigos/trap.rst deleted file mode 100644 index b04f0322..00000000 --- a/docs/source/pages/api/libs/stdbigos/trap.rst +++ /dev/null @@ -1,5 +0,0 @@ -==== -Trap -==== - -.. doxygengroup:: trap diff --git a/docs/source/pages/api/libs/trap.rst b/docs/source/pages/api/libs/trap.rst new file mode 100644 index 00000000..e8157d7d --- /dev/null +++ b/docs/source/pages/api/libs/trap.rst @@ -0,0 +1,5 @@ +=============== +RISC-V trap API +=============== + +.. doxygengroup:: trap diff --git a/docs/source/pages/api/trap.rst b/docs/source/pages/api/trap.rst deleted file mode 100644 index dd67eb09..00000000 --- a/docs/source/pages/api/trap.rst +++ /dev/null @@ -1,7 +0,0 @@ -Trap API -======== - -:Date: 09-03-2026 - - -.. doxygengroup:: trap diff --git a/include/stdbigos/csr.h b/include/stdbigos/csr.h index 54f83648..153361b1 100644 --- a/include/stdbigos/csr.h +++ b/include/stdbigos/csr.h @@ -2,6 +2,13 @@ #define STDBIGOS_CSR #include "types.h" +#include "csr_vals.h" + +/** + * @addtogroup stdbigos + * @addtogroup csr + * @{ + */ #define CSR_SWAP(csr, val) \ ({ \ @@ -57,15 +64,6 @@ __asm__ volatile("csrc " #csr ", %0" : : "rK"(_val) : "memory"); \ }) -/// @addtogroup stdbigos -/// @{ -/// @addtogroup csr -/// @{ - -static inline u32 hartid() { - return CSR_READ_RELAXED(mhartid); -} - static inline void wfi() { __asm__ volatile("wfi" ::: "memory"); } @@ -74,13 +72,12 @@ static inline void ebreak() { __asm__ volatile("ebreak" ::: "memory"); } static inline void intr_enable() { - CSR_SET(sstatus, 1 << 1); + CSR_SET(sstatus, CSR_SSTATUS_SIE); } static inline void intr_disable() { - CSR_CLEAR(sstatus, 1 << 1); + CSR_CLEAR(sstatus, CSR_SSTATUS_SIE); } -/// @} /// @} #endif // !STDBIGOS_CSR diff --git a/include/stdbigos/csr_vals.h b/include/stdbigos/csr_vals.h new file mode 100644 index 00000000..31f4080b --- /dev/null +++ b/include/stdbigos/csr_vals.h @@ -0,0 +1,22 @@ +#ifndef STDBIGOS_CSR_VALS_H +#define STDBIGOS_CSR_VALS_H + +#define CSR_SSTATUS_SIE (1ul << 1) +#define CSR_SSTATUS_SPIE (1ul << 5) +#define CSR_SSTATUS_UBE (1ul << 6) +#define CSR_SSTATUS_SPP (1ul << 8) +#define CSR_SSTATUS_VS_OFFSET 9 +#define CSR_SSTATUS_VS_MASK 0b11 +#define CSR_SSTATUS_FS_OFFSET 13 +#define CSR_SSTATUS_FS_MASK 0b11 +#define CSR_SSTATUS_XS_OFFSET 15 +#define CSR_SSTATUS_XS_MASK 0b11 +#define CSR_SSTATUS_SUM (1ul << 18) +#define CSR_SSTATUS_MXR (1ul << 19) +#define CSR_SSTATUS_SPELP (1ul << 23) +#define CSR_SSTATUS_SDT (1ul << 24) +#define CSR_SSTATUS_UXL_OFFSET 32 +#define CSR_SSTATUS_UXL_MASK 0b11 +#define CSR_SSTATUS_SD (1ul << 63) + +#endif diff --git a/include/stdbigos/trap.h b/include/stdbigos/trap.h deleted file mode 100644 index 4552f1ce..00000000 --- a/include/stdbigos/trap.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef STDBIGOS_TRAP -#define STDBIGOS_TRAP - -#include "types.h" - -/// @ingroup stdbigos -/// @{ -/// @ingroup trap -/// @{ - -typedef enum InterruptType { - IntSSoftware = 1, - // 2 is reserved - IntMSoftware = 3, - // 4 is reserved - IntSTimer = 5, - // 6 is reserved - IntMTimer = 7, - // 8 is reserved - IntSExternal = 9, - // 10 is reserved - IntMExternal = 11, - // 12-15 reserved - // >=16 are for platform use -} InterruptType; - -typedef enum ExceptionType { - ExcAddressMisaligned = 0, - ExcInstrAccessFault = 1, - ExcIllegalInstr = 2, - ExcBreakpoint = 3, - ExcLoadAddrMisaligned = 4, - ExcLoadAccessFault = 5, - ExcStoreAddressMisaligned = 6, - ExcStoreAccessFault = 7, - ExcEnvCallU = 8, - ExcEnvCallS = 9, - // 12 is reserved - ExcEnvCallM = 11, - ExcInstrPageFault = 12, - ExcLoadPageFault = 13, - // 14 is reserved - ExcStorePageFault = 15, - // 16-23 reserved - // 24-31 designated for custom use - // 32-47 reserved - // 48-64 designated for custom use -} ExceptionType; - -static inline bool is_interrupt(reg_t cause) { - return (ireg_t)cause < 0; -} - -static inline InterruptType get_interrupt_code(reg_t cause) { - return (cause << 1) >> 1; // strip highest bit -} - -static inline ExceptionType get_exception_code(reg_t cause) { - return cause; -} - -/// @} -/// @} - -#endif // !STDBIGOS_TRAP diff --git a/include/stdbigos/types.h b/include/stdbigos/types.h index ca0c2101..9814e777 100644 --- a/include/stdbigos/types.h +++ b/include/stdbigos/types.h @@ -9,6 +9,7 @@ /// @addtogroup types /// @{ +// NOLINTBEGIN(readability-identifier-naming) typedef uint8_t u8; typedef uint16_t u16; @@ -28,6 +29,7 @@ typedef int64_t i64; typedef unsigned long reg_t; typedef signed long ireg_t; +// NOLINTEND(readability-identifier-naming) static_assert(sizeof(reg_t) * 8 == __riscv_xlen); typedef enum { diff --git a/include/trap/trap.h b/include/trap/trap.h index 95943f1a..5b88ff93 100644 --- a/include/trap/trap.h +++ b/include/trap/trap.h @@ -1,18 +1,15 @@ -/** - * @file trap.h - * @brief Supervisor-mode trap dispatch and context transition helpers. - */ - -#ifndef KERNEL_TRAP_H_ -#define KERNEL_TRAP_H_ +#ifndef TRAP_H +#define TRAP_H #include #include /** - * @ingroup trap - * @brief Encoded interrupt cause values from the RISC-V `scause` CSR. + * @addtogroup trap + * @{ */ + +/// @brief Encoded interrupt cause values from the RISC-V `scause` CSR. typedef enum trap_interrupt_type { TRAP_INT_S_SOFTWARE = 1, TRAP_INT_S_TIMER = 5, @@ -44,7 +41,6 @@ typedef enum trap_exception_type { } trap_exception_type_t; /** - * @ingroup trap * @brief Saved trap context. * * Structure representing the saved context of a trap, including general-purpose registers and relevant CSRs. @@ -54,7 +50,7 @@ typedef enum trap_exception_type { */ typedef struct trap_frame { union { - /** @brief General-purpose registers */ + /// General-purpose registers reg_t gpr[31]; struct { reg_t ra; @@ -90,25 +86,26 @@ typedef struct trap_frame { reg_t t6; }; }; - /** @brief value of `sepc` CSR */ + /// value of `sepc` CSR reg_t sepc; - /** @brief value of `sstatus` CSR */ + /// value of `sstatus` CSR reg_t sstatus; + /// value of `stval` CSR + reg_t stval; + /// value of `scause` CSR + reg_t scause; } trap_frame_t; /** - * @ingroup trap * @brief Trap handler callback executed by the trap trampoline. */ typedef void (*pfn_trap_handler_t)(trap_frame_t* tf); /** - * @ingroup trap * @brief Continuation callback used by stack transition helpers. */ typedef void (*pfn_continuation_t)(void* user); /** - * @ingroup trap * @brief Returns `true` if a trap cause corresponds to an interrupt. * @param cause Raw `scause` value. * @return `true` if the cause is an interrupt, `false` if it is an exception. @@ -116,7 +113,6 @@ typedef void (*pfn_continuation_t)(void* user); bool trap_is_interrupt(reg_t cause); /** - * @ingroup trap * @brief Extracts interrupt code from `scause` by clearing the interrupt bit. * @param cause Raw `scause` value. * @return Interrupt code. @@ -124,7 +120,6 @@ bool trap_is_interrupt(reg_t cause); trap_interrupt_type_t trap_get_interrupt_code(reg_t cause); /** - * @ingroup trap * @brief Returns exception code from `scause`. * @param cause Raw `scause` value. * @return Exception code. @@ -132,31 +127,28 @@ trap_interrupt_type_t trap_get_interrupt_code(reg_t cause); trap_exception_type_t trap_get_exception_code(reg_t cause); /** - * @ingroup trap * @brief Installs trap entry and sets kernel trap callback. * * @param handler Non-null C trap handler. * - * @retval ERR_NONE - success + * @retval ERR_NONE success */ [[gnu::nonnull]] error_t trap_init(pfn_trap_handler_t handler); /** - * @ingroup trap * @brief Prepares a stack so trap restore can continue from a supplied frame. * * @param stack In/out top of the kernel stack pointer after the transition. Updated to point to the new top after * pushing the trap frame. * @param tf Trap frame copied onto the target stack. * - * @retval ERR_NONE - success + * @retval ERR_NONE success */ [[gnu::nonnull]] error_t trap_utils_prepare_stack_for_transition(void** stack, const trap_frame_t* tf); /** - * @ingroup trap * @brief Switches to `stack` and transfers control to `continuation(user)`. * * This is a helper routine for performing stack/context transitions, e.g. when @@ -171,7 +163,6 @@ error_t trap_utils_prepare_stack_for_transition(void** stack, const trap_frame_t void trap_utils_jump_with_stack(void* stack, void* user, pfn_continuation_t continuation); /** - * @ingroup trap * @brief Restores trap context from `stack`, optionally running `cleanup(user)` first on the new stack. * * This is a helper routine for performing trap return with an optional cleanup step. If `cleanup` is non-null, @@ -186,4 +177,6 @@ void trap_utils_jump_with_stack(void* stack, void* user, pfn_continuation_t cont [[noreturn, gnu::nonnull(1)]] void trap_restore_with_cleanup(void* stack, void* user, pfn_continuation_t cleanup); -#endif +/// @} + +#endif // !TRAP_H diff --git a/src/example_machine/entry.c b/src/example_machine/entry.c index 949684ed..7af9415f 100644 --- a/src/example_machine/entry.c +++ b/src/example_machine/entry.c @@ -39,7 +39,7 @@ void int_handler() { switch (int_no) { case INTERRUPT_TIMER_M: dputs("got timer interrupt\n"); - g_mtimecmp[hartid()] = *g_mtime + g_quant; + g_mtimecmp[CSR_READ(mhartid)] = *g_mtime + g_quant; break; default: dprintf("unknown interrupt (%ld)\n", int_no); break; } @@ -60,7 +60,7 @@ void start() { CSR_WRITE(mtvec, int_handler); // request a timer interrupt - g_mtimecmp[hartid()] = *g_mtime + g_quant; + g_mtimecmp[CSR_READ(mhartid)] = *g_mtime + g_quant; // set MIE in mstatus CSR_SET(mstatus, 8); diff --git a/src/example_trap/entry.c b/src/example_trap/entry.c index 3ee69669..6d56e105 100644 --- a/src/example_trap/entry.c +++ b/src/example_trap/entry.c @@ -5,7 +5,7 @@ #include void handle_exc(trap_exception_type_t exc, trap_frame_t* tf) { - [[maybe_unused]] reg_t stval = CSR_READ_RELAXED(stval); + [[maybe_unused]] reg_t stval = tf->stval; switch (exc) { case TRAP_EXC_ENV_CALL_U: @@ -25,7 +25,7 @@ void handle_exc(trap_exception_type_t exc, trap_frame_t* tf) { } void my_trap_handler(trap_frame_t* tf) { - reg_t scause = CSR_READ_RELAXED(scause); + reg_t scause = tf->scause; if (trap_is_interrupt(scause)) { DEBUG_PRINTF("got interrupt: %lu\n", (u64)trap_get_interrupt_code(scause)); } else { diff --git a/src/lib/trap/trap.S b/src/lib/trap/trap.S index ed99c4e9..c9d25c7d 100644 --- a/src/lib/trap/trap.S +++ b/src/lib/trap/trap.S @@ -4,9 +4,9 @@ .global trap_restore_with_cleanup .global trap_utils_jump_with_stack -.equ SSTATUS_SIE, 0x00000002 // Supervisor Interrupt Enable -.equ SSTATUS_SPP, 0x00000100 // Supervisor Previous Privilege (1 = S-mode, 0 = U-mode) -.equ FRAME_SIZE, 0x110 +.equ FRAME_SIZE, 0x120 + +#include // sscratch should have kernel stack pointer IIF in user-mode // when coming from kernel-space then it should be 0 @@ -16,44 +16,52 @@ .align 4 trap_entry: csrrw sp, sscratch, sp - csrrw t0, sscratch, t0 + csrrw a0, sscratch, a0 // sp <- sscratch - // t0 <- old sp - // sscratch <- old t0 - bnez sp, save_regs + // a0 <- old sp + // sscratch <- old a0 + bnez sp, .Lsave_regs -from_smode: +.Lfrom_smode: // sp == 0 - // t0 <- old sp (kernel stack pointer) - // sscratch <- old t0 + // a0 <- old sp (kernel stack pointer) + // sscratch <- old a0 // someone could be stupid and have made kernel's sp not 8-byte aligned -> align down to 16-bytes - and sp, t0, -16 + and sp, a0, -16 + + // we have our hart's info in tp + // TODO: use this to validate if we are not at the end of the stack + // otherwise setup panic stack and panic -save_regs: +.Lsave_regs: // sp != 0 - valid kernel stack pointer - // t0 <- old sp - // sscratch <- old t0 + // a0 <- old sp + // sscratch <- old a0 add sp, sp, -FRAME_SIZE // save original sp, restore original t0 in sscratch and restore sscratch invariant - sd t0, 2*8(sp) - csrrw t0, sscratch, zero + sd a0, 1*8(sp) + + csrrw a0, sscratch, zero // Save everything, but sp (x2) .irp i, 1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 sd x\i, (\i - 1)*8(sp) .endr - csrr t0, sepc - csrr t1, sstatus - - sd t0, 31*8(sp) - sd t1, 32*8(sp) + csrr a0, sepc + csrr a1, sstatus + csrr a2, stval + csrr a3, scause + sd a0, 31*8(sp) + sd a1, 32*8(sp) + sd a2, 33*8(sp) + sd a3, 34*8(sp) - andi t1, t1, SSTATUS_SPP - bnez t1, run_handler + andi a1, a1, CSR_SSTATUS_SPP + bnez a1, .Lrun_handler .option push .option norelax @@ -62,34 +70,37 @@ save_regs: ld tp, FRAME_SIZE(sp) -run_handler: +.Lrun_handler: mv a0, sp call trap_handler_trampoline kernel_trap_restore: - ld t0, 31*8(sp) - ld t1, 32*8(sp) + ld a0, 31*8(sp) + ld a1, 32*8(sp) // disable interrupts and restore sstatus and sepc - and t2, t1, ~SSTATUS_SIE - csrw sstatus, t2 - csrw sepc, t0 + and a2, a1, ~CSR_SSTATUS_SIE + // set double trap bit + li t0, CSR_SSTATUS_SDT + or a2, a2, t0 + csrw sstatus, a2 + csrw sepc, a0 - and t2, t1, SSTATUS_SPP - bnez t2, load_regs + and a2, a1, CSR_SSTATUS_SPP + bnez a2, .Lload_regs -restore_umode: +.Lrestore_umode: sd tp, FRAME_SIZE(sp) - add t0, sp, FRAME_SIZE - csrw sscratch, t0 + add a0, sp, FRAME_SIZE + csrw sscratch, a0 -load_regs: +.Lload_regs: .irp i, 1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 ld x\i, (\i - 1)*8(sp) .endr - ld sp, (2 - 1)*8(sp) + ld sp, 1*8(sp) sret trap_restore_with_cleanup: diff --git a/src/lib/trap/trap.c b/src/lib/trap/trap.c index daac064a..0b4229eb 100644 --- a/src/lib/trap/trap.c +++ b/src/lib/trap/trap.c @@ -11,14 +11,28 @@ extern void trap_entry(); static pfn_trap_handler_t g_trap_handler = nullptr; void trap_handler_trampoline(trap_frame_t* tf) { + // disable: + // - floating point extensions + // - vector extensions + // - MXR - memory executable readable + // - SUM - supervisor read userspace + // - SDT - double trap detection + CSR_CLEAR(sstatus, + (CSR_SSTATUS_VS_MASK << CSR_SSTATUS_VS_OFFSET) | + (CSR_SSTATUS_FS_MASK << CSR_SSTATUS_FS_OFFSET) | + CSR_SSTATUS_MXR | + CSR_SSTATUS_SUM | + CSR_SSTATUS_SDT | + 0 + ); if (g_trap_handler) g_trap_handler(tf); } error_t trap_init(pfn_trap_handler_t handler) { + g_trap_handler = handler; CSR_WRITE(sscratch, 0); CSR_WRITE(stvec, trap_entry); - g_trap_handler = handler; return ERR_NONE; } From 3fa270a41d89dd8ae9816649d9d260210155656b Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Thu, 19 Mar 2026 23:54:54 +0100 Subject: [PATCH 07/19] cleanup2 --- src/kernel/interrupt/trap_handler.c | 46 ----------------------------- 1 file changed, 46 deletions(-) delete mode 100644 src/kernel/interrupt/trap_handler.c diff --git a/src/kernel/interrupt/trap_handler.c b/src/kernel/interrupt/trap_handler.c deleted file mode 100644 index 8c7b3760..00000000 --- a/src/kernel/interrupt/trap_handler.c +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include -#include -#include - -static void handle_irq(trap_interrupt_type_t irq) { - switch (irq) { - case TRAP_INT_S_TIMER: - case TRAP_INT_S_SOFTWARE: - case TRAP_INT_S_EXTERNAL: - case TRAP_INT_COUNTER_OVERFLOW: return; - - default: - // we've got reserved signal - DEBUG_PRINTF("interrupt: got reserved number %lu\n", (u64)irq); - return; - } -} - -static void handle_exc(trap_exception_type_t exc, trap_frame_t* tf) { - [[maybe_unused]] reg_t stval = CSR_READ_RELAXED(stval); - - switch (exc) { - case TRAP_EXC_ENV_CALL_U: - intr_enable(); - tf->sepc += 4; // skip ECALL instruction - - // do something, registers are available in tf - - intr_disable(); - return; - default: - // we've got reserved exception - DEBUG_PRINTF("exception: got reserved number %lu\n", (u64)exc); - return; - } -} - -void kernel_trap_handler(trap_frame_t* tf) { - reg_t scause = CSR_READ_RELAXED(scause); - if (trap_is_interrupt(scause)) { - handle_irq(trap_get_interrupt_code(scause)); - } else { - handle_exc(trap_get_exception_code(scause), tf); - } -} From 54be6309cc96883db390cdccafb04ca1d2992962 Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Thu, 19 Mar 2026 23:57:19 +0100 Subject: [PATCH 08/19] cleanup3 --- include/stdbigos/csr_vals.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/stdbigos/csr_vals.h b/include/stdbigos/csr_vals.h index 31f4080b..b0d8e668 100644 --- a/include/stdbigos/csr_vals.h +++ b/include/stdbigos/csr_vals.h @@ -1,5 +1,5 @@ -#ifndef STDBIGOS_CSR_VALS_H -#define STDBIGOS_CSR_VALS_H +#ifndef STDBIGOS_CSR_VALS +#define STDBIGOS_CSR_VALS #define CSR_SSTATUS_SIE (1ul << 1) #define CSR_SSTATUS_SPIE (1ul << 5) @@ -19,4 +19,4 @@ #define CSR_SSTATUS_UXL_MASK 0b11 #define CSR_SSTATUS_SD (1ul << 63) -#endif +#endif // !STDBIGOS_CSR_VALS From a88cd928fc1055173f8573f3f54f1d906655a1c2 Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Thu, 19 Mar 2026 23:58:04 +0100 Subject: [PATCH 09/19] cleanup4 --- include/stdbigos/types.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/stdbigos/types.h b/include/stdbigos/types.h index 9814e777..ca0c2101 100644 --- a/include/stdbigos/types.h +++ b/include/stdbigos/types.h @@ -9,7 +9,6 @@ /// @addtogroup types /// @{ -// NOLINTBEGIN(readability-identifier-naming) typedef uint8_t u8; typedef uint16_t u16; @@ -29,7 +28,6 @@ typedef int64_t i64; typedef unsigned long reg_t; typedef signed long ireg_t; -// NOLINTEND(readability-identifier-naming) static_assert(sizeof(reg_t) * 8 == __riscv_xlen); typedef enum { From d469a16722dc0194d444e29db7fa2816e0929bbc Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Fri, 20 Mar 2026 00:53:02 +0100 Subject: [PATCH 10/19] add cooperative example --- CMake/toolchains/riscv64-common.cmake | 2 +- include/trap/trap.h | 2 + src/example_umode_concurrency/CMakeLists.txt | 5 + src/example_umode_concurrency/entry.c | 147 +++++++++++++++++++ src/lib/trap/trap.c | 2 +- 5 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 src/example_umode_concurrency/CMakeLists.txt create mode 100644 src/example_umode_concurrency/entry.c diff --git a/CMake/toolchains/riscv64-common.cmake b/CMake/toolchains/riscv64-common.cmake index f280c1c4..88297b97 100644 --- a/CMake/toolchains/riscv64-common.cmake +++ b/CMake/toolchains/riscv64-common.cmake @@ -10,7 +10,7 @@ set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_PROCESSOR riscv64) set(CMAKE_CROSSCOMPILING 1) -set(RISCV_ARCH "rv64imac_zicsr_zifencei" CACHE STRING "RISC-V instruction set") +set(RISCV_ARCH "rv64ima_zicsr_zifencei" CACHE STRING "RISC-V instruction set") set(RISCV_ABI "lp64" CACHE STRING "RISC-V abi") set(RISCV_CMODEL "medany" CACHE STRING "RISC-V cmodel") diff --git a/include/trap/trap.h b/include/trap/trap.h index 5b88ff93..39383b19 100644 --- a/include/trap/trap.h +++ b/include/trap/trap.h @@ -96,6 +96,8 @@ typedef struct trap_frame { reg_t scause; } trap_frame_t; +static_assert(sizeof(trap_frame_t) == 35 * sizeof(reg_t)); + /** * @brief Trap handler callback executed by the trap trampoline. */ diff --git a/src/example_umode_concurrency/CMakeLists.txt b/src/example_umode_concurrency/CMakeLists.txt new file mode 100644 index 00000000..2ce623a2 --- /dev/null +++ b/src/example_umode_concurrency/CMakeLists.txt @@ -0,0 +1,5 @@ +SETUP_EXECUTABLE(example_umode_concurrency) + +target_link_libraries(example_umode_concurrency PRIVATE startup trap Debug stdbigos) + +ADD_QEMU_TARGET(example_umode_concurrency) diff --git a/src/example_umode_concurrency/entry.c b/src/example_umode_concurrency/entry.c new file mode 100644 index 00000000..288cec3e --- /dev/null +++ b/src/example_umode_concurrency/entry.c @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include +#include +#include + +enum { + SYSCALL_PRINT = 1, +}; + +#define TASK_COUNT 2 + +static const u64 TIMER_QUANTUM = 50000; +static const reg_t SIE_STIE = (1ul << TRAP_INT_S_TIMER); + +typedef struct { + trap_frame_t tf; + bool initialized; +} task_ctx_t; + +static task_ctx_t g_tasks[TASK_COUNT]; +static u32 g_current_task = 0; + +// as our handlers are non-preemptive we only need one kernel stack +// but if we wanted kernel preemption, we would need to have one stack +// per task +static u8 g_kernel_mode_stack[4096] __attribute__((aligned(4096))); + +static u8 g_user_mode_stack_a[4096] __attribute__((aligned(4096))); +static u8 g_user_mode_stack_b[4096] __attribute__((aligned(4096))); + +static inline u64 read_time() { + u64 now; + __asm__ volatile("rdtime %0" : "=r"(now)); + return now; +} + +static void arm_next_timer() { + (void)sbi_set_timer(read_time() + TIMER_QUANTUM); +} + +static inline void user_sys_print(const char* str) { + register reg_t a0 __asm__("a0") = (reg_t)str; + register reg_t a7 __asm__("a7") = SYSCALL_PRINT; + __asm__ volatile("ecall" : "+r"(a0) : "r"(a7) : "memory"); +} + +[[noreturn]] void user_task_a() { + while (true) { + user_sys_print("[U-task A] running\n"); + u64 start = read_time(); + while (read_time() - start < 10000); + } +} + +[[noreturn]] void user_task_b() { + while (true) { + user_sys_print("[U-task B] running\n"); + u64 start = read_time(); + while (read_time() - start < 10000); + } +} + +static void init_task(task_ctx_t* task, void (*entry)(), void* user_stack_top) { + task->tf = (trap_frame_t){0}; + task->tf.sp = (reg_t)user_stack_top; + task->tf.sepc = (reg_t)entry; + task->tf.sstatus = CSR_READ_RELAXED(sstatus); + task->tf.sstatus &= ~CSR_SSTATUS_SPP; + task->tf.sstatus |= CSR_SSTATUS_SPIE; + task->initialized = true; +} + +static void handle_syscall(trap_frame_t* tf) { + tf->sepc += 4; + + switch (tf->a7) { + case SYSCALL_PRINT: + dputs((const char*)tf->a0); + tf->a0 = 0; + break; + default: + dprintf("unknown syscall id=%lu\n", (u64)tf->a7); + tf->a0 = (reg_t)-1; + break; + } +} + +static void switch_to_next_task(trap_frame_t* tf) { + g_tasks[g_current_task].tf = *tf; + g_current_task = (g_current_task + 1) % TASK_COUNT; + *tf = g_tasks[g_current_task].tf; +} + +void my_trap_handler(trap_frame_t* tf) { + const reg_t scause = tf->scause; + if (trap_is_interrupt(scause)) { + if (trap_get_interrupt_code(scause) == TRAP_INT_S_TIMER) { + if (read_time() < tf->stval) { + // Spurious timer interrupt, ignore. + return; + } + arm_next_timer(); + switch_to_next_task(tf); + dprintf("switched to task %u\n", g_current_task); + return; + } + + dprintf("unexpected interrupt %lu\n", (u64)trap_get_interrupt_code(scause)); + return; + } + + switch (trap_get_exception_code(scause)) { + case TRAP_EXC_ENV_CALL_U: + handle_syscall(tf); + break; + default: + dprintf("unexpected exception %lu stval=%p\n", (u64)trap_get_exception_code(scause), (void*)tf->stval); + break; + } +} + +void main([[maybe_unused]] u32 hartid, [[maybe_unused]] const void* fdt) { + if (trap_init(my_trap_handler) != ERR_NONE) { + dputs("trap_init failed\n"); + return; + } + + init_task(&g_tasks[0], user_task_a, &g_user_mode_stack_a[sizeof(g_user_mode_stack_a)]); + init_task(&g_tasks[1], user_task_b, &g_user_mode_stack_b[sizeof(g_user_mode_stack_b)]); + + dputs("starting U-mode concurrency example\n"); + CSR_SET(sie, SIE_STIE); + arm_next_timer(); + + void* kernel_stack_top = &g_kernel_mode_stack[sizeof(g_kernel_mode_stack)]; + trap_frame_t tf = g_tasks[g_current_task].tf; + + if (trap_utils_prepare_stack_for_transition(&kernel_stack_top, &tf) != ERR_NONE) { + dputs("failed to prepare transition stack\n"); + return; + } + + trap_restore_with_cleanup(kernel_stack_top, nullptr, nullptr); +} diff --git a/src/lib/trap/trap.c b/src/lib/trap/trap.c index 0b4229eb..150c7d46 100644 --- a/src/lib/trap/trap.c +++ b/src/lib/trap/trap.c @@ -38,7 +38,7 @@ error_t trap_init(pfn_trap_handler_t handler) { error_t trap_utils_prepare_stack_for_transition(void** stack, const trap_frame_t* tf) { u8* sp = (u8*)ALIGN_DOWN((uintptr_t)*stack, 16); - sp = sp - 16 - sizeof(*tf); // leave space for tp and trap frame + sp = sp - ALIGN_UP(16 + sizeof(*tf), 16); // leave space for tp and trap frame memcpy(sp, tf, sizeof(*tf)); *stack = sp; From dc80538f54ac9daa2529e831160d76a675a113f9 Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Fri, 20 Mar 2026 00:55:30 +0100 Subject: [PATCH 11/19] remove machine sample --- src/example_machine/CMakeLists.txt | 11 ----- src/example_machine/entry.c | 74 ------------------------------ src/example_machine/linker.ld | 26 ----------- src/example_machine/start.S | 11 ----- 4 files changed, 122 deletions(-) delete mode 100644 src/example_machine/CMakeLists.txt delete mode 100644 src/example_machine/entry.c delete mode 100644 src/example_machine/linker.ld delete mode 100644 src/example_machine/start.S diff --git a/src/example_machine/CMakeLists.txt b/src/example_machine/CMakeLists.txt deleted file mode 100644 index f54ad963..00000000 --- a/src/example_machine/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -SETUP_EXECUTABLE(example_machine) - -set(LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld) - -target_link_options(example_machine PRIVATE -static-pie -T ${LINKER_SCRIPT}) -set_target_properties(example_machine - PROPERTIES LINK_DEPENDS ${LINKER_SCRIPT}) - -target_link_libraries(example_machine PRIVATE Debug trap relocations) - -ADD_QEMU_TARGET(example_machine BIOS_IMAGE) diff --git a/src/example_machine/entry.c b/src/example_machine/entry.c deleted file mode 100644 index 7af9415f..00000000 --- a/src/example_machine/entry.c +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -// NOLINTBEGIN(readability-identifier-naming) -extern u8 bss_start[]; -extern u8 bss_end[]; -// NOLINTEND(readability-identifier-naming) - -static const u64 g_clint_base = 0x02000000; - -static volatile u64* g_mtime = (u64*)(g_clint_base + 0xBFF8); -static volatile u64* g_mtimecmp = (u64*)(g_clint_base + 0x4000); - -static const u64 g_quant = 50000llu; - -#define INTERRUPT_TIMER_M 7 - -void main() { - for (u32 i = 0;; ++i) { - CSR_CLEAR(mstatus, 8); // disable interrupts - dprintf("hello OS %u\n", i); - CSR_SET(mstatus, 8); // reenable interrupts - } -} - -[[gnu::interrupt("machine")]] -void int_handler() { - reg_t cause = CSR_READ(mcause); - if (trap_is_interrupt(cause)) { - // interrupt - reg_t int_no = trap_get_interrupt_code(cause); - - switch (int_no) { - case INTERRUPT_TIMER_M: - dputs("got timer interrupt\n"); - g_mtimecmp[CSR_READ(mhartid)] = *g_mtime + g_quant; - break; - default: dprintf("unknown interrupt (%ld)\n", int_no); break; - } - - CSR_CLEAR(mip, (reg_t)1 << int_no); - return; - } -} - -[[noreturn, gnu::used]] -void start() { - (void)self_relocate(); - const size_t bss_sz = (uintptr_t)bss_end - (uintptr_t)bss_start; - - memset(bss_start, '\0', bss_sz); - - // register handler - CSR_WRITE(mtvec, int_handler); - - // request a timer interrupt - g_mtimecmp[CSR_READ(mhartid)] = *g_mtime + g_quant; - - // set MIE in mstatus - CSR_SET(mstatus, 8); - - // set TIMER in mie - CSR_SET(mie, 1lu << INTERRUPT_TIMER_M); - - main(); - - while (true) wfi(); -} diff --git a/src/example_machine/linker.ld b/src/example_machine/linker.ld deleted file mode 100644 index dfc0d122..00000000 --- a/src/example_machine/linker.ld +++ /dev/null @@ -1,26 +0,0 @@ -ENTRY(_start); - -. = 0x80000000; - -SECTIONS { - /* Include entry point at start of binary */ - .text : ALIGN(4K) { - *(.init); - *(.text); - } - .bss : ALIGN(4K) { - PROVIDE(bss_start = .); - *(.bss); - *(COMMON); - . += 4096; - PROVIDE(__stack_top = .); - PROVIDE(__global_pointer$ = .); - PROVIDE(bss_end = .); - } - .rodata : ALIGN(4K) { - *(.rodata); - } - .data : ALIGN(4K) { - *(.data); - } -} diff --git a/src/example_machine/start.S b/src/example_machine/start.S deleted file mode 100644 index 7e3d66dd..00000000 --- a/src/example_machine/start.S +++ /dev/null @@ -1,11 +0,0 @@ -.section .init - -.global _start - -_start: - .option push - .option norelax - lla gp, __global_pointer$ - .option pop - lla sp, __stack_top - j start From b2b4938f3a9c57583a320d5cb4b736ee97137506 Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Fri, 20 Mar 2026 00:58:47 +0100 Subject: [PATCH 12/19] format --- include/stdbigos/csr.h | 2 +- src/example_umode_concurrency/entry.c | 26 ++++++++++++-------------- src/lib/trap/trap.c | 10 ++-------- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/include/stdbigos/csr.h b/include/stdbigos/csr.h index 153361b1..7d8c03f3 100644 --- a/include/stdbigos/csr.h +++ b/include/stdbigos/csr.h @@ -1,8 +1,8 @@ #ifndef STDBIGOS_CSR #define STDBIGOS_CSR -#include "types.h" #include "csr_vals.h" +#include "types.h" /** * @addtogroup stdbigos diff --git a/src/example_umode_concurrency/entry.c b/src/example_umode_concurrency/entry.c index 288cec3e..6d2009c1 100644 --- a/src/example_umode_concurrency/entry.c +++ b/src/example_umode_concurrency/entry.c @@ -50,17 +50,17 @@ static inline void user_sys_print(const char* str) { [[noreturn]] void user_task_a() { while (true) { user_sys_print("[U-task A] running\n"); - u64 start = read_time(); - while (read_time() - start < 10000); - } + u64 start = read_time(); + while (read_time() - start < 10000); + } } [[noreturn]] void user_task_b() { while (true) { user_sys_print("[U-task B] running\n"); - u64 start = read_time(); - while (read_time() - start < 10000); - } + u64 start = read_time(); + while (read_time() - start < 10000); + } } static void init_task(task_ctx_t* task, void (*entry)(), void* user_stack_top) { @@ -98,13 +98,13 @@ void my_trap_handler(trap_frame_t* tf) { const reg_t scause = tf->scause; if (trap_is_interrupt(scause)) { if (trap_get_interrupt_code(scause) == TRAP_INT_S_TIMER) { - if (read_time() < tf->stval) { - // Spurious timer interrupt, ignore. - return; - } + if (read_time() < tf->stval) { + // Spurious timer interrupt, ignore. + return; + } arm_next_timer(); switch_to_next_task(tf); - dprintf("switched to task %u\n", g_current_task); + dprintf("switched to task %u\n", g_current_task); return; } @@ -113,9 +113,7 @@ void my_trap_handler(trap_frame_t* tf) { } switch (trap_get_exception_code(scause)) { - case TRAP_EXC_ENV_CALL_U: - handle_syscall(tf); - break; + case TRAP_EXC_ENV_CALL_U: handle_syscall(tf); break; default: dprintf("unexpected exception %lu stval=%p\n", (u64)trap_get_exception_code(scause), (void*)tf->stval); break; diff --git a/src/lib/trap/trap.c b/src/lib/trap/trap.c index 150c7d46..041176d9 100644 --- a/src/lib/trap/trap.c +++ b/src/lib/trap/trap.c @@ -17,14 +17,8 @@ void trap_handler_trampoline(trap_frame_t* tf) { // - MXR - memory executable readable // - SUM - supervisor read userspace // - SDT - double trap detection - CSR_CLEAR(sstatus, - (CSR_SSTATUS_VS_MASK << CSR_SSTATUS_VS_OFFSET) | - (CSR_SSTATUS_FS_MASK << CSR_SSTATUS_FS_OFFSET) | - CSR_SSTATUS_MXR | - CSR_SSTATUS_SUM | - CSR_SSTATUS_SDT | - 0 - ); + CSR_CLEAR(sstatus, (CSR_SSTATUS_VS_MASK << CSR_SSTATUS_VS_OFFSET) | (CSR_SSTATUS_FS_MASK << CSR_SSTATUS_FS_OFFSET) | + CSR_SSTATUS_MXR | CSR_SSTATUS_SUM | CSR_SSTATUS_SDT | 0); if (g_trap_handler) g_trap_handler(tf); } From 27648ee03f7c09e58835b8c5bf8b0dfb3c818c1d Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Fri, 20 Mar 2026 01:03:14 +0100 Subject: [PATCH 13/19] fixup clang-tidy --- src/example_umode_concurrency/entry.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/example_umode_concurrency/entry.c b/src/example_umode_concurrency/entry.c index 6d2009c1..de9286d9 100644 --- a/src/example_umode_concurrency/entry.c +++ b/src/example_umode_concurrency/entry.c @@ -10,10 +10,9 @@ enum { SYSCALL_PRINT = 1, }; -#define TASK_COUNT 2 - -static const u64 TIMER_QUANTUM = 50000; -static const reg_t SIE_STIE = (1ul << TRAP_INT_S_TIMER); +#define TASK_COUNT 2 +#define TIMER_QUANTUM 50000ul +#define SIE_STIE (1ul << TRAP_INT_S_TIMER) typedef struct { trap_frame_t tf; From b80b64391d3ce1fed3b37b31437be535b040a631 Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Fri, 20 Mar 2026 01:10:08 +0100 Subject: [PATCH 14/19] remove unneeded stack --- src/example_umode_concurrency/entry.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/example_umode_concurrency/entry.c b/src/example_umode_concurrency/entry.c index de9286d9..77addbd4 100644 --- a/src/example_umode_concurrency/entry.c +++ b/src/example_umode_concurrency/entry.c @@ -22,11 +22,6 @@ typedef struct { static task_ctx_t g_tasks[TASK_COUNT]; static u32 g_current_task = 0; -// as our handlers are non-preemptive we only need one kernel stack -// but if we wanted kernel preemption, we would need to have one stack -// per task -static u8 g_kernel_mode_stack[4096] __attribute__((aligned(4096))); - static u8 g_user_mode_stack_a[4096] __attribute__((aligned(4096))); static u8 g_user_mode_stack_b[4096] __attribute__((aligned(4096))); @@ -132,13 +127,11 @@ void main([[maybe_unused]] u32 hartid, [[maybe_unused]] const void* fdt) { CSR_SET(sie, SIE_STIE); arm_next_timer(); - void* kernel_stack_top = &g_kernel_mode_stack[sizeof(g_kernel_mode_stack)]; - trap_frame_t tf = g_tasks[g_current_task].tf; - - if (trap_utils_prepare_stack_for_transition(&kernel_stack_top, &tf) != ERR_NONE) { - dputs("failed to prepare transition stack\n"); - return; - } + // as our handlers are non-preemptive we only need one kernel stack + // but if we wanted kernel preemption, we would need to have one stack + // per task - trap_restore_with_cleanup(kernel_stack_top, nullptr, nullptr); + // we will piggyback of our current stack + alignas(16) trap_frame_t tf = g_tasks[0].tf; + trap_restore_with_cleanup(&tf, nullptr, nullptr); } From 65d5d3eb527da4463a16f3fd81dc3b46434ed7e1 Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Fri, 20 Mar 2026 12:31:45 +0100 Subject: [PATCH 15/19] fixup spurrious timer detection --- src/example_umode_concurrency/entry.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/example_umode_concurrency/entry.c b/src/example_umode_concurrency/entry.c index 77addbd4..4d7187da 100644 --- a/src/example_umode_concurrency/entry.c +++ b/src/example_umode_concurrency/entry.c @@ -21,6 +21,7 @@ typedef struct { static task_ctx_t g_tasks[TASK_COUNT]; static u32 g_current_task = 0; +static u64 g_next_switch = 0; static u8 g_user_mode_stack_a[4096] __attribute__((aligned(4096))); static u8 g_user_mode_stack_b[4096] __attribute__((aligned(4096))); @@ -32,7 +33,8 @@ static inline u64 read_time() { } static void arm_next_timer() { - (void)sbi_set_timer(read_time() + TIMER_QUANTUM); + g_next_switch = read_time() + TIMER_QUANTUM; + (void)sbi_set_timer(g_next_switch); } static inline void user_sys_print(const char* str) { @@ -92,7 +94,7 @@ void my_trap_handler(trap_frame_t* tf) { const reg_t scause = tf->scause; if (trap_is_interrupt(scause)) { if (trap_get_interrupt_code(scause) == TRAP_INT_S_TIMER) { - if (read_time() < tf->stval) { + if (read_time() < g_next_switch) { // Spurious timer interrupt, ignore. return; } From 7ecfa494f55351465fdffe6c82f53104ccb69f1c Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Fri, 20 Mar 2026 12:45:17 +0100 Subject: [PATCH 16/19] use more reasonable time values --- src/example_umode_concurrency/entry.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/example_umode_concurrency/entry.c b/src/example_umode_concurrency/entry.c index 4d7187da..019d7d4d 100644 --- a/src/example_umode_concurrency/entry.c +++ b/src/example_umode_concurrency/entry.c @@ -10,8 +10,15 @@ enum { SYSCALL_PRINT = 1, }; +// extracted from fdt /cpus/timebase-frequency +// QEMU tells 10MHz +#define TIMEBASE_FREQUENCY 10000000 + #define TASK_COUNT 2 -#define TIMER_QUANTUM 50000ul +// switch every 4 seconds +// sleep for 1 second each +#define TIMER_QUANTUM (TIMEBASE_FREQUENCY * 4) +#define SLEEP_TIME (TIMEBASE_FREQUENCY * 1) #define SIE_STIE (1ul << TRAP_INT_S_TIMER) typedef struct { @@ -47,7 +54,7 @@ static inline void user_sys_print(const char* str) { while (true) { user_sys_print("[U-task A] running\n"); u64 start = read_time(); - while (read_time() - start < 10000); + while (read_time() - start < SLEEP_TIME); } } @@ -55,7 +62,7 @@ static inline void user_sys_print(const char* str) { while (true) { user_sys_print("[U-task B] running\n"); u64 start = read_time(); - while (read_time() - start < 10000); + while (read_time() - start < SLEEP_TIME); } } From 7039fa725cf95da7cf7b7376d09c6a87e3ccc3cc Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Fri, 20 Mar 2026 12:45:30 +0100 Subject: [PATCH 17/19] format --- src/example_umode_concurrency/entry.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/example_umode_concurrency/entry.c b/src/example_umode_concurrency/entry.c index 019d7d4d..037cd355 100644 --- a/src/example_umode_concurrency/entry.c +++ b/src/example_umode_concurrency/entry.c @@ -14,7 +14,7 @@ enum { // QEMU tells 10MHz #define TIMEBASE_FREQUENCY 10000000 -#define TASK_COUNT 2 +#define TASK_COUNT 2 // switch every 4 seconds // sleep for 1 second each #define TIMER_QUANTUM (TIMEBASE_FREQUENCY * 4) From 9ab19428634a3b97629af298184e803edb64be75 Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Fri, 20 Mar 2026 12:51:29 +0100 Subject: [PATCH 18/19] clang-tidy --- src/example_umode_concurrency/entry.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/example_umode_concurrency/entry.c b/src/example_umode_concurrency/entry.c index 037cd355..5b2f0d2f 100644 --- a/src/example_umode_concurrency/entry.c +++ b/src/example_umode_concurrency/entry.c @@ -12,7 +12,7 @@ enum { // extracted from fdt /cpus/timebase-frequency // QEMU tells 10MHz -#define TIMEBASE_FREQUENCY 10000000 +#define TIMEBASE_FREQUENCY 10000000ul #define TASK_COUNT 2 // switch every 4 seconds From 084a103c3fdb30ccb60083ee4da4de4ad4ae4768 Mon Sep 17 00:00:00 2001 From: Jakub Janeczko Date: Fri, 20 Mar 2026 12:54:02 +0100 Subject: [PATCH 19/19] better show concurrent execution --- src/example_umode_concurrency/entry.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/example_umode_concurrency/entry.c b/src/example_umode_concurrency/entry.c index 5b2f0d2f..fcf77cba 100644 --- a/src/example_umode_concurrency/entry.c +++ b/src/example_umode_concurrency/entry.c @@ -15,10 +15,11 @@ enum { #define TIMEBASE_FREQUENCY 10000000ul #define TASK_COUNT 2 -// switch every 4 seconds + +// switch 100 times per second // sleep for 1 second each -#define TIMER_QUANTUM (TIMEBASE_FREQUENCY * 4) -#define SLEEP_TIME (TIMEBASE_FREQUENCY * 1) +#define TIMER_QUANTUM (u64)(TIMEBASE_FREQUENCY / 100) +#define SLEEP_TIME (u64)(TIMEBASE_FREQUENCY * 1) #define SIE_STIE (1ul << TRAP_INT_S_TIMER) typedef struct { @@ -107,7 +108,6 @@ void my_trap_handler(trap_frame_t* tf) { } arm_next_timer(); switch_to_next_task(tf); - dprintf("switched to task %u\n", g_current_task); return; }