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