diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index db938864..ec952c33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,14 +62,6 @@ jobs: - name: Run integration tests run: cargo test - # test feature gates (only on one OS is enough) - - name: Test with only UEFI feature - if: runner.os == 'Linux' - run: cargo test --no-default-features --features uefi - - name: Test with only BIOS feature - if: runner.os == 'Linux' - run: cargo test --no-default-features --features bios - fmt: name: Check Formatting runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index a9a70725..415b0b55 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]] @@ -597,6 +598,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" @@ -649,9 +659,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", @@ -661,9 +671,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 e1364c64..b2c31837 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"] @@ -37,10 +38,12 @@ bootloader_api = { version = "0.11.0", path = "api" } bootloader-x86_64-common = { version = "0.11.0", path = "common" } bootloader-x86_64-bios-common = { version = "0.11.0", path = "bios/common" } + [features] -default = ["bios", "uefi"] -bios = ["dep:mbrman", "bootloader_test_runner/bios"] -uefi = ["dep:gpt", "bootloader_test_runner/uefi"] +default = ["bios", "uefi", "pxe"] +bios = ["mbrman"] +uefi = ["gpt"] +pxe = ["uefi"] [dependencies] anyhow = "1.0.32" @@ -55,6 +58,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 f2eb9548..99235653 100644 --- a/api/build.rs +++ b/api/build.rs @@ -22,9 +22,10 @@ fn main() { (88, 9), (97, 9), (106, 9), - (115, 1), - (116, 1), - (117, 1), + (115, 9), + (124, 1), + (125, 1), + (126, 1), ]; let mut code = String::new(); diff --git a/api/src/config.rs b/api/src/config.rs index a94ebdaf..7b91a4bd 100644 --- a/api/src/config.rs +++ b/api/src/config.rs @@ -49,7 +49,7 @@ impl BootloaderConfig { 0x3D, ]; #[doc(hidden)] - pub const SERIALIZED_LEN: usize = 118; + pub const SERIALIZED_LEN: usize = 127; /// Creates a new default configuration with the following values: /// @@ -97,6 +97,7 @@ impl BootloaderConfig { aslr, dynamic_range_start, dynamic_range_end, + ramdisk_memory, } = mappings; let FrameBuffer { minimum_framebuffer_height, @@ -145,7 +146,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], @@ -153,7 +156,7 @@ impl BootloaderConfig { }, ); - let buf = concat_106_9( + let buf = concat_115_9( buf, match minimum_framebuffer_width { Option::None => [0; 9], @@ -161,12 +164,12 @@ impl BootloaderConfig { }, ); - let log_level = concat_115_1(buf, (*log_level as u8).to_le_bytes()); + let log_level = concat_124_1(buf, (*log_level as u8).to_le_bytes()); let frame_buffer_logger_status = - concat_116_1(log_level, (*frame_buffer_logger_status as u8).to_le_bytes()); + concat_125_1(log_level, (*frame_buffer_logger_status as u8).to_le_bytes()); - concat_117_1( + concat_126_1( frame_buffer_logger_status, (*serial_logger_status as u8).to_le_bytes(), ) @@ -227,6 +230,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)?, @@ -257,6 +261,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) }; @@ -439,6 +444,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 { @@ -455,6 +463,7 @@ impl Mappings { aslr: false, dynamic_range_start: None, dynamic_range_end: None, + ramdisk_memory: Mapping::new_default(), } } @@ -487,6 +496,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..a1ba8566 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -98,6 +98,25 @@ 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(); + let kernel_page_size = (((kernel_len - 1) / 4096) + 1) as usize; + let ramdisk_start = KERNEL_DST.wrapping_add(kernel_page_size * 4096); + writeln!(screen::Writer, "Loading ramdisk...").unwrap(); + let ramdisk_len = match try_load_file( + "ramdisk", + KERNEL_DST.wrapping_add(kernel_page_size * 4096), + &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_start:#p}").unwrap(); + } let memory_map = unsafe { memory_map::query_memory_map() }.unwrap(); writeln!(screen::Writer, "{memory_map:x?}").unwrap(); @@ -129,6 +148,10 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { start: KERNEL_DST as u64, len: kernel_len, }, + ramdisk: Region { + start: ramdisk_start 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 +174,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 +217,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 d5685e9d..d9e10843 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -56,14 +56,18 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { PhysAddr::new(info.kernel.start) }; let kernel_size = info.kernel.len; - let mut frame_allocator = { - 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), - ) + let next_free_frame = match info.ramdisk.len { + 0 => PhysFrame::containing_address(kernel_start + kernel_size - 1u64) + 1, + _ => { + PhysFrame::containing_address(PhysAddr::new( + info.ramdisk.start + info.ramdisk.len - 1u64, + )) + 1 + } }; + let mut frame_allocator = LegacyFrameAllocator::new_starting_at( + next_free_frame, + memory_map.iter().copied().map(MemoryRegion), + ); // We identity-mapped all memory, so the offset between physical and virtual addresses is 0 let phys_offset = VirtAddr::new(0); @@ -126,6 +130,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/build.rs b/build.rs index e752d28f..bb2118c0 100644 --- a/build.rs +++ b/build.rs @@ -6,46 +6,50 @@ use std::{ const BOOTLOADER_VERSION: &str = env!("CARGO_PKG_VERSION"); fn main() { - let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + #[cfg(any(feature = "uefi", feature = "pxe"))] + uefi_main(); + #[cfg(feature = "bios")] + bios_main(); +} - #[cfg(feature = "uefi")] - { - let uefi_path = build_uefi_bootloader(&out_dir); - println!( - "cargo:rustc-env=UEFI_BOOTLOADER_PATH={}", - uefi_path.display() - ); - } +#[cfg(all(any(feature = "uefi", feature = "pxe"), not(docsrs_dummy_build)))] +fn uefi_main() { + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + let uefi_path = build_uefi_bootloader(&out_dir); + println!( + "cargo:rustc-env=UEFI_BOOTLOADER_PATH={}", + uefi_path.display() + ); +} - #[cfg(feature = "bios")] - { - let bios_boot_sector_path = build_bios_boot_sector(&out_dir); - println!( - "cargo:rustc-env=BIOS_BOOT_SECTOR_PATH={}", - bios_boot_sector_path.display() - ); - let bios_stage_2_path = build_bios_stage_2(&out_dir); - println!( - "cargo:rustc-env=BIOS_STAGE_2_PATH={}", - bios_stage_2_path.display() - ); +#[cfg(all(feature = "bios", not(docsrs_dummy_build)))] +fn bios_main() { + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + let bios_boot_sector_path = build_bios_boot_sector(&out_dir); + println!( + "cargo:rustc-env=BIOS_BOOT_SECTOR_PATH={}", + bios_boot_sector_path.display() + ); + let bios_stage_2_path = build_bios_stage_2(&out_dir); + println!( + "cargo:rustc-env=BIOS_STAGE_2_PATH={}", + bios_stage_2_path.display() + ); - let bios_stage_3_path = build_bios_stage_3(&out_dir); - println!( - "cargo:rustc-env=BIOS_STAGE_3_PATH={}", - bios_stage_3_path.display() - ); + let bios_stage_3_path = build_bios_stage_3(&out_dir); + println!( + "cargo:rustc-env=BIOS_STAGE_3_PATH={}", + bios_stage_3_path.display() + ); - let bios_stage_4_path = build_bios_stage_4(&out_dir); - println!( - "cargo:rustc-env=BIOS_STAGE_4_PATH={}", - bios_stage_4_path.display() - ); - } + let bios_stage_4_path = build_bios_stage_4(&out_dir); + println!( + "cargo:rustc-env=BIOS_STAGE_4_PATH={}", + bios_stage_4_path.display() + ); } -#[cfg(not(docsrs_dummy_build))] -#[cfg(feature = "uefi")] +#[cfg(all(any(feature = "uefi", feature = "pxe"), not(docsrs_dummy_build)))] fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); @@ -79,8 +83,7 @@ fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { } } -#[cfg(not(docsrs_dummy_build))] -#[cfg(feature = "bios")] +#[cfg(all(feature = "bios", not(docsrs_dummy_build)))] fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); @@ -122,8 +125,7 @@ fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { convert_elf_to_bin(elf_path) } -#[cfg(not(docsrs_dummy_build))] -#[cfg(feature = "bios")] +#[cfg(all(feature = "bios", not(docsrs_dummy_build)))] fn build_bios_stage_2(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); @@ -163,8 +165,7 @@ fn build_bios_stage_2(out_dir: &Path) -> PathBuf { convert_elf_to_bin(elf_path) } -#[cfg(not(docsrs_dummy_build))] -#[cfg(feature = "bios")] +#[cfg(all(feature = "bios", not(docsrs_dummy_build)))] fn build_bios_stage_3(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); @@ -204,8 +205,7 @@ fn build_bios_stage_3(out_dir: &Path) -> PathBuf { convert_elf_to_bin(elf_path) } -#[cfg(not(docsrs_dummy_build))] -#[cfg(feature = "bios")] +#[cfg(all(feature = "bios", not(docsrs_dummy_build)))] fn build_bios_stage_4(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); @@ -246,7 +246,7 @@ fn build_bios_stage_4(out_dir: &Path) -> PathBuf { convert_elf_to_bin(elf_path) } -#[cfg(feature = "bios")] +#[cfg(all(feature = "bios", not(docsrs_dummy_build)))] fn convert_elf_to_bin(elf_path: PathBuf) -> PathBuf { let flat_binary_path = elf_path.with_extension("bin"); @@ -276,23 +276,10 @@ fn convert_elf_to_bin(elf_path: PathBuf) -> PathBuf { // dummy implementations because docsrs builds have no network access -#[cfg(docsrs_dummy_build)] -fn build_uefi_bootloader(_out_dir: &Path) -> PathBuf { - PathBuf::new() -} -#[cfg(docsrs_dummy_build)] -fn build_bios_boot_sector(_out_dir: &Path) -> PathBuf { - PathBuf::new() -} -#[cfg(docsrs_dummy_build)] -fn build_bios_stage_2(_out_dir: &Path) -> PathBuf { - PathBuf::new() -} -#[cfg(docsrs_dummy_build)] -fn build_bios_stage_3(_out_dir: &Path) -> PathBuf { - PathBuf::new() -} -#[cfg(docsrs_dummy_build)] -fn build_bios_stage_4(_out_dir: &Path) -> PathBuf { - PathBuf::new() +#[cfg(all(feature = "uefi", docsrs_dummy_build))] +fn uefi_main() { + // stub } + +#[cfg(all(feature = "bios", docsrs_dummy_build))] +fn bios_main() {} diff --git a/common/src/lib.rs b/common/src/lib.rs index b431215e..ad92556f 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -77,6 +77,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. @@ -137,6 +139,7 @@ where &mut page_tables, system_info.framebuffer.as_ref(), &config, + &system_info, ); let boot_info = create_boot_info( &config, @@ -168,6 +171,7 @@ pub fn set_up_mappings( page_tables: &mut PageTables, framebuffer: Option<&RawFrameBufferInfo>, config: &BootloaderConfig, + system_info: &SystemInfo, ) -> Mappings where I: ExactSizeIterator + Clone, @@ -199,7 +203,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, @@ -284,6 +287,40 @@ 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, + Size4KiB::SIZE, + &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; + let ramdisk_physical_end_page = ramdisk_physical_start_page + ramdisk_page_count; + let start_page = Page::from_start_address(ramdisk_address_start) + .expect("the ramdisk start address must be page aligned"); + + 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"); @@ -358,6 +395,8 @@ where kernel_slice_start, kernel_slice_len, + ramdisk_slice_start, + ramdisk_slice_len, } } @@ -383,6 +422,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. @@ -492,6 +533,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/bios/mod.rs b/src/bios/mod.rs deleted file mode 100644 index 03952152..00000000 --- a/src/bios/mod.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::fat; -use anyhow::Context; -use std::{ - collections::BTreeMap, - path::{Path, PathBuf}, -}; -use tempfile::NamedTempFile; - -mod mbr; - -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, -} - -impl BiosBoot { - /// Start creating a disk image for the given bootloader ELF executable. - pub fn new(kernel_path: &Path) -> Self { - Self { - kernel: kernel_path.to_owned(), - } - } - - /// 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")); - let stage_2_path = Path::new(env!("BIOS_STAGE_2_PATH")); - - let fat_partition = self - .create_fat_partition() - .context("failed to create FAT partition")?; - - mbr::create_mbr_disk( - bootsector_path, - stage_2_path, - fat_partition.path(), - out_path, - ) - .context("failed to create BIOS MBR disk image")?; - - fat_partition - .close() - .context("failed to delete FAT partition after disk image creation")?; - - Ok(()) - } - - /// Creates an BIOS-bootable FAT partition with the kernel. - 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 mut files = BTreeMap::new(); - files.insert(crate::KERNEL_FILE_NAME, self.kernel.as_path()); - files.insert(BIOS_STAGE_3, stage_3_path); - files.insert(BIOS_STAGE_4, stage_4_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")?; - - Ok(out_file) - } -} diff --git a/src/uefi/gpt.rs b/src/gpt.rs similarity index 100% rename from src/uefi/gpt.rs rename to src/gpt.rs diff --git a/src/lib.rs b/src/lib.rs index cd41805a..4f172791 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,16 +4,162 @@ An experimental x86_64 bootloader that works on both BIOS and UEFI systems. #![warn(missing_docs)] -#[cfg(feature = "bios")] -mod bios; +use anyhow::Context; +use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, +}; +use tempfile::NamedTempFile; + mod fat; #[cfg(feature = "uefi")] -mod uefi; +mod gpt; #[cfg(feature = "bios")] -pub use bios::BiosBoot; - -#[cfg(feature = "uefi")] -pub use uefi::UefiBoot; +mod mbr; const KERNEL_FILE_NAME: &str = "kernel-x86_64"; +const RAMDISK_FILE_NAME: &str = "ramdisk"; + +struct DiskImageFile<'a> { + source: &'a PathBuf, + destination: &'a str, +} + +/// DiskImageBuilder helps create disk images for a specified set of files. +/// It can currently create MBR (BIOS), GPT (UEFI), and TFTP (UEFI) images. +pub struct DiskImageBuilder<'a> { + files: Vec>, +} + +impl<'a> DiskImageBuilder<'a> { + /// Create a new instance of DiskImageBuilder, with the specified kernel. + pub fn new(kernel: &'a PathBuf) -> Self { + let mut obj = Self::empty(); + obj.set_kernel(kernel); + obj + } + + /// Create a new, empty instance of DiskImageBuilder + pub fn empty() -> Self { + Self { files: Vec::new() } + } + /// Add or replace a ramdisk to be included in the final image. + pub fn set_ramdisk(&mut self, path: &'a PathBuf) { + self.add_or_replace_file(path, RAMDISK_FILE_NAME); + } + + /// Add or replace a kernel to be included in the final image. + pub fn set_kernel(&mut self, path: &'a PathBuf) { + self.add_or_replace_file(path, KERNEL_FILE_NAME) + } + + /// Add or replace arbitrary files. + /// NOTE: You can overwrite internal files if you choose, such as EFI/BOOT/BOOTX64.EFI + /// This can be useful in situations where you want to generate an image, but not use the provided bootloader. + pub fn add_or_replace_file(&mut self, path: &'a PathBuf, target: &'a str) { + self.files.insert( + 0, + DiskImageFile::<'a> { + source: &path, + destination: &target, + }, + ); + } + fn create_fat_filesystem_image( + &self, + internal_files: BTreeMap<&'a str, &'a Path>, + ) -> anyhow::Result { + let mut local_map = BTreeMap::new(); + + for k in internal_files { + local_map.insert(k.0, k.1); + } + + for f in self.files.as_slice() { + local_map.insert(f.destination, &f.source.as_path()); + } + + let out_file = NamedTempFile::new().context("failed to create temp file")?; + fat::create_fat_filesystem(local_map, out_file.path()) + .context("failed to create BIOS FAT filesystem")?; + + Ok(out_file) + } + #[cfg(feature = "bios")] + /// Create an MBR disk image for booting on BIOS systems. + pub fn create_bios_image(&self, image_filename: &Path) -> anyhow::Result<()> { + const BIOS_STAGE_3: &str = "boot-stage-3"; + const BIOS_STAGE_4: &str = "boot-stage-4"; + let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH")); + let stage_2_path = Path::new(env!("BIOS_STAGE_2_PATH")); + let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH")); + let stage_4_path = Path::new(env!("BIOS_STAGE_4_PATH")); + let mut internal_files = BTreeMap::new(); + internal_files.insert(BIOS_STAGE_3, stage_3_path); + internal_files.insert(BIOS_STAGE_4, stage_4_path); + + let fat_partition = self + .create_fat_filesystem_image(internal_files) + .context("failed to create FAT partition")?; + mbr::create_mbr_disk( + bootsector_path, + stage_2_path, + fat_partition.path(), + image_filename, + ) + .context("failed to create BIOS MBR disk image")?; + + fat_partition + .close() + .context("failed to delete FAT partition after disk image creation")?; + Ok(()) + } + + #[cfg(feature = "uefi")] + /// Create a GPT disk image for booting on UEFI systems. + pub fn create_uefi_image(&self, image_filename: &Path) -> anyhow::Result<()> { + const UEFI_BOOT_FILENAME: &str = "efi/boot/bootx64.efi"; + let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); + let mut internal_files = BTreeMap::new(); + internal_files.insert(UEFI_BOOT_FILENAME, bootloader_path); + let fat_partition = self + .create_fat_filesystem_image(internal_files) + .context("failed to create FAT partition")?; + gpt::create_gpt_disk(fat_partition.path(), image_filename) + .context("failed to create UEFI GPT disk image")?; + fat_partition + .close() + .context("failed to delete FAT partition after disk image creation")?; + + Ok(()) + } + + #[cfg(all(feature = "uefi", feature = "pxe"))] + /// Create a folder containing the needed files for UEFI TFTP/PXE booting. + pub fn create_uefi_tftp_folder(&self, tftp_path: &Path) -> anyhow::Result<()> { + const UEFI_TFTP_BOOT_FILENAME: &str = "bootloader"; + let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); + std::fs::create_dir_all(tftp_path) + .with_context(|| format!("failed to create out dir at {}", tftp_path.display()))?; + + let to = tftp_path.join(UEFI_TFTP_BOOT_FILENAME); + std::fs::copy(bootloader_path, &to).with_context(|| { + format!( + "failed to copy bootloader from {} to {}", + bootloader_path.display(), + to.display() + ) + })?; + + for f in self.files.as_slice() { + let to = tftp_path.join(f.destination); + let result = std::fs::copy(f.source, to); + if result.is_err() { + return Err(anyhow::Error::from(result.unwrap_err())); + } + } + + Ok(()) + } +} diff --git a/src/bios/mbr.rs b/src/mbr.rs similarity index 100% rename from src/bios/mbr.rs rename to src/mbr.rs diff --git a/src/uefi/mod.rs b/src/uefi/mod.rs deleted file mode 100644 index cfbc8c9f..00000000 --- a/src/uefi/mod.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::fat; -use anyhow::Context; -use std::{ - collections::BTreeMap, - path::{Path, PathBuf}, -}; -use tempfile::NamedTempFile; - -mod gpt; -mod pxe; - -/// Create disk images for booting on UEFI systems. -pub struct UefiBoot { - kernel: PathBuf, -} - -impl UefiBoot { - /// Start creating a disk image for the given bootloader ELF executable. - pub fn new(kernel_path: &Path) -> Self { - Self { - kernel: kernel_path.to_owned(), - } - } - - /// 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 - .create_fat_partition() - .context("failed to create FAT partition")?; - - gpt::create_gpt_disk(fat_partition.path(), out_path) - .context("failed to create UEFI GPT disk image")?; - - fat_partition - .close() - .context("failed to delete FAT partition after disk image creation")?; - - Ok(()) - } - - /// Prepare a folder for use with booting over UEFI_PXE. - /// - /// This places the bootloader executable under the path "bootloader". The - /// DHCP server should set the filename option to that path, otherwise the - /// 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")?; - - Ok(()) - } - - /// 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 mut files = BTreeMap::new(); - files.insert("efi/boot/bootx64.efi", bootloader_path); - files.insert(crate::KERNEL_FILE_NAME, self.kernel.as_path()); - - let out_file = NamedTempFile::new().context("failed to create temp file")?; - fat::create_fat_filesystem(files, out_file.path()) - .context("failed to create UEFI FAT filesystem")?; - - Ok(out_file) - } -} diff --git a/src/uefi/pxe.rs b/src/uefi/pxe.rs deleted file mode 100644 index 9329cec2..00000000 --- a/src/uefi/pxe.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::path::Path; - -use anyhow::Context; - -pub fn create_uefi_tftp_folder( - bootloader_path: &Path, - kernel_binary: &Path, - out_path: &Path, -) -> anyhow::Result<()> { - std::fs::create_dir_all(out_path) - .with_context(|| format!("failed to create out dir at {}", out_path.display()))?; - - let to = out_path.join("bootloader"); - std::fs::copy(bootloader_path, &to).with_context(|| { - format!( - "failed to copy bootloader from {} to {}", - bootloader_path.display(), - to.display() - ) - })?; - - let to = out_path.join("kernel-x86_64"); - std::fs::copy(kernel_binary, &to).with_context(|| { - format!( - "failed to copy kernel from {} to {}", - kernel_binary.display(), - to.display() - ) - })?; - - Ok(()) -} diff --git a/tests/default_settings.rs b/tests/default_settings.rs index d610508c..0a7e6c47 100644 --- a/tests/default_settings.rs +++ b/tests/default_settings.rs @@ -2,21 +2,24 @@ use bootloader_test_runner::run_test_kernel; #[test] fn basic_boot() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_basic_boot" - )); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_basic_boot"), + None, + ); } #[test] fn should_panic() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_should_panic" - )); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_should_panic"), + None, + ); } #[test] fn check_boot_info() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_check_boot_info" - )); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_check_boot_info"), + None, + ); } diff --git a/tests/higher_half.rs b/tests/higher_half.rs index c2b9ac91..ca5b2a47 100644 --- a/tests/higher_half.rs +++ b/tests/higher_half.rs @@ -2,24 +2,32 @@ use bootloader_test_runner::run_test_kernel; #[test] fn basic_boot() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_basic_boot")); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_basic_boot"), + None, + ); } #[test] fn should_panic() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_should_panic")); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_should_panic"), + None, + ); } #[test] fn check_boot_info() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_check_boot_info" - )); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_check_boot_info"), + None, + ); } #[test] fn verify_higher_half() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_verify_higher_half" - )); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_verify_higher_half"), + None, + ); } diff --git a/tests/lto.rs b/tests/lto.rs index 00cfe60f..fd3854f5 100644 --- a/tests/lto.rs +++ b/tests/lto.rs @@ -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..97252764 100644 --- a/tests/map_phys_mem.rs +++ b/tests/map_phys_mem.rs @@ -2,14 +2,16 @@ use bootloader_test_runner::run_test_kernel; #[test] fn check_boot_info() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_MAP_PHYS_MEM_check_boot_info" - )); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_MAP_PHYS_MEM_check_boot_info"), + None, + ); } #[test] fn access_phys_mem() { - run_test_kernel(env!( - "CARGO_BIN_FILE_TEST_KERNEL_MAP_PHYS_MEM_access_phys_mem" - )); + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_MAP_PHYS_MEM_access_phys_mem"), + None, + ); } diff --git a/tests/pie.rs b/tests/pie.rs index c2d30d80..0801d3e1 100644 --- a/tests/pie.rs +++ b/tests/pie.rs @@ -2,20 +2,20 @@ use bootloader_test_runner::run_test_kernel; #[test] fn basic_boot() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_basic_boot")); + run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_basic_boot"), None); } #[test] fn should_panic() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_should_panic")); + run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_should_panic"), None); } #[test] fn check_boot_info() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_check_boot_info")); + run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_check_boot_info"), None); } #[test] fn global_variable() { - run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_global_variable")); + run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_global_variable"), None); } diff --git a/tests/ramdisk.rs b/tests/ramdisk.rs new file mode 100644 index 00000000..a32f0d66 --- /dev/null +++ b/tests/ramdisk.rs @@ -0,0 +1,20 @@ +use std::path::Path; + +use bootloader_test_runner::run_test_kernel; +static RAMDISK_PATH: &str = "tests/ramdisk.txt"; + +#[test] +fn basic_boot() { + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_RAMDISK_basic_boot"), + Some(&Path::new(RAMDISK_PATH)), + ); +} + +#[test] +fn check_ramdisk() { + run_test_kernel( + env!("CARGO_BIN_FILE_TEST_KERNEL_RAMDISK_ramdisk"), + Some(&Path::new(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 127184a6..51421aa0 100644 --- a/tests/runner/Cargo.toml +++ b/tests/runner/Cargo.toml @@ -6,11 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[features] -bios = ["bootloader/bios"] -uefi = ["bootloader/uefi", "dep:ovmf-prebuilt"] - [dependencies] -bootloader = { path = "../..", default-features = false } +bootloader = { path = "../.." } strip-ansi-escapes = "0.1.1" -ovmf-prebuilt = { version = "0.1.0-alpha.1", optional = true } +ovmf-prebuilt = "0.1.0-alpha.1" diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index 3b580173..2df603ba 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -1,4 +1,8 @@ -use std::{io::Write, path::Path, process::Command}; +use std::{ + io::Write, + path::{Path, PathBuf}, + process::Command, +}; const QEMU_ARGS: &[&str] = &[ "-device", @@ -10,38 +14,36 @@ const QEMU_ARGS: &[&str] = &[ "--no-reboot", ]; -pub fn run_test_kernel(kernel_binary_path: &str) { +pub fn run_test_kernel(kernel_binary_path: &str, ramdisk_path: Option<&Path>) { + use bootloader::DiskImageBuilder; let kernel_path = Path::new(kernel_binary_path); - - #[cfg(feature = "uefi")] - { - // create a GPT disk image for UEFI booting - let gpt_path = kernel_path.with_extension("gpt"); - let uefi_builder = bootloader::UefiBoot::new(kernel_path); - uefi_builder.create_disk_image(&gpt_path).unwrap(); - - // create a TFTP folder with the kernel executable and UEFI bootloader for - // UEFI PXE booting - let tftp_path = 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_uefi_pxe(&tftp_path); + let ramdisk_path_buf = match ramdisk_path { + Some(rdp) => Some(rdp.to_path_buf()), + None => None, + }; + let ramdisk_path_buf = ramdisk_path_buf.as_ref(); + + // create an MBR disk image for legacy BIOS booting + let mbr_path = kernel_path.with_extension("mbr"); + let gpt_path = kernel_path.with_extension("gpt"); + let tftp_path = kernel_path.with_extension("tftp"); + let kernel_path_buf = kernel_path.to_path_buf(); + let mut image_builder = DiskImageBuilder::new(&kernel_path_buf); + + // Set ramdisk for test, if supplied. + if let Some(rdp) = ramdisk_path_buf { + image_builder.set_ramdisk(rdp); } - #[cfg(feature = "bios")] - { - // 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(); + image_builder.create_bios_image(&mbr_path).unwrap(); + image_builder.create_uefi_image(&gpt_path).unwrap(); + image_builder.create_uefi_tftp_folder(&tftp_path).unwrap(); - run_test_kernel_on_bios(&mbr_path); - } + run_test_kernel_on_uefi(&gpt_path); + run_test_kernel_on_bios(&mbr_path); + run_test_kernel_on_uefi_pxe(&tftp_path); } -#[cfg(feature = "uefi")] pub fn run_test_kernel_on_uefi(out_gpt_path: &Path) { let mut run_cmd = Command::new("qemu-system-x86_64"); run_cmd @@ -65,7 +67,6 @@ pub fn run_test_kernel_on_uefi(out_gpt_path: &Path) { } } -#[cfg(feature = "bios")] pub fn run_test_kernel_on_bios(out_mbr_path: &Path) { let mut run_cmd = Command::new("qemu-system-x86_64"); run_cmd @@ -88,7 +89,6 @@ pub fn run_test_kernel_on_bios(out_mbr_path: &Path) { } } -#[cfg(feature = "uefi")] pub fn run_test_kernel_on_uefi_pxe(out_tftp_path: &Path) { let mut run_cmd = Command::new("qemu-system-x86_64"); run_cmd.arg("-netdev").arg(format!( 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 0f7cd469..e6da4b50 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, }; @@ -66,7 +72,6 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { unsafe { *SYSTEM_TABLE.get() = Some(st.unsafe_clone()); } - st.stdout().clear().unwrap(); writeln!( st.stdout(), @@ -74,28 +79,65 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { ) .unwrap(); - let kernel = load_kernel(image, &st); + let mut boot_mode = BootMode::Disk; + let mut kernel = load_kernel(image, &mut st, boot_mode); + if kernel.is_none() { + writeln!( + st.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!(st.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); - let framebuffer = init_logger(&st, kernel.config); + writeln!( + st.stdout(), + "{}", + match ramdisk { + Some(_) => "Loaded ramdisk", + None => "Ramdisk not found.", + } + ) + .unwrap(); - // we no longer need the system table for printing panics + let framebuffer = init_logger(image, &st, kernel.config); 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(); + loop { + let ptr = st + .boot_services() + .allocate_pool(MemoryType::LOADER_DATA, memory_map_size.map_size) + .expect("Failed to allocate memory for mmap storage"); + + let storage = unsafe { slice::from_raw_parts_mut(ptr, memory_map_size.map_size) }; + + if st.boot_services().memory_map(storage).is_ok() { + break storage; + } + + // 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. + memory_map_size = st.boot_services().memory_map_size(); + // allocated memory region was not big enough -> free it again + st.boot_services() + .free_pool(ptr) + .expect("Failed to free temporary memory for memory map!"); + } }; log::trace!("exiting boot services"); @@ -107,7 +149,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 +168,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 +180,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 device_path = open_device_path_protocol(image, st)?; + let mut device_path = device_path.deref(); - this.open_protocol::( + 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(); + + let opened_handle = unsafe { + this.open_protocol::

( OpenProtocolParams { handle: fs_handle, agent: image, @@ -182,121 +282,99 @@ 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()); + + let file_handle = match file_handle_result { + Err(_) => return None, + Ok(handle) => handle, + }; + + 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); + 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. @@ -365,12 +443,27 @@ fn create_page_tables( } } -fn init_logger(st: &SystemTable, config: BootloaderConfig) -> Option { - let gop = st +fn init_logger( + image_handle: Handle, + st: &SystemTable, + config: BootloaderConfig, +) -> Option { + 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: image_handle, + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) + .ok()? + }; let mode = { let modes = gop.modes();