1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
|
// ---------------------------------------------------------------------------
// Identity page-table helpers — 4-level paging (x86_64).
//
// The kernel keeps an identity-mapped 4-level tree (no higher half). At
// boot it runs on UEFI's tables; `adopt_tables()` rebuilds that map into
// our own tables (UEFI maps its own table pages read-only, so the firmware
// tree cannot be modified in place) and switches CR3. After that the
// helpers walk our tree to map / unmap / query individual 4 KB pages 1:1.
// Page-table pages come from the physical page allocator. Firmware may
// have used 2 MB / 1 GB large pages for RAM, so the walk splits large pages
// into 4 KB tables on demand. Split tables stay allocated afterwards (no
// refcounting) — fine for a kernel that owns all memory.
// ---------------------------------------------------------------------------
use crate::memory::{alloc_pages, free_pages, PAGE_SIZE};
pub const PAGE_PRESENT: u64 = 1 << 0;
pub const PAGE_WRITABLE: u64 = 1 << 1;
pub const PAGE_USER: u64 = 1 << 2;
pub const PAGE_PS: u64 = 1 << 7; // large page (2 MB PDE, 1 GB PDPTE)
pub const PAGE_NX: u64 = 1 << 63;
/// Physical address bits in a page-table entry.
const PHYS_ADDR_MASK: u64 = 0x000F_FFFF_FFFF_F000;
const PAGE_2M: usize = 1 << 21;
const PAGE_1G: usize = 1 << 30;
/// Current PML4 root (physical address, identity-mapped).
pub fn cr3() -> usize {
let val: usize;
unsafe {
core::arch::asm!("mov {0}, cr3", out(reg) val, options(nostack, preserves_flags));
}
val
}
/// Full TLB flush by reloading CR3 (slow but bulletproof; map/unmap are
/// rare operations in this kernel).
fn flush_tlb_all() {
unsafe {
let cr3: usize;
core::arch::asm!("mov {0}, cr3", out(reg) cr3, options(nostack, preserves_flags));
core::arch::asm!("mov cr3, {0}", in(reg) cr3, options(nostack, preserves_flags));
}
}
/// Rebuild the identity map into our own page tables and switch CR3.
///
/// UEFI maps its own page-table pages read-only (R/W clear), so we cannot
/// modify the firmware tree in place: map/unmap would #PF. Instead, walk
/// the current tables, replicate every present leaf (4 KB / 2 MB / 1 GB)
/// into fresh tables from the page allocator, force R/W so the tree stays
/// editable, then load the new PML4 into CR3.
pub fn adopt_tables() -> bool {
unsafe {
let old_pml4 = cr3() as *const u64;
let Some(new_pml4) = alloc_pages(1) else {
return false;
};
core::ptr::write_bytes(new_pml4 as *mut u8, 0, PAGE_SIZE);
let new_pml4 = new_pml4 as *mut u64;
for l4 in 0..512 {
let l4e = old_pml4.add(l4).read_volatile();
if l4e & PAGE_PRESENT == 0 {
continue;
}
// Replicate this PDPT into a fresh one.
let old_pdpt = (l4e & PHYS_ADDR_MASK) as *const u64;
let Some(new_pdpt) = alloc_pages(1) else {
return false;
};
core::ptr::write_bytes(new_pdpt as *mut u8, 0, PAGE_SIZE);
let new_pdpt = new_pdpt as *mut u64;
*new_pml4.add(l4) =
(new_pdpt as u64) | PAGE_PRESENT | PAGE_WRITABLE | (l4e & PAGE_USER);
for l3 in 0..512 {
let pdpte = old_pdpt.add(l3).read_volatile();
if pdpte & PAGE_PRESENT == 0 {
continue;
}
if pdpte & PAGE_PS != 0 {
// 1 GB large page — copy the leaf, force writable.
*new_pdpt.add(l3) = pdpte | PAGE_WRITABLE;
continue;
}
let old_pd = (pdpte & PHYS_ADDR_MASK) as *const u64;
let Some(new_pd) = alloc_pages(1) else {
return false;
};
core::ptr::write_bytes(new_pd as *mut u8, 0, PAGE_SIZE);
let new_pd = new_pd as *mut u64;
*new_pdpt.add(l3) =
(new_pd as u64) | PAGE_PRESENT | PAGE_WRITABLE | (pdpte & PAGE_USER);
for l2 in 0..512 {
let pde = old_pd.add(l2).read_volatile();
if pde & PAGE_PRESENT == 0 {
continue;
}
if pde & PAGE_PS != 0 {
// 2 MB large page — copy the leaf, force writable.
*new_pd.add(l2) = pde | PAGE_WRITABLE;
continue;
}
let old_pt = (pde & PHYS_ADDR_MASK) as *const u64;
let Some(new_pt) = alloc_pages(1) else {
return false;
};
core::ptr::write_bytes(new_pt as *mut u8, 0, PAGE_SIZE);
let new_pt = new_pt as *mut u64;
*new_pd.add(l2) =
(new_pt as u64) | PAGE_PRESENT | PAGE_WRITABLE | (pde & PAGE_USER);
for l1 in 0..512 {
let pte = old_pt.add(l1).read_volatile();
if pte & PAGE_PRESENT != 0 {
*new_pt.add(l1) = pte | PAGE_WRITABLE;
}
}
}
}
}
// Switch to the new tables (CR3 reload flushes the TLB).
let new_cr3 = new_pml4 as usize;
core::arch::asm!("mov cr3, {0}", in(reg) new_cr3, options(nostack, preserves_flags));
true
}
}
/// Ensure the PDPT entry points at a page-directory table, splitting a
/// 1 GB large page at `base_1g` if present. Returns the PD pointer.
unsafe fn ensure_pd(pdpte: &mut u64, base_1g: usize) -> Option<*mut u64> {
unsafe {
if *pdpte & PAGE_PRESENT == 0 {
let page = alloc_pages(1)? as *mut u64;
core::ptr::write_bytes(page as *mut u8, 0, PAGE_SIZE);
*pdpte = (page as u64) | PAGE_PRESENT | PAGE_WRITABLE;
return Some(page);
}
if *pdpte & PAGE_PS != 0 {
// Split the 1 GB page into 512 × 2 MB pages.
let pd = alloc_pages(1)? as *mut u64;
core::ptr::write_bytes(pd as *mut u8, 0, PAGE_SIZE);
let inherit = *pdpte & (PAGE_USER | PAGE_NX);
for i in 0..512 {
let p = base_1g + i * PAGE_2M;
*pd.add(i) = (p as u64) | PAGE_PRESENT | PAGE_WRITABLE | PAGE_PS | inherit;
}
*pdpte = (pd as u64) | PAGE_PRESENT | PAGE_WRITABLE | (*pdpte & PAGE_USER);
return Some(pd);
}
Some(((*pdpte & PHYS_ADDR_MASK) as usize) as *mut u64)
}
}
/// Ensure the PD entry points at a page table, splitting a 2 MB large page
/// at `base_2m` if present. Returns the PT pointer.
unsafe fn ensure_pt(pde: &mut u64, base_2m: usize) -> Option<*mut u64> {
unsafe {
if *pde & PAGE_PRESENT == 0 {
let page = alloc_pages(1)? as *mut u64;
core::ptr::write_bytes(page as *mut u8, 0, PAGE_SIZE);
*pde = (page as u64) | PAGE_PRESENT | PAGE_WRITABLE;
return Some(page);
}
if *pde & PAGE_PS != 0 {
// Split the 2 MB page into 512 × 4 KB pages.
let pt = alloc_pages(1)? as *mut u64;
core::ptr::write_bytes(pt as *mut u8, 0, PAGE_SIZE);
let inherit = *pde & (PAGE_USER | PAGE_NX);
for i in 0..512 {
let p = base_2m + i * PAGE_SIZE;
*pt.add(i) = (p as u64) | PAGE_PRESENT | PAGE_WRITABLE | inherit;
}
*pde = (pt as u64) | PAGE_PRESENT | PAGE_WRITABLE | (*pde & PAGE_USER);
return Some(pt);
}
Some(((*pde & PHYS_ADDR_MASK) as usize) as *mut u64)
}
}
/// Identity-map the physical page at `phys` (must be 4 KB aligned).
/// `flags` adds to PRESENT (e.g. PAGE_WRITABLE, PAGE_USER).
pub fn map_page(phys: usize, flags: u64) -> bool {
if !phys.is_multiple_of(PAGE_SIZE) {
return false;
}
unsafe {
let pml4 = cr3() as *mut u64;
let l4 = (phys >> 39) & 0x1FF;
let l3 = (phys >> 30) & 0x1FF;
let l2 = (phys >> 21) & 0x1FF;
let l1 = (phys >> 12) & 0x1FF;
let pml4e = &mut *pml4.add(l4);
if *pml4e & PAGE_PRESENT == 0 {
let Some(page) = alloc_pages(1) else {
return false;
};
core::ptr::write_bytes(page as *mut u8, 0, PAGE_SIZE);
*pml4e = (page as u64) | PAGE_PRESENT | PAGE_WRITABLE;
}
// PML4 entries are never large pages.
let base_1g = phys & !(PAGE_1G - 1);
let pdpte = &mut *(((*pml4e & PHYS_ADDR_MASK) as usize) as *mut u64).add(l3);
let Some(pd) = ensure_pd(pdpte, base_1g) else {
return false;
};
let base_2m = phys & !(PAGE_2M - 1);
let pde = &mut *pd.add(l2);
let Some(pt) = ensure_pt(pde, base_2m) else {
return false;
};
let pte = &mut *pt.add(l1);
*pte = (phys as u64) | flags | PAGE_PRESENT;
flush_tlb_all();
true
}
}
/// Identity-map `count` consecutive pages starting at `phys`.
pub fn map_range(phys: usize, count: usize, flags: u64) -> bool {
for i in 0..count {
if !map_page(phys + i * PAGE_SIZE, flags) {
return false;
}
}
true
}
/// Unmap the 4 KB page at `phys`. Splits large pages as needed so only
/// this one page is removed. Returns false if it wasn't mapped.
pub fn unmap_page(phys: usize) -> bool {
if !phys.is_multiple_of(PAGE_SIZE) {
return false;
}
unsafe {
let pml4 = cr3() as *mut u64;
let l4 = (phys >> 39) & 0x1FF;
let l3 = (phys >> 30) & 0x1FF;
let l2 = (phys >> 21) & 0x1FF;
let l1 = (phys >> 12) & 0x1FF;
let pml4e = &mut *pml4.add(l4);
if *pml4e & PAGE_PRESENT == 0 {
return false;
}
let base_1g = phys & !(PAGE_1G - 1);
let pdpte = &mut *(((*pml4e & PHYS_ADDR_MASK) as usize) as *mut u64).add(l3);
if *pdpte & PAGE_PRESENT == 0 {
return false;
}
let Some(pd) = ensure_pd(pdpte, base_1g) else {
return false;
};
let base_2m = phys & !(PAGE_2M - 1);
let pde = &mut *pd.add(l2);
if *pde & PAGE_PRESENT == 0 {
return false;
}
let Some(pt) = ensure_pt(pde, base_2m) else {
return false;
};
let pte = &mut *pt.add(l1);
if *pte & PAGE_PRESENT == 0 {
return false;
}
*pte = 0;
flush_tlb_all();
true
}
}
/// Is the page at `phys` mapped (present at any level, incl. large pages)?
pub fn is_mapped(phys: usize) -> bool {
unsafe {
let pml4 = cr3() as *const u64;
let l4 = (phys >> 39) & 0x1FF;
let l3 = (phys >> 30) & 0x1FF;
let l2 = (phys >> 21) & 0x1FF;
let l1 = (phys >> 12) & 0x1FF;
let pml4e = *pml4.add(l4);
if pml4e & PAGE_PRESENT == 0 {
return false;
}
let pdpte = *((pml4e & PHYS_ADDR_MASK) as *const u64).add(l3);
if pdpte & PAGE_PRESENT == 0 {
return false;
}
if pdpte & PAGE_PS != 0 {
return true; // 1 GB page covers phys
}
let pde = *((pdpte & PHYS_ADDR_MASK) as *const u64).add(l2);
if pde & PAGE_PRESENT == 0 {
return false;
}
if pde & PAGE_PS != 0 {
return true; // 2 MB page covers phys
}
let pte = *((pde & PHYS_ADDR_MASK) as *const u64).add(l1);
pte & PAGE_PRESENT != 0
}
}
/// Smoke test: allocate two physical pages, unmap one, verify the mapping
/// state, remap it, verify data still works, and free both.
pub fn self_test() -> bool {
let Some(p1) = alloc_pages(1) else {
return false;
};
let Some(p2) = alloc_pages(1) else {
return false;
};
// Both pages must already be identity-mapped (they're RAM).
if !is_mapped(p1) || !is_mapped(p2) {
return false;
}
// Unmap p1 — only p1 should disappear.
if !unmap_page(p1) {
return false;
}
if is_mapped(p1) {
return false;
}
if !is_mapped(p2) {
return false;
}
// Remap and verify we can write through the mapping.
if !map_page(p1, PAGE_WRITABLE) {
return false;
}
if !is_mapped(p1) {
return false;
}
unsafe {
core::ptr::write_volatile(p1 as *mut u8, 0xCD);
if core::ptr::read_volatile(p1 as *const u8) != 0xCD {
return false;
}
}
free_pages(p1, 1);
free_pages(p2, 1);
true
}
|