ax_task/sched/system/task_system/
cpu_lifecycle.rs1use super::*;
4
5impl TaskSystem {
6 pub fn create_cpu_local(
8 &self,
9 cpu: CpuId,
10 ) -> Result<Pin<alloc::boxed::Box<CpuLocal>>, TaskError> {
11 let remote = Arc::clone(&self.state.lock().cpu_registration(cpu)?.remote);
12 Ok(CpuLocal::create(
13 cpu,
14 self.config,
15 remote,
16 Arc::clone(self.root_domain.rt_bandwidth()),
17 ))
18 }
19
20 pub fn cpu_remote(&self, cpu: CpuId) -> Option<&CpuRemote> {
22 self.cpu_remotes
23 .get(cpu.as_usize())
24 .map(Arc::as_ref)
25 .filter(|remote| remote.accepts_placement())
26 }
27
28 #[doc(hidden)]
35 pub fn runtime_cpu_remote_handle(&self, cpu: CpuId) -> CpuRemoteHandle {
36 self.cpu_remotes
37 .get(cpu.as_usize())
38 .map_or(CpuRemoteHandle::NONE, |remote| {
39 unsafe { CpuRemoteHandle::from_raw(Arc::as_ptr(remote).expose_provenance()) }
43 })
44 }
45
46 pub fn cpu_busy_runtime_ns(&self, cpu: CpuId) -> Result<u64, TaskError> {
48 let remote = self
49 .cpu_remotes
50 .get(cpu.as_usize())
51 .ok_or(TaskError::InvalidCpu(cpu.as_u32()))?;
52 if !remote.is_online() {
53 return Err(TaskError::CpuOffline(cpu.as_u32()));
54 }
55 Ok(remote.busy_runtime_ns())
56 }
57
58 pub(super) fn ensure_owner_cpu_online(&self, cpu: &CpuLocal) -> Result<(), TaskError> {
59 self.ensure_owner_cpu_context(cpu)?;
60 self.ensure_owner_cpu_registration_online(cpu)
61 }
62
63 pub(super) fn ensure_owner_cpu_registration_online(
66 &self,
67 cpu: &CpuLocal,
68 ) -> Result<(), TaskError> {
69 let remote = self
70 .cpu_remotes
71 .get(cpu.owner().as_usize())
72 .ok_or(TaskError::InvalidCpu(cpu.owner().as_u32()))?;
73 if Arc::ptr_eq(remote, cpu.remote()) && remote.is_online() {
74 Ok(())
75 } else {
76 Err(TaskError::CpuOffline(cpu.owner().as_u32()))
77 }
78 }
79
80 pub(super) fn ensure_owner_cpu_context(&self, cpu: &CpuLocal) -> Result<(), TaskError> {
89 if !cpu.is_online() {
90 return Ok(());
91 }
92 let published = unsafe { task_runtime::task_system_handle() }.into_raw();
96 let this = (self as *const Self).expose_provenance();
97 if published == 0 || published != this {
98 return Ok(());
99 }
100 match task_runtime::validate_owner_cpu_context() {
101 RuntimeStatus::Success => Ok(()),
102 RuntimeStatus::UnsafeContext => Err(TaskError::UnsafeContext),
103 status => Err(TaskError::RuntimeFailure(status as u32)),
104 }
105 }
106
107 pub fn bring_cpu_online(&self, mut cpu: Pin<&mut CpuLocal>) -> Result<(), TaskError> {
109 let _irq = IrqScope::enter();
110 self.ensure_owner_cpu_context(&cpu)?;
111 let id = cpu.owner();
112 let state = self.state.lock();
113 let mut root_domain = self.root_domain.lock();
114 let registration = state.cpu_registration(id)?;
115 if registration.remote.lifecycle_state() != crate::runtime::cpu::CpuLifecycleState::Offline
116 {
117 return Err(TaskError::CpuAlreadyOnline(id.as_u32()));
118 }
119 if !Arc::ptr_eq(®istration.remote, cpu.remote()) {
120 return Err(TaskError::InvalidRuntimeHandle);
121 }
122 if root_domain.online.contains(id) {
123 return Err(TaskError::InvalidConfiguration);
124 }
125 if state
126 .slots
127 .iter()
128 .filter_map(|slot| slot.record.as_ref())
129 .any(|record| {
130 let sched = record.sched.lock();
131 (matches!(sched.policy.base, SchedulePolicy::Deadline(_))
132 || matches!(sched.policy.requested_policy(), SchedulePolicy::Deadline(_)))
133 && !sched.affinity.affinity.contains(id)
134 })
135 {
136 return Err(TaskError::DeadlineAffinity);
137 }
138 ensure_runtime_success(task_runtime::prepare_cpu_online(RuntimeCpuId::new(
139 id.as_u32(),
140 )))?;
141 let monotonic_now = task_runtime::monotonic_now();
142 cpu.as_mut()
143 .reset_fair_balance(monotonic_now, self.config.balance_interval_ns());
144 let online_count = root_domain
145 .online
146 .count()
147 .checked_add(1)
148 .ok_or(TaskError::InvalidConfiguration)?;
149 let deadline_rebuild = state.deadline_bandwidth_rebuild(online_count)?;
150 self.root_domain.enable_rt_runtime(id);
151 assert!(
152 root_domain.insert_online(id, deadline_rebuild),
153 "validated offline CPU must be absent from the root domain"
154 );
155 assert!(
156 cpu.as_ref().get_ref().remote().mark_online(),
157 "validated offline CPU must accept final publication"
158 );
159 OwnerRqTxn::begin(self, cpu.remote()).commit();
160 if cpu
161 .lock_run_queue(RunQueueGuardSource::Lifecycle)
162 .has_runnable_rt()
163 {
164 self.root_domain.activate_rt_period(id, || monotonic_now);
165 }
166 Ok(())
167 }
168
169 pub fn take_cpu_offline(&self, mut cpu: Pin<&mut CpuLocal>) -> Result<(), TaskError> {
177 self.ensure_owner_cpu_context(&cpu)?;
178 let _irq = IrqScope::enter();
179 let id = cpu.owner();
180 let state = self.state.lock();
181 let mut root_domain = self.root_domain.lock();
182 let remote = Arc::clone(&state.cpu_registration(id)?.remote);
183 if !Arc::ptr_eq(&remote, cpu.remote()) {
184 return Err(TaskError::InvalidRuntimeHandle);
185 }
186 match remote.lifecycle_state() {
187 crate::runtime::cpu::CpuLifecycleState::Offline => {
188 return Err(TaskError::CpuOffline(id.as_u32()));
189 }
190 crate::runtime::cpu::CpuLifecycleState::Inactive
191 | crate::runtime::cpu::CpuLifecycleState::Draining => {
192 return Err(TaskError::CpuNotQuiescent(id.as_u32()));
193 }
194 crate::runtime::cpu::CpuLifecycleState::Online => {}
195 }
196 if root_domain.online.count() <= 1 {
197 return Err(TaskError::LastOnlineCpu(id.as_u32()));
198 }
199 if !root_domain.can_deactivate_cpu(id) {
200 return Err(TaskError::DeadlineAdmission);
201 }
202 let remaining_online = root_domain
203 .online
204 .count()
205 .checked_sub(1)
206 .ok_or(TaskError::InvalidConfiguration)?;
207 let deadline_rebuild = state.deadline_bandwidth_rebuild(remaining_online)?;
208 let rt_period_replacement = (0..root_domain.online.topology_len())
209 .map(|index| CpuId::new(index as u32))
210 .find(|candidate| *candidate != id && root_domain.online.contains(*candidate))
211 .ok_or(TaskError::LastOnlineCpu(id.as_u32()))?;
212
213 self.migrate_dormant_deadline_bandwidth_for_cpu_offline(&state, &root_domain, id)?;
214
215 if !remote.try_deactivate() {
216 Err(TaskError::CpuNotQuiescent(id.as_u32()))
217 } else if !Self::prepare_thread_targets_for_cpu_offline(&state, &root_domain, id)
218 || !remote.try_begin_draining()
219 {
220 remote.cancel_deactivation();
221 Err(TaskError::CpuNotQuiescent(id.as_u32()))
222 } else if !cpu.is_quiescent_for_offline()
223 || !Self::threads_allow_cpu_offline(&state, &root_domain, id)
224 {
225 remote.cancel_draining();
226 Err(TaskError::CpuNotQuiescent(id.as_u32()))
227 } else if let Err(error) = ensure_runtime_success(task_runtime::prepare_cpu_offline(
228 RuntimeCpuId::new(id.as_u32()),
229 )) {
230 remote.cancel_draining();
231 Err(error)
232 } else if !root_domain.remove_online(id, deadline_rebuild) {
233 remote.cancel_draining();
234 Err(TaskError::InvalidConfiguration)
235 } else {
236 cpu.as_mut().clear_fair_balance();
237 self.root_domain.disable_rt_runtime(id);
238 remote.finish_offline();
239 remote
240 .lock_run_queue(RunQueueGuardSource::Lifecycle)
241 .invalidate_domain_publication();
242 self.root_domain.publish_offline(id);
243 if self
244 .root_domain
245 .rt_bandwidth()
246 .migrate_owner(id, rt_period_replacement)
247 {
248 self.cpu_remotes[rt_period_replacement.as_usize()].kick_scheduler_work();
249 }
250 Ok(())
251 }
252 }
253
254 fn migrate_dormant_deadline_bandwidth_for_cpu_offline(
261 &self,
262 state: &TaskSystemState,
263 root_domain: &RootDomainState,
264 source: CpuId,
265 ) -> Result<(), TaskError> {
266 let source_remote = &self.cpu_remotes[source.as_usize()];
267 for record in state.slots.iter().filter_map(|slot| slot.record.as_ref()) {
268 let core = &record.core;
269 let mut sched = record.sched.lock();
270 if sched.deadline.bandwidth.reservation_owner() != Some(source) {
271 continue;
272 }
273 if sched.placement.queued_cpu().is_some()
274 || sched.placement.on_cpu().is_some()
275 || sched.placement.has_pending_migration()
276 {
277 return Err(TaskError::CpuNotQuiescent(source.as_u32()));
278 }
279 let target = (0..root_domain.online.topology_len())
280 .map(|index| CpuId::new(index as u32))
281 .find(|candidate| {
282 *candidate != source
283 && root_domain.online.contains(*candidate)
284 && sched.affinity.affinity.contains(*candidate)
285 && self.cpu_remotes[candidate.as_usize()].accepts_placement()
286 })
287 .ok_or(TaskError::DeadlineAffinity)?;
288 let target_remote = &self.cpu_remotes[target.as_usize()];
289 let publication = target_remote
290 .begin_publication()
291 .ok_or(TaskError::CpuNotQuiescent(target.as_u32()))?;
292
293 let mut source_rq = OwnerRqTxn::begin(self, source_remote);
294 Self::detach_owner_deadline_bandwidth_in_rq(
295 core,
296 &mut sched,
297 source_remote,
298 &mut source_rq,
299 );
300 source_rq.commit();
301
302 let active = sched.deadline.bandwidth.is_active();
303 let mut target_rq = OwnerRqTxn::begin(self, target_remote);
304 Self::attach_deadline_bandwidth_locked(
305 core,
306 &mut sched,
307 &mut target_rq,
308 target,
309 active,
310 );
311 target_rq.commit();
312 core.set_wake_cpu_hint(target);
313 drop(sched);
314 drop(publication);
315 self.publish_owner_deadline_refresh(core, target);
316 }
317 Ok(())
318 }
319
320 fn prepare_thread_targets_for_cpu_offline(
328 state: &TaskSystemState,
329 root_domain: &RootDomainState,
330 cpu: CpuId,
331 ) -> bool {
332 let is_idle = |id| {
333 state
334 .cpus
335 .iter()
336 .any(|registration| registration.remote.idle_thread() == Some(id))
337 };
338 let fallback_for = |affinity: &CpuSet| {
339 state
340 .cpus
341 .iter()
342 .enumerate()
343 .map(|(index, registration)| (CpuId::new(index as u32), registration))
344 .find(|(candidate, registration)| {
345 *candidate != cpu
346 && root_domain.online.contains(*candidate)
347 && registration.remote.accepts_placement()
348 && affinity.contains(*candidate)
349 })
350 .map(|(candidate, _)| candidate)
351 };
352
353 for record in state.slots.iter().filter_map(|slot| slot.record.as_ref()) {
354 if is_idle(record.core.id()) {
355 continue;
356 }
357
358 let sched = record.sched.lock();
359 if sched.lifecycle.state() == ThreadState::Exited {
360 continue;
361 }
362 if Self::is_parked_ktimer_worker(state, cpu, &record.core, &sched) {
363 continue;
364 }
365 if fallback_for(&sched.affinity.affinity).is_none() {
366 return false;
367 }
368 let physically_owned = sched.placement.queued_cpu() == Some(cpu)
369 || sched.placement.on_cpu() == Some(cpu)
370 || sched.placement.committed_migration_target() == Some(cpu)
371 || sched.deadline.bandwidth.reservation_owner() == Some(cpu)
372 || record.core.sleep_timer_cpu() == Some(cpu);
373 if physically_owned {
374 return false;
375 }
376 let has_other_placement = sched.placement.queued_cpu().is_some()
377 || sched.placement.on_cpu().is_some()
378 || sched.placement.has_pending_migration()
379 || sched.deadline.bandwidth.reservation_owner().is_some()
380 || record.core.sleep_timer_cpu().is_some();
381 if record.core.wake_cpu_hint() == Some(cpu) && has_other_placement {
382 return false;
383 }
384 }
385
386 for record in state.slots.iter().filter_map(|slot| slot.record.as_ref()) {
387 if is_idle(record.core.id()) {
388 continue;
389 }
390 let sched = record.sched.lock();
391 if Self::is_parked_ktimer_worker(state, cpu, &record.core, &sched) {
392 continue;
393 }
394 drop(sched);
395 if record.core.wake_cpu_hint() != Some(cpu) {
396 continue;
397 }
398 let sched = record.sched.lock();
399 if sched.lifecycle.state() == ThreadState::Exited {
400 continue;
401 }
402 let Some(fallback) = fallback_for(&sched.affinity.affinity) else {
403 return false;
404 };
405 record.core.set_wake_cpu_hint(fallback);
408 }
409 true
410 }
411
412 fn threads_allow_cpu_offline(
413 state: &TaskSystemState,
414 root_domain: &RootDomainState,
415 cpu: CpuId,
416 ) -> bool {
417 state
418 .slots
419 .iter()
420 .filter_map(|slot| slot.record.as_ref())
421 .all(|record| {
422 let id = record.core.id();
423 let is_idle = state
424 .cpus
425 .iter()
426 .any(|registration| registration.remote.idle_thread() == Some(id));
427 if is_idle {
428 return true;
429 }
430
431 let sched = record.sched.lock();
432 if sched.lifecycle.state() == ThreadState::Exited {
433 return true;
434 }
435 if Self::is_parked_ktimer_worker(state, cpu, &record.core, &sched) {
436 return true;
437 }
438 let has_remaining_destination = (0..state.cpus.len()).any(|index| {
439 let candidate = CpuId::new(index as u32);
440 candidate != cpu
441 && root_domain.online.contains(candidate)
442 && sched.affinity.affinity.contains(candidate)
443 });
444 let owned_by_cpu = sched.placement.queued_cpu() == Some(cpu)
445 || sched.placement.on_cpu() == Some(cpu)
446 || sched.placement.committed_migration_target() == Some(cpu)
447 || sched.deadline.bandwidth.reservation_owner() == Some(cpu)
448 || record.core.sleep_timer_cpu() == Some(cpu)
449 || record.core.wake_cpu_hint() == Some(cpu);
450 has_remaining_destination && !owned_by_cpu
451 })
452 }
453
454 fn is_parked_ktimer_worker(
458 state: &TaskSystemState,
459 cpu: CpuId,
460 core: &ThreadCore,
461 sched: &ThreadSchedState,
462 ) -> bool {
463 state.cpus.get(cpu.as_usize()).is_some_and(|registration| {
464 registration.remote.ktimer_worker() == Some(core.id())
465 && sched.lifecycle.state() == ThreadState::Blocked
466 && sched.placement.queued_cpu().is_none()
467 && sched.placement.on_cpu().is_none()
468 && !sched.placement.has_pending_migration()
469 && sched.deadline.bandwidth.reservation_owner().is_none()
470 && core.sleep_timer_cpu().is_none()
471 })
472 }
473
474 pub fn install_idle_thread(
476 &self,
477 mut cpu: Pin<&mut CpuLocal>,
478 thread: ThreadId,
479 ) -> Result<(), TaskError> {
480 self.ensure_owner_cpu_context(&cpu)?;
481 let core = {
482 let state = self.state.lock();
483 state.cpu_registration(cpu.owner())?;
484 Arc::clone(&state.thread_record(thread)?.core)
485 };
486 self.install_idle_core(cpu.as_mut(), core)
487 }
488}