diff --git a/integration-test/bins/multiboot2_chainloader/Cargo.toml b/integration-test/bins/multiboot2_chainloader/Cargo.toml index 25515ef8..19e08e7f 100644 --- a/integration-test/bins/multiboot2_chainloader/Cargo.toml +++ b/integration-test/bins/multiboot2_chainloader/Cargo.toml @@ -12,7 +12,6 @@ publish = false anyhow.workspace = true log.workspace = true good_memory_allocator.workspace = true -multiboot = "0.8" multiboot2.workspace = true multiboot2-header.workspace = true util.workspace = true diff --git a/integration-test/bins/multiboot2_chainloader/link.ld b/integration-test/bins/multiboot2_chainloader/link.ld index 754a44f9..c45c6c74 100644 --- a/integration-test/bins/multiboot2_chainloader/link.ld +++ b/integration-test/bins/multiboot2_chainloader/link.ld @@ -11,9 +11,9 @@ PHDRS SECTIONS { /* Chainloader linked at 8M, payload at 16M */ - .text 8M : AT(8M) ALIGN(4K) + .text 12M : AT(12M) ALIGN(4K) { - KEEP(*(.multiboot_header)); + KEEP(*(.multiboot2_header)); *(.text .text.*) } : kernel_rx diff --git a/integration-test/bins/multiboot2_chainloader/src/loader.rs b/integration-test/bins/multiboot2_chainloader/src/loader.rs index e543867c..d7465e7d 100644 --- a/integration-test/bins/multiboot2_chainloader/src/loader.rs +++ b/integration-test/bins/multiboot2_chainloader/src/loader.rs @@ -1,19 +1,57 @@ use alloc::boxed::Box; use elf_rs::{ElfFile, ProgramHeaderEntry, ProgramType}; +use log::{debug, info}; use multiboot2::{ BootLoaderNameTag, CommandLineTag, MaybeDynSized, MemoryArea, MemoryAreaType, MemoryMapTag, ModuleTag, SmbiosTag, }; +fn get_free_mmap_areas( + mbi: &multiboot2::BootInformation, +) -> Vec<(u64 /* start */, u64 /* size */)> { + match (mbi.memory_map_tag(), mbi.efi_memory_map_tag()) { + (Some(mmt), None) => mmt + .memory_areas() + .iter() + .filter(|ma| ma.typ() == MemoryAreaType::Available) + .map(|ma| (ma.start_address(), ma.size())) + .collect::>(), + (None, Some(mmt)) => mmt + .memory_areas() + .filter(|ma| ma.ty == EFIMemoryAreaType::CONVENTIONAL) + .map(|ma| (ma.phys_start, ma.page_count * 4096)) + .collect::>(), + _ => panic!("Invalid memory map"), + } +} + +fn assert_load_segment_fits_into_memory( + start: u64, + size: u64, + free_areas: &[(u64 /* start */, u64 /* size */)], +) { + let end = start + size; + let range = free_areas + .iter() + .find(|(a_start, a_size)| start >= *a_start && end <= a_start + a_size); + if let Some(range) = range { + debug!("Can load load segment (0x{start:x?}, {size:x?}) into free memory area {range:#x?}"); + } else { + panic!("Can't load load segment (0x{start:x?}, {size:x?}) into any area!"); + } +} + /// Loads the first module into memory. Assumes that the module is a ELF file. /// The handoff is performed according to the Multiboot2 spec. -pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! { +pub fn load_module(mbi: &multiboot2::BootInformation) -> ! { + let mut modules = mbi.module_tags(); + // Load the ELF from the Multiboot1 boot module. let elf_mod = modules.next().expect("Should have payload"); let elf_bytes = unsafe { core::slice::from_raw_parts( - elf_mod.start as *const u64 as *const u8, - (elf_mod.end - elf_mod.start) as usize, + elf_mod.start_address() as *const u64 as *const u8, + elf_mod.module_size() as usize, ) }; let elf = elf_rs::Elf32::from_bytes(elf_bytes).expect("Should be valid ELF"); @@ -28,10 +66,11 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! { log::info!("Multiboot2 header:\n{hdr:#?}"); } - // Map the load segments into memory (at their corresponding link). + // Load the load segments into memory (at their corresponding link address). { - let elf = elf_rs::Elf32::from_bytes(elf_bytes).expect("Should be valid ELF"); + let free_areas = get_free_mmap_areas(mbi); elf.program_header_iter() + .inspect(|ph| assert_load_segment_fits_into_memory(ph.vaddr(), ph.memsz(), &free_areas)) .filter(|ph| ph.ph_type() == ProgramType::LOAD) .for_each(|ph| { map_memory(ph); @@ -66,7 +105,7 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! { log::info!( "Handing over to ELF: {}", - elf_mod.string.unwrap_or("") + elf_mod.cmdline().unwrap_or("") ); // handoff @@ -84,7 +123,7 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! { /// address space. The loader assumes that the addresses to not clash with the /// loader (or anything else). fn map_memory(ph: ProgramHeaderEntry) { - log::debug!("Mapping LOAD segment {ph:#?}"); + debug!("Mapping LOAD segment {ph:#?}"); let dest_ptr = ph.vaddr() as *mut u8; let content = ph.content().expect("Should have content"); unsafe { core::ptr::copy(content.as_ptr(), dest_ptr, content.len()) }; diff --git a/integration-test/bins/multiboot2_chainloader/src/main.rs b/integration-test/bins/multiboot2_chainloader/src/main.rs index ca6b994d..15139752 100644 --- a/integration-test/bins/multiboot2_chainloader/src/main.rs +++ b/integration-test/bins/multiboot2_chainloader/src/main.rs @@ -3,7 +3,6 @@ #![feature(error_in_core)] mod loader; -mod multiboot; extern crate alloc; @@ -12,6 +11,7 @@ extern crate util; use util::init_environment; +core::arch::global_asm!(include_str!("multiboot2_header.S"), options(att_syntax)); core::arch::global_asm!(include_str!("start.S"), options(att_syntax)); /// Entry into the Rust code from assembly using the x86 SystemV calling @@ -20,7 +20,21 @@ core::arch::global_asm!(include_str!("start.S"), options(att_syntax)); fn rust_entry(multiboot_magic: u32, multiboot_hdr: *const u32) -> ! { init_environment(); log::debug!("multiboot_hdr={multiboot_hdr:x?}, multiboot_magic=0x{multiboot_magic:x?}"); - let mbi = multiboot::get_mbi(multiboot_magic, multiboot_hdr as u32).unwrap(); - let module_iter = mbi.modules().expect("Should provide modules"); - loader::load_module(module_iter); + assert_eq!(multiboot_magic, multiboot2::MAGIC); + let mbi = unsafe { multiboot2::BootInformation::load(multiboot_hdr.cast()) }.unwrap(); + + if let Some(mmap) = mbi.efi_memory_map_tag() { + log::debug!("efi memory map:",); + for desc in mmap.memory_areas() { + log::warn!( + " start=0x{:016x?} size={:016x?} type={:?}, attr={:?}", + desc.phys_start, + desc.page_count * 4096, + desc.ty, + desc.att + ); + } + } + + loader::load_module(&mbi); } diff --git a/integration-test/bins/multiboot2_chainloader/src/multiboot.rs b/integration-test/bins/multiboot2_chainloader/src/multiboot.rs deleted file mode 100644 index 0a0bd5f6..00000000 --- a/integration-test/bins/multiboot2_chainloader/src/multiboot.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! Parsing the Multiboot information. Glue code for the [`multiboot`] code. - -use anyhow::anyhow; -use core::ptr::addr_of_mut; -use core::slice; -use multiboot::information::{MemoryManagement, Multiboot, PAddr, SIGNATURE_EAX}; - -static mut MEMORY_MANAGEMENT: Mem = Mem; - -/// Returns an object to access the fields of the Multiboot information -/// structure. -pub fn get_mbi<'a>(magic: u32, ptr: u32) -> anyhow::Result> { - if magic != SIGNATURE_EAX { - return Err(anyhow!("Unknown Multiboot signature {magic:x}")); - } - let mmgmt: &mut dyn MemoryManagement = unsafe { &mut *addr_of_mut!(MEMORY_MANAGEMENT) }; - unsafe { Multiboot::from_ptr(ptr as u64, mmgmt) }.ok_or(anyhow!( - "Can't read Multiboot boot information from pointer" - )) -} - -/// Glue object between the global allocator and the multiboot crate. -struct Mem; - -impl MemoryManagement for Mem { - unsafe fn paddr_to_slice(&self, addr: PAddr, size: usize) -> Option<&'static [u8]> { - let ptr = addr as *const u64 as *const u8; - Some(slice::from_raw_parts(ptr, size)) - } - - // If you only want to read fields, you can simply return `None`. - unsafe fn allocate(&mut self, _length: usize) -> Option<(PAddr, &mut [u8])> { - None - } - - unsafe fn deallocate(&mut self, addr: PAddr) { - if addr != 0 { - unimplemented!() - } - } -} diff --git a/integration-test/bins/multiboot2_chainloader/src/multiboot2_header.S b/integration-test/bins/multiboot2_chainloader/src/multiboot2_header.S new file mode 100644 index 00000000..e2913ece --- /dev/null +++ b/integration-test/bins/multiboot2_chainloader/src/multiboot2_header.S @@ -0,0 +1,25 @@ +# The assembly code uses the GNU Assembly (GAS) flavor with Intel noprefix +# syntax. + +# Symbol from main.rs +.EXTERN start + +.code32 +.align 8 +.section .multiboot2_header + + mb2_header_start: + .long 0xe85250d6 # magic number + .long 0 # architecture 0 (protected mode i386) + .long mb2_header_end - mb2_header_start # header length + # checksum + .long 0x100000000 - (0xe85250d6 + 0 + (mb2_header_end - mb2_header_start)) + + # REQUIRED END TAG + .align 8 + .Lmb2_header_tag_end_start: + .word 0 # type (16bit) + .word 0 # flags (16bit) + .long .Lmb2_header_tag_end_end - .Lmb2_header_tag_end_start # size (32bit) + .Lmb2_header_tag_end_end: + mb2_header_end: diff --git a/integration-test/bins/multiboot2_chainloader/src/start.S b/integration-test/bins/multiboot2_chainloader/src/start.S index 4ce965cc..494cb669 100644 --- a/integration-test/bins/multiboot2_chainloader/src/start.S +++ b/integration-test/bins/multiboot2_chainloader/src/start.S @@ -3,19 +3,6 @@ .code32 -.section .multiboot_header, "a", @progbits - -/* - * Multiboot v1 Header. - * Required so that we can be booted by QEMU via the "-kernel" parameter. - */ -.align 8 -.global multiboot_header -multiboot_header: - .long 0x1badb002 - .long 0x0 - .long -0x1badb002 - .section .text .global start diff --git a/integration-test/bins/multiboot2_payload/link.ld b/integration-test/bins/multiboot2_payload/link.ld index 4891d4e6..5508227d 100644 --- a/integration-test/bins/multiboot2_payload/link.ld +++ b/integration-test/bins/multiboot2_payload/link.ld @@ -11,9 +11,9 @@ PHDRS SECTIONS { /* Chainloader linked at 8M, payload at 16M */ - .text 16M : AT(16M) ALIGN(4K) + .text 24M : AT(24M) ALIGN(4K) { - *(.multiboot2_header) + KEEP(*(.multiboot2_header)); *(.text .text.*) } : kernel_rx diff --git a/integration-test/bins/multiboot2_payload/src/multiboot2_header.S b/integration-test/bins/multiboot2_payload/src/multiboot2_header.S index efd90649..ac6be2e9 100644 --- a/integration-test/bins/multiboot2_payload/src/multiboot2_header.S +++ b/integration-test/bins/multiboot2_payload/src/multiboot2_header.S @@ -1,4 +1,3 @@ -# Multiboot2 Header definition. # The assembly code uses the GNU Assembly (GAS) flavor with Intel noprefix # syntax. diff --git a/integration-test/bins/util/src/allocator.rs b/integration-test/bins/util/src/allocator.rs index 017f3c2e..4caea3eb 100644 --- a/integration-test/bins/util/src/allocator.rs +++ b/integration-test/bins/util/src/allocator.rs @@ -1,10 +1,10 @@ use good_memory_allocator::SpinLockedAllocator; -#[repr(align(0x4000))] -struct Align16K(T); +#[repr(align(0x1000))] +struct PageAlign(T); -/// 16 KiB naturally aligned backing storage for heap. -static mut HEAP: Align16K<[u8; 0x4000]> = Align16K([0; 0x4000]); +/// 16 KiB page-aligned backing storage for heap. +static mut HEAP: PageAlign<[u8; 0x4000]> = PageAlign([0; 0x4000]); #[global_allocator] static ALLOCATOR: SpinLockedAllocator = SpinLockedAllocator::empty(); diff --git a/integration-test/run.sh b/integration-test/run.sh index 153aaa69..2bafed0c 100755 --- a/integration-test/run.sh +++ b/integration-test/run.sh @@ -44,11 +44,12 @@ function fn_build_rust_bins() { cargo build --release --verbose cd - + echo "Verifying multiboot2_chainloader ..." test -f $BINS_DIR/multiboot2_chainloader file --brief $BINS_DIR/multiboot2_chainloader | grep -q "ELF 32-bit LSB executable" - # For simplicity, the chainloader itself boots via Multiboot 1. Sufficient. - grub-file --is-x86-multiboot $BINS_DIR/multiboot2_chainloader + grub-file --is-x86-multiboot2 $BINS_DIR/multiboot2_chainloader + echo "Verifying multiboot2_payload ..." test -f $BINS_DIR/multiboot2_payload file --brief $BINS_DIR/multiboot2_payload | grep -q "ELF 32-bit LSB executable" grub-file --is-x86-multiboot2 $BINS_DIR/multiboot2_payload @@ -155,7 +156,7 @@ function fn_test_loader() { fn_build_limine_iso fn_run_test_bios $TEST_DIR/image.iso - #fn_run_test_uefi $TEST_DIR/image.iso + fn_run_test_uefi $TEST_DIR/image.iso } fn_main diff --git a/integration-test/tests/02-boot-loader-and-chainload/limine.cfg b/integration-test/tests/02-boot-loader-and-chainload/limine.cfg index 9ad47ad7..b2e90d94 100644 --- a/integration-test/tests/02-boot-loader-and-chainload/limine.cfg +++ b/integration-test/tests/02-boot-loader-and-chainload/limine.cfg @@ -4,8 +4,7 @@ VERBOSE=yes INTERFACE_BRANDING=integration-test :integration-test -# For simplicity reasons, the loader itself boots via Multiboot 1. Sufficient. -PROTOCOL=multiboot +PROTOCOL=multiboot2 KERNEL_PATH=boot:///kernel KERNEL_CMDLINE=some kernel cmdline MODULE_PATH=boot:///payload