Skip to main content

ostd/arch/x86/boot/linux_boot/
mod.rs

1// SPDX-License-Identifier: MPL-2.0
2
3//! The Linux 64-bit Boot Protocol supporting module.
4//!
5
6use linux_boot_params::{BootParams, E820Type, LINUX_BOOT_HEADER_MAGIC};
7
8use super::ToEarlyBootInfo;
9#[cfg(feature = "cvm_guest")]
10use crate::arch::init_cvm_guest;
11use crate::{
12    arch::if_tdx_enabled,
13    boot::{
14        BootloaderAcpiArg, BootloaderFramebufferArg,
15        memory_region::{MemoryRegion, MemoryRegionArray, MemoryRegionType},
16    },
17    mm::kspace::paddr_to_vaddr,
18};
19
20fn is_efi_boot(boot_params: &BootParams) -> bool {
21    const EFI32_LOADER_SIGNATURE: u32 = u32::from_le_bytes(*b"EL32");
22    const EFI64_LOADER_SIGNATURE: u32 = u32::from_le_bytes(*b"EL64");
23
24    let efi_info = boot_params.efi_info;
25    matches!(
26        efi_info.efi_loader_signature,
27        EFI32_LOADER_SIGNATURE | EFI64_LOADER_SIGNATURE
28    )
29}
30
31impl From<E820Type> for MemoryRegionType {
32    fn from(value: E820Type) -> Self {
33        match value {
34            E820Type::Ram => Self::Usable,
35            E820Type::Reserved => Self::Reserved,
36            E820Type::Acpi => Self::Reclaimable,
37            E820Type::Nvs => Self::NonVolatileSleep,
38            E820Type::Unusable => Self::BadMemory,
39            // All other memory regions are reserved.
40            // FIXME: Using Rust enum in this way can be unsound if the bootloader passes an
41            // unknown memory type to the kernel (e.g., due to a newer protocol version).
42            _ => Self::Reserved,
43        }
44    }
45}
46
47impl ToEarlyBootInfo for BootParams {
48    fn bootloader_name(&self) -> &'static str {
49        // The bootloaders have assigned IDs in Linux, see
50        // https://www.kernel.org/doc/Documentation/x86/boot.txt
51        // for details.
52        match self.hdr.type_of_loader {
53            0x0 => "LILO", // (0x00 reserved for pre-2.00 bootloader)
54            0x1 => "Loadlin",
55            0x2 => "bootsect-loader", // (0x20, all other values reserved)
56            0x3 => "Syslinux",
57            0x4 => "Etherboot/gPXE/iPXE",
58            0x5 => "ELILO",
59            0x7 => "GRUB",
60            0x8 => "U-Boot",
61            0x9 => "Xen",
62            0xA => "Gujin",
63            0xB => "Qemu",
64            0xC => "Arcturus Networks uCbootloader",
65            0xD => "kexec-tools",
66            0xE => "Extended loader",
67            0xF => "Special", // (0xFF = undefined)
68            0x10 => "Reserved",
69            0x11 => "Minimal Linux Bootloader <http://sebastian-plotz.blogspot.de>",
70            0x12 => "OVMF UEFI virtualization stack",
71            _ => "Unknown Linux Loader",
72        }
73    }
74
75    fn kernel_commandline(&self) -> Option<&'static str> {
76        if self.ext_cmd_line_ptr != 0 {
77            // TODO: We can support the above 4GiB command line after setting up
78            // linear mappings. By far, we cannot log the error because the serial is
79            // not up. Proceed as if there was no command line.
80            return None;
81        }
82
83        if self.hdr.cmd_line_ptr == 0 || self.hdr.cmdline_size == 0 {
84            return None;
85        }
86
87        let cmdline_ptr = paddr_to_vaddr(self.hdr.cmd_line_ptr as usize);
88        let cmdline_len = self.hdr.cmdline_size as usize;
89        // SAFETY:
90        // 1. The command line is safe to read because of the contract with the loader.
91        // 2. We reserve the command-line region in `finish_memory_regions`, so it will live as an
92        //    immutable reference for `'static`.
93        let cmdline = unsafe { core::slice::from_raw_parts(cmdline_ptr as *const u8, cmdline_len) };
94
95        // Now, unfortunately, there are silent errors because the serial is not up.
96        core::ffi::CStr::from_bytes_until_nul(cmdline)
97            .ok()?
98            .to_str()
99            .ok()
100    }
101
102    fn initramfs(&self) -> Option<&'static [u8]> {
103        if self.ext_ramdisk_image != 0 || self.ext_ramdisk_size != 0 {
104            // See the explanation in `kernel_commandline`.
105            return None;
106        }
107
108        if self.hdr.ramdisk_image == 0 || self.hdr.ramdisk_size == 0 {
109            return None;
110        }
111
112        let initramfs_ptr = paddr_to_vaddr(self.hdr.ramdisk_image as usize);
113        let initramfs_len = self.hdr.ramdisk_size as usize;
114        // SAFETY:
115        // 1. The initramfs is safe to read because of the contract with the loader.
116        // 2. We reserve the initramfs region in `memory_regions`, so it will live as an immutable
117        //    reference for `'static`.
118        let initramfs =
119            unsafe { core::slice::from_raw_parts(initramfs_ptr as *const u8, initramfs_len) };
120
121        Some(initramfs)
122    }
123
124    fn acpi_arg(&self) -> BootloaderAcpiArg {
125        let rsdp = self.acpi_rsdp_addr;
126
127        if rsdp == 0 {
128            if is_efi_boot(self) {
129                BootloaderAcpiArg::NotProvided
130            } else {
131                BootloaderAcpiArg::ScanBios
132            }
133        } else {
134            BootloaderAcpiArg::Rsdp(rsdp.try_into().expect("RSDP address overflowed!"))
135        }
136    }
137
138    fn framebuffer_arg(&self) -> Option<BootloaderFramebufferArg> {
139        let screen_info = self.screen_info;
140
141        let address = screen_info.lfb_base as usize | ((screen_info.ext_lfb_base as usize) << 32);
142        if address == 0 {
143            return None;
144        }
145
146        Some(BootloaderFramebufferArg {
147            address,
148            width: screen_info.lfb_width as usize,
149            height: screen_info.lfb_height as usize,
150            bpp: screen_info.lfb_depth as usize,
151        })
152    }
153
154    fn memory_regions(
155        &self,
156        initramfs: Option<&'static [u8]>,
157        kernel_cmdline: Option<&'static str>,
158        framebuffer_arg: Option<BootloaderFramebufferArg>,
159    ) -> MemoryRegionArray {
160        let mut regions = MemoryRegionArray::new();
161
162        // Add regions from E820.
163        let num_entries = self.e820_entries as usize;
164        for e820_entry in &self.e820_table[0..num_entries] {
165            regions
166                .push(MemoryRegion::new(
167                    e820_entry.addr.try_into().unwrap(),
168                    e820_entry.size.try_into().unwrap(),
169                    e820_entry.typ.into(),
170                ))
171                .unwrap();
172        }
173
174        // FIXME: Early versions of TDVF did not correctly report the location of AP's page tables as
175        // EfiACPIMemoryNVS. We need to manually reserve this memory region to prevent them from being
176        // corrupted. TDVF has now been upstreamed to OVMF, and this issue has been fixed in OVMF
177        // stable-202411 or later. See the commit for details:
178        // <https://github.com/tianocore/edk2/commit/383f729ac096b8deb279933fce86e83a5f7f5ec7>.
179        if_tdx_enabled!({
180            // The definition of these constants can be found in:
181            // <https://github.com/tianocore/edk2/blob/a7ab45ace25c4b987994158687d04de07ed20a96/OvmfPkg/IntelTdx/IntelTdxX64.fdf#L64-L71>
182            // <https://github.com/tianocore/edk2/blob/a7ab45ace25c4b987994158687d04de07ed20a96/OvmfPkg/Include/Fdf/OvmfPkgDefines.fdf.inc#L106>
183            regions
184                .push(MemoryRegion::new(
185                    // PcdOvmfSecPageTablesBase = $(MEMFD_BASE_ADDRESS) + 0x000000 = 0x800000
186                    0x800000,
187                    // PcdOvmfSecPageTablesSize = 0x006000
188                    0x006000,
189                    // EfiACPIMemoryNVS
190                    MemoryRegionType::NonVolatileSleep,
191                ))
192                .unwrap();
193        });
194
195        super::finish_memory_regions(regions, framebuffer_arg, initramfs, kernel_cmdline)
196    }
197}
198
199/// The entry point of the Rust code portion of Asterinas (with Linux boot parameters).
200///
201/// # Safety
202///
203/// - This function must be called only once at a proper timing in the BSP's boot assembly code.
204/// - The caller must follow C calling conventions and put the right arguments in registers.
205/// - If this function is called, entry points of other boot protocols must never be called.
206// SAFETY: The name does not collide with other symbols.
207#[unsafe(no_mangle)]
208unsafe extern "sysv64" fn __linux_boot(params_ptr: *const BootParams) -> ! {
209    let params = unsafe { &*params_ptr };
210    assert_eq!({ params.hdr.header }, LINUX_BOOT_HEADER_MAGIC);
211
212    use crate::boot::{EARLY_INFO, start_kernel};
213
214    #[cfg(feature = "cvm_guest")]
215    init_cvm_guest();
216
217    EARLY_INFO.call_once(|| params.to_early_boot_info());
218
219    // SAFETY: The safety is guaranteed by the safety preconditions and the fact that we call it
220    // once after setting up necessary resources.
221    unsafe { start_kernel() };
222}