Skip to content

Commit 59ea124

Browse files
committed
x86_64: support nested pages splitting
1 parent 28c7089 commit 59ea124

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::x86_64::kernel::processor;
1818
use crate::arch::x86_64::mm::{PhysAddr, VirtAddr};
19-
use crate::mm::{FrameAlloc, PageRangeAllocator};
19+
use crate::mm::{FrameAlloc, PageAlloc, PageRangeAllocator};
2020
use crate::{env, scheduler};
2121

2222
unsafe impl FrameAllocator<Size4KiB> for FrameAlloc {
@@ -173,15 +173,30 @@ pub fn map<S>(
173173
where
174174
M: Mapper<S>,
175175
S: PageSize + fmt::Debug,
176+
for<'a> OffsetPageTable<'a>: Mapper<S>,
176177
{
177178
let mut unmapped = false;
178179
for (page, frame) in pages.zip(frames) {
179180
// TODO: Require explicit unmaps
180-
let unmap = mapper.unmap(page);
181-
if let Ok((_frame, flush)) = unmap {
182-
unmapped = true;
183-
flush.flush();
184-
debug!("Had to unmap page {page:?} before mapping.");
181+
let unmap_result = mapper.unmap(page);
182+
match unmap_result {
183+
Ok((_, flush)) => {
184+
unmapped = true;
185+
flush.flush();
186+
debug!("Had to unmap page {page:?} before mapping.");
187+
}
188+
Err(UnmapError::PageNotMapped) => {
189+
// Expected case
190+
}
191+
Err(UnmapError::ParentEntryHugePage) => {
192+
// Must unmap completely
193+
unmapped = true;
194+
unmap::<S>(page.start_address().into(), 1);
195+
debug!("Had to unmap page {page:?} before mapping.");
196+
}
197+
Err(other) => {
198+
panic!("Failed to unmap page during mapping: {other:?}");
199+
}
185200
}
186201
let map = unsafe { mapper.map_to(page, frame, flags, &mut FrameAlloc) };
187202
match map {
@@ -250,6 +265,107 @@ where
250265
}
251266
}
252267

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

264-
for page in range {
380+
fn unmap_page<S>(page: Page<S>)
381+
where
382+
S: PageSize + fmt::Debug,
383+
for<'a> OffsetPageTable<'a>: Mapper<S>, {
265384
let unmap_result = unsafe { identity_mapped_page_table() }.unmap(page);
266385
match unmap_result {
267386
Ok((_frame, flush)) => flush.flush(),
@@ -270,9 +389,23 @@ where
270389
Err(UnmapError::PageNotMapped) => {
271390
debug!("Tried to unmap {page:?}, which was not mapped.");
272391
}
392+
Err(UnmapError::ParentEntryHugePage) if S::SIZE == Size4KiB::SIZE => {
393+
// Prepare new page and retry
394+
split_page(Page::<Size2MiB>::containing_address(page.start_address()));
395+
unmap_page(page);
396+
}
397+
Err(UnmapError::ParentEntryHugePage) if S::SIZE == Size2MiB::SIZE => {
398+
// Prepare new page and retry
399+
split_page(Page::<Size1GiB>::containing_address(page.start_address()));
400+
unmap_page(page);
401+
}
273402
Err(err) => panic!("{err:?}"),
274403
}
275404
}
405+
406+
for page in range {
407+
unmap_page(page);
408+
}
276409
}
277410

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

0 commit comments

Comments
 (0)