morpheus_bootloader/boot/arch/x86_64/
handoff.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//! Kernel handoff implementations
//!
//! Based on GRUB bootloader (the industry standard):
//! EFI Handover Protocol - used by Linux kernels 2.6.30 through 6.0
//!
//! Entry: kernel_addr + handover_offset + 512 (x86_64)
//! Calling convention: handover_func(image_handle, system_table, boot_params)
//!
//! GRUB just casts to function pointer and calls - compiler handles ABI conversion!

/// x86_64 offset added to handover_offset (from Linux kernel docs)
pub const EFI_HANDOVER_OFFSET_X64: u64 = 512;

/// EFI Handover Protocol function signature
///
/// CRITICAL: Uses Microsoft x64 calling convention (Win64 ABI), NOT System V!
/// Parameters: RCX = image_handle, RDX = system_table, R8 = boot_params
///
/// GRUB uses efi_call_3 wrapper to handle calling convention
type HandoverFunc = unsafe extern "efiapi" fn(*mut (), *mut (), *mut ()) -> !;

/// Boot protocol decision logic
///
/// Based on GRUB bootloader approach:
/// - EFI handover protocol when handover_offset != 0
///   → Bootloader does NOT call ExitBootServices (kernel does it)
/// - 32-bit protected mode fallback otherwise
///   → Bootloader MUST call ExitBootServices before jumping
#[derive(Debug, Copy, Clone)]
pub enum BootPath {
    /// EFI Handover Protocol
    /// Entry: kernel_addr + handover_offset + 512
    /// Boot services MUST be active (kernel will exit them)
    EfiHandover64 { entry: u64 },

    /// Legacy: 32-bit protected mode fallback
    /// Boot services MUST be exited before calling
    ProtectedMode32 { entry: u32 },
}

impl BootPath {
    /// Determine boot path to use
    ///
    /// GRUB uses handover_offset when available, regardless of kernel version.
    /// This works for kernels 3.x through 6.0 (handover removed in 6.1).
    pub fn choose(
        handover_offset: Option<u32>,
        startup_64: u64,
        protected_mode_entry: u32,
        in_long_mode: bool,
    ) -> Self {
        if in_long_mode {
            // Use EFI handover protocol if kernel supports it
            if let Some(offset) = handover_offset {
                if offset != 0 {
                    // GRUB formula: kernel_addr + handover_offset + 512 (x86_64)
                    let entry = startup_64 + offset as u64 + EFI_HANDOVER_OFFSET_X64;
                    return BootPath::EfiHandover64 { entry };
                }
            }
        }

        // Fallback: 32-bit protected mode
        BootPath::ProtectedMode32 {
            entry: protected_mode_entry,
        }
    }

    /// Execute the handoff (does not return)
    ///
    /// GRUB approach: Cast entry to function pointer and call directly.
    /// Compiler handles System V → Win64 ABI conversion automatically!
    pub unsafe fn execute(
        self,
        boot_params: u64,
        image_handle: *mut (),
        system_table: *mut (),
    ) -> ! {
        match self {
            BootPath::EfiHandover64 { entry } => {
                // GRUB-style: cast to function pointer
                let handover: HandoverFunc = core::mem::transmute(entry);

                // Call with image_handle, system_table, boot_params (GRUB order)
                handover(image_handle, system_table, boot_params as *mut ())
            }
            BootPath::ProtectedMode32 { entry } => {
                // Drop to 32-bit protected mode
                super::transitions::drop_to_protected_mode(entry, boot_params as u32)
            }
        }
    }
}