Skip to content

Commit

Permalink
core_interrupt, exception, and external_interrupt macros
Browse files Browse the repository at this point in the history
  • Loading branch information
romancardenas committed Jul 28, 2024
1 parent 7ce6769 commit 7a98baa
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 31 deletions.
4 changes: 3 additions & 1 deletion riscv-rt/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Add `no-interrupts` feature to opt-out the default implementation of `_dispatch_core_interrupt`
- Add `pre_init_trap` to detect early errors during the boot process.
- Add `v-trap` feature to enable interrupt handling in vectored mode.
- Add `interrupt` proc macro to help defining interrupt handlers.
- Add `core_interrupt` proc macro to help defining core interrupt handlers.
- Add `external_interrupt` proc macro to help defining external interrupt handlers.
- Add `exception` proc macro to help defining exception handlers.
If `v-trap` feature is enabled, this macro also generates its corresponding trap.

### Changed
Expand Down
1 change: 1 addition & 0 deletions riscv-rt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ links = "riscv-rt" # Prevent multiple versions of riscv-rt being linked

[dependencies]
riscv = { path = "../riscv", version = "0.12.0" }
riscv-pac = { path = "../riscv-pac", version = "0.2.0" }
riscv-rt-macros = { path = "macros", version = "0.2.1" }

[dev-dependencies]
Expand Down
87 changes: 84 additions & 3 deletions riscv-rt/examples/empty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,97 @@
extern crate panic_halt;
extern crate riscv_rt;

use riscv_rt::{entry, interrupt};
use riscv_rt::{core_interrupt, entry, exception, external_interrupt};

use riscv::{
interrupt::{Exception, Interrupt},
result::*,
};

/// Just a dummy type to test the `ExternalInterrupt` trait.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ExternalInterrupt {
GPIO,
UART,
}

unsafe impl riscv::InterruptNumber for ExternalInterrupt {
const MAX_INTERRUPT_NUMBER: usize = 1;

#[inline]
fn number(self) -> usize {
self as usize
}

#[inline]
fn from_number(value: usize) -> Result<Self> {
match value {
0 => Ok(Self::GPIO),
1 => Ok(Self::UART),
_ => Err(Error::InvalidVariant(value)),
}
}
}
unsafe impl riscv::ExternalInterruptNumber for ExternalInterrupt {}

#[entry]
fn main() -> ! {
// do something here
loop {}
}

#[interrupt]
fn MachineSoft() {
/* EXAMPLES OF USING THE core_interrupt MACRO FOR CORE INTERRUPT HANDLERS.
IF v-trap ENABLED, THE MACRO ALSO DEFINES _start_COREINTERRUPT_trap routines */

/// Handler with the simplest signature.
#[core_interrupt(Interrupt::SupervisorSoft)]
fn supervisor_soft() {
// do something here
loop {}
}

/// Handler with the most complete signature.
#[core_interrupt(Interrupt::SupervisorTimer)]
unsafe fn supervisor_timer() -> ! {
// do something here
loop {}
}

/* EXAMPLES OF USING THE external_interrupt MACRO FOR EXTERNAL INTERRUPT HANDLERS. */

/// Handler with the simplest signature.
#[external_interrupt(ExternalInterrupt::GPIO)]
fn external_gpio() {
// do something here
loop {}
}

/// Handler with the most complete signature.
#[external_interrupt(ExternalInterrupt::UART)]
unsafe fn external_uart() -> ! {
// do something here
loop {}
}

/* EXAMPLES OF USING THE exception MACRO FOR EXCEPTION HANDLERS. */

/// Handler with the simplest signature.
#[exception(Exception::InstructionMisaligned)]
fn instruction_misaligned() {
// do something here
loop {}
}

/// Handler with the most complete signature.
#[exception(Exception::IllegalInstruction)]
unsafe fn illegal_instruction(_trap: &riscv_rt::TrapFrame) -> ! {
// do something here
loop {}
}

// The reference to TrapFrame can be mutable if the handler needs to modify it.
#[exception(Exception::Breakpoint)]
unsafe fn breakpoint(_trap: &mut riscv_rt::TrapFrame) -> ! {
// do something here
loop {}
}
159 changes: 136 additions & 23 deletions riscv-rt/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use proc_macro2::Span;
use syn::{
parse::{self, Parse},
spanned::Spanned,
FnArg, ItemFn, LitInt, LitStr, PathArguments, ReturnType, Type, Visibility,
FnArg, ItemFn, LitInt, LitStr, Path, PathArguments, ReturnType, Type, Visibility,
};

use proc_macro::TokenStream;
Expand Down Expand Up @@ -313,12 +313,18 @@ pub fn loop_global_asm(input: TokenStream) -> TokenStream {
res.parse().unwrap()
}

#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
enum RiscvArch {
Rv32,
Rv64,
}

#[derive(Clone, Copy, Debug)]
enum RiscvPacItem {
ExternalInterrupt,
CoreInterrupt,
}

/// Size of the trap frame (in number of registers)
const TRAP_SIZE: usize = 16;

Expand Down Expand Up @@ -505,20 +511,122 @@ _continue_interrupt_trap:
}

#[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)
/// Attribute to declare an exception handler. The function must have the signature `[unsafe] fn(&[mut] riscv_rt::TrapFrame) [-> !]`.
pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
let f = parse_macro_input!(input as ItemFn);

// check the function arguments
if f.sig.inputs.len() > 1 {
return parse::Error::new(
f.sig.inputs.span(),
"`#[exception]` function must at have most one input argument",
)
.to_compile_error()
.into();
}

if let Some(param) = f.sig.inputs.first() {
let first_param_type = match param {
FnArg::Typed(t) => *t.ty.clone(),
_ => {
return parse::Error::new(param.span(), "invalid argument")
.to_compile_error()
.into();
}
};

let expected_types: Vec<Type> = vec![
parse_quote!(&riscv_rt::TrapFrame),
parse_quote!(&mut riscv_rt::TrapFrame),
];

if !expected_types.iter().any(|t| first_param_type == *t) {
return parse::Error::new(
first_param_type.span(),
"`#[exception]` function argument must be `&[mut] riscv_rt::TrapFrame`",
)
.to_compile_error()
.into();
}
}

// check the function signature
let valid_signature = f.sig.constness.is_none()
&& f.sig.asyncness.is_none()
&& f.vis == Visibility::Inherited
&& f.sig.abi.is_none()
&& f.sig.generics.params.is_empty()
&& f.sig.generics.where_clause.is_none()
&& f.sig.variadic.is_none()
&& match f.sig.output {
ReturnType::Default => true,
ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
};

if !valid_signature {
return parse::Error::new(
f.span(),
"`#[exception]` function must have signature `[unsafe] fn(&riscv_rt::TrapFrame) [-> !]`",
)
.to_compile_error()
.into();
}

let int_path = parse_macro_input!(args as Path);
let int_ident = &int_path.segments.last().unwrap().ident;
let export_name = format!("{:#}", int_ident);

quote!(
// Compile-time check to ensure the interrupt path implements the CoreInterruptNumber trait
const _: fn() = || {
fn assert_impl<T: riscv_rt::ExceptionNumber>(_arg: T) {}
assert_impl(#int_path);
};

#[export_name = #export_name]
#f
)
.into()
}

#[proc_macro_attribute]
/// Attribute to declare an core interrupt handler. The function must have the signature `[unsafe] fn() [-> !]`.
/// If the `v-trap` feature is enabled, this macro generates the corresponding interrupt trap handler in assembly.
pub fn core_interrupt_riscv32(args: TokenStream, input: TokenStream) -> TokenStream {
let arch = match () {
#[cfg(feature = "v-trap")]
() => Some(RiscvArch::Rv32),
#[cfg(not(feature = "v-trap"))]
() => None,
};
interrupt(args, input, RiscvPacItem::CoreInterrupt, arch)
}

#[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)
/// If the `v-trap` feature is enabled, this macro generates the corresponding interrupt trap handler in assembly.
pub fn core_interrupt_riscv64(args: TokenStream, input: TokenStream) -> TokenStream {
let arch = match () {
#[cfg(feature = "v-trap")]
() => Some(RiscvArch::Rv64),
#[cfg(not(feature = "v-trap"))]
() => None,
};
interrupt(args, input, RiscvPacItem::CoreInterrupt, arch)
}

fn interrupt(args: TokenStream, input: TokenStream, _arch: RiscvArch) -> TokenStream {
#[proc_macro_attribute]
/// Attribute to declare an external interrupt handler. The function must have the signature `[unsafe] fn() [-> !]`.
pub fn external_interrupt(args: TokenStream, input: TokenStream) -> TokenStream {
interrupt(args, input, RiscvPacItem::ExternalInterrupt, None)
}

fn interrupt(
args: TokenStream,
input: TokenStream,
pac_item: RiscvPacItem,
arch: Option<RiscvArch>,
) -> TokenStream {
let f = parse_macro_input!(input as ItemFn);

// check the function arguments
Expand Down Expand Up @@ -553,30 +661,35 @@ fn interrupt(args: TokenStream, input: TokenStream, _arch: RiscvArch) -> TokenSt
.into();
}

if !args.is_empty() {
return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
.to_compile_error()
.into();
}
let int_path = parse_macro_input!(args as Path);
let int_ident = &int_path.segments.last().unwrap().ident;
let export_name = format!("{:#}", int_ident);

// XXX should we blacklist other attributes?
let ident = &f.sig.ident;
let export_name = format!("{:#}", ident);
let start_trap = match arch {
Some(RiscvArch::Rv32) => start_interrupt_trap(int_ident, RiscvArch::Rv32),
Some(RiscvArch::Rv64) => start_interrupt_trap(int_ident, RiscvArch::Rv64),
None => proc_macro2::TokenStream::new(),
};

#[cfg(not(feature = "v-trap"))]
let start_trap = proc_macro2::TokenStream::new();
#[cfg(feature = "v-trap")]
let start_trap = start_interrupt_trap(ident, _arch);
let pac_trait = match pac_item {
RiscvPacItem::ExternalInterrupt => quote!(riscv_rt::ExternalInterruptNumber),
RiscvPacItem::CoreInterrupt => quote!(riscv_rt::CoreInterruptNumber),
};

quote!(
// Compile-time check to ensure the interrupt path implements the CoreInterruptNumber trait
const _: fn() = || {
fn assert_impl<T: #pac_trait>(_arg: T) {}
assert_impl(#int_path);
};

#start_trap
#[export_name = #export_name]
#f
)
.into()
}

#[cfg(feature = "v-trap")]
fn start_interrupt_trap(ident: &syn::Ident, arch: RiscvArch) -> proc_macro2::TokenStream {
let interrupt = ident.to_string();
let width = match arch {
Expand Down
9 changes: 5 additions & 4 deletions riscv-rt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,13 +472,14 @@ use riscv::register::scause as xcause;
#[cfg(not(feature = "s-mode"))]
use riscv::register::mcause as xcause;

pub use riscv_rt_macros::{entry, pre_init};
pub use riscv_rt_macros::{entry, exception, external_interrupt, pre_init};

#[cfg(riscv32)]
pub use riscv_rt_macros::interrupt_riscv32 as interrupt;
pub use riscv_pac::*;

#[cfg(riscv32)]
pub use riscv_rt_macros::core_interrupt_riscv32 as core_interrupt;
#[cfg(riscv64)]
pub use riscv_rt_macros::interrupt_riscv64 as interrupt;
pub use riscv_rt_macros::core_interrupt_riscv64 as core_interrupt;

/// We export this static with an informative name so that if an application attempts to link
/// two copies of riscv-rt together, linking will fail. We also declare a links key in
Expand Down

0 comments on commit 7a98baa

Please sign in to comment.