diff --git a/Cargo.lock b/Cargo.lock index 5d03193b..f762461b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,6 +366,15 @@ dependencies = [ "x86_64 0.13.6", ] +[[package]] +name = "test_kernel_higher_half" +version = "0.1.0" +dependencies = [ + "bootloader", + "uart_16550", + "x86_64 0.13.6", +] + [[package]] name = "test_kernel_map_phys_mem" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index e61b3174..3ee13ee0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "tests/runner", "tests/test_kernels/default_settings", "tests/test_kernels/map_phys_mem", + "tests/test_kernels/higher_half", ] [[bin]] diff --git a/Changelog.md b/Changelog.md index 4d2a809d..3100587f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,8 @@ # Unreleased - Fix build on latest Rust nightly by updating to `uefi` v0.9.0 ([#162](https://github.com/rust-osdev/bootloader/pull/162)) +- Fix higher half kernels by identity mapping context switch fn earlier ([#161](https://github.com/rust-osdev/bootloader/pull/161)) + - Also: improve reporting of mapping errors # 0.10.3 – 2021-05-05 diff --git a/src/binary/mod.rs b/src/binary/mod.rs index de5d7de6..caa56748 100644 --- a/src/binary/mod.rs +++ b/src/binary/mod.rs @@ -10,10 +10,9 @@ use level_4_entries::UsedLevel4Entries; use parsed_config::CONFIG; use usize_conversions::FromUsize; use x86_64::{ - registers, structures::paging::{ FrameAllocator, Mapper, OffsetPageTable, Page, PageTableFlags, PageTableIndex, PhysFrame, - Size2MiB, Size4KiB, + Size2MiB, }, PhysAddr, VirtAddr, }; @@ -90,13 +89,13 @@ where system_info.framebuffer_addr, system_info.framebuffer_info.byte_len, ); - let (boot_info, two_frames) = create_boot_info( + let boot_info = create_boot_info( frame_allocator, &mut page_tables, &mut mappings, system_info, ); - switch_to_kernel(page_tables, mappings, boot_info, two_frames); + switch_to_kernel(page_tables, mappings, boot_info); } /// Sets up mappings for a kernel stack and the framebuffer. @@ -148,9 +147,27 @@ where .allocate_frame() .expect("frame allocation failed when mapping a kernel stack"); let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; - unsafe { kernel_page_table.map_to(page, frame, flags, frame_allocator) } - .unwrap() - .flush(); + match unsafe { kernel_page_table.map_to(page, frame, flags, frame_allocator) } { + Ok(tlb) => tlb.flush(), + Err(err) => panic!("failed to map page {:?}: {:?}", page, err), + } + } + + // identity-map context switch function, so that we don't get an immediate pagefault + // after switching the active page table + let context_switch_function = PhysAddr::new(context_switch as *const () as u64); + let context_switch_function_start_frame: PhysFrame = + PhysFrame::containing_address(context_switch_function); + for frame in PhysFrame::range_inclusive( + context_switch_function_start_frame, + context_switch_function_start_frame + 1, + ) { + match unsafe { + kernel_page_table.identity_map(frame, PageTableFlags::PRESENT, frame_allocator) + } { + Ok(tlb) => tlb.flush(), + Err(err) => panic!("failed to identity map frame {:?}: {:?}", frame, err), + } } // map framebuffer @@ -166,9 +183,13 @@ where { let page = start_page + u64::from_usize(i); let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; - unsafe { kernel_page_table.map_to(page, frame, flags, frame_allocator) } - .unwrap() - .flush(); + match unsafe { kernel_page_table.map_to(page, frame, flags, frame_allocator) } { + Ok(tlb) => tlb.flush(), + Err(err) => panic!( + "failed to map page {:?} to frame {:?}: {:?}", + page, frame, err + ), + } } let framebuffer_virt_addr = start_page.start_address(); Some(framebuffer_virt_addr) @@ -189,9 +210,13 @@ where for frame in PhysFrame::range_inclusive(start_frame, end_frame) { let page = Page::containing_address(offset + frame.start_address().as_u64()); let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; - unsafe { kernel_page_table.map_to(page, frame, flags, frame_allocator) } - .unwrap() - .ignore(); + match unsafe { kernel_page_table.map_to(page, frame, flags, frame_allocator) } { + Ok(tlb) => tlb.ignore(), + Err(err) => panic!( + "failed to map page {:?} to frame {:?}: {:?}", + page, frame, err + ), + }; } Some(offset) @@ -262,7 +287,7 @@ pub fn create_boot_info<I, D>( page_tables: &mut PageTables, mappings: &mut Mappings, system_info: SystemInfo, -) -> (&'static mut BootInfo, TwoFrames) +) -> &'static mut BootInfo where I: ExactSizeIterator<Item = D> + Clone, D: LegacyMemoryRegion, @@ -286,21 +311,23 @@ where let frame = frame_allocator .allocate_frame() .expect("frame allocation for boot info failed"); - unsafe { + match unsafe { page_tables .kernel .map_to(page, frame, flags, &mut frame_allocator) + } { + Ok(tlb) => tlb.flush(), + Err(err) => panic!("failed to map page {:?}: {:?}", page, err), } - .unwrap() - .flush(); // we need to be able to access it too - unsafe { + match unsafe { page_tables .bootloader .map_to(page, frame, flags, &mut frame_allocator) + } { + Ok(tlb) => tlb.flush(), + Err(err) => panic!("failed to map page {:?}: {:?}", page, err), } - .unwrap() - .flush(); } let boot_info: &'static mut MaybeUninit<BootInfo> = @@ -310,9 +337,6 @@ where (boot_info, memory_regions) }; - // reserve two unused frames for context switch - let two_frames = TwoFrames::new(&mut frame_allocator); - log::info!("Create Memory Map"); // build memory map @@ -341,7 +365,7 @@ where tls_template: mappings.tls_template.into(), }); - (boot_info, two_frames) + boot_info } /// Switches to the kernel address space and jumps to the kernel entry point. @@ -349,11 +373,9 @@ pub fn switch_to_kernel( page_tables: PageTables, mappings: Mappings, boot_info: &'static mut BootInfo, - two_frames: TwoFrames, ) -> ! { let PageTables { kernel_level_4_frame, - kernel: kernel_page_table, .. } = page_tables; let addresses = Addresses { @@ -369,7 +391,7 @@ pub fn switch_to_kernel( ); unsafe { - context_switch(addresses, kernel_page_table, two_frames); + context_switch(addresses); } } @@ -388,32 +410,7 @@ pub struct PageTables { } /// Performs the actual context switch. -/// -/// This function uses the given `frame_allocator` to identity map itself in the kernel-level -/// page table. This is required to avoid a page fault after the context switch. Since this -/// function is relatively small, only up to two physical frames are required from the frame -/// allocator, so the [`TwoFrames`] type can be used here. -unsafe fn context_switch( - addresses: Addresses, - mut kernel_page_table: OffsetPageTable, - mut frame_allocator: impl FrameAllocator<Size4KiB>, -) -> ! { - // identity-map current and next frame, so that we don't get an immediate pagefault - // after switching the active page table - let current_addr = PhysAddr::new(registers::read_rip().as_u64()); - let current_frame: PhysFrame = PhysFrame::containing_address(current_addr); - for frame in PhysFrame::range_inclusive(current_frame, current_frame + 1) { - unsafe { - kernel_page_table.identity_map(frame, PageTableFlags::PRESENT, &mut frame_allocator) - } - .unwrap() - .flush(); - } - - // we don't need the kernel page table anymore - mem::drop(kernel_page_table); - - // do the context switch +unsafe fn context_switch(addresses: Addresses) -> ! { unsafe { asm!( "mov cr3, {}; mov rsp, {}; push 0; jmp {}", @@ -434,36 +431,6 @@ struct Addresses { boot_info: &'static mut crate::boot_info::BootInfo, } -/// Used for reversing two physical frames for identity mapping the context switch function. -/// -/// In order to prevent a page fault, the context switch function must be mapped identically in -/// both address spaces. The context switch function is small, so this mapping requires only -/// two physical frames (one frame is not enough because the linker might place the function -/// directly before a page boundary). Since the frame allocator no longer exists when the -/// context switch function is invoked, we use this type to reserve two physical frames -/// beforehand. -pub struct TwoFrames { - frames: [Option<PhysFrame>; 2], -} - -impl TwoFrames { - /// Creates a new instance by allocating two physical frames from the given frame allocator. - pub fn new(frame_allocator: &mut impl FrameAllocator<Size4KiB>) -> Self { - TwoFrames { - frames: [ - Some(frame_allocator.allocate_frame().unwrap()), - Some(frame_allocator.allocate_frame().unwrap()), - ], - } - } -} - -unsafe impl FrameAllocator<Size4KiB> for TwoFrames { - fn allocate_frame(&mut self) -> Option<PhysFrame<Size4KiB>> { - self.frames.iter_mut().find_map(|f| f.take()) - } -} - fn boot_info_location(used_entries: &mut UsedLevel4Entries) -> VirtAddr { CONFIG .boot_info_address diff --git a/tests/higher_half.rs b/tests/higher_half.rs new file mode 100644 index 00000000..4adf487d --- /dev/null +++ b/tests/higher_half.rs @@ -0,0 +1,32 @@ +use std::process::Command; + +#[test] +fn basic_boot() { + run_test_binary("basic_boot"); +} + +#[test] +fn should_panic() { + run_test_binary("should_panic"); +} + +#[test] +fn check_boot_info() { + run_test_binary("check_boot_info"); +} + +#[test] +fn verify_higher_half() { + run_test_binary("verify_higher_half"); +} + +fn run_test_binary(bin_name: &str) { + let mut cmd = Command::new(env!("CARGO")); + cmd.current_dir("tests/test_kernels/higher_half"); + cmd.arg("run"); + cmd.arg("--bin").arg(bin_name); + cmd.arg("--target").arg("x86_64-higher_half.json"); + cmd.arg("-Zbuild-std=core"); + cmd.arg("-Zbuild-std-features=compiler-builtins-mem"); + assert!(cmd.status().unwrap().success()); +} diff --git a/tests/test_kernels/higher_half/.cargo/config.toml b/tests/test_kernels/higher_half/.cargo/config.toml new file mode 100644 index 00000000..08a49f5a --- /dev/null +++ b/tests/test_kernels/higher_half/.cargo/config.toml @@ -0,0 +1,10 @@ +[unstable] +# TODO: Uncomment once https://github.com/rust-lang/cargo/issues/8643 is merged +# build-std = ["core"] + +[build] +# TODO: Uncomment once https://github.com/rust-lang/cargo/issues/8643 is merged +# target = "x86_64-example-kernel.json" + +[target.'cfg(target_os = "none")'] +runner = "cargo run --manifest-path ../../runner/Cargo.toml" \ No newline at end of file diff --git a/tests/test_kernels/higher_half/.gitignore b/tests/test_kernels/higher_half/.gitignore new file mode 100644 index 00000000..1de56593 --- /dev/null +++ b/tests/test_kernels/higher_half/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/tests/test_kernels/higher_half/Cargo.toml b/tests/test_kernels/higher_half/Cargo.toml new file mode 100644 index 00000000..1b9eb6d8 --- /dev/null +++ b/tests/test_kernels/higher_half/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "test_kernel_higher_half" +version = "0.1.0" +authors = ["Philipp Oppermann <dev@phil-opp.com>"] +edition = "2018" + +[dependencies] +bootloader = { path = "../../.." } +x86_64 = { version = "0.13.2", default-features = false, features = ["instructions", "inline_asm"] } +uart_16550 = "0.2.10" diff --git a/tests/test_kernels/higher_half/src/bin/basic_boot.rs b/tests/test_kernels/higher_half/src/bin/basic_boot.rs new file mode 100644 index 00000000..b812a9ac --- /dev/null +++ b/tests/test_kernels/higher_half/src/bin/basic_boot.rs @@ -0,0 +1,18 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use bootloader::{entry_point, BootInfo}; +use core::panic::PanicInfo; +use test_kernel_higher_half::{exit_qemu, QemuExitCode}; + +entry_point!(kernel_main); + +fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { + exit_qemu(QemuExitCode::Success); +} + +/// This function is called on panic. +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/higher_half/src/bin/check_boot_info.rs b/tests/test_kernels/higher_half/src/bin/check_boot_info.rs new file mode 100644 index 00000000..474f37c4 --- /dev/null +++ b/tests/test_kernels/higher_half/src/bin/check_boot_info.rs @@ -0,0 +1,68 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use bootloader::{boot_info::PixelFormat, entry_point, BootInfo}; +use core::panic::PanicInfo; +use test_kernel_higher_half::{exit_qemu, QemuExitCode}; + +entry_point!(kernel_main); + +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + // check memory regions + assert!(boot_info.memory_regions.len() > 4); + + // check framebuffer + let framebuffer = boot_info.framebuffer.as_ref().unwrap(); + assert_eq!(framebuffer.info().byte_len, framebuffer.buffer().len()); + if ![640, 1024].contains(&framebuffer.info().horizontal_resolution) { + panic!( + "unexpected horizontal_resolution `{}`", + framebuffer.info().horizontal_resolution + ); + } + if ![480, 768].contains(&framebuffer.info().vertical_resolution) { + panic!( + "unexpected vertical_resolution `{}`", + framebuffer.info().vertical_resolution + ); + } + if ![3, 4].contains(&framebuffer.info().bytes_per_pixel) { + panic!( + "unexpected bytes_per_pixel `{}`", + framebuffer.info().bytes_per_pixel + ); + } + if ![640, 1024].contains(&framebuffer.info().stride) { + panic!("unexpected stride `{}`", framebuffer.info().stride); + } + assert_eq!(framebuffer.info().pixel_format, PixelFormat::BGR); + assert_eq!( + framebuffer.buffer().len(), + framebuffer.info().stride + * framebuffer.info().vertical_resolution + * framebuffer.info().bytes_per_pixel + ); + + // check defaults for optional features + assert_eq!(boot_info.physical_memory_offset.into_option(), None); + assert_eq!(boot_info.recursive_index.into_option(), None); + + // check rsdp_addr + let rsdp = boot_info.rsdp_addr.into_option().unwrap(); + assert!(rsdp > 0x000E0000); + assert!(rsdp < 0x000FFFFF); + + // the test kernel has no TLS template + assert_eq!(boot_info.tls_template.into_option(), None); + + exit_qemu(QemuExitCode::Success); +} + +/// This function is called on panic. +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + use core::fmt::Write; + + let _ = writeln!(test_kernel_higher_half::serial(), "PANIC: {}", info); + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/higher_half/src/bin/should_panic.rs b/tests/test_kernels/higher_half/src/bin/should_panic.rs new file mode 100644 index 00000000..ea3bae75 --- /dev/null +++ b/tests/test_kernels/higher_half/src/bin/should_panic.rs @@ -0,0 +1,18 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use bootloader::{entry_point, BootInfo}; +use core::panic::PanicInfo; +use test_kernel_higher_half::{exit_qemu, QemuExitCode}; + +entry_point!(kernel_main); + +fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { + panic!(); +} + +/// This function is called on panic. +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + exit_qemu(QemuExitCode::Success); +} diff --git a/tests/test_kernels/higher_half/src/bin/verify_higher_half.rs b/tests/test_kernels/higher_half/src/bin/verify_higher_half.rs new file mode 100644 index 00000000..1d69c148 --- /dev/null +++ b/tests/test_kernels/higher_half/src/bin/verify_higher_half.rs @@ -0,0 +1,22 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use bootloader::{entry_point, BootInfo}; +use core::panic::PanicInfo; +use test_kernel_higher_half::{exit_qemu, QemuExitCode}; + +entry_point!(kernel_main); + +fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { + // verify that kernel is really running in the higher half of the address space + // (set in `x86_64-higher_half.json` custom target) + let rip = x86_64::registers::read_rip().as_u64(); + assert_eq!(rip & 0xffffffffffff0000, 0xffff800000000000); + exit_qemu(QemuExitCode::Success); +} + +/// This function is called on panic. +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/higher_half/src/lib.rs b/tests/test_kernels/higher_half/src/lib.rs new file mode 100644 index 00000000..4e46fdb6 --- /dev/null +++ b/tests/test_kernels/higher_half/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/higher_half/x86_64-higher_half.json b/tests/test_kernels/higher_half/x86_64-higher_half.json new file mode 100644 index 00000000..63b4a439 --- /dev/null +++ b/tests/test_kernels/higher_half/x86_64-higher_half.json @@ -0,0 +1,18 @@ +{ + "llvm-target": "x86_64-unknown-none", + "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": "64", + "target-c-int-width": "32", + "os": "none", + "executables": true, + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "panic-strategy": "abort", + "disable-redzone": true, + "features": "-mmx,-sse,+soft-float", + "pre-link-args": { + "ld.lld": ["--image-base", "0xFFFF800000000000"] + } + }