blob: 1ff893d85db0979cc41cb3774234c9151b92648b [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::os::raw::c_uint;
use std::os::unix::io::{AsRawFd, RawFd};
use std::time::Duration;
use anyhow::Result;
use dbus::ffidisp::{Connection, WatchEvent};
use dbus::tree::{Factory, MTFn, MethodErr, MethodInfo, MethodResult, Signal};
use sys_util::{error, 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;
fn get_available_memory_kb(m: &MethodInfo<MTFn<()>, ()>) -> 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<MTFn<()>, ()>) -> 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",
)),
}
}
fn get_memory_margins_kb(m: &MethodInfo<MTFn<()>, ()>) -> 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<MTFn<()>, ()>) -> 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<MTFn<()>, ()>) -> MethodResult {
let mode = match m.msg.read1::<u8>()? {
0 => common::GameMode::Off,
1 => common::GameMode::Borealis,
_ => return Err(MethodErr::failed("Unsupported game mode value")),
};
match common::set_game_mode(mode) {
Ok(()) => Ok(vec![m.msg.method_return()]),
Err(_) => Err(MethodErr::failed("Failed to set game mode")),
}
}
fn create_pressure_chrome_signal(f: &Factory<MTFn<()>, ()>) -> Signal<()> {
f.signal("MemoryPressureChrome", ())
.sarg::<u8, _>("pressure_level")
.sarg::<u64, _>("reclaim_target_kb")
}
fn create_pressure_arcvm_signal(f: &Factory<MTFn<()>, ()>) -> 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 f = Factory::new_fn::<()>();
// We create a tree with one object path inside and make that path introspectable.
let tree = f.tree(()).add(
f.object_path(PATH_NAME, ()).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_s(create_pressure_chrome_signal(&f))
.add_s(create_pressure_arcvm_signal(&f)),
),
);
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 {
TimerMsg,
DBusMsg(RawFd),
}
let poll_ctx = PollContext::<Token>::new()?;
poll_ctx.add(&check_timer.as_raw_fd(), Token::TimerMsg)?;
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::TimerMsg => {
// 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::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) {}
}
}
}
}
}