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