aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Adam Greig <adam@adamgreig.com> 2023-08-16 22:28:08 +0000
committerGravatar GitHub <noreply@github.com> 2023-08-16 22:28:08 +0000
commit1746a63ca16b68514ea23dcca1543aed00165452 (patch)
treefbed40673119c9a2e1148721e85fcd1449d2b9da
parent4651808e1de360e01b3e734c655aeb5310881b7c (diff)
parentfaf7bd3cf37343dec23f61c185e0ae22f684e991 (diff)
downloadcortex-m-1746a63ca16b68514ea23dcca1543aed00165452.tar.gz
cortex-m-1746a63ca16b68514ea23dcca1543aed00165452.tar.zst
cortex-m-1746a63ca16b68514ea23dcca1543aed00165452.zip
Merge pull request #476 from diondokter/optional-hardfault-trampoline
Hardfault trampoline is now optional
-rw-r--r--cortex-m-rt/CHANGELOG.md1
-rw-r--r--cortex-m-rt/macros/src/lib.rs224
-rw-r--r--cortex-m-rt/src/lib.rs66
-rw-r--r--cortex-m-rt/tests/README.md7
-rw-r--r--cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-2.rs18
-rw-r--r--cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-3.rs18
-rw-r--r--cortex-m-rt/tests/compile-fail/hard-fault-twice-mixed-trampoline.rs26
-rw-r--r--cortex-m-rt/tests/compile-fail/hard-fault-twice.rs2
8 files changed, 275 insertions, 87 deletions
diff --git a/cortex-m-rt/CHANGELOG.md b/cortex-m-rt/CHANGELOG.md
index fd45a1a..40dad58 100644
--- a/cortex-m-rt/CHANGELOG.md
+++ b/cortex-m-rt/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Add `zero-init-ram` feature to initialize RAM with zeros on startup. This can be necessary on
safety-critical hardware to properly initialize memory integrity measures.
+- Add optional `exception` argument for `HardFault`. It has one option `trampoline` which is true by default. When set to false, no trampoline will be created and the function will be called as the exception handler directly.
## [v0.7.3]
diff --git a/cortex-m-rt/macros/src/lib.rs b/cortex-m-rt/macros/src/lib.rs
index 27ee2f1..0477405 100644
--- a/cortex-m-rt/macros/src/lib.rs
+++ b/cortex-m-rt/macros/src/lib.rs
@@ -7,11 +7,14 @@ extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
-use std::collections::HashSet;
use std::iter;
+use std::{collections::HashSet, fmt::Display};
use syn::{
- parse, parse_macro_input, spanned::Spanned, AttrStyle, Attribute, FnArg, Ident, Item, ItemFn,
- ItemStatic, ReturnType, Stmt, Type, Visibility,
+ parse::{self, Parse},
+ parse_macro_input,
+ spanned::Spanned,
+ AttrStyle, Attribute, FnArg, Ident, Item, ItemFn, ItemStatic, ReturnType, Stmt, Type,
+ Visibility,
};
#[proc_macro_attribute]
@@ -113,21 +116,84 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
#[derive(Debug, PartialEq)]
enum Exception {
DefaultHandler,
- HardFault,
+ HardFault(HardFaultArgs),
NonMaskableInt,
Other,
}
+impl Display for Exception {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Exception::DefaultHandler => write!(f, "`DefaultHandler`"),
+ Exception::HardFault(_) => write!(f, "`HardFault` handler"),
+ Exception::NonMaskableInt => write!(f, "`NonMaskableInt` handler"),
+ Exception::Other => write!(f, "Other exception handler"),
+ }
+ }
+}
+
+#[derive(Debug, PartialEq)]
+struct HardFaultArgs {
+ trampoline: bool,
+}
+
+impl Default for HardFaultArgs {
+ fn default() -> Self {
+ Self { trampoline: true }
+ }
+}
+
+impl Parse for HardFaultArgs {
+ fn parse(input: parse::ParseStream) -> syn::Result<Self> {
+ let mut items = Vec::new();
+ // Read a list of `ident = value,`
+ loop {
+ if input.is_empty() {
+ break;
+ }
+
+ let name = input.parse::<Ident>()?;
+ input.parse::<syn::Token!(=)>()?;
+ let value = input.parse::<syn::Lit>()?;
+
+ items.push((name, value));
+
+ if input.is_empty() {
+ break;
+ }
+
+ input.parse::<syn::Token!(,)>()?;
+ }
+
+ let mut args = Self::default();
+
+ for (name, value) in items {
+ match name.to_string().as_str() {
+ "trampoline" => match value {
+ syn::Lit::Bool(val) => {
+ args.trampoline = val.value();
+ }
+ _ => {
+ return Err(syn::Error::new_spanned(
+ value,
+ "Not a valid value. `trampoline` takes a boolean literal",
+ ))
+ }
+ },
+ _ => {
+ return Err(syn::Error::new_spanned(name, "Not a valid argument name"));
+ }
+ }
+ }
+
+ Ok(args)
+ }
+}
+
#[proc_macro_attribute]
pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
let mut f = parse_macro_input!(input as ItemFn);
- if !args.is_empty() {
- return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
- .to_compile_error()
- .into();
- }
-
if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Exception) {
return error;
}
@@ -137,13 +203,35 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
let ident_s = ident.to_string();
let exn = match &*ident_s {
- "DefaultHandler" => Exception::DefaultHandler,
- "HardFault" => Exception::HardFault,
- "NonMaskableInt" => Exception::NonMaskableInt,
+ "DefaultHandler" => {
+ if !args.is_empty() {
+ return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
+ .to_compile_error()
+ .into();
+ }
+ Exception::DefaultHandler
+ }
+ "HardFault" => Exception::HardFault(parse_macro_input!(args)),
+ "NonMaskableInt" => {
+ if !args.is_empty() {
+ return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
+ .to_compile_error()
+ .into();
+ }
+ Exception::NonMaskableInt
+ }
// NOTE that at this point we don't check if the exception is available on the target (e.g.
// MemoryManagement is not available on Cortex-M0)
"MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall"
- | "DebugMonitor" | "PendSV" | "SysTick" => Exception::Other,
+ | "DebugMonitor" | "PendSV" | "SysTick" => {
+ if !args.is_empty() {
+ return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
+ .to_compile_error()
+ .into();
+ }
+
+ Exception::Other
+ }
_ => {
return parse::Error::new(ident.span(), "This is not a valid exception name")
.to_compile_error()
@@ -153,13 +241,9 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
if f.sig.unsafety.is_none() {
match exn {
- Exception::DefaultHandler | Exception::HardFault | Exception::NonMaskableInt => {
+ Exception::DefaultHandler | Exception::HardFault(_) | Exception::NonMaskableInt => {
// These are unsafe to define.
- let name = if exn == Exception::DefaultHandler {
- "`DefaultHandler`".to_string()
- } else {
- format!("`{:?}` handler", exn)
- };
+ let name = format!("{}", exn);
return parse::Error::new(ident.span(), format_args!("defining a {} is unsafe and requires an `unsafe fn` (see the cortex-m-rt docs)", name))
.to_compile_error()
.into();
@@ -232,17 +316,23 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
#f
)
}
- Exception::HardFault => {
+ Exception::HardFault(args) => {
let valid_signature = f.sig.constness.is_none()
&& f.vis == Visibility::Inherited
&& f.sig.abi.is_none()
- && f.sig.inputs.len() == 1
- && match &f.sig.inputs[0] {
- FnArg::Typed(arg) => match arg.ty.as_ref() {
- Type::Reference(r) => r.lifetime.is_none() && r.mutability.is_none(),
- _ => false,
- },
- _ => false,
+ && if args.trampoline {
+ f.sig.inputs.len() == 1
+ && match &f.sig.inputs[0] {
+ FnArg::Typed(arg) => match arg.ty.as_ref() {
+ Type::Reference(r) => {
+ r.lifetime.is_none() && r.mutability.is_none()
+ }
+ _ => false,
+ },
+ _ => false,
+ }
+ } else {
+ f.sig.inputs.is_empty()
}
&& f.sig.generics.params.is_empty()
&& f.sig.generics.where_clause.is_none()
@@ -255,7 +345,11 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
if !valid_signature {
return parse::Error::new(
fspan,
- "`HardFault` handler must have signature `unsafe fn(&ExceptionFrame) -> !`",
+ if args.trampoline {
+ "`HardFault` handler must have signature `unsafe fn(&ExceptionFrame) -> !`"
+ } else {
+ "`HardFault` handler must have signature `unsafe fn() -> !`"
+ },
)
.to_compile_error()
.into();
@@ -263,25 +357,63 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
f.sig.ident = Ident::new(&format!("__cortex_m_rt_{}", f.sig.ident), Span::call_site());
let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
- let ident = &f.sig.ident;
- let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
+ if args.trampoline {
+ let ident = &f.sig.ident;
- quote!(
- #(#cfgs)*
- #(#attrs)*
- #[doc(hidden)]
- #[export_name = "HardFault"]
- // Only emit link_section when building for embedded targets,
- // because some hosted platforms (used to check the build)
- // cannot handle the long link section names.
- #[cfg_attr(target_os = "none", link_section = ".HardFault.user")]
- pub unsafe extern "C" fn #tramp_ident(frame: &::cortex_m_rt::ExceptionFrame) {
- #ident(frame)
- }
+ let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
- #f
- )
+ quote!(
+ #(#cfgs)*
+ #(#attrs)*
+ #[doc(hidden)]
+ #[export_name = "_HardFault"]
+ unsafe extern "C" fn #tramp_ident(frame: &::cortex_m_rt::ExceptionFrame) {
+ #ident(frame)
+ }
+
+ #f
+
+ // HardFault exceptions are bounced through this trampoline which grabs the stack pointer at
+ // the time of the exception and passes it to the user's HardFault handler in r0.
+ // Depending on the stack mode in EXC_RETURN, fetches stack from either MSP or PSP.
+ core::arch::global_asm!(
+ ".cfi_sections .debug_frame
+ .section .HardFault.user, \"ax\"
+ .global HardFault
+ .type HardFault,%function
+ .thumb_func
+ .cfi_startproc
+ HardFault:",
+ "mov r0, lr
+ movs r1, #4
+ tst r0, r1
+ bne 0f
+ mrs r0, MSP
+ b _HardFault
+ 0:
+ mrs r0, PSP
+ b _HardFault",
+ ".cfi_endproc
+ .size HardFault, . - HardFault",
+ );
+ )
+ } else {
+ quote!(
+ #[doc(hidden)]
+ #[export_name = "_HardFault"]
+ unsafe extern "C" fn #tramp_ident() {
+ // This trampoline has no function except making the compiler diagnostics better.
+ }
+
+ #[export_name = "HardFault"]
+ // Only emit link_section when building for embedded targets,
+ // because some hosted platforms (used to check the build)
+ // cannot handle the long link section names.
+ #[cfg_attr(target_os = "none", link_section = ".HardFault.user")]
+ #f
+ )
+ }
}
Exception::NonMaskableInt | Exception::Other => {
let valid_signature = f.sig.constness.is_none()
@@ -634,7 +766,7 @@ fn check_attr_whitelist(attrs: &[Attribute], caller: WhiteListCaller) -> Result<
}
};
- return Err(parse::Error::new(attr.span(), &err_str)
+ return Err(parse::Error::new(attr.span(), err_str)
.to_compile_error()
.into());
}
diff --git a/cortex-m-rt/src/lib.rs b/cortex-m-rt/src/lib.rs
index a6d946c..920c989 100644
--- a/cortex-m-rt/src/lib.rs
+++ b/cortex-m-rt/src/lib.rs
@@ -225,12 +225,15 @@
//! - `DefaultHandler`. This is the default handler. If not overridden using `#[exception] fn
//! DefaultHandler(..` this will be an infinite loop.
//!
-//! - `HardFaultTrampoline`. This is the real hard fault handler. This function is simply a
-//! trampoline that jumps into the user defined hard fault handler named `HardFault`. The
-//! trampoline is required to set up the pointer to the stacked exception frame.
-//!
-//! - `HardFault`. This is the user defined hard fault handler. If not overridden using
-//! `#[exception] fn HardFault(..` it will default to an infinite loop.
+//! - `HardFault` and `_HardFault`. These function handle the hard fault handling and what they
+//! do depends on whether the hard fault is overridden and whether the trampoline is enabled (which it is by default).
+//! - No override: Both are the same function. The function is an infinite loop defined in the cortex-m-rt crate.
+//! - Trampoline enabled: `HardFault` is the real hard fault handler defined in assembly. This function is simply a
+//! trampoline that jumps into the rust defined `_HardFault` function. This second function jumps to the user-defined
+//! handler with the exception frame as parameter. This second jump is usually optimised away with inlining.
+//! - Trampoline disabled: `HardFault` is the user defined function. This means the user function is called directly
+//! from the vector table. `_HardFault` still exists, but is an empty function that is purely there for compiler
+//! diagnostics.
//!
//! - `__STACK_START`. This is the first entry in the `.vector_table` section. This symbol contains
//! the initial value of the stack pointer; this is where the stack will be located -- the stack
@@ -455,31 +458,6 @@ extern crate cortex_m_rt_macros as macros;
use core::arch::global_asm;
use core::fmt;
-// HardFault exceptions are bounced through this trampoline which grabs the stack pointer at
-// the time of the exception and passes it to the user's HardFault handler in r0.
-// Depending on the stack mode in EXC_RETURN, fetches stack from either MSP or PSP.
-#[cfg(cortex_m)]
-global_asm!(
- ".cfi_sections .debug_frame
- .section .HardFaultTrampoline, \"ax\"
- .global HardFaultTrampline
- .type HardFaultTrampline,%function
- .thumb_func
- .cfi_startproc
- HardFaultTrampoline:",
- "mov r0, lr
- movs r1, #4
- tst r0, r1
- bne 0f
- mrs r0, MSP
- b HardFault
- 0:
- mrs r0, PSP
- b HardFault",
- ".cfi_endproc
- .size HardFaultTrampoline, . - HardFaultTrampoline",
-);
-
/// Parse cfg attributes inside a global_asm call.
#[cfg(cortex_m)]
macro_rules! cfg_global_asm {
@@ -749,9 +727,18 @@ pub use macros::entry;
///
/// # Usage
///
-/// `#[exception] unsafe fn HardFault(..` sets the hard fault handler. The handler must have
-/// signature `unsafe fn(&ExceptionFrame) -> !`. This handler is not allowed to return as that can
-/// cause undefined behavior.
+/// ## HardFault handler
+///
+/// `#[exception(trampoline = true)] unsafe fn HardFault(..` sets the hard fault handler.
+/// If the trampoline parameter is set to true, the handler must have signature `unsafe fn(&ExceptionFrame) -> !`.
+/// If set to false, the handler must have signature `unsafe fn() -> !`.
+///
+/// This handler is not allowed to return as that can cause undefined behavior.
+///
+/// To maintain backwards compatibility the attribute can be used without trampoline parameter (`#[exception]`),
+/// which sets the trampoline to true.
+///
+/// ## Default handler
///
/// `#[exception] unsafe fn DefaultHandler(..` sets the *default* handler. All exceptions which have
/// not been assigned a handler will be serviced by this handler. This handler must have signature
@@ -759,6 +746,8 @@ pub use macros::entry;
/// number when the handler is servicing a core exception; `irqn` will be a positive number when the
/// handler is servicing a device specific exception (interrupt).
///
+/// ## Other handlers
+///
/// `#[exception] fn Name(..` overrides the default handler for the exception with the given `Name`.
/// These handlers must have signature `[unsafe] fn() [-> !]`. When overriding these other exception
/// it's possible to add state to them by declaring `static mut` variables at the beginning of the
@@ -1058,11 +1047,10 @@ pub fn heap_start() -> *mut u32 {
#[no_mangle]
pub static __RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset;
-#[allow(unused_variables)]
#[doc(hidden)]
#[cfg_attr(cortex_m, link_section = ".HardFault.default")]
#[no_mangle]
-pub unsafe extern "C" fn HardFault_(ef: &ExceptionFrame) -> ! {
+pub unsafe extern "C" fn HardFault_() -> ! {
#[allow(clippy::empty_loop)]
loop {}
}
@@ -1115,7 +1103,7 @@ extern "C" {
fn NonMaskableInt();
- fn HardFaultTrampoline();
+ fn HardFault();
#[cfg(not(armv6m))]
fn MemoryManagement();
@@ -1154,9 +1142,7 @@ pub static __EXCEPTIONS: [Vector; 14] = [
handler: NonMaskableInt,
},
// Exception 3: Hard Fault Interrupt.
- Vector {
- handler: HardFaultTrampoline,
- },
+ Vector { handler: HardFault },
// Exception 4: Memory Management Interrupt [not on Cortex-M0 variants].
#[cfg(not(armv6m))]
Vector {
diff --git a/cortex-m-rt/tests/README.md b/cortex-m-rt/tests/README.md
new file mode 100644
index 0000000..1d879fc
--- /dev/null
+++ b/cortex-m-rt/tests/README.md
@@ -0,0 +1,7 @@
+# How to run
+
+To run the compile tests, use the following command on the root of the project:
+
+```
+cargo test --package cortex-m-rt --test compiletest --features device
+```
diff --git a/cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-2.rs b/cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-2.rs
new file mode 100644
index 0000000..d20d832
--- /dev/null
+++ b/cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-2.rs
@@ -0,0 +1,18 @@
+#![no_main]
+#![no_std]
+
+extern crate cortex_m_rt;
+extern crate panic_halt;
+
+use cortex_m_rt::{entry, exception, ExceptionFrame};
+
+#[entry]
+fn foo() -> ! {
+ loop {}
+}
+
+#[exception(trampoline = true)]
+unsafe fn HardFault() -> ! {
+ //~^ ERROR `HardFault` handler must have signature `unsafe fn(&ExceptionFrame) -> !`
+ loop {}
+}
diff --git a/cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-3.rs b/cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-3.rs
new file mode 100644
index 0000000..62c8439
--- /dev/null
+++ b/cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-3.rs
@@ -0,0 +1,18 @@
+#![no_main]
+#![no_std]
+
+extern crate cortex_m_rt;
+extern crate panic_halt;
+
+use cortex_m_rt::{entry, exception, ExceptionFrame};
+
+#[entry]
+fn foo() -> ! {
+ loop {}
+}
+
+#[exception(trampoline = false)]
+unsafe fn HardFault(_ef: &ExceptionFrame) -> ! {
+ //~^ ERROR `HardFault` handler must have signature `unsafe fn() -> !`
+ loop {}
+}
diff --git a/cortex-m-rt/tests/compile-fail/hard-fault-twice-mixed-trampoline.rs b/cortex-m-rt/tests/compile-fail/hard-fault-twice-mixed-trampoline.rs
new file mode 100644
index 0000000..3610170
--- /dev/null
+++ b/cortex-m-rt/tests/compile-fail/hard-fault-twice-mixed-trampoline.rs
@@ -0,0 +1,26 @@
+#![no_main]
+#![no_std]
+
+extern crate cortex_m_rt;
+extern crate panic_halt;
+
+use cortex_m_rt::{entry, exception, ExceptionFrame};
+
+#[entry]
+fn foo() -> ! {
+ loop {}
+}
+
+#[exception(trampoline = false)]
+unsafe fn HardFault() -> ! {
+ loop {}
+}
+
+pub mod reachable {
+ use cortex_m_rt::{exception, ExceptionFrame};
+
+ #[exception] //~ ERROR symbol `_HardFault` is already defined
+ unsafe fn HardFault(_ef: &ExceptionFrame) -> ! {
+ loop {}
+ }
+}
diff --git a/cortex-m-rt/tests/compile-fail/hard-fault-twice.rs b/cortex-m-rt/tests/compile-fail/hard-fault-twice.rs
index 03b79a5..af80127 100644
--- a/cortex-m-rt/tests/compile-fail/hard-fault-twice.rs
+++ b/cortex-m-rt/tests/compile-fail/hard-fault-twice.rs
@@ -19,7 +19,7 @@ unsafe fn HardFault(_ef: &ExceptionFrame) -> ! {
pub mod reachable {
use cortex_m_rt::{exception, ExceptionFrame};
- #[exception] //~ ERROR symbol `HardFault` is already defined
+ #[exception] //~ ERROR symbol `_HardFault` is already defined
unsafe fn HardFault(_ef: &ExceptionFrame) -> ! {
loop {}
}