pub(crate) mod hvc;
use std::collections::BTreeMap;
use std::sync::Arc;
use anyhow::bail;
use anyhow::Context;
use base::warn;
use sync::Mutex;
use crate::BusDevice;
pub struct DevicePowerManager {
domains: Mutex<BTreeMap<usize, PowerDomain>>,
}
impl Default for DevicePowerManager {
fn default() -> Self {
Self::new()
}
}
impl DevicePowerManager {
pub fn new() -> Self {
Self {
domains: Mutex::new(BTreeMap::new()),
}
}
pub fn attach(
&mut self,
device: Arc<Mutex<dyn BusDevice>>,
domain_id: usize,
) -> anyhow::Result<()> {
let domains = self.domains.get_mut();
let domain = domains
.entry(domain_id)
.or_insert_with(|| PowerDomain::new(device.lock().initial_power_state()));
domain.attach(device)
}
pub fn power_on(&self, domain_id: usize) -> anyhow::Result<()> {
self.domains
.lock()
.get_mut(&domain_id)
.with_context(|| format!("Unknown PD {domain_id:#x}"))?
.power_on()
}
pub fn power_off(&self, domain_id: usize) -> anyhow::Result<()> {
self.domains
.lock()
.get_mut(&domain_id)
.with_context(|| format!("Unknown PD {domain_id:#x}"))?
.power_off()
}
}
struct PowerDomain {
devices: Vec<Arc<Mutex<dyn BusDevice>>>,
is_on: bool,
}
impl PowerDomain {
fn new(is_on: bool) -> Self {
Self {
devices: Vec::new(),
is_on,
}
}
fn attach(&mut self, device: Arc<Mutex<dyn BusDevice>>) -> anyhow::Result<()> {
let is_on = device.lock().initial_power_state();
if self.is_on != is_on {
bail!(
"Can't attach device to PD when states don't match: device is {}, PD is {}",
on_off_str(is_on),
on_off_str(self.is_on)
)
}
self.devices.push(device);
Ok(())
}
fn power_on(&mut self) -> anyhow::Result<()> {
self.power_on_off(true)
}
fn power_off(&mut self) -> anyhow::Result<()> {
self.power_on_off(false)
}
fn power_on_off(&mut self, on: bool) -> anyhow::Result<()> {
if self.is_on == on {
warn!("Ignoring attempt to update PD: already {}", on_off_str(on));
} else {
Self::switch_devices(&self.devices, on)?;
self.is_on = on;
}
Ok(())
}
fn switch_devices(devices: &[Arc<Mutex<dyn BusDevice>>], on: bool) -> anyhow::Result<()> {
for (i, device) in devices.iter().enumerate() {
let result = if on {
device.lock().power_on()
} else {
device.lock().power_off()
};
if let Err(e) = result {
warn!(
"Failed to switch {} device '{}': {e}",
on_off_str(on),
device.lock().debug_label()
);
Self::switch_devices(&devices[..i], !on).expect("PM failure during clean-up");
return Err(e);
}
}
Ok(())
}
}
fn on_off_str(on: bool) -> &'static str {
if on {
"ON"
} else {
"OFF"
}
}
#[cfg(test)]
mod tests {
use vm_control::DeviceId;
use vm_control::PlatformDeviceId;
use super::*;
use crate::BusDevice;
use crate::Suspendable;
pub struct MockPoweredDevice {
initial_power_state: bool,
is_on: bool,
fail_power_on: bool,
fail_power_off: bool,
}
impl MockPoweredDevice {
fn new(initial_power_state: bool) -> Self {
Self {
initial_power_state,
is_on: initial_power_state,
fail_power_on: false,
fail_power_off: false,
}
}
fn with_failure(
initial_power_state: bool,
fail_power_on: bool,
fail_power_off: bool,
) -> Self {
Self {
initial_power_state,
is_on: initial_power_state,
fail_power_on,
fail_power_off,
}
}
fn is_on(&self) -> bool {
self.is_on
}
}
impl BusDevice for MockPoweredDevice {
fn device_id(&self) -> DeviceId {
PlatformDeviceId::Mock.into()
}
fn debug_label(&self) -> String {
"mock device".to_owned()
}
fn supports_power_management(&self) -> anyhow::Result<bool> {
Ok(true)
}
fn initial_power_state(&self) -> bool {
self.initial_power_state
}
fn power_on(&mut self) -> anyhow::Result<()> {
if self.fail_power_on {
bail!("mock fail power on");
}
self.is_on = true;
Ok(())
}
fn power_off(&mut self) -> anyhow::Result<()> {
if self.fail_power_off {
bail!("mock fail power off");
}
self.is_on = false;
Ok(())
}
}
impl Suspendable for MockPoweredDevice {}
#[test]
fn domain_attaches_devices() {
let mock_starts_on1 = MockPoweredDevice::new(true);
let mock_starts_on2 = MockPoweredDevice::new(true);
let mock_starts_off1 = MockPoweredDevice::new(false);
let mock_starts_off2 = MockPoweredDevice::new(false);
let dev_starts_on1 = Arc::new(Mutex::new(mock_starts_on1));
let dev_starts_on2 = Arc::new(Mutex::new(mock_starts_on2));
let dev_starts_off1 = Arc::new(Mutex::new(mock_starts_off1));
let dev_starts_off2 = Arc::new(Mutex::new(mock_starts_off2));
let mut pd_starts_on = PowerDomain::new(true);
let mut pd_starts_off = PowerDomain::new(false);
assert!(pd_starts_on.attach(dev_starts_off1.clone()).is_err());
assert!(pd_starts_off.attach(dev_starts_on1.clone()).is_err());
assert!(pd_starts_on.attach(dev_starts_on1.clone()).is_ok());
assert!(pd_starts_off.attach(dev_starts_off1.clone()).is_ok());
assert!(pd_starts_on.attach(dev_starts_off2.clone()).is_err());
assert!(pd_starts_off.attach(dev_starts_on2.clone()).is_err());
assert!(pd_starts_on.attach(dev_starts_on2.clone()).is_ok());
assert!(pd_starts_off.attach(dev_starts_off2.clone()).is_ok());
}
#[test]
fn manager_powers_on() {
let mock1 = Arc::new(Mutex::new(MockPoweredDevice::new(false)));
let mock2 = Arc::new(Mutex::new(MockPoweredDevice::new(false)));
let mut power_manager = DevicePowerManager::new();
power_manager.attach(mock1.clone(), 0).unwrap();
power_manager.attach(mock2.clone(), 0).unwrap();
power_manager.power_on(0).unwrap();
assert!(mock1.lock().is_on());
assert!(mock2.lock().is_on());
}
#[test]
fn manager_powers_off() {
let mock1 = Arc::new(Mutex::new(MockPoweredDevice::new(true)));
let mock2 = Arc::new(Mutex::new(MockPoweredDevice::new(true)));
let mut power_manager = DevicePowerManager::new();
power_manager.attach(mock1.clone(), 0).unwrap();
power_manager.attach(mock2.clone(), 0).unwrap();
power_manager.power_off(0).unwrap();
assert!(!mock1.lock().is_on());
assert!(!mock2.lock().is_on());
}
#[test]
fn manager_cleans_up_failure() {
let mock1 = Arc::new(Mutex::new(MockPoweredDevice::new(false)));
let mock2 = Arc::new(Mutex::new(MockPoweredDevice::with_failure(
false, true, false,
)));
let mut power_manager = DevicePowerManager::new();
power_manager.attach(mock1.clone(), 0).unwrap();
power_manager.attach(mock2.clone(), 0).unwrap();
assert!(power_manager.power_on(0).is_err());
assert!(!mock2.lock().is_on());
assert!(!mock1.lock().is_on());
}
#[test]
fn manager_isolates_domains() {
let mock0_1 = Arc::new(Mutex::new(MockPoweredDevice::new(false)));
let mock0_2 = Arc::new(Mutex::new(MockPoweredDevice::new(false)));
let mock1_1 = Arc::new(Mutex::new(MockPoweredDevice::new(false)));
let mut power_manager = DevicePowerManager::new();
power_manager.attach(mock0_1.clone(), 0).unwrap();
power_manager.attach(mock0_2.clone(), 0).unwrap();
power_manager.attach(mock1_1.clone(), 1).unwrap();
power_manager.power_on(0).unwrap();
assert!(mock0_1.lock().is_on());
assert!(mock0_2.lock().is_on());
assert!(!mock1_1.lock().is_on());
power_manager.power_on(1).unwrap();
assert!(mock0_1.lock().is_on());
assert!(mock0_2.lock().is_on());
assert!(mock1_1.lock().is_on());
power_manager.power_off(0).unwrap();
assert!(!mock0_1.lock().is_on());
assert!(!mock0_2.lock().is_on());
assert!(mock1_1.lock().is_on());
}
#[test]
fn manager_rejects_unknown_domain() {
let power_manager = DevicePowerManager::new();
assert!(power_manager.power_on(42).is_err());
assert!(power_manager.power_off(42).is_err());
}
}