From 1e52e4c5e7c4c38c49bad53dac579331e5dbaaa8 Mon Sep 17 00:00:00 2001 From: Burkhard Mittelbach Date: Mon, 17 Jun 2024 01:06:06 +0200 Subject: [PATCH 01/14] Fix warning: unecessary import for alloc::Vec --- src/file_data_source.rs | 1 - 1 file changed, 1 deletion(-) 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}; From 25551270a856091b43a3f84b038c628f7eab186f Mon Sep 17 00:00:00 2001 From: Burkhard Mittelbach Date: Sun, 9 Jun 2024 00:19:34 +0200 Subject: [PATCH 02/14] Guard the lower 1MB of memory This commit fixes the review changes in #317. Most of the credit should go to Jason Couture Co-authored-by: Jason Couture --- Cargo.lock | 10 +++ Cargo.toml | 2 + common/src/legacy_memory_region.rs | 49 ++++++++++++-- tests/lower_memory_free.rs | 7 ++ .../test_kernels/lower_memory_free/Cargo.toml | 12 ++++ .../src/bin/lower_memory_free.rs | 65 +++++++++++++++++++ .../test_kernels/lower_memory_free/src/lib.rs | 27 ++++++++ 7 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 tests/lower_memory_free.rs create mode 100644 tests/test_kernels/lower_memory_free/Cargo.toml create mode 100644 tests/test_kernels/lower_memory_free/src/bin/lower_memory_free.rs create mode 100644 tests/test_kernels/lower_memory_free/src/lib.rs 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..1fbf1cfb 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 = 0x100_000; + impl LegacyFrameAllocator where I: ExactSizeIterator + Clone, @@ -40,20 +44,26 @@ 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 { + 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 +81,7 @@ where if self.next_frame <= end_frame { let ret = self.next_frame; self.next_frame += 1; + Some(ret) } else { None @@ -125,14 +136,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..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..1bc91aef --- /dev/null +++ b/tests/test_kernels/lower_memory_free/src/bin/lower_memory_free.rs @@ -0,0 +1,65 @@ +#![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; +const WRITE_TEST_UNTIL: u64 = 0x4000_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 phys_mem_offset = boot_info.physical_memory_offset.into_option().unwrap(); + + 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; + } + if region.kind == MemoryRegionKind::Usable && region.start < WRITE_TEST_UNTIL { + let end = core::cmp::min(region.end, WRITE_TEST_UNTIL); + // ensure region is actually writable + let addr = phys_mem_offset + region.start; + let size = end - region.start; + unsafe { + core::ptr::write_bytes(addr as *mut u8, 0xff, size as usize); + } + } + } + + writeln!(serial(), "Free lower memory page count: {}", count).unwrap(); + assert!(count > 0x10); // 0x10 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 +} From e09e68a9081d3c50414b58997fbe6af942e8a91b Mon Sep 17 00:00:00 2001 From: Burkhard Mittelbach Date: Mon, 17 Jun 2024 01:26:33 +0200 Subject: [PATCH 03/14] Rework: LegacyFrameAllocator::construct_memory_map The new implementation takes in a list of slices that are used by the bootloader. It then goes through all regions and checks if they overlap any of those slices. If so, the region is split and this process is started recursively until none of the usable regions overlap the memory used by the bootloader. --- bios/common/src/lib.rs | 1 + bios/stage-2/src/main.rs | 4 + bios/stage-4/src/main.rs | 17 +- common/src/legacy_memory_region.rs | 351 +++++++++++++++++------------ common/src/lib.rs | 20 +- 5 files changed, 238 insertions(+), 155 deletions(-) diff --git a/bios/common/src/lib.rs b/bios/common/src/lib.rs index 166e12df..de20d0e6 100644 --- a/bios/common/src/lib.rs +++ b/bios/common/src/lib.rs @@ -5,6 +5,7 @@ pub mod racy_cell; #[cfg_attr(feature = "debug", derive(Debug))] #[repr(C)] pub struct BiosInfo { + pub stage_3: Region, pub stage_4: Region, pub kernel: Region, pub ramdisk: Region, diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index b5b07d65..95ec8c79 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -145,6 +145,10 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { vesa_mode.enable().unwrap(); let mut info = BiosInfo { + stage_3: Region { + start: STAGE_3_DST as u64, + len: stage_3_len, + }, stage_4: Region { start: stage_4_dst as u64, len: stage_4_len, diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index cf159a61..028ec9ae 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -4,7 +4,8 @@ use crate::memory_descriptor::MemoryRegion; use bootloader_api::info::{FrameBufferInfo, PixelFormat}; use bootloader_boot_config::{BootConfig, LevelFilter}; -use bootloader_x86_64_bios_common::{BiosFramebufferInfo, BiosInfo, E820MemoryRegion}; +use bootloader_x86_64_bios_common::{BiosFramebufferInfo, BiosInfo, E820MemoryRegion, Region}; +use bootloader_x86_64_common::legacy_memory_region::UsedMemorySlice; use bootloader_x86_64_common::RawFrameBufferInfo; use bootloader_x86_64_common::{ legacy_memory_region::LegacyFrameAllocator, load_and_switch_to_kernel, Kernel, PageTables, @@ -55,9 +56,10 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { }; let kernel_size = info.kernel.len; let next_free_frame = PhysFrame::containing_address(PhysAddr::new(info.last_used_addr)) + 1; - let mut frame_allocator = LegacyFrameAllocator::new_starting_at( + let mut frame_allocator = LegacyFrameAllocator::new_with_used_slices( next_free_frame, memory_map.iter().copied().map(MemoryRegion), + used_memory_slices(info), ); // We identity-mapped all memory, so the offset between physical and virtual addresses is 0 @@ -216,6 +218,17 @@ fn init_logger( framebuffer_info } +fn used_memory_slices(info: &BiosInfo) -> impl Iterator + Clone { + // skip kernel and ramdisk because they are handled individually by the + // uefi/bios common code + [info.stage_3, info.stage_4, info.config_file] + .into_iter() + .map(|region| UsedMemorySlice { + start: PhysAddr::new(region.start).as_u64(), + len: region.len, + }) +} + /// Creates page table abstraction types for both the bootloader and kernel page tables. fn create_page_tables(frame_allocator: &mut impl FrameAllocator) -> PageTables { // We identity-mapped all memory, so the offset between physical and virtual addresses is 0 diff --git a/common/src/legacy_memory_region.rs b/common/src/legacy_memory_region.rs index 1fbf1cfb..8a74c268 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -1,10 +1,23 @@ use bootloader_api::info::{MemoryRegion, MemoryRegionKind}; -use core::mem::MaybeUninit; +use core::{ + iter::{empty, Empty}, + mem::MaybeUninit, +}; use x86_64::{ 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 length of the slice + pub len: u64, +} + /// 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. @@ -23,18 +36,19 @@ pub trait LegacyMemoryRegion: Copy + core::fmt::Debug { } /// A physical frame allocator based on a BIOS or UEFI provided memory map. -pub struct LegacyFrameAllocator { +pub struct LegacyFrameAllocator { original: I, memory_map: I, current_descriptor: Option, next_frame: PhysFrame, min_frame: PhysFrame, + used_slices: S, } /// Start address of the first frame that is not part of the lower 1MB of frames const LOWER_MEMORY_END_PAGE: u64 = 0x100_000; -impl LegacyFrameAllocator +impl LegacyFrameAllocator> where I: ExactSizeIterator + Clone, I::Item: LegacyMemoryRegion, @@ -56,14 +70,28 @@ where /// 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 { + Self::new_with_used_slices(frame, memory_map, empty()) + } +} + +impl LegacyFrameAllocator +where + I: ExactSizeIterator + Clone, + I::Item: LegacyMemoryRegion, + S: Iterator + Clone, +{ + pub fn new_with_used_slices(start_frame: PhysFrame, memory_map: I, used_slices: S) -> 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); + let frame = core::cmp::max(start_frame, lower_mem_end); Self { original: memory_map.clone(), memory_map, current_descriptor: None, next_frame: frame, min_frame: frame, + used_slices, } } @@ -126,64 +154,19 @@ 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 = Self::used_regions_iter( + self.min_frame, + self.next_frame, + kernel_slice_start, + kernel_slice_len, + ramdisk_slice_start, + ramdisk_slice_len, + self.used_slices, + ); + 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 && 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: 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 @@ -195,97 +178,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); } } @@ -298,6 +199,125 @@ where } } + fn used_regions_iter( + min_frame: PhysFrame, + next_free: PhysFrame, + kernel_slice_start: PhysAddr, + kernel_slice_len: u64, + ramdisk_slice_start: Option, + ramdisk_slice_len: u64, + used_slices: S, + ) -> impl Iterator + Clone { + BootloaderUsedMemorySliceIter { + bootloader: UsedMemorySlice { + start: min_frame.start_address().as_u64(), + // TODO: unit test that this is not an off by 1 + len: next_free.start_address() - min_frame.start_address(), + }, + kernel: UsedMemorySlice { + start: kernel_slice_start.as_u64(), + len: kernel_slice_len, + }, + ramdisk: ramdisk_slice_start.map(|start| UsedMemorySlice { + start: start.as_u64(), + len: ramdisk_slice_len, + }), + state: KernelRamIterState::Bootloader, + } + .chain(used_slices) + } + + // TODO unit test + fn split_and_add_region<'a, U>( + region: MemoryRegion, + regions: &mut [MaybeUninit], + next_index: &mut usize, + used_slices: U, + ) where + U: Iterator + Clone, + { + assert!(region.kind == MemoryRegionKind::Usable); + if region.start == region.end { + // skip zero sized regions + return; + } + + for slice in used_slices.clone() { + let slice_end = slice.start + slice.len; + if region.end <= slice.start || region.start >= slice_end { + // region and slice don't overlap + continue; + } + + if region.start >= slice.start && region.end <= slice_end { + // region is completly covered by slice + let bootloader = MemoryRegion { + start: region.start, + end: region.end, + kind: MemoryRegionKind::Bootloader, + }; + Self::add_region(bootloader, regions, next_index); + return; + } + if region.start < slice.start && region.end <= slice_end { + // there is a usable region before the bootloader slice + let before = MemoryRegion { + start: region.start, + end: slice.start, + kind: MemoryRegionKind::Usable, + }; + + let bootloader = MemoryRegion { + start: slice.start, + end: region.end, + kind: MemoryRegionKind::Bootloader, + }; + Self::split_and_add_region(before, regions, next_index, used_slices); + Self::add_region(bootloader, regions, next_index); + return; + } else if region.start < slice.start && region.end > slice_end { + // there is usable region before and after the bootloader slice + let before = MemoryRegion { + start: region.start, + end: slice.start, + kind: MemoryRegionKind::Usable, + }; + let bootloader = MemoryRegion { + start: slice.start, + end: slice_end, + kind: MemoryRegionKind::Bootloader, + }; + let after = MemoryRegion { + start: slice_end, + end: region.end, + kind: MemoryRegionKind::Usable, + }; + Self::split_and_add_region(before, regions, next_index, used_slices.clone()); + Self::add_region(bootloader, regions, next_index); + Self::split_and_add_region(after, regions, next_index, used_slices.clone()); + return; + } + if region.start >= slice.start && region.end > slice_end { + // there is a usable region after the bootloader slice + let bootloader = MemoryRegion { + start: region.start, + end: slice_end, + kind: MemoryRegionKind::Bootloader, + }; + let after = MemoryRegion { + start: slice_end, + end: region.end, + kind: MemoryRegionKind::Usable, + }; + Self::add_region(bootloader, regions, next_index); + Self::split_and_add_region(after, regions, next_index, used_slices); + return; + } + } + // region is not coverd by any slice + Self::add_region(region, regions, next_index); + } + fn add_region( region: MemoryRegion, regions: &mut [MaybeUninit], @@ -318,10 +338,11 @@ where } } -unsafe impl FrameAllocator for LegacyFrameAllocator +unsafe impl FrameAllocator for LegacyFrameAllocator where I: ExactSizeIterator + Clone, I::Item: LegacyMemoryRegion, + S: Iterator + Clone, { fn allocate_frame(&mut self) -> Option> { if let Some(current_descriptor) = self.current_descriptor { @@ -347,3 +368,41 @@ where None } } + +#[derive(Clone)] +struct BootloaderUsedMemorySliceIter { + bootloader: UsedMemorySlice, + kernel: UsedMemorySlice, + ramdisk: Option, + state: KernelRamIterState, +} + +#[derive(Clone)] +enum KernelRamIterState { + Bootloader, + Kernel, + Ramdisk, + Done, +} + +impl Iterator for BootloaderUsedMemorySliceIter { + type Item = UsedMemorySlice; + + fn next(&mut self) -> Option { + match self.state { + KernelRamIterState::Bootloader => { + self.state = KernelRamIterState::Kernel; + Some(self.bootloader) + } + KernelRamIterState::Kernel => { + self.state = KernelRamIterState::Ramdisk; + Some(self.kernel) + } + KernelRamIterState::Ramdisk => { + self.state = KernelRamIterState::Done; + self.ramdisk + } + KernelRamIterState::Done => None, + } + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index c17f68fc..9eae870d 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -10,6 +10,7 @@ use bootloader_api::{ }; use bootloader_boot_config::{BootConfig, LevelFilter}; use core::{alloc::Layout, arch::asm, mem::MaybeUninit, slice}; +use legacy_memory_region::UsedMemorySlice; use level_4_entries::UsedLevel4Entries; use usize_conversions::FromUsize; use x86_64::{ @@ -123,16 +124,17 @@ impl<'a> Kernel<'a> { /// This function is a convenience function that first calls [`set_up_mappings`], then /// [`create_boot_info`], and finally [`switch_to_kernel`]. The given arguments are passed /// directly to these functions, so see their docs for more info. -pub fn load_and_switch_to_kernel( +pub fn load_and_switch_to_kernel( kernel: Kernel, boot_config: BootConfig, - mut frame_allocator: LegacyFrameAllocator, + mut frame_allocator: LegacyFrameAllocator, mut page_tables: PageTables, system_info: SystemInfo, ) -> ! where I: ExactSizeIterator + Clone, D: LegacyMemoryRegion, + S: Iterator + Clone, { let config = kernel.config; let mut mappings = set_up_mappings( @@ -168,9 +170,9 @@ where /// /// This function reacts to unexpected situations (e.g. invalid kernel ELF file) with a panic, so /// errors are not recoverable. -pub fn set_up_mappings( +pub fn set_up_mappings( kernel: Kernel, - frame_allocator: &mut LegacyFrameAllocator, + frame_allocator: &mut LegacyFrameAllocator, page_tables: &mut PageTables, framebuffer: Option<&RawFrameBufferInfo>, config: &BootloaderConfig, @@ -179,6 +181,7 @@ pub fn set_up_mappings( where I: ExactSizeIterator + Clone, D: LegacyMemoryRegion, + S: Iterator + Clone, { let kernel_page_table = &mut page_tables.kernel; @@ -466,10 +469,10 @@ pub struct Mappings { /// address space at the same address. This makes it possible to return a Rust /// reference that is valid in both address spaces. The necessary physical frames /// are taken from the given `frame_allocator`. -pub fn create_boot_info( +pub fn create_boot_info( config: &BootloaderConfig, boot_config: &BootConfig, - mut frame_allocator: LegacyFrameAllocator, + mut frame_allocator: LegacyFrameAllocator, page_tables: &mut PageTables, mappings: &mut Mappings, system_info: SystemInfo, @@ -477,13 +480,16 @@ pub fn create_boot_info( where I: ExactSizeIterator + Clone, D: LegacyMemoryRegion, + S: Iterator + Clone, { log::info!("Allocate bootinfo"); // 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 + // TODO the assumption here about the regions is wrong + // figure out what the correct region count should be + let regions = frame_allocator.len() + 4 + 30; // up to 4 regions might be split into used/unused let memory_regions_layout = Layout::array::(regions).unwrap(); let (combined, memory_regions_offset) = boot_info_layout.extend(memory_regions_layout).unwrap(); From 444627888ca174e83232443aa5911f24c61383ba Mon Sep 17 00:00:00 2001 From: Burkhard Mittelbach Date: Sat, 22 Jun 2024 17:34:32 +0200 Subject: [PATCH 04/14] Trivial PR changes --- bios/stage-4/src/main.rs | 4 ++-- common/src/legacy_memory_region.rs | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index 028ec9ae..446a33a0 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -224,8 +224,8 @@ fn used_memory_slices(info: &BiosInfo) -> impl Iterator [info.stage_3, info.stage_4, info.config_file] .into_iter() .map(|region| UsedMemorySlice { - start: PhysAddr::new(region.start).as_u64(), - len: region.len, + start: region.start, + end: region.start + region.len, }) } diff --git a/common/src/legacy_memory_region.rs b/common/src/legacy_memory_region.rs index 8a74c268..fd32f548 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -14,8 +14,8 @@ use x86_64::{ pub struct UsedMemorySlice { /// the physical start of the slice pub start: u64, - /// the length of the slice - pub len: u64, + /// The physical end address (exclusive) of the region. + pub end: u64, } /// Abstraction trait for a memory region returned by the UEFI or BIOS firmware. @@ -46,7 +46,7 @@ pub struct LegacyFrameAllocator { } /// Start address of the first frame that is not part of the lower 1MB of frames -const LOWER_MEMORY_END_PAGE: u64 = 0x100_000; +const LOWER_MEMORY_END_PAGE: u64 = 0x10_0000; impl LegacyFrameAllocator> where @@ -212,15 +212,15 @@ where bootloader: UsedMemorySlice { start: min_frame.start_address().as_u64(), // TODO: unit test that this is not an off by 1 - len: next_free.start_address() - min_frame.start_address(), + end: next_free.start_address() - min_frame.start_address(), }, kernel: UsedMemorySlice { start: kernel_slice_start.as_u64(), - len: kernel_slice_len, + end: kernel_slice_len, }, ramdisk: ramdisk_slice_start.map(|start| UsedMemorySlice { start: start.as_u64(), - len: ramdisk_slice_len, + end: ramdisk_slice_len, }), state: KernelRamIterState::Bootloader, } @@ -243,7 +243,7 @@ where } for slice in used_slices.clone() { - let slice_end = slice.start + slice.len; + let slice_end = slice.start + slice.end; if region.end <= slice.start || region.start >= slice_end { // region and slice don't overlap continue; From f3d383e1be77318a2ee6abf8e84f2b07816424f1 Mon Sep 17 00:00:00 2001 From: Burkhard Mittelbach Date: Sat, 22 Jun 2024 17:51:47 +0200 Subject: [PATCH 05/14] New test to check usable memory is writable split out the write check from the lower_memory_free test. Those 2 aren't really related --- Cargo.lock | 10 +++++ Cargo.toml | 2 + .../src/bin/lower_memory_free.rs | 12 ------ .../write_usable_memory/Cargo.toml | 12 ++++++ .../src/bin/write_usable_memory.rs | 42 +++++++++++++++++++ .../write_usable_memory/src/lib.rs | 27 ++++++++++++ tests/write_usable_memory.rs | 7 ++++ 7 files changed, 100 insertions(+), 12 deletions(-) create mode 100644 tests/test_kernels/write_usable_memory/Cargo.toml create mode 100644 tests/test_kernels/write_usable_memory/src/bin/write_usable_memory.rs create mode 100644 tests/test_kernels/write_usable_memory/src/lib.rs create mode 100644 tests/write_usable_memory.rs diff --git a/Cargo.lock b/Cargo.lock index f257994d..34decae3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -177,6 +177,7 @@ dependencies = [ "test_kernel_min_stack", "test_kernel_pie", "test_kernel_ramdisk", + "test_kernel_write_usable_memory", ] [[package]] @@ -1133,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 4de19416..4ff329fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ members = [ "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"] @@ -69,6 +70,7 @@ test_kernel_ramdisk = { path = "tests/test_kernels/ramdisk", artifact = "bin", t 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/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 index 1bc91aef..ff337ec2 100644 --- 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 @@ -7,7 +7,6 @@ use bootloader_api::{ use test_kernel_lower_memory_free::{exit_qemu, QemuExitCode}; const LOWER_MEMORY_END_PAGE: u64 = 0x0010_0000; -const WRITE_TEST_UNTIL: u64 = 0x4000_0000; pub const BOOTLOADER_CONFIG: BootloaderConfig = { let mut config = BootloaderConfig::new_default(); @@ -21,8 +20,6 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { use core::fmt::Write; use test_kernel_lower_memory_free::serial; - let phys_mem_offset = boot_info.physical_memory_offset.into_option().unwrap(); - let mut count = 0; for region in boot_info.memory_regions.iter() { writeln!( @@ -38,15 +35,6 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { let pages = (end - region.start) / 4096; count += pages; } - if region.kind == MemoryRegionKind::Usable && region.start < WRITE_TEST_UNTIL { - let end = core::cmp::min(region.end, WRITE_TEST_UNTIL); - // ensure region is actually writable - let addr = phys_mem_offset + region.start; - let size = end - region.start; - unsafe { - core::ptr::write_bytes(addr as *mut u8, 0xff, size as usize); - } - } } writeln!(serial(), "Free lower memory page count: {}", count).unwrap(); 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" + )); +} From 3326e7ae10f3e78526fd3c7c3d4529d00614e786 Mon Sep 17 00:00:00 2001 From: Burkhard Mittelbach Date: Sat, 22 Jun 2024 18:17:20 +0200 Subject: [PATCH 06/14] Remove BootloaderUsedMemorySliceIter --- bios/stage-4/src/main.rs | 5 +-- common/src/legacy_memory_region.rs | 72 +++++++++--------------------- 2 files changed, 21 insertions(+), 56 deletions(-) diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index 446a33a0..cb52d6dc 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -223,10 +223,7 @@ fn used_memory_slices(info: &BiosInfo) -> impl Iterator // uefi/bios common code [info.stage_3, info.stage_4, info.config_file] .into_iter() - .map(|region| UsedMemorySlice { - start: region.start, - end: region.start + region.len, - }) + .map(|region| UsedMemorySlice::new_from_len(region.start, region.len)) } /// Creates page table abstraction types for both the bootloader and kernel page tables. diff --git a/common/src/legacy_memory_region.rs b/common/src/legacy_memory_region.rs index fd32f548..57a8bdc6 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -18,6 +18,16 @@ pub struct UsedMemorySlice { 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. @@ -208,22 +218,18 @@ where ramdisk_slice_len: u64, used_slices: S, ) -> impl Iterator + Clone { - BootloaderUsedMemorySliceIter { - bootloader: UsedMemorySlice { + [ + UsedMemorySlice { start: min_frame.start_address().as_u64(), - // TODO: unit test that this is not an off by 1 - end: next_free.start_address() - min_frame.start_address(), + end: next_free.start_address().as_u64(), }, - kernel: UsedMemorySlice { - start: kernel_slice_start.as_u64(), - end: kernel_slice_len, - }, - ramdisk: ramdisk_slice_start.map(|start| UsedMemorySlice { - start: start.as_u64(), - end: ramdisk_slice_len, - }), - state: KernelRamIterState::Bootloader, - } + 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)), + ) .chain(used_slices) } @@ -368,41 +374,3 @@ where None } } - -#[derive(Clone)] -struct BootloaderUsedMemorySliceIter { - bootloader: UsedMemorySlice, - kernel: UsedMemorySlice, - ramdisk: Option, - state: KernelRamIterState, -} - -#[derive(Clone)] -enum KernelRamIterState { - Bootloader, - Kernel, - Ramdisk, - Done, -} - -impl Iterator for BootloaderUsedMemorySliceIter { - type Item = UsedMemorySlice; - - fn next(&mut self) -> Option { - match self.state { - KernelRamIterState::Bootloader => { - self.state = KernelRamIterState::Kernel; - Some(self.bootloader) - } - KernelRamIterState::Kernel => { - self.state = KernelRamIterState::Ramdisk; - Some(self.kernel) - } - KernelRamIterState::Ramdisk => { - self.state = KernelRamIterState::Done; - self.ramdisk - } - KernelRamIterState::Done => None, - } - } -} From 57632044b881913e5d5e8cf758ced3b97a507efe Mon Sep 17 00:00:00 2001 From: Burkhard Mittelbach Date: Sat, 22 Jun 2024 19:03:27 +0200 Subject: [PATCH 07/14] Improve construct_memory_map implementation simplify the overlap check into single if condition and move to interative implementation --- common/src/legacy_memory_region.rs | 111 +++++++++++------------------ 1 file changed, 40 insertions(+), 71 deletions(-) diff --git a/common/src/legacy_memory_region.rs b/common/src/legacy_memory_region.rs index 57a8bdc6..4b51c3fe 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -1,5 +1,6 @@ use bootloader_api::info::{MemoryRegion, MemoryRegionKind}; use core::{ + cmp, iter::{empty, Empty}, mem::MaybeUninit, }; @@ -235,7 +236,7 @@ where // TODO unit test fn split_and_add_region<'a, U>( - region: MemoryRegion, + mut region: MemoryRegion, regions: &mut [MaybeUninit], next_index: &mut usize, used_slices: U, @@ -243,85 +244,53 @@ where U: Iterator + Clone, { assert!(region.kind == MemoryRegionKind::Usable); - if region.start == region.end { - // skip zero sized regions - return; - } - - for slice in used_slices.clone() { - let slice_end = slice.start + slice.end; - if region.end <= slice.start || region.start >= slice_end { - // region and slice don't overlap - continue; - } - - if region.start >= slice.start && region.end <= slice_end { - // region is completly covered by slice - let bootloader = MemoryRegion { - start: region.start, - end: region.end, - kind: MemoryRegionKind::Bootloader, - }; - Self::add_region(bootloader, regions, next_index); - return; - } - if region.start < slice.start && region.end <= slice_end { - // there is a usable region before the bootloader slice - let before = MemoryRegion { - start: region.start, - end: slice.start, - kind: MemoryRegionKind::Usable, - }; - - let bootloader = MemoryRegion { - start: slice.start, - end: region.end, - kind: MemoryRegionKind::Bootloader, - }; - Self::split_and_add_region(before, regions, next_index, used_slices); - Self::add_region(bootloader, regions, next_index); - return; - } else if region.start < slice.start && region.end > slice_end { - // there is usable region before and after the bootloader slice - let before = MemoryRegion { + // 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: slice.start, + end: overlap_start, kind: MemoryRegionKind::Usable, }; let bootloader = MemoryRegion { - start: slice.start, - end: slice_end, + start: overlap_start, + end: overlap_end, kind: MemoryRegionKind::Bootloader, }; - let after = MemoryRegion { - start: slice_end, - end: region.end, - kind: MemoryRegionKind::Usable, - }; - Self::split_and_add_region(before, regions, next_index, used_slices.clone()); + Self::add_region(usable, regions, next_index); Self::add_region(bootloader, regions, next_index); - Self::split_and_add_region(after, regions, next_index, used_slices.clone()); - return; - } - if region.start >= slice.start && region.end > slice_end { - // there is a usable region after the bootloader slice - let bootloader = MemoryRegion { - start: region.start, - end: slice_end, - kind: MemoryRegionKind::Bootloader, - }; - let after = MemoryRegion { - start: slice_end, - end: region.end, - kind: MemoryRegionKind::Usable, - }; - Self::add_region(bootloader, regions, next_index); - Self::split_and_add_region(after, regions, next_index, used_slices); - return; + // 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; } } - // region is not coverd by any slice - Self::add_region(region, regions, next_index); } fn add_region( From 0c2103204140c65ad7560e6a9afedd2dabefc57c Mon Sep 17 00:00:00 2001 From: Burkhard Mittelbach Date: Sat, 22 Jun 2024 19:06:45 +0200 Subject: [PATCH 08/14] tests for LegacyFrameAllocator::construct_memory_map This test also covers #445 --- .github/workflows/ci.yml | 2 + common/src/legacy_memory_region.rs | 246 +++++++++++++++++++++++++++++ common/src/lib.rs | 2 +- 3 files changed, 249 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec90853d..09170a46 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,8 @@ jobs: - uses: r7kamura/rust-problem-matchers@v1.1.0 - name: Run api tests run: cargo test -p bootloader_api + - name: Run bootloader common tests + run: cargo test -p bootloader-x86_64-common - name: Run integration tests run: cargo test -- --test-threads 1 diff --git a/common/src/legacy_memory_region.rs b/common/src/legacy_memory_region.rs index 4b51c3fe..c2c5c238 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -343,3 +343,249 @@ where None } } + +#[cfg(test)] +mod tests { + use super::*; + use bootloader_api::info::MemoryRegionKind; + + #[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_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 9eae870d..4a880188 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)] From 0f4dd1f074dc239c7f332d214d22a99980d912fe Mon Sep 17 00:00:00 2001 From: Burkhard Mittelbach Date: Sat, 22 Jun 2024 19:24:11 +0200 Subject: [PATCH 09/14] Test to ensure MemoryRegions are always frame aligned --- .github/workflows/ci.yml | 1 + common/src/legacy_memory_region.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09170a46..6cd687ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,6 +69,7 @@ jobs: - name: Run api tests run: cargo test -p bootloader_api - name: Run bootloader common tests + if: runner.os == 'Linux' run: cargo test -p bootloader-x86_64-common - name: Run integration tests run: cargo test -- --test-threads 1 diff --git a/common/src/legacy_memory_region.rs b/common/src/legacy_memory_region.rs index c2c5c238..f76ccb9a 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -389,6 +389,33 @@ mod tests { }] } + #[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(); From 9b3efcbef5bf1c060d1c96c33ab3cd14ab148130 Mon Sep 17 00:00:00 2001 From: Burkhard Mittelbach Date: Sun, 23 Jun 2024 12:30:55 +0200 Subject: [PATCH 10/14] fixup workflow condition --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6cd687ec..e2d9e796 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,7 @@ jobs: - name: Run api tests run: cargo test -p bootloader_api - name: Run bootloader common tests - if: runner.os == 'Linux' + if: runner.arch == 'x86' run: cargo test -p bootloader-x86_64-common - name: Run integration tests run: cargo test -- --test-threads 1 From b0cae120fd6b5c2047c9b93293b4475ee60c10c9 Mon Sep 17 00:00:00 2001 From: Burkhard Mittelbach Date: Sun, 23 Jun 2024 13:02:27 +0200 Subject: [PATCH 11/14] Ensure MemoryRegions are aligned to 4096 --- common/src/legacy_memory_region.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/common/src/legacy_memory_region.rs b/common/src/legacy_memory_region.rs index f76ccb9a..bdd9a63f 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -5,6 +5,7 @@ use core::{ mem::MaybeUninit, }; use x86_64::{ + align_down, align_up, structures::paging::{FrameAllocator, PhysFrame, Size4KiB}, PhysAddr, }; @@ -232,9 +233,12 @@ where .map(|start| UsedMemorySlice::new_from_len(start.as_u64(), ramdisk_slice_len)), ) .chain(used_slices) + .map(|slice| UsedMemorySlice { + start: align_down(slice.start, 0x1000), + end: align_up(slice.end, 0x1000), + }) } - // TODO unit test fn split_and_add_region<'a, U>( mut region: MemoryRegion, regions: &mut [MaybeUninit], @@ -347,7 +351,6 @@ where #[cfg(test)] mod tests { use super::*; - use bootloader_api::info::MemoryRegionKind; #[derive(Copy, Clone, Debug)] struct TestMemoryRegion { From b36417fd091932557e4556f09dea3816de28ac59 Mon Sep 17 00:00:00 2001 From: Burkhard Mittelbach Date: Sun, 23 Jun 2024 13:24:57 +0200 Subject: [PATCH 12/14] fix max memory region calculation --- common/src/legacy_memory_region.rs | 8 ++++++++ common/src/lib.rs | 4 +--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/common/src/legacy_memory_region.rs b/common/src/legacy_memory_region.rs index bdd9a63f..2a0eee3e 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -152,6 +152,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 slice can split an original region into 3 new regions, + // this means we need to reserve 2 extra spaces for each slice. + // There are 3 additional slices (kernel, ramdisk and the bootloader heap) + self.len() + (3 + self.used_slices.clone().count()) * 2 + } + /// 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 diff --git a/common/src/lib.rs b/common/src/lib.rs index 4a880188..9280eb14 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -487,9 +487,7 @@ where // allocate and map space for the boot info let (boot_info, memory_regions) = { let boot_info_layout = Layout::new::(); - // TODO the assumption here about the regions is wrong - // figure out what the correct region count should be - let regions = frame_allocator.len() + 4 + 30; // 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(); From 3b07272dba12fb877f5679ffe07820ce53edccd8 Mon Sep 17 00:00:00 2001 From: Burkhard Mittelbach Date: Sun, 23 Jun 2024 16:55:50 +0200 Subject: [PATCH 13/14] fix spelling mistake Co-authored-by: Tom Dohrmann --- .../test_kernels/lower_memory_free/src/bin/lower_memory_free.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index ff337ec2..9620d1ca 100644 --- 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 @@ -38,7 +38,7 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { } writeln!(serial(), "Free lower memory page count: {}", count).unwrap(); - assert!(count > 0x10); // 0x10 chosen arbirarily, we need _some_ free conventional memory, but not all of it. Some, especially on BIOS, may be reserved for hardware. + 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); } From 25f48f7e65600ff3acabeb1f7fcc1bae61fb7f83 Mon Sep 17 00:00:00 2001 From: Burkhard Mittelbach Date: Sun, 23 Jun 2024 22:35:45 +0200 Subject: [PATCH 14/14] Remove used_slice concept from LegacyFrameAllocator --- bios/common/src/lib.rs | 1 - bios/stage-2/src/main.rs | 4 -- bios/stage-4/src/main.rs | 14 +---- common/src/legacy_memory_region.rs | 83 +++++++++--------------------- common/src/lib.rs | 16 +++--- 5 files changed, 32 insertions(+), 86 deletions(-) diff --git a/bios/common/src/lib.rs b/bios/common/src/lib.rs index de20d0e6..166e12df 100644 --- a/bios/common/src/lib.rs +++ b/bios/common/src/lib.rs @@ -5,7 +5,6 @@ pub mod racy_cell; #[cfg_attr(feature = "debug", derive(Debug))] #[repr(C)] pub struct BiosInfo { - pub stage_3: Region, pub stage_4: Region, pub kernel: Region, pub ramdisk: Region, diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index 95ec8c79..b5b07d65 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -145,10 +145,6 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { vesa_mode.enable().unwrap(); let mut info = BiosInfo { - stage_3: Region { - start: STAGE_3_DST as u64, - len: stage_3_len, - }, stage_4: Region { start: stage_4_dst as u64, len: stage_4_len, diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index cb52d6dc..cf159a61 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -4,8 +4,7 @@ use crate::memory_descriptor::MemoryRegion; use bootloader_api::info::{FrameBufferInfo, PixelFormat}; use bootloader_boot_config::{BootConfig, LevelFilter}; -use bootloader_x86_64_bios_common::{BiosFramebufferInfo, BiosInfo, E820MemoryRegion, Region}; -use bootloader_x86_64_common::legacy_memory_region::UsedMemorySlice; +use bootloader_x86_64_bios_common::{BiosFramebufferInfo, BiosInfo, E820MemoryRegion}; use bootloader_x86_64_common::RawFrameBufferInfo; use bootloader_x86_64_common::{ legacy_memory_region::LegacyFrameAllocator, load_and_switch_to_kernel, Kernel, PageTables, @@ -56,10 +55,9 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { }; let kernel_size = info.kernel.len; let next_free_frame = PhysFrame::containing_address(PhysAddr::new(info.last_used_addr)) + 1; - let mut frame_allocator = LegacyFrameAllocator::new_with_used_slices( + let mut frame_allocator = LegacyFrameAllocator::new_starting_at( next_free_frame, memory_map.iter().copied().map(MemoryRegion), - used_memory_slices(info), ); // We identity-mapped all memory, so the offset between physical and virtual addresses is 0 @@ -218,14 +216,6 @@ fn init_logger( framebuffer_info } -fn used_memory_slices(info: &BiosInfo) -> impl Iterator + Clone { - // skip kernel and ramdisk because they are handled individually by the - // uefi/bios common code - [info.stage_3, info.stage_4, info.config_file] - .into_iter() - .map(|region| UsedMemorySlice::new_from_len(region.start, region.len)) -} - /// Creates page table abstraction types for both the bootloader and kernel page tables. fn create_page_tables(frame_allocator: &mut impl FrameAllocator) -> PageTables { // We identity-mapped all memory, so the offset between physical and virtual addresses is 0 diff --git a/common/src/legacy_memory_region.rs b/common/src/legacy_memory_region.rs index 2a0eee3e..ccff22fb 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -48,19 +48,18 @@ pub trait LegacyMemoryRegion: Copy + core::fmt::Debug { } /// A physical frame allocator based on a BIOS or UEFI provided memory map. -pub struct LegacyFrameAllocator { +pub struct LegacyFrameAllocator { original: I, memory_map: I, current_descriptor: Option, next_frame: PhysFrame, min_frame: PhysFrame, - used_slices: S, } /// 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> +impl LegacyFrameAllocator where I: ExactSizeIterator + Clone, I::Item: LegacyMemoryRegion, @@ -82,28 +81,16 @@ where /// 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 { - Self::new_with_used_slices(frame, memory_map, empty()) - } -} - -impl LegacyFrameAllocator -where - I: ExactSizeIterator + Clone, - I::Item: LegacyMemoryRegion, - S: Iterator + Clone, -{ - pub fn new_with_used_slices(start_frame: PhysFrame, memory_map: I, used_slices: S) -> 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(start_frame, lower_mem_end); + 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, - used_slices, } } @@ -154,10 +141,10 @@ where /// Calculate the maximum number of regions produced by [Self::construct_memory_map] pub fn memory_map_max_region_count(&self) -> usize { - // every used slice can split an original region into 3 new regions, - // this means we need to reserve 2 extra spaces for each slice. - // There are 3 additional slices (kernel, ramdisk and the bootloader heap) - self.len() + (3 + self.used_slices.clone().count()) * 2 + // 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. @@ -174,15 +161,22 @@ where ramdisk_slice_start: Option, ramdisk_slice_len: u64, ) -> &mut [MemoryRegion] { - let used_slices = Self::used_regions_iter( - self.min_frame, - self.next_frame, - kernel_slice_start, - kernel_slice_len, - ramdisk_slice_start, - ramdisk_slice_len, - self.used_slices, - ); + 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 { @@ -219,34 +213,6 @@ where } } - fn used_regions_iter( - min_frame: PhysFrame, - next_free: PhysFrame, - kernel_slice_start: PhysAddr, - kernel_slice_len: u64, - ramdisk_slice_start: Option, - ramdisk_slice_len: u64, - used_slices: S, - ) -> impl Iterator + Clone { - [ - UsedMemorySlice { - start: min_frame.start_address().as_u64(), - end: next_free.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)), - ) - .chain(used_slices) - .map(|slice| UsedMemorySlice { - start: align_down(slice.start, 0x1000), - end: align_up(slice.end, 0x1000), - }) - } - fn split_and_add_region<'a, U>( mut region: MemoryRegion, regions: &mut [MaybeUninit], @@ -325,11 +291,10 @@ where } } -unsafe impl FrameAllocator for LegacyFrameAllocator +unsafe impl FrameAllocator for LegacyFrameAllocator where I: ExactSizeIterator + Clone, I::Item: LegacyMemoryRegion, - S: Iterator + Clone, { fn allocate_frame(&mut self) -> Option> { if let Some(current_descriptor) = self.current_descriptor { diff --git a/common/src/lib.rs b/common/src/lib.rs index 9280eb14..1c8b1efe 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -10,7 +10,6 @@ use bootloader_api::{ }; use bootloader_boot_config::{BootConfig, LevelFilter}; use core::{alloc::Layout, arch::asm, mem::MaybeUninit, slice}; -use legacy_memory_region::UsedMemorySlice; use level_4_entries::UsedLevel4Entries; use usize_conversions::FromUsize; use x86_64::{ @@ -124,17 +123,16 @@ impl<'a> Kernel<'a> { /// This function is a convenience function that first calls [`set_up_mappings`], then /// [`create_boot_info`], and finally [`switch_to_kernel`]. The given arguments are passed /// directly to these functions, so see their docs for more info. -pub fn load_and_switch_to_kernel( +pub fn load_and_switch_to_kernel( kernel: Kernel, boot_config: BootConfig, - mut frame_allocator: LegacyFrameAllocator, + mut frame_allocator: LegacyFrameAllocator, mut page_tables: PageTables, system_info: SystemInfo, ) -> ! where I: ExactSizeIterator + Clone, D: LegacyMemoryRegion, - S: Iterator + Clone, { let config = kernel.config; let mut mappings = set_up_mappings( @@ -170,9 +168,9 @@ where /// /// This function reacts to unexpected situations (e.g. invalid kernel ELF file) with a panic, so /// errors are not recoverable. -pub fn set_up_mappings( +pub fn set_up_mappings( kernel: Kernel, - frame_allocator: &mut LegacyFrameAllocator, + frame_allocator: &mut LegacyFrameAllocator, page_tables: &mut PageTables, framebuffer: Option<&RawFrameBufferInfo>, config: &BootloaderConfig, @@ -181,7 +179,6 @@ pub fn set_up_mappings( where I: ExactSizeIterator + Clone, D: LegacyMemoryRegion, - S: Iterator + Clone, { let kernel_page_table = &mut page_tables.kernel; @@ -469,10 +466,10 @@ pub struct Mappings { /// address space at the same address. This makes it possible to return a Rust /// reference that is valid in both address spaces. The necessary physical frames /// are taken from the given `frame_allocator`. -pub fn create_boot_info( +pub fn create_boot_info( config: &BootloaderConfig, boot_config: &BootConfig, - mut frame_allocator: LegacyFrameAllocator, + mut frame_allocator: LegacyFrameAllocator, page_tables: &mut PageTables, mappings: &mut Mappings, system_info: SystemInfo, @@ -480,7 +477,6 @@ pub fn create_boot_info( where I: ExactSizeIterator + Clone, D: LegacyMemoryRegion, - S: Iterator + Clone, { log::info!("Allocate bootinfo");