1#[cfg(feature = "context")]
2use core::arch::naked_asm;
3use core::fmt;
4#[cfg(feature = "context")]
5use core::mem::{align_of, offset_of, size_of};
6
7#[cfg(feature = "context")]
8use ax_memory_addr::VirtAddr;
9
10#[cfg(feature = "context")]
11use crate::{KernelTlsBase, TaskLocalState, context::TaskAnchor};
12
13#[repr(C)]
15#[derive(Debug, Default, Clone, Copy)]
16pub struct TrapFrame {
17 pub regs: super::registers::GeneralRegisters,
19
20 pub vector: u64,
23 pub error_code: u64,
25
26 pub rip: u64,
29 pub cs: u64,
31 pub rflags: u64,
33 pub rsp: u64,
35 pub ss: u64,
37}
38
39impl TrapFrame {
40 pub fn interrupted_context(&self) -> crate::trap::InterruptedContext {
42 use crate::trap::{InterruptedContext, InterruptedPrivilege};
43 InterruptedContext {
44 pc: self.ip(),
45 sp: self.rsp as usize,
46 fp: self.regs.rbp as usize,
47 privilege: match self.origin() {
48 crate::trap::TrapOrigin::Kernel => InterruptedPrivilege::Kernel,
49 crate::trap::TrapOrigin::User => InterruptedPrivilege::User,
50 },
51 }
52 }
53
54 pub const fn origin(&self) -> crate::TrapOrigin {
56 if self.cs & 0b11 == 0 {
57 crate::TrapOrigin::Kernel
58 } else {
59 crate::TrapOrigin::User
60 }
61 }
62
63 pub const fn arg0(&self) -> usize {
65 self.regs.rdi as _
66 }
67
68 pub const fn set_arg0(&mut self, rdi: usize) {
70 self.regs.rdi = rdi as _;
71 }
72
73 pub const fn arg1(&self) -> usize {
75 self.regs.rsi as _
76 }
77
78 pub const fn set_arg1(&mut self, rsi: usize) {
80 self.regs.rsi = rsi as _;
81 }
82
83 pub const fn arg2(&self) -> usize {
85 self.regs.rdx as _
86 }
87
88 pub const fn set_arg2(&mut self, rdx: usize) {
90 self.regs.rdx = rdx as _;
91 }
92
93 pub const fn arg3(&self) -> usize {
95 self.regs.r10 as _
96 }
97
98 pub const fn set_arg3(&mut self, r10: usize) {
100 self.regs.r10 = r10 as _;
101 }
102
103 pub const fn arg4(&self) -> usize {
105 self.regs.r8 as _
106 }
107
108 pub const fn set_arg4(&mut self, r8: usize) {
110 self.regs.r8 = r8 as _;
111 }
112
113 pub const fn arg5(&self) -> usize {
115 self.regs.r9 as _
116 }
117
118 pub const fn set_arg5(&mut self, r9: usize) {
120 self.regs.r9 = r9 as _;
121 }
122
123 pub const fn ip(&self) -> usize {
125 self.rip as _
126 }
127
128 pub const fn set_ip(&mut self, rip: usize) {
130 self.rip = rip as _;
131 }
132
133 pub const fn sp(&self) -> usize {
135 self.rsp as _
136 }
137
138 pub const fn set_sp(&mut self, rsp: usize) {
140 self.rsp = rsp as _;
141 }
142
143 pub const fn sysno(&self) -> usize {
145 self.regs.rax as usize
146 }
147
148 pub const fn set_sysno(&mut self, rax: usize) {
150 self.regs.rax = rax as _;
151 }
152
153 pub const fn retval(&self) -> usize {
155 self.regs.rax as _
156 }
157
158 pub const fn set_retval(&mut self, rax: usize) {
160 self.regs.rax = rax as _;
161 }
162
163 pub fn backtrace_registers(&self) -> crate::trap::BacktraceRegisters {
165 crate::trap::BacktraceRegisters::new(self.regs.rbp as _, self.rip as _, 0)
166 }
167}
168
169#[repr(C)]
170#[derive(Debug, Default)]
171#[cfg(feature = "context")]
172struct ContextSwitchFrame {
173 r15: u64,
174 r14: u64,
175 r13: u64,
176 r12: u64,
177 rbx: u64,
178 rbp: u64,
179 rip: u64,
180}
181
182#[allow(missing_docs)]
190#[repr(C, align(16))]
191#[derive(Clone, Copy, Debug)]
192pub struct FxsaveArea {
193 pub fcw: u16,
194 pub fsw: u16,
195 pub ftw: u16,
196 pub fop: u16,
197 pub fip: u64,
198 pub fdp: u64,
199 pub mxcsr: u32,
200 pub mxcsr_mask: u32,
201 pub st: [u64; 16],
202 pub xmm: [u64; 32],
203 _padding: [u64; 12],
204}
205
206const _: () = assert!(core::mem::size_of::<FxsaveArea>() == 512);
207
208const XSAVE_AREA_SIZE: usize = 1024;
218#[cfg(feature = "fp-simd")]
219const XSAVE_HEADER_OFFSET: usize = 512;
220#[cfg(feature = "fp-simd")]
221const XSAVE_HEADER_SIZE: usize = 64;
222#[cfg(feature = "fp-simd")]
223const XSAVE_XCOMP_BV_OFFSET: usize = XSAVE_HEADER_OFFSET + size_of::<u64>();
224#[cfg(feature = "fp-simd")]
225const XSAVE_HEADER_RESERVED_OFFSET: usize = XSAVE_XCOMP_BV_OFFSET + size_of::<u64>();
226#[cfg(feature = "fp-simd")]
227const XFEATURE_MASK_FPSSE: u64 = (1 << 0) | (1 << 1);
228#[cfg(feature = "fp-simd")]
229const MXCSR_FALLBACK_MASK: u32 = 0x0000_ffbf;
230
231#[repr(C, align(64))]
241#[derive(Clone, Copy)]
242pub struct UserXstate {
243 legacy: FxsaveArea,
245 rest: [u8; XSAVE_AREA_SIZE - 512],
249}
250
251const _: () = assert!(core::mem::size_of::<UserXstate>() == XSAVE_AREA_SIZE);
252
253#[cfg(feature = "fp-simd")]
254impl UserXstate {
255 pub const fn initial() -> Self {
257 ExtendedState::default().area
258 }
259
260 pub fn user_size() -> Option<usize> {
265 if !ExtendedState::xsave_enabled() {
266 return None;
267 }
268 let size = core::arch::x86_64::__cpuid_count(0x0d, 0).ebx as usize;
269 assert!(
270 (XSAVE_HEADER_OFFSET + XSAVE_HEADER_SIZE..=XSAVE_AREA_SIZE).contains(&size),
271 "enabled x86 user xstate exceeds the task-owned XSAVE area",
272 );
273 Some(size)
274 }
275
276 pub fn user_feature_mask() -> u64 {
278 if ExtendedState::xsave_enabled() {
279 ExtendedState::xsave_mask()
280 } else {
281 XFEATURE_MASK_FPSSE
282 }
283 }
284
285 pub const fn fxsave_area(&self) -> &FxsaveArea {
287 &self.legacy
288 }
289
290 pub fn fxsave_bytes(&self) -> &[u8] {
292 unsafe {
294 core::slice::from_raw_parts(
295 (&self.legacy as *const FxsaveArea).cast::<u8>(),
296 size_of::<FxsaveArea>(),
297 )
298 }
299 }
300
301 pub fn user_bytes(&self) -> Option<&[u8]> {
303 let size = Self::user_size()?;
304 Some(unsafe { core::slice::from_raw_parts((self as *const Self).cast::<u8>(), size) })
307 }
308
309 pub fn replace_fxsave_area(&mut self, area: FxsaveArea) -> bool {
312 if !Self::mxcsr_is_valid(area.mxcsr) {
313 return false;
314 }
315 self.legacy = area;
316 let features = self.xstate_bv() | XFEATURE_MASK_FPSSE;
320 self.write_xstate_bv(features);
321 true
322 }
323
324 pub fn replace_fxsave_bytes(&mut self, bytes: &[u8]) -> bool {
326 if bytes.len() != size_of::<FxsaveArea>() {
327 return false;
328 }
329 let mut area = core::mem::MaybeUninit::<FxsaveArea>::zeroed();
330 unsafe {
333 core::ptr::copy_nonoverlapping(
334 bytes.as_ptr(),
335 area.as_mut_ptr().cast::<u8>(),
336 bytes.len(),
337 );
338 self.replace_fxsave_area(area.assume_init())
339 }
340 }
341
342 pub fn replace_user_bytes(&mut self, bytes: &[u8]) -> bool {
345 let Some(user_size) = Self::user_size() else {
346 return false;
347 };
348 if bytes.len() != user_size {
349 return false;
350 }
351 let xstate_bv = read_u64(bytes, XSAVE_HEADER_OFFSET);
352 let xcomp_bv = read_u64(bytes, XSAVE_XCOMP_BV_OFFSET);
353 if xstate_bv & !ExtendedState::xsave_mask() != 0
354 || xcomp_bv != 0
355 || bytes[XSAVE_HEADER_RESERVED_OFFSET..XSAVE_HEADER_OFFSET + XSAVE_HEADER_SIZE]
356 .iter()
357 .any(|byte| *byte != 0)
358 {
359 return false;
360 }
361 let mxcsr = u32::from_ne_bytes(
362 bytes[24..28]
363 .try_into()
364 .expect("the FXSAVE MXCSR field has a fixed width"),
365 );
366 if !Self::mxcsr_is_valid(mxcsr) {
367 return false;
368 }
369
370 let destination = unsafe {
373 core::slice::from_raw_parts_mut((self as *mut Self).cast::<u8>(), XSAVE_AREA_SIZE)
374 };
375 destination.fill(0);
376 destination[..user_size].copy_from_slice(bytes);
377 true
378 }
379
380 pub fn replace_user_bytes_prefix(&mut self, bytes: &[u8]) -> bool {
386 let Some(user_size) = Self::user_size() else {
387 return false;
388 };
389 if !(XSAVE_HEADER_OFFSET + XSAVE_HEADER_SIZE..=user_size).contains(&bytes.len()) {
390 return false;
391 }
392 let xstate_bv = read_u64(bytes, XSAVE_HEADER_OFFSET);
393 if xstate_bv & !ExtendedState::xsave_mask() != 0
394 || !xstate_components_fit(xstate_bv, bytes.len())
395 {
396 return false;
397 }
398
399 let mut complete = [0; XSAVE_AREA_SIZE];
400 complete[..bytes.len()].copy_from_slice(bytes);
401 self.replace_user_bytes(&complete[..user_size])
402 }
403
404 fn xstate_bv(&self) -> u64 {
405 read_u64(&self.rest, 0)
406 }
407
408 fn write_xstate_bv(&mut self, value: u64) {
409 let bytes = value.to_ne_bytes();
410 unsafe {
412 core::ptr::copy_nonoverlapping(
413 bytes.as_ptr(),
414 (self as *mut Self).cast::<u8>().add(XSAVE_HEADER_OFFSET),
415 bytes.len(),
416 )
417 };
418 }
419
420 fn mxcsr_is_valid(mxcsr: u32) -> bool {
421 let mut feature_image =
422 unsafe { core::mem::MaybeUninit::<FxsaveArea>::zeroed().assume_init() };
423 unsafe {
427 core::arch::x86_64::_fxsave64((&mut feature_image as *mut FxsaveArea).cast::<u8>())
428 };
429 let mask = if feature_image.mxcsr_mask == 0 {
430 MXCSR_FALLBACK_MASK
431 } else {
432 feature_image.mxcsr_mask
433 };
434 mxcsr & !mask == 0
435 }
436}
437
438#[cfg(feature = "fp-simd")]
439fn xstate_components_fit(xstate_bv: u64, supplied_size: usize) -> bool {
440 for feature in 2..u64::BITS {
441 if xstate_bv & (1 << feature) == 0 {
442 continue;
443 }
444 let component = core::arch::x86_64::__cpuid_count(0x0d, feature);
445 let offset = component.ebx as usize;
446 let size = component.eax as usize;
447 if size == 0
448 || offset
449 .checked_add(size)
450 .is_none_or(|end| end > supplied_size)
451 {
452 return false;
453 }
454 }
455 true
456}
457
458#[cfg(feature = "fp-simd")]
459fn read_u64(bytes: &[u8], offset: usize) -> u64 {
460 u64::from_ne_bytes(
461 bytes[offset..offset + size_of::<u64>()]
462 .try_into()
463 .expect("the XSAVE header field has a fixed width"),
464 )
465}
466
467pub struct ExtendedState {
473 area: UserXstate,
474}
475
476#[cfg(feature = "fp-simd")]
477impl ExtendedState {
478 #[inline]
481 pub fn fxsave_area(&self) -> &FxsaveArea {
482 &self.area.legacy
483 }
484
485 #[inline]
489 fn xsave_enabled() -> bool {
490 let cr4 = unsafe { x86::controlregs::cr4() };
492 cr4.contains(x86::controlregs::Cr4::CR4_ENABLE_OS_XSAVE)
493 }
494
495 #[inline]
498 fn xsave_mask() -> u64 {
499 unsafe { x86::controlregs::xcr0().bits() }
502 }
503
504 #[inline]
506 pub fn save(&mut self) {
507 let ptr = &mut self.area as *mut _ as *mut u8;
508 #[cfg(feature = "uspace")]
509 if let Some((mask, xsaveopt_enabled)) = super::local_state::current_cpu_user_xsave_config()
510 {
511 unsafe {
517 if xsaveopt_enabled {
518 core::arch::x86_64::_xsaveopt64(ptr, mask)
519 } else {
520 core::arch::x86_64::_xsave64(ptr, mask)
521 }
522 }
523 return;
524 }
525 if Self::xsave_enabled() {
526 unsafe { core::arch::x86_64::_xsave64(ptr, Self::xsave_mask()) }
529 } else {
530 unsafe { core::arch::x86_64::_fxsave64(ptr) }
532 }
533 }
534
535 #[inline]
537 pub fn restore(&self) {
538 let ptr = &self.area as *const _ as *const u8;
539 #[cfg(feature = "uspace")]
540 if let Some((mask, _)) = super::local_state::current_cpu_user_xsave_config() {
541 unsafe { core::arch::x86_64::_xrstor64(ptr, mask) }
545 return;
546 }
547 if Self::xsave_enabled() {
548 unsafe { core::arch::x86_64::_xrstor64(ptr, Self::xsave_mask()) }
552 } else {
553 unsafe { core::arch::x86_64::_fxrstor64(ptr) }
555 }
556 }
557
558 pub const fn default() -> Self {
560 let mut area: UserXstate = unsafe { core::mem::MaybeUninit::zeroed().assume_init() };
564 area.legacy.fcw = 0x37f;
565 area.legacy.ftw = 0x0000;
582 area.legacy.mxcsr = 0x1f80;
583 Self { area }
584 }
585}
586
587impl fmt::Debug for ExtendedState {
588 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
589 f.debug_struct("ExtendedState")
590 .field("fxsave_area", &self.area.legacy)
591 .finish()
592 }
593}
594
595#[repr(C)]
615#[derive(Debug)]
616#[cfg(feature = "context")]
617pub struct TaskContext {
618 kstack_top: VirtAddr,
620 rsp: u64,
622 task_local: TaskLocalState,
624 #[cfg(feature = "fp-simd")]
626 ext_state: ExtendedState,
627}
628
629#[cfg(feature = "context")]
632const _: () = {
633 assert!(size_of::<KernelTlsBase>() == size_of::<usize>());
634 assert!(align_of::<KernelTlsBase>() == align_of::<usize>());
635 assert!(offset_of!(TaskContext, kstack_top) == 0);
636 assert!(offset_of!(TaskContext, rsp) == size_of::<VirtAddr>());
637 assert!(offset_of!(TaskContext, task_local) == offset_of!(TaskContext, rsp) + size_of::<u64>());
638};
639
640#[cfg(feature = "context")]
641impl TaskContext {
642 pub fn new() -> Self {
650 Self {
651 kstack_top: va!(0),
652 rsp: 0,
653 task_local: TaskLocalState::default(),
654 #[cfg(feature = "fp-simd")]
655 ext_state: ExtendedState::default(),
656 }
657 }
658
659 pub fn init(&mut self, entry: usize, kstack_top: VirtAddr, kernel_tls: KernelTlsBase) {
662 unsafe {
663 let frame_ptr = (kstack_top.as_mut_ptr() as *mut u64).sub(1);
667 let frame_ptr = (frame_ptr as *mut ContextSwitchFrame).sub(1);
668 core::ptr::write(
669 frame_ptr,
670 ContextSwitchFrame {
671 rip: entry as _,
672 ..Default::default()
673 },
674 );
675 self.rsp = frame_ptr as u64;
676 }
677 self.kstack_top = kstack_top;
678 self.task_local.set_kernel_tls(kernel_tls);
679 }
680
681 pub fn set_task_anchor(&mut self, header: TaskAnchor) {
684 self.task_local.set_task_anchor(header);
685 }
686
687 pub const fn task_anchor(&self) -> Option<TaskAnchor> {
689 self.task_local.task_anchor()
690 }
691
692 pub fn prepare_switch_to(&mut self, _next_ctx: &Self) {
694 #[cfg(all(feature = "fp-simd", feature = "uspace"))]
695 {
696 let Some(current) = self.task_anchor() else {
697 super::local_state::assert_current_user_fp_unowned();
698 return;
699 };
700 let current = current.as_ptr().expose_provenance();
701 if super::local_state::current_user_fp_is_owner(current) {
702 self.ext_state.save();
703 super::local_state::clear_current_user_fp_owner_after_save(current);
704 }
705 }
706 #[cfg(all(feature = "fp-simd", not(feature = "uspace")))]
707 {
708 self.ext_state.save();
709 _next_ctx.ext_state.restore();
710 }
711 }
712
713 pub fn prepare_user_return_fp(&self) {
715 #[cfg(all(feature = "fp-simd", feature = "uspace"))]
716 {
717 let current = self
718 .task_anchor()
719 .expect("a userspace FPU owner requires a bound execution context")
720 .as_ptr()
721 .expose_provenance();
722 if super::local_state::current_user_fp_needs_restore(current) {
723 self.ext_state.restore();
724 super::local_state::publish_current_user_fp_owner(current);
725 }
726 }
727 }
728
729 #[cfg(all(feature = "fp-simd", feature = "uspace"))]
731 pub fn clone_user_fp_state_into(&self, child: &mut Self) {
732 assert!(
733 !core::ptr::eq(self, child),
734 "a cloned user FPU image requires a distinct task context",
735 );
736 assert!(
737 child.task_anchor().is_none(),
738 "a cloned user FPU image must be installed before context binding",
739 );
740 let current = self
741 .task_anchor()
742 .expect("a userspace FPU clone requires a bound execution context")
743 .as_ptr()
744 .expose_provenance();
745 if super::local_state::current_user_fp_needs_restore(current) {
746 self.ext_state.restore();
747 super::local_state::publish_current_user_fp_owner(current);
748 }
749 child.ext_state.save();
750 }
751
752 #[cfg(all(feature = "fp-simd", feature = "uspace"))]
754 pub fn capture_user_fp_state(&self) -> UserXstate {
755 let current = self
756 .task_anchor()
757 .expect("a userspace FPU snapshot requires a bound execution context")
758 .as_ptr()
759 .expose_provenance();
760 if super::local_state::current_user_fp_needs_restore(current) {
761 self.ext_state.restore();
762 super::local_state::publish_current_user_fp_owner(current);
763 }
764 let mut snapshot = ExtendedState::default();
765 snapshot.save();
766 snapshot.area
767 }
768
769 #[cfg(all(feature = "fp-simd", feature = "uspace"))]
771 pub fn replace_user_fp_state(&mut self, state: UserXstate) {
772 let current = self
773 .task_anchor()
774 .expect("a userspace FPU replacement requires a bound execution context")
775 .as_ptr()
776 .expose_provenance();
777 super::local_state::assert_current_user_fp_resettable(current);
778 self.ext_state.area = state;
779 self.ext_state.restore();
780 super::local_state::publish_current_user_fp_owner(current);
781 }
782
783 pub fn reset_user_fp_state(&mut self) {
785 #[cfg(all(feature = "fp-simd", feature = "uspace"))]
786 {
787 let current = self
788 .task_anchor()
789 .expect("a userspace FPU reset requires a bound execution context")
790 .as_ptr()
791 .expose_provenance();
792 super::local_state::assert_current_user_fp_resettable(current);
793 self.ext_state = ExtendedState::default();
794 self.ext_state.restore();
795 super::local_state::publish_current_user_fp_owner(current);
796 }
797 }
798
799 #[inline(always)]
808 pub unsafe fn switch_to(&mut self, next_ctx: &Self) {
809 unsafe { context_switch_raw(self, next_ctx) }
810 }
811}
812
813#[cfg(kernel_tls)]
814#[unsafe(naked)]
815#[cfg(feature = "context")]
816unsafe extern "C" fn context_switch_raw(_current_task: &mut TaskContext, _next_task: &TaskContext) {
817 naked_asm!(
818 "
819 .code64
820 push rbp
821 push rbx
822 push r12
823 push r13
824 push r14
825 push r15
826 mov [rdi + {rsp_offset}], rsp
827
828 // Save and restore task TLS only after all Rust helpers have finished.
829 mov ecx, {fs_base_msr}
830 rdmsr
831 shl rdx, 32
832 or rax, rdx
833 mov [rdi + {kernel_tls_offset}], rax
834 mov rax, [rsi + {kernel_tls_offset}]
835 mov rdx, rax
836 shr rdx, 32
837 mov ecx, {fs_base_msr}
838 wrmsr
839
840 mov rsp, [rsi + {rsp_offset}]
841 pop r15
842 pop r14
843 pop r13
844 pop r12
845 pop rbx
846 pop rbp
847 ret",
848 rsp_offset = const offset_of!(TaskContext, rsp),
849 kernel_tls_offset = const offset_of!(TaskContext, task_local)
850 + offset_of!(TaskLocalState, kernel_tls),
851 fs_base_msr = const 0xc000_0100_u32,
852 )
853}
854
855#[cfg(not(kernel_tls))]
856#[unsafe(naked)]
857#[cfg(feature = "context")]
858unsafe extern "C" fn context_switch_raw(_current_task: &mut TaskContext, _next_task: &TaskContext) {
859 naked_asm!(
860 "
861 .code64
862 push rbp
863 push rbx
864 push r12
865 push r13
866 push r14
867 push r15
868 mov [rdi + {rsp_offset}], rsp
869
870 // LinuxCurrent uses the already-published kernel GS slot. FS remains
871 // userspace-owned and must not be touched by a kernel task switch.
872 mov rsp, [rsi + {rsp_offset}]
873 pop r15
874 pop r14
875 pop r13
876 pop r12
877 pop rbx
878 pop rbp
879 ret",
880 rsp_offset = const offset_of!(TaskContext, rsp),
881 )
882}