diff --git a/local/docs/DESKTOP-STACK-CURRENT-STATUS.md b/local/docs/DESKTOP-STACK-CURRENT-STATUS.md index 28c0d444..0e1abde3 100644 --- a/local/docs/DESKTOP-STACK-CURRENT-STATUS.md +++ b/local/docs/DESKTOP-STACK-CURRENT-STATUS.md @@ -2,9 +2,16 @@ **Last updated:** 2026-04-29 **Canonical plan:** `local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md` (v3.0) -**Boot improvement plan:** `local/docs/BOOT-PROCESS-IMPROVEMENT-PLAN.md` (v1.0) +**Boot improvement plan:** `local/docs/BOOT-PROCESS-IMPROVEMENT-PLAN.md` (v1.1) **Source archival policy:** `local/docs/SOURCE-ARCHIVAL-POLICY.md` (v1.0) +## Recent Changes (2026-04-29, Wave C) + +- **Validation infrastructure consistency refresh**: + - `redbear-phase4-kde-check` now detects resolved KF6 shared-library version suffixes in addition to checking library presence, session entry points, and KDE runtime markers. + - `redbear-phase5-gpu-check` now detects GPU PCI vendor/device identity from `/scheme/pci` before reporting firmware / Mesa / DRM preflight state. + - `redbear-boot-check` now provides a bounded boot-surface checker for greeter-facing service ordering, DRM readiness, compositor socket creation, and greeter hello-protocol health. + ## Recent Changes (2026-04-29, Wave 7) - **KF6 surface made more honest for Phase 3/4**: @@ -160,8 +167,9 @@ greeter/auth/session-launch stack on the `redbear-full` desktop path. | `kwin` | **stub** | cmake configs + wrapper scripts that delegate to redbear-compositor | | `test-phase4-runtime.sh` | **exists** | Phase 4 KDE Plasma preflight harness (guest + QEMU modes) | | `test-phase5-gpu-runtime.sh` | **exists** | Phase 5 hardware GPU preflight harness (guest + QEMU modes) | -| `redbear-phase4-kde-check` | **builds** | Phase 4 KDE preflight: KF6 libraries, plasma binaries, session entry points, kirigami status | -| `redbear-phase5-gpu-check` | **builds** | Phase 5 GPU preflight: DRM device, GPU firmware, Mesa DRI drivers, display modes | +| `redbear-phase4-kde-check` | **builds** | Phase 4 KDE preflight: KF6 libraries plus resolved KF6 version suffixes, plasma binaries, session entry points, and kirigami status | +| `redbear-phase5-gpu-check` | **builds** | Phase 5 GPU preflight: DRM device, GPU PCI vendor/device detection, GPU firmware, Mesa DRI drivers, and display modes | +| `redbear-boot-check` | **builds** | Boot-surface preflight: `pcid-spawner`→greeter ordering contract, DRM device readiness, compositor socket creation, and greeter hello-protocol health | | | | | | **Phase 5 (Hardware GPU) — driver scaffold** | | | | `redox-drm` | **builds** | DRM scheme daemon with Intel Gen8-Gen12 + AMD device support and quirk tables; no hardware validation | diff --git a/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase4-kde-check.rs b/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase4-kde-check.rs index 8ee1a2e3..3f0e6490 100644 --- a/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase4-kde-check.rs +++ b/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase4-kde-check.rs @@ -24,6 +24,16 @@ const REDBEAR_KDE_SESSION_ENV_FILE: &str = "redbear-kde-session.env"; const REDBEAR_KDE_SESSION_READY_FILE: &str = "redbear-kde-session.ready"; #[cfg(target_os = "redox")] const REDBEAR_KDE_SESSION_PANEL_READY_FILE: &str = "redbear-kde-session.panel-ready"; +#[cfg(target_os = "redox")] +const KEY_KF6_LIBRARIES: &[&str] = &[ + "/usr/lib/libKF6CoreAddons.so", + "/usr/lib/libKF6ConfigCore.so", + "/usr/lib/libKF6I18n.so", + "/usr/lib/libKF6WindowSystem.so", + "/usr/lib/libKF6Notifications.so", + "/usr/lib/libKF6Service.so", + "/usr/lib/libKF6WaylandClient.so", +]; #[cfg(target_os = "redox")] #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -141,6 +151,7 @@ impl Report { struct JsonReport { overall_success: bool, kf6_libs_present: bool, + kf6_library_versions: bool, plasma_binaries_present: bool, session_entry: bool, session_environment: bool, @@ -164,6 +175,7 @@ impl Report { let report = JsonReport { overall_success: !self.any_failed(), kf6_libs_present: self.check_passed("KF6_LIBRARIES"), + kf6_library_versions: self.check_passed("KF6_LIBRARY_VERSIONS"), plasma_binaries_present: self.check_passed("PLASMA_BINARIES"), session_entry: self.check_passed("SESSION_ENTRY"), session_environment: self.check_passed("SESSION_ENVIRONMENT"), @@ -207,19 +219,10 @@ fn parse_args() -> Result { #[cfg(target_os = "redox")] fn check_kf6_libraries() -> Check { - let key_libs = [ - "/usr/lib/libKF6CoreAddons.so", - "/usr/lib/libKF6ConfigCore.so", - "/usr/lib/libKF6I18n.so", - "/usr/lib/libKF6WindowSystem.so", - "/usr/lib/libKF6Notifications.so", - "/usr/lib/libKF6Service.so", - "/usr/lib/libKF6WaylandClient.so", - ]; let mut found = 0usize; let mut missing = Vec::new(); - for lib in key_libs { + for lib in KEY_KF6_LIBRARIES { if Path::new(lib).exists() { found += 1; } else { @@ -231,7 +234,7 @@ fn check_kf6_libraries() -> Check { if missing.is_empty() { Check::pass( "KF6_LIBRARIES", - format!("{found}/{} key KF6 libraries found", key_libs.len()), + format!("{found}/{} key KF6 libraries found", KEY_KF6_LIBRARIES.len()), ) } else { let preview = missing @@ -242,17 +245,93 @@ fn check_kf6_libraries() -> Check { .join(", "); Check::pass( "KF6_LIBRARIES", - format!("{found}/{} found, missing: {preview}", key_libs.len()), + format!("{found}/{} found, missing: {preview}", KEY_KF6_LIBRARIES.len()), ) } } else { Check::fail( "KF6_LIBRARIES", - format!("only {found}/{} key KF6 libraries found", key_libs.len()), + format!( + "only {found}/{} key KF6 libraries found", + KEY_KF6_LIBRARIES.len() + ), ) } } +#[cfg(target_os = "redox")] +fn library_display_name(path: &str) -> &str { + path.rsplit('/').next().unwrap_or(path) +} + +#[cfg(target_os = "redox")] +fn detect_shared_library_version(path: &Path) -> Result { + let resolved = fs::canonicalize(path) + .map_err(|err| format!("failed to resolve {}: {err}", path.display()))?; + let file_name = resolved + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| format!("failed to read resolved file name for {}", path.display()))?; + + file_name + .rsplit_once(".so.") + .map(|(_, version)| version.to_string()) + .ok_or_else(|| { + format!( + "resolved library {} does not contain a version suffix", + resolved.display() + ) + }) +} + +#[cfg(target_os = "redox")] +fn check_kf6_library_versions() -> Check { + let mut versions = BTreeMap::>::new(); + let mut unresolved = Vec::new(); + + for lib in KEY_KF6_LIBRARIES { + let lib_path = Path::new(lib); + if !lib_path.exists() { + continue; + } + + match detect_shared_library_version(lib_path) { + Ok(version) => versions + .entry(version) + .or_default() + .push(library_display_name(lib).to_string()), + Err(err) => unresolved.push(err), + } + } + + let detected = versions.values().map(Vec::len).sum::(); + if detected >= 6 { + let mut detail_parts = versions + .iter() + .map(|(version, libs)| format!("{version} [{}]", libs.join(", "))) + .collect::>(); + if !unresolved.is_empty() { + detail_parts.push(format!("unresolved: {}", unresolved.join("; "))); + } + + Check::pass( + "KF6_LIBRARY_VERSIONS", + format!( + "detected version suffixes for {detected}/{} key KF6 libraries: {}", + KEY_KF6_LIBRARIES.len(), + detail_parts.join(" | ") + ), + ) + } else { + let detail = if unresolved.is_empty() { + String::from("no versioned KF6 libraries could be resolved") + } else { + unresolved.join("; ") + }; + Check::fail("KF6_LIBRARY_VERSIONS", detail) + } +} + #[cfg(target_os = "redox")] fn check_plasma_binaries() -> Check { let required = [ @@ -631,6 +710,7 @@ fn run() -> Result<(), String> { let mut report = Report::new(json_mode); report.add(check_kf6_libraries()); + report.add(check_kf6_library_versions()); report.add(check_plasma_binaries()); report.add(check_session_entry()); report.add(check_session_environment()); diff --git a/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase5-gpu-check.rs b/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase5-gpu-check.rs index d9a9dda0..3fc3c8f4 100644 --- a/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase5-gpu-check.rs +++ b/local/recipes/system/redbear-hwutils/source/src/bin/redbear-phase5-gpu-check.rs @@ -4,6 +4,11 @@ use std::process; +#[cfg(target_os = "redox")] +use redbear_hwutils::{PciLocation, lookup_pci_device_name, lookup_pci_vendor_name, parse_pci_location}; +#[cfg(target_os = "redox")] +use redox_driver_sys::pci::parse_device_info_from_config_space; + const PROGRAM: &str = "redbear-phase5-gpu-check"; const USAGE: &str = "Usage: redbear-phase5-gpu-check [--json]\n\n\ Phase 5 hardware GPU preflight check. Validates DRM device registration,\n\ @@ -120,6 +125,7 @@ impl Report { #[derive(serde::Serialize)] struct JsonReport { drm_device: bool, + gpu_pci_vendor: bool, gpu_firmware: bool, mesa_dri: bool, display_modes: bool, @@ -138,6 +144,11 @@ impl Report { .iter() .find(|c| c.name == "GPU_FIRMWARE") .map_or(false, |c| c.result == CheckResult::Pass); + let pci_vendor = self + .checks + .iter() + .find(|c| c.name == "GPU_PCI_VENDOR") + .map_or(false, |c| c.result == CheckResult::Pass); let mesa = self .checks .iter() @@ -176,6 +187,7 @@ impl Report { std::io::stdout(), &JsonReport { drm_device: drm, + gpu_pci_vendor: pci_vendor, gpu_firmware: firmware, mesa_dri: mesa, display_modes: modes, @@ -256,6 +268,101 @@ fn check_drm_device() -> Check { Check::fail("DRM_DEVICE", "no DRM device found at /scheme/drm/card0") } +#[cfg(target_os = "redox")] +#[derive(Clone, Debug)] +struct GpuPciDevice { + location: PciLocation, + vendor_id: u16, + device_id: u16, +} + +#[cfg(target_os = "redox")] +fn collect_gpu_pci_devices() -> Result, String> { + let entries = std::fs::read_dir("/scheme/pci") + .map_err(|err| format!("failed to read /scheme/pci: {err}"))?; + let mut devices = Vec::new(); + + for entry in entries.flatten() { + let file_name = entry.file_name(); + let Some(file_name) = file_name.to_str() else { + continue; + }; + let Some(location) = parse_pci_location(file_name) else { + continue; + }; + + let config_path = format!("{}/config", location.scheme_path()); + let config = match std::fs::read(&config_path) { + Ok(config) => config, + Err(_) => continue, + }; + if config.len() < 64 { + continue; + } + + let Some(info) = parse_device_info_from_config_space( + redox_driver_sys::pci::PciLocation { + segment: location.segment, + bus: location.bus, + device: location.device, + function: location.function, + }, + &config, + ) else { + continue; + }; + + if info.class_code != 0x03 { + continue; + } + + devices.push(GpuPciDevice { + location, + vendor_id: info.vendor_id, + device_id: info.device_id, + }); + } + + devices.sort_by_key(|device| device.location); + Ok(devices) +} + +#[cfg(target_os = "redox")] +fn format_gpu_pci_device(device: &GpuPciDevice) -> String { + let vendor = lookup_pci_vendor_name(device.vendor_id) + .unwrap_or_else(|| format!("vendor {:04x}", device.vendor_id)); + let device_name = lookup_pci_device_name(device.vendor_id, device.device_id) + .unwrap_or_else(|| format!("device {:04x}", device.device_id)); + + format!( + "{} {} ({})", + device.location, vendor, device_name + ) +} + +#[cfg(target_os = "redox")] +fn check_gpu_pci_vendor() -> Check { + match collect_gpu_pci_devices() { + Ok(devices) if devices.is_empty() => Check::fail( + "GPU_PCI_VENDOR", + "no display-class PCI device found under /scheme/pci", + ), + Ok(devices) => { + let preview = devices + .iter() + .take(3) + .map(format_gpu_pci_device) + .collect::>() + .join(", "); + Check::pass( + "GPU_PCI_VENDOR", + &format!("detected {} GPU PCI device(s): {preview}", devices.len()), + ) + } + Err(err) => Check::fail("GPU_PCI_VENDOR", &err), + } +} + #[cfg(target_os = "redox")] fn check_gpu_firmware() -> Check { let firmware_dirs = ["/lib/firmware/amdgpu", "/lib/firmware/i915"]; @@ -509,6 +616,7 @@ fn check_cs_ioctl_protocol() -> Check { fn check_hardware_rendering_ready(report: &Report) -> Check { let required = [ "DRM_DEVICE", + "GPU_PCI_VENDOR", "GPU_FIRMWARE", "MESA_DRI", "DISPLAY_MODES", @@ -557,6 +665,7 @@ fn run() -> Result<(), String> { let json_mode = parse_args()?; let mut report = Report::new(json_mode); report.add(check_drm_device()); + report.add(check_gpu_pci_vendor()); report.add(check_gpu_firmware()); report.add(check_mesa_dri_hardware()); report.add(check_display_modes());