diff --git a/Cargo.lock b/Cargo.lock index ec0ff080d9..e973ca2001 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,6 +5,8 @@ name = "acpid" version = "0.1.0" dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "num-derive 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "redox-log 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -619,6 +621,16 @@ dependencies = [ "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-integer" version = "0.1.43" @@ -1628,6 +1640,7 @@ dependencies = [ "checksum netutils 0.1.0 (git+https://gitlab.redox-os.org/redox-os/netutils.git?branch=redox-unix)" = "" "checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" "checksum ntpclient 0.0.1 (git+https://github.com/willem66745/ntpclient-rust)" = "" +"checksum num-derive 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" "checksum num-integer 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" "checksum num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" "checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" diff --git a/acpid/Cargo.toml b/acpid/Cargo.toml index 3b990a92cf..dc52205604 100644 --- a/acpid/Cargo.toml +++ b/acpid/Cargo.toml @@ -8,6 +8,8 @@ edition = "2018" [dependencies] log = "0.4" +num-derive = "0.3" +num-traits = "0.2" parking_lot = "0.11.1" plain = "0.2.3" redox-log = "0.1.1" diff --git a/acpid/src/acpi.rs b/acpid/src/acpi.rs index 5b1c24c52b..d2cdd9c295 100644 --- a/acpid/src/acpi.rs +++ b/acpid/src/acpi.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use std::convert::{TryFrom, TryInto}; use std::ops::Deref; -use std::sync::{Arc, atomic::{self, AtomicUsize}}; +use std::sync::Arc; use std::{fmt, mem}; use syscall::flag::PhysmapFlags; @@ -11,6 +11,9 @@ use thiserror::Error; use super::aml::AmlValue; +pub mod dmar; +use self::dmar::Dmar; + #[cfg(target_arch = "x86_64")] pub const PAGE_SIZE: usize = 4096; @@ -255,6 +258,7 @@ impl AcpiContext { } Fadt::init(&mut this); + Dmar::init(&this); crate::aml::init_namespace(&this); @@ -283,7 +287,7 @@ impl AcpiContext { 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(&mut self, signature: [u8; 4]) -> Option { + 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>> { diff --git a/acpid/src/acpi/dmar/drhd.rs b/acpid/src/acpi/dmar/drhd.rs new file mode 100644 index 0000000000..ee6254f636 --- /dev/null +++ b/acpid/src/acpi/dmar/drhd.rs @@ -0,0 +1,119 @@ +use std::ops::{Deref, DerefMut}; + +use syscall::io::Mmio; + +// TODO: Only wrap with Mmio where there are hardware-registers. (Some of these structs seem to be +// ring buffer entries, which are not to be treated the same way). + +pub struct DrhdPage { + virt: *mut Drhd, +} +impl DrhdPage { + pub fn map(base_phys: usize) -> syscall::Result { + assert_eq!(base_phys % crate::acpi::PAGE_SIZE, 0, "DRHD registers must be page-aligned"); + + // TODO: Uncachable? Can reads have side-effects? + let virt = unsafe { + syscall::physmap(base_phys, crate::acpi::PAGE_SIZE, syscall::PhysmapFlags::PHYSMAP_WRITE)? + } as *mut Drhd; + + Ok(Self { virt }) + } +} +impl Deref for DrhdPage { + type Target = Drhd; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.virt } + } +} +impl DerefMut for DrhdPage { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut *self.virt } + } +} +impl Drop for DrhdPage { + fn drop(&mut self) { + unsafe { + let _ = syscall::physunmap(self.virt as usize); + } + } +} + +#[repr(packed)] +pub struct DrhdFault { + pub sts: Mmio, + pub ctrl: Mmio, + pub data: Mmio, + pub addr: [Mmio; 2], + _rsv: [Mmio; 2], + pub log: Mmio, +} + +#[repr(packed)] +pub struct DrhdProtectedMemory { + pub en: Mmio, + pub low_base: Mmio, + pub low_limit: Mmio, + pub high_base: Mmio, + pub high_limit: Mmio, +} + +#[repr(packed)] +pub struct DrhdInvalidation { + pub queue_head: Mmio, + pub queue_tail: Mmio, + pub queue_addr: Mmio, + _rsv: Mmio, + pub cmpl_sts: Mmio, + pub cmpl_ctrl: Mmio, + pub cmpl_data: Mmio, + pub cmpl_addr: [Mmio; 2], +} + +#[repr(packed)] +pub struct DrhdPageRequest { + pub queue_head: Mmio, + pub queue_tail: Mmio, + pub queue_addr: Mmio, + _rsv: Mmio, + pub sts: Mmio, + pub ctrl: Mmio, + pub data: Mmio, + pub addr: [Mmio; 2], +} + +#[repr(packed)] +pub struct DrhdMtrrVariable { + pub base: Mmio, + pub mask: Mmio, +} + +#[repr(packed)] +pub struct DrhdMtrr { + pub cap: Mmio, + pub def_type: Mmio, + pub fixed: [Mmio; 11], + pub variable: [DrhdMtrrVariable; 10], +} + +#[repr(packed)] +pub struct Drhd { + pub version: Mmio, + _rsv: Mmio, + pub cap: Mmio, + pub ext_cap: Mmio, + pub gl_cmd: Mmio, + pub gl_sts: Mmio, + pub root_table: Mmio, + pub ctx_cmd: Mmio, + _rsv1: Mmio, + pub fault: DrhdFault, + _rsv2: Mmio, + pub pm: DrhdProtectedMemory, + pub invl: DrhdInvalidation, + _rsv3: Mmio, + pub intr_table: Mmio, + pub page_req: DrhdPageRequest, + pub mtrr: DrhdMtrr, +} diff --git a/acpid/src/acpi/dmar/mod.rs b/acpid/src/acpi/dmar/mod.rs new file mode 100644 index 0000000000..6d59c25e99 --- /dev/null +++ b/acpid/src/acpi/dmar/mod.rs @@ -0,0 +1,520 @@ +//! DMA Remapping Table -- `DMAR`. This is Intel's implementation of IOMMU functionality, known as +//! VT-d. +//! +//! Too understand what all of these structs mean, refer to the "Intel(R) Virtualization +//! Technology for Directed I/O" specification. + +// TODO: Move this code to a separate driver as well? + +use std::convert::TryFrom; +use std::ops::Deref; +use std::{fmt, mem}; + +use syscall::io::Io as _; + +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; + +use self::drhd::DrhdPage; +use crate::acpi::{AcpiContext, Sdt, SdtHeader}; + +pub mod drhd; + +#[repr(packed)] +pub struct DmarStruct { + pub sdt_header: SdtHeader, + pub host_addr_width: u8, + pub flags: u8, + pub _rsvd: [u8; 10], + + // This header is followed by N remapping structures. +} +unsafe impl plain::Plain for DmarStruct {} + +/// The DMA Remapping Table +#[derive(Debug)] +pub struct Dmar(Sdt); + +impl Dmar { + fn remmapping_structs_area(&self) -> &[u8] { + &self.0.as_slice()[mem::size_of::()..] + } +} + +impl Deref for Dmar { + type Target = DmarStruct; + + fn deref(&self) -> &Self::Target { + plain::from_bytes(self.0.as_slice()) + .expect("expected Dmar struct to already have checked the length, and alignment issues should be impossible due to #[repr(packed)]") + } +} + +impl Dmar { + // TODO: Again, perhaps put this code into a different driver, and read the table the regular + // way via the acpi scheme? + pub fn init(acpi_ctx: &AcpiContext) { + let dmar_sdt = match acpi_ctx.take_single_sdt(*b"DMAR") { + Some(dmar_sdt) => dmar_sdt, + None => { + log::warn!("Unable to find `DMAR` ACPI table."); + return; + } + }; + let dmar = match Dmar::new(dmar_sdt) { + Some(dmar) => dmar, + None => { + log::error!("Failed to parse DMAR table, possibly malformed."); + return; + } + }; + + log::info!("Found DMAR: {}: {}", dmar.host_addr_width, dmar.flags); + log::debug!("DMAR: {:?}", dmar); + + for dmar_entry in dmar.iter() { + log::debug!("DMAR entry: {:?}", dmar_entry); + match dmar_entry { + DmarEntry::Drhd(dmar_drhd) => { + let drhd = dmar_drhd.map(); + + log::debug!("VER: {:X}", drhd.version.read()); + log::debug!("CAP: {:X}", drhd.cap.read()); + log::debug!("EXT_CAP: {:X}", drhd.ext_cap.read()); + log::debug!("GCMD: {:X}", drhd.gl_cmd.read()); + log::debug!("GSTS: {:X}", drhd.gl_sts.read()); + log::debug!("RT: {:X}", drhd.root_table.read()); + }, + _ => () + } + } + } + + fn new(sdt: Sdt) -> Option { + assert_eq!(sdt.signature, *b"DMAR", "signature already checked against `DMAR`"); + if sdt.length() < mem::size_of::() { + log::error!("The DMAR table was too small ({} B < {} B).", sdt.length(), mem::size_of::()); + return None; + } + // No need to check alignment for #[repr(packed)] structs. + + Some(Dmar(sdt)) + } + + pub fn iter(&self) -> DmarIter<'_> { + DmarIter(DmarRawIter { bytes: self.remmapping_structs_area() }) + } +} + +/// DMAR DMA Remapping Hardware Unit Definition +#[derive(Clone, Copy, Debug)] +#[repr(packed)] +pub struct DmarDrhdHeader { + pub kind: u16, + pub length: u16, + + pub flags: u8, + pub _rsv: u8, + pub segment: u16, + pub base: u64, +} +unsafe impl plain::Plain for DmarDrhdHeader {} + +#[derive(Clone, Copy, Debug)] +#[repr(packed)] +pub struct DeviceScopeHeader { + pub ty: u8, + pub len: u8, + pub _rsvd: u16, + pub enumeration_id: u8, + pub start_bus_num: u8, + + // The variable-sized path comes after. +} +unsafe impl plain::Plain for DeviceScopeHeader {} + +pub struct DeviceScope(Box<[u8]>); + +impl DeviceScope { + pub fn try_new(raw: &[u8]) -> Option { + // TODO: Check ty. + + let header_bytes = match raw.get(..mem::size_of::()) { + Some(bytes) => bytes, + None => return None, + }; + let header = plain::from_bytes::(header_bytes) + .expect("length already checked, and alignment 1 (#[repr(packed)] should suffice"); + + let len = usize::from(header.len); + + if len > raw.len() { + log::warn!("Device scope smaller than len field."); + return None; + } + + Some(Self(raw.into())) + } +} + +impl fmt::Debug for DeviceScope { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DeviceScope") + .field("header", &*self as &DeviceScopeHeader) + .field("path", &self.path()) + .finish() + } +} + +impl Deref for DeviceScope { + type Target = DeviceScopeHeader; + + fn deref(&self) -> &Self::Target { + plain::from_bytes(&self.0) + .expect("expected length to be sufficient, and alignment (due to #[repr(packed)]") + } +} +impl DeviceScope { + pub fn path(&self) -> &[u8] { + &self.0[mem::size_of::()..] + } +} + +pub struct DmarDrhd(Box<[u8]>); + +impl DmarDrhd { + pub fn try_new(raw: &[u8]) -> Option { + if raw.len() < mem::size_of::() { + return None; + } + + Some(Self(raw.into())) + } + pub fn device_scope_area(&self) -> &[u8] { + &self.0[mem::size_of::()..] + } + pub fn map(&self) -> DrhdPage { + let base = usize::try_from(self.base).expect("expected u64 to fit within usize"); + + DrhdPage::map(base) + .expect("failed to map DRHD registers") + } +} +impl Deref for DmarDrhd { + type Target = DmarDrhdHeader; + + fn deref(&self) -> &Self::Target { + plain::from_bytes::(&self.0[..mem::size_of::()]) + .expect("length is already checked, and alignment 1 (#[repr(packed)] should suffice") + } +} +impl fmt::Debug for DmarDrhd { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DmarDrhd") + .field("header", &*self as &DmarDrhd) + // TODO: print out device scopes + .finish() + } +} + +/// DMAR Reserved Memory Region Reporting +#[derive(Clone, Copy, Debug)] +#[repr(packed)] +pub struct DmarRmrrHeader { + pub kind: u16, + pub length: u16, + pub _rsv: u16, + pub segment: u16, + pub base: u64, + pub limit: u64, + + // The device scopes come after. +} +unsafe impl plain::Plain for DmarRmrrHeader {} + +pub struct DmarRmrr(Box<[u8]>); + +impl DmarRmrr { + pub fn try_new(raw: &[u8]) -> Option { + if raw.len() < mem::size_of::() { + return None; + } + + Some(Self(raw.into())) + } +} +impl Deref for DmarRmrr { + type Target = DmarRmrrHeader; + + fn deref(&self) -> &Self::Target { + plain::from_bytes(&self.0[..mem::size_of::()]) + .expect("length already checked, and with #[repr(packed)] alignment should be okay") + } +} +impl fmt::Debug for DmarRmrr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DmarRmrr") + .field("header", &*self as &DmarRmrrHeader) + // TODO: print out device scopes + .finish() + } +} + +/// DMAR Root Port ATS Capability Reporting +#[derive(Clone, Copy, Debug)] +#[repr(packed)] +pub struct DmarAtsrHeader { + kind: u16, + length: u16, + flags: u8, + _rsv: u8, + segment: u16, + + // The device scopes come after. +} +unsafe impl plain::Plain for DmarAtsrHeader {} + +pub struct DmarAtsr(Box<[u8]>); + +impl DmarAtsr { + pub fn try_new(raw: &[u8]) -> Option { + if raw.len() < mem::size_of::() { + return None; + } + + Some(Self(raw.into())) + } +} +impl Deref for DmarAtsr { + type Target = DmarAtsrHeader; + + fn deref(&self) -> &Self::Target { + plain::from_bytes(&self.0[..mem::size_of::()]) + .expect("length already checked, and with #[repr(packed)] alignment should be okay") + } +} +impl fmt::Debug for DmarAtsr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DmarAtsr") + .field("header", &*self as &DmarAtsrHeader) + // TODO: print out device scopes + .finish() + } +} + +/// DMAR Remapping Hardware Static Affinity +#[derive(Clone, Copy, Debug)] +#[repr(packed)] +pub struct DmarRhsa { + pub kind: u16, + pub length: u16, + + pub _rsv: u32, + pub base: u64, + pub domain: u32, +} +unsafe impl plain::Plain for DmarRhsa {} +impl DmarRhsa { + pub fn try_new(raw: &[u8]) -> Option { + let bytes = raw.get(..mem::size_of::())?; + + let this = plain::from_bytes(bytes) + .expect("length is already checked, and alignment 1 should suffice (#[repr(packed)])"); + + Some(*this) + } +} + +/// DMAR ACPI Name-space Device Declaration +#[derive(Clone, Copy, Debug)] +#[repr(packed)] +pub struct DmarAnddHeader { + pub kind: u16, + pub length: u16, + + pub _rsv: [u8; 3], + pub acpi_dev: u8, + + // The device scopes come after. +} +unsafe impl plain::Plain for DmarAnddHeader {} + +pub struct DmarAndd(Box<[u8]>); + +impl DmarAndd { + pub fn try_new(raw: &[u8]) -> Option { + if raw.len() < mem::size_of::() { + return None; + } + + Some(Self(raw.into())) + } +} +impl Deref for DmarAndd { + type Target = DmarAnddHeader; + + fn deref(&self) -> &Self::Target { + plain::from_bytes(&self.0[..mem::size_of::()]) + .expect("length already checked, and with #[repr(packed)] alignment should be okay") + } +} +impl fmt::Debug for DmarAndd { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DmarAndd") + .field("header", &*self as &DmarAnddHeader) + // TODO: print out device scopes + .finish() + } +} + +/// DMAR ACPI Name-space Device Declaration +#[derive(Clone, Copy, Debug)] +#[repr(packed)] +pub struct DmarSatcHeader { + pub kind: u16, + pub length: u16, + + pub flags: u8, + pub _rsvd: u8, + pub seg_num: u16, + + // The device scopes come after. +} +unsafe impl plain::Plain for DmarSatcHeader {} + +pub struct DmarSatc(Box<[u8]>); + +impl DmarSatc { + pub fn try_new(raw: &[u8]) -> Option { + if raw.len() < mem::size_of::() { + return None; + } + + Some(Self(raw.into())) + } +} + +impl Deref for DmarSatc { + type Target = DmarSatcHeader; + + fn deref(&self) -> &Self::Target { + plain::from_bytes(&self.0[..mem::size_of::()]) + .expect("length already checked, and with #[repr(packed)] alignment should be okay") + } +} +impl fmt::Debug for DmarSatc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DmarSatc") + .field("header", &*self as &DmarSatcHeader) + // TODO: print out device scopes + .finish() + } +} + +/// The list of different "Remapping Structure Types". +/// +/// Refer to section 8.2 in the VTIO spec (as of revision 3.2). +#[derive(Clone, Copy, Debug, FromPrimitive)] +#[repr(u16)] +pub enum EntryType { + Drhd = 0, + Rmrr = 1, + Atsr = 2, + Rhsa = 3, + Andd = 4, + Satc = 5, +} + +/// DMAR Entries +#[derive(Debug)] +pub enum DmarEntry { + Drhd(DmarDrhd), + Rmrr(DmarRmrr), + Atsr(DmarAtsr), + Rhsa(DmarRhsa), + Andd(DmarAndd), + + // TODO: "SoC Integrated Address Translation Cache Reporting Structure". + Satc(DmarSatc), + + TooShort(EntryType), + Unknown(u16), +} + +struct DmarRawIter<'sdt> { + bytes: &'sdt [u8], +} + +impl<'sdt> Iterator for DmarRawIter<'sdt> { + type Item = (u16, &'sdt [u8]); + + fn next(&mut self) -> Option { + let type_bytes = match self.bytes.get(..2) { + Some(bytes) => bytes, + None => { + if !self.bytes.is_empty() { + log::warn!("DMAR table ended between two entries."); + } + return None; + } + }; + let len_bytes = match self.bytes.get(2..4) { + Some(bytes) => bytes, + None => { + log::warn!("DMAR table ended between two entries."); + return None; + } + }; + let remainder = &self.bytes[4..]; + + let type_bytes = <[u8; 2]>::try_from(type_bytes).expect("expected a 2-byte slice to be convertible to [u8; 2]"); + let len_bytes = <[u8; 2]>::try_from(type_bytes).expect("expected a 2-byte slice to be convertible to [u8; 2]"); + + let ty = u16::from_ne_bytes(type_bytes); + let len = u16::from_ne_bytes(len_bytes); + + let len = usize::try_from(len).expect("expected u16 to fit within usize"); + + if len > remainder.len() { + log::warn!("DMAR remapping structure length was smaller than the remaining length of the table."); + return None; + } + + let (current, residue) = self.bytes.split_at(len); + self.bytes = residue; + + Some((ty, current)) + } +} + +pub struct DmarIter<'sdt>(DmarRawIter<'sdt>); + +impl Iterator for DmarIter<'_> { + type Item = DmarEntry; + fn next(&mut self) -> Option { + let (raw_type, raw) = self.0.next()?; + + // NOTE: If any of these entries look incorrect, we should simply continue the iterator, + // and instead print a warning. + + let entry_type = match EntryType::from_u16(raw_type) { + Some(ty) => ty, + None => { + log::warn!("Encountered invalid entry type {} (length {})", raw_type, raw.len()); + return Some(DmarEntry::Unknown(raw_type)); + } + }; + + let item_opt = match entry_type { + EntryType::Drhd => DmarDrhd::try_new(raw).map(DmarEntry::Drhd), + EntryType::Rmrr => DmarRmrr::try_new(raw).map(DmarEntry::Rmrr), + EntryType::Atsr => DmarAtsr::try_new(raw).map(DmarEntry::Atsr), + EntryType::Rhsa => DmarRhsa::try_new(raw).map(DmarEntry::Rhsa), + EntryType::Andd => DmarAndd::try_new(raw).map(DmarEntry::Andd), + EntryType::Satc => DmarSatc::try_new(raw).map(DmarEntry::Satc), + }; + let item = item_opt.unwrap_or(DmarEntry::TooShort(entry_type)); + + Some(item) + } +}