Improve IOMMU self-test diagnostics

This commit is contained in:
2026-04-18 21:38:30 +01:00
parent 52ff34b60a
commit ec45c703de
2 changed files with 141 additions and 21 deletions
+138 -21
View File
@@ -22,6 +22,32 @@ struct StderrLogger {
level: LevelFilter, level: LevelFilter,
} }
#[cfg_attr(not(target_os = "redox"), allow(dead_code))]
struct DiscoveryResult {
units: Vec<AmdViUnit>,
source: DiscoverySource,
kernel_acpi_status: &'static str,
ivrs_path: Option<PathBuf>,
}
#[cfg_attr(not(target_os = "redox"), allow(dead_code))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum DiscoverySource {
KernelAcpi,
Filesystem,
None,
}
impl DiscoverySource {
fn as_str(self) -> &'static str {
match self {
Self::KernelAcpi => "kernel_acpi",
Self::Filesystem => "filesystem",
Self::None => "none",
}
}
}
impl log::Log for StderrLogger { impl log::Log for StderrLogger {
fn enabled(&self, metadata: &Metadata) -> bool { fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= self.level metadata.level() <= self.level
@@ -65,17 +91,22 @@ fn discover_ivrs_path() -> Option<PathBuf> {
discover_ivrs_path_from_candidates(&candidate_ivrs_paths()) discover_ivrs_path_from_candidates(&candidate_ivrs_paths())
} }
fn detect_units() -> Result<Vec<AmdViUnit>, String> { fn detect_units_from_ivrs_path(path: &PathBuf) -> Result<Vec<AmdViUnit>, String> {
let Some(path) = discover_ivrs_path() else { let bytes = fs::read(path)
return Ok(Vec::new());
};
let bytes = fs::read(&path)
.map_err(|err| format!("failed to read IVRS table from {}: {err}", path.display()))?; .map_err(|err| format!("failed to read IVRS table from {}: {err}", path.display()))?;
let units = AmdViUnit::detect(&bytes).map_err(|err| format!("failed to parse IVRS: {err}"))?; let units = AmdViUnit::detect(&bytes).map_err(|err| format!("failed to parse IVRS: {err}"))?;
Ok(units) Ok(units)
} }
fn detect_units_from_discovered_ivrs() -> Result<(Vec<AmdViUnit>, Option<PathBuf>), String> {
let Some(path) = discover_ivrs_path() else {
return Ok((Vec::new(), None));
};
let units = detect_units_from_ivrs_path(&path)?;
Ok((units, Some(path)))
}
#[cfg(target_os = "redox")] #[cfg(target_os = "redox")]
const ACPI_HEADER_LEN: usize = 36; const ACPI_HEADER_LEN: usize = 36;
@@ -149,13 +180,81 @@ fn detect_units_from_kernel_acpi() -> Result<Vec<AmdViUnit>, String> {
} }
#[cfg(target_os = "redox")] #[cfg(target_os = "redox")]
fn run() -> Result<(), String> { fn discover_units() -> Result<DiscoveryResult, String> {
let units = detect_units_from_kernel_acpi().or_else(|err| { match detect_units_from_kernel_acpi() {
Ok(units) if !units.is_empty() => Ok(DiscoveryResult {
units,
source: DiscoverySource::KernelAcpi,
kernel_acpi_status: "ok",
ivrs_path: None,
}),
Ok(_units) => {
let (units, ivrs_path) = detect_units_from_discovered_ivrs()?;
Ok(DiscoveryResult {
source: if ivrs_path.is_some() {
DiscoverySource::Filesystem
} else {
DiscoverySource::None
},
units,
kernel_acpi_status: "empty",
ivrs_path,
})
}
Err(err) => {
info!("iommu: kernel ACPI discovery unavailable: {err}"); info!("iommu: kernel ACPI discovery unavailable: {err}");
detect_units() let (units, ivrs_path) = detect_units_from_discovered_ivrs()?;
})?; Ok(DiscoveryResult {
info!("iommu: detected {} AMD-Vi unit(s)", units.len()); source: if ivrs_path.is_some() {
for (index, unit) in units.iter().enumerate() { DiscoverySource::Filesystem
} else {
DiscoverySource::None
},
units,
kernel_acpi_status: "error",
ivrs_path,
})
}
}
}
#[cfg(not(target_os = "redox"))]
fn discover_units() -> Result<DiscoveryResult, String> {
let (units, ivrs_path) = detect_units_from_discovered_ivrs()?;
Ok(DiscoveryResult {
source: if ivrs_path.is_some() {
DiscoverySource::Filesystem
} else {
DiscoverySource::None
},
units,
kernel_acpi_status: "unsupported",
ivrs_path,
})
}
#[cfg(target_os = "redox")]
fn run() -> Result<(), String> {
let discovery = discover_units()?;
if discovery.units.is_empty() {
info!(
"iommu: no AMD-Vi units found (source={}, kernel_acpi_status={}, ivrs_path={})",
discovery.source.as_str(),
discovery.kernel_acpi_status,
discovery
.ivrs_path
.as_ref()
.map(|path| path.display().to_string())
.unwrap_or_else(|| "none".to_string())
);
} else {
info!(
"iommu: detected {} AMD-Vi unit(s) via {}",
discovery.units.len(),
discovery.source.as_str()
);
}
for (index, unit) in discovery.units.iter().enumerate() {
info!( info!(
"iommu: discovered unit {} at MMIO {:#x}; initialization is deferred until first use", "iommu: discovered unit {} at MMIO {:#x}; initialization is deferred until first use",
index, index,
@@ -167,7 +266,7 @@ fn run() -> Result<(), String> {
Socket::create("iommu").map_err(|e| format!("failed to register iommu scheme: {e}"))?; Socket::create("iommu").map_err(|e| format!("failed to register iommu scheme: {e}"))?;
info!("iommu: registered scheme:iommu"); info!("iommu: registered scheme:iommu");
let mut scheme = IommuScheme::with_units(units); let mut scheme = IommuScheme::with_units(discovery.units);
loop { loop {
let request = match socket.next_request(SignalBehavior::Restart) { let request = match socket.next_request(SignalBehavior::Restart) {
@@ -204,11 +303,19 @@ fn run() -> Result<(), String> {
#[cfg(target_os = "redox")] #[cfg(target_os = "redox")]
fn run_self_test() -> Result<(), String> { fn run_self_test() -> Result<(), String> {
let mut units = detect_units_from_kernel_acpi().or_else(|err| { let discovery = discover_units()?;
info!("iommu: kernel ACPI discovery unavailable: {err}"); let mut units = discovery.units;
detect_units()
})?;
println!("discovery_source={}", discovery.source.as_str());
println!("kernel_acpi_status={}", discovery.kernel_acpi_status);
println!(
"ivrs_path={}",
discovery
.ivrs_path
.as_ref()
.map(|path| path.display().to_string())
.unwrap_or_else(|| "none".to_string())
);
println!("units_detected={}", units.len()); println!("units_detected={}", units.len());
if units.is_empty() { if units.is_empty() {
return Err("iommu self-test detected zero AMD-Vi unit(s)".to_string()); return Err("iommu self-test detected zero AMD-Vi unit(s)".to_string());
@@ -254,10 +361,11 @@ fn run_self_test() -> Result<(), String> {
#[cfg(not(target_os = "redox"))] #[cfg(not(target_os = "redox"))]
fn run() -> Result<(), String> { fn run() -> Result<(), String> {
let units = detect_units()?; let discovery = discover_units()?;
info!( info!(
"iommu: host build stub active; parsed {} AMD-Vi unit(s) from discovered IVRS source", "iommu: host build stub active; parsed {} AMD-Vi unit(s) via {}",
units.len() discovery.units.len(),
discovery.source.as_str()
); );
Ok(()) Ok(())
} }
@@ -292,7 +400,9 @@ fn main() {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{candidate_ivrs_paths, discover_ivrs_path_from_candidates}; use super::{
candidate_ivrs_paths, discover_ivrs_path_from_candidates, DiscoverySource,
};
use std::path::PathBuf; use std::path::PathBuf;
#[test] #[test]
@@ -313,4 +423,11 @@ mod tests {
let discovered = discover_ivrs_path_from_candidates(&candidates); let discovered = discover_ivrs_path_from_candidates(&candidates);
assert_eq!(discovered, Some(PathBuf::from("/tmp"))); assert_eq!(discovered, Some(PathBuf::from("/tmp")));
} }
#[test]
fn discovery_source_strings_are_stable() {
assert_eq!(DiscoverySource::KernelAcpi.as_str(), "kernel_acpi");
assert_eq!(DiscoverySource::Filesystem.as_str(), "filesystem");
assert_eq!(DiscoverySource::None.as_str(), "none");
}
} }
@@ -46,6 +46,9 @@ fn run() -> Result<(), String> {
if !stdout.contains("units_detected=") { if !stdout.contains("units_detected=") {
return Err("iommu self-test did not report detected unit count".to_string()); return Err("iommu self-test did not report detected unit count".to_string());
} }
if !stdout.contains("discovery_source=") {
return Err("iommu self-test did not report discovery source".to_string());
}
if !stdout.contains("units_initialized_now=") { if !stdout.contains("units_initialized_now=") {
return Err("iommu self-test did not report initialized unit count".to_string()); return Err("iommu self-test did not report initialized unit count".to_string());
} }