blob: 3c4ed292bfa0e156535c174125a38f2df680c1cd [file] [log] [blame]
// Copyright 2020 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::error::PowerManagerError;
use crate::log_if_err;
use crate::message::{Message, MessageReturn};
use crate::node::Node;
use crate::utils::connect_proxy;
use anyhow::{format_err, Error};
use async_trait::async_trait;
use fidl_fuchsia_device as fdev;
use fuchsia_inspect::{self as inspect, NumericProperty, Property};
use fuchsia_zircon as zx;
use serde_derive::Deserialize;
use serde_json as json;
use std::collections::HashMap;
use std::rc::Rc;
/// Node: DeviceControlHandler
///
/// Summary: Provides an interface to control the power, performance, and sleep states of a devhost
/// device
///
/// Handles Messages:
/// - GetPerformanceState
/// - SetPerformanceState
///
/// Sends Messages: N/A
///
/// FIDL dependencies:
/// - fuchsia.device.Controller: the node uses this protocol to control the power, performance,
/// and sleep states of a devhost device
pub const MAX_PERF_STATES: u32 = fdev::MAX_DEVICE_PERFORMANCE_STATES;
/// A builder for constructing the DeviceControlhandler node
pub struct DeviceControlHandlerBuilder<'a> {
driver_path: String,
driver_proxy: Option<fdev::ControllerProxy>,
inspect_root: Option<&'a inspect::Node>,
}
impl<'a> DeviceControlHandlerBuilder<'a> {
pub fn new_with_driver_path(driver_path: String) -> Self {
Self { driver_path, driver_proxy: None, inspect_root: None }
}
#[cfg(test)]
pub fn new_with_proxy(driver_path: String, proxy: fdev::ControllerProxy) -> Self {
Self { driver_path, driver_proxy: Some(proxy), inspect_root: None }
}
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)
}
#[cfg(test)]
pub fn with_inspect_root(mut self, root: &'a inspect::Node) -> Self {
self.inspect_root = Some(root);
self
}
pub fn build(self) -> Result<Rc<DeviceControlHandler>, Error> {
// Optionally use the default proxy
let proxy = if self.driver_proxy.is_none() {
connect_proxy::<fdev::ControllerMarker>(&self.driver_path)?
} else {
self.driver_proxy.unwrap()
};
// Optionally use the default inspect root node
let inspect_root = self.inspect_root.unwrap_or(inspect::component::inspector().root());
Ok(Rc::new(DeviceControlHandler {
driver_path: self.driver_path.clone(),
driver_proxy: proxy,
inspect: InspectData::new(
inspect_root,
format!("DeviceControlHandler ({})", self.driver_path),
),
}))
}
}
pub struct DeviceControlHandler {
driver_path: String,
driver_proxy: fdev::ControllerProxy,
/// A struct for managing Component Inspection data
inspect: InspectData,
}
impl DeviceControlHandler {
async fn handle_get_performance_state(&self) -> Result<MessageReturn, PowerManagerError> {
fuchsia_trace::duration!(
"power_manager",
"DeviceControlHandler::handle_get_performance_state",
"driver" => self.driver_path.as_str()
);
let result = self.get_performance_state().await;
log_if_err!(result, "Failed to get performance state");
fuchsia_trace::instant!(
"power_manager",
"DeviceControlHandler::get_performance_state_result",
fuchsia_trace::Scope::Thread,
"driver" => self.driver_path.as_str(),
"result" => format!("{:?}", result).as_str()
);
match result {
Ok(state) => Ok(MessageReturn::GetPerformanceState(state)),
Err(e) => {
self.inspect.get_performance_state_errors.add(1);
Err(PowerManagerError::GenericError(e))
}
}
}
async fn get_performance_state(&self) -> Result<u32, Error> {
let state =
self.driver_proxy.get_current_performance_state().await.map_err(|e| {
format_err!("{}: get_performance_state IPC failed: {}", self.name(), e)
})?;
Ok(state)
}
async fn handle_set_performance_state(
&self,
in_state: u32,
) -> Result<MessageReturn, PowerManagerError> {
fuchsia_trace::duration!(
"power_manager",
"DeviceControlHandler::handle_set_performance_state",
"driver" => self.driver_path.as_str(),
"state" => in_state
);
let result = self.set_performance_state(in_state).await;
log_if_err!(result, "Failed to set performance state");
fuchsia_trace::instant!(
"power_manager",
"DeviceControlHandler::set_performance_state_result",
fuchsia_trace::Scope::Thread,
"driver" => self.driver_path.as_str(),
"result" => format!("{:?}", result).as_str()
);
match result {
Ok(_) => {
self.inspect.perf_state.set(in_state.into());
Ok(MessageReturn::SetPerformanceState)
}
Err(e) => {
self.inspect.set_performance_state_errors.add(1);
self.inspect.last_set_performance_state_error.set(format!("{}", e).as_str());
Err(PowerManagerError::GenericError(e))
}
}
}
async fn set_performance_state(&self, in_state: u32) -> Result<(), Error> {
// Make the FIDL call
let (status, _out_state) =
self.driver_proxy.set_performance_state(in_state).await.map_err(|e| {
format_err!("{}: set_performance_state IPC failed: {}", self.name(), e)
})?;
// Check the status code
zx::Status::ok(status).map_err(|e| {
format_err!("{}: set_performance_state driver returned error: {}", self.name(), e)
})?;
Ok(())
}
}
#[async_trait(?Send)]
impl Node for DeviceControlHandler {
fn name(&self) -> String {
format!("DeviceControlHandler ({})", self.driver_path)
}
async fn handle_message(&self, msg: &Message) -> Result<MessageReturn, PowerManagerError> {
match msg {
Message::GetPerformanceState => self.handle_get_performance_state().await,
Message::SetPerformanceState(state) => self.handle_set_performance_state(*state).await,
_ => Err(PowerManagerError::Unsupported),
}
}
}
struct InspectData {
perf_state: inspect::UintProperty,
get_performance_state_errors: inspect::UintProperty,
set_performance_state_errors: inspect::UintProperty,
last_set_performance_state_error: inspect::StringProperty,
}
impl InspectData {
fn new(parent: &inspect::Node, name: String) -> Self {
// Create a local root node and properties
let root = parent.create_child(name);
let perf_state = root.create_uint("performance_state", 0);
let get_performance_state_errors = root.create_uint("get_performance_state_errors", 0);
let set_performance_state_errors = root.create_uint("set_performance_state_errors", 0);
let last_set_performance_state_error =
root.create_string("last_set_performance_state_error", "");
// Pass ownership of the new node to the parent node, otherwise it'll be dropped
parent.record(root);
InspectData {
perf_state,
get_performance_state_errors,
set_performance_state_errors,
last_set_performance_state_error,
}
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use fuchsia_async as fasync;
use futures::TryStreamExt;
use inspect::assert_inspect_tree;
use std::cell::Cell;
fn setup_fake_driver(
get_performance_state: impl Fn() -> u32 + 'static,
mut set_performance_state: impl FnMut(u32) + 'static,
) -> fdev::ControllerProxy {
let (proxy, mut stream) =
fidl::endpoints::create_proxy_and_stream::<fdev::ControllerMarker>().unwrap();
fasync::Task::local(async move {
while let Ok(req) = stream.try_next().await {
match req {
Some(fdev::ControllerRequest::GetCurrentPerformanceState { responder }) => {
let _ = responder.send(get_performance_state());
}
Some(fdev::ControllerRequest::SetPerformanceState {
requested_state,
responder,
}) => {
set_performance_state(requested_state as u32);
let _ = responder.send(zx::Status::OK.into_raw(), requested_state);
}
_ => assert!(false),
}
}
})
.detach();
proxy
}
pub fn setup_test_node(
get_performance_state: impl Fn() -> u32 + 'static,
set_performance_state: impl FnMut(u32) + 'static,
) -> Rc<DeviceControlHandler> {
DeviceControlHandlerBuilder::new_with_proxy(
"Fake".to_string(),
setup_fake_driver(get_performance_state, set_performance_state),
)
.build()
.unwrap()
}
pub fn setup_simple_test_node() -> Rc<DeviceControlHandler> {
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);
};
setup_test_node(get_performance_state, set_performance_state)
}
/// Tests that an unsupported message is handled gracefully and an Unsupported error is returned
#[fasync::run_singlethreaded(test)]
async fn test_unsupported_msg() {
let node = setup_simple_test_node();
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.
#[fasync::run_singlethreaded(test)]
async fn test_performance_state() {
let node = setup_simple_test_node();
// 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 for the presence and correctness of dynamically-added inspect data
#[fasync::run_singlethreaded(test)]
async fn test_inspect_data() {
let inspector = inspect::Inspector::new();
let _node = DeviceControlHandlerBuilder::new_with_proxy(
"Fake".to_string(),
fidl::endpoints::create_proxy::<fdev::ControllerMarker>().unwrap().0,
)
.with_inspect_root(inspector.root())
.build()
.unwrap();
assert_inspect_tree!(
inspector,
root: {
"DeviceControlHandler (Fake)": contains {}
}
);
}
/// Tests that well-formed configuration JSON does not panic the `new_from_json` function.
#[fasync::run_singlethreaded(test)]
async fn test_new_from_json() {
let json_data = json::json!({
"type": "DeviceControlHandler",
"name": "dev_control",
"config": {
"driver_path": "/dev/class/cpu-ctrl/000"
}
});
let _ = DeviceControlHandlerBuilder::new_from_json(json_data, &HashMap::new());
}
}