use std::collections::BTreeMap; use std::convert::{TryFrom, TryInto}; use std::ops::Deref; use std::sync::Arc; use std::{fmt, mem}; use syscall::flag::PhysmapFlags; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use thiserror::Error; use super::aml::AmlValue; pub mod dmar; use self::dmar::Dmar; #[cfg(target_arch = "aarch64")] pub const PAGE_SIZE: usize = 4096; #[cfg(target_arch = "x86")] pub const PAGE_SIZE: usize = 4096; #[cfg(target_arch = "x86_64")] pub const PAGE_SIZE: usize = 4096; /// The raw SDT header struct, as defined by the ACPI specification. #[derive(Copy, Clone, Debug)] #[repr(packed)] pub struct SdtHeader { pub signature: [u8; 4], pub length: u32, pub revision: u8, pub checksum: u8, pub oem_id: [u8; 6], pub oem_table_id: [u8; 8], pub oem_revision: u32, pub creator_id: u32, pub creator_revision: u32, } unsafe impl plain::Plain for SdtHeader {} impl SdtHeader { pub fn signature(&self) -> SdtSignature { SdtSignature { signature: self.signature, oem_id: self.oem_id, oem_table_id: self.oem_table_id, } } pub fn length(&self) -> usize { self .length .try_into() .expect("expected usize to be at least 32 bits") } } #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct SdtSignature { pub signature: [u8; 4], pub oem_id: [u8; 6], pub oem_table_id: [u8; 8], } impl fmt::Display for SdtSignature { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}-{}-{}", String::from_utf8_lossy(&self.signature), String::from_utf8_lossy(&self.oem_id), String::from_utf8_lossy(&self.oem_table_id)) } } #[derive(Debug, Error)] pub enum TablePhysLoadError { // TODO: Make syscall::Error implement std::error::Error, when enabling a Cargo feature. #[error("i/o error: {0}")] Io(#[from] std::io::Error), #[error("invalid SDT: {0}")] Validity(#[from] InvalidSdtError), } #[derive(Debug, Error)] pub enum InvalidSdtError { #[error("invalid size")] InvalidSize, #[error("invalid checksum")] BadChecksum, } struct PhysmapGuard { virt: *const u8, size: usize, } impl PhysmapGuard { fn map(page: usize, page_count: usize) -> std::io::Result { let size = page_count * PAGE_SIZE; let virt = unsafe { syscall::call::physmap(page, size, PhysmapFlags::empty()) .map_err(|error| std::io::Error::from_raw_os_error(error.errno))? }; Ok(Self { virt: virt as *const u8, size, }) } } impl Deref for PhysmapGuard { type Target = [u8]; fn deref(&self) -> &Self::Target { unsafe { std::slice::from_raw_parts(self.virt as *const u8, self.size) } } } impl Drop for PhysmapGuard { fn drop(&mut self) { unsafe { let _ = syscall::physunmap(self.virt as usize); } } } #[derive(Clone)] pub struct Sdt(Arc<[u8]>); impl Sdt { pub fn new(slice: Arc<[u8]>) -> Result { let header = match plain::from_bytes::(&slice) { Ok(header) => header, Err(plain::Error::TooShort) => return Err(InvalidSdtError::InvalidSize), Err(plain::Error::BadAlignment) => panic!("plain::from_bytes failed due to alignment, but SdtHeader is #[repr(packed)]!"), }; if header.length() != slice.len() { return Err(InvalidSdtError::InvalidSize); } let checksum = slice.iter().copied().fold(0_u8, |current_sum, item| current_sum.wrapping_add(item)); if checksum != 0 { return Err(InvalidSdtError::BadChecksum); } Ok(Self(slice)) } pub fn load_from_physical(physaddr: usize) -> Result { let physaddr_start_page = physaddr / PAGE_SIZE * PAGE_SIZE; let physaddr_page_offset = physaddr % PAGE_SIZE; // Begin by reading and validating the header first. The SDT header is always 36 bytes // long, and can thus span either one or two page table frames. let needs_extra_page = (PAGE_SIZE - physaddr_page_offset).checked_sub(mem::size_of::()).is_none(); let page_table_count = 1 + if needs_extra_page { 1 } else { 0 }; let pages = PhysmapGuard::map(physaddr_start_page, page_table_count)?; assert!(pages.len() >= mem::size_of::()); let sdt_mem = &pages[physaddr_page_offset..]; let sdt = plain::from_bytes::(&sdt_mem[..mem::size_of::()]) .expect("either alignment is wrong, or the length is too short, both of which are already checked for"); let total_length = sdt.length(); let base_length = std::cmp::min(total_length, sdt_mem.len()); let extended_length = total_length - base_length; let mut loaded = sdt_mem[..base_length].to_owned(); loaded.reserve(extended_length); const SIMULTANEOUS_PAGE_COUNT: usize = 4; let mut left = extended_length; let mut offset = physaddr_start_page + page_table_count * PAGE_SIZE; let length_per_iteration = PAGE_SIZE * SIMULTANEOUS_PAGE_COUNT; while left > 0 { let to_copy = std::cmp::min(left, length_per_iteration); let additional_pages = PhysmapGuard::map(offset, length_per_iteration)?; loaded.extend(&additional_pages[..to_copy]); left -= to_copy; offset += to_copy; } assert_eq!(left, 0); Self::new(loaded.into()).map_err(Into::into) } pub fn as_slice(&self) -> &[u8] { &self.0 } } impl Deref for Sdt { type Target = SdtHeader; fn deref(&self) -> &Self::Target { plain::from_bytes::(&self.0) .expect("expected already validated Sdt to be able to get its header") } } impl Sdt { pub fn data(&self) -> &[u8] { &self.0[mem::size_of::()..] } } impl fmt::Debug for Sdt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Sdt") .field("header", &*self as &SdtHeader) .field("extra_len", &self.data().len()) .finish() } } pub struct Dsdt(Sdt); pub struct Ssdt(Sdt); pub struct AcpiContext { tables: Vec, dsdt: Option, fadt: Option, // TODO: Remove Option. This is not kernel code, and not static, but we still need to replace // every match where namespace{,_mut}() are used. namespace: RwLock>>, // TODO: The kernel ACPI code seemed to use load_table quite ubiquitously, however ACPI 5.1 // states that DDBHandles can only be obtained when loading XSDT-pointed tables. So, we'll // generate an index only for those. sdt_order: RwLock>>, pub next_ctx: RwLock, } impl AcpiContext { pub fn init(rxsdt_physaddrs: impl Iterator) -> Self { let tables = rxsdt_physaddrs.map(|physaddr| { let physaddr: usize = physaddr .try_into() .expect("expected ACPI addresses to be compatible with the current word size"); log::debug!("TABLE AT {:#>08X}", physaddr); Sdt::load_from_physical(physaddr) .expect("failed to load physical SDT") }).collect::>(); let mut this = Self { tables, dsdt: None, fadt: None, namespace: RwLock::new(Some(BTreeMap::new())), next_ctx: RwLock::new(0), sdt_order: RwLock::new(Vec::new()), }; for table in &this.tables { this.new_index(&table.signature()); } Fadt::init(&mut this); //TODO (hangs on real hardware): Dmar::init(&this); crate::aml::init_namespace(&this); for (path, _) in this.namespace.get_mut().as_mut().unwrap().iter() { log::trace!("ACPI NS: {}", path); } this } pub fn dsdt(&self) -> Option<&Dsdt> { self.dsdt.as_ref() } pub fn ssdts(&self) -> impl Iterator + '_ { self.find_multiple_sdts(*b"SSDT").map(|sdt| Ssdt(sdt.clone())) } fn find_single_sdt_pos(&self, signature: [u8; 4]) -> Option { let count = self.tables.iter().filter(|sdt| sdt.signature == signature).count(); if count > 1 { log::warn!("Expected only a single SDT of signature `{}` ({:?}), but there were {}", String::from_utf8_lossy(&signature), signature, count); } self.tables.iter().position(|sdt| sdt.signature == signature) } pub fn find_multiple_sdts<'a>(&'a self, signature: [u8; 4]) -> impl Iterator { self.tables.iter().filter(move |sdt| sdt.signature == signature) } pub fn take_single_sdt(&self, signature: [u8; 4]) -> Option { self.find_single_sdt_pos(signature).map(|pos| self.tables[pos].clone()) } pub fn namespace(&self) -> RwLockReadGuard<'_, Option>> { self.namespace.read() } pub fn namespace_mut(&self) -> RwLockWriteGuard<'_, Option>> { self.namespace.write() } pub fn fadt(&self) -> Option<&Fadt> { self.fadt.as_ref() } pub fn sdt_from_signature(&self, signature: &SdtSignature) -> Option<&Sdt> { self.tables.iter().find(|sdt| sdt.signature == signature.signature && sdt.oem_id == signature.oem_id && sdt.oem_table_id == signature.oem_table_id) } pub fn get_signature_from_index(&self, index: usize) -> Option { self.sdt_order.read().get(index).copied().flatten() } pub fn get_index_from_signature(&self, signature: &SdtSignature) -> Option { self.sdt_order.read().iter().rposition(|sig| sig.map_or(false, |sig| &sig == signature)) } pub fn tables(&self) -> &[Sdt] { &self.tables } pub fn new_index(&self, signature: &SdtSignature) { self.sdt_order.write().push(Some(*signature)); } } #[repr(packed)] #[derive(Clone, Copy, Debug)] pub struct FadtStruct { pub header: SdtHeader, pub firmware_ctrl: u32, pub dsdt: u32, // field used in ACPI 1.0; no longer in use, for compatibility only reserved: u8, pub preferred_power_managament: u8, pub sci_interrupt: u16, pub smi_command_port: u32, pub acpi_enable: u8, pub acpi_disable: u8, pub s4_bios_req: u8, pub pstate_control: u8, pub pm1a_event_block: u32, pub pm1b_event_block: u32, pub pm1a_control_block: u32, pub pm1b_control_block: u32, pub pm2_control_block: u32, pub pm_timer_block: u32, pub gpe0_block: u32, pub gpe1_block: u32, pub pm1_event_length: u8, pub pm1_control_length: u8, pub pm2_control_length: u8, pub pm_timer_length: u8, pub gpe0_ength: u8, pub gpe1_length: u8, pub gpe1_base: u8, pub c_state_control: u8, pub worst_c2_latency: u16, pub worst_c3_latency: u16, pub flush_size: u16, pub flush_stride: u16, pub duty_offset: u8, pub duty_width: u8, pub day_alarm: u8, pub month_alarm: u8, pub century: u8, // reserved in ACPI 1.0; used since ACPI 2.0+ pub boot_architecture_flags: u16, reserved2: u8, pub flags: u32, } unsafe impl plain::Plain for FadtStruct {} #[repr(packed)] #[derive(Clone, Copy, Debug, Default)] pub struct GenericAddressStructure { address_space: u8, bit_width: u8, bit_offset: u8, access_size: u8, address: u64, } #[repr(packed)] #[derive(Clone, Copy, Debug)] pub struct FadtAcpi2Struct { // 12 byte structure; see below for details pub reset_reg: GenericAddressStructure, pub reset_value: u8, reserved3: [u8; 3], // 64bit pointers - Available on ACPI 2.0+ pub x_firmware_control: u64, pub x_dsdt: u64, pub x_pm1a_event_block: GenericAddressStructure, pub x_pm1b_event_block: GenericAddressStructure, pub x_pm1a_control_block: GenericAddressStructure, pub x_pm1b_control_block: GenericAddressStructure, pub x_pm2_control_block: GenericAddressStructure, pub x_pm_timer_block: GenericAddressStructure, pub x_gpe0_block: GenericAddressStructure, pub x_gpe1_block: GenericAddressStructure, } unsafe impl plain::Plain for FadtAcpi2Struct {} #[derive(Clone)] pub struct Fadt(Sdt); impl Fadt { pub fn acpi_2_struct(&self) -> Option<&FadtAcpi2Struct> { let bytes = &self.0.0[mem::size_of::()..]; match plain::from_bytes::(bytes) { Ok(fadt2) => Some(fadt2), Err(plain::Error::TooShort) => None, Err(plain::Error::BadAlignment) => unreachable!("plain::from_bytes reported bad alignment, but FadtAcpi2Struct is #[repr(packed)]"), } } } impl Deref for Fadt { type Target = FadtStruct; fn deref(&self) -> &Self::Target { plain::from_bytes::(&self.0.0) .expect("expected FADT struct to already be validated in Deref impl") } } impl Fadt { pub fn new(sdt: Sdt) -> Option { if sdt.signature != *b"FACP" || sdt.length() < mem::size_of::() { return None; } Some(Fadt(sdt)) } pub fn init(context: &mut AcpiContext) { let fadt_sdt = context .take_single_sdt(*b"FACP") .expect("expected ACPI to always have a FADT"); let fadt = match Fadt::new(fadt_sdt) { Some(fadt) => fadt, None => { log::error!("Failed to find FADT"); return; } }; let dsdt_ptr = match fadt.acpi_2_struct() { Some(fadt2) => usize::try_from(fadt2.x_dsdt).unwrap_or_else(|_| { usize::try_from(fadt.dsdt) .expect("expected any given u32 to fit within usize") }), None => usize::try_from(fadt.dsdt) .expect("expected any given u32 to fit within usize") }; log::debug!("FACP at {:X}", {dsdt_ptr}); let dsdt_sdt = match Sdt::load_from_physical(fadt.dsdt as usize) { Ok(dsdt) => dsdt, Err(error) => { log::error!("Failed to load DSDT: {}", error); return; } }; context.fadt = Some(fadt.clone()); context.dsdt = Some(Dsdt(dsdt_sdt.clone())); context.tables.push(dsdt_sdt); } } pub enum PossibleAmlTables { Dsdt(Dsdt), Ssdt(Ssdt), } impl PossibleAmlTables { pub fn try_new(inner: Sdt) -> Option { match &inner.signature { b"DSDT" => Some(Self::Dsdt(Dsdt(inner))), b"SSDT" => Some(Self::Ssdt(Ssdt(inner))), _ => None, } } } impl AmlContainingTable for PossibleAmlTables { fn aml(&self) -> &[u8] { match self { Self::Dsdt(dsdt) => dsdt.aml(), Self::Ssdt(ssdt) => ssdt.aml(), } } fn header(&self) -> &SdtHeader { match self { Self::Dsdt(dsdt) => dsdt.header(), Self::Ssdt(ssdt) => ssdt.header(), } } } pub trait AmlContainingTable { fn aml(&self) -> &[u8]; fn header(&self) -> &SdtHeader; } impl AmlContainingTable for &T where T: AmlContainingTable, { fn aml(&self) -> &[u8] { T::aml(*self) } fn header(&self) -> &SdtHeader { T::header(*self) } } impl AmlContainingTable for Dsdt { fn aml(&self) -> &[u8] { self.0.data() } fn header(&self) -> &SdtHeader { &*self.0 } } impl AmlContainingTable for Ssdt { fn aml(&self) -> &[u8] { self.0.data() } fn header(&self) -> &SdtHeader { &*self.0 } }