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:
2026-07-23 16:22:07 +09:00
parent c6fb24ae28
commit d945483915
9 changed files with 983 additions and 38 deletions
+10
View File
@@ -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);
}
}