diff --git a/Cargo.lock b/Cargo.lock index 645da8cd..bcdbe41a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,7 @@ dependencies = [ "test_kernel_higher_half", "test_kernel_map_phys_mem", "test_kernel_pie", + "test_kernel_ramdisk", ] [[package]] @@ -148,7 +149,10 @@ name = "bootloader_test_runner" version = "0.1.0" dependencies = [ "bootloader", + "lazy_static", "ovmf-prebuilt", + "paste", + "rand", "strip-ansi-escapes", ] @@ -266,6 +270,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.122" @@ -356,6 +366,12 @@ version = "0.1.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa50141d081512ab30fd9e7e7692476866df5098b028536ad6680212e717fa8d" +[[package]] +name = "paste" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -596,6 +612,15 @@ dependencies = [ "x86_64", ] +[[package]] +name = "test_kernel_ramdisk" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550", + "x86_64", +] + [[package]] name = "thiserror" version = "1.0.30" @@ -647,9 +672,9 @@ dependencies = [ [[package]] name = "uefi" -version = "0.16.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705535cf386e4b033cc7acdea55ec8710f3dde2f07457218791aac35c83be21f" +checksum = "07b87700863d65dd4841556be3374d8d4f9f8dbb577ad93a39859e70b3b91f35" dependencies = [ "bitflags", "log", @@ -659,9 +684,9 @@ dependencies = [ [[package]] name = "uefi-macros" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9917831bc5abb78c2e6a0f4fba2be165105ed53d288718c999e0efbd433bb7" +checksum = "275f054a1d9fd7e43a2ce91cc24298a87b281117dea8afc120ae95faa0e96b94" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 4bea9b23..3046cec0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = [ "tests/test_kernels/higher_half", "tests/test_kernels/pie", "tests/test_kernels/lto", + "tests/test_kernels/ramdisk" ] exclude = ["examples/basic", "examples/test_framework"] @@ -50,6 +51,7 @@ test_kernel_default_settings = { path = "tests/test_kernels/default_settings", a test_kernel_higher_half = { path = "tests/test_kernels/higher_half", artifact = "bin", target = "x86_64-unknown-none" } test_kernel_map_phys_mem = { path = "tests/test_kernels/map_phys_mem", artifact = "bin", target = "x86_64-unknown-none" } test_kernel_pie = { path = "tests/test_kernels/pie", artifact = "bin", target = "x86_64-unknown-none" } +test_kernel_ramdisk = { path = "tests/test_kernels/ramdisk", artifact = "bin", target = "x86_64-unknown-none" } [profile.dev] panic = "abort" diff --git a/api/build.rs b/api/build.rs index bacb5bf9..5edc9c1d 100644 --- a/api/build.rs +++ b/api/build.rs @@ -22,7 +22,8 @@ fn main() { (88, 9), (97, 9), (106, 9), - (115, 1), + (115, 9), + (124, 1), ]; let mut code = String::new(); diff --git a/api/src/config.rs b/api/src/config.rs index a7660746..c4fa0e77 100644 --- a/api/src/config.rs +++ b/api/src/config.rs @@ -39,7 +39,7 @@ impl BootloaderConfig { 0x3D, ]; #[doc(hidden)] - pub const SERIALIZED_LEN: usize = 116; + pub const SERIALIZED_LEN: usize = 125; /// Creates a new default configuration with the following values: /// @@ -83,6 +83,7 @@ impl BootloaderConfig { aslr, dynamic_range_start, dynamic_range_end, + ramdisk_memory, } = mappings; let FrameBuffer { minimum_framebuffer_height, @@ -131,7 +132,9 @@ impl BootloaderConfig { }, ); - let buf = concat_97_9( + let buf = concat_97_9(buf, ramdisk_memory.serialize()); + + let buf = concat_106_9( buf, match minimum_framebuffer_height { Option::None => [0; 9], @@ -139,15 +142,16 @@ impl BootloaderConfig { }, ); - let buf = concat_106_9( + let buf = concat_115_9( buf, match minimum_framebuffer_width { Option::None => [0; 9], Option::Some(addr) => concat_1_8([1], addr.to_le_bytes()), }, ); + let buf = concat_124_1(buf, (*log_level as u8).to_le_bytes()); - concat_115_1(buf, (*log_level as u8).to_le_bytes()) + buf } /// Tries to deserialize a config byte array that was created using [`Self::serialize`]. @@ -205,6 +209,7 @@ impl BootloaderConfig { let (&dynamic_range_start, s) = split_array_ref(s); let (&dynamic_range_end_some, s) = split_array_ref(s); let (&dynamic_range_end, s) = split_array_ref(s); + let (&ramdisk_memory, s) = split_array_ref(s); let mappings = Mappings { kernel_stack: Mapping::deserialize(&kernel_stack)?, @@ -235,6 +240,7 @@ impl BootloaderConfig { [1] => Option::Some(u64::from_le_bytes(dynamic_range_end)), _ => return Err("invalid dynamic range end value"), }, + ramdisk_memory: Mapping::deserialize(&ramdisk_memory)?, }; (mappings, s) }; @@ -398,6 +404,9 @@ pub struct Mappings { /// /// Defaults to `0xffff_ffff_ffff_f000`. pub dynamic_range_end: Option, + /// Virtual address to map ramdisk image, if present on disk + /// Defaults to dynamic + pub ramdisk_memory: Mapping, } impl Mappings { @@ -414,6 +423,7 @@ impl Mappings { aslr: false, dynamic_range_start: None, dynamic_range_end: None, + ramdisk_memory: Mapping::new_default(), } } @@ -446,6 +456,7 @@ impl Mappings { } else { Option::None }, + ramdisk_memory: Mapping::random(), } } } diff --git a/api/src/info.rs b/api/src/info.rs index 248362ab..020644bd 100644 --- a/api/src/info.rs +++ b/api/src/info.rs @@ -52,6 +52,10 @@ pub struct BootInfo { pub rsdp_addr: Optional, /// The thread local storage (TLS) template of the kernel executable, if present. pub tls_template: Optional, + /// Ramdisk address, if loaded + pub ramdisk_addr: Optional, + /// Ramdisk image size, set to 0 if addr is None + pub ramdisk_len: u64, } impl BootInfo { @@ -67,6 +71,8 @@ impl BootInfo { recursive_index: Optional::None, rsdp_addr: Optional::None, tls_template: Optional::None, + ramdisk_addr: Optional::None, + ramdisk_len: 0, } } } diff --git a/bios/common/src/lib.rs b/bios/common/src/lib.rs index c55c6d31..59a7737d 100644 --- a/bios/common/src/lib.rs +++ b/bios/common/src/lib.rs @@ -7,6 +7,7 @@ pub mod racy_cell; pub struct BiosInfo { pub stage_4: Region, pub kernel: Region, + pub ramdisk: Region, pub framebuffer: BiosFramebufferInfo, pub memory_map_addr: u32, pub memory_map_len: u16, diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index b8c85bd7..ca439af5 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -27,6 +27,7 @@ const BOOTLOADER_SECOND_STAGE_PARTITION_TYPE: u8 = 0x20; const STAGE_3_DST: *mut u8 = 0x0010_0000 as *mut u8; // 1MiB (typically 14MiB accessible here) const STAGE_4_DST: *mut u8 = 0x0020_0000 as *mut u8; // 2MiB (typically still 13MiB accessible here) const KERNEL_DST: *mut u8 = 0x0100_0000 as *mut u8; // 16MiB +const RAMDISK_DST: *mut u8 = 0x0400_0000 as *mut u8; // 64MiB static mut DISK_BUFFER: AlignedArrayBuffer<0x4000> = AlignedArrayBuffer { buffer: [0; 0x4000], @@ -98,6 +99,17 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { writeln!(screen::Writer, "loading kernel...").unwrap(); let kernel_len = load_file("kernel-x86_64", KERNEL_DST, &mut fs, &mut disk, disk_buffer); writeln!(screen::Writer, "kernel loaded at {KERNEL_DST:#p}").unwrap(); + writeln!(screen::Writer, "Loading ramdisk...").unwrap(); + let ramdisk_len = match try_load_file("ramdisk", RAMDISK_DST, &mut fs, &mut disk, disk_buffer) { + Some(s) => s, + None => 0u64, + }; + + if ramdisk_len == 0 { + writeln!(screen::Writer, "No ramdisk found, skipping.").unwrap(); + } else { + writeln!(screen::Writer, "Loaded ramdisk at {RAMDISK_DST:#p}").unwrap(); + } let memory_map = unsafe { memory_map::query_memory_map() }.unwrap(); writeln!(screen::Writer, "{memory_map:x?}").unwrap(); @@ -129,6 +141,10 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { start: KERNEL_DST as u64, len: kernel_len, }, + ramdisk: Region { + start: RAMDISK_DST as u64, + len: ramdisk_len, + }, memory_map_addr: memory_map.as_mut_ptr() as u32, memory_map_len: memory_map.len().try_into().unwrap(), framebuffer: BiosFramebufferInfo { @@ -151,17 +167,16 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { } } -fn load_file( +fn try_load_file( file_name: &str, dst: *mut u8, fs: &mut fat::FileSystem, disk: &mut disk::DiskAccess, disk_buffer: &mut AlignedArrayBuffer<16384>, -) -> u64 { +) -> Option { let disk_buffer_size = disk_buffer.buffer.len(); - let file = fs - .find_file_in_root_dir(file_name, disk_buffer) - .expect("file not found"); + let file = fs.find_file_in_root_dir(file_name, disk_buffer)?; + let file_size = file.file_size().into(); let mut total_offset = 0; @@ -195,7 +210,17 @@ fn load_file( total_offset += usize::try_from(len).unwrap(); } } - file_size + Some(file_size) +} + +fn load_file( + file_name: &str, + dst: *mut u8, + fs: &mut fat::FileSystem, + disk: &mut disk::DiskAccess, + disk_buffer: &mut AlignedArrayBuffer<16384>, +) -> u64 { + try_load_file(file_name, dst, fs, disk, disk_buffer).expect("file not found") } /// Taken from https://github.com/rust-lang/rust/blob/e100ec5bc7cd768ec17d75448b29c9ab4a39272b/library/core/src/slice/mod.rs#L1673-L1677 diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index d6a50b9c..3a9c11d0 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -56,13 +56,21 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { PhysAddr::new(info.kernel.start) }; let kernel_size = info.kernel.len; - let mut frame_allocator = { + let mut frame_allocator = if info.ramdisk.start == 0 { let kernel_end = PhysFrame::containing_address(kernel_start + kernel_size - 1u64); let next_free = kernel_end + 1; LegacyFrameAllocator::new_starting_at( next_free, memory_map.iter().copied().map(MemoryRegion), ) + } else { + let ramdisk_end = + PhysFrame::containing_address(PhysAddr::new(info.ramdisk.start + info.ramdisk.len)); + let next_free = ramdisk_end + 1; + LegacyFrameAllocator::new_starting_at( + next_free, + memory_map.iter().copied().map(MemoryRegion), + ) }; // We identity-mapped all memory, so the offset between physical and virtual addresses is 0 @@ -121,6 +129,11 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { info: framebuffer_info, }), rsdp_addr: detect_rsdp(), + ramdisk_addr: match info.ramdisk.len { + 0 => None, + _ => Some(info.ramdisk.start), + }, + ramdisk_len: info.ramdisk.len, }; load_and_switch_to_kernel(kernel, frame_allocator, page_tables, system_info); diff --git a/common/src/lib.rs b/common/src/lib.rs index fc1c7d59..c78b45bd 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -60,6 +60,8 @@ pub struct SystemInfo { pub framebuffer: Option, /// Address of the _Root System Description Pointer_ structure of the ACPI standard. pub rsdp_addr: Option, + pub ramdisk_addr: Option, + pub ramdisk_len: u64, } /// The physical address of the framebuffer and information about the framebuffer. @@ -120,6 +122,7 @@ where &mut page_tables, system_info.framebuffer.as_ref(), &config, + &system_info, ); let boot_info = create_boot_info( &config, @@ -151,6 +154,7 @@ pub fn set_up_mappings( page_tables: &mut PageTables, framebuffer: Option<&RawFrameBufferInfo>, config: &BootloaderConfig, + system_info: &SystemInfo, ) -> Mappings where I: ExactSizeIterator + Clone, @@ -182,7 +186,6 @@ where ) .expect("no entry point"); log::info!("Entry point at: {:#x}", entry_point.as_u64()); - // create a stack let stack_start_addr = mapping_addr( config.mappings.kernel_stack, @@ -267,6 +270,39 @@ where } else { None }; + let ramdisk_slice_len = system_info.ramdisk_len; + let ramdisk_slice_start = if let Some(ramdisk_address) = system_info.ramdisk_addr { + let ramdisk_address_start = mapping_addr( + config.mappings.ramdisk_memory, + system_info.ramdisk_len, + 8, + &mut used_entries, + ); + let physical_address = PhysAddr::new(ramdisk_address); + let ramdisk_physical_start_page: PhysFrame = + PhysFrame::containing_address(physical_address); + let ramdisk_page_count = (system_info.ramdisk_len - 1 / Size4KiB::SIZE) + 1; + let ramdisk_physical_end_page = ramdisk_physical_start_page + ramdisk_page_count; + let start_page = Page::containing_address(ramdisk_address_start); + + let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; + for (i, frame) in + PhysFrame::range_inclusive(ramdisk_physical_start_page, ramdisk_physical_end_page) + .enumerate() + { + let page = start_page + i as u64; + match unsafe { kernel_page_table.map_to(page, frame, flags, frame_allocator) } { + Ok(tlb) => tlb.ignore(), + Err(err) => panic!( + "Failed to map page {:?} to frame {:?}: {:?}", + page, frame, err + ), + }; + } + Some(ramdisk_address_start) + } else { + None + }; let physical_memory_offset = if let Some(mapping) = config.mappings.physical_memory { log::info!("Map physical memory"); @@ -341,6 +377,8 @@ where kernel_slice_start, kernel_slice_len, + ramdisk_slice_start, + ramdisk_slice_len, } } @@ -366,6 +404,8 @@ pub struct Mappings { pub kernel_slice_start: u64, /// Size of the kernel slice allocation in memory. pub kernel_slice_len: u64, + pub ramdisk_slice_start: Option, + pub ramdisk_slice_len: u64, } /// Allocates and initializes the boot info struct and the memory map. @@ -475,6 +515,11 @@ where info.recursive_index = mappings.recursive_index.map(Into::into).into(); info.rsdp_addr = system_info.rsdp_addr.map(|addr| addr.as_u64()).into(); info.tls_template = mappings.tls_template.into(); + info.ramdisk_addr = mappings + .ramdisk_slice_start + .map(|addr| addr.as_u64()) + .into(); + info.ramdisk_len = mappings.ramdisk_slice_len; info }); diff --git a/src/lib.rs b/src/lib.rs index 11d1ebf0..a650d6f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,12 +17,14 @@ mod mbr; mod pxe; const KERNEL_FILE_NAME: &str = "kernel-x86_64"; +const RAMDISK_FILE_NAME: &str = "ramdisk"; const BIOS_STAGE_3: &str = "boot-stage-3"; const BIOS_STAGE_4: &str = "boot-stage-4"; /// Create disk images for booting on legacy BIOS systems. pub struct BiosBoot { kernel: PathBuf, + ramdisk: Option, } impl BiosBoot { @@ -30,9 +32,16 @@ impl BiosBoot { pub fn new(kernel_path: &Path) -> Self { Self { kernel: kernel_path.to_owned(), + ramdisk: None, } } + /// Add a ramdisk file to the image + pub fn set_ramdisk(&mut self, ramdisk_path: &Path) -> &mut Self { + self.ramdisk = Some(ramdisk_path.to_owned()); + self + } + /// Create a bootable UEFI disk image at the given path. pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> { let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH")); @@ -61,12 +70,15 @@ impl BiosBoot { fn create_fat_partition(&self) -> anyhow::Result { let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH")); let stage_4_path = Path::new(env!("BIOS_STAGE_4_PATH")); + let kernel_path = self.kernel.as_path(); let mut files = BTreeMap::new(); - files.insert(KERNEL_FILE_NAME, self.kernel.as_path()); + files.insert(KERNEL_FILE_NAME, kernel_path); files.insert(BIOS_STAGE_3, stage_3_path); files.insert(BIOS_STAGE_4, stage_4_path); - + if let Some(ramdisk_path) = &self.ramdisk { + files.insert(RAMDISK_FILE_NAME, ramdisk_path); + } let out_file = NamedTempFile::new().context("failed to create temp file")?; fat::create_fat_filesystem(files, out_file.path()) .context("failed to create BIOS FAT filesystem")?; @@ -78,6 +90,7 @@ impl BiosBoot { /// Create disk images for booting on UEFI systems. pub struct UefiBoot { kernel: PathBuf, + ramdisk: Option, } impl UefiBoot { @@ -85,9 +98,16 @@ impl UefiBoot { pub fn new(kernel_path: &Path) -> Self { Self { kernel: kernel_path.to_owned(), + ramdisk: None, } } + /// Add a ramdisk file to the disk image + pub fn set_ramdisk(&mut self, ramdisk_path: &Path) -> &mut Self { + self.ramdisk = Some(ramdisk_path.to_owned()); + self + } + /// Create a bootable BIOS disk image at the given path. pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> { let fat_partition = self @@ -111,9 +131,17 @@ impl UefiBoot { /// bootloader won't be found. pub fn create_pxe_tftp_folder(&self, out_path: &Path) -> anyhow::Result<()> { let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); - - pxe::create_uefi_tftp_folder(bootloader_path, self.kernel.as_path(), out_path) - .context("failed to create UEFI PXE tftp folder")?; + let ramdisk_path = match self.ramdisk.as_ref() { + Some(rd) => Some(rd.as_path()), + None => None, + }; + pxe::create_uefi_tftp_folder( + bootloader_path, + self.kernel.as_path(), + ramdisk_path, + out_path, + ) + .context("failed to create UEFI PXE tftp folder")?; Ok(()) } @@ -121,10 +149,14 @@ impl UefiBoot { /// Creates an UEFI-bootable FAT partition with the kernel. fn create_fat_partition(&self) -> anyhow::Result { let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); - + let kernel_path = self.kernel.as_path(); let mut files = BTreeMap::new(); files.insert("efi/boot/bootx64.efi", bootloader_path); - files.insert(KERNEL_FILE_NAME, self.kernel.as_path()); + files.insert(KERNEL_FILE_NAME, kernel_path); + + if let Some(ramdisk_path) = &self.ramdisk { + files.insert(RAMDISK_FILE_NAME, ramdisk_path); + } let out_file = NamedTempFile::new().context("failed to create temp file")?; fat::create_fat_filesystem(files, out_file.path()) diff --git a/src/pxe.rs b/src/pxe.rs index 9329cec2..84c7b0d6 100644 --- a/src/pxe.rs +++ b/src/pxe.rs @@ -5,6 +5,7 @@ use anyhow::Context; pub fn create_uefi_tftp_folder( bootloader_path: &Path, kernel_binary: &Path, + ramdisk_path: Option<&Path>, out_path: &Path, ) -> anyhow::Result<()> { std::fs::create_dir_all(out_path) @@ -27,6 +28,16 @@ pub fn create_uefi_tftp_folder( to.display() ) })?; + let to = out_path.join("ramdisk"); + if let Some(rp) = ramdisk_path { + std::fs::copy(rp, &to).with_context(|| { + format!( + "failed to copy ramdisk from {} to {}", + rp.display(), + to.display() + ) + })?; + } Ok(()) } diff --git a/tests/default_settings.rs b/tests/default_settings.rs index d610508c..622592ba 100644 --- a/tests/default_settings.rs +++ b/tests/default_settings.rs @@ -1,22 +1,14 @@ -use bootloader_test_runner::run_test_kernel; +use bootloader_test_runner::define_test; +const BASIC_BOOT_KERNEL: &str = env!("CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_basic_boot"); +const SHOULD_PANIC_KERNEL: &str = env!("CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_should_panic"); +const CHECK_BOOT_INFO_KERNEL: &str = + env!("CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_check_boot_info"); -#[test] -fn basic_boot() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_basic_boot" - )); -} - -#[test] -fn should_panic() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_should_panic" - )); -} - -#[test] -fn check_boot_info() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_check_boot_info" - )); -} +define_test!(basic_boot, BASIC_BOOT_KERNEL); +define_test!(should_panic, SHOULD_PANIC_KERNEL); +define_test!(check_boot_info, CHECK_BOOT_INFO_KERNEL); +define_test!( + disable_default_ramdisk_macro_test, + BASIC_BOOT_KERNEL, + without_ramdisk_tests +); diff --git a/tests/higher_half.rs b/tests/higher_half.rs index c2b9ac91..899df7b8 100644 --- a/tests/higher_half.rs +++ b/tests/higher_half.rs @@ -1,25 +1,11 @@ -use bootloader_test_runner::run_test_kernel; - -#[test] -fn basic_boot() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_basic_boot")); -} - -#[test] -fn should_panic() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_should_panic")); -} - -#[test] -fn check_boot_info() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_check_boot_info" - )); -} - -#[test] -fn verify_higher_half() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_verify_higher_half" - )); -} +use bootloader_test_runner::define_test; +const BASIC_BOOT_KERNEL: &str = env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_basic_boot"); +const SHOULD_PANIC_KERNEL: &str = env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_should_panic"); +const CHECK_BOOT_INFO_KERNEL: &str = env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_check_boot_info"); +const VERIFY_HIGHER_HALF_KERNEL: &str = + env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_verify_higher_half"); + +define_test!(basic_boot, BASIC_BOOT_KERNEL); +define_test!(should_panic, SHOULD_PANIC_KERNEL); +define_test!(check_boot_info, CHECK_BOOT_INFO_KERNEL); +define_test!(verify_higher_half, VERIFY_HIGHER_HALF_KERNEL); diff --git a/tests/lto.rs b/tests/lto.rs index 00cfe60f..0a93979a 100644 --- a/tests/lto.rs +++ b/tests/lto.rs @@ -1,7 +1,7 @@ use std::{path::Path, process::Command}; use bootloader_test_runner::run_test_kernel; - +// This test must remain as is, or the LTO build fails due to parallel testing. #[test] fn basic_boot() { // build test kernel manually to force-enable link-time optimization @@ -21,5 +21,5 @@ fn basic_boot() { .join("basic_boot"); assert!(kernel_path.exists()); - run_test_kernel(kernel_path.as_path().to_str().unwrap()); + run_test_kernel(kernel_path.as_path().to_str().unwrap(), None); } diff --git a/tests/map_phys_mem.rs b/tests/map_phys_mem.rs index b19ba987..0ac9a63c 100644 --- a/tests/map_phys_mem.rs +++ b/tests/map_phys_mem.rs @@ -1,15 +1,8 @@ -use bootloader_test_runner::run_test_kernel; +use bootloader_test_runner::define_test; +const CHECK_BOOT_INFO_KERNEL: &str = + env!("CARGO_BIN_FILE_TEST_KERNEL_MAP_PHYS_MEM_check_boot_info"); +const ACCESS_PHYS_MEM_KERNEL: &str = + env!("CARGO_BIN_FILE_TEST_KERNEL_MAP_PHYS_MEM_access_phys_mem"); -#[test] -fn check_boot_info() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_MAP_PHYS_MEM_check_boot_info" - )); -} - -#[test] -fn access_phys_mem() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_MAP_PHYS_MEM_access_phys_mem" - )); -} +define_test!(check_boot_info, CHECK_BOOT_INFO_KERNEL); +define_test!(access_phys_mem, ACCESS_PHYS_MEM_KERNEL); diff --git a/tests/pie.rs b/tests/pie.rs index c2d30d80..dbf1559e 100644 --- a/tests/pie.rs +++ b/tests/pie.rs @@ -1,21 +1,9 @@ -use bootloader_test_runner::run_test_kernel; - -#[test] -fn basic_boot() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_basic_boot")); -} - -#[test] -fn should_panic() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_should_panic")); -} - -#[test] -fn check_boot_info() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_check_boot_info")); -} - -#[test] -fn global_variable() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_global_variable")); -} +use bootloader_test_runner::define_test; +const BASIC_BOOT_KERNEL: &str = env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_basic_boot"); +const SHOULD_PANIC_KERNEL: &str = env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_should_panic"); +const CHECK_BOOT_INFO_KERNEL: &str = env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_check_boot_info"); +const GLOBAL_VARIABLE_KERNEL: &str = env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_global_variable"); +define_test!(basic_boot, BASIC_BOOT_KERNEL); +define_test!(should_panic, SHOULD_PANIC_KERNEL); +define_test!(check_boot_info, CHECK_BOOT_INFO_KERNEL); +define_test!(global_variable, GLOBAL_VARIABLE_KERNEL); diff --git a/tests/ramdisk.rs b/tests/ramdisk.rs new file mode 100644 index 00000000..8f94b20e --- /dev/null +++ b/tests/ramdisk.rs @@ -0,0 +1,6 @@ +use bootloader_test_runner::define_test; +static RAMDISK_PATH: &str = "tests/ramdisk.txt"; +static BASIC_BOOT_KERNEL: &str = env!("CARGO_BIN_FILE_TEST_KERNEL_RAMDISK_basic_boot"); +static RAMDISK_KERNEL: &str = env!("CARGO_BIN_FILE_TEST_KERNEL_RAMDISK_ramdisk"); +define_test!(basic_boot, BASIC_BOOT_KERNEL, RAMDISK_PATH); +define_test!(ramdisk, RAMDISK_KERNEL, RAMDISK_PATH); diff --git a/tests/ramdisk.txt b/tests/ramdisk.txt new file mode 100644 index 00000000..09e6dbc1 --- /dev/null +++ b/tests/ramdisk.txt @@ -0,0 +1 @@ +Test ramdisk. \ No newline at end of file diff --git a/tests/runner/Cargo.toml b/tests/runner/Cargo.toml index 51421aa0..5b84ae44 100644 --- a/tests/runner/Cargo.toml +++ b/tests/runner/Cargo.toml @@ -10,3 +10,6 @@ edition = "2021" bootloader = { path = "../.." } strip-ansi-escapes = "0.1.1" ovmf-prebuilt = "0.1.0-alpha.1" +paste = "1.0.11" +lazy_static = "1.4.0" +rand = "0.8.5" \ No newline at end of file diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index 7f50cd42..67665032 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -1,4 +1,16 @@ -use std::{io::Write, path::Path, process::Command}; +use std::{ + io::Write, + path::{Path, PathBuf}, + process::Command, +}; + +pub extern crate lazy_static; +pub extern crate paste; +#[doc(hidden)] +pub use lazy_static::lazy_static; +#[doc(hidden)] +pub use paste::paste; +use rand::Rng; const QEMU_ARGS: &[&str] = &[ "-device", @@ -10,27 +22,82 @@ const QEMU_ARGS: &[&str] = &[ "--no-reboot", ]; -pub fn run_test_kernel(kernel_binary_path: &str) { +pub fn generate_test_image_filename(path: &Path) -> PathBuf { + let s: String = rand::thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(8) + .map(char::from) + .collect(); + path.with_file_name(s) +} + +pub fn run_test_kernel_bios(kernel_binary_path: &str, ramdisk_path: Option<&str>) { let kernel_path = Path::new(kernel_binary_path); + let ramdisk_path = match ramdisk_path { + Some(rdp) => Some(Path::new(rdp)), + None => None, + }; // create an MBR disk image for legacy BIOS booting - let mbr_path = kernel_path.with_extension("mbr"); - bootloader::BiosBoot::new(kernel_path) - .create_disk_image(&mbr_path) - .unwrap(); + let mbr_path = generate_test_image_filename(kernel_path).with_extension("mbr"); + let mut bios_builder = bootloader::BiosBoot::new(kernel_path); + + // Set ramdisk for test, if supplied. + if let Some(rdp) = ramdisk_path { + bios_builder.set_ramdisk(rdp); + } + + bios_builder.create_disk_image(&mbr_path).unwrap(); + + run_test_kernel_on_bios(&mbr_path); +} + +pub fn run_test_kernel_uefi(kernel_binary_path: &str, ramdisk_path: Option<&str>) { + let kernel_path = Path::new(kernel_binary_path); + let ramdisk_path = match ramdisk_path { + Some(rdp) => Some(Path::new(rdp)), + None => None, + }; // create a GPT disk image for UEFI booting - let gpt_path = kernel_path.with_extension("gpt"); - let uefi_builder = bootloader::UefiBoot::new(kernel_path); + let gpt_path = generate_test_image_filename(kernel_path).with_extension("gpt"); + let mut uefi_builder = bootloader::UefiBoot::new(kernel_path); + + // Set ramdisk for test, if supplied. + if let Some(rdp) = ramdisk_path { + uefi_builder.set_ramdisk(rdp); + } + uefi_builder.create_disk_image(&gpt_path).unwrap(); + run_test_kernel_on_uefi(&gpt_path); +} + +pub fn run_test_kernel(kernel_binary_path: &str, ramdisk_path: Option<&str>) { + run_test_kernel_uefi(kernel_binary_path, ramdisk_path); + run_test_kernel_bios(kernel_binary_path, ramdisk_path); + run_test_kernel_tftp(kernel_binary_path, ramdisk_path); +} + +pub fn run_test_kernel_tftp(kernel_binary_path: &str, ramdisk_path: Option<&str>) { + let kernel_path = Path::new(kernel_binary_path); + let ramdisk_path = match ramdisk_path { + Some(rdp) => Some(Path::new(rdp)), + None => None, + }; + + let mut uefi_builder = bootloader::UefiBoot::new(kernel_path); + + // Set ramdisk for test, if supplied. + if let Some(rdp) = ramdisk_path { + uefi_builder.set_ramdisk(rdp); + } + // create a TFTP folder with the kernel executable and UEFI bootloader for // UEFI PXE booting - let tftp_path = kernel_path.with_extension(".tftp"); + let tftp_path = generate_test_image_filename(kernel_path).with_extension(".tftp"); uefi_builder.create_pxe_tftp_folder(&tftp_path).unwrap(); - run_test_kernel_on_uefi(&gpt_path); - run_test_kernel_on_bios(&mbr_path); run_test_kernel_on_uefi_pxe(&tftp_path); } @@ -103,3 +170,118 @@ pub fn run_test_kernel_on_uefi_pxe(out_tftp_path: &Path) { other => panic!("Test failed with unexpected exit code `{:?}`", other), } } + +#[macro_export] +/// Creates a series of test functions for a given kernel image to cover bios, uefi, and tftp +/// +/// define_test!(name, kernel) will generate all 3 tests, with a ramdisk and no-ramdisk variant. +/// define_test!(name, kernel, ramdisk) will generate all 3 tests, with the specified ramdisk +/// define_test!(name, kernel, without_ramdisk_tests) will generate all 3 tests, with only the no-ramdisk variant +macro_rules! define_test { + ($test_name: ident, $bin: tt) => { + $crate::paste! { + #[test] + fn [< $test_name _uefi_without_ramdisk >]() { + $crate::run_test_kernel_uefi( + $bin, + None + ); + } + + #[test] + fn [< $test_name _tftp_without_ramdisk >]() { + $crate::run_test_kernel_tftp( + $bin, + None + ); + } + + #[test] + fn [< $test_name _bios_without_ramdisk >]() { + $crate::run_test_kernel_bios( + $bin, + None + ); + } + + + #[test] + fn [< $test_name _uefi_with_ramdisk >]() { + $crate::run_test_kernel_uefi( + $bin, + Some("tests/ramdisk.txt") + ); + } + + #[test] + fn [< $test_name _tftp_with_ramdisk >]() { + $crate::run_test_kernel_tftp( + $bin, + Some("tests/ramdisk.txt") + ); + } + + #[test] + fn [< $test_name _bios_with_ramdisk >]() { + $crate::run_test_kernel_bios( + $bin, + Some("tests/ramdisk.txt") + ); + } + } + }; + ($test_name: ident, $bin:tt, without_ramdisk_tests) => { + $crate::paste! { + #[test] + fn [< $test_name _uefi_without_ramdisk >]() { + $crate::run_test_kernel_uefi( + $bin, + None + ); + } + + #[test] + fn [< $test_name _tftp_without_ramdisk >]() { + $crate::run_test_kernel_tftp( + $bin, + None + ); + } + + #[test] + fn [< $test_name _bios_without_ramdisk >]() { + $crate::run_test_kernel_bios( + $bin, + None + ); + } + } + }; + ($test_name: ident, $bin: tt, $ramdisk: tt) => { + $crate::paste! { + #[test] + fn [< $test_name _uefi_with_ramdisk >]() { + $crate::run_test_kernel_uefi( + $bin, + Some($ramdisk) + ); + } + + #[test] + fn [< $test_name _tftp_with_ramdisk >]() { + $crate::run_test_kernel_tftp( + $bin, + Some($ramdisk) + ); + } + + #[test] + fn [< $test_name _bios_with_ramdisk >]() { + $crate::run_test_kernel_bios( + $bin, + Some($ramdisk) + ); + } + } + }; +} diff --git a/tests/test_kernels/default_settings/src/bin/basic_boot.rs b/tests/test_kernels/default_settings/src/bin/basic_boot.rs index e6bd3a0b..8924e1c0 100644 --- a/tests/test_kernels/default_settings/src/bin/basic_boot.rs +++ b/tests/test_kernels/default_settings/src/bin/basic_boot.rs @@ -2,11 +2,13 @@ #![no_main] // disable all Rust-level entry points use bootloader_api::{entry_point, BootInfo}; -use test_kernel_default_settings::{exit_qemu, QemuExitCode}; +use core::fmt::Write; +use test_kernel_default_settings::{exit_qemu, serial, QemuExitCode}; entry_point!(kernel_main); -fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + writeln!(serial(), "Entered kernel with boot info: {:?}", boot_info).unwrap(); exit_qemu(QemuExitCode::Success); } @@ -14,8 +16,6 @@ fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { #[panic_handler] #[cfg(not(test))] fn panic(info: &core::panic::PanicInfo) -> ! { - use core::fmt::Write; - - let _ = writeln!(test_kernel_default_settings::serial(), "PANIC: {}", info); + let _ = writeln!(serial(), "PANIC: {}", info); exit_qemu(QemuExitCode::Failed); } diff --git a/tests/test_kernels/ramdisk/Cargo.toml b/tests/test_kernels/ramdisk/Cargo.toml new file mode 100644 index 00000000..4258b0a5 --- /dev/null +++ b/tests/test_kernels/ramdisk/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "test_kernel_ramdisk" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2021" + +[dependencies] +bootloader_api = { path = "../../../api" } +x86_64 = { version = "0.14.7", default-features = false, features = [ + "instructions", + "inline_asm", +] } +uart_16550 = "0.2.10" diff --git a/tests/test_kernels/ramdisk/src/bin/basic_boot.rs b/tests/test_kernels/ramdisk/src/bin/basic_boot.rs new file mode 100644 index 00000000..515cd22d --- /dev/null +++ b/tests/test_kernels/ramdisk/src/bin/basic_boot.rs @@ -0,0 +1,21 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use bootloader_api::{entry_point, BootInfo}; +use test_kernel_ramdisk::{exit_qemu, QemuExitCode}; + +entry_point!(kernel_main); + +fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { + exit_qemu(QemuExitCode::Success); +} + +/// This function is called on panic. +#[panic_handler] +#[cfg(not(test))] +fn panic(info: &core::panic::PanicInfo) -> ! { + use core::fmt::Write; + + let _ = writeln!(test_kernel_ramdisk::serial(), "PANIC: {}", info); + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/ramdisk/src/bin/ramdisk.rs b/tests/test_kernels/ramdisk/src/bin/ramdisk.rs new file mode 100644 index 00000000..d2bbda87 --- /dev/null +++ b/tests/test_kernels/ramdisk/src/bin/ramdisk.rs @@ -0,0 +1,32 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use bootloader_api::{entry_point, BootInfo}; +use core::{fmt::Write, ptr::slice_from_raw_parts}; +use test_kernel_ramdisk::{exit_qemu, serial, QemuExitCode, RAMDISK_CONTENTS}; + +entry_point!(kernel_main); + +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + writeln!(serial(), "Boot info: {:?}", boot_info).unwrap(); + assert!(boot_info.ramdisk_addr.into_option().is_some()); + assert_eq!(boot_info.ramdisk_len as usize, RAMDISK_CONTENTS.len()); + let actual_ramdisk = unsafe { + &*slice_from_raw_parts( + boot_info.ramdisk_addr.into_option().unwrap() as *const u8, + boot_info.ramdisk_len as usize, + ) + }; + writeln!(serial(), "Actual contents: {:?}", actual_ramdisk).unwrap(); + assert_eq!(RAMDISK_CONTENTS, actual_ramdisk); + + exit_qemu(QemuExitCode::Success); +} + +/// This function is called on panic. +#[cfg(not(test))] +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + let _ = writeln!(test_kernel_ramdisk::serial(), "PANIC: {}", info); + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/ramdisk/src/lib.rs b/tests/test_kernels/ramdisk/src/lib.rs new file mode 100644 index 00000000..00ea92a6 --- /dev/null +++ b/tests/test_kernels/ramdisk/src/lib.rs @@ -0,0 +1,29 @@ +#![no_std] + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum QemuExitCode { + Success = 0x10, + Failed = 0x11, +} + +pub static RAMDISK_CONTENTS: &[u8] = include_bytes!("../../../ramdisk.txt"); + +pub fn exit_qemu(exit_code: QemuExitCode) -> ! { + use x86_64::instructions::{nop, port::Port}; + + unsafe { + let mut port = Port::new(0xf4); + port.write(exit_code as u32); + } + + loop { + nop(); + } +} + +pub fn serial() -> uart_16550::SerialPort { + let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) }; + port.init(); + port +} diff --git a/uefi/Cargo.toml b/uefi/Cargo.toml index a2dfe632..a637db6e 100644 --- a/uefi/Cargo.toml +++ b/uefi/Cargo.toml @@ -12,5 +12,5 @@ repository.workspace = true bootloader_api = { workspace = true } bootloader-x86_64-common = { workspace = true } log = "0.4.14" -uefi = "0.16.0" +uefi = "0.18.0" x86_64 = "0.14.8" diff --git a/uefi/src/main.rs b/uefi/src/main.rs index d96d60c1..edd6f834 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -8,7 +8,12 @@ use bootloader_api::{info::FrameBufferInfo, BootloaderConfig}; use bootloader_x86_64_common::{ legacy_memory_region::LegacyFrameAllocator, Kernel, RawFrameBufferInfo, SystemInfo, }; -use core::{cell::UnsafeCell, fmt::Write, mem, ptr, slice}; +use core::{ + cell::UnsafeCell, + fmt::Write, + ops::{Deref, DerefMut}, + ptr, slice, +}; use uefi::{ prelude::{entry, Boot, Handle, Status, SystemTable}, proto::{ @@ -23,9 +28,10 @@ use uefi::{ pxe::{BaseCode, DhcpV4Packet}, IpAddress, }, + ProtocolPointer, }, table::boot::{ - AllocateType, MemoryDescriptor, MemoryType, OpenProtocolAttributes, OpenProtocolParams, + AllocateType, MemoryType, OpenProtocolAttributes, OpenProtocolParams, ScopedProtocol, }, CStr16, CStr8, }; @@ -63,39 +69,77 @@ fn efi_main(image: Handle, st: SystemTable) -> Status { fn main_inner(image: Handle, mut st: SystemTable) -> Status { // temporarily clone the y table for printing panics + unsafe { *SYSTEM_TABLE.get() = Some(st.unsafe_clone()); } + let stdout = unsafe { &mut *SYSTEM_TABLE.get() }; + let stdout = stdout.as_mut().unwrap(); + let stdout = stdout.stdout(); + stdout.clear().unwrap(); + writeln!(stdout, "UEFI bootloader started; trying to load kernel").unwrap(); + + let mut boot_mode = BootMode::Disk; + let mut kernel = load_kernel(image, &mut st, boot_mode); + if kernel.is_none() { + writeln!( + stdout, + "Failed to load kernel via {:?}, trying TFTP", + boot_mode + ) + .unwrap(); + // Try TFTP boot + boot_mode = BootMode::Tftp; + kernel = load_kernel(image, &mut st, boot_mode); + } + let kernel = kernel.expect("Failed to load kernel"); + writeln!(stdout, "Trying to load ramdisk via {:?}", boot_mode).unwrap(); + // Ramdisk must load from same source, or not at all. + let ramdisk = load_ramdisk(image, &mut st, boot_mode); - st.stdout().clear().unwrap(); writeln!( - st.stdout(), - "UEFI bootloader started; trying to load kernel" + stdout, + "{}", + match ramdisk { + Some(_) => "Loaded ramdisk", + None => "Ramdisk not found.", + } ) .unwrap(); - let kernel = load_kernel(image, &st); - let framebuffer = init_logger(&st, kernel.config); - - // we no longer need the system table for printing panics unsafe { *SYSTEM_TABLE.get() = None; } - log::info!("UEFI bootloader started"); log::info!("Reading kernel and configuration from disk was successful"); if let Some(framebuffer) = framebuffer { log::info!("Using framebuffer at {:#x}", framebuffer.addr); } - let mmap_storage = { - let max_mmap_size = - st.boot_services().memory_map_size().map_size + 8 * mem::size_of::(); - let ptr = st - .boot_services() - .allocate_pool(MemoryType::LOADER_DATA, max_mmap_size)?; - unsafe { slice::from_raw_parts_mut(ptr, max_mmap_size) } + let mut memory_map_size = st.boot_services().memory_map_size(); + let mut target_size = memory_map_size.map_size + (8 * memory_map_size.entry_size); + let mut storage: &mut [u8]; + loop { + let ptr = st + .boot_services() + .allocate_pool(MemoryType::LOADER_DATA, target_size) + .expect("Failed to allocate memory for mmap storage"); + storage = unsafe { slice::from_raw_parts_mut(ptr, target_size) }; + if let Err(_) = st.boot_services().memory_map(storage) { + memory_map_size = st.boot_services().memory_map_size(); + // By measuring the size here, we can find out exactly how much we need. + // We may hit this code twice, if the map allocation ends up spanning more pages. + let next_target_size = memory_map_size.map_size + (8 * memory_map_size.entry_size); + target_size = next_target_size; + st.boot_services() + .free_pool(ptr) + .expect("Failed to free temporary memory for memory map!"); + continue; + } + break; + } + storage }; log::trace!("exiting boot services"); @@ -107,7 +151,13 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { LegacyFrameAllocator::new(memory_map.copied().map(UefiMemoryDescriptor)); let page_tables = create_page_tables(&mut frame_allocator); - + let mut ramdisk_len = 0u64; + let ramdisk_addr = if let Some(rd) = ramdisk { + ramdisk_len = rd.len() as u64; + Some(rd.as_ptr() as usize as u64) + } else { + None + }; let system_info = SystemInfo { framebuffer, rsdp_addr: { @@ -120,6 +170,8 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { .or_else(|| config_entries.find(|entry| matches!(entry.guid, cfg::ACPI_GUID))); rsdp.map(|entry| PhysAddr::new(entry.address as u64)) }, + ramdisk_addr: ramdisk_addr, + ramdisk_len: ramdisk_len, }; bootloader_x86_64_common::load_and_switch_to_kernel( @@ -130,51 +182,101 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { ); } -fn load_kernel(image: Handle, st: &SystemTable) -> Kernel<'static> { - let kernel_slice = load_kernel_file(image, st).expect("couldn't find kernel"); - Kernel::parse(kernel_slice) +#[derive(Clone, Copy, Debug)] +pub enum BootMode { + Disk, + Tftp, } -/// Try to load a kernel file from the boot device. -fn load_kernel_file(image: Handle, st: &SystemTable) -> Option<&'static mut [u8]> { - load_kernel_file_from_disk(image, st) - .or_else(|| load_kernel_file_from_tftp_boot_server(image, st)) +fn load_ramdisk( + image: Handle, + st: &mut SystemTable, + boot_mode: BootMode, +) -> Option<&'static mut [u8]> { + load_file_from_boot_method(image, st, "ramdisk\0", boot_mode) } -fn load_kernel_file_from_disk(image: Handle, st: &SystemTable) -> Option<&'static mut [u8]> { - let file_system_raw = { - let this = st.boot_services(); - let loaded_image = this - .open_protocol::( - OpenProtocolParams { - handle: image, - agent: image, - controller: None, - }, - OpenProtocolAttributes::Exclusive, - ) - .expect("Failed to retrieve `LoadedImage` protocol from handle"); - let loaded_image = unsafe { &*loaded_image.interface.get() }; +fn load_kernel( + image: Handle, + st: &mut SystemTable, + boot_mode: BootMode, +) -> Option> { + let kernel_slice = load_file_from_boot_method(image, st, "kernel-x86_64\0", boot_mode)?; + Some(Kernel::parse(kernel_slice)) +} - let device_handle = loaded_image.device(); +fn load_file_from_boot_method( + image: Handle, + st: &mut SystemTable, + filename: &str, + boot_mode: BootMode, +) -> Option<&'static mut [u8]> { + match boot_mode { + BootMode::Disk => load_file_from_disk(filename, image, st), + BootMode::Tftp => load_file_from_tftp_boot_server(filename, image, st), + } +} - let device_path = this - .open_protocol::( - OpenProtocolParams { - handle: device_handle, - agent: image, - controller: None, - }, - OpenProtocolAttributes::Exclusive, - ) - .expect("Failed to retrieve `DevicePath` protocol from image's device handle"); - let mut device_path = unsafe { &*device_path.interface.get() }; +fn open_device_path_protocol( + image: Handle, + st: &SystemTable, +) -> Option> { + let this = st.boot_services(); + let loaded_image = unsafe { + this.open_protocol::( + OpenProtocolParams { + handle: image, + agent: image, + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) + }; + + if loaded_image.is_err() { + log::error!("Failed to open protocol LoadedImage"); + return None; + } + let loaded_image = loaded_image.unwrap(); + let loaded_image = loaded_image.deref(); + + let device_handle = loaded_image.device(); + + let device_path = unsafe { + this.open_protocol::( + OpenProtocolParams { + handle: device_handle, + agent: image, + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) + }; + if device_path.is_err() { + log::error!("Failed to open protocol DevicePath"); + return None; + } + Some(device_path.unwrap()) +} - let fs_handle = this - .locate_device_path::(&mut device_path) - .ok()?; +fn locate_and_open_protocol( + image: Handle, + st: &SystemTable, +) -> Option> { + let this = st.boot_services(); + let mut device_path = open_device_path_protocol(image, st)?; + let mut device_path = device_path.deref(); + + let fs_handle = this.locate_device_path::

(&mut device_path); + if fs_handle.is_err() { + log::error!("Failed to open device path"); + return None; + } + + let fs_handle = fs_handle.unwrap(); - this.open_protocol::( + let opened_handle = unsafe { + this.open_protocol::

( OpenProtocolParams { handle: fs_handle, agent: image, @@ -182,121 +284,101 @@ fn load_kernel_file_from_disk(image: Handle, st: &SystemTable) -> Option<& }, OpenProtocolAttributes::Exclusive, ) + }; + + if opened_handle.is_err() { + log::error!("Failed to open protocol {}", core::any::type_name::

()); + return None; } - .unwrap(); - let file_system = unsafe { &mut *file_system_raw.interface.get() }; + Some(opened_handle.unwrap()) +} + +fn load_file_from_disk( + name: &str, + image: Handle, + st: &SystemTable, +) -> Option<&'static mut [u8]> { + let mut file_system_raw = locate_and_open_protocol::(image, st)?; + let file_system = file_system_raw.deref_mut(); let mut root = file_system.open_volume().unwrap(); - let mut buf = [0; 14 * 2]; - let filename = CStr16::from_str_with_buf("kernel-x86_64", &mut buf).unwrap(); - let kernel_file_handle = root - .open(filename, FileMode::Read, FileAttribute::empty()) - .expect("Failed to load kernel (expected file named `kernel-x86_64`)"); - let mut kernel_file = match kernel_file_handle.into_type().unwrap() { + let mut buf = [0u16; 256]; + assert!(name.len() < 256); + let filename = CStr16::from_str_with_buf(name.trim_end_matches('\0'), &mut buf) + .expect("Failed to convert string to utf16"); + + let file_handle_result = root.open(filename, FileMode::Read, FileAttribute::empty()); + + if file_handle_result.is_err() { + return None; + } + + let file_handle = file_handle_result.unwrap(); + + let mut file = match file_handle.into_type().unwrap() { uefi::proto::media::file::FileType::Regular(f) => f, uefi::proto::media::file::FileType::Dir(_) => panic!(), }; let mut buf = [0; 500]; - let kernel_info: &mut FileInfo = kernel_file.get_info(&mut buf).unwrap(); - let kernel_size = usize::try_from(kernel_info.file_size()).unwrap(); + let file_info: &mut FileInfo = file.get_info(&mut buf).unwrap(); + let file_size = usize::try_from(file_info.file_size()).unwrap(); - let kernel_ptr = st + let file_ptr = st .boot_services() .allocate_pages( AllocateType::AnyPages, MemoryType::LOADER_DATA, - ((kernel_size - 1) / 4096) + 1, + ((file_size - 1) / 4096) + 1, ) .unwrap() as *mut u8; - unsafe { ptr::write_bytes(kernel_ptr, 0, kernel_size) }; - let kernel_slice = unsafe { slice::from_raw_parts_mut(kernel_ptr, kernel_size) }; - kernel_file.read(kernel_slice).unwrap(); + unsafe { ptr::write_bytes(file_ptr, 0, file_size) }; + let file_slice = unsafe { slice::from_raw_parts_mut(file_ptr, file_size) }; + file.read(file_slice).unwrap(); - Some(kernel_slice) + Some(file_slice) } /// Try to load a kernel from a TFTP boot server. -fn load_kernel_file_from_tftp_boot_server( +fn load_file_from_tftp_boot_server( + name: &str, image: Handle, st: &SystemTable, ) -> Option<&'static mut [u8]> { - let this = st.boot_services(); - - // Try to locate a `BaseCode` protocol on the boot device. - - let loaded_image = this - .open_protocol::( - OpenProtocolParams { - handle: image, - agent: image, - controller: None, - }, - OpenProtocolAttributes::Exclusive, - ) - .expect("Failed to retrieve `LoadedImage` protocol from handle"); - let loaded_image = unsafe { &*loaded_image.interface.get() }; - - let device_handle = loaded_image.device(); - - let device_path = this - .open_protocol::( - OpenProtocolParams { - handle: device_handle, - agent: image, - controller: None, - }, - OpenProtocolAttributes::Exclusive, - ) - .expect("Failed to retrieve `DevicePath` protocol from image's device handle"); - let mut device_path = unsafe { &*device_path.interface.get() }; - - let base_code_handle = this.locate_device_path::(&mut device_path).ok()?; - - let base_code_raw = this - .open_protocol::( - OpenProtocolParams { - handle: base_code_handle, - agent: image, - controller: None, - }, - OpenProtocolAttributes::Exclusive, - ) - .unwrap(); - let base_code = unsafe { &mut *base_code_raw.interface.get() }; + let mut base_code_raw = locate_and_open_protocol::(image, st)?; + let base_code = base_code_raw.deref_mut(); // Find the TFTP boot server. let mode = base_code.mode(); assert!(mode.dhcp_ack_received); let dhcpv4: &DhcpV4Packet = mode.dhcp_ack.as_ref(); let server_ip = IpAddress::new_v4(dhcpv4.bootp_si_addr); + let mut buf = [0u8; 256]; + assert!(name.len() < 256); - let filename = CStr8::from_bytes_with_nul(b"kernel-x86_64\0").unwrap(); + let filename = CStr8::from_bytes_with_nul(name.as_bytes()).unwrap(); // Determine the kernel file size. - let file_size = base_code - .tftp_get_file_size(&server_ip, filename) - .expect("Failed to query the kernel file size"); - let kernel_size = - usize::try_from(file_size).expect("The kernel file size should fit into usize"); + let file_size = base_code.tftp_get_file_size(&server_ip, &filename).ok()?; + let kernel_size = usize::try_from(file_size).expect("The file size should fit into usize"); // Allocate some memory for the kernel file. - let kernel_ptr = st + let ptr = st .boot_services() .allocate_pages( AllocateType::AnyPages, MemoryType::LOADER_DATA, ((kernel_size - 1) / 4096) + 1, ) - .expect("Failed to allocate memory for the kernel file") as *mut u8; - let kernel_slice = unsafe { slice::from_raw_parts_mut(kernel_ptr, kernel_size) }; + .expect("Failed to allocate memory for the file") as *mut u8; + let slice = unsafe { slice::from_raw_parts_mut(ptr, kernel_size) }; // Load the kernel file. base_code - .tftp_read_file(&server_ip, filename, Some(kernel_slice)) + .tftp_read_file(&server_ip, &filename, Some(slice)) .expect("Failed to read kernel file from the TFTP boot server"); - Some(kernel_slice) + Some(slice) } /// Creates page table abstraction types for both the bootloader and kernel page tables. @@ -366,11 +448,22 @@ fn create_page_tables( } fn init_logger(st: &SystemTable, config: BootloaderConfig) -> Option { - let gop = st + let gop_handle = st .boot_services() - .locate_protocol::() + .get_handle_for_protocol::() .ok()?; - let gop = unsafe { &mut *gop.get() }; + let mut gop = unsafe { + st.boot_services() + .open_protocol::( + OpenProtocolParams { + handle: gop_handle, + agent: st.boot_services().image_handle(), + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) + .ok()? + }; let mode = { let modes = gop.modes(); @@ -418,9 +511,8 @@ fn init_logger(st: &SystemTable, config: BootloaderConfig) -> Option