1use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
2
3use ax_task::runtime::cpu::{LocalIrqState, PreemptGuardToken};
4
5use super::*;
6
7static SCHED_SWITCH_TRACE_HOOK: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut());
8static SCHED_SWITCH_TRACE_ENABLED: AtomicBool = AtomicBool::new(false);
9
10unsafe fn membarrier_ipi_memory_barrier(_arg: *mut ()) {
11 core::sync::atomic::fence(Ordering::SeqCst);
12}
13
14unsafe fn membarrier_ipi_refresh_run_queue(_arg: *mut ()) {
15 ax_task::sync::membarrier::refresh_current_membarrier_run_queue()
16 .unwrap_or_else(|error| panic!("membarrier rq refresh failed in IPI: {error}"));
17}
18
19pub type SchedSwitchTraceHook = fn(SchedSwitchRecord) -> Option<fn()>;
24
25pub fn install_sched_switch_trace_hook(hook: SchedSwitchTraceHook) {
30 let hook = hook as *mut ();
31 match SCHED_SWITCH_TRACE_HOOK.compare_exchange(
32 core::ptr::null_mut(),
33 hook,
34 Ordering::AcqRel,
35 Ordering::Acquire,
36 ) {
37 Ok(_) => {}
38 Err(installed) => assert_eq!(installed, hook, "scheduler trace hook already installed"),
39 }
40}
41
42pub fn publish_sched_switch_trace_gate(enabled: bool) {
44 SCHED_SWITCH_TRACE_ENABLED.store(enabled, Ordering::Release);
45}
46
47struct ArceOsTaskRuntime;
48
49impl_task_runtime! {
50 impl TaskRuntime for ArceOsTaskRuntime {
51 unsafe fn task_system_handle() -> TaskSystemHandle {
52 runtime_task_system_handle()
53 }
54
55 unsafe fn current_cpu_owner_handles() -> CurrentCpuOwnerHandles {
56 unsafe { with_current_cpu_pin(current_cpu_owner_handles) }
60 }
61
62 unsafe fn current_cpu_remote_handle() -> CpuRemoteHandle {
63 unsafe { scheduler_current_cpu_remote_handle() }
67 }
68
69 fn current_thread_identity() -> ThreadIdentityV1 {
70 scheduler_current_thread_identity()
71 }
72
73 fn current_thread_publication() -> CurrentThreadPublication {
74 scheduler_current_thread_publication()
75 }
76
77 fn current_preemption_pending() -> bool {
78 cpu_local::current_preemption_pending().unwrap_or_else(|error| {
79 panic!("current preemption state is unavailable: {error}")
80 })
81 }
82
83 unsafe fn cpu_remote_handle(cpu: RuntimeCpuId) -> CpuRemoteHandle {
84 cpu_remote(cpu).map_or(CpuRemoteHandle::NONE, |cpu| {
85 unsafe {
88 CpuRemoteHandle::from_raw((cpu as *const CpuRemote).expose_provenance())
89 }
90 })
91 }
92
93 unsafe fn current_cpu_id() -> RuntimeCpuId {
94 let cpu = unsafe { cpu_local::current_cpu_index() }
97 .unwrap_or_else(|error| panic!("task runtime CPU index is invalid: {error}"));
98 RuntimeCpuId::new(cpu.as_u32())
99 }
100
101 fn prepare_cpu_online(cpu: RuntimeCpuId) -> RuntimeStatus {
102 if cpu != unsafe { Self::current_cpu_id() } {
105 return RuntimeStatus::InvalidArgument;
106 }
107 crate::clock_event_runtime::init_timer();
108 RuntimeStatus::Success
109 }
110
111 fn prepare_cpu_offline(cpu: RuntimeCpuId) -> RuntimeStatus {
112 if cpu != unsafe { Self::current_cpu_id() } {
115 return RuntimeStatus::InvalidArgument;
116 }
117 release_current_active_address_space();
125 crate::clock_event_runtime::take_current_clock_event_offline();
126 RuntimeStatus::Success
127 }
128
129 fn local_irq_save_and_disable() -> LocalIrqState {
130 #[cfg(any(test, feature = "host-test"))]
131 {
132 unsafe { LocalIrqState::from_raw(0) }
137 }
138 #[cfg(not(any(test, feature = "host-test")))]
139 {
140 let was_enabled = ax_cpu::interrupt::irqs_enabled();
141 ax_cpu::interrupt::disable_irqs();
142 unsafe { LocalIrqState::from_raw(usize::from(was_enabled)) }
145 }
146 }
147
148 unsafe fn local_irq_restore(state: LocalIrqState) {
149 #[cfg(any(test, feature = "host-test"))]
150 {
151 debug_assert_eq!(state.into_raw(), 0);
152 }
153 #[cfg(not(any(test, feature = "host-test")))]
154 {
155 if state.into_raw() != 0 {
156 ax_cpu::interrupt::enable_irqs();
157 } else {
158 ax_cpu::interrupt::disable_irqs();
159 }
160 }
161 }
162
163 fn irq_guard_enter() -> IrqGuardToken {
164 #[cfg(any(test, feature = "host-test"))]
165 {
166 unsafe { IrqGuardToken::from_raw(1) }
168 }
169 #[cfg(not(any(test, feature = "host-test")))]
170 {
171 if crate::guard::inherits_hardirq_cpu_owner() {
172 return IrqGuardToken::NONE;
173 }
174 crate::guard::enter_irq();
175 unsafe { IrqGuardToken::from_raw(1) }
177 }
178 }
179
180 unsafe fn irq_guard_exit(token: IrqGuardToken) {
181 #[cfg(not(any(test, feature = "host-test")))]
182 if !token.is_none() {
183 crate::guard::exit_irq("task runtime");
184 }
185 #[cfg(any(test, feature = "host-test"))]
186 let _ = token;
187 }
188
189 fn preempt_guard_enter() -> PreemptGuardToken {
190 #[cfg(any(test, feature = "host-test"))]
191 {
192 unsafe { PreemptGuardToken::from_raw(1) }
194 }
195 #[cfg(not(any(test, feature = "host-test")))]
196 {
197 match crate::guard::enter_lock_preempt() {
198 Some(token) => {
199 unsafe { PreemptGuardToken::from_raw(token.into_raw()) }
202 }
203 None => PreemptGuardToken::NONE,
204 }
205 }
206 }
207
208 unsafe fn preempt_guard_exit(token: PreemptGuardToken) {
209 assert!(
210 !token.is_none(),
211 "inherited owner scope passed to ordinary preemption exit"
212 );
213 #[cfg(not(any(test, feature = "host-test")))]
214 {
215 let token = unsafe { cpu_local::PreemptionToken::from_raw(token.into_raw()) }
216 .expect("task preemption token must retain its architecture owner");
217 crate::guard::exit_preempt(token);
218 }
219 }
220
221 unsafe fn preempt_guard_exit_irq_return(token: PreemptGuardToken) {
222 assert!(
223 !token.is_none(),
224 "inherited owner scope passed to IRQ-return preemption exit"
225 );
226 #[cfg(not(any(test, feature = "host-test")))]
227 {
228 let token = unsafe { cpu_local::PreemptionToken::from_raw(token.into_raw()) }
229 .expect("IRQ-return token must retain its architecture owner");
230 crate::guard::exit_preempt_from_irq_return(token);
231 }
232 }
233
234 fn hardirq_enter() {
235 #[cfg(feature = "irq-time-accounting")]
236 crate::irq_time::enter();
237 }
238
239 fn hardirq_exit() {
240 #[cfg(feature = "irq-time-accounting")]
241 crate::irq_time::exit();
242 }
243
244 fn publish_local_scheduler_work() -> bool {
245 #[cfg(any(test, feature = "host-test"))]
246 {
247 false
248 }
249 #[cfg(not(any(test, feature = "host-test")))]
250 {
251 crate::guard::publish_local_scheduler_work()
252 }
253 }
254
255 fn finish_context_switch_tail() -> bool {
256 finish_runtime_context_switch_tail()
257 }
258
259 fn finish_initial_context_switch() {
260 crate::guard::finish_initial_context_switch();
261 }
262
263 fn scheduler_frame_guard_enter(
264 origin: ax_task::runtime::switch::RuntimeScheduleOrigin,
265 entry: ax_task::runtime::switch::RuntimeSchedulerEntry,
266 ) -> RuntimeSchedulerFrameEnterResult {
267 crate::guard::enter_scheduler_frame_guard(origin, entry)
268 }
269
270 fn scheduler_frame_guard_exit(
271 return_to: ax_task::runtime::switch::RuntimeSchedulerReturn,
272 needs_reschedule: bool,
273 ) -> bool {
274 crate::guard::exit_scheduler_frame_guard(return_to, needs_reschedule)
275 }
276
277 fn in_hard_irq() -> bool {
278 #[cfg(any(test, feature = "host-test"))]
279 {
280 false
281 }
282 #[cfg(not(any(test, feature = "host-test")))]
283 {
284 ax_hal::irq::in_irq_context()
285 }
286 }
287
288 fn validate_schedule_context(
289 origin: ax_task::runtime::switch::RuntimeScheduleOrigin,
290 ) -> RuntimeStatus {
291 crate::guard::validate_schedule_context(origin)
292 }
293
294 fn validate_owner_cpu_context() -> RuntimeStatus {
295 crate::guard::validate_owner_cpu_context()
296 }
297
298 fn monotonic_now() -> ax_task::time::MonotonicInstant {
299 ax_task::time::MonotonicInstant::from_nanos(
300 ax_hal::time::monotonic_time_nanos(),
301 )
302 .expect("platform monotonic clock exceeded the signed ktime domain")
303 }
304
305 fn rq_clock_sample() -> ax_task::runtime::cpu::RqClockSample {
306 #[cfg(feature = "qperf-metrics")]
307 let clock_started_ns = ax_hal::time::monotonic_time_nanos();
308 let clock_ns = unsafe { ax_hal::time::scheduler_clock_source() }
311 .unwrap_or_else(|error| {
312 panic!("current scheduler clock source is unavailable: {error}")
313 });
314 #[cfg(feature = "qperf-metrics")]
315 let irq_time_started_ns = ax_hal::time::monotonic_time_nanos();
316 #[cfg(feature = "irq-time-accounting")]
317 let irq_time_ns = crate::irq_time::total_current();
318 #[cfg(feature = "qperf-metrics")]
319 {
320 ax_task::diagnostics::qperf_record_switch_scheduler_detail(
321 15,
322 clock_started_ns,
323 irq_time_started_ns,
324 );
325 }
326 #[cfg(all(feature = "qperf-metrics", feature = "irq-time-accounting"))]
327 {
328 let irq_time_finished_ns = ax_hal::time::monotonic_time_nanos();
329 ax_task::diagnostics::qperf_record_switch_scheduler_detail(
330 16,
331 irq_time_started_ns,
332 irq_time_finished_ns,
333 );
334 }
335 #[cfg(feature = "irq-time-accounting")]
336 return ax_task::runtime::cpu::RqClockSample::new(
337 ax_task::sched::SchedulerTimestamp::from_nanos(clock_ns),
338 irq_time_ns,
339 );
340 #[cfg(not(feature = "irq-time-accounting"))]
341 ax_task::runtime::cpu::RqClockSample::without_irq_time_accounting(
342 ax_task::sched::SchedulerTimestamp::from_nanos(clock_ns),
343 )
344 }
345
346 fn publish_scheduler_deadline(update: ax_task::runtime::cpu::SchedulerDeadlineUpdate) {
347 crate::clock_event_runtime::publish_local_scheduler_deadline(update);
348 }
349
350 fn publish_scheduler_runtime_deadline(
351 update: ax_task::runtime::cpu::SchedulerRuntimeDeadline,
352 ) {
353 crate::clock_event_runtime::publish_local_scheduler_runtime_deadline(update);
354 }
355
356 fn idle_exit_restart_scheduler_tick() {
357 crate::clock_event_runtime::restart_current_scheduler_tick_after_idle(
358 crate::clock_event_runtime::monotonic_now(),
359 );
360 }
361
362 fn notify_scheduler_cpu(cpu: RuntimeCpuId) -> RuntimeStatus {
363 #[cfg(any(feature = "ipi", feature = "wake-ipi"))]
364 {
365 let cpu_id = cpu.as_u32() as usize;
366 if cpu_id >= ax_hal::cpu_num() {
367 return RuntimeStatus::InvalidArgument;
368 }
369 match ax_ipi::notify_cpu(ax_hal::irq::CpuId(cpu_id)) {
370 Ok(notification) => {
371 if notification == ax_ipi::IpiNotification::Sent {
372 #[cfg(feature = "qperf-metrics")]
373 record_scheduler_ipi_send();
374 }
375 RuntimeStatus::Success
376 }
377 Err(ax_hal::irq::IrqError::InvalidCpu) => RuntimeStatus::InvalidArgument,
378 Err(ax_hal::irq::IrqError::CpuOffline) => RuntimeStatus::NotInitialized,
379 Err(ax_hal::irq::IrqError::Busy) => RuntimeStatus::Busy,
380 Err(ax_hal::irq::IrqError::NoMemory) => RuntimeStatus::NoMemory,
381 Err(ax_hal::irq::IrqError::Unsupported) => RuntimeStatus::Unsupported,
382 Err(_) => RuntimeStatus::Platform,
383 }
384 }
385 #[cfg(not(any(feature = "ipi", feature = "wake-ipi")))]
386 {
387 let _ = cpu;
388 RuntimeStatus::Unsupported
389 }
390 }
391
392 fn wait_for_interrupt() {
393 let idle_exit_guard = crate::task::sync::PreemptGuard::new();
399 ax_cpu::interrupt::disable_irqs();
400 unsafe {
401 ax_task::runtime::cpu::finish_current_cpu_idle_polling()
405 }
406 .expect("idle handoff requires an initialized current CPU");
407 #[cfg(feature = "fault-injection")]
408 let injected_probe = super::creation_probe::publish_idle_probe_at_wait();
409 #[cfg(feature = "fault-injection")]
410 if super::creation_probe::idle_probe_pending() {
411 crate::clock_event_runtime::restart_current_scheduler_tick_after_idle(
415 crate::clock_event_runtime::monotonic_now(),
416 );
417 ax_cpu::interrupt::enable_irqs();
418 drop(idle_exit_guard);
419 return;
420 }
421 let mut now = crate::clock_event_runtime::monotonic_now();
422 let mut needs_reschedule = ax_task::runtime::cpu::current_cpu_needs_resched()
423 .expect("idle handoff requires an initialized current CPU");
424 if needs_reschedule
425 || crate::clock_event_runtime::local_clock_event_has_immediate_work(now)
426 {
427 crate::clock_event_runtime::restart_current_scheduler_tick_after_idle(now);
428 ax_cpu::interrupt::enable_irqs();
429 drop(idle_exit_guard);
430 return;
431 }
432
433 crate::clock_event_runtime::stop_current_scheduler_tick_for_idle();
438 now = crate::clock_event_runtime::monotonic_now();
439 needs_reschedule = ax_task::runtime::cpu::current_cpu_needs_resched()
440 .expect("idle handoff requires an initialized current CPU");
441 if needs_reschedule
442 || crate::clock_event_runtime::local_clock_event_has_immediate_work(now)
443 {
444 crate::clock_event_runtime::restart_current_scheduler_tick_after_idle(now);
445 ax_cpu::interrupt::enable_irqs();
446 drop(idle_exit_guard);
447 return;
448 }
449
450 #[cfg(feature = "fault-injection")]
451 assert!(!injected_probe || !super::creation_probe::idle_probe_pending(),
452 "idle must recheck pending owner probe before WFI");
453 ax_cpu::interrupt::wait_for_irqs_disabled();
454
455 let irq_guard = crate::task::sync::IrqSaveGuard::new();
460 now = crate::clock_event_runtime::monotonic_now();
461 needs_reschedule = ax_task::runtime::cpu::current_cpu_needs_resched()
462 .expect("idle wake requires an initialized current CPU");
463 if needs_reschedule
464 || crate::clock_event_runtime::local_clock_event_has_immediate_work(now)
465 {
466 crate::clock_event_runtime::restart_current_scheduler_tick_after_idle(now);
467 }
468 drop(irq_guard);
469 drop(idle_exit_guard);
470 }
471
472 fn allocate_stack(_request: StackRequest) -> RuntimeHandleResult {
473 match allocate_runtime_stack(_request) {
474 Ok(handle) => RuntimeHandleResult::success(handle.into_raw()),
475 Err(status) => RuntimeHandleResult::failure(status),
476 }
477 }
478
479 fn deallocate_stack(_stack: StackHandle) {
480 assert_eq!(
481 deallocate_runtime_stack(_stack),
482 RuntimeStatus::Success,
483 "reclaimable task stack destruction failed"
484 );
485 }
486
487 fn allocate_kernel_tls() -> RuntimeHandleResult {
488 allocate_runtime_tls()
489 }
490
491 fn deallocate_tls(_tls: TlsHandle) {
492 assert_eq!(
493 deallocate_runtime_tls(_tls),
494 RuntimeStatus::Success,
495 "reclaimable task TLS destruction failed"
496 );
497 }
498
499 fn create_kernel_context(_request: KernelContextRequest) -> RuntimeHandleResult {
500 create_runtime_context(_request)
501 }
502
503 fn create_user_context(_request: UserContextRequest) -> RuntimeHandleResult {
504 create_user_runtime_context(_request)
505 }
506
507 fn bind_context_thread(binding: ContextThreadBinding) -> RuntimeStatus {
508 bind_runtime_context_thread(binding)
509 }
510
511 fn destroy_context(_context: ExecutionContextHandle) {
512 assert_eq!(
513 destroy_runtime_context(_context),
514 RuntimeStatus::Success,
515 "task context remained live after scheduler switch tail"
516 );
517 }
518
519 fn destroy_address_space(
520 address_space: AddressSpaceHandle,
521 ) -> AddressSpaceDestroyOutcome {
522 destroy_runtime_address_space(address_space)
523 }
524
525 fn arm_address_space_reclaim(
526 address_space: AddressSpaceHandle,
527 ) -> AddressSpaceReclaimArmOutcome {
528 arm_runtime_address_space_reclaim(address_space)
529 }
530
531 fn address_space_membarrier_state(
532 address_space: AddressSpaceHandle,
533 ) -> AddressSpaceMembarrierState {
534 runtime_address_space_membarrier_state(address_space)
535 }
536
537 fn update_address_space_membarrier_state(
538 address_space: AddressSpaceHandle,
539 registration: MembarrierRegistration,
540 phase: MembarrierRegistrationPhase,
541 ) -> AddressSpaceMembarrierState {
542 update_runtime_address_space_membarrier_state(address_space, registration, phase)
543 }
544
545 fn synchronize_membarrier_cpu(
546 cpu: RuntimeCpuId,
547 action: RuntimeMembarrierAction,
548 ) -> RuntimeStatus {
549 let cpu = cpu.as_u32() as usize;
550 let callback = match action {
551 RuntimeMembarrierAction::MemoryBarrier => membarrier_ipi_memory_barrier,
552 RuntimeMembarrierAction::RefreshRunQueue => {
553 membarrier_ipi_refresh_run_queue
554 }
555 };
556 #[cfg(feature = "ipi")]
557 {
558 match unsafe {
561 crate::ipi_delivery::run_on_cpu_sync(cpu, callback, core::ptr::null_mut())
562 } {
563 Ok(()) => RuntimeStatus::Success,
564 Err(ax_hal::irq::IrqError::CpuOffline) => RuntimeStatus::Busy,
565 Err(ax_hal::irq::IrqError::InvalidCpu) => RuntimeStatus::InvalidArgument,
566 Err(_) => RuntimeStatus::Platform,
567 }
568 }
569 #[cfg(not(feature = "ipi"))]
570 {
571 let current = unsafe { Self::current_cpu_id() }.as_u32() as usize;
574 if cpu != current {
575 return RuntimeStatus::Unsupported;
576 }
577 unsafe { callback(core::ptr::null_mut()) };
579 RuntimeStatus::Success
580 }
581 }
582
583 unsafe fn switch_context(plan: RuntimeSwitchPlan) {
584 unsafe { switch_runtime_context(plan) };
587 }
588
589 fn flush_tlb_local(_start: usize, _size: usize) {
590 ax_cpu::mmu::flush_tlb(None);
591 }
592
593 fn trace_sched_switch(record: SchedSwitchRecord) -> Option<fn()> {
594 if !SCHED_SWITCH_TRACE_ENABLED.load(Ordering::Acquire) {
595 return None;
596 }
597 let hook = SCHED_SWITCH_TRACE_HOOK.load(Ordering::Acquire);
598 if hook.is_null() {
599 return None;
600 }
601 let hook = unsafe { core::mem::transmute::<*mut (), SchedSwitchTraceHook>(hook) };
604 hook(record)
605 }
606
607 fn emergency_console_write(message: &str) {
608 ax_hal::console::write_bytes(message.as_bytes());
609 }
610
611 fn fatal_invariant(code: u32, argument: usize) -> ! {
612 panic!("ax-task invariant {code} failed with argument {argument:#x}")
613 }
614 }
615}