From 4d4e0dbb7d3af628091faf21b226fabd843ee8c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Sat, 4 May 2024 17:55:31 +0200 Subject: [PATCH] _dispatch_interrupt function and docs --- riscv-rt/CHANGELOG.md | 2 + riscv-rt/link.x.in | 19 +++-- riscv-rt/macros/src/lib.rs | 163 +++++++++++++++++++++++-------------- riscv-rt/src/asm.rs | 15 ++-- riscv-rt/src/lib.rs | 66 +++++++++++++-- 5 files changed, 182 insertions(+), 83 deletions(-) diff --git a/riscv-rt/CHANGELOG.md b/riscv-rt/CHANGELOG.md index 7348a237..073b4505 100644 --- a/riscv-rt/CHANGELOG.md +++ b/riscv-rt/CHANGELOG.md @@ -19,6 +19,8 @@ If `v-trap` feature is enabled, this macro also generates its corresponding trap - Moved all the assembly code to `asm.rs` - Use `weak` symbols for functions such as `_mp_hook` or `_start_trap` - `abort` is now `weak`, so it is possible to link third-party libraries including this symbol. +- `_start_trap_rust` now only deals with exceptions. When an interrupt is detected, it now calls +to `_dispatch_interrupt`. ### Removed diff --git a/riscv-rt/link.x.in b/riscv-rt/link.x.in index 462ddd58..fc73f8ac 100644 --- a/riscv-rt/link.x.in +++ b/riscv-rt/link.x.in @@ -35,15 +35,20 @@ PROVIDE(_heap_size = 0); and then returns. Users can override this alias by defining the symbol themselves */ EXTERN(_start_trap); +/* Default interrupt trap entry point. When vectored trap mode is enabled, + the riscv-rt crate provides an implementation of this function, which saves caller saved + registers, calls the the DefaultHandler ISR, restores caller saved registers and returns. */ +PROVIDE(_start_DefaultHandler_trap = _start_trap); + /* When vectored trap mode is enabled, each interrupt source must implement its own trap entry point. By default, all interrupts start in _start_trap. However, users can override these alias by defining the symbol themselves */ -PROVIDE(_start_SupervisorSoft_trap = _start_trap); -PROVIDE(_start_MachineSoft_trap = _start_trap); -PROVIDE(_start_SupervisorTimer_trap = _start_trap); -PROVIDE(_start_MachineTimer_trap = _start_trap); -PROVIDE(_start_SupervisorExternal_trap = _start_trap); -PROVIDE(_start_MachineExternal_trap = _start_trap); +PROVIDE(_start_SupervisorSoft_trap = _start_DefaultHandler_trap); +PROVIDE(_start_MachineSoft_trap = _start_DefaultHandler_trap); +PROVIDE(_start_SupervisorTimer_trap = _start_DefaultHandler_trap); +PROVIDE(_start_MachineTimer_trap = _start_DefaultHandler_trap); +PROVIDE(_start_SupervisorExternal_trap = _start_DefaultHandler_trap); +PROVIDE(_start_MachineExternal_trap = _start_DefaultHandler_trap); /** EXCEPTION HANDLERS **/ @@ -61,7 +66,7 @@ PROVIDE(Breakpoint = ExceptionHandler); PROVIDE(LoadMisaligned = ExceptionHandler); PROVIDE(LoadFault = ExceptionHandler); PROVIDE(StoreMisaligned = ExceptionHandler); -PROVIDE(StoreFault = ExceptionHandler);; +PROVIDE(StoreFault = ExceptionHandler); PROVIDE(UserEnvCall = ExceptionHandler); PROVIDE(SupervisorEnvCall = ExceptionHandler); PROVIDE(MachineEnvCall = ExceptionHandler); diff --git a/riscv-rt/macros/src/lib.rs b/riscv-rt/macros/src/lib.rs index f48f7870..1e1f32a5 100644 --- a/riscv-rt/macros/src/lib.rs +++ b/riscv-rt/macros/src/lib.rs @@ -319,9 +319,11 @@ enum RiscvArch { Rv64, } +/// Size of the trap frame (in number of registers) const TRAP_SIZE: usize = 16; #[rustfmt::skip] +/// List of the register names to be stored in the trap frame const TRAP_FRAME: [&str; TRAP_SIZE] = [ "ra", "t0", @@ -341,6 +343,14 @@ const TRAP_FRAME: [&str; TRAP_SIZE] = [ "a7", ]; +/// Generate the assembly instructions to store the trap frame. +/// +/// The `arch` parameter is used to determine the width of the registers. +/// +/// The `filter` function is used to filter which registers to store. +/// This is useful to optimize the binary size in vectored interrupt mode, which divides the trap +/// frame storage in two parts: the first part saves space in the stack and stores only the `a0` register, +/// while the second part stores the remaining registers. fn store_trap bool>(arch: RiscvArch, mut filter: T) -> String { let (width, store) = match arch { RiscvArch::Rv32 => (4, "sw"), @@ -357,6 +367,8 @@ fn store_trap bool>(arch: RiscvArch, mut filter: T) -> String stores.join("\n") } +/// Generate the assembly instructions to load the trap frame. +/// The `arch` parameter is used to determine the width of the registers. fn load_trap(arch: RiscvArch) -> String { let (width, load) = match arch { RiscvArch::Rv32 => (4, "lw"), @@ -369,16 +381,31 @@ fn load_trap(arch: RiscvArch) -> String { loads.join("\n") } +/// Generates weak `_start_trap` function in assembly for RISCV-32 targets. +/// +/// This implementation stores all registers in the trap frame and calls `_start_trap_rust`. +/// The trap frame is allocated on the stack and deallocated after the call. #[proc_macro] pub fn weak_start_trap_riscv32(_input: TokenStream) -> TokenStream { weak_start_trap(RiscvArch::Rv32) } +/// Generates weak `_start_trap` function in assembly for RISCV-64 targets. +/// +/// This implementation stores all registers in the trap frame and calls `_start_trap_rust`. +/// The trap frame is allocated on the stack and deallocated after the call. #[proc_macro] pub fn weak_start_trap_riscv64(_input: TokenStream) -> TokenStream { weak_start_trap(RiscvArch::Rv64) } +/// Generates weak `_start_trap` function in assembly. +/// +/// This implementation stores all registers in the trap frame and calls `_start_trap_rust`. +/// The trap frame is allocated on the stack and deallocated after the call. +/// +/// The `arch` parameter is used to determine the width of the registers. +/// The macro also ensures that the trap frame size is 16-byte aligned. fn weak_start_trap(arch: RiscvArch) -> TokenStream { let width = match arch { RiscvArch::Rv32 => 4, @@ -398,7 +425,7 @@ fn weak_start_trap(arch: RiscvArch) -> TokenStream { #[cfg(not(feature = "s-mode"))] let ret = "mret"; - let instructions: proc_macro2::TokenStream = format!( + format!( " core::arch::global_asm!( \".section .trap, \\\"ax\\\" @@ -415,26 +442,76 @@ _start_trap: \");" ) .parse() - .unwrap(); + .unwrap() +} - #[cfg(feature = "v-trap")] - let v_trap = v_trap::continue_interrupt_trap(arch); - #[cfg(not(feature = "v-trap"))] - let v_trap = proc_macro2::TokenStream::new(); +/// Generates vectored interrupt trap functions in assembly for RISCV-32 targets. +#[cfg(feature = "v-trap")] +#[proc_macro] +pub fn vectored_interrupt_trap_riscv32(_input: TokenStream) -> TokenStream { + vectored_interrupt_trap(RiscvArch::Rv32) +} - quote!( - #instructions - #v_trap - ) - .into() +/// Generates vectored interrupt trap functions in assembly for RISCV-64 targets. +#[cfg(feature = "v-trap")] +#[proc_macro] +pub fn vectored_interrupt_trap_riscv64(_input: TokenStream) -> TokenStream { + vectored_interrupt_trap(RiscvArch::Rv64) +} + +#[cfg(feature = "v-trap")] +/// Generates global '_start_DefaultHandler_trap' and '_continue_interrupt_trap' functions in assembly. +/// The '_start_DefaultHandler_trap' function stores the trap frame partially (only register a0) and +/// jumps to the interrupt handler. The '_continue_interrupt_trap' function stores the trap frame +/// partially (all registers except a0), jumps to the interrupt handler, and restores the trap frame. +fn vectored_interrupt_trap(arch: RiscvArch) -> TokenStream { + let width = match arch { + RiscvArch::Rv32 => 4, + RiscvArch::Rv64 => 8, + }; + let store_start = store_trap(arch, |reg| reg == "a0"); + let store_continue = store_trap(arch, |reg| reg != "a0"); + let load = load_trap(arch); + + #[cfg(feature = "s-mode")] + let ret = "sret"; + #[cfg(not(feature = "s-mode"))] + let ret = "mret"; + + let instructions = format!( + " +core::arch::global_asm!( +\".section .trap, \\\"ax\\\" + +.global _start_DefaultHandler_trap +_start_DefaultHandler_trap: + addi sp, sp, -{TRAP_SIZE} * {width} // allocate space for trap frame + {store_start} // store trap partially (only register a0) + la a0, DefaultHandler // load interrupt handler address into a0 + +.global _continue_interrupt_trap +_continue_interrupt_trap: + {store_continue} // store trap partially (all registers except a0) + jalr ra, a0, 0 // jump to corresponding interrupt handler (address stored in a0) + {load} // restore trap frame + addi sp, sp, {TRAP_SIZE} * {width} // deallocate space for trap frame + {ret} // return from interrupt +\");" + ); + + instructions.parse().unwrap() } #[proc_macro_attribute] +/// Attribute to declare an interrupt handler. The function must have the signature `[unsafe] fn() [-> !]`. +/// If the `v-trap` feature is enabled, this macro generates the interrupt trap handler in assembly for RISCV-32 targets. pub fn interrupt_riscv32(args: TokenStream, input: TokenStream) -> TokenStream { interrupt(args, input, RiscvArch::Rv32) } #[proc_macro_attribute] +/// Attribute to declare an interrupt handler. The function must have the signature `[unsafe] fn() [-> !]`. +/// If the `v-trap` feature is enabled, this macro generates the interrupt trap handler in assembly for RISCV-32 targets. pub fn interrupt_riscv64(args: TokenStream, input: TokenStream) -> TokenStream { interrupt(args, input, RiscvArch::Rv64) } @@ -487,7 +564,7 @@ fn interrupt(args: TokenStream, input: TokenStream, _arch: RiscvArch) -> TokenSt #[cfg(not(feature = "v-trap"))] let start_trap = proc_macro2::TokenStream::new(); #[cfg(feature = "v-trap")] - let start_trap = v_trap::start_interrupt_trap(ident, _arch); + let start_trap = start_interrupt_trap(ident, _arch); quote!( #start_trap @@ -498,25 +575,19 @@ fn interrupt(args: TokenStream, input: TokenStream, _arch: RiscvArch) -> TokenSt } #[cfg(feature = "v-trap")] -mod v_trap { - use super::*; - - pub(crate) fn start_interrupt_trap( - ident: &syn::Ident, - arch: RiscvArch, - ) -> proc_macro2::TokenStream { - let interrupt = ident.to_string(); - let width = match arch { - RiscvArch::Rv32 => 4, - RiscvArch::Rv64 => 8, - }; - let store = store_trap(arch, |r| r == "a0"); +fn start_interrupt_trap(ident: &syn::Ident, arch: RiscvArch) -> proc_macro2::TokenStream { + let interrupt = ident.to_string(); + let width = match arch { + RiscvArch::Rv32 => 4, + RiscvArch::Rv64 => 8, + }; + let store = store_trap(arch, |r| r == "a0"); - let instructions = format!( - " + let instructions = format!( + " core::arch::global_asm!( \".section .trap, \\\"ax\\\" - .align {width} + .align 2 .global _start_{interrupt}_trap _start_{interrupt}_trap: addi sp, sp, -{TRAP_SIZE} * {width} // allocate space for trap frame @@ -524,39 +595,7 @@ core::arch::global_asm!( la a0, {interrupt} // load interrupt handler address into a0 j _continue_interrupt_trap // jump to common part of interrupt trap \");" - ); - - instructions.parse().unwrap() - } - - pub(crate) fn continue_interrupt_trap(arch: RiscvArch) -> proc_macro2::TokenStream { - let width = match arch { - RiscvArch::Rv32 => 4, - RiscvArch::Rv64 => 8, - }; - let store = store_trap(arch, |reg| reg != "a0"); - let load = load_trap(arch); + ); - #[cfg(feature = "s-mode")] - let ret = "sret"; - #[cfg(not(feature = "s-mode"))] - let ret = "mret"; - - let instructions = format!( - " -core::arch::global_asm!( - \".section .trap, \\\"ax\\\" - .align {width} // TODO is this necessary? - .global _continue_interrupt_trap - _continue_interrupt_trap: - {store} // store trap partially (all registers except a0) - jalr ra, a0, 0 // jump to corresponding interrupt handler (address stored in a0) - {load} // restore trap frame - addi sp, sp, {TRAP_SIZE} * {width} // deallocate space for trap frame - {ret} // return from interrupt -\");" - ); - - instructions.parse().unwrap() - } + instructions.parse().unwrap() } diff --git a/riscv-rt/src/asm.rs b/riscv-rt/src/asm.rs index 384b2b03..247400f4 100644 --- a/riscv-rt/src/asm.rs +++ b/riscv-rt/src/asm.rs @@ -282,6 +282,11 @@ riscv_rt_macros::weak_start_trap_riscv32!(); #[cfg(riscv64)] riscv_rt_macros::weak_start_trap_riscv64!(); +#[cfg(all(riscv32, feature = "v-trap"))] +riscv_rt_macros::vectored_interrupt_trap_riscv32!(); +#[cfg(all(riscv64, feature = "v-trap"))] +riscv_rt_macros::vectored_interrupt_trap_riscv64!(); + #[cfg(feature = "v-trap")] cfg_global_asm!( // Set the vector mode to vectored. @@ -297,15 +302,15 @@ cfg_global_asm!( _vector_table: j _start_trap // Interrupt 0 is used for exceptions j _start_SupervisorSoft_trap - j _start_trap // Interrupt 2 is reserved + j _start_DefaultHandler_trap // Interrupt 2 is reserved j _start_MachineSoft_trap - j _start_trap // Interrupt 4 is reserved + j _start_DefaultHandler_trap // Interrupt 4 is reserved j _start_SupervisorTimer_trap - j _start_trap // Interrupt 6 is reserved + j _start_DefaultHandler_trap // Interrupt 6 is reserved j _start_MachineTimer_trap - j _start_trap // Interrupt 8 is reserved + j _start_DefaultHandler_trap // Interrupt 8 is reserved j _start_SupervisorExternal_trap - j _start_trap // Interrupt 10 is reserved + j _start_DefaultHandler_trap // Interrupt 10 is reserved j _start_MachineExternal_trap // default table does not include the remaining interrupts. diff --git a/riscv-rt/src/lib.rs b/riscv-rt/src/lib.rs index 72272168..113908e7 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -354,6 +354,18 @@ //! } //! ``` //! +//! You can also use the `#[interrupt]` macro to define interrupt handlers: +//! +//! ``` no_run +//! #[riscv_rt::interrupt] +//! fn MachineTimer() { +//! // ... +//! } +//! ``` +//! +//! In direct mode, this macro is equivalent to defining a function with the same name. +//! However, in vectored mode, this macro will generate a proper trap handler for the interrupt. +//! //! If interrupt handler is not explicitly defined, `DefaultHandler` is called. //! //! ### `DefaultHandler` @@ -413,6 +425,33 @@ //! FLASH : ORIGIN = 0x20000000, LENGTH = 16M //! } //! ``` +//! +//! ## `v-trap` +//! +//! The vectored trap feature (`v-trap`) can be activated via [Cargo features](https://doc.rust-lang.org/cargo/reference/features.html). +//! +//! For example: +//! ``` text +//! [dependencies] +//! riscv-rt = {features=["v-trap"]} +//! ``` +//! When the vectored trap feature is enabled, the trap vector is set to `_vector_table` in vectored mode. +//! This table is a list of `j _start_INTERRUPT_trap` instructions, where `INTERRUPT` is the name of the interrupt. +//! +//! ### Defining interrupt handlers in vectored mode +//! +//! In vectored mode, each interrupt must also have a corresponding trap handler. +//! Therefore, using `export_name` or `no_mangle` is not enough to define an interrupt handler. +//! The [`interrupt`] macro will generate the trap handler for the interrupt: +//! +//! ``` no_run +//! #[riscv_rt::interrupt] +//! fn MachineTimer() { +//! // ... +//! } +//! ``` +//! +//! This will generate a function named `_start_MachineTimer_trap` that calls the interrupt handler `MachineTimer`. // NOTE: Adapted from cortex-m/src/lib.rs #![no_std] @@ -481,7 +520,7 @@ pub struct TrapFrame { pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) { extern "C" { fn ExceptionHandler(trap_frame: &TrapFrame); - fn DefaultHandler(); + fn _dispatch_interrupt(code: usize); } let cause = xcause::read(); @@ -499,15 +538,8 @@ pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) { } else { ExceptionHandler(trap_frame); } - } else if code < __INTERRUPTS.len() { - let h = &__INTERRUPTS[code]; - if let Some(handler) = h { - handler(); - } else { - DefaultHandler(); - } } else { - DefaultHandler(); + _dispatch_interrupt(code); } } @@ -549,6 +581,22 @@ pub static __EXCEPTIONS: [Option; 16] = [ Some(StorePageFault), ]; +#[export_name = "_dispatch_interrupt"] +unsafe extern "C" fn dispatch_interrupt(code: usize) { + extern "C" { + fn DefaultHandler(); + } + + if code < __INTERRUPTS.len() { + let h = &__INTERRUPTS[code]; + if let Some(handler) = h { + handler(); + } else { + DefaultHandler(); + } + } +} + extern "C" { fn SupervisorSoft(); fn MachineSoft();