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
42pub(crate) const TASK_STACK_ALIGN: usize = 16;
45
46#[derive(Debug, Clone, Copy, Eq, PartialEq)]
48pub struct TaskId(u64);
49
50#[repr(u8)]
52#[derive(Debug, Clone, Copy, Eq, PartialEq)]
53pub enum TaskState {
54 Running = 1,
56 Ready = 2,
58 Blocked = 3,
61 Exited = 4,
63}
64
65#[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 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#[cfg(feature = "task-ext")]
102#[extern_trait::extern_trait(
103 pub AxTaskExt
105)]
106pub trait TaskExt {
107 fn on_enter(&self) {}
109 fn on_leave(&self) {}
111}
112
113pub 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 cpumask: SpinLock<AxCpuMask>,
125
126 sched_policy: AtomicI32,
128
129 sched_priority: AtomicI32,
131
132 in_wait_queue: AtomicBool,
134
135 cpu_id: AtomicU32,
137 #[cfg(feature = "smp")]
139 on_cpu: AtomicBool,
140 #[cfg(feature = "smp")]
151 wake_handoff: AtomicPtr<AxTask>,
152
153 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 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 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 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 pub const fn id(&self) -> TaskId {
238 self.id
239 }
240
241 pub fn name(&self) -> String {
243 self.name.lock_irqsave().clone()
244 }
245
246 pub fn set_name(&self, name: &str) {
248 *self.name.lock_irqsave() = String::from(name);
249 }
250
251 pub fn id_name(&self) -> alloc::string::String {
253 alloc::format!("Task({}, {:?})", self.id.as_u64(), self.name())
254 }
255
256 #[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 #[cfg(feature = "task-ext")]
269 pub fn task_ext(&self) -> Option<&AxTaskExt> {
270 self.task_ext.as_ref()
271 }
272
273 #[cfg(feature = "task-ext")]
275 pub fn task_ext_mut(&mut self) -> &mut Option<AxTaskExt> {
276 &mut self.task_ext
277 }
278
279 #[inline]
281 pub const fn ctx_mut(&mut self) -> &mut TaskContext {
282 self.ctx.get_mut()
283 }
284
285 #[cfg(feature = "uspace")]
289 pub fn switch_page_table(&self, root: ax_memory_addr::PhysAddr) {
290 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 f(unsafe { &mut *self.held_locks.get() })
301 }
302
303 #[inline]
307 pub fn cpu_id(&self) -> u32 {
308 self.cpu_id.load(Ordering::Acquire)
309 }
310
311 #[inline]
315 pub fn cpumask(&self) -> AxCpuMask {
316 *self.cpumask.lock_irqsave()
317 }
318
319 #[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 #[inline]
350 pub fn poll_interrupt(&self, cx: &Context) -> Poll<()> {
351 self.interrupt_waker.register(cx.waker());
356 if self.interrupted.consume() {
357 Poll::Ready(())
358 } else {
359 Poll::Pending
360 }
361 }
362
363 #[inline]
367 pub fn clear_interrupt(&self) {
368 let snapshot = self.interrupt_snapshot();
369 self.acknowledge_interrupt(snapshot);
370 }
371
372 #[inline]
376 pub fn take_interrupt(&self) -> bool {
377 self.interrupted.consume()
378 }
379
380 #[inline]
387 pub fn interrupted(&self) -> bool {
388 self.interrupted.is_pending()
389 }
390
391 #[inline]
393 pub fn interrupt(&self) {
394 self.interrupted.publish();
395 self.interrupt_waker.wake();
396 }
397
398 #[inline]
400 pub fn interrupt_snapshot(&self) -> InterruptSnapshot {
401 self.interrupted.snapshot()
402 }
403
404 #[inline]
406 pub fn acknowledge_interrupt(&self, snapshot: InterruptSnapshot) {
407 self.interrupted.acknowledge(snapshot);
408 }
409}
410
411impl 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 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 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 let header = unsafe { Pin::new_unchecked(&execution_context.header) };
480 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 unsafe { Pin::new_unchecked(&execution_context.header) }
494 }
495
496 #[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 #[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 #[inline]
554 pub(crate) fn timer_ticket(&self) -> u64 {
555 self.timer_ticket_id.load(Ordering::Acquire)
556 }
557
558 #[inline]
560 pub(crate) fn set_timer_ticket(&self, timer_ticket_id: u64) {
561 assert!(timer_ticket_id != 0);
564 self.timer_ticket_id
565 .store(timer_ticket_id, Ordering::Release);
566 }
567
568 #[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 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 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 #[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 #[cfg(feature = "smp")]
713 #[inline]
714 pub(crate) fn on_cpu(&self) -> bool {
715 self.on_cpu.load(Ordering::SeqCst)
716 }
717
718 #[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 #[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 self.wake_handoff.store(ptr, Ordering::SeqCst);
734 }
735
736 #[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 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 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 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 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
1053pub struct CurrentTask(ManuallyDrop<AxTaskRef>);
1057
1058impl CurrentTask {
1059 pub(crate) fn try_get() -> Option<Self> {
1060 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 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 #[allow(clippy::should_implement_trait)]
1082 pub fn clone(&self) -> AxTaskRef {
1083 self.0.deref().clone()
1084 }
1085
1086 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 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); 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 crate::run_queue::clear_prev_task_on_cpu();
1143 }
1144 crate::runtime_preempt::finish_initial_context_switch();
1148 #[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 let id1 = TaskId(1);
1165 let id2 = TaskId(2);
1166 assert!(id1 != id2);
1167 assert_eq!(id1, TaskId(1));
1168
1169 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 assert_eq!(TASK_STACK_ALIGN, 16);
1179
1180 #[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 let id1 = TaskId(100);
1192 let id2 = TaskId(200);
1193
1194 assert_eq!(id1, TaskId(100));
1196 assert!(id1 != id2);
1197
1198 let id4 = id1;
1200 assert!(id4 == id1);
1201
1202 true
1203 }
1204
1205 fn task_state_all_variants_hold_for_test() -> bool {
1206 let running = TaskState::Running;
1208 let ready = TaskState::Ready;
1209 let blocked = TaskState::Blocked;
1210 let exited = TaskState::Exited;
1211
1212 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 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}