diff --git a/local/patches/legacy-superseded-2026-07-22/SUPERSEDED.md b/local/patches/legacy-superseded-2026-07-22/SUPERSEDED.md new file mode 100644 index 0000000000..df624bb81c --- /dev/null +++ b/local/patches/legacy-superseded-2026-07-22/SUPERSEDED.md @@ -0,0 +1,43 @@ +# Supersession Audit Log — pcid bind/uevent Patches + +**Date:** 2026-07-22 +**Source:** `local/patches/base/` +**Destination:** `local/patches/legacy-superseded-2026-07-22/base/` +**Trigger:** driver-manager v3.0 major assessment +(`local/docs/evidence/driver-manager/ASSESSMENT-2026-07-22.md`, finding B1/G1) + +Per AGENTS.md § "Orphan-Patch Supersession Decision Tree": both patches +were orphans — their content exists nowhere in the `local/sources/base` +fork (zero matching commits), and because `base` is a `path =` fork +recipe, the cookbook never applied them. driver-manager v1.3–v2.2 was +written against the *assumed* `/scheme/pci//bind` endpoint from +`P3-pcid-bind-scheme.patch` and would have defer-looped forever at +runtime (every open → ENOENT → Deferred). + +## Classification + +### `P3-pcid-bind-scheme.patch` → SUPERSEDED (design rejected) + +Bucket (a) — superseded by a better in-tree mechanism. The patch adds a +separate `/scheme/pci//bind` endpoint with an `EALREADY` claim +map. pcid already provides atomic exclusivity: a second open of a +device's `channel` returns `ENOLCK` +(`local/sources/base/drivers/pcid/src/scheme.rs:396-398`), and +pcid-spawner has used channel-as-claim in production since the +beginning. driver-manager v3.0 (P0-1) collapses its claim into the +channel open, making the patch's endpoint redundant. The duplicate +exclusivity mechanism is rejected per the no-redundant-mechanisms rule. + +### `P3-pcid-uevent-format-fix.patch` → SUPERSEDED (stub + duplication) + +Two parts: (1) a `/scheme/pci/uevent` endpoint whose reads return 0 +bytes — a placeholder, not an event stream; superseded by the accepted +250 ms hotplug enumeration-poll model. A real push model (fevent-driven) +is future work under plan P2 and will not reuse this stub. (2) AER +register content that duplicates `P3-pcid-aer-scheme.patch`, which is +retained. + +### `P3-pcid-aer-scheme.patch` → RETAINED in `local/patches/base/` + +Bucket (c) — genuine MISSING-UPSTREAM gap, kept as the blueprint for +plan P2-1 (per-device AER register files produced by pcid). Not moved. diff --git a/local/patches/base/P3-pcid-bind-scheme.patch b/local/patches/legacy-superseded-2026-07-22/base/P3-pcid-bind-scheme.patch similarity index 100% rename from local/patches/base/P3-pcid-bind-scheme.patch rename to local/patches/legacy-superseded-2026-07-22/base/P3-pcid-bind-scheme.patch diff --git a/local/patches/base/P3-pcid-uevent-format-fix.patch b/local/patches/legacy-superseded-2026-07-22/base/P3-pcid-uevent-format-fix.patch similarity index 100% rename from local/patches/base/P3-pcid-uevent-format-fix.patch rename to local/patches/legacy-superseded-2026-07-22/base/P3-pcid-uevent-format-fix.patch 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 c3028c787f..c76b09b04e 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 @@ -1,10 +1,7 @@ //! Modern-technology-surface helper module for [`DeviceManager`]. //! -//! See migration plan § 5.1 D2.2 + D2.3. Provides concrete (non-stub) +//! See migration plan § 5.1 D2.3. Provides concrete (non-stub) //! implementations of: -//! - C-state and P-state coordination: the manager publishes JSON -//! advisories to `/var/run/driver-manager-{cstate,pstate}.json` so -//! downstream `thermald` and `cpufreqd` can read them. //! - IOMMU group registration: queries `/scheme/iommu/domain/N` and //! assigns each device a group number. //! - MSI-X vector proposal: counts available vectors and proposes an @@ -12,185 +9,14 @@ //! - NUMA node lookup: queries `/scheme/numad` and reports the node id //! for a given BDF. //! -//! These helpers are real implementations, not stubs. If a downstream -//! `cpufreqd` / `thermald` / `iommu` / `numad` is not present at runtime -//! the helper reports a clean error and the manager logs+skips. +//! If the `iommu` / `numad` scheme is not present at runtime the helper +//! reports a clean error or a deterministic synthetic fallback and the +//! caller logs+skips. extern crate alloc; use std::fs; -use std::io::Write; -use std::path::{Path, PathBuf}; - -use crate::device::DeviceId; - -const CSTATE_PATH_DEFAULT: &str = "/var/run/driver-manager-cstate.json"; -const PSTATE_PATH_DEFAULT: &str = "/var/run/driver-manager-pstate.json"; - -/// Where the C-state advisory JSON should be written. The default is -/// `/var/run/driver-manager-cstate.json`; tests override this to a -/// tmpfile path so they don't write to the live system. -pub fn cstate_advisory_path() -> PathBuf { - if let Ok(p) = std::env::var("DRIVER_MANAGER_CSTATE_PATH") { - return PathBuf::from(p); - } - PathBuf::from(CSTATE_PATH_DEFAULT) -} - -/// Where the P-state advisory JSON should be written. -pub fn pstate_advisory_path() -> PathBuf { - if let Ok(p) = std::env::var("DRIVER_MANAGER_PSTATE_PATH") { - return PathBuf::from(p); - } - PathBuf::from(PSTATE_PATH_DEFAULT) -} - -/// C-state coordinator. Records "C-state go-deeper" events (e.g. when a -/// device is unbound, the manager may want to nudge thermald toward a -/// deeper CPU idle state). -pub struct CStateCoordinator { - path: PathBuf, - history: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct CStateEvent { - pub device: DeviceId, - /// Suggested new C-state level (`0` = C0 active, `1` = C1 halt, etc.). - pub suggested_level: u8, - /// Optional reason (free-form short string). - pub reason: String, -} - -impl CStateCoordinator { - pub fn new(path: PathBuf) -> Self { - Self { - path, - history: Vec::new(), - } - } - - pub fn default_at(path: PathBuf) -> Self { - Self::new(path) - } - - /// Record a C-state event and flush the advisory to disk. - pub fn record(&mut self, event: CStateEvent) -> Result<(), String> { - self.history.push(event.clone()); - self.write_advisory() - } - - /// Write the cumulative advisory to disk. - pub fn write_advisory(&self) -> Result<(), String> { - let mut s = String::new(); - s.push_str("{\"events\":["); - for (i, e) in self.history.iter().enumerate() { - if i > 0 { - s.push(','); - } - s.push_str(&format!( - "{{\"device\":\"{}\",\"level\":{},\"reason\":\"{}\"}}", - escape_json(&e.device.path), - e.suggested_level, - escape_json(&e.reason), - )); - } - s.push_str("]}"); - write_atomic(&self.path, s.as_bytes()) - .map_err(|e| format!("cstate write failed: {}", e)) - } - - pub fn event_count(&self) -> usize { - self.history.len() - } -} - -/// P-state coordinator. Records "P-state go-faster" or "P-state -/// go-slower" events for downstream `cpufreqd`. -pub struct PStateCoordinator { - path: PathBuf, - history: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct PStateEvent { - pub device: DeviceId, - /// Suggested P-state index (`0` = lowest performance, `n-1` = max). - pub suggested_state: u8, - pub reason: String, -} - -impl PStateCoordinator { - pub fn new(path: PathBuf) -> Self { - Self { - path, - history: Vec::new(), - } - } - - pub fn default_at(path: PathBuf) -> Self { - Self::new(path) - } - - pub fn record(&mut self, event: PStateEvent) -> Result<(), String> { - self.history.push(event.clone()); - self.write_advisory() - } - - pub fn write_advisory(&self) -> Result<(), String> { - let mut s = String::new(); - s.push_str("{\"events\":["); - for (i, e) in self.history.iter().enumerate() { - if i > 0 { - s.push(','); - } - s.push_str(&format!( - "{{\"device\":\"{}\",\"state\":{},\"reason\":\"{}\"}}", - escape_json(&e.device.path), - e.suggested_state, - escape_json(&e.reason), - )); - } - s.push_str("]}"); - write_atomic(&self.path, s.as_bytes()) - .map_err(|e| format!("pstate write failed: {}", e)) - } - - pub fn event_count(&self) -> usize { - self.history.len() - } -} - -fn escape_json(s: &str) -> String { - let mut out = String::with_capacity(s.len()); - for ch in s.chars() { - match ch { - '"' => out.push_str("\\\""), - '\\' => out.push_str("\\\\"), - '\n' => out.push_str("\\n"), - '\r' => out.push_str("\\r"), - '\t' => out.push_str("\\t"), - c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)), - c => out.push(c), - } - } - out -} - -fn write_atomic(path: &Path, bytes: &[u8]) -> std::io::Result<()> { - if let Some(parent) = path.parent() { - if !parent.as_os_str().is_empty() { - fs::create_dir_all(parent)?; - } - } - let tmp = path.with_extension("tmp"); - { - let mut f = fs::File::create(&tmp)?; - f.write_all(bytes)?; - f.sync_data()?; - } - fs::rename(&tmp, path) -} +use std::path::Path; /// IOMMU group for a PCI device. Reads `/scheme/iommu/domain/N` files /// and returns the assigned group number. If the iommu daemon is not @@ -339,59 +165,6 @@ pub fn numa_node_for(bdf: &str) -> NumaNode { #[cfg(test)] mod tests { use super::*; - use std::env; - use std::sync::Mutex; - - static ENV_LOCK: Mutex<()> = Mutex::new(()); - - fn temp_path(label: &str) -> PathBuf { - let pid = std::process::id(); - let nanos = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_nanos(); - std::env::temp_dir().join(format!("rb-dm-{}-{}-{}", label, pid, nanos)) - } - - #[test] - fn cstate_coordinator_writes_valid_json() { - let p = temp_path("cstate.json"); - let mut coord = CStateCoordinator::new(p.clone()); - let dev = DeviceId { - bus: "pci".to_string(), - path: "0000:00:1f.2".to_string(), - }; - coord - .record(CStateEvent { - device: dev, - suggested_level: 3, - reason: "storage unbound".to_string(), - }) - .unwrap(); - let body = fs::read_to_string(&p).unwrap(); - assert!(body.contains("\"level\":3")); - assert!(body.contains("storage unbound")); - assert_eq!(coord.event_count(), 1); - } - - #[test] - fn pstate_coordinator_writes_valid_json() { - let p = temp_path("pstate.json"); - let mut coord = PStateCoordinator::new(p.clone()); - let dev = DeviceId { - bus: "pci".to_string(), - path: "0000:01:00.0".to_string(), - }; - coord - .record(PStateEvent { - device: dev, - suggested_state: 7, - reason: "GPU bind".to_string(), - }) - .unwrap(); - let body = fs::read_to_string(&p).unwrap(); - assert!(body.contains("\"state\":7")); - } #[test] fn iommu_group_returns_synthetic_when_scheme_missing() { @@ -422,12 +195,4 @@ mod tests { let n = numa_node_for("0000:00:00.0"); assert_eq!(n.source, NumaSource::Synthetic); } - - #[test] - fn escape_json_handles_quotes_and_backslashes() { - let s = escape_json("a\"b\\c\n"); - assert!(s.contains("\\\"")); - assert!(s.contains("\\\\")); - assert!(s.contains("\\n")); - } } diff --git a/local/recipes/system/driver-manager/source/src/config.rs b/local/recipes/system/driver-manager/source/src/config.rs index 6044bf2c69..178500136f 100644 --- a/local/recipes/system/driver-manager/source/src/config.rs +++ b/local/recipes/system/driver-manager/source/src/config.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::fs::{self, File, OpenOptions}; +use std::fs; use std::os::fd::{AsRawFd, FromRawFd, OwnedFd}; use std::path::Path; use std::process::Command; @@ -25,7 +25,7 @@ const POLL_INTERVAL_MS: u64 = 50; #[derive(Debug)] struct SpawnedDriver { pid: u32, - bind_handle: File, + channel_fd: OwnedFd, } #[derive(Debug)] @@ -518,41 +518,6 @@ fn pci_device_path(info: &DeviceInfo) -> String { } } -fn claim_pci_device(info: &DeviceInfo) -> Result<(String, File), ProbeResult> { - let device_path = pci_device_path(info); - let bind_path = format!("{}/bind", device_path); - - match OpenOptions::new().read(true).write(true).open(&bind_path) { - Ok(bind_handle) => Ok((device_path, bind_handle)), - Err(err) => match err.raw_os_error() { - Some(code) if code == syscall::EALREADY as i32 || code == 114 => { - log::debug!("device {} already claimed via {}", info.id.path, bind_path); - Err(ProbeResult::NotSupported) - } - _ => Err(ProbeResult::Deferred { - reason: format!("bind {} failed: {}", bind_path, err), - }), - }, - } -} - -fn open_pcid_channel(device_path: &str) -> Result { - let mut handle = match PciFunctionHandle::connect_by_path(Path::new(device_path)) { - Ok(handle) => handle, - Err(err) => { - return Err(ProbeResult::Deferred { - reason: format!("open channel for {} failed: {}", device_path, err), - }); - } - }; - - handle.enable_device(); - - let channel_fd = handle.into_inner_fd(); - let channel_fd = unsafe { OwnedFd::from_raw_fd(channel_fd) }; - Ok(channel_fd) -} - fn check_scheme_available(name: &str) -> bool { if std::path::Path::new(&format!("/scheme/{}", name)).exists() { return true; @@ -588,13 +553,23 @@ impl Driver for DriverConfig { } } - // Claim the device BEFORE checking exclusive_with. The claim is - // atomic (pcid's bind is exclusive) so a concurrent probe of the - // same device is handled correctly by pcid's lock. Once we own - // the bind handle, we check exclusive_with before spawning. - let (device_path, bind_handle) = match claim_pci_device(info) { - Ok(claimed) => claimed, - Err(result) => return result, + // 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 + // the same model pcid-spawner uses — the channel fd doubles as + // the claim and is later handed to the spawned child. + let device_path = pci_device_path(info); + let mut handle = match PciFunctionHandle::connect_by_path(Path::new(&device_path)) { + Ok(handle) => handle, + Err(err) => { + if err.raw_os_error() == Some(syscall::ENOLCK as i32) { + log::debug!("device {} already claimed (ENOLCK)", device_key); + return ProbeResult::NotSupported; + } + return ProbeResult::Deferred { + reason: format!("channel open for {} failed: {}", device_path, err), + }; + } }; // Mutual exclusion: if this driver is exclusive_with another driver @@ -703,10 +678,9 @@ impl Driver for DriverConfig { log::info!("probing {} with driver {}", device_key, self.name); - let channel_fd = match open_pcid_channel(&device_path) { - Ok(channel_fd) => channel_fd, - Err(result) => return result, - }; + handle.enable_device(); + let channel_fd = handle.into_inner_fd(); + let channel_fd = unsafe { OwnedFd::from_raw_fd(channel_fd) }; let mut cmd = Command::new(&actual_path); for arg in &self.command[1..] { @@ -734,6 +708,16 @@ 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(), + ); + 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()); + match cmd.spawn() { Ok(child) => { let pid = child.id(); @@ -744,20 +728,10 @@ impl Driver for DriverConfig { device_key ); let mut spawned = self.spawned.lock().unwrap_or_else(|e| e.into_inner()); - spawned.insert(device_key.clone(), SpawnedDriver { pid, bind_handle }); + spawned.insert(device_key.clone(), SpawnedDriver { pid, channel_fd }); if let Ok(mut p2d) = self.pid_to_device.lock() { p2d.insert(pid, device_key.clone()); } - if let Some(mt) = crate::modern_tech::modern_tech_for_path(&device_key) { - mt.on_bind(&info.id, "spawned"); - let iommu_grp = mt.iommu_group(&info.id); - let numa_node = mt.numa_node(&info.id); - let msix_count = mt.msix_proposal(&info.id, 1, 16); - log::info!( - "spawn-modern-tech: device={} iommu_group={} numa_node={} msix_recommended={}", - device_key, iommu_grp, numa_node, msix_count - ); - } ProbeResult::Bound } Err(e) => ProbeResult::Fatal { @@ -783,13 +757,13 @@ impl Driver for DriverConfig { match binding { Some(binding) => { - let bind_fd = binding.bind_handle.as_raw_fd(); + let channel_fd = binding.channel_fd.as_raw_fd(); log::info!( - "unbind-request: device {} from driver {} (pid {}, bind fd {})", + "unbind-request: device {} from driver {} (pid {}, channel fd {})", device_key, self.name, binding.pid, - bind_fd + channel_fd ); match signal_then_collect(binding.pid, Duration::from_millis(GRACE_PERIOD_MS)) { Ok(()) => { @@ -799,9 +773,6 @@ impl Driver for DriverConfig { self.name, binding.pid ); - if let Some(mt) = crate::modern_tech::modern_tech_for_path(&device_key) { - mt.on_unbind(&info.id, "exited cleanly"); - } Ok(()) } Err(why) => { diff --git a/local/recipes/system/driver-manager/source/src/exec.rs b/local/recipes/system/driver-manager/source/src/exec.rs deleted file mode 100644 index b5e9960b2c..0000000000 --- a/local/recipes/system/driver-manager/source/src/exec.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::process::Command; - -#[allow(dead_code)] -pub fn spawn_driver(command: &[String]) -> Result { - if command.is_empty() { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "empty command", - )); - } - - let mut cmd = Command::new(&command[0]); - for arg in &command[1..] { - cmd.arg(arg); - } - - cmd.spawn() -} diff --git a/local/recipes/system/driver-manager/source/src/main.rs b/local/recipes/system/driver-manager/source/src/main.rs index d6b9225f82..13bf5890c8 100644 --- a/local/recipes/system/driver-manager/source/src/main.rs +++ b/local/recipes/system/driver-manager/source/src/main.rs @@ -2,13 +2,11 @@ mod aer; mod config; #[cfg(test)] mod end_to_end_test; -mod exec; mod heartbeat; mod hotplug; #[cfg(test)] mod linux_loader; mod modalias; -mod modern_tech; mod pciehp; mod policy; mod quirks; @@ -300,8 +298,6 @@ fn main() { let heartbeat_handle = heartbeat.handle(); let _heartbeat_thread = heartbeat.spawn(); - crate::modern_tech::init_modern_tech(); - let policy_dir_str = policy_dir.to_string(); let policy_path = std::path::PathBuf::from(&policy_dir_str); let shared_blacklist = std::sync::Arc::new( diff --git a/local/recipes/system/driver-manager/source/src/modern_tech.rs b/local/recipes/system/driver-manager/source/src/modern_tech.rs deleted file mode 100644 index 881a1b279a..0000000000 --- a/local/recipes/system/driver-manager/source/src/modern_tech.rs +++ /dev/null @@ -1,166 +0,0 @@ -//! Modern-technology wiring for driver-manager. Each bind / unbind -//! event triggers C-state and P-state advisories (JSON to -//! `/var/run/driver-manager-{cstate,pstate}.json`), plus IOMMU group -//! assignment and NUMA node lookup for the device. These are real -//! implementations, not stubs; the downstream `cpufreqd` and `thermald` -//! daemons can read the JSON when they grow the hook. -//! -//! See `redox-driver-core::modern_technology` for the helpers. - -use redox_driver_core::device::DeviceId; -use redox_driver_core::modern_technology::{ - cstate_advisory_path, iommu_group_for, numa_node_for, propose_msix_vectors, - pstate_advisory_path, CStateCoordinator, CStateEvent, PStateCoordinator, PStateEvent, -}; - -/// One coordinator per process. The `Mutex<...>` serialises access so -/// that hotplug + cold-bind can both write without conflicting on the -/// underlying file. -pub struct ModernTech { - cstate: std::sync::Mutex, - pstate: std::sync::Mutex, -} - -/// Process-wide `ModernTech` instance. Initialized in `main()` and -/// consulted by `config::probe()` and `config::remove()` on bind/unbind. -/// A `None` value (e.g. in tests that never call `init`) is treated as -/// no-op (matches the behaviour of the missing-scheme path in -/// `redox-driver-core::modern_technology`). -pub static MODERN_TECH: std::sync::OnceLock = std::sync::OnceLock::new(); - -pub fn init_modern_tech() { - let _ = MODERN_TECH.set(ModernTech::new()); -} - -pub fn modern_tech_for_path(device_key: &str) -> Option<&'static ModernTech> { - let _ = device_key; - MODERN_TECH.get() -} - -impl ModernTech { - pub fn new() -> Self { - Self { - cstate: std::sync::Mutex::new(CStateCoordinator::new(cstate_advisory_path())), - pstate: std::sync::Mutex::new(PStateCoordinator::new(pstate_advisory_path())), - } - } - - /// Called when a device binds. Emits a P-state advisory nudging - /// cpufreqd toward a higher state (the bound driver may need more - /// bandwidth) and a C-state advisory that says "if nothing else - /// happens, we can idle". - pub fn on_bind(&self, device: &DeviceId, reason: &str) { - if device.bus != "pci" { - return; - } - if let Ok(mut coord) = self.pstate.lock() { - let _ = coord.record(PStateEvent { - device: device.clone(), - suggested_state: 7, - reason: reason.to_string(), - }); - } - if let Ok(mut coord) = self.cstate.lock() { - let _ = coord.record(CStateEvent { - device: device.clone(), - suggested_level: 1, - reason: format!("bound: {}", reason), - }); - } - } - - /// Called when a device unbinds. Emits a C-state advisory saying - /// "we can drop to a deeper idle" and a P-state advisory that - /// says "we can lower performance". - pub fn on_unbind(&self, device: &DeviceId, reason: &str) { - if device.bus != "pci" { - return; - } - if let Ok(mut coord) = self.cstate.lock() { - let _ = coord.record(CStateEvent { - device: device.clone(), - suggested_level: 3, - reason: format!("unbound: {}", reason), - }); - } - if let Ok(mut coord) = self.pstate.lock() { - let _ = coord.record(PStateEvent { - device: device.clone(), - suggested_state: 4, - reason: format!("unbound: {}", reason), - }); - } - } - - /// Returns IOMMU group + source for the device. Logs the result - /// and returns the group number; if the group is synthetic, we log - /// a warning so operators can see when iommu scheme is missing. - pub fn iommu_group(&self, device: &DeviceId) -> u32 { - if device.bus != "pci" { - return 0; - } - let g = iommu_group_for(&device.path); - g.into_inner().group - } - - /// Returns the NUMA node id for the device. - pub fn numa_node(&self, device: &DeviceId) -> u32 { - if device.bus != "pci" { - return 0; - } - numa_node_for(&device.path).node - } - - /// Propose an MSI-X vector count for the device, with the same - /// default range the kernel would use. - pub fn msix_proposal(&self, device: &DeviceId, min: u16, max: u16) -> u16 { - if device.bus != "pci" { - return min; - } - propose_msix_vectors(&device.path, min, max).recommended - } -} - -impl Default for ModernTech { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn devinfo(bus: &str, path: &str) -> DeviceId { - DeviceId { - bus: bus.to_string(), - path: path.to_string(), - } - } - - #[test] - fn on_bind_skips_non_pci() { - let m = ModernTech::new(); - let usb = devinfo("usb", "1-1"); - m.on_bind(&usb, "test"); - m.on_unbind(&usb, "test"); - assert_eq!(m.iommu_group(&usb), 0); - assert_eq!(m.numa_node(&usb), 0); - } - - #[test] - fn on_bind_emits_advisories_for_pci() { - let m = ModernTech::new(); - let p = devinfo("pci", "0000:00:1f.2"); - m.on_bind(&p, "first-bind"); - m.on_unbind(&p, "test-unbind"); - let _ = m.iommu_group(&p); - let _ = m.numa_node(&p); - let _ = m.msix_proposal(&p, 4, 16); - } - - #[test] - fn default_constructor_works() { - let _ = ModernTech::default(); - } -}