uefi_loader/
main.rs

1//! # UEFI Loader Main Entry Point
2
3#![cfg_attr(not(test), no_std)]
4#![no_main]
5#![allow(unsafe_code, dead_code)]
6extern crate alloc;
7
8mod elf;
9mod file_system;
10mod framebuffer;
11mod memory;
12mod rsdp;
13mod tracing;
14mod uefi_mmap;
15mod vmem;
16
17use crate::elf::parser::ElfHeader;
18use crate::file_system::load_file;
19use crate::framebuffer::get_framebuffer;
20use crate::memory::alloc_trampoline_stack;
21use crate::rsdp::find_rsdp_addr;
22use crate::tracing::trace_boot_info;
23use crate::uefi_mmap::exit_boot_services;
24use crate::vmem::create_kernel_pagetables;
25use alloc::boxed::Box;
26use kernel_info::boot::{KernelBootInfo, MemoryMapInfo};
27use kernel_qemu::qemu_trace;
28use kernel_vmem::addresses::{PhysicalAddress, VirtualAddress};
29use uefi::boot::PAGE_SIZE;
30use uefi::cstr16;
31use uefi::prelude::*;
32
33// TODO: Add proper documentation.
34const TRAMPOLINE_STACK_SIZE_BYTES: usize = 64 * 1024;
35
36#[entry]
37#[allow(clippy::too_many_lines)]
38fn efi_main() -> Status {
39    // Initialize logging and allocator helpers
40    if uefi::helpers::init().is_err() {
41        return Status::UNSUPPORTED;
42    }
43
44    qemu_trace!("UEFI Loader reporting to QEMU\n");
45    uefi::println!("Attempting to load kernel.elf ...");
46
47    let elf_bytes = match load_file(cstr16!("\\EFI\\Boot\\kernel.elf")) {
48        Ok(bytes) => bytes,
49        Err(status) => {
50            uefi::println!("Failed to load kernel.elf. Exiting.");
51            return status;
52        }
53    };
54
55    // Parse ELF64, collect PT_LOAD segments and entry address
56    let Ok(parsed) = ElfHeader::parse_elf64(&elf_bytes) else {
57        uefi::println!("kernel.elf is not a valid x86_64 ELF64");
58        return Status::UNSUPPORTED;
59    };
60
61    uefi::println!("Loading kernel segments into memory ...");
62    let kernel_segments = match elf::loader::load_pt_load_segments_hi(&elf_bytes, &parsed) {
63        Ok(segments) => segments,
64        Err(e) => {
65            uefi::println!("Failed to load PT_LOAD segments: {e:?}");
66            return e.into();
67        }
68    };
69
70    uefi::println!(
71        "kernel.elf loaded successfully: entry={}, segments={}",
72        parsed.entry,
73        parsed.segments.len()
74    );
75
76    let fb = match get_framebuffer() {
77        Ok(fb) => fb,
78        Err(status) => {
79            return status;
80        }
81    };
82
83    // Locate RSDP before exiting boot services; if not found, set 0.
84    let rsdp_addr: u64 = find_rsdp_addr();
85
86    let boot_info = KernelBootInfo {
87        // Memory map fields are filled right after exit_boot_services returns the owned map:
88        mmap: MemoryMapInfo {
89            mmap_ptr: 0,
90            mmap_len: 0,
91            mmap_desc_size: 0,
92            mmap_desc_version: 0,
93        },
94        rsdp_addr,
95        fb,
96    };
97
98    // Heap-allocate and leak the boot info.
99    let boot_info = Box::leak(Box::new(boot_info));
100    uefi::println!("Kernel boot info: {:#?}", core::ptr::from_ref(boot_info));
101
102    // The trampoline code must also be mapped, otherwise we won't be able to execute it
103    // when switching the CR3 page tables.
104    let tramp_code_va = VirtualAddress::new(switch_to_kernel as usize as u64);
105    let tramp_code_len: usize = PAGE_SIZE; // should be enough
106
107    // Allocate a trampoline stack (with guard page)
108    let (tramp_stack_base_phys, tramp_stack_top_va) =
109        alloc_trampoline_stack(TRAMPOLINE_STACK_SIZE_BYTES, true);
110
111    // Pass identity-mapped low pointer
112    let bi_ptr_va = VirtualAddress::from_ptr(core::ptr::from_ref::<KernelBootInfo>(boot_info));
113
114    // Build page tables
115    let Ok(pml4_phys) = create_kernel_pagetables(
116        &kernel_segments,
117        tramp_code_va,
118        tramp_code_len,
119        tramp_stack_base_phys,
120        TRAMPOLINE_STACK_SIZE_BYTES,
121        bi_ptr_va,
122    ) else {
123        uefi::println!("Failed to create kernel page tables");
124        return Status::OUT_OF_RESOURCES;
125    };
126
127    boot_info.mmap = match exit_boot_services() {
128        Ok(value) => value,
129        Err(value) => return value,
130    };
131
132    // Off we pop.
133    unsafe {
134        trace_boot_info(boot_info, bi_ptr_va, parsed.entry, tramp_stack_top_va);
135        enable_wp_nxe_pge();
136
137        // Activate our CR3 and jump to kernel entry (higher-half VA)
138        switch_to_kernel(
139            pml4_phys,
140            parsed.entry, // this is the higher-half VMA from ELF header
141            bi_ptr_va,
142            tramp_stack_top_va,
143        )
144    }
145}
146
147#[allow(clippy::items_after_statements)]
148unsafe fn enable_wp_nxe_pge() {
149    // CR0.WP = 1 (write-protect in supervisor)
150    qemu_trace!("Enabling supervisor write protection ...\n");
151    let mut cr0: u64;
152    unsafe {
153        core::arch::asm!("mov {}, cr0", out(reg) cr0, options(nomem, preserves_flags));
154    }
155    cr0 |= 1 << 16;
156    unsafe {
157        core::arch::asm!("mov cr0, {}", in(reg) cr0, options(nomem, preserves_flags));
158    }
159
160    // EFER.NXE = 1
161    qemu_trace!("Setting EFER.NXE ...\n");
162    const MSR_EFER: u32 = 0xC000_0080; // TODO: Document this properly
163    let (mut lo, mut hi): (u32, u32);
164    unsafe {
165        core::arch::asm!("rdmsr", in("ecx") MSR_EFER, out("eax") lo, out("edx") hi, options(nomem, preserves_flags));
166    }
167    let mut efer = u64::from(hi) << 32 | u64::from(lo);
168    efer |= 1 << 11;
169    lo = u32::try_from(efer).expect("failed to cast efer to u32"); // TODO: Handle properly
170    hi = (efer >> 32) as u32;
171    unsafe {
172        core::arch::asm!("wrmsr", in("ecx") MSR_EFER, in("eax") lo, in("edx") hi, options(nomem, preserves_flags));
173    }
174
175    // CR4.PGE = 1 (global pages)
176    qemu_trace!("Enabling global pages ...\n");
177    let mut cr4: u64;
178    unsafe {
179        core::arch::asm!("mov {}, cr4", out(reg) cr4, options(nomem, preserves_flags));
180    }
181    cr4 |= 1 << 7;
182    unsafe {
183        core::arch::asm!("mov cr4, {}", in(reg) cr4, options(nomem, preserves_flags));
184    }
185}
186
187type PageTablePhysicalAddress = PhysicalAddress;
188type KernelVirtualAddress = VirtualAddress;
189type BootInfoVirtualAddress = VirtualAddress;
190type TrampolineStackVirtualAddress = VirtualAddress;
191
192/// Enter the kernel via a tiny trampoline.
193/// - `new_cr3`: phys addr of PML4 (4KiB aligned)
194/// - `kernel_entry`: higher-half VA (your `extern "win64" fn(*const BootInfo) -> !`)
195/// - `boot_info`: higher-half VA (or low VA if you pass identity-mapped pointer)
196/// - `tramp_stack_top`: VA of the top of the trampoline stack (identity-mapped in both maps)
197#[inline(never)]
198unsafe fn switch_to_kernel(
199    pml4_phys: PageTablePhysicalAddress,
200    kernel_entry_va: KernelVirtualAddress,
201    boot_info_ptr_va: BootInfoVirtualAddress,
202    tramp_stack_top_va: TrampolineStackVirtualAddress,
203) -> ! {
204    qemu_trace!("UEFI is about to jump into Kernel land. Ciao Kakao ...\n");
205    unsafe {
206        core::arch::asm!(
207            "cli",
208            // Set up stack pointer
209            "mov    rsp, rdx",
210            // Set up page tables
211            "mov    cr3, rdi",
212            // Set up arguments for sysv64: rdi = boot_info, rsi = (unused), rdx = (unused)
213            "mov    rdi, r8",
214            // Set up kernel entry address in rax
215            "mov    rax, rsi",
216            // Align RSP down to 16-byte boundary
217            "and    rsp, -16",
218            // Emulate a CALL by pushing a dummy return address (kernel entry never returns)
219            "push   0",
220            "jmp    rax",
221            in("rdi") pml4_phys.as_u64(),
222            in("rsi") kernel_entry_va.as_u64(),
223            in("rdx") tramp_stack_top_va.as_u64(),
224            in("r8")  boot_info_ptr_va.as_u64(),
225            options(noreturn)
226        )
227    }
228}