Skip to content

Commit

Permalink
_dispatch_interrupt function and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
romancardenas committed May 4, 2024
1 parent e3aa1c2 commit 7bdb6e9
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 84 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/riscv-rt.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
on:
push:
branches: [ master, vectored-rt ]
branches: [ master ]
pull_request:
merge_group:

Expand Down
2 changes: 2 additions & 0 deletions riscv-rt/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
19 changes: 12 additions & 7 deletions riscv-rt/link.x.in
Original file line number Diff line number Diff line change
Expand Up @@ -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 **/

Expand All @@ -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);
Expand Down
163 changes: 101 additions & 62 deletions riscv-rt/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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<T: FnMut(&str) -> bool>(arch: RiscvArch, mut filter: T) -> String {
let (width, store) = match arch {
RiscvArch::Rv32 => (4, "sw"),
Expand All @@ -357,6 +367,8 @@ fn store_trap<T: FnMut(&str) -> 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"),
Expand All @@ -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,
Expand All @@ -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\\\"
Expand All @@ -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)
}
Expand Down Expand Up @@ -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
Expand All @@ -498,65 +575,27 @@ 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
{store} // store trap partially (only register a0)
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()
}
15 changes: 10 additions & 5 deletions riscv-rt/src/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
Loading

0 comments on commit 7bdb6e9

Please sign in to comment.