Skip to main content

ax_task/
irq_notify.rs

1use core::sync::atomic::{AtomicBool, Ordering};
2
3use crate::WaitQueue;
4
5/// IRQ-safe deferred notification primitive.
6///
7/// `IrqNotify` separates a hard-IRQ notification from the slow work that must
8/// run in task context. IRQ handlers call [`notify_irq`](Self::notify_irq) to
9/// publish a pending bit and wake a deferred worker. The worker then drains the
10/// bit and performs deferred work, such as consuming acknowledged device
11/// completions.
12pub struct IrqNotify {
13    pending: AtomicBool,
14    wait: WaitQueue,
15}
16
17impl Default for IrqNotify {
18    fn default() -> Self {
19        Self::new()
20    }
21}
22
23impl IrqNotify {
24    /// Creates an empty notification object.
25    pub const fn new() -> Self {
26        Self {
27            pending: AtomicBool::new(false),
28            wait: WaitQueue::new(),
29        }
30    }
31
32    /// Publishes a pending notification from IRQ context.
33    ///
34    /// This method is IRQ-safe: it does not allocate, does not call arbitrary
35    /// wakers, and does not perform slow poll wakeups. Repeated notifications
36    /// coalesce into one pending bit until a worker drains it.
37    pub fn notify_irq(&self) {
38        self.pending.store(true, Ordering::Release);
39        self.wait.notify_one_from_irq();
40    }
41
42    /// Publishes a pending notification from task context.
43    ///
44    /// Prefer [`notify_irq`](Self::notify_irq) inside hard IRQ callbacks. This
45    /// method exists for task/deferred code that wants the same coalescing
46    /// behavior while still allowing the scheduler to observe a normal wake.
47    pub fn notify(&self) {
48        self.pending.store(true, Ordering::Release);
49        self.wait.notify_one(true);
50    }
51
52    /// Returns whether a notification is currently pending.
53    pub fn is_pending(&self) -> bool {
54        self.pending.load(Ordering::Acquire)
55    }
56
57    /// Drains the pending bit.
58    ///
59    /// Returns `true` if at least one notification was pending.
60    pub fn drain(&self) -> bool {
61        self.pending.swap(false, Ordering::AcqRel)
62    }
63
64    /// Blocks until at least one pending notification is available, then drains it.
65    #[track_caller]
66    pub fn wait(&self) {
67        self.wait.wait_until(|| self.drain());
68    }
69
70    /// Blocks until a pending notification is consumed or `duration` elapses.
71    ///
72    /// Returns `true` only when the deadline elapsed without consuming a
73    /// notification.
74    #[track_caller]
75    pub fn wait_timeout(&self, duration: core::time::Duration) -> bool {
76        self.wait.wait_timeout_until(duration, || self.drain())
77    }
78}
79
80#[cfg(test)]
81mod coverage_tests {
82    use super::*;
83
84    fn irq_notify_constructor_and_pending_hold_for_test() -> bool {
85        // Test IrqNotify::new() creates a non-pending instance
86        let notify = IrqNotify::new();
87        assert!(!notify.is_pending());
88
89        // Test Default trait
90        let default_notify = IrqNotify::default();
91        assert!(!default_notify.is_pending());
92
93        true
94    }
95
96    fn irq_notify_drain_logic_hold_for_test() -> bool {
97        // Test drain on a fresh IrqNotify returns false (nothing pending)
98        let notify = IrqNotify::new();
99        assert!(!notify.drain()); // Nothing to drain
100        assert!(!notify.is_pending()); // Still not pending after drain
101
102        true
103    }
104
105    #[test]
106    fn irq_notify_constructor_and_pending_hold() {
107        assert!(irq_notify_constructor_and_pending_hold_for_test());
108    }
109
110    #[test]
111    fn irq_notify_drain_logic_hold() {
112        assert!(irq_notify_drain_logic_hold_for_test());
113    }
114}