1use alloc::sync::Arc;
15use core::{
16 hint::spin_loop,
17 ptr,
18 sync::atomic::{AtomicPtr, AtomicU64, Ordering},
19};
20
21use crate::thread::ThreadWakeHandle;
22
23const REGISTRATION_PHASE_BITS: u32 = 2;
24const REGISTRATION_PHASE_MASK: u64 = (1 << REGISTRATION_PHASE_BITS) - 1;
25const REGISTRATION_GENERATION_MAX: u64 = u64::MAX >> REGISTRATION_PHASE_BITS;
26const IRQ_NOTIFY_CAS_BUDGET: usize = 8;
27
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29#[repr(u64)]
30enum RegistrationPhase {
31 Detached = 0,
32 Attached = 1,
33 Notifying = 2,
34}
35
36const fn registration_state(generation: u64, phase: RegistrationPhase) -> u64 {
37 (generation << REGISTRATION_PHASE_BITS) | phase as u64
38}
39
40const fn registration_generation(state: u64) -> u64 {
41 state >> REGISTRATION_PHASE_BITS
42}
43
44#[repr(align(8))]
45struct WaiterSentinel {
46 _tag: u8,
47}
48
49static PENDING_WAITER_SENTINEL: WaiterSentinel = WaiterSentinel { _tag: 1 };
50static NOTIFYING_WAITER_SENTINEL: WaiterSentinel = WaiterSentinel { _tag: 2 };
51static NOTIFYING_PENDING_WAITER_SENTINEL: WaiterSentinel = WaiterSentinel { _tag: 3 };
52
53fn waiter_sentinel(sentinel: &'static WaiterSentinel) -> *mut IrqWaitNode {
54 ptr::from_ref(sentinel).cast_mut().cast()
55}
56
57fn pending_waiter() -> *mut IrqWaitNode {
58 waiter_sentinel(&PENDING_WAITER_SENTINEL)
59}
60
61fn notifying_waiter() -> *mut IrqWaitNode {
62 waiter_sentinel(&NOTIFYING_WAITER_SENTINEL)
63}
64
65fn notifying_pending_waiter() -> *mut IrqWaitNode {
66 waiter_sentinel(&NOTIFYING_PENDING_WAITER_SENTINEL)
67}
68
69fn is_notification_sentinel(waiter: *mut IrqWaitNode) -> bool {
70 waiter == notifying_waiter() || waiter == notifying_pending_waiter()
71}
72
73fn registration_phase(state: u64) -> RegistrationPhase {
74 match state & REGISTRATION_PHASE_MASK {
75 0 => RegistrationPhase::Detached,
76 1 => RegistrationPhase::Attached,
77 2 => RegistrationPhase::Notifying,
78 _ => unreachable!("registration phase exceeds its bit mask"),
79 }
80}
81
82#[derive(Debug)]
83enum IrqWaitWake {
84 Thread(ThreadWakeHandle),
85}
86
87impl IrqWaitWake {
88 fn wake(&self) -> crate::thread::WakeResult {
89 match self {
90 Self::Thread(wake) => wake.wake(),
91 }
92 }
93}
94
95#[derive(Debug)]
97struct IrqWaitNode {
98 wake: IrqWaitWake,
99 state: AtomicU64,
100}
101
102impl IrqWaitNode {
103 fn new(wake: IrqWaitWake) -> Self {
104 Self {
105 wake,
106 state: AtomicU64::new(registration_state(0, RegistrationPhase::Detached)),
107 }
108 }
109
110 fn reserve(&self) -> Option<u64> {
111 let mut state = self.state.load(Ordering::Acquire);
112 loop {
113 if registration_phase(state) != RegistrationPhase::Detached {
114 return None;
115 }
116 let generation = registration_generation(state)
117 .checked_add(1)
118 .filter(|generation| *generation <= REGISTRATION_GENERATION_MAX)
119 .expect("IRQ wait registration generation exhausted");
120 let attached = registration_state(generation, RegistrationPhase::Attached);
121 match self.state.compare_exchange_weak(
122 state,
123 attached,
124 Ordering::AcqRel,
125 Ordering::Acquire,
126 ) {
127 Ok(_) => return Some(generation),
128 Err(observed) => state = observed,
129 }
130 }
131 }
132
133 fn cancel(&self, generation: u64) {
134 self.state
135 .compare_exchange(
136 registration_state(generation, RegistrationPhase::Attached),
137 registration_state(generation, RegistrationPhase::Detached),
138 Ordering::Release,
139 Ordering::Acquire,
140 )
141 .expect("only an attached IRQ wait registration can be cancelled");
142 }
143
144 fn begin_notification(&self) -> u64 {
145 let state = self.state.load(Ordering::Acquire);
146 let generation = registration_generation(state);
147 assert_eq!(
148 registration_phase(state),
149 RegistrationPhase::Attached,
150 "an IRQ wait cell took a registration it no longer owned"
151 );
152 self.state
153 .compare_exchange(
154 state,
155 registration_state(generation, RegistrationPhase::Notifying),
156 Ordering::AcqRel,
157 Ordering::Acquire,
158 )
159 .expect("IRQ wait registration ownership changed after cell removal");
160 generation
161 }
162
163 fn finish_notification(&self, generation: u64) {
172 self.state
173 .compare_exchange(
174 registration_state(generation, RegistrationPhase::Notifying),
175 registration_state(generation, RegistrationPhase::Detached),
176 Ordering::Release,
177 Ordering::Acquire,
178 )
179 .expect("IRQ wait notification generation changed while in flight");
180 }
181
182 fn is_attached(&self, generation: u64) -> bool {
183 self.state.load(Ordering::Acquire)
184 == registration_state(generation, RegistrationPhase::Attached)
185 }
186
187 fn is_quiescent(&self, generation: u64) -> bool {
188 let state = self.state.load(Ordering::Acquire);
189 registration_generation(state) != generation
190 || registration_phase(state) == RegistrationPhase::Detached
191 }
192}
193
194fn publish_cell_owner(node: Arc<IrqWaitNode>) -> *mut IrqWaitNode {
195 Arc::into_raw(node).cast_mut()
196}
197
198unsafe fn take_cell_owner(node: *mut IrqWaitNode) -> Arc<IrqWaitNode> {
199 unsafe {
200 Arc::from_raw(node)
203 }
204}
205
206#[derive(Debug)]
213pub struct IrqWaitRegistration {
214 node: Arc<IrqWaitNode>,
215}
216
217impl IrqWaitRegistration {
218 pub fn new(wake: ThreadWakeHandle) -> Self {
220 Self {
221 node: Arc::new(IrqWaitNode::new(IrqWaitWake::Thread(wake))),
222 }
223 }
224}
225
226#[must_use = "an IRQ wait token must enter its drain lifetime before storage is reused"]
233pub struct IrqWaitToken<'cell> {
234 registration: Arc<IrqWaitNode>,
235 generation: u64,
236 cell: &'cell IrqWaitCell,
237}
238
239impl IrqWaitToken<'_> {
240 pub const fn generation(&self) -> u64 {
242 self.generation
243 }
244
245 pub fn is_attached(&self) -> bool {
251 self.registration.is_attached(self.generation)
252 }
253
254 pub fn detach(self) -> IrqWaitDrain {
261 let cell = self.cell;
262 cell.detach(self)
263 }
264
265 fn belongs_to(&self, cell: &IrqWaitCell) -> bool {
266 ptr::eq(self.cell, cell)
267 }
268}
269
270impl core::fmt::Debug for IrqWaitToken<'_> {
271 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
272 formatter
273 .debug_struct("IrqWaitToken")
274 .field("generation", &self.generation)
275 .field("attached", &self.is_attached())
276 .finish()
277 }
278}
279
280#[must_use = "an IRQ wait drain must finish before registration storage is reused"]
287pub struct IrqWaitDrain {
288 registration: Arc<IrqWaitNode>,
289 generation: u64,
290}
291
292impl IrqWaitDrain {
293 pub fn is_quiescent(&self) -> bool {
295 self.registration.is_quiescent(self.generation)
296 }
297
298 pub fn finish(self) {
306 while !self.is_quiescent() {
307 spin_loop();
308 }
309 }
310}
311
312impl core::fmt::Debug for IrqWaitDrain {
313 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
314 formatter
315 .debug_struct("IrqWaitDrain")
316 .field("generation", &self.generation)
317 .field("quiescent", &self.is_quiescent())
318 .finish()
319 }
320}
321
322#[derive(Debug)]
324pub enum IrqRegisterResult<'cell> {
325 Registered(IrqWaitToken<'cell>),
327 ConsumedPending,
332 NotificationInFlight(IrqWaitToken<'cell>),
337 Occupied,
339}
340
341#[derive(Clone, Copy, Debug, Eq, PartialEq)]
343pub enum IrqNotifyResult {
344 Notified,
349 Pending,
352}
353
354#[derive(Debug)]
356pub struct IrqWaitCell {
357 waiter: AtomicPtr<IrqWaitNode>,
358}
359
360impl IrqWaitCell {
361 pub const fn new() -> Self {
363 Self {
364 waiter: AtomicPtr::new(ptr::null_mut()),
365 }
366 }
367
368 pub fn register<'cell>(
370 &'cell self,
371 registration: &IrqWaitRegistration,
372 ) -> IrqRegisterResult<'cell> {
373 let registration = Arc::clone(®istration.node);
374 let Some(generation) = registration.reserve() else {
375 return IrqRegisterResult::Occupied;
376 };
377 let registration_ptr = publish_cell_owner(Arc::clone(®istration));
378 let token = IrqWaitToken {
379 registration,
380 generation,
381 cell: self,
382 };
383 let pending = pending_waiter();
384 let mut observed = self.waiter.load(Ordering::Acquire);
385 loop {
386 if observed == pending {
387 match self.waiter.compare_exchange(
388 pending,
389 ptr::null_mut(),
390 Ordering::AcqRel,
391 Ordering::Acquire,
392 ) {
393 Ok(_) => {
394 token.registration.cancel(generation);
395 unsafe { drop(take_cell_owner(registration_ptr)) };
398 return IrqRegisterResult::ConsumedPending;
399 }
400 Err(current) => {
401 observed = current;
402 continue;
403 }
404 }
405 }
406 if !observed.is_null() {
407 token.registration.cancel(generation);
408 unsafe { drop(take_cell_owner(registration_ptr)) };
411 return IrqRegisterResult::Occupied;
412 }
413 match self.waiter.compare_exchange(
414 ptr::null_mut(),
415 registration_ptr,
416 Ordering::Release,
417 Ordering::Acquire,
418 ) {
419 Ok(_) => break,
420 Err(current) => observed = current,
421 }
422 }
423
424 if self.waiter.load(Ordering::Acquire) == registration_ptr {
425 IrqRegisterResult::Registered(token)
426 } else {
427 IrqRegisterResult::NotificationInFlight(token)
429 }
430 }
431
432 fn detach(&self, token: IrqWaitToken<'_>) -> IrqWaitDrain {
433 assert!(
434 token.belongs_to(self),
435 "an IRQ wait token must be detached by its publishing cell"
436 );
437 let registration = token.registration;
438 let state = registration.state.load(Ordering::Acquire);
439 if registration_generation(state) == token.generation {
440 let registration_ptr = Arc::as_ptr(®istration).cast_mut();
441 if self
442 .waiter
443 .compare_exchange(
444 registration_ptr,
445 ptr::null_mut(),
446 Ordering::AcqRel,
447 Ordering::Acquire,
448 )
449 .is_ok()
450 {
451 registration.cancel(token.generation);
452 unsafe { drop(take_cell_owner(registration_ptr)) };
455 }
456 }
457 IrqWaitDrain {
458 registration,
459 generation: token.generation,
460 }
461 }
462
463 pub fn notify(&self) -> IrqNotifyResult {
477 let _preempt = (!crate::runtime::task_runtime::in_hard_irq())
480 .then(crate::runtime::lock::PreemptScope::enter);
481 self.notify_claimed()
482 }
483
484 fn notify_claimed(&self) -> IrqNotifyResult {
485 let pending = pending_waiter();
486 let notifying = notifying_waiter();
487 let notifying_pending = notifying_pending_waiter();
488 let mut observed = self.waiter.load(Ordering::Acquire);
489 for _ in 0..IRQ_NOTIFY_CAS_BUDGET {
490 if observed == pending {
491 match self.waiter.compare_exchange(
492 pending,
493 pending,
494 Ordering::AcqRel,
495 Ordering::Acquire,
496 ) {
497 Ok(_) => return IrqNotifyResult::Pending,
498 Err(current) => {
499 observed = current;
500 continue;
501 }
502 }
503 }
504 if observed == notifying_pending {
505 return IrqNotifyResult::Pending;
506 }
507 if observed == notifying {
508 match self.waiter.compare_exchange(
509 notifying,
510 notifying_pending,
511 Ordering::AcqRel,
512 Ordering::Acquire,
513 ) {
514 Ok(_) => return IrqNotifyResult::Pending,
515 Err(current) => {
516 observed = current;
517 continue;
518 }
519 }
520 }
521 if observed.is_null() {
522 match self.waiter.compare_exchange(
523 ptr::null_mut(),
524 pending,
525 Ordering::Release,
526 Ordering::Acquire,
527 ) {
528 Ok(_) => return IrqNotifyResult::Pending,
529 Err(current) => {
530 observed = current;
531 continue;
532 }
533 }
534 }
535 match self.waiter.compare_exchange(
536 observed,
537 notifying,
538 Ordering::AcqRel,
539 Ordering::Acquire,
540 ) {
541 Ok(waiter) => {
542 let registration = unsafe { take_cell_owner(waiter) };
545 let (generation, result) = Self::wake_registration(®istration);
546 self.finish_notification(result);
547 registration.finish_notification(generation);
548 return Self::notification_result(result);
549 }
550 Err(current) => observed = current,
551 }
552 }
553
554 let waiter = self.waiter.swap(pending, Ordering::AcqRel);
558 if waiter.is_null() || waiter == pending || is_notification_sentinel(waiter) {
559 return IrqNotifyResult::Pending;
560 }
561 let registration = unsafe { take_cell_owner(waiter) };
564 let (generation, result) = Self::wake_registration(®istration);
565 registration.finish_notification(generation);
566 Self::notification_result(result)
567 }
568
569 pub fn is_pending(&self) -> bool {
571 matches!(
572 self.waiter.load(Ordering::Acquire),
573 waiter if waiter == pending_waiter() || waiter == notifying_pending_waiter()
574 )
575 }
576
577 fn wake_registration(registration: &IrqWaitNode) -> (u64, crate::thread::WakeResult) {
578 let generation = registration.begin_notification();
579 let result = registration.wake.wake();
580 (generation, result)
581 }
582
583 fn finish_notification(&self, result: crate::thread::WakeResult) {
584 let pending = pending_waiter();
585 let notifying = notifying_waiter();
586 let notifying_pending = notifying_pending_waiter();
587 let delivered = matches!(
588 result,
589 crate::thread::WakeResult::Notified | crate::thread::WakeResult::AlreadyPending
590 );
591 let mut observed = self.waiter.load(Ordering::Acquire);
592 loop {
593 let next = if observed == notifying {
594 if delivered { ptr::null_mut() } else { pending }
595 } else if observed == notifying_pending {
596 pending
597 } else if observed == pending {
598 return;
599 } else {
600 panic!("IRQ wait cell notification ownership changed while wake was in flight");
601 };
602 match self.waiter.compare_exchange_weak(
603 observed,
604 next,
605 Ordering::AcqRel,
606 Ordering::Acquire,
607 ) {
608 Ok(_) => return,
609 Err(current) => observed = current,
610 }
611 }
612 }
613
614 const fn notification_result(result: crate::thread::WakeResult) -> IrqNotifyResult {
615 match result {
616 crate::thread::WakeResult::Notified | crate::thread::WakeResult::AlreadyPending => {
617 IrqNotifyResult::Notified
618 }
619 crate::thread::WakeResult::Exited | crate::thread::WakeResult::Unavailable => {
620 IrqNotifyResult::Pending
621 }
622 }
623 }
624}
625
626impl Drop for IrqWaitCell {
627 fn drop(&mut self) {
628 let waiter = core::mem::replace(self.waiter.get_mut(), ptr::null_mut());
629 if waiter.is_null() || waiter == pending_waiter() {
630 return;
631 }
632 assert!(
633 !is_notification_sentinel(waiter),
634 "exclusive IRQ wait cell teardown found an in-flight notifier",
635 );
636
637 let registration = unsafe { take_cell_owner(waiter) };
640 let state = registration.state.load(Ordering::Acquire);
641 assert_eq!(
642 registration_phase(state),
643 RegistrationPhase::Attached,
644 "exclusive IRQ wait cell teardown found an in-flight notifier",
645 );
646 registration.cancel(registration_generation(state));
647 }
648}
649
650impl Default for IrqWaitCell {
651 fn default() -> Self {
652 Self::new()
653 }
654}
655
656#[cfg(all(test, not(miri)))]
657mod loom_tests;