iommu: AMD-Vi interrupt-remapping structural correction
IVRS parser (acpi.rs): - Parse IVHD type 0x40 (40-byte header, same layout as 0x11) - Parse IVMD types 0x20/0x21/0x22 into IvmdInfo struct - IVMD scope determined by type, exclusion by flags (bit 4 = EXCL_RANGE) - UNITY read/write flags applied independently - Fix IVHD header size: 24 bytes for 0x10, 40 for 0x11/0x40 Interrupt remap table (interrupt.rs): - Remove global 16-byte IRTE assumption - IRT format selected per-unit from IVHD EFR field (GASup bit) - Legacy32 (4-byte) vs GA128 (16-byte) IRTE formats - EFR-driven encode/decode for both formats - Remapped MSI address and direct-APIC message encoding helpers - Restructure: lib crate (lib_core.rs) for host-testable pure logic Build restructure: - Split into lib (acpi, interrupt) and bin (redox-specific) targets - Feature-gate redox-driver-sys behind 'redox' feature - Host-runnable tests: cargo test --lib --no-default-features Kernel: remove unconditional-true iommu_validate_msi_irq gate pcid/msi: add remapped_message_address/data helpers for BDF-aware MSI Self-test: IRTE programming verification on first device use Tests: 23/23 pass (10 acpi + 13 interrupt)
This commit is contained in:
@@ -3,10 +3,22 @@ name = "iommu"
|
||||
version = "0.3.2"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "iommu"
|
||||
path = "src/lib_core.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "iommu"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
default = ["redox"]
|
||||
redox = ["dep:redox-driver-sys", "dep:redox-scheme", "dep:syscall"]
|
||||
|
||||
[dependencies]
|
||||
redox-driver-sys = { version = "0.3", path = "../../../drivers/redox-driver-sys/source" }
|
||||
redox-scheme = { path = "../../../../../local/sources/redox-scheme" }
|
||||
syscall = { path = "../../../../../local/sources/syscall", package = "redox_syscall" }
|
||||
redox-driver-sys = { version = "0.3", path = "../../../drivers/redox-driver-sys/source", optional = true }
|
||||
redox-scheme = { path = "../../../../../local/sources/redox-scheme", optional = true }
|
||||
syscall = { path = "../../../../../local/sources/syscall", package = "redox_syscall", optional = true }
|
||||
log = { version = "0.4", features = ["std"] }
|
||||
|
||||
[patch.crates-io]
|
||||
|
||||
@@ -7,8 +7,10 @@ const IVHD_HEADER_BYTES: usize = 0x18;
|
||||
|
||||
const IVHD_TYPE_10: u8 = 0x10;
|
||||
const IVHD_TYPE_11: u8 = 0x11;
|
||||
const IVHD_TYPE_40: u8 = 0x40;
|
||||
const IVMD_TYPE_20: u8 = 0x20;
|
||||
const IVMD_TYPE_21: u8 = 0x21;
|
||||
const IVMD_TYPE_22: u8 = 0x22;
|
||||
|
||||
const IVHD_ALL: u8 = 0x00;
|
||||
const IVHD_SEL: u8 = 0x01;
|
||||
@@ -149,6 +151,35 @@ pub struct IvrsInfo {
|
||||
pub revision: u8,
|
||||
pub iv_info: u32,
|
||||
pub units: Vec<IommuUnitInfo>,
|
||||
pub ivmd_entries: Vec<IvmdInfo>,
|
||||
}
|
||||
|
||||
/// IVMD (I/O Virtualization Memory Definition) block.
|
||||
///
|
||||
/// Scope is determined by the entry type (0x20=all, 0x21=single, 0x22=range).
|
||||
/// Exclusion semantics come from the flags field (bit 4 = `IVMD_FLAG_EXCL_RANGE`,
|
||||
/// with UNITY read/write bits applied independently).
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct IvmdInfo {
|
||||
pub entry_type: u8,
|
||||
pub flags: u8,
|
||||
pub start_bdf: Bdf,
|
||||
pub end_bdf: Bdf,
|
||||
pub pci_segment_group: u16,
|
||||
}
|
||||
|
||||
impl IvmdInfo {
|
||||
pub const FLAG_UNITY_READ: u8 = 0x01;
|
||||
pub const FLAG_UNITY_WRITE: u8 = 0x02;
|
||||
pub const FLAG_EXCL_RANGE: u8 = 0x10;
|
||||
|
||||
pub fn is_exclusion(&self) -> bool {
|
||||
self.flags & Self::FLAG_EXCL_RANGE != 0
|
||||
}
|
||||
|
||||
pub fn is_unity(&self) -> bool {
|
||||
self.flags & (Self::FLAG_UNITY_READ | Self::FLAG_UNITY_WRITE) != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -228,6 +259,7 @@ pub fn parse_ivrs(bytes: &[u8]) -> Result<IvrsInfo, IvrsError> {
|
||||
let iv_info = read_u32(table, ACPI_HEADER_BYTES).ok_or(IvrsError::TooShort)?;
|
||||
|
||||
let mut units = Vec::new();
|
||||
let mut ivmd_entries = Vec::new();
|
||||
let mut offset = IVRS_HEADER_BYTES;
|
||||
let mut skipped_qemu_padding = false;
|
||||
while offset < table.len() {
|
||||
@@ -260,13 +292,10 @@ pub fn parse_ivrs(bytes: &[u8]) -> Result<IvrsInfo, IvrsError> {
|
||||
}
|
||||
|
||||
let entry = &table[offset..offset + entry_length];
|
||||
if matches!(entry_type, IVHD_TYPE_10 | IVHD_TYPE_11) {
|
||||
if matches!(entry_type, IVHD_TYPE_10 | IVHD_TYPE_11 | IVHD_TYPE_40) {
|
||||
units.push(parse_ivhd(entry, offset)?);
|
||||
}
|
||||
|
||||
if matches!(entry_type, IVMD_TYPE_20 | IVMD_TYPE_21) {
|
||||
offset += entry_length;
|
||||
continue;
|
||||
} else if matches!(entry_type, IVMD_TYPE_20 | IVMD_TYPE_21 | IVMD_TYPE_22) {
|
||||
ivmd_entries.push(parse_ivmd(entry, offset)?);
|
||||
}
|
||||
|
||||
offset += entry_length;
|
||||
@@ -276,11 +305,17 @@ pub fn parse_ivrs(bytes: &[u8]) -> Result<IvrsInfo, IvrsError> {
|
||||
revision,
|
||||
iv_info,
|
||||
units,
|
||||
ivmd_entries,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_ivhd(entry: &[u8], table_offset: usize) -> Result<IommuUnitInfo, IvrsError> {
|
||||
if entry.len() < IVHD_HEADER_BYTES {
|
||||
let header_size = match entry.first().copied() {
|
||||
Some(IVHD_TYPE_10) => 24usize,
|
||||
Some(IVHD_TYPE_11 | IVHD_TYPE_40) => 40,
|
||||
_ => IVHD_HEADER_BYTES,
|
||||
};
|
||||
if entry.len() < header_size {
|
||||
return Err(IvrsError::InvalidIvhdLength {
|
||||
offset: table_offset,
|
||||
length: entry.len(),
|
||||
@@ -288,7 +323,7 @@ fn parse_ivhd(entry: &[u8], table_offset: usize) -> Result<IommuUnitInfo, IvrsEr
|
||||
}
|
||||
|
||||
let mut device_entries = Vec::new();
|
||||
let mut offset = IVHD_HEADER_BYTES;
|
||||
let mut offset = header_size;
|
||||
while offset < entry.len() {
|
||||
let kind = entry[offset];
|
||||
match kind {
|
||||
@@ -399,6 +434,31 @@ fn parse_ivhd(entry: &[u8], table_offset: usize) -> Result<IommuUnitInfo, IvrsEr
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_ivmd(entry: &[u8], table_offset: usize) -> Result<IvmdInfo, IvrsError> {
|
||||
if entry.len() < 24 {
|
||||
return Err(IvrsError::TruncatedEntry {
|
||||
offset: table_offset,
|
||||
});
|
||||
}
|
||||
Ok(IvmdInfo {
|
||||
entry_type: entry[0],
|
||||
flags: entry[1],
|
||||
start_bdf: Bdf(
|
||||
read_u16(entry, 4).ok_or(IvrsError::TruncatedEntry {
|
||||
offset: table_offset + 4,
|
||||
})?,
|
||||
),
|
||||
end_bdf: Bdf(
|
||||
read_u16(entry, 6).ok_or(IvrsError::TruncatedEntry {
|
||||
offset: table_offset + 6,
|
||||
})?,
|
||||
),
|
||||
pci_segment_group: read_u16(entry, 8).ok_or(IvrsError::TruncatedEntry {
|
||||
offset: table_offset + 8,
|
||||
})?,
|
||||
})
|
||||
}
|
||||
|
||||
fn ensure_remaining(
|
||||
entry: &[u8],
|
||||
offset: usize,
|
||||
@@ -490,7 +550,8 @@ mod tests {
|
||||
}
|
||||
|
||||
fn build_ivhd(mmio_base: u64, iommu_bdf: Bdf, entries: &[u8]) -> Vec<u8> {
|
||||
let length = (0x18 + entries.len()) as u16;
|
||||
let header = 40usize; // IVHD type 0x11/0x40 uses 40-byte header
|
||||
let length = (header + entries.len()) as u16;
|
||||
let mut bytes = vec![0u8; length as usize];
|
||||
bytes[0] = 0x11;
|
||||
bytes[1] = 0xA0;
|
||||
@@ -501,7 +562,7 @@ mod tests {
|
||||
bytes[16..18].copy_from_slice(&0u16.to_le_bytes());
|
||||
bytes[18..20].copy_from_slice(&0x01c2u16.to_le_bytes());
|
||||
bytes[20..24].copy_from_slice(&0x00aa_5500u32.to_le_bytes());
|
||||
bytes[24..].copy_from_slice(entries);
|
||||
bytes[header..].copy_from_slice(entries);
|
||||
bytes
|
||||
}
|
||||
|
||||
@@ -571,4 +632,100 @@ mod tests {
|
||||
|
||||
assert!(unit.handles_device(Bdf::new(0x80, 0x1f, 7)));
|
||||
}
|
||||
|
||||
// ── IVHD type 0x40 (40-byte header, same layout as 0x11) ──────────
|
||||
|
||||
fn build_ivhd_type40(mmio_base: u64, iommu_bdf: Bdf, entries: &[u8]) -> Vec<u8> {
|
||||
let length = (40 + entries.len()) as u16;
|
||||
let mut bytes = vec![0u8; length as usize];
|
||||
bytes[0] = 0x40;
|
||||
bytes[1] = 0xA0;
|
||||
bytes[2..4].copy_from_slice(&length.to_le_bytes());
|
||||
bytes[4..6].copy_from_slice(&iommu_bdf.raw().to_le_bytes());
|
||||
bytes[6..8].copy_from_slice(&0x0040u16.to_le_bytes());
|
||||
bytes[8..16].copy_from_slice(&mmio_base.to_le_bytes());
|
||||
bytes[16..18].copy_from_slice(&0u16.to_le_bytes());
|
||||
bytes[18..20].copy_from_slice(&0x01c2u16.to_le_bytes());
|
||||
bytes[20..24].copy_from_slice(&0x0033_cc00u32.to_le_bytes());
|
||||
bytes[40..].copy_from_slice(entries);
|
||||
bytes
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_ivhd_type_0x40() {
|
||||
// Given: IVRS with type 0x40 IVHD block
|
||||
let entries = [0x00, 0x00, 0x00, 0x00];
|
||||
let table = build_ivrs(&[build_ivhd_type40(
|
||||
0xfee0_0000,
|
||||
Bdf::new(0, 0x18, 2),
|
||||
&entries,
|
||||
)]);
|
||||
// When: parsed
|
||||
let parsed = parse_ivrs(&table).unwrap_or_else(|err| panic!("IVRS parse failed: {err}"));
|
||||
// Then: unit has correct MMIO base, EFR, and device entries
|
||||
assert_eq!(parsed.units.len(), 1);
|
||||
assert_eq!(parsed.units[0].entry_type, 0x40);
|
||||
assert_eq!(parsed.units[0].mmio_base, 0xfee0_0000);
|
||||
assert_eq!(parsed.units[0].iommu_efr, 0x0033_cc00);
|
||||
assert_eq!(parsed.units[0].device_entries.len(), 1);
|
||||
assert!(parsed.units[0].handles_device(Bdf::new(0x42, 0x0f, 0)));
|
||||
}
|
||||
|
||||
// ── IVMD parsing ────────────────────────────────────────────────────
|
||||
|
||||
fn build_ivmd(entry_type: u8, flags: u8, start_bdf: Bdf, end_bdf: Bdf, segment: u16) -> Vec<u8> {
|
||||
let mut bytes = vec![0u8; 24];
|
||||
bytes[0] = entry_type;
|
||||
bytes[1] = flags;
|
||||
bytes[2..4].copy_from_slice(&(24u16).to_le_bytes());
|
||||
bytes[4..6].copy_from_slice(&start_bdf.raw().to_le_bytes());
|
||||
bytes[6..8].copy_from_slice(&end_bdf.raw().to_le_bytes());
|
||||
bytes[8..10].copy_from_slice(&segment.to_le_bytes());
|
||||
bytes
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_ivmd_type_0x20_all_devices() {
|
||||
let ivmd = build_ivmd(0x20, 0x10, Bdf::new(0, 1, 0), Bdf::new(0, 3, 7), 0);
|
||||
let ivhd = build_ivhd(0xfee0_0000, Bdf::new(0, 0x18, 2), &[0x00, 0, 0, 0]);
|
||||
let table = build_ivrs(&[ivhd, ivmd]);
|
||||
let parsed = parse_ivrs(&table).unwrap_or_else(|err| panic!("IVRS parse failed: {err}"));
|
||||
assert!(parsed.units.len() >= 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_ivmd_type_0x21_single_device() {
|
||||
let ivmd = build_ivmd(0x21, 0, Bdf::new(0, 5, 0), Bdf::new(0, 5, 0), 0);
|
||||
let ivhd = build_ivhd(0xfee0_0000, Bdf::new(0, 0x18, 2), &[0x00, 0, 0, 0]);
|
||||
let table = build_ivrs(&[ivhd, ivmd]);
|
||||
let parsed = parse_ivrs(&table).unwrap_or_else(|err| panic!("IVRS parse failed: {err}"));
|
||||
assert!(parsed.units.len() >= 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_ivmd_type_0x22_device_range() {
|
||||
let ivmd = build_ivmd(0x22, 0, Bdf::new(0, 4, 0), Bdf::new(0, 6, 7), 0);
|
||||
let ivhd = build_ivhd(0xfee0_0000, Bdf::new(0, 0x18, 2), &[0x00, 0, 0, 0]);
|
||||
let table = build_ivrs(&[ivhd, ivmd]);
|
||||
let parsed = parse_ivrs(&table).unwrap_or_else(|err| panic!("IVRS parse failed: {err}"));
|
||||
assert!(parsed.units.len() >= 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ivmd_exclusion_flag_is_parsed_separately_from_type() {
|
||||
let ivmd = build_ivmd(0x20, 0x11, Bdf::new(0, 1, 0), Bdf::new(0, 1, 7), 0);
|
||||
let ivhd = build_ivhd(0xfee0_0000, Bdf::new(0, 0x18, 2), &[0x00, 0, 0, 0]);
|
||||
let table = build_ivrs(&[ivhd, ivmd]);
|
||||
let parsed = parse_ivrs(&table).unwrap_or_else(|err| panic!("IVRS parse failed: {err}"));
|
||||
assert!(parsed.units.len() >= 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ivmd_type_0x20_not_exclusion_by_default() {
|
||||
let ivmd = build_ivmd(0x20, 0x00, Bdf::new(0, 1, 0), Bdf::new(0, 1, 7), 0);
|
||||
let ivhd = build_ivhd(0xfee0_0000, Bdf::new(0, 0x18, 2), &[0x00, 0, 0, 0]);
|
||||
let table = build_ivrs(&[ivhd, ivmd]);
|
||||
let parsed = parse_ivrs(&table).unwrap_or_else(|err| panic!("IVRS parse failed: {err}"));
|
||||
assert!(parsed.units.len() >= 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use redox_driver_sys::memory::{CacheType, MmioProt, MmioRegion};
|
||||
use crate::acpi::{parse_ivrs, Bdf, IommuUnitInfo, IvrsError};
|
||||
use crate::command_buffer::{CommandBuffer, CommandEntry, EventLog, EventLogEntry};
|
||||
use crate::device_table::{DeviceTable, DeviceTableEntry};
|
||||
use crate::interrupt::InterruptRemapTable;
|
||||
use crate::interrupt::{irt_format_from_efr, InterruptRemapTable};
|
||||
use crate::mmio::{control, ext_feature, offsets, status, AMD_VI_MMIO_BYTES};
|
||||
use crate::page_table::DomainPageTables;
|
||||
|
||||
@@ -98,8 +98,10 @@ impl AmdViUnit {
|
||||
let command_buffer =
|
||||
CommandBuffer::new(DEFAULT_CMD_ENTRIES).map_err(|err| err.to_string())?;
|
||||
let event_log = EventLog::new(DEFAULT_EVT_ENTRIES).map_err(|err| err.to_string())?;
|
||||
let irt_format = irt_format_from_efr(self.info.iommu_efr, self.info.entry_type);
|
||||
let interrupt_table =
|
||||
InterruptRemapTable::new_allocated(DEFAULT_IRT_ENTRIES).map_err(|err| err.to_string())?;
|
||||
InterruptRemapTable::new_allocated(DEFAULT_IRT_ENTRIES, irt_format)
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
self.program_bars(&device_table, &command_buffer, &event_log)?;
|
||||
self.reset_ring_pointers()?;
|
||||
@@ -188,6 +190,22 @@ impl AmdViUnit {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Program a test IRTE (source 0, vector 0x20) into the interrupt remap
|
||||
/// table and return the allocated index. Used by the self-test to verify
|
||||
/// IRT hardware functionality.
|
||||
pub fn program_test_irte(&self) -> Result<usize, String> {
|
||||
let table = self
|
||||
.interrupt_table
|
||||
.as_ref()
|
||||
.ok_or_else(|| "interrupt remap table not initialized".to_string())?;
|
||||
let irte = crate::interrupt::Irte::new(0, 0, 0x20);
|
||||
let index = table
|
||||
.find_free()
|
||||
.ok_or_else(|| "no free IRTE entries".to_string())?;
|
||||
table.program_entry(index, &irte);
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
pub fn drain_events(&mut self) -> Result<Vec<AmdViEvent>, String> {
|
||||
let mut drained = Vec::new();
|
||||
if !self.initialized {
|
||||
@@ -391,9 +409,9 @@ mod tests {
|
||||
use super::AmdViUnit;
|
||||
|
||||
fn build_ivrs_with_unit() -> Vec<u8> {
|
||||
let mut table = vec![0u8; 40 + 28];
|
||||
let mut table = vec![0u8; 40 + 44];
|
||||
table[0..4].copy_from_slice(b"IVRS");
|
||||
table[4..8].copy_from_slice(&(68u32).to_le_bytes());
|
||||
table[4..8].copy_from_slice(&(84u32).to_le_bytes());
|
||||
table[8] = 3;
|
||||
table[10..16].copy_from_slice(b"RDBEAR");
|
||||
table[16..24].copy_from_slice(b"AMDVI ");
|
||||
@@ -401,14 +419,14 @@ mod tests {
|
||||
let offset = 40;
|
||||
table[offset] = 0x11;
|
||||
table[offset + 1] = 0x20;
|
||||
table[offset + 2..offset + 4].copy_from_slice(&(28u16).to_le_bytes());
|
||||
table[offset + 2..offset + 4].copy_from_slice(&(44u16).to_le_bytes());
|
||||
table[offset + 4..offset + 6].copy_from_slice(&Bdf::new(0, 0x18, 2).raw().to_le_bytes());
|
||||
table[offset + 6..offset + 8].copy_from_slice(&0x40u16.to_le_bytes());
|
||||
table[offset + 8..offset + 16].copy_from_slice(&0xfee0_0000u64.to_le_bytes());
|
||||
table[offset + 16..offset + 18].copy_from_slice(&0u16.to_le_bytes());
|
||||
table[offset + 18..offset + 20].copy_from_slice(&0x0081u16.to_le_bytes());
|
||||
table[offset + 20..offset + 24].copy_from_slice(&0u32.to_le_bytes());
|
||||
table[offset + 24..offset + 28].copy_from_slice(&[0x00, 0, 0, 0]);
|
||||
table[offset + 40..offset + 44].copy_from_slice(&[0x00, 0, 0, 0]);
|
||||
|
||||
let checksum =
|
||||
(!table.iter().fold(0u8, |sum, byte| sum.wrapping_add(*byte))).wrapping_add(1);
|
||||
|
||||
@@ -1,11 +1,59 @@
|
||||
use log::info;
|
||||
//! AMD-Vi Interrupt Remapping — IRTE, IRT format selection, remapped message encoding.
|
||||
//!
|
||||
//! The IRT format is selected per IOMMU unit from the IVHD EFR field:
|
||||
//! - Legacy 32-bit (IVHD type 0x10, or EFR GA bit not set) — 4-byte entries.
|
||||
//! - GA 128-bit (IVHD type 0x11/0x40 with EFR GA bit set) — 16-byte entries.
|
||||
//!
|
||||
//! DTE programming, table sizing, and MSI message address encoding all depend on the format.
|
||||
|
||||
#[cfg(feature = "redox")]
|
||||
use redox_driver_sys::dma::DmaBuffer;
|
||||
|
||||
pub const IRTE_SIZE: usize = 16;
|
||||
pub const IRTE_PRESENT: u64 = 1 << 0;
|
||||
const DMA_ALIGNMENT: usize = 4096;
|
||||
/// Interrupt Remapping Table entry format.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum IrtFormat {
|
||||
/// 32-bit IRTE (legacy — 4 bytes). Used for IVHD type 0x10 or when EFR GA is absent.
|
||||
Legacy32,
|
||||
/// Guest-Addressing 128-bit IRTE (16 bytes). Used for IVHD type 0x11/0x40 with EFR GA bit.
|
||||
GA128,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
impl IrtFormat {
|
||||
/// Number of bytes per IRTE.
|
||||
pub const fn entry_size(self) -> usize {
|
||||
match self {
|
||||
Self::Legacy32 => 4,
|
||||
Self::GA128 => 16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// EFR bit 7: Guest-Addressing Supported.
|
||||
const EFR_GASUP: u32 = 1 << 7;
|
||||
|
||||
/// Select the IRT format from an IVHD's EFR field and IVHD type.
|
||||
///
|
||||
/// - IVHD type 0x10: always Legacy32.
|
||||
/// - IVHD type 0x11/0x40: GA128 if EFR bit 7 (GASup) is set, else Legacy32.
|
||||
pub const fn irt_format_from_efr(efr: u32, ivhd_type: u8) -> IrtFormat {
|
||||
match ivhd_type {
|
||||
0x10 => IrtFormat::Legacy32,
|
||||
_ => {
|
||||
if efr & EFR_GASUP != 0 {
|
||||
IrtFormat::GA128
|
||||
} else {
|
||||
IrtFormat::Legacy32
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── IRTE encoding / decoding ───────────────────────────────────────────
|
||||
|
||||
/// An Interrupt Remapping Table Entry (IRTE).
|
||||
///
|
||||
/// Fields are format-independent; `encode` / `decode` dispatch on format.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Irte {
|
||||
pub present: bool,
|
||||
pub source_id: u16,
|
||||
@@ -16,128 +64,402 @@ pub struct Irte {
|
||||
}
|
||||
|
||||
impl Irte {
|
||||
pub fn new(source_id: u16, dest_id: u32, vector: u8) -> Self {
|
||||
Self { present: true, source_id, dest_id, vector, delivery_mode: 0, trigger_mode: 0 }
|
||||
}
|
||||
|
||||
pub fn encode(&self) -> [u64; 2] {
|
||||
let mut low: u64 = IRTE_PRESENT;
|
||||
low |= u64::from(self.vector) & 0xFF;
|
||||
low |= (u64::from(self.delivery_mode) & 0x7) << 8;
|
||||
low |= u64::from(self.dest_id) << 40;
|
||||
let high: u64 = u64::from(self.source_id);
|
||||
[low, high]
|
||||
}
|
||||
|
||||
pub fn decode(low: u64, high: u64) -> Self {
|
||||
pub const fn new(source_id: u16, dest_id: u32, vector: u8) -> Self {
|
||||
Self {
|
||||
present: low & IRTE_PRESENT != 0,
|
||||
vector: (low & 0xFF) as u8,
|
||||
delivery_mode: ((low >> 8) & 0x7) as u8,
|
||||
trigger_mode: ((low >> 15) & 0x1) as u8,
|
||||
dest_id: ((low >> 40) & 0xFFFF_FFFF) as u32,
|
||||
source_id: (high & 0xFFFF) as u16,
|
||||
present: true,
|
||||
source_id,
|
||||
dest_id,
|
||||
vector,
|
||||
delivery_mode: 0,
|
||||
trigger_mode: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode this IRTE into raw dwords in the given format.
|
||||
pub fn encode(&self, format: IrtFormat) -> Vec<u32> {
|
||||
match format {
|
||||
IrtFormat::Legacy32 => {
|
||||
// 32-bit IRTE (AMD IOMMU spec §3.4.1, Table 23)
|
||||
let mut dword: u32 = 0;
|
||||
if self.present {
|
||||
dword |= 1 << 0;
|
||||
}
|
||||
dword |= (u32::from(self.vector) & 0xFF) << 16;
|
||||
dword |= (u32::from(self.dest_id) & 0xFF) << 8;
|
||||
dword |= (u32::from(self.delivery_mode) & 0x7) << 4;
|
||||
vec![dword]
|
||||
}
|
||||
IrtFormat::GA128 => {
|
||||
// 128-bit IRTE (AMD IOMMU spec §3.4.2, Table 25)
|
||||
// DWord 0: RemapEnable, IntType, DM, Destination[12:5], Vector[7:0]
|
||||
let mut dw0: u32 = 0;
|
||||
if self.present {
|
||||
dw0 |= 1 << 0;
|
||||
}
|
||||
dw0 |= (u32::from(self.vector) & 0xFF) << 16;
|
||||
dw0 |= (u32::from(self.dest_id) & 0x1FE0) << (5 - 5); // dest bits 12:5 at bits 12:5
|
||||
dw0 |= (u32::from(self.delivery_mode) & 0x7) << 8;
|
||||
// DWord 1: GuestMode(0), Destination[31:13] (bits 18:0)
|
||||
let mut dw1: u32 = 0;
|
||||
dw1 |= (self.dest_id >> 13) & 0x7_FFFF;
|
||||
// DWord 2: GA Tag[15:0] from source_id (bits 31:16)
|
||||
let dw2: u32 = u32::from(self.source_id) << 16;
|
||||
// DWord 3: GA Address[31:0] (unused)
|
||||
let dw3: u32 = 0;
|
||||
vec![dw0, dw1, dw2, dw3]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode raw dwords back into an IRTE.
|
||||
pub fn decode(dwords: &[u32], format: IrtFormat) -> Self {
|
||||
match format {
|
||||
IrtFormat::Legacy32 => {
|
||||
let dw = dwords.first().copied().unwrap_or(0);
|
||||
Self {
|
||||
present: dw & 1 != 0,
|
||||
vector: ((dw >> 16) & 0xFF) as u8,
|
||||
delivery_mode: ((dw >> 4) & 0x7) as u8,
|
||||
trigger_mode: 0,
|
||||
dest_id: ((dw >> 8) & 0xFF) as u32,
|
||||
source_id: 0,
|
||||
}
|
||||
}
|
||||
IrtFormat::GA128 => {
|
||||
let dw0 = dwords.first().copied().unwrap_or(0);
|
||||
let dw1 = dwords.get(1).copied().unwrap_or(0);
|
||||
let dw2 = dwords.get(2).copied().unwrap_or(0);
|
||||
Self {
|
||||
present: dw0 & 1 != 0,
|
||||
vector: ((dw0 >> 16) & 0xFF) as u8,
|
||||
delivery_mode: ((dw0 >> 8) & 0x7) as u8,
|
||||
trigger_mode: 0,
|
||||
dest_id: (dw0 & 0x1FE0) | ((dw1 & 0x7_FFFF) << 13),
|
||||
source_id: ((dw2 >> 16) & 0xFFFF) as u16,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Remapped MSI message address encoding ──────────────────────────────
|
||||
|
||||
/// Compute the remapped MSI message address for a given IRTE index and format.
|
||||
///
|
||||
/// AMD IOMMU spec §3.4.3: the address encodes the IRTE index and the interrupt
|
||||
/// remapping table pointer so the IOMMU can look up the correct entry.
|
||||
pub const fn remapped_msi_address(irte_index: u16, format: IrtFormat) -> u64 {
|
||||
match format {
|
||||
IrtFormat::Legacy32 => {
|
||||
// Legacy format: address = 0xFEEX_XXXX where bits encode the index.
|
||||
// AMD IOMMU spec: address[31:20] = 0xFEE, address[19:5] = IRTE index.
|
||||
0xFEE0_0000u64 | ((irte_index as u64 & 0x7FFF) << 5)
|
||||
}
|
||||
IrtFormat::GA128 => {
|
||||
// GA 128-bit format: similar encoding, uses 0xFEE0_0000 base.
|
||||
// The IOMMU distinguishes GA vs legacy via DTE.IV field.
|
||||
0xFEE0_0000u64 | ((irte_index as u64 & 0x7FFF) << 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// When the IOMMU is NOT used, direct-APIC message address for a given destination APIC ID.
|
||||
pub const fn direct_apic_address(destination_id: u8) -> u64 {
|
||||
0xFEE0_0000u64 | ((destination_id as u64) << 12)
|
||||
}
|
||||
|
||||
/// Direct-APIC message data encoding (no remapping).
|
||||
pub const fn direct_apic_data(vector: u8, delivery_mode: u8) -> u32 {
|
||||
(vector as u32) | ((delivery_mode as u32 & 0x7) << 8)
|
||||
}
|
||||
|
||||
// ── Interrupt Remap Table (DMA-backed, redox-only) ─────────────────────
|
||||
|
||||
/// In-memory descriptor for a DMA-allocated interrupt remap table.
|
||||
#[cfg(feature = "redox")]
|
||||
pub struct InterruptRemapTable {
|
||||
pub entries: usize,
|
||||
/// RAII holder for the DMA buffer allocated by `new_allocated`.
|
||||
/// The field is never read; the buffer is kept alive so the
|
||||
/// allocation lives as long as the table. Dropping the table
|
||||
/// releases the underlying DMA pages.
|
||||
pub format: IrtFormat,
|
||||
/// RAII holder for the DMA buffer.
|
||||
#[allow(dead_code)]
|
||||
buffer: Option<DmaBuffer>,
|
||||
base: usize,
|
||||
}
|
||||
|
||||
#[cfg(feature = "redox")]
|
||||
impl InterruptRemapTable {
|
||||
pub fn new(base_addr: usize, size: usize) -> Self {
|
||||
Self { base: base_addr, entries: size / IRTE_SIZE, buffer: None }
|
||||
pub fn new(base_addr: usize, size: usize, format: IrtFormat) -> Self {
|
||||
let entry_size = format.entry_size();
|
||||
Self {
|
||||
base: base_addr,
|
||||
entries: if entry_size > 0 { size / entry_size } else { 0 },
|
||||
buffer: None,
|
||||
format,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_allocated(entry_count: usize) -> Result<Self, &'static str> {
|
||||
pub fn new_allocated(
|
||||
entry_count: usize,
|
||||
format: IrtFormat,
|
||||
) -> Result<Self, &'static str> {
|
||||
let entry_size = format.entry_size();
|
||||
let byte_len = entry_count
|
||||
.checked_mul(IRTE_SIZE)
|
||||
.checked_mul(entry_size)
|
||||
.ok_or("IRTE table size overflow")?;
|
||||
let buffer = DmaBuffer::allocate(byte_len, DMA_ALIGNMENT)
|
||||
let buffer = DmaBuffer::allocate(byte_len, 4096)
|
||||
.map_err(|_| "failed to allocate interrupt remap table")?;
|
||||
if !buffer.is_physically_contiguous() {
|
||||
return Err("interrupt remap table allocation is not physically contiguous");
|
||||
}
|
||||
let base = buffer.physical_address();
|
||||
Ok(Self { base, entries: entry_count, buffer: Some(buffer) })
|
||||
Ok(Self {
|
||||
base,
|
||||
entries: entry_count,
|
||||
buffer: Some(buffer),
|
||||
format,
|
||||
})
|
||||
}
|
||||
|
||||
fn addr(&self) -> usize { self.base }
|
||||
fn addr(&self) -> usize {
|
||||
self.base
|
||||
}
|
||||
|
||||
pub fn program_entry(&self, index: usize, irte: &Irte) -> bool {
|
||||
if index >= self.entries { return false; }
|
||||
let e = irte.encode();
|
||||
let off = index * IRTE_SIZE;
|
||||
// SAFETY: caller must verify the safety contract for this operation
|
||||
unsafe {
|
||||
core::ptr::write_volatile((self.addr() + off) as *mut u64, e[0]);
|
||||
core::ptr::write_volatile((self.addr() + off + 8) as *mut u64, e[1]);
|
||||
if index >= self.entries {
|
||||
return false;
|
||||
}
|
||||
info!("IRTE[{}]: src={:04x} dest={:08x} vec={}", index, irte.source_id, irte.dest_id, irte.vector);
|
||||
let dwords = irte.encode(self.format);
|
||||
let entry_size = self.format.entry_size();
|
||||
let off = index * entry_size;
|
||||
for (i, &dw) in dwords.iter().enumerate() {
|
||||
unsafe {
|
||||
core::ptr::write_volatile((self.addr() + off + i * 4) as *mut u32, dw);
|
||||
}
|
||||
}
|
||||
log::info!(
|
||||
"IRTE[{}] fmt={:?} src={:04x} dest={:08x} vec={}",
|
||||
index,
|
||||
self.format,
|
||||
irte.source_id,
|
||||
irte.dest_id,
|
||||
irte.vector
|
||||
);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn invalidate_entry(&self, index: usize) {
|
||||
if index >= self.entries { return; }
|
||||
// SAFETY: caller guarantees pointer is valid, aligned, and live
|
||||
unsafe { core::ptr::write_volatile((self.addr() + index * IRTE_SIZE) as *mut u64, 0u64); }
|
||||
if index >= self.entries {
|
||||
return;
|
||||
}
|
||||
let entry_size = self.format.entry_size();
|
||||
let off = index * entry_size;
|
||||
let dwords = entry_size / 4;
|
||||
for i in 0..dwords {
|
||||
unsafe {
|
||||
core::ptr::write_volatile((self.addr() + off + i * 4) as *mut u32, 0u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_free(&self) -> Option<usize> {
|
||||
for i in 0..self.entries {
|
||||
let off = i * IRTE_SIZE;
|
||||
if unsafe { core::ptr::read_volatile((self.addr() + off) as *const u64) & IRTE_PRESENT == 0 } {
|
||||
let off = i * self.format.entry_size();
|
||||
if unsafe { core::ptr::read_volatile((self.addr() + off) as *const u32) & 1 == 0 } {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn physical_address(&self) -> usize { self.base }
|
||||
pub fn physical_address(&self) -> usize {
|
||||
self.base
|
||||
}
|
||||
|
||||
pub fn len_encoding(&self) -> u64 {
|
||||
let total_size = self.entries * IRTE_SIZE;
|
||||
if total_size == 0 { return 0; }
|
||||
let power = (total_size.next_power_of_two().trailing_zeros() as u64)
|
||||
.saturating_sub(12);
|
||||
let total_size = self.entries * self.format.entry_size();
|
||||
if total_size == 0 {
|
||||
return 0;
|
||||
}
|
||||
let power =
|
||||
(total_size.next_power_of_two().trailing_zeros() as u64).saturating_sub(12);
|
||||
power & 0xF
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IrqRemapManager {
|
||||
pub tables: Vec<InterruptRemapTable>,
|
||||
}
|
||||
// ── Tests ──────────────────────────────────────────────────────────────
|
||||
|
||||
impl IrqRemapManager {
|
||||
pub fn new() -> Self { Self { tables: Vec::new() } }
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
pub fn remap_interrupt(&self, sid: u16, dest: u32, vector: u8) -> Option<usize> {
|
||||
let irte = Irte::new(sid, dest, vector);
|
||||
for table in &self.tables {
|
||||
if let Some(i) = table.find_free() {
|
||||
table.program_entry(i, &irte);
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
None
|
||||
// ── IRT format selection ───────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn irt_format_legacy32_for_ivhd_type_0x10() {
|
||||
// Given: IVHD type 0x10 always uses legacy 32-bit format
|
||||
// When: selecting format from EFR and type
|
||||
// Then: returns Legacy32 regardless of EFR
|
||||
assert_eq!(
|
||||
irt_format_from_efr(0, 0x10),
|
||||
IrtFormat::Legacy32
|
||||
);
|
||||
assert_eq!(
|
||||
irt_format_from_efr(EFR_GASUP, 0x10),
|
||||
IrtFormat::Legacy32
|
||||
);
|
||||
}
|
||||
|
||||
pub fn validate_msi(&self, addr: u64, _data: u32) -> bool {
|
||||
let idx = ((addr >> 5) & 0x7FFF) as usize;
|
||||
for table in &self.tables {
|
||||
if idx < table.entries {
|
||||
let off = idx * IRTE_SIZE;
|
||||
return unsafe { core::ptr::read_volatile((table.addr() + off) as *const u64) & IRTE_PRESENT != 0 };
|
||||
}
|
||||
}
|
||||
false
|
||||
#[test]
|
||||
fn irt_format_ga128_when_efr_ga_supported() {
|
||||
// Given: IVHD type 0x11 with EFR GA bit set
|
||||
// When: selecting format
|
||||
// Then: returns GA128
|
||||
assert_eq!(
|
||||
irt_format_from_efr(EFR_GASUP, 0x11),
|
||||
IrtFormat::GA128
|
||||
);
|
||||
assert_eq!(
|
||||
irt_format_from_efr(EFR_GASUP, 0x40),
|
||||
IrtFormat::GA128
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn irt_format_legacy32_when_efr_ga_absent() {
|
||||
// Given: IVHD type 0x11 but EFR GA bit not set
|
||||
// When: selecting format
|
||||
// Then: returns Legacy32
|
||||
assert_eq!(
|
||||
irt_format_from_efr(0, 0x11),
|
||||
IrtFormat::Legacy32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn irt_format_legacy32_when_efr_zero() {
|
||||
// Given: EFR = 0 (no features)
|
||||
// When: selecting format for type 0x11
|
||||
// Then: Legacy32
|
||||
assert_eq!(
|
||||
irt_format_from_efr(0, 0x11),
|
||||
IrtFormat::Legacy32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn irt_entry_sizes() {
|
||||
assert_eq!(IrtFormat::Legacy32.entry_size(), 4);
|
||||
assert_eq!(IrtFormat::GA128.entry_size(), 16);
|
||||
}
|
||||
|
||||
// ── IRTE encoding / decoding ───────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn irte_legacy32_encode_decode_roundtrip() {
|
||||
// Given: an IRTE with known fields
|
||||
let irte = Irte {
|
||||
present: true,
|
||||
source_id: 0,
|
||||
dest_id: 0x42,
|
||||
vector: 0x30,
|
||||
delivery_mode: 0, // Fixed
|
||||
trigger_mode: 0,
|
||||
};
|
||||
// When: encode to legacy32 then decode back
|
||||
let dwords = irte.encode(IrtFormat::Legacy32);
|
||||
let decoded = Irte::decode(&dwords, IrtFormat::Legacy32);
|
||||
// Then: vector and destination survive roundtrip
|
||||
assert_eq!(decoded.present, true);
|
||||
assert_eq!(decoded.vector, 0x30);
|
||||
assert_eq!(decoded.dest_id, 0x42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn irte_ga128_encode_decode_roundtrip() {
|
||||
// Given: an IRTE with dest_id that fits the GA128 layout (bits 31:5 stored)
|
||||
let irte = Irte {
|
||||
present: true,
|
||||
source_id: 0xABCD,
|
||||
dest_id: 0x1234_5660,
|
||||
vector: 0x55,
|
||||
delivery_mode: 0,
|
||||
trigger_mode: 0,
|
||||
};
|
||||
// When: encode to GA128 then decode back
|
||||
let dwords = irte.encode(IrtFormat::GA128);
|
||||
let decoded = Irte::decode(&dwords, IrtFormat::GA128);
|
||||
// Then: fields survive roundtrip (GA128 stores bits 31:5 of dest_id)
|
||||
assert_eq!(decoded.present, true);
|
||||
assert_eq!(decoded.vector, 0x55);
|
||||
assert_eq!(decoded.dest_id, 0x1234_5660);
|
||||
assert_eq!(decoded.source_id, 0xABCD);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn irte_legacy32_not_present() {
|
||||
// Given: IRTE with present=false
|
||||
let irte = Irte {
|
||||
present: false,
|
||||
source_id: 0,
|
||||
dest_id: 0,
|
||||
vector: 0,
|
||||
delivery_mode: 0,
|
||||
trigger_mode: 0,
|
||||
};
|
||||
// When: encode
|
||||
let dwords = irte.encode(IrtFormat::Legacy32);
|
||||
// Then: first dword bit 0 is 0
|
||||
assert_eq!(dwords[0] & 1, 0);
|
||||
let decoded = Irte::decode(&dwords, IrtFormat::Legacy32);
|
||||
assert!(!decoded.present);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn irte_ga128_not_present() {
|
||||
// Given: GA128 IRTE with present=false
|
||||
let irte = Irte {
|
||||
present: false,
|
||||
source_id: 0,
|
||||
dest_id: 0,
|
||||
vector: 0,
|
||||
delivery_mode: 0,
|
||||
trigger_mode: 0,
|
||||
};
|
||||
let dwords = irte.encode(IrtFormat::GA128);
|
||||
assert_eq!(dwords[0] & 1, 0);
|
||||
let decoded = Irte::decode(&dwords, IrtFormat::GA128);
|
||||
assert!(!decoded.present);
|
||||
}
|
||||
|
||||
// ── MSI message address encoding ───────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn remapped_msi_address_legacy32_encodes_index() {
|
||||
// Given: IRTE index 5 in legacy32 format
|
||||
// When: computing remapped address
|
||||
// Then: address has correct base and index
|
||||
let addr = remapped_msi_address(5, IrtFormat::Legacy32);
|
||||
assert_eq!((addr >> 20) & 0xFFF, 0xFEE);
|
||||
assert_eq!((addr >> 5) & 0x7FFF, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remapped_msi_address_zero_index() {
|
||||
let addr = remapped_msi_address(0, IrtFormat::GA128);
|
||||
assert_eq!((addr >> 5) & 0x7FFF, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_apic_address_encodes_destination() {
|
||||
let addr = direct_apic_address(0xAB);
|
||||
assert_eq!(addr & 0xFFFF_F000, 0xFEE0_0000 | ((0xABu64) << 12));
|
||||
assert_eq!((addr >> 12) & 0xFF, 0xAB);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_apic_data_encodes_vector_and_delivery() {
|
||||
let data = direct_apic_data(0x30, 0); // Fixed, vector 0x30
|
||||
assert_eq!(data & 0xFF, 0x30);
|
||||
assert_eq!((data >> 8) & 0x7, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
//! Core library for the IOMMU daemon — pure logic, host-testable.
|
||||
//!
|
||||
//! This lib crate contains modules that compile without redox-driver-sys
|
||||
//! (no MMIO, no DMA allocation, no scheme IPC). The binary crate links
|
||||
//! these plus the redox-specific modules.
|
||||
|
||||
pub mod acpi;
|
||||
pub mod interrupt;
|
||||
@@ -1,16 +1,32 @@
|
||||
//! IOMMU daemon — provides scheme:iommu for DMA remapping.
|
||||
//! Includes interrupt remapping (IR) via IRTE tables.
|
||||
|
||||
mod interrupt;
|
||||
// Redox-specific modules (depend on redox-driver-sys).
|
||||
#[cfg(feature = "redox")]
|
||||
mod amd_vi;
|
||||
#[cfg(feature = "redox")]
|
||||
mod command_buffer;
|
||||
#[cfg(feature = "redox")]
|
||||
mod device_table;
|
||||
#[cfg(feature = "redox")]
|
||||
mod mmio;
|
||||
#[cfg(feature = "redox")]
|
||||
mod page_table;
|
||||
#[cfg(feature = "redox")]
|
||||
mod scheme_impl;
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
|
||||
use iommu::amd_vi::AmdViUnit;
|
||||
// Re-export pure-logic modules from the lib crate so they are available
|
||||
// at the bin crate root (shared compilation, no duplicate source).
|
||||
pub use iommu::{acpi, interrupt};
|
||||
|
||||
use amd_vi::AmdViUnit;
|
||||
#[cfg(target_os = "redox")]
|
||||
use iommu::IommuScheme;
|
||||
use scheme_impl::IommuScheme;
|
||||
use log::{error, info, LevelFilter, Metadata, Record};
|
||||
#[cfg(target_os = "redox")]
|
||||
use redox_driver_sys::memory::{CacheType, MmioProt, MmioRegion};
|
||||
@@ -399,6 +415,30 @@ fn run_self_test() -> Result<(), String> {
|
||||
println!("units_initialized_after={}", initialized_after);
|
||||
println!("events_drained={}", events_drained);
|
||||
|
||||
// Program a test IRTE on the first initialized unit to verify the
|
||||
// interrupt remap table is functional at the hardware level.
|
||||
let mut irte_allocated = false;
|
||||
for (index, unit) in units.iter().enumerate() {
|
||||
if !unit.initialized() {
|
||||
continue;
|
||||
}
|
||||
match unit.program_test_irte() {
|
||||
Ok(irte_index) => {
|
||||
println!("irte_programmed_unit={}", index);
|
||||
println!("irte_test_index={}", irte_index);
|
||||
irte_allocated = true;
|
||||
}
|
||||
Err(err) => {
|
||||
println!("irte_program_warning_unit_{}={}", index, err);
|
||||
}
|
||||
}
|
||||
break; // one unit is enough for the self-test
|
||||
}
|
||||
if !irte_allocated {
|
||||
println!("irte_programmed_unit=none");
|
||||
println!("irte_test_index=none");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+6
-11
@@ -1,18 +1,11 @@
|
||||
//! AMD-Vi-backed scheme:iommu implementation.
|
||||
|
||||
pub mod acpi;
|
||||
pub mod amd_vi;
|
||||
pub mod command_buffer;
|
||||
pub mod device_table;
|
||||
pub mod interrupt;
|
||||
pub mod mmio;
|
||||
pub mod page_table;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use acpi::{parse_bdf, Bdf};
|
||||
use amd_vi::AmdViUnit;
|
||||
use page_table::{DomainPageTables, MappingFlags};
|
||||
use crate::acpi::{parse_bdf, Bdf};
|
||||
use crate::amd_vi::AmdViUnit;
|
||||
use crate::interrupt::InterruptRemapTable;
|
||||
use crate::page_table::{DomainPageTables, MappingFlags};
|
||||
use redox_scheme::scheme::SchemeSync;
|
||||
use redox_scheme::{CallerCtx, OpenResult};
|
||||
use syscall::data::Stat;
|
||||
@@ -36,6 +29,8 @@ pub mod opcode {
|
||||
pub const ASSIGN_DEVICE: u16 = 0x0020;
|
||||
pub const UNASSIGN_DEVICE: u16 = 0x0021;
|
||||
pub const DRAIN_EVENTS: u16 = 0x0030;
|
||||
pub const ALLOC_IRTE: u16 = 0x0040;
|
||||
pub const FREE_IRTE: u16 = 0x0041;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
Reference in New Issue
Block a user