svd2rust/generate/
interrupt.rs

1use std::collections::HashMap;
2use std::fmt::Write;
3
4use crate::config::RustEdition;
5use crate::svd::Peripheral;
6use proc_macro2::{Span, TokenStream};
7use quote::quote;
8
9use crate::util::{self, ident};
10use crate::{Config, Target};
11use anyhow::Result;
12
13/// Generates code for `src/interrupt.rs`
14pub fn render(
15    target: Target,
16    peripherals: &[Peripheral],
17    device_x: &mut String,
18    config: &Config,
19) -> Result<TokenStream> {
20    let interrupts = peripherals
21        .iter()
22        .flat_map(|p| {
23            p.interrupt.iter().map(move |i| {
24                (i, p.group_name.clone(), {
25                    match p {
26                        Peripheral::Single(info) => info.name.clone(),
27                        Peripheral::Array(info, dim_element) => {
28                            svd_rs::array::names(info, dim_element).next().unwrap()
29                        }
30                    }
31                })
32            })
33        })
34        .map(|i| (i.0.value, (i.0, i.1, i.2)))
35        .collect::<HashMap<_, _>>();
36
37    let mut interrupts = interrupts.into_values().collect::<Vec<_>>();
38    interrupts.sort_by_key(|i| i.0.value);
39
40    let mut root = TokenStream::new();
41    let mut from_arms = TokenStream::new();
42    let mut elements = TokenStream::new();
43    let mut names = vec![];
44    let mut names_cfg_attr = vec![];
45    let mut variants = TokenStream::new();
46
47    // Current position in the vector table
48    let mut pos = 0;
49    let mut mod_items = TokenStream::new();
50    let span = Span::call_site();
51    let feature_format = config.ident_formats.get("peripheral_feature").unwrap();
52    for interrupt in &interrupts {
53        while pos < interrupt.0.value {
54            elements.extend(quote!(Vector { _reserved: 0 },));
55            pos += 1;
56        }
57        pos += 1;
58
59        let i_ty = ident(&interrupt.0.name, config, "interrupt", span);
60        let description = format!(
61            "{} - {}",
62            interrupt.0.value,
63            interrupt
64                .0
65                .description
66                .as_deref()
67                .map(util::respace)
68                .as_deref()
69                .map(util::escape_special_chars)
70                .unwrap_or_else(|| interrupt.0.name.as_str().into())
71        );
72
73        let value = util::unsuffixed(interrupt.0.value);
74
75        let mut feature_attribute_flag = false;
76        let mut feature_attribute = TokenStream::new();
77        let mut not_feature_attribute = TokenStream::new();
78        if config.feature_group && interrupt.1.is_some() {
79            let feature_name = feature_format.apply(interrupt.1.as_ref().unwrap());
80            feature_attribute_flag = true;
81            feature_attribute.extend(quote! { #[cfg(feature = #feature_name)] });
82            not_feature_attribute.extend(quote! { feature = #feature_name, });
83        }
84        if config.feature_peripheral {
85            let feature_name = feature_format.apply(&interrupt.2);
86            feature_attribute_flag = true;
87            feature_attribute.extend(quote! { #[cfg(feature = #feature_name)] });
88            not_feature_attribute.extend(quote! { feature = #feature_name, });
89        }
90        let not_feature_attribute = quote! { #[cfg(not(all(#not_feature_attribute)))] };
91
92        variants.extend(quote! {
93            #[doc = #description]
94            #feature_attribute
95            #i_ty = #value,
96        });
97
98        from_arms.extend(quote! {
99            #feature_attribute
100            #value => Ok(Interrupt::#i_ty),
101        });
102
103        if feature_attribute_flag {
104            elements.extend(quote! {
105                #not_feature_attribute
106                Vector { _reserved: 0 },
107                #feature_attribute
108                Vector { _handler: #i_ty },
109            });
110        } else {
111            elements.extend(quote!(Vector { _handler: #i_ty },));
112        }
113        names.push(i_ty);
114        names_cfg_attr.push(feature_attribute);
115    }
116
117    let (nomangle, xtern) = if config.edition >= RustEdition::E2024 {
118        (quote!(#[unsafe(no_mangle)]), quote!(unsafe extern))
119    } else {
120        (quote!(#[no_mangle]), quote!(extern))
121    };
122
123    let n = util::unsuffixed(pos);
124    match target {
125        Target::CortexM => {
126            for name in &names {
127                writeln!(device_x, "PROVIDE({name} = DefaultHandler);")?;
128            }
129
130            let link_section_name = config
131                .interrupt_link_section
132                .as_deref()
133                .unwrap_or(".vector_table.interrupts");
134            let link_section_attr = if config.edition >= RustEdition::E2024 {
135                quote!(#[unsafe(link_section = #link_section_name)])
136            } else {
137                quote!(#[link_section = #link_section_name])
138            };
139
140            root.extend(quote! {
141                #[cfg(feature = "rt")]
142                #xtern "C" {
143                    #(#names_cfg_attr fn #names();)*
144                }
145
146                #[doc(hidden)]
147                #[repr(C)]
148                pub union Vector {
149                    _handler: unsafe extern "C" fn(),
150                    _reserved: u32,
151                }
152
153                #[cfg(feature = "rt")]
154                #[doc(hidden)]
155                #link_section_attr
156                #nomangle
157                pub static __INTERRUPTS: [Vector; #n] = [
158                    #elements
159                ];
160            });
161        }
162        Target::Msp430 => {
163            for name in &names {
164                writeln!(device_x, "PROVIDE({name} = DefaultHandler);").unwrap();
165            }
166
167            let link_section_name = config
168                .interrupt_link_section
169                .as_deref()
170                .unwrap_or(".vector_table.interrupts");
171            let link_section_attr = if config.edition >= RustEdition::E2024 {
172                quote!(#[unsafe(link_section = #link_section_name)])
173            } else {
174                quote!(#[link_section = #link_section_name])
175            };
176
177            root.extend(quote! {
178                #[cfg(feature = "rt")]
179                extern "msp430-interrupt" {
180                    #(#names_cfg_attr fn #names();)*
181                }
182
183                #[doc(hidden)]
184                #[repr(C)]
185                pub union Vector {
186                    _handler: unsafe extern "msp430-interrupt" fn(),
187                    _reserved: u16,
188                }
189
190                #[cfg(feature = "rt")]
191                #[doc(hidden)]
192                #link_section_attr
193                #nomangle
194                #[used]
195                pub static __INTERRUPTS:
196                    [Vector; #n] = [
197                        #elements
198                    ];
199            });
200        }
201        Target::RISCV => {
202            for name in &names {
203                writeln!(device_x, "PROVIDE({name} = DefaultHandler);")?;
204            }
205
206            let link_section_attr = config.interrupt_link_section.as_ref().map(|section| {
207                if config.edition >= RustEdition::E2024 {
208                    quote!(#[unsafe(link_section = #section)])
209                } else {
210                    quote!(#[link_section = #section])
211                }
212            });
213
214            root.extend(quote! {
215                #[cfg(feature = "rt")]
216                #xtern "C" {
217                    #(#names_cfg_attr fn #names();)*
218                }
219
220                #[doc(hidden)]
221                #[repr(C)]
222                pub union Vector {
223                    pub _handler: unsafe extern "C" fn(),
224                    pub _reserved: usize,
225                }
226
227                #[cfg(feature = "rt")]
228                #[doc(hidden)]
229                #link_section_attr
230                #nomangle
231                pub static __EXTERNAL_INTERRUPTS: [Vector; #n] = [
232                    #elements
233                ];
234            });
235        }
236        Target::XtensaLX => {
237            for name in &names {
238                writeln!(device_x, "PROVIDE({name} = DefaultHandler);")?;
239            }
240
241            let link_section_attr = config.interrupt_link_section.as_ref().map(|section| {
242                if config.edition >= RustEdition::E2024 {
243                    quote!(#[unsafe(link_section = #section)])
244                } else {
245                    quote!(#[link_section = #section])
246                }
247            });
248
249            root.extend(quote! {
250                #[cfg(feature = "rt")]
251                #xtern "C" {
252                    #(#names_cfg_attr fn #names();)*
253                }
254
255                #[doc(hidden)]
256                #[repr(C)]
257                pub union Vector {
258                    pub _handler: unsafe extern "C" fn(),
259                    _reserved: u32,
260                }
261
262                #[cfg(feature = "rt")]
263                #[doc(hidden)]
264                #link_section_attr
265                #nomangle
266                pub static __INTERRUPTS: [Vector; #n] = [
267                    #elements
268                ];
269            });
270        }
271        Target::Mips => {}
272        Target::None => {}
273    }
274
275    let self_token = quote!(self);
276    let (enum_repr, nr_expr) = if variants.is_empty() {
277        (quote!(), quote!(match #self_token {}))
278    } else {
279        (quote!(#[repr(u16)]), quote!(#self_token as u16))
280    };
281
282    let defmt = config
283        .impl_defmt
284        .as_ref()
285        .map(|feature| quote!(#[cfg_attr(feature = #feature, derive(defmt::Format))]));
286
287    if target == Target::Msp430 {
288        let interrupt_enum = quote! {
289            ///Enumeration of all the interrupts. This enum is seldom used in application or library crates. It is present primarily for documenting the device's implemented interrupts.
290            #defmt
291            #[derive(Copy, Clone, Debug, PartialEq, Eq)]
292            #enum_repr
293            pub enum Interrupt {
294                #variants
295            }
296        };
297
298        root.extend(interrupt_enum);
299    } else {
300        let interrupt_enum = quote! {
301            ///Enumeration of all the interrupts.
302            #defmt
303            #[derive(Copy, Clone, Debug, PartialEq, Eq)]
304            #enum_repr
305            pub enum Interrupt {
306                #variants
307            }
308        };
309
310        match target {
311            Target::CortexM => {
312                root.extend(quote! {
313                    #interrupt_enum
314
315                    unsafe impl cortex_m::interrupt::InterruptNumber for Interrupt {
316                        #[inline(always)]
317                        fn number(#self_token) -> u16 {
318                            #nr_expr
319                        }
320                    }
321                });
322            }
323            Target::XtensaLX => {
324                root.extend(quote! {
325                    #interrupt_enum
326
327                    /// TryFromInterruptError
328                    #defmt
329                    #[derive(Debug, Copy, Clone)]
330                    pub struct TryFromInterruptError(());
331
332                    impl Interrupt {
333
334                        /// Attempt to convert a given value into an `Interrupt`
335                        #[inline]
336                        pub fn try_from(value: u16) -> Result<Self, TryFromInterruptError> {
337                            match value {
338                                #from_arms
339                                _ => Err(TryFromInterruptError(())),
340                            }
341                        }
342                    }
343                });
344            }
345            _ => {
346                mod_items.extend(quote! {
347                    #interrupt_enum
348
349                    /// TryFromInterruptError
350                    #defmt
351                    #[derive(Debug, Copy, Clone)]
352                    pub struct TryFromInterruptError(());
353
354                    impl Interrupt {
355
356                        /// Attempt to convert a given value into an `Interrupt`
357                        #[inline]
358                        pub fn try_from(value: u8) -> Result<Self, TryFromInterruptError> {
359                            match value {
360                                #from_arms
361                                _ => Err(TryFromInterruptError(())),
362                            }
363                        }
364                    }
365                });
366            }
367        }
368    }
369
370    if target != Target::None {
371        let abi = match target {
372            Target::Msp430 => "msp430-interrupt",
373            _ => "C",
374        };
375
376        if target != Target::CortexM
377            && target != Target::Msp430
378            && target != Target::XtensaLX
379            && target != Target::Mips
380        {
381            mod_items.extend(quote! {
382                #[cfg(feature = "rt")]
383                #[macro_export]
384                /// Assigns a handler to an interrupt
385                ///
386                /// This macro takes two arguments: the name of an interrupt and the path to the
387                /// function that will be used as the handler of that interrupt. That function
388                /// must have signature `fn()`.
389                ///
390                /// Optionally, a third argument may be used to declare interrupt local data.
391                /// The handler will have exclusive access to these *local* variables on each
392                /// invocation. If the third argument is used then the signature of the handler
393                /// function must be `fn(&mut $NAME::Locals)` where `$NAME` is the first argument
394                /// passed to the macro.
395                ///
396                /// # Example
397                ///
398                /// ``` ignore
399                /// interrupt!(TIM2, periodic);
400                ///
401                /// fn periodic() {
402                ///     print!(".");
403                /// }
404                ///
405                /// interrupt!(TIM3, tick, locals: {
406                ///     tick: bool = false;
407                /// });
408                ///
409                /// fn tick(locals: &mut TIM3::Locals) {
410                ///     locals.tick = !locals.tick;
411                ///
412                ///     if locals.tick {
413                ///         println!("Tick");
414                ///     } else {
415                ///         println!("Tock");
416                ///     }
417                /// }
418                /// ```
419                macro_rules! interrupt {
420                    ($NAME:ident, $path:path, locals: {
421                        $($lvar:ident:$lty:ty = $lval:expr;)*
422                    }) => {
423                        #[allow(non_snake_case)]
424                        mod $NAME {
425                            pub struct Locals {
426                                $(
427                                    pub $lvar: $lty,
428                                )*
429                            }
430                        }
431
432                        #[allow(non_snake_case)]
433                        #nomangle
434                        pub extern #abi fn $NAME() {
435                            // check that the handler exists
436                            let _ = $crate::interrupt::Interrupt::$NAME;
437
438                            static mut LOCALS: self::$NAME::Locals =
439                                self::$NAME::Locals {
440                                    $(
441                                        $lvar: $lval,
442                                    )*
443                                };
444
445                            // type checking
446                            let f: fn(&mut self::$NAME::Locals) = $path;
447                            f(unsafe { &mut LOCALS });
448                        }
449                    };
450                    ($NAME:ident, $path:path) => {
451                        #[allow(non_snake_case)]
452                        #nomangle
453                        pub extern #abi fn $NAME() {
454                            // check that the handler exists
455                            let _ = $crate::interrupt::Interrupt::$NAME;
456
457                            // type checking
458                            let f: fn() = $path;
459                            f();
460                        }
461                    }
462                }
463            });
464        }
465    }
466
467    if !interrupts.is_empty()
468        && target != Target::CortexM
469        && target != Target::XtensaLX
470        && target != Target::Msp430
471    {
472        root.extend(quote! {
473            #[doc(hidden)]
474            pub mod interrupt {
475                #mod_items
476            }
477        });
478
479        root.extend(quote! {
480            pub use self::interrupt::Interrupt;
481        });
482    }
483
484    Ok(root)
485}