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> {
179 self.ensure_owner_cpu_context(&cpu)?;
180 let _irq = IrqScope::enter();
181 let id = cpu.owner();
182 let state = self.state.lock();
183 let mut root_domain = self.root_domain.lock();
184 let remote = Arc::clone(&state.cpu_registration(id)?.remote);
185 if !Arc::ptr_eq(&remote, cpu.remote()) {
186 return Err(TaskError::InvalidRuntimeHandle);
187 }
188 let result = (|| {
189 match remote.lifecycle_state() {
190 crate::runtime::cpu::CpuLifecycleState::Offline => {
191 return Err(TaskError::CpuOffline(id.as_u32()));
192 }
193 crate::runtime::cpu::CpuLifecycleState::Draining => {
194 return Err(TaskError::CpuNotQuiescent(id.as_u32()));
195 }
196 crate::runtime::cpu::CpuLifecycleState::Online
197 | crate::runtime::cpu::CpuLifecycleState::Inactive => {}
198 }
199 if state
203 .slots
204 .iter()
205 .filter_map(|slot| slot.record.as_ref())
206 .any(|record| {
207 record
208 .activation
209 .as_ref()
210 .is_some_and(|activation| activation.target() == id)
211 })
212 {
213 #[cfg(feature = "fault-injection")]
214 crate::runtime::cpu::record_idle_offline_rejection(
215 crate::runtime::cpu::IdleOfflineRejection::PlacementPublication,
216 );
217 return Err(TaskError::CpuNotQuiescent(id.as_u32()));
218 }
219 if root_domain.online.count() <= 1 {
220 return Err(TaskError::LastOnlineCpu(id.as_u32()));
221 }
222 if !root_domain.can_deactivate_cpu(id) {
223 return Err(TaskError::DeadlineAdmission);
224 }
225 let remaining_online = root_domain
226 .online
227 .count()
228 .checked_sub(1)
229 .ok_or(TaskError::InvalidConfiguration)?;
230 let deadline_rebuild = state.deadline_bandwidth_rebuild(remaining_online)?;
231 let rt_period_replacement = (0..root_domain.online.topology_len())
232 .map(|index| CpuId::new(index as u32))
233 .find(|candidate| *candidate != id && root_domain.online.contains(*candidate))
234 .ok_or(TaskError::LastOnlineCpu(id.as_u32()))?;
235
236 self.migrate_dormant_deadline_bandwidth_for_cpu_offline(&state, &root_domain, id)?;
237
238 if remote.lifecycle_state() == crate::runtime::cpu::CpuLifecycleState::Online
239 && !remote.try_deactivate()
240 {
241 #[cfg(feature = "fault-injection")]
242 crate::runtime::cpu::record_idle_offline_rejection(
243 crate::runtime::cpu::IdleOfflineRejection::PlacementPublication,
244 );
245 Err(TaskError::CpuNotQuiescent(id.as_u32()))
246 } else if !remote.try_begin_draining() {
247 #[cfg(feature = "fault-injection")]
248 crate::runtime::cpu::record_idle_offline_rejection(
249 crate::runtime::cpu::IdleOfflineRejection::OwnerPublication,
250 );
251 Err(TaskError::NotReady)
252 } else if !cpu.is_quiescent_for_offline() {
253 #[cfg(feature = "fault-injection")]
254 crate::runtime::cpu::record_idle_offline_rejection(
255 if cpu.needs_reschedule() || cpu.has_remote_work() {
256 crate::runtime::cpu::IdleOfflineRejection::SchedulerWork
257 } else {
258 crate::runtime::cpu::IdleOfflineRejection::CpuState
259 },
260 );
261 if cpu.needs_reschedule() || cpu.has_remote_work() {
262 Err(TaskError::NotReady)
263 } else {
264 Err(TaskError::CpuNotQuiescent(id.as_u32()))
265 }
266 } else if !Self::prepare_thread_targets_for_cpu_offline(&state, &root_domain, id) {
267 #[cfg(feature = "fault-injection")]
268 crate::runtime::cpu::record_idle_offline_rejection(
269 crate::runtime::cpu::IdleOfflineRejection::ThreadTarget,
270 );
271 Err(TaskError::CpuNotQuiescent(id.as_u32()))
272 } else if !Self::threads_allow_cpu_offline(&state, &root_domain, id) {
273 #[cfg(feature = "fault-injection")]
274 crate::runtime::cpu::record_idle_offline_rejection(
275 crate::runtime::cpu::IdleOfflineRejection::ThreadOwnership,
276 );
277 Err(TaskError::CpuNotQuiescent(id.as_u32()))
278 } else if let Err(error) = ensure_runtime_success(task_runtime::prepare_cpu_offline(
279 RuntimeCpuId::new(id.as_u32()),
280 )) {
281 Err(error)
282 } else if !root_domain.remove_online(id, deadline_rebuild) {
283 Err(TaskError::InvalidConfiguration)
284 } else {
285 cpu.as_mut().clear_fair_balance();
286 self.root_domain.disable_rt_runtime(id);
287 remote.finish_offline();
288 remote
289 .lock_run_queue(RunQueueGuardSource::Lifecycle)
290 .invalidate_domain_publication();
291 self.root_domain.publish_offline(id);
292 if self
293 .root_domain
294 .rt_bandwidth()
295 .migrate_owner(id, rt_period_replacement)
296 {
297 self.cpu_remotes[rt_period_replacement.as_usize()].kick_scheduler_work();
298 }
299 Ok(())
300 }
301 })();
302 match (&result, remote.lifecycle_state()) {
305 (Err(TaskError::NotReady), crate::runtime::cpu::CpuLifecycleState::Draining) => {
306 remote.resume_owner_drain()
307 }
308 (Err(TaskError::NotReady), _) => {}
309 (Err(_), crate::runtime::cpu::CpuLifecycleState::Draining) => remote.cancel_draining(),
310 (Err(_), crate::runtime::cpu::CpuLifecycleState::Inactive) => {
311 remote.cancel_deactivation()
312 }
313 _ => {}
314 }
315 result
316 }
317
318 fn migrate_dormant_deadline_bandwidth_for_cpu_offline(
325 &self,
326 state: &TaskSystemState,
327 root_domain: &RootDomainState,
328 source: CpuId,
329 ) -> Result<(), TaskError> {
330 let source_remote = &self.cpu_remotes[source.as_usize()];
331 for record in state.slots.iter().filter_map(|slot| slot.record.as_ref()) {
332 let core = &record.core;
333 let mut sched = record.sched.lock();
334 if sched.deadline.bandwidth.reservation_owner() != Some(source) {
335 continue;
336 }
337 if sched.placement.queued_cpu().is_some()
338 || sched.placement.on_cpu().is_some()
339 || sched.placement.has_pending_migration()
340 {
341 return Err(TaskError::CpuNotQuiescent(source.as_u32()));
342 }
343 let target = (0..root_domain.online.topology_len())
344 .map(|index| CpuId::new(index as u32))
345 .find(|candidate| {
346 *candidate != source
347 && root_domain.online.contains(*candidate)
348 && sched.affinity.affinity.contains(*candidate)
349 && self.cpu_remotes[candidate.as_usize()].accepts_placement()
350 })
351 .ok_or(TaskError::DeadlineAffinity)?;
352 let target_remote = &self.cpu_remotes[target.as_usize()];
353 let publication = target_remote
354 .begin_publication()
355 .ok_or(TaskError::CpuNotQuiescent(target.as_u32()))?;
356
357 let mut source_rq = OwnerRqTxn::begin(self, source_remote);
358 Self::detach_owner_deadline_bandwidth_in_rq(
359 core,
360 &mut sched,
361 source_remote,
362 &mut source_rq,
363 );
364 source_rq.commit();
365
366 let active = sched.deadline.bandwidth.is_active();
367 let mut target_rq = OwnerRqTxn::begin(self, target_remote);
368 Self::attach_deadline_bandwidth_locked(
369 core,
370 &mut sched,
371 &mut target_rq,
372 target,
373 active,
374 );
375 target_rq.commit();
376 core.set_wake_cpu_hint(target);
377 drop(sched);
378 drop(publication);
379 self.publish_owner_deadline_refresh(core, target);
380 }
381 Ok(())
382 }
383
384 fn prepare_thread_targets_for_cpu_offline(
392 state: &TaskSystemState,
393 root_domain: &RootDomainState,
394 cpu: CpuId,
395 ) -> bool {
396 let is_idle = |id| {
397 state
398 .cpus
399 .iter()
400 .any(|registration| registration.remote.idle_thread() == Some(id))
401 };
402 let fallback_for = |affinity: &CpuSet| {
403 state
404 .cpus
405 .iter()
406 .enumerate()
407 .map(|(index, registration)| (CpuId::new(index as u32), registration))
408 .find(|(candidate, registration)| {
409 *candidate != cpu
410 && root_domain.online.contains(*candidate)
411 && registration.remote.accepts_placement()
412 && affinity.contains(*candidate)
413 })
414 .map(|(candidate, _)| candidate)
415 };
416
417 for record in state.slots.iter().filter_map(|slot| slot.record.as_ref()) {
418 if is_idle(record.core.id()) {
419 continue;
420 }
421
422 let sched = record.sched.lock();
423 if sched.lifecycle.state() == ThreadState::Exited {
424 continue;
425 }
426 if Self::is_parked_ktimer_worker(state, cpu, &record.core, &sched) {
427 continue;
428 }
429 if fallback_for(&sched.affinity.affinity).is_none() {
430 return false;
431 }
432 let physically_owned = sched.placement.queued_cpu() == Some(cpu)
433 || sched.placement.on_cpu() == Some(cpu)
434 || sched.placement.committed_migration_target() == Some(cpu)
435 || sched.deadline.bandwidth.reservation_owner() == Some(cpu)
436 || record.core.sleep_timer_cpu() == Some(cpu);
437 if physically_owned {
438 return false;
439 }
440 let has_other_placement = sched.placement.queued_cpu().is_some()
441 || sched.placement.on_cpu().is_some()
442 || sched.placement.has_pending_migration()
443 || sched.deadline.bandwidth.reservation_owner().is_some()
444 || record.core.sleep_timer_cpu().is_some();
445 if record.core.wake_cpu_hint() == Some(cpu) && has_other_placement {
446 return false;
447 }
448 }
449
450 for record in state.slots.iter().filter_map(|slot| slot.record.as_ref()) {
451 if is_idle(record.core.id()) {
452 continue;
453 }
454 let sched = record.sched.lock();
455 if Self::is_parked_ktimer_worker(state, cpu, &record.core, &sched) {
456 continue;
457 }
458 drop(sched);
459 if record.core.wake_cpu_hint() != Some(cpu) {
460 continue;
461 }
462 let sched = record.sched.lock();
463 if sched.lifecycle.state() == ThreadState::Exited {
464 continue;
465 }
466 let Some(fallback) = fallback_for(&sched.affinity.affinity) else {
467 return false;
468 };
469 record.core.set_wake_cpu_hint(fallback);
472 }
473 true
474 }
475
476 fn threads_allow_cpu_offline(
477 state: &TaskSystemState,
478 root_domain: &RootDomainState,
479 cpu: CpuId,
480 ) -> bool {
481 state
482 .slots
483 .iter()
484 .filter_map(|slot| slot.record.as_ref())
485 .all(|record| {
486 let id = record.core.id();
487 let is_idle = state
488 .cpus
489 .iter()
490 .any(|registration| registration.remote.idle_thread() == Some(id));
491 let sched = record.sched.lock();
492 if sched.affinity.migration_cpu == Some(cpu) {
495 return false;
496 }
497 if is_idle || sched.lifecycle.state() == ThreadState::Exited {
498 return true;
499 }
500 if Self::is_parked_ktimer_worker(state, cpu, &record.core, &sched) {
501 return true;
502 }
503 let has_remaining_destination = (0..state.cpus.len()).any(|index| {
504 let candidate = CpuId::new(index as u32);
505 candidate != cpu
506 && root_domain.online.contains(candidate)
507 && sched.affinity.affinity.contains(candidate)
508 });
509 let owned_by_cpu = sched.placement.queued_cpu() == Some(cpu)
510 || sched.placement.on_cpu() == Some(cpu)
511 || sched.placement.committed_migration_target() == Some(cpu)
512 || sched.deadline.bandwidth.reservation_owner() == Some(cpu)
513 || record.core.sleep_timer_cpu() == Some(cpu)
514 || record.core.wake_cpu_hint() == Some(cpu);
515 has_remaining_destination && !owned_by_cpu
516 })
517 }
518
519 fn is_parked_ktimer_worker(
523 state: &TaskSystemState,
524 cpu: CpuId,
525 core: &ThreadCore,
526 sched: &ThreadSchedState,
527 ) -> bool {
528 state.cpus.get(cpu.as_usize()).is_some_and(|registration| {
529 registration.remote.ktimer_worker() == Some(core.id())
530 && sched.lifecycle.state() == ThreadState::Blocked
531 && sched.placement.queued_cpu().is_none()
532 && sched.placement.on_cpu().is_none()
533 && !sched.placement.has_pending_migration()
534 && sched.deadline.bandwidth.reservation_owner().is_none()
535 && core.sleep_timer_cpu().is_none()
536 })
537 }
538
539 pub fn install_idle_thread(
541 &self,
542 mut cpu: Pin<&mut CpuLocal>,
543 thread: ThreadId,
544 ) -> Result<(), TaskError> {
545 self.ensure_owner_cpu_context(&cpu)?;
546 let core = {
547 let state = self.state.lock();
548 state.cpu_registration(cpu.owner())?;
549 Arc::clone(&state.thread_record(thread)?.core)
550 };
551 self.install_idle_core(cpu.as_mut(), core)
552 }
553}