diff --git a/litebox_platform_lvbs/src/arch/x86/gdt.rs b/litebox_platform_lvbs/src/arch/x86/gdt.rs index daa5f7bb0..507d328eb 100644 --- a/litebox_platform_lvbs/src/arch/x86/gdt.rs +++ b/litebox_platform_lvbs/src/arch/x86/gdt.rs @@ -1,6 +1,8 @@ //! Global Descriptor Table (GDT) and Task State Segment (TSS) -use crate::host::per_cpu_variables::{with_per_cpu_variables, with_per_cpu_variables_mut}; +use crate::host::per_cpu_variables::{ + PerCpuVariablesAsm, with_per_cpu_variables_asm, with_per_cpu_variables_mut, +}; use alloc::boxed::Box; use x86_64::{ PrivilegeLevel, VirtAddr, @@ -77,11 +79,8 @@ impl Default for GdtWrapper { } fn setup_gdt_tss() { - const STACK_ALIGNMENT: u64 = 16; - - let stack_top = with_per_cpu_variables(|per_cpu_variables| { - per_cpu_variables.interrupt_stack_top() & !(STACK_ALIGNMENT - 1) - }); + let stack_top = with_per_cpu_variables_asm(PerCpuVariablesAsm::get_interrupt_stack_ptr); + let stack_top = u64::try_from(stack_top).unwrap(); let mut tss = Box::new(AlignedTss(TaskStateSegment::new())); tss.0.interrupt_stack_table[0] = VirtAddr::new(stack_top); diff --git a/litebox_platform_lvbs/src/arch/x86/mm/paging.rs b/litebox_platform_lvbs/src/arch/x86/mm/paging.rs index a832417a0..ea1e3118a 100644 --- a/litebox_platform_lvbs/src/arch/x86/mm/paging.rs +++ b/litebox_platform_lvbs/src/arch/x86/mm/paging.rs @@ -335,6 +335,7 @@ impl X64PageTable<'_, M, ALIGN> { /// # Panics /// Panics if the page table is invalid #[allow(clippy::similar_names)] + #[expect(dead_code)] pub(crate) fn change_address_space(&self) -> PhysFrame { let p4_va = core::ptr::from_ref::(self.inner.lock().level_4_table()); let p4_pa = M::va_to_pa(VirtAddr::new(p4_va as u64)); @@ -354,6 +355,7 @@ impl X64PageTable<'_, M, ALIGN> { /// To this end, we use this function to match the physical frame of the page table contained in each user /// context structure with the CR3 value in a system call context (before changing the page table). #[allow(clippy::similar_names)] + #[expect(dead_code)] pub(crate) fn get_physical_frame(&self) -> PhysFrame { let p4_va = core::ptr::from_ref::(self.inner.lock().level_4_table()); let p4_pa = M::va_to_pa(VirtAddr::new(p4_va as u64)); diff --git a/litebox_platform_lvbs/src/host/per_cpu_variables.rs b/litebox_platform_lvbs/src/host/per_cpu_variables.rs index 50f1a4180..26021324b 100644 --- a/litebox_platform_lvbs/src/host/per_cpu_variables.rs +++ b/litebox_platform_lvbs/src/host/per_cpu_variables.rs @@ -33,7 +33,6 @@ pub struct PerCpuVariables { hvcall_input: [u8; PAGE_SIZE], hvcall_output: [u8; PAGE_SIZE], pub vtl0_state: VtlState, - pub vtl1_state: VtlState, pub vtl0_locked_regs: ControlRegMap, pub gdt: Option<&'static gdt::GdtWrapper>, vtl0_xsave_area_addr: VirtAddr, @@ -45,7 +44,7 @@ impl PerCpuVariables { const XSAVE_ALIGNMENT: usize = 64; // XSAVE and XRSTORE require a 64-byte aligned buffer const XSAVE_MASK: u64 = 0b11; // let XSAVE and XRSTORE deal with x87 and SSE states - pub fn kernel_stack_top(&self) -> u64 { + pub(crate) fn kernel_stack_top(&self) -> u64 { &raw const self.kernel_stack as u64 + (self.kernel_stack.len() - 1) as u64 } @@ -167,25 +166,6 @@ static mut BSP_VARIABLES: PerCpuVariables = PerCpuVariables { hvcall_output: [0u8; PAGE_SIZE], vtl0_state: VtlState { rbp: 0, - cr2: 0, - rax: 0, - rbx: 0, - rcx: 0, - rdx: 0, - rsi: 0, - rdi: 0, - r8: 0, - r9: 0, - r10: 0, - r11: 0, - r12: 0, - r13: 0, - r14: 0, - r15: 0, - }, - vtl1_state: VtlState { - rbp: 0, - cr2: 0, rax: 0, rbx: 0, rcx: 0, @@ -210,28 +190,84 @@ static mut BSP_VARIABLES: PerCpuVariables = PerCpuVariables { tls: VirtAddr::zero(), }; -/// Specify the layout of Kernel TLS area. +/// Specify the layout of PerCpuVariables for Assembly area. +/// +/// Unlike userland platforms, this kernel platform does't rely on the `tbss` section +/// to specify Kernel TLS because there is no ELF loader that will set up TLS for it. +/// +/// Note that kernel & host and user & guest are interchangeable in this context. +/// We use "kernel" and "user" here to emphasize that there must be hardware-enforced +/// mode transitions (i.e., ring transitions through iretq/syscall) unlike userland +/// platforms. +/// +/// TODO: Consider unifying with `PerCpuVariables` if possible. #[non_exhaustive] +#[cfg(target_arch = "x86_64")] #[repr(C)] #[derive(Clone)] -pub struct KernelTls { - pub kernel_stack_ptr: Cell, // gs:[0x0] - pub interrupt_stack_ptr: Cell, // gs:[0x8] - pub vtl_return_addr: Cell, // gs:[0x10] +pub struct PerCpuVariablesAsm { + /// Initial kernel stack pointer to reset the kernel stack on VTL switch + kernel_stack_ptr: Cell, // gs:[0x0] + /// Initial interrupt stack pointer for x86 IST + interrupt_stack_ptr: Cell, // gs:[0x8] + /// Return address for call-based VTL switching + vtl_return_addr: Cell, // gs:[0x10] + /// Scratch pad + scratch: Cell, // gs:[0x18] + /// Top address of VTL0 VTLState + vtl0_state_top_addr: Cell, // gs:[0x20] + /// Current kernel stack pointer + cur_kernel_stack_ptr: Cell, // gs:[0x28] + /// Current kernel base pointer + cur_kernel_base_ptr: Cell, // gs:[0x30] + /// Top address of the user context area + user_context_top_addr: Cell, // gs:[0x38] } -/// Kernel TLS offsets. Difference between the GS value and an offset is used to access -/// the corresponding variable. -#[non_exhaustive] -#[repr(usize)] -pub enum KernelTlsOffset { - KernelStackPtr = offset_of!(KernelTls, kernel_stack_ptr), - InterruptStackPtr = offset_of!(KernelTls, interrupt_stack_ptr), - VtlReturnAddr = offset_of!(KernelTls, vtl_return_addr), +impl PerCpuVariablesAsm { + pub fn set_kernel_stack_ptr(&self, sp: usize) { + self.kernel_stack_ptr.set(sp); + } + pub fn set_interrupt_stack_ptr(&self, sp: usize) { + self.interrupt_stack_ptr.set(sp); + } + pub fn get_interrupt_stack_ptr(&self) -> usize { + self.interrupt_stack_ptr.get() + } + pub fn set_vtl_return_addr(&self, addr: usize) { + self.vtl_return_addr.set(addr); + } + pub fn set_vtl0_state_top_addr(&self, addr: usize) { + self.vtl0_state_top_addr.set(addr); + } + pub const fn kernel_stack_ptr_offset() -> usize { + offset_of!(PerCpuVariablesAsm, kernel_stack_ptr) + } + pub const fn interrupt_stack_ptr_offset() -> usize { + offset_of!(PerCpuVariablesAsm, interrupt_stack_ptr) + } + pub const fn vtl_return_addr_offset() -> usize { + offset_of!(PerCpuVariablesAsm, vtl_return_addr) + } + pub const fn scratch_offset() -> usize { + offset_of!(PerCpuVariablesAsm, scratch) + } + pub const fn vtl0_state_top_addr_offset() -> usize { + offset_of!(PerCpuVariablesAsm, vtl0_state_top_addr) + } + pub const fn cur_kernel_stack_ptr_offset() -> usize { + offset_of!(PerCpuVariablesAsm, cur_kernel_stack_ptr) + } + pub const fn cur_kernel_base_ptr_offset() -> usize { + offset_of!(PerCpuVariablesAsm, cur_kernel_base_ptr) + } + pub const fn user_context_top_addr_offset() -> usize { + offset_of!(PerCpuVariablesAsm, user_context_top_addr) + } } -/// Wrapper struct to maintain `RefCell` along with `KernelTls`. -/// This struct allows assembly code to read/write some TLS area via the GS register (e.g., to +/// Wrapper struct to maintain `RefCell` along with `PerCpuVariablesAsm`. +/// This struct allows assembly code to read/write some PerCpuVariables area via the GS register (e.g., to /// save/restore RIP/RSP). Currently, `PerCpuVariables` is protected by `RefCell` such that /// assembly code cannot easily access it. /// TODO: Let's consider whether we should maintain these two types of TLS areas (for Rust and @@ -240,18 +276,23 @@ pub enum KernelTlsOffset { /// this might be unsafe. #[repr(C)] pub struct RefCellWrapper { - /// Make some TLS area be accessible via the GS register. This is mainly for assembly code - ktls: KernelTls, + /// Make some PerCpuVariablesAsm area be accessible via the GS register. This is mainly for assembly code + pcv_asm: PerCpuVariablesAsm, /// RefCell which will be stored in the GS register inner: RefCell, } impl RefCellWrapper { pub const fn new(value: T) -> Self { RefCellWrapper { - ktls: KernelTls { + pcv_asm: PerCpuVariablesAsm { kernel_stack_ptr: Cell::new(0), interrupt_stack_ptr: Cell::new(0), vtl_return_addr: Cell::new(0), + scratch: Cell::new(0), + vtl0_state_top_addr: Cell::new(0), + cur_kernel_stack_ptr: Cell::new(0), + cur_kernel_base_ptr: Cell::new(0), + user_context_top_addr: Cell::new(0), }, inner: RefCell::new(value), } @@ -320,46 +361,46 @@ where f(per_cpu_variables) } -/// Execute a closure with a reference to the current kernel TLS. +/// Execute a closure with a reference to the current PerCpuVariablesAsm. /// /// # Panics /// Panics if GSBASE is not set or it contains a non-canonical address. -pub fn with_kernel_tls(f: F) -> R +pub fn with_per_cpu_variables_asm(f: F) -> R where - F: FnOnce(&KernelTls) -> R, + F: FnOnce(&PerCpuVariablesAsm) -> R, R: Sized + 'static, { - let ktls_addr = unsafe { + let pcv_asm_addr = unsafe { let gsbase = rdgsbase(); let addr = VirtAddr::try_new(u64::try_from(gsbase).unwrap()) .expect("GS contains a non-canonical address"); addr.as_ptr::>() - .cast::() + .cast::() }; - let ktls = unsafe { &*ktls_addr }; + let pcv_asm = unsafe { &*pcv_asm_addr }; - f(ktls) + f(pcv_asm) } -/// Execute a closure with a mutable reference to the current kernel TLS. +/// Execute a closure with a mutable reference to the current PerCpuVariablesAsm. /// /// # Panics /// Panics if GSBASE is not set or it contains a non-canonical address. -pub fn with_kernel_tls_mut(f: F) -> R +pub fn with_per_cpu_variables_asm_mut(f: F) -> R where - F: FnOnce(&mut KernelTls) -> R, + F: FnOnce(&mut PerCpuVariablesAsm) -> R, R: Sized + 'static, { - let ktls_addr: *mut KernelTls = unsafe { + let pcv_asm_addr: *mut PerCpuVariablesAsm = unsafe { let gsbase = rdgsbase(); let addr = VirtAddr::try_new(u64::try_from(gsbase).unwrap()) .expect("GS contains a non-canonical address"); addr.as_mut_ptr::>() - .cast::() + .cast::() }; - let ktls = unsafe { &mut *ktls_addr }; + let pcv_asm = unsafe { &mut *pcv_asm_addr }; - f(ktls) + f(pcv_asm) } /// Get or initialize a `RefCell` that contains a pointer to the current core's per-CPU variables. @@ -444,6 +485,32 @@ pub fn allocate_per_cpu_variables() { } } +/// Initialize PerCpuVariable and PerCpuVariableAsm for the current core. +/// +/// Currently, it initializes the kernel and interrupt stack pointers in the PerCpuVariablesAsm area. +/// +/// # Panics +/// Panics if the per-CPU variables are not properly initialized. +pub fn init_per_cpu_variables() { + const STACK_ALIGNMENT: usize = 16; + with_per_cpu_variables_mut(|per_cpu_variables| { + let kernel_sp = + usize::try_from(per_cpu_variables.kernel_stack_top()).unwrap() & !(STACK_ALIGNMENT - 1); + let interrupt_sp = usize::try_from(per_cpu_variables.interrupt_stack_top()).unwrap() + & !(STACK_ALIGNMENT - 1); + let vtl0_state_top_addr = usize::try_from( + &raw const per_cpu_variables.vtl0_state as u64 + + core::mem::size_of::() as u64, + ) + .unwrap(); + with_per_cpu_variables_asm_mut(|pcv_asm| { + pcv_asm.set_kernel_stack_ptr(kernel_sp); + pcv_asm.set_interrupt_stack_ptr(interrupt_sp); + pcv_asm.set_vtl0_state_top_addr(vtl0_state_top_addr); + }); + }); +} + /// Get the XSAVE area size based on enabled features (XCR0) fn get_xsave_area_size() -> usize { let cpuid = raw_cpuid::CpuId::new(); diff --git a/litebox_platform_lvbs/src/lib.rs b/litebox_platform_lvbs/src/lib.rs index 7498447da..9ff9faf11 100644 --- a/litebox_platform_lvbs/src/lib.rs +++ b/litebox_platform_lvbs/src/lib.rs @@ -5,27 +5,31 @@ #![cfg_attr(feature = "interrupt", feature(abi_x86_interrupt))] use crate::{ - mshv::{vsm::Vtl0KernelInfo, vtl1_mem_layout::PAGE_SIZE}, + host::per_cpu_variables::PerCpuVariablesAsm, mshv::vsm::Vtl0KernelInfo, user_context::UserContextMap, }; - use core::{ arch::asm, sync::atomic::{AtomicU32, AtomicU64}, }; -use litebox::platform::page_mgmt::DeallocationError; use litebox::platform::{ DebugLogProvider, IPInterfaceProvider, ImmediatelyWokenUp, PageManagementProvider, - Punchthrough, RawMutexProvider, StdioProvider, TimeProvider, UnblockedOrTimedOut, + Punchthrough, PunchthroughProvider, PunchthroughToken, RawMutex as _, RawMutexProvider, + RawPointerProvider, StdioProvider, TimeProvider, UnblockedOrTimedOut, + page_mgmt::DeallocationError, }; -use litebox::platform::{ - PunchthroughProvider, PunchthroughToken, RawMutex as _, RawPointerProvider, +use litebox::{ + mm::linux::{PAGE_SIZE, PageRange}, + platform::page_mgmt::FixedAddressBehavior, + shim::ContinueOperation, }; -use litebox::{mm::linux::PageRange, platform::page_mgmt::FixedAddressBehavior}; use litebox_common_linux::{PunchthroughSyscall, errno::Errno}; -use x86_64::structures::paging::{ - PageOffset, PageSize, PageTableFlags, PhysFrame, Size4KiB, frame::PhysFrameRange, - mapper::MapToError, +use x86_64::{ + VirtAddr, + structures::paging::{ + PageOffset, PageSize, PageTableFlags, PhysFrame, Size4KiB, frame::PhysFrameRange, + mapper::MapToError, + }, }; extern crate alloc; @@ -47,6 +51,7 @@ pub struct LinuxKernel { page_table: mm::PageTable, vtl1_phys_frame_range: PhysFrameRange, vtl0_kernel_info: Vtl0KernelInfo, + #[expect(dead_code)] user_contexts: UserContextMap, } @@ -755,6 +760,251 @@ impl StdioProvider for LinuxKernel { } } +/// Runs a user thread with the given initial context. +/// +/// # Safety +/// The context must be valid user context. +pub unsafe fn run_thread( + shim: impl litebox::shim::EnterShim, + ctx: &mut litebox_common_linux::PtRegs, +) { + // Currently, `litebox_platform_lvbs` uses `swapgs` to efficiently switch between + // kernel and user GS base values during kernel-user mode transitions. + // This `swapgs` usage can pontetially leak a kernel address to the user, so + // we clear the `KernelGsBase` MSR before running the user thread. + crate::arch::write_kernel_gsbase_msr(VirtAddr::zero()); + run_thread_inner(&shim, ctx); +} + +struct ThreadContext<'a> { + shim: &'a dyn litebox::shim::EnterShim, + ctx: &'a mut litebox_common_linux::PtRegs, +} + +fn run_thread_inner( + shim: &dyn litebox::shim::EnterShim, + ctx: &mut litebox_common_linux::PtRegs, +) { + let ctx_ptr = core::ptr::from_mut(ctx); + let mut thread_ctx = ThreadContext { shim, ctx }; + // `thread_ctx` will be passed to `syscall_handler` later. + // `ctx_ptr` is to let `run_thread_arch` easily access `ctx` (i.e., not to deal with + // member variable offset calculation in assembly code). + unsafe { run_thread_arch(&mut thread_ctx, ctx_ptr) }; +} + +/// Save callee-saved registers onto the stack. +#[cfg(target_arch = "x86_64")] +macro_rules! SAVE_CALLEE_SAVED_REGISTERS_ASM { + () => { + " + push rbp + mov rbp, rsp + push rbx + push r12 + push r13 + push r14 + push r15 + " + }; +} + +/// Restore callee-saved registers from the stack. +#[cfg(target_arch = "x86_64")] +macro_rules! RESTORE_CALLEE_SAVED_REGISTERS_ASM { + () => { + " + lea rsp, [rbp - 5 * 8] + pop r15 + pop r14 + pop r13 + pop r12 + pop rbx + pop rbp + " + }; +} + +/// Save user context right after `syscall`-driven mode transition to the memory area +/// pointed by the current stack pointer (`rsp`). +/// +/// `rsp` can point to the current CPU stack or the *top address* of a memory area which +/// has enough space for storing the `PtRegs` structure using the `push` instructions +/// (i.e., from high addresses down to low ones). +/// +/// Prerequisite: +/// - Store user `rsp` in `r11` before calling this macro. +/// - Store the userspace return address in `rcx` (`syscall` does this automatically). +#[cfg(target_arch = "x86_64")] +macro_rules! SAVE_SYSCALL_USER_CONTEXT_ASM { + () => { + " + push 0x2b // pt_regs->ss = __USER_DS + push r11 // pt_regs->rsp + pushfq // pt_regs->eflags + push 0x33 // pt_regs->cs = __USER_CS + push rcx // pt_regs->rip + push rax // pt_regs->orig_rax + push rdi // pt_regs->rdi + push rsi // pt_regs->rsi + push rdx // pt_regs->rdx + push rcx // pt_regs->rcx + push -38 // pt_regs->rax = -ENOSYS + push r8 // pt_regs->r8 + push r9 // pt_regs->r9 + push r10 // pt_regs->r10 + push [rsp + 88] // pt_regs->r11 = rflags + push rbx // pt_regs->rbx + push rbp // pt_regs->rbp + push r12 // pt_regs->r12 + push r13 // pt_regs->r13 + push r14 // pt_regs->r14 + push r15 // pt_regs->r15 + " + }; +} + +/// Restore user context from the memory area pointed by the current `rsp`. +/// +/// This macro uses the `pop` instructions (i.e., from low addresses up to high ones) such that +/// it requires the start address of the memory area (not the top one). +/// +/// Prerequisite: The memory area has `PtRegs` structure containing user context. +#[cfg(target_arch = "x86_64")] +macro_rules! RESTORE_USER_CONTEXT_ASM { + () => { + " + pop r15 + pop r14 + pop r13 + pop r12 + pop rbp + pop rbx + pop r11 + pop r10 + pop r9 + pop r8 + pop rax + pop rcx + pop rdx + pop rsi + pop rdi + add rsp, 8 // skip pt_regs->orig_rax + // Stack already has all the values needed for iretq (rip, cs, flags, rsp, ds) + // from the `PtRegs` structure. + " + }; +} + +#[cfg(target_arch = "x86_64")] +#[unsafe(naked)] +unsafe extern "C" fn run_thread_arch( + thread_ctx: &mut ThreadContext, + ctx: *mut litebox_common_linux::PtRegs, +) { + core::arch::naked_asm!( + SAVE_CALLEE_SAVED_REGISTERS_ASM!(), + "push rdi", // save `thread_ctx` + // Save kernel rsp and rbp and user context top in PerCpuVariablesAsm. + "mov gs:[{cur_kernel_sp_off}], rsp", + "mov gs:[{cur_kernel_bp_off}], rbp", + "lea r8, [rsi + {USER_CONTEXT_SIZE}]", + "mov gs:[{user_context_top_off}], r8", + "call {init_handler}", + "jmp done", + ".globl syscall_callback", + "syscall_callback:", + "swapgs", + "mov r11, rsp", // store user `rsp` in `r11` + "mov rsp, gs:[{user_context_top_off}]", // `rsp` points to the top address of user context area + SAVE_SYSCALL_USER_CONTEXT_ASM!(), + "mov rbp, gs:[{cur_kernel_bp_off}]", + "mov rsp, gs:[{cur_kernel_sp_off}]", + // Handle the syscall. This will jump back to the user but + // will return if the thread is exiting. + "mov rdi, [rsp]", // pass `thread_ctx` + "call {syscall_handler}", + "jmp done", + // Exception and interrupt callback placeholders + // IDT handler functions will jump to these labels to + // handle user-mode exceptions/interrupts. + ".globl exception_callback", + "exception_callback:", + "jmp done", + ".globl interrupt_callback", + "interrupt_callback:", + "jmp done", + "done:", + "mov rbp, gs:[{cur_kernel_bp_off}]", + "mov rsp, gs:[{cur_kernel_sp_off}]", + RESTORE_CALLEE_SAVED_REGISTERS_ASM!(), + "ret", + cur_kernel_sp_off = const { PerCpuVariablesAsm::cur_kernel_stack_ptr_offset() }, + cur_kernel_bp_off = const { PerCpuVariablesAsm::cur_kernel_base_ptr_offset() }, + user_context_top_off = const { PerCpuVariablesAsm::user_context_top_addr_offset() }, + USER_CONTEXT_SIZE = const core::mem::size_of::(), + init_handler = sym init_handler, + syscall_handler = sym syscall_handler, + ); +} + +#[allow(clippy::cast_sign_loss)] +unsafe extern "C" fn syscall_handler(thread_ctx: &mut ThreadContext) { + thread_ctx.call_shim(|shim, ctx| shim.syscall(ctx)); +} + +/// Calls `f` in order to call into a shim entrypoint. +impl ThreadContext<'_> { + fn call_shim( + &mut self, + f: impl FnOnce( + &dyn litebox::shim::EnterShim, + &mut litebox_common_linux::PtRegs, + ) -> ContinueOperation, + ) { + let op = f(self.shim, self.ctx); + match op { + ContinueOperation::ResumeGuest => unsafe { switch_to_user(self.ctx) }, + ContinueOperation::ExitThread => {} + } + } +} + +fn init_handler(thread_ctx: &mut ThreadContext) { + thread_ctx.call_shim(|shim, ctx| shim.init(ctx)); +} + +// Switches to the provided user context with the user mode. +/// +/// # Safety +/// The context must be valid user context. +#[cfg(target_arch = "x86_64")] +#[unsafe(naked)] +unsafe extern "C" fn switch_to_user(_ctx: &litebox_common_linux::PtRegs) -> ! { + core::arch::naked_asm!( + "switch_to_user_start:", + // Flush TLB by reloading CR3 + "mov rax, cr3", + "mov cr3, rax", + "xor eax, eax", + // Restore user context from ctx. + "mov rsp, rdi", + RESTORE_USER_CONTEXT_ASM!(), + // clear the GS base register (as the `KernelGsBase` MSR contains 0) + // while writing the current GS base value to `KernelGsBase`. + "swapgs", + "iretq", + "switch_to_user_end:", + ); +} + +// Note on user page table management: +// The legacy platform code creates a new page table to load a program in a separate +// address space and destroys it when the program terminates. This is why the old syscall +// handler invokes `change_address_space()`. Previously, the platform does all these because +// it is the one running the event loop. Once we have an `upcall` mechanism, the runner +// should be the one manage all these. + // NOTE: The below code is a naive workaround to let LVBS code to access the platform. // Rather than doing this, we should implement LVBS interface/provider for the platform. diff --git a/litebox_platform_lvbs/src/mshv/vtl_switch.rs b/litebox_platform_lvbs/src/mshv/vtl_switch.rs index 0c9ce4f41..9087b4895 100644 --- a/litebox_platform_lvbs/src/mshv/vtl_switch.rs +++ b/litebox_platform_lvbs/src/mshv/vtl_switch.rs @@ -4,35 +4,35 @@ use crate::{ host::{ hv_hypercall_page_address, per_cpu_variables::{ - KernelTlsOffset, with_kernel_tls_mut, with_per_cpu_variables, + PerCpuVariablesAsm, with_per_cpu_variables, with_per_cpu_variables_asm_mut, with_per_cpu_variables_mut, }, }, mshv::{ - HV_REGISTER_VSM_CODEPAGE_OFFSETS, HV_VTL_NORMAL, HV_VTL_SECURE, - HvRegisterVsmCodePageOffsets, NUM_VTLCALL_PARAMS, VTL_ENTRY_REASON_INTERRUPT, - VTL_ENTRY_REASON_LOWER_VTL_CALL, VsmFunction, hvcall_vp::hvcall_get_vp_registers, - vsm::vsm_dispatch, vsm_intercept::vsm_handle_intercept, vsm_optee_smc, + HV_REGISTER_VSM_CODEPAGE_OFFSETS, HV_VTL_NORMAL, HvRegisterVsmCodePageOffsets, + NUM_VTLCALL_PARAMS, VTL_ENTRY_REASON_INTERRUPT, VTL_ENTRY_REASON_LOWER_VTL_CALL, + VsmFunction, hvcall_vp::hvcall_get_vp_registers, vsm::vsm_dispatch, + vsm_intercept::vsm_handle_intercept, vsm_optee_smc, }, }; use core::arch::{asm, naked_asm}; use litebox_common_linux::errno::Errno; use num_enum::TryFromPrimitive; -/// A function to return to VTL0 using the Hyper-V hypercall stub. +/// Assembly macro to return to VTL0 using the Hyper-V hypercall stub. /// Although Hyper-V lets each core use the same VTL return address, this implementation -/// uses per-TLS return address to avoid using a mutable global variable. -#[expect(clippy::inline_always)] -#[inline(always)] -pub fn vtl_return() { - unsafe { - core::arch::asm!( - "xor ecx, ecx", - "mov rax, gs:[{vtl_ret_addr_off}]", - "call rax", - vtl_ret_addr_off = const { KernelTlsOffset::VtlReturnAddr as usize }, - ); - } +/// uses per-CPU return address to avoid using a mutable global variable. +/// `ret_off` is the offset of the PerCpuVariablesAsm which holds the VTL return address. +macro_rules! VTL_RETURN_ASM { + ($ret_off:tt) => { + concat!( + "xor ecx, ecx\n", + "mov rax, gs:[", + stringify!($ret_off), + "]\n", + "call rax\n", + ) + }; } // The following registers are shared between different VTLs. @@ -41,11 +41,11 @@ pub fn vtl_return() { // we should save/restore VTL0 registers. For now, we conservatively save/restore all // VTL0/VTL1 registers (results in performance degradation) but we can optimize it later. /// Struct to save VTL state (general-purpose registers) +#[allow(clippy::pub_underscore_fields)] #[derive(Default, Clone, Copy)] #[repr(C)] pub struct VtlState { pub rbp: u64, - pub cr2: u64, pub rax: u64, pub rbx: u64, pub rcx: u64, @@ -60,7 +60,9 @@ pub struct VtlState { pub r13: u64, pub r14: u64, pub r15: u64, + // CR2 // DR[0-6] + // We use a separeate buffer to save/register extended states // X87, XMM, AVX, XCR } @@ -80,237 +82,149 @@ impl VtlState { } } -fn save_vtl_state_to_per_cpu_variables(vtl: u8, vtl_state: *const VtlState) { - with_per_cpu_variables_mut(|per_cpu_variables| match vtl { - HV_VTL_NORMAL => per_cpu_variables - .vtl0_state - .clone_from(unsafe { &*vtl_state }), - HV_VTL_SECURE => per_cpu_variables - .vtl1_state - .clone_from(unsafe { &*vtl_state }), - _ => panic!("Invalid VTL number: {}", vtl), - }); -} - -// Save CPU registers to a global data structure through using a stack -#[unsafe(naked)] -unsafe extern "C" fn save_vtl0_state() { - naked_asm!( - "push r15", - "push r14", - "push r13", - "push r12", - "push r11", - "push r10", - "push r9", - "push r8", - "push rdi", - "push rsi", - "push rdx", - "push rcx", - "push rbx", - "push rax", - "mov rax, cr2", - "push rax", - "push rbp", - "mov rbp, rsp", - "mov edi, {vtl}", - "mov rsi, rsp", - "and rsp, {stack_alignment}", - "call {save_vtl_state_to_per_cpu_variables}", - "mov rsp, rbp", - "add rsp, {register_space}", - "ret", - vtl = const HV_VTL_NORMAL, - stack_alignment = const STACK_ALIGNMENT, - save_vtl_state_to_per_cpu_variables = sym save_vtl_state_to_per_cpu_variables, - register_space = const core::mem::size_of::(), - ); -} -const STACK_ALIGNMENT: isize = -16; - -#[unsafe(naked)] -unsafe extern "C" fn save_vtl1_state() { - naked_asm!( - "push r15", - "push r14", - "push r13", - "push r12", - "push r11", - "push r10", - "push r9", - "push r8", - "push rdi", - "push rsi", - "push rdx", - "push rcx", - "push rbx", - "push rax", - "mov rax, cr2", - "push rax", - "push rbp", - "mov rbp, rsp", - "mov edi, {vtl}", - "mov rsi, rsp", - "and rsp, {stack_alignment}", - "call {save_vtl_state_to_per_cpu_variables}", - "mov rsp, rbp", - "add rsp, {register_space}", - "ret", - vtl = const HV_VTL_SECURE, - stack_alignment = const STACK_ALIGNMENT, - save_vtl_state_to_per_cpu_variables = sym save_vtl_state_to_per_cpu_variables, - register_space = const core::mem::size_of::(), - ); -} - -fn load_vtl_state_from_per_cpu_variables(vtl: u8, vtl_state: *mut VtlState) { - with_per_cpu_variables_mut(|per_cpu_variables| match vtl { - HV_VTL_NORMAL => unsafe { vtl_state.copy_from(&raw const per_cpu_variables.vtl0_state, 1) }, - HV_VTL_SECURE => unsafe { vtl_state.copy_from(&raw const per_cpu_variables.vtl1_state, 1) }, - _ => panic!("Invalid VTL number: {}", vtl), - }); -} - -// Restore CPU registers from the global data structure through using a stack. -#[unsafe(naked)] -unsafe extern "C" fn load_vtl_state(vtl: u8) { - naked_asm!( - "sub rsp, {register_space}", - "mov rbp, rsp", - // rdi holds the VTL number - "mov rsi, rsp", - "and rsp, {stack_alignment}", - "call {load_vtl_state_from_per_cpu_variables}", - "mov rsp, rbp", - "pop rbp", - "pop rax", - "mov cr2, rax", - "pop rax", - "pop rbx", - "pop rcx", - "pop rdx", - "pop rsi", - "pop rdi", - "pop r8", - "pop r9", - "pop r10", - "pop r11", - "pop r12", - "pop r13", - "pop r14", - "pop r15", - "ret", - register_space = const core::mem::size_of::(), - stack_alignment = const STACK_ALIGNMENT, - load_vtl_state_from_per_cpu_variables = sym load_vtl_state_from_per_cpu_variables, - ); -} - pub fn vtl_switch_loop_entry(platform: Option<&'static crate::Platform>) -> ! { if let Some(platform) = platform { crate::set_platform_low(platform); } - - unsafe { - save_vtl0_state(); - } - // This is a dummy call to satisfy load_vtl0_state() with reasonable register values. - // We do not save VTL0 registers during VTL1 initialization. - - jump_to_vtl_switch_loop_with_stack_cleanup(); -} - -/// This function lets VTL1 return to VTL0. Before returning to VTL0, it re-initializes -/// the VTL1 kernel stack to discard any leftovers (e.g., unwind, panic, ...). -#[allow(clippy::inline_always)] -#[inline(always)] -fn jump_to_vtl_switch_loop_with_stack_cleanup() -> ! { - with_per_cpu_variables_mut(|per_cpu_variables| { - per_cpu_variables.restore_extended_states(HV_VTL_NORMAL); - }); - - let stack_top = - with_per_cpu_variables(crate::host::per_cpu_variables::PerCpuVariables::kernel_stack_top); unsafe { asm!( - "mov rsp, rax", - "and rsp, {stack_alignment}", - "call {loop}", - in("rax") stack_top, loop = sym vtl_switch_loop, - stack_alignment = const STACK_ALIGNMENT, - options(noreturn) + "jmp {vtl_switch_loop_asm}", + vtl_switch_loop_asm = sym vtl_switch_loop_asm, + options(noreturn, nostack, preserves_flags), ); } } -/// expose `vtl_switch_loop` to the outside (e.g., the syscall handler) -#[unsafe(naked)] -pub(crate) unsafe extern "C" fn jump_to_vtl_switch_loop() -> ! { - naked_asm!( - "call {loop}", - loop = sym vtl_switch_loop, - ); +/// Assembly macro to save VTL state to the memory area pointed by the current +/// stack pointer (`rsp`). +/// +/// `rsp` can point to the current CPU stack or the *top address* of a memory area which +/// has enough space for storing the `VtlState` structure using the `push` instructions +/// (i.e., from high addresses down to low ones). +macro_rules! SAVE_VTL_STATE_ASM { + () => { + " + push r15 + push r14 + push r13 + push r12 + push r11 + push r10 + push r9 + push r8 + push rdi + push rsi + push rdx + push rcx + push rbx + push rax + push rbp + " + }; } -/// VTL switch loop +/// Assembly macro to restore VTL state from the memory area pointed by the current `rsp`. /// -/// # Panics -/// Panic if it encounters an unknown VTL entry reason. -fn vtl_switch_loop() -> ! { - loop { - unsafe { - save_vtl1_state(); - load_vtl_state(HV_VTL_NORMAL); - } +/// This macro uses the `pop` instructions (i.e., from low addresses up to high ones) such that +/// it requires the start address of the memory area (not the top one). +/// +/// Prerequisite: The memory area has `VtlState` structure containing user context. +macro_rules! LOAD_VTL_STATE_ASM { + () => { + " + pop rbp + pop rax + pop rbx + pop rcx + pop rdx + pop rsi + pop rdi + pop r8 + pop r9 + pop r10 + pop r11 + pop r12 + pop r13 + pop r14 + pop r15 + " + }; +} - vtl_return(); - // VTL calls and intercepts (i.e., returns from synthetic interrupt handlers) land here. +#[unsafe(naked)] +unsafe extern "C" fn vtl_switch_loop_asm() -> ! { + naked_asm!( + "1:", + "mov rsp, gs:[{kernel_sp_off}]", // reset kernel stack pointer. Hyper-V saves/restores rsp and rip. + VTL_RETURN_ASM!({vtl_ret_addr_off}), + // *** VTL1 resumes here regardless of the entry reason (VTL switch or intercept) *** + "mov gs:[{scratch_off}], rsp", // save VTL1's rsp to scratch + "mov rsp, gs:[{vtl0_state_top_addr_off}]", // point rsp to the top address of VtlState for VTL0 + SAVE_VTL_STATE_ASM!(), + "mov rsp, gs:[{scratch_off}]", // restore VTL1's rsp from scratch + // TODO: XSAVE + "mov rbp, rsp", // rbp contains VTL0's stack frame, so update it. + "call {loop_body}", + // TODO: XRSTOR + "mov r8, gs:[{vtl0_state_top_addr_off}]", + "lea rsp, [r8 - {VTL_STATE_SIZE}]", // point rsp to the start address of VtlState + LOAD_VTL_STATE_ASM!(), + // *** VTL0 state is recovered. Do not put any code tampering with them here *** + "jmp 1b", + kernel_sp_off = const { PerCpuVariablesAsm::kernel_stack_ptr_offset() }, + vtl_ret_addr_off = const { PerCpuVariablesAsm::vtl_return_addr_offset() }, + scratch_off = const { PerCpuVariablesAsm::scratch_offset() }, + vtl0_state_top_addr_off = + const { PerCpuVariablesAsm::vtl0_state_top_addr_offset() }, + VTL_STATE_SIZE = const core::mem::size_of::(), + loop_body = sym vtl_switch_loop_body, + ) +} - unsafe { - save_vtl0_state(); - load_vtl_state(HV_VTL_SECURE); - } +unsafe extern "C" fn vtl_switch_loop_body() { + // TODO: We must save/restore VTL1's state when there is RPC from VTL1 to VTL0 (e.g., dynamically + // loading OP-TEE TAs). This should use global data structures since the core which makes the RPC + // can be different from the core where the VTL1 is running. + // TODO: Even if we don't have RPC from VTL1 to VTL0, we may still need to save VTL1's state for + // debugging purposes. - // Since we do not know whether the VTL0 kernel saves its extended states (e.g., if a VTL switch - // is due to memory or register access violation, the VTL0 kernel might not have saved - // its states), we conservatively save and restore its extended states on every VTL switch. - with_per_cpu_variables_mut(|per_cpu_variables| { - per_cpu_variables.save_extended_states(HV_VTL_NORMAL); - }); + // Since we do not know whether the VTL0 kernel saves its extended states (e.g., if a VTL switch + // is due to memory or register access violation, the VTL0 kernel might not have saved + // its states), we conservatively save and restore its extended states on every VTL switch. + with_per_cpu_variables_mut(|per_cpu_variables| { + per_cpu_variables.save_extended_states(HV_VTL_NORMAL); + }); - let reason = with_per_cpu_variables(|per_cpu_variables| unsafe { - (*per_cpu_variables.hv_vp_assist_page_as_ptr()).vtl_entry_reason - }); - match VtlEntryReason::try_from(reason).unwrap_or(VtlEntryReason::Unknown) { - #[allow(clippy::cast_sign_loss)] - VtlEntryReason::VtlCall => { - let params = with_per_cpu_variables(|per_cpu_variables| { - per_cpu_variables.vtl0_state.get_vtlcall_params() + let reason = with_per_cpu_variables(|per_cpu_variables| unsafe { + (*per_cpu_variables.hv_vp_assist_page_as_ptr()).vtl_entry_reason + }); + match VtlEntryReason::try_from(reason).unwrap_or(VtlEntryReason::Unknown) { + #[allow(clippy::cast_sign_loss)] + VtlEntryReason::VtlCall => { + let params = with_per_cpu_variables(|per_cpu_variables| { + per_cpu_variables.vtl0_state.get_vtlcall_params() + }); + if VsmFunction::try_from(u32::try_from(params[0]).unwrap_or(u32::MAX)) + .unwrap_or(VsmFunction::Unknown) + == VsmFunction::Unknown + { + todo!("unknown function ID = {:#x}", params[0]); + } else { + let result = vtlcall_dispatch(¶ms); + with_per_cpu_variables_mut(|per_cpu_variables| { + per_cpu_variables.set_vtl_return_value(result as u64); }); - if VsmFunction::try_from(u32::try_from(params[0]).unwrap_or(u32::MAX)) - .unwrap_or(VsmFunction::Unknown) - == VsmFunction::Unknown - { - todo!("unknown function ID = {:#x}", params[0]); - } else { - let result = vtlcall_dispatch(¶ms); - with_per_cpu_variables_mut(|per_cpu_variables| { - per_cpu_variables.set_vtl_return_value(result as u64); - }); - jump_to_vtl_switch_loop_with_stack_cleanup(); - } - } - VtlEntryReason::Interrupt => { - vsm_handle_intercept(); - jump_to_vtl_switch_loop_with_stack_cleanup(); - } - VtlEntryReason::Unknown => { - panic!("Unknown VTL entry reason"); } } - // do not put any code which might corrupt registers + VtlEntryReason::Interrupt => { + vsm_handle_intercept(); + } + VtlEntryReason::Unknown => {} } + + with_per_cpu_variables(|per_cpu_variables| { + per_cpu_variables.restore_extended_states(HV_VTL_NORMAL); + }); } fn vtlcall_dispatch(params: &[u64; NUM_VTLCALL_PARAMS]) -> i64 { @@ -331,8 +245,8 @@ pub(crate) fn mshv_vsm_get_code_page_offsets() -> Result<(), Errno> { let vtl_return_address = hvcall_page .checked_add(usize::from(code_page_offsets.vtl_return_offset())) .ok_or(Errno::EOVERFLOW)?; - with_kernel_tls_mut(|ktls| { - ktls.vtl_return_addr.set(vtl_return_address); + with_per_cpu_variables_asm_mut(|pcv_asm| { + pcv_asm.set_vtl_return_addr(vtl_return_address); }); Ok(()) } diff --git a/litebox_platform_lvbs/src/syscall_entry.rs b/litebox_platform_lvbs/src/syscall_entry.rs index e8f036858..1db8d5123 100644 --- a/litebox_platform_lvbs/src/syscall_entry.rs +++ b/litebox_platform_lvbs/src/syscall_entry.rs @@ -1,13 +1,6 @@ -use crate::debug_serial_println; -use crate::{ - host::per_cpu_variables::{with_per_cpu_variables, with_per_cpu_variables_mut}, - mshv::vtl_switch::jump_to_vtl_switch_loop, - user_context::UserSpaceManagement, -}; -use core::arch::{asm, naked_asm}; -use litebox::shim::ContinueOperation; +use crate::host::per_cpu_variables::with_per_cpu_variables; +use core::arch::naked_asm; use litebox_common_linux::PtRegs; -use litebox_common_optee::SyscallContext; use x86_64::{ VirtAddr, registers::{ @@ -16,214 +9,14 @@ use x86_64::{ }, }; -// Generic x86_64 syscall support with a minor extension for realizing OP-TEE's -// up to 8 syscall arguments (r12 and r13 for the 6th and 7th arguments). -// -// rax: system call number -// rdi: arg0 -// rsi: arg1 -// rdx: arg2 -// r10: arg3 -// r8: arg4 -// r9: arg5 -// r12: arg6 (*) -// r13: arg7 (*) -// -// the `syscall` instruction automatically sets the following registers: -// rcx: userspace return address (note. arg3 for normal func call) -// r11: userspace rflags -// -// the `sysretq` instruction uses the following registers: -// rax: syscall return value -// rcx: userspace return address -// r11: userspace rflags -// Note. rsp should point to the userspace stack before calling `sysretq` - -static SHIM: spin::Once< +pub(crate) static SHIM: spin::Once< &'static (dyn litebox::shim::EnterShim + Send + Sync), > = spin::Once::new(); -#[cfg(target_arch = "x86_64")] -#[derive(Clone, Copy, Debug)] -#[repr(C)] -pub struct SyscallContextRaw { - rdi: u64, // arg0 - rsi: u64, // arg1 - rdx: u64, // arg2 - r10: u64, // arg3 - r8: u64, // arg4 - r9: u64, // arg5 - r12: u64, // arg6 - r13: u64, // arg7 - rcx: u64, // userspace return address - r11: u64, // userspace rflags - rsp: u64, // userspace stack pointer -} - -impl SyscallContextRaw { - /// # Panics - /// Panics if the index is out of bounds (greater than 7). - pub fn arg_index(&self, index: usize) -> u64 { - match index { - 0 => self.rdi, - 1 => self.rsi, - 2 => self.rdx, - 3 => self.r10, - 4 => self.r8, - 5 => self.r9, - 6 => self.r12, - 7 => self.r13, - _ => panic!("BUG: Invalid syscall argument index: {}", index), - } - } - - pub fn user_rip(&self) -> Option { - VirtAddr::try_new(self.rcx).ok() - } - - pub fn user_rflags(&self) -> RFlags { - RFlags::from_bits_truncate(self.r11) - } - - pub fn user_rsp(&self) -> Option { - VirtAddr::try_new(self.rsp).ok() - } - - #[expect(clippy::cast_possible_truncation)] - pub fn syscall_context(&self) -> SyscallContext { - SyscallContext::new(&[ - self.rdi as usize, - self.rsi as usize, - self.rdx as usize, - self.r10 as usize, - self.r8 as usize, - self.r9 as usize, - self.r12 as usize, - self.r13 as usize, - ]) - } - - #[expect(clippy::cast_possible_truncation)] - pub fn to_pt_regs(&self, rax: u64) -> PtRegs { - PtRegs { - r15: 0, - r14: 0, - r13: self.r13 as usize, - r12: self.r12 as usize, - rbp: 0, - rbx: 0, - r11: self.r11 as usize, - r10: self.r10 as usize, - r9: self.r9 as usize, - r8: self.r8 as usize, - rax: 0, - rcx: self.rcx as usize, - rdx: self.rdx as usize, - rsi: self.rsi as usize, - rdi: self.rdi as usize, - orig_rax: rax as usize, - rip: 0, - cs: 0, - eflags: 0, - rsp: self.rsp as usize, - ss: 0, - } - } -} - -#[allow(clippy::similar_names)] -#[allow(unreachable_code)] -fn syscall_entry(sysnr: u64, ctx_raw: *const SyscallContextRaw) -> usize { - let &shim = SHIM.get().expect("Shim should be initialized"); - - debug_serial_println!("sysnr = {:#x}, ctx_raw = {:#x}", sysnr, ctx_raw as usize); - let ctx_raw = unsafe { &*ctx_raw }; - - assert!( - ctx_raw.user_rip().is_some() && ctx_raw.user_rsp().is_some(), - "BUG: userspace RIP or RSP is invalid" - ); - - // save user context - crate::platform_low() - .save_user_context( - ctx_raw.user_rip().unwrap(), - ctx_raw.user_rsp().unwrap(), - ctx_raw.user_rflags(), - ) - .expect("Failed to save user context"); - - let mut ctx = ctx_raw.to_pt_regs(sysnr); - - // call the syscall handler passed down from the shim - let sysret = match shim.syscall(&mut ctx) { - ContinueOperation::ResumeGuest | ContinueOperation::ExitThread => ctx.rax, - }; - - // TODO: We should decide whether we place this function here, OP-TEE shim, or separate it into - // multiple functions and place them in the appropriate places. - // In OP-TEE TAs, a system call can have three different return paths: - // 1. Return to the user space to resume its execution: This implies a TA is in the middle of its execution. - // It does not yet complete a request from a VTL0 client (e.g., sign a message) and makes several syscalls to do so. - // 2. Switch to VTL0 with a final outcome: a TA completes a client's request and returns a final outcome to VTL0. - // 3. Switch to VTL0 to interact with VTL0: a TA can initiate an RPC to VTL0 to interact with its client app or services. - // OP-TEE Shim is expected to host a logic to decide a return path, Platform is expected to host a logic to change - // address spaces, and LVBS Runner is expected to host a logic to switch to VTL0. - - // placeholder for returning to the user space - if sysret == 0 { - return sysret; - } - - let stack_top = with_per_cpu_variables_mut(|per_cpu_variables| { - per_cpu_variables.set_vtl_return_value(0); - per_cpu_variables.kernel_stack_top() - }); - unsafe { - asm!( - "mov rsp, rax", - "and rsp, -16", - in("rax") stack_top - ); - } - - crate::platform_low().page_table.change_address_space(); - unsafe { jump_to_vtl_switch_loop() } - unreachable!() -} - #[unsafe(naked)] unsafe extern "C" fn syscall_entry_wrapper() { - naked_asm!( - "swapgs", - "push rsp", - "push r11", - "push rcx", - "push r13", - "push r12", - "push r9", - "push r8", - "push r10", - "push rdx", - "push rsi", - "push rdi", - "mov rdi, rax", - "mov rsi, rsp", - "and rsp, {stack_alignment}", - "call {syscall_entry}", - "add rsp, {register_space}", - "pop rcx", - "pop r11", - "pop rbp", - "swapgs", - "sysretq", - stack_alignment = const STACK_ALIGNMENT, - syscall_entry = sym syscall_entry, - register_space = const core::mem::size_of::() - core::mem::size_of::() * NUM_REGISTERS_TO_POP, - ); + naked_asm!("jmp syscall_callback"); } -const NUM_REGISTERS_TO_POP: usize = 3; -const STACK_ALIGNMENT: isize = -16; /// This function enables 64-bit syscall extensions and sets up the necessary MSRs. /// It must be called for each core. diff --git a/litebox_platform_lvbs/src/user_context.rs b/litebox_platform_lvbs/src/user_context.rs index ec9a6d7d7..a2d419823 100644 --- a/litebox_platform_lvbs/src/user_context.rs +++ b/litebox_platform_lvbs/src/user_context.rs @@ -16,7 +16,7 @@ use x86_64::{ /// UserSpace management trait for creating and managing a separate address space for a user process, task, or session. /// Define it as a trait because it might need to work for various configurations like different page sizes. -#[allow(dead_code)] +#[expect(dead_code)] pub trait UserSpaceManagement { /// Create a new user address space (i.e., a new user page table) and context, and returns `userspace_id` for it. /// The page table also maps the kernel address space (the entire physical space for now, a portion of it in the future) @@ -59,6 +59,7 @@ pub trait UserSpaceManagement { /// (pointed by `rsp`) and restored by the system call or interrupt handler. /// TODO: Since the user stack might have no space to store all registers, we can extend this structure in /// the future to store these registers. +#[expect(dead_code)] pub struct UserContext { pub page_table: crate::mm::PageTable, pub rip: VirtAddr, @@ -80,6 +81,7 @@ impl UserContext { } /// Data structure to hold a map of user contexts indexed by their ID. +#[expect(dead_code)] pub struct UserContextMap { inner: spin::mutex::SpinMutex>, } diff --git a/litebox_runner_lvbs/src/main.rs b/litebox_runner_lvbs/src/main.rs index 958b4360b..3aadad045 100644 --- a/litebox_runner_lvbs/src/main.rs +++ b/litebox_runner_lvbs/src/main.rs @@ -5,7 +5,10 @@ use core::arch::asm; use litebox_platform_lvbs::{ arch::{enable_extended_states, enable_fsgsbase, get_core_id, instrs::hlt_loop}, - host::{bootparam::parse_boot_info, per_cpu_variables::with_per_cpu_variables}, + host::{ + bootparam::parse_boot_info, + per_cpu_variables::{PerCpuVariablesAsm, init_per_cpu_variables}, + }, mm::MemoryProvider, serial_println, }; @@ -116,16 +119,14 @@ pub unsafe extern "C" fn _start() -> ! { enable_fsgsbase(); enable_extended_states(); - let stack_top = with_per_cpu_variables( - litebox_platform_lvbs::host::per_cpu_variables::PerCpuVariables::kernel_stack_top, - ); + init_per_cpu_variables(); unsafe { asm!( - "mov rsp, rax", - "and rsp, -16", + "mov rsp, gs:[{kernel_sp_off}]", "call {kernel_main}", - in("rax") stack_top, kernel_main = sym kernel_main + kernel_sp_off = const { PerCpuVariablesAsm::kernel_stack_ptr_offset() }, + kernel_main = sym kernel_main ); }