kernel_api/sync/irq_cell.rs
1use core::cell::{Cell, UnsafeCell};
2use core::fmt::{Debug, Formatter};
3use core::marker::{PhantomData, Unsize};
4use core::mem::ManuallyDrop;
5use core::ops::{CoerceUnsized, Deref, DerefMut, DispatchFromDyn};
6
7/// An interrupt safe, single-threaded mutable memory location.
8///
9/// This prevents deadlocks between interrupt handlers and main kernel code by
10/// keeping interrupts disabled during the time a lock is held.
11#[clippy::has_significant_drop]
12pub struct IrqCell<T: ?Sized> {
13 state: Cell<Option<usize>>,
14 data: UnsafeCell<T>
15}
16
17impl<T> IrqCell<T> {
18 /// Creates a new `IrqCell` containing `val`.
19 ///
20 /// # Examples
21 ///
22 /// ```
23 /// use kernel_api::sync::IrqCell;
24 ///
25 /// let c = IrqCell::new(5);
26 pub const fn new(val: T) -> Self {
27 Self { state: Cell::new(None), data: UnsafeCell::new(val) }
28 }
29}
30
31impl<T: ?Sized> IrqCell<T> {
32 /// Locks the `IrqCell`, disabling interrupts on the current CPU.
33 ///
34 /// # Panics
35 ///
36 /// This function will panic if the `IrqCell` is already locked by the current CPU.
37 ///
38 /// # Examples
39 ///
40 /// ```
41 /// use kernel_api::sync::IrqCell;
42 ///
43 /// let c = IrqCell::new(5);
44 /// {
45 /// let mut guard = c.lock();
46 /// // Interrupts are now disabled to the end of this block
47 /// *guard = 10;
48 /// }
49 ///
50 /// assert_eq!(*c.lock(), 10);
51 /// ```
52 ///
53 /// Causing a deadlock will cause a panic:
54 ///
55 /// ```should_panic
56 /// use kernel_api::sync::IrqCell;
57 ///
58 /// let c = IrqCell::new(5);
59 /// let x = c.lock();
60 /// let y = c.lock();
61 /// ```
62 pub fn lock(&self) -> IrqGuard<'_, T> {
63 // Unsafety: is this actually needed?
64 assert!(self.state.get().is_none(), "IrqCell cannot be borrowed multiple times");
65
66 self.state.set(Some(crate::bridge::irq::disable()));
67 IrqGuard { cell: self, _phantom_not_send: PhantomData }
68 }
69
70 /// Creates a new [`IrqGuard`] without checking if the `IrqCell` is locked.
71 ///
72 /// # Safety
73 ///
74 /// The `IrqCell` must already be logically locked by the current CPU.
75 ///
76 /// Only one `IrqGuard` must exist at any one time.
77 pub unsafe fn make_guard_unchecked(&self) -> IrqGuard<'_, T> {
78 // Unsafety: is this actually needed?
79 debug_assert!(self.state.get().is_some(), "Created IrqGuard for unlocked IrqCell");
80
81 IrqGuard { cell: self, _phantom_not_send: PhantomData }
82 }
83
84 /// Forcibly unlocks the `IrqCell`.
85 ///
86 /// This is useful when combined with `mem::forget` to keep an `IrqCell`
87 /// locked without holding onto a [`IrqGuard`].
88 ///
89 /// # Safety
90 ///
91 /// The current CPU must logically own an [`IrqGuard`] that has discarded in a way
92 /// that prevents its [`Drop`] impl being called.
93 pub unsafe fn unlock(&self) {
94 let old_state = self.state.take();
95 // SAFETY: `old_state` always exists while lock is held, which caller guarantees
96 crate::bridge::irq::set(unsafe { old_state.unwrap_unchecked() });
97 }
98}
99
100impl<T: Debug> Debug for IrqCell<T> {
101 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
102 let guard = self.lock();
103 f.debug_struct("IrqCell")
104 .field("data", &*guard)
105 .finish_non_exhaustive()?;
106 IrqGuard::unlock(guard);
107 Ok(())
108 }
109}
110
111/// An RAII implementation of a "scoped lock" of an [`IrqCell`]. When this structure is
112/// dropped (falls out of scope), the lock will be unlocked.
113///
114/// The data protected by the `IrqCell` can be accessed through this guard via its
115/// [`Deref`] and [`DerefMut`] implementations.
116///
117/// This structure is created by the [`lock`](`IrqCell::lock`) and
118/// [`make_guard_unchecked`](`IrqCell::make_guard_unchecked`) methods on
119/// [`IrqCell`].
120pub struct IrqGuard<'cell, T: ?Sized> {
121 cell: &'cell IrqCell<T>,
122 _phantom_not_send: PhantomData<*mut u8>, // Dropping guard on other core would cause interrupts to be enabled in the wrong place
123}
124
125impl<T: ?Sized> IrqGuard<'_, T> {
126 /// Unlocks the [`IrqCell`], keeping interrupts disabled regardless of their previous state.
127 ///
128 ///
129 pub fn unlock_no_interrupts(this: Self) {
130 let this = ManuallyDrop::new(this);
131 this.cell.state.take();
132 }
133
134 /// Unlocks the [`IrqCell`], restoring interrupts to their previous state.
135 ///
136 /// This is equivalent to dropping the `IrqGuard` but more explicit.
137 fn unlock(this: Self) {
138 drop(this);
139 }
140}
141
142impl<T: Debug> Debug for IrqGuard<'_, T> {
143 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
144 f.debug_struct("IrqGuard")
145 .field("cell", &**self)
146 .finish()
147 }
148}
149
150impl<T: ?Sized> Deref for IrqGuard<'_, T> {
151 type Target = T;
152
153 fn deref(&self) -> &T {
154 // SAFETY: Own IrqGuard therefore must have exclusive access to the data stored in the IrqCell
155 unsafe { &*self.cell.data.get() }
156 }
157}
158
159impl<T: ?Sized> DerefMut for IrqGuard<'_, T> {
160 fn deref_mut(&mut self) -> &mut T {
161 // SAFETY: Own IrqGuard therefore must have exclusive access to the data stored in the IrqCell
162 unsafe { &mut *self.cell.data.get() }
163 }
164}
165
166impl<T: ?Sized> Drop for IrqGuard<'_, T> {
167 fn drop(&mut self) {
168 // SAFETY: called from IrqGuard drop impl so will only be called once and caller must hold an IrqGuard
169 unsafe { self.cell.unlock(); }
170 }
171}
172
173impl<'a, T: ?Sized + Unsize<U>, U: ?Sized> DispatchFromDyn<IrqGuard<'a, U>> for IrqGuard<'a, T> {}
174impl<'b, T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<IrqGuard<'b, U>> for IrqGuard<'b, T> {}