diff --git a/Cargo.lock b/Cargo.lock index 43f70bbdb6..4de6b9cbba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,6 +31,20 @@ dependencies = [ "tock-registers 0.10.1", ] +[[package]] +name = "acpi" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b763acdc1d85c36d61acf97a59938f23202d0e8efe45e8759de10c02db242744" +dependencies = [ + "bit_field", + "bitflags 2.13.1", + "byteorder", + "log", + "pci_types", + "spinning_top", +] + [[package]] name = "ahash" version = "0.8.12" @@ -868,6 +882,7 @@ name = "hermit-kernel" version = "0.13.0" dependencies = [ "aarch64-cpu 11.2.0", + "acpi", "ahash", "align-address 0.4.0", "anstyle", @@ -899,6 +914,7 @@ dependencies = [ "hermit-macro", "hermit-sync", "llvm-tools", + "lock_api", "log", "mem-barrier", "memory_addresses 0.4.0", diff --git a/Cargo.toml b/Cargo.toml index 2902b54529..96df5af028 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,7 +104,7 @@ uhyve = ["hermit-entry", "dep:uhyve-interface"] ## This is not useful on [microvm]s and [Uhyve]. ## ## [ACPI]: https://uefi.org/specs/ACPI/6.6/ -acpi = [] +acpi = ["dep:acpi"] ## Enables using the [FSGSBASE] instruction family. ## @@ -335,6 +335,7 @@ workspace = true [dependencies] hermit-macro = { version = "=0.1.0", path = "hermit-macro" } +acpi = { version = "6", optional = true } ahash = { version = "0.8", default-features = false } align-address = "0.4" anstyle = { version = "1", default-features = false } @@ -358,6 +359,7 @@ hashbrown = { version = "0.17", default-features = false } heapless = "0.9" hermit-entry = { version = "0.10", features = ["kernel"], optional = true } hermit-sync = "0.1" +lock_api = "0.4" log = { version = "0.4", default-features = false } mem-barrier = { version = "0.1.0", optional = true, features = ["nightly"] } num_enum = { version = "0.7", default-features = false } diff --git a/src/acpi/handler.rs b/src/acpi/handler.rs new file mode 100644 index 0000000000..51105a46b5 --- /dev/null +++ b/src/acpi/handler.rs @@ -0,0 +1,289 @@ +use alloc::sync::Arc; +use alloc::vec::Vec; +use core::hint; +use core::ptr::{self, NonNull}; + +use acpi::aml::AmlError; +use acpi::{Handle, Handler, PciAddress, PhysicalMapping}; +use align_address::Align; +use hermit_sync::{RawSpinMutex, SpinMutex}; +use lock_api::RawMutex; +#[cfg(target_arch = "x86_64")] +use x86_64::instructions::port::Port; + +use crate::arch::kernel::{core_local, processor}; +use crate::arch::mm::paging::{self, BasePageSize}; +use crate::scheduler::PerCoreSchedulerExt; + +#[derive(Default, Clone, Debug)] +pub struct AcpiHandler { + state: Arc, +} + +#[derive(Default, Debug)] +struct State { + mutexes: SpinMutex>>, +} + +impl Handler for AcpiHandler { + unsafe fn map_physical_region( + &self, + physical_address: usize, + size: usize, + ) -> PhysicalMapping { + let physical_start = physical_address.align_down(0x1000); + let physical_end = (physical_address + size).align_up(0x1000); + let mapped_length = physical_end - physical_start; + let handler = self.clone(); + + trace!( + "Mapping physical region... paddr = {physical_start:#x}, len = {mapped_length:#x}" + ); + + for paddr in (physical_start..physical_start + mapped_length).step_by(0x1000) { + paging::identity_map::(paddr.into()); + } + + let virtual_start = ptr::with_exposed_provenance_mut(physical_address); + let virtual_start = NonNull::new(virtual_start).unwrap(); + let region_length = size; + + PhysicalMapping { + physical_start, + virtual_start, + region_length, + mapped_length, + handler, + } + } + + fn unmap_physical_region(region: &PhysicalMapping) { + trace!( + "Unmapping physical region... paddr = {:#x}, len = {:#x}", + region.physical_start, region.mapped_length + ); + // We don't unmap currently. + } + + fn read_u8(&self, address: usize) -> u8 { + trace!("read_u8({address:#x})"); + let ptr = ptr::with_exposed_provenance(address); + unsafe { *ptr } + } + + fn read_u16(&self, address: usize) -> u16 { + trace!("read_u16({address:#x})"); + let ptr = ptr::with_exposed_provenance(address); + unsafe { *ptr } + } + + fn read_u32(&self, address: usize) -> u32 { + trace!("read_u32({address:#x})"); + let ptr = ptr::with_exposed_provenance(address); + unsafe { *ptr } + } + + fn read_u64(&self, address: usize) -> u64 { + trace!("read_u64({address:#x})"); + let ptr = ptr::with_exposed_provenance(address); + unsafe { *ptr } + } + + fn write_u8(&self, address: usize, value: u8) { + trace!("write_u8({address:#x}, {value:#x})"); + let ptr = ptr::with_exposed_provenance_mut(address); + unsafe { + *ptr = value; + } + } + + fn write_u16(&self, address: usize, value: u16) { + trace!("write_u16({address:#x}, {value:#x})"); + let ptr = ptr::with_exposed_provenance_mut(address); + unsafe { + *ptr = value; + } + } + + fn write_u32(&self, address: usize, value: u32) { + trace!("write_u32({address:#x}, {value:#x})"); + let ptr = ptr::with_exposed_provenance_mut(address); + unsafe { + *ptr = value; + } + } + + fn write_u64(&self, address: usize, value: u64) { + trace!("write_u64({address:#x}, {value:#x})"); + let ptr = ptr::with_exposed_provenance_mut(address); + unsafe { + *ptr = value; + } + } + + fn read_io_u8(&self, port: u16) -> u8 { + trace!("read_io_u8({port:#x})"); + cfg_select! { + target_arch = "x86_64" => unsafe { Port::new(port).read() }, + _ => unimplemented!(), + } + } + + fn read_io_u16(&self, port: u16) -> u16 { + trace!("read_io_u16({port:#x})"); + cfg_select! { + target_arch = "x86_64" => unsafe { Port::new(port).read() }, + _ => unimplemented!(), + } + } + + fn read_io_u32(&self, port: u16) -> u32 { + trace!("read_io_u32({port:#x})"); + cfg_select! { + target_arch = "x86_64" => unsafe { Port::new(port).read() }, + _ => unimplemented!(), + } + } + + fn write_io_u8(&self, port: u16, value: u8) { + trace!("write_io_u8({port:#x}, {value:#x})"); + cfg_select! { + target_arch = "x86_64" => unsafe { Port::new(port).write(value) }, + _ => unimplemented!(), + } + } + + fn write_io_u16(&self, port: u16, value: u16) { + trace!("write_io_u16({port:#x}, {value:#x})"); + cfg_select! { + target_arch = "x86_64" => unsafe { Port::new(port).write(value) }, + _ => unimplemented!(), + } + } + + fn write_io_u32(&self, port: u16, value: u32) { + trace!("write_io_u32({port:#x}, {value:#x})"); + cfg_select! { + target_arch = "x86_64" => unsafe { Port::new(port).write(value) }, + _ => unimplemented!(), + } + } + + fn read_pci_u8(&self, address: PciAddress, offset: u16) -> u8 { + trace!("read_pci_u8({address}, {offset:#x})"); + todo!() + } + + fn read_pci_u16(&self, address: PciAddress, offset: u16) -> u16 { + trace!("read_pci_u16({address}, {offset:#x})"); + todo!("needs an arch-unified PCI interface") + } + + fn read_pci_u32(&self, address: PciAddress, offset: u16) -> u32 { + trace!("read_pci_u32({address}, {offset:#x})"); + todo!("needs an arch-unified PCI interface") + } + + fn write_pci_u8(&self, address: PciAddress, offset: u16, value: u8) { + trace!("write_pci_u8({address}, {offset:#x}, {value:#x})"); + todo!("needs an arch-unified PCI interface") + } + + fn write_pci_u16(&self, address: PciAddress, offset: u16, value: u16) { + trace!("write_pci_u16({address}, {offset:#x}, {value:#x})"); + todo!("needs an arch-unified PCI interface") + } + + fn write_pci_u32(&self, address: PciAddress, offset: u16, value: u32) { + trace!("write_pci_u32({address}, {offset:#x}, {value:#x})"); + todo!("needs an arch-unified PCI interface") + } + + fn nanos_since_boot(&self) -> u64 { + trace!("nanos_since_boot()"); + processor::get_timer_ticks() * 1000 + } + + fn stall(&self, microseconds: u64) { + trace!("stall({microseconds}µs)"); + + // FIXME: This is taken from x86-64's `udelay()`. + // We should make `udelay()` cross-architecture, instead. + let end = processor::get_timestamp() + u64::from(processor::get_frequency()) * microseconds; + while processor::get_timestamp() < end { + hint::spin_loop(); + } + } + + fn sleep(&self, milliseconds: u64) { + trace!("sleep({milliseconds}ms)"); + + // FIXME: This is taken from `usleep()`. + // We should create an always-sleeping function and use that here. + let core_scheduler = core_local::core_scheduler(); + let wakeup_time = processor::get_timer_ticks() + milliseconds * 1000; + core_scheduler.block_current_task(Some(wakeup_time)); + core_scheduler.reschedule(); + } + + fn create_mutex(&self) -> Handle { + trace!("create_mutex()"); + let mut mutexes = self.state.mutexes.lock(); + + let i = u32::try_from(mutexes.len()).unwrap(); + mutexes.push(Arc::new(RawSpinMutex::INIT)); + + Handle(i) + } + + fn acquire(&self, mutex: Handle, timeout: u16) -> Result<(), AmlError> { + // FIXME: This mutex should be reentrant and suspend threads. To do that, we should rework + // `crate::synch::recmutex` with `lock_api::ReentrantMutex` in a way that handles timeouts. + // The implementation should be based on futexes and might be used to provide pthread APIs + // in the future. + + trace!("acquire({mutex:?}, {timeout}ms)"); + + let raw_mutex = self.raw_mutex(mutex)?; + + match timeout { + 0 => match raw_mutex.try_lock() { + true => Ok(()), + false => Err(AmlError::MutexAcquireTimeout), + }, + 1..0xffff => { + let end = processor::get_timestamp() + + u64::from(processor::get_frequency()) * u64::from(timeout); + while processor::get_timestamp() < end { + if raw_mutex.try_lock() { + return Ok(()); + } + + self.sleep(1); + } + + Err(AmlError::MutexAcquireTimeout) + } + 0xffff => { + raw_mutex.lock(); + Ok(()) + } + } + } + + fn release(&self, mutex: Handle) { + trace!("release({mutex:?})"); + + let raw_mutex = self.raw_mutex(mutex).unwrap(); + unsafe { raw_mutex.unlock() } + } +} + +impl AcpiHandler { + fn raw_mutex(&self, mutex: Handle) -> Result, AmlError> { + let mutexes = self.state.mutexes.lock(); + let index = usize::try_from(mutex.0).map_err(|_| AmlError::IndexOutOfBounds)?; + let mutex = mutexes.get(index).ok_or(AmlError::IndexOutOfBounds)?; + Ok(mutex.clone()) + } +} diff --git a/src/acpi/mod.rs b/src/acpi/mod.rs new file mode 100644 index 0000000000..98fb62bae6 --- /dev/null +++ b/src/acpi/mod.rs @@ -0,0 +1,121 @@ +#![cfg_attr( + any(target_arch = "aarch64", target_arch = "riscv64"), + expect(dead_code, unused_imports) +)] + +mod handler; +mod spec; + +use alloc::vec::Vec; +use core::num::NonZero; +use core::str::FromStr; + +use acpi::address::MappedGas; +use acpi::aml::namespace::AmlName; +use acpi::aml::object::{Object, WrappedObject}; +use acpi::platform::AcpiPlatform; +use acpi::registers::FixedRegisters; +use acpi::sdt::fadt::Fadt; +use acpi::{AcpiError, AcpiTable, AcpiTables, Handler, PhysicalMapping, aml}; +use bit_field::BitField; +use hermit_sync::OnceCell; + +use self::handler::AcpiHandler; +pub use self::spec::*; +use crate::env::{self, StartInfo}; + +static ACPI_PLATFORM: OnceCell> = OnceCell::new(); +static AML_INTERPRETER: OnceCell> = OnceCell::new(); + +pub fn init() { + #[cfg(feature = "uhyve")] + use env::UhyveStartInfo; + + #[cfg(feature = "uhyve")] + if env::start_info().is_uhyve() { + return; + } + + let handler = AcpiHandler::default(); + + let Some(rsdp_paddr) = rsdp_paddr(&handler) else { + return; + }; + + info!("Reading ACPI tables..."); + let tables = unsafe { AcpiTables::from_rsdp(handler.clone(), rsdp_paddr.get()).unwrap() }; + let platform = AcpiPlatform::new(tables, handler).unwrap(); + let platform = ACPI_PLATFORM + .try_insert(platform) + .unwrap_or_else(|_| panic!("ACPI platform should not be initialized")); + + info!("Creating AML interpreter..."); + let aml_interpreter = aml::Interpreter::new_from_platform(platform).unwrap(); + AML_INTERPRETER + .set(aml_interpreter) + .unwrap_or_else(|_| panic!("AML interpreter should not be initialized")); +} + +pub fn find_table() -> Option> { + ACPI_PLATFORM.get()?.tables.find_table() +} + +#[cfg_attr(not(target_arch = "x86_64"), expect(unused_variables))] +fn rsdp_paddr(handler: &H) -> Option> { + if let Some(rsdp_paddr) = env::start_info().rsdp_addr() { + info!("Found RSDP paddr in start info: {rsdp_paddr:#x}"); + return Some(rsdp_paddr); + } + + #[cfg(target_arch = "x86_64")] + if let Ok(rsdp) = unsafe { acpi::rsdp::Rsdp::search_for_on_bios(handler.clone()) } { + let rsdp_paddr = rsdp.virtual_start.addr(); + info!("Found RSDP paddr by searching on BIOS systems: {rsdp_paddr:#x}"); + return Some(rsdp_paddr); + } + + warn!("Could not find RSDP paddr."); + None +} + +/// Enters the ACPI S5 soft off system state. +/// +/// For details, see [16.1.7. Transitioning from the Working to the Soft Off State — 16. Waking and Sleeping — ACPI Specification 6.6 documentation]. +/// +/// [16.1.7. Transitioning from the Working to the Soft Off State — 16. Waking and Sleeping — ACPI Specification 6.6 documentation]: https://uefi.org/specs/ACPI/6.6/16_Waking_and_Sleeping.html#transitioning-from-the-working-to-the-soft-off-state +pub fn shutdown() -> Option { + debug!("Entering ACPI S5 soft off state..."); + + let aml_interpreter = AML_INTERPRETER.get()?; + + // Read the S5 system state package. The contents are defined in + // https://uefi.org/specs/ACPI/6.6/07_Power_and_Performance_Mgmt.html#sx-system-states + let s5_path = AmlName::from_str(r"\_S5").ok()?; + let s5_object = aml_interpreter.evaluate(s5_path, Vec::new()).ok()?; + let Object::Package(s5) = &*s5_object else { + return None; + }; + + let fadt = find_table::()?; + + let fixed_registers = FixedRegisters::new(&fadt, fadt.handler.clone()).ok()?; + write_pm1x_cnt(&fixed_registers.pm1_control_registers.pm1a, &s5[0]).ok()?; + + None +} + +fn write_pm1x_cnt( + pm1x_cnt: &MappedGas, + slp_typx: &WrappedObject, +) -> Result<(), AcpiError> { + let slp_typx = slp_typx.as_integer().map_err(AcpiError::Aml)?; + + let mut value = pm1x_cnt.read()?; + // SLP_TYPx + value.set_bits(10..13, slp_typx); + // SLP_EN + value.set_bit(13, true); + pm1x_cnt.write(value)?; + + Ok(()) +} diff --git a/src/acpi/spec.rs b/src/acpi/spec.rs new file mode 100644 index 0000000000..405eb8365d --- /dev/null +++ b/src/acpi/spec.rs @@ -0,0 +1,13 @@ +bitflags! { + /// Multiple APIC Flags. + /// + /// For reference, see [Table 5.20 Multiple APIC Flags — 5. ACPI Software Programming Model — ACPI Specification 6.6 documentation]. + /// + /// [Table 5.20 Multiple APIC Flags — 5. ACPI Software Programming Model — ACPI Specification 6.6 documentation]: https://uefi.org/specs/ACPI/6.6/05_ACPI_Software_Programming_Model.html#multiple-apic-flags + pub struct MultipleApicFlags: u32 { + /// A one indicates that the system also has a PC-AT-compatible dual-8259 setup. + /// + /// The 8259 vectors must be disabled (that is, masked) when enabling the ACPI APIC operation. + const PCAT_COMPAT = 1; + } +} diff --git a/src/arch/x86_64/kernel/acpi.rs b/src/arch/x86_64/kernel/acpi.rs deleted file mode 100644 index 4951f19e44..0000000000 --- a/src/arch/x86_64/kernel/acpi.rs +++ /dev/null @@ -1,544 +0,0 @@ -use core::{ptr, slice, str}; - -use align_address::Align; -use hermit_sync::OnceCell; -use memory_addresses::{PhysAddr, VirtAddr}; -use x86_64::instructions::port::Port; -use x86_64::structures::paging::{PageTableFlags, PhysFrame}; - -use crate::arch::mm::paging; -use crate::arch::mm::paging::{BasePageSize, LargePageSize, PageSize}; -use crate::env::{self, StartInfo}; - -/// 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); -/// Minimum physical address where a valid EBDA must be located. -const EBDA_MINIMUM_ADDRESS: PhysAddr = PhysAddr::new(0x400); -/// The size of the EBDA window that is searched for an ACPI RSDP. -const EBDA_WINDOW_SIZE: usize = 1024; -/// The lower bound of the other address range, where the ACPI RSDP could be located. -const RSDP_SEARCH_ADDRESS_LOW: PhysAddr = PhysAddr::new(0xe_0000); -/// The upper bound of the other address range, where the ACPI RSDP could be located. -const RSDP_SEARCH_ADDRESS_HIGH: PhysAddr = PhysAddr::new(0xf_ffff); -/// Length in bytes of the structure, over which the basic (ACPI 1.0) checksum is calculated. -const RSDP_CHECKSUM_LENGTH: usize = 20; -/// Length in byte sof the structure, over which the extended (ACPI 2.0+) checksum is calculated. -const RSDP_XCHECKSUM_LENGTH: usize = 36; - -/// ACPI AML opcode indicating that a name follows. -const AML_NAMEOP: u8 = 0x08; -/// ACPI AML opcode indicating that a package follows. -const AML_PACKAGEOP: u8 = 0x12; -/// ACPI AML opcode indicating a single zero byte as the data. -const AML_ZEROOP: u8 = 0x00; -/// ACPI AML opcode indicating a single one byte as the data. -const AML_ONEOP: u8 = 0x01; -/// ACPI AML opcode indicating that a single byte with the data follows. -const AML_BYTEPREFIX: u8 = 0x0a; - -/// Bit to enable an ACPI Sleep State. -const SLP_EN: u16 = 1 << 13; - -/// The "Multiple APIC Description Table" (MADT) preserved for get_apic_table(). -static MADT: OnceCell> = OnceCell::new(); - -/// The MCFG table, to address PCIe configuration space -#[cfg(feature = "pci")] -static MCFG: OnceCell> = OnceCell::new(); - -/// The PM1A Control I/O Port for powering off the computer through ACPI. -static PM1A_CNT_BLK: OnceCell> = OnceCell::new(); -/// The Sleeping State Type code for powering off the computer through ACPI. -static SLP_TYPA: OnceCell = OnceCell::new(); - -/// The "Root System Description Pointer" structure providing pointers to all other ACPI tables. -#[repr(C, packed)] -struct AcpiRsdp { - signature: [u8; 8], - checksum: u8, - oem_id: [u8; 6], - revision: u8, - rsdt_physical_address: u32, - length: u32, - xsdt_physical_address: u64, - extended_checksum: u8, - reserved: [u8; 3], -} - -impl AcpiRsdp { - fn oem_id(&self) -> &str { - str::from_utf8(&self.oem_id).unwrap() - } -} - -/// The header of (almost) every ACPI table. -#[derive(Clone, Copy, Debug)] -#[repr(C, packed)] -struct AcpiSdtHeader { - signature: [u8; 4], - length: u32, - revision: u8, - checksum: u8, - oem_id: [u8; 6], - oem_table_id: [u8; 8], - oem_revision: u32, - creator_id: u32, - creator_revision: u32, -} - -impl AcpiSdtHeader { - fn signature(&self) -> &str { - str::from_utf8(&self.signature).unwrap() - } -} - -/// A convenience structure to work with an ACPI table. -/// Maps a single table to memory and frees the memory when a variable of this structure goes out of scope. -#[derive(Debug)] -pub struct AcpiTable<'a> { - header: &'a AcpiSdtHeader, -} - -impl AcpiTable<'_> { - 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::( - virt_addr, - expected_phys_addr, - 1, - PageTableFlags::NO_EXECUTE, - ); - } - } - - let header_ptr = ptr::with_exposed_provenance::(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 }, - } - } - - fn header_start_address(&self) -> usize { - ptr::from_ref(self.header).addr() - } - - pub fn table_start_address(&self) -> usize { - self.header_start_address() + size_of::() - } - - pub fn table_end_address(&self) -> usize { - self.header_start_address() + self.header.length as usize - } - - pub fn table_byte_len(&self) -> usize { - self.header.length as usize - size_of::() - } -} - -/// The ACPI Generic Address Structure (GAS). -/// Described in ACPI Specification 6.2 A, 5.2.3.2 Generic Address Structure. -#[repr(C, packed)] -struct AcpiGenericAddress { - address_space: u8, - bit_width: u8, - bit_offset: u8, - access_size: u8, - address: u64, -} - -const GENERIC_ADDRESS_IO_SPACE: u8 = 1; - -/// The "Fixed ACPI Description Table" (FADT), also called "Fixed ACPI Control Pointer" (FACP). -/// Described in ACPI Specification 6.2 A, 5.2.9 Fixed ACPI Description Table (FADT). -#[repr(C, packed)] -struct AcpiFadt { - firmware_ctrl: u32, - dsdt: u32, - reserved1: u8, - preferred_pm_profile: u8, - sci_int: u16, - smi_cmd: u32, - acpi_enable: u8, - acpi_disable: u8, - s4bios_req: u8, - pstate_cnt: u8, - pm1a_evt_blk: u32, - pm1b_evt_blk: u32, - pm1a_cnt_blk: u32, - pm1b_cnt_blk: u32, - pm2_cnt_blk: u32, - pm_tmr_blk: u32, - gpe0_blk: u32, - gpe1_blk: u32, - pm1_evt_len: u8, - pm1_cnt_len: u8, - pm2_cnt_len: u8, - pm_tmr_len: u8, - gpe0_blk_len: u8, - gpe1_blk_len: u8, - gpe1_base: u8, - cst_cnt: u8, - p_lvl2_lat: u16, - p_lvl3_lat: u16, - flush_size: u16, - flush_stride: u16, - duty_offset: u8, - duty_width: u8, - day_alrm: u8, - mon_alrm: u8, - century: u8, - iapc_boot_arch: u16, - reserved2: u8, - flags: u32, - reset_reg: AcpiGenericAddress, - reset_value: u8, - arm_boot_arch: u16, - fadt_minor_version: u8, - x_firmware_ctrl: u64, - x_dsdt: u64, - x_pm1a_evt_blk: AcpiGenericAddress, - x_pm1b_evt_blk: AcpiGenericAddress, - x_pm1a_cnt_blk: AcpiGenericAddress, - x_pm1b_cnt_blk: AcpiGenericAddress, - x_pm2_cnt_blk: AcpiGenericAddress, - x_pm_tmr_blk: AcpiGenericAddress, - x_gpe0_blk: AcpiGenericAddress, - x_gpe1_blk: AcpiGenericAddress, - sleep_control_reg: AcpiGenericAddress, - sleep_status_reg: AcpiGenericAddress, - hypervisor_vendor_id: u64, -} - -/// Verifies the checksum of an ACPI table. -/// Tables supporting this feature contain a "checksum" field. The value of this field is chosen, so that a -/// (wrapping) sum over all table fields equals zero. -fn verify_checksum(start_address: usize, length: usize) -> Result<(), ()> { - // Get a slice over all bytes of the structure that are considered for the checksum. - let slice = - unsafe { slice::from_raw_parts(ptr::with_exposed_provenance(start_address), length) }; - - // Perform a wrapping sum over these bytes. - let checksum = slice.iter().fold(0, |acc: u8, x| acc.wrapping_add(*x)); - - // This sum must equal to zero to be valid. - if checksum == 0 { Ok(()) } else { Err(()) } -} - -/// Tries to find the ACPI RSDP within the specified address range. -/// Returns a reference to it within the Ok() if successful or an empty Err() on failure. -fn detect_rsdp(start_address: PhysAddr, end_address: PhysAddr) -> Result<&'static AcpiRsdp, ()> { - // Trigger page mapping in the first iteration! - let mut current_page = 0; - - // Look for the ACPI RSDP in all possible 16-byte aligned addresses within this range. - for current_address in (start_address.as_usize()..end_address.as_usize()).step_by(16) { - // Have we crossed a page boundary in the last iteration? - if current_address / BasePageSize::SIZE as usize > current_page { - // Identity-map this possible page of the RSDP. - let frame = PhysFrame::::containing_address(x86_64::PhysAddr::new( - current_address as u64, - )); - paging::identity_map::(frame.start_address().into()); - current_page = current_address / BasePageSize::SIZE as usize; - } - - // Verify the signature to find out if this is really an ACPI RSDP. - let rsdp = unsafe { &*(ptr::with_exposed_provenance::(current_address)) }; - if &rsdp.signature != b"RSD PTR " { - continue; - } - - // Verify the basic checksum. - if verify_checksum(current_address, RSDP_CHECKSUM_LENGTH).is_err() { - debug!("Found an ACPI table at {current_address:#X}, but its RSDP checksum is invalid"); - continue; - } - - // Verify the extended checksum if this is an ACPI 2.0-compliant table. - if rsdp.revision >= 2 && verify_checksum(current_address, RSDP_XCHECKSUM_LENGTH).is_err() { - debug!( - "Found an ACPI table at {current_address:#X}, but its RSDP extended checksum is invalid" - ); - continue; - } - - // We were successful! Return a pointer to the RSDT (whose 64-bit address is called XSDT in this structure). - info!( - "Found an ACPI revision {} table at {:#X} with OEM ID \"{}\"", - rsdp.revision, - current_address, - rsdp.oem_id() - ); - return Ok(rsdp); - } - - // We found no valid ACPI RSDP. - Err(()) -} - -/// Detects ACPI support of the computer system. -/// Returns a reference to the ACPI RSDP within the Ok() if successful or an empty Err() on failure. -fn detect_acpi() -> Result<&'static AcpiRsdp, ()> { - if let Some(rsdp_addr) = env::start_info().rsdp_addr() { - trace!("RSDP detected successfully at {rsdp_addr:#x?}"); - let rsdp = unsafe { - ptr::with_exposed_provenance::(rsdp_addr.get()) - .as_ref() - .unwrap() - }; - assert!(&rsdp.signature == b"RSD PTR ", "RSDP Address not valid!"); - return Ok(rsdp); - } - - // Get the address of the EBDA. - let frame = PhysFrame::::containing_address(EBDA_PTR_LOCATION.into()); - paging::identity_map::(frame.start_address().into()); - let ebda_ptr_location: &u16 = - unsafe { &*(VirtAddr::from(EBDA_PTR_LOCATION.as_u64()).as_ptr()) }; - let ebda_address = PhysAddr::new(u64::from(*ebda_ptr_location) << 4); - - // Check if the pointed address is valid. This check is also done in ACPICA. - if ebda_address > EBDA_MINIMUM_ADDRESS { - // Try to find an RSDP within the 1 KiB window of the EBDA. - if let Ok(rsdp) = detect_rsdp(ebda_address, ebda_address + EBDA_WINDOW_SIZE) { - return Ok(rsdp); - } - } - - // If we didn't find anything above, check the other memory range for an RSDP. - if let Ok(rsdp) = detect_rsdp(RSDP_SEARCH_ADDRESS_LOW, RSDP_SEARCH_ADDRESS_HIGH) { - return Ok(rsdp); - } - - // We didn't find any ACPI tables. - Err(()) -} - -fn search_s5_in_table(table: AcpiTable<'_>) { - // Get the AML code. - // As we do not implement an AML interpreter, we search through the bytecode. - let aml = unsafe { - slice::from_raw_parts( - ptr::with_exposed_provenance(table.table_start_address()), - table.table_byte_len(), - ) - }; - - // Find the "_S5_" object in the bytecode. - let s5 = [b'_', b'S', b'5', b'_', AML_PACKAGEOP]; - let s5_position = aml.windows(s5.len()).position(|window| window == s5); - let Some(i) = s5_position else { - return; - }; - - // We have found an "_S5_" object that looks valid. - // To be sure, verify that it begins with an AML_NAMEOP or an AML_NAMEOP and a backslash. - if i > 2 && (aml[i - 1] == AML_NAMEOP || (aml[i - 2] == AML_NAMEOP && aml[i - 1] == b'\\')) { - // This is a valid "_S5_" object. - // It should be followed by this structure: - // - single byte for PkgLength (index 5) - // - single byte for NumElements (index 6) - let pkg_length = aml[i + 5]; - let num_elements = aml[i + 6]; - - // Bits 6-7 of PkgLength are non-zero for larger packages, resulting in a different structure. - // This mustn't be the case for the "_S5_" object. - if pkg_length & 0b1100_0000 == 0 && num_elements > 0 { - // The next byte is an opcode describing the data. - // It is usually the byte prefix, indicating that the actual data is the single byte following the opcode. - // However, if the data is a zero or one byte, this may also be indicated by the opcode. - let op = aml[i + 7]; - let slp_typa = match op { - AML_ZEROOP => 0, - AML_ONEOP => 1, - AML_BYTEPREFIX => aml[i + 8], - _ => return, - }; - - // All assumptions are correct, so slp_typa is supposed to contain valid information. - // Now we have all information we need for powering off through ACPI. - // - // Note that Power Off may also be controlled through PM1B_CNT_BLK / SLP_TYPB - // according to the ACPI Specification. However, this has not yet been observed on real computers - // and therefore not implemented. - SLP_TYPA.set(slp_typa).unwrap(); - } - } -} - -fn parse_fadt(fadt: AcpiTable<'_>) { - // Get us a reference to the actual fields of the FADT table. - // Note that not all fields may be accessible depending on the ACPI revision of the computer. - // Always check fadt.table_end_address() when accessing an optional field! - let fadt_table = - unsafe { &*ptr::with_exposed_provenance::(fadt.table_start_address()) }; - - // Check if the FADT is large enough to hold an x_pm1a_cnt_blk field and if this field is non-zero. - // In that case, it shall be preferred over the I/O port specified in pm1a_cnt_blk. - // As all PM1 control registers are supposed to be in I/O space, we can simply check the address_space field - // of x_pm1a_cnt_blk to determine the validity of x_pm1a_cnt_blk. - let x_pm1a_cnt_blk_field_address = ptr::from_ref(&fadt_table.x_pm1a_cnt_blk).addr(); - let pm1a_cnt_blk = if x_pm1a_cnt_blk_field_address < fadt.table_end_address() - && fadt_table.x_pm1a_cnt_blk.address_space == GENERIC_ADDRESS_IO_SPACE - { - fadt_table.x_pm1a_cnt_blk.address as u16 - } else { - fadt_table.pm1a_cnt_blk as u16 - }; - PM1A_CNT_BLK.set(Port::new(pm1a_cnt_blk)).unwrap(); - - // Map the "Differentiated System Description Table" (DSDT). - let x_dsdt_field_address = (&raw const fadt_table.x_dsdt).addr(); - let dsdt_address = if x_dsdt_field_address < fadt.table_end_address() && fadt_table.x_dsdt > 0 { - PhysAddr::new(fadt_table.x_dsdt) - } else { - PhysAddr::new(fadt_table.dsdt.into()) - }; - let dsdt = AcpiTable::map(dsdt_address); - - // Check it. - assert!( - dsdt.header.signature() == "DSDT", - "DSDT at {:p} has invalid signature \"{}\"", - dsdt_address, - dsdt.header.signature() - ); - assert!( - verify_checksum(dsdt.header_start_address(), dsdt.header.length as usize).is_ok(), - "DSDT at {dsdt_address:p} has invalid checksum" - ); - - // Try to find the "_S5_" object for SLP_TYPA in the DSDT AML bytecode. - // It may also be in an SSDT though. - search_s5_in_table(dsdt); -} - -fn parse_ssdt(ssdt: AcpiTable<'_>) { - // We don't need to parse the SSDT if we already have information about the "_S5_" object - // (e.g. from the DSDT or a previous SSDT). - if SLP_TYPA.get().is_some() { - return; - } - - // Otherwise, just try to find "_S5_" information in the AML bytecode of this SSDT. - search_s5_in_table(ssdt); -} - -pub fn get_madt() -> Option<&'static AcpiTable<'static>> { - MADT.get() -} - -#[cfg(feature = "pci")] -pub fn get_mcfg_table() -> Option<&'static AcpiTable<'static>> { - MCFG.get() -} - -pub fn poweroff() { - let (Some(mut pm1a_cnt_blk), Some(&slp_typa)) = (PM1A_CNT_BLK.get().cloned(), SLP_TYPA.get()) - else { - warn!("ACPI Power Off is not available"); - return; - }; - - let bits = (u16::from(slp_typa) << 10) | SLP_EN; - debug!("Powering Off through ACPI (port {pm1a_cnt_blk:?}, bitmask {bits:#X})"); - unsafe { - pm1a_cnt_blk.write(bits); - } -} - -pub fn init() { - #[cfg(feature = "uhyve")] - use env::UhyveStartInfo; - - #[cfg(feature = "uhyve")] - if env::start_info().is_uhyve() { - return; - } - - // Detect the RSDP and get a pointer to either the XSDT (64-bit) or RSDT (32-bit), whichever is available. - // Both are called RSDT in the following. - let rsdp = detect_acpi().expect("Hermit requires an ACPI-compliant system"); - let rsdt_physical_address = if rsdp.revision >= 2 { - PhysAddr::new(rsdp.xsdt_physical_address) - } else { - PhysAddr::new(rsdp.rsdt_physical_address.into()) - }; - - // Map the RSDT. - let rsdt = AcpiTable::map(rsdt_physical_address); - - // The RSDT contains pointers to all available ACPI tables. - // Iterate through them. - let mut current_address = rsdt.table_start_address(); - while current_address < rsdt.table_end_address() { - // Depending on the RSDP revision, either an XSDT or an RSDT has been chosen above. - // The XSDT contains 64-bit pointers whereas the RSDT has 32-bit pointers. - let table_physical_address = if rsdp.revision >= 2 { - let address = unsafe { - PhysAddr::new(ptr::with_exposed_provenance::(current_address).read_unaligned()) - }; - current_address += size_of::(); - address - } else { - let address = unsafe { - PhysAddr::new( - ptr::with_exposed_provenance::(current_address) - .read_unaligned() - .into(), - ) - }; - current_address += size_of::(); - address - }; - - let table = AcpiTable::map(table_physical_address); - debug!("Found ACPI table: {}", table.header.signature()); - - if table.header.signature() == "APIC" { - // The "Multiple APIC Description Table" (MADT) aka "APIC Table" (APIC) - // Check and save the entire APIC table for the get_apic_table() call. - assert!( - verify_checksum(table.header_start_address(), table.header.length as usize).is_ok(), - "MADT at {table_physical_address:p} has invalid checksum" - ); - MADT.set(table).unwrap(); - } else if table.header.signature() == "FACP" { - // The "Fixed ACPI Description Table" (FADT) aka "Fixed ACPI Control Pointer" (FACP) - // Check and parse this table for the poweroff() call. - assert!( - verify_checksum(table.header_start_address(), table.header.length as usize).is_ok(), - "FADT at {table_physical_address:p} has invalid checksum" - ); - parse_fadt(table); - } else if table.header.signature() == "SSDT" { - assert!( - verify_checksum(table.header_start_address(), table.header.length as usize).is_ok(), - "SSDT at {table_physical_address:p} has invalid checksum" - ); - parse_ssdt(table); - } else if table.header.signature() == "MCFG" { - #[cfg(feature = "pci")] - { - assert!( - verify_checksum(table.header_start_address(), table.header.length as usize) - .is_ok(), - "MCFG at {table_physical_address:p} has invalid checksum" - ); - MCFG.set(table).unwrap(); - } - } - } -} diff --git a/src/arch/x86_64/kernel/apic.rs b/src/arch/x86_64/kernel/apic.rs index 597571245a..0671b0a34b 100644 --- a/src/arch/x86_64/kernel/apic.rs +++ b/src/arch/x86_64/kernel/apic.rs @@ -1,8 +1,6 @@ use alloc::vec::Vec; #[cfg(feature = "smp")] use core::arch::x86_64::_mm_mfence; -#[cfg(feature = "acpi")] -use core::fmt; use core::hint::spin_loop; use core::{cmp, ptr}; @@ -15,8 +13,6 @@ use x86_64::registers::control::Cr3; use x86_64::registers::model_specific::Msr; use super::interrupts::IDT; -#[cfg(feature = "acpi")] -use crate::arch::kernel::acpi; #[cfg(feature = "smp")] use crate::arch::kernel::core_local::*; use crate::arch::kernel::{interrupts, processor}; @@ -194,63 +190,6 @@ struct ApicIoEntry { addr: u32, } -#[cfg(feature = "acpi")] -#[repr(C, packed)] -struct AcpiMadtHeader { - local_apic_address: u32, - flags: u32, -} - -#[cfg(feature = "acpi")] -#[repr(C, packed)] -struct AcpiMadtRecordHeader { - entry_type: u8, - length: u8, -} - -#[cfg(feature = "acpi")] -#[repr(C, packed)] -struct ProcessorLocalApicRecord { - acpi_processor_id: u8, - apic_id: u8, - flags: u32, -} - -#[cfg(feature = "acpi")] -impl fmt::Display for ProcessorLocalApicRecord { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{{ acpi_processor_id: {}, ", { self.acpi_processor_id })?; - write!(f, "apic_id: {}, ", { self.apic_id })?; - write!(f, "flags: {} }}", { self.flags })?; - Ok(()) - } -} - -#[cfg(feature = "acpi")] -const CPU_FLAG_ENABLED: u32 = 1 << 0; - -#[cfg(feature = "acpi")] -#[repr(C, packed)] -struct IoApicRecord { - id: u8, - reserved: u8, - address: u32, - global_system_interrupt_base: u32, -} - -#[cfg(feature = "acpi")] -impl fmt::Display for IoApicRecord { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{{ id: {}, ", { self.id })?; - write!(f, "reserved: {}, ", { self.reserved })?; - write!(f, "address: {:#X}, ", { self.address })?; - write!(f, "global_system_interrupt_base: {} }}", { - self.global_system_interrupt_base - })?; - Ok(()) - } -} - #[cfg(feature = "smp")] extern "x86-interrupt" fn tlb_flush_handler(stack_frame: interrupts::ExceptionStackFrame) { swapgs(&stack_frame); @@ -315,51 +254,30 @@ fn init_ioapic_address(phys_addr: PhysAddr) { #[cfg(feature = "acpi")] fn detect_from_acpi() -> Result { - // Get the Multiple APIC Description Table (MADT) from the ACPI information and its specific table header. - let madt = acpi::get_madt().ok_or(())?; - let madt_header = - unsafe { &*(ptr::with_exposed_provenance::(madt.table_start_address())) }; - - // Jump to the actual table entries (after the table header). - let mut current_address = madt.table_start_address() + size_of::(); - - // Loop through all table entries. - while current_address < madt.table_end_address() { - let record = - unsafe { &*(ptr::with_exposed_provenance::(current_address)) }; - current_address += size_of::(); - - match record.entry_type { - 0 => { - // Processor Local APIC - let processor_local_apic_record = unsafe { - &*(ptr::with_exposed_provenance::(current_address)) - }; - debug!("Found Processor Local APIC record: {processor_local_apic_record}"); - - if processor_local_apic_record.flags & CPU_FLAG_ENABLED > 0 { - add_local_apic_id(processor_local_apic_record.apic_id); - } - } - 1 => { - // I/O APIC - let ioapic_record = - unsafe { &*(ptr::with_exposed_provenance::(current_address)) }; - debug!("Found I/O APIC record: {ioapic_record}"); + use acpi::sdt::madt::{Madt, MadtEntry}; - init_ioapic_address(PhysAddr::new(ioapic_record.address.into())); + use crate::acpi::MultipleApicFlags; + + let madt = crate::acpi::find_table::().ok_or(())?; + + for entry in madt.get().entries() { + debug!("MADT entry: {entry:?}"); + match entry { + MadtEntry::LocalApic(local_apic_entry) => { + let flags = MultipleApicFlags::from_bits_retain(local_apic_entry.flags); + if flags.contains(MultipleApicFlags::PCAT_COMPAT) { + add_local_apic_id(local_apic_entry.apic_id); + } } - _ => { - // Just ignore other entries for now. + MadtEntry::IoApic(io_apic_entry) => { + let paddr = PhysAddr::new(io_apic_entry.io_apic_address.into()); + init_ioapic_address(paddr); } + _ => (), } - - current_address += record.length as usize - size_of::(); } - // Successfully derived all information from the MADT. - // Return the physical address of the Local APIC. - Ok(PhysAddr::new(madt_header.local_apic_address.into())) + Ok(PhysAddr::new(madt.get().local_apic_address.into())) } /// Search Floating Pointer Structure of the Multiprocessing Specification diff --git a/src/arch/x86_64/kernel/mod.rs b/src/arch/x86_64/kernel/mod.rs index 0fbb9f689e..1612340743 100644 --- a/src/arch/x86_64/kernel/mod.rs +++ b/src/arch/x86_64/kernel/mod.rs @@ -12,8 +12,6 @@ use crate::arch::kernel::core_local::*; #[cfg(feature = "uhyve")] use crate::env::{self, UhyveStartInfo}; -#[cfg(feature = "acpi")] -mod acpi; pub mod apic; pub mod core_local; pub mod gdt; @@ -81,7 +79,7 @@ pub fn boot_processor_init() { systemtime::init(); #[cfg(feature = "acpi")] - acpi::init(); + crate::acpi::init(); #[cfg(feature = "pci")] pci::init(); diff --git a/src/arch/x86_64/kernel/pci.rs b/src/arch/x86_64/kernel/pci.rs index 583eefae04..49cdc1e99c 100644 --- a/src/arch/x86_64/kernel/pci.rs +++ b/src/arch/x86_64/kernel/pci.rs @@ -14,7 +14,7 @@ const CONFIG_DATA: Port = Port::new(0xcfc); pub enum PciConfigRegion { Pci(LegacyPciConfigRegion), #[cfg(feature = "acpi")] - PciE(pcie::McfgEntry), + PciE(pcie::McfgConfigRegion), } impl ConfigRegionAccess for PciConfigRegion { @@ -123,49 +123,36 @@ fn scan_bus(bus_range: impl IntoIterator + Debug, pci_config: PciConf #[cfg(feature = "acpi")] mod pcie { - use core::{ptr, slice}; - + use acpi::sdt::mcfg::{Mcfg, McfgEntry}; use memory_addresses::{PhysAddr, VirtAddr}; use pci_types::{ConfigRegionAccess, PciAddress}; use super::PciConfigRegion; - use crate::arch::kernel::acpi; use crate::arch::mm::paging::{ self, LargePageSize, PageTableEntryFlags, PageTableEntryFlagsExt, }; use crate::mm::device_alloc::DeviceAlloc; pub fn init_pcie() -> bool { - let Some(table) = acpi::get_mcfg_table() else { + let Some(mcfg) = crate::acpi::find_table::() else { return false; }; - let start = ptr::with_exposed_provenance::(table.table_start_address() + 8); - let len = table.table_byte_len() / size_of::(); - let entries = unsafe { slice::from_raw_parts(start, len) }; + let mut found = false; - if entries.is_empty() { - return false; + for mcfg_entry in mcfg.entries() { + init_pcie_bus(mcfg_entry); + found = true; } - for entry in entries { - init_pcie_bus(entry); - } - - true + found } #[derive(Clone, Copy, Debug)] - #[repr(C, packed)] - pub struct McfgEntry { - pub base_address: u64, - pub pci_segment_group: u16, - pub bus_number_start: u8, - pub bus_number_end: u8, - _reserved: u32, - } + #[repr(transparent)] + pub struct McfgConfigRegion(McfgEntry); - impl McfgEntry { + impl McfgConfigRegion { pub fn pci_config_space_address( &self, bus_number: u8, @@ -173,7 +160,7 @@ mod pcie { function: u8, ) -> PhysAddr { PhysAddr::new( - self.base_address + self.0.base_address + ((u64::from(bus_number) << 20) | ((u64::from(device) & 0x1f) << 15) | ((u64::from(function) & 0x7) << 12)), @@ -181,11 +168,11 @@ mod pcie { } } - impl ConfigRegionAccess for McfgEntry { + impl ConfigRegionAccess for McfgConfigRegion { unsafe fn read(&self, address: PciAddress, offset: u16) -> u32 { - assert!(address.segment() == self.pci_segment_group); - assert!(address.bus() >= self.bus_number_start); - assert!(address.bus() <= self.bus_number_end); + assert!(address.segment() == self.0.pci_segment_group); + assert!(address.bus() >= self.0.bus_number_start); + assert!(address.bus() <= self.0.bus_number_end); let phys_addr = self.pci_config_space_address(address.bus(), address.device(), address.function()) @@ -196,9 +183,9 @@ mod pcie { } unsafe fn write(&self, address: PciAddress, offset: u16, value: u32) { - assert!(address.segment() == self.pci_segment_group); - assert!(address.bus() >= self.bus_number_start); - assert!(address.bus() <= self.bus_number_end); + assert!(address.segment() == self.0.pci_segment_group); + assert!(address.bus() >= self.0.bus_number_start); + assert!(address.bus() <= self.0.bus_number_end); let phys_addr = self.pci_config_space_address(address.bus(), address.device(), address.function()) @@ -231,7 +218,7 @@ mod pcie { super::scan_bus( bus_entry.bus_number_start..=bus_entry.bus_number_end, - PciConfigRegion::PciE(*bus_entry), + PciConfigRegion::PciE(McfgConfigRegion(*bus_entry)), ); } } diff --git a/src/arch/x86_64/kernel/processor.rs b/src/arch/x86_64/kernel/processor.rs index d1db87e567..c9c72563c9 100644 --- a/src/arch/x86_64/kernel/processor.rs +++ b/src/arch/x86_64/kernel/processor.rs @@ -1088,7 +1088,7 @@ pub fn shutdown(error_code: i32) -> ! { #[cfg(feature = "acpi")] { - super::acpi::poweroff(); + crate::acpi::shutdown(); } triple_fault() diff --git a/src/lib.rs b/src/lib.rs index 6ab2f3ace9..89ea7a63d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,6 +95,8 @@ mod macros; #[macro_use] mod logging; +#[cfg(feature = "acpi")] +mod acpi; pub mod arch; #[cfg(all(feature = "common-os", target_arch = "x86_64"))] pub mod common_os;