aarch32_rt_macros/
lib.rs

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