From 1e713389ec22792581c7d218a7134a899a8dab59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Tue, 19 Nov 2024 09:35:19 +0100 Subject: [PATCH] Use conditional dependencies in riscv-rt depending on base extension --- riscv-rt/Cargo.toml | 2 +- riscv-rt/build.rs | 9 +- riscv-rt/macros/Cargo.toml | 2 +- riscv-rt/macros/src/lib.rs | 210 +++++++++++++++++++++---------------- riscv-rt/src/asm.rs | 24 +++-- riscv-rt/src/lib.rs | 21 ++-- 6 files changed, 154 insertions(+), 114 deletions(-) diff --git a/riscv-rt/Cargo.toml b/riscv-rt/Cargo.toml index 92f4055e..991d3a17 100644 --- a/riscv-rt/Cargo.toml +++ b/riscv-rt/Cargo.toml @@ -25,7 +25,7 @@ riscv-target-parser = { path = "../riscv-target-parser", version = "0.1.0" } [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.2" } +riscv-rt-macros = { path = "macros", version = "0.3.0" } [dev-dependencies] panic-halt = "1.0.0" diff --git a/riscv-rt/build.rs b/riscv-rt/build.rs index 59be6a7b..1bd0832e 100644 --- a/riscv-rt/build.rs +++ b/riscv-rt/build.rs @@ -28,18 +28,11 @@ fn main() { let cargo_flags = env::var("CARGO_ENCODED_RUSTFLAGS").unwrap(); if let Ok(target) = RiscvTarget::build(&target, &cargo_flags) { - let width = target.width(); - if matches!(width, riscv_target_parser::Width::W128) { - panic!("Unsupported RISC-V target: {width}"); - } - if target.base_extension().is_none() { - panic!("Unsupported RISC-V target: no base extension"); - } for flag in target.rustc_flags() { // Required until target_feature risc-v is stable and in-use println!("cargo:rustc-check-cfg=cfg({flag})"); println!("cargo:rustc-cfg={flag}"); } - add_linker_script(width.into()).unwrap(); + add_linker_script(target.width().into()).unwrap(); } } diff --git a/riscv-rt/macros/Cargo.toml b/riscv-rt/macros/Cargo.toml index f856c7b8..e067dbd3 100644 --- a/riscv-rt/macros/Cargo.toml +++ b/riscv-rt/macros/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["riscv", "runtime", "startup"] license = "MIT OR Apache-2.0" name = "riscv-rt-macros" repository = "https://github.com/rust-embedded/riscv" -version = "0.2.2" +version = "0.3.0" edition = "2021" [lib] diff --git a/riscv-rt/macros/src/lib.rs b/riscv-rt/macros/src/lib.rs index 3cd75e1a..f19575ca 100644 --- a/riscv-rt/macros/src/lib.rs +++ b/riscv-rt/macros/src/lib.rs @@ -6,6 +6,8 @@ extern crate proc_macro2; extern crate quote; extern crate syn; +use std::vec; + use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; use syn::{ @@ -359,56 +361,60 @@ pub fn loop_global_asm(input: TokenStream) -> TokenStream { #[derive(Clone, Copy, Debug)] enum RiscvArch { - Rv32, - Rv64, + Rv32I, + Rv32E, + Rv64I, + Rv64E, +} + +impl syn::parse::Parse for RiscvArch { + fn parse(input: parse::ParseStream) -> syn::Result { + let ident: syn::Ident = input.parse()?; + match ident.to_string().as_str() { + "rv32i" => Ok(Self::Rv32I), + "rv32e" => Ok(Self::Rv32E), + "rv64i" => Ok(Self::Rv64I), + "rv64e" => Ok(Self::Rv64E), + _ => Err(syn::Error::new(ident.span(), "Invalid RISC-V architecture")), + } + } } impl RiscvArch { fn width(&self) -> usize { match self { - Self::Rv32 => 4, - Self::Rv64 => 8, + Self::Rv32I | Self::Rv32E => 4, + Self::Rv64I | Self::Rv64E => 8, } } fn store(&self) -> &str { match self { - Self::Rv32 => "sw", - Self::Rv64 => "sd", + Self::Rv32I | Self::Rv32E => "sw", + Self::Rv64I | Self::Rv64E => "sd", } } fn load(&self) -> &str { match self { - Self::Rv32 => "lw", - Self::Rv64 => "ld", + Self::Rv32I | Self::Rv32E => "lw", + Self::Rv64I | Self::Rv64E => "ld", } } -} -/// 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", - "t1", - "t2", - "t3", - "t4", - "t5", - "t6", - "a0", - "a1", - "a2", - "a3", - "a4", - "a5", - "a6", - "a7", -]; + fn trap_frame(&self) -> Vec<&str> { + match self { + Self::Rv32I | Self::Rv64I => vec![ + "ra", "t0", "t1", "t2", "t3", "t4", "t5", "t6", "a0", "a1", "a2", "a3", "a4", "a5", + "a6", "a7", + ], + Self::Rv32E => vec![ + "ra", "t0", "t1", "t2", "a0", "a1", "a2", "a3", "a4", "a5", "_r0", "_r1", + ], + Self::Rv64E => vec!["ra", "t0", "t1", "t2", "a0", "a1", "a2", "a3", "a4", "a5"], + } + } +} /// Generate the assembly instructions to store the trap frame. /// @@ -421,11 +427,11 @@ const TRAP_FRAME: [&str; TRAP_SIZE] = [ fn store_trap bool>(arch: RiscvArch, mut filter: T) -> String { let width = arch.width(); let store = arch.store(); - TRAP_FRAME + arch.trap_frame() .iter() .enumerate() - .filter(|(_, ®)| filter(reg)) - .map(|(i, reg)| format!("{store} {reg}, {i}*{width}(sp)")) + .filter(|(_, ®)| !reg.starts_with('_') && filter(reg)) + .map(|(i, reg)| format!(" {store} {reg}, {i}*{width}(sp)")) .collect::>() .join("\n") } @@ -435,43 +441,27 @@ fn store_trap bool>(arch: RiscvArch, mut filter: T) -> String fn load_trap(arch: RiscvArch) -> String { let width = arch.width(); let load = arch.load(); - TRAP_FRAME + arch.trap_frame() .iter() .enumerate() - .map(|(i, reg)| format!("{load} {reg}, {i}*{width}(sp)")) + .filter(|(_, ®)| !reg.starts_with('_')) + .map(|(i, reg)| format!(" {load} {reg}, {i}*{width}(sp)")) .collect::>() .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. +/// 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. #[proc_macro] -pub fn weak_start_trap_riscv64(_input: TokenStream) -> TokenStream { - weak_start_trap(RiscvArch::Rv64) -} +pub fn weak_start_trap(input: TokenStream) -> TokenStream { + let arch = parse_macro_input!(input as RiscvArch); -/// 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 = arch.width(); + let trap_size = arch.trap_frame().len(); // ensure we do not break that sp is 16-byte aligned - if (TRAP_SIZE * width) % 16 != 0 { + if (trap_size * width) % 16 != 0 { return parse::Error::new(Span::call_site(), "Trap frame size must be 16-byte aligned") .to_compile_error() .into(); @@ -491,12 +481,12 @@ core::arch::global_asm!( .align {width} .weak _start_trap _start_trap: - addi sp, sp, - {TRAP_SIZE} * {width} - {store} + addi sp, sp, - {trap_size} * {width} +{store} add a0, sp, zero jal ra, _start_trap_rust - {load} - addi sp, sp, {TRAP_SIZE} * {width} +{load} + addi sp, sp, {trap_size} * {width} {ret} ");"# ) @@ -504,27 +494,16 @@ _start_trap: .unwrap() } -/// 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) -} - -/// 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 { +pub fn vectored_interrupt_trap(input: TokenStream) -> TokenStream { + let arch = parse_macro_input!(input as RiscvArch); let width = arch.width(); + let trap_size = arch.trap_frame().len(); let store_start = store_trap(arch, |reg| reg == "a0"); let store_continue = store_trap(arch, |reg| reg != "a0"); let load = load_trap(arch); @@ -542,17 +521,17 @@ core::arch::global_asm!( .align 4 .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) + 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 .align 4 .global _continue_interrupt_trap _continue_interrupt_trap: - {store_continue} // store trap partially (all registers except a0) +{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 +{load} // restore trap frame + addi sp, sp, {trap_size} * {width} // deallocate space for trap frame {ret} // return from interrupt ");"# ); @@ -664,10 +643,64 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { /// loop{}; /// } /// ``` -pub fn core_interrupt_riscv32(args: TokenStream, input: TokenStream) -> TokenStream { +pub fn core_interrupt_rv32i(args: TokenStream, input: TokenStream) -> TokenStream { + let arch = match () { + #[cfg(feature = "v-trap")] + () => Some(RiscvArch::Rv32I), + #[cfg(not(feature = "v-trap"))] + () => None, + }; + trap(args, input, RiscvPacItem::CoreInterrupt, arch) +} + +#[proc_macro_attribute] +/// Attribute to declare a core interrupt handler. +/// +/// The function must have the signature `[unsafe] fn() [-> !]`. +/// +/// The argument of the macro must be a path to a variant of an enum that implements the `riscv_rt::CoreInterruptNumber` trait. +/// +/// If the `v-trap` feature is enabled, this macro generates the corresponding interrupt trap handler in assembly. +/// +/// # Example +/// +/// ``` ignore,no_run +/// #[riscv_rt::core_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] +/// fn supervisor_soft() -> ! { +/// loop{}; +/// } +/// ``` +pub fn core_interrupt_rv32e(args: TokenStream, input: TokenStream) -> TokenStream { + let arch = match () { + #[cfg(feature = "v-trap")] + () => Some(RiscvArch::Rv32E), + #[cfg(not(feature = "v-trap"))] + () => None, + }; + trap(args, input, RiscvPacItem::CoreInterrupt, arch) +} + +#[proc_macro_attribute] +/// Attribute to declare a core interrupt handler. +/// +/// The function must have the signature `[unsafe] fn() [-> !]`. +/// +/// The argument of the macro must be a path to a variant of an enum that implements the `riscv_rt::CoreInterruptNumber` trait. +/// +/// If the `v-trap` feature is enabled, this macro generates the corresponding interrupt trap handler in assembly. +/// +/// # Example +/// +/// ``` ignore,no_run +/// #[riscv_rt::core_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)] +/// fn supervisor_soft() -> ! { +/// loop{}; +/// } +/// ``` +pub fn core_interrupt_rv64i(args: TokenStream, input: TokenStream) -> TokenStream { let arch = match () { #[cfg(feature = "v-trap")] - () => Some(RiscvArch::Rv32), + () => Some(RiscvArch::Rv64I), #[cfg(not(feature = "v-trap"))] () => None, }; @@ -691,10 +724,10 @@ pub fn core_interrupt_riscv32(args: TokenStream, input: TokenStream) -> TokenStr /// loop{}; /// } /// ``` -pub fn core_interrupt_riscv64(args: TokenStream, input: TokenStream) -> TokenStream { +pub fn core_interrupt_rv64e(args: TokenStream, input: TokenStream) -> TokenStream { let arch = match () { #[cfg(feature = "v-trap")] - () => Some(RiscvArch::Rv64), + () => Some(RiscvArch::Rv64E), #[cfg(not(feature = "v-trap"))] () => None, }; @@ -784,6 +817,7 @@ fn trap( fn start_interrupt_trap(ident: &syn::Ident, arch: RiscvArch) -> proc_macro2::TokenStream { let interrupt = ident.to_string(); let width = arch.width(); + let trap_size = arch.trap_frame().len(); let store = store_trap(arch, |r| r == "a0"); let instructions = format!( @@ -793,8 +827,8 @@ core::arch::global_asm!( .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) + 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 ");"# diff --git a/riscv-rt/src/asm.rs b/riscv-rt/src/asm.rs index fbf87448..43f7a2b1 100644 --- a/riscv-rt/src/asm.rs +++ b/riscv-rt/src/asm.rs @@ -278,15 +278,23 @@ _pre_init_trap: j _pre_init_trap", ); -#[cfg(target_arch = "riscv32")] -riscv_rt_macros::weak_start_trap_riscv32!(); -#[cfg(target_arch = "riscv64")] -riscv_rt_macros::weak_start_trap_riscv64!(); +#[cfg(all(target_arch = "riscv32", riscvi))] +riscv_rt_macros::weak_start_trap!(rv32i); +#[cfg(all(target_arch = "riscv32", riscve))] +riscv_rt_macros::weak_start_trap!(rv32e); +#[cfg(all(target_arch = "riscv64", riscvi))] +riscv_rt_macros::weak_start_trap!(rv64i); +#[cfg(all(target_arch = "riscv64", riscve))] +riscv_rt_macros::weak_start_trap!(rv64e); -#[cfg(all(target_arch = "riscv32", feature = "v-trap"))] -riscv_rt_macros::vectored_interrupt_trap_riscv32!(); -#[cfg(all(target_arch = "riscv64", feature = "v-trap"))] -riscv_rt_macros::vectored_interrupt_trap_riscv64!(); +#[cfg(all(target_arch = "riscv32", riscvi, feature = "v-trap"))] +riscv_rt_macros::vectored_interrupt_trap!(rv32i); +#[cfg(all(target_arch = "riscv32", riscve, feature = "v-trap"))] +riscv_rt_macros::vectored_interrupt_trap!(rv32e); +#[cfg(all(target_arch = "riscv64", riscvi, feature = "v-trap"))] +riscv_rt_macros::vectored_interrupt_trap!(rv64i); +#[cfg(all(target_arch = "riscv64", riscve, feature = "v-trap"))] +riscv_rt_macros::vectored_interrupt_trap!(rv64e); #[rustfmt::skip] global_asm!( diff --git a/riscv-rt/src/lib.rs b/riscv-rt/src/lib.rs index e550b229..30324fe3 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -551,12 +551,17 @@ pub use riscv_rt_macros::{entry, exception, external_interrupt, pre_init}; pub use riscv_pac::*; -#[cfg(target_arch = "riscv32")] -pub use riscv_rt_macros::core_interrupt_riscv32 as core_interrupt; -#[cfg(target_arch = "riscv64")] -pub use riscv_rt_macros::core_interrupt_riscv64 as core_interrupt; -#[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64")))] -pub use riscv_rt_macros::core_interrupt_riscv64 as core_interrupt; // just for docs, tests, etc. +#[cfg(all(target_arch = "riscv32", riscve))] +pub use riscv_rt_macros::core_interrupt_rv32e as core_interrupt; +#[cfg(all(target_arch = "riscv32", riscvi))] +pub use riscv_rt_macros::core_interrupt_rv32i as core_interrupt; +#[cfg(all(target_arch = "riscv64", riscve))] +pub use riscv_rt_macros::core_interrupt_rv64e as core_interrupt; +#[cfg(any( + all(target_arch = "riscv64", riscvi), + not(any(target_arch = "riscv32", target_arch = "riscv64")) +))] +pub use riscv_rt_macros::core_interrupt_rv64i 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 @@ -608,9 +613,9 @@ pub struct TrapFrame { #[cfg(not(riscve))] /// `x17`: argument register `a7`. Used to pass the eighth argument to a function. pub a7: usize, - #[cfg(riscve)] + #[cfg(all(target_arch = "riscv32", riscve))] _reserved0: usize, - #[cfg(riscve)] + #[cfg(all(target_arch = "riscv32", riscve))] _reserved1: usize, }