devices/
power.rs

1// Copyright 2026 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Abstract device power management.
6
7pub(crate) mod hvc;
8
9use std::collections::BTreeMap;
10use std::sync::Arc;
11
12use anyhow::bail;
13use anyhow::Context;
14use base::warn;
15use sync::Mutex;
16
17use crate::BusDevice;
18
19/// A device container for routing PM requests.
20pub struct DevicePowerManager {
21    domains: Mutex<BTreeMap<usize, PowerDomain>>,
22}
23
24impl Default for DevicePowerManager {
25    fn default() -> Self {
26        Self::new()
27    }
28}
29
30impl DevicePowerManager {
31    pub fn new() -> Self {
32        Self {
33            domains: Mutex::new(BTreeMap::new()),
34        }
35    }
36
37    /// Add a `device` to a power domain; domain IDs are abstract values managed by the caller.
38    ///
39    /// Fails if the `device`'s initial state does not match other devices in the power domain.
40    pub fn attach(
41        &mut self,
42        device: Arc<Mutex<dyn BusDevice>>,
43        domain_id: usize,
44    ) -> anyhow::Result<()> {
45        let domains = self.domains.get_mut();
46        let domain = domains
47            .entry(domain_id)
48            .or_insert_with(|| PowerDomain::new(device.lock().initial_power_state()));
49        domain.attach(device)
50    }
51
52    /// Powers all devices contained in a domain on.
53    pub fn power_on(&self, domain_id: usize) -> anyhow::Result<()> {
54        self.domains
55            .lock()
56            .get_mut(&domain_id)
57            .with_context(|| format!("Unknown PD {domain_id:#x}"))?
58            .power_on()
59    }
60
61    /// Powers all devices contained in a domain off.
62    pub fn power_off(&self, domain_id: usize) -> anyhow::Result<()> {
63        self.domains
64            .lock()
65            .get_mut(&domain_id)
66            .with_context(|| format!("Unknown PD {domain_id:#x}"))?
67            .power_off()
68    }
69}
70
71struct PowerDomain {
72    devices: Vec<Arc<Mutex<dyn BusDevice>>>,
73    is_on: bool,
74}
75
76impl PowerDomain {
77    fn new(is_on: bool) -> Self {
78        Self {
79            devices: Vec::new(),
80            is_on,
81        }
82    }
83
84    fn attach(&mut self, device: Arc<Mutex<dyn BusDevice>>) -> anyhow::Result<()> {
85        let is_on = device.lock().initial_power_state();
86        if self.is_on != is_on {
87            bail!(
88                "Can't attach device to PD when states don't match: device is {}, PD is {}",
89                on_off_str(is_on),
90                on_off_str(self.is_on)
91            )
92        }
93        self.devices.push(device);
94        Ok(())
95    }
96
97    fn power_on(&mut self) -> anyhow::Result<()> {
98        self.power_on_off(true)
99    }
100
101    fn power_off(&mut self) -> anyhow::Result<()> {
102        self.power_on_off(false)
103    }
104
105    fn power_on_off(&mut self, on: bool) -> anyhow::Result<()> {
106        if self.is_on == on {
107            warn!("Ignoring attempt to update PD: already {}", on_off_str(on));
108        } else {
109            Self::switch_devices(&self.devices, on)?;
110            self.is_on = on;
111        }
112
113        Ok(())
114    }
115
116    fn switch_devices(devices: &[Arc<Mutex<dyn BusDevice>>], on: bool) -> anyhow::Result<()> {
117        for (i, device) in devices.iter().enumerate() {
118            let result = if on {
119                device.lock().power_on()
120            } else {
121                device.lock().power_off()
122            };
123            if let Err(e) = result {
124                warn!(
125                    "Failed to switch {} device '{}': {e}",
126                    on_off_str(on),
127                    device.lock().debug_label()
128                );
129                Self::switch_devices(&devices[..i], !on).expect("PM failure during clean-up");
130                return Err(e);
131            }
132        }
133
134        Ok(())
135    }
136}
137
138fn on_off_str(on: bool) -> &'static str {
139    if on {
140        "ON"
141    } else {
142        "OFF"
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use vm_control::DeviceId;
149    use vm_control::PlatformDeviceId;
150
151    use super::*;
152    use crate::BusDevice;
153    use crate::Suspendable;
154
155    pub struct MockPoweredDevice {
156        initial_power_state: bool,
157        is_on: bool,
158        fail_power_on: bool,
159        fail_power_off: bool,
160    }
161
162    impl MockPoweredDevice {
163        fn new(initial_power_state: bool) -> Self {
164            Self {
165                initial_power_state,
166                is_on: initial_power_state,
167                fail_power_on: false,
168                fail_power_off: false,
169            }
170        }
171
172        fn with_failure(
173            initial_power_state: bool,
174            fail_power_on: bool,
175            fail_power_off: bool,
176        ) -> Self {
177            Self {
178                initial_power_state,
179                is_on: initial_power_state,
180                fail_power_on,
181                fail_power_off,
182            }
183        }
184
185        fn is_on(&self) -> bool {
186            self.is_on
187        }
188    }
189
190    impl BusDevice for MockPoweredDevice {
191        fn device_id(&self) -> DeviceId {
192            PlatformDeviceId::Mock.into()
193        }
194
195        fn debug_label(&self) -> String {
196            "mock device".to_owned()
197        }
198
199        fn supports_power_management(&self) -> anyhow::Result<bool> {
200            Ok(true)
201        }
202
203        fn initial_power_state(&self) -> bool {
204            self.initial_power_state
205        }
206
207        fn power_on(&mut self) -> anyhow::Result<()> {
208            if self.fail_power_on {
209                bail!("mock fail power on");
210            }
211            self.is_on = true;
212            Ok(())
213        }
214
215        fn power_off(&mut self) -> anyhow::Result<()> {
216            if self.fail_power_off {
217                bail!("mock fail power off");
218            }
219            self.is_on = false;
220            Ok(())
221        }
222    }
223
224    impl Suspendable for MockPoweredDevice {}
225
226    #[test]
227    fn domain_attaches_devices() {
228        let mock_starts_on1 = MockPoweredDevice::new(true);
229        let mock_starts_on2 = MockPoweredDevice::new(true);
230        let mock_starts_off1 = MockPoweredDevice::new(false);
231        let mock_starts_off2 = MockPoweredDevice::new(false);
232
233        let dev_starts_on1 = Arc::new(Mutex::new(mock_starts_on1));
234        let dev_starts_on2 = Arc::new(Mutex::new(mock_starts_on2));
235        let dev_starts_off1 = Arc::new(Mutex::new(mock_starts_off1));
236        let dev_starts_off2 = Arc::new(Mutex::new(mock_starts_off2));
237
238        let mut pd_starts_on = PowerDomain::new(true);
239        let mut pd_starts_off = PowerDomain::new(false);
240
241        // Attach wrong device to empty domain.
242        assert!(pd_starts_on.attach(dev_starts_off1.clone()).is_err());
243        assert!(pd_starts_off.attach(dev_starts_on1.clone()).is_err());
244
245        // Attach right device to empty domain.
246        assert!(pd_starts_on.attach(dev_starts_on1.clone()).is_ok());
247        assert!(pd_starts_off.attach(dev_starts_off1.clone()).is_ok());
248
249        // Attach wrong device to non-empty domain.
250        assert!(pd_starts_on.attach(dev_starts_off2.clone()).is_err());
251        assert!(pd_starts_off.attach(dev_starts_on2.clone()).is_err());
252
253        // Attach right device to non-empty domain.
254        assert!(pd_starts_on.attach(dev_starts_on2.clone()).is_ok());
255        assert!(pd_starts_off.attach(dev_starts_off2.clone()).is_ok());
256    }
257
258    #[test]
259    fn manager_powers_on() {
260        let mock1 = Arc::new(Mutex::new(MockPoweredDevice::new(false)));
261        let mock2 = Arc::new(Mutex::new(MockPoweredDevice::new(false)));
262
263        let mut power_manager = DevicePowerManager::new();
264        power_manager.attach(mock1.clone(), 0).unwrap();
265        power_manager.attach(mock2.clone(), 0).unwrap();
266
267        power_manager.power_on(0).unwrap();
268
269        assert!(mock1.lock().is_on());
270        assert!(mock2.lock().is_on());
271    }
272
273    #[test]
274    fn manager_powers_off() {
275        let mock1 = Arc::new(Mutex::new(MockPoweredDevice::new(true)));
276        let mock2 = Arc::new(Mutex::new(MockPoweredDevice::new(true)));
277
278        let mut power_manager = DevicePowerManager::new();
279        power_manager.attach(mock1.clone(), 0).unwrap();
280        power_manager.attach(mock2.clone(), 0).unwrap();
281
282        power_manager.power_off(0).unwrap();
283
284        assert!(!mock1.lock().is_on());
285        assert!(!mock2.lock().is_on());
286    }
287
288    #[test]
289    fn manager_cleans_up_failure() {
290        // mock1 succeeds, mock2 fails. mock1 should be rolled back.
291        let mock1 = Arc::new(Mutex::new(MockPoweredDevice::new(false)));
292        let mock2 = Arc::new(Mutex::new(MockPoweredDevice::with_failure(
293            false, true, false,
294        )));
295
296        let mut power_manager = DevicePowerManager::new();
297        power_manager.attach(mock1.clone(), 0).unwrap();
298        power_manager.attach(mock2.clone(), 0).unwrap();
299
300        // Expect failure when powering on
301        assert!(power_manager.power_on(0).is_err());
302
303        // mock2 failed to power on, so it should be off.
304        assert!(!mock2.lock().is_on());
305        // mock1 should have been powered on, then rolled back to off.
306        assert!(!mock1.lock().is_on());
307    }
308
309    #[test]
310    fn manager_isolates_domains() {
311        let mock0_1 = Arc::new(Mutex::new(MockPoweredDevice::new(false)));
312        let mock0_2 = Arc::new(Mutex::new(MockPoweredDevice::new(false)));
313        let mock1_1 = Arc::new(Mutex::new(MockPoweredDevice::new(false)));
314
315        let mut power_manager = DevicePowerManager::new();
316        power_manager.attach(mock0_1.clone(), 0).unwrap();
317        power_manager.attach(mock0_2.clone(), 0).unwrap();
318        power_manager.attach(mock1_1.clone(), 1).unwrap();
319
320        // Power on domain 0
321        power_manager.power_on(0).unwrap();
322        assert!(mock0_1.lock().is_on());
323        assert!(mock0_2.lock().is_on());
324        assert!(!mock1_1.lock().is_on());
325
326        // Power on domain 1
327        power_manager.power_on(1).unwrap();
328        assert!(mock0_1.lock().is_on());
329        assert!(mock0_2.lock().is_on());
330        assert!(mock1_1.lock().is_on());
331
332        // Power off domain 0
333        power_manager.power_off(0).unwrap();
334        assert!(!mock0_1.lock().is_on());
335        assert!(!mock0_2.lock().is_on());
336        assert!(mock1_1.lock().is_on());
337    }
338
339    #[test]
340    fn manager_rejects_unknown_domain() {
341        let power_manager = DevicePowerManager::new();
342        assert!(power_manager.power_on(42).is_err());
343        assert!(power_manager.power_off(42).is_err());
344    }
345}