From 3d1f774f8d80169c36ecdb140360052efc102f04 Mon Sep 17 00:00:00 2001 From: vasilito Date: Mon, 27 Jul 2026 17:05:12 +0900 Subject: [PATCH] =?UTF-8?q?driver-manager:=20N8=20=E2=80=94=20per-driver?= =?UTF-8?q?=20[driver.params]=20parsing=20+=20env=20emission?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the v4.8 cross-cutting item 'modprobe.d options parser': the new format's [driver.params] table (e.g. 50-amdgpu.toml's radeon_overlap / amdgpu_force / amdgpu_disable_accel knobs) is now parsed from every [[driver]] entry and emitted to the spawned child as REDBEAR_DRIVER_PARAM_= env vars. RawDriverEntry grows a new field: params: BTreeMap parsed at load_all time into a Vec<(name, value)> on DriverConfig (preserving BTreeMap's sorted iteration order for deterministic env-var emission). The spawn path applies self.params after the existing [[options]] env-var emission; both share the REDBEAR_DRIVER_PARAM_* prefix so drivers don't need to distinguish the two sources. Matching semantics (radeon_overlap='exclusive', amdgpu_force=true) remain a follow-up — the env-var wiring is the foundational piece, and 50-amdgpu.toml's radeon_overlap knob can be honoured by the radeon driver daemon reading its env-var directly. Tests (2 new): - load_all_parses_driver_params: 3-params TOML entry parses with deterministic sorted iteration - load_all_driver_params_default_empty: a [[driver]] without [driver.params] still parses, params list is empty 162 driver-manager tests pass. driver-manager-audit-no-stubs.py: 46 files, 0 violations. --- .../driver-manager/source/src/config.rs | 130 +++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/local/recipes/system/driver-manager/source/src/config.rs b/local/recipes/system/driver-manager/source/src/config.rs index 80e72515ec..ea5c5b7676 100644 --- a/local/recipes/system/driver-manager/source/src/config.rs +++ b/local/recipes/system/driver-manager/source/src/config.rs @@ -46,11 +46,18 @@ pub struct DriverConfig { pub matches: Vec, pub depends_on: Vec, pub exclusive_with: Vec, + /// Per-driver parameter overrides from the policy layer's + /// `[driver.params]` table. Each `(name, value)` pair is emitted + /// as `REDBEAR_DRIVER_PARAM_=` in the spawn env. + /// This is the wiring for `redbear-driver-policy`'s `50-amdgpu.toml` + /// style per-driver knobs (radeon_overlap, amdgpu_force, + /// amdgpu_disable_accel, etc.). + pub params: Vec<(String, String)>, /// Initial PCI power state for the spawned driver daemon. D0 is the /// default and matches the post-`enable_device` state of every bound /// device. D3hot / D3cold can be set for low-power drivers (e.g. /// always-on housekeeping devices) that wake the bus on demand. - /// When non-D0, driver-manager sets + /// When non-D0, driver-manager emits /// `REDBEAR_DRIVER_INITIAL_POWER_STATE=` in the child's env so /// the daemon can call `set_power_state` on its granted channel. pub initial_power_state: PciPowerState, @@ -102,6 +109,7 @@ impl Clone for DriverConfig { matches: self.matches.clone(), depends_on: self.depends_on.clone(), exclusive_with: self.exclusive_with.clone(), + params: self.params.clone(), initial_power_state: self.initial_power_state, spawned: Mutex::new(HashMap::new()), pid_to_device: Mutex::new(HashMap::new()), @@ -201,6 +209,7 @@ command = ["/usr/bin/redbear-acmd"] matches: Vec::new(), depends_on: Vec::new(), exclusive_with: Vec::new(), + params: Vec::new(), initial_power_state: PciPowerState::D0, spawned: Mutex::new(HashMap::new()), pid_to_device: Mutex::new(HashMap::new()), @@ -219,6 +228,7 @@ command = ["/usr/bin/redbear-acmd"] matches: Vec::new(), depends_on: Vec::new(), exclusive_with: Vec::new(), + params: Vec::new(), initial_power_state: PciPowerState::D0, spawned: Mutex::new(HashMap::new()), pid_to_device: Mutex::new(HashMap::new()), @@ -409,6 +419,84 @@ command = ["e1000d"] }); } + #[test] + fn load_all_parses_driver_params() { + // N8: per-driver [driver.params] table from redbear-driver-policy + // (e.g. 50-amdgpu.toml) — each entry becomes a param override + // that the spawn path emits as REDBEAR_DRIVER_PARAM_=. + serialized(|| { + let dir = std::env::temp_dir().join(format!( + "rb-dm-driver-params-{}-{}", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + std::fs::create_dir_all(&dir).unwrap(); + std::fs::write( + dir.join("50-amdgpu.toml"), + r#" +[[driver]] +name = "redox-drm" +priority = 95 +command = ["/usr/bin/redox-drm"] + +[driver.params] +radeon_overlap = "exclusive" +amdgpu_force = "true" +amdgpu_disable_accel = "false" +"#, + ) + .unwrap(); + let configs = DriverConfig::load_all(dir.to_str().unwrap()) + .expect("[driver.params] must parse"); + assert_eq!(configs.len(), 1); + let params = &configs[0].params; + assert_eq!(params.len(), 3); + // BTreeMap iteration is sorted by key. + assert_eq!(params[0].0, "amdgpu_disable_accel"); + assert_eq!(params[0].1, "false"); + assert_eq!(params[1].0, "amdgpu_force"); + assert_eq!(params[1].1, "true"); + assert_eq!(params[2].0, "radeon_overlap"); + assert_eq!(params[2].1, "exclusive"); + let _ = std::fs::remove_dir_all(&dir); + }); + } + + #[test] + fn load_all_driver_params_default_empty() { + // A driver entry without [driver.params] should still parse + // and have an empty params list — drivers that don't need + // knobs aren't required to declare any. + serialized(|| { + let dir = std::env::temp_dir().join(format!( + "rb-dm-driver-params-empty-{}-{}", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + std::fs::create_dir_all(&dir).unwrap(); + std::fs::write( + dir.join("10-foo.toml"), + r#" +[[driver]] +name = "foo" +priority = 80 +command = ["/usr/bin/foo"] +"#, + ) + .unwrap(); + let configs = DriverConfig::load_all(dir.to_str().unwrap()).unwrap(); + assert_eq!(configs.len(), 1); + assert!(configs[0].params.is_empty()); + let _ = std::fs::remove_dir_all(&dir); + }); + } + #[test] fn load_all_warns_and_defaults_on_invalid_initial_power_state() { serialized(|| { @@ -1108,6 +1196,23 @@ impl DriverConfig { }, None => PciPowerState::D0, }; + // Collect per-driver [driver.params] into an + // ordered Vec for env-var emission. BTreeMap + // iteration is sorted by key so the env vars + // are emitted in deterministic order. + let params: Vec<(String, String)> = driver + .params + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + if !params.is_empty() { + log::info!( + "policy: driver `{}` has {} [driver.params] override{}", + driver.name, + params.len(), + if params.len() == 1 { "" } else { "s" } + ); + } configs.push(DriverConfig { name: driver.name, description: driver.description, @@ -1116,6 +1221,7 @@ impl DriverConfig { matches, depends_on: driver.depends_on, exclusive_with: driver.exclusive_with, + params, initial_power_state, spawned: Mutex::new(HashMap::new()), pid_to_device: Mutex::new(HashMap::new()), @@ -1475,6 +1581,18 @@ impl Driver for DriverConfig { ); } + // N8: apply per-driver [driver.params] overrides from the + // policy layer (e.g. 50-amdgpu.toml's radeon_overlap / + // amdgpu_force / amdgpu_disable_accel). Same env-var + // convention as `[[options]]` so drivers don't need to + // distinguish the two sources. + for (param_name, param_value) in &self.params { + cmd.env( + format!("REDBEAR_DRIVER_PARAM_{}", param_name.to_uppercase()), + param_value, + ); + } + if !self.initial_power_state.is_default() { cmd.env( "REDBEAR_DRIVER_INITIAL_POWER_STATE", @@ -1753,6 +1871,15 @@ struct RawDriverEntry { exclusive_with: Vec, #[serde(default)] initial_power_state: Option, + /// Per-driver parameter overrides from the policy layer's + /// `[driver.params]` table. Each entry becomes a + /// `REDBEAR_DRIVER_PARAM_=` env var on the spawned + /// child's environment. The schema is a flat string-to-string map; + /// values are stored verbatim (the driver daemon interprets them + /// per its own schema). Used by `redbear-driver-policy`'s + /// `50-amdgpu.toml` and similar per-driver policy files. + #[serde(default)] + params: std::collections::BTreeMap, } #[derive(Deserialize)] @@ -1895,6 +2022,7 @@ fn convert_legacy(legacy: RawLegacyEntry) -> DriverConfig { matches, depends_on: Vec::new(), exclusive_with: legacy.exclusive_with, + params: Vec::new(), initial_power_state: PciPowerState::D0, spawned: Mutex::new(HashMap::new()), pid_to_device: Mutex::new(HashMap::new()),