Skip to content

Commit 9468182

Browse files
Merge pull request #328 from rust-embedded/post-init
`riscv-rt`: Add `__post_init` Rust function
2 parents 2d2d7c6 + 0d05388 commit 9468182

File tree

14 files changed

+171
-22
lines changed

14 files changed

+171
-22
lines changed

riscv-rt/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- New `post-init` feature to run a Rust `__post_init` function before jumping to `main`.
13+
- New `#[riscv_rt::post_init]` attribute to aid in the definition of the `__post_init` function.
14+
1015
### Changed
1116

1217
- `main` function no longer needs to be close to `_start`. A linker script may copy

riscv-rt/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ links = "riscv-rt" # Prevent multiple versions of riscv-rt being linked
1414

1515
[package.metadata.docs.rs]
1616
default-target = "riscv64imac-unknown-none-elf"
17-
features = ["pre-init"]
17+
features = ["pre-init", "post-init"]
1818
targets = [
1919
"riscv32i-unknown-none-elf", "riscv32imc-unknown-none-elf", "riscv32imac-unknown-none-elf",
2020
"riscv64imac-unknown-none-elf", "riscv64gc-unknown-none-elf",
@@ -33,6 +33,7 @@ panic-halt = "1.0.0"
3333

3434
[features]
3535
pre-init = []
36+
post-init = []
3637
s-mode = ["riscv-rt-macros/s-mode"]
3738
single-hart = []
3839
v-trap = ["riscv-rt-macros/v-trap"]

riscv-rt/macros/src/lib.rs

Lines changed: 109 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -63,26 +63,6 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
6363
.into();
6464
}
6565

66-
fn check_correct_type(argument: &PatType, ty: &str) -> Option<TokenStream> {
67-
let inv_type_message = format!("argument type must be {ty}");
68-
69-
if !is_correct_type(&argument.ty, ty) {
70-
let error = parse::Error::new(argument.ty.span(), inv_type_message);
71-
72-
Some(error.to_compile_error().into())
73-
} else {
74-
None
75-
}
76-
}
77-
fn check_argument_type(argument: &FnArg, ty: &str) -> Option<TokenStream> {
78-
let argument_error = parse::Error::new(argument.span(), "invalid argument");
79-
let argument_error = argument_error.to_compile_error().into();
80-
81-
match argument {
82-
FnArg::Typed(argument) => check_correct_type(argument, ty),
83-
FnArg::Receiver(_) => Some(argument_error),
84-
}
85-
}
8666
#[cfg(not(feature = "u-boot"))]
8767
for argument in f.sig.inputs.iter() {
8868
if let Some(message) = check_argument_type(argument, "usize") {
@@ -181,6 +161,28 @@ fn is_correct_type(ty: &Type, name: &str) -> bool {
181161
}
182162
}
183163

164+
fn check_correct_type(argument: &PatType, ty: &str) -> Option<TokenStream> {
165+
let inv_type_message = format!("argument type must be {ty}");
166+
167+
if !is_correct_type(&argument.ty, ty) {
168+
let error = parse::Error::new(argument.ty.span(), inv_type_message);
169+
170+
Some(error.to_compile_error().into())
171+
} else {
172+
None
173+
}
174+
}
175+
176+
fn check_argument_type(argument: &FnArg, ty: &str) -> Option<TokenStream> {
177+
let argument_error = parse::Error::new(argument.span(), "invalid argument");
178+
let argument_error = argument_error.to_compile_error().into();
179+
180+
match argument {
181+
FnArg::Typed(argument) => check_correct_type(argument, ty),
182+
FnArg::Receiver(_) => Some(argument_error),
183+
}
184+
}
185+
184186
/// Attribute to mark which function will be called at the beginning of the reset handler.
185187
/// You must enable the `pre_init` feature in the `riscv-rt` crate to use this macro.
186188
///
@@ -263,6 +265,93 @@ pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream {
263265
.into()
264266
}
265267

268+
/// Attribute to mark which function will be called before jumping to the entry point.
269+
/// You must enable the `post-init` feature in the `riscv-rt` crate to use this macro.
270+
///
271+
/// In contrast with `__pre_init`, this function is called after the static variables
272+
/// are initialized, so it is safe to access them. It is also safe to run Rust code.
273+
///
274+
/// The function must have the signature of `[unsafe] fn([usize])`, where the argument
275+
/// corresponds to the hart ID of the current hart. This is useful for multi-hart systems
276+
/// to perform hart-specific initialization.
277+
///
278+
/// # IMPORTANT
279+
///
280+
/// This attribute can appear at most *once* in the dependency graph.
281+
///
282+
/// # Examples
283+
///
284+
/// ```
285+
/// use riscv_rt_macros::post_init;
286+
/// #[post_init]
287+
/// unsafe fn before_main(hart_id: usize) {
288+
/// // do something here
289+
/// }
290+
/// ```
291+
#[proc_macro_attribute]
292+
pub fn post_init(args: TokenStream, input: TokenStream) -> TokenStream {
293+
let f = parse_macro_input!(input as ItemFn);
294+
295+
// check the function arguments
296+
if f.sig.inputs.len() > 1 {
297+
return parse::Error::new(
298+
f.sig.inputs.last().unwrap().span(),
299+
"`#[post_init]` function has too many arguments",
300+
)
301+
.to_compile_error()
302+
.into();
303+
}
304+
for argument in f.sig.inputs.iter() {
305+
if let Some(message) = check_argument_type(argument, "usize") {
306+
return message;
307+
};
308+
}
309+
310+
// check the function signature
311+
let valid_signature = f.sig.constness.is_none()
312+
&& f.sig.asyncness.is_none()
313+
&& f.vis == Visibility::Inherited
314+
&& f.sig.abi.is_none()
315+
&& f.sig.generics.params.is_empty()
316+
&& f.sig.generics.where_clause.is_none()
317+
&& f.sig.variadic.is_none()
318+
&& match f.sig.output {
319+
ReturnType::Default => true,
320+
ReturnType::Type(_, ref ty) => match **ty {
321+
Type::Tuple(ref tuple) => tuple.elems.is_empty(),
322+
_ => false,
323+
},
324+
};
325+
326+
if !valid_signature {
327+
return parse::Error::new(
328+
f.span(),
329+
"`#[post_init]` function must have signature `[unsafe] fn([usize])`",
330+
)
331+
.to_compile_error()
332+
.into();
333+
}
334+
335+
if !args.is_empty() {
336+
return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
337+
.to_compile_error()
338+
.into();
339+
}
340+
341+
// XXX should we blacklist other attributes?
342+
let attrs = f.attrs;
343+
let ident = f.sig.ident;
344+
let args = f.sig.inputs;
345+
let block = f.block;
346+
347+
quote!(
348+
#[export_name = "__post_init"]
349+
#(#attrs)*
350+
unsafe fn #ident(#args) #block
351+
)
352+
.into()
353+
}
354+
266355
struct AsmLoopArgs {
267356
asm_template: String,
268357
count_from: usize,

riscv-rt/src/lib.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,14 @@
553553
//! );
554554
//! ```
555555
//!
556+
//! ## `post-init`
557+
//!
558+
//! When enabled, the runtime will execute the `__post_init` function to be run before jumping to the main function.
559+
//! If the feature is enabled, the `__post_init` function must be defined in the user code (i.e., no default implementation
560+
//! is provided by this crate). If the feature is disabled, the `__post_init` function is not required.
561+
//!
562+
//! You can use the [`#[post_init]`][attr-post-init] attribute to define a post-init function with Rust.
563+
//!
556564
//! ## `single-hart`
557565
//!
558566
//! Saves a little code size if there is only one hart on the target.
@@ -595,6 +603,7 @@
595603
//! [attr-external-interrupt]: attr.external_interrupt.html
596604
//! [attr-core-interrupt]: attr.core_interrupt.html
597605
//! [attr-pre-init]: attr.pre_init.html
606+
//! [attr-post-init]: attr.post_init.html
598607
599608
// NOTE: Adapted from cortex-m/src/lib.rs
600609
#![no_std]
@@ -624,6 +633,8 @@ use riscv::register::{
624633
pub use riscv_pac::*;
625634
pub use riscv_rt_macros::{core_interrupt, entry, exception, external_interrupt};
626635

636+
#[cfg(feature = "post-init")]
637+
pub use riscv_rt_macros::post_init;
627638
#[cfg(feature = "pre-init")]
628639
pub use riscv_rt_macros::pre_init;
629640

@@ -650,10 +661,14 @@ pub static __ONCE__: () = ();
650661
#[export_name = "_start_rust"]
651662
pub unsafe extern "C" fn start_rust(a0: usize, a1: usize, a2: usize) -> ! {
652663
extern "Rust" {
664+
#[cfg(feature = "post-init")]
665+
fn __post_init(a0: usize);
653666
fn _setup_interrupts();
654667
fn hal_main(a0: usize, a1: usize, a2: usize) -> !;
655668
}
656669

670+
#[cfg(feature = "post-init")]
671+
__post_init(a0);
657672
_setup_interrupts();
658673
hal_main(a0, a1, a2);
659674
}

tests-trybuild/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ edition = "2021"
55

66
[dependencies]
77
riscv = { path = "../riscv" }
8-
riscv-rt = { path = "../riscv-rt", features = ["no-exceptions", "no-interrupts"] }
8+
riscv-rt = { path = "../riscv-rt", features = ["no-exceptions", "no-interrupts", "post-init"] }
99
trybuild = "1.0"
1010

1111
[features]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#[riscv_rt::post_init]
2+
fn before_main(_hart_id: usize, _dtb: usize) {}
3+
4+
fn main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: `#[post_init]` function has too many arguments
2+
--> tests/riscv-rt/post_init/fail_arg_count.rs:2:33
3+
|
4+
2 | fn before_main(_hart_id: usize, _dtb: usize) {}
5+
| ^^^^
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#[riscv_rt::post_init]
2+
fn before_main(_hart_id: String) {}
3+
4+
fn main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: argument type must be usize
2+
--> tests/riscv-rt/post_init/fail_arg_type.rs:2:26
3+
|
4+
2 | fn before_main(_hart_id: String) {}
5+
| ^^^^^^
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#[riscv_rt::post_init]
2+
async fn before_main() {}
3+
4+
fn main() {}

0 commit comments

Comments
 (0)