@@ -8,12 +8,15 @@ use alloc::sync::Arc;
88use alloc:: vec:: Vec ;
99use core:: cell:: RefCell ;
1010use core:: ptr;
11+ #[ cfg( all( target_arch = "x86_64" , feature = "smp" , not( feature = "idle-poll" ) ) ) ]
12+ use core:: sync:: atomic:: AtomicU8 ;
1113use core:: sync:: atomic:: { AtomicI32 , AtomicU32 , Ordering } ;
1214
1315use ahash:: RandomState ;
1416use crossbeam_utils:: Backoff ;
1517use hashbrown:: { HashMap , hash_map} ;
1618use hermit_sync:: * ;
19+ use mem_barrier:: { BarrierKind , BarrierType } ;
1720#[ cfg( target_arch = "riscv64" ) ]
1821use riscv:: register:: sstatus;
1922use timer_interrupts:: TimerList ;
@@ -44,6 +47,129 @@ static WAITING_TASKS: InterruptTicketMutex<BTreeMap<TaskId, VecDeque<TaskHandle>
4447/// Map between Task ID and TaskHandle
4548static 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.
49175pub 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