driver-manager: P0 — claim-via-channel collapse, orphan-patch resolution, modern_tech/exec removal
P0-1: Collapse the device claim into pcid's channel open (ENOLCK exclusivity) — the pcid-spawner model. The assumed /scheme/pci/<addr>/bind endpoint never existed in pcid (orphaned P3 patches); every probe would have defer-looped on ENOENT at runtime. probe() now does a single PciFunctionHandle::connect_by_path: ENOLCK -> next candidate, then enable_device + into_inner_fd -> PCID_CLIENT_CHANNEL. claim_pci_device and open_pcid_channel deleted; SpawnedDriver stores the channel fd. P0-2: Resolve orphaned patches per the decision tree: P3-pcid-bind-scheme.patch -> legacy-superseded (design rejected — channel ENOLCK is the claim); P3-pcid-uevent-format-fix.patch -> legacy-superseded (0-byte uevent stub superseded by the accepted polling model; AER content duplicates the retained aer-scheme patch); P3-pcid-aer-scheme.patch retained as the P2-1 producer blueprint. SUPERSEDED.md audit log added. P0-3: Remove advisory theater and suppressed dead code: - modern_tech.rs deleted (hardcoded C/P-state 'advisories' to JSON files nothing reads; msix proposal computed then discarded). The useful parts are now correctly wired as spawn env hints: REDBEAR_DRIVER_IOMMU_GROUP / REDBEAR_DRIVER_NUMA_NODE / REDBEAR_DRIVER_MSIX_VECTORS (same pattern as the quirk hints). - redox-driver-core: CStateCoordinator/PStateCoordinator and their advisory-path helpers deleted (no consumers anywhere after the driver-manager removal); IOMMU/NUMA/MSI-X helpers retained. - exec.rs deleted (dead spawn_driver with #[allow(dead_code)]). 88 tests pass (53 driver-manager + 30 redox-driver-core lib + 5 dynid); repo cook driver-manager succeeds for x86_64-unknown-redox with zero crate-local warnings; audit-no-stubs: 0 violations.
This commit is contained in:
@@ -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/<addr>/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/<addr>/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.
|
||||
@@ -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<CStateEvent>,
|
||||
}
|
||||
|
||||
#[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<PStateEvent>,
|
||||
}
|
||||
|
||||
#[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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<OwnedFd, ProbeResult> {
|
||||
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) => {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
use std::process::Command;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn spawn_driver(command: &[String]) -> Result<std::process::Child, std::io::Error> {
|
||||
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()
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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<CStateCoordinator>,
|
||||
pstate: std::sync::Mutex<PStateCoordinator>,
|
||||
}
|
||||
|
||||
/// 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<ModernTech> = 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user