1use alloc::{
4 collections::BTreeMap,
5 string::String,
6 sync::{Arc, Weak},
7};
8use core::fmt;
9
10use ax_memory_addr::VirtAddr;
11
12#[cfg(feature = "lockdep")]
13pub use crate::lockdep::{HeldLock, HeldLockStack};
14pub(crate) use crate::run_queue::{current_run_queue, select_run_queue, select_wake_run_queue};
15use crate::sync::PreemptIrqSaveState;
16#[cfg_attr(doc, doc(cfg(feature = "task-ext")))]
17#[cfg(feature = "task-ext")]
18pub use crate::task::{AxTaskExt, TaskExt};
19pub use crate::{
20 interrupt::InterruptSnapshot,
21 task::{CurrentTask, TaskId, TaskInner, TaskState},
22 timers::{
23 ClockEventControl, HardKernelTimerAction, HardKernelTimerCallback, KernelTimerAction,
24 KernelTimerCallback, KernelTimerCancelOutcome, KernelTimerError, KernelTimerHandle,
25 MonotonicDeadline, MonotonicInstant, RestartableKernelTimerCallback, TimerCpuId,
26 arm_hard_kernel_timer, cancel_kernel_timer, disarm_hard_kernel_timer, init_timer_service,
27 register_hard_restartable_kernel_timer, register_kernel_timer,
28 register_restartable_kernel_timer, register_timer_callback,
29 },
30 wait_queue::WaitQueue,
31};
32
33pub type AxTaskRef = Arc<AxTask>;
35
36pub type WeakAxTaskRef = Weak<AxTask>;
38
39static TASK_REGISTRY: ax_lazyinit::LazyLock<crate::sync::SpinRwLock<BTreeMap<u64, WeakAxTaskRef>>> =
40 ax_lazyinit::LazyLock::new(|| crate::sync::SpinRwLock::new(BTreeMap::new()));
41
42pub type AxCpuMask = ax_cpumask::CpuMask<{ crate::build_info::CPU_CAPACITY }>;
44
45pub fn default_task_stack_size() -> usize {
47 crate::build_info::DEFAULT_TASK_STACK_SIZE
48}
49
50cfg_if::cfg_if! {
51 if #[cfg(feature = "sched-rr")] {
52 const MAX_TIME_SLICE: usize = 5;
53 pub(crate) type AxTask = ax_sched::RRTask<TaskInner, MAX_TIME_SLICE>;
54 pub(crate) type Scheduler = ax_sched::RRScheduler<TaskInner, MAX_TIME_SLICE>;
55 } else if #[cfg(feature = "sched-cfs")] {
56 pub(crate) type AxTask = ax_sched::CFSTask<TaskInner>;
57 pub(crate) type Scheduler = ax_sched::CFScheduler<TaskInner>;
58 } else {
59 pub(crate) type AxTask = ax_sched::FifoTask<TaskInner>;
61 pub(crate) type Scheduler = ax_sched::FifoScheduler<TaskInner>;
62 }
63}
64
65pub fn current_may_uninit() -> Option<CurrentTask> {
68 CurrentTask::try_get()
69}
70
71#[cfg(feature = "stack-guard-page")]
73pub fn diagnose_current_stack_guard_page_fault(fault_addr: VirtAddr) -> bool {
74 current_may_uninit().is_some_and(|curr| curr.diagnose_stack_guard_page_fault(fault_addr))
75}
76
77pub fn current() -> CurrentTask {
83 CurrentTask::get()
84}
85
86#[doc(hidden)]
88pub fn disable_preempt() -> usize {
89 #[cfg(feature = "preempt")]
90 {
91 crate::runtime_preempt::enter()
92 }
93 #[cfg(not(feature = "preempt"))]
94 {
95 0
96 }
97}
98
99#[doc(hidden)]
101pub fn enable_preempt(token: usize) {
102 #[cfg(feature = "preempt")]
103 {
104 crate::runtime_preempt::exit(token);
105 }
106 #[cfg(not(feature = "preempt"))]
107 let _ = token;
108}
109
110#[doc(hidden)]
112pub fn enable_preempt_from_irq_return(token: usize) {
113 #[cfg(feature = "preempt")]
114 {
115 crate::runtime_preempt::exit_from_irq_return(token);
116 }
117 #[cfg(not(feature = "preempt"))]
118 let _ = token;
119}
120
121#[doc(hidden)]
123pub fn runtime_preemption_pending() -> bool {
124 #[cfg(feature = "preempt")]
125 {
126 current_may_uninit().is_some_and(|curr| curr.preemption_pending())
127 }
128 #[cfg(not(feature = "preempt"))]
129 {
130 false
131 }
132}
133
134#[doc(hidden)]
136pub fn runtime_preempt_current() {
137 #[cfg(feature = "preempt")]
138 crate::task::TaskInner::current_check_preempt_pending();
139}
140
141#[cfg(feature = "lockdep")]
142#[doc(hidden)]
143pub fn collect_current_task_held_locks(snapshot: &mut crate::sync::HeldLockSnapshot) {
144 let _irq_guard = crate::sync::IrqSaveGuard::new();
145 if let Some(curr) = current_may_uninit() {
146 curr.with_held_locks(|stack| snapshot.extend(stack));
147 }
148}
149
150#[cfg(feature = "lockdep")]
151#[doc(hidden)]
152pub fn push_current_task_held_lock(held: crate::sync::HeldLock) {
153 let _irq_guard = crate::sync::IrqSaveGuard::new();
154 if let Some(curr) = current_may_uninit() {
155 curr.with_held_locks(|stack| stack.push(held));
156 }
157}
158
159#[cfg(feature = "lockdep")]
160#[doc(hidden)]
161pub fn pop_current_task_held_lock(lock_addr: usize) {
162 let _irq_guard = crate::sync::IrqSaveGuard::new();
163 if let Some(curr) = current_may_uninit() {
164 curr.with_held_locks(|stack| stack.pop_checked(lock_addr));
165 }
166}
167
168#[cfg(feature = "lockdep")]
169pub fn with_current_lockdep_stack<R>(f: impl FnOnce(&mut HeldLockStack) -> R) -> R {
170 current().with_held_locks(f)
171}
172
173pub fn init_scheduler() {
175 info!("Initialize scheduling...");
176
177 #[cfg(feature = "host-test")]
178 ax_hal::percpu::initialize_host_test_cpu();
179
180 crate::run_queue::init();
182
183 info!(" use {} scheduler.", Scheduler::scheduler_name());
184}
185
186pub(crate) fn cpu_mask_full() -> AxCpuMask {
187 use ax_lazyinit::LazyLock;
188
189 static CPU_MASK_FULL: LazyLock<AxCpuMask> = LazyLock::new(|| {
190 let cpu_num = ax_hal::cpu_num();
191 let mut cpumask = AxCpuMask::new();
192 for cpu_id in 0..cpu_num {
193 cpumask.set(cpu_id, true);
194 }
195 cpumask
196 });
197
198 *CPU_MASK_FULL
199}
200
201pub fn init_scheduler_secondary(stack_ptr: VirtAddr, stack_size: usize) {
203 crate::run_queue::init_secondary(stack_ptr, stack_size);
204}
205
206pub fn on_timer_tick() {
210 on_timer_irq(true);
211}
212
213pub fn on_timer_irq(scheduler_tick: bool) {
215 crate::timers::check_events(scheduler_tick);
216 if scheduler_tick {
217 current_run_queue::<crate::sync::RawState>().scheduler_timer_tick();
220 }
221}
222
223#[doc(hidden)]
224pub fn next_timer_deadline_nanos() -> Option<u64> {
225 crate::timers::next_deadline_nanos()
226}
227
228pub fn cpu_busy_ticks(cpu: usize) -> u64 {
235 crate::run_queue::BUSY_TICKS
236 .get(cpu)
237 .map_or(0, |t| t.load(core::sync::atomic::Ordering::Relaxed))
238}
239
240pub fn spawn_task(task: TaskInner) -> AxTaskRef {
242 spawn_task_with(task, |_| {})
243}
244
245pub fn spawn_task_with<F>(task: TaskInner, initialize: F) -> AxTaskRef
256where
257 F: FnOnce(&AxTaskRef),
258{
259 let task_ref = task.into_arc();
260 initialize_task_before_schedule(&task_ref, initialize, |task_ref| {
261 register_task(task_ref);
262 select_run_queue::<PreemptIrqSaveState>(task_ref).add_task(task_ref.clone());
263 });
264 task_ref
265}
266
267fn initialize_task_before_schedule<T>(
268 task: &T,
269 initialize: impl FnOnce(&T),
270 schedule: impl FnOnce(&T),
271) {
272 initialize(task);
273 schedule(task);
274}
275
276pub fn spawn_raw<F>(f: F, name: String, stack_size: usize) -> AxTaskRef
280where
281 F: FnOnce() + Send + 'static,
282{
283 spawn_task(TaskInner::new(f, name, stack_size))
284}
285
286pub fn spawn_with_name<F>(f: F, name: String) -> AxTaskRef
290where
291 F: FnOnce() + Send + 'static,
292{
293 spawn_raw(f, name, default_task_stack_size())
294}
295
296pub fn spawn<F>(f: F) -> AxTaskRef
303where
304 F: FnOnce() + Send + 'static,
305{
306 spawn_with_name(f, String::new())
307}
308
309pub fn set_priority(prio: isize) -> bool {
319 current_run_queue::<PreemptIrqSaveState>().set_current_priority(prio)
320}
321
322#[track_caller]
328pub fn set_current_affinity(cpumask: AxCpuMask) -> bool {
329 might_sleep();
330
331 if cpumask.is_empty() {
332 false
333 } else {
334 let curr = current().clone();
335
336 curr.set_cpumask(cpumask);
337 #[cfg(feature = "smp")]
340 if !cpumask.get(ax_hal::percpu::this_cpu_id()) {
341 let migration_task = TaskInner::new(
343 move || crate::run_queue::migrate_entry(curr),
344 "migration-task".into(),
345 default_task_stack_size(),
346 )
347 .into_arc();
348
349 current_run_queue::<PreemptIrqSaveState>().migrate_current(migration_task);
351 }
352 true
353 }
354}
355
356#[track_caller]
359pub fn yield_now() {
360 might_sleep();
361
362 yield_now_unchecked();
363}
364
365#[doc(hidden)]
371pub(crate) fn yield_now_unchecked() {
372 current_run_queue::<PreemptIrqSaveState>().yield_current()
373}
374
375#[track_caller]
377pub fn sleep(dur: core::time::Duration) {
378 sleep_until(ax_hal::time::monotonic_time() + dur);
379}
380
381#[track_caller]
384pub fn sleep_until(deadline: ax_hal::time::TimeValue) {
385 might_sleep();
386 current_run_queue::<PreemptIrqSaveState>().sleep_until(deadline);
387}
388
389#[track_caller]
391pub fn exit(exit_code: i32) -> ! {
392 might_sleep();
393
394 current_run_queue::<PreemptIrqSaveState>().exit_current(exit_code)
395}
396
397fn current_irq_context() -> bool {
398 #[cfg(not(feature = "host-test"))]
399 {
400 ax_hal::irq::in_irq_context()
401 }
402 #[cfg(feature = "host-test")]
403 {
404 false
405 }
406}
407
408fn current_cpu_id() -> usize {
409 #[cfg(not(feature = "host-test"))]
410 {
411 ax_hal::percpu::this_cpu_id()
412 }
413 #[cfg(feature = "host-test")]
414 {
415 0
416 }
417}
418
419#[derive(Clone, Copy)]
420struct AtomicContextReasons {
421 irq_disabled: bool,
422 irq_context: bool,
423 preempt_disabled: bool,
424}
425
426impl AtomicContextReasons {
427 const fn is_atomic(self) -> bool {
428 self.irq_disabled || self.irq_context || self.preempt_disabled
429 }
430}
431
432impl fmt::Display for AtomicContextReasons {
433 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
434 let mut wrote_any = false;
435 f.write_str("[")?;
436 if self.irq_disabled {
437 f.write_str("irq_disabled")?;
438 wrote_any = true;
439 }
440 if self.irq_context {
441 if wrote_any {
442 f.write_str(",")?;
443 }
444 f.write_str("irq_context")?;
445 wrote_any = true;
446 }
447 if self.preempt_disabled {
448 if wrote_any {
449 f.write_str(",")?;
450 }
451 f.write_str("preempt_disabled")?;
452 wrote_any = true;
453 }
454 if !wrote_any {
455 f.write_str("none")?;
456 }
457 f.write_str("]")
458 }
459}
460
461#[derive(Clone, Copy)]
462struct AtomicContextSnapshot {
463 irq_enabled: bool,
464 irq_context: bool,
465 preempt_count: usize,
466 cpu_id: usize,
467 task_id: Option<u64>,
468 task_state: Option<TaskState>,
469}
470
471impl AtomicContextSnapshot {
472 fn capture() -> Self {
473 let current = current_may_uninit();
474 let preempt_count = {
475 #[cfg(feature = "preempt")]
476 {
477 let task_depth = current.as_ref().map_or(0, |curr| curr.preempt_count());
478 #[cfg(not(feature = "host-test"))]
479 {
480 task_depth
481 }
482 #[cfg(feature = "host-test")]
483 {
484 task_depth + crate::sync::host_preempt_depth()
485 }
486 }
487 #[cfg(not(feature = "preempt"))]
488 {
489 0
490 }
491 };
492
493 Self {
494 irq_enabled: ax_hal::asm::irqs_enabled(),
495 irq_context: current_irq_context(),
496 preempt_count,
497 cpu_id: current_cpu_id(),
498 task_id: current.as_ref().map(|curr| curr.id().as_u64()),
499 task_state: current.as_ref().map(|curr| curr.state()),
500 }
501 }
502
503 fn reasons(self) -> AtomicContextReasons {
504 let irq_disabled = !self.irq_enabled;
505
506 AtomicContextReasons {
507 irq_disabled,
508 irq_context: self.irq_context,
509 preempt_disabled: self.preempt_count != 0,
510 }
511 }
512
513 fn is_atomic(self) -> bool {
514 self.reasons().is_atomic()
515 }
516}
517
518pub fn in_atomic_context() -> bool {
524 AtomicContextSnapshot::capture().is_atomic()
525}
526
527#[track_caller]
531pub fn might_sleep() {
532 might_sleep_at(core::panic::Location::caller());
533}
534
535#[doc(hidden)]
540pub fn might_sleep_at(caller: &'static core::panic::Location<'static>) {
541 let snapshot = AtomicContextSnapshot::capture();
542 if snapshot.is_atomic() {
543 panic_atomic_sleep(snapshot, caller);
544 }
545}
546
547#[cfg(not(feature = "lockdep"))]
548fn panic_atomic_sleep(
549 snapshot: AtomicContextSnapshot,
550 caller: &'static core::panic::Location<'static>,
551) -> ! {
552 panic!(
553 "sleeping or rescheduling is not allowed in atomic context: caller={}, reasons={}, \
554 irq_enabled={}, irq_context={}, preempt_count={}, cpu_id={}, task_id={:?}, \
555 task_state={:?}",
556 caller,
557 snapshot.reasons(),
558 snapshot.irq_enabled,
559 snapshot.irq_context,
560 snapshot.preempt_count,
561 snapshot.cpu_id,
562 snapshot.task_id,
563 snapshot.task_state
564 );
565}
566
567#[cfg(feature = "lockdep")]
568fn panic_atomic_sleep(
569 snapshot: AtomicContextSnapshot,
570 caller: &'static core::panic::Location<'static>,
571) -> ! {
572 let held_locks = crate::sync::current_task_held_lock_snapshot();
573 panic!(
574 "sleeping or rescheduling is not allowed in atomic context: caller={}, reasons={}, \
575 irq_enabled={}, irq_context={}, preempt_count={}, cpu_id={}, task_id={:?}, \
576 task_state={:?}, held_locks={}",
577 caller,
578 snapshot.reasons(),
579 snapshot.irq_enabled,
580 snapshot.irq_context,
581 snapshot.preempt_count,
582 snapshot.cpu_id,
583 snapshot.task_id,
584 snapshot.task_state,
585 held_locks
586 );
587}
588
589pub fn wake_task(task: &AxTaskRef) {
600 task.interrupt();
604
605 if task.state() == TaskState::Blocked {
617 let mut rq = select_run_queue::<PreemptIrqSaveState>(task);
618 rq.unblock_task(task.clone(), false);
619 }
620}
621
622pub fn register_task(task: &AxTaskRef) {
626 TASK_REGISTRY
627 .write()
628 .insert(task.id().as_u64(), Arc::downgrade(task));
629}
630
631pub fn task_by_id(task_id: TaskId) -> Option<AxTaskRef> {
633 TASK_REGISTRY
634 .read()
635 .get(&task_id.as_u64())
636 .and_then(|task| task.upgrade())
637}
638
639pub fn wake_task_by_id(task_id: TaskId) -> bool {
641 let Some(task) = task_by_id(task_id) else {
642 return false;
643 };
644 wake_task(&task);
645 true
646}
647
648pub fn run_idle() -> ! {
653 loop {
654 yield_now_unchecked();
655 trace!("idle task: waiting for IRQs...");
656 #[cfg(not(feature = "host-test"))]
657 ax_hal::asm::wait_for_irqs();
658 }
659}
660
661#[cfg(test)]
662mod tests {
663 use core::cell::Cell;
664
665 #[test]
666 #[cfg(feature = "host-test")]
667 fn host_atomic_context_query_does_not_require_cpu_local_state() {
668 assert!(!super::in_atomic_context());
669 }
670
671 #[test]
672 fn task_initialization_precedes_scheduling() {
673 let initialized = Cell::new(false);
674
675 super::initialize_task_before_schedule(
676 &(),
677 |_| initialized.set(true),
678 |_| {
679 assert!(
680 initialized.get(),
681 "task was scheduled before initialization"
682 )
683 },
684 );
685
686 assert!(initialized.get());
687 }
688}
689
690#[cfg(test)]
691mod std_tests {
692 use super::*;
693
694 fn axtask_api_constants_hold_for_test() -> bool {
695 let stack_size = default_task_stack_size();
697 assert!(stack_size > 0);
698 assert!(stack_size.is_multiple_of(4096)); true
701 }
702
703 fn axtask_api_type_aliases_hold_for_test() -> bool {
704 let _type_check: Option<super::AxTaskRef> = None;
708 let _weak_check: Option<super::WeakAxTaskRef> = None;
709
710 true
711 }
712
713 fn axtask_api_scheduler_name_hold_for_test() -> bool {
714 let name = super::Scheduler::scheduler_name();
716 assert!(!name.is_empty());
717
718 true
719 }
720
721 fn axtask_api_task_registry_functions_exist_hold_for_test() -> bool {
722 let _lookup: fn(super::TaskId) -> Option<super::AxTaskRef> = super::task_by_id;
723 let _wake: fn(super::TaskId) -> bool = super::wake_task_by_id;
724
725 true
726 }
727
728 #[test]
729 fn axtask_api_constants_hold() {
730 assert!(axtask_api_constants_hold_for_test());
731 }
732
733 #[test]
734 fn axtask_api_type_aliases_hold() {
735 assert!(axtask_api_type_aliases_hold_for_test());
736 }
737
738 #[test]
739 fn axtask_api_scheduler_name_hold() {
740 assert!(axtask_api_scheduler_name_hold_for_test());
741 }
742
743 #[test]
744 fn axtask_api_task_registry_functions_exist_hold() {
745 assert!(axtask_api_task_registry_functions_exist_hold_for_test());
746 }
747}