Skip to content

Commit fed860a

Browse files
committed
implement the new context switch
1 parent 29b9912 commit fed860a

File tree

3 files changed

+178
-29
lines changed

3 files changed

+178
-29
lines changed

Cargo.lock

+1-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,6 @@ pre-release-replacements = [
102102
{ file = "Changelog.md", search = "# Unreleased", replace = "# Unreleased\n\n# {{version}} – {{date}}", exactly = 1 },
103103
]
104104
pre-release-commit-message = "Release version {{version}}"
105+
106+
[patch.crates-io]
107+
x86_64 = { git = "https://github.com/Freax13/x86_64", rev = "1335841" }

common/src/lib.rs

+174-27
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ use bootloader_api::{
1010
};
1111
use core::{alloc::Layout, arch::asm, mem::MaybeUninit, slice};
1212
use level_4_entries::UsedLevel4Entries;
13-
use usize_conversions::FromUsize;
13+
use usize_conversions::{FromUsize, IntoUsize};
1414
use x86_64::{
1515
structures::paging::{
1616
page_table::PageTableLevel, FrameAllocator, Mapper, OffsetPageTable, Page, PageSize,
17-
PageTableFlags, PageTableIndex, PhysFrame, Size2MiB, Size4KiB,
17+
PageTable, PageTableFlags, PageTableIndex, PhysFrame, Size2MiB, Size4KiB,
1818
},
1919
PhysAddr, VirtAddr,
2020
};
@@ -145,6 +145,7 @@ where
145145
I: ExactSizeIterator<Item = D> + Clone,
146146
D: LegacyMemoryRegion,
147147
{
148+
let bootloader_page_table = &mut page_tables.bootloader;
148149
let kernel_page_table = &mut page_tables.kernel;
149150

150151
let mut used_entries = UsedLevel4Entries::new(
@@ -195,23 +196,6 @@ where
195196
}
196197
}
197198

198-
// identity-map context switch function, so that we don't get an immediate pagefault
199-
// after switching the active page table
200-
let context_switch_function = PhysAddr::new(context_switch as *const () as u64);
201-
let context_switch_function_start_frame: PhysFrame =
202-
PhysFrame::containing_address(context_switch_function);
203-
for frame in PhysFrame::range_inclusive(
204-
context_switch_function_start_frame,
205-
context_switch_function_start_frame + 1,
206-
) {
207-
match unsafe {
208-
kernel_page_table.identity_map(frame, PageTableFlags::PRESENT, frame_allocator)
209-
} {
210-
Ok(tlb) => tlb.flush(),
211-
Err(err) => panic!("failed to identity map frame {:?}: {:?}", frame, err),
212-
}
213-
}
214-
215199
// create, load, and identity-map GDT (required for working `iretq`)
216200
let gdt_frame = frame_allocator
217201
.allocate_frame()
@@ -319,6 +303,151 @@ where
319303
None
320304
};
321305

306+
// Setup memory for the context switch.
307+
// We set up two regions of memory:
308+
// 1. "context switch page" - This page contains only a single instruction
309+
// to switch to the kernel's page table. It's placed right before the
310+
// kernel's entrypoint, so that the last instruction the bootloader
311+
// executes is the page table switch and we don't need to jump to the
312+
// entrypoint.
313+
// 2. "trampoline" - The "context switch page" might overlap with the
314+
// bootloader's memory, so we can't map it into the bootloader's address
315+
// space. Instead we map a trampoline at an address of our choosing and
316+
// jump to it instead. The trampoline will then switch to a new page
317+
// table (context switch page table) that contains the "context switch
318+
// page" and jump to it.
319+
320+
let phys_offset = kernel_page_table.phys_offset();
321+
let translate_frame_to_virt = |frame: PhysFrame| phys_offset + frame.start_address().as_u64();
322+
323+
// The switching the page table is a 3 byte instruction.
324+
// Check that subtraction 3 from the entrypoint won't jump the gap in the address space.
325+
if (0xffff_8000_0000_0000..=0xffff_8000_0000_0002).contains(&entry_point.as_u64()) {
326+
panic!("The kernel's entrypoint must not be located between 0xffff_8000_0000_0000 and 0xffff_8000_0000_0002");
327+
}
328+
// Determine the address where we should place the page table switch instruction.
329+
let entrypoint_page: Page = Page::containing_address(entry_point);
330+
let addr_just_before_entrypoint = entry_point.as_u64().wrapping_sub(3);
331+
let context_switch_addr = VirtAddr::new(addr_just_before_entrypoint);
332+
let context_switch_page: Page = Page::containing_address(context_switch_addr);
333+
334+
// Choose the address for the trampoline. The address shouldn't overlap
335+
// with the bootloader's memory or the context switch page.
336+
let trampoline_page_candidate1: Page =
337+
Page::from_start_address(VirtAddr::new(0xffff_ffff_ffff_f000)).unwrap();
338+
let trampoline_page_candidate2: Page =
339+
Page::from_start_address(VirtAddr::new(0xffff_ffff_ffff_c000)).unwrap();
340+
let trampoline_page = if context_switch_page != trampoline_page_candidate1
341+
&& entrypoint_page != trampoline_page_candidate1
342+
{
343+
trampoline_page_candidate1
344+
} else {
345+
trampoline_page_candidate2
346+
};
347+
348+
// Prepare the trampoline.
349+
let trampoline_frame = frame_allocator
350+
.allocate_frame()
351+
.expect("Failed to allocate memory for trampoline");
352+
// Write two instructions to the trampoline:
353+
// 1. Load the context switch page table
354+
// 2. Jump to the context switch
355+
unsafe {
356+
let trampoline: *mut u8 = translate_frame_to_virt(trampoline_frame).as_mut_ptr();
357+
// mov cr3, rdx
358+
trampoline.add(0).write(0x0f);
359+
trampoline.add(1).write(0x22);
360+
trampoline.add(2).write(0xda);
361+
// jmp r13
362+
trampoline.add(3).write(0x41);
363+
trampoline.add(4).write(0xff);
364+
trampoline.add(5).write(0xe5);
365+
}
366+
367+
// Write the instruction to switch to the final kernel page table to the context switch page.
368+
let context_switch_frame = frame_allocator
369+
.allocate_frame()
370+
.expect("Failed to allocate memory for context switch page");
371+
// mov cr3, rax
372+
let instruction_bytes = [0x0f, 0x22, 0xd8];
373+
let context_switch_ptr: *mut u8 = translate_frame_to_virt(context_switch_frame).as_mut_ptr();
374+
for (i, b) in instruction_bytes.into_iter().enumerate() {
375+
// We can let the offset wrap around because we map the frame twice
376+
// if the context switch is near a page boundary.
377+
let offset = (context_switch_addr.as_u64().into_usize()).wrapping_add(i) % 4096;
378+
379+
unsafe {
380+
// Write the instruction byte.
381+
context_switch_ptr.add(offset).write(b);
382+
}
383+
}
384+
385+
// Create a new page table for use during the context switch.
386+
let context_switch_page_table_frame = frame_allocator
387+
.allocate_frame()
388+
.expect("Failed to allocate frame for context switch page table");
389+
let context_switch_page_table: &mut PageTable = {
390+
let ptr: *mut PageTable =
391+
translate_frame_to_virt(context_switch_page_table_frame).as_mut_ptr();
392+
// create a new, empty page table
393+
unsafe {
394+
ptr.write(PageTable::new());
395+
&mut *ptr
396+
}
397+
};
398+
let mut context_switch_page_table =
399+
unsafe { OffsetPageTable::new(context_switch_page_table, phys_offset) };
400+
401+
// Map the trampoline and the context switch.
402+
unsafe {
403+
// Map the trampoline page into both the bootloader's page table and
404+
// the context switch page table.
405+
bootloader_page_table
406+
.map_to(
407+
trampoline_page,
408+
trampoline_frame,
409+
PageTableFlags::PRESENT,
410+
frame_allocator,
411+
)
412+
.expect("Failed to map trampoline into main page table")
413+
.ignore();
414+
context_switch_page_table
415+
.map_to(
416+
trampoline_page,
417+
trampoline_frame,
418+
PageTableFlags::PRESENT,
419+
frame_allocator,
420+
)
421+
.expect("Failed to map trampoline into context switch page table")
422+
.ignore();
423+
424+
// Map the context switch only into the context switch page table.
425+
context_switch_page_table
426+
.map_to(
427+
context_switch_page,
428+
context_switch_frame,
429+
PageTableFlags::PRESENT,
430+
frame_allocator,
431+
)
432+
.expect("Failed to map context switch into context switch page table")
433+
.ignore();
434+
435+
// If the context switch is near a page boundary, map the entrypoint
436+
// page to the same frame in case the page table switch instruction
437+
// crosses a page boundary.
438+
if context_switch_page != entrypoint_page {
439+
context_switch_page_table
440+
.map_to(
441+
entrypoint_page,
442+
context_switch_frame,
443+
PageTableFlags::PRESENT,
444+
frame_allocator,
445+
)
446+
.expect("Failed to map context switch into context switch page table")
447+
.ignore();
448+
}
449+
}
450+
322451
Mappings {
323452
framebuffer: framebuffer_virt_addr,
324453
entry_point,
@@ -330,6 +459,10 @@ where
330459

331460
kernel_slice_start,
332461
kernel_slice_len,
462+
context_switch_trampoline: trampoline_page.start_address(),
463+
context_switch_page_table,
464+
context_switch_page_table_frame,
465+
context_switch_addr,
333466
}
334467
}
335468

@@ -355,6 +488,14 @@ pub struct Mappings {
355488
pub kernel_slice_start: u64,
356489
/// Size of the kernel slice allocation in memory.
357490
pub kernel_slice_len: u64,
491+
/// The address of the context switch trampoline in the bootloader's address space.
492+
pub context_switch_trampoline: VirtAddr,
493+
/// The page table used for context switch from the bootloader to the kernel.
494+
pub context_switch_page_table: OffsetPageTable<'static>,
495+
/// The physical frame where the level 4 page table of the context switch address space is stored.
496+
pub context_switch_page_table_frame: PhysFrame,
497+
/// Address just before the kernel's entrypoint.
498+
pub context_switch_addr: VirtAddr,
358499
}
359500

360501
/// Allocates and initializes the boot info struct and the memory map.
@@ -470,15 +611,17 @@ pub fn switch_to_kernel(
470611
..
471612
} = page_tables;
472613
let addresses = Addresses {
614+
context_switch_trampoline: mappings.context_switch_trampoline,
615+
context_switch_page_table: mappings.context_switch_page_table_frame,
616+
context_switch_addr: mappings.context_switch_addr,
473617
page_table: kernel_level_4_frame,
474618
stack_top: mappings.stack_end.start_address(),
475-
entry_point: mappings.entry_point,
476619
boot_info,
477620
};
478621

479622
log::info!(
480-
"Jumping to kernel entry point at {:?}",
481-
addresses.entry_point
623+
"Switching to kernel entry point at {:?}",
624+
mappings.entry_point
482625
);
483626

484627
unsafe {
@@ -504,21 +647,25 @@ pub struct PageTables {
504647
unsafe fn context_switch(addresses: Addresses) -> ! {
505648
unsafe {
506649
asm!(
507-
"mov cr3, {}; mov rsp, {}; push 0; jmp {}",
508-
in(reg) addresses.page_table.start_address().as_u64(),
650+
"mov rsp, {}; sub rsp, 8; jmp {}",
509651
in(reg) addresses.stack_top.as_u64(),
510-
in(reg) addresses.entry_point.as_u64(),
652+
in(reg) addresses.context_switch_trampoline.as_u64(),
653+
in("rdx") addresses.context_switch_page_table.start_address().as_u64(),
654+
in("r13") addresses.context_switch_addr.as_u64(),
655+
in("rax") addresses.page_table.start_address().as_u64(),
511656
in("rdi") addresses.boot_info as *const _ as usize,
657+
options(noreturn),
512658
);
513659
}
514-
unreachable!();
515660
}
516661

517662
/// Memory addresses required for the context switch.
518663
struct Addresses {
664+
context_switch_trampoline: VirtAddr,
665+
context_switch_page_table: PhysFrame,
666+
context_switch_addr: VirtAddr,
519667
page_table: PhysFrame,
520668
stack_top: VirtAddr,
521-
entry_point: VirtAddr,
522669
boot_info: &'static mut BootInfo,
523670
}
524671

0 commit comments

Comments
 (0)