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..a10861b9 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"] @@ -55,6 +56,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 38a58fff..771948de 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..d8335e14 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -98,6 +98,20 @@ 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", ramdisk_start, &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 +143,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 +169,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 +212,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/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 index 03952152..78ebe957 100644 --- a/src/bios/mod.rs +++ b/src/bios/mod.rs @@ -14,6 +14,7 @@ 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 { @@ -21,10 +22,17 @@ impl BiosBoot { pub fn new(kernel_path: &Path) -> Self { Self { kernel: kernel_path.to_owned(), + ramdisk: None, } } - /// Create a bootable UEFI disk image at the given path. + /// 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 BIOS 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")); @@ -57,6 +65,9 @@ impl BiosBoot { 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); + if let Some(ramdisk_path) = &self.ramdisk { + files.insert(crate::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/lib.rs b/src/lib.rs index cd41805a..4f540c04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,3 +17,4 @@ pub use bios::BiosBoot; pub use uefi::UefiBoot; const KERNEL_FILE_NAME: &str = "kernel-x86_64"; +const RAMDISK_FILE_NAME: &str = "ramdisk"; diff --git a/src/uefi/mod.rs b/src/uefi/mod.rs index cfbc8c9f..e2d273dd 100644 --- a/src/uefi/mod.rs +++ b/src/uefi/mod.rs @@ -12,6 +12,7 @@ mod pxe; /// Create disk images for booting on UEFI systems. pub struct UefiBoot { kernel: PathBuf, + ramdisk: Option, } impl UefiBoot { @@ -19,10 +20,17 @@ impl UefiBoot { pub fn new(kernel_path: &Path) -> Self { Self { kernel: kernel_path.to_owned(), + ramdisk: None, } } - /// Create a bootable BIOS disk image at the given path. + /// 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 UEFI disk image at the given path. pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> { let fat_partition = self .create_fat_partition() @@ -46,8 +54,13 @@ impl UefiBoot { 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")?; + pxe::create_uefi_tftp_folder( + bootloader_path, + self.kernel.as_path(), + self.ramdisk.as_deref(), + out_path, + ) + .context("failed to create UEFI PXE tftp folder")?; Ok(()) } @@ -59,6 +72,9 @@ impl UefiBoot { let mut files = BTreeMap::new(); files.insert("efi/boot/bootx64.efi", bootloader_path); files.insert(crate::KERNEL_FILE_NAME, self.kernel.as_path()); + if let Some(ramdisk_path) = &self.ramdisk { + files.insert(crate::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/uefi/pxe.rs b/src/uefi/pxe.rs index 9329cec2..84c7b0d6 100644 --- a/src/uefi/pxe.rs +++ b/src/uefi/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/ramdisk.rs b/tests/ramdisk.rs new file mode 100644 index 00000000..bdd7f9db --- /dev/null +++ b/tests/ramdisk.rs @@ -0,0 +1,20 @@ +use std::path::Path; + +use bootloader_test_runner::run_test_kernel_with_ramdisk; +static RAMDISK_PATH: &str = "tests/ramdisk.txt"; + +#[test] +fn basic_boot() { + run_test_kernel_with_ramdisk( + env!("CARGO_BIN_FILE_TEST_KERNEL_RAMDISK_basic_boot"), + Some(Path::new(RAMDISK_PATH)), + ); +} + +#[test] +fn check_ramdisk() { + run_test_kernel_with_ramdisk( + 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/src/lib.rs b/tests/runner/src/lib.rs index 3b580173..f9491493 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -11,13 +11,21 @@ const QEMU_ARGS: &[&str] = &[ ]; pub fn run_test_kernel(kernel_binary_path: &str) { + run_test_kernel_with_ramdisk(kernel_binary_path, None) +} + +pub fn run_test_kernel_with_ramdisk(kernel_binary_path: &str, ramdisk_path: Option<&Path>) { 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); + 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(); // create a TFTP folder with the kernel executable and UEFI bootloader for @@ -33,9 +41,12 @@ pub fn run_test_kernel(kernel_binary_path: &str) { { // 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 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); } 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();