blob: 22390485ac0c45c47491d5a24f5fe8917338e48b [file] [log] [blame]
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Implements a basic power_manager client.
use std::sync::Arc;
use std::time::{Duration, Instant};
use anyhow::{Context as AnyhowContext, Result};
use dbus::blocking::Connection;
use log::{debug, error, info};
use sync::Mutex;
use system_api::client::OrgChromiumPowerManager;
/// Define the default maximum duration the powerd proxy will wait for method
/// call responses.
const POWERD_DBUS_PROXY_TIMEOUT: Duration = Duration::from_secs(60);
/// Define the amount of time to process requests for before checking for a
/// result. Make this long enough that we're not busy looping, but short enough
/// that an extra period won't be noticeable to humans.
const POWERD_PROCESS_PERIOD: Duration = Duration::from_millis(50);
/// Define how long to wait around for powerd to finish the RequestSuspend
/// method before printing impatiently that we're still waiting. This should be
/// long enough that a correctly functioning run will not see it, but short
/// enough that a confused developer at the console with a broken system will.
const POWERD_REPRINT_PERIOD: Duration = Duration::from_secs(30);
/// Values for the flavor parameter of powerd's RequestSuspend method.
#[repr(u32)]
enum PowerdSuspendFlavor {
FromDiskPrepare = 3,
FromDiskAbort = 4,
}
/// Implements a pending resume ticket. When created, it tells powerd to prepare
/// for imminent resume. When dropped, notifies powerd that the resume has been
/// aborted.
pub struct PowerdPendingResume {}
impl PowerdPendingResume {
pub fn new() -> Result<Self> {
powerd_request_suspend(PowerdSuspendFlavor::FromDiskPrepare)?;
wait_for_hibernate_resume_ready()?;
Ok(PowerdPendingResume {})
}
}
impl Drop for PowerdPendingResume {
fn drop(&mut self) {
if let Err(e) = powerd_request_suspend(PowerdSuspendFlavor::FromDiskAbort) {
error!("Failed to notify powerd of aborted resume: {}", e);
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct HibernateResumeReady {}
impl dbus::arg::ReadAll for HibernateResumeReady {
fn read(_i: &mut dbus::arg::Iter) -> std::result::Result<Self, dbus::arg::TypeMismatchError> {
Ok(HibernateResumeReady {})
}
}
impl dbus::message::SignalArgs for HibernateResumeReady {
const NAME: &'static str = "HibernateResumeReady";
const INTERFACE: &'static str = "org.chromium.PowerManager";
}
/// Helper function to wait for a HibernateResumeReady signal to come in from powerd.
fn wait_for_hibernate_resume_ready() -> Result<()> {
// First open up a connection to the session bus.
let conn = Connection::new_system().context("Failed to start system dbus connection")?;
// Second, create a wrapper struct around the connection that makes it easy
// to send method calls to a specific destination and path.
let proxy = conn.with_proxy(
"org.chromium.PowerManager",
"/org/chromium/PowerManager",
POWERD_DBUS_PROXY_TIMEOUT,
);
// Set up a handler to record every time a signal comes in.
let signals = Arc::new(Mutex::new(Vec::new()));
let signals_copy = signals.clone();
proxy.match_signal(
move |signal: HibernateResumeReady, _: &Connection, _: &dbus::Message| {
signals_copy.lock().push(signal);
// Return false to abandon the match.
false
},
)?;
info!("Waiting for HibernateResumeReady signal");
loop {
let end_time = Instant::now() + POWERD_REPRINT_PERIOD;
while Instant::now() < end_time {
// Wait for signals.
conn.process(POWERD_PROCESS_PERIOD).unwrap();
if signals.lock().len() != 0 {
debug!("Got HibernateResumeReady signal");
return Ok(());
}
}
info!("Still waiting for HibernateResumeReady signal");
}
}
/// Helper function to make a simple RequestSuspend d-bus call to powerd.
fn powerd_request_suspend(flavor: PowerdSuspendFlavor) -> Result<()> {
// First open up a connection to the session bus.
let conn = Connection::new_system().context("Failed to start system dbus connection")?;
// Second, create a wrapper struct around the connection that makes it easy
// to send method calls to a specific destination and path.
let proxy = conn.with_proxy(
"org.chromium.PowerManager",
"/org/chromium/PowerManager",
POWERD_DBUS_PROXY_TIMEOUT,
);
let external_wakeup_count: u64 = u64::MAX;
let wakeup_timeout: i32 = 0;
let flavor = flavor as u32;
info!("Calling powerd RequestSuspend, flavor {}", flavor);
proxy.request_suspend(external_wakeup_count, wakeup_timeout, flavor)?;
debug!("RequestSuspend returned");
Ok(())
}