driver-manager: LDR unified claim + linux-kpi real APIs + scheme operator surface
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 <file.c> 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.
This commit is contained in:
@@ -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
|
||||
@@ -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"]
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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<i32>,
|
||||
_irq_handles: Vec<std::fs::File>,
|
||||
}
|
||||
|
||||
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<Option<CurrentDevice>> = Mutex::new(None);
|
||||
static ref REGISTERED_PROBE: Mutex<Option<PciDriverProbe>> = Mutex::new(None);
|
||||
static ref IRQ_VECTORS: Mutex<HashMap<usize, AllocatedVectors>> = Mutex::new(HashMap::new());
|
||||
static ref PCID_HANDLE: Mutex<Option<SendableHandle>> = 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<pcid_interface::PciFunctionHandle, i32> {
|
||||
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<R>(
|
||||
dev: *mut PciDev,
|
||||
f: impl FnOnce(&mut pcid_interface::PciFunctionHandle) -> R,
|
||||
) -> Result<R, i32> {
|
||||
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<AllocatedVectors, i32> {
|
||||
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::<Vec<_>>();
|
||||
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<AllocatedVectors, i32> {
|
||||
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<AllocatedVectors, i32> {
|
||||
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<HashMap<usize, u32>> = Mutex::new(HashMap::new());
|
||||
static ref SAVED_CONFIG: Mutex<HashMap<usize, [u32; 16]>> = 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<u32, i32> {
|
||||
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<u32, i32> {
|
||||
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<PciDeviceInfo> {
|
||||
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<PciLocation> {
|
||||
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::<u8>().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!(
|
||||
|
||||
@@ -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<String> {
|
||||
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<String>,
|
||||
|
||||
@@ -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<String, Vec<DriverMatch>>,
|
||||
/// 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<DeviceId, String>,
|
||||
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/<drv>/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<ProbeEvent> {
|
||||
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<DeviceInfo> = 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<Item = &Box<dyn Bus>> {
|
||||
self.buses.iter()
|
||||
@@ -405,6 +498,44 @@ impl DeviceManager {
|
||||
}
|
||||
|
||||
fn probe_device(&mut self, info: DeviceInfo, events: &mut Vec<ProbeEvent>) {
|
||||
// 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() {
|
||||
|
||||
@@ -960,7 +960,7 @@ pub fn enumerate_pci_all() -> Result<Vec<PciDeviceInfo>> {
|
||||
enumerate_pci_filtered(None)
|
||||
}
|
||||
|
||||
fn parse_scheme_entry(name: &str) -> Option<PciLocation> {
|
||||
pub fn parse_scheme_entry(name: &str) -> Option<PciLocation> {
|
||||
let parts: Vec<&str> = name.splitn(3, "--").collect();
|
||||
if parts.len() != 3 {
|
||||
return None;
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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<HashMap<String, String>>,
|
||||
events: Mutex<VecDeque<String>>,
|
||||
manager: Mutex<Option<Arc<std::sync::Mutex<DeviceManager>>>>,
|
||||
#[cfg(target_os = "redox")]
|
||||
handles: Mutex<BTreeMap<usize, HandleKind>>,
|
||||
#[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<std::sync::Mutex<DeviceManager>>) {
|
||||
if let Ok(mut slot) = self.manager.lock() {
|
||||
*slot = Some(manager);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn with_manager<R>(&self, f: impl FnOnce(&mut DeviceManager) -> R) -> Result<R> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
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 '<driver> <pci_addr>' to force-bind\n".to_string())
|
||||
}
|
||||
HandleKind::Unbind => Ok("write '<pci_addr>' to unbind\n".to_string()),
|
||||
HandleKind::NewId => Ok(
|
||||
"write '<driver> <vendor> <device> [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<DriverManagerScheme>) -> std::result::Res
|
||||
pub fn start_scheme_server(_scheme: Arc<DriverManagerScheme>) -> 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user