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}