Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hardfault trampoline is now optional #476

Merged
1 change: 1 addition & 0 deletions cortex-m-rt/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
224 changes: 178 additions & 46 deletions cortex-m-rt/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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;
}
Expand All @@ -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()
Expand All @@ -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();
Expand Down Expand Up @@ -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()
Expand All @@ -255,33 +345,75 @@ 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();
}

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()
Expand Down Expand Up @@ -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());
}
Expand Down
Loading
Loading