diff --git a/local/recipes/system/driver-manager/source/src/config.rs b/local/recipes/system/driver-manager/source/src/config.rs index 34ebfb4d44..0048aad104 100644 --- a/local/recipes/system/driver-manager/source/src/config.rs +++ b/local/recipes/system/driver-manager/source/src/config.rs @@ -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), +} + +impl U16OrList { + fn into_vec(self) -> Vec { + match self { + U16OrList::Scalar(v) => vec![v], + U16OrList::List(v) => v, + } + } +} + #[derive(Deserialize)] struct RawDriverMatch { vendor: Option, - device: Option, + device: Option, class: Option, subclass: Option, prog_if: Option, @@ -128,6 +152,40 @@ struct RawDriverMatch { subsystem_device: Option, } +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 { + 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| 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 = 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 = 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 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 = - 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,