From b1f9dc0f8a1ff30bb61e5af4f768f750b566b0ad Mon Sep 17 00:00:00 2001 From: Zoe Date: Fri, 28 Aug 2026 10:14:39 -0500 Subject: [PATCH] feat: ring3 --- src/arch/x86_64/apic.rs | 8 - src/arch/x86_64/cpu.rs | 28 +++- src/arch/x86_64/gdt.rs | 41 ++++- src/arch/x86_64/interrupts/exceptions.rs | 13 +- src/arch/x86_64/interrupts/idt.rs | 16 +- src/arch/x86_64/io_apic.rs | 8 +- src/arch/x86_64/mod.rs | 35 ++++ src/arch/x86_64/paging.rs | 55 ++++++- src/main.rs | 128 +++++---------- src/memory/address_space.rs | 48 +++++- src/memory/mod.rs | 2 + src/memory/stack.rs | 196 +++++++++++++++++++++++ 12 files changed, 458 insertions(+), 120 deletions(-) create mode 100644 src/memory/stack.rs diff --git a/src/arch/x86_64/apic.rs b/src/arch/x86_64/apic.rs index 723a266..a03e9e0 100644 --- a/src/arch/x86_64/apic.rs +++ b/src/arch/x86_64/apic.rs @@ -127,14 +127,6 @@ impl LocalApic { return Err(LocalApicError::NotBootSystemProcessor); } - println!("BSP: {}", bsp); - println!("x2Apic: {}", x2apix); - println!("enabled: {}", enabled); - println!( - "xApic physical address: {:x}", - xapic_physical_addr.as_usize() - ); - let access = match x2apix { true => LocalApicAccess::X2Apic, false => { diff --git a/src/arch/x86_64/cpu.rs b/src/arch/x86_64/cpu.rs index a997467..834599e 100644 --- a/src/arch/x86_64/cpu.rs +++ b/src/arch/x86_64/cpu.rs @@ -11,6 +11,7 @@ pub enum CpuFeaturesError { pub(crate) struct CpuFeatures { pub nx_supported: bool, pub nx_enabled: bool, + pub global_pages: bool, pub physical_address_bits: u8, pub virtual_address_bits: u8, pub five_level_paging_active: bool, @@ -23,22 +24,23 @@ pub fn detect_features_and_enable() -> Result { let mut features = CpuFeatures { nx_supported: false, nx_enabled: false, + global_pages: false, physical_address_bits: 0, virtual_address_bits: 0, five_level_paging_active: false, }; - let cpuid_result = core::arch::x86_64::__cpuid_count(0x80000000, 0); + let cpuid_result = core::arch::x86_64::__cpuid(0x80000000); if cpuid_result.eax < 0x80000008 { return Err(CpuFeaturesError::CpuidFeaturesNotSupported); } - let cpuid_result = core::arch::x86_64::__cpuid_count(0x80000001, 0); + let cpuid_result = core::arch::x86_64::__cpuid(0x80000001); features.nx_supported = cpuid_result.edx & (1 << 20) != 0; - let cpuid_result = core::arch::x86_64::__cpuid_count(0x80000008, 0); + let cpuid_result = core::arch::x86_64::__cpuid(0x80000008); features.physical_address_bits = (cpuid_result.eax & 0xFF) as u8; if !(12..=52).contains(&features.physical_address_bits) { @@ -57,8 +59,16 @@ pub fn detect_features_and_enable() -> Result { return Err(CpuFeaturesError::InvalidVirtualAddressWidth); } + let cpuid_result = core::arch::x86_64::__cpuid(0x1); + + features.global_pages = cpuid_result.edx & (1 << 13) != 0; + + if features.global_pages { + let cr4 = read_cr4(); + write_cr4(cr4 | 1 << 7); + } + if features.nx_supported { - let cpuid_result = core::arch::x86_64::__cpuid_count(0x1, 0); let msr_supported = cpuid_result.edx & (1 << 5) != 0; if !msr_supported { @@ -78,6 +88,16 @@ pub fn detect_features_and_enable() -> Result { Ok(features) } +fn write_cr4(value: usize) { + unsafe { + asm!( + "mov cr4, {}", + in(reg) value, + options(nostack, preserves_flags) + ); + } +} + fn read_cr4() -> usize { let value: usize; diff --git a/src/arch/x86_64/gdt.rs b/src/arch/x86_64/gdt.rs index 4ee33ad..9873280 100644 --- a/src/arch/x86_64/gdt.rs +++ b/src/arch/x86_64/gdt.rs @@ -1,13 +1,15 @@ use core::arch::asm; +use crate::memory::VirtualAddr; + #[repr(C, align(8))] struct Gdt { - entries: [u64; 5], + entries: [u64; 7], } impl Gdt { pub const fn new() -> Self { - Self { entries: [0; 5] } + Self { entries: [0; 7] } } } @@ -47,7 +49,9 @@ const DOUBLE_FAULT_STACK_SIZE: usize = 16 * 1024; pub(super) const KERNEL_CODE_SELECTOR: u16 = 1 * 8; pub(super) const KERNEL_DATA_SELECTOR: u16 = 2 * 8; -pub(super) const TSS_SELECTOR: u16 = 3 * 8; +pub(super) const USER_CODE_SELECTOR: u16 = (3 * 8) | 3; +pub(super) const USER_DATA_SELECTOR: u16 = (4 * 8) | 3; +pub(super) const TSS_SELECTOR: u16 = 5 * 8; #[repr(align(16))] #[allow(dead_code)] // field 0 is read, rust just cant tell @@ -74,6 +78,8 @@ pub fn init() { 0, kernel_code_descriptor(), kernel_data_descriptor(), + user_code_descriptor(), + user_data_descriptor(), tss_low, tss_high, ], @@ -84,6 +90,12 @@ pub fn init() { } } +pub fn set_kernel_stack(stack_top: VirtualAddr) { + unsafe { + TSS.privilege_stacks[0] = stack_top.as_usize() as u64; + } +} + fn gdt_reload() { unsafe { asm!( @@ -119,17 +131,34 @@ fn gdt_reload() { } const PRESENT: u64 = 1 << 47; -const USER_DESCRIPTOR: u64 = 1 << 44; +const CODE_DATA_DESCRIPTOR: u64 = 1 << 44; +const USER_PRIVILEGE: u64 = 3 << 45; const EXECUTABLE: u64 = 1 << 43; const READ_WRITE: u64 = 1 << 41; +const GRANULARITY: u64 = 1 << 55; +const SIZE: u64 = 1 << 54; const LONG_MODE: u64 = 1 << 53; fn kernel_code_descriptor() -> u64 { - PRESENT | USER_DESCRIPTOR | EXECUTABLE | READ_WRITE | LONG_MODE + PRESENT | CODE_DATA_DESCRIPTOR | EXECUTABLE | READ_WRITE | LONG_MODE | GRANULARITY } fn kernel_data_descriptor() -> u64 { - PRESENT | USER_DESCRIPTOR | READ_WRITE + PRESENT | CODE_DATA_DESCRIPTOR | READ_WRITE | SIZE | GRANULARITY +} + +fn user_code_descriptor() -> u64 { + PRESENT + | CODE_DATA_DESCRIPTOR + | USER_PRIVILEGE + | EXECUTABLE + | READ_WRITE + | LONG_MODE + | GRANULARITY +} + +fn user_data_descriptor() -> u64 { + PRESENT | CODE_DATA_DESCRIPTOR | USER_PRIVILEGE | READ_WRITE | SIZE | GRANULARITY } fn tss_descriptor(tss: *const TaskStateSegment) -> [u64; 2] { diff --git a/src/arch/x86_64/interrupts/exceptions.rs b/src/arch/x86_64/interrupts/exceptions.rs index 0754055..1034aec 100644 --- a/src/arch/x86_64/interrupts/exceptions.rs +++ b/src/arch/x86_64/interrupts/exceptions.rs @@ -46,11 +46,20 @@ fatal_with_error_code!(stack_segment_fault_handler, "STACK-SEGMENT FAULT"); fatal_with_error_code!(general_protection_handler, "GENERAL PROTECTION FAULT"); fatal_with_error_code!(alignment_check_handler, "ALIGNMENT CHECK"); +extern "x86-interrupt" fn user_test_exit_handler(frame: InterruptStackFrame) { + if frame.code_segment & 0b11 != 3 { + panic!("user_test_exit_handler called from kernel"); + } + + println!("User test exit"); + hcf(); +} + pub(super) fn install(idt: &mut idt::Idt) { idt.set_handler(0, divide_error_handler, 0); idt.set_handler(1, debug_handler, 0); idt.set_handler(2, non_maskable_interrupt_handler, 0); - idt.set_handler(3, breakpoint_handler, 0); + idt.set_user_handler(3, breakpoint_handler, 0); idt.set_handler(6, invalid_opcode_handler, 0); idt.set_handler(7, device_not_available_handler, 0); idt.set_error_code_handler(8, double_fault_handler, 1); @@ -63,6 +72,8 @@ pub(super) fn install(idt: &mut idt::Idt) { idt.set_error_code_handler(17, alignment_check_handler, 0); idt.set_handler(18, machine_check_handler, 0); idt.set_handler(19, simd_floating_point_handler, 0); + + idt.set_user_handler(0x80, user_test_exit_handler, 0); } fn read_cr2() -> u64 { diff --git a/src/arch/x86_64/interrupts/idt.rs b/src/arch/x86_64/interrupts/idt.rs index 22bce49..b71f9af 100644 --- a/src/arch/x86_64/interrupts/idt.rs +++ b/src/arch/x86_64/interrupts/idt.rs @@ -20,7 +20,7 @@ impl IdtEntry { const fn missing() -> Self { return Self { offset_low: 0, - code_selector: 0x08, + code_selector: 0, ist: 0, attributes: 0, offset_middle: 0, @@ -49,6 +49,8 @@ pub(super) struct InterruptStackFrame { const INTERRUPT_GATE: u8 = 0b1110; const PRESENT: u8 = 1 << 7; const KERNEL_INTERRUPT_GATE: u8 = PRESENT | INTERRUPT_GATE; +const USER_DPL: u8 = 3 << 5; +const USER_INTERRUPT_GATE: u8 = PRESENT | USER_DPL | INTERRUPT_GATE; pub(super) type Handler = extern "x86-interrupt" fn(InterruptStackFrame); pub(super) type ErrorCodeHandler = extern "x86-interrupt" fn(InterruptStackFrame, u64); @@ -65,7 +67,7 @@ impl Idt { } pub(super) fn set_handler(&mut self, vector: u8, handler: Handler, ist: u8) { - self.set_handler_address(vector, handler as usize, ist); + self.set_handler_address(vector, handler as usize, ist, KERNEL_INTERRUPT_GATE); } pub(super) fn set_error_code_handler( @@ -74,17 +76,21 @@ impl Idt { handler: ErrorCodeHandler, ist: u8, ) { - self.set_handler_address(vector, handler as usize, ist); + self.set_handler_address(vector, handler as usize, ist, KERNEL_INTERRUPT_GATE); } - fn set_handler_address(&mut self, vector: u8, address: usize, ist: u8) { + pub(super) fn set_user_handler(&mut self, vector: u8, handler: Handler, ist: u8) { + self.set_handler_address(vector, handler as usize, ist, USER_INTERRUPT_GATE); + } + + fn set_handler_address(&mut self, vector: u8, address: usize, ist: u8, attributes: u8) { self.entries[vector as usize] = IdtEntry { offset_low: address as u16, offset_middle: (address >> 16) as u16, offset_high: (address >> 32) as u32, code_selector: KERNEL_CODE_SELECTOR, ist: ist & 0b111, - attributes: KERNEL_INTERRUPT_GATE, + attributes, reserved: 0, }; } diff --git a/src/arch/x86_64/io_apic.rs b/src/arch/x86_64/io_apic.rs index 68701e3..c4d55d3 100644 --- a/src/arch/x86_64/io_apic.rs +++ b/src/arch/x86_64/io_apic.rs @@ -17,6 +17,7 @@ const MASKED: u32 = 1 << 16; pub const IOAPIC_VIRTUAL_ADDRESS: VirtualAddr = VirtualAddr::new(0xFFFF_FFFD_1000_0000); #[derive(Debug)] +#[allow(unused)] pub enum IoApicError { IdMismatch { expected: u8, actual: u8 }, GsiOutsideRange, @@ -73,13 +74,6 @@ impl IoApic { actual: id, }); } - println!("IOAPIC ID: {:#X}", id); - println!("IOAPIC version: {:#X}", version & 0xFF); - - println!( - "IOAPIC redirection entry count: {:#X}", - io_apic.redirection_entry_count - ); Ok(io_apic) } diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs index 1dc1f1e..fb96e33 100644 --- a/src/arch/x86_64/mod.rs +++ b/src/arch/x86_64/mod.rs @@ -46,6 +46,7 @@ pub fn init() -> ArchState { } #[derive(Debug)] +#[allow(unused)] pub enum InterruptInitError { InvalidLocalApicId, InvalidLocalApicAddress, @@ -162,6 +163,8 @@ pub fn init_interrupt_controller( /// - The stack is currently mapped, writable, and 16-byte aligned pub unsafe fn enter_kernel(stack_top: VirtualAddr, handoff: *mut KernelHandoff) -> ! { unsafe { + gdt::set_kernel_stack(stack_top); + asm!( "mov rsp, {stack_top}", "xor rbp, rbp", @@ -175,6 +178,38 @@ pub unsafe fn enter_kernel(stack_top: VirtualAddr, handoff: *mut KernelHandoff) }; } +/// # Safety +/// +/// - `user_instruction_pointer` and `user_stack_pointer` must be valid user mappings. +/// - The active address space must contain the kernel and supplied user mappings. +pub unsafe fn enter_user( + user_instruction_pointer: VirtualAddr, + user_stack_pointer: VirtualAddr, +) -> ! { + println!("Entering user mode"); + + unsafe { + asm!( + "mov ds, {user_data_selector:x}", + "mov es, {user_data_selector:x}", + "mov fs, {user_data_selector:x}", + "mov gs, {user_data_selector:x}", // ss is handled by iretq + + "push {user_data_selector}", + "push {user_stack_pointer}", + "pushfq", + "push {user_code_selector}", + "push {user_instruction_pointer}", + "iretq", + user_data_selector = in(reg) gdt::USER_DATA_SELECTOR as usize, + user_code_selector = in(reg) gdt::USER_CODE_SELECTOR as usize, + user_instruction_pointer = in(reg) user_instruction_pointer.as_usize(), + user_stack_pointer = in(reg) user_stack_pointer.as_usize(), + options(noreturn) + ); + } +} + pub fn halt() { unsafe { asm!("hlt"); diff --git a/src/arch/x86_64/paging.rs b/src/arch/x86_64/paging.rs index 93e6a0c..7cf05a4 100644 --- a/src/arch/x86_64/paging.rs +++ b/src/arch/x86_64/paging.rs @@ -14,6 +14,7 @@ pub const PAGE_TABLE_ENTRIES: usize = 512; #[derive(Clone, Copy, Debug)] pub struct PagingConfig { physical_address_bits: u8, + global_pages: bool, nx_enabled: bool, mode: PagingMode, } @@ -23,6 +24,7 @@ impl PagingConfig { Self { physical_address_bits: features.physical_address_bits, nx_enabled: features.nx_enabled, + global_pages: features.global_pages, mode: if features.five_level_paging_active { PagingMode::FiveLevel } else { @@ -119,6 +121,7 @@ impl PageTableEntry { const WRITABLE: u64 = 1 << 1; const USER_ACCESSIBLE: u64 = 1 << 2; const HUGE_PAGE: u64 = 1 << 7; + const GLOBAL: u64 = 1 << 8; const NX: u64 = 1 << 63; const WRITE_THROUGH: u64 = 1 << 3; @@ -130,6 +133,7 @@ impl PageTableEntry { permissions: PagePermissions, cache_policy: CachePolicy, config: PagingConfig, + global: bool, ) -> Result { if physical_address.as_usize() >= config.physical_address_limit() { return Err(PageTableEntryError::PhysicalAddressTooLarge); @@ -151,6 +155,10 @@ impl PageTableEntry { value |= Self::NX; } + if global && config.global_pages { + value |= Self::GLOBAL; + } + // TODO: these bit positions very by page size // set PAT match cache_policy { @@ -200,6 +208,10 @@ impl PageTableEntry { self.0 & Self::HUGE_PAGE != 0 } + fn is_global(&self) -> bool { + self.0 & Self::GLOBAL != 0 + } + fn table_frame(&self, config: PagingConfig) -> Option { if !self.is_present() || self.is_huge() { return None; @@ -254,8 +266,8 @@ pub(crate) enum PageTableCreateError { pub struct PageTable { pub direct_map: DirectMap, - frame: OwnedFrame, config: PagingConfig, + frame: OwnedFrame, } impl PageTable { @@ -279,6 +291,10 @@ impl PageTable { }) } + pub const fn config(&self) -> PagingConfig { + self.config + } + fn is_active(&self) -> bool { let cr3 = unsafe { read_cr3(self.config) }; @@ -395,6 +411,7 @@ impl PageTable { permissions: PagePermissions, allocator: &mut FrameAllocator, cache_policy: CachePolicy, + global: bool, ) -> Result<(), MapError> { let address = mapped_addr.as_usize(); @@ -411,6 +428,7 @@ impl PageTable { permissions, cache_policy, self.config, + global, ) .map_err(|error| match error { PageTableEntryError::PhysicalAddressTooLarge => MapError::PhysicalAddressTooLarge, @@ -644,6 +662,17 @@ impl PageTable { Ok(frame) } + pub fn copy_kernel_mappings_to(&self, destination: &mut PageTable) { + let src = self + .table(self.frame.frame_address()) + .expect("source page table outside direct map"); + let dest = destination + .table_mut(destination.frame.frame_address()) + .expect("destination page table outside direct map"); + + dest[256..512].copy_from_slice(&src[256..512]); + } + fn flush_tlb_if_active(&self, page: VirtualAddr) { debug_assert!(self.is_canonical(page.as_usize())); @@ -702,6 +731,30 @@ impl PageTable { unsafe { allocator.dealloc(self.frame) }; } + + pub unsafe fn destroy_user(mut self, allocator: &mut FrameAllocator) { + assert!(!self.is_active(), "attempted to destroy active page table"); + + let root_frame = self.frame.frame_address(); + + for idx in 0..256 { + let entry = self.table(root_frame).expect("table outside direct map")[idx]; + if !entry.is_present() || entry.is_huge() { + continue; + } + + let child = entry + .table_frame(self.config) + .expect("invalid page table entry"); + self.destroy_children(child, 1, allocator); + + self.table_mut(root_frame) + .expect("table outside direct map")[idx] = PageTableEntry::null(); + unsafe { allocator.dealloc(OwnedFrame::from_raw(child)) }; + } + + unsafe { allocator.dealloc(self.frame) }; + } } fn translate_huge_page( diff --git a/src/main.rs b/src/main.rs index 7d15bea..8516997 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,86 +11,11 @@ mod platform; use crate::{ debug::serial, - memory::{AddressSpace, MemoryRegionKind, VirtualAddr}, + memory::{ + AddressSpace, KernelStack, MemoryRegionKind, PagePermissions, UserStack, VirtualAddr, + }, }; -#[derive(Debug)] -#[allow(unused)] -enum KernelStackCreateError { - AddressOverflow, - OutOfFrames, - GuardPageMapped, - Map(memory::MapError), -} - -struct KernelStack { - start: memory::VirtualAddr, - pages: usize, -} - -const BOOTSTRAP_STACK_TOP: usize = 0xFFFF_FFFE_0000_0000; -const KERNEL_STACK_SIZE: usize = 64 * 1024; -const KERNEL_STACK_GUARD_SIZE: usize = memory::FRAME_SIZE; - -const BOOTSTRAP_STACK_START: usize = BOOTSTRAP_STACK_TOP - KERNEL_STACK_SIZE; -const BOOTSTRAP_STACK_GUARD: usize = BOOTSTRAP_STACK_START - KERNEL_STACK_GUARD_SIZE; - -impl KernelStack { - fn allocate( - address_space: &mut AddressSpace, - allocator: &mut memory::FrameAllocator, - ) -> Result { - let kernel_stack_start = VirtualAddr::new(BOOTSTRAP_STACK_START); - - let guard_page = VirtualAddr::new(BOOTSTRAP_STACK_GUARD); - - if address_space.to_physical(guard_page).is_some() { - // guard page should be *unmapped* so we get a page fault if we try to access it - return Err(KernelStackCreateError::GuardPageMapped); - } - - let mut i = 0; - // on error we will just leak the frames - // because like what are going to do if we recover them? Die happily without leaking frames? idgaf - while i < KERNEL_STACK_SIZE { - let frame = allocator - .alloc() - .ok_or(KernelStackCreateError::OutOfFrames)?; - address_space - .map( - frame.frame_address().start_address(), - VirtualAddr::new( - kernel_stack_start - .as_usize() - .checked_add(i) - .ok_or(KernelStackCreateError::AddressOverflow)?, - ), - memory::PagePermissions::new(true, false, false), - allocator, - memory::CachePolicy::WriteBack, - ) - .map_err(|err| KernelStackCreateError::Map(err))?; - i += memory::FRAME_SIZE; - } - - debug_assert!(address_space.to_physical(guard_page).is_none()); - - Ok(Self { - start: kernel_stack_start, - pages: i / memory::FRAME_SIZE, - }) - } - - pub fn top(&self) -> Result { - Ok(VirtualAddr::new( - self.start - .as_usize() - .checked_add(self.pages * memory::FRAME_SIZE) - .ok_or(KernelStackCreateError::AddressOverflow)?, - )) - } -} - pub struct KernelHandoff { allocator: memory::FrameAllocator, address_space: AddressSpace, @@ -134,7 +59,7 @@ pub extern "C" fn _start() -> ! { .to_virtual(handoff_frame.frame_address().start_address()) .expect("failed to map kernel handoff"); - let bootstrap_stack_top = bootstrap_stack.top().unwrap(); + let bootstrap_stack_top = bootstrap_stack.top(); let handoff = KernelHandoff { allocator, address_space, @@ -177,10 +102,7 @@ pub unsafe extern "C" fn kernel_main(handoff: *mut KernelHandoff) -> ! { MemoryRegionKind::BootloaderReclaimable, ); - println!( - "Initializing local ACPI... {:#X}", - boot_info.rsdp.as_usize() - ); + println!("Initializing local ACPI...",); let acpi = platform::acpi::init(&boot_info, direct_map).expect("failed to initialize ACPI"); @@ -193,7 +115,45 @@ pub unsafe extern "C" fn kernel_main(handoff: *mut KernelHandoff) -> ! { arch::init_interrupt_controller(&madt, &mut allocator, &mut address_space) .expect("failed to initialize interrupt controller"); - println!("interrupt controller: {:?}", interrupt_controller); + let mut user_addr_space = address_space + .new_user(&mut allocator) + .expect("failed to create user address space"); + + let user_stack = UserStack::allocate(&mut user_addr_space, &mut allocator) + .expect("failed to allocate user stack"); + + let user_instruction_pointer = VirtualAddr::new(0x8000); + let user_code_page = allocator + .alloc() + .expect("failed to allocate user code page") + .frame_address(); + user_addr_space + .map( + user_code_page.start_address(), + user_instruction_pointer, + PagePermissions::new(true, true, true), + &mut allocator, + memory::CachePolicy::WriteBack, + ) + .expect("failed to map user code page"); + + unsafe { + user_addr_space.activate(); + + let user_code: [u8; 5] = [ + 0xCC, // INT3 + 0xCD, 0x80, // INT 0x80 + 0xEB, 0xFE, // JMP -2 (loop forever if exit returns) + ]; + + core::ptr::copy_nonoverlapping( + user_code.as_ptr(), + user_instruction_pointer.as_mut_ptr::(), + user_code.len(), + ); + + arch::enter_user(user_instruction_pointer, user_stack.top()); + } hcf(); } diff --git a/src/memory/address_space.rs b/src/memory/address_space.rs index a8ce682..a6944e0 100644 --- a/src/memory/address_space.rs +++ b/src/memory/address_space.rs @@ -20,6 +20,8 @@ pub enum MapError { OutOfMemory, PageTableUnavailable, CorruptedPageTable, + InvalidUserAddress, + InvalidUserMap, } impl From for MapError { @@ -46,6 +48,7 @@ pub enum UnmapError { MappingConflict, PageTableUnavailable, CorruptedPageTable, + InvalidUserAddress, } impl From for UnmapError { @@ -79,6 +82,7 @@ impl From for AddressSpaceCreateError { } } +#[derive(Debug, PartialEq, Eq)] enum AddressSpaceKind { Kernel, User, @@ -168,8 +172,15 @@ impl AddressSpace { Ok(space) } - pub fn new_user(kernel_space: &AddressSpace, allocator: &mut FrameAllocator) -> Self { - todo!() + pub fn new_user(&self, allocator: &mut FrameAllocator) -> Result { + let mut user_root = PageTable::new(self.root.direct_map, self.root.config(), allocator)?; + + self.root.copy_kernel_mappings_to(&mut user_root); + + Ok(Self { + root: user_root, + kind: AddressSpaceKind::User, + }) } pub fn map( @@ -180,11 +191,33 @@ impl AddressSpace { allocator: &mut FrameAllocator, cache_policy: CachePolicy, ) -> Result<(), MapError> { + let global = self.kind == AddressSpaceKind::Kernel; + + if self.kind == AddressSpaceKind::User { + if virtual_addr.as_usize() >= 0x0000_8000_0000_0000 { + return Err(MapError::InvalidUserAddress); + } + + if !permissions.user_accessible { + return Err(MapError::InvalidUserMap); + } + + // TODO: a user address space should not be able to map kernel memory + // or ACPI memory, or anything like that + } + let frame = FrameAddr::from_start_address(physical_addr) .ok_or(MapError::PhysicalAddressUnaligned)?; self.root - .map(virtual_addr, frame, permissions, allocator, cache_policy) + .map( + virtual_addr, + frame, + permissions, + allocator, + cache_policy, + global, + ) .map_err(MapError::from) } @@ -272,6 +305,10 @@ impl AddressSpace { virtual_addr: VirtualAddr, allocator: &mut FrameAllocator, ) -> Result { + if self.kind == AddressSpaceKind::User && virtual_addr.as_usize() >= 0x0000_8000_0000_0000 { + return Err(UnmapError::InvalidUserAddress); + } + unsafe { self.root.unmap(virtual_addr, allocator) }.map_err(UnmapError::from) } @@ -293,7 +330,10 @@ impl AddressSpace { /// no CPU or kernel operation can access its paging structures. pub unsafe fn destroy(self, allocator: &mut FrameAllocator) { unsafe { - self.root.destroy(allocator); + match self.kind { + AddressSpaceKind::Kernel => self.root.destroy(allocator), + AddressSpaceKind::User => self.root.destroy_user(allocator), + } } } } diff --git a/src/memory/mod.rs b/src/memory/mod.rs index 55c8c76..5a9f95f 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -1,9 +1,11 @@ mod address_space; mod frame; +pub mod stack; #[allow(unused)] pub use address_space::{AddressSpace, AddressSpaceCreateError, MapError, UnmapError}; pub use frame::{FRAME_SIZE, FrameAddr, FrameAllocator, OwnedFrame}; +pub use stack::{KernelStack, StackCreateError, UserStack}; pub struct KernelSegment { pub physical_base: PhysicalAddr, diff --git a/src/memory/stack.rs b/src/memory/stack.rs new file mode 100644 index 0000000..6741f84 --- /dev/null +++ b/src/memory/stack.rs @@ -0,0 +1,196 @@ +use crate::memory::{ + AddressSpace, CachePolicy, FRAME_SIZE, FrameAllocator, MapError, OwnedFrame, PagePermissions, + VirtualAddr, +}; + +const STACK_PAGES: usize = 16; +const STACK_SIZE: usize = STACK_PAGES * FRAME_SIZE; + +const KERNEL_STACK_TOP: VirtualAddr = VirtualAddr::new(0xFFFF_FFFE_0000_0000); +const USER_STACK_TOP: VirtualAddr = VirtualAddr::new(0x0000_7FFF_FFFF_F000); + +#[derive(Debug)] +pub enum StackCreateError { + AddressOverflow, + UnalignedStackTop, + OutOfFrames, + GuardPageMapped, + Map(MapError), +} + +struct StackMapping { + guard_page: VirtualAddr, + mapped_start: VirtualAddr, + top: VirtualAddr, +} + +impl StackMapping { + fn allocate( + address_space: &mut AddressSpace, + allocator: &mut FrameAllocator, + top: VirtualAddr, + permissions: PagePermissions, + ) -> Result { + if top.as_usize() % FRAME_SIZE != 0 { + return Err(StackCreateError::UnalignedStackTop); + } + + let mapped_start = VirtualAddr::new( + top.as_usize() + .checked_sub(STACK_SIZE) + .ok_or(StackCreateError::AddressOverflow)?, + ); + let guard_page = VirtualAddr::new( + mapped_start + .as_usize() + .checked_sub(FRAME_SIZE) + .ok_or(StackCreateError::AddressOverflow)?, + ); + + if address_space.to_physical(guard_page).is_some() { + return Err(StackCreateError::GuardPageMapped); + } + + let mut mapped_pages = 0; + + while mapped_pages < STACK_PAGES { + let virtual_address = VirtualAddr::new( + mapped_start + .as_usize() + .checked_add(mapped_pages * FRAME_SIZE) + .ok_or(StackCreateError::AddressOverflow)?, + ); + let frame = match allocator.alloc() { + Some(frame) => frame, + None => { + Self::rollback(address_space, allocator, mapped_start, mapped_pages); + return Err(StackCreateError::OutOfFrames); + } + }; + + if let Err(error) = address_space.map( + frame.frame_address().start_address(), + virtual_address, + permissions, + allocator, + CachePolicy::WriteBack, + ) { + unsafe { allocator.dealloc(frame) }; + Self::rollback(address_space, allocator, mapped_start, mapped_pages); + return Err(StackCreateError::Map(error)); + } + + let _ = frame.into_raw(); + mapped_pages += 1; + } + + debug_assert!(address_space.to_physical(guard_page).is_none()); + + Ok(Self { + guard_page, + mapped_start, + top, + }) + } + + fn rollback( + address_space: &mut AddressSpace, + allocator: &mut FrameAllocator, + mapped_start: VirtualAddr, + mapped_pages: usize, + ) { + for page in (0..mapped_pages).rev() { + let virtual_address = VirtualAddr::new(mapped_start.as_usize() + page * FRAME_SIZE); + let frame = unsafe { + address_space + .unmap(virtual_address, allocator) + .expect("failed to roll back stack mapping") + }; + unsafe { allocator.dealloc(OwnedFrame::from_raw(frame)) }; + } + } + + /// # Safety + /// + /// The caller must ensure this stack is not active on any CPU and cannot be accessed by any + /// kernel operation while it is being destroyed. + unsafe fn destroy(self, address_space: &mut AddressSpace, allocator: &mut FrameAllocator) { + for page in (0..STACK_PAGES).rev() { + let virtual_address = + VirtualAddr::new(self.mapped_start.as_usize() + page * FRAME_SIZE); + let frame = unsafe { + address_space + .unmap(virtual_address, allocator) + .expect("stack mapping was unexpectedly missing") + }; + unsafe { allocator.dealloc(OwnedFrame::from_raw(frame)) }; + } + + debug_assert!(address_space.to_physical(self.guard_page).is_none()); + } +} + +pub struct KernelStack { + mapping: StackMapping, +} + +impl KernelStack { + pub fn allocate( + address_space: &mut AddressSpace, + allocator: &mut FrameAllocator, + ) -> Result { + let mapping = StackMapping::allocate( + address_space, + allocator, + KERNEL_STACK_TOP, + PagePermissions::new(true, false, false), + )?; + + Ok(Self { mapping }) + } + + pub const fn top(&self) -> VirtualAddr { + self.mapping.top + } + + /// # Safety + /// + /// The caller must ensure this stack is not active on any CPU and cannot be accessed by any + /// kernel operation while it is being destroyed. + pub unsafe fn destroy(self, address_space: &mut AddressSpace, allocator: &mut FrameAllocator) { + unsafe { self.mapping.destroy(address_space, allocator) }; + } +} + +pub struct UserStack { + mapping: StackMapping, +} + +impl UserStack { + pub fn allocate( + address_space: &mut AddressSpace, + allocator: &mut FrameAllocator, + ) -> Result { + let top = VirtualAddr::new(USER_STACK_TOP.as_usize()); + let mapping = StackMapping::allocate( + address_space, + allocator, + top, + PagePermissions::new(true, false, true), + )?; + + Ok(Self { mapping }) + } + + pub const fn top(&self) -> VirtualAddr { + self.mapping.top + } + + /// # Safety + /// + /// The caller must ensure this stack is not active in any thread and cannot be accessed while + /// it is being destroyed. + pub unsafe fn destroy(self, address_space: &mut AddressSpace, allocator: &mut FrameAllocator) { + unsafe { self.mapping.destroy(address_space, allocator) }; + } +}