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/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/include/stdbigos/csr.h b/include/stdbigos/csr.h index 6c345a64..7d8c03f3 100644 --- a/include/stdbigos/csr.h +++ b/include/stdbigos/csr.h @@ -1,8 +1,15 @@ #ifndef STDBIGOS_CSR #define STDBIGOS_CSR +#include "csr_vals.h" #include "types.h" +/** + * @addtogroup stdbigos + * @addtogroup csr + * @{ + */ + #define CSR_SWAP(csr, val) \ ({ \ reg_t _val = (reg_t)(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"); } @@ -73,8 +71,13 @@ static inline void wfi() { static inline void ebreak() { __asm__ volatile("ebreak" ::: "memory"); } +static inline void intr_enable() { + CSR_SET(sstatus, CSR_SSTATUS_SIE); +} +static inline void intr_disable() { + 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..b0d8e668 --- /dev/null +++ b/include/stdbigos/csr_vals.h @@ -0,0 +1,22 @@ +#ifndef STDBIGOS_CSR_VALS +#define STDBIGOS_CSR_VALS + +#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 // !STDBIGOS_CSR_VALS 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/trap/trap.h b/include/trap/trap.h new file mode 100644 index 00000000..39383b19 --- /dev/null +++ b/include/trap/trap.h @@ -0,0 +1,184 @@ +#ifndef TRAP_H +#define TRAP_H + +#include +#include + +/** + * @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, + TRAP_INT_S_EXTERNAL = 9, + TRAP_INT_COUNTER_OVERFLOW = 13, + // >=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, + 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_STORE_PAGE_FAULT = 15, + 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; + +/** + * @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 { + /// General-purpose registers + reg_t gpr[31]; + struct { + 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; + }; + }; + /// value of `sepc` CSR + reg_t sepc; + /// value of `sstatus` CSR + reg_t sstatus; + /// value of `stval` CSR + reg_t stval; + /// value of `scause` CSR + 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. + */ +typedef void (*pfn_trap_handler_t)(trap_frame_t* tf); +/** + * @brief Continuation callback used by stack transition helpers. + */ +typedef void (*pfn_continuation_t)(void* user); + +/** + * @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); + +/** + * @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); + +/** + * @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); + +/** + * @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); + +/** + * @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); + +/** + * @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); + +/** + * @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); + +/// @} + +#endif // !TRAP_H diff --git a/src/example_machine/CMakeLists.txt b/src/example_machine/CMakeLists.txt deleted file mode 100644 index cb12f40d..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 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 856f9e2b..00000000 --- a/src/example_machine/entry.c +++ /dev/null @@ -1,72 +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; - -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 (is_interrupt(cause)) { - // interrupt - reg_t int_no = get_interrupt_code(cause); - - switch (int_no) { - case IntMTimer: - dputs("got timer interrupt\n"); - g_mtimecmp[hartid()] = *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[hartid()] = *g_mtime + g_quant; - - // set MIE in mstatus - CSR_SET(mstatus, 8); - - // set TIMER in mie - CSR_SET(mie, 1lu << IntMTimer); - - 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 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..6d56e105 --- /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 = tf->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 = tf->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 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" + "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 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"); + 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/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..fcf77cba --- /dev/null +++ b/src/example_umode_concurrency/entry.c @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include + +enum { + SYSCALL_PRINT = 1, +}; + +// extracted from fdt /cpus/timebase-frequency +// QEMU tells 10MHz +#define TIMEBASE_FREQUENCY 10000000ul + +#define TASK_COUNT 2 + +// switch 100 times per second +// sleep for 1 second each +#define TIMER_QUANTUM (u64)(TIMEBASE_FREQUENCY / 100) +#define SLEEP_TIME (u64)(TIMEBASE_FREQUENCY * 1) +#define 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; +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))); + +static inline u64 read_time() { + u64 now; + __asm__ volatile("rdtime %0" : "=r"(now)); + return now; +} + +static void arm_next_timer() { + g_next_switch = read_time() + TIMER_QUANTUM; + (void)sbi_set_timer(g_next_switch); +} + +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 < SLEEP_TIME); + } +} + +[[noreturn]] void user_task_b() { + while (true) { + user_sys_print("[U-task B] running\n"); + u64 start = read_time(); + while (read_time() - start < SLEEP_TIME); + } +} + +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() < g_next_switch) { + // Spurious timer interrupt, ignore. + return; + } + arm_next_timer(); + switch_to_next_task(tf); + 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(); + + // 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 + + // we will piggyback of our current stack + alignas(16) trap_frame_t tf = g_tasks[0].tf; + trap_restore_with_cleanup(&tf, nullptr, nullptr); +} 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/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/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; 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..c9d25c7d --- /dev/null +++ b/src/lib/trap/trap.S @@ -0,0 +1,117 @@ +.text + +.global trap_entry +.global trap_restore_with_cleanup +.global trap_utils_jump_with_stack + +.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 + +// 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 a0, sscratch, a0 + + // sp <- sscratch + // a0 <- old sp + // sscratch <- old a0 + bnez sp, .Lsave_regs + +.Lfrom_smode: + // sp == 0 + // 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, 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 + +.Lsave_regs: + // sp != 0 - valid kernel stack pointer + // a0 <- old sp + // sscratch <- old a0 + + add sp, sp, -FRAME_SIZE + // save original sp, restore original t0 in sscratch and restore sscratch invariant + 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 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 a1, a1, CSR_SSTATUS_SPP + bnez a1, .Lrun_handler + +.option push +.option norelax + lla gp, __global_pointer$ +.option pop + + ld tp, FRAME_SIZE(sp) + +.Lrun_handler: + mv a0, sp + call trap_handler_trampoline + +kernel_trap_restore: + ld a0, 31*8(sp) + ld a1, 32*8(sp) + + // disable interrupts and restore sstatus and sepc + 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 a2, a1, CSR_SSTATUS_SPP + bnez a2, .Lload_regs + +.Lrestore_umode: + sd tp, FRAME_SIZE(sp) + + add a0, sp, FRAME_SIZE + csrw sscratch, a0 + +.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, 1*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..041176d9 --- /dev/null +++ b/src/lib/trap/trap.c @@ -0,0 +1,52 @@ +#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) { + // 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); + 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 - ALIGN_UP(16 + sizeof(*tf), 16); // leave space for tp and trap frame + memcpy(sp, tf, sizeof(*tf)); + *stack = sp; + + 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; +}