diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec90853d..e2d9e796 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,9 @@ jobs: - uses: r7kamura/rust-problem-matchers@v1.1.0 - name: Run api tests run: cargo test -p bootloader_api + - name: Run bootloader common tests + if: runner.arch == 'x86' + run: cargo test -p bootloader-x86_64-common - name: Run integration tests run: cargo test -- --test-threads 1 diff --git a/Cargo.lock b/Cargo.lock index 28cb9d7a..34decae3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,10 +172,12 @@ 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", "test_kernel_ramdisk", + "test_kernel_write_usable_memory", ] [[package]] @@ -1078,6 +1080,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" @@ -1123,6 +1134,15 @@ dependencies = [ "x86_64", ] +[[package]] +name = "test_kernel_write_usable_memory" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550", + "x86_64", +] + [[package]] name = "thiserror" version = "1.0.41" diff --git a/Cargo.toml b/Cargo.toml index 94d582c2..4ff329fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,8 @@ members = [ "tests/test_kernels/lto", "tests/test_kernels/ramdisk", "tests/test_kernels/min_stack", + "tests/test_kernels/lower_memory_free", + "tests/test_kernels/write_usable_memory", ] exclude = ["examples/basic", "examples/test_framework"] @@ -67,6 +69,8 @@ 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" } +test_kernel_write_usable_memory = { path = "tests/test_kernels/write_usable_memory", 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..ccff22fb 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -1,10 +1,35 @@ use bootloader_api::info::{MemoryRegion, MemoryRegionKind}; -use core::mem::MaybeUninit; +use core::{ + cmp, + iter::{empty, Empty}, + mem::MaybeUninit, +}; use x86_64::{ + align_down, align_up, structures::paging::{FrameAllocator, PhysFrame, Size4KiB}, PhysAddr, }; +/// A slice of memory that is used by the bootloader and needs to be reserved +/// in the kernel +#[derive(Clone, Copy, Debug)] +pub struct UsedMemorySlice { + /// the physical start of the slice + pub start: u64, + /// The physical end address (exclusive) of the region. + pub end: u64, +} + +impl UsedMemorySlice { + /// Creates a new slice + pub fn new_from_len(start: u64, len: u64) -> Self { + Self { + start, + end: start + len, + } + } +} + /// Abstraction trait for a memory region returned by the UEFI or BIOS firmware. pub trait LegacyMemoryRegion: Copy + core::fmt::Debug { /// Returns the physical start address of the region. @@ -28,8 +53,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_0000; + impl LegacyFrameAllocator where I: ExactSizeIterator + Clone, @@ -40,20 +69,28 @@ 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)); + // Also skip at least the lower 1MB of frames, there are use cases that require lower conventional memory access (Such as SMP SIPI). + 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 { + // skip frame 0 because the rust core library does not see 0 as a valid address + // Also skip at least the lower 1MB of frames, there are use cases that require lower conventional memory access (Such as SMP SIPI). + 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 +108,7 @@ where if self.next_frame <= end_frame { let ret = self.next_frame; self.next_frame += 1; + Some(ret) } else { None @@ -101,6 +139,14 @@ where .unwrap() } + /// Calculate the maximum number of regions produced by [Self::construct_memory_map] + pub fn memory_map_max_region_count(&self) -> usize { + // every used region can split an original region into 3 new regions, + // this means we need to reserve 2 extra spaces for each region. + // There are 3 used regions: kernel, ramdisk and the bootloader heap + self.len() + 6 + } + /// Converts this type to a boot info memory map. /// /// The memory map is placed in the given `regions` slice. The length of the given slice @@ -115,34 +161,26 @@ where 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()); + let used_slices = [ + UsedMemorySlice { + start: self.min_frame.start_address().as_u64(), + end: self.next_frame.start_address().as_u64(), + }, + UsedMemorySlice::new_from_len(kernel_slice_start.as_u64(), kernel_slice_len), + ] + .into_iter() + .chain( + ramdisk_slice_start + .map(|start| UsedMemorySlice::new_from_len(start.as_u64(), ramdisk_slice_len)), + ) + .map(|slice| UsedMemorySlice { + start: align_down(slice.start, 0x1000), + end: align_up(slice.end, 0x1000), + }); + let mut next_index = 0; for descriptor in self.original { - let mut start = descriptor.start(); - let end = start + descriptor.len(); - let next_free = self.next_frame.start_address(); let kind = match descriptor.kind() { - MemoryRegionKind::Usable => { - if end <= next_free { - MemoryRegionKind::Bootloader - } else if descriptor.start() >= next_free { - MemoryRegionKind::Usable - } else { - // part of the region is used -> add it separately - let used_region = MemoryRegion { - start: descriptor.start().as_u64(), - end: next_free.as_u64(), - kind: MemoryRegionKind::Bootloader, - }; - Self::add_region(used_region, regions, &mut next_index); - - // add unused part normally - start = next_free; - MemoryRegionKind::Usable - } - } _ if descriptor.usable_after_bootloader_exit() => { // Region was not usable before, but it will be as soon as // the bootloader passes control to the kernel. We don't @@ -154,97 +192,15 @@ where other => other, }; + let end = descriptor.start() + descriptor.len(); let region = MemoryRegion { - start: start.as_u64(), + start: descriptor.start().as_u64(), end: end.as_u64(), kind, }; - - // 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 - { - // region overlaps with kernel -> we might need to split it - - // ensure that the kernel allocation does not span multiple regions - assert!( - kernel_slice_start >= region.start, - "region overlaps with kernel, but kernel begins before region \ - (kernel_slice_start: {kernel_slice_start:#x}, region_start: {:#x})", - region.start - ); - assert!( - kernel_slice_end <= region.end, - "region overlaps with kernel, but region ends before kernel \ - (kernel_slice_end: {kernel_slice_end:#x}, region_end: {:#x})", - region.end, - ); - - // split the region into three parts - let before_kernel = MemoryRegion { - end: kernel_slice_start, - ..region - }; - let kernel = MemoryRegion { - start: kernel_slice_start, - end: kernel_slice_end, - kind: MemoryRegionKind::Bootloader, - }; - let after_kernel = MemoryRegion { - start: kernel_slice_end, - ..region - }; - - // add the three regions (empty regions are ignored in `add_region`) - 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); + if region.kind == MemoryRegionKind::Usable { + Self::split_and_add_region(region, regions, &mut next_index, used_slices.clone()); } else { - // add the region normally Self::add_region(region, regions, &mut next_index); } } @@ -257,6 +213,64 @@ where } } + fn split_and_add_region<'a, U>( + mut region: MemoryRegion, + regions: &mut [MaybeUninit], + next_index: &mut usize, + used_slices: U, + ) where + U: Iterator + Clone, + { + assert!(region.kind == MemoryRegionKind::Usable); + // Each loop iteration takes a chunk of `region` and adds it to + // `regions`. Do this until `region` is empty. + while region.start != region.end { + // Check if there is overlap between `region` and `used_slices`. + if let Some((overlap_start, overlap_end)) = used_slices + .clone() + .map(|slice| { + // Calculate the start and end points of the overlap + // between `slice` and `region`. If `slice` and `region` + // don't overlap, the range will be ill-formed + // (overlap_start > overlap_end). + let overlap_start = cmp::max(region.start, slice.start); + let overlap_end = cmp::min(region.end, slice.end); + (overlap_start, overlap_end) + }) + .filter(|(overlap_start, overlap_end)| { + // Only consider non-empty overlap. + overlap_start < overlap_end + }) + .min_by_key(|&(overlap_start, _)| { + // Find the earliest overlap. + overlap_start + }) + { + // There's no overlapping used slice before `overlap_start`, so + // we know that memory between `region.start` and + // `overlap_start` is usable. + let usable = MemoryRegion { + start: region.start, + end: overlap_start, + kind: MemoryRegionKind::Usable, + }; + let bootloader = MemoryRegion { + start: overlap_start, + end: overlap_end, + kind: MemoryRegionKind::Bootloader, + }; + Self::add_region(usable, regions, next_index); + Self::add_region(bootloader, regions, next_index); + // Continue after the overlapped region. + region.start = overlap_end; + } else { + // There's no overlap. We can add the whole region. + Self::add_region(region, regions, next_index); + break; + } + } + } + fn add_region( region: MemoryRegion, regions: &mut [MaybeUninit], @@ -306,3 +320,275 @@ where None } } + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Copy, Clone, Debug)] + struct TestMemoryRegion { + start: PhysAddr, + len: u64, + kind: MemoryRegionKind, + } + + impl LegacyMemoryRegion for TestMemoryRegion { + fn start(&self) -> PhysAddr { + self.start + } + + fn len(&self) -> u64 { + assert!(self.len % 4096 == 0); + self.len + } + + fn kind(&self) -> MemoryRegionKind { + self.kind + } + + fn usable_after_bootloader_exit(&self) -> bool { + match self.kind { + MemoryRegionKind::Usable => true, + _ => false, + } + } + } + + // we need some kind of max phys memory, 4GB seems reasonable + const MAX_PHYS_ADDR: u64 = 0x4000_0000; + + fn create_single_test_region() -> Vec { + vec![TestMemoryRegion { + start: PhysAddr::new(0), + len: MAX_PHYS_ADDR, + kind: MemoryRegionKind::Usable, + }] + } + + #[test] + fn test_all_regions_frame_alligned() { + let regions = create_single_test_region(); + let mut allocator = LegacyFrameAllocator::new(regions.into_iter()); + // allocate at least 1 frame + allocator.allocate_frame(); + + let mut regions = [MaybeUninit::uninit(); 10]; + let kernel_slice_start = PhysAddr::new(0x50000); + let kernel_slice_len = 0x0500; + let ramdisk_slice_start = None; + let ramdisk_slice_len = 0; + + let kernel_regions = allocator.construct_memory_map( + &mut regions, + kernel_slice_start, + kernel_slice_len, + ramdisk_slice_start, + ramdisk_slice_len, + ); + + for region in kernel_regions.iter() { + assert!(region.start % 0x1000 == 0); + assert!(region.end % 0x1000 == 0); + } + } + + #[test] + fn test_kernel_and_ram_in_same_region() { + let regions = create_single_test_region(); + let mut allocator = LegacyFrameAllocator::new(regions.into_iter()); + // allocate at least 1 frame + allocator.allocate_frame(); + + let mut regions = [MaybeUninit::uninit(); 10]; + let kernel_slice_start = PhysAddr::new(0x50000); + let kernel_slice_len = 0x1000; + let ramdisk_slice_start = Some(PhysAddr::new(0x60000)); + let ramdisk_slice_len = 0x2000; + + let kernel_regions = allocator.construct_memory_map( + &mut regions, + kernel_slice_start, + kernel_slice_len, + ramdisk_slice_start, + ramdisk_slice_len, + ); + let mut kernel_regions = kernel_regions.iter(); + // usable memory before the kernel + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x0000, + end: 0x50000, + kind: MemoryRegionKind::Usable + }) + ); + // kernel + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x50000, + end: 0x51000, + kind: MemoryRegionKind::Bootloader + }) + ); + // usabel memory between kernel and ramdisk + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x51000, + end: 0x60000, + kind: MemoryRegionKind::Usable + }) + ); + // ramdisk + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x60000, + end: 0x62000, + kind: MemoryRegionKind::Bootloader + }) + ); + // usabele memory after ramdisk, up until bootloader allocated memory + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x62000, + end: 0x10_0000, + kind: MemoryRegionKind::Usable + }) + ); + // bootloader allocated memory + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x10_0000, + end: 0x10_1000, + kind: MemoryRegionKind::Bootloader + }) + ); + // rest is free + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x10_1000, + end: MAX_PHYS_ADDR, + kind: MemoryRegionKind::Usable + }) + ); + assert_eq!(kernel_regions.next(), None); + } + + #[test] + fn test_multiple_regions() { + let regions = vec![ + TestMemoryRegion { + start: PhysAddr::new(0), + len: 0x10_0000, + kind: MemoryRegionKind::Usable, + }, + TestMemoryRegion { + start: PhysAddr::new(0x10_0000), + len: 0x5000, + kind: MemoryRegionKind::UnknownBios(0), + }, + TestMemoryRegion { + start: PhysAddr::new(0x10_5000), + len: MAX_PHYS_ADDR - 0x10_5000, + kind: MemoryRegionKind::Usable, + }, + ]; + let mut allocator = LegacyFrameAllocator::new(regions.into_iter()); + // allocate at least 1 frame + allocator.allocate_frame(); + + let mut regions = [MaybeUninit::uninit(); 10]; + let kernel_slice_start = PhysAddr::new(0x50000); + let kernel_slice_len = 0x1000; + let ramdisk_slice_start = Some(PhysAddr::new(0x60000)); + let ramdisk_slice_len = 0x2000; + + let kernel_regions = allocator.construct_memory_map( + &mut regions, + kernel_slice_start, + kernel_slice_len, + ramdisk_slice_start, + ramdisk_slice_len, + ); + let mut kernel_regions = kernel_regions.iter(); + + // usable memory before the kernel + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x0000, + end: 0x50000, + kind: MemoryRegionKind::Usable + }) + ); + // kernel + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x50000, + end: 0x51000, + kind: MemoryRegionKind::Bootloader + }) + ); + // usabel memory between kernel and ramdisk + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x51000, + end: 0x60000, + kind: MemoryRegionKind::Usable + }) + ); + // ramdisk + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x60000, + end: 0x62000, + kind: MemoryRegionKind::Bootloader + }) + ); + // usabele memory after ramdisk, up until bootloader allocated memory + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x62000, + end: 0x10_0000, + kind: MemoryRegionKind::Usable + }) + ); + // the unknown bios region + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x10_0000, + end: 0x10_5000, + kind: MemoryRegionKind::UnknownBios(0) + }) + ); + // bootloader allocated memory, this gets pushed back by the bios region + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x10_5000, + end: 0x10_6000, + kind: MemoryRegionKind::Bootloader + }) + ); + // rest is free + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x10_6000, + end: MAX_PHYS_ADDR, + kind: MemoryRegionKind::Usable + }) + ); + assert_eq!(kernel_regions.next(), None); + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index c17f68fc..1c8b1efe 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,4 +1,4 @@ -#![no_std] +#![cfg_attr(not(test), no_std)] #![feature(step_trait)] #![deny(unsafe_op_in_unsafe_fn)] @@ -483,7 +483,7 @@ where // allocate and map space for the boot info let (boot_info, memory_regions) = { let boot_info_layout = Layout::new::(); - let regions = frame_allocator.len() + 4; // up to 4 regions might be split into used/unused + let regions = frame_allocator.memory_map_max_region_count(); let memory_regions_layout = Layout::array::(regions).unwrap(); let (combined, memory_regions_offset) = boot_info_layout.extend(memory_regions_layout).unwrap(); diff --git a/src/file_data_source.rs b/src/file_data_source.rs index 5724d96f..47656ff8 100644 --- a/src/file_data_source.rs +++ b/src/file_data_source.rs @@ -1,4 +1,3 @@ -use alloc::vec::Vec; use anyhow::Context; use core::fmt::{Debug, Formatter}; 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..65b06da2 --- /dev/null +++ b/tests/test_kernels/lower_memory_free/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "test_kernel_lower_memory_free" +version = "0.1.0" +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..9620d1ca --- /dev/null +++ b/tests/test_kernels/lower_memory_free/src/bin/lower_memory_free.rs @@ -0,0 +1,53 @@ +#![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 test_kernel_lower_memory_free::{exit_qemu, QemuExitCode}; + +const LOWER_MEMORY_END_PAGE: u64 = 0x0010_0000; + +pub const BOOTLOADER_CONFIG: BootloaderConfig = { + let mut config = BootloaderConfig::new_default(); + config.mappings.physical_memory = Some(Mapping::FixedAddress(0x0000_4000_0000_0000)); + config +}; + +entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); + +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); // 0x10 chosen arbitrarily, 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 +} diff --git a/tests/test_kernels/write_usable_memory/Cargo.toml b/tests/test_kernels/write_usable_memory/Cargo.toml new file mode 100644 index 00000000..88cf640f --- /dev/null +++ b/tests/test_kernels/write_usable_memory/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "test_kernel_write_usable_memory" +version = "0.1.0" +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/write_usable_memory/src/bin/write_usable_memory.rs b/tests/test_kernels/write_usable_memory/src/bin/write_usable_memory.rs new file mode 100644 index 00000000..95a27d37 --- /dev/null +++ b/tests/test_kernels/write_usable_memory/src/bin/write_usable_memory.rs @@ -0,0 +1,42 @@ +#![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 test_kernel_write_usable_memory::{exit_qemu, QemuExitCode}; + +pub const BOOTLOADER_CONFIG: BootloaderConfig = { + let mut config = BootloaderConfig::new_default(); + config.mappings.physical_memory = Some(Mapping::FixedAddress(0x0000_4000_0000_0000)); + config +}; + +entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); + +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + let phys_mem_offset = boot_info.physical_memory_offset.into_option().unwrap(); + + for region in boot_info.memory_regions.iter() { + if region.kind == MemoryRegionKind::Usable { + // ensure region is actually writable + let addr = phys_mem_offset + region.start; + let size = region.end - region.start; + unsafe { + core::ptr::write_bytes(addr as *mut u8, 0xff, size as usize); + } + } + } + + 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_write_usable_memory::serial(), "PANIC: {}", info); + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/write_usable_memory/src/lib.rs b/tests/test_kernels/write_usable_memory/src/lib.rs new file mode 100644 index 00000000..4e46fdb6 --- /dev/null +++ b/tests/test_kernels/write_usable_memory/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 +} diff --git a/tests/write_usable_memory.rs b/tests/write_usable_memory.rs new file mode 100644 index 00000000..a2c7b11a --- /dev/null +++ b/tests/write_usable_memory.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_WRITE_USABLE_MEMORY_write_usable_memory" + )); +}