Skip to main content

kernel/mem/x86_64/
vmm.rs

1// SPDX-License-Identifier: GPL-3.0-only
2//! Virtual Memory Manager (VMM) for x86_64: owns the kernel page table and maps
3//! and unmaps pages at 4 KiB granularity.
4//!
5//! Authors: MarioS271
6
7use super::vmm_helpers::*;
8use crate::{kdebug, kinfo};
9use crate::mem::pmm::Pmm;
10use x86_64::registers::control::Cr3;
11use x86_64::structures::paging::{PageTable, PageTableFlags, PageTableIndex, PhysFrame};
12use x86_64::{PhysAddr, VirtAddr};
13
14/// Kernel page-table state used by all VMM operations.
15pub struct Vmm {
16    /// Virtual address of the active PML4 (the table installed in CR3).
17    pub plm4_ptr: *mut PageTable,
18    /// Higher-Half Direct Map offset: add to any physical address to get its virtual address.
19    pub hhdm_offset: u64,
20}
21
22// Safe because Vmm is written once during init (single-threaded) and then
23// only read after that.
24unsafe impl Send for Vmm {}
25unsafe impl Sync for Vmm {}
26
27impl Vmm {
28    /// Initialize the VMM and install a kernel-owned PML4 into CR3.
29    ///
30    /// # Panics
31    /// Panics if the PMM cannot allocate the PML4 frame (out of memory).
32    pub fn init(pmm: &Pmm, hhdm_offset: u64) -> Self {
33        let limine_plm4_ptr = (Cr3::read().0.start_address().as_u64() + hhdm_offset) as *const PageTable;
34
35        let plm4_ptr = (
36            pmm.alloc().unwrap_or_else(|| out_of_memory_panic()).as_u64() + hhdm_offset
37        ) as *mut PageTable;
38
39        // Safe because the PMM gives us a valid piece of memory
40        unsafe {
41            core::ptr::write_bytes(plm4_ptr, 0x00, 1);
42        }
43
44        for entry in 256..512 {
45            // Both pointers are valid and only touched here at time of execution
46            unsafe {
47                (&mut (*plm4_ptr))[entry] = (&(*limine_plm4_ptr))[entry].clone();
48            }
49        }
50
51        let phys_addr_u64 = plm4_ptr as u64 - hhdm_offset;
52        let phys_frame = PhysFrame::containing_address(PhysAddr::new(phys_addr_u64));
53        let current_cr3_flags = Cr3::read().1;
54
55        // Using the same plm4 as provided by limine before, just copied so that the kernel
56        // is able to own it (limine's plm4 was safe and functional)
57        unsafe {
58            Cr3::write(phys_frame, current_cr3_flags);
59        }
60
61        kdebug!("[VMM] allocated frame for plm4 at phys addr {phys_addr_u64:#x}");
62        kinfo!("Initialized VMM");
63
64        Vmm { plm4_ptr, hhdm_offset }
65    }
66
67    /// Map one 4 KiB virtual page to a physical frame. `PRESENT` is forced on regardless of `flags`.
68    ///
69    /// # Safety
70    /// The caller must ensure `virt` is a valid kernel virtual address and `phys` is a
71    /// valid, PMM-allocated frame.
72    ///
73    /// # Panics
74    /// Panics if the PMM runs out of frames when allocating an intermediate page table.
75    pub unsafe fn map_page(&self, pmm: &Pmm, virt: VirtAddr, phys: PhysAddr, flags: PageTableFlags) {
76        let mut current_pagetable: *mut PageTable = self.plm4_ptr;
77        let intermediate_flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE | (flags & PageTableFlags::USER_ACCESSIBLE);
78
79        for level in (2..5).rev() {
80            let index: PageTableIndex;
81
82            match level {
83                4 => { index = virt.p4_index() }
84                3 => { index = virt.p3_index() }
85                2 => { index = virt.p2_index() }
86                _ => unreachable!()
87            }
88
89            let entry = &mut (&mut (*current_pagetable))[index];
90
91            if !entry.flags().contains(PageTableFlags::PRESENT) {
92                let frame = alloc_zeroed_frame(pmm, self.hhdm_offset);
93                entry.set_frame(frame, intermediate_flags);
94            }
95
96            current_pagetable = (entry.frame().unwrap().start_address().as_u64() + self.hhdm_offset) as *mut PageTable;
97        }
98
99        let entry = &mut (&mut (*current_pagetable))[virt.p1_index()];
100        entry.set_frame(PhysFrame::containing_address(phys), flags | PageTableFlags::PRESENT);
101    }
102
103    /// Unmap one 4 KiB virtual page and flush its TLB entry.
104    ///
105    /// # Safety
106    /// The caller must ensure `virt` was previously mapped and that no live code or
107    /// data references the page after this call returns.
108    ///
109    /// # Panics
110    /// Panics if any level of the walk is not present (page was never mapped).
111    pub unsafe fn unmap_page(&self, virt: VirtAddr) {
112        let mut current_pagetable: *mut PageTable = self.plm4_ptr;
113
114        for level in (2..5).rev() {
115            let index: PageTableIndex;
116
117            match level {
118                4 => { index = virt.p4_index() }
119                3 => { index = virt.p3_index() }
120                2 => { index = virt.p2_index() }
121                _ => unreachable!()
122            }
123
124            let entry = &mut (&mut (*current_pagetable))[index];
125
126            if !entry.flags().contains(PageTableFlags::PRESENT) {
127                invalid_unmap_panic();
128            }
129
130            current_pagetable = (entry.frame().unwrap().start_address().as_u64() + self.hhdm_offset) as *mut PageTable;
131        }
132
133        let entry = &mut (&mut (*current_pagetable))[virt.p1_index()];
134
135        if !entry.flags().contains(PageTableFlags::PRESENT) {
136            invalid_unmap_panic();
137        }
138
139        entry.set_unused();
140        x86_64::instructions::tlb::flush(virt);
141    }
142}