Skip to content

Commit 54cfef7

Browse files
committed
feat(smp): re-add an anti-spurious-wakeup feature
This one has tons of comments to convince myself it works. I am relatively convinced, but concurrent processing is hard you know?
1 parent f4942c9 commit 54cfef7

3 files changed

Lines changed: 136 additions & 1 deletion

File tree

src/arch/x86_64/kernel/apic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -886,7 +886,7 @@ pub fn ipi_tlb_flush() {
886886
#[allow(unused_variables)]
887887
pub fn wakeup_core(core_id_to_wakeup: CoreId) {
888888
#[cfg(all(feature = "smp", not(feature = "idle-poll")))]
889-
if core_id_to_wakeup != core_id() && !processor::supports_mwait() {
889+
if core_id_to_wakeup != core_id() && !processor::supports_mwait() && scheduler::core_wake_up(core_id_to_wakeup) {
890890
without_interrupts(|| {
891891
let apic_ids = CPU_LOCAL_APIC_IDS.lock();
892892
let local_apic_id = apic_ids[core_id_to_wakeup as usize];

src/arch/x86_64/kernel/interrupts.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ pub(crate) fn enable_and_wait() {
8282
);
8383
}
8484
} else {
85+
#[cfg(feature = "smp")]
86+
if !scheduler::core_sleep() {
87+
return;
88+
}
89+
8590
enable_and_hlt();
8691
}
8792
}

src/scheduler/mod.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ use alloc::sync::Arc;
88
use alloc::vec::Vec;
99
use core::cell::RefCell;
1010
use core::ptr;
11+
#[cfg(all(target_arch = "x86_64", feature = "smp", not(feature = "idle-poll")))]
12+
use core::sync::atomic::AtomicU8;
1113
use core::sync::atomic::{AtomicI32, AtomicU32, Ordering};
1214

1315
use ahash::RandomState;
1416
use crossbeam_utils::Backoff;
1517
use hashbrown::{HashMap, hash_map};
1618
use hermit_sync::*;
19+
use mem_barrier::{BarrierKind, BarrierType};
1720
#[cfg(target_arch = "riscv64")]
1821
use riscv::register::sstatus;
1922
use timer_interrupts::TimerList;
@@ -44,6 +47,129 @@ static WAITING_TASKS: InterruptTicketMutex<BTreeMap<TaskId, VecDeque<TaskHandle>
4447
/// Map between Task ID and TaskHandle
4548
static TASKS: InterruptTicketMutex<BTreeMap<TaskId, TaskHandle>> =
4649
InterruptTicketMutex::new(BTreeMap::new());
50+
#[cfg(all(target_arch = "x86_64", feature = "smp", not(feature = "idle-poll")))]
51+
static CORE_HLT_STATE: RwSpinLock<Vec<CoreState>> = RwSpinLock::new(Vec::new());
52+
53+
#[cfg(all(target_arch = "x86_64", feature = "smp", not(feature = "idle-poll")))]
54+
struct CoreState(AtomicU8);
55+
56+
/// # Race condition prevention
57+
///
58+
/// Two methods matter here: [arch::x86_64::interrupts::enable_and_wait] and [arch::x86_64::apic::wakeup_core].
59+
/// `enable_and_wait` checks the status, ensuring it is set to 0+setting it to 1, then sleeps.
60+
/// `wakeup_core` checks the status, ensuring it is set to 1+setting it to 0, then issues interrupt.
61+
///
62+
/// A race would happen if `go_to_sleep` returns true, and `wake_up` returns false.
63+
/// The result of these two methods must therefore be atomically determined in a single operation.
64+
/// No ordering should exist in which that condition can happen.
65+
/// Any other variant is okay:
66+
/// - if `go_to_sleep` is false and `wake_up` is true, we have a un-necessary core wakeup (which is okay)
67+
/// - if both are false, the core is not entering sleep and is therefore not woken up
68+
/// - if both are true, the core sleeps and is woken up
69+
#[cfg(all(target_arch = "x86_64", feature = "smp", not(feature = "idle-poll")))]
70+
impl CoreState {
71+
/// The core is currently busy and should not be interrupted for new tasks
72+
const STATUS_ACTIVE: u8 = 0;
73+
74+
/// The core is currently halted and can be interrupted for new tasks
75+
const STATUS_IDLE: u8 = 1;
76+
77+
/// Another core tried to wake up this core but it was already active.
78+
const STATUS_DONT_SLEEP: u8 = 2;
79+
80+
pub fn new() -> Self {
81+
Self(AtomicU8::new(CoreState::STATUS_ACTIVE))
82+
}
83+
84+
#[inline(always)]
85+
pub fn set_active(&self) {
86+
self.0.store(CoreState::STATUS_ACTIVE, Ordering::SeqCst);
87+
}
88+
89+
/// Indicates that this core will go to HLT.
90+
/// This must be called *before* entering a HLT loop, and *with interrupts disabled*.
91+
/// Returns a boolean indicating if the core should enter the HLT loop or not
92+
#[inline(always)]
93+
pub fn go_to_sleep(&self) -> bool {
94+
if self
95+
.0
96+
.compare_exchange(
97+
CoreState::STATUS_ACTIVE,
98+
CoreState::STATUS_IDLE,
99+
Ordering::SeqCst,
100+
Ordering::Relaxed,
101+
)
102+
.is_err()
103+
{
104+
// There is a possible race condition here, because the two atomic operations can be
105+
// interleaved, but this has no effect on the final result: we're not going to sleep
106+
// anyway.
107+
// If the state was already set to ACTIVE, this has no effect.
108+
// Normally, nothing can set the state to IDLE except this method.
109+
// So the race should have no effect.
110+
// IN ANY CASE: as per top-level comments, returning false is the safe default here.
111+
self.set_active();
112+
false
113+
} else {
114+
// We have correctly read status ACTIVE and set STATUS_IDLE. We go to sleep. This is
115+
// safe because:
116+
// - Another `wake_up` could be processing, either BEFORE or AFTER the atomic `swap`.
117+
// * BEFORE:
118+
// We have set the state to STATUS_IDLE, so `swap` will read that value and
119+
// wake_up will return true ==> SAFE.
120+
// * AFTER: **We cannot be here!** Indeed, `swap` has set the status to
121+
// `STATUS_DONT_SLEEP`, so `compare_exchange` is in the `Err` case.
122+
true
123+
}
124+
}
125+
126+
/// Request to wake up this core.
127+
/// Returns a boolean indicating if an interrupt should be sent, or if the core is already active
128+
#[inline(always)]
129+
pub fn wake_up(&self) -> bool {
130+
// Ask the core not to sleep.
131+
// This makes sure that if the two atomic operations become interleaved, the core will
132+
// not go to sleep with us assuming it was running.
133+
let previous_state = self.0.swap(CoreState::STATUS_DONT_SLEEP, Ordering::SeqCst);
134+
135+
// If the core was idle, we can actually wake it up
136+
if previous_state == CoreState::STATUS_IDLE {
137+
// Again, this operation is not necessarily ordered.
138+
// - Another `go_to_sleep` could be processing, either BEFORE or AFTER the atomic op.
139+
// * BEFORE:
140+
// We have set the state to STATUS_DONT_SLEEP, so the atomic op will read that value
141+
// and go_to_sleep will return false. We will send an interrupt which could have
142+
// been avoided, but that's okay.
143+
// * AFTER:
144+
// The value read at the atomic OP does not matter here, in any case we WILL send
145+
// the interrupt and wake the core up.
146+
// IN ANY CASE: as per top-level comments, returning true is the safe default here.
147+
self.set_active();
148+
true
149+
} else {
150+
// We don't wake up the core. This is safe, because:
151+
// - Another `go_to_sleep` could be processing, either BEFORE or AFTER the atomic op.
152+
// * BEFORE:
153+
// We have set the state to STATUS_DONT_SLEEP, so the atomic op will read that value
154+
// and go_to_sleep will return false ==> SAFE.
155+
// * AFTER: **We cannot be here!** Indeed, the atomic operation in `go_to_sleep`
156+
// also sets the state to STATUS_IDLE, so `previous_state` MUST BE STATUS_IDLE.
157+
false
158+
}
159+
}
160+
}
161+
162+
#[inline]
163+
#[cfg(all(target_arch = "x86_64", feature = "smp", not(feature = "idle-poll")))]
164+
pub fn core_sleep() -> bool {
165+
CORE_HLT_STATE.read()[usize::try_from(core_id()).unwrap()].go_to_sleep()
166+
}
167+
168+
#[inline]
169+
#[cfg(all(target_arch = "x86_64", feature = "smp", not(feature = "idle-poll")))]
170+
pub fn core_wake_up(core_id: CoreId) -> bool {
171+
CORE_HLT_STATE.read()[usize::try_from(core_id).unwrap()].wake_up()
172+
}
47173

48174
/// Unique identifier for a core.
49175
pub type CoreId = u32;
@@ -887,6 +1013,10 @@ pub(crate) fn add_current_core() {
8871013
core_id.try_into().unwrap(),
8881014
&CoreLocal::get().scheduler_input,
8891015
);
1016+
#[cfg(all(target_arch = "x86_64", not(feature = "idle-poll")))]
1017+
CORE_HLT_STATE
1018+
.write()
1019+
.insert(core_id.try_into().unwrap(), CoreState::new());
8901020
}
8911021
}
8921022

0 commit comments

Comments
 (0)