ax_runtime/thread/
runtime_impl.rs1use 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 #[cfg(feature = "paging")]
118 if let Err(error) = crate::kernel_mapping::retry_kernel_tlb_reclaims() {
119 error!("failed to retry kernel TLB quarantine before CPU offline: {error}");
120 return RuntimeStatus::Platform;
121 }
122 release_current_active_address_space();
126 crate::clock_event_runtime::take_current_clock_event_offline();
127 RuntimeStatus::Success
128 }
129
130 fn local_irq_save_and_disable() -> LocalIrqState {
131 let was_enabled = ax_hal::asm::irqs_enabled();
132 ax_hal::asm::disable_irqs();
133 unsafe { LocalIrqState::from_raw(usize::from(was_enabled)) }
136 }
137
138 unsafe fn local_irq_restore(state: LocalIrqState) {
139 if state.into_raw() != 0 {
140 ax_hal::asm::enable_irqs();
141 } else {
142 ax_hal::asm::disable_irqs();
143 }
144 }
145
146 fn irq_guard_enter() -> IrqGuardToken {
147 #[cfg(any(test, feature = "host-test"))]
148 {
149 unsafe { IrqGuardToken::from_raw(1) }
151 }
152 #[cfg(not(any(test, feature = "host-test")))]
153 {
154 if crate::guard::inherits_hardirq_cpu_owner() {
155 return IrqGuardToken::NONE;
156 }
157 crate::guard::enter_irq();
158 unsafe { IrqGuardToken::from_raw(1) }
160 }
161 }
162
163 unsafe fn irq_guard_exit(token: IrqGuardToken) {
164 #[cfg(not(any(test, feature = "host-test")))]
165 if !token.is_none() {
166 crate::guard::exit_irq("task runtime");
167 }
168 #[cfg(any(test, feature = "host-test"))]
169 let _ = token;
170 }
171
172 fn preempt_guard_enter() -> PreemptGuardToken {
173 #[cfg(any(test, feature = "host-test"))]
174 {
175 unsafe { PreemptGuardToken::from_raw(1) }
177 }
178 #[cfg(not(any(test, feature = "host-test")))]
179 {
180 match crate::guard::enter_lock_preempt() {
181 Some(token) => {
182 unsafe { PreemptGuardToken::from_raw(token.into_raw()) }
185 }
186 None => PreemptGuardToken::NONE,
187 }
188 }
189 }
190
191 unsafe fn preempt_guard_exit(token: PreemptGuardToken) {
192 assert!(
193 !token.is_none(),
194 "inherited owner scope passed to ordinary preemption exit"
195 );
196 #[cfg(not(any(test, feature = "host-test")))]
197 {
198 let token = unsafe { cpu_local::PreemptionToken::from_raw(token.into_raw()) }
199 .expect("task preemption token must retain its architecture owner");
200 crate::guard::exit_preempt(token);
201 }
202 }
203
204 unsafe fn preempt_guard_exit_irq_return(token: PreemptGuardToken) {
205 assert!(
206 !token.is_none(),
207 "inherited owner scope passed to IRQ-return preemption exit"
208 );
209 #[cfg(not(any(test, feature = "host-test")))]
210 {
211 let token = unsafe { cpu_local::PreemptionToken::from_raw(token.into_raw()) }
212 .expect("IRQ-return token must retain its architecture owner");
213 crate::guard::exit_preempt_from_irq_return(token);
214 }
215 }
216
217 fn hardirq_enter() {
218 #[cfg(feature = "irq-time-accounting")]
219 crate::irq_time::enter();
220 }
221
222 fn hardirq_exit() {
223 #[cfg(feature = "irq-time-accounting")]
224 crate::irq_time::exit();
225 }
226
227 fn publish_local_scheduler_work() -> bool {
228 #[cfg(any(test, feature = "host-test"))]
229 {
230 false
231 }
232 #[cfg(not(any(test, feature = "host-test")))]
233 {
234 crate::guard::publish_local_scheduler_work()
235 }
236 }
237
238 fn finish_context_switch_tail() -> bool {
239 finish_runtime_context_switch_tail()
240 }
241
242 fn finish_initial_context_switch() {
243 crate::guard::finish_initial_context_switch();
244 }
245
246 fn scheduler_frame_guard_enter(
247 origin: ax_task::runtime::switch::RuntimeScheduleOrigin,
248 entry: ax_task::runtime::switch::RuntimeSchedulerEntry,
249 ) -> RuntimeSchedulerFrameEnterResult {
250 crate::guard::enter_scheduler_frame_guard(origin, entry)
251 }
252
253 fn scheduler_frame_guard_exit(
254 return_to: ax_task::runtime::switch::RuntimeSchedulerReturn,
255 needs_reschedule: bool,
256 ) -> bool {
257 crate::guard::exit_scheduler_frame_guard(return_to, needs_reschedule)
258 }
259
260 fn in_hard_irq() -> bool {
261 #[cfg(any(test, feature = "host-test"))]
262 {
263 false
264 }
265 #[cfg(not(any(test, feature = "host-test")))]
266 {
267 ax_hal::irq::in_irq_context()
268 }
269 }
270
271 fn validate_schedule_context(
272 origin: ax_task::runtime::switch::RuntimeScheduleOrigin,
273 ) -> RuntimeStatus {
274 crate::guard::validate_schedule_context(origin)
275 }
276
277 fn validate_owner_cpu_context() -> RuntimeStatus {
278 crate::guard::validate_owner_cpu_context()
279 }
280
281 fn monotonic_now() -> ax_task::time::MonotonicInstant {
282 ax_task::time::MonotonicInstant::from_nanos(
283 ax_hal::time::monotonic_time_nanos(),
284 )
285 .expect("platform monotonic clock exceeded the signed ktime domain")
286 }
287
288 fn rq_clock_sample() -> ax_task::runtime::cpu::RqClockSample {
289 #[cfg(feature = "qperf-metrics")]
290 let clock_started_ns = ax_hal::time::monotonic_time_nanos();
291 let clock_ns = unsafe { ax_hal::time::scheduler_clock_source() }
294 .unwrap_or_else(|error| {
295 panic!("current scheduler clock source is unavailable: {error}")
296 });
297 #[cfg(feature = "qperf-metrics")]
298 let irq_time_started_ns = ax_hal::time::monotonic_time_nanos();
299 #[cfg(feature = "irq-time-accounting")]
300 let irq_time_ns = crate::irq_time::total_current();
301 #[cfg(feature = "qperf-metrics")]
302 {
303 ax_task::diagnostics::qperf_record_switch_scheduler_detail(
304 15,
305 clock_started_ns,
306 irq_time_started_ns,
307 );
308 }
309 #[cfg(all(feature = "qperf-metrics", feature = "irq-time-accounting"))]
310 {
311 let irq_time_finished_ns = ax_hal::time::monotonic_time_nanos();
312 ax_task::diagnostics::qperf_record_switch_scheduler_detail(
313 16,
314 irq_time_started_ns,
315 irq_time_finished_ns,
316 );
317 }
318 #[cfg(feature = "irq-time-accounting")]
319 return ax_task::runtime::cpu::RqClockSample::new(
320 ax_task::sched::SchedulerTimestamp::from_nanos(clock_ns),
321 irq_time_ns,
322 );
323 #[cfg(not(feature = "irq-time-accounting"))]
324 ax_task::runtime::cpu::RqClockSample::without_irq_time_accounting(
325 ax_task::sched::SchedulerTimestamp::from_nanos(clock_ns),
326 )
327 }
328
329 fn publish_scheduler_deadline(update: ax_task::runtime::cpu::SchedulerDeadlineUpdate) {
330 crate::clock_event_runtime::publish_local_scheduler_deadline(update);
331 }
332
333 fn publish_scheduler_runtime_deadline(
334 update: ax_task::runtime::cpu::SchedulerRuntimeDeadline,
335 ) {
336 crate::clock_event_runtime::publish_local_scheduler_runtime_deadline(update);
337 }
338
339 fn idle_exit_restart_scheduler_tick() {
340 crate::clock_event_runtime::restart_current_scheduler_tick_after_idle(
341 crate::clock_event_runtime::monotonic_now(),
342 );
343 }
344
345 fn notify_scheduler_cpu(cpu: RuntimeCpuId) -> RuntimeStatus {
346 #[cfg(any(feature = "ipi", feature = "wake-ipi"))]
347 {
348 let cpu_id = cpu.as_u32() as usize;
349 if cpu_id >= ax_hal::cpu_num() {
350 return RuntimeStatus::InvalidArgument;
351 }
352 match ax_ipi::notify_cpu(ax_hal::irq::CpuId(cpu_id)) {
353 Ok(notification) => {
354 if notification == ax_ipi::IpiNotification::Sent {
355 #[cfg(feature = "qperf-metrics")]
356 record_scheduler_ipi_send();
357 }
358 RuntimeStatus::Success
359 }
360 Err(ax_hal::irq::IrqError::InvalidCpu) => RuntimeStatus::InvalidArgument,
361 Err(ax_hal::irq::IrqError::CpuOffline) => RuntimeStatus::NotInitialized,
362 Err(ax_hal::irq::IrqError::Busy) => RuntimeStatus::Busy,
363 Err(ax_hal::irq::IrqError::NoMemory) => RuntimeStatus::NoMemory,
364 Err(ax_hal::irq::IrqError::Unsupported) => RuntimeStatus::Unsupported,
365 Err(_) => RuntimeStatus::Platform,
366 }
367 }
368 #[cfg(not(any(feature = "ipi", feature = "wake-ipi")))]
369 {
370 let _ = cpu;
371 RuntimeStatus::Unsupported
372 }
373 }
374
375 fn wait_for_interrupt() {
376 let idle_exit_guard = crate::task::sync::PreemptGuard::new();
382 ax_hal::asm::disable_irqs();
383 unsafe {
384 ax_task::runtime::cpu::finish_current_cpu_idle_polling()
388 }
389 .expect("idle handoff requires an initialized current CPU");
390 let mut now = crate::clock_event_runtime::monotonic_now();
391 let mut needs_reschedule = ax_task::runtime::cpu::current_cpu_needs_resched()
392 .expect("idle handoff requires an initialized current CPU");
393 if needs_reschedule
394 || crate::clock_event_runtime::local_clock_event_has_immediate_work(now)
395 {
396 crate::clock_event_runtime::restart_current_scheduler_tick_after_idle(now);
397 ax_hal::asm::enable_irqs();
398 drop(idle_exit_guard);
399 return;
400 }
401
402 crate::clock_event_runtime::stop_current_scheduler_tick_for_idle();
407 now = crate::clock_event_runtime::monotonic_now();
408 needs_reschedule = ax_task::runtime::cpu::current_cpu_needs_resched()
409 .expect("idle handoff requires an initialized current CPU");
410 if needs_reschedule
411 || crate::clock_event_runtime::local_clock_event_has_immediate_work(now)
412 {
413 crate::clock_event_runtime::restart_current_scheduler_tick_after_idle(now);
414 ax_hal::asm::enable_irqs();
415 drop(idle_exit_guard);
416 return;
417 }
418
419 ax_hal::asm::wait_for_irqs_disabled();
420
421 let irq_guard = crate::task::sync::IrqSaveGuard::new();
426 now = crate::clock_event_runtime::monotonic_now();
427 needs_reschedule = ax_task::runtime::cpu::current_cpu_needs_resched()
428 .expect("idle wake requires an initialized current CPU");
429 if needs_reschedule
430 || crate::clock_event_runtime::local_clock_event_has_immediate_work(now)
431 {
432 crate::clock_event_runtime::restart_current_scheduler_tick_after_idle(now);
433 }
434 drop(irq_guard);
435 drop(idle_exit_guard);
436 }
437
438 fn allocate_stack(_request: StackRequest) -> RuntimeHandleResult {
439 match allocate_runtime_stack(_request) {
440 Ok(handle) => RuntimeHandleResult::success(handle.into_raw()),
441 Err(status) => RuntimeHandleResult::failure(status),
442 }
443 }
444
445 fn deallocate_stack(_stack: StackHandle) {
446 assert_eq!(
447 deallocate_runtime_stack(_stack),
448 RuntimeStatus::Success,
449 "reclaimable task stack destruction failed"
450 );
451 }
452
453 fn allocate_kernel_tls() -> RuntimeHandleResult {
454 allocate_runtime_tls()
455 }
456
457 fn deallocate_tls(_tls: TlsHandle) {
458 assert_eq!(
459 deallocate_runtime_tls(_tls),
460 RuntimeStatus::Success,
461 "reclaimable task TLS destruction failed"
462 );
463 }
464
465 fn create_kernel_context(_request: KernelContextRequest) -> RuntimeHandleResult {
466 create_runtime_context(_request)
467 }
468
469 fn create_user_context(_request: UserContextRequest) -> RuntimeHandleResult {
470 create_user_runtime_context(_request)
471 }
472
473 fn bind_context_thread(binding: ContextThreadBinding) -> RuntimeStatus {
474 bind_runtime_context_thread(binding)
475 }
476
477 fn destroy_context(_context: ExecutionContextHandle) {
478 assert_eq!(
479 destroy_runtime_context(_context),
480 RuntimeStatus::Success,
481 "task context remained live after scheduler switch tail"
482 );
483 }
484
485 fn destroy_address_space(
486 address_space: AddressSpaceHandle,
487 ) -> AddressSpaceDestroyOutcome {
488 destroy_runtime_address_space(address_space)
489 }
490
491 fn arm_address_space_reclaim(
492 address_space: AddressSpaceHandle,
493 ) -> AddressSpaceReclaimArmOutcome {
494 arm_runtime_address_space_reclaim(address_space)
495 }
496
497 fn address_space_membarrier_state(
498 address_space: AddressSpaceHandle,
499 ) -> AddressSpaceMembarrierState {
500 runtime_address_space_membarrier_state(address_space)
501 }
502
503 fn update_address_space_membarrier_state(
504 address_space: AddressSpaceHandle,
505 registration: MembarrierRegistration,
506 phase: MembarrierRegistrationPhase,
507 ) -> AddressSpaceMembarrierState {
508 update_runtime_address_space_membarrier_state(address_space, registration, phase)
509 }
510
511 fn synchronize_membarrier_cpu(
512 cpu: RuntimeCpuId,
513 action: RuntimeMembarrierAction,
514 ) -> RuntimeStatus {
515 let cpu = cpu.as_u32() as usize;
516 let callback = match action {
517 RuntimeMembarrierAction::MemoryBarrier => membarrier_ipi_memory_barrier,
518 RuntimeMembarrierAction::RefreshRunQueue => {
519 membarrier_ipi_refresh_run_queue
520 }
521 };
522 #[cfg(feature = "ipi")]
523 {
524 match unsafe {
527 crate::ipi_delivery::run_on_cpu_sync(cpu, callback, core::ptr::null_mut())
528 } {
529 Ok(()) => RuntimeStatus::Success,
530 Err(ax_hal::irq::IrqError::CpuOffline) => RuntimeStatus::Busy,
531 Err(ax_hal::irq::IrqError::InvalidCpu) => RuntimeStatus::InvalidArgument,
532 Err(_) => RuntimeStatus::Platform,
533 }
534 }
535 #[cfg(not(feature = "ipi"))]
536 {
537 let current = unsafe { Self::current_cpu_id() }.as_u32() as usize;
540 if cpu != current {
541 return RuntimeStatus::Unsupported;
542 }
543 unsafe { callback(core::ptr::null_mut()) };
545 RuntimeStatus::Success
546 }
547 }
548
549 unsafe fn switch_context(plan: RuntimeSwitchPlan) {
550 unsafe { switch_runtime_context(plan) };
553 }
554
555 fn flush_tlb_local(_start: usize, _size: usize) {
556 ax_hal::asm::flush_tlb(None);
557 }
558
559 fn trace_sched_switch(record: SchedSwitchRecord) -> Option<fn()> {
560 if !SCHED_SWITCH_TRACE_ENABLED.load(Ordering::Acquire) {
561 return None;
562 }
563 let hook = SCHED_SWITCH_TRACE_HOOK.load(Ordering::Acquire);
564 if hook.is_null() {
565 return None;
566 }
567 let hook = unsafe { core::mem::transmute::<*mut (), SchedSwitchTraceHook>(hook) };
570 hook(record)
571 }
572
573 fn emergency_console_write(message: &str) {
574 ax_hal::console::write_bytes(message.as_bytes());
575 }
576
577 fn fatal_invariant(code: u32, argument: usize) -> ! {
578 panic!("ax-task invariant {code} failed with argument {argument:#x}")
579 }
580 }
581}