rtic_macros/codegen/bindings/
cortex.rs

1use crate::{
2    analyze::Analysis as CodegenAnalysis,
3    codegen::util,
4    syntax::{analyze::Analysis as SyntaxAnalysis, ast::App},
5};
6use proc_macro2::{Span, TokenStream as TokenStream2};
7use quote::quote;
8use std::collections::HashSet;
9use syn::{parse, Attribute, Ident};
10
11#[cfg(feature = "cortex-m-basepri")]
12pub use basepri::*;
13#[cfg(feature = "cortex-m-source-masking")]
14pub use source_masking::*;
15
16/// Whether `name` is an exception with configurable priority
17fn is_exception(name: &Ident) -> bool {
18    let s = name.to_string();
19
20    matches!(
21        &*s,
22        "MemoryManagement"
23            | "BusFault"
24            | "UsageFault"
25            | "SecureFault"
26            | "SVCall"
27            | "DebugMonitor"
28            | "PendSV"
29            | "SysTick"
30    )
31}
32
33pub fn interrupt_ident() -> Ident {
34    let span = Span::call_site();
35    Ident::new("interrupt", span)
36}
37
38pub fn interrupt_mod(app: &App) -> TokenStream2 {
39    let device = &app.args.device;
40    let interrupt = interrupt_ident();
41    quote!(#device::#interrupt)
42}
43
44pub fn check_stack_overflow_before_init(
45    _app: &App,
46    _analysis: &CodegenAnalysis,
47) -> Vec<TokenStream2> {
48    vec![quote!(
49        // Check for stack overflow using symbols from `cortex-m-rt`.
50        extern "C" {
51            pub static _stack_start: u32;
52            pub static __ebss: u32;
53        }
54
55        let stack_start = &_stack_start as *const _ as u32;
56        let ebss = &__ebss as *const _ as u32;
57
58        if stack_start > ebss {
59            // No flip-link usage, check the MSP for overflow.
60            if rtic::export::msp::read() <= ebss {
61                panic!("Stack overflow after allocating executors");
62            }
63        }
64    )]
65}
66
67#[cfg(feature = "cortex-m-source-masking")]
68mod source_masking {
69    use super::*;
70    use std::collections::HashMap;
71
72    /// Generates a `Mutex` implementation
73    #[allow(clippy::too_many_arguments)]
74    pub fn impl_mutex(
75        app: &App,
76        analysis: &CodegenAnalysis,
77        cfgs: &[Attribute],
78        resources_prefix: bool,
79        name: &Ident,
80        ty: &TokenStream2,
81        ceiling: u8,
82        ptr: &TokenStream2,
83    ) -> TokenStream2 {
84        let path = if resources_prefix {
85            quote!(shared_resources::#name)
86        } else {
87            quote!(#name)
88        };
89
90        // Computing mapping of used interrupts to masks
91        let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id));
92
93        let mut prio_to_masks = HashMap::new();
94        let device = &app.args.device;
95        // let mut uses_exceptions_with_resources = false;
96
97        let mut mask_ids = Vec::new();
98
99        for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().flat_map(|task| {
100            if !is_exception(&task.args.binds) {
101                Some((&task.args.priority, &task.args.binds))
102            } else {
103                None
104            }
105        })) {
106            let v: &mut Vec<_> = prio_to_masks.entry(priority - 1).or_default();
107            v.push(quote!(#device::Interrupt::#name as u32));
108            mask_ids.push(quote!(#device::Interrupt::#name as u32));
109        }
110
111        // Call rtic::export::create_mask([Mask; N]), where the array is the list of shifts
112
113        let mut mask_arr = Vec::new();
114        // NOTE: 0..3 assumes max 4 priority levels according to M0, M23 spec
115        for i in 0..3 {
116            let v = if let Some(v) = prio_to_masks.get(&i) {
117                v.clone()
118            } else {
119                Vec::new()
120            };
121
122            mask_arr.push(quote!(
123                rtic::export::create_mask([#(#v),*])
124            ));
125        }
126
127        quote!(
128            #(#cfgs)*
129            impl<'a> rtic::Mutex for #path<'a> {
130                type T = #ty;
131
132                #[inline(always)]
133                fn lock<RTIC_INTERNAL_R>(&mut self, f: impl FnOnce(&mut #ty) -> RTIC_INTERNAL_R) -> RTIC_INTERNAL_R {
134                    /// Priority ceiling
135                    const CEILING: u8 = #ceiling;
136                    const N_CHUNKS: usize = rtic::export::compute_mask_chunks([#(#mask_ids),*]);
137                    const MASKS: [rtic::export::Mask<N_CHUNKS>; 3] = [#(#mask_arr),*];
138
139                    unsafe {
140                        rtic::export::lock(
141                            #ptr,
142                            CEILING,
143                            &MASKS,
144                            f,
145                        )
146                    }
147                }
148            }
149        )
150    }
151
152    pub fn extra_assertions(_: &App, _: &SyntaxAnalysis) -> Vec<TokenStream2> {
153        vec![]
154    }
155}
156
157#[cfg(feature = "cortex-m-basepri")]
158mod basepri {
159    use super::*;
160
161    /// Generates a `Mutex` implementation
162    #[allow(clippy::too_many_arguments)]
163    pub fn impl_mutex(
164        app: &App,
165        _analysis: &CodegenAnalysis,
166        cfgs: &[Attribute],
167        resources_prefix: bool,
168        name: &Ident,
169        ty: &TokenStream2,
170        ceiling: u8,
171        ptr: &TokenStream2,
172    ) -> TokenStream2 {
173        let path = if resources_prefix {
174            quote!(shared_resources::#name)
175        } else {
176            quote!(#name)
177        };
178
179        let device = &app.args.device;
180        quote!(
181            #(#cfgs)*
182            impl<'a> rtic::Mutex for #path<'a> {
183                type T = #ty;
184
185                #[inline(always)]
186                fn lock<RTIC_INTERNAL_R>(&mut self, f: impl FnOnce(&mut #ty) -> RTIC_INTERNAL_R) -> RTIC_INTERNAL_R {
187                    /// Priority ceiling
188                    const CEILING: u8 = #ceiling;
189
190                    unsafe {
191                        rtic::export::lock(
192                            #ptr,
193                            CEILING,
194                            #device::NVIC_PRIO_BITS,
195                            f,
196                        )
197                    }
198                }
199            }
200        )
201    }
202
203    pub fn extra_assertions(_: &App, _: &SyntaxAnalysis) -> Vec<TokenStream2> {
204        vec![]
205    }
206}
207
208pub fn pre_init_preprocessing(_app: &mut App, _analysis: &SyntaxAnalysis) -> parse::Result<()> {
209    Ok(())
210}
211
212pub fn pre_init_checks(app: &App, _: &SyntaxAnalysis) -> Vec<TokenStream2> {
213    let mut stmts = vec![];
214
215    // check that all dispatchers exists in the `Interrupt` enumeration regardless of whether
216    // they are used or not
217    let interrupt = interrupt_ident();
218    let rt_err = util::rt_err_ident();
219
220    for name in app.args.dispatchers.keys() {
221        stmts.push(quote!(let _ = #rt_err::#interrupt::#name;));
222    }
223
224    stmts
225}
226
227pub fn pre_init_enable_interrupts(app: &App, analysis: &CodegenAnalysis) -> Vec<TokenStream2> {
228    let mut stmts = vec![];
229
230    let interrupt = interrupt_ident();
231    let rt_err = util::rt_err_ident();
232    let device = &app.args.device;
233    let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS);
234    let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id));
235
236    // Unmask interrupts and set their priorities
237    for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().filter_map(|task| {
238        if is_exception(&task.args.binds) {
239            // We do exceptions in another pass
240            None
241        } else {
242            Some((&task.args.priority, &task.args.binds))
243        }
244    })) {
245        let es = format!(
246            "Maximum priority used by interrupt vector '{name}' is more than supported by hardware"
247        );
248        // Compile time assert that this priority is supported by the device
249        stmts.push(quote!(
250            const _: () =  if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); };
251        ));
252
253        stmts.push(quote!(
254            core.NVIC.set_priority(
255                #rt_err::#interrupt::#name,
256                rtic::export::cortex_logical2hw(#priority, #nvic_prio_bits),
257            );
258        ));
259
260        // NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended
261        // interrupt is implementation defined
262        stmts.push(quote!(rtic::export::NVIC::unmask(#rt_err::#interrupt::#name);));
263    }
264
265    // Set exception priorities
266    for (name, priority) in app.hardware_tasks.values().filter_map(|task| {
267        if is_exception(&task.args.binds) {
268            Some((&task.args.binds, task.args.priority))
269        } else {
270            None
271        }
272    }) {
273        let es = format!(
274            "Maximum priority used by interrupt vector '{name}' is more than supported by hardware"
275        );
276        // Compile time assert that this priority is supported by the device
277        stmts.push(quote!(
278            const _: () =  if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); };
279        ));
280
281        stmts.push(quote!(core.SCB.set_priority(
282            rtic::export::SystemHandler::#name,
283            rtic::export::cortex_logical2hw(#priority, #nvic_prio_bits),
284        );));
285    }
286
287    stmts
288}
289
290pub fn architecture_specific_analysis(app: &App, _: &SyntaxAnalysis) -> parse::Result<()> {
291    // Check that external (device-specific) interrupts are not named after known (Cortex-M)
292    // exceptions
293    for name in app.args.dispatchers.keys() {
294        let name_s = name.to_string();
295
296        match &*name_s {
297            "NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault"
298            | "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => {
299                return Err(parse::Error::new(
300                    name.span(),
301                    "Cortex-M exceptions can't be used as `extern` interrupts",
302                ));
303            }
304
305            _ => {}
306        }
307    }
308
309    // Check that there are enough external interrupts to dispatch the software tasks and the timer
310    // queue handler
311    let mut first = None;
312    let priorities = app
313        .software_tasks
314        .iter()
315        .map(|(name, task)| {
316            first = Some(name);
317            task.args.priority
318        })
319        .filter(|prio| *prio > 0)
320        .collect::<HashSet<_>>();
321
322    let need = priorities.len();
323    let given = app.args.dispatchers.len();
324    if need > given {
325        let s = {
326            format!(
327                "not enough interrupts to dispatch \
328                    all software tasks (need: {need}; given: {given})"
329            )
330        };
331
332        // If not enough tasks and first still is None, may cause
333        // "custom attribute panicked" due to unwrap on None
334        return Err(parse::Error::new(first.unwrap().span(), s));
335    }
336
337    // Check that all exceptions are valid; only exceptions with configurable priorities are
338    // accepted
339    for (name, task) in &app.hardware_tasks {
340        let name_s = task.args.binds.to_string();
341        match &*name_s {
342            "NonMaskableInt" | "HardFault" => {
343                return Err(parse::Error::new(
344                    name.span(),
345                    "only exceptions with configurable priority can be used as hardware tasks",
346                ));
347            }
348
349            _ => {}
350        }
351    }
352
353    Ok(())
354}
355
356pub fn interrupt_entry(_app: &App, _analysis: &CodegenAnalysis) -> Vec<TokenStream2> {
357    vec![]
358}
359
360pub fn interrupt_exit(_app: &App, _analysis: &CodegenAnalysis) -> Vec<TokenStream2> {
361    vec![]
362}
363
364pub fn async_entry(
365    _app: &App,
366    _analysis: &CodegenAnalysis,
367    _dispatcher_name: Ident,
368) -> Vec<TokenStream2> {
369    vec![]
370}
371
372pub fn async_prio_limit(app: &App, analysis: &CodegenAnalysis) -> Vec<TokenStream2> {
373    let max = if let Some(max) = analysis.max_async_prio {
374        quote!(#max)
375    } else {
376        // No limit
377        let device = &app.args.device;
378        quote!(1 << #device::NVIC_PRIO_BITS)
379    };
380
381    vec![quote!(
382        /// Holds the maximum priority level for use by async HAL drivers.
383        #[no_mangle]
384        static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8 = #max;
385    )]
386}
387pub fn handler_config(
388    _app: &App,
389    _analysis: &CodegenAnalysis,
390    _dispatcher_name: Ident,
391) -> Vec<TokenStream2> {
392    vec![]
393}
394
395pub fn extra_modules(_app: &App, _analysis: &SyntaxAnalysis) -> Vec<TokenStream2> {
396    vec![]
397}