Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/power.rs
5392 views
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
7
pub(crate) mod hvc;
8
9
use std::collections::BTreeMap;
10
use std::sync::Arc;
11
12
use anyhow::bail;
13
use anyhow::Context;
14
use base::warn;
15
use sync::Mutex;
16
17
use crate::BusDevice;
18
19
/// A device container for routing PM requests.
20
pub struct DevicePowerManager {
21
domains: Mutex<BTreeMap<usize, PowerDomain>>,
22
}
23
24
impl Default for DevicePowerManager {
25
fn default() -> Self {
26
Self::new()
27
}
28
}
29
30
impl 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
71
struct PowerDomain {
72
devices: Vec<Arc<Mutex<dyn BusDevice>>>,
73
is_on: bool,
74
}
75
76
impl 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
138
fn on_off_str(on: bool) -> &'static str {
139
if on {
140
"ON"
141
} else {
142
"OFF"
143
}
144
}
145
146
#[cfg(test)]
147
mod 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
}
346
347