Skip to main content

kernel/arch/x86_64/mm/vmm/paging/
setup_kernel_paging.rs

1// SPDX-License-Identifier: GPL-3.0-only
2//! VMM Paging (x86_64): `setup_kernel_paging`
3//!
4//! Authors: MarioS271
5
6use crate::lib::addr::{PhysAddr, VirtAddr};
7use crate::lib::panic::kernel_panic;
8use crate::lib::panic_codes::PanicCode;
9use crate::lib::types::boot_info::KernelSectionInfo;
10use crate::mm::layout;
11use crate::mm::pmm::{Pmm, FRAME_SIZE};
12use crate::mm::vmm::boot_mapping::{build_boot_mappings, BootMappings};
13use crate::mm::vmm::traits::VmmPaging;
14use crate::mm::vmm::Vmm;
15use crate::state::kstate::KSTATE;
16use crate::kinfo;
17use x86_64::registers::control::{Cr3, Efer, EferFlags};
18use x86_64::structures::paging::{PageTable, PageTableFlags, PhysFrame};
19
20// TODO: add KASLR
21
22/// Build the kernels page tables and switch to them
23pub fn setup_kernel_paging(sections: &KernelSectionInfo) -> (VirtAddr, BootMappings) {
24    // Safety: NXE is supported on all long mode capable CPUs
25    unsafe {
26        Efer::update(|efer| efer.insert(EferFlags::NO_EXECUTE_ENABLE));
27    }
28
29    let mut pmm = unsafe { KSTATE.mm.pmm().lock() };
30
31    let pml4_frame = pmm.alloc_frame_zeroed().unwrap_or_else(
32        || kernel_panic(
33            PanicCode::OutOfMemory,
34            "Could not allocate a PML4 for the kernel, out of memory"
35        )
36    );
37
38    let page_ptr = VirtAddr::from_phys(pml4_frame);
39    let total_mem = pmm.get_total_mem();
40
41    let boot_mappings = build_boot_mappings(sections, total_mem);
42
43    for mapping in &boot_mappings {
44        // Safety: page_ptr is a freshly allocated frame and the phys ranges come from the
45        // memorymap and the loaded kernel image where nothing is mapped yet
46        unsafe {
47            Vmm::map_range(
48                &mut pmm,
49                page_ptr,
50                mapping.virt,
51                mapping.phys,
52                mapping.size,
53                Vmm::vma_flags_to_page_flags(mapping.flags),
54                mapping.can_overmap
55            ).unwrap_or_else(
56                |_| kernel_panic(
57                    PanicCode::OutOfMemory,
58                    "Could not build the kernel page tables, out of memory"
59                )
60            );
61        }
62    }
63
64    create_zeroed_pdpts(&mut pmm, page_ptr);
65    verify_against_bootloader(page_ptr, sections);
66
67    // Safety: everything the kernel is currently executing and/or working with has been mapped and
68    // verified in the new pages and will resolve the same as before
69    unsafe {
70        Cr3::write(
71            PhysFrame::containing_address(pml4_frame.as_x86_64()),
72            Cr3::read().1
73        );
74    }
75
76    kinfo!("Initialized kernel paging (PML4 is at phys {:#x})", pml4_frame.as_u64());
77
78    (page_ptr, boot_mappings)
79}
80
81/// Prepopulate the higher half with empty PDPTs so that if the kernel PML4 higher half ever gets
82/// cloned (like when creating processes), no stale higher halfs exist that aren't synced with the
83/// kernel's (which could cause page faults when switching into the kernel with user pages)
84///
85/// TODO: only create a zeroed pdpt for areas where we actually need them
86fn create_zeroed_pdpts(pmm: &mut Pmm, page_ptr: VirtAddr) {
87    // Safety: page_ptr points to a newly allocated and zeroed frame that nothing else currently
88    // references
89    let pml4 = unsafe { &mut *page_ptr.as_mut_ptr::<PageTable>() };
90
91    for index in 256..512 {
92        let entry = &mut (*pml4)[index];
93
94        if !entry.is_unused() {
95            continue;
96        }
97
98        let pdpt_frame = pmm.alloc_frame_zeroed().unwrap_or_else(
99            || kernel_panic(
100                PanicCode::OutOfMemory,
101                "Could not allocate a PDPT while prepopulating the kernel PML4, out of memory"
102            )
103        );
104
105        entry.set_addr(
106            pdpt_frame.as_x86_64(),
107            PageTableFlags::PRESENT | PageTableFlags::WRITABLE
108        );
109    }
110}
111
112/// Verify whether the newly constructed page tables match what the bootloader gave us in critical
113/// sections, and panic if they don't to avoid a page fault/triple fault
114fn verify_against_bootloader(page_ptr: VirtAddr, sections: &KernelSectionInfo) {
115    let old_page_ptr = VirtAddr::from_phys(
116        PhysAddr::new(Cr3::read().0.start_address().as_u64())
117    );
118
119    let rsp: u64;
120    unsafe { core::arch::asm!("mov {}, rsp", out(reg) rsp, options(nostack, nomem)) };
121
122    let probes = [
123        rsp,
124        sections.kernel_start,
125        sections.kernel_text_end - 1,
126        sections.kernel_rodata_end - 1,
127        sections.kernel_end - 1,
128        layout::KERNEL_HHDM_BASE.as_u64() + FRAME_SIZE
129    ];
130
131    for probe in probes {
132        let old = Vmm::translate(old_page_ptr, VirtAddr::new(probe));
133        let new = Vmm::translate(page_ptr, VirtAddr::new(probe));
134
135        if old != new {
136            kernel_panic(
137                PanicCode::InitFailure,
138                "New kernel-owned page tables do not match with the bootloader's, which would triple-fault on CR3 switch"
139            );
140        }
141    }
142}