Skip to main content

hpm_riscv_rt_macros/
lib.rs

1//! Procedural macros for hpm-riscv-rt
2//!
3//! This crate provides:
4//! - `#[entry]` - Define the program entry point
5//! - `#[pre_init]` - Define a pre-initialization function
6//! - `#[fast]` - Place functions/statics in ILM/DLM
7//! - `#[external_interrupt]` - Define PLIC external interrupt handlers
8
9use proc_macro::TokenStream;
10use quote::quote;
11use syn::{
12    parse::Parse, parse::ParseStream, parse_macro_input, spanned::Spanned, Expr, Item, ItemFn,
13};
14
15/// Attribute to declare the entry point of the program.
16///
17/// The function must have the signature `fn() -> !` (never returns).
18///
19/// # Example
20///
21/// ```ignore
22/// #[entry]
23/// fn main() -> ! {
24///     loop {}
25/// }
26/// ```
27#[proc_macro_attribute]
28pub fn entry(_args: TokenStream, input: TokenStream) -> TokenStream {
29    let f = parse_macro_input!(input as ItemFn);
30
31    let fn_attrs = &f.attrs;
32    let fn_vis = &f.vis;
33    let fn_sig = &f.sig;
34    let fn_block = &f.block;
35
36    quote!(
37        #(#fn_attrs)*
38        #[unsafe(export_name = "main")]
39        #fn_vis #fn_sig #fn_block
40    )
41    .into()
42}
43
44/// Attribute to declare a function that runs before RAM is initialized.
45///
46/// The function must have the signature `unsafe fn()`.
47/// At this point:
48/// - Stack is valid
49/// - .data and .bss are NOT initialized
50/// - Interrupts are disabled
51///
52/// # Example
53///
54/// ```ignore
55/// #[pre_init]
56/// unsafe fn setup_watchdog() {
57///     // Disable watchdog before RAM init
58/// }
59/// ```
60#[proc_macro_attribute]
61pub fn pre_init(_args: TokenStream, input: TokenStream) -> TokenStream {
62    let f = parse_macro_input!(input as ItemFn);
63
64    let fn_attrs = &f.attrs;
65    let fn_vis = &f.vis;
66    let fn_sig = &f.sig;
67    let fn_block = &f.block;
68
69    quote!(
70        #(#fn_attrs)*
71        #[unsafe(export_name = "__pre_init")]
72        #fn_vis #fn_sig #fn_block
73    )
74    .into()
75}
76
77/// Place a function or static into fast memory (ILM/DLM).
78///
79/// Functions are placed into `.fast.text` section (ILM).
80/// Statics are placed into `.fast.data` or `.fast.bss` section (DLM).
81///
82/// # Example
83///
84/// ```ignore
85/// use hpm_riscv_rt::fast;
86///
87/// #[fast]
88/// fn critical_function() {
89///     // This function runs from ILM
90/// }
91///
92/// #[fast]
93/// static BUFFER: [u8; 1024] = [0; 1024];
94/// ```
95#[proc_macro_attribute]
96pub fn fast(_args: TokenStream, input: TokenStream) -> TokenStream {
97    let item = parse_macro_input!(input as Item);
98
99    match item {
100        Item::Fn(f) => quote!(
101            #[unsafe(link_section = ".fast.text")]
102            #[inline(never)]
103            #f
104        )
105        .into(),
106        Item::Static(item) => {
107            // Check if it's uninitialized (MaybeUninit::uninit())
108            let section = if is_uninit_expr(&item.expr) {
109                quote!(#[unsafe(link_section = ".fast.bss")])
110            } else {
111                quote!(#[unsafe(link_section = ".fast.data")])
112            };
113
114            quote!(
115                #section
116                #item
117            )
118            .into()
119        }
120        _ => {
121            let span = item.span();
122            syn::Error::new(span, "#[fast] can only be applied to functions or statics")
123                .to_compile_error()
124                .into()
125        }
126    }
127}
128
129fn is_uninit_expr(expr: &Expr) -> bool {
130    if let Expr::Call(call) = expr {
131        let s = quote!(#call).to_string();
132        s.contains("MaybeUninit") && (s.contains("uninit()") || s.contains("uninit_array()"))
133    } else {
134        false
135    }
136}
137
138/// Argument for the external_interrupt attribute.
139struct ExternalInterruptArg {
140    interrupt: syn::Path,
141}
142
143impl Parse for ExternalInterruptArg {
144    fn parse(input: ParseStream) -> syn::Result<Self> {
145        Ok(ExternalInterruptArg {
146            interrupt: input.parse()?,
147        })
148    }
149}
150
151/// Define an external interrupt handler for HPMicro PLIC.
152///
153/// This macro generates an interrupt handler function that will be called
154/// when the specified PLIC interrupt occurs. The function is exported with
155/// the interrupt name so it can be placed in the vector table.
156///
157/// # Example
158///
159/// ```ignore
160/// use hpm_riscv_rt::external_interrupt;
161/// use hpm_pac::interrupt;
162///
163/// #[external_interrupt(interrupt::UART0)]
164/// fn uart0_handler() {
165///     // Handle UART0 interrupt
166/// }
167/// ```
168///
169/// # Safety
170///
171/// The handler function runs in interrupt context. It must:
172/// - Not block or wait
173/// - Complete quickly
174/// - Handle the interrupt source to prevent re-triggering
175#[proc_macro_attribute]
176pub fn external_interrupt(args: TokenStream, input: TokenStream) -> TokenStream {
177    let args = parse_macro_input!(args as ExternalInterruptArg);
178    let f = parse_macro_input!(input as ItemFn);
179
180    let interrupt_path = &args.interrupt;
181    let fn_name = &f.sig.ident;
182    let fn_body = &f.block;
183    let fn_attrs = &f.attrs;
184    let fn_vis = &f.vis;
185
186    // Get the interrupt name from the path (last segment)
187    let interrupt_name = interrupt_path
188        .segments
189        .last()
190        .map(|s| &s.ident)
191        .expect("interrupt path should have at least one segment");
192    quote!(
193        #(#fn_attrs)*
194        #[unsafe(no_mangle)]
195        #[unsafe(link_section = ".fast.text")]
196        #fn_vis unsafe extern "riscv-interrupt-m" fn #interrupt_name() {
197            // The original function body wrapped in unsafe
198            #[inline(always)]
199            unsafe fn #fn_name() #fn_body
200
201            let mepc: usize;
202            let mstatus: usize;
203            let mxstatus: usize;
204            let mcctlbeginaddr: usize;
205            let mcctldata: usize;
206            core::arch::asm!(
207                "csrr {mepc}, mepc",
208                "csrr {mstatus}, mstatus",
209                "csrr {mxstatus}, 0x7c4",
210                "csrr {mcctlbeginaddr}, 0x7cb",
211                "csrr {mcctldata}, 0x7cd",
212                mepc = out(reg) mepc,
213                mstatus = out(reg) mstatus,
214                mxstatus = out(reg) mxstatus,
215                mcctlbeginaddr = out(reg) mcctlbeginaddr,
216                mcctldata = out(reg) mcctldata,
217                options(nomem, nostack),
218            );
219
220            #fn_name();
221
222            // Match the HPM SDK external-IRQ epilogue. HPM cache-control and
223            // prefetch state is CSR-backed and must survive interrupt work.
224            core::arch::asm!(
225                "csrw mstatus, {mstatus}",
226                "csrw mepc, {mepc}",
227                "csrw 0x7c4, {mxstatus}",
228                "csrw 0x7cd, {mcctldata}",
229                "csrw 0x7cb, {mcctlbeginaddr}",
230                "fence io, io",
231                mstatus = in(reg) mstatus,
232                mepc = in(reg) mepc,
233                mxstatus = in(reg) mxstatus,
234                mcctldata = in(reg) mcctldata,
235                mcctlbeginaddr = in(reg) mcctlbeginaddr,
236                options(nostack),
237            );
238        }
239    )
240    .into()
241}