@@ -10,11 +10,11 @@ use bootloader_api::{
10
10
} ;
11
11
use core:: { alloc:: Layout , arch:: asm, mem:: MaybeUninit , slice} ;
12
12
use level_4_entries:: UsedLevel4Entries ;
13
- use usize_conversions:: FromUsize ;
13
+ use usize_conversions:: { FromUsize , IntoUsize } ;
14
14
use x86_64:: {
15
15
structures:: paging:: {
16
16
page_table:: PageTableLevel , FrameAllocator , Mapper , OffsetPageTable , Page , PageSize ,
17
- PageTableFlags , PageTableIndex , PhysFrame , Size2MiB , Size4KiB ,
17
+ PageTable , PageTableFlags , PageTableIndex , PhysFrame , Size2MiB , Size4KiB ,
18
18
} ,
19
19
PhysAddr , VirtAddr ,
20
20
} ;
@@ -145,6 +145,7 @@ where
145
145
I : ExactSizeIterator < Item = D > + Clone ,
146
146
D : LegacyMemoryRegion ,
147
147
{
148
+ let bootloader_page_table = & mut page_tables. bootloader ;
148
149
let kernel_page_table = & mut page_tables. kernel ;
149
150
150
151
let mut used_entries = UsedLevel4Entries :: new (
@@ -195,23 +196,6 @@ where
195
196
}
196
197
}
197
198
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
-
215
199
// create, load, and identity-map GDT (required for working `iretq`)
216
200
let gdt_frame = frame_allocator
217
201
. allocate_frame ( )
@@ -319,6 +303,151 @@ where
319
303
None
320
304
} ;
321
305
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
+
322
451
Mappings {
323
452
framebuffer : framebuffer_virt_addr,
324
453
entry_point,
@@ -330,6 +459,10 @@ where
330
459
331
460
kernel_slice_start,
332
461
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,
333
466
}
334
467
}
335
468
@@ -355,6 +488,14 @@ pub struct Mappings {
355
488
pub kernel_slice_start : u64 ,
356
489
/// Size of the kernel slice allocation in memory.
357
490
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 ,
358
499
}
359
500
360
501
/// Allocates and initializes the boot info struct and the memory map.
@@ -470,15 +611,17 @@ pub fn switch_to_kernel(
470
611
..
471
612
} = page_tables;
472
613
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 ,
473
617
page_table : kernel_level_4_frame,
474
618
stack_top : mappings. stack_end . start_address ( ) ,
475
- entry_point : mappings. entry_point ,
476
619
boot_info,
477
620
} ;
478
621
479
622
log:: info!(
480
- "Jumping to kernel entry point at {:?}" ,
481
- addresses . entry_point
623
+ "Switching to kernel entry point at {:?}" ,
624
+ mappings . entry_point
482
625
) ;
483
626
484
627
unsafe {
@@ -504,21 +647,25 @@ pub struct PageTables {
504
647
unsafe fn context_switch ( addresses : Addresses ) -> ! {
505
648
unsafe {
506
649
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 {}" ,
509
651
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( ) ,
511
656
in( "rdi" ) addresses. boot_info as * const _ as usize ,
657
+ options( noreturn) ,
512
658
) ;
513
659
}
514
- unreachable ! ( ) ;
515
660
}
516
661
517
662
/// Memory addresses required for the context switch.
518
663
struct Addresses {
664
+ context_switch_trampoline : VirtAddr ,
665
+ context_switch_page_table : PhysFrame ,
666
+ context_switch_addr : VirtAddr ,
519
667
page_table : PhysFrame ,
520
668
stack_top : VirtAddr ,
521
- entry_point : VirtAddr ,
522
669
boot_info : & ' static mut BootInfo ,
523
670
}
524
671
0 commit comments