ostd/arch/x86/boot/linux_boot/
mod.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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// SPDX-License-Identifier: MPL-2.0

//! The Linux 64-bit Boot Protocol supporting module.
//!

use linux_boot_params::{BootParams, E820Type, LINUX_BOOT_HEADER_MAGIC};

#[cfg(feature = "cvm_guest")]
use crate::arch::init_cvm_guest;
use crate::{
    arch::if_tdx_enabled,
    boot::{
        memory_region::{MemoryRegion, MemoryRegionArray, MemoryRegionType},
        BootloaderAcpiArg, BootloaderFramebufferArg,
    },
    mm::kspace::paddr_to_vaddr,
};

fn parse_bootloader_name(boot_params: &BootParams) -> &str {
    // The bootloaders have assigned IDs in Linux, see
    // https://www.kernel.org/doc/Documentation/x86/boot.txt
    // for details.
    match boot_params.hdr.type_of_loader {
        0x0 => "LILO", // (0x00 reserved for pre-2.00 bootloader)
        0x1 => "Loadlin",
        0x2 => "bootsect-loader", // (0x20, all other values reserved)
        0x3 => "Syslinux",
        0x4 => "Etherboot/gPXE/iPXE",
        0x5 => "ELILO",
        0x7 => "GRUB",
        0x8 => "U-Boot",
        0x9 => "Xen",
        0xA => "Gujin",
        0xB => "Qemu",
        0xC => "Arcturus Networks uCbootloader",
        0xD => "kexec-tools",
        0xE => "Extended loader",
        0xF => "Special", // (0xFF = undefined)
        0x10 => "Reserved",
        0x11 => "Minimal Linux Bootloader <http://sebastian-plotz.blogspot.de>",
        0x12 => "OVMF UEFI virtualization stack",
        _ => "Unknown Linux Loader",
    }
}

fn parse_kernel_commandline(boot_params: &BootParams) -> Option<&str> {
    if boot_params.ext_cmd_line_ptr != 0 {
        // TODO: We can support the above 4GiB command line after setting up
        // linear mappings. By far, we cannot log the error because the serial is
        // not up. Proceed as if there was no command line.
        return None;
    }

    if boot_params.hdr.cmd_line_ptr == 0 || boot_params.hdr.cmdline_size == 0 {
        return None;
    }

    let cmdline_ptr = paddr_to_vaddr(boot_params.hdr.cmd_line_ptr as usize);
    let cmdline_len = boot_params.hdr.cmdline_size as usize;
    // SAFETY: The command line is safe to read because of the contract with the loader.
    let cmdline = unsafe { core::slice::from_raw_parts(cmdline_ptr as *const u8, cmdline_len) };

    // Now, unfortunately, there are silent errors because the serial is not up.
    core::ffi::CStr::from_bytes_until_nul(cmdline)
        .ok()?
        .to_str()
        .ok()
}

fn parse_initramfs(boot_params: &BootParams) -> Option<&[u8]> {
    if boot_params.ext_ramdisk_image != 0 || boot_params.ext_ramdisk_size != 0 {
        // See the explanation in `parse_kernel_commandline`.
        return None;
    }

    if boot_params.hdr.ramdisk_image == 0 || boot_params.hdr.ramdisk_size == 0 {
        return None;
    }

    let initramfs_ptr = paddr_to_vaddr(boot_params.hdr.ramdisk_image as usize);
    let initramfs_len = boot_params.hdr.ramdisk_size as usize;
    // SAFETY: The initramfs is safe to read because of the contract with the loader.
    let initramfs =
        unsafe { core::slice::from_raw_parts(initramfs_ptr as *const u8, initramfs_len) };

    Some(initramfs)
}

fn parse_acpi_arg(boot_params: &BootParams) -> BootloaderAcpiArg {
    let rsdp = boot_params.acpi_rsdp_addr;

    if rsdp == 0 {
        BootloaderAcpiArg::NotProvided
    } else {
        BootloaderAcpiArg::Rsdp(rsdp.try_into().expect("RSDP address overflowed!"))
    }
}

fn parse_framebuffer_info(boot_params: &BootParams) -> Option<BootloaderFramebufferArg> {
    let screen_info = boot_params.screen_info;

    let address = screen_info.lfb_base as usize | ((screen_info.ext_lfb_base as usize) << 32);
    if address == 0 {
        return None;
    }

    Some(BootloaderFramebufferArg {
        address,
        width: screen_info.lfb_width as usize,
        height: screen_info.lfb_height as usize,
        bpp: screen_info.lfb_depth as usize,
    })
}

impl From<E820Type> for MemoryRegionType {
    fn from(value: E820Type) -> Self {
        match value {
            E820Type::Ram => Self::Usable,
            E820Type::Reserved => Self::Reserved,
            E820Type::Acpi => Self::Reclaimable,
            E820Type::Nvs => Self::NonVolatileSleep,
            E820Type::Unusable => Self::BadMemory,
            // All other memory regions are reserved.
            // FIXME: Using Rust enum in this way can be unsound if the bootloader passes an
            // unknown memory type to the kernel (e.g., due to a newer protocol version).
            _ => Self::Reserved,
        }
    }
}

fn parse_memory_regions(boot_params: &BootParams) -> MemoryRegionArray {
    let mut regions = MemoryRegionArray::new();

    // Add regions from E820.
    let num_entries = boot_params.e820_entries as usize;
    for e820_entry in &boot_params.e820_table[0..num_entries] {
        regions
            .push(MemoryRegion::new(
                e820_entry.addr.try_into().unwrap(),
                e820_entry.size.try_into().unwrap(),
                e820_entry.typ.into(),
            ))
            .unwrap();
    }

    // Add the framebuffer region.
    if let Some(fb) = parse_framebuffer_info(boot_params) {
        regions.push(MemoryRegion::framebuffer(&fb)).unwrap();
    }

    // Add the kernel region.
    regions.push(MemoryRegion::kernel()).unwrap();

    // Add the initramfs region.
    if let Some(initramfs) = parse_initramfs(boot_params) {
        regions.push(MemoryRegion::module(initramfs)).unwrap();
    }

    // Add the AP boot code region that will be copied into by the BSP.
    regions
        .push(super::smp::reclaimable_memory_region())
        .unwrap();

    // Add the region of the kernel cmdline since some bootloaders do not provide it.
    if let Some(kcmdline) = parse_kernel_commandline(boot_params) {
        regions
            .push(MemoryRegion::module(kcmdline.as_bytes()))
            .unwrap();
    }

    // FIXME: Early versions of TDVF did not correctly report the location of AP's page tables as
    // EfiACPIMemoryNVS. We need to manually reserve this memory region to prevent them from being
    // corrupted. TDVF has now been upstreamed to OVMF, and this issue has been fixed in OVMF
    // stable-202411 or later. See the commit for details:
    // <https://github.com/tianocore/edk2/commit/383f729ac096b8deb279933fce86e83a5f7f5ec7>.
    if_tdx_enabled!({
        // The definition of these constants can be found in:
        // <https://github.com/tianocore/edk2/blob/a7ab45ace25c4b987994158687d04de07ed20a96/OvmfPkg/IntelTdx/IntelTdxX64.fdf#L64-L71>
        // <https://github.com/tianocore/edk2/blob/a7ab45ace25c4b987994158687d04de07ed20a96/OvmfPkg/Include/Fdf/OvmfPkgDefines.fdf.inc#L106>
        regions
            .push(MemoryRegion::new(
                // PcdOvmfSecPageTablesBase = $(MEMFD_BASE_ADDRESS) + 0x000000 = 0x800000
                0x800000,
                // PcdOvmfSecPageTablesSize = 0x006000
                0x006000,
                // EfiACPIMemoryNVS
                MemoryRegionType::NonVolatileSleep,
            ))
            .unwrap();
    });

    regions.into_non_overlapping()
}

/// The entry point of the Rust code portion of Asterinas.
#[no_mangle]
unsafe extern "sysv64" fn __linux_boot(params_ptr: *const BootParams) -> ! {
    let params = unsafe { &*params_ptr };
    assert_eq!({ params.hdr.header }, LINUX_BOOT_HEADER_MAGIC);

    use crate::boot::{call_ostd_main, EarlyBootInfo, EARLY_INFO};

    #[cfg(feature = "cvm_guest")]
    init_cvm_guest();

    EARLY_INFO.call_once(|| EarlyBootInfo {
        bootloader_name: parse_bootloader_name(params),
        kernel_cmdline: parse_kernel_commandline(params).unwrap_or(""),
        initramfs: parse_initramfs(params),
        acpi_arg: parse_acpi_arg(params),
        framebuffer_arg: parse_framebuffer_info(params),
        memory_regions: parse_memory_regions(params),
    });

    call_ostd_main();
}