blob: 2fb77d2262c6aad0381c0b7faa774ccf4afec2ca [file] [log] [blame]
// Copyright 2021 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 crate::dev_control_handler::{self, DeviceControlHandler, DeviceControlHandlerBuilder};
use crate::error::PowerManagerError;
use crate::message::{Message, MessageReturn};
use crate::node::Node;
use crate::types::{Hertz, PState, Volts};
use crate::utils::connect_to_driver;
use anyhow::{Context as _, Error};
use async_trait::async_trait;
use fidl_fuchsia_hardware_cpu_ctrl as fcpu_ctrl;
use fuchsia_inspect as inspect;
use serde_derive::Deserialize;
use serde_json as json;
use std::collections::HashMap;
use std::rc::Rc;
/// Node: CpuDeviceHandler
///
/// Summary: Provides an interface to interact with a CPU driver, both as a generic driver via
/// fuchsia.device.Controller and specifically as a CPU driver via
/// fuchsia.hardware.cpu_ctrl.Device. Light wrapper around DeviceControlHandler.
/// Similar to CpuControlHandler in its logical management of a single CPU device, but is
/// more narrowly-scoped, as it does not administer thermal policy.
///
/// Handles Messages:
/// - GetPerformanceState
/// - SetPerformanceState
/// - GetCpuPerformanceStates
///
/// Sends Messages (via proxy to owned DeviceControlHandler):
/// - GetPerformanceState
/// - SetPerformanceState
///
/// FIDL dependencies:
/// - fuchsia.device.Controller: used via the owned DeviceControlHandler to control the
/// performance states of a CPU device
/// - fuchsia.hardware.cpu_ctrl.Device: used to query descriptions of CPU performance states
//
// TODO(fxbug.dev/84191): Update summary when CpuControlHandler is removed.
/// Builder struct for CpuDeviceHandler.
pub struct CpuDeviceHandlerBuilder<'a, 'b> {
/// Path to the CPU driver
driver_path: String,
/// Builder for the DeviceControlHandler that CpuDeviceHandler will own
dev_handler_builder: DeviceControlHandlerBuilder<'b>,
cpu_ctrl_proxy: Option<fcpu_ctrl::DeviceProxy>,
inspect_root: Option<&'a inspect::Node>,
}
impl<'a, 'b> CpuDeviceHandlerBuilder<'a, 'b> {
pub fn new_from_json(json_data: json::Value, _nodes: &HashMap<String, Rc<dyn Node>>) -> Self {
#[derive(Deserialize)]
struct Config {
driver_path: String,
}
#[derive(Deserialize)]
struct JsonData {
config: Config,
}
let data: JsonData = json::from_value(json_data).unwrap();
Self::new_with_driver_path(data.config.driver_path)
}
/// Constructs a CpuDeviceHandlerBuilder from the provided CPU driver path
pub fn new_with_driver_path(driver_path: String) -> Self {
Self {
driver_path: driver_path.clone(),
dev_handler_builder: DeviceControlHandlerBuilder::new_with_driver_path(driver_path),
cpu_ctrl_proxy: None,
inspect_root: None,
}
}
/// Test-only interface to construct a builder with fake proxies
#[cfg(test)]
fn new_with_proxies(
driver_path: String,
controller_proxy: fidl_fuchsia_device::ControllerProxy,
cpu_ctrl_proxy: fcpu_ctrl::DeviceProxy,
) -> Self {
let dev_handler_builder =
DeviceControlHandlerBuilder::new_with_proxy(driver_path.clone(), controller_proxy);
Self {
driver_path,
dev_handler_builder,
cpu_ctrl_proxy: Some(cpu_ctrl_proxy),
inspect_root: None,
}
}
/// Test-only interface to override the Inspect root
#[cfg(test)]
pub fn with_inspect_root(mut self, root: &'a inspect::Node) -> Self {
self.inspect_root = Some(root);
self
}
/// Retrieves all P-states from the provided cpu_ctrl proxy
async fn get_pstates(
cpu_driver_path: &str,
cpu_ctrl_proxy: &fcpu_ctrl::DeviceProxy,
) -> Result<Vec<PState>, Error> {
fuchsia_trace::duration!(
"power_manager",
"CpuDeviceHandler::get_pstates",
"driver" => cpu_driver_path
);
// Query P-state metadata from the cpu_ctrl interface. Each supported performance state
// has accompanying P-state metadata.
let mut pstates = Vec::new();
for i in 0..dev_control_handler::MAX_PERF_STATES {
if let Ok(info) = cpu_ctrl_proxy.get_performance_state_info(i).await? {
pstates.push(PState {
frequency: Hertz(info.frequency_hz as f64),
voltage: Volts(info.voltage_uv as f64 / 1e6),
})
} else {
break;
}
}
Ok(pstates)
}
/// Checks that the given list of P-states satisfies the following conditions:
/// - Contains at least one element;
/// - Is primarily sorted by frequency;
/// - Is strictly secondarily sorted by voltage.
fn validate_pstates(pstates: &Vec<PState>) -> Result<(), Error> {
if pstates.len() == 0 {
anyhow::bail!("Must have at least one P-state");
} else if pstates.len() > 1 {
for pair in pstates.as_slice().windows(2) {
if pair[1].frequency > pair[0].frequency
|| (pair[1].frequency == pair[0].frequency
&& pair[1].voltage >= pair[0].voltage)
{
anyhow::bail!(
"P-states must be primarily sorted by decreasing frequency and secondarily \
sorted by decreasing voltage; violated by {:?} and {:?}.",
pair[0],
pair[1]
);
}
}
}
Ok(())
}
pub async fn build(self) -> Result<Rc<CpuDeviceHandler>, Error> {
// Optionally use the default proxy
let cpu_ctrl_proxy = match self.cpu_ctrl_proxy {
Some(proxy) => proxy,
None => connect_to_driver::<fcpu_ctrl::DeviceMarker>(&self.driver_path)
.await
.context("Failed connecting to CPU driver")?,
};
let pstates = Self::get_pstates(&self.driver_path, &cpu_ctrl_proxy).await?;
Self::validate_pstates(&pstates)?;
// Optionally use the default inspect root node
let inspect_root = self.inspect_root.unwrap_or(inspect::component::inspector().root());
// Create the root Inspect node for CpuDeviceHandler
let local_root =
inspect_root.create_child(format!("CpuDeviceHandler ({})", &self.driver_path));
Self::add_pstates_to_inspect(&local_root, &pstates);
// Build the DeviceControlHandler
let dev_handler_builder = self.dev_handler_builder.with_inspect_root(&local_root);
let dev_control_handler = dev_handler_builder.build().await?;
// Since CpuDeviceHandler has no dynamic Inspect data, its node will be owned by the root
inspect_root.record(local_root);
Ok(Rc::new(CpuDeviceHandler {
driver_path: self.driver_path,
dev_control_handler,
pstates,
}))
}
fn add_pstates_to_inspect(parent: &inspect::Node, pstates: &Vec<PState>) {
parent.record_child("P-states", |pstates_node| {
// Iterate P-states in reverse order so that the Inspect nodes appear in the same order
// as the vector (`record_child` inserts nodes at the head).
for (i, pstate) in pstates.iter().enumerate().rev() {
pstates_node.record_child(format!("pstate_{:02}", i), |node| {
node.record_double("voltage (V)", pstate.voltage.0);
node.record_double("frequency (Hz)", pstate.frequency.0);
});
}
});
}
}
pub struct CpuDeviceHandler {
/// Path to the underlying CPU driver
driver_path: String,
/// Child node to handle GetPerformanceState and SetPerformanceState
dev_control_handler: Rc<DeviceControlHandler>,
/// All P-states provided by the underlying CPU driver
pstates: Vec<PState>,
}
#[async_trait(?Send)]
impl Node for CpuDeviceHandler {
fn name(&self) -> String {
format!("CpuDeviceHandler ({})", self.driver_path)
}
async fn handle_message(&self, msg: &Message) -> Result<MessageReturn, PowerManagerError> {
match msg {
&Message::GetPerformanceState | Message::SetPerformanceState(_) => {
self.dev_control_handler.handle_message(msg).await
}
Message::GetCpuPerformanceStates => {
Ok(MessageReturn::GetCpuPerformanceStates(self.pstates.clone()))
}
_ => Err(PowerManagerError::Unsupported),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use fuchsia_async as fasync;
use fuchsia_zircon as zx;
use futures::TryStreamExt;
use inspect::assert_data_tree;
use std::cell::Cell;
// Creates a fake fuchsia.device.Controller proxy
fn setup_fake_controller_proxy() -> fidl_fuchsia_device::ControllerProxy {
let perf_state = Rc::new(Cell::new(0));
let perf_state_clone_1 = perf_state.clone();
let perf_state_clone_2 = perf_state.clone();
let get_performance_state = move || perf_state_clone_1.get();
let set_performance_state = move |state| {
perf_state_clone_2.set(state);
};
dev_control_handler::tests::setup_fake_driver(get_performance_state, set_performance_state)
}
/// Creates a fake fuchsia.hardware.cpu_ctrl.Device proxy
fn setup_fake_cpu_ctrl_proxy(pstates: Vec<PState>) -> fcpu_ctrl::DeviceProxy {
let (proxy, mut stream) =
fidl::endpoints::create_proxy_and_stream::<fcpu_ctrl::DeviceMarker>().unwrap();
fasync::Task::local(async move {
while let Ok(req) = stream.try_next().await {
match req {
Some(fcpu_ctrl::DeviceRequest::GetPerformanceStateInfo {
state,
responder,
}) => {
let index = state as usize;
let mut result = if index < pstates.len() {
Ok(fcpu_ctrl::CpuPerformanceStateInfo {
frequency_hz: pstates[index].frequency.0 as i64,
voltage_uv: (pstates[index].voltage.0 * 1e6) as i64,
})
} else {
Err(zx::Status::NOT_SUPPORTED.into_raw())
};
let _ = responder.send(&mut result);
}
Some(other) => panic!("Unexpected request: {:?}", other),
None => break, // Stream terminates when client is dropped
}
}
})
.detach();
proxy
}
async fn setup_simple_test_node(pstates: Vec<PState>) -> Rc<CpuDeviceHandler> {
let builder = CpuDeviceHandlerBuilder::new_with_proxies(
"fake_path".to_string(),
setup_fake_controller_proxy(),
setup_fake_cpu_ctrl_proxy(pstates),
);
builder.build().await.unwrap()
}
/// Tests that an unsupported message is handled gracefully and an Unsupported error is returned
#[fasync::run_singlethreaded(test)]
async fn test_unsupported_msg() {
let pstates = vec![PState { frequency: Hertz(1e9), voltage: Volts(1.0) }];
let node = setup_simple_test_node(pstates).await;
match node.handle_message(&Message::ReadTemperature).await {
Err(PowerManagerError::Unsupported) => {}
e => panic!("Unexpected return value: {:?}", e),
}
}
/// Tests that the Get/SetPerformanceState messages cause the node to call the appropriate
/// device controller FIDL APIs via DeviceControllerHandler.
#[fasync::run_singlethreaded(test)]
async fn test_performance_state() {
let pstates = vec![PState { frequency: Hertz(1e9), voltage: Volts(1.0) }];
let node = setup_simple_test_node(pstates).await;
// Send SetPerformanceState message to set a state of 1
let commanded_perf_state = 1;
match node
.handle_message(&Message::SetPerformanceState(commanded_perf_state))
.await
.unwrap()
{
MessageReturn::SetPerformanceState => {}
e => panic!("Unexpected return value: {:?}", e),
}
// Verify GetPerformanceState reads back the same state
let received_perf_state =
match node.handle_message(&Message::GetPerformanceState).await.unwrap() {
MessageReturn::GetPerformanceState(state) => state,
e => panic!("Unexpected return value: {:?}", e),
};
assert_eq!(commanded_perf_state, received_perf_state);
// Send SetPerformanceState message to set a state of 2
let commanded_perf_state = 2;
match node
.handle_message(&Message::SetPerformanceState(commanded_perf_state))
.await
.unwrap()
{
MessageReturn::SetPerformanceState => {}
e => panic!("Unexpected return value: {:?}", e),
}
// Verify GetPerformanceState reads back the same state
let received_perf_state =
match node.handle_message(&Message::GetPerformanceState).await.unwrap() {
MessageReturn::GetPerformanceState(state) => state,
e => panic!("Unexpected return value: {:?}", e),
};
assert_eq!(commanded_perf_state, received_perf_state);
}
/// Tests that a GetCpuPerformanceStates message is handled properly.
#[fasync::run_singlethreaded(test)]
async fn test_get_cpu_performance_states() {
let pstates = vec![
PState { frequency: Hertz(1.4e9), voltage: Volts(0.9) },
PState { frequency: Hertz(1.3e9), voltage: Volts(0.8) },
PState { frequency: Hertz(1.2e9), voltage: Volts(0.7) },
];
let node = setup_simple_test_node(pstates.clone()).await;
let received_pstates =
match node.handle_message(&Message::GetCpuPerformanceStates).await.unwrap() {
MessageReturn::GetCpuPerformanceStates(v) => v,
e => panic!("Unexpected return value: {:?}", e),
};
assert_eq!(pstates, received_pstates);
}
/// Tests that P-state validation works as expected.
#[fasync::run_singlethreaded(test)]
async fn test_pstate_validation() {
// Primary sort by frequency is violated.
let pstates = vec![
PState { frequency: Hertz(1.5e9), voltage: Volts(1.0) },
PState { frequency: Hertz(1.6e9), voltage: Volts(1.0) },
];
let builder = CpuDeviceHandlerBuilder::new_with_proxies(
"fake_path".to_string(),
setup_fake_controller_proxy(),
setup_fake_cpu_ctrl_proxy(pstates),
);
assert!(builder.build().await.is_err());
// Secondary sort by voltage is violated.
let pstates = vec![
PState { frequency: Hertz(1.5e9), voltage: Volts(1.0) },
PState { frequency: Hertz(1.5e9), voltage: Volts(1.1) },
];
let builder = CpuDeviceHandlerBuilder::new_with_proxies(
"fake_path".to_string(),
setup_fake_controller_proxy(),
setup_fake_cpu_ctrl_proxy(pstates),
);
assert!(builder.build().await.is_err());
// Duplicated P-state (detected as violation of secondary sort by voltage).
let pstates = vec![
PState { frequency: Hertz(1.5e9), voltage: Volts(1.0) },
PState { frequency: Hertz(1.5e9), voltage: Volts(1.0) },
];
let builder = CpuDeviceHandlerBuilder::new_with_proxies(
"fake_path".to_string(),
setup_fake_controller_proxy(),
setup_fake_cpu_ctrl_proxy(pstates),
);
assert!(builder.build().await.is_err());
}
/// Tests that Inspect data is populated as expected
#[fasync::run_singlethreaded(test)]
async fn test_inspect_data() {
let pstates = vec![
PState { frequency: Hertz(1.3e9), voltage: Volts(0.8) },
PState { frequency: Hertz(1.2e9), voltage: Volts(0.7) },
];
let inspector = inspect::Inspector::new();
let builder = CpuDeviceHandlerBuilder::new_with_proxies(
"fake_path".to_string(),
setup_fake_controller_proxy(),
setup_fake_cpu_ctrl_proxy(pstates.clone()),
)
.with_inspect_root(inspector.root());
let _node = builder.build().await.unwrap();
assert_data_tree!(
inspector,
root: {
"CpuDeviceHandler (fake_path)": {
"P-states": {
pstate_00: {
"frequency (Hz)": pstates[0].frequency.0,
"voltage (V)": pstates[0].voltage.0,
},
pstate_01: {
"frequency (Hz)": pstates[1].frequency.0,
"voltage (V)": pstates[1].voltage.0,
},
},
"DeviceControlHandler (fake_path)": contains {
performance_state: 0u64,
}
}
}
);
}
}