Skip to content

Commit 06003e7

Browse files
committed
x86_64: support nested pages splitting
1 parent c5e0584 commit 06003e7

1 file changed

Lines changed: 142 additions & 9 deletions

File tree

src/arch/x86_64/mm/paging.rs

Lines changed: 142 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ pub use x86_64::structures::idt::InterruptStackFrame as ExceptionStackFrame;
88
use x86_64::structures::idt::PageFaultErrorCode;
99
pub use x86_64::structures::paging::PageTableFlags as PageTableEntryFlags;
1010
use x86_64::structures::paging::frame::PhysFrameRange;
11-
use x86_64::structures::paging::mapper::{MapToError, MappedFrame, TranslateResult, UnmapError};
11+
use x86_64::structures::paging::mapper::{MapToError, MappedFrame, MapperFlush, TranslateResult, UnmapError};
1212
use x86_64::structures::paging::page::PageRange;
1313
use x86_64::structures::paging::{
14-
FrameAllocator, Mapper, OffsetPageTable, Page, PageTable, PhysFrame, Size4KiB, Translate,
14+
FrameAllocator, Mapper, OffsetPageTable, Page, PageTable, PhysFrame, Size1GiB, Size2MiB, Size4KiB, Translate,
1515
};
1616

1717
use crate::arch::kernel::processor;
1818
use crate::arch::mm::{PhysAddr, VirtAddr};
19-
use crate::mm::{FrameAlloc, PageRangeAllocator};
19+
use crate::mm::{FrameAlloc, PageAlloc, PageRangeAllocator};
2020
use crate::scheduler;
2121

2222
unsafe impl FrameAllocator<Size4KiB> for FrameAlloc {
@@ -191,15 +191,30 @@ pub fn map<S>(
191191
where
192192
M: Mapper<S>,
193193
S: PageSize + fmt::Debug,
194+
for<'a> OffsetPageTable<'a>: Mapper<S>,
194195
{
195196
let mut unmapped = false;
196197
for (page, frame) in pages.zip(frames) {
197198
// TODO: Require explicit unmaps
198-
let unmap = mapper.unmap(page);
199-
if let Ok((_frame, flush)) = unmap {
200-
unmapped = true;
201-
flush.flush();
202-
debug!("Had to unmap page {page:?} before mapping.");
199+
let unmap_result = mapper.unmap(page);
200+
match unmap_result {
201+
Ok((_, flush)) => {
202+
unmapped = true;
203+
flush.flush();
204+
debug!("Had to unmap page {page:?} before mapping.");
205+
}
206+
Err(UnmapError::PageNotMapped) => {
207+
// Expected case
208+
}
209+
Err(UnmapError::ParentEntryHugePage) => {
210+
// Must unmap completely
211+
unmapped = true;
212+
unmap::<S>(page.start_address().into(), 1);
213+
debug!("Had to unmap page {page:?} before mapping.");
214+
}
215+
Err(other) => {
216+
panic!("Failed to unmap page during mapping: {other:?}");
217+
}
203218
}
204219
let map = unsafe { mapper.map_to(page, frame, flags, &mut FrameAlloc) };
205220
match map {
@@ -268,6 +283,107 @@ where
268283
}
269284
}
270285

286+
/// Prepare a new page table
287+
fn split_page<S: PageSize>(page: Page<S>) {
288+
assert_ne!(S::SIZE, Size4KiB::SIZE, "cannot split small page");
289+
let is_huge_page = S::SIZE == Size1GiB::SIZE;
290+
291+
// Allocate new page table, map it temporarily
292+
let pt_frame: PhysFrame<Size4KiB> = FrameAlloc.allocate_frame().unwrap();
293+
let pt_page = PageAlloc::allocate(PageLayout::from_size(Size4KiB::SIZE as usize).unwrap()).unwrap();
294+
let pt_page = VirtAddr::new(pt_page.start() as u64);
295+
296+
let flags = PageTableEntryFlags::WRITABLE | PageTableEntryFlags::NO_EXECUTE | PageTableEntryFlags::PRESENT;
297+
map::<Size4KiB>(pt_page, pt_frame.start_address().into(), 1, flags);
298+
299+
// Fill it with entries
300+
let mut table_explorer = unsafe { identity_mapped_page_table() };
301+
let (start_addr, flags) = match table_explorer.translate(page.start_address()) {
302+
TranslateResult::Mapped { frame, flags, .. } => {
303+
let start_addr = match frame {
304+
MappedFrame::Size2MiB(frame) if S::SIZE == Size2MiB::SIZE => {
305+
frame.start_address()
306+
}
307+
MappedFrame::Size1GiB(frame) if S::SIZE == Size1GiB::SIZE => {
308+
frame.start_address()
309+
}
310+
MappedFrame::Size1GiB(_) if S::SIZE == Size2MiB::SIZE => {
311+
// We were trying to split a large page, and we got a huge page -- we should split it
312+
// Split the parent page first, then retry
313+
split_page(Page::<Size1GiB>::containing_address(page.start_address()));
314+
return split_page(page);
315+
}
316+
other => {
317+
panic!("Unexpected frame mapping {other:?} when trying to split {page:?}")
318+
}
319+
};
320+
321+
(start_addr, flags)
322+
}
323+
TranslateResult::NotMapped => {
324+
panic!("Tried to split a page that is not mapped!")
325+
}
326+
TranslateResult::InvalidFrameAddress(addr) => {
327+
panic!("Tried to split a page that maps to invalid physical address {addr:x?}")
328+
}
329+
};
330+
331+
let flags = if is_huge_page {
332+
flags // keep the HUGE flag
333+
} else {
334+
// Remove the large page flag, because we map to 4KiB frames
335+
flags.difference(PageTableEntryFlags::HUGE_PAGE)
336+
};
337+
338+
// Build the page table!
339+
let pt = pt_page.as_mut_ptr::<PageTable>();
340+
let pt = unsafe {
341+
let pt = pt.as_mut().unwrap();
342+
pt.zero();
343+
pt
344+
};
345+
346+
let child_page_size = if is_huge_page {
347+
Size2MiB::SIZE
348+
} else {
349+
Size4KiB::SIZE
350+
};
351+
352+
for (offset, entry) in pt.iter_mut().enumerate() {
353+
let offset = (offset as u64) * child_page_size;
354+
355+
entry.set_addr(
356+
start_addr + offset,
357+
flags,
358+
);
359+
}
360+
361+
// We can now replace the entry in the page table
362+
let offset = table_explorer.phys_offset();
363+
let p4 = table_explorer.level_4_table_mut();
364+
let p3 = &mut p4[page.p4_index()];
365+
let p3 = offset + p3.addr().as_u64();
366+
let p3 = unsafe { &mut *p3.as_mut_ptr::<PageTable>() };
367+
368+
if is_huge_page {
369+
let flags = p3[page.p3_index()].flags() - PageTableEntryFlags::HUGE_PAGE;
370+
p3[page.p3_index()].set_frame(pt_frame, flags);
371+
372+
} else {
373+
let p2 = &mut p3[page.p3_index()];
374+
let p2 = offset + p2.addr().as_u64();
375+
let p2 = unsafe { &mut *p2.as_mut_ptr::<PageTable>() };
376+
377+
let flags = p2[page.start_address().p2_index()].flags() - PageTableEntryFlags::HUGE_PAGE;
378+
p2[page.start_address().p2_index()].set_frame(pt_frame, flags);
379+
}
380+
381+
// Unmap temporary pt mapping
382+
unmap::<Size4KiB>(pt_page, 1);
383+
384+
MapperFlush::new(page).flush();
385+
}
386+
271387
pub fn unmap<S>(virtual_address: VirtAddr, count: usize)
272388
where
273389
S: PageSize + fmt::Debug,
@@ -279,7 +395,10 @@ where
279395
let last_page = first_page + count as u64;
280396
let range = Page::range(first_page, last_page);
281397

282-
for page in range {
398+
fn unmap_page<S>(page: Page<S>)
399+
where
400+
S: PageSize + fmt::Debug,
401+
for<'a> OffsetPageTable<'a>: Mapper<S>, {
283402
let unmap_result = unsafe { identity_mapped_page_table() }.unmap(page);
284403
match unmap_result {
285404
Ok((_frame, flush)) => flush.flush(),
@@ -288,9 +407,23 @@ where
288407
Err(UnmapError::PageNotMapped) => {
289408
debug!("Tried to unmap {page:?}, which was not mapped.");
290409
}
410+
Err(UnmapError::ParentEntryHugePage) if S::SIZE == Size4KiB::SIZE => {
411+
// Prepare new page and retry
412+
split_page(Page::<Size2MiB>::containing_address(page.start_address()));
413+
unmap_page(page);
414+
}
415+
Err(UnmapError::ParentEntryHugePage) if S::SIZE == Size2MiB::SIZE => {
416+
// Prepare new page and retry
417+
split_page(Page::<Size1GiB>::containing_address(page.start_address()));
418+
unmap_page(page);
419+
}
291420
Err(err) => panic!("{err:?}"),
292421
}
293422
}
423+
424+
for page in range {
425+
unmap_page(page);
426+
}
294427
}
295428

296429
#[cfg(not(feature = "common-os"))]

0 commit comments

Comments
 (0)