use std::collections::{BTreeMap, BTreeSet}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; use redox_driver_core::device::DeviceId; use redox_driver_core::driver::ProbeResult; use redox_driver_core::manager::DeviceManager; use redox_driver_core::manager::ProbeEvent; use crate::scheme::{DriverManagerScheme, notify_bind, notify_unbind}; /// Maximum times a single deferred device+driver pair is retried before /// permanently abandoning it. Deferred means a dependency scheme (e.g. /// scheme:firmware) is not yet ready, so the device may bind later. const MAX_DEFERRED_RETRIES: u32 = 5; pub fn run_hotplug_loop( manager: Arc>, scheme: Arc, poll_interval_ms: u64, ) { log::info!( "hotplug: starting event loop ({} ms poll)", poll_interval_ms ); let mut deferred_retries: BTreeMap<(String, String), u32> = BTreeMap::new(); loop { thread::sleep(Duration::from_millis(poll_interval_ms)); let events = match manager.lock() { Ok(mut mgr) => mgr.enumerate(), Err(err) => { log::error!("hotplug: failed to enumerate devices: manager lock poisoned: {err}"); break; } }; let mut seen_pci_devices = BTreeSet::new(); let mut pci_enumerated = false; for event in &events { match event { ProbeEvent::BusEnumerated { bus, .. } => { if bus == "pci" { pci_enumerated = true; } } ProbeEvent::BusEnumerationFailed { bus, error } => { log::error!("hotplug: bus {} enumeration failed: {:?}", bus, error); } ProbeEvent::AlreadyBound { device, driver_name, } => { track_pci_device(device, &mut seen_pci_devices); log::debug!("hotplug: already bound {} -> {}", device.path, driver_name); } ProbeEvent::ProbeCompleted { device, driver_name, result, } => { track_pci_device(device, &mut seen_pci_devices); let key = (device.path.clone(), driver_name.clone()); match result { ProbeResult::Bound => { log::info!("hotplug: bound {} -> {}", device.path, driver_name); notify_bound_device(scheme.as_ref(), device, driver_name); } ProbeResult::Deferred { reason } => { let retries = deferred_retries.entry(key).or_insert(0); *retries += 1; if *retries <= MAX_DEFERRED_RETRIES { log::info!( "hotplug: deferred {} -> {} ({})", device.path, driver_name, reason ); } else if *retries == MAX_DEFERRED_RETRIES + 1 { log::warn!( "hotplug: giving up on {} -> {} after {} retries: {}", device.path, driver_name, MAX_DEFERRED_RETRIES, reason ); if let Ok(mut skipped) = crate::config::PERMANENTLY_SKIPPED.lock() { skipped.insert(( device.path.clone(), driver_name.clone(), )); } } } ProbeResult::Fatal { reason } => { log::error!( "hotplug: fatal {} -> {} ({})", device.path, driver_name, reason ); if let Ok(mut skipped) = crate::config::PERMANENTLY_SKIPPED.lock() { skipped.insert(key); } } ProbeResult::NotSupported => { log::debug!( "hotplug: not supported {} -> {}", device.path, driver_name ); if let Ok(mut skipped) = crate::config::PERMANENTLY_SKIPPED.lock() { skipped.insert(key); } } } } ProbeEvent::NoDriverFound { device } => { track_pci_device(device, &mut seen_pci_devices); log::debug!("hotplug: no driver for new device {}", device.path); } _ => {} } } if pci_enumerated { for pci_addr in scheme.bound_device_addresses() { if !seen_pci_devices.contains(&pci_addr) { let device_id = DeviceId { bus: String::from("pci"), path: pci_addr.clone(), }; match manager.lock() { Ok(mut mgr) => match mgr.remove_device(&device_id) { Ok(Some(driver_name)) => { log::info!("hotplug: removed {} from {}", pci_addr, driver_name); notify_unbind(scheme.as_ref(), &pci_addr); } Ok(core::option::Option::None) => { log::warn!( "hotplug: {} disappeared but had no manager binding", pci_addr ); notify_unbind(scheme.as_ref(), &pci_addr); } Err(err) => { log::error!( "hotplug: failed to detach removed device {}: {:?}", pci_addr, err ); } }, Err(err) => { log::error!( "hotplug: failed to detach removed device {}: manager lock poisoned: {}", pci_addr, err ); break; } } } } } let retry_events = match manager.lock() { Ok(mut mgr) => mgr.retry_deferred(), Err(err) => { log::error!( "hotplug: failed to retry deferred probes: manager lock poisoned: {err}" ); break; } }; let mut resolved = 0usize; for event in &retry_events { if let ProbeEvent::ProbeCompleted { device, driver_name, result, } = event { if *result == ProbeResult::Bound { resolved += 1; notify_bound_device(scheme.as_ref(), device, driver_name); } } } if resolved > 0 { log::info!("hotplug: resolved {} deferred probes", resolved); } } } fn track_pci_device(device: &DeviceId, seen_pci_devices: &mut BTreeSet) { if device.bus == "pci" { seen_pci_devices.insert(device.path.clone()); } } fn notify_bound_device(scheme: &DriverManagerScheme, device: &DeviceId, driver_name: &str) { // PCI devices use the pcid-compatible bind notification. // ACPI devices may be notified through other mechanisms in the future. if device.bus == "pci" { notify_bind(scheme, &device.path, driver_name); } }