Files
RedBear-OS/local/recipes/system/driver-manager/source/src/main.rs
T
vasilito cd05afdcb1 initfs: exit after enumeration, skip scheme path checks
- Skip binary existence check in probe(): Redox scheme paths
  (especially /scheme/initfs/) may block on open/stat indefinitely.
  Command::new() spawn fails cleanly if binary missing.
- In initfs mode: use synchronous probe, do bounded deferred
  retries, then exit. Rootfs instance handles hotplug.
- Avoids pcid config handle read hang that blocks async threads.
2026-05-31 19:22:39 +03:00

663 lines
22 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
mod config;
mod exec;
mod hotplug;
mod scheme;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use std::{env, fs, process};
use redox_driver_core::device::DeviceId;
use redox_driver_core::driver::ProbeResult;
use redox_driver_core::manager::{DeviceManager, ManagerConfig, ProbeEvent};
use redox_driver_pci::PciBus;
use redox_driver_acpi::AcpiBus;
use std::fs::OpenOptions;
use std::io::Write;
use config::DriverConfig;
use scheme::{DriverManagerScheme, notify_bind};
/// Global flag set by SIGTERM handler to request graceful shutdown.
static SHUTDOWN_REQUESTED: AtomicBool = AtomicBool::new(false);
extern "C" fn sigterm_handler(_sig: i32) {
SHUTDOWN_REQUESTED.store(true, Ordering::SeqCst);
}
fn install_sigterm_handler() {
unsafe {
libc::signal(libc::SIGTERM, sigterm_handler as *const () as usize);
}
}
struct StderrLogger;
const BOOT_TIMELINE_PATH: &str = "/tmp/redbear-boot-timeline.json";
impl log::Log for StderrLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::Level::Info
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
eprintln!("[{}] driver-manager: {}", record.level(), record.args());
}
}
fn flush(&self) {}
}
/// Wait for a scheme to become available by polling `/scheme/{name}`.
/// Returns true if the scheme appeared within the timeout, false otherwise.
///
/// In initfs mode, driver-manager starts before pcid and acpid. The PCI bus
/// scheme (`/scheme/pci`) is registered by pcid, which is spawned by hwd
/// (service 40). The ACPI bus scheme (`/scheme/acpi`) is registered by acpid
/// (service 41). Without waiting, enumeration fails with 0 devices and the
/// deferred retry loop (3 × 500ms) is too short.
fn wait_for_scheme(name: &str, timeout: Duration) -> bool {
let path = format!("/scheme/{}", name);
let start = Instant::now();
let poll_interval = Duration::from_millis(100);
while start.elapsed() < timeout {
// Use read_dir instead of Path::exists() — Redox scheme paths
// may not respond to exists()/metadata() while still being
// functional for directory enumeration.
if fs::read_dir(&path).is_ok() {
log::info!(
"scheme {} available after {}ms",
name,
start.elapsed().as_millis()
);
return true;
}
thread::sleep(poll_interval);
}
log::warn!(
"scheme {} not available after {}ms",
name,
timeout.as_millis()
);
false
}
fn run_enumeration(
manager: &Arc<Mutex<DeviceManager>>,
scheme: &DriverManagerScheme,
initfs: bool,
) -> (usize, usize) {
let enum_start = Instant::now();
let events = match manager.lock() {
Ok(mut mgr) => mgr.enumerate(),
Err(err) => {
log::error!("failed to enumerate devices: manager lock poisoned: {err}");
return (0, 0);
}
};
let enum_duration = enum_start.elapsed();
let mut bound = 0usize;
let mut deferred = 0usize;
for event in &events {
log_timeline(event);
match event {
ProbeEvent::ProbeCompleted {
device,
driver_name,
result,
} => match result {
ProbeResult::Bound => {
log::info!("bound: {} -> {}", device.path, driver_name);
notify_bound_device(scheme, device, driver_name);
bound += 1;
}
ProbeResult::Deferred { reason } => {
log::info!("deferred: {} -> {} ({})", device.path, driver_name, reason);
deferred += 1;
}
ProbeResult::Fatal { reason } => {
log::error!("fatal: {} -> {} ({})", device.path, driver_name, reason);
}
_ => {}
},
ProbeEvent::BusEnumerated { bus, device_count } => {
log::info!("bus {} enumerated {} device(s)", bus, device_count);
}
ProbeEvent::BusEnumerationFailed { bus, error } => {
if initfs && *bus == "pci" {
log::warn!("bus {} enumeration not yet ready (initfs, pcid may still be starting): {:?}", bus, error);
} else {
log::error!("bus {} enumeration failed: {:?}", bus, error);
}
}
ProbeEvent::AlreadyBound {
device,
driver_name,
} => {
log::debug!("already bound: {} -> {}", device.path, driver_name);
}
ProbeEvent::NoDriverFound { device } => {
log::info!("no driver found for {} device {}", device.bus, device.path);
}
ProbeEvent::MissingDriver {
device,
driver_name,
} => {
log::error!(
"deferred probe lost driver: {} device {} wanted {}",
device.bus,
device.path,
driver_name
);
}
}
}
log::info!(
"enumeration complete: {} bound, {} deferred ({}ms total)",
bound,
deferred,
enum_duration.as_millis()
);
(bound, deferred)
}
fn notify_bound_device(scheme: &DriverManagerScheme, device: &DeviceId, driver_name: &str) {
// Notify for both PCI and ACPI devices
notify_bind(scheme, &device.path, driver_name);
}
fn reset_timeline_log() {
// Best-effort: truncate or create empty. On scheme filesystems that
// don't support truncate on existing files, this may fail — that's OK,
// the append path will handle it.
match fs::write(BOOT_TIMELINE_PATH, "") {
Ok(()) => {}
Err(_) => {
let _ = fs::remove_file(BOOT_TIMELINE_PATH);
}
}
}
fn json_escape(value: &str) -> String {
let mut escaped = String::with_capacity(value.len());
for ch in value.chars() {
match ch {
'\\' => escaped.push_str("\\\\"),
'"' => escaped.push_str("\\\""),
'\n' => escaped.push_str("\\n"),
'\r' => escaped.push_str("\\r"),
'\t' => escaped.push_str("\\t"),
ch if ch.is_control() => escaped.push_str(&format!("\\u{:04x}", ch as u32)),
ch => escaped.push(ch),
}
}
escaped
}
fn log_timeline(event: &ProbeEvent) {
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis();
let entry = match event {
ProbeEvent::BusEnumerated { bus, device_count } => format!(
r#"{{"ts":{},"event":"bus_enumerated","bus":"{}","count":{}}}"#,
timestamp,
json_escape(bus),
device_count
),
ProbeEvent::BusEnumerationFailed { bus, error } => format!(
r#"{{"ts":{},"event":"bus_enumeration_failed","bus":"{}","error":"{}"}}"#,
timestamp,
json_escape(bus),
json_escape(&format!("{error:?}"))
),
ProbeEvent::ProbeCompleted {
device,
driver_name,
result,
} => {
let status = match result {
ProbeResult::Bound => "bound",
ProbeResult::Deferred { .. } => "deferred",
ProbeResult::Fatal { .. } => "failed",
ProbeResult::NotSupported => "skipped",
};
format!(
r#"{{"ts":{},"event":"probe","device":"{}","driver":"{}","status":"{}"}}"#,
timestamp,
json_escape(&device.path),
json_escape(driver_name),
status
)
}
ProbeEvent::NoDriverFound { device } => format!(
r#"{{"ts":{},"event":"no_driver","bus":"{}","device":"{}"}}"#,
timestamp,
json_escape(&device.bus),
json_escape(&device.path)
),
ProbeEvent::AlreadyBound {
device,
driver_name,
} => format!(
r#"{{"ts":{},"event":"already_bound","bus":"{}","device":"{}","driver":"{}"}}"#,
timestamp,
json_escape(&device.bus),
json_escape(&device.path),
json_escape(driver_name)
),
ProbeEvent::MissingDriver {
device,
driver_name,
} => format!(
r#"{{"ts":{},"event":"missing_driver","bus":"{}","device":"{}","driver":"{}"}}"#,
timestamp,
json_escape(&device.bus),
json_escape(&device.path),
json_escape(driver_name)
),
};
match OpenOptions::new()
.create(true)
.append(true)
.open(BOOT_TIMELINE_PATH)
{
Ok(mut file) => {
if let Err(err) = writeln!(file, "{entry}") {
// EPIPE or other write errors can occur when /tmp is backed
// by a scheme that doesn't support append writes, or when the
// filesystem is not yet fully ready. Log once and suppress
// all subsequent write errors to avoid log spam.
static WRITE_ERROR_LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
if !WRITE_ERROR_LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
log::warn!("failed to append boot timeline entry to {BOOT_TIMELINE_PATH}: {err} (suppressing further write errors)");
}
}
}
Err(err) => {
// EEXIST (os error 17) can occur when the file already exists
// but the scheme filesystem doesn't support create+append.
// EPIPE and other errors occur when /tmp isn't ready.
// Log once and suppress all subsequent open errors.
static OPEN_ERROR_LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
if !OPEN_ERROR_LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
log::warn!("failed to open boot timeline log at {BOOT_TIMELINE_PATH}: {err} (suppressing further open errors)");
}
}
}
}
fn run_status() {
// Print the boot timeline log if it exists.
match fs::read_to_string(BOOT_TIMELINE_PATH) {
Ok(content) => {
if content.trim().is_empty() {
println!("No boot timeline data found at {}", BOOT_TIMELINE_PATH);
println!("Driver manager has not completed enumeration yet.");
return;
}
println!("=== Red Bear OS Driver Manager Status ===");
println!();
let mut bound = 0usize;
let mut deferred = 0usize;
let mut failed = 0usize;
let mut no_driver = 0usize;
let mut buses = Vec::new();
for line in content.lines() {
if line.trim().is_empty() {
continue;
}
// Parse JSON timeline entries
if line.contains("\"event\":\"bus_enumerated\"") {
if let Some(bus) = extract_json_string(line, "bus") {
if let Some(count) = extract_json_number(line, "count") {
buses.push((bus, count));
}
}
} else if line.contains("\"status\":\"bound\"") {
bound += 1;
} else if line.contains("\"status\":\"deferred\"") {
deferred += 1;
} else if line.contains("\"status\":\"failed\"") {
failed += 1;
} else if line.contains("\"event\":\"no_driver\"") {
no_driver += 1;
}
}
println!("Bus enumeration:");
for (bus, count) in &buses {
println!(" {}: {} device(s)", bus, count);
}
println!();
println!("Driver binding:");
println!(" bound: {}", bound);
println!(" deferred: {}", deferred);
println!(" failed: {}", failed);
println!(" no driver: {}", no_driver);
println!();
println!("Timeline log: {}", BOOT_TIMELINE_PATH);
}
Err(err) => {
println!("Cannot read {}: {}", BOOT_TIMELINE_PATH, err);
println!("Driver manager may not have run yet.");
}
}
}
/// Extract a JSON string value for a given key from a single-line JSON object.
fn extract_json_string(line: &str, key: &str) -> Option<String> {
let pattern = format!("\"{}\":\"", key);
let start = line.find(&pattern)?;
let value_start = start + pattern.len();
let end = line[value_start..].find('"')?;
Some(line[value_start..value_start + end].to_string())
}
/// Extract a JSON number value for a given key from a single-line JSON object.
fn extract_json_number(line: &str, key: &str) -> Option<usize> {
let pattern = format!("\"{}\":", key);
let start = line.find(&pattern)?;
let value_start = start + pattern.len();
let rest = &line[value_start..];
let end = rest.find(|c: char| !c.is_ascii_digit()).unwrap_or(rest.len());
rest[..end].parse().ok()
}
fn main() {
log::set_logger(&StderrLogger).ok();
log::set_max_level(log::LevelFilter::Info);
// Install SIGTERM handler for graceful shutdown
install_sigterm_handler();
let args: Vec<String> = env::args().collect();
let initfs = args.iter().any(|a| a == "--initfs");
let hotplug_mode = args.iter().any(|a| a == "--hotplug");
let status_mode = args.iter().any(|a| a == "--status");
// --status: print the current device registry from the boot timeline log
// and exit. This is for diagnostics: "what did driver-manager find?"
if status_mode {
run_status();
return;
}
let config_dir = if initfs {
"/scheme/initfs/lib/drivers.d"
} else {
"/lib/drivers.d"
};
let driver_configs = match DriverConfig::load_all(config_dir) {
Ok(c) => c,
Err(e) => {
log::error!("failed to load driver configs: {}", e);
process::exit(1);
}
};
if driver_configs.is_empty() {
log::warn!("no driver configs found in {}", config_dir);
process::exit(0);
}
log::info!("loaded {} driver config(s)", driver_configs.len());
let manager_config = ManagerConfig {
max_concurrent_probes: 4,
deferred_retry_ms: 500,
async_probe: !initfs,
};
let manager = Arc::new(Mutex::new(DeviceManager::new(manager_config.clone())));
let scheme = Arc::new(DriverManagerScheme::new());
match manager.lock() {
Ok(mut mgr) => {
// Register PCI bus first (higher priority — storage, network, GPU).
// Mirrors Linux's pci_scan_child_bus() via subsys_initcall.
mgr.register_bus(Box::new(PciBus::new()));
// Register ACPI bus for platform/I2C/SPI/GPIO/thermal devices.
// Mirrors Linux's acpi_bus_scan() which walks the namespace for
// _HID/_CID/_STA/_CRS. ACPI devices are enumerated from
// /scheme/acpi/symbols/ which acpid populates from the AML
// interpreter.
mgr.register_bus(Box::new(AcpiBus::new()));
for dc in &driver_configs {
mgr.register_driver(Box::new(dc.clone()));
}
}
Err(err) => {
log::error!("failed to configure driver manager: manager lock poisoned: {err}");
process::exit(1);
}
}
let mgr_clone = Arc::clone(&manager);
let scheme_clone = Arc::clone(&scheme);
// Ensure /tmp exists before writing the boot timeline log.
let _ = std::fs::create_dir_all("/tmp");
reset_timeline_log();
// In initfs mode, pcid (spawned by hwd at service 40) may not have
// registered /scheme/pci yet when driver-manager starts at service 00.
// Wait for required bus schemes to appear before enumerating.
if initfs {
wait_for_scheme("pci", Duration::from_secs(30));
wait_for_scheme("acpi", Duration::from_secs(10));
}
if manager_config.async_probe {
let handle = thread::spawn(move || {
let (bound, deferred) = run_enumeration(&mgr_clone, scheme_clone.as_ref(), initfs);
log::info!("async enum: {} bound, {} deferred", bound, deferred);
});
if handle.join().is_err() {
log::error!("initial enumeration thread panicked");
process::exit(1);
}
} else {
let (bound, deferred) = run_enumeration(&manager, scheme.as_ref(), initfs);
log::info!("enum complete: {} bound, {} deferred", bound, deferred);
}
match scheme::start_scheme_server(Arc::clone(&scheme)) {
Ok(true) => {
log::info!("driver-manager: scheme server started successfully");
}
Ok(false) => {
log::warn!("driver-manager: scheme already registered — another instance is active, continuing without scheme server");
}
Err(err) => {
log::error!("{err}");
process::exit(1);
}
}
if hotplug_mode {
log::info!("entering hotplug event loop");
hotplug::run_hotplug_loop(manager.clone(), scheme.clone(), 2000);
idle_forever();
}
// In initfs mode: do bounded retries for deferred probes, then exit
// so init can proceed to rootfs mount. The rootfs driver-manager
// instance (started by the rootfs service) handles hotplug and
// persistent management.
if initfs {
let max_retries = 10u32;
for retry in 1..=max_retries {
if SHUTDOWN_REQUESTED.load(Ordering::SeqCst) {
log::info!("driver-manager: SIGTERM received, shutting down");
graceful_shutdown();
process::exit(0);
}
reap_all_drivers(&driver_configs);
thread::sleep(Duration::from_millis(500));
let retry_events = match manager.lock() {
Ok(mut mgr) => mgr.retry_deferred(),
Err(err) => {
log::error!("failed to retry deferred probes: manager lock poisoned: {err}");
break;
}
};
let mut remaining = 0;
let mut newly_bound = 0;
for event in &retry_events {
log_timeline(event);
if let ProbeEvent::ProbeCompleted {
device,
driver_name,
result,
} = event
{
match result {
ProbeResult::Bound => {
newly_bound += 1;
notify_bound_device(scheme.as_ref(), device, driver_name);
}
ProbeResult::Deferred { .. } => remaining += 1,
_ => {}
}
}
}
if remaining == 0 {
log::info!("all deferred resolved after {} retries (initfs)", retry);
break;
}
if newly_bound > 0 {
log::info!(
"initfs retry #{}: {} new, {} remaining",
retry,
newly_bound,
remaining
);
}
}
log::info!("driver-manager: initfs phase complete, exiting");
return;
}
let max_retries = 3u32;
for retry in 1..=max_retries {
if SHUTDOWN_REQUESTED.load(Ordering::SeqCst) {
log::info!("driver-manager: SIGTERM received during deferred retry, shutting down");
graceful_shutdown();
process::exit(0);
}
// Check for crashed drivers during retry loop
reap_all_drivers(&driver_configs);
thread::sleep(Duration::from_millis(500));
let retry_events = match manager.lock() {
Ok(mut mgr) => mgr.retry_deferred(),
Err(err) => {
log::error!("failed to retry deferred probes: manager lock poisoned: {err}");
process::exit(1);
}
};
let mut remaining = 0;
let mut newly_bound = 0;
for event in &retry_events {
log_timeline(event);
if let ProbeEvent::ProbeCompleted {
device,
driver_name,
result,
} = event
{
match result {
ProbeResult::Bound => {
newly_bound += 1;
notify_bound_device(scheme.as_ref(), device, driver_name);
}
ProbeResult::Deferred { .. } => remaining += 1,
_ => {}
}
}
}
if remaining == 0 {
log::info!("all deferred resolved after {} retries", retry);
idle_forever();
}
if newly_bound > 0 {
log::info!(
"retry #{}: {} new, {} remaining",
retry,
newly_bound,
remaining
);
}
}
log::warn!("deferred probe retry limit reached");
idle_forever();
}
fn idle_forever() -> ! {
log::info!("driver-manager: entering persistent idle loop");
loop {
thread::sleep(Duration::from_secs(5));
if SHUTDOWN_REQUESTED.load(Ordering::SeqCst) {
log::info!("driver-manager: SIGTERM received, performing graceful shutdown");
graceful_shutdown();
process::exit(0);
}
// Periodically check for exited child drivers
reap_all_drivers(&[]);
}
}
/// Poll all driver configs for exited children and log the results.
fn reap_all_drivers(driver_configs: &[DriverConfig]) {
for dc in driver_configs {
let exited = dc.reap_exited_children();
for (device_key, driver_name, code) in &exited {
log::warn!(
"reaped crashed driver: {} for device {} (exit {})",
driver_name,
device_key,
code
);
}
}
}
fn graceful_shutdown() {
// The DeviceManager and spawned children are managed by DriverConfig instances
// which track their child processes. On shutdown, we log and exit cleanly.
// Child processes will be orphaned but the kernel reaps them.
log::info!("driver-manager: clean shutdown complete");
}