@@ -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 {
355488pub  kernel_slice_start :  u64 , 
356489    /// Size of the kernel slice allocation in memory. 
357490pub  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