aboutsummaryrefslogtreecommitdiff
path: root/embassy-mcxa/src/interrupt.rs
blob: 725f8d49922e94129ddbbc9ff7b9fa46453c0e87 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
//! Minimal interrupt helpers mirroring embassy-imxrt style for OS_EVENT and LPUART2.
//! Type-level interrupt traits and bindings are provided by the
//! `embassy_hal_internal::interrupt_mod!` macro via the generated module below.

// TODO(AJM): As of 2025-11-13, we need to do a pass to ensure safety docs
// are complete prior to release.
#![allow(clippy::missing_safety_doc)]

mod generated {
    #[rustfmt::skip]
    embassy_hal_internal::interrupt_mod!(
        ADC0,
        ADC1,
        ADC2,
        ADC3,
        DMA_CH0,
        DMA_CH1,
        DMA_CH2,
        DMA_CH3,
        DMA_CH4,
        DMA_CH5,
        DMA_CH6,
        DMA_CH7,
        GPIO0,
        GPIO1,
        GPIO2,
        GPIO3,
        GPIO4,
        LPI2C0,
        LPI2C1,
        LPI2C2,
        LPI2C3,
        LPUART0,
        LPUART1,
        LPUART2,
        LPUART3,
        LPUART4,
        LPUART5,
        OS_EVENT,
        RTC,
    );
}

use core::sync::atomic::{AtomicU16, AtomicU32, Ordering};

pub use generated::interrupt::{Priority, typelevel};

use crate::pac::Interrupt;

/// Trait for configuring and controlling interrupts.
///
/// This trait provides a consistent interface for interrupt management across
/// different interrupt sources, similar to embassy-imxrt's InterruptExt.
pub trait InterruptExt {
    /// Clear any pending interrupt in NVIC.
    fn unpend(&self);

    /// Set NVIC priority for this interrupt.
    fn set_priority(&self, priority: Priority);

    /// Enable this interrupt in NVIC.
    ///
    /// # Safety
    /// This function is unsafe because it can enable interrupts that may not be
    /// properly configured, potentially leading to undefined behavior.
    unsafe fn enable(&self);

    /// Disable this interrupt in NVIC.
    ///
    /// # Safety
    /// This function is unsafe because disabling interrupts may leave the system
    /// in an inconsistent state if the interrupt was expected to fire.
    unsafe fn disable(&self);

    /// Check if the interrupt is pending in NVIC.
    fn is_pending(&self) -> bool;
}

#[derive(Clone, Copy, Debug, Default)]
pub struct DefaultHandlerSnapshot {
    pub vector: u16,
    pub count: u32,
    pub cfsr: u32,
    pub hfsr: u32,
    pub stacked_pc: u32,
    pub stacked_lr: u32,
    pub stacked_sp: u32,
}

static LAST_DEFAULT_VECTOR: AtomicU16 = AtomicU16::new(0);
static LAST_DEFAULT_COUNT: AtomicU32 = AtomicU32::new(0);
static LAST_DEFAULT_CFSR: AtomicU32 = AtomicU32::new(0);
static LAST_DEFAULT_HFSR: AtomicU32 = AtomicU32::new(0);
static LAST_DEFAULT_PC: AtomicU32 = AtomicU32::new(0);
static LAST_DEFAULT_LR: AtomicU32 = AtomicU32::new(0);
static LAST_DEFAULT_SP: AtomicU32 = AtomicU32::new(0);

#[inline]
pub fn default_handler_snapshot() -> DefaultHandlerSnapshot {
    DefaultHandlerSnapshot {
        vector: LAST_DEFAULT_VECTOR.load(Ordering::Relaxed),
        count: LAST_DEFAULT_COUNT.load(Ordering::Relaxed),
        cfsr: LAST_DEFAULT_CFSR.load(Ordering::Relaxed),
        hfsr: LAST_DEFAULT_HFSR.load(Ordering::Relaxed),
        stacked_pc: LAST_DEFAULT_PC.load(Ordering::Relaxed),
        stacked_lr: LAST_DEFAULT_LR.load(Ordering::Relaxed),
        stacked_sp: LAST_DEFAULT_SP.load(Ordering::Relaxed),
    }
}

#[inline]
pub fn clear_default_handler_snapshot() {
    LAST_DEFAULT_VECTOR.store(0, Ordering::Relaxed);
    LAST_DEFAULT_COUNT.store(0, Ordering::Relaxed);
    LAST_DEFAULT_CFSR.store(0, Ordering::Relaxed);
    LAST_DEFAULT_HFSR.store(0, Ordering::Relaxed);
    LAST_DEFAULT_PC.store(0, Ordering::Relaxed);
    LAST_DEFAULT_LR.store(0, Ordering::Relaxed);
    LAST_DEFAULT_SP.store(0, Ordering::Relaxed);
}

/// OS_EVENT interrupt helper with methods similar to embassy-imxrt's InterruptExt.
pub struct OsEvent;
pub const OS_EVENT: OsEvent = OsEvent;

impl InterruptExt for OsEvent {
    /// Clear any pending OS_EVENT in NVIC.
    #[inline]
    fn unpend(&self) {
        cortex_m::peripheral::NVIC::unpend(Interrupt::OS_EVENT);
    }

    /// Set NVIC priority for OS_EVENT.
    #[inline]
    fn set_priority(&self, priority: Priority) {
        unsafe {
            let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC;
            nvic.set_priority(Interrupt::OS_EVENT, u8::from(priority));
        }
    }

    /// Enable OS_EVENT in NVIC.
    #[inline]
    unsafe fn enable(&self) {
        cortex_m::peripheral::NVIC::unmask(Interrupt::OS_EVENT);
    }

    /// Disable OS_EVENT in NVIC.
    #[inline]
    unsafe fn disable(&self) {
        cortex_m::peripheral::NVIC::mask(Interrupt::OS_EVENT);
    }

    /// Check if OS_EVENT is pending in NVIC.
    #[inline]
    fn is_pending(&self) -> bool {
        cortex_m::peripheral::NVIC::is_pending(Interrupt::OS_EVENT)
    }
}

impl OsEvent {
    /// Configure OS_EVENT interrupt for timer operation.
    /// This sets up the NVIC priority, enables the interrupt, and ensures global interrupts are enabled.
    /// Also performs a software event to wake any pending WFE.
    pub fn configure_for_timer(&self, priority: Priority) {
        // Configure NVIC
        self.unpend();
        self.set_priority(priority);
        unsafe {
            self.enable();
        }

        // Ensure global interrupts are enabled in no-reset scenarios (e.g., cargo run)
        // Debuggers typically perform a reset which leaves PRIMASK=0; cargo run may not.
        unsafe {
            cortex_m::interrupt::enable();
        }

        // Wake any executor WFE that might be sleeping when we armed the first deadline
        cortex_m::asm::sev();
    }
}

/// LPUART2 interrupt helper with methods similar to embassy-imxrt's InterruptExt.
pub struct Lpuart2;
pub const LPUART2: Lpuart2 = Lpuart2;

impl InterruptExt for Lpuart2 {
    /// Clear any pending LPUART2 in NVIC.
    #[inline]
    fn unpend(&self) {
        cortex_m::peripheral::NVIC::unpend(Interrupt::LPUART2);
    }

    /// Set NVIC priority for LPUART2.
    #[inline]
    fn set_priority(&self, priority: Priority) {
        unsafe {
            let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC;
            nvic.set_priority(Interrupt::LPUART2, u8::from(priority));
        }
    }

    /// Enable LPUART2 in NVIC.
    #[inline]
    unsafe fn enable(&self) {
        cortex_m::peripheral::NVIC::unmask(Interrupt::LPUART2);
    }

    /// Disable LPUART2 in NVIC.
    #[inline]
    unsafe fn disable(&self) {
        cortex_m::peripheral::NVIC::mask(Interrupt::LPUART2);
    }

    /// Check if LPUART2 is pending in NVIC.
    #[inline]
    fn is_pending(&self) -> bool {
        cortex_m::peripheral::NVIC::is_pending(Interrupt::LPUART2)
    }
}

impl Lpuart2 {
    /// Configure LPUART2 interrupt for UART operation.
    /// This sets up the NVIC priority, enables the interrupt, and ensures global interrupts are enabled.
    pub fn configure_for_uart(&self, priority: Priority) {
        // Configure NVIC
        self.unpend();
        self.set_priority(priority);
        unsafe {
            self.enable();
        }

        // Ensure global interrupts are enabled in no-reset scenarios (e.g., cargo run)
        // Debuggers typically perform a reset which leaves PRIMASK=0; cargo run may not.
        unsafe {
            cortex_m::interrupt::enable();
        }
    }

    /// Install LPUART2 handler into the RAM vector table.
    /// Safety: See `install_irq_handler`.
    pub unsafe fn install_handler(&self, handler: unsafe extern "C" fn()) {
        install_irq_handler(Interrupt::LPUART2, handler);
    }
}

pub struct Rtc;
pub const RTC: Rtc = Rtc;

impl InterruptExt for Rtc {
    /// Clear any pending RTC in NVIC.
    #[inline]
    fn unpend(&self) {
        cortex_m::peripheral::NVIC::unpend(Interrupt::RTC);
    }

    /// Set NVIC priority for RTC.
    #[inline]
    fn set_priority(&self, priority: Priority) {
        unsafe {
            let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC;
            nvic.set_priority(Interrupt::RTC, u8::from(priority));
        }
    }

    /// Enable RTC in NVIC.
    #[inline]
    unsafe fn enable(&self) {
        cortex_m::peripheral::NVIC::unmask(Interrupt::RTC);
    }

    /// Disable RTC in NVIC.
    #[inline]
    unsafe fn disable(&self) {
        cortex_m::peripheral::NVIC::mask(Interrupt::RTC);
    }

    /// Check if RTC is pending in NVIC.
    #[inline]
    fn is_pending(&self) -> bool {
        cortex_m::peripheral::NVIC::is_pending(Interrupt::RTC)
    }
}

pub struct Gpio0;
pub const GPIO0: Gpio0 = Gpio0;

impl InterruptExt for Gpio0 {
    /// Clear any pending GPIO0 in NVIC.
    #[inline]
    fn unpend(&self) {
        cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO0);
    }

    /// Set NVIC priority for GPIO0.
    #[inline]
    fn set_priority(&self, priority: Priority) {
        unsafe {
            let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC;
            nvic.set_priority(Interrupt::GPIO0, u8::from(priority));
        }
    }

    /// Enable GPIO0 in NVIC.
    #[inline]
    unsafe fn enable(&self) {
        cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO0);
    }

    /// Disable GPIO0 in NVIC.
    #[inline]
    unsafe fn disable(&self) {
        cortex_m::peripheral::NVIC::mask(Interrupt::GPIO0);
    }

    /// Check if GPIO0 is pending in NVIC.
    #[inline]
    fn is_pending(&self) -> bool {
        cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO0)
    }
}

pub struct Gpio1;
pub const GPIO1: Gpio1 = Gpio1;

impl InterruptExt for Gpio1 {
    /// Clear any pending GPIO1 in NVIC.
    #[inline]
    fn unpend(&self) {
        cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO1);
    }

    /// Set NVIC priority for GPIO1.
    #[inline]
    fn set_priority(&self, priority: Priority) {
        unsafe {
            let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC;
            nvic.set_priority(Interrupt::GPIO1, u8::from(priority));
        }
    }

    /// Enable GPIO1 in NVIC.
    #[inline]
    unsafe fn enable(&self) {
        cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO1);
    }

    /// Disable GPIO1 in NVIC.
    #[inline]
    unsafe fn disable(&self) {
        cortex_m::peripheral::NVIC::mask(Interrupt::GPIO1);
    }

    /// Check if GPIO1 is pending in NVIC.
    #[inline]
    fn is_pending(&self) -> bool {
        cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO1)
    }
}

pub struct Gpio2;
pub const GPIO2: Gpio2 = Gpio2;

impl InterruptExt for Gpio2 {
    /// Clear any pending GPIO2 in NVIC.
    #[inline]
    fn unpend(&self) {
        cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO2);
    }

    /// Set NVIC priority for GPIO2.
    #[inline]
    fn set_priority(&self, priority: Priority) {
        unsafe {
            let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC;
            nvic.set_priority(Interrupt::GPIO2, u8::from(priority));
        }
    }

    /// Enable GPIO2 in NVIC.
    #[inline]
    unsafe fn enable(&self) {
        cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO2);
    }

    /// Disable GPIO2 in NVIC.
    #[inline]
    unsafe fn disable(&self) {
        cortex_m::peripheral::NVIC::mask(Interrupt::GPIO2);
    }

    /// Check if GPIO2 is pending in NVIC.
    #[inline]
    fn is_pending(&self) -> bool {
        cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO2)
    }
}

pub struct Gpio3;
pub const GPIO3: Gpio3 = Gpio3;

impl InterruptExt for Gpio3 {
    /// Clear any pending GPIO3 in NVIC.
    #[inline]
    fn unpend(&self) {
        cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO3);
    }

    /// Set NVIC priority for GPIO3.
    #[inline]
    fn set_priority(&self, priority: Priority) {
        unsafe {
            let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC;
            nvic.set_priority(Interrupt::GPIO3, u8::from(priority));
        }
    }

    /// Enable GPIO3 in NVIC.
    #[inline]
    unsafe fn enable(&self) {
        cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO3);
    }

    /// Disable GPIO3 in NVIC.
    #[inline]
    unsafe fn disable(&self) {
        cortex_m::peripheral::NVIC::mask(Interrupt::GPIO3);
    }

    /// Check if GPIO3 is pending in NVIC.
    #[inline]
    fn is_pending(&self) -> bool {
        cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO3)
    }
}

pub struct Gpio4;
pub const GPIO4: Gpio4 = Gpio4;

impl InterruptExt for Gpio4 {
    /// Clear any pending GPIO4 in NVIC.
    #[inline]
    fn unpend(&self) {
        cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO4);
    }

    /// Set NVIC priority for GPIO4.
    #[inline]
    fn set_priority(&self, priority: Priority) {
        unsafe {
            let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC;
            nvic.set_priority(Interrupt::GPIO4, u8::from(priority));
        }
    }

    /// Enable GPIO4 in NVIC.
    #[inline]
    unsafe fn enable(&self) {
        cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO4);
    }

    /// Disable GPIO4 in NVIC.
    #[inline]
    unsafe fn disable(&self) {
        cortex_m::peripheral::NVIC::mask(Interrupt::GPIO4);
    }

    /// Check if GPIO4 is pending in NVIC.
    #[inline]
    fn is_pending(&self) -> bool {
        cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO4)
    }
}

/// Set VTOR (Vector Table Offset) to a RAM-based vector table.
/// Pass a pointer to the first word in the RAM table (stack pointer slot 0).
/// Safety: Caller must ensure the RAM table is valid and aligned as required by the core.
pub unsafe fn vtor_set_ram_vector_base(base: *const u32) {
    core::ptr::write_volatile(0xE000_ED08 as *mut u32, base as u32);
}

/// Install an interrupt handler into the current VTOR-based vector table.
/// This writes the function pointer at index 16 + irq number.
/// Safety: Caller must ensure VTOR points at a writable RAM table and that `handler`
/// has the correct ABI and lifetime.
pub unsafe fn install_irq_handler(irq: Interrupt, handler: unsafe extern "C" fn()) {
    let vtor_base = core::ptr::read_volatile(0xE000_ED08 as *const u32) as *mut u32;
    let idx = 16 + (irq as isize as usize);
    core::ptr::write_volatile(vtor_base.add(idx), handler as usize as u32);
}

impl OsEvent {
    /// Convenience to install the OS_EVENT handler into the RAM vector table.
    /// Safety: See `install_irq_handler`.
    pub unsafe fn install_handler(&self, handler: extern "C" fn()) {
        install_irq_handler(Interrupt::OS_EVENT, handler);
    }
}

/// Install OS_EVENT handler by raw address. Useful to avoid fn pointer type mismatches.
/// Safety: Caller must ensure the address is a valid `extern "C" fn()` handler.
pub unsafe fn os_event_install_handler_raw(handler_addr: usize) {
    let vtor_base = core::ptr::read_volatile(0xE000_ED08 as *const u32) as *mut u32;
    let idx = 16 + (Interrupt::OS_EVENT as isize as usize);
    core::ptr::write_volatile(vtor_base.add(idx), handler_addr as u32);
}

/// Provide a conservative default IRQ handler that avoids wedging the system.
/// It clears all NVIC pending bits and returns, so spurious or reserved IRQs
/// don’t trap the core in an infinite WFI loop during bring-up.
#[no_mangle]
pub unsafe extern "C" fn DefaultHandler() {
    let active = core::ptr::read_volatile(0xE000_ED04 as *const u32) & 0x1FF;
    let cfsr = core::ptr::read_volatile(0xE000_ED28 as *const u32);
    let hfsr = core::ptr::read_volatile(0xE000_ED2C as *const u32);

    let sp = cortex_m::register::msp::read();
    let stacked = sp as *const u32;
    // Stacked registers follow ARMv8-M procedure call standard order
    let stacked_pc = unsafe { stacked.add(6).read() };
    let stacked_lr = unsafe { stacked.add(5).read() };

    LAST_DEFAULT_VECTOR.store(active as u16, Ordering::Relaxed);
    LAST_DEFAULT_CFSR.store(cfsr, Ordering::Relaxed);
    LAST_DEFAULT_HFSR.store(hfsr, Ordering::Relaxed);
    LAST_DEFAULT_COUNT.fetch_add(1, Ordering::Relaxed);
    LAST_DEFAULT_PC.store(stacked_pc, Ordering::Relaxed);
    LAST_DEFAULT_LR.store(stacked_lr, Ordering::Relaxed);
    LAST_DEFAULT_SP.store(sp, Ordering::Relaxed);

    // Do nothing here: on some MCUs/TrustZone setups, writing NVIC from a spurious
    // handler can fault if targeting the Secure bank. Just return.
    cortex_m::asm::dsb();
    cortex_m::asm::isb();
}