Verified Commit c6efa649 authored by 4lDO2's avatar 4lDO2 🖖

Add DMAR parsing.

In the future, this will probably be moved into its own driver, probably
called `intelvtdd` or `intelvfiod`. This is because `acpid` already
provides an interface to read from ACPI tables, which `pcid` has used
for quite long (when that scheme was provided by the kernel). AMD
implements IOMMU as a PCI function, so letting these be separate drivers
would certainly be beneficial.

The same thing might apply for HPET or MADT, which are the only ACPI
tables still parsed in the kernel.
parent 391f7c41
......@@ -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)" = "<none>"
"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)" = "<none>"
"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"
......
......@@ -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"
......
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<Item = &'a Sdt> {
self.tables.iter().filter(move |sdt| sdt.signature == signature)
}
pub fn take_single_sdt(&mut self, signature: [u8; 4]) -> Option<Sdt> {
pub fn take_single_sdt(&self, signature: [u8; 4]) -> Option<Sdt> {
self.find_single_sdt_pos(signature).map(|pos| self.tables[pos].clone())
}
pub fn namespace(&self) -> RwLockReadGuard<'_, Option<BTreeMap<String, AmlValue>>> {
......
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<Self> {
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<u32>,
pub ctrl: Mmio<u32>,
pub data: Mmio<u32>,
pub addr: [Mmio<u32>; 2],
_rsv: [Mmio<u64>; 2],
pub log: Mmio<u64>,
}
#[repr(packed)]
pub struct DrhdProtectedMemory {
pub en: Mmio<u32>,
pub low_base: Mmio<u32>,
pub low_limit: Mmio<u32>,
pub high_base: Mmio<u64>,
pub high_limit: Mmio<u64>,
}
#[repr(packed)]
pub struct DrhdInvalidation {
pub queue_head: Mmio<u64>,
pub queue_tail: Mmio<u64>,
pub queue_addr: Mmio<u64>,
_rsv: Mmio<u32>,
pub cmpl_sts: Mmio<u32>,
pub cmpl_ctrl: Mmio<u32>,
pub cmpl_data: Mmio<u32>,
pub cmpl_addr: [Mmio<u32>; 2],
}
#[repr(packed)]
pub struct DrhdPageRequest {
pub queue_head: Mmio<u64>,
pub queue_tail: Mmio<u64>,
pub queue_addr: Mmio<u64>,
_rsv: Mmio<u32>,
pub sts: Mmio<u32>,
pub ctrl: Mmio<u32>,
pub data: Mmio<u32>,
pub addr: [Mmio<u32>; 2],
}
#[repr(packed)]
pub struct DrhdMtrrVariable {
pub base: Mmio<u64>,
pub mask: Mmio<u64>,
}
#[repr(packed)]
pub struct DrhdMtrr {
pub cap: Mmio<u64>,
pub def_type: Mmio<u64>,
pub fixed: [Mmio<u64>; 11],
pub variable: [DrhdMtrrVariable; 10],
}
#[repr(packed)]
pub struct Drhd {
pub version: Mmio<u32>,
_rsv: Mmio<u32>,
pub cap: Mmio<u64>,
pub ext_cap: Mmio<u64>,
pub gl_cmd: Mmio<u32>,
pub gl_sts: Mmio<u32>,
pub root_table: Mmio<u64>,
pub ctx_cmd: Mmio<u64>,
_rsv1: Mmio<u32>,
pub fault: DrhdFault,
_rsv2: Mmio<u32>,
pub pm: DrhdProtectedMemory,
pub invl: DrhdInvalidation,
_rsv3: Mmio<u64>,
pub intr_table: Mmio<u64>,
pub page_req: DrhdPageRequest,
pub mtrr: DrhdMtrr,
}
//! 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::<DmarStruct>()..]
}
}
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<Dmar> {
assert_eq!(sdt.signature, *b"DMAR", "signature already checked against `DMAR`");
if sdt.length() < mem::size_of::<DmarStruct>() {
log::error!("The DMAR table was too small ({} B < {} B).", sdt.length(), mem::size_of::<Dmar>());
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<Self> {
// TODO: Check ty.
let header_bytes = match raw.get(..mem::size_of::<DeviceScopeHeader>()) {
Some(bytes) => bytes,
None => return None,
};
let header = plain::from_bytes::<DeviceScopeHeader>(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::<DeviceScopeHeader>()..]
}
}
pub struct DmarDrhd(Box<[u8]>);
impl DmarDrhd {
pub fn try_new(raw: &[u8]) -> Option<Self> {
if raw.len() < mem::size_of::<DmarDrhdHeader>() {
return None;
}
Some(Self(raw.into()))
}
pub fn device_scope_area(&self) -> &[u8] {
&self.0[mem::size_of::<DmarDrhdHeader>()..]
}
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::<DmarDrhdHeader>(&self.0[..mem::size_of::<DmarDrhdHeader>()])
.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<Self> {
if raw.len() < mem::size_of::<DmarRmrrHeader>() {
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::<DmarRmrrHeader>()])
.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<Self> {
if raw.len() < mem::size_of::<DmarAtsrHeader>() {
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::<DmarAtsrHeader>()])
.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<Self> {
let bytes = raw.get(..mem::size_of::<DmarRhsa>())?;
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<Self> {
if raw.len() < mem::size_of::<DmarAnddHeader>() {
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::<DmarAnddHeader>()])
.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<Self> {
if raw.len() < mem::size_of::<DmarSatcHeader>() {
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::<DmarSatcHeader>()])
.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)]