kernel_vmem/lib.rs
1//! # Virtual Memory Support
2//!
3//! Minimal x86-64 paging helpers for a hobby OS loader/kernel.
4//!
5//! ## What you get
6//! - An [`address space`](address_space) describing a `PML4` root page table.
7//! - Tiny [`PhysicalAddress`]/[`VirtualAddress`](addresses::VirtualAddress) newtypes (u64) to avoid mixing address kinds.
8//! - A [`PageSize`](addresses::PageSize) enum for 4 KiB / 2 MiB / 1 GiB mappings.
9//! - x86-64 page-table [`VirtualMemoryPageBits`] with practical explanations.
10//! - A 4 KiB-aligned [`PageTable`] wrapper and index helpers.
11//! - A tiny allocator/mapper interface ([`FrameAlloc`], [`PhysMapper`]).
12//!
13//! ## x86-64 Virtual Address → Physical Address Walk
14//!
15//! Each 48-bit virtual address is divided into five fields:
16//!
17//! ```text
18//! | 47‒39 | 38‒30 | 29‒21 | 20‒12 | 11‒0 |
19//! | PML4 | PDPT | PD | PT | Offset |
20//! ```
21//!
22//! The CPU uses these fields as **indices** into four levels of page tables,
23//! each level containing 512 (2⁹) entries of 8 bytes (64 bits) each.
24//!
25//! ```text
26//! PML4 → PDPT → PD → PT → Physical Page
27//! │ │ │ │
28//! │ │ │ └───► PTE (Page Table Entry) → maps 4 KiB page
29//! │ │ └────────────► PDE (Page Directory Entry) → PS=1 → 2 MiB page
30//! │ └─────────────────────► PDPTE (Page Directory Pointer Table Entry) → PS=1 → 1 GiB page
31//! └──────────────────────────────► PML4E (Page Map Level 4 Entry)
32//! ```
33//!
34//! ### Levels and their roles
35//!
36//! | Level | Table name | Entry name | Description |
37//! |:------|:------------|:-----------|:-------------|
38//! | 1 | **PML4** (Page Map Level 4) | **PML4E** | Top-level table; each entry points to a PDPT. One PML4 table per address space, referenced by Control Register 3 ([`CR3`](https://wiki.osdev.org/CPU_Registers_x86#CR3)). |
39//! | 2 | **PDPT** (Page Directory Pointer Table) | **PDPTE** | Each entry points to a PD. If `PS=1`, it directly maps a 1 GiB page (leaf). |
40//! | 3 | **PD** (Page Directory) | **PDE** | Each entry points to a PT. If `PS=1`, it directly maps a 2 MiB page (leaf). |
41//! | 4 | **PT** (Page Table) | **PTE** | Each entry maps a 4 KiB physical page (always a leaf). |
42//!
43//! ### Leaf vs. non-leaf entries
44//!
45//! - A **leaf entry** directly maps physical memory — it contains the physical base address
46//! and the permission bits ([`PRESENT`](VirtualMemoryPageBits::with_present), [`WRITABLE`](VirtualMemoryPageBits::with_writable), [`USER`](VirtualMemoryPageBits::with_user), [`GLOBAL`](VirtualMemoryPageBits::with_global), [`NX`](VirtualMemoryPageBits::with_no_execute), etc.).
47//! - A **PTE** is always a leaf (maps 4 KiB).
48//! - A **PDE** with `PS=1` is a leaf (maps 2 MiB).
49//! - A **PDPTE** with `PS=1` is a leaf (maps 1 GiB).
50//!
51//! - A **non-leaf entry** points to the next lower table level and continues the walk.
52//! For example, a PML4E points to a PDPT, and a PDE with `PS=0` points to a PT.
53//!
54//! ### Offset
55//!
56//! - The final **Offset** field (bits 11–0) selects the byte inside the 4 KiB (or larger) page.
57//!
58//! ### Summary
59//!
60//! A canonical 48-bit virtual address is effectively:
61//!
62//! ```text
63//! VA = [PML4:9] [PDPT:9] [PD:9] [PT:9] [Offset:12]
64//! ```
65//!
66//! This creates a four-level translation tree that can map up to **256 TiB** of
67//! virtual address space, using leaf pages of 1 GiB, 2 MiB, or 4 KiB depending
68//! on which level the translation stops.
69
70#![cfg_attr(not(test), no_std)]
71#![allow(unsafe_code, clippy::inline_always)]
72
73pub mod address_space;
74pub mod addresses;
75mod bits;
76pub mod page_table;
77
78pub use crate::address_space::AddressSpace;
79use crate::addresses::{PhysicalAddress, PhysicalPage, Size4K};
80pub use crate::bits::VirtualMemoryPageBits;
81use crate::page_table::pd::PageDirectory;
82use crate::page_table::pdpt::PageDirectoryPointerTable;
83use crate::page_table::pml4::PageMapLevel4;
84use crate::page_table::pt::PageTable;
85/// Re-export constants as info module.
86pub use kernel_info::memory as info;
87
88/// Minimal allocator that hands out **4 KiB** page-table frames.
89pub trait FrameAlloc {
90 /// Allocate a zeroed 4 KiB page suitable for a page-table.
91 fn alloc_4k(&mut self) -> Option<PhysicalPage<Size4K>>;
92
93 /// Deallocate a zeroed 4 KiB page suitable for a page-table.
94 fn free_4k(&mut self, pa: PhysicalPage<Size4K>);
95}
96
97/// Mapper capable of temporarily viewing physical frames as typed tables.
98pub trait PhysMapper {
99 /// Map a 4 KiB physical frame and get a **mutable** reference to type `T`.
100 ///
101 /// # Safety
102 /// The implementation must ensure that the returned reference aliases the
103 /// mapped frame, and that writes reach memory.
104 #[allow(clippy::mut_from_ref)]
105 unsafe fn phys_to_mut<T>(&self, at: PhysicalAddress) -> &mut T;
106}
107
108/// Mapper capable of temporarily viewing physical frames as typed tables.
109pub trait PhysMapperExt: PhysMapper {
110 /// Borrow the [`PageMapLevel4`] (PML4) located in the given 4 KiB
111 /// physical frame.
112 ///
113 /// # Arguments
114 /// * `page` - The `page` parameter identifies the physical frame whose contents
115 /// are interpreted as a PML4 table. The frame must contain either a
116 /// valid or zero-initialized PML4.
117 #[inline]
118 #[allow(clippy::mut_from_ref)]
119 fn pml4_mut(&self, page: PhysicalPage<Size4K>) -> &mut PageMapLevel4 {
120 // SAFETY: 4 KiB alignment guaranteed by `PhysicalPage<Size4K>`.
121 unsafe { self.phys_to_mut::<PageMapLevel4>(page.base()) }
122 }
123
124 /// Sets the provided [`PageMapLevel4`] at the specified `page`.
125 #[inline]
126 fn set_pml4(&self, page: PhysicalPage<Size4K>, pml4: PageMapLevel4) {
127 *self.pml4_mut(page) = pml4;
128 }
129
130 /// Sets the provided [`PageMapLevel4`] at the specified `page`.
131 #[inline]
132 fn zero_pml4(&self, page: PhysicalPage<Size4K>) {
133 self.set_pml4(page, PageMapLevel4::zeroed());
134 }
135
136 /// Borrow a [`PageDirectoryPointerTable`] (PDPT) located in the given 4 KiB
137 /// physical frame.
138 ///
139 /// # Arguments
140 /// * `page` - The `page` parameter identifies the physical frame whose contents
141 /// are interpreted as a PDP table. The frame must contain either a
142 /// valid or zero-initialized PDPT.
143 #[inline]
144 #[allow(clippy::mut_from_ref)]
145 fn pdpt_mut(&self, page: PhysicalPage<Size4K>) -> &mut PageDirectoryPointerTable {
146 // SAFETY: 4 KiB alignment guaranteed by `PhysicalPage<Size4K>`.
147 unsafe { self.phys_to_mut::<PageDirectoryPointerTable>(page.base()) }
148 }
149
150 /// Sets the provided [`PageDirectoryPointerTable`] at the specified `page`.
151 #[inline]
152 fn set_pdpt(&self, page: PhysicalPage<Size4K>, pml4: PageDirectoryPointerTable) {
153 *self.pdpt_mut(page) = pml4;
154 }
155
156 /// Sets the provided [`PageDirectoryPointerTable`] at the specified `page`.
157 #[inline]
158 fn zero_pdpt(&self, page: PhysicalPage<Size4K>) {
159 self.set_pdpt(page, PageDirectoryPointerTable::zeroed());
160 }
161
162 /// Borrow a [`PageDirectory`] (PD) located in the given 4 KiB
163 /// physical frame.
164 ///
165 /// # Arguments
166 /// * `page` - The `page` parameter identifies the physical frame whose contents
167 /// are interpreted as a PD table. The frame must contain either a
168 /// valid or zero-initialized PD.
169 #[inline]
170 #[allow(clippy::mut_from_ref)]
171 fn pd_mut(&self, page: PhysicalPage<Size4K>) -> &mut PageDirectory {
172 // SAFETY: 4 KiB alignment guaranteed by `PhysicalPage<Size4K>`.
173 unsafe { self.phys_to_mut::<PageDirectory>(page.base()) }
174 }
175
176 /// Sets the provided [`PageDirectory`] at the specified `page`.
177 #[inline]
178 fn set_pd(&self, page: PhysicalPage<Size4K>, pml4: PageDirectory) {
179 *self.pd_mut(page) = pml4;
180 }
181
182 /// Sets the provided [`PageDirectory`] at the specified `page`.
183 #[inline]
184 fn zero_pd(&self, page: PhysicalPage<Size4K>) {
185 self.set_pd(page, PageDirectory::zeroed());
186 }
187
188 /// Borrow a [`PageTable`] (PT) located in the given 4 KiB
189 /// physical frame.
190 ///
191 /// # Arguments
192 /// * `page` - The `page` parameter identifies the physical frame whose contents
193 /// are interpreted as a PT. The frame must contain either a
194 /// valid or zero-initialized PT.
195 #[inline]
196 #[allow(clippy::mut_from_ref)]
197 fn pt_mut(&self, page: PhysicalPage<Size4K>) -> &mut PageTable {
198 // SAFETY: 4 KiB alignment guaranteed by `PhysicalPage<Size4K>`.
199 unsafe { self.phys_to_mut::<PageTable>(page.base()) }
200 }
201
202 /// Sets the provided [`PageTable`] at the specified `page`.
203 #[inline]
204 fn set_pt(&self, page: PhysicalPage<Size4K>, pml4: PageTable) {
205 *self.pt_mut(page) = pml4;
206 }
207
208 /// Sets the provided [`PageTable`] at the specified `page`.
209 #[inline]
210 fn zero_pt(&self, page: PhysicalPage<Size4K>) {
211 self.set_pt(page, PageTable::zeroed());
212 }
213}
214
215impl<T> PhysMapperExt for T where T: PhysMapper {}
216
217/// Reads the current value of the **CR3 register** (the page table base register)
218/// and returns the physical address of the top-level page table (PML4).
219///
220/// # Safety
221/// This function is **unsafe** because it directly accesses a CPU control register.
222/// It must only be called in privileged (ring 0) code where paging is active and
223/// the CR3 contents are valid. Calling it from user mode or before enabling paging
224/// will cause undefined behavior.
225///
226/// # Details
227/// - On x86-64, CR3 holds the **physical base address** of the currently active
228/// PML4 (Page Map Level 4) table.
229/// - The low 12 bits of CR3 contain **flags** (e.g., PCID, reserved bits),
230/// so this function masks them out to obtain a 4 KiB-aligned physical address.
231/// - The returned address represents the root of the current virtual memory
232/// hierarchy used for address translation.
233///
234/// # Returns
235/// The 4 KiB-aligned [`PhysicalAddress`] of the current PML4 table.
236///
237/// # Example
238/// ```no_run
239/// use kernel_vmem::read_cr3_phys;
240///
241/// // SAFETY: Only call from kernel mode with paging enabled.
242/// let current_pml4 = unsafe { read_cr3_phys() };
243/// println!("Active PML4 base: {:#x}", current_pml4.as_u64());
244/// ```
245#[allow(clippy::inline_always)]
246#[inline(always)]
247#[must_use]
248pub unsafe fn read_cr3_phys() -> PhysicalAddress {
249 let mut cr3: u64;
250 unsafe {
251 core::arch::asm!("mov {}, cr3", out(reg) cr3, options(nomem, nostack, preserves_flags));
252 }
253
254 // In 4- and 5-level paging, CR3[51:12] is the base. Upper bits should be zero.
255 debug_assert_eq!(cr3 >> 52, 0, "CR3 has nonzero high bits: {cr3:#018x}");
256
257 // Clear PCID / low bits by turning it into a 4K page base and back to an address.
258 let page = PhysicalAddress::from(cr3).page::<Size4K>(); // drop low 12 bits
259 PhysicalAddress::from(page)
260}