1use core::{
2 arch::naked_asm,
3 fmt,
4 mem::{align_of, offset_of, size_of},
5 ptr::NonNull,
6};
7
8use ax_memory_addr::VirtAddr;
9use cpu_local::{ExecutionContextHeader, PreparedContextSwitch};
10
11use crate::{KernelTlsBase, TaskLocalState};
12
13#[allow(missing_docs)]
15#[repr(C)]
16#[derive(Debug, Default, Clone, Copy)]
17pub struct TrapFrame {
18 pub rax: u64,
19 pub rcx: u64,
20 pub rdx: u64,
21 pub rbx: u64,
22 pub rbp: u64,
23 pub rsi: u64,
24 pub rdi: u64,
25 pub r8: u64,
26 pub r9: u64,
27 pub r10: u64,
28 pub r11: u64,
29 pub r12: u64,
30 pub r13: u64,
31 pub r14: u64,
32 pub r15: u64,
33
34 pub vector: u64,
36 pub error_code: u64,
37
38 pub rip: u64,
40 pub cs: u64,
41 pub rflags: u64,
42 pub rsp: u64,
43 pub ss: u64,
44}
45
46impl TrapFrame {
47 pub const fn origin(&self) -> crate::TrapOrigin {
49 if self.cs & 0b11 == 0 {
50 crate::TrapOrigin::Kernel
51 } else {
52 crate::TrapOrigin::User
53 }
54 }
55
56 pub const fn arg0(&self) -> usize {
58 self.rdi as _
59 }
60
61 pub const fn set_arg0(&mut self, rdi: usize) {
63 self.rdi = rdi as _;
64 }
65
66 pub const fn arg1(&self) -> usize {
68 self.rsi as _
69 }
70
71 pub const fn set_arg1(&mut self, rsi: usize) {
73 self.rsi = rsi as _;
74 }
75
76 pub const fn arg2(&self) -> usize {
78 self.rdx as _
79 }
80
81 pub const fn set_arg2(&mut self, rdx: usize) {
83 self.rdx = rdx as _;
84 }
85
86 pub const fn arg3(&self) -> usize {
88 self.r10 as _
89 }
90
91 pub const fn set_arg3(&mut self, r10: usize) {
93 self.r10 = r10 as _;
94 }
95
96 pub const fn arg4(&self) -> usize {
98 self.r8 as _
99 }
100
101 pub const fn set_arg4(&mut self, r8: usize) {
103 self.r8 = r8 as _;
104 }
105
106 pub const fn arg5(&self) -> usize {
108 self.r9 as _
109 }
110
111 pub const fn set_arg5(&mut self, r9: usize) {
113 self.r9 = r9 as _;
114 }
115
116 pub const fn ip(&self) -> usize {
118 self.rip as _
119 }
120
121 pub const fn set_ip(&mut self, rip: usize) {
123 self.rip = rip as _;
124 }
125
126 pub const fn sp(&self) -> usize {
128 self.rsp as _
129 }
130
131 pub const fn set_sp(&mut self, rsp: usize) {
133 self.rsp = rsp as _;
134 }
135
136 pub const fn sysno(&self) -> usize {
138 self.rax as usize
139 }
140
141 pub const fn set_sysno(&mut self, rax: usize) {
143 self.rax = rax as _;
144 }
145
146 pub const fn retval(&self) -> usize {
148 self.rax as _
149 }
150
151 pub const fn set_retval(&mut self, rax: usize) {
153 self.rax = rax as _;
154 }
155
156 pub fn backtrace(&self) -> axbacktrace::Backtrace {
158 axbacktrace::Backtrace::capture_trap(self.rbp as _, self.rip as _, 0)
159 }
160}
161
162#[repr(C)]
163#[derive(Debug, Default)]
164struct ContextSwitchFrame {
165 r15: u64,
166 r14: u64,
167 r13: u64,
168 r12: u64,
169 rbx: u64,
170 rbp: u64,
171 rip: u64,
172}
173
174#[allow(missing_docs)]
182#[repr(C, align(16))]
183#[derive(Clone, Copy, Debug)]
184pub struct FxsaveArea {
185 pub fcw: u16,
186 pub fsw: u16,
187 pub ftw: u16,
188 pub fop: u16,
189 pub fip: u64,
190 pub fdp: u64,
191 pub mxcsr: u32,
192 pub mxcsr_mask: u32,
193 pub st: [u64; 16],
194 pub xmm: [u64; 32],
195 _padding: [u64; 12],
196}
197
198const _: () = assert!(core::mem::size_of::<FxsaveArea>() == 512);
199
200const XSAVE_AREA_SIZE: usize = 1024;
210#[cfg(feature = "fp-simd")]
211const XSAVE_HEADER_OFFSET: usize = 512;
212#[cfg(feature = "fp-simd")]
213const XSAVE_HEADER_SIZE: usize = 64;
214#[cfg(feature = "fp-simd")]
215const XSAVE_XCOMP_BV_OFFSET: usize = XSAVE_HEADER_OFFSET + size_of::<u64>();
216#[cfg(feature = "fp-simd")]
217const XSAVE_HEADER_RESERVED_OFFSET: usize = XSAVE_XCOMP_BV_OFFSET + size_of::<u64>();
218#[cfg(feature = "fp-simd")]
219const XFEATURE_MASK_FPSSE: u64 = (1 << 0) | (1 << 1);
220#[cfg(feature = "fp-simd")]
221const MXCSR_FALLBACK_MASK: u32 = 0x0000_ffbf;
222
223#[repr(C, align(64))]
233#[derive(Clone, Copy)]
234pub struct UserXstate {
235 legacy: FxsaveArea,
237 rest: [u8; XSAVE_AREA_SIZE - 512],
241}
242
243const _: () = assert!(core::mem::size_of::<UserXstate>() == XSAVE_AREA_SIZE);
244
245#[cfg(feature = "fp-simd")]
246impl UserXstate {
247 pub const fn initial() -> Self {
249 ExtendedState::default().area
250 }
251
252 pub fn user_size() -> Option<usize> {
257 if !ExtendedState::xsave_enabled() {
258 return None;
259 }
260 let size = core::arch::x86_64::__cpuid_count(0x0d, 0).ebx as usize;
261 assert!(
262 (XSAVE_HEADER_OFFSET + XSAVE_HEADER_SIZE..=XSAVE_AREA_SIZE).contains(&size),
263 "enabled x86 user xstate exceeds the task-owned XSAVE area",
264 );
265 Some(size)
266 }
267
268 pub fn user_feature_mask() -> u64 {
270 if ExtendedState::xsave_enabled() {
271 ExtendedState::xsave_mask()
272 } else {
273 XFEATURE_MASK_FPSSE
274 }
275 }
276
277 pub const fn fxsave_area(&self) -> &FxsaveArea {
279 &self.legacy
280 }
281
282 pub fn fxsave_bytes(&self) -> &[u8] {
284 unsafe {
286 core::slice::from_raw_parts(
287 (&self.legacy as *const FxsaveArea).cast::<u8>(),
288 size_of::<FxsaveArea>(),
289 )
290 }
291 }
292
293 pub fn user_bytes(&self) -> Option<&[u8]> {
295 let size = Self::user_size()?;
296 Some(unsafe { core::slice::from_raw_parts((self as *const Self).cast::<u8>(), size) })
299 }
300
301 pub fn replace_fxsave_area(&mut self, area: FxsaveArea) -> bool {
304 if !Self::mxcsr_is_valid(area.mxcsr) {
305 return false;
306 }
307 self.legacy = area;
308 if ExtendedState::xsave_enabled() {
309 let features = self.xstate_bv() | XFEATURE_MASK_FPSSE;
310 self.write_xstate_bv(features);
311 }
312 true
313 }
314
315 pub fn replace_fxsave_bytes(&mut self, bytes: &[u8]) -> bool {
317 if bytes.len() != size_of::<FxsaveArea>() {
318 return false;
319 }
320 let mut area = core::mem::MaybeUninit::<FxsaveArea>::zeroed();
321 unsafe {
324 core::ptr::copy_nonoverlapping(
325 bytes.as_ptr(),
326 area.as_mut_ptr().cast::<u8>(),
327 bytes.len(),
328 );
329 self.replace_fxsave_area(area.assume_init())
330 }
331 }
332
333 pub fn replace_user_bytes(&mut self, bytes: &[u8]) -> bool {
336 let Some(user_size) = Self::user_size() else {
337 return false;
338 };
339 if bytes.len() != user_size {
340 return false;
341 }
342 let xstate_bv = read_u64(bytes, XSAVE_HEADER_OFFSET);
343 let xcomp_bv = read_u64(bytes, XSAVE_XCOMP_BV_OFFSET);
344 if xstate_bv & !ExtendedState::xsave_mask() != 0
345 || xcomp_bv != 0
346 || bytes[XSAVE_HEADER_RESERVED_OFFSET..XSAVE_HEADER_OFFSET + XSAVE_HEADER_SIZE]
347 .iter()
348 .any(|byte| *byte != 0)
349 {
350 return false;
351 }
352 let mxcsr = u32::from_ne_bytes(
353 bytes[24..28]
354 .try_into()
355 .expect("the FXSAVE MXCSR field has a fixed width"),
356 );
357 if !Self::mxcsr_is_valid(mxcsr) {
358 return false;
359 }
360
361 let destination = unsafe {
364 core::slice::from_raw_parts_mut((self as *mut Self).cast::<u8>(), XSAVE_AREA_SIZE)
365 };
366 destination.fill(0);
367 destination[..user_size].copy_from_slice(bytes);
368 true
369 }
370
371 pub fn replace_user_bytes_prefix(&mut self, bytes: &[u8]) -> bool {
377 let Some(user_size) = Self::user_size() else {
378 return false;
379 };
380 if !(XSAVE_HEADER_OFFSET + XSAVE_HEADER_SIZE..=user_size).contains(&bytes.len()) {
381 return false;
382 }
383 let xstate_bv = read_u64(bytes, XSAVE_HEADER_OFFSET);
384 if xstate_bv & !ExtendedState::xsave_mask() != 0
385 || !xstate_components_fit(xstate_bv, bytes.len())
386 {
387 return false;
388 }
389
390 let mut complete = [0; XSAVE_AREA_SIZE];
391 complete[..bytes.len()].copy_from_slice(bytes);
392 self.replace_user_bytes(&complete[..user_size])
393 }
394
395 fn xstate_bv(&self) -> u64 {
396 read_u64(
397 self.user_bytes()
398 .expect("xstate header requires XSAVE support"),
399 XSAVE_HEADER_OFFSET,
400 )
401 }
402
403 fn write_xstate_bv(&mut self, value: u64) {
404 let bytes = value.to_ne_bytes();
405 unsafe {
407 core::ptr::copy_nonoverlapping(
408 bytes.as_ptr(),
409 (self as *mut Self).cast::<u8>().add(XSAVE_HEADER_OFFSET),
410 bytes.len(),
411 )
412 };
413 }
414
415 fn mxcsr_is_valid(mxcsr: u32) -> bool {
416 let mut feature_image =
417 unsafe { core::mem::MaybeUninit::<FxsaveArea>::zeroed().assume_init() };
418 unsafe {
422 core::arch::x86_64::_fxsave64((&mut feature_image as *mut FxsaveArea).cast::<u8>())
423 };
424 let mask = if feature_image.mxcsr_mask == 0 {
425 MXCSR_FALLBACK_MASK
426 } else {
427 feature_image.mxcsr_mask
428 };
429 mxcsr & !mask == 0
430 }
431}
432
433#[cfg(feature = "fp-simd")]
434fn xstate_components_fit(xstate_bv: u64, supplied_size: usize) -> bool {
435 for feature in 2..u64::BITS {
436 if xstate_bv & (1 << feature) == 0 {
437 continue;
438 }
439 let component = core::arch::x86_64::__cpuid_count(0x0d, feature);
440 let offset = component.ebx as usize;
441 let size = component.eax as usize;
442 if size == 0
443 || offset
444 .checked_add(size)
445 .is_none_or(|end| end > supplied_size)
446 {
447 return false;
448 }
449 }
450 true
451}
452
453#[cfg(feature = "fp-simd")]
454fn read_u64(bytes: &[u8], offset: usize) -> u64 {
455 u64::from_ne_bytes(
456 bytes[offset..offset + size_of::<u64>()]
457 .try_into()
458 .expect("the XSAVE header field has a fixed width"),
459 )
460}
461
462pub struct ExtendedState {
468 area: UserXstate,
469}
470
471#[cfg(feature = "fp-simd")]
472impl ExtendedState {
473 #[inline]
476 pub fn fxsave_area(&self) -> &FxsaveArea {
477 &self.area.legacy
478 }
479
480 #[inline]
484 #[cfg(not(feature = "host-test"))]
485 fn xsave_enabled() -> bool {
486 let cr4 = unsafe { x86::controlregs::cr4() };
488 cr4.contains(x86::controlregs::Cr4::CR4_ENABLE_OS_XSAVE)
489 }
490
491 #[inline]
495 #[cfg(feature = "host-test")]
496 fn xsave_enabled() -> bool {
497 false
498 }
499
500 #[inline]
503 fn xsave_mask() -> u64 {
504 unsafe { x86::controlregs::xcr0().bits() }
507 }
508
509 #[inline]
511 pub fn save(&mut self) {
512 let ptr = &mut self.area as *mut _ as *mut u8;
513 #[cfg(feature = "uspace")]
514 if let Some((mask, xsaveopt_enabled)) = super::local_state::current_cpu_user_xsave_config()
515 {
516 unsafe {
522 if xsaveopt_enabled {
523 core::arch::x86_64::_xsaveopt64(ptr, mask)
524 } else {
525 core::arch::x86_64::_xsave64(ptr, mask)
526 }
527 }
528 return;
529 }
530 if Self::xsave_enabled() {
531 unsafe { core::arch::x86_64::_xsave64(ptr, Self::xsave_mask()) }
534 } else {
535 unsafe { core::arch::x86_64::_fxsave64(ptr) }
537 }
538 }
539
540 #[inline]
542 pub fn restore(&self) {
543 let ptr = &self.area as *const _ as *const u8;
544 #[cfg(feature = "uspace")]
545 if let Some((mask, _)) = super::local_state::current_cpu_user_xsave_config() {
546 unsafe { core::arch::x86_64::_xrstor64(ptr, mask) }
550 return;
551 }
552 if Self::xsave_enabled() {
553 unsafe { core::arch::x86_64::_xrstor64(ptr, Self::xsave_mask()) }
557 } else {
558 unsafe { core::arch::x86_64::_fxrstor64(ptr) }
560 }
561 }
562
563 pub const fn default() -> Self {
565 let mut area: UserXstate = unsafe { core::mem::MaybeUninit::zeroed().assume_init() };
569 area.legacy.fcw = 0x37f;
570 area.legacy.ftw = 0x0000;
587 area.legacy.mxcsr = 0x1f80;
588 Self { area }
589 }
590}
591
592impl fmt::Debug for ExtendedState {
593 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
594 f.debug_struct("ExtendedState")
595 .field("fxsave_area", &self.area.legacy)
596 .finish()
597 }
598}
599
600#[repr(C)]
620#[derive(Debug)]
621pub struct TaskContext {
622 kstack_top: VirtAddr,
624 rsp: u64,
626 task_local: TaskLocalState,
628 #[cfg(feature = "fp-simd")]
630 ext_state: ExtendedState,
631}
632
633const _: () = {
636 assert!(size_of::<KernelTlsBase>() == size_of::<usize>());
637 assert!(align_of::<KernelTlsBase>() == align_of::<usize>());
638 assert!(offset_of!(TaskContext, kstack_top) == 0);
639 assert!(offset_of!(TaskContext, rsp) == size_of::<VirtAddr>());
640 assert!(offset_of!(TaskContext, task_local) == offset_of!(TaskContext, rsp) + size_of::<u64>());
641};
642
643impl TaskContext {
644 pub fn new() -> Self {
652 Self {
653 kstack_top: va!(0),
654 rsp: 0,
655 task_local: TaskLocalState::new(),
656 #[cfg(feature = "fp-simd")]
657 ext_state: ExtendedState::default(),
658 }
659 }
660
661 pub fn init(&mut self, entry: usize, kstack_top: VirtAddr, kernel_tls: KernelTlsBase) {
664 unsafe {
665 let frame_ptr = (kstack_top.as_mut_ptr() as *mut u64).sub(1);
669 let frame_ptr = (frame_ptr as *mut ContextSwitchFrame).sub(1);
670 core::ptr::write(
671 frame_ptr,
672 ContextSwitchFrame {
673 rip: entry as _,
674 ..Default::default()
675 },
676 );
677 self.rsp = frame_ptr as u64;
678 }
679 self.kstack_top = kstack_top;
680 self.task_local.set_kernel_tls(kernel_tls);
681 }
682
683 pub fn set_context_header(&mut self, header: NonNull<ExecutionContextHeader>) {
686 self.task_local.set_context_header(header);
687 }
688
689 pub const fn context_header(&self) -> Option<NonNull<ExecutionContextHeader>> {
691 self.task_local.context_header()
692 }
693
694 pub fn prepare_switch_to(&mut self, _next_ctx: &Self) {
696 #[cfg(all(feature = "fp-simd", feature = "uspace"))]
697 {
698 let Some(current) = self.context_header() else {
699 super::local_state::assert_current_user_fp_unowned();
700 return;
701 };
702 let current = current.as_ptr().expose_provenance();
703 if super::local_state::current_user_fp_is_owner(current) {
704 self.ext_state.save();
705 super::local_state::clear_current_user_fp_owner_after_save(current);
706 }
707 }
708 #[cfg(all(feature = "fp-simd", not(feature = "uspace")))]
709 {
710 self.ext_state.save();
711 _next_ctx.ext_state.restore();
712 }
713 }
714
715 pub fn prepare_user_return_fp(&self) {
717 #[cfg(all(feature = "fp-simd", feature = "uspace"))]
718 {
719 let current = self
720 .context_header()
721 .expect("a userspace FPU owner requires a bound execution context")
722 .as_ptr()
723 .expose_provenance();
724 if super::local_state::current_user_fp_needs_restore(current) {
725 self.ext_state.restore();
726 super::local_state::publish_current_user_fp_owner(current);
727 }
728 }
729 }
730
731 #[cfg(all(feature = "fp-simd", feature = "uspace"))]
733 pub fn clone_user_fp_state_into(&self, child: &mut Self) {
734 assert!(
735 !core::ptr::eq(self, child),
736 "a cloned user FPU image requires a distinct task context",
737 );
738 assert!(
739 child.context_header().is_none(),
740 "a cloned user FPU image must be installed before context binding",
741 );
742 let current = self
743 .context_header()
744 .expect("a userspace FPU clone requires a bound execution context")
745 .as_ptr()
746 .expose_provenance();
747 if super::local_state::current_user_fp_needs_restore(current) {
748 self.ext_state.restore();
749 super::local_state::publish_current_user_fp_owner(current);
750 }
751 child.ext_state.save();
752 }
753
754 #[cfg(all(feature = "fp-simd", feature = "uspace"))]
756 pub fn capture_user_fp_state(&self) -> UserXstate {
757 let current = self
758 .context_header()
759 .expect("a userspace FPU snapshot requires a bound execution context")
760 .as_ptr()
761 .expose_provenance();
762 if super::local_state::current_user_fp_needs_restore(current) {
763 self.ext_state.restore();
764 super::local_state::publish_current_user_fp_owner(current);
765 }
766 let mut snapshot = ExtendedState::default();
767 snapshot.save();
768 snapshot.area
769 }
770
771 #[cfg(all(feature = "fp-simd", feature = "uspace"))]
773 pub fn replace_user_fp_state(&mut self, state: UserXstate) {
774 let current = self
775 .context_header()
776 .expect("a userspace FPU replacement requires a bound execution context")
777 .as_ptr()
778 .expose_provenance();
779 super::local_state::assert_current_user_fp_resettable(current);
780 self.ext_state.area = state;
781 self.ext_state.restore();
782 super::local_state::publish_current_user_fp_owner(current);
783 }
784
785 pub fn reset_user_fp_state(&mut self) {
787 #[cfg(all(feature = "fp-simd", feature = "uspace"))]
788 {
789 let current = self
790 .context_header()
791 .expect("a userspace FPU reset requires a bound execution context")
792 .as_ptr()
793 .expose_provenance();
794 super::local_state::assert_current_user_fp_resettable(current);
795 self.ext_state = ExtendedState::default();
796 self.ext_state.restore();
797 super::local_state::publish_current_user_fp_owner(current);
798 }
799 }
800
801 #[inline(always)]
809 pub unsafe fn switch_to_prepared(
810 &mut self,
811 next_ctx: &Self,
812 prepared: PreparedContextSwitch<'_>,
813 ) {
814 unsafe { prepared.commit() };
815 unsafe { context_switch_raw(self, next_ctx) }
816 }
817}
818
819#[cfg(kernel_tls)]
820#[unsafe(naked)]
821unsafe extern "C" fn context_switch_raw(_current_task: &mut TaskContext, _next_task: &TaskContext) {
822 naked_asm!(
823 "
824 .code64
825 push rbp
826 push rbx
827 push r12
828 push r13
829 push r14
830 push r15
831 mov [rdi + {rsp_offset}], rsp
832
833 // Save and restore task TLS only after all Rust helpers have finished.
834 mov ecx, {fs_base_msr}
835 rdmsr
836 shl rdx, 32
837 or rax, rdx
838 mov [rdi + {kernel_tls_offset}], rax
839 mov rax, [rsi + {kernel_tls_offset}]
840 mov rdx, rax
841 shr rdx, 32
842 mov ecx, {fs_base_msr}
843 wrmsr
844
845 mov rsp, [rsi + {rsp_offset}]
846 pop r15
847 pop r14
848 pop r13
849 pop r12
850 pop rbx
851 pop rbp
852 ret",
853 rsp_offset = const offset_of!(TaskContext, rsp),
854 kernel_tls_offset = const offset_of!(TaskContext, task_local)
855 + offset_of!(TaskLocalState, kernel_tls),
856 fs_base_msr = const 0xc000_0100_u32,
857 )
858}
859
860#[cfg(all(test, feature = "host-test", feature = "uspace"))]
861mod tests {
862 use super::*;
863
864 #[test]
865 fn context_prepare_does_not_override_the_runtime_address_space_commit() {
866 unsafe { crate::asm::write_user_page_table(0x1000.into()) };
868 let mut previous = TaskContext::new();
869 unsafe { crate::asm::write_user_page_table(0x2000.into()) };
870 let next = TaskContext::new();
871
872 unsafe { crate::asm::write_user_page_table(0x3000.into()) };
875 previous.prepare_switch_to(&next);
876
877 assert_eq!(crate::asm::read_user_page_table().as_usize(), 0x3000);
878 }
879}
880
881#[cfg(not(kernel_tls))]
882#[unsafe(naked)]
883unsafe extern "C" fn context_switch_raw(_current_task: &mut TaskContext, _next_task: &TaskContext) {
884 naked_asm!(
885 "
886 .code64
887 push rbp
888 push rbx
889 push r12
890 push r13
891 push r14
892 push r15
893 mov [rdi + {rsp_offset}], rsp
894
895 // LinuxCurrent uses the already-published kernel GS slot. FS remains
896 // userspace-owned and must not be touched by a kernel task switch.
897 mov rsp, [rsi + {rsp_offset}]
898 pop r15
899 pop r14
900 pop r13
901 pop r12
902 pop rbx
903 pop rbp
904 ret",
905 rsp_offset = const offset_of!(TaskContext, rsp),
906 )
907}