fix: expose driver-manager boot observability

This commit is contained in:
2026-05-09 01:33:34 +01:00
parent 533e253e1e
commit 65ded583e8
2 changed files with 91 additions and 58 deletions
@@ -9,31 +9,31 @@ const USAGE: &str = "Usage: redbear-boot-check [--json]\n\n\
Boot process runtime check. Validates critical boot services are\n\
properly ordered, DRM device is ready, and greeter is healthy.";
#[cfg(target_os = "redox")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum CheckResult {
Pass,
Fail,
#[cfg(not(target_os = "redox"))]
Skip,
}
#[cfg(target_os = "redox")]
impl CheckResult {
fn label(self) -> &'static str {
match self {
Self::Pass => "PASS",
Self::Fail => "FAIL",
#[cfg(not(target_os = "redox"))]
Self::Skip => "SKIP",
}
}
}
#[cfg(target_os = "redox")]
struct Check {
name: String,
result: CheckResult,
detail: String,
}
#[cfg(target_os = "redox")]
impl Check {
fn pass(name: &str, detail: &str) -> Self {
Check {
@@ -49,21 +49,15 @@ impl Check {
detail: detail.to_string(),
}
}
#[cfg(not(target_os = "redox"))]
fn skip(name: &str, detail: &str) -> Self {
Check {
name: name.to_string(),
result: CheckResult::Skip,
detail: detail.to_string(),
}
}
}
#[cfg(target_os = "redox")]
struct Report {
checks: Vec<Check>,
json_mode: bool,
}
#[cfg(target_os = "redox")]
impl Report {
fn new(json_mode: bool) -> Self {
Report {
@@ -89,8 +83,6 @@ impl Report {
let icon = match check.result {
CheckResult::Pass => "[PASS]",
CheckResult::Fail => "[FAIL]",
#[cfg(not(target_os = "redox"))]
CheckResult::Skip => "[SKIP]",
};
println!("{icon} {}: {}", check.name, check.detail);
}
@@ -104,16 +96,17 @@ impl Report {
}
#[derive(serde::Serialize)]
struct JsonReport {
driver_manager: bool,
pcid_spawner: bool,
drm_device: bool,
compositor_socket: bool,
greeter_service: bool,
checks: Vec<JsonCheck>,
}
let pcid = self
let driver_manager = self
.checks
.iter()
.find(|c| c.name == "PCID_SPAWNER")
.find(|c| c.name == "DRIVER_MANAGER")
.map_or(false, |c| c.result == CheckResult::Pass);
let drm = self
.checks
@@ -142,7 +135,8 @@ impl Report {
if let Err(err) = serde_json::to_writer(
std::io::stdout(),
&JsonReport {
pcid_spawner: pcid,
driver_manager,
pcid_spawner: driver_manager,
drm_device: drm,
compositor_socket: socket,
greeter_service: greeter,
@@ -171,22 +165,32 @@ fn parse_args() -> Result<bool, String> {
}
#[cfg(target_os = "redox")]
fn check_pcid_spawner() -> Check {
let service = "/usr/lib/init.d/00_pcid-spawner.service";
if std::path::Path::new(service).exists() {
if std::path::Path::new("/scheme/pci").exists() {
fn check_driver_manager() -> Check {
let config_service = "/etc/init.d/00_driver-manager.service";
let package_service = "/usr/lib/init.d/00_driver-manager.service";
if std::path::Path::new(config_service).exists()
|| std::path::Path::new(package_service).exists()
{
if std::path::Path::new("/scheme/driver-manager").exists()
&& std::path::Path::new("/scheme/pci").exists()
{
Check::pass(
"PCID_SPAWNER",
"pcid-spawner service present, /scheme/pci registered",
"DRIVER_MANAGER",
"driver-manager service present, /scheme/driver-manager and /scheme/pci registered",
)
} else if std::path::Path::new("/scheme/pci").exists() {
Check::fail(
"DRIVER_MANAGER",
"driver-manager installed but /scheme/driver-manager not registered",
)
} else {
Check::fail(
"PCID_SPAWNER",
"pcid-spawner installed but /scheme/pci not registered",
"DRIVER_MANAGER",
"driver-manager installed but /scheme/pci not registered",
)
}
} else {
Check::fail("PCID_SPAWNER", "pcid-spawner service not found")
Check::fail("DRIVER_MANAGER", "driver-manager service not found")
}
}
@@ -217,8 +221,11 @@ fn check_compositor_socket() -> Check {
#[cfg(target_os = "redox")]
fn check_greeter_service() -> Check {
let service = "/usr/lib/init.d/20_greeter.service";
if !std::path::Path::new(service).exists() {
let config_service = "/etc/init.d/20_greeter.service";
let package_service = "/usr/lib/init.d/20_greeter.service";
if !std::path::Path::new(config_service).exists()
&& !std::path::Path::new(package_service).exists()
{
return Check::fail("GREETER_SERVICE", "greeter service definition not found");
}
let greeterd = "/usr/bin/redbear-greeterd";
@@ -236,23 +243,6 @@ fn check_greeter_service() -> Check {
}
}
#[cfg(not(target_os = "redox"))]
fn check_pcid_spawner() -> Check {
Check::skip("PCID_SPAWNER", "requires Redox runtime")
}
#[cfg(not(target_os = "redox"))]
fn check_drm_device() -> Check {
Check::skip("DRM_DEVICE", "requires Redox runtime")
}
#[cfg(not(target_os = "redox"))]
fn check_compositor_socket() -> Check {
Check::skip("COMPOSITOR_SOCKET", "requires Redox runtime")
}
#[cfg(not(target_os = "redox"))]
fn check_greeter_service() -> Check {
Check::skip("GREETER_SERVICE", "requires Redox runtime")
}
fn run() -> Result<(), String> {
#[cfg(not(target_os = "redox"))]
{
@@ -261,13 +251,13 @@ fn run() -> Result<(), String> {
return Err(String::new());
}
println!("{PROGRAM}: boot check requires Redox runtime");
return Ok(());
Ok(())
}
#[cfg(target_os = "redox")]
{
let json_mode = parse_args()?;
let mut report = Report::new(json_mode);
report.add(check_pcid_spawner());
report.add(check_driver_manager());
report.add(check_drm_device());
report.add(check_compositor_socket());
report.add(check_greeter_service());
@@ -195,6 +195,10 @@ enum BootTimelineEvent {
bus: String,
count: usize,
},
BusEnumerationFailed {
bus: String,
error: String,
},
Probe {
device: String,
driver: String,
@@ -357,13 +361,13 @@ const INTEGRATIONS: &[IntegrationCheck] = &[
functional_probe: Some(probe_smolnetd_surface),
},
IntegrationCheck {
name: "pcid-spawner",
name: "driver-manager",
category: "Core",
description: "PCI driver autoload daemon",
artifact_path: Some("/usr/bin/pcid-spawner"),
control_path: Some("/scheme/pci"),
test_hint: "lspci",
note: "The PCI scheme proves discovery is live, but not which driver handled each device.",
description: "Device driver lifecycle and PCI handoff daemon",
artifact_path: Some("/usr/bin/driver-manager"),
control_path: Some("/scheme/driver-manager"),
test_hint: "redbear-info --boot",
note: "The driver-manager scheme proves the lifecycle daemon is live; PCI enumeration is reported separately through /scheme/pci and the boot timeline.",
functional_probe: Some(probe_directory_readable),
},
IntegrationCheck {
@@ -599,6 +603,7 @@ fn run() -> Result<(), String> {
OutputMode::Table => print_table(&report, options.verbose),
OutputMode::Json => print_json(&report),
OutputMode::Test => print_tests(&report, options.verbose),
OutputMode::Tui => {}
OutputMode::Quirks => {}
OutputMode::Probe => {}
OutputMode::Boot => {}
@@ -1516,6 +1521,7 @@ fn output_mode_flag(mode: OutputMode) -> &'static str {
OutputMode::Table => "table output",
OutputMode::Json => "--json",
OutputMode::Test => "--test",
OutputMode::Tui => "--tui",
OutputMode::Quirks => "--quirks",
OutputMode::Probe => "--probe",
OutputMode::Boot => "--boot",
@@ -2551,6 +2557,18 @@ fn collect_boot_timeline(runtime: &Runtime) -> Result<Vec<BootTimelineEntry>, St
.map(|value| value as usize)
.ok_or_else(|| format!("boot timeline entry {} is missing count", index + 1))?,
},
"bus_enumeration_failed" => BootTimelineEvent::BusEnumerationFailed {
bus: value
.get("bus")
.and_then(JsonValue::as_str)
.ok_or_else(|| format!("boot timeline entry {} is missing bus", index + 1))?
.to_string(),
error: value
.get("error")
.and_then(JsonValue::as_str)
.ok_or_else(|| format!("boot timeline entry {} is missing error", index + 1))?
.to_string(),
},
"probe" => BootTimelineEvent::Probe {
device: value
.get("device")
@@ -2592,12 +2610,24 @@ fn print_boot_timeline(entries: &[BootTimelineEntry]) {
let mut bound = 0usize;
let mut deferred = 0usize;
let mut failed = 0usize;
let mut bus_failures = 0usize;
for entry in entries {
let delta = entry.ts.saturating_sub(first_ts);
match &entry.event {
BootTimelineEvent::BusEnumerated { bus, count } => {
println!("[{delta:>4}ms] bus {bus} enumerated {count} device(s)");
println!(
"[{delta:>4}ms] bus {} enumerated {count} device(s)",
format_timeline_text(bus)
);
}
BootTimelineEvent::BusEnumerationFailed { bus, error } => {
bus_failures += 1;
println!(
"[{delta:>4}ms] bus {} enumeration failed: {}",
format_timeline_text(bus),
format_timeline_text(error)
);
}
BootTimelineEvent::Probe {
device,
@@ -2612,15 +2642,17 @@ fn print_boot_timeline(entries: &[BootTimelineEntry]) {
}
println!(
"[{delta:>4}ms] probed {} -> {} ({})",
format_timeline_device(device),
driver,
format_timeline_text(&format_timeline_device(device)),
format_timeline_text(driver),
status.as_str()
);
}
}
}
println!("Total: {bound} bound, {deferred} deferred, {failed} failed");
println!(
"Total: {bound} bound, {deferred} deferred, {failed} failed, {bus_failures} bus failure(s)"
);
}
fn collect_device_status(runtime: &Runtime, requested: &str) -> Result<DeviceStatusReport, String> {
@@ -2866,6 +2898,10 @@ fn format_timeline_device(device: &str) -> String {
})
}
fn format_timeline_text(value: &str) -> String {
value.escape_default().to_string()
}
fn latest_boot_status_for_device(
runtime: &Runtime,
location: &redox_driver_sys::pci::PciLocation,
@@ -4218,19 +4254,25 @@ mod tests {
BOOT_TIMELINE_PATH,
concat!(
"{\"ts\":1000,\"event\":\"bus_enumerated\",\"bus\":\"pci\",\"count\":5}\n",
"{\"ts\":1040,\"event\":\"bus_enumeration_failed\",\"bus\":\"usb\",\"error\":\"SchemeUnavailable\"}\n",
"{\"ts\":1120,\"event\":\"probe\",\"device\":\"pci/00:02:0\",\"driver\":\"redox-drm\",\"status\":\"bound\"}\n",
"{\"ts\":1750,\"event\":\"probe\",\"device\":\"pci/00:19:0\",\"driver\":\"e1000d\",\"status\":\"deferred\"}\n"
),
);
let timeline = collect_boot_timeline(&Runtime::from_root(root.clone())).unwrap();
assert_eq!(timeline.len(), 3);
assert_eq!(timeline.len(), 4);
assert!(matches!(
timeline[0].event,
BootTimelineEvent::BusEnumerated { ref bus, count } if bus == "pci" && count == 5
));
assert!(matches!(
timeline[1].event,
BootTimelineEvent::BusEnumerationFailed { ref bus, ref error }
if bus == "usb" && error == "SchemeUnavailable"
));
assert!(matches!(
timeline[2].event,
BootTimelineEvent::Probe {
ref driver,
status: BootProbeStatus::Bound,
@@ -4238,6 +4280,7 @@ mod tests {
} if driver == "redox-drm"
));
assert_eq!(format_timeline_device("pci/00:02:0"), "00.02.0");
assert_eq!(format_timeline_text("pci\n\u{1b}[31m"), "pci\\n\\u{1b}[31m");
fs::remove_dir_all(root).unwrap();
}