8822113df8
- redox-driver-core: DeviceManager stores drivers as Arc<dyn Driver>; ConcurrentDeviceManager jobs carry priority-ordered candidate lists (static match + dynids); workers invoke the real Driver::probe() with serial-equivalent per-device semantics. The previous synthetic-Bound dispatcher reported bindings with no driver spawned and bypassed exclusive_with/quirks/blacklist on buses with >= 4 devices. - scheme.rs: SchemeSync::write matches the redox-scheme trait (&[u8]); /modalias write stores the lookup result per-handle, read returns it; O_WRONLY/O_RDWR from syscall::flag (usize) not libc (i32). - config.rs: fix double-claim bug — probe() claimed the device before exclusive_with and again before spawn; the second pcid bind would always fail EALREADY on real hardware. One claim threaded to spawn. - main.rs: set_registered_drivers() at startup (exclusive_with and /modalias were no-ops against an empty registry); heartbeat handle threaded into enumerate + hotplug; end_to_end_test/linux_loader cfg(test)-gated. - reaper.rs/sighup.rs: really install SIGCHLD/SIGHUP handlers via libc::signal (previous install fns were empty placeholders; the reaper and blacklist reload never fired in production). - unified_events.rs: AER events routed through route_to_driver with a live bound-device snapshot (new bound_device_pairs scheme accessor). - Dead code removed or test-gated: standalone pciehp/AER listener threads, ProbeOutcome enum, SharedBlacklist::len/snapshot, placeholder install fns, heartbeat cv/stop, set_reload_flag. - 94 tests pass (56 driver-manager + 33 redox-driver-core lib + 5 dynid); zero crate-local warnings on host and x86_64-unknown-redox; audit-no-stubs: 0 violations.
389 lines
12 KiB
Rust
389 lines
12 KiB
Rust
//! Linux `pci_device_id` parsing for driver-manager.
|
|
//!
|
|
//! Reads a Linux driver's `pci_device_id` table from C source (the
|
|
//! `PCI_DEVICE`, `PCI_DEVICE_CLASS`, `PCI_VDEVICE` macros) and converts
|
|
//! each entry to a `redox_driver_core::r#match::DriverMatch`. Used to
|
|
//! generate `[[driver.match]]` TOML entries from a Linux driver's
|
|
//! existing `id_table` definition.
|
|
//!
|
|
//! The parser is a simple lexer — not a full C parser — so it handles
|
|
//! the most common `pci_device_id` shapes used by drivers in Linux 7.1
|
|
//! (see `include/linux/mod_devicetable.h`).
|
|
|
|
use std::fs;
|
|
use std::path::Path;
|
|
|
|
use redox_driver_core::r#match::DriverMatch;
|
|
|
|
/// One parsed Linux `pci_device_id` entry.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct LinuxPciId {
|
|
pub vendor: u16,
|
|
pub device: u16,
|
|
pub subvendor: u16,
|
|
pub subdevice: u16,
|
|
pub class: u32,
|
|
pub class_mask: u32,
|
|
}
|
|
|
|
impl LinuxPciId {
|
|
fn from_u32(vendor: u32, device: u32, subvendor: u32, subdevice: u32, class: u32, class_mask: u32) -> Self {
|
|
Self {
|
|
vendor: vendor as u16,
|
|
device: device as u16,
|
|
subvendor: subvendor as u16,
|
|
subdevice: subdevice as u16,
|
|
class,
|
|
class_mask,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Parse a Linux C source file for `pci_device_id` entries. Returns an
|
|
/// error if the file cannot be read or contains no entries.
|
|
pub fn parse_linux_id_table(path: &Path) -> Result<Vec<LinuxPciId>, String> {
|
|
let body = fs::read_to_string(path)
|
|
.map_err(|e| format!("read {} failed: {}", path.display(), e))?;
|
|
parse_linux_id_table_from_source(&body)
|
|
}
|
|
|
|
/// Parse `pci_device_id` entries from a string of C source. Useful for
|
|
/// tests and for callers that already have the source in memory.
|
|
pub fn parse_linux_id_table_from_source(source: &str) -> Result<Vec<LinuxPciId>, String> {
|
|
let mut out: Vec<LinuxPciId> = Vec::new();
|
|
let mut offset = 0usize;
|
|
|
|
while let Some(pos) = find_next_entry(source, offset)? {
|
|
offset = pos.end_pos;
|
|
out.push(pos.entry);
|
|
}
|
|
|
|
Ok(out)
|
|
}
|
|
|
|
struct EntryFound {
|
|
entry: LinuxPciId,
|
|
end_pos: usize,
|
|
}
|
|
|
|
fn find_next_entry(source: &str, from: usize) -> Result<Option<EntryFound>, String> {
|
|
let rest = &source[from..];
|
|
|
|
for prefix in &["PCI_DEVICE(", "PCI_DEVICE_CLASS(", "PCI_VDEVICE(", "PCI_DEVICE_SUB(", "PCI_DEVICE("] {
|
|
if let Some(start) = rest.find(prefix) {
|
|
let abs_start = from + start;
|
|
let after = &rest[start + prefix.len()..];
|
|
let parsed = parse_entry_at(after, prefix)?;
|
|
return Ok(Some(EntryFound {
|
|
entry: parsed.entry,
|
|
end_pos: abs_start + prefix.len() + parsed.consumed,
|
|
}));
|
|
}
|
|
}
|
|
Ok(None)
|
|
}
|
|
|
|
struct ParsedEntry {
|
|
entry: LinuxPciId,
|
|
consumed: usize,
|
|
}
|
|
|
|
fn parse_entry_at(source: &str, prefix: &str) -> Result<ParsedEntry, String> {
|
|
match prefix {
|
|
"PCI_DEVICE(" | "PCI_DEVICE_SUB(" => {
|
|
let (vals, consumed) = read_hex_list(source)?;
|
|
if vals.len() < 2 {
|
|
return Err(format!("PCI_DEVICE requires 2+ args, got {}", vals.len()));
|
|
}
|
|
let subvendor = vals.get(2).copied().unwrap_or(0xFFFF);
|
|
let subdevice = vals.get(3).copied().unwrap_or(0xFFFF);
|
|
Ok(ParsedEntry {
|
|
entry: LinuxPciId::from_u32(
|
|
vals[0],
|
|
vals[1],
|
|
subvendor,
|
|
subdevice,
|
|
0,
|
|
0,
|
|
),
|
|
consumed,
|
|
})
|
|
}
|
|
"PCI_DEVICE_CLASS(" => {
|
|
let (vals, consumed) = read_hex_list(source)?;
|
|
if vals.is_empty() {
|
|
return Err("PCI_DEVICE_CLASS requires at least 1 arg".to_string());
|
|
}
|
|
let class_mask = vals.get(1).copied().unwrap_or(0xFFFFFF);
|
|
let class = vals[0];
|
|
Ok(ParsedEntry {
|
|
entry: LinuxPciId::from_u32(0, 0, 0, 0, class, class_mask),
|
|
consumed,
|
|
})
|
|
}
|
|
"PCI_VDEVICE(" => {
|
|
let (vals, consumed) = read_hex_list(source)?;
|
|
if vals.len() < 2 {
|
|
return Err(format!("PCI_VDEVICE requires 2+ args, got {}", vals.len()));
|
|
}
|
|
Ok(ParsedEntry {
|
|
entry: LinuxPciId::from_u32(vals[0], vals[1], 0, 0, 0, 0),
|
|
consumed,
|
|
})
|
|
}
|
|
_ => Err(format!("unsupported prefix: {}", prefix)),
|
|
}
|
|
}
|
|
|
|
fn read_hex_list(source: &str) -> Result<(Vec<u32>, usize), String> {
|
|
let mut vals = Vec::new();
|
|
let mut i = 0usize;
|
|
let chars: Vec<char> = source.chars().collect();
|
|
let mut current = String::new();
|
|
while i < chars.len() {
|
|
match chars[i] {
|
|
'0'..='9' | 'a'..='f' | 'A'..='F' => {
|
|
current.push(chars[i]);
|
|
}
|
|
'x' | 'X' => {
|
|
current.push(chars[i]);
|
|
}
|
|
')' | '}' => {
|
|
if !current.is_empty() {
|
|
if let Some(v) = parse_hex(¤t) {
|
|
vals.push(v);
|
|
}
|
|
}
|
|
return Ok((vals, i));
|
|
}
|
|
',' | ' ' | '\t' | '\n' | '\r' => {
|
|
if !current.is_empty() {
|
|
if let Some(v) = parse_hex(¤t) {
|
|
vals.push(v);
|
|
current.clear();
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
current.push(chars[i]);
|
|
}
|
|
}
|
|
i += 1;
|
|
}
|
|
Ok((vals, i))
|
|
}
|
|
|
|
fn parse_hex(s: &str) -> Option<u32> {
|
|
let s = s.trim();
|
|
if s.is_empty() {
|
|
return None;
|
|
}
|
|
// Handle named vendor constants like PCI_VENDOR_ID_INTEL.
|
|
if let Some(v) = lookup_named_vendor_constant(s) {
|
|
return Some(v);
|
|
}
|
|
if let Some(hex) = s.strip_prefix("0x") {
|
|
u32::from_str_radix(hex, 16).ok()
|
|
} else {
|
|
s.parse::<u32>().ok()
|
|
}
|
|
}
|
|
|
|
/// Lookup named vendor constants from the Linux pci_device_id header
|
|
/// (PCI_VENDOR_ID_INTEL, PCI_VENDOR_ID_AMD, etc.).
|
|
fn lookup_named_vendor_constant(s: &str) -> Option<u32> {
|
|
// Map of common named constants used by Linux drivers (subset of
|
|
// the table in `include/linux/pci_ids.h`). Extend as needed.
|
|
let table: &[(&str, u32)] = &[
|
|
("PCI_VENDOR_ID_INTEL", 0x8086),
|
|
("PCI_VENDOR_ID_AMD", 0x1002),
|
|
("PCI_VENDOR_ID_NVIDIA", 0x10DE),
|
|
("PCI_VENDOR_ID_QCOM", 0x168C),
|
|
("PCI_VENDOR_ID_REALTEK", 0x10EC),
|
|
("PCI_VENDOR_ID_BROADCOM", 0x14E4),
|
|
("PCI_VENDOR_ID_AQUANTIA", 0x1D6A),
|
|
("PCI_VENDOR_ID_MARVELL", 0x11AB),
|
|
("PCI_VENDOR_ID_AMPERE", 0x1C5C),
|
|
("PCI_VENDOR_ID_MICROSOFT", 0x1414),
|
|
("PCI_VENDOR_ID_SONY", 0x104D),
|
|
("PCI_VENDOR_ID_TI", 0x104C),
|
|
("PCI_VENDOR_ID_RENESAS", 0x1912),
|
|
("PCI_VENDOR_ID_NOVELL", 0x8086),
|
|
("PCI_VENDOR_ID_SIS", 0x1039),
|
|
("PCI_VENDOR_ID_VIATECH", 0x1106),
|
|
("PCI_VENDOR_ID_HYGON", 0x1D94),
|
|
("INTEL", 0x8086),
|
|
("AMD", 0x1002),
|
|
("NVIDIA", 0x10DE),
|
|
("QCOM", 0x168C),
|
|
("REALTEK", 0x10EC),
|
|
("BROADCOM", 0x14E4),
|
|
("AQUANTIA", 0x1D6A),
|
|
("MARVELL", 0x11AB),
|
|
("AMPERE", 0x1C5C),
|
|
("MICROSOFT", 0x1414),
|
|
("SONY", 0x104D),
|
|
("TI", 0x104C),
|
|
("RENESAS", 0x1912),
|
|
("NOVELL", 0x8086),
|
|
("SIS", 0x1039),
|
|
("VIATECH", 0x1106),
|
|
("HYGON", 0x1D94),
|
|
];
|
|
table
|
|
.iter()
|
|
.find(|(name, _)| *name == s)
|
|
.map(|(_, v)| *v)
|
|
}
|
|
|
|
/// Convert a Linux `pci_device_id` entry to a `DriverMatch`. The
|
|
/// conversion preserves the Linux fields; the DriverMatch uses the
|
|
/// same meaning for each field. The Linux class field is a packed
|
|
/// 3-byte value `(base << 16) | (subclass << 8) | prog_if` and is
|
|
/// decoded back into separate class / subclass / prog_if fields here.
|
|
pub fn to_driver_match(id: &LinuxPciId) -> DriverMatch {
|
|
DriverMatch {
|
|
vendor: if id.vendor == 0 { None } else { Some(id.vendor) },
|
|
device: if id.device == 0 { None } else { Some(id.device) },
|
|
class: if id.class == 0 {
|
|
None
|
|
} else {
|
|
Some((id.class >> 16) as u8)
|
|
},
|
|
subclass: if id.class == 0 {
|
|
None
|
|
} else {
|
|
Some((id.class >> 8) as u8)
|
|
},
|
|
prog_if: if id.class == 0 {
|
|
None
|
|
} else {
|
|
Some(id.class as u8)
|
|
},
|
|
subsystem_vendor: if id.subvendor == 0xFFFF || id.subvendor == 0 {
|
|
None
|
|
} else {
|
|
Some(id.subvendor)
|
|
},
|
|
subsystem_device: if id.subdevice == 0xFFFF || id.subdevice == 0 {
|
|
None
|
|
} else {
|
|
Some(id.subdevice)
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Convert a list of Linux `pci_device_id` entries to `DriverMatch` entries.
|
|
pub fn to_driver_matches(ids: &[LinuxPciId]) -> Vec<DriverMatch> {
|
|
ids.iter().map(to_driver_match).collect()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn parse_pci_device_simple() {
|
|
let src = r#"
|
|
static const struct pci_device_id e1000_id_table[] = {
|
|
{ PCI_DEVICE(0x8086, 0x100E) },
|
|
{ 0 }
|
|
};
|
|
"#;
|
|
let ids = parse_linux_id_table_from_source(src).unwrap();
|
|
assert_eq!(ids.len(), 1);
|
|
assert_eq!(ids[0].vendor, 0x8086);
|
|
assert_eq!(ids[0].device, 0x100E);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_pci_device_sub() {
|
|
let src = r#"
|
|
{ PCI_DEVICE_SUB(0x8086, 0x100E, 0x8086, 0x0A06) }
|
|
"#;
|
|
let ids = parse_linux_id_table_from_source(src).unwrap();
|
|
assert_eq!(ids.len(), 1);
|
|
assert_eq!(ids[0].subvendor, 0x8086);
|
|
assert_eq!(ids[0].subdevice, 0x0A06);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_pci_vdevice() {
|
|
let src = r#"
|
|
{ PCI_VDEVICE(INTEL, 0x100E) }
|
|
"#;
|
|
let ids = parse_linux_id_table_from_source(src).unwrap();
|
|
assert_eq!(ids.len(), 1);
|
|
assert_eq!(ids[0].vendor, 0x8086);
|
|
assert_eq!(ids[0].device, 0x100E);
|
|
}
|
|
|
|
#[test]
|
|
fn to_driver_match_conversion() {
|
|
let id = LinuxPciId {
|
|
vendor: 0x8086,
|
|
device: 0x100E,
|
|
subvendor: 0x8086,
|
|
subdevice: 0x0A06,
|
|
class: 0x02,
|
|
class_mask: 0,
|
|
};
|
|
let m = to_driver_match(&id);
|
|
assert_eq!(m.vendor, Some(0x8086));
|
|
assert_eq!(m.device, Some(0x100E));
|
|
assert_eq!(m.subsystem_vendor, Some(0x8086));
|
|
assert_eq!(m.subsystem_device, Some(0x0A06));
|
|
}
|
|
|
|
#[test]
|
|
fn class_decoding() {
|
|
let id = LinuxPciId {
|
|
vendor: 0,
|
|
device: 0,
|
|
subvendor: 0,
|
|
subdevice: 0,
|
|
class: 0x02 << 16,
|
|
class_mask: 0x00FF00,
|
|
};
|
|
let m = to_driver_match(&id);
|
|
assert_eq!(m.class, Some(0x02));
|
|
assert_eq!(m.subclass, Some(0x00));
|
|
}
|
|
|
|
#[test]
|
|
fn parse_linux_id_table_reads_file() {
|
|
let dir = std::env::temp_dir().join("rb-dm-linux-loader-file");
|
|
let _ = fs::remove_dir_all(&dir);
|
|
fs::create_dir_all(&dir).unwrap();
|
|
let path = dir.join("e1000_main.c");
|
|
fs::write(
|
|
&path,
|
|
r#"
|
|
static const struct pci_device_id e1000_id_table[] = {
|
|
{ PCI_DEVICE(0x8086, 0x100E) },
|
|
{ 0 }
|
|
};
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
let ids = parse_linux_id_table(&path).unwrap();
|
|
assert_eq!(ids.len(), 1);
|
|
assert_eq!(ids[0].vendor, 0x8086);
|
|
assert_eq!(ids[0].device, 0x100E);
|
|
let _ = fs::remove_dir_all(&dir);
|
|
}
|
|
|
|
#[test]
|
|
fn to_driver_matches_batch_conversion() {
|
|
let ids = parse_linux_id_table_from_source(
|
|
r#"{ PCI_DEVICE(0x8086, 0x100E) }
|
|
{ PCI_DEVICE(0x10ec, 0x8168) }"#,
|
|
)
|
|
.unwrap();
|
|
let matches = to_driver_matches(&ids);
|
|
assert_eq!(matches.len(), 2);
|
|
assert_eq!(matches[0].vendor, Some(0x8086));
|
|
assert_eq!(matches[1].vendor, Some(0x10ec));
|
|
}
|
|
}
|