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
1 change: 1 addition & 0 deletions riscv-macros/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Added

- New `setup_interrupts` macro for custom interrupt setup routines.
- New `exception`, `core_interrupt`, and `external_interrupt` macros for trap handlers.
- New `entry` macro for the Rust entry point (for `riscv-rt`).
- New `rvrt-u-boot` feature to adapt `entry` macro for U-Boot.
Expand Down
3 changes: 3 additions & 0 deletions riscv-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ repository = "https://github.com/rust-embedded/riscv"
version = "0.4.1"
edition = "2021"

[package.metadata.docs.rs]
features = ["riscv-rt"]

[lib]
proc-macro = true

Expand Down
31 changes: 31 additions & 0 deletions riscv-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub fn pac_enum(attr: TokenStream, item: TokenStream) -> TokenStream {
}

/// Attribute to mark which function will be called before jumping to the entry point.
///
/// You must enable the `post-init` feature in the `riscv-rt` crate to use this macro.
///
/// In contrast with `__pre_init`, this function is called after the static variables
Expand Down Expand Up @@ -91,6 +92,36 @@ pub fn post_init(args: TokenStream, input: TokenStream) -> TokenStream {
riscv_rt::Fn::post_init(args, input)
}

/// Attribute to mark which function will set interrupts before jumping to the entry point.
///
/// The `riscv-rt` crates provides a default implementation that works for most cases.
/// If you want to provide your own implementation, you must enable the `custom-setup-interrupts`
/// feature in the `riscv-rt` crate and use this macro on your function.
/// The `riscv-rt` crate re-exports this macro if the `custom-setup-interrupts` feature is enabled,
/// so you can use it as `riscv_rt::setup_interrupts` without depending on `riscv-macros` directly.
///
/// The function must have the signature of `[unsafe] fn([usize])`, where the argument
/// corresponds to the hart ID of the current hart. This is useful for multi-hart systems
/// to perform hart-specific interrupt setup.
///
/// # IMPORTANT
///
/// This attribute can appear at most *once* in the dependency graph.
///
/// # Examples
///
/// ```
/// #[riscv_macros::setup_interrupts]
/// unsafe fn setup_interrupts(hart_id: usize) {
/// // do something here
/// }
/// ```
#[cfg(feature = "riscv-rt")]
#[proc_macro_attribute]
pub fn setup_interrupts(args: TokenStream, input: TokenStream) -> TokenStream {
riscv_rt::Fn::setup_interrupts(args, input)
}

/// Attribute to declare the entry point of the program
///
/// The specified function will be called by the reset handler *after* RAM has been initialized.
Expand Down
23 changes: 18 additions & 5 deletions riscv-macros/src/riscv_rt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ impl TrapType {
/// Enum representing the supported runtime function attributes
pub enum Fn {
PostInit,
SetupInterrupts,
Entry,
Trap(Trap),
}
Expand All @@ -48,6 +49,12 @@ impl Fn {
Self::PostInit.quote_fn(input, errors)
}

/// Convenience method to generate the token stream for the `setup_interrupts` attribute
pub fn setup_interrupts(args: TokenStream, input: TokenStream) -> TokenStream {
let errors = Self::SetupInterrupts.check_args_empty(args).err();
Self::SetupInterrupts.quote_fn(input, errors)
}

/// Convenience method to generate the token stream for the `entry` attribute
pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
let errors = Self::Entry.check_args_empty(args).err();
Expand Down Expand Up @@ -141,7 +148,12 @@ impl Fn {
fn check_inputs(&self, inputs: &Punctuated<FnArg, Comma>, errors: &mut Option<Error>) {
// Use this match to specify expected input arguments for different functions in the future
match self {
Self::PostInit => self.check_fn_args(inputs, &["usize"], errors),
Self::PostInit | Self::SetupInterrupts => {
#[cfg(not(feature = "rvrt-u-boot"))]
self.check_fn_args(inputs, &["usize"], errors);
#[cfg(feature = "rvrt-u-boot")]
self.check_fn_args(inputs, &[], errors);
}
#[cfg(not(feature = "rvrt-u-boot"))]
Self::Entry => self.check_fn_args(inputs, &["usize", "usize", "usize"], errors),
#[cfg(feature = "rvrt-u-boot")]
Expand All @@ -161,7 +173,7 @@ impl Fn {
fn check_output(&self, output: &ReturnType, errors: &mut Option<Error>) {
// Use this match to specify expected output types for different functions in the future
match self {
Self::PostInit => check_output_empty(output, errors),
Self::PostInit | Self::SetupInterrupts => check_output_empty(output, errors),
Self::Entry => check_output_never(output, errors),
Self::Trap(_) => check_output_empty_or_never(output, errors),
}
Expand All @@ -177,7 +189,7 @@ impl Fn {

// Use this match to specify extra items for different functions in the future
match self {
Self::PostInit | Self::Entry => None,
Self::PostInit | Self::SetupInterrupts | Self::Entry => None,
Self::Trap(Trap { path, ty }) => {
let mut extras = vec![];

Expand Down Expand Up @@ -217,12 +229,13 @@ impl Fn {
// Use this match to specify export names for different functions in the future
let export_name = match self {
Self::PostInit => Some("__post_init".to_string()),
Self::SetupInterrupts => Some("_setup_interrupts".to_string()),
Self::Entry => Some("main".to_string()),
Self::Trap(Trap { path, .. }) => Some(path.segments.last().unwrap().ident.to_string()),
};

export_name.map(|name| match self {
Self::PostInit | Self::Trap(_) => quote! {
Self::PostInit | Self::SetupInterrupts | Self::Trap(_) => quote! {
#[export_name = #name]
},
Self::Entry => quote! {
Expand All @@ -237,7 +250,7 @@ impl Fn {
// Use this match to specify section names for different functions in the future
let section_name: Option<String> = match self {
// TODO: check if we want specific sections for these functions
Self::PostInit | Self::Entry | Self::Trap(_) => None,
Self::PostInit | Self::SetupInterrupts | Self::Entry | Self::Trap(_) => None,
};

section_name.map(|section| quote! {
Expand Down
9 changes: 9 additions & 0 deletions riscv-rt/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## v0.19.0 - Unreleased

### Added

- New `custom-setup-interrupts` feature to opt-out default implementation of
`_setup_interrupts`. When this feature is active, the `riscv-rt` crate reexports
the `riscv_macros::setup_interrupts` attribute macro.

### Changed

- `_setup_interrupts` can now optionally receive an `usize` input argument
with the ID of the running hart. This allows users to implement hart-specific
interrupt setup in multi-hart targets.
- Use macros from `riscv-macros` instead of `riscv-rt-macros`

### Removed
Expand Down
3 changes: 2 additions & 1 deletion riscv-rt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ links = "riscv-rt" # Prevent multiple versions of riscv-rt being linked

[package.metadata.docs.rs]
default-target = "riscv64imac-unknown-none-elf"
features = ["pre-init", "post-init"]
features = ["pre-init", "post-init", "custom-setup-interrupts"]
targets = [
"riscv32i-unknown-none-elf", "riscv32imc-unknown-none-elf", "riscv32imac-unknown-none-elf",
"riscv64imac-unknown-none-elf", "riscv64gc-unknown-none-elf",
Expand All @@ -38,6 +38,7 @@ riscv = { path = "../riscv", version = "0.16.0", features = ["critical-section-s
[features]
pre-init = []
post-init = []
custom-setup-interrupts = []
s-mode = ["riscv-macros/s-mode"]
single-hart = []
v-trap = ["riscv/rt-v-trap", "riscv-macros/rt-v-trap"]
Expand Down
7 changes: 0 additions & 7 deletions riscv-rt/link.x.in
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,6 @@ PROVIDE(_mp_hook = _default_mp_hook);
EXTERN(_default_start_trap);
PROVIDE(_start_trap = _default_start_trap);

/* Default interrupt setup entry point. If not _setup_interrupts symbol is provided, then
_setup_interrupts maps to _default_setup_interrupts, which in direct mode sets the value
of the xtvec register to _start_trap and, in vectored mode, sets its value to
_vector_table and enables vectored mode. */
EXTERN(_default_setup_interrupts);
PROVIDE(_setup_interrupts = _default_setup_interrupts);

/* Default main routine. If no hal_main symbol is provided, then hal_main maps to main, which
is usually defined by final users via the #[riscv_rt::entry] attribute. Using hal_main
instead of main directly allow HALs to inject code before jumping to user main. */
Expand Down
79 changes: 45 additions & 34 deletions riscv-rt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,24 +371,6 @@
//! );
//! ```
//!
//! ## `_setup_interrupts`
//!
//! This function is called right before the main function and is responsible for setting up
//! the interrupt controller.
//!
//! Default implementation sets the trap vector to `_start_trap` in direct mode.
//! If the `v-trap` feature is enabled, the trap vector is set to `_vector_table`
//! in vectored mode. Users can override this function by defining their own `_setup_interrupts`.
//!
//! This function can be redefined in the following way:
//!
//! ``` no_run
//! #[export_name = "_setup_interrupts"]
//! pub fn setup_interrupts() {
//! // ...
//! }
//! ```
//!
//! ## `hal_main`
//!
//! Internally, `riscv-rt` does not jump to the `main` function created by the user using the
Expand Down Expand Up @@ -561,6 +543,28 @@
//!
//! You can use the [`#[post_init]`][attr-post-init] attribute to define a post-init function with Rust.
//!
//! ## `custom-setup-interrupts`
//!
//! The `riscv-rt` crate provides a default implementation for the `_setup_interrupts` function.
//! This function is called right before the main function and is responsible for setting up the interrupt controller.
//! Default implementation sets the trap vector to `_start_trap` in direct mode. If the `v-trap` feature is enabled,
//! the trap vector is set to `_vector_table` in vectored mode.
//!
//! However, in some cases, users may want to provide their own implementation of this function to customize the interrupt
//! setup process. Users can override this function by:
//!
//! 1. Enabling the `custom-setup-interrupts` feature to opt-out the default implementation.
//! 2. Using the [`#[setup_interrupts]`][attr-setup-interrupts] attribute on their custom function.
//!
//! This function can be redefined in the following way:
//!
//! ``` no_run
//! #[riscv_rt::setup_interrupts]
//! fn setup_interrupts(hart_id: usize) {
//! // ...
//! }
//!```
//!
//! ## `single-hart`
//!
//! Saves a little code size if there is only one hart on the target.
Expand Down Expand Up @@ -617,9 +621,10 @@
//!
//! ## `u-boot`
//!
//! When the U-boot feature is enabled, acceptable signature for `#[entry]` macros is changed. This is required
//! because when booting from elf, U-boot passes `argc` and `argv`. This feature also implies `single-hart`.
//! The only way to get boot-hart is through fdt, so other harts initialization is up to you.
//! When the U-boot feature is enabled, acceptable signature for `#[post_init]`, `#[setup_interrupts]`,
//! and`#[entry]` macros is changed. This is required because when booting from elf, U-boot passes
//! `argc` and `argv`. This feature also implies `single-hart`. The only way to get boot-hart is through
//! fdt, so other harts initialization is up to you.
//!
//! ## `pre-default-start-trap`
//!
Expand Down Expand Up @@ -672,6 +677,7 @@
//! [attr-external-interrupt]: attr.external_interrupt.html
//! [attr-core-interrupt]: attr.core_interrupt.html
//! [attr-post-init]: attr.post_init.html
//! [attr-setup-interrupts]: attr.setup_interrupts.html

// NOTE: Adapted from cortex-m/src/lib.rs
#![no_std]
Expand All @@ -687,23 +693,24 @@ pub mod exceptions;
pub mod interrupts;

#[cfg(feature = "s-mode")]
use riscv::register::{
scause as xcause,
stvec::{self as xtvec, Stvec as Xtvec, TrapMode},
};
use riscv::register::scause as xcause;
#[cfg(all(feature = "s-mode", not(feature = "custom-setup-interrupts")))]
use riscv::register::stvec::{self as xtvec, Stvec as Xtvec, TrapMode};

#[cfg(not(feature = "s-mode"))]
use riscv::register::{
mcause as xcause,
mtvec::{self as xtvec, Mtvec as Xtvec, TrapMode},
};
use riscv::register::mcause as xcause;
#[cfg(all(not(feature = "s-mode"), not(feature = "custom-setup-interrupts")))]
use riscv::register::mtvec::{self as xtvec, Mtvec as Xtvec, TrapMode};

pub use riscv_macros::{core_interrupt, entry, exception, external_interrupt};
pub use riscv_types::*;

#[cfg(feature = "post-init")]
pub use riscv_macros::post_init;

#[cfg(feature = "custom-setup-interrupts")]
pub use riscv_macros::setup_interrupts;

/// 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
/// Cargo.toml which is the more modern way to solve the same problem, but we have to keep
Expand All @@ -729,13 +736,13 @@ pub unsafe extern "C" fn start_rust(a0: usize, a1: usize, a2: usize) -> ! {
extern "Rust" {
#[cfg(feature = "post-init")]
fn __post_init(a0: usize);
fn _setup_interrupts();
fn _setup_interrupts(a0: usize);
fn hal_main(a0: usize, a1: usize, a2: usize) -> !;
}

#[cfg(feature = "post-init")]
__post_init(a0);
_setup_interrupts();
_setup_interrupts(a0);
hal_main(a0, a1, a2);
}

Expand All @@ -746,14 +753,18 @@ pub unsafe extern "C" fn start_rust(a0: usize, a1: usize, a2: usize) -> ! {
///
/// # Note
///
/// Users can override this function by defining their own `_setup_interrupts` function.
/// You can define your own `_setup_interrupts` function to override the default implementation.
/// To do so, you must enable the `custom-setup-interrupts` feature to opt-out the default implementation.
/// Then, you can use the [`riscv_macros::setup_interrupts`] attribute on your custom function.
/// This macro is re-exported by this crate if the `custom-setup-interrupts` feature is enabled.
///
/// # Safety
///
/// This function should not be called directly by the user, and should instead
/// be invoked by the runtime implicitly. It is expected to be called before the main function.
#[export_name = "_default_setup_interrupts"]
pub unsafe extern "Rust" fn setup_interrupts() {
#[cfg(not(feature = "custom-setup-interrupts"))]
#[riscv_macros::setup_interrupts]
unsafe fn default_setup_interrupts() {
extern "C" {
#[cfg(not(feature = "v-trap"))]
fn _start_trap();
Expand Down
1 change: 1 addition & 0 deletions tests-trybuild/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ edition = "2021"

[dependencies]
riscv = { path = "../riscv" }
riscv-macros = { path = "../riscv-macros" }
riscv-rt = { path = "../riscv-rt", features = ["no-exceptions", "no-interrupts", "post-init"] }
trybuild = "1.0"

Expand Down
8 changes: 8 additions & 0 deletions tests-trybuild/tests/riscv-rt/setup_interrupts/fail_full.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#[riscv_macros::setup_interrupts(arg)]
pub const async extern "Rust" fn setup_interrupts<'a, T>(_h: u32, _d: &'a T, _v: ...) -> !
where
T: Copy,
{
}

fn main() {}
Loading
Loading