Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions cmake/arch/riscv/hal/CMRX.cmake
Original file line number Diff line number Diff line change
@@ -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()
36 changes: 36 additions & 0 deletions cmake/arch/riscv/hal/cmrx_sections.ld
Original file line number Diff line number Diff line change
@@ -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 = .);
}
}
9 changes: 9 additions & 0 deletions include/cmrx/arch/riscv/context_switch.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

#include <stdbool.h>
#include <stdint.h>
#include <cmrx/arch/riscv/exception_frame.h>

void os_riscv_context_switch_request(bool activate);
bool os_riscv_context_switch_is_pending(void);
void os_riscv_context_switch_safe_point(void);
260 changes: 260 additions & 0 deletions include/cmrx/arch/riscv/exception_frame.h
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>

/** 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__ */

/** @} */
Loading
Loading