Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 24 additions & 81 deletions src/arch/x86_64/kernel/acpi.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
use core::{ptr, slice, str};

use align_address::Align;
use free_list::{PageLayout, PageRange};
use hermit_sync::OnceCell;
use memory_addresses::{PhysAddr, VirtAddr};
use x86_64::instructions::port::Port;
use x86_64::structures::paging::PhysFrame;
use x86_64::structures::paging::{PageTableFlags, PhysFrame};

use crate::arch::mm::paging;
use crate::arch::mm::paging::{
BasePageSize, PageSize, PageTableEntryFlags, PageTableEntryFlagsExt,
};
use crate::arch::mm::paging::{BasePageSize, LargePageSize, PageSize};
use crate::env;
use crate::mm::{PageAlloc, PageRangeAllocator};

/// Memory at this physical address is supposed to contain a pointer to the Extended BIOS Data Area (EBDA).
const EBDA_PTR_LOCATION: PhysAddr = PhysAddr::new(0x0000_040e);
Expand Down Expand Up @@ -101,74 +97,36 @@ impl AcpiSdtHeader {
#[derive(Debug)]
pub struct AcpiTable<'a> {
header: &'a AcpiSdtHeader,
allocated_virtual_address: VirtAddr,
allocated_length: usize,
}

impl AcpiTable<'_> {
fn map(physical_address: PhysAddr) -> Self {
if env::is_uefi() {
// For UEFI Systems, the tables are already mapped so we only need to return a proper reference to the table
let allocated_virtual_address = VirtAddr::new(physical_address.as_u64());
let header = unsafe {
allocated_virtual_address
.as_ptr::<AcpiSdtHeader>()
.as_ref()
.unwrap()
};
let allocated_length = usize::try_from(header.length).unwrap();

return Self {
header,
allocated_virtual_address,
allocated_length,
};
}

let mut flags = PageTableEntryFlags::empty();
flags.normal().read_only().execute_disable();

// Allocate two 4 KiB pages for the table and map it.
// This guarantees that we can access at least the "length" field of the table header when its physical address
// crosses a page boundary.
let mut allocated_length = 2 * BasePageSize::SIZE as usize;
let mut count = allocated_length / BasePageSize::SIZE as usize;

let physical_map_address = physical_address.align_down(BasePageSize::SIZE);
let offset = (physical_address - physical_map_address) as usize;
let layout = PageLayout::from_size(allocated_length).unwrap();
let page_range = PageAlloc::allocate(layout).unwrap();
let mut virtual_address = VirtAddr::from(page_range.start());
paging::map::<BasePageSize>(virtual_address, physical_map_address, count, flags);

// Get a pointer to the header and query the table length.
let mut header_ptr: *const AcpiSdtHeader = (virtual_address + offset).as_ptr();
let table_length = unsafe { (*header_ptr).length } as usize;

// Remap if the length exceeds what we've allocated.
if table_length > allocated_length - offset {
let range =
PageRange::from_start_len(virtual_address.as_usize(), allocated_length).unwrap();
unsafe {
PageAlloc::deallocate(range);
fn map(phys_addr: PhysAddr) -> Self {
// Allocate at least two consecutive pages to ensure the `length` field is always readable, even when it is on the next page.
let page_count = 2;
let frame_start_addr = phys_addr.align_down(LargePageSize::SIZE);

for i in 0..page_count {
let virt_addr = VirtAddr::new(frame_start_addr.as_u64()) + i * LargePageSize::SIZE;
let phys_addr = paging::virtual_to_physical(virt_addr);
let expected_phys_addr = PhysAddr::new(virt_addr.as_u64());

// Does not use `paging::identity_map()` since this mapping should not be `WRITABLE` and be `NO_EXECUTE`.
if phys_addr != Some(expected_phys_addr) {
paging::map::<LargePageSize>(
virt_addr,
expected_phys_addr,
1,
PageTableFlags::NO_EXECUTE,
);
}

allocated_length = (table_length + offset).align_up(BasePageSize::SIZE as usize);
count = allocated_length / BasePageSize::SIZE as usize;

let layout = PageLayout::from_size(allocated_length).unwrap();
let page_range = PageAlloc::allocate(layout).unwrap();
virtual_address = VirtAddr::from(page_range.start());
paging::map::<BasePageSize>(virtual_address, physical_map_address, count, flags);

header_ptr = (virtual_address + offset).as_ptr();
}

// Return the table.
let header_ptr = ptr::with_exposed_provenance::<AcpiSdtHeader>(phys_addr.as_usize());
let table_length = u64::from(unsafe { (*header_ptr).length });
assert!(phys_addr + table_length <= frame_start_addr + page_count * LargePageSize::SIZE);

Self {
header: unsafe { &*header_ptr },
allocated_virtual_address: virtual_address,
allocated_length,
}
}

Expand All @@ -189,21 +147,6 @@ impl AcpiTable<'_> {
}
}

impl Drop for AcpiTable<'_> {
fn drop(&mut self) {
if !env::is_uefi() {
let range = PageRange::from_start_len(
self.allocated_virtual_address.as_usize(),
self.allocated_length,
)
.unwrap();
unsafe {
PageAlloc::deallocate(range);
}
}
}
}

/// The ACPI Generic Address Structure (GAS).
/// Described in ACPI Specification 6.2 A, 5.2.3.2 Generic Address Structure.
#[repr(C, packed)]
Expand Down
67 changes: 18 additions & 49 deletions src/arch/x86_64/kernel/apic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ use crate::arch::mm::paging::{
BasePageSize, PageSize, PageTableEntryFlags, PageTableEntryFlagsExt,
};
use crate::arch::swapgs;
use crate::mm::{PageAlloc, PageBox, PageRangeAllocator};
use crate::mm::PageBox;
use crate::scheduler::CoreId;
use crate::{arch, env, scheduler};
use crate::{arch, scheduler};

/// APIC Location and Status (R/W) See Table 35-2. See Section 10.4.4, Local APIC Status and Location.
const IA32_APIC_BASE: Msr = Msr::new(0x1b);
Expand Down Expand Up @@ -306,22 +306,11 @@ pub fn local_apic_id_count() -> u32 {
}

fn init_ioapic_address(phys_addr: PhysAddr) {
if env::is_uefi() {
// UEFI systems have already id mapped everything, so we can just set the physical address as the virtual one
IOAPIC_ADDRESS
.set(VirtAddr::new(phys_addr.as_u64()))
.unwrap();
} else {
let layout = PageLayout::from_size(BasePageSize::SIZE as usize).unwrap();
let page_range = PageAlloc::allocate(layout).unwrap();
let ioapic_address = VirtAddr::from(page_range.start());
IOAPIC_ADDRESS.set(ioapic_address).unwrap();
debug!("Mapping IOAPIC at {phys_addr:p} to virtual address {ioapic_address:p}");
paging::identity_map::<BasePageSize>(phys_addr);

let mut flags = PageTableEntryFlags::empty();
flags.device().writable().execute_disable();
paging::map::<BasePageSize>(ioapic_address, phys_addr, 1, flags);
}
IOAPIC_ADDRESS
.set(VirtAddr::new(phys_addr.as_u64()))
.unwrap();
}

#[cfg(not(feature = "acpi"))]
Expand Down Expand Up @@ -508,7 +497,7 @@ fn default_apic() -> PhysAddr {

fn apic_addr() -> PhysAddr {
#[cfg(feature = "uhyve")]
if env::is_uhyve() {
if crate::env::is_uhyve() {
return default_apic();
}

Expand All @@ -528,25 +517,12 @@ pub fn init() {
// Initialize x2APIC or xAPIC, depending on what's available.
if processor::supports_x2apic() {
init_x2apic();
} else if env::is_uefi() {
// already id mapped in UEFI systems, just use the physical address as virtual one
} else {
paging::identity_map::<BasePageSize>(local_apic_physical_address);

LOCAL_APIC_ADDRESS
.set(VirtAddr::new(local_apic_physical_address.as_u64()))
.unwrap();
} else {
// We use the traditional xAPIC mode available on all x86-64 CPUs.
// It uses a mapped page for communication.
let layout = PageLayout::from_size(BasePageSize::SIZE as usize).unwrap();
let page_range = PageAlloc::allocate(layout).unwrap();
let local_apic_address = VirtAddr::from(page_range.start());
LOCAL_APIC_ADDRESS.set(local_apic_address).unwrap();
debug!(
"Mapping Local APIC at {local_apic_physical_address:p} to virtual address {local_apic_address:p}"
);

let mut flags = PageTableEntryFlags::empty();
flags.device().writable().execute_disable();
paging::map::<BasePageSize>(local_apic_address, local_apic_physical_address, 1, flags);
}

// Set gates to ISRs for the APIC interrupts we are going to enable.
Expand Down Expand Up @@ -751,8 +727,6 @@ pub fn init_next_processor_variables() {
/// This is partly confirmed by <https://wiki.osdev.org/Symmetric_Multiprocessing>
#[cfg(all(target_os = "none", feature = "smp"))]
pub fn boot_application_processors() {
use x86_64::structures::paging::Translate;

use crate::arch::start::smp;

let smp_boot_code = include_bytes!(concat!(core::env!("OUT_DIR"), "/boot.bin"));
Expand All @@ -764,24 +738,19 @@ pub fn boot_application_processors() {
);
debug!("SMP boot code is {} bytes long", smp_boot_code.len());

if env::is_uefi() {
// Since UEFI already provides identity-mapped pagetables, we only have to sanity-check the identity mapping
let pt = unsafe { paging::identity_mapped_page_table() };
let virt_addr = SMP_BOOT_CODE_ADDRESS;
let phys_addr = pt.translate_addr(virt_addr.into()).unwrap();
assert_eq!(phys_addr.as_u64(), virt_addr.as_u64());
} else {
// Identity-map the boot code page and copy over the code.
debug!("Mapping SMP boot code to physical and virtual address {SMP_BOOT_CODE_ADDRESS:p}");
let mut flags = PageTableEntryFlags::empty();
flags.normal().writable();
// Ensure identity mapping
// Does not use `paging::identity_map()` since this mapping must not be `NO_EXECUTE`.
let phys_addr = paging::virtual_to_physical(SMP_BOOT_CODE_ADDRESS);
let expected_phys_addr = PhysAddr::new(SMP_BOOT_CODE_ADDRESS.as_u64());
if phys_addr != Some(expected_phys_addr) {
paging::map::<BasePageSize>(
SMP_BOOT_CODE_ADDRESS,
PhysAddr::new(SMP_BOOT_CODE_ADDRESS.as_u64()),
expected_phys_addr,
1,
flags,
PageTableEntryFlags::WRITABLE,
);
}

unsafe {
// FIXME: do bounds checking. Better yet: do the copy via slices
SMP_BOOT_CODE_ADDRESS
Expand Down
41 changes: 31 additions & 10 deletions src/arch/x86_64/mm/paging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ use x86_64::structures::paging::frame::PhysFrameRange;
use x86_64::structures::paging::mapper::{MapToError, MappedFrame, TranslateResult, UnmapError};
use x86_64::structures::paging::page::PageRange;
use x86_64::structures::paging::{
FrameAllocator, Mapper, OffsetPageTable, Page, PageTable, PhysFrame, Size4KiB, Translate,
FrameAllocator, Mapper, OffsetPageTable, Page, PageTable, PageTableIndex, PhysFrame, Size4KiB,
Translate,
};

use crate::arch::kernel::processor;
use crate::arch::mm::{PhysAddr, VirtAddr};
use crate::mm::{FrameAlloc, PageRangeAllocator};
use crate::{env, scheduler};
use crate::scheduler;

unsafe impl FrameAllocator<Size4KiB> for FrameAlloc {
fn allocate_frame(&mut self) -> Option<PhysFrame<Size4KiB>> {
Expand All @@ -32,11 +33,12 @@ unsafe impl FrameAllocator<Size4KiB> for FrameAlloc {
}

pub trait PageTableEntryFlagsExt {
#[cfg_attr(not(any(feature = "pci", feature = "vga")), expect(dead_code))]
fn device(&mut self) -> &mut Self;

fn normal(&mut self) -> &mut Self;

#[cfg(feature = "acpi")]
#[expect(dead_code)]
fn read_only(&mut self) -> &mut Self;

fn writable(&mut self) -> &mut Self;
Expand Down Expand Up @@ -65,7 +67,6 @@ impl PageTableEntryFlagsExt for PageTableEntryFlags {
self
}

#[cfg(feature = "acpi")]
fn read_only(&mut self) -> &mut Self {
self.remove(PageTableEntryFlags::WRITABLE);
self
Expand Down Expand Up @@ -115,6 +116,21 @@ pub unsafe fn identity_mapped_page_table() -> OffsetPageTable<'static> {
}
}

/// Returns true if the level 4 page table has a recursive entry.
///
/// This is useful for compatibility with the Hermit loader version 0.5.6.
// FIXME: Remove once we drop support for loader 0.5.6
pub fn is_recursive() -> bool {
let identity_mapped_page_table = unsafe { identity_mapped_page_table() };
let level_4_table = identity_mapped_page_table.level_4_table();

let recursive_index = PageTableIndex::new(511);
let level_4_table_virt_addr = ptr::from_ref(level_4_table).addr();
let recursive_index_phys_addr = level_4_table[recursive_index].addr().as_u64() as usize;

level_4_table_virt_addr == recursive_index_phys_addr
}

/// Translate a virtual memory address to a physical one.
pub fn virtual_to_physical(virtual_address: VirtAddr) -> Option<PhysAddr> {
let addr = x86_64::VirtAddr::from(virtual_address);
Expand Down Expand Up @@ -313,14 +329,13 @@ pub fn init() {
log_page_tables();
}

if env::is_uefi() {
make_p4_writable();
}
ensure_p4_writable();
}

fn make_p4_writable() {
debug!("Making P4 table writable");

/// Makes the level 4 page table writable.
///
/// This is useful when reusing UEFI's page tables which might not be writable.
fn ensure_p4_writable() {
let mut pt = unsafe { identity_mapped_page_table() };

let p4_page = {
Expand All @@ -333,6 +348,12 @@ fn make_p4_writable() {
unreachable!()
};

if flags.contains(PageTableEntryFlags::WRITABLE) {
return;
}

debug!("Making P4 table writable...");

let make_writable = || unsafe {
let flags = flags | PageTableEntryFlags::WRITABLE;
match frame {
Expand Down
4 changes: 0 additions & 4 deletions src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,6 @@ pub fn uhyve_num_cpus() -> Option<NonZero<usize>> {
}
}

pub fn is_uefi() -> bool {
fdt().is_some_and(|fdt| fdt.root().compatible().first() == "hermit,uefi")
}

pub fn fdt_addr() -> Option<NonZero<usize>> {
boot_info()
.hardware_info
Expand Down
Loading
Loading