Skip to content
This repository was archived by the owner on Nov 28, 2023. It is now read-only.

Commit 3fd342f

Browse files
bors[bot]Disasm
andcommitted
Merge #27
27: Add 'entry' and 'pre_init' attributes r=dvc94ch a=Disasm Implementation is based on [`cortex-m-rt-macros`](https://github.com/rust-embedded/cortex-m-rt/tree/master/macros) code. This implementation has been changed to make `static mut` unsafe inside entry point and different handlers. Related: #20 Co-authored-by: Vadim Kaushan <[email protected]>
2 parents 59247bb + 32991f2 commit 3fd342f

File tree

5 files changed

+280
-39
lines changed

5 files changed

+280
-39
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ license = "ISC"
1111
[dependencies]
1212
r0 = "0.2.2"
1313
riscv = "0.5.0"
14+
riscv-rt-macros = { path = "macros", version = "0.1.5" }
1415

1516
[features]
1617
inline-asm = ["riscv/inline-asm"]

link.x

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM));
55

66
PROVIDE(trap_handler = default_trap_handler);
77

8+
/* # Pre-initialization function */
9+
/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function,
10+
then the function this points to will be called before the RAM is initialized. */
11+
PROVIDE(__pre_init = default_pre_init);
12+
813
SECTIONS
914
{
1015
PROVIDE(_stext = ORIGIN(FLASH));

macros/Cargo.toml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[package]
2+
authors = [
3+
"The RISC-V Team <[email protected]>",
4+
"Jorge Aparicio <[email protected]>",
5+
]
6+
categories = ["embedded", "no-std"]
7+
description = "Attributes re-exported in `riscv-rt`"
8+
documentation = "https://docs.rs/riscv-rt"
9+
keywords = ["riscv", "runtime", "startup"]
10+
license = "MIT OR Apache-2.0"
11+
name = "riscv-rt-macros"
12+
repository = "https://github.com/rust-embedded/riscv-rt"
13+
version = "0.1.5"
14+
15+
[lib]
16+
proc-macro = true
17+
18+
[dependencies]
19+
quote = "0.6.8"
20+
proc-macro2 = "0.4.20"
21+
22+
[dependencies.syn]
23+
features = ["extra-traits", "full"]
24+
version = "0.15.13"
25+
26+
[dependencies.rand]
27+
version = "0.5.5"
28+
default-features = false
29+
30+
[dev-dependencies]
31+
riscv-rt = { path = "..", version = "0.4.0" }

macros/src/lib.rs

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
#![deny(warnings)]
2+
3+
extern crate proc_macro;
4+
extern crate rand;
5+
#[macro_use]
6+
extern crate quote;
7+
extern crate core;
8+
extern crate proc_macro2;
9+
#[macro_use]
10+
extern crate syn;
11+
12+
use proc_macro2::Span;
13+
use rand::Rng;
14+
use rand::SeedableRng;
15+
use std::sync::atomic::{AtomicUsize, Ordering};
16+
use std::time::{SystemTime, UNIX_EPOCH};
17+
use syn::{
18+
parse, spanned::Spanned, Ident, ItemFn, ReturnType, Type, Visibility,
19+
};
20+
21+
static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
22+
23+
use proc_macro::TokenStream;
24+
25+
/// Attribute to declare the entry point of the program
26+
///
27+
/// **IMPORTANT**: This attribute must appear exactly *once* in the dependency graph. Also, if you
28+
/// are using Rust 1.30 the attribute must be used on a reachable item (i.e. there must be no
29+
/// private modules between the item and the root of the crate); if the item is in the root of the
30+
/// crate you'll be fine. This reachability restriction doesn't apply to Rust 1.31 and newer releases.
31+
///
32+
/// The specified function will be called by the reset handler *after* RAM has been initialized. In
33+
/// the case of the `thumbv7em-none-eabihf` target the FPU will also be enabled before the function
34+
/// is called.
35+
///
36+
/// The type of the specified function must be `[unsafe] fn() -> !` (never ending function)
37+
///
38+
/// # Properties
39+
///
40+
/// The entry point will be called by the reset handler. The program can't reference to the entry
41+
/// point, much less invoke it.
42+
///
43+
/// # Examples
44+
///
45+
/// - Simple entry point
46+
///
47+
/// ``` no_run
48+
/// # #![no_main]
49+
/// # use riscv_rt_macros::entry;
50+
/// #[entry]
51+
/// fn main() -> ! {
52+
/// loop {
53+
/// /* .. */
54+
/// }
55+
/// }
56+
/// ```
57+
#[proc_macro_attribute]
58+
pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
59+
let f = parse_macro_input!(input as ItemFn);
60+
61+
// check the function signature
62+
let valid_signature = f.constness.is_none()
63+
&& f.vis == Visibility::Inherited
64+
&& f.abi.is_none()
65+
&& f.decl.inputs.is_empty()
66+
&& f.decl.generics.params.is_empty()
67+
&& f.decl.generics.where_clause.is_none()
68+
&& f.decl.variadic.is_none()
69+
&& match f.decl.output {
70+
ReturnType::Default => false,
71+
ReturnType::Type(_, ref ty) => match **ty {
72+
Type::Never(_) => true,
73+
_ => false,
74+
},
75+
};
76+
77+
if !valid_signature {
78+
return parse::Error::new(
79+
f.span(),
80+
"`#[entry]` function must have signature `[unsafe] fn() -> !`",
81+
)
82+
.to_compile_error()
83+
.into();
84+
}
85+
86+
if !args.is_empty() {
87+
return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
88+
.to_compile_error()
89+
.into();
90+
}
91+
92+
// XXX should we blacklist other attributes?
93+
let attrs = f.attrs;
94+
let unsafety = f.unsafety;
95+
let hash = random_ident();
96+
let stmts = f.block.stmts;
97+
98+
quote!(
99+
#[export_name = "main"]
100+
#(#attrs)*
101+
pub #unsafety fn #hash() -> ! {
102+
#(#stmts)*
103+
}
104+
)
105+
.into()
106+
}
107+
108+
/// Attribute to mark which function will be called at the beginning of the reset handler.
109+
///
110+
/// **IMPORTANT**: This attribute can appear at most *once* in the dependency graph. Also, if you
111+
/// are using Rust 1.30 the attribute must be used on a reachable item (i.e. there must be no
112+
/// private modules between the item and the root of the crate); if the item is in the root of the
113+
/// crate you'll be fine. This reachability restriction doesn't apply to Rust 1.31 and newer
114+
/// releases.
115+
///
116+
/// The function must have the signature of `unsafe fn()`.
117+
///
118+
/// The function passed will be called before static variables are initialized. Any access of static
119+
/// variables will result in undefined behavior.
120+
///
121+
/// # Examples
122+
///
123+
/// ```
124+
/// # use riscv_rt_macros::pre_init;
125+
/// #[pre_init]
126+
/// unsafe fn before_main() {
127+
/// // do something here
128+
/// }
129+
///
130+
/// # fn main() {}
131+
/// ```
132+
#[proc_macro_attribute]
133+
pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream {
134+
let f = parse_macro_input!(input as ItemFn);
135+
136+
// check the function signature
137+
let valid_signature = f.constness.is_none()
138+
&& f.vis == Visibility::Inherited
139+
&& f.unsafety.is_some()
140+
&& f.abi.is_none()
141+
&& f.decl.inputs.is_empty()
142+
&& f.decl.generics.params.is_empty()
143+
&& f.decl.generics.where_clause.is_none()
144+
&& f.decl.variadic.is_none()
145+
&& match f.decl.output {
146+
ReturnType::Default => true,
147+
ReturnType::Type(_, ref ty) => match **ty {
148+
Type::Tuple(ref tuple) => tuple.elems.is_empty(),
149+
_ => false,
150+
},
151+
};
152+
153+
if !valid_signature {
154+
return parse::Error::new(
155+
f.span(),
156+
"`#[pre_init]` function must have signature `unsafe fn()`",
157+
)
158+
.to_compile_error()
159+
.into();
160+
}
161+
162+
if !args.is_empty() {
163+
return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
164+
.to_compile_error()
165+
.into();
166+
}
167+
168+
// XXX should we blacklist other attributes?
169+
let attrs = f.attrs;
170+
let ident = f.ident;
171+
let block = f.block;
172+
173+
quote!(
174+
#[export_name = "__pre_init"]
175+
#(#attrs)*
176+
pub unsafe fn #ident() #block
177+
)
178+
.into()
179+
}
180+
181+
// Creates a random identifier
182+
fn random_ident() -> Ident {
183+
let secs = SystemTime::now()
184+
.duration_since(UNIX_EPOCH)
185+
.unwrap()
186+
.as_secs();
187+
188+
let count: u64 = CALL_COUNT.fetch_add(1, Ordering::SeqCst) as u64;
189+
let mut seed: [u8; 16] = [0; 16];
190+
191+
for (i, v) in seed.iter_mut().take(8).enumerate() {
192+
*v = ((secs >> (i * 8)) & 0xFF) as u8
193+
}
194+
195+
for (i, v) in seed.iter_mut().skip(8).enumerate() {
196+
*v = ((count >> (i * 8)) & 0xFF) as u8
197+
}
198+
199+
let mut rng = rand::rngs::SmallRng::from_seed(seed);
200+
Ident::new(
201+
&(0..16)
202+
.map(|i| {
203+
if i == 0 || rng.gen() {
204+
('a' as u8 + rng.gen::<u8>() % 25) as char
205+
} else {
206+
('0' as u8 + rng.gen::<u8>() % 10) as char
207+
}
208+
})
209+
.collect::<String>(),
210+
Span::call_site(),
211+
)
212+
}

src/lib.rs

Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
//!
99
//! - Before main initialization of the FPU (for targets that have a FPU).
1010
//!
11-
//! - An `entry!` macro to declare the entry point of the program.
11+
//! - `#[entry]` to declare the entry point of the program
12+
//! - `#[pre_init]` to run code *before* `static` variables are initialized
1213
//!
1314
//! - A linker script that encodes the memory layout of a generic RISC-V
1415
//! microcontroller. This linker script is missing some information that must
@@ -46,8 +47,8 @@
4647
//! use riscv_rt::entry;
4748
//!
4849
//! // use `main` as the entry point of this application
49-
//! entry!(main);
50-
//!
50+
//! // `main` is not allowed to return
51+
//! #[entry]
5152
//! fn main() -> ! {
5253
//! // do something here
5354
//! loop { }
@@ -180,15 +181,26 @@
180181
//! }
181182
//! }
182183
//! ```
184+
//!
185+
//! ## `pre_init!`
186+
//!
187+
//! A user-defined function can be run at the start of the reset handler, before RAM is
188+
//! initialized. The macro `pre_init!` can be called to set the function to be run. The function is
189+
//! intended to perform actions that cannot wait the time it takes for RAM to be initialized, such
190+
//! as disabling a watchdog. As the function is called before RAM is initialized, any access of
191+
//! static variables will result in undefined behavior.
183192
184193
// NOTE: Adapted from cortex-m/src/lib.rs
185194
#![no_std]
186195
#![deny(missing_docs)]
187196
#![deny(warnings)]
188197

189198
extern crate riscv;
199+
extern crate riscv_rt_macros as macros;
190200
extern crate r0;
191201

202+
pub use macros::{entry, pre_init};
203+
192204
use riscv::register::{mstatus, mtvec};
193205

194206
extern "C" {
@@ -214,50 +226,26 @@ extern "C" {
214226
/// never returns.
215227
#[link_section = ".init.rust"]
216228
#[export_name = "_start_rust"]
217-
pub extern "C" fn start_rust() -> ! {
218-
extern "C" {
219-
// This symbol will be provided by the user via the `entry!` macro
229+
pub unsafe extern "C" fn start_rust() -> ! {
230+
extern "Rust" {
231+
// This symbol will be provided by the user via `#[entry]`
220232
fn main() -> !;
221-
}
222233

223-
unsafe {
224-
r0::zero_bss(&mut _sbss, &mut _ebss);
225-
r0::init_data(&mut _sdata, &mut _edata, &_sidata);
234+
// This symbol will be provided by the user via `#[pre_init]`
235+
fn __pre_init();
226236
}
227237

228-
// TODO: Enable FPU when available
238+
__pre_init();
229239

230-
unsafe {
231-
// Set mtvec to _start_trap
232-
mtvec::write(&_start_trap as *const _ as usize, mtvec::TrapMode::Direct);
240+
r0::zero_bss(&mut _sbss, &mut _ebss);
241+
r0::init_data(&mut _sdata, &mut _edata, &_sidata);
233242

234-
main();
235-
}
236-
}
243+
// TODO: Enable FPU when available
237244

245+
// Set mtvec to _start_trap
246+
mtvec::write(&_start_trap as *const _ as usize, mtvec::TrapMode::Direct);
238247

239-
/// Macro to define the entry point of the program
240-
///
241-
/// **NOTE** This macro must be invoked once and must be invoked from an accessible module, ideally
242-
/// from the root of the crate.
243-
///
244-
/// Usage: `entry!(path::to::entry::point)`
245-
///
246-
/// The specified function will be called by the reset handler *after* RAM has been initialized.
247-
///
248-
/// The signature of the specified function must be `fn() -> !` (never ending function).
249-
#[macro_export]
250-
macro_rules! entry {
251-
($path:expr) => {
252-
#[inline(never)]
253-
#[export_name = "main"]
254-
pub extern "C" fn __impl_main() -> ! {
255-
// validate the signature of the program entry point
256-
let f: fn() -> ! = $path;
257-
258-
f()
259-
}
260-
};
248+
main();
261249
}
262250

263251

@@ -287,3 +275,7 @@ pub extern "C" fn start_trap_rust() {
287275
/// Default Trap Handler
288276
#[no_mangle]
289277
pub fn default_trap_handler() {}
278+
279+
#[doc(hidden)]
280+
#[no_mangle]
281+
pub unsafe extern "Rust" fn default_pre_init() {}

0 commit comments

Comments
 (0)