diff --git a/Cargo.lock b/Cargo.lock index 28cb9d7a..f257994d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,6 +172,7 @@ dependencies = [ "test_kernel_config_file", "test_kernel_default_settings", "test_kernel_higher_half", + "test_kernel_lower_memory_free", "test_kernel_map_phys_mem", "test_kernel_min_stack", "test_kernel_pie", @@ -1078,6 +1079,15 @@ dependencies = [ "x86_64", ] +[[package]] +name = "test_kernel_lower_memory_free" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550", + "x86_64", +] + [[package]] name = "test_kernel_lto" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 94d582c2..4de19416 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "tests/test_kernels/lto", "tests/test_kernels/ramdisk", "tests/test_kernels/min_stack", + "tests/test_kernels/lower_memory_free", ] exclude = ["examples/basic", "examples/test_framework"] @@ -67,6 +68,7 @@ test_kernel_pie = { path = "tests/test_kernels/pie", artifact = "bin", target = test_kernel_ramdisk = { path = "tests/test_kernels/ramdisk", artifact = "bin", target = "x86_64-unknown-none" } test_kernel_config_file = { path = "tests/test_kernels/config_file", artifact = "bin", target = "x86_64-unknown-none" } test_kernel_min_stack = { path = "tests/test_kernels/min_stack", artifact = "bin", target = "x86_64-unknown-none" } +test_kernel_lower_memory_free = { path = "tests/test_kernels/lower_memory_free", artifact = "bin", target = "x86_64-unknown-none" } [profile.dev] panic = "abort" diff --git a/common/src/legacy_memory_region.rs b/common/src/legacy_memory_region.rs index e57c74cd..9b49e24b 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -28,8 +28,12 @@ pub struct LegacyFrameAllocator { memory_map: I, current_descriptor: Option, next_frame: PhysFrame, + min_frame: PhysFrame, } +/// Start address of the first frame that is not part of the lower 1MB of frames +const LOWER_MEMORY_END_PAGE: u64 = 0x10_000; + impl LegacyFrameAllocator where I: ExactSizeIterator + Clone, @@ -40,20 +44,25 @@ where /// Skips the frame at physical address zero to avoid potential problems. For example /// identity-mapping the frame at address zero is not valid in Rust, because Rust's `core` /// library assumes that references can never point to virtual address `0`. + /// Also skips the lower 1MB of frames, there are use cases that require lower conventional memory access (Such as SMP SIPI). pub fn new(memory_map: I) -> Self { // skip frame 0 because the rust core library does not see 0 as a valid address - let start_frame = PhysFrame::containing_address(PhysAddr::new(0x1000)); + let start_frame = PhysFrame::containing_address(PhysAddr::new(LOWER_MEMORY_END_PAGE)); Self::new_starting_at(start_frame, memory_map) } /// Creates a new frame allocator based on the given legacy memory regions. Skips any frames - /// before the given `frame`. + /// before the given `frame` or `0x10000`(1MB) whichever is higher, there are use cases that require + /// lower conventional memory access (Such as SMP SIPI). pub fn new_starting_at(frame: PhysFrame, memory_map: I) -> Self { + let lower_mem_end = PhysFrame::containing_address(PhysAddr::new(LOWER_MEMORY_END_PAGE)); + let frame = core::cmp::max(frame, lower_mem_end); Self { original: memory_map.clone(), memory_map, current_descriptor: None, next_frame: frame, + min_frame: frame, } } @@ -71,6 +80,7 @@ where if self.next_frame <= end_frame { let ret = self.next_frame; self.next_frame += 1; + Some(ret) } else { None @@ -125,14 +135,44 @@ where let next_free = self.next_frame.start_address(); let kind = match descriptor.kind() { MemoryRegionKind::Usable => { - if end <= next_free { + if end <= next_free && start >= self.min_frame.start_address() { MemoryRegionKind::Bootloader } else if descriptor.start() >= next_free { MemoryRegionKind::Usable + } else if end <= self.min_frame.start_address() { + // treat regions before min_frame as usable + // this allows for access to the lower 1MB of frames + MemoryRegionKind::Usable + } else if end <= next_free { + // part of the region is used -> add it separately + // first part of the region is in lower 1MB, later part is used + let free_region = MemoryRegion { + start: descriptor.start().as_u64(), + end: self.min_frame.start_address().as_u64(), + kind: MemoryRegionKind::Usable, + }; + Self::add_region(free_region, regions, &mut next_index); + + // add bootloader part normally + start = self.min_frame.start_address(); + MemoryRegionKind::Bootloader } else { + if start < self.min_frame.start_address() { + // part of the region is in lower memory + let lower_region = MemoryRegion { + start: start.as_u64(), + end: self.min_frame.start_address().as_u64(), + kind: MemoryRegionKind::Usable, + }; + Self::add_region(lower_region, regions, &mut next_index); + + start = self.min_frame.start_address(); + } + // part of the region is used -> add it separately + // first part of the region is used, later part is free let used_region = MemoryRegion { - start: descriptor.start().as_u64(), + start: start.as_u64(), end: next_free.as_u64(), kind: MemoryRegionKind::Bootloader, }; diff --git a/tests/lower_memory_free.rs b/tests/lower_memory_free.rs new file mode 100644 index 00000000..aedaf061 --- /dev/null +++ b/tests/lower_memory_free.rs @@ -0,0 +1,7 @@ +use bootloader_test_runner::run_test_kernel; +#[test] +fn lower_memory_free() { + run_test_kernel(env!( + "CARGO_BIN_FILE_TEST_KERNEL_LOWER_MEMORY_FREE_lower_memory_free" + )); +} diff --git a/tests/test_kernels/lower_memory_free/Cargo.toml b/tests/test_kernels/lower_memory_free/Cargo.toml new file mode 100644 index 00000000..d2af9d17 --- /dev/null +++ b/tests/test_kernels/lower_memory_free/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "test_kernel_lower_memory_free" +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/lower_memory_free/src/bin/lower_memory_free.rs b/tests/test_kernels/lower_memory_free/src/bin/lower_memory_free.rs new file mode 100644 index 00000000..7bd03054 --- /dev/null +++ b/tests/test_kernels/lower_memory_free/src/bin/lower_memory_free.rs @@ -0,0 +1,45 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use bootloader_api::{entry_point, info::MemoryRegionKind, BootInfo}; +use test_kernel_lower_memory_free::{exit_qemu, QemuExitCode}; + +const LOWER_MEMORY_END_PAGE: u64 = 0x0010_0000; + +entry_point!(kernel_main); + +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + use core::fmt::Write; + use test_kernel_lower_memory_free::serial; + + let mut count = 0; + for region in boot_info.memory_regions.iter() { + writeln!( + serial(), + "Region: {:016x}-{:016x} - {:?}", + region.start, + region.end, + region.kind + ) + .unwrap(); + if region.kind == MemoryRegionKind::Usable && region.start < LOWER_MEMORY_END_PAGE { + let end = core::cmp::min(region.end, LOWER_MEMORY_END_PAGE); + let pages = (end - region.start) / 4096; + count += pages; + } + } + + writeln!(serial(), "Free lower memory page count: {}", count).unwrap(); + assert!(count > 0x10); // 0x20 chosen arbirarily, we need _some_ free conventional memory, but not all of it. Some, especially on BIOS, may be reserved for hardware. + 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_lower_memory_free::serial(), "PANIC: {}", info); + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/lower_memory_free/src/lib.rs b/tests/test_kernels/lower_memory_free/src/lib.rs new file mode 100644 index 00000000..4e46fdb6 --- /dev/null +++ b/tests/test_kernels/lower_memory_free/src/lib.rs @@ -0,0 +1,27 @@ +#![no_std] + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum QemuExitCode { + Success = 0x10, + Failed = 0x11, +} + +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 +}