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