Browse Source

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: https://github.com/rust-embedded/riscv-rt/pull/20

Co-authored-by: Vadim Kaushan <admin@disasm.info>
bors[bot] 6 years ago
parent
commit
816b3ce66f
5 changed files with 280 additions and 39 deletions
  1. 1 0
      riscv-rt/Cargo.toml
  2. 5 0
      riscv-rt/link.x
  3. 31 0
      riscv-rt/macros/Cargo.toml
  4. 212 0
      riscv-rt/macros/src/lib.rs
  5. 31 39
      riscv-rt/src/lib.rs

+ 1 - 0
riscv-rt/Cargo.toml

@@ -11,6 +11,7 @@ license = "ISC"
 [dependencies]
 r0 = "0.2.2"
 riscv = "0.5.0"
+riscv-rt-macros = { path = "macros", version = "0.1.5" }
 
 [features]
 inline-asm = ["riscv/inline-asm"]

+ 5 - 0
riscv-rt/link.x

@@ -5,6 +5,11 @@ PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM));
 
 PROVIDE(trap_handler = default_trap_handler);
 
+/* # Pre-initialization function */
+/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function,
+   then the function this points to will be called before the RAM is initialized. */
+PROVIDE(__pre_init = default_pre_init);
+
 SECTIONS
 {
   PROVIDE(_stext = ORIGIN(FLASH));

+ 31 - 0
riscv-rt/macros/Cargo.toml

@@ -0,0 +1,31 @@
+[package]
+authors = [
+    "The RISC-V Team <risc-v@teams.rust-embedded.org>",
+    "Jorge Aparicio <jorge@japaric.io>",
+]
+categories = ["embedded", "no-std"]
+description = "Attributes re-exported in `riscv-rt`"
+documentation = "https://docs.rs/riscv-rt"
+keywords = ["riscv", "runtime", "startup"]
+license = "MIT OR Apache-2.0"
+name = "riscv-rt-macros"
+repository = "https://github.com/rust-embedded/riscv-rt"
+version = "0.1.5"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+quote = "0.6.8"
+proc-macro2 = "0.4.20"
+
+[dependencies.syn]
+features = ["extra-traits", "full"]
+version = "0.15.13"
+
+[dependencies.rand]
+version = "0.5.5"
+default-features = false
+
+[dev-dependencies]
+riscv-rt = { path = "..", version = "0.4.0" }

+ 212 - 0
riscv-rt/macros/src/lib.rs

@@ -0,0 +1,212 @@
+#![deny(warnings)]
+
+extern crate proc_macro;
+extern crate rand;
+#[macro_use]
+extern crate quote;
+extern crate core;
+extern crate proc_macro2;
+#[macro_use]
+extern crate syn;
+
+use proc_macro2::Span;
+use rand::Rng;
+use rand::SeedableRng;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::time::{SystemTime, UNIX_EPOCH};
+use syn::{
+    parse, spanned::Spanned, Ident, ItemFn, ReturnType, Type, Visibility,
+};
+
+static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
+
+use proc_macro::TokenStream;
+
+/// Attribute to declare the entry point of the program
+///
+/// **IMPORTANT**: This attribute must appear exactly *once* in the dependency graph. Also, if you
+/// are using Rust 1.30 the attribute must be used on a reachable item (i.e. there must be no
+/// private modules between the item and the root of the crate); if the item is in the root of the
+/// crate you'll be fine. This reachability restriction doesn't apply to Rust 1.31 and newer releases.
+///
+/// The specified function will be called by the reset handler *after* RAM has been initialized. In
+/// the case of the `thumbv7em-none-eabihf` target the FPU will also be enabled before the function
+/// is called.
+///
+/// The type of the specified function must be `[unsafe] fn() -> !` (never ending function)
+///
+/// # Properties
+///
+/// The entry point will be called by the reset handler. The program can't reference to the entry
+/// point, much less invoke it.
+///
+/// # Examples
+///
+/// - Simple entry point
+///
+/// ``` no_run
+/// # #![no_main]
+/// # use riscv_rt_macros::entry;
+/// #[entry]
+/// fn main() -> ! {
+///     loop {
+///         /* .. */
+///     }
+/// }
+/// ```
+#[proc_macro_attribute]
+pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
+    let f = parse_macro_input!(input as ItemFn);
+
+    // check the function signature
+    let valid_signature = f.constness.is_none()
+        && f.vis == Visibility::Inherited
+        && f.abi.is_none()
+        && f.decl.inputs.is_empty()
+        && f.decl.generics.params.is_empty()
+        && f.decl.generics.where_clause.is_none()
+        && f.decl.variadic.is_none()
+        && match f.decl.output {
+            ReturnType::Default => false,
+            ReturnType::Type(_, ref ty) => match **ty {
+                Type::Never(_) => true,
+                _ => false,
+            },
+        };
+
+    if !valid_signature {
+        return parse::Error::new(
+            f.span(),
+            "`#[entry]` function must have signature `[unsafe] fn() -> !`",
+        )
+        .to_compile_error()
+        .into();
+    }
+
+    if !args.is_empty() {
+        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
+            .to_compile_error()
+            .into();
+    }
+
+    // XXX should we blacklist other attributes?
+    let attrs = f.attrs;
+    let unsafety = f.unsafety;
+    let hash = random_ident();
+    let stmts = f.block.stmts;
+
+    quote!(
+        #[export_name = "main"]
+        #(#attrs)*
+        pub #unsafety fn #hash() -> ! {
+            #(#stmts)*
+        }
+    )
+    .into()
+}
+
+/// Attribute to mark which function will be called at the beginning of the reset handler.
+///
+/// **IMPORTANT**: This attribute can appear at most *once* in the dependency graph. Also, if you
+/// are using Rust 1.30 the attribute must be used on a reachable item (i.e. there must be no
+/// private modules between the item and the root of the crate); if the item is in the root of the
+/// crate you'll be fine. This reachability restriction doesn't apply to Rust 1.31 and newer
+/// releases.
+///
+/// The function must have the signature of `unsafe fn()`.
+///
+/// The function passed will be called before static variables are initialized. Any access of static
+/// variables will result in undefined behavior.
+///
+/// # Examples
+///
+/// ```
+/// # use riscv_rt_macros::pre_init;
+/// #[pre_init]
+/// unsafe fn before_main() {
+///     // do something here
+/// }
+///
+/// # fn main() {}
+/// ```
+#[proc_macro_attribute]
+pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream {
+    let f = parse_macro_input!(input as ItemFn);
+
+    // check the function signature
+    let valid_signature = f.constness.is_none()
+        && f.vis == Visibility::Inherited
+        && f.unsafety.is_some()
+        && f.abi.is_none()
+        && f.decl.inputs.is_empty()
+        && f.decl.generics.params.is_empty()
+        && f.decl.generics.where_clause.is_none()
+        && f.decl.variadic.is_none()
+        && match f.decl.output {
+            ReturnType::Default => true,
+            ReturnType::Type(_, ref ty) => match **ty {
+                Type::Tuple(ref tuple) => tuple.elems.is_empty(),
+                _ => false,
+            },
+        };
+
+    if !valid_signature {
+        return parse::Error::new(
+            f.span(),
+            "`#[pre_init]` function must have signature `unsafe fn()`",
+        )
+        .to_compile_error()
+        .into();
+    }
+
+    if !args.is_empty() {
+        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
+            .to_compile_error()
+            .into();
+    }
+
+    // XXX should we blacklist other attributes?
+    let attrs = f.attrs;
+    let ident = f.ident;
+    let block = f.block;
+
+    quote!(
+        #[export_name = "__pre_init"]
+        #(#attrs)*
+        pub unsafe fn #ident() #block
+    )
+    .into()
+}
+
+// Creates a random identifier
+fn random_ident() -> Ident {
+    let secs = SystemTime::now()
+        .duration_since(UNIX_EPOCH)
+        .unwrap()
+        .as_secs();
+
+    let count: u64 = CALL_COUNT.fetch_add(1, Ordering::SeqCst) as u64;
+    let mut seed: [u8; 16] = [0; 16];
+
+    for (i, v) in seed.iter_mut().take(8).enumerate() {
+        *v = ((secs >> (i * 8)) & 0xFF) as u8
+    }
+
+    for (i, v) in seed.iter_mut().skip(8).enumerate() {
+        *v = ((count >> (i * 8)) & 0xFF) as u8
+    }
+
+    let mut rng = rand::rngs::SmallRng::from_seed(seed);
+    Ident::new(
+        &(0..16)
+            .map(|i| {
+                if i == 0 || rng.gen() {
+                    ('a' as u8 + rng.gen::<u8>() % 25) as char
+                } else {
+                    ('0' as u8 + rng.gen::<u8>() % 10) as char
+                }
+            })
+            .collect::<String>(),
+        Span::call_site(),
+    )
+}

+ 31 - 39
riscv-rt/src/lib.rs

@@ -8,7 +8,8 @@
 //!
 //! - Before main initialization of the FPU (for targets that have a FPU).
 //!
-//! - An `entry!` macro to declare the entry point of the program.
+//! - `#[entry]` to declare the entry point of the program
+//! - `#[pre_init]` to run code *before* `static` variables are initialized
 //!
 //! - A linker script that encodes the memory layout of a generic RISC-V
 //!   microcontroller. This linker script is missing some information that must
@@ -46,8 +47,8 @@
 //! use riscv_rt::entry;
 //!
 //! // use `main` as the entry point of this application
-//! entry!(main);
-//!
+//! // `main` is not allowed to return
+//! #[entry]
 //! fn main() -> ! {
 //!     // do something here
 //!     loop { }
@@ -180,6 +181,14 @@
 //!     }
 //! }
 //! ```
+//!
+//! ## `pre_init!`
+//!
+//! A user-defined function can be run at the start of the reset handler, before RAM is
+//! initialized. The macro `pre_init!` can be called to set the function to be run. The function is
+//! intended to perform actions that cannot wait the time it takes for RAM to be initialized, such
+//! as disabling a watchdog. As the function is called before RAM is initialized, any access of
+//! static variables will result in undefined behavior.
 
 // NOTE: Adapted from cortex-m/src/lib.rs
 #![no_std]
@@ -187,8 +196,11 @@
 #![deny(warnings)]
 
 extern crate riscv;
+extern crate riscv_rt_macros as macros;
 extern crate r0;
 
+pub use macros::{entry, pre_init};
+
 use riscv::register::{mstatus, mtvec};
 
 extern "C" {
@@ -214,50 +226,26 @@ extern "C" {
 /// never returns.
 #[link_section = ".init.rust"]
 #[export_name = "_start_rust"]
-pub extern "C" fn start_rust() -> ! {
-    extern "C" {
-        // This symbol will be provided by the user via the `entry!` macro
+pub unsafe extern "C" fn start_rust() -> ! {
+    extern "Rust" {
+        // This symbol will be provided by the user via `#[entry]`
         fn main() -> !;
-    }
 
-    unsafe {
-        r0::zero_bss(&mut _sbss, &mut _ebss);
-        r0::init_data(&mut _sdata, &mut _edata, &_sidata);
+        // This symbol will be provided by the user via `#[pre_init]`
+        fn __pre_init();
     }
 
-    // TODO: Enable FPU when available
+    __pre_init();
 
-    unsafe {
-        // Set mtvec to _start_trap
-        mtvec::write(&_start_trap as *const _ as usize, mtvec::TrapMode::Direct);
+    r0::zero_bss(&mut _sbss, &mut _ebss);
+    r0::init_data(&mut _sdata, &mut _edata, &_sidata);
 
-        main();
-    }
-}
+    // TODO: Enable FPU when available
 
+    // Set mtvec to _start_trap
+    mtvec::write(&_start_trap as *const _ as usize, mtvec::TrapMode::Direct);
 
-/// Macro to define the entry point of the program
-///
-/// **NOTE** This macro must be invoked once and must be invoked from an accessible module, ideally
-/// from the root of the crate.
-///
-/// Usage: `entry!(path::to::entry::point)`
-///
-/// The specified function will be called by the reset handler *after* RAM has been initialized.
-///
-/// The signature of the specified function must be `fn() -> !` (never ending function).
-#[macro_export]
-macro_rules! entry {
-    ($path:expr) => {
-        #[inline(never)]
-        #[export_name = "main"]
-        pub extern "C" fn __impl_main() -> ! {
-            // validate the signature of the program entry point
-            let f: fn() -> ! = $path;
-
-            f()
-        }
-    };
+    main();
 }
 
 
@@ -287,3 +275,7 @@ pub extern "C" fn start_trap_rust() {
 /// Default Trap Handler
 #[no_mangle]
 pub fn default_trap_handler() {}
+
+#[doc(hidden)]
+#[no_mangle]
+pub unsafe extern "Rust" fn default_pre_init() {}