diff --git a/local/recipes/drivers/redox-driver-core/source/src/modern_technology.rs b/local/recipes/drivers/redox-driver-core/source/src/modern_technology.rs index 9a9f5abe74..d8ba3b953a 100644 --- a/local/recipes/drivers/redox-driver-core/source/src/modern_technology.rs +++ b/local/recipes/drivers/redox-driver-core/source/src/modern_technology.rs @@ -86,6 +86,17 @@ fn hash_bdf(bdf: &str) -> u32 { h } +/// Env-var value for `REDBEAR_DRIVER_IOMMU_GROUP`. Real scheme groups are +/// passed as decimal strings; synthetic groups (no iommu daemon) are +/// reported as `"0"` so drivers do not trust a hash as a real +/// isolation group. +pub fn iommu_group_env_value(bdf: &str) -> String { + match iommu_group_for(bdf) { + IommuGroups::Available(g) => g.group.to_string(), + IommuGroups::Unavailable(_) => "0".to_string(), + } +} + /// MSI-X vector proposal for a child driver. #[derive(Clone, Debug, PartialEq, Eq)] pub struct MsiXProposal { @@ -178,6 +189,17 @@ pub fn numa_node_for(bdf: &str) -> NumaNode { } } +/// Env-var value for `REDBEAR_DRIVER_NUMA_NODE`. Real scheme nodes are +/// passed as decimal strings; synthetic nodes (no numad daemon) are +/// reported as `"0"` so drivers do not trust a hash as a real node id. +pub fn numa_node_env_value(bdf: &str) -> String { + if Path::new(&format!("/scheme/numad/device/{}", bdf)).exists() { + numa_node_for(bdf).node.to_string() + } else { + "0".to_string() + } +} + #[cfg(test)] mod tests { use super::*; @@ -211,4 +233,18 @@ mod tests { let n = numa_node_for("0000:00:00.0"); assert_eq!(n.source, NumaSource::Synthetic); } + + #[test] + fn iommu_group_env_value_is_zero_when_synthetic() { + // With no /scheme/iommu/domain/ on the host test + // environment, the helper must report "0" rather than a fake + // hash, so drivers do not trust a synthetic value as a real + // isolation group. + assert_eq!(iommu_group_env_value("0000:99:99.9"), "0"); + } + + #[test] + fn numa_node_env_value_is_zero_when_synthetic() { + assert_eq!(numa_node_env_value("0000:99:99.9"), "0"); + } } diff --git a/local/recipes/system/driver-manager/source/src/config.rs b/local/recipes/system/driver-manager/source/src/config.rs index f9e9d77dfb..3d4421048b 100644 --- a/local/recipes/system/driver-manager/source/src/config.rs +++ b/local/recipes/system/driver-manager/source/src/config.rs @@ -585,6 +585,26 @@ impl Driver for DriverConfig { } } + let early_quirks = crate::quirks::decide_for_phase( + info, + redox_driver_sys::quirks::QuirkPhase::Early, + ); + if early_quirks.has_flags() { + log::info!( + "early-quirks: driver={} device={} flags={}", + self.name, + device_key, + early_quirks.summary_short() + ); + } + if early_quirks.flags.contains(redox_driver_sys::quirks::PciQuirkFlags::WRONG_CLASS) + || early_quirks + .flags + .contains(redox_driver_sys::quirks::PciQuirkFlags::BROKEN_BRIDGE) + { + return ProbeResult::NotSupported; + } + // Claim the device by opening its pcid channel. The channel is // exclusive: pcid returns ENOLCK to a second opener, so a // concurrent probe of the same device loses atomically. This is @@ -740,13 +760,14 @@ impl Driver for DriverConfig { cmd.env("REDBEAR_DRIVER_DISABLE_ACCEL", "1"); } - let iommu_group = redox_driver_core::modern_technology::iommu_group_for(&info.id.path); cmd.env( "REDBEAR_DRIVER_IOMMU_GROUP", - iommu_group.into_inner().group.to_string(), + redox_driver_core::modern_technology::iommu_group_env_value(&info.id.path), + ); + cmd.env( + "REDBEAR_DRIVER_NUMA_NODE", + redox_driver_core::modern_technology::numa_node_env_value(&info.id.path), ); - let numa_node = redox_driver_core::modern_technology::numa_node_for(&info.id.path); - cmd.env("REDBEAR_DRIVER_NUMA_NODE", numa_node.node.to_string()); let msix = redox_driver_core::modern_technology::propose_msix_vectors(&info.id.path, 1, 16); cmd.env("REDBEAR_DRIVER_MSIX_VECTORS", msix.recommended.to_string()); diff --git a/local/recipes/system/driver-manager/source/src/main.rs b/local/recipes/system/driver-manager/source/src/main.rs index 863f2f8903..61557f6a95 100644 --- a/local/recipes/system/driver-manager/source/src/main.rs +++ b/local/recipes/system/driver-manager/source/src/main.rs @@ -383,6 +383,7 @@ fn main() { )); let scheme_for_events = Arc::clone(&scheme); + let scheme_for_events_aer = Arc::clone(&scheme); let _events_thread = unified_events::spawn_unified_listener( std::path::PathBuf::from("/scheme/pci/aer"), std::path::PathBuf::from("/scheme/pci/pciehp"), @@ -390,7 +391,24 @@ fn main() { move |event| { match event { unified_events::UnifiedEvent::Aer(e) => { - log::info!("AER: event device={} severity={:?}", e.device, e.severity); + let binds = scheme_for_events_aer.bound_device_pairs(); + let action = crate::aer::route_to_driver(&e, &binds); + log::info!( + "AER: device={} severity={:?} action={:?}", + e.device, + e.severity, + action + ); + #[cfg(target_os = "redox")] + if let Some(action_str) = + scheme::DriverManagerScheme::recovery_action_str(action) + { + scheme_for_events_aer.dispatch_recovery(&e.device, action_str); + } + #[cfg(not(target_os = "redox"))] + { + let _ = scheme::DriverManagerScheme::recovery_action_str(action); + } } unified_events::UnifiedEvent::Pciehp(e) => { log::info!("pciehp: event kind={} device={}", e.kind.label(), e.device); diff --git a/local/recipes/system/driver-manager/source/src/quirks.rs b/local/recipes/system/driver-manager/source/src/quirks.rs index 12a810bd1f..f0e21b55ab 100644 --- a/local/recipes/system/driver-manager/source/src/quirks.rs +++ b/local/recipes/system/driver-manager/source/src/quirks.rs @@ -12,7 +12,7 @@ extern crate alloc; use redox_driver_core::device::DeviceInfo; use redox_driver_sys::pci::{PciDeviceInfo, PciLocation}; -use redox_driver_sys::quirks::{lookup_pci_quirks, PciQuirkFlags}; +use redox_driver_sys::quirks::{lookup_pci_quirks, lookup_pci_quirks_for_phase, PciQuirkFlags, QuirkPhase}; /// Convert the core device-info record to the redox-driver-sys quirk-lookup /// input. Fields that core tracks as `Option` (vendor / device / @@ -88,8 +88,16 @@ impl QuirkDecision { /// Compute the manager's spawn-time decision for one device by querying /// `redox-driver-sys`'s curated quirk tables. pub fn decide(info: &DeviceInfo) -> QuirkDecision { + decide_for_phase(info, QuirkPhase::Enable) +} + +pub fn decide_for_phase(info: &DeviceInfo, phase: QuirkPhase) -> QuirkDecision { let input = to_quirks_input(info); - let flags = lookup_pci_quirks(&input); + let flags = if phase == QuirkPhase::Enable { + lookup_pci_quirks(&input) + } else { + lookup_pci_quirks_for_phase(&input, phase) + }; summarize(flags) } diff --git a/local/recipes/system/driver-manager/source/src/scheme.rs b/local/recipes/system/driver-manager/source/src/scheme.rs index b119a161e1..8560dd9917 100644 --- a/local/recipes/system/driver-manager/source/src/scheme.rs +++ b/local/recipes/system/driver-manager/source/src/scheme.rs @@ -14,6 +14,7 @@ use redox_driver_core::manager::DeviceManager; use redox_driver_core::manager::ProbeEvent; #[cfg(target_os = "redox")] use redox_driver_core::driver::ProbeResult; +use redox_driver_core::driver::RecoveryAction; #[cfg(target_os = "redox")] use redox_scheme::scheme::SchemeSync; #[cfg(target_os = "redox")] @@ -351,6 +352,72 @@ impl DriverManagerScheme { self.push_event_line(format!("{event:?}\n")); } + /// Execute a recovery action for a bound device. Called from: + /// - the `/recover` scheme endpoint (operator-initiated), + /// - the unified AER/pciehp listener (auto-dispatch on event). + /// + /// Action vocabulary matches Linux's `pci_ers_result` outcomes: + /// - `reset_device` — remove the device, sleep 100 ms, rebind (FLR-style) + /// - `rescan_bus` — re-enumerate all buses (slot-level recovery) + /// - `disconnect` — remove the device and clear driver-params + /// + /// Unknown actions log an error and are no-ops; AER events with no + /// bound driver are filtered upstream by `route_to_driver`. + #[cfg(target_os = "redox")] + pub fn dispatch_recovery(&self, addr: &str, action: &str) { + let id = DeviceId { + bus: "pci".to_string(), + path: addr.to_string(), + }; + log::warn!("AER recovery: device={} action={}", id.path, action); + match action { + "reset_device" => { + if let Err(err) = self.with_manager(|mgr| mgr.remove_device(&id)) { + log::warn!("AER recovery: remove_device failed: {err:?}"); + return; + } + std::thread::sleep(std::time::Duration::from_millis(100)); + match self.with_manager(|mgr| mgr.bind_device(&id, "")) { + Ok(events) => { + for event in &events { + self.record_probe_event(event); + } + } + Err(err) => log::warn!("AER recovery: rebind failed: {err:?}"), + } + } + "rescan_bus" => match self.with_manager(|mgr| mgr.enumerate()) { + Ok(events) => { + for event in &events { + self.record_probe_event(event); + } + } + Err(err) => log::warn!("AER recovery: enumerate failed: {err:?}"), + }, + "disconnect" => { + if let Err(err) = self.with_manager(|mgr| mgr.remove_device(&id)) { + log::warn!("AER recovery: remove_device failed: {err:?}"); + return; + } + notify_unbind(self, addr); + } + _ => log::error!("AER recovery: unknown action '{}'", action), + } + } + + /// Map a `RecoveryAction` (from `Driver::on_error` / AER routing) + /// to the action vocabulary accepted by `dispatch_recovery`. + /// Returns `None` for actions that don't need an auto-dispatch + /// (e.g. `Handled`, `Fatal` which is operator-only). + pub fn recovery_action_str(action: RecoveryAction) -> Option<&'static str> { + match action { + RecoveryAction::Handled => None, + RecoveryAction::ResetDevice => Some("reset_device"), + RecoveryAction::RescanBus => Some("rescan_bus"), + RecoveryAction::Fatal => None, + } + } + #[cfg(target_os = "redox")] fn write_operator(&self, kind: HandleKind, line: &str) -> Result<()> { match kind { @@ -434,36 +501,7 @@ impl DriverManagerScheme { let mut parts = line.split_whitespace(); let addr = parts.next().ok_or(Error::new(EINVAL))?; let action = parts.next().unwrap_or("reset_device"); - let id = DeviceId { - bus: "pci".to_string(), - path: addr.to_string(), - }; - log::warn!( - "AER recovery: device={} action={}", - id.path, - action - ); - match action { - "reset_device" => { - let _event = self.with_manager(|mgr| mgr.remove_device(&id))?; - thread::sleep(std::time::Duration::from_millis(100)); - let events = self.with_manager(|mgr| mgr.bind_device(&id, ""))?; - for event in &events { - self.record_probe_event(event); - } - } - "rescan_bus" => { - let events = self.with_manager(|mgr| mgr.enumerate())?; - for event in &events { - self.record_probe_event(event); - } - } - "disconnect" => { - let _event = self.with_manager(|mgr| mgr.remove_device(&id))?; - notify_unbind(self, addr); - } - _ => return Err(Error::new(EINVAL)), - } + self.dispatch_recovery(addr, action); Ok(()) } _ => Err(Error::new(EACCES)), @@ -778,6 +816,40 @@ pub fn start_scheme_server(_scheme: Arc) -> std::result::Re #[cfg(test)] mod tests { use super::parse_new_id; + use crate::DriverManagerScheme; + use redox_driver_core::driver::RecoveryAction; + + #[test] + fn recovery_action_handled_is_noop() { + assert_eq!( + DriverManagerScheme::recovery_action_str(RecoveryAction::Handled), + None + ); + } + + #[test] + fn recovery_action_reset_device_maps_to_reset_device_str() { + assert_eq!( + DriverManagerScheme::recovery_action_str(RecoveryAction::ResetDevice), + Some("reset_device") + ); + } + + #[test] + fn recovery_action_rescan_bus_maps_to_rescan_bus_str() { + assert_eq!( + DriverManagerScheme::recovery_action_str(RecoveryAction::RescanBus), + Some("rescan_bus") + ); + } + + #[test] + fn recovery_action_fatal_is_operator_only() { + assert_eq!( + DriverManagerScheme::recovery_action_str(RecoveryAction::Fatal), + None + ); + } #[test] fn new_id_parses_vendor_device() {