Skip to main content

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> {}