From edfaff15f4f04eb26ad3a21245f3d7d9c7d3c427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Wed, 14 Jan 2026 14:49:37 +0100 Subject: [PATCH 1/2] riscv-macros: add entry attribute --- riscv-macros/CHANGELOG.md | 1 + riscv-macros/Cargo.toml | 1 + riscv-macros/src/lib.rs | 45 ++++++++++++++++++++++++++++++++++++ riscv-macros/src/riscv_rt.rs | 33 +++++++++++++++++++++++++- riscv-rt/CHANGELOG.md | 1 + riscv-rt/Cargo.toml | 2 +- riscv-rt/src/lib.rs | 3 ++- 7 files changed, 83 insertions(+), 3 deletions(-) diff --git a/riscv-macros/CHANGELOG.md b/riscv-macros/CHANGELOG.md index 2e9ddaff..bf0523c4 100644 --- a/riscv-macros/CHANGELOG.md +++ b/riscv-macros/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added +- `entry` macro for the `riscv-rt` crate (migrated from `riscv-rt-macros`). - `post_init` macro for the `riscv-rt` crate (migrated from `riscv-rt-macros`). ## v0.4.0 - 2025-12-19 diff --git a/riscv-macros/Cargo.toml b/riscv-macros/Cargo.toml index 4a0e8d64..69f91aba 100644 --- a/riscv-macros/Cargo.toml +++ b/riscv-macros/Cargo.toml @@ -19,6 +19,7 @@ proc-macro = true rt = [] rt-v-trap = ["rt"] riscv-rt = ["rt", "syn/extra-traits", "syn/full"] +u-boot = [] [dependencies] proc-macro2 = "1.0" diff --git a/riscv-macros/src/lib.rs b/riscv-macros/src/lib.rs index bbe19a1d..61b8b15e 100644 --- a/riscv-macros/src/lib.rs +++ b/riscv-macros/src/lib.rs @@ -90,3 +90,48 @@ pub fn pac_enum(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn post_init(args: TokenStream, input: TokenStream) -> TokenStream { riscv_rt::Fn::post_init(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. +/// If present, the FPU will also be enabled before the function is called. +/// +/// # Signature +/// +/// ## Regular Usage +/// +/// The type of the specified function must be `[unsafe] fn([usize[, usize[, usize]]]) -> !` (never ending function). +/// The optional arguments correspond to the values passed in registers `a0`, `a1`, and `a2`. +/// The first argument holds the hart ID of the current hart, which is useful for multi-hart systems. +/// The other two arguments are currently unused and reserved for future use. +/// +/// ## With U-Boot +/// +/// This runtime supports being booted by U-Boot. In this case, the entry point function +/// must have the signature `[unsafe] fn([c_int[, *const *const c_char]]) -> !`, where the first argument +/// corresponds to the `argc` parameter and the second argument corresponds to the `argv` parameter passed by U-Boot. +/// +/// Remember to enable the `u-boot` feature in the `riscv-rt` crate to use this functionality. +/// +/// # IMPORTANT +/// +/// This attribute can appear at most *once* in the dependency graph. +/// +/// The entry point will be called by the reset handler. The program can't reference to the entry +/// point, much less invoke it. +/// +/// # Examples +/// +/// ``` no_run +/// #[riscv_macros::entry] +/// fn main() -> ! { +/// loop { +/// /* .. */ +/// } +/// } +/// ``` +#[cfg(feature = "riscv-rt")] +#[proc_macro_attribute] +pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { + riscv_rt::Fn::entry(args, input) +} diff --git a/riscv-macros/src/riscv_rt.rs b/riscv-macros/src/riscv_rt.rs index c690037b..e0fae516 100644 --- a/riscv-macros/src/riscv_rt.rs +++ b/riscv-macros/src/riscv_rt.rs @@ -8,10 +8,19 @@ use syn::{ /// Enum representing the supported runtime function attributes pub enum Fn { + Entry, PostInit, } impl Fn { + /// Convenience method to generate the token stream for the `entry` attribute + pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { + match Self::Entry.check_args_empty(args) { + Ok(_) => Self::Entry.quote_fn(input), + Err(e) => e.to_compile_error().into(), + } + } + /// Convenience method to generate the token stream for the `post_init` attribute pub fn post_init(args: TokenStream, input: TokenStream) -> TokenStream { match Self::PostInit.check_args_empty(args) { @@ -83,6 +92,7 @@ impl Fn { const fn attr_name(&self) -> &'static str { // Use this match to specify attribute names for different functions in the future match self { + Self::Entry => "entry", Self::PostInit => "post_init", } } @@ -91,6 +101,10 @@ impl Fn { const fn expected_signature(&self) -> &'static str { // Use this match to specify expected signatures for different functions in the future match self { + #[cfg(not(feature = "u-boot"))] + Self::Entry => "[unsafe] fn([usize[, usize[, usize]]]) -> !", + #[cfg(feature = "u-boot")] + Self::Entry => "[unsafe] fn([c_int[, *const *const c_char]]) -> !", Self::PostInit => "[unsafe] fn([usize])", } } @@ -99,6 +113,10 @@ impl Fn { fn check_inputs(&self, inputs: &Punctuated) -> Result<()> { // Use this match to specify expected input arguments for different functions in the future match self { + #[cfg(not(feature = "u-boot"))] + Self::Entry => self.check_fn_args(inputs, &["usize", "usize", "usize"]), + #[cfg(feature = "u-boot")] + Self::Entry => self.check_fn_args(inputs, &["c_int", "*const *const c_char"]), Self::PostInit => self.check_fn_args(inputs, &["usize"]), } } @@ -107,6 +125,7 @@ impl Fn { fn check_output(&self, output: &ReturnType) -> Result<()> { // Use this match to specify expected output types for different functions in the future match self { + Self::Entry => check_output_never(output), Self::PostInit => check_output_empty(output), } } @@ -115,6 +134,7 @@ impl Fn { fn export_name(&self, _f: &ItemFn) -> Option { // Use this match to specify export names for different functions in the future let export_name = match self { + Self::Entry => Some("main".to_string()), Self::PostInit => Some("__post_init".to_string()), }; @@ -129,7 +149,7 @@ impl Fn { fn link_section(&self, _f: &ItemFn) -> Option { // Use this match to specify section names for different functions in the future let section_name: Option = match self { - Self::PostInit => None, + Self::Entry | Self::PostInit => None, }; section_name.map(|section| quote! { @@ -236,3 +256,14 @@ fn check_output_empty(output: &ReturnType) -> Result<()> { }, } } + +/// Make sure the output type is `!` (never) +fn check_output_never(output: &ReturnType) -> Result<()> { + match output { + ReturnType::Type(_, ty) => match **ty { + Type::Never(_) => Ok(()), + _ => Err(Error::new(ty.span(), "return type must be !")), + }, + ReturnType::Default => Err(Error::new(output.span(), "return type must be !")), + } +} diff --git a/riscv-rt/CHANGELOG.md b/riscv-rt/CHANGELOG.md index 8b3298d4..1e731e52 100644 --- a/riscv-rt/CHANGELOG.md +++ b/riscv-rt/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- Using `entry` from `riscv-macros` instead of `riscv-rt-macros`. - Using `post_init` from `riscv-macros` instead of `riscv-rt-macros`. ## v0.17.1 - 2026-01-13 diff --git a/riscv-rt/Cargo.toml b/riscv-rt/Cargo.toml index 2ce65faf..70111590 100644 --- a/riscv-rt/Cargo.toml +++ b/riscv-rt/Cargo.toml @@ -42,7 +42,7 @@ post-init = [] s-mode = ["riscv-rt-macros/s-mode"] single-hart = [] v-trap = ["riscv-rt-macros/v-trap", "riscv/rt-v-trap", "riscv-macros/rt-v-trap"] -u-boot = ["riscv-rt-macros/u-boot", "single-hart"] +u-boot = ["riscv-rt-macros/u-boot", "single-hart", "riscv-macros/u-boot"] no-interrupts = [] no-exceptions = [] no-mhartid = ["single-hart"] diff --git a/riscv-rt/src/lib.rs b/riscv-rt/src/lib.rs index 08886a43..b1f26adb 100644 --- a/riscv-rt/src/lib.rs +++ b/riscv-rt/src/lib.rs @@ -699,7 +699,8 @@ use riscv::register::{ mtvec::{self as xtvec, Mtvec as Xtvec, TrapMode}, }; -pub use riscv_rt_macros::{core_interrupt, entry, exception, external_interrupt}; +pub use riscv_macros::entry; +pub use riscv_rt_macros::{core_interrupt, exception, external_interrupt}; pub use riscv_types::*; #[cfg(feature = "post-init")] From 39b8d884222cfcef8fbeaab780a15147d4cfcac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas=20Rodr=C3=ADguez?= Date: Wed, 14 Jan 2026 14:50:39 +0100 Subject: [PATCH 2/2] riscv-macros: add tests for entry attribute --- tests-trybuild/tests/riscv-rt/entry/fail_arg_count.rs | 4 ++++ tests-trybuild/tests/riscv-rt/entry/fail_arg_count.stderr | 5 +++++ tests-trybuild/tests/riscv-rt/entry/fail_arg_type.rs | 4 ++++ tests-trybuild/tests/riscv-rt/entry/fail_arg_type.stderr | 5 +++++ tests-trybuild/tests/riscv-rt/entry/fail_async.rs | 4 ++++ tests-trybuild/tests/riscv-rt/entry/fail_async.stderr | 5 +++++ tests-trybuild/tests/riscv-rt/entry/fail_public.rs | 4 ++++ tests-trybuild/tests/riscv-rt/entry/fail_public.stderr | 5 +++++ tests-trybuild/tests/riscv-rt/entry/fail_return_empty.rs | 4 ++++ .../tests/riscv-rt/entry/fail_return_empty.stderr | 7 +++++++ tests-trybuild/tests/riscv-rt/entry/fail_return_tuple.rs | 4 ++++ .../tests/riscv-rt/entry/fail_return_tuple.stderr | 5 +++++ tests-trybuild/tests/riscv-rt/entry/pass_safe.rs | 6 ++++++ tests-trybuild/tests/riscv-rt/entry/pass_unsafe.rs | 6 ++++++ 14 files changed, 68 insertions(+) create mode 100644 tests-trybuild/tests/riscv-rt/entry/fail_arg_count.rs create mode 100644 tests-trybuild/tests/riscv-rt/entry/fail_arg_count.stderr create mode 100644 tests-trybuild/tests/riscv-rt/entry/fail_arg_type.rs create mode 100644 tests-trybuild/tests/riscv-rt/entry/fail_arg_type.stderr create mode 100644 tests-trybuild/tests/riscv-rt/entry/fail_async.rs create mode 100644 tests-trybuild/tests/riscv-rt/entry/fail_async.stderr create mode 100644 tests-trybuild/tests/riscv-rt/entry/fail_public.rs create mode 100644 tests-trybuild/tests/riscv-rt/entry/fail_public.stderr create mode 100644 tests-trybuild/tests/riscv-rt/entry/fail_return_empty.rs create mode 100644 tests-trybuild/tests/riscv-rt/entry/fail_return_empty.stderr create mode 100644 tests-trybuild/tests/riscv-rt/entry/fail_return_tuple.rs create mode 100644 tests-trybuild/tests/riscv-rt/entry/fail_return_tuple.stderr create mode 100644 tests-trybuild/tests/riscv-rt/entry/pass_safe.rs create mode 100644 tests-trybuild/tests/riscv-rt/entry/pass_unsafe.rs diff --git a/tests-trybuild/tests/riscv-rt/entry/fail_arg_count.rs b/tests-trybuild/tests/riscv-rt/entry/fail_arg_count.rs new file mode 100644 index 00000000..622c1248 --- /dev/null +++ b/tests-trybuild/tests/riscv-rt/entry/fail_arg_count.rs @@ -0,0 +1,4 @@ +#[riscv_rt::entry] +fn entry_point(_hart_id: usize, _dtb: usize, _other: usize, _one_more: usize) {} + +fn main() {} diff --git a/tests-trybuild/tests/riscv-rt/entry/fail_arg_count.stderr b/tests-trybuild/tests/riscv-rt/entry/fail_arg_count.stderr new file mode 100644 index 00000000..c3709b70 --- /dev/null +++ b/tests-trybuild/tests/riscv-rt/entry/fail_arg_count.stderr @@ -0,0 +1,5 @@ +error: `#[entry]` function has too many input arguments + --> tests/riscv-rt/entry/fail_arg_count.rs:2:61 + | +2 | fn entry_point(_hart_id: usize, _dtb: usize, _other: usize, _one_more: usize) {} + | ^^^^^^^^^ diff --git a/tests-trybuild/tests/riscv-rt/entry/fail_arg_type.rs b/tests-trybuild/tests/riscv-rt/entry/fail_arg_type.rs new file mode 100644 index 00000000..8cb54a8e --- /dev/null +++ b/tests-trybuild/tests/riscv-rt/entry/fail_arg_type.rs @@ -0,0 +1,4 @@ +#[riscv_rt::entry] +fn entry_point(_hart_id: String) {} + +fn main() {} diff --git a/tests-trybuild/tests/riscv-rt/entry/fail_arg_type.stderr b/tests-trybuild/tests/riscv-rt/entry/fail_arg_type.stderr new file mode 100644 index 00000000..868e576d --- /dev/null +++ b/tests-trybuild/tests/riscv-rt/entry/fail_arg_type.stderr @@ -0,0 +1,5 @@ +error: argument type must be usize + --> tests/riscv-rt/entry/fail_arg_type.rs:2:26 + | +2 | fn entry_point(_hart_id: String) {} + | ^^^^^^ diff --git a/tests-trybuild/tests/riscv-rt/entry/fail_async.rs b/tests-trybuild/tests/riscv-rt/entry/fail_async.rs new file mode 100644 index 00000000..3279f212 --- /dev/null +++ b/tests-trybuild/tests/riscv-rt/entry/fail_async.rs @@ -0,0 +1,4 @@ +#[riscv_rt::entry] +async fn entry_point() {} + +fn main() {} diff --git a/tests-trybuild/tests/riscv-rt/entry/fail_async.stderr b/tests-trybuild/tests/riscv-rt/entry/fail_async.stderr new file mode 100644 index 00000000..33cd0f4c --- /dev/null +++ b/tests-trybuild/tests/riscv-rt/entry/fail_async.stderr @@ -0,0 +1,5 @@ +error: `#[entry]` function signature must be `[unsafe] fn([usize[, usize[, usize]]]) -> !` + --> tests/riscv-rt/entry/fail_async.rs:2:1 + | +2 | async fn entry_point() {} + | ^^^^^ diff --git a/tests-trybuild/tests/riscv-rt/entry/fail_public.rs b/tests-trybuild/tests/riscv-rt/entry/fail_public.rs new file mode 100644 index 00000000..820bc1d0 --- /dev/null +++ b/tests-trybuild/tests/riscv-rt/entry/fail_public.rs @@ -0,0 +1,4 @@ +#[riscv_rt::entry] +pub fn entry_point() -> ! {} + +fn main() {} diff --git a/tests-trybuild/tests/riscv-rt/entry/fail_public.stderr b/tests-trybuild/tests/riscv-rt/entry/fail_public.stderr new file mode 100644 index 00000000..7eb3fbb4 --- /dev/null +++ b/tests-trybuild/tests/riscv-rt/entry/fail_public.stderr @@ -0,0 +1,5 @@ +error: `#[entry]` function must be private + --> tests/riscv-rt/entry/fail_public.rs:2:1 + | +2 | pub fn entry_point() -> ! {} + | ^^^ diff --git a/tests-trybuild/tests/riscv-rt/entry/fail_return_empty.rs b/tests-trybuild/tests/riscv-rt/entry/fail_return_empty.rs new file mode 100644 index 00000000..261817f2 --- /dev/null +++ b/tests-trybuild/tests/riscv-rt/entry/fail_return_empty.rs @@ -0,0 +1,4 @@ +#[riscv_rt::entry] +fn entry_point() {} + +fn main() {} diff --git a/tests-trybuild/tests/riscv-rt/entry/fail_return_empty.stderr b/tests-trybuild/tests/riscv-rt/entry/fail_return_empty.stderr new file mode 100644 index 00000000..96d951c7 --- /dev/null +++ b/tests-trybuild/tests/riscv-rt/entry/fail_return_empty.stderr @@ -0,0 +1,7 @@ +error: return type must be ! + --> tests/riscv-rt/entry/fail_return_empty.rs:1:1 + | +1 | #[riscv_rt::entry] + | ^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `riscv_rt::entry` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests-trybuild/tests/riscv-rt/entry/fail_return_tuple.rs b/tests-trybuild/tests/riscv-rt/entry/fail_return_tuple.rs new file mode 100644 index 00000000..89084d27 --- /dev/null +++ b/tests-trybuild/tests/riscv-rt/entry/fail_return_tuple.rs @@ -0,0 +1,4 @@ +#[riscv_rt::entry] +fn entry_point() -> () {} + +fn main() {} diff --git a/tests-trybuild/tests/riscv-rt/entry/fail_return_tuple.stderr b/tests-trybuild/tests/riscv-rt/entry/fail_return_tuple.stderr new file mode 100644 index 00000000..4427b519 --- /dev/null +++ b/tests-trybuild/tests/riscv-rt/entry/fail_return_tuple.stderr @@ -0,0 +1,5 @@ +error: return type must be ! + --> tests/riscv-rt/entry/fail_return_tuple.rs:2:21 + | +2 | fn entry_point() -> () {} + | ^^ diff --git a/tests-trybuild/tests/riscv-rt/entry/pass_safe.rs b/tests-trybuild/tests/riscv-rt/entry/pass_safe.rs new file mode 100644 index 00000000..701f93af --- /dev/null +++ b/tests-trybuild/tests/riscv-rt/entry/pass_safe.rs @@ -0,0 +1,6 @@ +#[riscv_rt::entry] +fn entry_point(_hart_id: usize) -> ! { + loop {} +} + +fn main() {} // TEST OK diff --git a/tests-trybuild/tests/riscv-rt/entry/pass_unsafe.rs b/tests-trybuild/tests/riscv-rt/entry/pass_unsafe.rs new file mode 100644 index 00000000..e0bbc874 --- /dev/null +++ b/tests-trybuild/tests/riscv-rt/entry/pass_unsafe.rs @@ -0,0 +1,6 @@ +#[riscv_rt::entry] +unsafe fn entry_point(_hart_id: usize) -> ! { + loop {} +} + +fn main() {} // TEST OK