Skip to content

Commit c854baa

Browse files
committed
locking: define lock types that use TPR for reentrancy protection
Using TPR for reentrancy protection means that higher priority interrupts can continue to be delivered while locks are held. Each TPR-protected lock defines the TPR with which it is associated, so any attempt to acquire the lock from a higher priority interrupt will panic due to TPR inversion. Signed-off-by: Jon Lange <[email protected]>
1 parent f507cb9 commit c854baa

File tree

5 files changed

+122
-34
lines changed

5 files changed

+122
-34
lines changed

kernel/src/locking/common.rs

+41-17
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,77 @@
33
// Copyright (c) 2024 SUSE LLC
44
//
55
// Author: Joerg Roedel <[email protected]>
6-
use crate::cpu::IrqGuard;
6+
use crate::cpu::{IrqGuard, TprGuard};
77
use core::marker::PhantomData;
88

9-
/// Abstracts IRQ state handling when taking and releasing locks. There are two
10-
/// implemenations:
9+
/// Abstracts TPR and interrupt state handling when taking and releasing
10+
/// locks. There are three implemenations:
1111
///
1212
/// * [IrqUnsafeLocking] implements the methods as no-ops and does not change
13-
/// any IRQ state.
14-
/// * [IrqSafeLocking] actually disables and enables IRQs in the methods,
15-
/// making a lock IRQ-safe by using this structure.
13+
/// any IRQ or TPR state.
14+
/// * [IrqGuardLocking] actually disables and enables IRQs in the methods,
15+
/// ensuring that no interrupt can be taken while the lock is held.
16+
/// * [TprGuardLocking] raises and lowers TPR while the lock is held,
17+
/// ensuring that no higher priority interrupt can be taken while the lock
18+
/// is held. This will panic when attempting to acquire a lower priority
19+
/// lock from a higher priority interrupt context.
1620
pub trait IrqLocking {
17-
/// Associated helper function to disable IRQs and create an instance of
18-
/// the implementing struct. This is used by lock implementations.
21+
/// Associated helper function to modify TPR/interrupt state when a lock
22+
/// is acquired. This is used by lock implementations and will return an
23+
/// instance of the object.
1924
///
2025
/// # Returns
2126
///
2227
/// New instance of implementing struct.
23-
fn irqs_disable() -> Self;
28+
fn acquire_lock() -> Self;
2429
}
2530

26-
/// Implements the IRQ state handling methods as no-ops. For use it IRQ-unsafe
27-
/// locks.
31+
/// Implements the IRQ state handling methods as no-ops. Locks defined with
32+
/// this state handler are not safe with respect to reentrancy due to
33+
/// interrupt delivery.
2834
#[derive(Debug, Default)]
2935
pub struct IrqUnsafeLocking;
3036

3137
impl IrqLocking for IrqUnsafeLocking {
32-
fn irqs_disable() -> Self {
38+
fn acquire_lock() -> Self {
3339
Self {}
3440
}
3541
}
3642

37-
/// Properly implements the IRQ state handling methods. For use it IRQ-safe
38-
/// locks.
43+
/// Implements the state handling methods for locks that disable interrupts.
3944
#[derive(Debug, Default)]
40-
pub struct IrqSafeLocking {
45+
pub struct IrqGuardLocking {
4146
/// IrqGuard to keep track of IRQ state. IrqGuard implements Drop, which
4247
/// will re-enable IRQs when the struct goes out of scope.
4348
_guard: IrqGuard,
4449
/// Make type explicitly !Send + !Sync
4550
phantom: PhantomData<*const ()>,
4651
}
4752

48-
impl IrqLocking for IrqSafeLocking {
49-
fn irqs_disable() -> Self {
53+
impl IrqLocking for IrqGuardLocking {
54+
fn acquire_lock() -> Self {
5055
Self {
5156
_guard: IrqGuard::new(),
5257
phantom: PhantomData,
5358
}
5459
}
5560
}
61+
62+
/// Implements the state handling methods for locks that raise and lower TPR.
63+
#[derive(Debug, Default)]
64+
pub struct TprGuardLocking<const TPR: usize> {
65+
/// TprGuard to keep track of IRQ state. TprGuard implements Drop, which
66+
/// will lower TPR as required when the struct goes out of scope.
67+
_guard: TprGuard,
68+
/// Make type explicitly !Send + !Sync
69+
phantom: PhantomData<*const ()>,
70+
}
71+
72+
impl<const TPR: usize> IrqLocking for TprGuardLocking<TPR> {
73+
fn acquire_lock() -> Self {
74+
Self {
75+
_guard: TprGuard::raise(TPR),
76+
phantom: PhantomData,
77+
}
78+
}
79+
}

kernel/src/locking/mod.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ pub mod common;
88
pub mod rwlock;
99
pub mod spinlock;
1010

11-
pub use common::{IrqLocking, IrqSafeLocking, IrqUnsafeLocking};
11+
pub use common::{IrqGuardLocking, IrqLocking, TprGuardLocking};
1212
pub use rwlock::{
13-
RWLock, RWLockIrqSafe, ReadLockGuard, ReadLockGuardIrqSafe, WriteLockGuard,
14-
WriteLockGuardIrqSafe,
13+
RWLock, RWLockAnyTpr, RWLockIrqSafe, RWLockTpr, ReadLockGuard, ReadLockGuardAnyTpr,
14+
ReadLockGuardIrqSafe, WriteLockGuard, WriteLockGuardAnyTpr, WriteLockGuardIrqSafe,
15+
};
16+
pub use spinlock::{
17+
LockGuard, LockGuardAnyTpr, LockGuardIrqSafe, RawLockGuard, SpinLock, SpinLockAnyTpr,
18+
SpinLockIrqSafe, SpinLockTpr,
1519
};
16-
pub use spinlock::{LockGuard, LockGuardIrqSafe, RawLockGuard, SpinLock, SpinLockIrqSafe};

kernel/src/locking/rwlock.rs

+42-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Author: Joerg Roedel <[email protected]>
66

77
use super::common::*;
8+
use crate::types::TPR_LOCK;
89
use core::cell::UnsafeCell;
910
use core::marker::PhantomData;
1011
use core::ops::{Deref, DerefMut};
@@ -41,7 +42,9 @@ impl<T, I> Deref for RawReadLockGuard<'_, T, I> {
4142
}
4243

4344
pub type ReadLockGuard<'a, T> = RawReadLockGuard<'a, T, IrqUnsafeLocking>;
44-
pub type ReadLockGuardIrqSafe<'a, T> = RawReadLockGuard<'a, T, IrqSafeLocking>;
45+
pub type ReadLockGuardIrqSafe<'a, T> = RawReadLockGuard<'a, T, IrqGuardLocking>;
46+
pub type ReadLockGuardAnyTpr<'a, T, const TPR: usize> =
47+
RawReadLockGuard<'a, T, TprGuardLocking<TPR>>;
4548

4649
/// A guard that provides exclusive write access to the data protected by [`RWLock`]
4750
#[derive(Debug)]
@@ -81,7 +84,9 @@ impl<T, I> DerefMut for RawWriteLockGuard<'_, T, I> {
8184
}
8285

8386
pub type WriteLockGuard<'a, T> = RawWriteLockGuard<'a, T, IrqUnsafeLocking>;
84-
pub type WriteLockGuardIrqSafe<'a, T> = RawWriteLockGuard<'a, T, IrqSafeLocking>;
87+
pub type WriteLockGuardIrqSafe<'a, T> = RawWriteLockGuard<'a, T, IrqGuardLocking>;
88+
pub type WriteLockGuardAnyTpr<'a, T, const TPR: usize> =
89+
RawWriteLockGuard<'a, T, TprGuardLocking<TPR>>;
8590

8691
/// A simple Read-Write Lock (RWLock) that allows multiple readers or
8792
/// one exclusive writer.
@@ -216,7 +221,7 @@ impl<T, I: IrqLocking> RawRWLock<T, I> {
216221
///
217222
/// A [`ReadLockGuard`] that provides read access to the protected data.
218223
pub fn lock_read(&self) -> RawReadLockGuard<'_, T, I> {
219-
let irq_state = I::irqs_disable();
224+
let irq_state = I::acquire_lock();
220225
loop {
221226
let val = self.wait_for_writers();
222227
let (readers, _) = split_val(val);
@@ -246,7 +251,7 @@ impl<T, I: IrqLocking> RawRWLock<T, I> {
246251
///
247252
/// A [`WriteLockGuard`] that provides write access to the protected data.
248253
pub fn lock_write(&self) -> RawWriteLockGuard<'_, T, I> {
249-
let irq_state = I::irqs_disable();
254+
let irq_state = I::acquire_lock();
250255

251256
// Waiting for current writer to finish
252257
loop {
@@ -277,7 +282,9 @@ impl<T, I: IrqLocking> RawRWLock<T, I> {
277282
}
278283

279284
pub type RWLock<T> = RawRWLock<T, IrqUnsafeLocking>;
280-
pub type RWLockIrqSafe<T> = RawRWLock<T, IrqSafeLocking>;
285+
pub type RWLockIrqSafe<T> = RawRWLock<T, IrqGuardLocking>;
286+
pub type RWLockAnyTpr<T, const TPR: usize> = RawRWLock<T, TprGuardLocking<TPR>>;
287+
pub type RWLockTpr<T> = RWLockAnyTpr<T, { TPR_LOCK }>;
281288

282289
mod tests {
283290
#[test]
@@ -380,7 +387,7 @@ mod tests {
380387

381388
// Lock for read
382389
let guard = lock.lock_read();
383-
// IRQs must still be enabled;
390+
// IRQs must be disabled
384391
assert!(irqs_disabled());
385392
// Unlock
386393
drop(guard);
@@ -391,4 +398,33 @@ mod tests {
391398
raw_irqs_disable();
392399
}
393400
}
401+
402+
#[test]
403+
#[cfg_attr(not(test_in_svsm), ignore = "Can only be run inside guest")]
404+
fn rw_lock_tpr() {
405+
use crate::cpu::irq_state::raw_get_tpr;
406+
use crate::locking::*;
407+
use crate::types::TPR_LOCK;
408+
409+
assert_eq!(raw_get_tpr(), 0);
410+
let lock = RWLockTpr::new(0);
411+
412+
// Lock for write
413+
let guard = lock.lock_write();
414+
// TPR must be raised
415+
assert_eq!(raw_get_tpr(), TPR_LOCK);
416+
// Unlock
417+
drop(guard);
418+
// TPR must be restored
419+
assert_eq!(raw_get_tpr(), 0);
420+
421+
// Lock for read
422+
let guard = lock.lock_read();
423+
// TPR must be raised
424+
assert_eq!(raw_get_tpr(), TPR_LOCK);
425+
// Unlock
426+
drop(guard);
427+
// TPR must be restored
428+
assert_eq!(raw_get_tpr(), 0);
429+
}
394430
}

kernel/src/locking/spinlock.rs

+29-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Author: Joerg Roedel <[email protected]>
66

77
use super::common::*;
8+
use crate::types::TPR_LOCK;
89
use core::cell::UnsafeCell;
910
use core::marker::PhantomData;
1011
use core::ops::{Deref, DerefMut};
@@ -29,7 +30,7 @@ use core::sync::atomic::{AtomicU64, Ordering};
2930
/// ```
3031
#[derive(Debug)]
3132
#[must_use = "if unused the SpinLock will immediately unlock"]
32-
pub struct RawLockGuard<'a, T, I = IrqUnsafeLocking> {
33+
pub struct RawLockGuard<'a, T, I> {
3334
holder: &'a AtomicU64,
3435
data: &'a mut T,
3536
#[expect(dead_code)]
@@ -64,7 +65,8 @@ impl<T, I> DerefMut for RawLockGuard<'_, T, I> {
6465
}
6566

6667
pub type LockGuard<'a, T> = RawLockGuard<'a, T, IrqUnsafeLocking>;
67-
pub type LockGuardIrqSafe<'a, T> = RawLockGuard<'a, T, IrqSafeLocking>;
68+
pub type LockGuardIrqSafe<'a, T> = RawLockGuard<'a, T, IrqGuardLocking>;
69+
pub type LockGuardAnyTpr<'a, T, const TPR: usize> = RawLockGuard<'a, T, TprGuardLocking<TPR>>;
6870

6971
/// A simple ticket-spinlock implementation for protecting concurrent data
7072
/// access.
@@ -95,7 +97,7 @@ pub type LockGuardIrqSafe<'a, T> = RawLockGuard<'a, T, IrqSafeLocking>;
9597
/// };
9698
/// ```
9799
#[derive(Debug, Default)]
98-
pub struct RawSpinLock<T, I = IrqUnsafeLocking> {
100+
pub struct RawSpinLock<T, I> {
99101
/// This atomic counter is incremented each time a thread attempts to
100102
/// acquire the lock. It helps to determine the order in which threads
101103
/// acquire the lock.
@@ -150,7 +152,7 @@ impl<T, I: IrqLocking> RawSpinLock<T, I> {
150152
/// }; // Lock is automatically released when `guard` goes out of scope.
151153
/// ```
152154
pub fn lock(&self) -> RawLockGuard<'_, T, I> {
153-
let irq_state = I::irqs_disable();
155+
let irq_state = I::acquire_lock();
154156

155157
let ticket = self.current.fetch_add(1, Ordering::Relaxed);
156158
loop {
@@ -172,7 +174,7 @@ impl<T, I: IrqLocking> RawSpinLock<T, I> {
172174
/// successfully acquired, it returns a [`LockGuard`] that automatically
173175
/// releases the lock when it goes out of scope.
174176
pub fn try_lock(&self) -> Option<RawLockGuard<'_, T, I>> {
175-
let irq_state = I::irqs_disable();
177+
let irq_state = I::acquire_lock();
176178

177179
let current = self.current.load(Ordering::Relaxed);
178180
let holder = self.holder.load(Ordering::Acquire);
@@ -198,13 +200,16 @@ impl<T, I: IrqLocking> RawSpinLock<T, I> {
198200
}
199201

200202
pub type SpinLock<T> = RawSpinLock<T, IrqUnsafeLocking>;
201-
pub type SpinLockIrqSafe<T> = RawSpinLock<T, IrqSafeLocking>;
203+
pub type SpinLockIrqSafe<T> = RawSpinLock<T, IrqGuardLocking>;
204+
pub type SpinLockAnyTpr<T, const TPR: usize> = RawSpinLock<T, TprGuardLocking<TPR>>;
205+
pub type SpinLockTpr<T> = SpinLockAnyTpr<T, { TPR_LOCK }>;
202206

203207
#[cfg(test)]
204208
mod tests {
205209
use super::*;
206-
use crate::cpu::irq_state::{raw_irqs_disable, raw_irqs_enable};
210+
use crate::cpu::irq_state::{raw_get_tpr, raw_irqs_disable, raw_irqs_enable};
207211
use crate::cpu::{irqs_disabled, irqs_enabled};
212+
use crate::types::TPR_LOCK;
208213

209214
#[test]
210215
fn test_spin_lock() {
@@ -277,4 +282,21 @@ mod tests {
277282
raw_irqs_disable();
278283
}
279284
}
285+
286+
#[test]
287+
#[cfg_attr(not(test_in_svsm), ignore = "Can only be run inside guest")]
288+
fn spin_trylock_tpr() {
289+
assert_eq!(raw_get_tpr(), 0);
290+
291+
let spin_lock = SpinLockTpr::new(0);
292+
293+
// TPR is zero - taking the lock must succeed and raise TPR.
294+
let g1 = spin_lock.try_lock();
295+
assert!(g1.is_some());
296+
assert_eq!(raw_get_tpr(), TPR_LOCK);
297+
298+
// Release lock and check if that resets TPR.
299+
drop(g1);
300+
assert_eq!(raw_get_tpr(), 0);
301+
}
280302
}

kernel/src/types.rs

+3
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,6 @@ impl TryFrom<usize> for Bytes {
9393
}
9494
}
9595
}
96+
97+
pub const TPR_NORMAL: usize = 0;
98+
pub const TPR_LOCK: usize = 2;

0 commit comments

Comments
 (0)