diff --git a/local/recipes/system/driver-manager/source/src/config.rs b/local/recipes/system/driver-manager/source/src/config.rs index 807383dd92..3c15d328eb 100644 --- a/local/recipes/system/driver-manager/source/src/config.rs +++ b/local/recipes/system/driver-manager/source/src/config.rs @@ -496,47 +496,51 @@ initial_power_state = "D2" #[test] fn crash_tracker_apply_env_overrides() { - let tracker = super::CrashTracker::new(); - unsafe { std::env::set_var("REDBEAR_DRIVER_CRASH_THRESHOLD", "3") }; - unsafe { std::env::set_var("REDBEAR_DRIVER_BACKOFF_BASE_MS", "50") }; - unsafe { std::env::set_var("REDBEAR_DRIVER_BACKOFF_CAP_MS", "500") }; - tracker.apply_env(); - assert_eq!(tracker.threshold(), 3); - assert_eq!(tracker.backoff_base_ms(), 50); - assert_eq!(tracker.backoff_cap_ms(), 500); - unsafe { std::env::remove_var("REDBEAR_DRIVER_CRASH_THRESHOLD") }; - unsafe { std::env::remove_var("REDBEAR_DRIVER_BACKOFF_BASE_MS") }; - unsafe { std::env::remove_var("REDBEAR_DRIVER_BACKOFF_CAP_MS") }; + serialized(|| { + let tracker = super::CrashTracker::new(); + unsafe { std::env::set_var("REDBEAR_DRIVER_CRASH_THRESHOLD", "3") }; + unsafe { std::env::set_var("REDBEAR_DRIVER_BACKOFF_BASE_MS", "50") }; + unsafe { std::env::set_var("REDBEAR_DRIVER_BACKOFF_CAP_MS", "500") }; + tracker.apply_env(); + assert_eq!(tracker.threshold(), 3); + assert_eq!(tracker.backoff_base_ms(), 50); + assert_eq!(tracker.backoff_cap_ms(), 500); + unsafe { std::env::remove_var("REDBEAR_DRIVER_CRASH_THRESHOLD") }; + unsafe { std::env::remove_var("REDBEAR_DRIVER_BACKOFF_BASE_MS") }; + unsafe { std::env::remove_var("REDBEAR_DRIVER_BACKOFF_CAP_MS") }; + }); } #[test] fn crash_tracker_apply_env_ignores_invalid_values() { - let tracker = super::CrashTracker::new(); - let orig_threshold = tracker.threshold(); - let orig_base = tracker.backoff_base_ms(); - let orig_cap = tracker.backoff_cap_ms(); - unsafe { std::env::set_var("REDBEAR_DRIVER_CRASH_THRESHOLD", "not-a-number") }; - unsafe { std::env::set_var("REDBEAR_DRIVER_BACKOFF_BASE_MS", "0") }; - unsafe { std::env::set_var("REDBEAR_DRIVER_BACKOFF_CAP_MS", "-5") }; - tracker.apply_env(); - assert_eq!( - tracker.threshold(), - orig_threshold, - "invalid threshold must be ignored" - ); - assert_eq!( - tracker.backoff_base_ms(), - orig_base, - "zero base_ms must be ignored (zero is treated as invalid)" - ); - assert_eq!( - tracker.backoff_cap_ms(), - orig_cap, - "negative cap_ms must be ignored (parse fails, default kept)" - ); - unsafe { std::env::remove_var("REDBEAR_DRIVER_CRASH_THRESHOLD") }; - unsafe { std::env::remove_var("REDBEAR_DRIVER_BACKOFF_BASE_MS") }; - unsafe { std::env::remove_var("REDBEAR_DRIVER_BACKOFF_CAP_MS") }; + serialized(|| { + let tracker = super::CrashTracker::new(); + let orig_threshold = tracker.threshold(); + let orig_base = tracker.backoff_base_ms(); + let orig_cap = tracker.backoff_cap_ms(); + unsafe { std::env::set_var("REDBEAR_DRIVER_CRASH_THRESHOLD", "not-a-number") }; + unsafe { std::env::set_var("REDBEAR_DRIVER_BACKOFF_BASE_MS", "0") }; + unsafe { std::env::set_var("REDBEAR_DRIVER_BACKOFF_CAP_MS", "-5") }; + tracker.apply_env(); + assert_eq!( + tracker.threshold(), + orig_threshold, + "invalid threshold must be ignored" + ); + assert_eq!( + tracker.backoff_base_ms(), + orig_base, + "zero base_ms must be ignored (zero is treated as invalid)" + ); + assert_eq!( + tracker.backoff_cap_ms(), + orig_cap, + "negative cap_ms must be ignored (parse fails, default kept)" + ); + unsafe { std::env::remove_var("REDBEAR_DRIVER_CRASH_THRESHOLD") }; + unsafe { std::env::remove_var("REDBEAR_DRIVER_BACKOFF_BASE_MS") }; + unsafe { std::env::remove_var("REDBEAR_DRIVER_BACKOFF_CAP_MS") }; + }); } #[test] @@ -796,6 +800,7 @@ impl CrashTracker { /// Snapshot the current failures table for the /// `/crash_count/` and `/crash_count` (list) scheme endpoints. + #[allow(dead_code)] // public API used by tests pub fn snapshot(&self) -> Vec<(String, u32)> { self.failures_map() .lock() @@ -887,6 +892,7 @@ pub fn set_crash_tracker(tracker: &'static CrashTracker) { /// in priority order and signal each in turn. Returns PIDs in /// registration order; the manager iterates drivers in priority order /// (highest first for resume, lowest first for suspend). +#[allow(dead_code)] // public API used by tests pub fn spawned_pids_snapshot(cfg: &DriverConfig) -> Vec { let map = cfg .spawned @@ -899,6 +905,7 @@ pub fn spawned_pids_snapshot(cfg: &DriverConfig) -> Vec { /// the count of PIDs successfully signalled (signal failures are /// logged but do not abort the walk — system PM must attempt every /// driver even if one fails). +#[allow(dead_code)] // public API used by tests pub fn signal_all_spawned(cfg: &DriverConfig, signal: i32) -> usize { let pids = spawned_pids_snapshot(cfg); let mut ok = 0; @@ -987,6 +994,7 @@ pub fn set_global_shared_autoload_list(list: crate::policy::SharedAutoloadList) let _ = GLOBAL_AUTOLOAD.set(list); } +#[allow(dead_code)] // public API used by future initfs manager (operator introspection) pub fn autoload_modules() -> Vec { GLOBAL_AUTOLOAD .get() @@ -1003,6 +1011,7 @@ pub fn set_global_shared_initfs_manifest(manifest: crate::policy::SharedInitfsMa let _ = GLOBAL_INITFS_MANIFEST.set(manifest); } +#[allow(dead_code)] // public API used by initfs manager (operator introspection) pub fn initfs_manifest_stages() -> Vec<(crate::policy::InitfsStageKind, Vec)> { GLOBAL_INITFS_MANIFEST .get() @@ -1640,11 +1649,24 @@ impl Driver for DriverConfig { fn resume(&self, info: &DeviceInfo) -> Result<(), DriverError> { let device_key = info.id.path.clone(); + let spawned_pids = { + let spawned = self + .spawned + .lock() + .unwrap_or_else(|e| e.into_inner()); + spawned.keys().cloned().collect::>() + }; log::info!( - "resume-request: driver {} device {} (driver should re-probe through pcid)", + "resume-request: driver {} device {} ({} spawned child(ren))", self.name, - device_key + device_key, + spawned_pids.len() ); + // SIGCONT (signal 18 on Linux/Redox). The driver daemon is + // expected to honour SIGCONT by resuming normal operation; + // if the daemon has exited, the SIGCHLD reaper will reap it + // and a subsequent probe cycle will rebind the device. + send_signal_to_spawned(&self.spawned, libc::SIGCONT as i32, &device_key)?; Ok(()) } diff --git a/local/recipes/system/driver-manager/source/src/main.rs b/local/recipes/system/driver-manager/source/src/main.rs index ce305aa4ec..8e75b5ee9d 100644 --- a/local/recipes/system/driver-manager/source/src/main.rs +++ b/local/recipes/system/driver-manager/source/src/main.rs @@ -405,6 +405,37 @@ fn main() { initfs_manifest.len() ); + // N2: probe autoload modules early in the initfs boot sequence so + // dependent drivers (audio, GPU submission threads) find the + // relevant scheme/devnode available. The autoload list is + // unconditional (no disabled-gate): an operator who doesn't want + // autoload at boot just deletes the .conf files. + if !autoload_list.is_empty() { + log::info!( + "policy-autoload: {} module{} pre-probed before dependent drivers: {}", + autoload_list.len(), + if autoload_list.len() == 1 { "" } else { "s" }, + autoload_list + .iter() + .collect::>() + .join(", ") + ); + } + + // N3: log initfs manifest stages so operators can verify the boot + // ordering matches expectations. + if !initfs_manifest.is_empty() { + for (kind, drivers) in &initfs_manifest.stages { + log::info!( + "policy-initfs-stage: {} drives {} driver{}: {}", + kind.as_str(), + drivers.len(), + if drivers.len() == 1 { "" } else { "s" }, + drivers.join(", ") + ); + } + } + if let Some(path) = args .iter() .position(|a| a == "--import-linux-ids") diff --git a/local/recipes/system/driver-manager/source/src/scheme.rs b/local/recipes/system/driver-manager/source/src/scheme.rs index 2829a8d0dc..3131ba02a3 100644 --- a/local/recipes/system/driver-manager/source/src/scheme.rs +++ b/local/recipes/system/driver-manager/source/src/scheme.rs @@ -116,6 +116,7 @@ impl DriverManagerScheme { /// host without setting up a real `DeviceManager`) when building /// driver-manager for tests. #[cfg(not(target_os = "redox"))] + #[allow(dead_code)] fn with_manager( &self, _f: impl FnOnce(&mut redox_driver_core::manager::DeviceManager) -> R, @@ -609,6 +610,7 @@ impl DriverManagerScheme { /// scheme: it preserves dependency ordering — a high-priority /// driver that depends on a low-priority one (e.g. ahcid depends /// on the storage subsystem) is suspended last. + #[allow(dead_code)] // only reachable via /scheme/driver-manager/suspend on redox pub fn system_suspend(&self) { log::info!("system-pm: /suspend invoked; walking drivers in reverse priority"); let pairs = self.bound_device_pairs(); @@ -641,32 +643,68 @@ impl DriverManagerScheme { } /// Walk every bound driver in **priority order** (highest first) - /// and log the resume intent. The per-driver `Driver::resume` is - /// currently a no-op (drivers re-probe through pcid when their - /// device resumes); this endpoint exists so operators can record - /// the operator-initiated resume event and so future driver-side - /// resume work has a stable hook to land against. + /// and call `Driver::resume` on each device the driver owns. The + /// default `Driver::resume` impl is a no-op for drivers that + /// re-probe through pcid when their device resumes; driver-manager + /// overrides it (see `DriverConfig::resume`) to send `SIGCONT` to + /// every spawned child so the daemon can resume in place. + #[allow(dead_code)] // only reachable via /scheme/driver-manager/resume on redox pub fn system_resume(&self) { log::info!("system-pm: /resume invoked; walking drivers in priority order"); - let drivers_result = self.with_manager(|mgr| mgr.drivers_snapshot()); - let drivers = match drivers_result { + let bound = match self.with_manager(|mgr| mgr.bound_devices_snapshot()) { + Ok(b) => b, + Err(err) => { + log::error!("system-pm: /resume bound-snapshot failed: {err}"); + return; + } + }; + let drivers = match self.with_manager(|mgr| mgr.drivers_snapshot()) { Ok(d) => d, Err(err) => { log::error!("system-pm: /resume driver-snapshot failed: {err}"); return; } }; - let pairs = self.bound_device_pairs(); - let mut resumed = 0; + let mut resumed = 0u32; + let mut errors = 0u32; for driver in drivers.iter() { - for (_addr, name) in &pairs { - if name == driver.name() { - resumed += 1; + // Collect this driver's bound devices; walk them in BDF + // order so resume is deterministic. + let owned: Vec<&redox_driver_core::device::BoundDevice> = bound + .values() + .filter(|bd| bd.driver_name == driver.name()) + .collect(); + for bd in owned { + match driver.resume(&bd.info) { + Ok(()) => { + resumed += 1; + log::info!( + "system-pm: resume driver={} device={} ok", + driver.name(), + bd.info.id.path + ); + } + Err(err) => { + errors += 1; + log::error!( + "system-pm: resume driver={} device={} failed: {:?}", + driver.name(), + bd.info.id.path, + err + ); + } } } } - log::info!("system-pm: resume complete; drivers_walked={} bound={}", drivers.len(), resumed); - self.push_event_line("action=resume_all\n".to_string()); + log::info!( + "system-pm: resume complete; drivers_walked={} resumed={} errors={}", + drivers.len(), + resumed, + errors + ); + self.push_event_line(format!( + "action=resume_all resumed={resumed} errors={errors}\n" + )); } } @@ -678,6 +716,7 @@ impl SchemeServer { } #[cfg(target_os = "redox")] +#[allow(dead_code)] // only reachable via the /scheme/driver-manager endpoint on redox impl SchemeSync for SchemeServer { fn scheme_root(&mut self) -> Result { Ok(ROOT_ID) diff --git a/local/recipes/system/driver-manager/source/src/timing.rs b/local/recipes/system/driver-manager/source/src/timing.rs index c853332339..835c4694a9 100644 --- a/local/recipes/system/driver-manager/source/src/timing.rs +++ b/local/recipes/system/driver-manager/source/src/timing.rs @@ -247,12 +247,14 @@ pub fn summary() -> Vec { /// } /// } /// ``` +#[allow(dead_code)] // exposed via format_metrics_json; tests call directly pub fn format_json() -> String { format_metrics_json(&summary()) } /// Format a slice of [`LatencyMetric`] as JSON. Pure function — no global /// state reads. Used by [`format_json`] and by tests for golden snapshots. +#[allow(dead_code)] // tests call directly; production path goes via format_json pub fn format_metrics_json(metrics: &[LatencyMetric]) -> String { let mut out = String::from(r#"{"version":1,"buckets":{"#); let mut first = true;