driver-manager: QEMU gate passed — thread::scope hang fix, initfs scheme skip, graphics split
Runtime validation of the cutover in QEMU (q35, e1000 + AHCI): - thread::scope's park/unpark path hangs on Redox (scoped worker completed all work but the scope join never returned — the boot stalled inside every concurrent enumeration). The concurrent probe pool now uses plain thread::spawn + JoinHandle::join (all captures were already owned/Arc); the counting semaphore is Arc-based so guards are 'static. bound map is Mutex (RwLock write was unproven on target). - initfs driver-manager no longer registers scheme:driver-manager — the transient initfs manager's registration survived into the rootfs phase and made the resident manager's registration fail EEXIST (which then exit(1)'d the rootfs manager). - config: matchless [[driver]] entries now parse (serde default) — 70-usb-class.toml's USB-class drivers (no [[driver.match]]) broke config loading entirely on the first gate. Includes a regression test for load_all with matchless entries. - graphics: 30-graphics.toml moves from the shared redbear-device-services.toml to redbear-full.toml (redox-drm is a full-only driver; mini no longer defers it every hotplug cycle). vesad removed from drivers.d — it is an init-managed service, not a spawnable driver. Gate evidence: initfs 'bound: 0000--00--1f.2 -> ahcid', switchroot, rootfs 'bound: 0000--00--02.0 -> e1000d' with ZERO deferred, scheme:driver-manager registered, resident hotplug loop (250ms), pcid-spawner dormant on both phases.
This commit is contained in:
@@ -479,27 +479,6 @@ data = """
|
||||
pattern = "i915/adlp_dmc_ver2_16.bin"
|
||||
chain = ["i915/adlp_dmc_ver2_14.bin", "i915/adlp_dmc_ver2_12.bin"]
|
||||
"""
|
||||
[[files]]
|
||||
path = "/lib/drivers.d/30-graphics.toml"
|
||||
data = """
|
||||
[[driver]]
|
||||
name = "vesad"
|
||||
description = "VESA BIOS display driver"
|
||||
priority = 60
|
||||
command = ["/usr/lib/drivers/vesad"]
|
||||
|
||||
[[driver.match]]
|
||||
class = 0x03
|
||||
|
||||
[[driver]]
|
||||
name = "redox-drm"
|
||||
description = "DRM/KMS display driver (AMD + Intel)"
|
||||
priority = 60
|
||||
command = ["/usr/bin/redox-drm"]
|
||||
|
||||
[[driver.match]]
|
||||
class = 0x03
|
||||
"""
|
||||
|
||||
# Firmware fallback chain configs
|
||||
[[files]]
|
||||
|
||||
@@ -587,6 +587,51 @@ members = ["greeter"]
|
||||
gid = 100
|
||||
members = ["messagebus"]
|
||||
|
||||
[[files]]
|
||||
path = "/lib/drivers.d/30-graphics.toml"
|
||||
data = """
|
||||
[[driver]]
|
||||
name = "redox-drm"
|
||||
description = "DRM/KMS display driver (AMD + Intel + VirtIO)"
|
||||
priority = 60
|
||||
command = ["/usr/bin/redox-drm"]
|
||||
|
||||
[[driver.match]]
|
||||
class = 0x03
|
||||
|
||||
[[driver]]
|
||||
name = "virtio-gpud"
|
||||
description = "VirtIO GPU driver"
|
||||
priority = 60
|
||||
command = ["/usr/lib/drivers/virtio-gpud"]
|
||||
|
||||
[[driver.match]]
|
||||
vendor = 0x1AF4
|
||||
class = 0x03
|
||||
|
||||
[[driver]]
|
||||
name = "redox-drm"
|
||||
description = "Intel GPU display driver"
|
||||
priority = 61
|
||||
command = ["/usr/bin/redox-drm"]
|
||||
|
||||
[[driver.match]]
|
||||
vendor = 0x8086
|
||||
class = 0x03
|
||||
subclass = 0x00
|
||||
|
||||
[[driver]]
|
||||
name = "redox-drm"
|
||||
description = "AMD GPU display driver"
|
||||
priority = 61
|
||||
command = ["/usr/bin/redox-drm"]
|
||||
|
||||
[[driver.match]]
|
||||
vendor = 0x1002
|
||||
class = 0x03
|
||||
subclass = 0x00
|
||||
"""
|
||||
|
||||
[[files]]
|
||||
path = "/etc/pcid.d/ihdgd.toml"
|
||||
data = """
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
# Graphics and display drivers
|
||||
|
||||
[[driver]]
|
||||
name = "vesad"
|
||||
description = "VESA BIOS display driver"
|
||||
priority = 60
|
||||
command = ["/usr/lib/drivers/vesad"]
|
||||
|
||||
[[driver.match]]
|
||||
class = 0x03
|
||||
# vesad runs as an init-managed service from initfs (framebuffer console) and
|
||||
# has no rootfs binary, so it is not spawnable here and must not be listed.
|
||||
|
||||
[[driver]]
|
||||
name = "redox-drm"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use std::sync::{mpsc, Arc, Condvar, Mutex, RwLock};
|
||||
use std::sync::{mpsc, Arc, Condvar, Mutex};
|
||||
use std::thread;
|
||||
|
||||
use crate::device::{BoundDevice, DeviceId, DeviceInfo};
|
||||
@@ -31,21 +31,21 @@ impl CountingSemaphore {
|
||||
}
|
||||
}
|
||||
|
||||
fn acquire(&self) -> SemaphoreGuard<'_> {
|
||||
fn acquire(self: &Arc<Self>) -> SemaphoreGuard {
|
||||
let mut count = self.inner.lock().unwrap_or_else(|e| e.into_inner());
|
||||
while *count == 0 {
|
||||
count = self.cvar.wait(count).unwrap_or_else(|e| e.into_inner());
|
||||
}
|
||||
*count -= 1;
|
||||
SemaphoreGuard { sem: self }
|
||||
SemaphoreGuard { sem: self.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
struct SemaphoreGuard<'a> {
|
||||
sem: &'a CountingSemaphore,
|
||||
struct SemaphoreGuard {
|
||||
sem: Arc<CountingSemaphore>,
|
||||
}
|
||||
|
||||
impl<'a> Drop for SemaphoreGuard<'a> {
|
||||
impl Drop for SemaphoreGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Ok(mut count) = self.sem.inner.lock() {
|
||||
*count += 1;
|
||||
@@ -64,7 +64,7 @@ pub struct ConcurrentDeviceManager {
|
||||
/// Pre-events emitted in order before any probe result.
|
||||
pre_events: Vec<ProbeEvent>,
|
||||
/// Bound-device map shared with workers.
|
||||
bound: Arc<RwLock<BTreeMap<DeviceId, BoundDevice>>>,
|
||||
bound: Arc<Mutex<BTreeMap<DeviceId, BoundDevice>>>,
|
||||
/// Deferred queue shared with workers.
|
||||
deferred: Arc<Mutex<Vec<(DeviceInfo, String)>>>,
|
||||
}
|
||||
@@ -132,7 +132,7 @@ impl ConcurrentDeviceManager {
|
||||
Self {
|
||||
jobs,
|
||||
pre_events,
|
||||
bound: Arc::new(RwLock::new(mgr.bound_devices_snapshot())),
|
||||
bound: Arc::new(Mutex::new(mgr.bound_devices_snapshot())),
|
||||
deferred: Arc::new(Mutex::new(mgr.deferred_queue_snapshot())),
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,7 @@ impl ConcurrentDeviceManager {
|
||||
|
||||
/// Snapshot of the bound map (post-probe).
|
||||
pub fn bound_snapshot(&self) -> BTreeMap<DeviceId, BoundDevice> {
|
||||
self.bound.read().map(|m| m.clone()).unwrap_or_default()
|
||||
self.bound.lock().map(|m| m.clone()).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Snapshot of the deferred queue.
|
||||
@@ -177,21 +177,26 @@ impl ConcurrentDeviceManager {
|
||||
.map(|_| semaphore.acquire())
|
||||
.collect();
|
||||
|
||||
thread::scope(|s| {
|
||||
for ((device_id, info, candidates), permit) in
|
||||
self.jobs.iter().cloned().zip(permits.drain(..))
|
||||
{
|
||||
let tx = tx.clone();
|
||||
let bound = Arc::clone(&bound);
|
||||
let deferred = Arc::clone(&deferred);
|
||||
s.spawn(move || {
|
||||
let _permit = permit;
|
||||
for outcome in run_probe(bound, deferred, device_id, info, candidates) {
|
||||
let _ = tx.send(outcome);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// Plain spawn + join: every capture is owned (Arc/moved), so
|
||||
// scoped threads are unnecessary, and JoinHandle::join does not
|
||||
// depend on the park/unpark path that thread::scope relies on.
|
||||
let mut handles = Vec::new();
|
||||
for ((device_id, info, candidates), permit) in
|
||||
self.jobs.iter().cloned().zip(permits.drain(..))
|
||||
{
|
||||
let tx = tx.clone();
|
||||
let bound = Arc::clone(&bound);
|
||||
let deferred = Arc::clone(&deferred);
|
||||
handles.push(thread::spawn(move || {
|
||||
let _permit = permit;
|
||||
for outcome in run_probe(bound, deferred, device_id, info, candidates) {
|
||||
let _ = tx.send(outcome);
|
||||
}
|
||||
}));
|
||||
}
|
||||
for handle in handles {
|
||||
let _ = handle.join();
|
||||
}
|
||||
drop(tx);
|
||||
|
||||
while let Ok(event) = rx.try_recv() {
|
||||
@@ -202,7 +207,7 @@ impl ConcurrentDeviceManager {
|
||||
}
|
||||
|
||||
fn run_probe(
|
||||
bound: Arc<RwLock<BTreeMap<DeviceId, BoundDevice>>>,
|
||||
bound: Arc<Mutex<BTreeMap<DeviceId, BoundDevice>>>,
|
||||
deferred: Arc<Mutex<Vec<(DeviceInfo, String)>>>,
|
||||
device_id: DeviceId,
|
||||
info: DeviceInfo,
|
||||
@@ -214,7 +219,7 @@ fn run_probe(
|
||||
let result = driver.probe(&info);
|
||||
match &result {
|
||||
ProbeResult::Bound => {
|
||||
if let Ok(mut map) = bound.write() {
|
||||
if let Ok(mut map) = bound.lock() {
|
||||
map.insert(
|
||||
device_id.clone(),
|
||||
BoundDevice {
|
||||
@@ -561,7 +566,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn semaphore_releases_on_drop() {
|
||||
let sem = CountingSemaphore::new(1);
|
||||
let sem = Arc::new(CountingSemaphore::new(1));
|
||||
{
|
||||
let _p = sem.acquire();
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ struct RawDriverMatch {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{is_process_alive, signal_then_collect};
|
||||
use super::{is_process_alive, signal_then_collect, DriverConfig};
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Mutex;
|
||||
|
||||
@@ -81,6 +81,38 @@ mod tests {
|
||||
body()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_all_accepts_matchless_driver_entries() {
|
||||
serialized(|| {
|
||||
let dir = std::env::temp_dir().join(format!(
|
||||
"rb-dm-matchless-{}-{}",
|
||||
std::process::id(),
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos()
|
||||
));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
std::fs::write(
|
||||
dir.join("70-usb-class.toml"),
|
||||
r#"
|
||||
[[driver]]
|
||||
name = "redbear-acmd"
|
||||
description = "USB CDC ACM serial driver"
|
||||
priority = 70
|
||||
command = ["/usr/bin/redbear-acmd"]
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let configs = DriverConfig::load_all(dir.to_str().unwrap())
|
||||
.expect("matchless driver entries must load");
|
||||
assert_eq!(configs.len(), 1);
|
||||
assert_eq!(configs[0].name, "redbear-acmd");
|
||||
assert!(configs[0].matches.is_empty());
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_process_alive_returns_false_for_unused_pid() {
|
||||
let dead = u32::MAX;
|
||||
@@ -885,6 +917,7 @@ struct RawDriverEntry {
|
||||
#[serde(default)]
|
||||
command: Vec<String>,
|
||||
#[serde(rename = "match")]
|
||||
#[serde(default)]
|
||||
r#match: Vec<RawDriverMatch>,
|
||||
#[serde(default)]
|
||||
depends_on: Vec<String>,
|
||||
|
||||
@@ -452,7 +452,9 @@ fn main() {
|
||||
log::info!("enum complete: {} bound, {} deferred", bound, deferred);
|
||||
}
|
||||
|
||||
if let Err(err) = scheme::start_scheme_server(Arc::clone(&scheme)) {
|
||||
if initfs {
|
||||
log::info!("initfs mode: skipping scheme registration (transient)");
|
||||
} else if let Err(err) = scheme::start_scheme_server(Arc::clone(&scheme)) {
|
||||
log::error!("{err}");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user