Skip to main content

ax_task/sched/system/cpu/remote/
lifecycle.rs

1use super::*;
2
3const CPU_LIFECYCLE_OFFLINE: usize = 1 << (usize::BITS - 1);
4const CPU_LIFECYCLE_INACTIVE: usize = 1 << (usize::BITS - 2);
5const CPU_LIFECYCLE_DRAINING: usize = CPU_LIFECYCLE_OFFLINE | CPU_LIFECYCLE_INACTIVE;
6const CPU_LIFECYCLE_MASK: usize = CPU_LIFECYCLE_DRAINING;
7const CPU_PUBLICATION_COUNT_MASK: usize = !CPU_LIFECYCLE_MASK;
8const CPU_PUBLICATION_OVERFLOW_INVARIANT: u32 = 0x4350_5542;
9const CPU_PUBLICATION_RELEASE_INVARIANT: u32 = 0x4350_5544;
10
11pub(super) const INITIAL_CPU_LIFECYCLE_STATE: usize = CPU_LIFECYCLE_OFFLINE;
12
13#[derive(Clone, Copy)]
14enum CpuPublicationClass {
15    Placement,
16    OwnerControl,
17}
18
19impl CpuPublicationClass {
20    const fn accepts(self, state: usize) -> bool {
21        match self {
22            Self::Placement => state & CPU_LIFECYCLE_MASK == 0,
23            Self::OwnerControl => state & CPU_LIFECYCLE_OFFLINE == 0,
24        }
25    }
26}
27
28#[derive(Debug)]
29pub(super) struct CpuPublicationState {
30    state: AtomicUsize,
31}
32
33impl CpuPublicationState {
34    pub(super) const fn new() -> Self {
35        Self {
36            state: AtomicUsize::new(INITIAL_CPU_LIFECYCLE_STATE),
37        }
38    }
39}
40
41/// Placement and remote-publication state of one logical CPU.
42#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub enum CpuLifecycleState {
44    /// The CPU accepts placement and remote scheduler publications.
45    Online,
46    /// New placement is closed while owner-directed control delivery may finish.
47    Inactive,
48    /// Every remote publication is closed while the owner proves work is gone.
49    Draining,
50    /// The CPU owns no schedulable work and is absent from the root domain.
51    Offline,
52}
53
54impl CpuRemote {
55    /// Returns the CPU's placement and publication lifecycle.
56    pub fn lifecycle_state(&self) -> CpuLifecycleState {
57        match self.publication.state.load(Ordering::Acquire) & CPU_LIFECYCLE_MASK {
58            0 => CpuLifecycleState::Online,
59            CPU_LIFECYCLE_INACTIVE => CpuLifecycleState::Inactive,
60            CPU_LIFECYCLE_DRAINING => CpuLifecycleState::Draining,
61            CPU_LIFECYCLE_OFFLINE => CpuLifecycleState::Offline,
62            _ => unreachable!("CPU lifecycle mask has four encoded states"),
63        }
64    }
65
66    /// Returns whether owner initialization and online publication completed.
67    pub fn is_online(&self) -> bool {
68        matches!(
69            self.lifecycle_state(),
70            CpuLifecycleState::Online | CpuLifecycleState::Inactive
71        )
72    }
73
74    /// Returns whether new runnable placement may target this CPU.
75    pub(crate) fn accepts_placement(&self) -> bool {
76        self.lifecycle_state() == CpuLifecycleState::Online
77    }
78
79    pub(crate) fn mark_online(&self) -> bool {
80        self.publication
81            .state
82            .compare_exchange(
83                CPU_LIFECYCLE_OFFLINE,
84                0,
85                Ordering::Release,
86                Ordering::Acquire,
87            )
88            .is_ok()
89    }
90
91    pub(crate) fn try_deactivate(&self) -> bool {
92        // Matching the exact zero-valued Online state proves that no target-rq
93        // placement transaction spans the transition. Owner-directed control
94        // delivery remains allowed until final draining.
95        let inactive = self
96            .publication
97            .state
98            .compare_exchange(
99                0,
100                CPU_LIFECYCLE_INACTIVE,
101                Ordering::AcqRel,
102                Ordering::Acquire,
103            )
104            .is_ok();
105        if inactive {
106            self.cancel_idle_pull_if_uncommitted();
107        }
108        inactive
109    }
110
111    pub(crate) fn cancel_deactivation(&self) {
112        let mut current = self.publication.state.load(Ordering::Acquire);
113        loop {
114            if current & CPU_LIFECYCLE_MASK != CPU_LIFECYCLE_INACTIVE {
115                task_runtime::fatal_invariant(
116                    CPU_PUBLICATION_RELEASE_INVARIANT,
117                    self.owner.as_u32() as usize,
118                );
119            }
120            let online = current & !CPU_LIFECYCLE_INACTIVE;
121            match self.publication.state.compare_exchange_weak(
122                current,
123                online,
124                Ordering::Release,
125                Ordering::Acquire,
126            ) {
127                Ok(_) => return,
128                Err(actual) => current = actual,
129            }
130        }
131    }
132
133    pub(crate) fn try_begin_draining(&self) -> bool {
134        // An exact inactive state also proves that every owner-directed control
135        // delivery has completed its publication and doorbell transaction.
136        self.publication
137            .state
138            .compare_exchange(
139                CPU_LIFECYCLE_INACTIVE,
140                CPU_LIFECYCLE_DRAINING,
141                Ordering::AcqRel,
142                Ordering::Acquire,
143            )
144            .is_ok()
145    }
146
147    pub(crate) fn cancel_draining(&self) {
148        if self
149            .publication
150            .state
151            .compare_exchange(
152                CPU_LIFECYCLE_DRAINING,
153                0,
154                Ordering::Release,
155                Ordering::Acquire,
156            )
157            .is_err()
158        {
159            task_runtime::fatal_invariant(
160                CPU_PUBLICATION_RELEASE_INVARIANT,
161                self.owner.as_u32() as usize,
162            );
163        }
164    }
165
166    pub(crate) fn finish_offline(&self) {
167        self.reset_scheduler_for_offline();
168        if self
169            .publication
170            .state
171            .compare_exchange(
172                CPU_LIFECYCLE_DRAINING,
173                CPU_LIFECYCLE_OFFLINE,
174                Ordering::Release,
175                Ordering::Acquire,
176            )
177            .is_err()
178        {
179            task_runtime::fatal_invariant(
180                CPU_PUBLICATION_RELEASE_INVARIANT,
181                self.owner.as_u32() as usize,
182            );
183        }
184    }
185
186    pub(crate) fn begin_publication(&self) -> Option<CpuRemotePublication<'_>> {
187        self.try_acquire_publication(CpuPublicationClass::Placement)
188            .then(|| CpuRemotePublication { remote: self })
189    }
190
191    fn try_acquire_publication(&self, class: CpuPublicationClass) -> bool {
192        let mut current = self.publication.state.load(Ordering::Acquire);
193        loop {
194            if !class.accepts(current) {
195                return false;
196            }
197            let count = current & CPU_PUBLICATION_COUNT_MASK;
198            if count == CPU_PUBLICATION_COUNT_MASK {
199                task_runtime::fatal_invariant(
200                    CPU_PUBLICATION_OVERFLOW_INVARIANT,
201                    self.owner.as_u32() as usize,
202                );
203            }
204            match self.publication.state.compare_exchange_weak(
205                current,
206                current + 1,
207                Ordering::AcqRel,
208                Ordering::Acquire,
209            ) {
210                Ok(_) => {
211                    #[cfg(feature = "qperf-metrics")]
212                    match class {
213                        CpuPublicationClass::Placement => {
214                            crate::diagnostics::counters::record_cpu_placement_publication_acquire(
215                            );
216                        }
217                        CpuPublicationClass::OwnerControl => {
218                            crate::diagnostics::counters::record_cpu_owner_control_publication_acquire();
219                        }
220                    }
221                    return true;
222                }
223                Err(actual) => current = actual,
224            }
225        }
226    }
227
228    /// Pins an online placement target across an owner context switch.
229    ///
230    /// Linux holds the CPU-hotplug/read-side ownership that makes the selected
231    /// destination runqueue stable before it commits `TASK_ON_RQ_MIGRATING`.
232    /// The owned form carries the same lifetime proof through switch tail,
233    /// where a borrowed runqueue guard cannot survive the architecture switch.
234    pub(crate) fn begin_owned_publication(self: &Arc<Self>) -> Option<OwnedCpuRemotePublication> {
235        self.try_acquire_publication(CpuPublicationClass::Placement)
236            .then(|| OwnedCpuRemotePublication {
237                remote: Arc::clone(self),
238            })
239    }
240
241    pub(crate) fn begin_owner_delivery(&self) -> Option<CpuRemotePublication<'_>> {
242        self.try_acquire_publication(CpuPublicationClass::OwnerControl)
243            .then(|| CpuRemotePublication { remote: self })
244    }
245
246    pub(crate) fn is_quiescent_for_offline(&self) -> bool {
247        self.publication.state.load(Ordering::Acquire) == CPU_LIFECYCLE_DRAINING
248            && self.ktimer_is_quiescent_for_offline()
249            && self.deadline_is_quiescent_for_offline()
250            && !self.needs_reschedule()
251            && !self.has_remote_work()
252            && !self.is_idle_polling()
253            && self.idle_pull_is_quiescent()
254    }
255}
256
257pub(crate) struct CpuRemotePublication<'remote> {
258    remote: &'remote CpuRemote,
259}
260
261#[derive(Debug)]
262pub(crate) struct OwnedCpuRemotePublication {
263    remote: Arc<CpuRemote>,
264}
265
266impl OwnedCpuRemotePublication {
267    pub(crate) fn publish_owner_control(
268        self,
269        node: Pin<&'static InboxNode>,
270        message: InboxMessage,
271    ) -> PublishResult {
272        self.remote.publish_owner_control_owned(node, message)
273    }
274}
275
276impl CpuRemotePublication<'_> {
277    pub(crate) fn publish_owner_control(
278        self,
279        node: Pin<&'static InboxNode>,
280        message: InboxMessage,
281    ) -> PublishResult {
282        self.remote.publish_owner_control_owned(node, message)
283    }
284}
285
286impl Drop for CpuRemotePublication<'_> {
287    fn drop(&mut self) {
288        release_publication(self.remote);
289    }
290}
291
292impl Drop for OwnedCpuRemotePublication {
293    fn drop(&mut self) {
294        release_publication(&self.remote);
295    }
296}
297
298fn release_publication(remote: &CpuRemote) {
299    let mut current = remote.publication.state.load(Ordering::Acquire);
300    loop {
301        if current & CPU_LIFECYCLE_OFFLINE != 0 || current & CPU_PUBLICATION_COUNT_MASK == 0 {
302            task_runtime::fatal_invariant(
303                CPU_PUBLICATION_RELEASE_INVARIANT,
304                remote.owner.as_u32() as usize,
305            );
306        }
307        match remote.publication.state.compare_exchange_weak(
308            current,
309            current - 1,
310            Ordering::Release,
311            Ordering::Acquire,
312        ) {
313            Ok(_) => return,
314            Err(actual) => current = actual,
315        }
316    }
317}