blob: 921189167f0440cb27ed6339db9c7dfcadc5af9b [file] [log] [blame]
// Copyright 2021 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.
use std::convert::TryFrom;
use std::fmt;
use std::os::raw::c_uint;
use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::Rc;
use std::time::Duration;
use anyhow::Result;
use dbus::ffidisp::{Connection, WatchEvent};
use dbus_tree::{MTFn, MethodErr, MethodResult};
use sys_util::{error, warn, PollContext, PollToken, TimerFd, WatchingEvents};
use crate::common;
use crate::memory;
const SERVICE_NAME: &str = "org.chromium.ResourceManager";
const PATH_NAME: &str = "/org/chromium/ResourceManager";
const INTERFACE_NAME: &str = SERVICE_NAME;
// Context data for the D-Bus service.
struct DbusContext {
reset_game_mode_timer: Rc<TimerFd>,
}
// The basic implementation of the Debug trait.
// The associated data requires a debug bound, due to https://github.com/rust-lang/rust/issues/31518.
impl fmt::Debug for DbusContext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("DbusContext").finish()
}
}
// Defines the custom data types for the D-Bus service.
#[derive(Default)]
struct CustomData;
impl dbus_tree::DataType for CustomData {
type Tree = ();
type ObjectPath = DbusContext;
type Interface = ();
type Property = ();
type Method = ();
type Signal = ();
}
type Factory = dbus_tree::Factory<MTFn<CustomData>, CustomData>;
type MethodInfo<'a> = dbus_tree::MethodInfo<'a, MTFn<CustomData>, CustomData>;
type Signal = dbus_tree::Signal<CustomData>;
fn get_available_memory_kb(m: &MethodInfo) -> MethodResult {
match memory::get_background_available_memory_kb() {
// One message will be returned - the method return (and should always be there).
Ok(available) => Ok(vec![m.msg.method_return().append1(available)]),
Err(_) => Err(MethodErr::failed("Couldn't get available memory")),
}
}
fn get_foreground_available_memory_kb(m: &MethodInfo) -> MethodResult {
match memory::get_foreground_available_memory_kb() {
Ok(available) => Ok(vec![m.msg.method_return().append1(available)]),
Err(_) => Err(MethodErr::failed(
"Couldn't get foreground available memory",
)),
}
}
#[allow(clippy::unnecessary_wraps)]
fn get_memory_margins_kb(m: &MethodInfo) -> MethodResult {
let margins = memory::get_memory_margins_kb();
Ok(vec![m.msg.method_return().append2(margins.0, margins.1)])
}
fn get_game_mode(m: &MethodInfo) -> MethodResult {
match common::get_game_mode() {
Ok(game_mode) => Ok(vec![m.msg.method_return().append1(game_mode as u8)]),
Err(_) => Err(MethodErr::failed("Failed to get game mode")),
}
}
fn set_game_mode(m: &MethodInfo) -> MethodResult {
let mode_raw = m.msg.read1::<u8>()?;
let mode = common::GameMode::try_from(mode_raw)
.map_err(|_| MethodErr::failed("Unsupported game mode value"))?;
common::set_game_mode(mode).map_err(|_| MethodErr::failed("Failed to set game mode"))?;
let timer = &m.path.get_data().reset_game_mode_timer;
timer
.clear()
.map_err(|_| MethodErr::failed("Failed to clear the game mode restore timer"))?;
Ok(vec![m.msg.method_return()])
}
fn set_game_mode_with_timeout(m: &MethodInfo) -> MethodResult {
let old_game_mode =
common::get_game_mode().map_err(|_| MethodErr::failed("Failed to get game mode"))?;
let (mode_raw, timeout_raw): (u8, u32) = m.msg.read2()?;
let mode = common::GameMode::try_from(mode_raw)
.map_err(|_| MethodErr::failed("Unsupported game mode value"))?;
let timeout = Duration::from_secs(timeout_raw.into());
common::set_game_mode(mode).map_err(|_| MethodErr::failed("Failed to set game mode"))?;
let timer = &m.path.get_data().reset_game_mode_timer;
timer
.reset(timeout, None)
.map_err(|_| MethodErr::failed("Failed to set the game mode restore timer"))?;
Ok(vec![m.msg.method_return().append1(old_game_mode as u8)])
}
fn get_rtc_audio_active(m: &MethodInfo) -> MethodResult {
match common::get_rtc_audio_active() {
Ok(active) => Ok(vec![m.msg.method_return().append1(active as u8)]),
Err(_) => Err(MethodErr::failed("Failed to get RTC audio activity")),
}
}
fn set_rtc_audio_active(m: &MethodInfo) -> MethodResult {
let active_raw = m.msg.read1::<u8>()?;
let active = common::RTCAudioActive::try_from(active_raw)
.map_err(|_| MethodErr::failed("Unsupported RTC audio active value"))?;
match common::set_rtc_audio_active(active) {
Ok(()) => Ok(vec![m.msg.method_return()]),
Err(_) => Err(MethodErr::failed("Failed to set RTC audio activity")),
}
}
fn create_pressure_chrome_signal(f: &Factory) -> Signal {
f.signal("MemoryPressureChrome", ())
.sarg::<u8, _>("pressure_level")
.sarg::<u64, _>("reclaim_target_kb")
}
fn create_pressure_arcvm_signal(f: &Factory) -> Signal {
f.signal("MemoryPressureArcvm", ())
.sarg::<u8, _>("pressure_level")
.sarg::<u64, _>("reclaim_target_kb")
}
fn send_pressure_signal(conn: &Connection, signal: &Signal, level: u8, reclaim_target_kb: u64) {
let msg = signal
.msg(&PATH_NAME.into(), &INTERFACE_NAME.into())
.append2(level, reclaim_target_kb);
if conn.send(msg).is_err() {
error!("Send pressure signal failed.");
}
}
pub fn service_main() -> Result<()> {
// Starting up a connection to the system bus and request a name.
let conn = Connection::new_system()?;
conn.register_name(SERVICE_NAME, 0)?;
let reset_game_mode_timer = Rc::new(TimerFd::new()?);
let f = dbus_tree::Factory::new_fn::<CustomData>();
// We create a tree with one object path inside and make that path introspectable.
let tree = f.tree(()).add(
f.object_path(
PATH_NAME,
DbusContext {
reset_game_mode_timer: reset_game_mode_timer.clone(),
},
)
.introspectable()
.add(
f.interface(INTERFACE_NAME, ())
.add_m(
f.method("GetAvailableMemoryKB", (), get_available_memory_kb)
// Our method has one output argument.
.outarg::<u64, _>("available"),
)
.add_m(
f.method(
"GetForegroundAvailableMemoryKB",
(),
get_foreground_available_memory_kb,
)
.outarg::<u64, _>("available"),
)
.add_m(
f.method("GetMemoryMarginsKB", (), get_memory_margins_kb)
.outarg::<u64, _>("reply"),
)
.add_m(
f.method("GetGameMode", (), get_game_mode)
.outarg::<u8, _>("game_mode"),
)
.add_m(
f.method("SetGameMode", (), set_game_mode)
.inarg::<u8, _>("game_mode"),
)
.add_m(
f.method("SetGameModeWithTimeout", (), set_game_mode_with_timeout)
.inarg::<u8, _>("game_mode")
.inarg::<u32, _>("timeout"),
)
.add_s(create_pressure_chrome_signal(&f))
.add_s(create_pressure_arcvm_signal(&f))
.add_m(
f.method("GetRTCAudioActive", (), get_rtc_audio_active)
.outarg::<u8, _>("mode"),
)
.add_m(
f.method("SetRTCAudioActive", (), set_rtc_audio_active)
.inarg::<u8, _>("mode"),
),
),
);
tree.set_registered(&conn, true)?;
conn.add_handler(tree);
let pressure_chrome_signal = create_pressure_chrome_signal(&f);
let pressure_arcvm_signal = create_pressure_arcvm_signal(&f);
let check_timer = TimerFd::new()?;
let check_interval = Duration::from_millis(1000);
check_timer.reset(check_interval, Some(check_interval))?;
#[derive(PollToken)]
enum Token {
CheckTimer,
ResetGameModeTimer,
DBusMsg(RawFd),
}
let poll_ctx = PollContext::<Token>::new()?;
poll_ctx.add(&check_timer.as_raw_fd(), Token::CheckTimer)?;
poll_ctx.add(
&reset_game_mode_timer.as_raw_fd(),
Token::ResetGameModeTimer,
)?;
for watch in conn.watch_fds() {
let mut events = WatchingEvents::empty();
if watch.readable() {
events = events.set_read()
}
if watch.writable() {
events = events.set_write()
}
poll_ctx.add_fd_with_events(&watch.fd(), events, Token::DBusMsg(watch.fd()))?;
}
loop {
// Wait for events.
for event in poll_ctx.wait()?.iter() {
match event.token() {
Token::CheckTimer => {
// wait() reads the fd. It's necessary to read periodic timerfd after each
// timerout.
check_timer.wait()?;
match memory::get_memory_pressure_status() {
Ok(pressure_status) => {
send_pressure_signal(
&conn,
&pressure_chrome_signal,
pressure_status.chrome_level as u8,
pressure_status.chrome_reclaim_target_kb,
);
if pressure_status.arcvm_level != memory::PressureLevelArcvm::None {
send_pressure_signal(
&conn,
&pressure_arcvm_signal,
pressure_status.arcvm_level as u8,
pressure_status.arcvm_reclaim_target_kb,
);
}
}
Err(e) => error!("get_memory_pressure_status() failed: {}", e),
}
}
Token::ResetGameModeTimer => {
warn!("Game mode heartbeat timed out.");
// wait() reads the fd. It's necessary to read timerfd after timeout.
reset_game_mode_timer.wait()?;
if common::set_game_mode(common::GameMode::Off).is_err() {
error!("Reset game mode failed.");
}
}
Token::DBusMsg(fd) => {
let mut revents = 0;
if event.readable() {
revents += WatchEvent::Readable as c_uint;
}
if event.writable() {
revents += WatchEvent::Writable as c_uint;
}
// Iterate through the watch items would call next() to process messages.
for _item in conn.watch_handle(fd, revents) {}
}
}
}
}
}