diff --git a/common/src/legacy_memory_region.rs b/common/src/legacy_memory_region.rs index 6f4b1741..e57c74cd 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -112,9 +112,12 @@ where regions: &mut [MaybeUninit], kernel_slice_start: PhysAddr, kernel_slice_len: u64, + ramdisk_slice_start: Option, + ramdisk_slice_len: u64, ) -> &mut [MemoryRegion] { let mut next_index = 0; let kernel_slice_start = kernel_slice_start.as_u64(); + let ramdisk_slice_start = ramdisk_slice_start.map(|a| a.as_u64()); for descriptor in self.original { let mut start = descriptor.start(); @@ -157,8 +160,9 @@ where kind, }; - // check if region overlaps with kernel + // check if region overlaps with kernel or ramdisk let kernel_slice_end = kernel_slice_start + kernel_slice_len; + let ramdisk_slice_end = ramdisk_slice_start.map(|s| s + ramdisk_slice_len); if region.kind == MemoryRegionKind::Usable && kernel_slice_start < region.end && kernel_slice_end > region.start @@ -198,6 +202,47 @@ where Self::add_region(before_kernel, regions, &mut next_index); Self::add_region(kernel, regions, &mut next_index); Self::add_region(after_kernel, regions, &mut next_index); + } else if region.kind == MemoryRegionKind::Usable + && ramdisk_slice_start.map(|s| s < region.end).unwrap_or(false) + && ramdisk_slice_end.map(|e| e > region.start).unwrap_or(false) + { + // region overlaps with ramdisk -> we might need to split it + let ramdisk_slice_start = ramdisk_slice_start.unwrap(); + let ramdisk_slice_end = ramdisk_slice_end.unwrap(); + + // ensure that the ramdisk allocation does not span multiple regions + assert!( + ramdisk_slice_start >= region.start, + "region overlaps with ramdisk, but ramdisk begins before region \ + (ramdisk_start: {ramdisk_slice_start:#x}, region_start: {:#x})", + region.start + ); + assert!( + ramdisk_slice_end <= region.end, + "region overlaps with ramdisk, but region ends before ramdisk \ + (ramdisk_end: {ramdisk_slice_end:#x}, region_end: {:#x})", + region.end, + ); + + // split the region into three parts + let before_ramdisk = MemoryRegion { + end: ramdisk_slice_start, + ..region + }; + let ramdisk = MemoryRegion { + start: ramdisk_slice_start, + end: ramdisk_slice_end, + kind: MemoryRegionKind::Bootloader, + }; + let after_ramdisk = MemoryRegion { + start: ramdisk_slice_end, + ..region + }; + + // add the three regions (empty regions are ignored in `add_region`) + Self::add_region(before_ramdisk, regions, &mut next_index); + Self::add_region(ramdisk, regions, &mut next_index); + Self::add_region(after_ramdisk, regions, &mut next_index); } else { // add the region normally Self::add_region(region, regions, &mut next_index); diff --git a/common/src/lib.rs b/common/src/lib.rs index 3c407644..2c5aaba4 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -293,14 +293,14 @@ where None }; let ramdisk_slice_len = system_info.ramdisk_len; - let ramdisk_slice_start = if let Some(ramdisk_address) = system_info.ramdisk_addr { + let ramdisk_slice_phys_start = system_info.ramdisk_addr.map(PhysAddr::new); + let ramdisk_slice_start = if let Some(physical_address) = ramdisk_slice_phys_start { let start_page = mapping_addr_page_aligned( config.mappings.ramdisk_memory, system_info.ramdisk_len, &mut used_entries, "ramdisk start", ); - 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; @@ -404,6 +404,7 @@ where kernel_slice_len, kernel_image_offset, + ramdisk_slice_phys_start, ramdisk_slice_start, ramdisk_slice_len, } @@ -433,6 +434,7 @@ pub struct Mappings { pub kernel_slice_len: u64, /// Relocation offset of the kernel image in virtual memory. pub kernel_image_offset: VirtAddr, + pub ramdisk_slice_phys_start: Option, pub ramdisk_slice_start: Option, pub ramdisk_slice_len: u64, } @@ -516,6 +518,8 @@ where memory_regions, mappings.kernel_slice_start, mappings.kernel_slice_len, + mappings.ramdisk_slice_phys_start, + mappings.ramdisk_slice_len, ); log::info!("Create bootinfo"); diff --git a/tests/ramdisk.rs b/tests/ramdisk.rs index bdd7f9db..08c86f9b 100644 --- a/tests/ramdisk.rs +++ b/tests/ramdisk.rs @@ -18,3 +18,11 @@ fn check_ramdisk() { Some(Path::new(RAMDISK_PATH)), ); } + +#[test] +fn memory_map() { + run_test_kernel_with_ramdisk( + env!("CARGO_BIN_FILE_TEST_KERNEL_RAMDISK_memory_map"), + Some(Path::new(RAMDISK_PATH)), + ); +} diff --git a/tests/test_kernels/ramdisk/src/bin/memory_map.rs b/tests/test_kernels/ramdisk/src/bin/memory_map.rs new file mode 100644 index 00000000..b939a420 --- /dev/null +++ b/tests/test_kernels/ramdisk/src/bin/memory_map.rs @@ -0,0 +1,88 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use bootloader_api::{ + config::Mapping, entry_point, info::MemoryRegionKind, BootInfo, BootloaderConfig, +}; +use core::{fmt::Write, ptr::slice_from_raw_parts}; +use test_kernel_ramdisk::{exit_qemu, serial, QemuExitCode, RAMDISK_CONTENTS}; +use x86_64::{ + structures::paging::{OffsetPageTable, PageTable, PageTableFlags, Translate}, + VirtAddr, +}; + +pub const BOOTLOADER_CONFIG: BootloaderConfig = { + let mut config = BootloaderConfig::new_default(); + config.mappings.physical_memory = Some(Mapping::FixedAddress(0x0000_6000_0000_0000)); + config +}; + +entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); + +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + writeln!(serial(), "Boot info: {boot_info:?}").unwrap(); + + let phys_mem_offset = VirtAddr::new(boot_info.physical_memory_offset.into_option().unwrap()); + let level_4_table = unsafe { active_level_4_table(phys_mem_offset) }; + let page_table = unsafe { OffsetPageTable::new(level_4_table, phys_mem_offset) }; + + let ramdisk_start_addr = VirtAddr::new(boot_info.ramdisk_addr.into_option().unwrap()); + assert_eq!(boot_info.ramdisk_len as usize, RAMDISK_CONTENTS.len()); + let ramdisk_end_addr = ramdisk_start_addr + boot_info.ramdisk_len; + + let mut next_addr = ramdisk_start_addr; + while next_addr < ramdisk_end_addr { + let phys_addr = match page_table.translate(next_addr) { + x86_64::structures::paging::mapper::TranslateResult::Mapped { + frame, + offset: _, + flags, + } => { + assert!(flags.contains(PageTableFlags::PRESENT)); + assert!(flags.contains(PageTableFlags::WRITABLE)); + + next_addr += frame.size(); + + frame.start_address() + } + other => panic!("invalid result: {other:?}"), + }; + let region = boot_info + .memory_regions + .iter() + .find(|r| r.start <= phys_addr.as_u64() && r.end > phys_addr.as_u64()) + .unwrap(); + assert_eq!(region.kind, MemoryRegionKind::Bootloader); + } + + 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); +} + +pub unsafe fn active_level_4_table(physical_memory_offset: VirtAddr) -> &'static mut PageTable { + use x86_64::registers::control::Cr3; + + let (level_4_table_frame, _) = Cr3::read(); + + let phys = level_4_table_frame.start_address(); + let virt = physical_memory_offset + phys.as_u64(); + let page_table_ptr: *mut PageTable = virt.as_mut_ptr(); + + &mut *page_table_ptr // unsafe +}