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}