Skip to main content

ax_task/
task.rs

1use alloc::{boxed::Box, string::String, sync::Arc};
2#[cfg(not(feature = "stack-guard-page"))]
3use core::alloc::Layout;
4#[cfg(feature = "smp")]
5use core::sync::atomic::AtomicPtr;
6use core::{
7    cell::{Cell, UnsafeCell},
8    fmt,
9    mem::{ManuallyDrop, offset_of},
10    ops::Deref,
11    pin::Pin,
12    ptr::NonNull,
13    sync::atomic::{AtomicBool, AtomicI32, AtomicU8, AtomicU32, AtomicU64, Ordering},
14    task::{Context, Poll},
15};
16
17#[cfg(feature = "tls")]
18use ax_hal::tls::TlsArea;
19use ax_hal::{
20    context::{KernelTlsBase, TaskContext},
21    percpu::ExecutionContextHeader,
22};
23use ax_lazyinit::LazyInit;
24#[cfg(feature = "stack-guard-page")]
25use ax_memory_addr::PAGE_SIZE_4K;
26use ax_memory_addr::{VirtAddr, align_up_4k};
27use futures_util::task::AtomicWaker;
28
29#[cfg(feature = "lockdep")]
30use crate::lockdep::HeldLockStack;
31use crate::{
32    AxCpuMask, AxTask, AxTaskRef, WaitQueue,
33    interrupt::{InterruptSnapshot, InterruptState},
34    sync::SpinLock,
35};
36
37#[cfg(target_pointer_width = "64")]
38const STACK_END_MAGIC: usize = 0x57AC_CE11_57AC_CE11usize;
39#[cfg(target_pointer_width = "32")]
40const STACK_END_MAGIC: usize = 0x57AC_CE11usize;
41
42/// Required alignment for task kernel stacks. x86_64 task context setup relies
43/// on the ABI-mandated 16-byte stack alignment at task entry.
44pub(crate) const TASK_STACK_ALIGN: usize = 16;
45
46/// A unique identifier for a thread.
47#[derive(Debug, Clone, Copy, Eq, PartialEq)]
48pub struct TaskId(u64);
49
50/// The possible states of a task.
51#[repr(u8)]
52#[derive(Debug, Clone, Copy, Eq, PartialEq)]
53pub enum TaskState {
54    /// Task is running on some CPU.
55    Running = 1,
56    /// Task is ready to run on some scheduler's ready queue.
57    Ready   = 2,
58    /// Task is blocked (in the wait queue or timer list),
59    /// and it has finished its scheduling process, it can be wake up by `notify()` on any run queue safely.
60    Blocked = 3,
61    /// Task is exited and waiting for being dropped.
62    Exited  = 4,
63}
64
65/// Task-owned wrapper around the scheduler-neutral architecture header.
66///
67/// The header is the first field so the architecture `current` identity can
68/// be converted directly to this wrapper without a second publication slot.
69#[repr(C)]
70struct TaskExecutionContext {
71    header: ExecutionContextHeader,
72    owner: NonNull<AxTask>,
73}
74
75impl TaskExecutionContext {
76    fn new(owner: NonNull<AxTask>, bootstrap: bool) -> Self {
77        Self {
78            header: if bootstrap {
79                ExecutionContextHeader::new_bootstrap()
80            } else {
81                ExecutionContextHeader::new()
82            },
83            owner,
84        }
85    }
86
87    /// Reconstructs the task wrapper whose offset-zero header is current.
88    ///
89    /// # Safety
90    ///
91    /// `header` must point to the header of a live `TaskExecutionContext`, and
92    /// the scheduler must retain the raw current-task reference while used.
93    unsafe fn from_header(header: NonNull<ExecutionContextHeader>) -> &'static Self {
94        unsafe { &*header.as_ptr().cast::<Self>() }
95    }
96}
97
98const _: () = assert!(offset_of!(TaskExecutionContext, header) == 0);
99
100/// User-defined task extended data.
101#[cfg(feature = "task-ext")]
102#[extern_trait::extern_trait(
103    /// The impl proxy type for [`TaskExt`].
104    pub AxTaskExt
105)]
106pub trait TaskExt {
107    /// Called when the task is switched in.
108    fn on_enter(&self) {}
109    /// Called when the task is switched out.
110    fn on_leave(&self) {}
111}
112
113/// The inner task structure.
114pub struct TaskInner {
115    id: TaskId,
116    name: SpinLock<String>,
117    is_idle: bool,
118    is_init: bool,
119
120    entry: Cell<Option<Box<dyn FnOnce()>>>,
121    state: AtomicU8,
122
123    /// CPU affinity mask.
124    cpumask: SpinLock<AxCpuMask>,
125
126    /// Scheduling policy of the task.
127    sched_policy: AtomicI32,
128
129    /// Scheduling priority of the task.
130    sched_priority: AtomicI32,
131
132    /// Mark whether the task is in the wait queue.
133    in_wait_queue: AtomicBool,
134
135    /// Used to indicate the CPU ID where the task is running or will run.
136    cpu_id: AtomicU32,
137    /// Used to indicate whether the task is running on a CPU.
138    #[cfg(feature = "smp")]
139    on_cpu: AtomicBool,
140    /// One-shot cross-core wake handoff.
141    ///
142    /// When a remote CPU wins the `Blocked -> Ready` transition for this task
143    /// while it is still `on_cpu` (its context not yet fully saved on its owning
144    /// CPU), the waker must NOT enqueue it — and must not spin on `on_cpu`
145    /// either (that is the cross-core mutual-wake deadlock). Instead it records
146    /// the target run-queue in `cpu_id` and stashes an owned reference here; the
147    /// owning CPU drains it in `clear_prev_task_on_cpu()` once `on_cpu` is false,
148    /// then enqueues + kicks the target. Holds a `*const AxTask` produced by
149    /// `Arc::into_raw` (null = empty). See `run_queue::put_task_with_state`.
150    #[cfg(feature = "smp")]
151    wake_handoff: AtomicPtr<AxTask>,
152
153    /// A ticket ID used to identify the timer event.
154    /// Set by `set_timer_ticket()` when creating a timer event in `set_alarm_wakeup()`,
155    /// expired by setting it as zero in `timer_ticket_expired()`, which is called by `cancel_events()`.
156    timer_ticket_id: AtomicU64,
157
158    #[cfg(feature = "preempt")]
159    need_resched: AtomicBool,
160    #[cfg(feature = "preempt")]
161    force_resched: AtomicBool,
162
163    interrupted: InterruptState,
164    interrupt_waker: AtomicWaker,
165
166    exit_code: AtomicI32,
167    wait_for_exit: WaitQueue,
168
169    kstack: TaskStack,
170    ctx: UnsafeCell<TaskContext>,
171    /// Pinned identity and CPU-binding state published by the switch tail.
172    execution_context: LazyInit<TaskExecutionContext>,
173    #[cfg(feature = "lockdep")]
174    held_locks: UnsafeCell<HeldLockStack>,
175
176    #[cfg(feature = "task-ext")]
177    task_ext: Option<AxTaskExt>,
178
179    #[cfg(feature = "tls")]
180    tls: TlsArea,
181}
182
183impl TaskId {
184    fn new() -> Self {
185        static ID_COUNTER: AtomicU64 = AtomicU64::new(1);
186        Self(ID_COUNTER.fetch_add(1, Ordering::Relaxed))
187    }
188
189    /// Convert the task ID to a `u64`.
190    pub const fn as_u64(&self) -> u64 {
191        self.0
192    }
193}
194
195impl From<u8> for TaskState {
196    #[inline]
197    fn from(state: u8) -> Self {
198        match state {
199            1 => Self::Running,
200            2 => Self::Ready,
201            3 => Self::Blocked,
202            4 => Self::Exited,
203            _ => unreachable!(),
204        }
205    }
206}
207
208unsafe impl Send for TaskInner {}
209unsafe impl Sync for TaskInner {}
210
211impl TaskInner {
212    /// Create a new task with the given entry function and stack size.
213    pub fn new<F>(entry: F, name: String, stack_size: usize) -> Self
214    where
215        F: FnOnce() + Send + 'static,
216    {
217        let kstack = TaskStack::alloc(align_up_4k(stack_size));
218        let mut t = Self::new_common(TaskId::new(), name, kstack);
219        debug!("new task: {}", t.id_name());
220
221        #[cfg(feature = "tls")]
222        let kernel_tls = KernelTlsBase::new(t.tls.tls_ptr() as usize);
223        #[cfg(not(feature = "tls"))]
224        let kernel_tls = KernelTlsBase::new(0);
225        let kstack_top = t.kstack.top();
226
227        t.entry = Cell::new(Some(Box::new(entry)));
228        t.ctx_mut()
229            .init(task_entry as *const () as usize, kstack_top, kernel_tls);
230        if t.name() == "idle" {
231            t.is_idle = true;
232        }
233        t
234    }
235
236    /// Gets the ID of the task.
237    pub const fn id(&self) -> TaskId {
238        self.id
239    }
240
241    /// Gets the name of the task.
242    pub fn name(&self) -> String {
243        self.name.lock_irqsave().clone()
244    }
245
246    /// Set the name of the task.
247    pub fn set_name(&self, name: &str) {
248        *self.name.lock_irqsave() = String::from(name);
249    }
250
251    /// Get a combined string of the task ID and name.
252    pub fn id_name(&self) -> alloc::string::String {
253        alloc::format!("Task({}, {:?})", self.id.as_u64(), self.name())
254    }
255
256    /// Wait for the task to exit, and return the exit code.
257    ///
258    /// It will return immediately if the task has already exited (but not dropped).
259    #[track_caller]
260    pub fn join(&self) -> i32 {
261        crate::api::might_sleep();
262        self.wait_for_exit
263            .wait_until(|| self.state() == TaskState::Exited);
264        self.exit_code.load(Ordering::Acquire)
265    }
266
267    /// Returns a reference to the task extended data.
268    #[cfg(feature = "task-ext")]
269    pub fn task_ext(&self) -> Option<&AxTaskExt> {
270        self.task_ext.as_ref()
271    }
272
273    /// Returns a mutable reference to the task extended data.
274    #[cfg(feature = "task-ext")]
275    pub fn task_ext_mut(&mut self) -> &mut Option<AxTaskExt> {
276        &mut self.task_ext
277    }
278
279    /// Returns a mutable reference to the task context.
280    #[inline]
281    pub const fn ctx_mut(&mut self) -> &mut TaskContext {
282        self.ctx.get_mut()
283    }
284
285    /// Updates the page table root stored in this task's context and switches
286    /// the hardware page table immediately. Only safe to call on the current
287    /// running task.
288    #[cfg(feature = "uspace")]
289    pub fn switch_page_table(&self, root: ax_memory_addr::PhysAddr) {
290        // SAFETY: we are the current task and no other thread touches our ctx.
291        unsafe { (*self.ctx.get()).set_page_table_root(root) };
292        unsafe { ax_hal::asm::write_user_page_table(root) };
293        ax_hal::asm::flush_tlb(None);
294    }
295
296    #[cfg(feature = "lockdep")]
297    pub(crate) fn with_held_locks<R>(&self, f: impl FnOnce(&mut HeldLockStack) -> R) -> R {
298        // SAFETY: the held-lock stack belongs to the current task and is only
299        // mutated by the current task while lockdep tracking is active.
300        f(unsafe { &mut *self.held_locks.get() })
301    }
302
303    /// Returns the CPU ID where the task is running or will run.
304    ///
305    /// Note: the task may not be running on the CPU, it just exists in the run queue.
306    #[inline]
307    pub fn cpu_id(&self) -> u32 {
308        self.cpu_id.load(Ordering::Acquire)
309    }
310
311    /// Gets the cpu affinity mask of the task.
312    ///
313    /// Returns the cpu affinity mask of the task in type [`AxCpuMask`].
314    #[inline]
315    pub fn cpumask(&self) -> AxCpuMask {
316        *self.cpumask.lock_irqsave()
317    }
318
319    /// Sets the cpu affinity mask of the task.
320    ///
321    /// # Arguments
322    /// `cpumask` - The cpu affinity mask to be set in type [`AxCpuMask`].
323    #[inline]
324    pub fn set_cpumask(&self, cpumask: AxCpuMask) {
325        *self.cpumask.lock_irqsave() = cpumask
326    }
327
328    #[inline]
329    pub fn sched_policy(&self) -> i32 {
330        self.sched_policy.load(Ordering::Acquire)
331    }
332
333    #[inline]
334    pub fn set_sched_policy(&self, policy: i32) {
335        self.sched_policy.store(policy, Ordering::Release)
336    }
337
338    #[inline]
339    pub fn sched_priority(&self) -> i32 {
340        self.sched_priority.load(Ordering::Acquire)
341    }
342
343    #[inline]
344    pub fn set_sched_priority(&self, prio: i32) {
345        self.sched_priority.store(prio, Ordering::Release)
346    }
347
348    /// Polls whether the task has been interrupted.
349    #[inline]
350    pub fn poll_interrupt(&self, cx: &Context) -> Poll<()> {
351        // Register the waker BEFORE rechecking the flag. Under preemptive
352        // scheduling a timer IRQ between an initial swap and register could
353        // allow `interrupt()` to run and call `wake()` on an empty waker
354        // slot — the wake is lost. Registering first closes the window.
355        self.interrupt_waker.register(cx.waker());
356        if self.interrupted.consume() {
357            Poll::Ready(())
358        } else {
359            Poll::Pending
360        }
361    }
362
363    /// Acknowledges all interruption publications visible at this call.
364    ///
365    /// Publications that race after the internal snapshot remain pending.
366    #[inline]
367    pub fn clear_interrupt(&self) {
368        let snapshot = self.interrupt_snapshot();
369        self.acknowledge_interrupt(snapshot);
370    }
371
372    /// Consumes the interruption publications currently visible to this task.
373    ///
374    /// Returns `true` if the task was interrupted.
375    #[inline]
376    pub fn take_interrupt(&self) -> bool {
377        self.interrupted.consume()
378    }
379
380    /// Checks whether the task has been interrupted without clearing
381    /// the flag.
382    ///
383    /// This is a non-consuming read, unlike [`Self::take_interrupt`]. Use this
384    /// when the interrupt flag needs to remain set for subsequent
385    /// consumers (e.g., an [`crate::future::interruptible`] future wrapper).
386    #[inline]
387    pub fn interrupted(&self) -> bool {
388        self.interrupted.is_pending()
389    }
390
391    /// Interrupts the task.
392    #[inline]
393    pub fn interrupt(&self) {
394        self.interrupted.publish();
395        self.interrupt_waker.wake();
396    }
397
398    /// Captures the interruption publications visible before a safe-point scan.
399    #[inline]
400    pub fn interrupt_snapshot(&self) -> InterruptSnapshot {
401        self.interrupted.snapshot()
402    }
403
404    /// Acknowledges the interruption publications covered by `snapshot`.
405    #[inline]
406    pub fn acknowledge_interrupt(&self, snapshot: InterruptSnapshot) {
407        self.interrupted.acknowledge(snapshot);
408    }
409}
410
411// private methods
412impl TaskInner {
413    fn new_common(id: TaskId, name: String, kstack: TaskStack) -> Self {
414        Self {
415            id,
416            name: SpinLock::new(name),
417            is_idle: false,
418            is_init: false,
419            entry: Cell::new(None),
420            state: AtomicU8::new(TaskState::Ready as u8),
421            // By default, the task is allowed to run on all CPUs.
422            cpumask: SpinLock::new(crate::api::cpu_mask_full()),
423            sched_policy: AtomicI32::new(0),
424            sched_priority: AtomicI32::new(0),
425            in_wait_queue: AtomicBool::new(false),
426            timer_ticket_id: AtomicU64::new(0),
427            cpu_id: AtomicU32::new(0),
428            #[cfg(feature = "smp")]
429            on_cpu: AtomicBool::new(false),
430            #[cfg(feature = "smp")]
431            wake_handoff: AtomicPtr::new(core::ptr::null_mut()),
432            #[cfg(feature = "preempt")]
433            need_resched: AtomicBool::new(false),
434            #[cfg(feature = "preempt")]
435            force_resched: AtomicBool::new(false),
436            interrupted: InterruptState::new(),
437            interrupt_waker: AtomicWaker::new(),
438            exit_code: AtomicI32::new(0),
439            wait_for_exit: WaitQueue::new(),
440            kstack,
441            ctx: UnsafeCell::new(TaskContext::new()),
442            execution_context: LazyInit::new(),
443            #[cfg(feature = "lockdep")]
444            held_locks: UnsafeCell::new(HeldLockStack::new()),
445            #[cfg(feature = "task-ext")]
446            task_ext: None,
447            #[cfg(feature = "tls")]
448            tls: TlsArea::alloc(),
449        }
450    }
451
452    /// Creates an "init task" using the current CPU states, to use as the
453    /// current task.
454    ///
455    /// As it is the current task, no other task can switch to it until it
456    /// switches out.
457    ///
458    /// And there is no need to set the `entry`, `kstack` or `tls` fields, as
459    /// they will be filled automatically when the task is switches out.
460    pub(crate) fn new_init(name: String, kstack: TaskStack) -> Self {
461        let mut t = Self::new_common(TaskId::new(), name, kstack);
462        t.is_init = true;
463        #[cfg(feature = "smp")]
464        t.set_on_cpu(true);
465        if t.name() == "idle" {
466            t.is_idle = true;
467        }
468        t
469    }
470
471    pub(crate) fn into_arc(self) -> AxTaskRef {
472        let task = Arc::new(AxTask::new(self));
473        let owner = NonNull::from(Arc::as_ref(&task));
474        let execution_context = task
475            .execution_context
476            .init_once(TaskExecutionContext::new(owner, task.is_init()));
477        // SAFETY: the header is stored in the Arc-owned task and never moves
478        // after this task becomes visible to a scheduler.
479        let header = unsafe { Pin::new_unchecked(&execution_context.header) };
480        // SAFETY: the Arc is not visible to any scheduler yet, so this is the
481        // only access to its architecture context.
482        unsafe { (*task.ctx_mut_ptr()).set_context_header(header.as_non_null()) };
483        task
484    }
485
486    pub(crate) fn context_header(&self) -> Pin<&ExecutionContextHeader> {
487        let execution_context = self
488            .execution_context
489            .get()
490            .expect("task execution context must be initialized after Arc allocation");
491        // SAFETY: `into_arc` initializes this field only after the containing
492        // scheduler task reaches its permanent Arc allocation.
493        unsafe { Pin::new_unchecked(&execution_context.header) }
494    }
495
496    /// Returns the current state of the task.
497    #[inline]
498    pub fn state(&self) -> TaskState {
499        self.state.load(Ordering::Acquire).into()
500    }
501
502    #[inline]
503    pub(crate) fn set_state(&self, state: TaskState) {
504        self.state.store(state as u8, Ordering::Release)
505    }
506
507    /// Transition the task state from `current_state` to `new_state`,
508    /// Returns `true` if the current state is `current_state` and the state is successfully set to `new_state`,
509    /// otherwise returns `false`.
510    #[inline]
511    pub(crate) fn transition_state(&self, current_state: TaskState, new_state: TaskState) -> bool {
512        self.state
513            .compare_exchange(
514                current_state as u8,
515                new_state as u8,
516                Ordering::AcqRel,
517                Ordering::Acquire,
518            )
519            .is_ok()
520    }
521
522    #[inline]
523    pub(crate) fn is_running(&self) -> bool {
524        matches!(self.state(), TaskState::Running)
525    }
526
527    #[inline]
528    pub(crate) fn is_ready(&self) -> bool {
529        matches!(self.state(), TaskState::Ready)
530    }
531
532    #[inline]
533    pub(crate) const fn is_init(&self) -> bool {
534        self.is_init
535    }
536
537    #[inline]
538    pub(crate) const fn is_idle(&self) -> bool {
539        self.is_idle
540    }
541
542    #[inline]
543    pub(crate) fn in_wait_queue(&self) -> bool {
544        self.in_wait_queue.load(Ordering::Acquire)
545    }
546
547    #[inline]
548    pub(crate) fn set_in_wait_queue(&self, in_wait_queue: bool) {
549        self.in_wait_queue.store(in_wait_queue, Ordering::Release);
550    }
551
552    /// Returns task's current timer ticket ID.
553    #[inline]
554    pub(crate) fn timer_ticket(&self) -> u64 {
555        self.timer_ticket_id.load(Ordering::Acquire)
556    }
557
558    /// Set the timer ticket ID.
559    #[inline]
560    pub(crate) fn set_timer_ticket(&self, timer_ticket_id: u64) {
561        // CAN NOT set timer_ticket_id to 0,
562        // because 0 is used to indicate the timer event is expired.
563        assert!(timer_ticket_id != 0);
564        self.timer_ticket_id
565            .store(timer_ticket_id, Ordering::Release);
566    }
567
568    /// Expire timer ticket ID by setting it to 0,
569    /// it can be used to identify one timer event is triggered or expired.
570    #[inline]
571    pub(crate) fn timer_ticket_expired(&self) {
572        self.timer_ticket_id.store(0, Ordering::Release);
573    }
574
575    #[inline]
576    #[cfg(feature = "preempt")]
577    pub(crate) fn set_preempt_pending(&self, pending: bool) {
578        self.need_resched.store(pending, Ordering::Release)
579    }
580
581    #[inline]
582    #[cfg(feature = "preempt")]
583    pub(crate) fn set_force_resched_pending(&self, pending: bool) {
584        self.force_resched.store(pending, Ordering::Release)
585    }
586
587    #[inline]
588    #[cfg(feature = "preempt")]
589    fn force_resched_pending(&self) -> bool {
590        self.force_resched.load(Ordering::Acquire)
591    }
592
593    #[inline]
594    #[cfg(feature = "preempt")]
595    pub(crate) fn preemption_pending(&self) -> bool {
596        self.force_resched_pending() || self.need_resched.load(Ordering::Acquire)
597    }
598
599    #[inline]
600    #[cfg(all(
601        test,
602        feature = "preempt",
603        feature = "smp",
604        feature = "ipi",
605        feature = "host-test"
606    ))]
607    pub(crate) fn preempt_pending_for_test(&self) -> bool {
608        self.need_resched.load(Ordering::Acquire)
609    }
610
611    #[inline]
612    #[cfg(all(
613        test,
614        feature = "preempt",
615        feature = "smp",
616        feature = "ipi",
617        feature = "host-test"
618    ))]
619    pub(crate) fn force_resched_pending_for_test(&self) -> bool {
620        self.force_resched_pending()
621    }
622
623    #[inline]
624    #[cfg(feature = "preempt")]
625    fn take_force_resched_pending(&self) -> bool {
626        self.force_resched.swap(false, Ordering::AcqRel)
627    }
628
629    #[inline]
630    #[cfg(feature = "preempt")]
631    pub(crate) fn preempt_count(&self) -> usize {
632        #[cfg(feature = "host-test")]
633        return 0;
634        #[cfg(not(feature = "host-test"))]
635        crate::runtime_preempt::depth()
636    }
637
638    #[inline]
639    #[cfg(feature = "preempt")]
640    pub(crate) fn can_preempt(&self, current_disable_count: usize) -> bool {
641        #[cfg(feature = "host-test")]
642        return crate::sync::host_preempt_depth() == current_disable_count;
643        #[cfg(not(feature = "host-test"))]
644        {
645            crate::runtime_preempt::depth() == current_disable_count
646        }
647    }
648
649    #[cfg(feature = "preempt")]
650    pub(crate) fn current_check_preempt_pending() {
651        use crate::sync::PreemptIrqSaveState;
652        let curr = crate::current();
653        if (curr.force_resched_pending() || curr.need_resched.load(Ordering::Acquire))
654            && curr.can_preempt(0)
655        {
656            // Note: if we want to print log msg during `preempt_resched`, we have to
657            // disable preemption here, because the ax-log may cause preemption.
658            let mut rq = crate::current_run_queue::<PreemptIrqSaveState>();
659            if curr.take_force_resched_pending() {
660                rq.force_resched()
661            } else if curr.need_resched.load(Ordering::Acquire) {
662                rq.preempt_resched()
663            }
664        }
665    }
666
667    /// Notify all tasks that join on this task.
668    pub(crate) fn notify_exit(&self, exit_code: i32) {
669        self.set_state(TaskState::Exited);
670        self.exit_code.store(exit_code, Ordering::Release);
671        self.wait_for_exit.notify_all(false);
672    }
673
674    #[inline]
675    pub(crate) const unsafe fn ctx_mut_ptr(&self) -> *mut TaskContext {
676        self.ctx.get()
677    }
678
679    #[inline]
680    pub(crate) fn check_stack_canary(&self) {
681        if self.kstack.is_canary_intact() {
682            return;
683        }
684
685        panic!(
686            "stack overflow/corruption detected for {}: stack=[{:#x}..{:#x}), expected magic={:#x}",
687            self.id_name(),
688            self.kstack.bottom().as_usize(),
689            self.kstack.top().as_usize(),
690            STACK_END_MAGIC
691        );
692    }
693
694    /// Set the CPU ID where the task is running or will run.
695    #[cfg(feature = "smp")]
696    #[inline]
697    pub(crate) fn set_cpu_id(&self, cpu_id: u32) {
698        self.cpu_id.store(cpu_id, Ordering::Release);
699    }
700
701    /// Returns whether the task is running on a CPU.
702    ///
703    /// It is used to protect the task from being moved to a different run queue
704    /// while it has not finished its scheduling process.
705    /// The `on_cpu field is set to `true` when the task is preparing to run on a CPU,
706    /// and it is set to `false` when the task has finished its scheduling process in `clear_prev_task_on_cpu()`.
707    ///
708    /// `SeqCst` because it participates in a store-before-load (Dekker) handshake
709    /// with [`Self::stash_wake`]/[`Self::take_wake`] across two distinct atomics
710    /// (`on_cpu` and `wake_handoff`); Acquire/Release would permit the
711    /// "both sides observe the other's stale value" lost-wakeup execution.
712    #[cfg(feature = "smp")]
713    #[inline]
714    pub(crate) fn on_cpu(&self) -> bool {
715        self.on_cpu.load(Ordering::SeqCst)
716    }
717
718    /// Sets whether the task is running on a CPU. `SeqCst`, see [`Self::on_cpu`].
719    #[cfg(feature = "smp")]
720    #[inline]
721    pub(crate) fn set_on_cpu(&self, on_cpu: bool) {
722        self.on_cpu.store(on_cpu, Ordering::SeqCst)
723    }
724
725    /// Stash an owned reference for a deferred cross-core wake (see the
726    /// `wake_handoff` field). Transfers ownership of `task` into the slot via
727    /// `Arc::into_raw`. Must be paired with exactly one [`Self::take_wake`].
728    #[cfg(feature = "smp")]
729    #[inline]
730    pub(crate) fn stash_wake(&self, task: AxTaskRef) {
731        let ptr = Arc::into_raw(task) as *mut AxTask;
732        // SeqCst: ordered with the `on_cpu` handshake (see `on_cpu`).
733        self.wake_handoff.store(ptr, Ordering::SeqCst);
734    }
735
736    /// Atomically consume a stashed deferred-wake reference, if any. Returns the
737    /// owned `AxTaskRef` to exactly one caller (the swap is the single arbiter);
738    /// all other callers get `None`.
739    #[cfg(feature = "smp")]
740    #[inline]
741    pub(crate) fn take_wake(&self) -> Option<AxTaskRef> {
742        let ptr = self
743            .wake_handoff
744            .swap(core::ptr::null_mut(), Ordering::SeqCst);
745        if ptr.is_null() {
746            None
747        } else {
748            // Safety: `ptr` came from `Arc::into_raw` in `stash_wake`, and the
749            // swap guarantees a single consumer, so this reconstructs the unique
750            // owning `Arc` exactly once.
751            Some(unsafe { Arc::from_raw(ptr as *const AxTask) })
752        }
753    }
754}
755
756impl fmt::Debug for TaskInner {
757    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
758        f.debug_struct("TaskInner")
759            .field("id", &self.id)
760            .field("name", &self.name)
761            .field("state", &self.state())
762            .finish()
763    }
764}
765
766impl Drop for TaskInner {
767    fn drop(&mut self) {
768        debug!("task drop: {}", self.id_name());
769    }
770}
771
772pub(crate) struct TaskStack {
773    ptr: usize,
774    size: usize,
775    #[cfg(not(feature = "stack-guard-page"))]
776    align: usize,
777    #[cfg(feature = "stack-guard-page")]
778    alloc_pages: usize,
779    kind: TaskStackKind,
780}
781
782#[derive(Debug, Clone, Copy, Eq, PartialEq)]
783enum TaskStackKind {
784    #[cfg(not(feature = "stack-guard-page"))]
785    Alloc,
786    #[cfg(feature = "stack-guard-page")]
787    GuardedAlloc,
788    Borrowed,
789}
790
791impl TaskStack {
792    pub fn alloc(size: usize) -> Self {
793        cfg_if::cfg_if! {
794            if #[cfg(feature = "stack-guard-page")] {
795                Self::alloc_guarded(size)
796            } else {
797                Self::alloc_plain(size)
798            }
799        }
800    }
801
802    #[cfg(not(feature = "stack-guard-page"))]
803    fn alloc_plain(size: usize) -> Self {
804        let align = TASK_STACK_ALIGN;
805        let layout = Layout::from_size_align(size, align).unwrap();
806        let ptr = unsafe { alloc::alloc::alloc(layout) as usize };
807        assert_ne!(ptr, 0, "task stack allocation failed");
808        let stack = Self {
809            ptr,
810            size,
811            align,
812            kind: TaskStackKind::Alloc,
813        };
814        unsafe { stack.write_canary() };
815        stack
816    }
817
818    #[cfg(feature = "stack-guard-page")]
819    fn alloc_guarded(size: usize) -> Self {
820        let usable_size = align_up_4k(size);
821        let guarded_size = usable_size
822            .checked_add(PAGE_SIZE_4K)
823            .expect("guarded task stack size overflow");
824        let pages = guarded_size / PAGE_SIZE_4K;
825        let base = ax_alloc::global_allocator()
826            .alloc_pages(pages, PAGE_SIZE_4K, ax_alloc::UsageKind::Global)
827            .expect("guarded task stack allocation failed");
828        let usable_bottom = base + PAGE_SIZE_4K;
829        let stack = Self {
830            ptr: usable_bottom,
831            size: usable_size,
832            alloc_pages: pages,
833            kind: TaskStackKind::GuardedAlloc,
834        };
835        stack.unmap_guard_page();
836        unsafe { stack.write_canary() };
837        stack
838    }
839
840    pub fn borrowed(bottom: VirtAddr, size: usize, align: usize) -> Self {
841        assert_ne!(bottom.as_usize(), 0, "static task stack pointer is null");
842        #[cfg(feature = "stack-guard-page")]
843        let _ = align;
844        let stack = Self {
845            ptr: bottom.as_usize(),
846            size,
847            #[cfg(not(feature = "stack-guard-page"))]
848            align,
849            #[cfg(feature = "stack-guard-page")]
850            alloc_pages: 0,
851            kind: TaskStackKind::Borrowed,
852        };
853        unsafe { stack.write_canary() };
854        stack
855    }
856
857    #[inline]
858    pub fn bottom(&self) -> VirtAddr {
859        VirtAddr::from(self.ptr)
860    }
861
862    #[inline]
863    pub fn top(&self) -> VirtAddr {
864        VirtAddr::from(self.ptr + self.size)
865    }
866
867    #[cfg(feature = "stack-guard-page")]
868    #[inline]
869    fn guard_bottom(&self) -> VirtAddr {
870        debug_assert_eq!(self.kind, TaskStackKind::GuardedAlloc);
871        VirtAddr::from(self.ptr - PAGE_SIZE_4K)
872    }
873
874    #[cfg(feature = "stack-guard-page")]
875    #[inline]
876    fn guard_top(&self) -> VirtAddr {
877        self.guard_bottom() + PAGE_SIZE_4K
878    }
879
880    #[cfg(feature = "stack-guard-page")]
881    #[inline]
882    fn contains_guard_addr(&self, addr: VirtAddr) -> bool {
883        matches!(self.kind, TaskStackKind::GuardedAlloc)
884            && self.guard_bottom() <= addr
885            && addr < self.guard_top()
886    }
887
888    #[cfg(feature = "stack-guard-page")]
889    fn unmap_guard_page(&self) {
890        let guard_bottom = self.guard_bottom();
891        ax_mm::kernel_aspace()
892            .lock()
893            .unmap(guard_bottom, PAGE_SIZE_4K)
894            .expect("failed to unmap task stack guard page");
895        flush_stack_guard_tlb(guard_bottom);
896    }
897
898    #[cfg(feature = "stack-guard-page")]
899    fn remap_guard_page(&self) {
900        let guard_bottom = self.guard_bottom();
901        ax_mm::kernel_aspace()
902            .lock()
903            .map_linear(
904                guard_bottom,
905                ax_hal::mem::virt_to_phys(guard_bottom),
906                PAGE_SIZE_4K,
907                ax_hal::paging::MappingFlags::READ | ax_hal::paging::MappingFlags::WRITE,
908            )
909            .expect("failed to restore task stack guard page mapping");
910        flush_stack_guard_tlb(guard_bottom);
911    }
912
913    #[inline]
914    fn canary_ptr(&self) -> *mut usize {
915        self.ptr as *mut usize
916    }
917
918    #[inline]
919    unsafe fn write_canary(&self) {
920        unsafe { self.canary_ptr().write(STACK_END_MAGIC) };
921    }
922
923    #[inline]
924    pub fn is_canary_intact(&self) -> bool {
925        unsafe { self.canary_ptr().read() == STACK_END_MAGIC }
926    }
927
928    #[cfg(all(test, not(feature = "stack-guard-page")))]
929    fn corrupt_canary_for_test(&self) {
930        unsafe { self.canary_ptr().write(0) };
931    }
932}
933
934#[cfg(all(
935    feature = "stack-guard-page",
936    not(all(feature = "smp", feature = "ipi"))
937))]
938fn flush_stack_guard_tlb(vaddr: VirtAddr) {
939    ax_hal::asm::flush_tlb(Some(vaddr));
940}
941
942#[cfg(all(feature = "stack-guard-page", feature = "smp", feature = "ipi"))]
943fn flush_stack_guard_tlb(vaddr: VirtAddr) {
944    let _guard = crate::sync::PreemptGuard::new();
945    let current_cpu = ax_hal::percpu::this_cpu_id();
946
947    core::sync::atomic::fence(Ordering::SeqCst);
948
949    for cpu_id in 0..ax_hal::cpu_num() {
950        if cpu_id == current_cpu || !ax_ipi::wait_until_cpu_ready(cpu_id) {
951            continue;
952        }
953
954        unsafe fn flush_on_target(argument: *mut ()) {
955            let address = unsafe { &*(argument as *const VirtAddr) };
956            ax_hal::asm::flush_tlb(Some(*address));
957        }
958
959        // SAFETY: call_on_cpu is synchronous, so the stack-borrowed address
960        // remains valid until the target finishes the hard-IRQ-safe TLB flush.
961        unsafe {
962            ax_ipi::call_on_cpu(
963                ax_hal::irq::CpuId(cpu_id),
964                flush_on_target,
965                core::ptr::from_ref(&vaddr).cast_mut().cast(),
966            )
967        }
968        .unwrap_or_else(|error| {
969            panic!("failed to flush stack guard TLB on CPU {cpu_id}: {error:?}")
970        });
971    }
972
973    ax_hal::asm::flush_tlb(Some(vaddr));
974}
975
976#[cfg(feature = "stack-guard-page")]
977impl TaskInner {
978    /// Reports whether `fault_addr` hits this task's stack guard page.
979    pub fn diagnose_stack_guard_page_fault(&self, fault_addr: VirtAddr) -> bool {
980        if !self.kstack.contains_guard_addr(fault_addr) {
981            return false;
982        }
983
984        error!(
985            "task stack guard page hit for {}: fault_addr={:#x}, stack=[{:#x}..{:#x}), \
986             guard=[{:#x}..{:#x})",
987            self.id_name(),
988            fault_addr.as_usize(),
989            self.kstack.bottom().as_usize(),
990            self.kstack.top().as_usize(),
991            self.kstack.guard_bottom().as_usize(),
992            self.kstack.guard_top().as_usize(),
993        );
994        true
995    }
996}
997
998impl Drop for TaskStack {
999    fn drop(&mut self) {
1000        match self.kind {
1001            #[cfg(not(feature = "stack-guard-page"))]
1002            TaskStackKind::Alloc => {
1003                let layout = Layout::from_size_align(self.size, self.align).unwrap();
1004                unsafe { alloc::alloc::dealloc(self.ptr as *mut u8, layout) }
1005            }
1006            #[cfg(feature = "stack-guard-page")]
1007            TaskStackKind::GuardedAlloc => {
1008                self.remap_guard_page();
1009                ax_alloc::global_allocator().dealloc_pages(
1010                    self.guard_bottom().as_usize(),
1011                    self.alloc_pages,
1012                    ax_alloc::UsageKind::Global,
1013                );
1014            }
1015            TaskStackKind::Borrowed => {}
1016        }
1017    }
1018}
1019
1020#[cfg(test)]
1021mod stack_tests {
1022    use super::{TASK_STACK_ALIGN, TaskStack};
1023
1024    #[cfg(not(feature = "stack-guard-page"))]
1025    #[test]
1026    fn task_stack_canary_detects_corruption() {
1027        let stack = TaskStack::alloc(0x1000);
1028        assert!(stack.is_canary_intact());
1029
1030        stack.corrupt_canary_for_test();
1031
1032        assert!(!stack.is_canary_intact());
1033    }
1034
1035    #[cfg(not(feature = "stack-guard-page"))]
1036    #[cfg(target_arch = "x86_64")]
1037    #[test]
1038    fn task_stack_top_stays_16_byte_aligned() {
1039        // x86_64 TaskContext::init() builds the initial switch frame from
1040        // kstack_top and assumes the ABI-required 16-byte stack alignment.
1041        let stack = TaskStack::alloc(0x1000);
1042        assert_eq!(stack.top().as_usize() % TASK_STACK_ALIGN, 0);
1043    }
1044
1045    #[cfg(feature = "stack-guard-page")]
1046    #[test]
1047    fn borrowed_task_stack_top_stays_16_byte_aligned_with_guard_feature() {
1048        let stack = TaskStack::borrowed(0x1000.into(), 0x1000, TASK_STACK_ALIGN);
1049        assert_eq!(stack.top().as_usize() % TASK_STACK_ALIGN, 0);
1050    }
1051}
1052
1053/// A wrapper of [`AxTaskRef`] as the current task.
1054///
1055/// It won't change the reference count of the task when created or dropped.
1056pub struct CurrentTask(ManuallyDrop<AxTaskRef>);
1057
1058impl CurrentTask {
1059    pub(crate) fn try_get() -> Option<Self> {
1060        // SAFETY: the scheduler keeps one raw strong reference for the current
1061        // task until `set_current` transfers ownership to the next task. This
1062        // bootstrap read is also used by the preemption guard implementation,
1063        // so it cannot require that same guard to have been acquired already.
1064        let header = NonNull::new(unsafe { ax_hal::percpu::current_context_raw() }.cast_mut())?;
1065        if ax_hal::percpu::is_permanent_boot_context(header) {
1066            return None;
1067        }
1068        // SAFETY: scheduler publication accepts only the offset-zero header of
1069        // a live task wrapper and retains a raw strong reference while current.
1070        let context = unsafe { TaskExecutionContext::from_header(header) };
1071        Some(Self(unsafe {
1072            ManuallyDrop::new(AxTaskRef::from_raw(context.owner.as_ptr()))
1073        }))
1074    }
1075
1076    pub(crate) fn get() -> Self {
1077        Self::try_get().expect("current task is uninitialized")
1078    }
1079
1080    /// Clone the inner `AxTaskRef`.
1081    #[allow(clippy::should_implement_trait)]
1082    pub fn clone(&self) -> AxTaskRef {
1083        self.0.deref().clone()
1084    }
1085
1086    /// Returns `true` if the current task is the same as `other`.
1087    pub fn ptr_eq(&self, other: &AxTaskRef) -> bool {
1088        Arc::ptr_eq(&self.0, other)
1089    }
1090
1091    pub(crate) unsafe fn init_current(init_task: AxTaskRef) {
1092        assert!(init_task.is_init());
1093        // SAFETY: scheduler initialization runs on an offline CPU before any
1094        // task switch or migration can occur.
1095        let header = init_task.context_header();
1096        unsafe {
1097            ax_hal::percpu::with_cpu_pin(|pin| {
1098                #[cfg(feature = "tls")]
1099                ax_hal::percpu::install_bootstrap_kernel_tls(
1100                    pin,
1101                    KernelTlsBase::new(init_task.tls.tls_ptr() as usize),
1102                );
1103                ax_hal::percpu::install_bootstrap_context(pin, header)
1104            })
1105        }
1106        .expect("CPU-local area must precede task initialization")
1107        .expect("bootstrap current-context state must install");
1108        let _ = Arc::into_raw(init_task);
1109    }
1110
1111    pub(crate) unsafe fn set_current(prev: Self, next: AxTaskRef) {
1112        let Self(arc) = prev;
1113        ManuallyDrop::into_inner(arc); // `call Arc::drop()` to decrease prev task reference count.
1114        let _ = Arc::into_raw(next);
1115    }
1116}
1117
1118#[cfg(all(test, feature = "host-test"))]
1119mod current_task_tests {
1120    #[test]
1121    fn permanent_boot_context_is_not_a_published_task() {
1122        std::thread::spawn(|| {
1123            ax_hal::percpu::initialize_host_test_cpu();
1124            assert!(super::CurrentTask::try_get().is_none());
1125        })
1126        .join()
1127        .expect("boot-context probe panicked");
1128    }
1129}
1130
1131impl Deref for CurrentTask {
1132    type Target = AxTaskRef;
1133
1134    fn deref(&self) -> &Self::Target {
1135        &self.0
1136    }
1137}
1138
1139extern "C" fn task_entry() -> ! {
1140    unsafe {
1141        // Clear the prev task on CPU before running the task entry function.
1142        crate::run_queue::clear_prev_task_on_cpu();
1143    }
1144    // A CPU-owned preemption word carries the switch guard across the raw
1145    // transfer. Unlike a resumed task, a new task has no suspended caller to
1146    // finish that guard, so its first-entry tail completes the handoff here.
1147    crate::runtime_preempt::finish_initial_context_switch();
1148    // Enable IRQs before running the task entry function.
1149    #[cfg(not(feature = "host-test"))]
1150    ax_hal::asm::enable_irqs();
1151    let task = crate::current();
1152    if let Some(entry) = task.entry.take() {
1153        entry()
1154    }
1155    crate::exit(0);
1156}
1157
1158#[cfg(test)]
1159mod coverage_tests {
1160    use super::*;
1161
1162    fn task_id_and_state_hold_for_test() -> bool {
1163        // Test TaskId
1164        let id1 = TaskId(1);
1165        let id2 = TaskId(2);
1166        assert!(id1 != id2);
1167        assert_eq!(id1, TaskId(1));
1168
1169        // Test TaskState variants
1170        assert!(TaskState::Running as u8 == 1);
1171        assert!(TaskState::Ready as u8 == 2);
1172
1173        true
1174    }
1175
1176    fn task_constants_hold_for_test() -> bool {
1177        // Test TASK_STACK_ALIGN constant
1178        assert_eq!(TASK_STACK_ALIGN, 16);
1179
1180        // Test STACK_END_MAGIC for 64-bit
1181        #[cfg(target_pointer_width = "64")]
1182        const {
1183            assert!(STACK_END_MAGIC == 0x57AC_CE11_57AC_CE11usize);
1184        }
1185
1186        true
1187    }
1188
1189    fn task_id_operations_hold_for_test() -> bool {
1190        // Test TaskId operations
1191        let id1 = TaskId(100);
1192        let id2 = TaskId(200);
1193
1194        // Test equality
1195        assert_eq!(id1, TaskId(100));
1196        assert!(id1 != id2);
1197
1198        // Test copy
1199        let id4 = id1;
1200        assert!(id4 == id1);
1201
1202        true
1203    }
1204
1205    fn task_state_all_variants_hold_for_test() -> bool {
1206        // Test all TaskState variants
1207        let running = TaskState::Running;
1208        let ready = TaskState::Ready;
1209        let blocked = TaskState::Blocked;
1210        let exited = TaskState::Exited;
1211
1212        // Verify all are different
1213        assert!(core::mem::discriminant(&running) != core::mem::discriminant(&ready));
1214        assert!(core::mem::discriminant(&ready) != core::mem::discriminant(&blocked));
1215        assert!(core::mem::discriminant(&blocked) != core::mem::discriminant(&exited));
1216
1217        // Verify ordinal values
1218        assert!(running as u8 == 1);
1219        assert!(ready as u8 == 2);
1220        assert!(blocked as u8 == 3);
1221        assert!(exited as u8 == 4);
1222
1223        true
1224    }
1225
1226    #[test]
1227    fn task_id_and_state_hold() {
1228        assert!(task_id_and_state_hold_for_test());
1229    }
1230
1231    #[test]
1232    fn task_constants_hold() {
1233        assert!(task_constants_hold_for_test());
1234    }
1235
1236    #[test]
1237    fn task_id_operations_hold() {
1238        assert!(task_id_operations_hold_for_test());
1239    }
1240
1241    #[test]
1242    fn task_state_all_variants_hold() {
1243        assert!(task_state_all_variants_hold_for_test());
1244    }
1245}