diff --git a/kernel/asm/ring3_enter.S b/kernel/asm/ring3_enter.S new file mode 100644 index 0000000..b9a89ec --- /dev/null +++ b/kernel/asm/ring3_enter.S @@ -0,0 +1,64 @@ +/* opencoreC - Ring 3 User Mode Transition (IRETQ) */ +.global enter_user_mode +.extern set_kernel_stack + +.section .text +/* + * void enter_user_mode(uint64_t entry_rip, uint64_t user_rsp, uint64_t pml4_paddr, uint64_t kernel_stack_top); + * RDI = entry_rip + * RSI = user_rsp + * RDX = pml4_paddr + * RCX = kernel_stack_top + */ +enter_user_mode: + /* 1. Save kernel stack in TSS (RSP0) for future interrupts/syscalls */ + push rdi + push rsi + push rdx + mov rdi, rcx + call set_kernel_stack + pop rdx + pop rsi + pop rdi + + /* 2. Switch to User Address Space (PML4) */ + mov cr3, rdx + + /* 3. Build IRETQ Stack Frame: + * [RSP + 32] = SS (User Data: 0x18 | 3 = 0x1B) + * [RSP + 24] = RSP (User Stack Pointer) + * [RSP + 16] = RFLAGS (Interrupt Flag Enabled: 0x202) + * [RSP + 8] = CS (User Code: 0x20 | 3 = 0x23) + * [RSP + 0] = RIP (User Entry Point) + */ + mov ax, 0x1B + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + push 0x1B /* User SS */ + push rsi /* User RSP */ + push 0x0202 /* RFLAGS (IF enabled) */ + push 0x23 /* User CS */ + push rdi /* User RIP */ + + /* 4. Clear general-purpose registers */ + xor rax, rax + xor rbx, rbx + xor rcx, rcx + xor rdx, rdx + xor rsi, rsi + xor rdi, rdi + xor rbp, rbp + xor r8, r8 + xor r9, r9 + xor r10, r10 + xor r11, r11 + xor r12, r12 + xor r13, r13 + xor r14, r14 + xor r15, r15 + + /* 5. Jump to Ring 3 */ + iretq diff --git a/kernel/src/arch/gdt.rs b/kernel/src/arch/gdt.rs index 14cbc9e..c4a1f3c 100644 --- a/kernel/src/arch/gdt.rs +++ b/kernel/src/arch/gdt.rs @@ -94,7 +94,8 @@ pub unsafe fn init() { ); } -pub fn set_kernel_stack(stack_top: u64) { +#[no_mangle] +pub extern "C" fn set_kernel_stack(stack_top: u64) { unsafe { let tss_ptr = core::ptr::addr_of_mut!(TSS); (*tss_ptr).rsp0 = stack_top; diff --git a/kernel/src/loader/elf.rs b/kernel/src/loader/elf.rs new file mode 100644 index 0000000..b8efccf --- /dev/null +++ b/kernel/src/loader/elf.rs @@ -0,0 +1,135 @@ +//! ELF64 Executable Binary Parser and Loader for Ring 3 User Space + +use crate::mm::vmm::{VMM, PAGE_PRESENT, PAGE_WRITABLE, PAGE_USER}; +use crate::mm::pfa::{PFA, PAGE_SIZE}; +use crate::kprintln; + +#[repr(C, packed)] +pub struct Elf64Header { + pub ident: [u8; 16], + pub elf_type: u16, + pub machine: u16, + pub version: u32, + pub entry: u64, + pub phoff: u64, + pub shoff: u64, + pub flags: u32, + pub ehsize: u16, + pub phentsize: u16, + pub phnum: u16, + pub shentsize: u16, + pub shnum: u16, + pub shstrndx: u16, +} + +#[repr(C, packed)] +pub struct Elf64ProgramHeader { + pub p_type: u32, + pub p_flags: u32, + pub p_offset: u64, + pub p_vaddr: u64, + pub p_paddr: u64, + pub p_filesz: u64, + pub p_memsz: u64, + pub p_align: u64, +} + +pub const PT_LOAD: u32 = 1; +pub const PF_X: u32 = 1 << 0; +pub const PF_W: u32 = 1 << 1; +pub const PF_R: u32 = 1 << 2; + +pub struct LoadedProcess { + pub entry_rip: u64, + pub user_rsp: u64, + pub pml4_paddr: u64, + pub kernel_stack_top: u64, +} + +pub unsafe fn load_elf(elf_bytes: &[u8], hhdm_offset: u64) -> Result { + if elf_bytes.len() < core::mem::size_of::() { + return Err("ELF file too small"); + } + + let header = &*(elf_bytes.as_ptr() as *const Elf64Header); + + // Validate ELF Magic "\x7fELF" + if header.ident[0] != 0x7F || header.ident[1] != b'E' || header.ident[2] != b'L' || header.ident[3] != b'F' { + return Err("Invalid ELF magic"); + } + + // Must be 64-bit (class 2) and x86_64 machine (0x3E) + if header.ident[4] != 2 || header.machine != 0x3E { + return Err("Not a 64-bit x86_64 ELF"); + } + + let vmm_ptr = core::ptr::addr_of_mut!(VMM); + let pml4_paddr = (*vmm_ptr).create_user_address_space().ok_or("Failed to allocate user PML4")?; + + let phoff = header.phoff as usize; + let phentsize = header.phentsize as usize; + let phnum = header.phnum as usize; + let entry_rip = header.entry; + + for i in 0..phnum { + let phdr_ptr = elf_bytes.as_ptr().add(phoff + i * phentsize) as *const Elf64ProgramHeader; + let phdr = &*phdr_ptr; + + if phdr.p_type == PT_LOAD { + let vaddr_start = phdr.p_vaddr; + let memsz = phdr.p_memsz as usize; + let filesz = phdr.p_filesz as usize; + let file_offset = phdr.p_offset as usize; + + let mut flags = PAGE_PRESENT | PAGE_USER; + if (phdr.p_flags & PF_W) != 0 { + flags |= PAGE_WRITABLE; + } + + // Map and copy segment page by page + let page_count = (memsz + PAGE_SIZE - 1) / PAGE_SIZE; + for page in 0..page_count { + let page_vaddr = vaddr_start + (page * PAGE_SIZE) as u64; + let page_paddr = (*core::ptr::addr_of_mut!(PFA)).alloc_frame().ok_or("Out of memory for ELF segment")?; + + // Map in user page table + (*vmm_ptr).map_page(pml4_paddr, page_vaddr, page_paddr, flags) + .map_err(|_| "Failed to map user ELF page")?; + + // Copy data from file + let dest = (page_paddr + hhdm_offset) as *mut u8; + let page_file_start = page * PAGE_SIZE; + if page_file_start < filesz { + let copy_len = core::cmp::min(PAGE_SIZE, filesz - page_file_start); + let src = elf_bytes.as_ptr().add(file_offset + page_file_start); + core::ptr::copy_nonoverlapping(src, dest, copy_len); + } + } + } + } + + // Allocate User Stack: 4 pages at 0x00007FFF_0000_0000 + let user_stack_base = 0x00007FFF_0000_0000u64; + let user_stack_pages = 4; + for page in 0..user_stack_pages { + let page_vaddr = user_stack_base + (page * PAGE_SIZE) as u64; + let page_paddr = (*core::ptr::addr_of_mut!(PFA)).alloc_frame().ok_or("Out of memory for user stack")?; + (*vmm_ptr).map_page(pml4_paddr, page_vaddr, page_paddr, PAGE_PRESENT | PAGE_WRITABLE | PAGE_USER) + .map_err(|_| "Failed to map user stack")?; + } + let user_rsp = user_stack_base + (user_stack_pages * PAGE_SIZE) as u64 - 16; + + // Allocate Kernel Stack (TSS RSP0): 4 pages + let kstack_frame = (*core::ptr::addr_of_mut!(PFA)).alloc_frame().ok_or("Out of memory for kernel stack")?; + let kernel_stack_top = kstack_frame + hhdm_offset + (PAGE_SIZE as u64) - 16; + + kprintln!("[LOADER] ELF Loaded: Entry RIP: {:#x}, User RSP: {:#x}, User PML4: {:#x}", + entry_rip, user_rsp, pml4_paddr); + + Ok(LoadedProcess { + entry_rip, + user_rsp, + pml4_paddr, + kernel_stack_top, + }) +} diff --git a/kernel/src/loader/mod.rs b/kernel/src/loader/mod.rs new file mode 100644 index 0000000..f6808c2 --- /dev/null +++ b/kernel/src/loader/mod.rs @@ -0,0 +1 @@ +pub mod elf; diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 3986ad4..b8b1326 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -7,6 +7,7 @@ pub mod arch; pub mod mm; pub mod ipc; pub mod sched; +pub mod loader; use core::panic::PanicInfo; use limine_requests::*; @@ -14,6 +15,16 @@ use limine_requests::*; // Include assembly trampolines directly into the binary core::arch::global_asm!(include_str!("../asm/context.S")); core::arch::global_asm!(include_str!("../asm/syscall_entry.S")); +core::arch::global_asm!(include_str!("../asm/ring3_enter.S")); + +extern "C" { + fn enter_user_mode( + entry_rip: u64, + user_rsp: u64, + pml4_paddr: u64, + kernel_stack_top: u64, + ) -> !; +} #[no_mangle] pub extern "C" fn _start() -> ! { @@ -64,26 +75,7 @@ pub extern "C" fn _start() -> ! { // 7. Initialize Scheduler sched::init(); - // 8. Inspect Boot Modules (Initial Userspace Servers) - unsafe { - let mod_resp = MODULE_REQUEST.response; - if !mod_resp.is_null() { - let count = (*mod_resp).module_count; - kprintln!("[BOOT] Found {} initial userspace module(s):", count); - for i in 0..count { - let mod_file = *(*mod_resp).modules.add(i as usize); - let path_cstr = (*mod_file).path; - let path_len = (0..256).find(|&j| *path_cstr.add(j) == 0).unwrap_or(0); - let path = core::str::from_utf8(core::slice::from_raw_parts(path_cstr, path_len)).unwrap_or("unknown"); - kprintln!(" - Module [{}]: {} (size: {} bytes, addr: {:p})", - i, path, (*mod_file).size, (*mod_file).address); - } - } else { - kprintln!("[BOOT] No external modules passed by bootloader."); - } - } - - // 9. Kernel Self-Test: Fast-Path IPC Ping + // 8. Self-Test Fast-Path IPC Ping kprintln!("[TEST] Executing Kernel Fast-Path IPC Self-Test..."); let (status, resp_op, val) = ipc::fastpath::handle_fastpath_call( ipc::fastpath::CAP_KERNEL_CONTROL, @@ -92,7 +84,35 @@ pub extern "C" fn _start() -> ! { ); kprintln!("[TEST] IPC Fast-Path Ping Result: status={}, opcode={:#x}, val={}", status, resp_op, val); - kprintln!("[KERNEL] Initialization complete. Entering kernel idle loop.\n"); + // 9. Inspect and Load Initial Userspace Process (Ring 3 Transition) + unsafe { + let mod_resp = MODULE_REQUEST.response; + if !mod_resp.is_null() && (*mod_resp).module_count > 0 { + kprintln!("[BOOT] Loading root userspace module [0] into Ring 3..."); + let mod_file = *(*mod_resp).modules; + let elf_slice = core::slice::from_raw_parts((*mod_file).address, (*mod_file).size as usize); + + match loader::elf::load_elf(elf_slice, hhdm_offset) { + Ok(proc) => { + kprintln!("[BOOT] Transitioning CPU to Ring 3 (User Mode) via IRETQ..."); + kprintln!("-------------------------------------------------------"); + enter_user_mode( + proc.entry_rip, + proc.user_rsp, + proc.pml4_paddr, + proc.kernel_stack_top, + ); + } + Err(err) => { + kprintln!("[ERROR] Failed to load ELF process: {}", err); + } + } + } else { + kprintln!("[BOOT] No userspace modules detected. Staying in Ring 0 idle loop."); + } + } + + kprintln!("[KERNEL] Entering idle loop.\n"); loop { unsafe { diff --git a/kernel/src/mm/vmm.rs b/kernel/src/mm/vmm.rs index fc72e91..472718b 100644 --- a/kernel/src/mm/vmm.rs +++ b/kernel/src/mm/vmm.rs @@ -29,10 +29,43 @@ impl VirtualMemoryManager { } #[inline] - fn phys_to_virt(&self, paddr: u64) -> *mut T { + pub fn phys_to_virt(&self, paddr: u64) -> *mut T { (paddr + self.hhdm_offset) as *mut T } + #[inline] + pub fn read_cr3(&self) -> u64 { + let cr3: u64; + unsafe { + core::arch::asm!("mov {0}, cr3", out(reg) cr3, options(nostack, preserves_flags)); + } + cr3 + } + + #[inline] + pub fn load_cr3(&self, pml4_paddr: u64) { + unsafe { + core::arch::asm!("mov cr3, {0}", in(reg) pml4_paddr, options(nostack, preserves_flags)); + } + } + + /// Create a new user PML4 with higher-half kernel space cloned + pub unsafe fn create_user_address_space(&self) -> Option { + let pfa_ptr = core::ptr::addr_of_mut!(PFA); + let user_pml4_paddr = (*pfa_ptr).alloc_frame()?; + let user_pml4 = self.phys_to_virt::(user_pml4_paddr); + + let current_cr3 = self.read_cr3() & PAGE_MASK; + let kernel_pml4 = self.phys_to_virt::(current_cr3); + + // Copy kernel mappings (higher half: 256..512) + for i in 256..512 { + (*user_pml4).entries[i] = (*kernel_pml4).entries[i]; + } + + Some(user_pml4_paddr) + } + /// Map a single 4KiB page into the specified PML4 pub unsafe fn map_page( &self, @@ -63,8 +96,10 @@ impl VirtualMemoryManager { // 4. Map the physical page in the PT (*pt).entries[pt_idx] = (paddr & PAGE_MASK) | flags | PAGE_PRESENT; - // Invalidate TLB for this virtual address - core::arch::asm!("invlpg [{0}]", in(reg) vaddr, options(nostack, preserves_flags)); + // Invalidate TLB for this virtual address if current address space + if (self.read_cr3() & PAGE_MASK) == pml4_paddr { + core::arch::asm!("invlpg [{0}]", in(reg) vaddr, options(nostack, preserves_flags)); + } Ok(()) }