diff --git a/cmake/arch/riscv/hal/CMRX.cmake b/cmake/arch/riscv/hal/CMRX.cmake new file mode 100644 index 00000000..2e9405fb --- /dev/null +++ b/cmake/arch/riscv/hal/CMRX.cmake @@ -0,0 +1,65 @@ +# CMRX CMake module for RISC-V HAL +# +# Provides minimal cmake infrastructure for RISC-V targets. +# Note: MPU linker script management is not yet implemented for RISC-V. + +message(STATUS "RISC-V HAL CMRX CMake component loaded") + +find_program(PYTHON_EXE NAMES python3 python REQUIRED DOC "Python 3 executable") + +# Path to the CMRX sections linker script fragment +set(CMRX_RISCV_SECTIONS_LD "${CMAKE_CURRENT_LIST_DIR}/cmrx_sections.ld") + +# Stub: add_firmware wraps add_executable for RISC-V +# Full linker script management is not yet implemented +function(add_firmware FW_NAME) + get_property(CMRX_ROOT_DIR GLOBAL PROPERTY CMRX_ROOT_DIR) + if ("${CMRX_ROOT_DIR}" STREQUAL "") + message(FATAL_ERROR "CMRX source tree directory property not set! Did you forget to add_subdirectory(cmrx)?") + endif() + + add_executable(${FW_NAME} ${ARGN}) + set_property(TARGET ${FW_NAME} PROPERTY CMRX_IS_FIRMWARE 1) + + # Generate map file for analysis + target_link_options(${FW_NAME} PUBLIC -Wl,-Map=${FW_NAME}.map) + + # Include the CMRX sections linker script fragment. + # This defines .cmrx_applications and .cmrx_thread_create sections + # with __applications_start/__applications_end and + # __thread_create_start/__thread_create_end boundary symbols. + target_link_options(${FW_NAME} PUBLIC + "-T${CMRX_RISCV_SECTIONS_LD}" + ) +endfunction() + +# Stub: target_add_applications links applications to firmware +# Full linker script application support is not yet implemented +function(target_add_applications TGT_NAME) + target_link_libraries(${TGT_NAME} ${ARGN}) + + get_target_property(IS_FIRMWARE ${TGT_NAME} CMRX_IS_FIRMWARE) + if ("${IS_FIRMWARE}" EQUAL "1") + foreach(LIBRARY ${ARGN}) + get_target_property(IS_APPLICATION ${LIBRARY} CMRX_IS_APPLICATION) + if ("${IS_APPLICATION}" EQUAL "1") + message(STATUS "Adding application ${LIBRARY} to firmware ${TGT_NAME}") + endif() + endforeach() + endif() +endfunction() + +## Add firmware application definition +# RISC-V implementation mirrors the generic/static-library model used by +# other architectures and marks the target as a CMRX application. +function(add_application NAME) + get_property(CMRX_ROOT_DIR GLOBAL PROPERTY CMRX_ROOT_DIR) + if ("${CMRX_ROOT_DIR}" STREQUAL "") + message(FATAL_ERROR "CMRX source tree directory property not set! Did you forget to add_subdirectory(cmrx)?") + endif() + + add_library(${NAME} STATIC EXCLUDE_FROM_ALL ${ARGN}) + set_property(TARGET ${NAME} PROPERTY CMRX_IS_APPLICATION 1) + target_compile_definitions(${NAME} PRIVATE -D APPLICATION_NAME=${NAME}) + target_link_libraries(${NAME} stdlib) +endfunction() diff --git a/cmake/arch/riscv/hal/cmrx_sections.ld b/cmake/arch/riscv/hal/cmrx_sections.ld new file mode 100644 index 00000000..297387cf --- /dev/null +++ b/cmake/arch/riscv/hal/cmrx_sections.ld @@ -0,0 +1,36 @@ +/* + * CMRX linker script fragment for RISC-V. + * + * This linker script defines the CMRX static initialization sections + * and their boundary symbols. + * + * The CMRX macros OS_APPLICATION and OS_THREAD_CREATE place data in + * .applications and .thread_create sections respectively. + */ + +SECTIONS +{ + /* Application definitions table */ + .cmrx_applications : { + PROVIDE(__applications_start = .); + KEEP(*(.applications)) + KEEP(*(.applications.*)) + PROVIDE(__applications_end = .); + } + + /* Thread creation table */ + .cmrx_thread_create : { + PROVIDE(__thread_create_start = .); + KEEP(*(.thread_create)) + KEEP(*(.thread_create.*)) + PROVIDE(__thread_create_end = .); + } + + /* Syscall table entries */ + .cmrx_syscall : { + PROVIDE(__syscall_start = .); + KEEP(*(.syscall)) + KEEP(*(.syscall.*)) + PROVIDE(__syscall_end = .); + } +} diff --git a/include/cmrx/arch/riscv/context_switch.h b/include/cmrx/arch/riscv/context_switch.h new file mode 100644 index 00000000..b37bf150 --- /dev/null +++ b/include/cmrx/arch/riscv/context_switch.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include +#include + +void os_riscv_context_switch_request(bool activate); +bool os_riscv_context_switch_is_pending(void); +void os_riscv_context_switch_safe_point(void); diff --git a/include/cmrx/arch/riscv/exception_frame.h b/include/cmrx/arch/riscv/exception_frame.h new file mode 100644 index 00000000..8031f09d --- /dev/null +++ b/include/cmrx/arch/riscv/exception_frame.h @@ -0,0 +1,260 @@ +#pragma once + +/** @defgroup arch_riscv_exception_frame RISC-V Exception Frame + * @ingroup arch_riscv + * + * Unified exception frame for RISC-V trap handlers and context switching. + * + * This header defines the standard full-context frame saved on trap entry + * and restored on trap exit. It is the RISC-V equivalent of the ARM + * ExceptionFrame in cortex.h. + * + * The layout covers all general-purpose registers that must be preserved + * across a trap (x1/ra, x5-x31), plus the mepc and mstatus CSRs. + * Registers x0 (zero), x2 (sp), x3 (gp), and x4 (tp) are excluded: + * x0 is hardwired to zero; sp is saved separately in the thread control + * block; gp and tp are program-global constants in M-mode. + * + * The frame is 32 words (128 bytes), keeping the stack 16-byte aligned + * per the RISC-V psABI (riscv-abi documentation, section 2.1). + * + * References: + * - RISC-V Privileged Specification (mepc, mstatus, mcause) + * - RISC-V psABI (register conventions, stack alignment) + * @{ + */ + +/* + * Byte offsets — usable from both C and assembly. + * + * Caller-saved registers (offsets 0-60): + */ +#define EF_RA 0 +#define EF_T0 4 +#define EF_T1 8 +#define EF_T2 12 +#define EF_A0 16 +#define EF_A1 20 +#define EF_A2 24 +#define EF_A3 28 +#define EF_A4 32 +#define EF_A5 36 +#define EF_A6 40 +#define EF_A7 44 +#define EF_T3 48 +#define EF_T4 52 +#define EF_T5 56 +#define EF_T6 60 + +/* Callee-saved registers (offsets 64-108): */ +#define EF_S0 64 +#define EF_S1 68 +#define EF_S2 72 +#define EF_S3 76 +#define EF_S4 80 +#define EF_S5 84 +#define EF_S6 88 +#define EF_S7 92 +#define EF_S8 96 +#define EF_S9 100 +#define EF_S10 104 +#define EF_S11 108 + +/* CSRs (offsets 112-116): */ +#define EF_MEPC 112 +#define EF_MSTATUS 116 + +/** Total frame size in bytes. */ +#define EXCEPTION_FRAME_SIZE 128 + +/* Stringification helpers for use inside inline asm macros. */ +#define _RISCV_EFS(x) #x +#define RISCV_EFS(x) _RISCV_EFS(x) + +/** Save application context. + * Pushes a full ExceptionFrame onto the current stack: all GP registers + * (ra, t0-t6, a0-a7, s0-s11) plus mepc and mstatus. + * Uses t0/t1 as scratch for CSR reads (they are saved beforehand). + * @note This is defined as a macro so it can live inside naked functions. + */ +#define SAVE_CONTEXT() \ +asm volatile( \ + "addi sp, sp, -" RISCV_EFS(EXCEPTION_FRAME_SIZE) "\n\t" \ + "sw ra, " RISCV_EFS(EF_RA) "(sp)\n\t" \ + "sw t0, " RISCV_EFS(EF_T0) "(sp)\n\t" \ + "sw t1, " RISCV_EFS(EF_T1) "(sp)\n\t" \ + "sw t2, " RISCV_EFS(EF_T2) "(sp)\n\t" \ + "sw a0, " RISCV_EFS(EF_A0) "(sp)\n\t" \ + "sw a1, " RISCV_EFS(EF_A1) "(sp)\n\t" \ + "sw a2, " RISCV_EFS(EF_A2) "(sp)\n\t" \ + "sw a3, " RISCV_EFS(EF_A3) "(sp)\n\t" \ + "sw a4, " RISCV_EFS(EF_A4) "(sp)\n\t" \ + "sw a5, " RISCV_EFS(EF_A5) "(sp)\n\t" \ + "sw a6, " RISCV_EFS(EF_A6) "(sp)\n\t" \ + "sw a7, " RISCV_EFS(EF_A7) "(sp)\n\t" \ + "sw t3, " RISCV_EFS(EF_T3) "(sp)\n\t" \ + "sw t4, " RISCV_EFS(EF_T4) "(sp)\n\t" \ + "sw t5, " RISCV_EFS(EF_T5) "(sp)\n\t" \ + "sw t6, " RISCV_EFS(EF_T6) "(sp)\n\t" \ + "sw s0, " RISCV_EFS(EF_S0) "(sp)\n\t" \ + "sw s1, " RISCV_EFS(EF_S1) "(sp)\n\t" \ + "sw s2, " RISCV_EFS(EF_S2) "(sp)\n\t" \ + "sw s3, " RISCV_EFS(EF_S3) "(sp)\n\t" \ + "sw s4, " RISCV_EFS(EF_S4) "(sp)\n\t" \ + "sw s5, " RISCV_EFS(EF_S5) "(sp)\n\t" \ + "sw s6, " RISCV_EFS(EF_S6) "(sp)\n\t" \ + "sw s7, " RISCV_EFS(EF_S7) "(sp)\n\t" \ + "sw s8, " RISCV_EFS(EF_S8) "(sp)\n\t" \ + "sw s9, " RISCV_EFS(EF_S9) "(sp)\n\t" \ + "sw s10, " RISCV_EFS(EF_S10) "(sp)\n\t" \ + "sw s11, " RISCV_EFS(EF_S11) "(sp)\n\t" \ + "csrr t0, mepc\n\t" \ + "csrr t1, mstatus\n\t" \ + "sw t0, " RISCV_EFS(EF_MEPC) "(sp)\n\t" \ + "sw t1, " RISCV_EFS(EF_MSTATUS) "(sp)\n\t" \ + : : : "memory" \ +) + +/** Load application context. + * Restores all GP registers and CSRs (mepc, mstatus) from the + * ExceptionFrame on the current stack and pops the frame. + * Does NOT execute mret — the caller handles that. + * @note This is defined as a macro so it can live inside naked functions. + */ +#define LOAD_CONTEXT() \ +asm volatile( \ + "lw t0, " RISCV_EFS(EF_MEPC) "(sp)\n\t" \ + "lw t1, " RISCV_EFS(EF_MSTATUS) "(sp)\n\t" \ + "csrw mepc, t0\n\t" \ + "csrw mstatus, t1\n\t" \ + "lw s0, " RISCV_EFS(EF_S0) "(sp)\n\t" \ + "lw s1, " RISCV_EFS(EF_S1) "(sp)\n\t" \ + "lw s2, " RISCV_EFS(EF_S2) "(sp)\n\t" \ + "lw s3, " RISCV_EFS(EF_S3) "(sp)\n\t" \ + "lw s4, " RISCV_EFS(EF_S4) "(sp)\n\t" \ + "lw s5, " RISCV_EFS(EF_S5) "(sp)\n\t" \ + "lw s6, " RISCV_EFS(EF_S6) "(sp)\n\t" \ + "lw s7, " RISCV_EFS(EF_S7) "(sp)\n\t" \ + "lw s8, " RISCV_EFS(EF_S8) "(sp)\n\t" \ + "lw s9, " RISCV_EFS(EF_S9) "(sp)\n\t" \ + "lw s10, " RISCV_EFS(EF_S10) "(sp)\n\t" \ + "lw s11, " RISCV_EFS(EF_S11) "(sp)\n\t" \ + "lw ra, " RISCV_EFS(EF_RA) "(sp)\n\t" \ + "lw t0, " RISCV_EFS(EF_T0) "(sp)\n\t" \ + "lw t1, " RISCV_EFS(EF_T1) "(sp)\n\t" \ + "lw t2, " RISCV_EFS(EF_T2) "(sp)\n\t" \ + "lw a0, " RISCV_EFS(EF_A0) "(sp)\n\t" \ + "lw a1, " RISCV_EFS(EF_A1) "(sp)\n\t" \ + "lw a2, " RISCV_EFS(EF_A2) "(sp)\n\t" \ + "lw a3, " RISCV_EFS(EF_A3) "(sp)\n\t" \ + "lw a4, " RISCV_EFS(EF_A4) "(sp)\n\t" \ + "lw a5, " RISCV_EFS(EF_A5) "(sp)\n\t" \ + "lw a6, " RISCV_EFS(EF_A6) "(sp)\n\t" \ + "lw a7, " RISCV_EFS(EF_A7) "(sp)\n\t" \ + "lw t3, " RISCV_EFS(EF_T3) "(sp)\n\t" \ + "lw t4, " RISCV_EFS(EF_T4) "(sp)\n\t" \ + "lw t5, " RISCV_EFS(EF_T5) "(sp)\n\t" \ + "lw t6, " RISCV_EFS(EF_T6) "(sp)\n\t" \ + "addi sp, sp, " RISCV_EFS(EXCEPTION_FRAME_SIZE) "\n\t" \ + : : : "memory" \ +) + +#ifndef __ASSEMBLER__ + +#include + +/** Full RISC-V trap context frame. + * + * Saved on the thread stack by every trap entry (timer ISR, exception + * handler) and restored on trap exit. When a context switch swaps SP + * between save and restore, the restore operates on the new thread's + * frame, launching it transparently. + * + * Initial thread stacks are populated with this same layout by + * os_thread_populate_stack(). + */ +typedef struct { + /* Caller-saved registers */ + uint32_t ra; + uint32_t t0; + uint32_t t1; + uint32_t t2; + uint32_t a0; + uint32_t a1; + uint32_t a2; + uint32_t a3; + uint32_t a4; + uint32_t a5; + uint32_t a6; + uint32_t a7; + uint32_t t3; + uint32_t t4; + uint32_t t5; + uint32_t t6; + /* Callee-saved registers */ + uint32_t s0; + uint32_t s1; + uint32_t s2; + uint32_t s3; + uint32_t s4; + uint32_t s5; + uint32_t s6; + uint32_t s7; + uint32_t s8; + uint32_t s9; + uint32_t s10; + uint32_t s11; + /* CSRs */ + uint32_t mepc; + uint32_t mstatus; + /* Padding for 16-byte alignment */ + uint32_t _pad[2]; +} ExceptionFrame; + +_Static_assert(sizeof(ExceptionFrame) == EXCEPTION_FRAME_SIZE, + "ExceptionFrame size must match EXCEPTION_FRAME_SIZE"); + +/** How many uint32_t slots the exception frame occupies. */ +#define EXCEPTION_FRAME_WORDS (sizeof(ExceptionFrame) / sizeof(uint32_t)) + +/** Initial mstatus value for newly created threads. + * + * MPIE = 1 (bit 7): after mret, MIE is set so interrupts are enabled. + * MPP = 3 (bits 12:11): stay in M-mode after mret. + * + * These are standard RISC-V privileged specification bit positions. + */ +#define CMRX_RISCV_INITIAL_MSTATUS ((1u << 7) | (3u << 11)) + +/** Retrieve a syscall argument from the exception frame. + * @param frame exception frame pointer + * @param argno argument index (0-3 maps to a0-a3) + */ +static inline uint32_t riscv_exception_get_arg(const ExceptionFrame *frame, + unsigned argno) +{ + switch (argno) { + case 0: return frame->a0; + case 1: return frame->a1; + case 2: return frame->a2; + case 3: return frame->a3; + default: return 0; + } +} + +/** Retrieve the syscall ID from the exception frame (a7 per RISC-V ecall ABI). */ +static inline uint8_t riscv_exception_get_syscall_id(const ExceptionFrame *frame) +{ + return (uint8_t)frame->a7; +} + +/** Write the syscall return value into the exception frame (a0). */ +static inline void riscv_exception_set_retval(ExceptionFrame *frame, + int32_t retval) +{ + frame->a0 = (uint32_t)retval; +} + +#endif /* __ASSEMBLER__ */ + +/** @} */ diff --git a/include/cmrx/arch/riscv/hal.h b/include/cmrx/arch/riscv/hal.h new file mode 100644 index 00000000..a85efa16 --- /dev/null +++ b/include/cmrx/arch/riscv/hal.h @@ -0,0 +1,49 @@ +/* + * Thin internal RISC-V HAL (CMSIS-like) for CMRX. + * + * Purpose: + * - Provide a minimal, swappable API for CSR and interrupt primitives needed by + * the RISC-V thread switcher and its safe-point logic. + * + * Notes: + * - This header intentionally does not define CSR bit layouts or trap semantics. + * - Unit-test builds use a testing backend implemented with static fakes. + */ +#pragma once + +#include +#include + +/* ---- Minimal CSR accessors (subset justified by switcher needs) ---- */ + +uint32_t cmrx_riscv_csr_read_mstatus(void); +void cmrx_riscv_csr_write_mstatus(uint32_t value); + +uint32_t cmrx_riscv_csr_read_mepc(void); +void cmrx_riscv_csr_write_mepc(uint32_t value); + +uint32_t cmrx_riscv_csr_read_mcause(void); +void cmrx_riscv_csr_write_mcause(uint32_t value); + +uint32_t cmrx_riscv_csr_read_mtvec(void); +void cmrx_riscv_csr_write_mtvec(uint32_t value); + +uint32_t cmrx_riscv_csr_read_mie(void); +void cmrx_riscv_csr_write_mie(uint32_t value); + +uint32_t cmrx_riscv_csr_read_mip(void); +void cmrx_riscv_csr_write_mip(uint32_t value); + +/* ---- Interrupt primitives ---- */ + +void cmrx_riscv_irq_disable(void); +void cmrx_riscv_irq_enable(void); +bool cmrx_riscv_irq_is_enabled(void); + +/* ---- Minimal barriers ---- */ + +static inline void cmrx_riscv_compiler_barrier(void) +{ + __asm__ volatile("" ::: "memory"); +} + diff --git a/quirks/CMakeLists.txt b/quirks/CMakeLists.txt index 6cbecc3d..07706580 100644 --- a/quirks/CMakeLists.txt +++ b/quirks/CMakeLists.txt @@ -4,7 +4,11 @@ if (TARGET pico_stdlib) if (NOT TARGET cmrx) message(FATAL_ERROR "CMRX target does not exist yet! Include this quirk after CMRX directory has been added!") endif() - - add_subdirectory(pico-sdk) - + + if (DEFINED PICO_PLATFORM AND PICO_PLATFORM STREQUAL "rp2350-riscv") + add_subdirectory(pico-sdk-riscv) + else() + add_subdirectory(pico-sdk) + endif() + endif() diff --git a/quirks/pico-sdk-riscv/CMakeLists.txt b/quirks/pico-sdk-riscv/CMakeLists.txt new file mode 100644 index 00000000..d67a38c0 --- /dev/null +++ b/quirks/pico-sdk-riscv/CMakeLists.txt @@ -0,0 +1,31 @@ +# CMRX quirk for Pico SDK RISC-V (RP2350) +# +# Provides: +# - Machine timer ISR with full ExceptionFrame save/restore +# - Exception dispatcher with full ExceptionFrame save/restore +# - ecall exception handler for CMRX syscalls + +if(NOT DEFINED PICO_PLATFORM) + return() +endif() + +if(NOT PICO_PLATFORM STREQUAL "rp2350-riscv") + return() +endif() + +message(STATUS "CMRX: Pico SDK RISC-V quirk enabled") + +target_sources(os PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/cmrx_timer_isr.c + ${CMAKE_CURRENT_LIST_DIR}/cmrx_exception_dispatcher.c + ${CMAKE_CURRENT_LIST_DIR}/cmrx_ecall_handler.c +) + +target_include_directories(os PRIVATE + ${CMRX_ROOT_DIR}/include +) + +target_link_libraries(os PRIVATE + pico_base_headers + hardware_regs +) diff --git a/quirks/pico-sdk-riscv/README.md b/quirks/pico-sdk-riscv/README.md new file mode 100644 index 00000000..276af534 --- /dev/null +++ b/quirks/pico-sdk-riscv/README.md @@ -0,0 +1,36 @@ +# Pico SDK RISC-V Quirk + +This directory contains integration code for running CMRX on RP2350 RISC-V using Pico SDK. + +## Problem + +The CMRX RISC-V context switcher uses a "safe-point" model where pending context switches +are checked and executed at defined boundaries (typically trap/IRQ exit). The portable +RISC-V code provides the safe-point function but does not hook it into any specific +trap entry/exit mechanism. + +On Pico SDK for RISC-V, the external IRQ handler (`isr_riscv_machine_external_irq`) is +declared weak in `crt0_riscv.S`, allowing it to be overridden. + +## Solution + +This quirk provides a replacement `isr_riscv_machine_external_irq` that: + +1. Performs the same IRQ dispatch as the original Pico SDK handler +2. Calls `os_riscv_context_switch_safe_point()` after all IRQs are dispatched +3. Restores context and returns via `mret` + +The safe-point call happens at a well-defined boundary where: +- All IRQs have been serviced +- IRQs are globally disabled (`mstatus.MIE` = 0) +- Caller-saved registers are saved on the stack + +## Files + +- `crt0_riscv_cmrx.S` - Replacement external IRQ handler with safe-point hook +- `CMakeLists.txt` - Build integration (activated when `PICO_PLATFORM=rp2350-riscv`) + +## Usage + +This quirk is automatically enabled when building CMRX for Pico SDK RISC-V. Ensure the +quirks subdirectory is included after the main CMRX target is defined. diff --git a/quirks/pico-sdk-riscv/cmrx_ecall_handler.c b/quirks/pico-sdk-riscv/cmrx_ecall_handler.c new file mode 100644 index 00000000..723bdf5b --- /dev/null +++ b/quirks/pico-sdk-riscv/cmrx_ecall_handler.c @@ -0,0 +1,33 @@ +/* + * CMRX RISC-V ecall (syscall) handler for Pico SDK (RP2350). + * + * Called by the CMRX exception dispatcher with a pointer to the full + * ExceptionFrame. Advances mepc past the ecall instruction + * (RISC-V Privileged Specification, Section 3.1.14), dispatches the + * syscall, and writes the return value into the frame's a0. + * + * The context switch safe-point is handled by the exception dispatcher, + * not here. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +extern int os_system_call(uint32_t arg0, uint32_t arg1, uint32_t arg2, + uint32_t arg3, uint8_t syscall_id); + +void isr_riscv_machine_ecall_mmode_exception(ExceptionFrame *frame) +{ + frame->mepc += 4; + + uint32_t arg0 = riscv_exception_get_arg(frame, 0); + uint32_t arg1 = riscv_exception_get_arg(frame, 1); + uint32_t arg2 = riscv_exception_get_arg(frame, 2); + uint32_t arg3 = riscv_exception_get_arg(frame, 3); + uint8_t syscall_id = riscv_exception_get_syscall_id(frame); + + int retval = os_system_call(arg0, arg1, arg2, arg3, syscall_id); + riscv_exception_set_retval(frame, retval); +} diff --git a/quirks/pico-sdk-riscv/cmrx_exception_dispatcher.c b/quirks/pico-sdk-riscv/cmrx_exception_dispatcher.c new file mode 100644 index 00000000..97711ed5 --- /dev/null +++ b/quirks/pico-sdk-riscv/cmrx_exception_dispatcher.c @@ -0,0 +1,63 @@ +/* + * CMRX RISC-V exception dispatcher for Pico SDK (RP2350). + * + * Overrides the weak isr_riscv_machine_exception from + * exception_table_riscv.S. Uses the arch-level SAVE_CONTEXT / + * LOAD_CONTEXT macros for the full ExceptionFrame, adding only the + * SDK-specific mscratch protocol and exception table dispatch. + * + * Individual exception handlers receive the ExceptionFrame pointer in a0. + * + * This file is derived from pico-sdk exception_table_riscv.S. + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +__attribute__((naked, section(".time_critical.cmrx_exception_handler"))) +void isr_riscv_machine_exception(void) +{ + /* Pico SDK mscratch protocol: swap ra with mscratch for nested detection */ + asm volatile( + "csrrw ra, mscratch, ra\n\t" + "bnez ra, __halt_on_unhandled_exception\n\t" + ::: "memory" + ); + + SAVE_CONTEXT(); + + /* Fix ra: SAVE_CONTEXT stored ra=0; overwrite with actual ra from mscratch */ + asm volatile( + "csrr t0, mscratch\n\t" + "sw t0, " RISCV_EFS(EF_RA) "(sp)\n\t" + + "csrr t0, mcause\n\t" + "li t1, 11\n\t" + "bgtu t0, t1, 1f\n\t" + + "la t1, __riscv_exception_table\n\t" + "slli t0, t0, 2\n\t" + "add t0, t0, t1\n\t" + "lw t0, (t0)\n\t" + "mv a0, sp\n\t" + "jalr ra, t0\n\t" + + "call os_riscv_context_switch_safe_point\n\t" + "j 2f\n\t" + + "1:\n\t" + "csrr ra, mscratch\n\t" + "j __halt_on_unhandled_exception\n\t" + + "2:\n\t" + ::: "memory" + ); + + LOAD_CONTEXT(); + asm volatile( + "csrw mscratch, zero\n\t" + "mret\n\t" + ::: "memory" + ); +} diff --git a/quirks/pico-sdk-riscv/cmrx_timer_isr.c b/quirks/pico-sdk-riscv/cmrx_timer_isr.c new file mode 100644 index 00000000..052331ce --- /dev/null +++ b/quirks/pico-sdk-riscv/cmrx_timer_isr.c @@ -0,0 +1,24 @@ +/* + * CMRX RISC-V machine timer ISR for Pico SDK (RP2350). + * + * Overrides the weak isr_riscv_machine_timer from crt0_riscv.S. + * Saves a full ExceptionFrame so that a context switch during the + * safe-point restores the new thread's complete register state. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +__attribute__((naked, section(".time_critical.cmrx_timer_handler"))) +void isr_riscv_machine_timer(void) +{ + SAVE_CONTEXT(); + asm volatile( + "call cmrx_machine_timer_handler\n\t" + "call os_riscv_context_switch_safe_point\n\t" + ::: "memory" + ); + LOAD_CONTEXT(); + asm volatile("mret\n\t" ::: "memory"); +} diff --git a/src/extra/CMakeLists.txt b/src/extra/CMakeLists.txt index 7e69376c..9d62cb67 100644 --- a/src/extra/CMakeLists.txt +++ b/src/extra/CMakeLists.txt @@ -1,17 +1,19 @@ if (NOT UNIT_TESTING_BUILD) - set(aux_systick_SRCS systick.c) - add_library(aux_systick STATIC EXCLUDE_FROM_ALL ${aux_systick_SRCS}) - target_link_libraries(aux_systick cmsis_headers) - if (CMRX_STDLIB_USE_CMSIS_CORE) - target_link_libraries(aux_systick cmsis_core) - endif() - target_compile_options(aux_systick PRIVATE -mgeneral-regs-only) + if ("${CMRX_ARCH}" STREQUAL "arm") + set(aux_systick_SRCS systick.c) + add_library(aux_systick STATIC EXCLUDE_FROM_ALL ${aux_systick_SRCS}) + target_link_libraries(aux_systick cmsis_headers) + if (CMRX_STDLIB_USE_CMSIS_CORE) + target_link_libraries(aux_systick cmsis_core) + endif() + target_compile_options(aux_systick PRIVATE -mgeneral-regs-only) - set(aux_ipi_SRCS cm_ipi.c) - add_library(aux_ipi STATIC EXCLUDE_FROM_ALL ${aux_ipi_SRCS}) - target_link_libraries(aux_ipi cmsis_headers) - if (CMRX_STDLIB_USE_CMSIS_CORE) - target_link_libraries(aux_ipi cmsis_core) + set(aux_ipi_SRCS cm_ipi.c) + add_library(aux_ipi STATIC EXCLUDE_FROM_ALL ${aux_ipi_SRCS}) + target_link_libraries(aux_ipi cmsis_headers) + if (CMRX_STDLIB_USE_CMSIS_CORE) + target_link_libraries(aux_ipi cmsis_core) + endif() endif() set(phy_emu_SRCS emulator.c) diff --git a/src/lib/arch/riscv/arch.cmake b/src/lib/arch/riscv/arch.cmake new file mode 100644 index 00000000..c30cc70e --- /dev/null +++ b/src/lib/arch/riscv/arch.cmake @@ -0,0 +1,2 @@ +# Customization of stdlib target for RISC-V architecture +# (placeholder for future RISC-V specific dependencies) diff --git a/src/lib/arch/riscv/mutex.c b/src/lib/arch/riscv/mutex.c new file mode 100644 index 00000000..fcf2fa74 --- /dev/null +++ b/src/lib/arch/riscv/mutex.c @@ -0,0 +1,144 @@ +/* + * RISC-V mutex/futex implementation for CMRX. + * + * Uses LR.W/SC.W (load-reserved/store-conditional) from the RISC-V A + * extension for atomic fast-path lock and unlock. + * + * futex_t must be placed at a 4-byte aligned address for LR.W/SC.W to + * operate correctly. + */ + +#if defined(__riscv_atomic) || defined(__riscv_a) + +#include +#include +#include +#include +#include + +/** Lock futex. + * Perform atomic futex lock using LR.W/SC.W. It is only possible to lock an + * unlocked mutex. Locking may fail spuriously due to hardware specifics of + * load-reserved/store-conditional. Recursive mutexes are not supported. + * @param futex futex to be locked + * @param thread_id identification of calling thread + * @param max_depth unused + * @returns 0 if futex lock was successful, 1 if locking failed for whatever reason + */ +int __futex_fast_lock(futex_t *futex, uint8_t thread_id, unsigned max_depth) +{ + uint32_t loaded, stored, new_val; + (void)max_depth; + + uint32_t *word = (uint32_t *)(uintptr_t)futex; + + __asm__ volatile ("lr.w %0, (%1)" + : "=r"(loaded) : "r"(word) : "memory"); + + if ((loaded & 0xFFu) != 0) { + return FUTEX_FAILURE; + } + + new_val = (loaded & ~0xFFFFu) | ((uint32_t)thread_id << 8) | 1u; + + __asm__ volatile ("sc.w %0, %2, (%1)" + : "=r"(stored) : "r"(word), "r"(new_val) : "memory"); + + return (stored == 0) ? FUTEX_SUCCESS : FUTEX_FAILURE; +} + +/** Unlock futex. + * This function performs fast unlock of futex if that is possible. + * If mutex is not locked, this action will fail. It is an undefined + * behavior to unlock mutex locked by another thread. + * @param futex Futex to be unlocked + * @param thread_id Numeric identification of futex owner + * @returns 0 if futex unlock was successful, 1 if unlocking failed for + * whatever reason. + */ +int __futex_fast_unlock(futex_t *futex, uint8_t thread_id) +{ + uint32_t loaded, stored, new_val; + + uint32_t *word = (uint32_t *)(uintptr_t)futex; + + __asm__ volatile ("lr.w %0, (%1)" + : "=r"(loaded) : "r"(word) : "memory"); + + uint8_t state = (uint8_t)(loaded & 0xFFu); + if (state == 0) { + return FUTEX_FAILURE; + } + + ASSERT(((loaded >> 8) & 0xFFu) == thread_id); + if (((loaded >> 8) & 0xFFu) != thread_id) { + return FUTEX_FAILURE; + } + + new_val = (loaded & ~0xFFu) | (state - 1u); + + __asm__ volatile ("sc.w %0, %2, (%1)" + : "=r"(stored) : "r"(word), "r"(new_val) : "memory"); + + return (stored == 0) ? FUTEX_SUCCESS : FUTEX_FAILURE; +} + +int futex_init(futex_t *restrict futex) +{ + ASSERT(futex != NULL); + futex->owner = 0xFF; + futex->state = 0; + return 0; +} + +int futex_lock(futex_t *futex) +{ + ASSERT(futex != NULL); + uint8_t thread_id = get_tid(); + int success; + do { + success = __futex_fast_lock(futex, thread_id, 0); + if (success != FUTEX_SUCCESS) { + wait_for_object_value(&futex->state, 0, 0, + NOTIFY_PRIORITY_INHERIT(futex->owner)); + } + } while (success != FUTEX_SUCCESS); + return 0; +} + +int futex_trylock(futex_t *futex) +{ + ASSERT(futex != NULL); + uint8_t thread_id = get_tid(); + return __futex_fast_lock(futex, thread_id, 0); +} + +int futex_unlock(futex_t *futex) +{ + ASSERT(futex != NULL); + uint8_t thread_id = get_tid(); + int success; + do { + success = __futex_fast_unlock(futex, thread_id); + if (success == FUTEX_SUCCESS) { + notify_object2(&futex->state, NOTIFY_PRIORITY_DROP); + } + } while (success != FUTEX_SUCCESS); + return 0; +} + +int futex_destroy(futex_t *futex) +{ + ASSERT(futex != NULL); + futex->state = 0; + futex->owner = 0xFF; + return 0; +} + +#else + +#error "RISC-V A extension (atomics) is required for the LR.W/SC.W-based mutex. \ + Target -march= does not include 'a'. Either enable the A extension or \ + provide a non-atomic fallback implementation." + +#endif \ No newline at end of file diff --git a/src/os/arch/riscv/CMakeLists.txt b/src/os/arch/riscv/CMakeLists.txt new file mode 100644 index 00000000..1dc46972 --- /dev/null +++ b/src/os/arch/riscv/CMakeLists.txt @@ -0,0 +1,13 @@ +set(cmrx_riscv_SRCS + hal/riscv_hal_backend.c + hal/riscv_hal_backend_csr.c + context_switch_core.c + context_switch_request.c + sched.c + mpu.c + rpc.c + static.c +) + +target_sources(os PRIVATE ${cmrx_riscv_SRCS}) + diff --git a/src/os/arch/riscv/context_switch_core.c b/src/os/arch/riscv/context_switch_core.c new file mode 100644 index 00000000..d8e62dc5 --- /dev/null +++ b/src/os/arch/riscv/context_switch_core.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static volatile bool pending_context_switch[OS_NUM_CORES]; + +#define os_riscv_context_switch_barrier() __asm__ volatile("" ::: "memory") + +void os_riscv_context_switch_request(bool activate) +{ + pending_context_switch[coreid()] = activate; + os_riscv_context_switch_barrier(); +} + +bool os_riscv_context_switch_is_pending(void) +{ + return pending_context_switch[coreid()]; +} + +static void os_riscv_context_switch_update_state(void) +{ + struct OS_core_state_t * const cpu_state = &core[coreid()]; + + if (os_threads[cpu_state->thread_current].state == THREAD_STATE_RUNNING) + { + os_threads[cpu_state->thread_current].state = THREAD_STATE_READY; + } + + cpu_state->thread_current = cpu_state->thread_next; + os_threads[cpu_state->thread_current].state = THREAD_STATE_RUNNING; +} + +__attribute__((noinline, used)) static uint32_t os_riscv_context_switch_prepare(void) +{ + if (!os_riscv_context_switch_is_pending()) + { + return 0u; + } + + os_riscv_context_switch_request(false); + os_riscv_context_switch_update_state(); + return 1u; +} + +/* Pure SP swap between old and new task. + * + * The full register context (ExceptionFrame) is saved/restored by the + * trap handler assembly that calls the safe-point, not by this function. + */ +__attribute__((naked)) void os_riscv_context_switch_perform(void) +{ + __asm__ volatile( + "la t0, cpu_context\n\t" + "lw t1, %[old_task_off](t0)\n\t" + "sw sp, %[thread_sp_off](t1)\n\t" + "lw t2, %[new_task_off](t0)\n\t" + "lw sp, %[thread_sp_off](t2)\n\t" + "ret\n\t" + : + : [old_task_off] "i"(offsetof(struct OS_scheduling_context_t, old_task)), + [new_task_off] "i"(offsetof(struct OS_scheduling_context_t, new_task)), + [thread_sp_off] "i"(offsetof(struct OS_thread_t, sp)) + : "t0", "t1", "t2", "memory"); +} + +__attribute__((naked)) void os_riscv_context_switch_safe_point(void) +{ + __asm__ volatile( + "mv s0, ra\n\t" + "call os_riscv_context_switch_prepare\n\t" + "mv ra, s0\n\t" + "beqz a0, 1f\n\t" + "j os_riscv_context_switch_perform\n\t" + "1:\n\t" + "ret\n\t" + : + : + : "t0", "a0", "memory"); +} diff --git a/src/os/arch/riscv/context_switch_request.c b/src/os/arch/riscv/context_switch_request.c new file mode 100644 index 00000000..1ff08189 --- /dev/null +++ b/src/os/arch/riscv/context_switch_request.c @@ -0,0 +1,8 @@ +#include +#include +#include + +void os_request_context_switch(bool activate) +{ + os_riscv_context_switch_request(activate); +} diff --git a/src/os/arch/riscv/hal/arch/application.h b/src/os/arch/riscv/hal/arch/application.h new file mode 100644 index 00000000..5ae4a904 --- /dev/null +++ b/src/os/arch/riscv/hal/arch/application.h @@ -0,0 +1,41 @@ +#pragma once + +/* + * RISC-V HAL application metadata placement. + * + * The RP2350 RISC-V linker fragment defines .applications and .thread_create + * sections and boundary symbols consumed by CMRX startup. + */ +#define CMRX_VTABLE_SPECIFIER __attribute__((section(".vtable."))) const + +#define CMRX_APPLICATION_INSTANCE_ATTRIBUTES __attribute__((used, section(".applications"))) +#define CMRX_THREAD_AUTOCREATE_ATTRIBUTES __attribute__((used, section(".thread_create"))) + +#define CMRX_APPLICATION_INSTANCE_CONSTRUCTOR(application) \ +void * __APPL_SYMBOL(application, data_start) = (void *)1; \ +void * __APPL_SYMBOL(application, data_end) = (void *)1; \ +void * __APPL_SYMBOL(application, bss_start) = (void *)1; \ +void * __APPL_SYMBOL(application, bss_end) = (void *)1; \ +void * __APPL_SYMBOL(application, shared_start) = (void *)1; \ +void * __APPL_SYMBOL(application, shared_end) = (void *)1; \ +void * __APPL_SYMBOL(application, vtable_start) = (void *)1; \ +void * __APPL_SYMBOL(application, vtable_end) = (void *)1; \ +CMRX_APPLICATION_INSTANCE_ATTRIBUTES const struct OS_process_definition_t __APPL_SYMBOL(application, instance) = { \ + { \ + { &__APPL_SYMBOL(application, data_start), &__APPL_SYMBOL(application, data_end) }, \ + { &__APPL_SYMBOL(application, bss_start), &__APPL_SYMBOL(application, bss_end) }, \ + { __APPL_SYMBOL(application, mmio_start), __APPL_SYMBOL(application, mmio_end) }, \ + { __APPL_SYMBOL(application, mmio_2_start), __APPL_SYMBOL(application, mmio_2_end) }, \ + { &__APPL_SYMBOL(application, shared_start), &__APPL_SYMBOL(application, shared_end) } \ + }, \ + { &__APPL_SYMBOL(application, vtable_start), &__APPL_SYMBOL(application, vtable_end) } \ +} + +#define CMRX_THREAD_AUTOCREATE_CONSTRUCTOR(application, entrypoint, data, priority, core) \ +CMRX_THREAD_AUTOCREATE_ATTRIBUTES const struct OS_thread_create_t __APPL_SYMBOL(application, thread_create_ ## entrypoint) = { \ + &__APPL_SYMBOL(application, instance), \ + entrypoint, \ + data, \ + priority, \ + core \ +} diff --git a/src/os/arch/riscv/hal/arch/assert.h b/src/os/arch/riscv/hal/arch/assert.h new file mode 100644 index 00000000..da19ea0b --- /dev/null +++ b/src/os/arch/riscv/hal/arch/assert.h @@ -0,0 +1,7 @@ +#pragma once + +#define ASSERT(cond) \ +if (!(cond)) \ +{\ + asm volatile("ebreak\n\t");\ +} diff --git a/src/os/arch/riscv/hal/arch/corelocal.h b/src/os/arch/riscv/hal/arch/corelocal.h new file mode 100644 index 00000000..12e1d134 --- /dev/null +++ b/src/os/arch/riscv/hal/arch/corelocal.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +/* + * RISC-V corelocal portability layer. + * + * For single-core builds, these are simple macros. + * For SMP builds, actual implementations would be needed. + */ + +typedef void (*callback_t)(); + +extern void os_core_sleep(void); + +#ifndef CMRX_ARCH_SMP_SUPPORTED + +# define coreid() 0 +# define OS_NUM_CORES 1 +# define os_smp_lock() +# define os_smp_unlock() + +/* + * Core lock/unlock via interrupt disable/enable. + * Uses RISC-V HAL primitives. + */ +static inline void os_core_lock(void) +{ + cmrx_riscv_irq_disable(); +} + +static inline void os_core_unlock(void) +{ + cmrx_riscv_irq_enable(); +} + +#else + +/* SMP mode - not implemented yet */ +extern unsigned coreid(void); +extern void os_core_lock(void); +extern void os_core_unlock(void); +extern void os_smp_lock(void); +extern void os_smp_unlock(void); + +#ifndef OS_NUM_CORES +#error "Macro OS_NUM_CORES is not defined. Use -DOS_NUM_CORES=x to tell the CMRX kernel how many cores it manages!" +#endif + +#endif /* CMRX_ARCH_SMP_SUPPORTED */ + diff --git a/src/os/arch/riscv/hal/arch/mpu.h b/src/os/arch/riscv/hal/arch/mpu.h new file mode 100644 index 00000000..a7cd582c --- /dev/null +++ b/src/os/arch/riscv/hal/arch/mpu.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +/* + * Stub MPU definitions required by the kernel headers. + * Memory protection is not implemented by this HAL. + */ + +struct MPU_Registers { + uint32_t REG1; + uint32_t REG2; +}; + +typedef struct MPU_Registers MPU_State[MPU_STATE_SIZE]; + diff --git a/src/os/arch/riscv/hal/arch/runtime.h b/src/os/arch/riscv/hal/arch/runtime.h new file mode 100644 index 00000000..22ac069e --- /dev/null +++ b/src/os/arch/riscv/hal/arch/runtime.h @@ -0,0 +1,11 @@ +#pragma once + +/* No special handling yet; platform integration will define this as needed. */ + +struct Arch_State_t { + /* intentionally left empty */ +}; + +#define os_init_arch(x) +#define os_init_core(x) + diff --git a/src/os/arch/riscv/hal/arch/static.h b/src/os/arch/riscv/hal/arch/static.h new file mode 100644 index 00000000..a0a0908d --- /dev/null +++ b/src/os/arch/riscv/hal/arch/static.h @@ -0,0 +1,8 @@ +#pragma once + +unsigned static_init_thread_count(); +const struct OS_thread_create_t * static_init_thread_table(); + +unsigned static_init_process_count(); +const struct OS_process_definition_t * static_init_process_table(); + diff --git a/src/os/arch/riscv/hal/arch/syscall.h b/src/os/arch/riscv/hal/arch/syscall.h new file mode 100644 index 00000000..52f22651 --- /dev/null +++ b/src/os/arch/riscv/hal/arch/syscall.h @@ -0,0 +1,17 @@ +#pragma once + +extern struct Syscall_Entry_t __syscall_start; +extern struct Syscall_Entry_t __syscall_end; + +static inline struct Syscall_Entry_t *os_syscalls_start(void) +{ + return &__syscall_start; +} + +static inline struct Syscall_Entry_t *os_syscalls_end(void) +{ + return &__syscall_end; +} + +#define REGISTER_SYSCALLS(...) \ +static SYSCALL_DEFINITION struct Syscall_Entry_t syscalls_ ## __COUNTER__[] = { __VA_ARGS__ } diff --git a/src/os/arch/riscv/hal/arch/sysenter.h b/src/os/arch/riscv/hal/arch/sysenter.h new file mode 100644 index 00000000..fb5e2ac3 --- /dev/null +++ b/src/os/arch/riscv/hal/arch/sysenter.h @@ -0,0 +1,34 @@ +#pragma once + +/* + * RISC-V syscall implementation. + * + * Uses standard RISC-V syscall convention: + * - Syscall number in a7 + * - Arguments in a0-a3 (already there from C calling convention) + * - Return value in a0 + * + * The ecall instruction causes mcause=11 (environment call from M-mode) + * which is handled by isr_riscv_machine_ecall_mmode_exception. + */ + +#define __SYSCALL __attribute__((naked)) __attribute__((noinline)) + +/* + * Perform syscall via ecall instruction. + * @param no syscall number (placed in a7 before ecall) + * + * The function arguments are already in a0-a3 per RISC-V calling convention. + * We load the syscall number into a7, execute ecall, then return. + * The ecall handler will place the return value in a0. + */ +#define __SVC(no, ...) \ + asm volatile( \ + "li a7, %[syscall_id]\n\t" \ + "ecall\n\t" \ + "ret\n\t" \ + : \ + : [syscall_id] "i" (no) \ + : "a7" \ + ) + diff --git a/src/os/arch/riscv/hal/riscv_hal_backend.c b/src/os/arch/riscv/hal/riscv_hal_backend.c new file mode 100644 index 00000000..80be0ea4 --- /dev/null +++ b/src/os/arch/riscv/hal/riscv_hal_backend.c @@ -0,0 +1,21 @@ +#include + +/* MIE is bit 3 in mstatus (riscv-privileged architecture document, Figure 3.6, Section 3.1.6.1, p.21). */ +#define CMRX_RISCV_MSTATUS_MIE_MASK (1u << 3u) + +void cmrx_riscv_irq_disable(void) +{ + const uint32_t mstatus = cmrx_riscv_csr_read_mstatus(); + cmrx_riscv_csr_write_mstatus(mstatus & ~CMRX_RISCV_MSTATUS_MIE_MASK); +} + +void cmrx_riscv_irq_enable(void) +{ + const uint32_t mstatus = cmrx_riscv_csr_read_mstatus(); + cmrx_riscv_csr_write_mstatus(mstatus | CMRX_RISCV_MSTATUS_MIE_MASK); +} + +bool cmrx_riscv_irq_is_enabled(void) +{ + return (cmrx_riscv_csr_read_mstatus() & CMRX_RISCV_MSTATUS_MIE_MASK) != 0u; +} diff --git a/src/os/arch/riscv/hal/riscv_hal_backend_csr.c b/src/os/arch/riscv/hal/riscv_hal_backend_csr.c new file mode 100644 index 00000000..6f71f0c7 --- /dev/null +++ b/src/os/arch/riscv/hal/riscv_hal_backend_csr.c @@ -0,0 +1,73 @@ +#include + +uint32_t cmrx_riscv_csr_read_mstatus(void) +{ + uint32_t value = 0u; + __asm__ volatile("csrr %0, mstatus" : "=r"(value)); + return value; +} + +void cmrx_riscv_csr_write_mstatus(uint32_t value) +{ + __asm__ volatile("csrw mstatus, %0" : : "r"(value)); +} + +uint32_t cmrx_riscv_csr_read_mepc(void) +{ + uint32_t value = 0u; + __asm__ volatile("csrr %0, mepc" : "=r"(value)); + return value; +} + +void cmrx_riscv_csr_write_mepc(uint32_t value) +{ + __asm__ volatile("csrw mepc, %0" : : "r"(value)); +} + +uint32_t cmrx_riscv_csr_read_mcause(void) +{ + uint32_t value = 0u; + __asm__ volatile("csrr %0, mcause" : "=r"(value)); + return value; +} + +void cmrx_riscv_csr_write_mcause(uint32_t value) +{ + __asm__ volatile("csrw mcause, %0" : : "r"(value)); +} + +uint32_t cmrx_riscv_csr_read_mtvec(void) +{ + uint32_t value = 0u; + __asm__ volatile("csrr %0, mtvec" : "=r"(value)); + return value; +} + +void cmrx_riscv_csr_write_mtvec(uint32_t value) +{ + __asm__ volatile("csrw mtvec, %0" : : "r"(value)); +} + +uint32_t cmrx_riscv_csr_read_mie(void) +{ + uint32_t value = 0u; + __asm__ volatile("csrr %0, mie" : "=r"(value)); + return value; +} + +void cmrx_riscv_csr_write_mie(uint32_t value) +{ + __asm__ volatile("csrw mie, %0" : : "r"(value)); +} + +uint32_t cmrx_riscv_csr_read_mip(void) +{ + uint32_t value = 0u; + __asm__ volatile("csrr %0, mip" : "=r"(value)); + return value; +} + +void cmrx_riscv_csr_write_mip(uint32_t value) +{ + __asm__ volatile("csrw mip, %0" : : "r"(value)); +} diff --git a/src/os/arch/riscv/hal/testing/CMakeLists.txt b/src/os/arch/riscv/hal/testing/CMakeLists.txt new file mode 100644 index 00000000..a2848e74 --- /dev/null +++ b/src/os/arch/riscv/hal/testing/CMakeLists.txt @@ -0,0 +1,9 @@ +target_sources(os PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/riscv_hal_fake.c + ${CMAKE_CURRENT_SOURCE_DIR}/riscv_context_switch_fake.c +) + +target_sources(test_kernel PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/riscv_hal.c + ${CMAKE_CURRENT_SOURCE_DIR}/riscv_context_switch.c +) diff --git a/src/os/arch/riscv/hal/testing/riscv_context_switch.c b/src/os/arch/riscv/hal/testing/riscv_context_switch.c new file mode 100644 index 00000000..61baddea --- /dev/null +++ b/src/os/arch/riscv/hal/testing/riscv_context_switch.c @@ -0,0 +1,114 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "riscv_context_switch_fake.h" + +static uint32_t riscv_stack_a[64] __attribute__((aligned(16))); +static uint32_t riscv_stack_b[64] __attribute__((aligned(16))); + +extern bool riscv_context_switch_called; +extern bool riscv_context_switch_perform_switch; +extern uint32_t *riscv_context_switch_fake_sp; + +static intmax_t riscv_ptr_value(const void *ptr) +{ + return (intmax_t)(uintptr_t) ptr; +} + +static void riscv_reset_state(void) +{ + memset(&cpu_context, 0, sizeof(cpu_context)); + memset(os_threads, 0, sizeof(os_threads)); + memset(core, 0, sizeof(core)); + os_riscv_context_switch_request(false); + riscv_context_switch_called = false; + riscv_context_switch_perform_switch = true; + riscv_context_switch_fake_sp = NULL; +} + +static void riscv_setup_switch(uint32_t **old_sp, uint32_t **new_saved_sp) +{ + riscv_reset_state(); + + core[0].thread_current = 0; + core[0].thread_next = 1; + + os_threads[0].state = THREAD_STATE_RUNNING; + os_threads[1].state = THREAD_STATE_READY; + + cpu_context.old_task = &os_threads[0]; + cpu_context.new_task = &os_threads[1]; + + *old_sp = &riscv_stack_a[64]; + *new_saved_sp = &riscv_stack_b[32]; + + cpu_context.new_task->sp = *new_saved_sp; + riscv_context_switch_fake_sp = *old_sp; +} + +CTEST_DATA(riscv_context_switch_reset) { +}; + +CTEST_SETUP(riscv_context_switch_reset) { + riscv_reset_state(); +} + +CTEST2(riscv_context_switch_reset, request_set_clear) +{ + os_riscv_context_switch_request(false); + ASSERT_EQUAL(os_riscv_context_switch_is_pending(), false); + + os_riscv_context_switch_request(true); + ASSERT_EQUAL(os_riscv_context_switch_is_pending(), true); + + os_riscv_context_switch_request(false); + ASSERT_EQUAL(os_riscv_context_switch_is_pending(), false); +} + +CTEST_DATA(riscv_context_switch_safe_point) { + uint32_t *old_sp; + uint32_t *new_saved_sp; +}; + +CTEST_SETUP(riscv_context_switch_safe_point) { + uint32_t *old_sp = NULL; + uint32_t *new_saved_sp = NULL; + + memset(data, 0, sizeof(*data)); + riscv_setup_switch(&old_sp, &new_saved_sp); + data->old_sp = old_sp; + data->new_saved_sp = new_saved_sp; +} + +CTEST2(riscv_context_switch_safe_point, safe_point_consumes_once) +{ + os_riscv_context_switch_request(true); + + os_riscv_context_switch_safe_point(); + ASSERT_EQUAL(os_riscv_context_switch_is_pending(), false); + + uint32_t *saved_old_sp = cpu_context.old_task->sp; + os_riscv_context_switch_safe_point(); + ASSERT_EQUAL(riscv_ptr_value(cpu_context.old_task->sp), riscv_ptr_value(saved_old_sp)); +} + +CTEST2(riscv_context_switch_safe_point, sp_bookkeeping_and_state_transition) +{ + os_riscv_context_switch_request(true); + os_riscv_context_switch_safe_point(); + + ASSERT_EQUAL(riscv_ptr_value(cpu_context.old_task->sp), + riscv_ptr_value(data->old_sp)); + ASSERT_EQUAL(riscv_ptr_value(riscv_context_switch_fake_sp), + riscv_ptr_value(data->new_saved_sp)); + + ASSERT_EQUAL(core[0].thread_current, 1); + ASSERT_EQUAL(os_threads[0].state, THREAD_STATE_READY); + ASSERT_EQUAL(os_threads[1].state, THREAD_STATE_RUNNING); +} diff --git a/src/os/arch/riscv/hal/testing/riscv_context_switch_fake.c b/src/os/arch/riscv/hal/testing/riscv_context_switch_fake.c new file mode 100644 index 00000000..02ed19dd --- /dev/null +++ b/src/os/arch/riscv/hal/testing/riscv_context_switch_fake.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "riscv_context_switch_fake.h" + +bool riscv_context_switch_called = false; +bool riscv_context_switch_perform_switch = true; +uint32_t *riscv_context_switch_fake_sp = NULL; + +static volatile bool pending_context_switch[OS_NUM_CORES]; + +void os_riscv_context_switch_request(bool activate) +{ + pending_context_switch[coreid()] = activate; +} + +bool os_riscv_context_switch_is_pending(void) +{ + return pending_context_switch[coreid()]; +} + +static void os_riscv_context_switch_update_state(void) +{ + struct OS_core_state_t * const cpu_state = &core[coreid()]; + + if (os_threads[cpu_state->thread_current].state == THREAD_STATE_RUNNING) + { + os_threads[cpu_state->thread_current].state = THREAD_STATE_READY; + } + + cpu_state->thread_current = cpu_state->thread_next; + os_threads[cpu_state->thread_current].state = THREAD_STATE_RUNNING; +} + +static void os_riscv_context_switch_perform(void) +{ + cpu_context.old_task->sp = riscv_context_switch_fake_sp; + riscv_context_switch_fake_sp = cpu_context.new_task->sp; + riscv_context_switch_called = true; +} + +void os_riscv_context_switch_safe_point(void) +{ + if (!os_riscv_context_switch_is_pending()) + { + return; + } + + os_riscv_context_switch_request(false); + + if (!riscv_context_switch_perform_switch) + { + return; + } + + os_riscv_context_switch_update_state(); + os_riscv_context_switch_perform(); +} diff --git a/src/os/arch/riscv/hal/testing/riscv_context_switch_fake.h b/src/os/arch/riscv/hal/testing/riscv_context_switch_fake.h new file mode 100644 index 00000000..e55ff0de --- /dev/null +++ b/src/os/arch/riscv/hal/testing/riscv_context_switch_fake.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +extern bool riscv_context_switch_called; +extern bool riscv_context_switch_perform_switch; +extern uint32_t *riscv_context_switch_fake_sp; diff --git a/src/os/arch/riscv/hal/testing/riscv_hal.c b/src/os/arch/riscv/hal/testing/riscv_hal.c new file mode 100644 index 00000000..ae7fe0b8 --- /dev/null +++ b/src/os/arch/riscv/hal/testing/riscv_hal.c @@ -0,0 +1,45 @@ +#include + +#include + +CTEST(riscv_hal, csr_read_write_roundtrip) +{ + /* mstatus */ + cmrx_riscv_csr_write_mstatus(0x11223344u); + ASSERT_EQUAL(cmrx_riscv_csr_read_mstatus(), 0x11223344u); + cmrx_riscv_csr_write_mstatus(0xa5a5a5a5u); + ASSERT_EQUAL(cmrx_riscv_csr_read_mstatus(), 0xa5a5a5a5u); + + /* mepc */ + cmrx_riscv_csr_write_mepc(0x01020304u); + ASSERT_EQUAL(cmrx_riscv_csr_read_mepc(), 0x01020304u); + + /* mcause */ + cmrx_riscv_csr_write_mcause(0x8000000bu); + ASSERT_EQUAL(cmrx_riscv_csr_read_mcause(), 0x8000000bu); + + /* mtvec */ + cmrx_riscv_csr_write_mtvec(0x00001000u); + ASSERT_EQUAL(cmrx_riscv_csr_read_mtvec(), 0x00001000u); + + /* mie */ + cmrx_riscv_csr_write_mie(0x00000088u); + ASSERT_EQUAL(cmrx_riscv_csr_read_mie(), 0x00000088u); + + /* mip */ + cmrx_riscv_csr_write_mip(0x00000008u); + ASSERT_EQUAL(cmrx_riscv_csr_read_mip(), 0x00000008u); +} + +CTEST(riscv_hal, irq_enable_disable) +{ + cmrx_riscv_irq_disable(); + ASSERT_EQUAL(cmrx_riscv_irq_is_enabled(), false); + + cmrx_riscv_irq_enable(); + ASSERT_EQUAL(cmrx_riscv_irq_is_enabled(), true); + + cmrx_riscv_irq_disable(); + ASSERT_EQUAL(cmrx_riscv_irq_is_enabled(), false); +} + diff --git a/src/os/arch/riscv/hal/testing/riscv_hal_fake.c b/src/os/arch/riscv/hal/testing/riscv_hal_fake.c new file mode 100644 index 00000000..2e35e3a5 --- /dev/null +++ b/src/os/arch/riscv/hal/testing/riscv_hal_fake.c @@ -0,0 +1,50 @@ +/* + * RISC-V HAL backend used by unit tests. + * + * This backend provides in-memory fake CSR values and interrupt state so the HAL + * API can be tested on the host without any vendor headers or hardware. + */ + +/* + * NOTE: Some unit-test build configurations may redefine `static` for testability. + * This backend must keep its fakes translation-unit local, so we explicitly undo + * that here if needed. + */ +#ifdef static +#undef static +#endif + +#include + +static uint32_t fake_mstatus; +static uint32_t fake_mepc; +static uint32_t fake_mcause; +static uint32_t fake_mtvec; +static uint32_t fake_mie; +static uint32_t fake_mip; + +/* IRQ enable state is represented as a uint32_t fake (0/1) per requirements. */ +static uint32_t fake_irq_enabled; + +uint32_t cmrx_riscv_csr_read_mstatus(void) { return fake_mstatus; } +void cmrx_riscv_csr_write_mstatus(uint32_t value) { fake_mstatus = value; } + +uint32_t cmrx_riscv_csr_read_mepc(void) { return fake_mepc; } +void cmrx_riscv_csr_write_mepc(uint32_t value) { fake_mepc = value; } + +uint32_t cmrx_riscv_csr_read_mcause(void) { return fake_mcause; } +void cmrx_riscv_csr_write_mcause(uint32_t value) { fake_mcause = value; } + +uint32_t cmrx_riscv_csr_read_mtvec(void) { return fake_mtvec; } +void cmrx_riscv_csr_write_mtvec(uint32_t value) { fake_mtvec = value; } + +uint32_t cmrx_riscv_csr_read_mie(void) { return fake_mie; } +void cmrx_riscv_csr_write_mie(uint32_t value) { fake_mie = value; } + +uint32_t cmrx_riscv_csr_read_mip(void) { return fake_mip; } +void cmrx_riscv_csr_write_mip(uint32_t value) { fake_mip = value; } + +void cmrx_riscv_irq_disable(void) { fake_irq_enabled = 0u; } +void cmrx_riscv_irq_enable(void) { fake_irq_enabled = 1u; } +bool cmrx_riscv_irq_is_enabled(void) { return fake_irq_enabled != 0u; } + diff --git a/src/os/arch/riscv/mpu.c b/src/os/arch/riscv/mpu.c new file mode 100644 index 00000000..17b78b47 --- /dev/null +++ b/src/os/arch/riscv/mpu.c @@ -0,0 +1,109 @@ +/* + * CMRX RISC-V Memory Protection stubs. + * + * RISC-V uses PMP (Physical Memory Protection) instead of MPU. + * This file provides stub implementations to allow the kernel to build + * without actual memory protection. + * + * Memory protection for RISC-V is NOT implemented in this port. + * These stubs allow the kernel to link and run without protection. + */ + +#include +#include +#include +#include +#include +#include + +/* + * Restore MPU state. + * Stub - does nothing since PMP is not implemented. + */ +int mpu_restore(const MPU_State *hosted_state, const MPU_State *parent_state) +{ + (void)hosted_state; + (void)parent_state; + return E_OK; +} + +/* + * Initialize stack protection for a thread. + * Stub - does nothing since PMP is not implemented. + */ +int mpu_init_stack(int thread_id) +{ + (void)thread_id; + return E_OK; +} + +/* + * Start memory protection. + * Stub - does nothing since PMP is not implemented. + */ +void os_memory_protection_start(void) +{ + /* PMP not implemented */ +} + +/* + * Stop memory protection. + * Stub - does nothing since PMP is not implemented. + */ +void os_memory_protection_stop(void) +{ + /* PMP not implemented */ +} + +/* + * Set an MPU region. + * Stub - does nothing since PMP is not implemented. + */ +int mpu_set_region(uint8_t region, const void *base, uint32_t size, uint8_t cls) +{ + (void)region; + (void)base; + (void)size; + (void)cls; + return E_OK; +} + +/* + * Configure an MPU region. + * Stub - does nothing since PMP is not implemented. + */ +int mpu_configure_region(uint8_t region, const void *base, uint32_t size, + uint8_t cls, uint32_t *RBAR, uint32_t *RASR) +{ + (void)region; + (void)base; + (void)size; + (void)cls; + + if (RBAR) *RBAR = 0; + if (RASR) *RASR = 0; + + return E_OK; +} + +/* + * Clear an MPU region. + * Stub - does nothing since PMP is not implemented. + */ +int mpu_clear_region(uint8_t region) +{ + (void)region; + return E_OK; +} + +/* + * Check if an address is within MPU bounds. + * Stub - always returns false since PMP is not implemented. + */ +bool mpu_check_bounds(const MPU_State *state, uint8_t region, uint32_t *address) +{ + (void)state; + (void)region; + (void)address; + return false; +} diff --git a/src/os/arch/riscv/rpc.c b/src/os/arch/riscv/rpc.c new file mode 100644 index 00000000..cc899812 --- /dev/null +++ b/src/os/arch/riscv/rpc.c @@ -0,0 +1,20 @@ +#include +#include + +int os_rpc_call(unsigned long arg0, unsigned long arg1, unsigned long arg2, unsigned long arg3) +{ + (void)arg0; + (void)arg1; + (void)arg2; + (void)arg3; + return E_NOTAVAIL; +} + +int os_rpc_return(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint32_t arg3) +{ + (void)arg0; + (void)arg1; + (void)arg2; + (void)arg3; + return E_NOTAVAIL; +} diff --git a/src/os/arch/riscv/sched.c b/src/os/arch/riscv/sched.c new file mode 100644 index 00000000..aa32e860 --- /dev/null +++ b/src/os/arch/riscv/sched.c @@ -0,0 +1,171 @@ +/* + * CMRX RISC-V scheduler architecture support. + * + * Provides RISC-V implementations for scheduler primitives: + * - Thread stack population + * - Thread boot + * - Kernel shutdown + * - Syscall return value manipulation + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Populate stack of new thread so it can be executed. + * + * Places an ExceptionFrame at the top of the thread stack. When the + * trap handler restores from this frame after a context switch, the + * thread starts executing at 'entrypoint' with 'data' in a0 and + * os_thread_dispose in ra. + * + * This follows the same pattern as ARM's os_thread_populate_stack + * which builds an ExceptionFrame for PendSV to restore. + */ +uint32_t *os_thread_populate_stack(int stack_id, unsigned stack_size, + entrypoint_t *entrypoint, void *data) +{ + uint32_t *stack = os_stack_get(stack_id); + ExceptionFrame *frame = (ExceptionFrame *)&stack[stack_size - EXCEPTION_FRAME_WORDS]; + + memset(frame, 0, sizeof(*frame)); + frame->ra = (uint32_t)os_thread_dispose; + frame->a0 = (uint32_t)data; + frame->mepc = (uint32_t)entrypoint; + frame->mstatus = CMRX_RISCV_INITIAL_MSTATUS; + + return (uint32_t *)frame; +} + +/** Platform-specific way of initializing threads. + * Populates the stack of the thread with the initial values. + * @param thread pointer to the thread to initialize + * @param stack_size size of the stack + * @param entrypoint address of the entrypoint + * @param data pointer to the data + */ +void os_thread_initialize_arch(struct OS_thread_t * thread, unsigned stack_size, entrypoint_t * entrypoint, void * data) +{ + thread->sp = os_thread_populate_stack(thread->stack_id, stack_size, entrypoint, data); +} + +/* + * Create a process. + * + * For RISC-V without PMP/MPU implementation, this is a minimal stub + * that just records the process definition. + */ +int os_process_create(Process_t process_id, + const struct OS_process_definition_t *definition) +{ + if (process_id >= OS_PROCESSES) { + return E_OUT_OF_RANGE; + } + + if (os_processes[process_id].definition != NULL) { + return E_INVALID; + } + + os_processes[process_id].definition = definition; + + /* No MPU configuration - PMP not implemented */ + + return E_OK; +} + +/* + * Boot the first thread. + * + * Reads the ExceptionFrame from the thread stack and uses mret to + * enter user code. This mirrors the normal trap-return path: mepc + * and mstatus are restored via CSR writes, then mret jumps to mepc + * while promoting MPIE → MIE (enabling interrupts). + */ +__attribute__((noreturn)) +void os_boot_thread(Thread_t boot_thread) +{ + struct OS_thread_t *thread = os_thread_get(boot_thread); + ExceptionFrame *frame = (ExceptionFrame *)thread->sp; + + __asm__ volatile( + "csrw mepc, %[mepc]\n\t" + "csrw mstatus, %[mstatus]\n\t" + "mv sp, %[sp_after]\n\t" + "mv ra, %[ra]\n\t" + "mv a0, %[a0]\n\t" + "mret\n\t" + : + : [mepc] "r"(frame->mepc), + [mstatus] "r"(frame->mstatus), + [sp_after] "r"((uint32_t *)frame + EXCEPTION_FRAME_WORDS), + [ra] "r"(frame->ra), + [a0] "r"(frame->a0) + : "memory" + ); + + __builtin_unreachable(); +} + +/* + * Default CMRX shutdown handler. + * This can be overridden by the application. + */ +__attribute__((weak, noreturn)) +void cmrx_shutdown_handler(void) +{ + /* Default: infinite loop */ + while (1) { + __asm__ volatile("wfi"); + } +} + +/* + * Perform the kernel shutdown. + * Disables interrupts and calls the shutdown handler. + */ +__attribute__((noreturn)) +void os_kernel_shutdown(void) +{ + os_core_lock(); /* Disable interrupts */ + cmrx_shutdown_handler(); + + /* Never reached */ + __builtin_unreachable(); +} + +/* + * Set the return value for a syscall on a specific thread. + * + * This is used for async syscalls where the return value is set + * after the thread has been blocked and will resume later. + * + * For RISC-V, we need to modify a0 in the thread's saved context. + */ +int os_set_syscall_return_value(Thread_t thread_id, int32_t retval) +{ + struct OS_thread_t *thread = os_thread_get(thread_id); + if (thread == NULL) { + return E_INVALID; + } + + ExceptionFrame *frame = (ExceptionFrame *)thread->sp; + riscv_exception_set_retval(frame, retval); + + return E_OK; +} + +/* + * Put the CPU core to sleep. + * Uses RISC-V WFI (Wait For Interrupt) instruction. + */ +void os_core_sleep(void) +{ + __asm__ volatile("wfi"); +} diff --git a/src/os/arch/riscv/static.c b/src/os/arch/riscv/static.c new file mode 100644 index 00000000..281b197f --- /dev/null +++ b/src/os/arch/riscv/static.c @@ -0,0 +1,56 @@ +/* + * CMRX RISC-V static initialization. + * + * Provides functions to retrieve static initialization structures + * for threads and processes defined at compile time. + * + * These functions rely on linker-generated symbols that mark the + * boundaries of the application and thread tables. + */ + +#include +#include + +/* + * Linker-generated symbols marking the boundaries of the + * statically-defined application and thread tables. + * + * These are populated by the OS_APPLICATION and OS_THREAD_CREATE macros. + */ +extern const struct OS_process_definition_t __applications_start; +extern const struct OS_process_definition_t __applications_end; + +extern const struct OS_thread_create_t __thread_create_start; +extern const struct OS_thread_create_t __thread_create_end; + +/* + * Get the count of statically-defined threads. + */ +unsigned static_init_thread_count(void) +{ + return &__thread_create_end - &__thread_create_start; +} + +/* + * Get pointer to the static thread creation table. + */ +const struct OS_thread_create_t *static_init_thread_table(void) +{ + return &__thread_create_start; +} + +/* + * Get the count of statically-defined processes/applications. + */ +unsigned static_init_process_count(void) +{ + return &__applications_end - &__applications_start; +} + +/* + * Get pointer to the static process definition table. + */ +const struct OS_process_definition_t *static_init_process_table(void) +{ + return &__applications_start; +} diff --git a/src/os/arch/testing/CMakeLists.txt b/src/os/arch/testing/CMakeLists.txt index 57cf279c..4e9a3996 100644 --- a/src/os/arch/testing/CMakeLists.txt +++ b/src/os/arch/testing/CMakeLists.txt @@ -9,3 +9,5 @@ set(cmrx_testing_SRCS ) target_sources(os PRIVATE ${cmrx_testing_SRCS}) target_link_libraries(os PRIVATE ctest) + +add_subdirectory(../riscv/hal/testing ${CMAKE_CURRENT_BINARY_DIR}/riscv_hal_testing) diff --git a/src/os/kernel/tests/CMakeLists.txt b/src/os/kernel/tests/CMakeLists.txt index 90bb709e..da0fd46c 100644 --- a/src/os/kernel/tests/CMakeLists.txt +++ b/src/os/kernel/tests/CMakeLists.txt @@ -49,3 +49,16 @@ add_executable(test_algo ${test_algo_SRCS}) target_link_libraries(test_algo ctest) add_test(NAME test_algo COMMAND test_algo) + +get_property(CMRX_ROOT_DIR GLOBAL PROPERTY CMRX_ROOT_DIR) +set(riscv_hal_backend_test_SRCS + riscv_hal_backend.c + riscv_hal_backend_fake_csr.c + ${CMRX_ROOT_DIR}/src/os/arch/riscv/hal/riscv_hal_backend.c + ) + +add_executable(test_riscv_hal_backend ${riscv_hal_backend_test_SRCS}) +target_include_directories(test_riscv_hal_backend PRIVATE ${CMRX_ROOT_DIR}/include) +target_link_libraries(test_riscv_hal_backend ctest) +add_test(NAME test_riscv_hal_backend COMMAND test_riscv_hal_backend) + diff --git a/src/os/kernel/tests/riscv_hal_backend.c b/src/os/kernel/tests/riscv_hal_backend.c new file mode 100644 index 00000000..0317273f --- /dev/null +++ b/src/os/kernel/tests/riscv_hal_backend.c @@ -0,0 +1,32 @@ +#include + +#include + +/* MIE is bit 3 in mstatus (RISC-V Privileged Specification, Section 3.1.6.1). */ +#define CMRX_RISCV_MSTATUS_MIE_MASK (1u << 3u) + +CTEST(riscv_hal_backend, irq_enable_sets_mie_only) +{ + const uint32_t base = 0x5a5a5a5au & ~CMRX_RISCV_MSTATUS_MIE_MASK; + + cmrx_riscv_csr_write_mstatus(base); + cmrx_riscv_irq_enable(); + + const uint32_t after = cmrx_riscv_csr_read_mstatus(); + ASSERT_EQUAL(after & CMRX_RISCV_MSTATUS_MIE_MASK, CMRX_RISCV_MSTATUS_MIE_MASK); + ASSERT_EQUAL(after & ~CMRX_RISCV_MSTATUS_MIE_MASK, base & ~CMRX_RISCV_MSTATUS_MIE_MASK); + ASSERT_EQUAL(cmrx_riscv_irq_is_enabled(), true); +} + +CTEST(riscv_hal_backend, irq_disable_clears_mie_only) +{ + const uint32_t base = 0xa5a5a5a5u | CMRX_RISCV_MSTATUS_MIE_MASK; + + cmrx_riscv_csr_write_mstatus(base); + cmrx_riscv_irq_disable(); + + const uint32_t after = cmrx_riscv_csr_read_mstatus(); + ASSERT_EQUAL(after & CMRX_RISCV_MSTATUS_MIE_MASK, 0u); + ASSERT_EQUAL(after & ~CMRX_RISCV_MSTATUS_MIE_MASK, base & ~CMRX_RISCV_MSTATUS_MIE_MASK); + ASSERT_EQUAL(cmrx_riscv_irq_is_enabled(), false); +} diff --git a/src/os/kernel/tests/riscv_hal_backend_fake_csr.c b/src/os/kernel/tests/riscv_hal_backend_fake_csr.c new file mode 100644 index 00000000..86df6fd9 --- /dev/null +++ b/src/os/kernel/tests/riscv_hal_backend_fake_csr.c @@ -0,0 +1,26 @@ +#include + +static uint32_t fake_mstatus; +static uint32_t fake_mepc; +static uint32_t fake_mcause; +static uint32_t fake_mtvec; +static uint32_t fake_mie; +static uint32_t fake_mip; + +uint32_t cmrx_riscv_csr_read_mstatus(void) { return fake_mstatus; } +void cmrx_riscv_csr_write_mstatus(uint32_t value) { fake_mstatus = value; } + +uint32_t cmrx_riscv_csr_read_mepc(void) { return fake_mepc; } +void cmrx_riscv_csr_write_mepc(uint32_t value) { fake_mepc = value; } + +uint32_t cmrx_riscv_csr_read_mcause(void) { return fake_mcause; } +void cmrx_riscv_csr_write_mcause(uint32_t value) { fake_mcause = value; } + +uint32_t cmrx_riscv_csr_read_mtvec(void) { return fake_mtvec; } +void cmrx_riscv_csr_write_mtvec(uint32_t value) { fake_mtvec = value; } + +uint32_t cmrx_riscv_csr_read_mie(void) { return fake_mie; } +void cmrx_riscv_csr_write_mie(uint32_t value) { fake_mie = value; } + +uint32_t cmrx_riscv_csr_read_mip(void) { return fake_mip; } +void cmrx_riscv_csr_write_mip(uint32_t value) { fake_mip = value; }