@@ -10,11 +10,11 @@ use bootloader_api::{
1010} ;
1111use core:: { alloc:: Layout , arch:: asm, mem:: MaybeUninit , slice} ;
1212use level_4_entries:: UsedLevel4Entries ;
13- use usize_conversions:: FromUsize ;
13+ use usize_conversions:: { FromUsize , IntoUsize } ;
1414use 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 {
504647unsafe 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.
518663struct 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