Skip to main content

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

1// SPDX-License-Identifier: MPL-2.0
2
3use core::arch::global_asm;
4
5use multiboot2::{BootInformation, BootInformationHeader, MemoryAreaType};
6
7use crate::{
8    boot::{
9        BootloaderAcpiArg, BootloaderFramebufferArg,
10        memory_region::{MemoryRegion, MemoryRegionArray, MemoryRegionType},
11    },
12    mm::{Paddr, kspace::paddr_to_vaddr},
13};
14
15global_asm!(include_str!("header.S"));
16
17fn parse_bootloader_name(mb2_info: &BootInformation) -> Option<&'static str> {
18    let name = mb2_info.boot_loader_name_tag()?.name().ok()?;
19
20    // SAFETY: The address of `name` is physical and the bootloader name will live for `'static`.
21    Some(unsafe { make_str_vaddr_static(name) })
22}
23
24fn parse_kernel_commandline(mb2_info: &BootInformation) -> Option<&'static str> {
25    let cmdline = mb2_info.command_line_tag()?.cmdline().ok()?;
26
27    // SAFETY: The address of `cmdline` is physical and the command line will live for `'static`.
28    Some(unsafe { make_str_vaddr_static(cmdline) })
29}
30
31unsafe fn make_str_vaddr_static(str: &str) -> &'static str {
32    let vaddr = paddr_to_vaddr(str.as_ptr() as Paddr);
33
34    // SAFETY: The safety is upheld by the caller.
35    let bytes = unsafe { core::slice::from_raw_parts(vaddr as *const u8, str.len()) };
36
37    core::str::from_utf8(bytes).unwrap()
38}
39
40fn parse_initramfs(mb2_info: &BootInformation) -> Option<&'static [u8]> {
41    let module_tag = mb2_info.module_tags().next()?;
42
43    let initramfs_ptr = paddr_to_vaddr(module_tag.start_address() as usize);
44    let initramfs_len = module_tag.module_size() as usize;
45    // SAFETY:
46    // 1. The initramfs is safe to read because of the contract with the loader.
47    // 2. We reserve the initramfs region in `parse_memory_regions`, so it will live as an immutable
48    //    reference for `'static`.
49    let initramfs =
50        unsafe { core::slice::from_raw_parts(initramfs_ptr as *const u8, initramfs_len) };
51
52    Some(initramfs)
53}
54
55fn parse_acpi_arg(mb2_info: &BootInformation) -> BootloaderAcpiArg {
56    if let Some(v2_tag) = mb2_info.rsdp_v2_tag() {
57        // Check for RSDP v2
58        BootloaderAcpiArg::Xsdt(v2_tag.xsdt_address())
59    } else if let Some(v1_tag) = mb2_info.rsdp_v1_tag() {
60        // Fall back to RSDP v1
61        BootloaderAcpiArg::Rsdt(v1_tag.rsdt_address())
62    } else if is_efi_boot(mb2_info) {
63        BootloaderAcpiArg::NotProvided
64    } else {
65        BootloaderAcpiArg::ScanBios
66    }
67}
68
69fn is_efi_boot(mb2_info: &BootInformation) -> bool {
70    mb2_info.efi_sdt32_tag().is_some()
71        || mb2_info.efi_sdt64_tag().is_some()
72        || mb2_info.efi_memory_map_tag().is_some()
73        || mb2_info.efi_bs_not_exited_tag().is_some()
74        || mb2_info.efi_ih32_tag().is_some()
75        || mb2_info.efi_ih64_tag().is_some()
76}
77
78fn parse_framebuffer_info(mb2_info: &BootInformation) -> Option<BootloaderFramebufferArg> {
79    let fb_tag = mb2_info.framebuffer_tag()?.ok()?;
80
81    Some(BootloaderFramebufferArg {
82        address: fb_tag.address() as usize,
83        width: fb_tag.width() as usize,
84        height: fb_tag.height() as usize,
85        bpp: fb_tag.bpp() as usize,
86    })
87}
88
89impl From<MemoryAreaType> for MemoryRegionType {
90    fn from(value: MemoryAreaType) -> Self {
91        match value {
92            MemoryAreaType::Available => Self::Usable,
93            MemoryAreaType::Reserved => Self::Reserved,
94            MemoryAreaType::AcpiAvailable => Self::Reclaimable,
95            MemoryAreaType::ReservedHibernate => Self::NonVolatileSleep,
96            MemoryAreaType::Defective => Self::BadMemory,
97            MemoryAreaType::Custom(_) => Self::Reserved,
98        }
99    }
100}
101
102fn parse_memory_regions(mb2_info: &BootInformation) -> MemoryRegionArray {
103    let mut regions = MemoryRegionArray::new();
104
105    // Add the regions returned by Grub.
106    let memory_regions_tag = mb2_info
107        .memory_map_tag()
108        .expect("No memory regions are found in the Multiboot2 header!");
109    for region in memory_regions_tag.memory_areas() {
110        let start = region.start_address();
111        let end = region.end_address();
112        let area_typ: MemoryRegionType = MemoryAreaType::from(region.typ()).into();
113        let region = MemoryRegion::new(
114            start.try_into().unwrap(),
115            (end - start).try_into().unwrap(),
116            area_typ,
117        );
118        regions.push(region).unwrap();
119    }
120
121    // Add the framebuffer region since Grub does not specify it.
122    if let Some(fb) = parse_framebuffer_info(mb2_info) {
123        regions.push(MemoryRegion::framebuffer(&fb)).unwrap();
124    }
125
126    // Add the kernel region since Grub does not specify it.
127    regions.push(MemoryRegion::kernel()).unwrap();
128
129    // Add the initramfs region.
130    if let Some(initramfs) = parse_initramfs(mb2_info) {
131        regions.push(MemoryRegion::module(initramfs)).unwrap();
132    }
133
134    // Add the AP boot code region that will be copied into by the BSP.
135    regions
136        .push(super::smp::reclaimable_memory_region())
137        .unwrap();
138
139    // Add the kernel cmdline and boot loader name region since Grub does not specify it.
140    if let Some(kcmdline) = parse_kernel_commandline(mb2_info) {
141        regions
142            .push(MemoryRegion::module(kcmdline.as_bytes()))
143            .unwrap();
144    }
145    if let Some(bootloader_name) = parse_bootloader_name(mb2_info) {
146        regions
147            .push(MemoryRegion::module(bootloader_name.as_bytes()))
148            .unwrap();
149    }
150
151    regions.into_non_overlapping()
152}
153
154/// The entry point of the Rust code portion of Asterinas (with multiboot2 parameters).
155///
156/// # Safety
157///
158/// - This function must be called only once at a proper timing in the BSP's boot assembly code.
159/// - The caller must follow C calling conventions and put the right arguments in registers.
160/// - If this function is called, entry points of other boot protocols must never be called.
161// SAFETY: The name does not collide with other symbols.
162#[unsafe(no_mangle)]
163unsafe extern "sysv64" fn __multiboot2_entry(boot_magic: u32, boot_params: u64) -> ! {
164    assert_eq!(boot_magic, multiboot2::MAGIC);
165    let mb2_info =
166        unsafe { BootInformation::load(boot_params as *const BootInformationHeader).unwrap() };
167
168    use crate::boot::{EARLY_INFO, EarlyBootInfo, start_kernel};
169
170    EARLY_INFO.call_once(|| EarlyBootInfo {
171        bootloader_name: parse_bootloader_name(&mb2_info).unwrap_or("Unknown Multiboot2 Loader"),
172        kernel_cmdline: parse_kernel_commandline(&mb2_info).unwrap_or(""),
173        initramfs: parse_initramfs(&mb2_info),
174        acpi_arg: parse_acpi_arg(&mb2_info),
175        framebuffer_arg: parse_framebuffer_info(&mb2_info),
176        memory_regions: parse_memory_regions(&mb2_info),
177    });
178
179    // SAFETY: The safety is guaranteed by the safety preconditions and the fact that we call it
180    // once after setting up necessary resources.
181    unsafe { start_kernel() };
182}