From 65ded583e8b886812e6a557da8093151027416bd Mon Sep 17 00:00:00 2001 From: Vasilito Date: Sat, 9 May 2026 01:33:34 +0100 Subject: [PATCH] fix: expose driver-manager boot observability --- .../source/src/bin/redbear-boot-check.rs | 84 ++++++++----------- .../system/redbear-info/source/src/main.rs | 65 +++++++++++--- 2 files changed, 91 insertions(+), 58 deletions(-) diff --git a/local/recipes/system/redbear-hwutils/source/src/bin/redbear-boot-check.rs b/local/recipes/system/redbear-hwutils/source/src/bin/redbear-boot-check.rs index 013df22fa6..3dab5d10cb 100644 --- a/local/recipes/system/redbear-hwutils/source/src/bin/redbear-boot-check.rs +++ b/local/recipes/system/redbear-hwutils/source/src/bin/redbear-boot-check.rs @@ -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, 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, } - 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 { } #[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()); diff --git a/local/recipes/system/redbear-info/source/src/main.rs b/local/recipes/system/redbear-info/source/src/main.rs index 297f4926f6..f0966b0f1b 100644 --- a/local/recipes/system/redbear-info/source/src/main.rs +++ b/local/recipes/system/redbear-info/source/src/main.rs @@ -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, 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 { @@ -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(); }