Files
RedBear-OS/local/recipes/system/driver-manager/source/src/main.rs
T

554 lines
19 KiB
Rust

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) {}
}
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: true,
};
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();
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();
}
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");
}