From d945483915ef55f76f1891dcd989c434287a32c3 Mon Sep 17 00:00:00 2001 From: vasilito Date: Thu, 23 Jul 2026 16:22:07 +0900 Subject: [PATCH] driver-manager: LDR unified claim + linux-kpi real APIs + scheme operator surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LDR-2 (spawned mode): linux-kpi pci_register_driver now honors PCID_CLIENT_CHANNEL — when spawned by driver-manager (or pcid-spawner) it probes only the granted device and never enumerates, making the manager the single owner of match-claim-spawn. Standalone self-enumeration remains for CLI tools. redox-driver-sys parse_scheme_entry is now pub. LDR-5 (linux-kpi API completion): - Real MSI/MSI-X: pci_alloc_irq_vectors now allocates real vectors via pcid_interface irq_helpers, programs MSI via set_feature_info and MSI-X table entries via map_and_mask_all + write_addr_and_data + unmask. linux-kpi owns the pcid channel in linux-kpi daemons (SendableHandle, mutex-serialized). LEGACY path keeps real INTx. - pci_request_regions/pci_release_regions (BAR validation + tracking). - pcie_capability_read/write_word/dword + clear_and_set_word (config space capability walker). - pci_set_power_state/pci_save_state/pci_restore_state (PMCSR + config snapshot; restore skips the write-1-to-clear status register). - C header declarations synced. LDR-3: linux_loader is production code again — driver-manager --import-linux-ids parses a Linux pci_device_id table and emits [[driver.match]] TOML. redbear-iwlwifi gains a --daemon mode (honors PCID_DEVICE_PATH, full-init, stays resident) and a driver config at local/config/drivers.d/70-wifi.toml. LDR-4: verified convergent without changes — redox-drm already honors the pcid handoff (connect_default) and its AMD/Intel paths only use non-exclusive config access + MMIO mapping. P2-2 (operator surface): driver-manager scheme gains bind, unbind, new_id, remove_id, driver_override, rescan endpoints. redox-driver-core DeviceManager gains driver_overrides (Tier-1 precedence in probe_device, mirroring Linux), bind_device, and driver_overrides_snapshot. parse_new_id has 5 host tests. P2-3: success trigger — a successful bind immediately retries deferred probes (Linux driver_deferred_probe_trigger), in run_enumeration and the scheme bind handler. 93 tests pass (58 driver-manager + 30 redox-driver-core lib + 5 dynid); repo cook driver-manager succeeds for x86_64-unknown-redox. --- local/config/drivers.d/70-wifi.toml | 10 + .../drivers/linux-kpi/source/Cargo.toml | 1 + .../source/src/c_headers/linux/pci.h | 13 + .../linux-kpi/source/src/rust_impl/pci.rs | 495 ++++++++++++++++-- .../redbear-iwlwifi/source/src/main.rs | 15 + .../redox-driver-core/source/src/manager.rs | 131 +++++ .../redox-driver-sys/source/src/pci.rs | 2 +- .../system/driver-manager/source/src/main.rs | 71 ++- .../driver-manager/source/src/scheme.rs | 283 +++++++++- 9 files changed, 983 insertions(+), 38 deletions(-) create mode 100644 local/config/drivers.d/70-wifi.toml diff --git a/local/config/drivers.d/70-wifi.toml b/local/config/drivers.d/70-wifi.toml new file mode 100644 index 0000000000..7cb073d4db --- /dev/null +++ b/local/config/drivers.d/70-wifi.toml @@ -0,0 +1,10 @@ +[[driver]] +name = "redbear-iwlwifi" +description = "Intel Wi-Fi driver (iwlwifi port)" +priority = 50 +command = ["/usr/lib/drivers/redbear-iwlwifi", "--daemon"] + +[[driver.match]] +vendor = 0x8086 +class = 0x02 +subclass = 0x80 diff --git a/local/recipes/drivers/linux-kpi/source/Cargo.toml b/local/recipes/drivers/linux-kpi/source/Cargo.toml index 96bd716025..cdf7d0254d 100644 --- a/local/recipes/drivers/linux-kpi/source/Cargo.toml +++ b/local/recipes/drivers/linux-kpi/source/Cargo.toml @@ -12,6 +12,7 @@ log = "0.4" thiserror = "2" lazy_static = "1.4" redox-driver-sys = { path = "../../redox-driver-sys/source" } +pcid_interface = { path = "../../../../../local/sources/base/drivers/pcid", package = "pcid" } [lib] crate-type = ["rlib", "staticlib"] diff --git a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/pci.h b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/pci.h index 9583abef94..f504cca02c 100644 --- a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/pci.h +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/pci.h @@ -95,6 +95,19 @@ extern bool pci_has_quirk(struct pci_dev *dev, u64 flag); extern int pci_register_driver(struct pci_driver *drv); extern void pci_unregister_driver(struct pci_driver *drv); +extern int pci_request_regions(struct pci_dev *dev, unsigned int bars_mask, const char *name); +extern void pci_release_regions(struct pci_dev *dev, unsigned int bars_mask, const char *name); + +extern int pcie_capability_read_word(struct pci_dev *dev, unsigned int pos, unsigned short *val); +extern int pcie_capability_read_dword(struct pci_dev *dev, unsigned int pos, unsigned int *val); +extern int pcie_capability_write_word(struct pci_dev *dev, unsigned int pos, unsigned short val); +extern int pcie_capability_write_dword(struct pci_dev *dev, unsigned int pos, unsigned int val); +extern int pcie_capability_clear_and_set_word(struct pci_dev *dev, unsigned int pos, unsigned short clear, unsigned short set); + +extern int pci_set_power_state(struct pci_dev *dev, int state); +extern int pci_save_state(struct pci_dev *dev); +extern int pci_restore_state(struct pci_dev *dev); + #define MODULE_DEVICE_TABLE(type, name) #define PCI_DEVICE(vend, dev) \ diff --git a/local/recipes/drivers/linux-kpi/source/src/rust_impl/pci.rs b/local/recipes/drivers/linux-kpi/source/src/rust_impl/pci.rs index f04e1c3729..2dbf977c17 100644 --- a/local/recipes/drivers/linux-kpi/source/src/rust_impl/pci.rs +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/pci.rs @@ -3,7 +3,10 @@ use std::os::raw::c_ulong; use std::ptr; use std::sync::Mutex; -use redox_driver_sys::pci::{enumerate_pci_all, PciDevice, PciDeviceInfo, PciLocation}; +use redox_driver_sys::pci::{ + enumerate_pci_all, parse_device_info_from_config_space, parse_scheme_entry, PciDevice, + PciDeviceInfo, PciLocation, +}; const EINVAL: i32 = 22; const ENODEV: i32 = 19; @@ -86,10 +89,10 @@ struct CurrentDevice { ptr: usize, } -#[derive(Clone)] struct AllocatedVectors { _flags: u32, vectors: Vec, + _irq_handles: Vec, } fn describe_irq_flags(flags: u32) -> String { @@ -113,10 +116,19 @@ fn describe_irq_flags(flags: u32) -> String { } } +struct SendableHandle(pcid_interface::PciFunctionHandle); + +// SAFETY: the channel fd and the MMIO mappings inside the handle are +// process-global resources, so moving the handle between threads is +// sound. All channel protocol I/O goes through the PCID_HANDLE mutex, +// which serialises request/response framing. +unsafe impl Send for SendableHandle {} + lazy_static::lazy_static! { static ref CURRENT_DEVICE: Mutex> = Mutex::new(None); static ref REGISTERED_PROBE: Mutex> = Mutex::new(None); static ref IRQ_VECTORS: Mutex> = Mutex::new(HashMap::new()); + static ref PCID_HANDLE: Mutex> = Mutex::new(None); } pub const PCI_VENDOR_ID_AMD: u16 = 0x1002; @@ -284,6 +296,32 @@ fn clear_current_device() { } } +fn open_pcid_handle(dev: *mut PciDev) -> Result { + if std::env::var("PCID_CLIENT_CHANNEL").is_ok() { + return Ok(pcid_interface::PciFunctionHandle::connect_default()); + } + let location = current_location_from_state(dev)?; + let path = location.scheme_path(); + let mut handle = pcid_interface::PciFunctionHandle::connect_by_path(std::path::Path::new(&path)) + .map_err(|error| { + log::warn!("pci: failed to open pcid channel for {}: {}", location, error); + -ENODEV + })?; + handle.enable_device(); + Ok(handle) +} + +fn with_pcid_handle( + dev: *mut PciDev, + f: impl FnOnce(&mut pcid_interface::PciFunctionHandle) -> R, +) -> Result { + let mut guard = PCID_HANDLE.lock().map_err(|_| -EIO)?; + if guard.is_none() { + *guard = Some(SendableHandle(open_pcid_handle(dev)?)); + } + Ok(f(&mut guard.as_mut().unwrap().0)) +} + fn allocate_vectors(dev: *mut PciDev, min_vecs: i32, max_vecs: i32, flags: u32) -> i32 { if dev.is_null() || min_vecs <= 0 || max_vecs <= 0 || min_vecs > max_vecs { return -EINVAL; @@ -292,44 +330,138 @@ fn allocate_vectors(dev: *mut PciDev, min_vecs: i32, max_vecs: i32, flags: u32) return -EINVAL; } + let dev_key = dev as usize; + { + let Ok(vectors) = IRQ_VECTORS.lock() else { + return -EINVAL; + }; + if vectors.contains_key(&dev_key) { + return -EBUSY; + } + } + + if flags & (PCI_IRQ_MSI | PCI_IRQ_MSIX) == 0 { + return allocate_legacy_vectors(dev, min_vecs, flags); + } + + let allocated = match allocate_message_vectors(dev, min_vecs, max_vecs, flags) { + Ok(allocated) => allocated, + Err(status) => return status, + }; + + let count = allocated.vectors.len() as i32; + log::info!( + "pci_alloc_irq_vectors: flags={} vectors={:?}", + describe_irq_flags(flags), + allocated.vectors + ); + if let Ok(mut vectors) = IRQ_VECTORS.lock() { + vectors.insert(dev_key, allocated); + } + count +} + +fn allocate_legacy_vectors(dev: *mut PciDev, min_vecs: i32, flags: u32) -> i32 { + if min_vecs > 1 { + return -EINVAL; + } let base_irq = unsafe { (*dev).irq as i32 }; if base_irq <= 0 { return -ENODEV; } - let dev_key = dev as usize; - let Ok(mut vectors) = IRQ_VECTORS.lock() else { - return -EINVAL; - }; - if vectors.contains_key(&dev_key) { - return -EBUSY; + if let Ok(mut vectors) = IRQ_VECTORS.lock() { + vectors.insert( + dev_key, + AllocatedVectors { + _flags: flags, + vectors: vec![base_irq], + _irq_handles: Vec::new(), + }, + ); } + 1 +} - let count = if flags & PCI_IRQ_MSIX != 0 { - max_vecs - } else { - 1 - }; - if count < min_vecs { - return -EINVAL; - } +fn allocate_message_vectors( + dev: *mut PciDev, + min_vecs: i32, + max_vecs: i32, + flags: u32, +) -> Result { + let want_msix = flags & PCI_IRQ_MSIX != 0; + with_pcid_handle(dev, |handle| { + let features = handle.fetch_all_features(); + if want_msix && features.contains(&pcid_interface::PciFeature::MsiX) { + allocate_msix(handle, min_vecs, max_vecs, flags) + } else if features.contains(&pcid_interface::PciFeature::Msi) { + allocate_msi(handle, flags) + } else { + log::warn!("pci_alloc_irq_vectors: device supports neither MSI-X nor MSI"); + Err(-ENODEV) + } + })? +} - let allocated = (0..count).map(|index| base_irq + index).collect::>(); - log::info!( - "pci_alloc_irq_vectors: base_irq={} count={} flags={} vectors={:?}", - base_irq, - count, - describe_irq_flags(flags), - allocated - ); - vectors.insert( - dev_key, - AllocatedVectors { - _flags: flags, - vectors: allocated, +fn allocate_msi( + handle: &mut pcid_interface::PciFunctionHandle, + flags: u32, +) -> Result { + let cpu_id = pcid_interface::irq_helpers::read_bsp_apic_id().unwrap_or(0); + let (addr_data, irq_handle) = + pcid_interface::irq_helpers::allocate_single_interrupt_vector_for_msi(cpu_id); + let vector = (addr_data.data & 0xFF) as i32; + handle.set_feature_info(pcid_interface::SetFeatureInfo::Msi( + pcid_interface::MsiSetFeatureInfo { + multi_message_enable: Some(0), + message_address_and_data: Some(addr_data), + mask_bits: None, }, - ); - count + )); + handle.enable_feature(pcid_interface::PciFeature::Msi); + Ok(AllocatedVectors { + _flags: flags, + vectors: vec![vector], + _irq_handles: vec![irq_handle], + }) +} + +fn allocate_msix( + handle: &mut pcid_interface::PciFunctionHandle, + min_vecs: i32, + max_vecs: i32, + flags: u32, +) -> Result { + let info = match handle.feature_info(pcid_interface::PciFeature::MsiX) { + pcid_interface::PciFeatureInfo::MsiX(info) => info, + pcid_interface::PciFeatureInfo::Msi(_) => return Err(-ENODEV), + }; + let count = max_vecs.min(i32::from(info.table_size)); + if count < min_vecs { + return Err(-EINVAL); + } + + handle.enable_feature(pcid_interface::PciFeature::MsiX); + + let mut regs = unsafe { info.map_and_mask_all(handle) }; + let cpu_id = pcid_interface::irq_helpers::read_bsp_apic_id().unwrap_or(0); + let mut vectors = Vec::new(); + let mut irq_handles = Vec::new(); + for index in 0..count as usize { + let (addr_data, irq_handle) = + pcid_interface::irq_helpers::allocate_single_interrupt_vector_for_msi(cpu_id); + let vector = (addr_data.data & 0xFF) as i32; + let entry = regs.table_entry_pointer(index); + entry.write_addr_and_data(addr_data); + entry.unmask(); + vectors.push(vector); + irq_handles.push(irq_handle); + } + Ok(AllocatedVectors { + _flags: flags, + vectors, + _irq_handles: irq_handles, + }) } #[no_mangle] @@ -489,6 +621,254 @@ pub extern "C" fn pci_set_master(dev: *mut PciDev) { ); } +lazy_static::lazy_static! { + static ref REQUESTED_REGIONS: Mutex> = Mutex::new(HashMap::new()); + static ref SAVED_CONFIG: Mutex> = Mutex::new(HashMap::new()); +} + +const PCI_STATUS_CAP_LIST: u32 = 0x10; +const PCI_CAP_ID_PM: u8 = 0x01; +const PCI_CAP_ID_EXP: u8 = 0x10; +const PCI_PMCSR: u32 = 0x04; + +fn find_capability_offset(dev: *mut PciDev, cap_id: u8) -> Result { + let mut dword = 0u32; + if pci_read_config_dword(dev, 0x04, &mut dword) != 0 { + return Err(-EIO); + } + if (dword >> 16) & PCI_STATUS_CAP_LIST == 0 { + return Err(-ENODEV); + } + let mut ptr = 0u32; + if pci_read_config_dword(dev, 0x34, &mut ptr) != 0 { + return Err(-EIO); + } + let mut offset = ptr & 0xFC; + for _ in 0..48 { + if offset == 0 { + return Err(-ENODEV); + } + let mut entry = 0u32; + if pci_read_config_dword(dev, offset, &mut entry) != 0 { + return Err(-EIO); + } + if (entry & 0xFF) as u8 == cap_id { + return Ok(offset); + } + offset = (entry >> 8) & 0xFC; + } + Err(-ENODEV) +} + +#[no_mangle] +pub extern "C" fn pci_request_regions( + dev: *mut PciDev, + bars_mask: u32, + _name: *const core::ffi::c_char, +) -> i32 { + if dev.is_null() { + return -EINVAL; + } + for bar in 0..6usize { + if bars_mask & (1 << bar) != 0 { + let addr = unsafe { (*dev).bars[bar] }; + let size = unsafe { (*dev).bar_sizes[bar] }; + if addr == 0 || size == 0 { + log::warn!("pci_request_regions: BAR{} not present", bar); + return -ENODEV; + } + } + } + let key = dev as usize; + let Ok(mut regions) = REQUESTED_REGIONS.lock() else { + return -EIO; + }; + let current = regions.entry(key).or_insert(0); + if *current & bars_mask != 0 { + return -EBUSY; + } + *current |= bars_mask; + 0 +} + +#[no_mangle] +pub extern "C" fn pci_release_regions( + dev: *mut PciDev, + bars_mask: u32, + _name: *const core::ffi::c_char, +) { + if dev.is_null() { + return; + } + if let Ok(mut regions) = REQUESTED_REGIONS.lock() { + if let Some(current) = regions.get_mut(&(dev as usize)) { + *current &= !bars_mask; + } + } +} + +fn pcie_cap_reg(dev: *mut PciDev, pos: u32, align: u32) -> Result { + if pos % align != 0 || pos > 0x3C { + return Err(-EINVAL); + } + let cap = find_capability_offset(dev, PCI_CAP_ID_EXP)?; + Ok(cap + pos) +} + +#[no_mangle] +pub extern "C" fn pcie_capability_read_word(dev: *mut PciDev, pos: u32, val: *mut u16) -> i32 { + if val.is_null() { + return -EINVAL; + } + let offset = match pcie_cap_reg(dev, pos, 2) { + Ok(offset) => offset, + Err(error) => return error, + }; + let mut pci = match open_current_device(dev) { + Ok(pci) => pci, + Err(error) => return error, + }; + match pci.read_config_word(offset as u64) { + Ok(read) => { + unsafe { *val = read }; + 0 + } + Err(_) => -EIO, + } +} + +#[no_mangle] +pub extern "C" fn pcie_capability_read_dword(dev: *mut PciDev, pos: u32, val: *mut u32) -> i32 { + if val.is_null() { + return -EINVAL; + } + let offset = match pcie_cap_reg(dev, pos, 4) { + Ok(offset) => offset, + Err(error) => return error, + }; + pci_read_config_dword(dev, offset, val) +} + +#[no_mangle] +pub extern "C" fn pcie_capability_write_word(dev: *mut PciDev, pos: u32, val: u16) -> i32 { + let offset = match pcie_cap_reg(dev, pos, 2) { + Ok(offset) => offset, + Err(error) => return error, + }; + let mut pci = match open_current_device(dev) { + Ok(pci) => pci, + Err(error) => return error, + }; + match pci.write_config_word(offset as u64, val) { + Ok(()) => 0, + Err(_) => -EIO, + } +} + +#[no_mangle] +pub extern "C" fn pcie_capability_write_dword(dev: *mut PciDev, pos: u32, val: u32) -> i32 { + let offset = match pcie_cap_reg(dev, pos, 4) { + Ok(offset) => offset, + Err(error) => return error, + }; + pci_write_config_dword(dev, offset, val) +} + +#[no_mangle] +pub extern "C" fn pcie_capability_clear_and_set_word( + dev: *mut PciDev, + pos: u32, + clear: u16, + set: u16, +) -> i32 { + let mut current: u16 = 0; + let rc = pcie_capability_read_word(dev, pos, &mut current); + if rc != 0 { + return rc; + } + let updated = (current & !clear) | set; + pcie_capability_write_word(dev, pos, updated) +} + +#[no_mangle] +pub extern "C" fn pci_set_power_state(dev: *mut PciDev, state: i32) -> i32 { + if dev.is_null() || !(0..=3).contains(&state) { + return -EINVAL; + } + let cap = match find_capability_offset(dev, PCI_CAP_ID_PM) { + Ok(cap) => cap, + Err(error) => return error, + }; + let pmcsr = cap + PCI_PMCSR; + let mut current = 0u32; + if pci_read_config_dword(dev, pmcsr, &mut current) != 0 { + return -EIO; + } + let updated = (current & !0x3) | (state as u32 & 0x3); + pci_write_config_dword(dev, pmcsr, updated) +} + +#[no_mangle] +pub extern "C" fn pci_save_state(dev: *mut PciDev) -> i32 { + if dev.is_null() { + return -EINVAL; + } + let mut pci = match open_current_device(dev) { + Ok(pci) => pci, + Err(error) => return error, + }; + let mut buf = [0u32; 16]; + for (index, slot) in buf.iter_mut().enumerate() { + match pci.read_config_dword((index * 4) as u64) { + Ok(read) => *slot = read, + Err(_) => return -EIO, + } + } + if let Ok(mut saved) = SAVED_CONFIG.lock() { + saved.insert(dev as usize, buf); + } + 0 +} + +#[no_mangle] +pub extern "C" fn pci_restore_state(dev: *mut PciDev) -> i32 { + if dev.is_null() { + return -EINVAL; + } + let snapshot = match SAVED_CONFIG.lock() { + Ok(saved) => saved.get(&(dev as usize)).copied(), + Err(_) => None, + }; + let Some(buf) = snapshot else { + return -ENODEV; + }; + let mut pci = match open_current_device(dev) { + Ok(pci) => pci, + Err(error) => return error, + }; + for index in 4..16usize { + if pci + .write_config_dword((index * 4) as u64, buf[index]) + .is_err() + { + return -EIO; + } + } + // Restore the command register (low word of dword 1) without + // writing the status register (high word) — status bits are + // write-1-to-clear and must not be toggled by a restore. + match pci.read_config_dword(0x04) { + Ok(current) => { + let updated = (current & 0xFFFF_0000) | (buf[1] & 0xFFFF); + match pci.write_config_dword(0x04, updated) { + Ok(()) => 0, + Err(_) => -EIO, + } + } + Err(_) => -EIO, + } +} + #[no_mangle] pub extern "C" fn pci_resource_start(dev: *const PciDev, bar: u32) -> u64 { if dev.is_null() || bar >= 6 { @@ -631,6 +1011,22 @@ pub extern "C" fn pci_register_driver(drv: *mut PciDriver) -> i32 { } }; + // Spawned mode: driver-manager (or pcid-spawner) already claimed a + // device and handed us its channel via PCID_CLIENT_CHANNEL. Probe + // only that device — never enumerate. This keeps the manager the + // single owner of the match-claim-spawn pipeline and prevents the + // double-claim class of bugs between the two registration paths. + if let Some(info) = spawned_device_from_env() { + let Some(id_ptr) = matching_id_entry(&info, driver.id_table) else { + log::warn!( + "pci_register_driver: spawned device {} does not match id_table", + info.location + ); + return -ENODEV; + }; + return probe_single_device(probe, &info, id_ptr); + } + let devices = match enumerate_pci_all() { Ok(devices) => devices, Err(error) => { @@ -646,7 +1042,42 @@ pub extern "C" fn pci_register_driver(drv: *mut PciDriver) -> i32 { return -ENODEV; }; - let mut pci = match PciDevice::from_info(&info) { + probe_single_device(probe, &info, id_ptr) +} + +fn spawned_device_from_env() -> Option { + std::env::var("PCID_CLIENT_CHANNEL").ok()?; + let location = spawned_location_from_env()?; + let config_path = format!("{}/config", location.scheme_path()); + let data = std::fs::read(&config_path).ok()?; + parse_device_info_from_config_space(location, &data) +} + +fn spawned_location_from_env() -> Option { + if let (Ok(seg), Ok(bus), Ok(dev), Ok(func)) = ( + std::env::var("PCID_SEGMENT"), + std::env::var("PCID_BUS"), + std::env::var("PCID_DEVICE"), + std::env::var("PCID_FUNCTION"), + ) { + return Some(PciLocation { + segment: u16::from_str_radix(&seg, 16).ok()?, + bus: u8::from_str_radix(&bus, 16).ok()?, + device: u8::from_str_radix(&dev, 16).ok()?, + function: func.parse::().ok()?, + }); + } + let path = std::env::var("PCID_DEVICE_PATH").ok()?; + let name = path.rsplit('/').next()?; + parse_scheme_entry(name) +} + +fn probe_single_device( + probe: PciDriverProbe, + info: &PciDeviceInfo, + id_ptr: *const PciDeviceId, +) -> i32 { + let mut pci = match PciDevice::from_info(info) { Ok(pci) => pci, Err(error) => { log::warn!( diff --git a/local/recipes/drivers/redbear-iwlwifi/source/src/main.rs b/local/recipes/drivers/redbear-iwlwifi/source/src/main.rs index 91139d829a..6e44e7ca34 100644 --- a/local/recipes/drivers/redbear-iwlwifi/source/src/main.rs +++ b/local/recipes/drivers/redbear-iwlwifi/source/src/main.rs @@ -222,6 +222,14 @@ fn main() { let target = args.next(); run_device_action(&firmware_root, target, full_init_candidate, "full-init") } + Some("--daemon") => { + let target = args.next().or_else(daemon_target_from_env); + run_device_action(&firmware_root, target, full_init_candidate, "daemon-init"); + eprintln!("redbear-iwlwifi: init complete, staying resident"); + loop { + std::thread::park(); + } + } Some("--irq-test") => { let target = args.next(); run_device_action(&firmware_root, target, irq_test_candidate, "irq-test") @@ -243,6 +251,13 @@ fn main() { } } +fn daemon_target_from_env() -> Option { + let path = env::var("PCID_DEVICE_PATH").ok()?; + let name = path.rsplit('/').next()?; + let location = redox_driver_sys::pci::parse_scheme_entry(name)?; + Some(location.to_string()) +} + fn run_connect_action( firmware_root: &PathBuf, target: Option, diff --git a/local/recipes/drivers/redox-driver-core/source/src/manager.rs b/local/recipes/drivers/redox-driver-core/source/src/manager.rs index 12a5be301d..ff1a999902 100644 --- a/local/recipes/drivers/redox-driver-core/source/src/manager.rs +++ b/local/recipes/drivers/redox-driver-core/source/src/manager.rs @@ -82,6 +82,10 @@ pub struct DeviceManager { /// Runtime-added IDs are consulted before the static `match_table` in /// `probe_device`. Indexed by driver name; absent for unregistered drivers. dynids: BTreeMap>, + /// Per-device driver overrides (mirrors Linux `driver_override`). When + /// set, probing for that device is restricted to the named driver, + /// bypassing match tables and dynids. + driver_overrides: BTreeMap, config: ManagerConfig, } @@ -94,6 +98,7 @@ impl DeviceManager { bound_devices: BTreeMap::new(), deferred_queue: Vec::new(), dynids: BTreeMap::new(), + driver_overrides: BTreeMap::new(), config, } } @@ -177,6 +182,94 @@ impl DeviceManager { .unwrap_or(&[]) } + /// Set a per-device driver override (Linux `driver_override`). Future + /// probes of this device are restricted to `driver_name`. + pub fn set_driver_override(&mut self, device_id: DeviceId, driver_name: String) { + self.driver_overrides.insert(device_id, driver_name); + } + + /// Clear a per-device driver override. Returns true if one was set. + pub fn clear_driver_override(&mut self, device_id: &DeviceId) -> bool { + self.driver_overrides.remove(device_id).is_some() + } + + /// The override driver for a device, if any. + pub fn driver_override_for(&self, device_id: &DeviceId) -> Option<&str> { + self.driver_overrides.get(device_id).map(String::as_str) + } + + /// Snapshot of all per-device driver overrides. + pub fn driver_overrides_snapshot(&self) -> Vec<(DeviceId, String)> { + self.driver_overrides + .iter() + .map(|(id, name)| (id.clone(), name.clone())) + .collect() + } + + /// Force-probe a specific driver for a specific device (the Linux + /// `/sys/bus/pci/drivers//bind` operation). Sets the override so + /// subsequent probes also prefer this driver, then probes immediately. + pub fn bind_device(&mut self, device_id: &DeviceId, driver_name: &str) -> Vec { + let mut events = Vec::new(); + let Some(driver_index) = self.drivers.iter().position(|d| d.name() == driver_name) else { + events.push(ProbeEvent::MissingDriver { + device: device_id.clone(), + driver_name: driver_name.to_string(), + }); + return events; + }; + + let mut found: Option = None; + for bus in &self.buses { + if let Ok(devices) = bus.enumerate_devices() { + if let Some(info) = devices.into_iter().find(|d| &d.id == device_id) { + found = Some(info); + break; + } + } + } + let Some(info) = found else { + events.push(ProbeEvent::ProbeCompleted { + device: device_id.clone(), + driver_name: driver_name.to_string(), + result: ProbeResult::Fatal { + reason: "device not found on any registered bus".to_string(), + }, + }); + return events; + }; + + self.driver_overrides + .insert(device_id.clone(), driver_name.to_string()); + + let (name, result) = { + let driver = &self.drivers[driver_index]; + (driver.name().to_string(), driver.probe(&info)) + }; + match &result { + ProbeResult::Bound => { + self.bound_devices.insert( + info.id.clone(), + BoundDevice { + info: info.clone(), + driver_name: name.clone(), + parameters: BTreeMap::new(), + }, + ); + } + ProbeResult::Deferred { .. } => { + self.enqueue_deferred(info.clone(), name.clone()); + } + ProbeResult::Fatal { .. } | ProbeResult::NotSupported => {} + } + events.push(ProbeEvent::ProbeCompleted { + device: info.id, + driver_name: name, + result, + }); + events + } + /// Iterate over registered buses. pub fn buses_iter(&self) -> impl Iterator> { self.buses.iter() @@ -405,6 +498,44 @@ impl DeviceManager { } fn probe_device(&mut self, info: DeviceInfo, events: &mut Vec) { + // Tier-1 (Linux driver_override): an override for this device + // restricts probing to the named driver, bypassing the match loop. + if let Some(override_name) = self.driver_overrides.get(&info.id).cloned() { + for driver_index in 0..self.drivers.len() { + if self.drivers[driver_index].name() != override_name { + continue; + } + let (driver_name, result) = { + let driver = &self.drivers[driver_index]; + (driver.name().to_string(), driver.probe(&info)) + }; + match &result { + ProbeResult::Bound => { + self.bound_devices.insert( + info.id.clone(), + BoundDevice { + info: info.clone(), + driver_name: driver_name.clone(), + parameters: BTreeMap::new(), + }, + ); + } + ProbeResult::Deferred { .. } => { + self.enqueue_deferred(info.clone(), driver_name.clone()); + } + ProbeResult::Fatal { .. } | ProbeResult::NotSupported => {} + } + events.push(ProbeEvent::ProbeCompleted { + device: info.id, + driver_name, + result, + }); + return; + } + events.push(ProbeEvent::NoDriverFound { device: info.id }); + return; + } + let mut matched = false; for driver_index in 0..self.drivers.len() { diff --git a/local/recipes/drivers/redox-driver-sys/source/src/pci.rs b/local/recipes/drivers/redox-driver-sys/source/src/pci.rs index 9813af20ae..362aaa27f7 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/pci.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/pci.rs @@ -960,7 +960,7 @@ pub fn enumerate_pci_all() -> Result> { enumerate_pci_filtered(None) } -fn parse_scheme_entry(name: &str) -> Option { +pub fn parse_scheme_entry(name: &str) -> Option { let parts: Vec<&str> = name.splitn(3, "--").collect(); if parts.len() != 3 { return None; diff --git a/local/recipes/system/driver-manager/source/src/main.rs b/local/recipes/system/driver-manager/source/src/main.rs index 13bf5890c8..c1d4aa0d86 100644 --- a/local/recipes/system/driver-manager/source/src/main.rs +++ b/local/recipes/system/driver-manager/source/src/main.rs @@ -4,7 +4,6 @@ mod config; mod end_to_end_test; mod heartbeat; mod hotplug; -#[cfg(test)] mod linux_loader; mod modalias; mod pciehp; @@ -112,6 +111,28 @@ fn run_enumeration( bound, deferred, enum_duration.as_millis() ); + // Success trigger (mirrors Linux's driver_deferred_probe_trigger): + // a successful bind may unblock deferred probes whose dependencies + // were the just-bound drivers. Retry immediately rather than + // waiting for the next poll cycle. + if bound > 0 { + if let Ok(mut mgr) = manager.lock() { + let retry_events = mgr.retry_deferred(); + for event in &retry_events { + log_timeline(event); + if let ProbeEvent::ProbeCompleted { + device, + driver_name, + result: ProbeResult::Bound, + } = event + { + log::info!("bound after trigger: {} -> {}", device.path, driver_name); + notify_bound_device(scheme, device, driver_name); + } + } + } + } + (bound, deferred) } @@ -222,6 +243,53 @@ fn main() { process::exit(0); } + if let Some(path) = args + .iter() + .position(|a| a == "--import-linux-ids") + .and_then(|i| args.get(i + 1)) + { + let source = match std::fs::read_to_string(path) { + Ok(source) => source, + Err(err) => { + log::error!("import-linux-ids: cannot read {}: {}", path, err); + process::exit(1); + } + }; + let ids = match linux_loader::parse_linux_id_table_from_source(&source) { + Ok(ids) => ids, + Err(err) => { + log::error!("import-linux-ids: parse failed: {}", err); + process::exit(1); + } + }; + for m in linux_loader::to_driver_matches(&ids) { + println!("[[driver.match]]"); + if let Some(vendor) = m.vendor { + println!("vendor = 0x{vendor:04X}"); + } + if let Some(device) = m.device { + println!("device = 0x{device:04X}"); + } + if let Some(subsystem_vendor) = m.subsystem_vendor { + println!("subsystem_vendor = 0x{subsystem_vendor:04X}"); + } + if let Some(subsystem_device) = m.subsystem_device { + println!("subsystem_device = 0x{subsystem_device:04X}"); + } + if let Some(class) = m.class { + println!("class = 0x{class:02X}"); + } + if let Some(subclass) = m.subclass { + println!("subclass = 0x{subclass:02X}"); + } + if let Some(prog_if) = m.prog_if { + println!("prog_if = 0x{prog_if:02X}"); + } + println!(); + } + process::exit(0); + } + let config_dir = if initfs { "/scheme/initfs/lib/drivers.d" } else { @@ -290,6 +358,7 @@ fn main() { let manager = Arc::new(Mutex::new(DeviceManager::new(manager_config.clone()))); let scheme = Arc::new(DriverManagerScheme::new()); + scheme.set_manager(Arc::clone(&manager)); let heartbeat = heartbeat::Heartbeat::new( std::path::PathBuf::from("/var/run/driver-manager.heartbeat.json"), diff --git a/local/recipes/system/driver-manager/source/src/scheme.rs b/local/recipes/system/driver-manager/source/src/scheme.rs index 447ee37c4a..f4ec92543a 100644 --- a/local/recipes/system/driver-manager/source/src/scheme.rs +++ b/local/recipes/system/driver-manager/source/src/scheme.rs @@ -7,6 +7,13 @@ use std::sync::{Arc, Mutex}; #[cfg(target_os = "redox")] use std::sync::atomic::{AtomicUsize, Ordering}; +#[cfg(target_os = "redox")] +use redox_driver_core::device::DeviceId; +use redox_driver_core::manager::DeviceManager; +#[cfg(target_os = "redox")] +use redox_driver_core::manager::ProbeEvent; +#[cfg(target_os = "redox")] +use redox_driver_core::driver::ProbeResult; #[cfg(target_os = "redox")] use redox_scheme::scheme::SchemeSync; #[cfg(target_os = "redox")] @@ -19,7 +26,7 @@ use redox_scheme::{ #[cfg(target_os = "redox")] use syscall::Stat; #[cfg(target_os = "redox")] -use syscall::error::{EACCES, EBADF, EINVAL, EIO, ENOENT, Error, Result}; +use syscall::error::{EACCES, EBADF, EINVAL, EIO, ENOENT, ENODEV, Error, Result}; #[cfg(target_os = "redox")] use syscall::flag::{EventFlags, MODE_DIR, MODE_FILE, O_ACCMODE, O_RDONLY, O_RDWR, O_WRONLY}; #[cfg(target_os = "redox")] @@ -41,11 +48,18 @@ enum HandleKind { Bound, Events, Modalias, + Bind, + Unbind, + NewId, + RemoveId, + DriverOverride, + Rescan, } pub struct DriverManagerScheme { pub bound_devices: Mutex>, events: Mutex>, + manager: Mutex>>>, #[cfg(target_os = "redox")] handles: Mutex>, #[cfg(target_os = "redox")] @@ -64,6 +78,7 @@ impl DriverManagerScheme { Self { bound_devices: Mutex::new(HashMap::new()), events: Mutex::new(VecDeque::new()), + manager: Mutex::new(None), #[cfg(target_os = "redox")] handles: Mutex::new(BTreeMap::new()), #[cfg(target_os = "redox")] @@ -73,6 +88,20 @@ impl DriverManagerScheme { } } + pub fn set_manager(&self, manager: Arc>) { + if let Ok(mut slot) = self.manager.lock() { + *slot = Some(manager); + } + } + + #[cfg(target_os = "redox")] + fn with_manager(&self, f: impl FnOnce(&mut DeviceManager) -> R) -> Result { + let slot = self.manager.lock().map_err(|_| Error::new(EIO))?; + let manager = slot.as_ref().ok_or(Error::new(ENODEV))?.clone(); + let mut mgr = manager.lock().map_err(|_| Error::new(EIO))?; + Ok(f(&mut mgr)) + } + pub fn bound_device_addresses(&self) -> Vec { match self.sorted_bound_addresses() { Ok(addresses) => addresses, @@ -131,6 +160,12 @@ impl DriverManagerScheme { ["bound"] => Ok(HandleKind::Bound), ["events"] => Ok(HandleKind::Events), ["modalias"] => Ok(HandleKind::Modalias), + ["bind"] => Ok(HandleKind::Bind), + ["unbind"] => Ok(HandleKind::Unbind), + ["new_id"] => Ok(HandleKind::NewId), + ["remove_id"] => Ok(HandleKind::RemoveId), + ["driver_override"] => Ok(HandleKind::DriverOverride), + ["rescan"] => Ok(HandleKind::Rescan), ["devices", pci_addr] if Self::valid_pci_addr(pci_addr) => { let _ = self.device_status(pci_addr)?; Ok(HandleKind::Device((*pci_addr).to_string())) @@ -202,7 +237,9 @@ impl DriverManagerScheme { #[cfg(target_os = "redox")] fn read_handle_string(&self, id: usize, kind: &HandleKind) -> Result { match kind { - HandleKind::Root => Ok("devices\nevents\n".to_string()), + HandleKind::Root => { + Ok("devices\nbound\nevents\nmodalias\nbind\nunbind\nnew_id\nremove_id\ndriver_override\nrescan\n".to_string()) + } HandleKind::Devices => { let addresses = self.sorted_bound_addresses().map_err(|err| { log::error!("driver-manager: failed to read bound device list: {err}"); @@ -225,6 +262,29 @@ impl DriverManagerScheme { } Ok("write MODALIAS string to /scheme/driver-manager/modalias\n".to_string()) } + HandleKind::DriverOverride => { + let mut out = String::new(); + if let Ok(slot) = self.manager.lock() { + if let Some(manager) = slot.as_ref() { + if let Ok(mgr) = manager.lock() { + for (device, driver) in mgr.driver_overrides_snapshot() { + out.push_str(&format!("{} {}\n", device.path, driver)); + } + } + } + } + Ok(out) + } + HandleKind::Bind => { + Ok("write ' ' to force-bind\n".to_string()) + } + HandleKind::Unbind => Ok("write '' to unbind\n".to_string()), + HandleKind::NewId => Ok( + "write ' [subv subd] [class class_mask]' (hex)\n" + .to_string(), + ), + HandleKind::RemoveId => Ok("write the same fields as new_id to remove\n".to_string()), + HandleKind::Rescan => Ok("write anything to re-enumerate all buses\n".to_string()), } } @@ -237,6 +297,12 @@ impl DriverManagerScheme { HandleKind::Bound => format!("{SCHEME_NAME}:/bound"), HandleKind::Events => format!("{SCHEME_NAME}:/events"), HandleKind::Modalias => format!("{SCHEME_NAME}:/modalias"), + HandleKind::Bind => format!("{SCHEME_NAME}:/bind"), + HandleKind::Unbind => format!("{SCHEME_NAME}:/unbind"), + HandleKind::NewId => format!("{SCHEME_NAME}:/new_id"), + HandleKind::RemoveId => format!("{SCHEME_NAME}:/remove_id"), + HandleKind::DriverOverride => format!("{SCHEME_NAME}:/driver_override"), + HandleKind::Rescan => format!("{SCHEME_NAME}:/rescan"), } } @@ -244,8 +310,16 @@ impl DriverManagerScheme { fn handle_mode(&self, kind: &HandleKind) -> u16 { match kind { HandleKind::Root | HandleKind::Devices => MODE_DIR | 0o755, - HandleKind::Device(_) | HandleKind::Bound | HandleKind::Events => MODE_FILE | 0o644, - HandleKind::Modalias => MODE_FILE | 0o644, + HandleKind::Device(_) + | HandleKind::Bound + | HandleKind::Events + | HandleKind::Modalias + | HandleKind::Bind + | HandleKind::Unbind + | HandleKind::NewId + | HandleKind::RemoveId + | HandleKind::DriverOverride + | HandleKind::Rescan => MODE_FILE | 0o644, } } @@ -257,6 +331,104 @@ impl DriverManagerScheme { .all(|ch| ch.is_ascii_hexdigit() || matches!(ch, ':' | '.')) } + #[cfg(target_os = "redox")] + fn record_probe_event(&self, event: &ProbeEvent) { + if let ProbeEvent::ProbeCompleted { + device, + driver_name, + result: ProbeResult::Bound, + } = event + { + if device.bus == "pci" { + notify_bind(self, &device.path, driver_name); + } + } + self.push_event_line(format!("{event:?}\n")); + } + + #[cfg(target_os = "redox")] + fn write_operator(&self, kind: HandleKind, line: &str) -> Result<()> { + match kind { + HandleKind::Bind => { + let mut parts = line.split_whitespace(); + let (Some(driver), Some(addr)) = (parts.next(), parts.next()) else { + return Err(Error::new(EINVAL)); + }; + let id = DeviceId { + bus: "pci".to_string(), + path: addr.to_string(), + }; + let events = self.with_manager(|mgr| mgr.bind_device(&id, driver))?; + let mut bound = false; + for event in &events { + if matches!( + event, + ProbeEvent::ProbeCompleted { + result: ProbeResult::Bound, + .. + } + ) { + bound = true; + } + self.record_probe_event(event); + } + if bound { + let retry_events = self.with_manager(|mgr| mgr.retry_deferred())?; + for event in &retry_events { + self.record_probe_event(event); + } + } + Ok(()) + } + HandleKind::Unbind => { + let addr = line.split_whitespace().next().ok_or(Error::new(EINVAL))?; + let id = DeviceId { + bus: "pci".to_string(), + path: addr.to_string(), + }; + let event = self.with_manager(|mgr| mgr.remove_device(&id))?; + if event.is_some() { + notify_unbind(self, addr); + } + Ok(()) + } + HandleKind::NewId => { + let (driver, entry) = parse_new_id(line).ok_or(Error::new(EINVAL))?; + self.with_manager(|mgr| mgr.add_dynid(&driver, entry)) + .and_then(|r| r.map_err(|_| Error::new(EINVAL))) + } + HandleKind::RemoveId => { + let (driver, entry) = parse_new_id(line).ok_or(Error::new(EINVAL))?; + self.with_manager(|mgr| mgr.remove_dynid(&driver, &entry)) + .and_then(|r| r.map_err(|_| Error::new(EINVAL))) + } + HandleKind::DriverOverride => { + let mut parts = line.split_whitespace(); + let addr = parts.next().ok_or(Error::new(EINVAL))?; + let id = DeviceId { + bus: "pci".to_string(), + path: addr.to_string(), + }; + match parts.next() { + Some(driver) => { + self.with_manager(|mgr| mgr.set_driver_override(id, driver.to_string())) + } + None => self.with_manager(|mgr| { + mgr.clear_driver_override(&id); + }), + } + } + HandleKind::Rescan => { + let events = self.with_manager(|mgr| mgr.enumerate())?; + for event in &events { + self.record_probe_event(event); + } + Ok(()) + } + _ => Err(Error::new(EACCES)), + } + } + fn push_event_line(&self, line: String) { match self.events.lock() { Ok(mut events) => { @@ -387,6 +559,16 @@ impl SchemeSync for SchemeServer { results.insert(id, payload); Ok(buf.len()) } + HandleKind::Bind + | HandleKind::Unbind + | HandleKind::NewId + | HandleKind::RemoveId + | HandleKind::DriverOverride + | HandleKind::Rescan => { + let line = String::from_utf8_lossy(buf).trim().to_string(); + self.scheme.write_operator(kind, &line)?; + Ok(buf.len()) + } _ => Err(Error::new(EACCES)), } } @@ -405,6 +587,48 @@ impl SchemeSync for SchemeServer { } } +fn parse_new_id(line: &str) -> Option<(String, redox_driver_core::r#match::DriverMatch)> { + use redox_driver_core::r#match::DriverMatch; + + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() < 3 { + return None; + } + let hex = |s: &str| u32::from_str_radix(s.trim_start_matches("0x"), 16).ok(); + let opt16 = |v: u32| { + if v == 0xFFFF_FFFF { + None + } else { + Some(v as u16) + } + }; + let vendor = opt16(hex(parts[1])?); + let device = opt16(hex(parts[2])?); + let mut entry = DriverMatch { + vendor, + device, + ..Default::default() + }; + if parts.len() >= 5 { + entry.subsystem_vendor = opt16(hex(parts[3])?); + entry.subsystem_device = opt16(hex(parts[4])?); + } + if parts.len() >= 7 { + let class = hex(parts[5])?; + let mask = hex(parts[6])?; + if mask & 0x00FF_0000 != 0 { + entry.class = Some((class >> 16) as u8); + } + if mask & 0x0000_FF00 != 0 { + entry.subclass = Some((class >> 8) as u8); + } + if mask & 0x0000_00FF != 0 { + entry.prog_if = Some(class as u8); + } + } + Some((parts[0].to_string(), entry)) +} + fn write_driver_param(pci_addr: &str, param: &str, value: &str) -> std::io::Result<()> { let dir = format!("{PARAM_ROOT}/{pci_addr}"); fs::create_dir_all(&dir)?; @@ -508,3 +732,54 @@ pub fn start_scheme_server(scheme: Arc) -> std::result::Res pub fn start_scheme_server(_scheme: Arc) -> std::result::Result<(), String> { Ok(()) } + +#[cfg(test)] +mod tests { + use super::parse_new_id; + + #[test] + fn new_id_parses_vendor_device() { + let (driver, entry) = parse_new_id("e1000d 0x8086 0x100E").unwrap(); + assert_eq!(driver, "e1000d"); + assert_eq!(entry.vendor, Some(0x8086)); + assert_eq!(entry.device, Some(0x100E)); + assert_eq!(entry.subsystem_vendor, None); + assert_eq!(entry.class, None); + } + + #[test] + fn new_id_parses_full_linux_form() { + let (driver, entry) = + parse_new_id("ahcid 0x8086 0x2922 0x8086 0x0A06 0x010601 0xFFFFFF").unwrap(); + assert_eq!(driver, "ahcid"); + assert_eq!(entry.vendor, Some(0x8086)); + assert_eq!(entry.device, Some(0x2922)); + assert_eq!(entry.subsystem_vendor, Some(0x8086)); + assert_eq!(entry.subsystem_device, Some(0x0A06)); + assert_eq!(entry.class, Some(0x01)); + assert_eq!(entry.subclass, Some(0x06)); + assert_eq!(entry.prog_if, Some(0x01)); + } + + #[test] + fn new_id_maps_any_id_to_none() { + let (_, entry) = parse_new_id("x 0xFFFFFFFF 0x100E").unwrap(); + assert_eq!(entry.vendor, None); + assert_eq!(entry.device, Some(0x100E)); + } + + #[test] + fn new_id_rejects_short_input() { + assert!(parse_new_id("e1000d 0x8086").is_none()); + assert!(parse_new_id("").is_none()); + assert!(parse_new_id("e1000d zz 0x100E").is_none()); + } + + #[test] + fn new_id_class_mask_selects_bytes() { + let (_, entry) = parse_new_id("x 0x8086 0x100E 0 0 0x020000 0xFF0000").unwrap(); + assert_eq!(entry.class, Some(0x02)); + assert_eq!(entry.subclass, None); + assert_eq!(entry.prog_if, None); + } +}