driver-manager: accept device id list in [[driver.match]]
The shipped /lib/drivers.d/10-network.toml scopes vendor+class matches to
specific device ids with `device = [0x8168, 0x8169]` (rtl8168d) and a
42-id ixgbed list — the documented, intended form. But RawDriverMatch
parsed `device` as a single u16, so the list form failed to deserialize
("invalid type: sequence, expected u16") and took the whole file down,
leaving driver-manager with zero network drivers loaded — including
virtio-netd, so no network came up at all even in QEMU.
Parse `device` as scalar-or-list (U16OrList) and fan each raw match out
to one DriverMatch per id (an OR alternative carrying the shared
vendor/class/subclass constraints). Scalar and device-less matches keep
their previous 1:1 behaviour. Adds regression tests for both forms.
This commit is contained in:
@@ -117,10 +117,34 @@ impl Clone for DriverConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// A PCI id field (`device`) that the config may write either as a single id
|
||||
/// (`device = 0x8139`) or as a list of ids (`device = [0x8168, 0x8169]`). A
|
||||
/// list means "match any of these ids" — an OR alternative — and is the form
|
||||
/// the network/graphics driver configs use to scope a vendor+class match to the
|
||||
/// specific device ids a driver's register layout supports. Without this, the
|
||||
/// list form fails to deserialize (`invalid type: sequence, expected u16`) and
|
||||
/// takes the entire file down with it, so driver-manager loads zero drivers
|
||||
/// from it (observed: no network drivers, including virtio-netd, get bound).
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
enum U16OrList {
|
||||
Scalar(u16),
|
||||
List(Vec<u16>),
|
||||
}
|
||||
|
||||
impl U16OrList {
|
||||
fn into_vec(self) -> Vec<u16> {
|
||||
match self {
|
||||
U16OrList::Scalar(v) => vec![v],
|
||||
U16OrList::List(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RawDriverMatch {
|
||||
vendor: Option<u16>,
|
||||
device: Option<u16>,
|
||||
device: Option<U16OrList>,
|
||||
class: Option<u8>,
|
||||
subclass: Option<u8>,
|
||||
prog_if: Option<u8>,
|
||||
@@ -128,6 +152,40 @@ struct RawDriverMatch {
|
||||
subsystem_device: Option<u16>,
|
||||
}
|
||||
|
||||
impl RawDriverMatch {
|
||||
/// Expand one raw match into one `DriverMatch` per listed `device` id.
|
||||
/// No `device` (or an empty list) yields a single device-less match,
|
||||
/// preserving the previous 1:1 behaviour; a scalar yields one match; a
|
||||
/// list fans out to one match per id, each an independent OR alternative
|
||||
/// carrying the same vendor/class/subclass constraints.
|
||||
fn expand(self) -> Vec<DriverMatch> {
|
||||
let RawDriverMatch {
|
||||
vendor,
|
||||
device,
|
||||
class,
|
||||
subclass,
|
||||
prog_if,
|
||||
subsystem_vendor,
|
||||
subsystem_device,
|
||||
} = self;
|
||||
// All captured fields are Copy, so this closure is reusable (Fn).
|
||||
let mk = |dev: Option<u16>| DriverMatch {
|
||||
vendor,
|
||||
device: dev,
|
||||
class,
|
||||
subclass,
|
||||
prog_if,
|
||||
subsystem_vendor,
|
||||
subsystem_device,
|
||||
};
|
||||
match device.map(U16OrList::into_vec) {
|
||||
None => vec![mk(None)],
|
||||
Some(ids) if ids.is_empty() => vec![mk(None)],
|
||||
Some(ids) => ids.into_iter().map(|id| mk(Some(id))).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
@@ -465,6 +523,63 @@ command = ["/usr/bin/redbear-acmd"]
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_format_device_list_expands_to_one_match_per_id() {
|
||||
// Reproduces the shipped 10-network.toml rtl8168d block: a vendor+class
|
||||
// match scoped to two device ids via `device = [0x8168, 0x8169]`. Before
|
||||
// the U16OrList fix this failed with `invalid type: sequence, expected
|
||||
// u16` and took the whole file (incl. virtio-netd) down with it.
|
||||
let toml_str = r#"
|
||||
[[driver]]
|
||||
name = "rtl8168d"
|
||||
command = ["/usr/lib/drivers/rtl8168d"]
|
||||
|
||||
[[driver.match]]
|
||||
vendor = 0x10EC
|
||||
class = 2
|
||||
subclass = 0
|
||||
device = [0x8168, 0x8169]
|
||||
"#;
|
||||
let parsed: super::RawDriverToml = toml::from_str(toml_str).expect("parse [[driver]]");
|
||||
let entry = parsed.driver.into_iter().next().unwrap();
|
||||
let matches: Vec<super::DriverMatch> = entry
|
||||
.r#match
|
||||
.into_iter()
|
||||
.flat_map(super::RawDriverMatch::expand)
|
||||
.collect();
|
||||
assert_eq!(matches.len(), 2, "device list must fan out to one match per id");
|
||||
assert_eq!(matches[0].device, Some(0x8168));
|
||||
assert_eq!(matches[1].device, Some(0x8169));
|
||||
// Shared constraints are carried onto every expanded alternative.
|
||||
for m in &matches {
|
||||
assert_eq!(m.vendor, Some(0x10EC));
|
||||
assert_eq!(m.class, Some(2));
|
||||
assert_eq!(m.subclass, Some(0));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_format_scalar_device_is_single_match() {
|
||||
let toml_str = r#"
|
||||
[[driver]]
|
||||
name = "rtl8139d"
|
||||
command = ["/usr/lib/drivers/rtl8139d"]
|
||||
|
||||
[[driver.match]]
|
||||
vendor = 0x10EC
|
||||
device = 0x8139
|
||||
"#;
|
||||
let parsed: super::RawDriverToml = toml::from_str(toml_str).expect("parse [[driver]]");
|
||||
let entry = parsed.driver.into_iter().next().unwrap();
|
||||
let matches: Vec<super::DriverMatch> = entry
|
||||
.r#match
|
||||
.into_iter()
|
||||
.flat_map(super::RawDriverMatch::expand)
|
||||
.collect();
|
||||
assert_eq!(matches.len(), 1);
|
||||
assert_eq!(matches[0].device, Some(0x8139));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_vendor_plus_device_converts_to_one_match() {
|
||||
let toml_str = r#"
|
||||
@@ -1364,20 +1479,6 @@ pub fn spawn_decision_gate() -> SpawnDecision {
|
||||
SpawnDecision::Spawn
|
||||
}
|
||||
|
||||
impl From<RawDriverMatch> for DriverMatch {
|
||||
fn from(r: RawDriverMatch) -> Self {
|
||||
DriverMatch {
|
||||
vendor: r.vendor,
|
||||
device: r.device,
|
||||
class: r.class,
|
||||
subclass: r.subclass,
|
||||
prog_if: r.prog_if,
|
||||
subsystem_vendor: r.subsystem_vendor,
|
||||
subsystem_device: r.subsystem_device,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DriverConfig {
|
||||
/// Load every `.toml` config file in `dir` and return the merged,
|
||||
/// priority-sorted list of driver configs.
|
||||
@@ -1408,7 +1509,7 @@ impl DriverConfig {
|
||||
Ok(parsed) => {
|
||||
for driver in parsed.driver {
|
||||
let matches: Vec<DriverMatch> =
|
||||
driver.r#match.into_iter().map(DriverMatch::from).collect();
|
||||
driver.r#match.into_iter().flat_map(RawDriverMatch::expand).collect();
|
||||
let initial_power_state = match &driver.initial_power_state {
|
||||
Some(s) => match PciPowerState::from_toml(s) {
|
||||
Ok(state) => state,
|
||||
|
||||
Reference in New Issue
Block a user