blob: 0182f169218a328528f2c645579f69ee8ded1fc7 [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 anyhow::{format_err, Context as _, Error};
use fidl_fuchsia_hardware_gpu_clock as fidl_gpu;
use fidl_fuchsia_hardware_thermal as fidl_thermal;
use fuchsia_async as fasync;
use fuchsia_syslog::{fx_log_err, fx_log_info};
use fuchsia_zircon as zx;
use std::convert::TryInto;
use std::fs::File;
use std::os::unix::io::AsRawFd;
use std::path::Path;
static CPU_THERMAL_DRIVER_PATH: &str = "/dev/class/thermal/000";
static GPU_CLOCK_DRIVER_PATH: &str = "/dev/class/gpu-thermal/000";
fn match_driver(path: &Path) -> Result<File, Error> {
let dir = path.parent().ok_or(format_err!("Invalid driver path"))?;
let name = path.file_name().ok_or(format_err!("Invalid driver name"))?;
let dir_fd = File::open(&dir).context("Failed to open driver path")?;
let st = fdio::watch_directory(&dir_fd, zx::sys::ZX_TIME_INFINITE, |event, matched| {
if event == fdio::WatchEvent::AddFile && matched == name {
Err(zx::Status::STOP)
} else {
Ok(())
}
});
if st == zx::Status::STOP {
Ok(File::open(&path).context("Failed to open file")?)
} else {
Err(format_err!("Watcher terminated without finding sensor"))
}
}
fn get_service_handle(fd: &File) -> Result<u32, Error> {
let fd = fd.as_raw_fd();
let mut handle = 0;
zx::Status::ok(unsafe { fdio::fdio_sys::fdio_get_service_handle(fd, &mut handle) })?;
Ok(handle)
}
fn get_service_channel(path: &Path) -> Result<zx::Channel, Error> {
let fd = match_driver(path).context("Failed to match driver")?;
let handle = get_service_handle(&fd).context("Failed to get service handle")?;
Ok(zx::Channel::from(unsafe { zx::Handle::from_raw(handle) }))
}
fn get_cpu_thermal_proxy() -> Result<fidl_thermal::DeviceProxy, Error> {
let channel = get_service_channel(Path::new(CPU_THERMAL_DRIVER_PATH))?;
Ok(fidl_thermal::DeviceProxy::new(fasync::Channel::from_channel(channel)?))
}
fn get_gpu_clock_proxy() -> Result<fidl_gpu::ClockProxy, Error> {
let channel = get_service_channel(Path::new(GPU_CLOCK_DRIVER_PATH))?;
Ok(fidl_gpu::ClockProxy::new(fasync::Channel::from_channel(channel)?))
}
async fn get_thermal_info(
proxy: &fidl_thermal::DeviceProxy,
) -> Result<fidl_thermal::ThermalDeviceInfo, Error> {
let (status, info) = proxy.get_device_info().await?;
zx::Status::ok(status)
.context(format!("get_device_info failed for CPU thermal device: {}", status))?;
let info = *info.ok_or(format_err!("get_device_info returned mising info"))?;
if info.num_trip_points == 0 {
Err(format_err!("Trip points not supported"))
} else if !info.active_cooling && !info.passive_cooling {
Err(format_err!("No active or passive cooling present on device"))
} else {
Ok(info)
}
}
async fn get_notify_port(proxy: &fidl_thermal::DeviceProxy) -> Result<zx::Port, Error> {
let (status, port) = proxy.get_state_change_port().await?;
zx::Status::ok(status)
.context(format!("get_state_change_port failed for CPU thermal device: {}", status))?;
let port = port.ok_or(format_err!("get_state_change_port returned missing info"))?;
Ok(port)
}
fn get_trip_idx(packet: &zx::Packet, info: &fidl_thermal::ThermalDeviceInfo) -> Result<u32, Error> {
let trip_idx: u32 = packet.key().try_into()?;
if trip_idx < info.num_trip_points {
Ok(trip_idx)
} else {
Err(format_err!("Invalid trip index: terminating thermd"))
}
}
async fn handle_packet(
packet: zx::Packet,
info: &fidl_thermal::ThermalDeviceInfo,
cpu_proxy: &fidl_thermal::DeviceProxy,
gpu_proxy: &fidl_gpu::ClockProxy,
) -> Result<(), Error> {
let trip_idx = get_trip_idx(&packet, info)? as usize;
fx_log_info!("Trip point updated ({})", trip_idx);
if info.passive_cooling {
let big_cluster_opp = info.trip_point_info[trip_idx].big_cluster_dvfs_opp;
// Set DVFS Opp for Big Cluster.
let status = cpu_proxy
.set_dvfs_operating_point(
big_cluster_opp,
fidl_thermal::PowerDomain::BigClusterPowerDomain,
)
.await?;
if zx::Status::ok(status).is_err() {
fx_log_err!("ERROR: Failed to set DVFS OPP for big cluster");
}
// Check if it's big little.
if info.big_little {
// Set the DVFS Opp for Little Cluster.
let little_cluster_opp = info.trip_point_info[trip_idx].little_cluster_dvfs_opp;
let status = cpu_proxy
.set_dvfs_operating_point(
little_cluster_opp,
fidl_thermal::PowerDomain::LittleClusterPowerDomain,
)
.await?;
if zx::Status::ok(status).is_err() {
fx_log_err!("ERROR: Failed to set DVFS OPP for little cluster");
}
}
}
if info.active_cooling {
let fan_level = info.trip_point_info[trip_idx].fan_level;
let status = cpu_proxy.set_fan_level(fan_level).await?;
if zx::Status::ok(status).is_err() {
fx_log_err!("ERROR: Failed to set fan level");
}
}
if info.gpu_throttling {
let gpu_clk_freq_source = info.trip_point_info[trip_idx].gpu_clk_freq_source;
let status = gpu_proxy.set_frequency_source(gpu_clk_freq_source).await?;
if zx::Status::ok(status).is_err() {
fx_log_err!("ERROR: Failed to change GPU clock freq source");
}
}
Ok(())
}
#[fasync::run_singlethreaded]
async fn main() -> Result<(), Error> {
fuchsia_syslog::init_with_tags(&["thermd"])?;
fx_log_info!("started");
// TODO(braval): This sleep is not needed here but leaving it here
// since the Intel thermd has it. Clean up when both deamons are
// unified
zx::Duration::from_seconds(3).sleep();
let cpu_thermal_proxy = get_cpu_thermal_proxy()?;
let gpu_clock_proxy = get_gpu_clock_proxy()?;
let thermal_info = get_thermal_info(&cpu_thermal_proxy).await?;
let port = get_notify_port(&cpu_thermal_proxy).await?;
fx_log_info!("Entering loop...");
loop {
let packet = port.wait(zx::Time::INFINITE).context("Failed to wait on port")?;
handle_packet(packet, &thermal_info, &cpu_thermal_proxy, &gpu_clock_proxy).await?;
}
}
#[cfg(test)]
mod tests {
use {
super::*,
fidl::endpoints::RequestStream,
futures::TryStreamExt,
std::sync::{Arc, RwLock},
};
fn generate_packet(key: u64) -> zx::Packet {
zx::Packet::from_user_packet(key, 0, zx::UserPacket::from_u8_array([0; 32]))
}
fn spawn_fake_thermal_service(
state: Arc<RwLock<DeviceState>>,
mut stream: fidl_thermal::DeviceRequestStream,
) {
fasync::Task::spawn(async move {
while let Ok(req) = stream.try_next().await {
match req {
Some(fidl_thermal::DeviceRequest::GetDeviceInfo { responder }) => {
let _st = responder.send(0, None);
}
Some(fidl_thermal::DeviceRequest::SetDvfsOperatingPoint {
op_idx,
power_domain,
responder,
}) => {
match power_domain {
fidl_thermal::PowerDomain::BigClusterPowerDomain => {
state.write().unwrap().big_op_idx = op_idx;
}
fidl_thermal::PowerDomain::LittleClusterPowerDomain => {
state.write().unwrap().little_op_idx = op_idx;
}
}
let _st = responder.send(0);
}
Some(fidl_thermal::DeviceRequest::SetFanLevel { fan_level, responder }) => {
state.write().unwrap().fan_level = fan_level;
let _st = responder.send(0);
}
_ => {
println!("Unknown request");
}
}
}
})
.detach()
}
fn spawn_fake_gpu_service(
state: Arc<RwLock<DeviceState>>,
mut stream: fidl_gpu::ClockRequestStream,
) {
fasync::Task::spawn(async move {
while let Ok(req) = stream.try_next().await {
match req {
Some(fidl_gpu::ClockRequest::SetFrequencySource { source, responder }) => {
state.write().unwrap().gpu_freq_source = source;
let _st = responder.send(0);
}
_ => {
println!("Unknown request");
}
}
}
})
.detach()
}
// test that invalid trip point key is rejected
async fn test_trip_point_key(mut info: fidl_thermal::ThermalDeviceInfo) {
info.num_trip_points = 2;
assert_eq!(get_trip_idx(&generate_packet(0), &info).unwrap(), 0);
assert_eq!(get_trip_idx(&generate_packet(1), &info).unwrap(), 1);
assert!(get_trip_idx(&generate_packet(2), &info).is_err());
}
// tests that handle_packet will call the expected APIs to configure the thermal service
// based on packet input
async fn test_iterate_trip_points(
info: fidl_thermal::ThermalDeviceInfo,
cpu_proxy: &fidl_thermal::DeviceProxy,
gpu_proxy: &fidl_gpu::ClockProxy,
state: &Arc<RwLock<DeviceState>>,
) {
println!("test_iterate_trip_points");
for i in 0..info.num_trip_points {
println!("Sending trip point {}", i);
let packet = generate_packet(i.into());
let status = handle_packet(packet, &info, &cpu_proxy, &gpu_proxy).await;
let trip_info = info.trip_point_info[i as usize];
assert!(status.is_ok());
assert_eq!(state.read().unwrap().big_op_idx, trip_info.big_cluster_dvfs_opp);
assert_eq!(state.read().unwrap().little_op_idx, trip_info.little_cluster_dvfs_opp);
assert_eq!(state.read().unwrap().fan_level, trip_info.fan_level);
assert_eq!(state.read().unwrap().gpu_freq_source, trip_info.gpu_clk_freq_source);
}
}
fn generate_thermal_info() -> fidl_thermal::ThermalDeviceInfo {
let empty_trip_point = fidl_thermal::ThermalTemperatureInfo {
up_temp_celsius: 0.0,
down_temp_celsius: 0.0,
fan_level: 0,
big_cluster_dvfs_opp: 0,
little_cluster_dvfs_opp: 0,
gpu_clk_freq_source: 0,
};
let empty_opp_entry = fidl_thermal::OperatingPointEntry { freq_hz: 0, volt_uv: 0 };
let empty_opp =
fidl_thermal::OperatingPoint { opp: [empty_opp_entry; 16], latency: 0, count: 0 };
let mut info = fidl_thermal::ThermalDeviceInfo {
active_cooling: true,
passive_cooling: true,
gpu_throttling: true,
num_trip_points: 3,
big_little: true,
critical_temp_celsius: 0.0,
trip_point_info: [empty_trip_point; 16],
opps: [empty_opp; 2],
};
info.trip_point_info[0] = fidl_thermal::ThermalTemperatureInfo {
up_temp_celsius: 0.0,
down_temp_celsius: 0.0,
fan_level: 1000,
big_cluster_dvfs_opp: 1001,
little_cluster_dvfs_opp: 1002,
gpu_clk_freq_source: 1003,
};
info.trip_point_info[1] = fidl_thermal::ThermalTemperatureInfo {
up_temp_celsius: 0.0,
down_temp_celsius: 0.0,
fan_level: 2000,
big_cluster_dvfs_opp: 2001,
little_cluster_dvfs_opp: 2002,
gpu_clk_freq_source: 2003,
};
info.trip_point_info[2] = fidl_thermal::ThermalTemperatureInfo {
up_temp_celsius: 0.0,
down_temp_celsius: 0.0,
fan_level: 3000,
big_cluster_dvfs_opp: 3001,
little_cluster_dvfs_opp: 3002,
gpu_clk_freq_source: 3003,
};
info
}
fn thermal_test_setup() -> (
fidl_thermal::ThermalDeviceInfo,
fidl_thermal::DeviceProxy,
fidl_thermal::DeviceRequestStream,
) {
let (client, server) = zx::Channel::create().unwrap();
let client = fasync::Channel::from_channel(client).unwrap();
let server = fasync::Channel::from_channel(server).unwrap();
let proxy = fidl_thermal::DeviceProxy::new(client);
let stream = fidl_thermal::DeviceRequestStream::from_channel(server);
(generate_thermal_info(), proxy, stream)
}
fn gpu_test_setup() -> (fidl_gpu::ClockProxy, fidl_gpu::ClockRequestStream) {
let (client, server) = zx::Channel::create().unwrap();
let client = fasync::Channel::from_channel(client).unwrap();
let server = fasync::Channel::from_channel(server).unwrap();
let proxy = fidl_gpu::ClockProxy::new(client);
let stream = fidl_gpu::ClockRequestStream::from_channel(server);
(proxy, stream)
}
struct DeviceState {
big_op_idx: u16,
little_op_idx: u16,
fan_level: u32,
gpu_freq_source: u32,
}
#[fasync::run_singlethreaded(test)]
async fn test_handle_packet() {
let (t_info, t_proxy, t_stream) = thermal_test_setup();
let (g_proxy, g_stream) = gpu_test_setup();
let state: Arc<RwLock<DeviceState>> = Arc::new(RwLock::new(DeviceState {
big_op_idx: 0,
little_op_idx: 0,
fan_level: 0,
gpu_freq_source: 0,
}));
spawn_fake_thermal_service(state.clone(), t_stream);
spawn_fake_gpu_service(state.clone(), g_stream);
test_trip_point_key(t_info).await;
test_iterate_trip_points(t_info, &t_proxy, &g_proxy, &state).await;
}
}