Skip to main content

cortex_ar_rt_macros/
lib.rs

1//! Macros for the cortex-a-rt and cortex-r-rt libraries
2//!
3//! Provides `#[entry]`, `#[exception(...)]` and `#[irq]` attribute macros.
4//!
5//! Do not use this crate directly.
6//!
7//! Based on <https://github.com/rust-embedded/cortex-m/tree/c-m-rt-v0.7.5/cortex-m-rt/macros>.
8
9extern crate proc_macro;
10
11use proc_macro::{TokenStream, TokenTree};
12use proc_macro2::Span;
13use quote::quote;
14use syn::{
15    parse, parse_macro_input, spanned::Spanned, AttrStyle, Attribute, Ident, ItemFn, ReturnType,
16    Type, Visibility,
17};
18
19/// Creates an `unsafe` program entry point (i.e. a `kmain` function).
20///
21/// It's `unsafe` because you are not supposed to call it - it should only be
22/// called from the start-up code once initialisation is complete.
23///
24/// When placed on a function like:
25///
26/// ```rust ignore
27/// #[entry]
28/// fn foo() -> ! {
29///     panic!("On no")
30/// }
31/// ```
32///
33/// You get something like:
34///
35/// ```rust
36/// #[doc(hidden)]
37/// #[export_name = "kmain"]
38/// pub unsafe extern "C" fn __cortex_ar_rt_kmain() -> ! {
39///     foo()
40/// }
41///
42/// fn foo() -> ! {
43///     panic!("On no")
44/// }
45/// ```
46///
47/// The symbol `kmain` is what the assembly code in both the cortex-r-rt and
48/// cortex-a-rt start-up code will jump to, and the `extern "C"` makes it sound
49/// to call from assembly.
50#[proc_macro_attribute]
51pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
52    let f = parse_macro_input!(input as ItemFn);
53
54    // check the function signature.
55    //
56    // it should be `fn foo() -> !` or `unsafe fn foo() -> !`
57    let valid_signature = f.sig.constness.is_none()
58        && f.vis == Visibility::Inherited
59        && f.sig.abi.is_none()
60        && f.sig.inputs.is_empty()
61        && f.sig.generics.params.is_empty()
62        && f.sig.generics.where_clause.is_none()
63        && f.sig.variadic.is_none()
64        && match f.sig.output {
65            ReturnType::Default => false,
66            ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
67        };
68
69    if !valid_signature {
70        return parse::Error::new(
71            f.span(),
72            "`#[entry]` function must have signature `[unsafe] fn() -> !`",
73        )
74        .to_compile_error()
75        .into();
76    }
77
78    if !args.is_empty() {
79        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
80            .to_compile_error()
81            .into();
82    }
83
84    let tramp_ident = Ident::new("__cortex_ar_rt_kmain", Span::call_site());
85    let ident = &f.sig.ident;
86
87    if let Err(error) = check_attr_whitelist(&f.attrs, Kind::Entry) {
88        return error;
89    }
90
91    let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
92
93    quote!(
94        #(#cfgs)*
95        #(#attrs)*
96        #[doc(hidden)]
97        #[export_name = "kmain"]
98        pub unsafe extern "C" fn #tramp_ident() -> ! {
99            #ident()
100        }
101
102        #f
103    )
104    .into()
105}
106
107/// The set of exceptions we can handle.
108#[derive(Debug, PartialEq)]
109enum Exception {
110    Undefined,
111    SupervisorCall,
112    PrefetchAbort,
113    DataAbort,
114    Irq,
115}
116
117impl std::fmt::Display for Exception {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        match self {
120            Exception::Undefined => write!(f, "Undefined"),
121            Exception::SupervisorCall => write!(f, "SupervisorCall"),
122            Exception::PrefetchAbort => write!(f, "PrefetchAbort"),
123            Exception::DataAbort => write!(f, "DataAbort"),
124            Exception::Irq => write!(f, "Irq"),
125        }
126    }
127}
128
129/// Creates an `unsafe` exception handler.
130///
131/// It's `unsafe` because you are not supposed to call it - it should only be
132/// called from assembly routines registered in the interrupt vector table.
133///
134/// When placed on a function like:
135///
136/// ```rust ignore
137/// #[exception(Undefined)]
138/// fn foo(addr: usize) -> ! {
139///     panic!("On no")
140/// }
141/// ```
142///
143/// You get something like:
144///
145/// ```rust
146/// #[doc(hidden)]
147/// #[export_name = "_undefined_handler"]
148/// pub unsafe extern "C" fn __cortex_ar_rt_undefined_handler(addr: usize) -> ! {
149///     foo(addr)
150/// }
151///
152/// fn foo(addr: usize) -> ! {
153///     panic!("On no")
154/// }
155/// ```
156///
157/// The supported arguments are:
158///
159/// * Undefined (creates `_undefined_handler`)
160/// * SupervisorCall (creates `_svc_handler`)
161/// * PrefetchAbort (creates `_prefetch_abort_handler`)
162/// * DataAbort (creates `_data_abort_handler`)
163/// * Irq (creates `_irq_handler`) - although people should prefer `#[irq]`.
164#[proc_macro_attribute]
165pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
166    handle_exception_interrupt(args, input, Kind::Exception)
167}
168
169/// Creates an `unsafe` interrupt handler.
170///
171/// It's `unsafe` because you are not supposed to call it - it should only be
172/// called from assembly routines registered in the interrupt vector table.
173///
174/// When placed on a function like:
175///
176/// ```rust ignore
177/// #[irq]
178/// fn foo(addr: usize) -> ! {
179///     panic!("On no")
180/// }
181/// ```
182///
183/// You get something like:
184///
185/// ```rust
186/// #[doc(hidden)]
187/// #[export_name = "_irq_handler"]
188/// pub unsafe extern "C" fn __cortex_ar_rt_irq_handler(addr: usize) -> ! {
189///     foo(addr)
190/// }
191///
192/// fn foo(addr: usize) -> ! {
193///     panic!("On no")
194/// }
195/// ```
196///
197/// This is preferred over `#[exception(Irq)` because most people
198/// probably won't consider interrupts to be a form of exception.
199#[proc_macro_attribute]
200pub fn irq(args: TokenStream, input: TokenStream) -> TokenStream {
201    handle_exception_interrupt(args, input, Kind::Interrupt)
202}
203
204/// Note if we got `#[entry]`, `#[exception(...)]` or `#[irq]`
205#[derive(PartialEq, Eq, Clone, Copy, Debug)]
206enum Kind {
207    /// Corresponds to `#[entry]`
208    Entry,
209    /// Corresponds to `#[exception(...)]`
210    Exception,
211    /// Corresponds to `#[irq]`
212    Interrupt,
213}
214
215/// A common routine for handling exception or interrupt functions
216fn handle_exception_interrupt(args: TokenStream, input: TokenStream, kind: Kind) -> TokenStream {
217    let f = parse_macro_input!(input as ItemFn);
218
219    if let Err(error) = check_attr_whitelist(&f.attrs, kind) {
220        return error;
221    }
222
223    let returns_never = match f.sig.output {
224        ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
225        _ => false,
226    };
227
228    let exception = match kind {
229        Kind::Entry => {
230            panic!("Don't handle #[entry] with `handle_exception_interrupt`!");
231        }
232        Kind::Exception => {
233            let mut args_iter = args.into_iter();
234            let Some(TokenTree::Ident(exception_name)) = args_iter.next() else {
235                return parse::Error::new(
236                    Span::call_site(),
237                    "This attribute requires the name of the exception as the first argument",
238                )
239                .to_compile_error()
240                .into();
241            };
242            if args_iter.next().is_some() {
243                return parse::Error::new(
244                    Span::call_site(),
245                    "This attribute accepts only one argument",
246                )
247                .to_compile_error()
248                .into();
249            }
250            match exception_name.to_string().as_str() {
251                "Undefined" => {
252                    if !returns_never && f.sig.unsafety.is_none() {
253                        return parse::Error::new(
254                            exception_name.span().into(),
255                            "Undefined handlers that don't return ! must be unsafe",
256                        )
257                        .to_compile_error()
258                        .into();
259                    }
260                    Exception::Undefined
261                }
262                "SupervisorCall" => Exception::SupervisorCall,
263                "PrefetchAbort" => {
264                    if !returns_never && f.sig.unsafety.is_none() {
265                        return parse::Error::new(
266                            exception_name.span().into(),
267                            "PrefetchAbort handlers that don't return ! must be unsafe",
268                        )
269                        .to_compile_error()
270                        .into();
271                    }
272                    Exception::PrefetchAbort
273                }
274                "DataAbort" => {
275                    if !returns_never && f.sig.unsafety.is_none() {
276                        return parse::Error::new(
277                            exception_name.span().into(),
278                            "DataAbort handlers that don't return ! must be unsafe",
279                        )
280                        .to_compile_error()
281                        .into();
282                    }
283                    Exception::DataAbort
284                }
285                "Irq" => Exception::Irq,
286                _ => {
287                    return parse::Error::new(
288                        exception_name.span().into(),
289                        "This is not a valid exception name",
290                    )
291                    .to_compile_error()
292                    .into();
293                }
294            }
295        }
296        Kind::Interrupt => Exception::Irq,
297    };
298
299    let ident = &f.sig.ident;
300    let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
301
302    let handler = match exception {
303        // extern "C" fn _undefined_handler(addr: usize) -> !;
304        // unsafe extern "C" fn _undefined_handler(addr: usize) -> usize;
305        Exception::Undefined => {
306            let tramp_ident = Ident::new("__cortex_ar_rt_undefined_handler", Span::call_site());
307            if returns_never {
308                quote!(
309                    #(#cfgs)*
310                    #(#attrs)*
311                    #[doc(hidden)]
312                    #[export_name = "_undefined_handler"]
313                    pub unsafe extern "C" fn #tramp_ident(addr: usize) -> ! {
314                        #ident(addr)
315                    }
316
317                    #f
318                )
319            } else {
320                quote!(
321                    #(#cfgs)*
322                    #(#attrs)*
323                    #[doc(hidden)]
324                    #[export_name = "_undefined_handler"]
325                    pub unsafe extern "C" fn #tramp_ident(addr: usize) -> usize {
326                        unsafe {
327                            #ident(addr)
328                        }
329                    }
330
331                    #f
332                )
333            }
334        }
335        // extern "C" fn _prefetch_abort_handler(addr: usize) -> !;
336        // unsafe extern "C" fn _prefetch_abort_handler(addr: usize) -> usize;
337        Exception::PrefetchAbort => {
338            let tramp_ident =
339                Ident::new("__cortex_ar_rt_prefetch_abort_handler", Span::call_site());
340            if returns_never {
341                quote!(
342                    #(#cfgs)*
343                    #(#attrs)*
344                    #[doc(hidden)]
345                    #[export_name = "_prefetch_abort_handler"]
346                    pub unsafe extern "C" fn #tramp_ident(addr: usize) -> ! {
347                        #ident(addr)
348                    }
349
350                    #f
351                )
352            } else {
353                quote!(
354                    #(#cfgs)*
355                    #(#attrs)*
356                    #[doc(hidden)]
357                    #[export_name = "_prefetch_abort_handler"]
358                    pub unsafe extern "C" fn #tramp_ident(addr: usize) -> usize {
359                        unsafe {
360                            #ident(addr)
361                        }
362                    }
363
364                    #f
365                )
366            }
367        }
368        // extern "C" fn _data_abort_handler(addr: usize) -> !;
369        // unsafe extern "C" fn _data_abort_handler(addr: usize) -> usize;
370        Exception::DataAbort => {
371            let tramp_ident = Ident::new("__cortex_ar_rt_data_abort_handler", Span::call_site());
372            if returns_never {
373                quote!(
374                    #(#cfgs)*
375                    #(#attrs)*
376                    #[doc(hidden)]
377                    #[export_name = "_data_abort_handler"]
378                    pub unsafe extern "C" fn #tramp_ident(addr: usize) -> ! {
379                        #ident(addr)
380                    }
381
382                    #f
383                )
384            } else {
385                quote!(
386                    #(#cfgs)*
387                    #(#attrs)*
388                    #[doc(hidden)]
389                    #[export_name = "_data_abort_handler"]
390                    pub unsafe extern "C" fn #tramp_ident(addr: usize) -> usize {
391                        unsafe {
392                            #ident(addr)
393                        }
394                    }
395
396                    #f
397                )
398            }
399        }
400        // extern "C" fn _svc_handler(addr: usize);
401        Exception::SupervisorCall => {
402            let tramp_ident = Ident::new("__cortex_ar_rt_svc_handler", Span::call_site());
403            quote!(
404                #(#cfgs)*
405                #(#attrs)*
406                #[doc(hidden)]
407                #[export_name = "_svc_handler"]
408                pub unsafe extern "C" fn #tramp_ident(arg: u32) {
409                    #ident(arg)
410                }
411
412                #f
413            )
414        }
415        // extern "C" fn _irq_handler(addr: usize);
416        Exception::Irq => {
417            let tramp_ident = Ident::new("__cortex_ar_rt_irq_handler", Span::call_site());
418            quote!(
419                #(#cfgs)*
420                #(#attrs)*
421                #[doc(hidden)]
422                #[export_name = "_irq_handler"]
423                pub unsafe extern "C" fn #tramp_ident() {
424                    #ident()
425                }
426
427                #f
428            )
429        }
430    };
431
432    quote!(
433        #handler
434    )
435    .into()
436}
437
438/// Given a list of attributes, split them into `cfg` and non-`cfg`.
439///
440/// Returns `(cfgs, non_cfgs)`.
441fn extract_cfgs(attrs: Vec<Attribute>) -> (Vec<Attribute>, Vec<Attribute>) {
442    let mut cfgs = vec![];
443    let mut not_cfgs = vec![];
444
445    for attr in attrs {
446        if eq(&attr, "cfg") {
447            cfgs.push(attr);
448        } else {
449            not_cfgs.push(attr);
450        }
451    }
452
453    (cfgs, not_cfgs)
454}
455
456/// Check whether any disallowed attributes have been applied to our entry/exception function.
457fn check_attr_whitelist(attrs: &[Attribute], caller: Kind) -> Result<(), TokenStream> {
458    let whitelist = &[
459        "doc",
460        "link_section",
461        "cfg",
462        "allow",
463        "warn",
464        "deny",
465        "forbid",
466        "cold",
467        "naked",
468        "expect",
469    ];
470
471    'o: for attr in attrs {
472        for val in whitelist {
473            if eq(attr, val) {
474                continue 'o;
475            }
476        }
477
478        let err_str = match caller {
479            Kind::Entry => {
480                "this attribute is not allowed on a cortex-r-rt/cortex-a-rt entry point"
481            }
482            Kind::Exception => {
483                "this attribute is not allowed on an exception handler controlled by cortex-r-rt/cortex-a-rt"
484            }
485            Kind::Interrupt => {
486                "this attribute is not allowed on an interrupt handler controlled by cortex-r-rt/cortex-a-rt"
487            }
488        };
489
490        return Err(parse::Error::new(attr.span(), err_str)
491            .to_compile_error()
492            .into());
493    }
494
495    Ok(())
496}
497
498/// Returns `true` if `attr.path` matches `name`
499fn eq(attr: &Attribute, name: &str) -> bool {
500    attr.style == AttrStyle::Outer && attr.path().is_ident(name)
501}