blob: dacd729b1a312a36513b4b17476b4f381ef6380d [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::error::PowerManagerError;
use crate::message::{Message, MessageResult, MessageReturn};
use crate::node::Node;
use anyhow::{format_err, Error};
use async_trait::async_trait;
use fidl_fuchsia_kernel as fkernel;
use fuchsia_inspect::{self as inspect, NumericProperty as _, Property as _};
use fuchsia_zircon::sys;
use fuchsia_zircon::{self as zx, prelude::AsHandleRef};
use std::rc::Rc;
/// Node: SyscallHandler
///
/// Summary: Executes syscalls so that these calls can be easily mocked by tests for dependent
/// nodes.
///
/// Handles Messages:
/// - GetNumCpus
/// - SetCpuPerformanceInfo
///
/// FIDL dependencies: None
#[derive(Default)]
pub struct SyscallHandlerBuilder<'a> {
/// A fake CPU resource for injection into SyscallHandler.
cpu_resource: Option<zx::Resource>,
/// A fake Inspect root for injection into Syscall Handler.
inspect_root: Option<&'a inspect::Node>,
}
impl<'a> SyscallHandlerBuilder<'a> {
pub fn new() -> Self {
Self::default()
}
#[cfg(test)]
fn with_cpu_resource(mut self, resource: zx::Resource) -> Self {
self.cpu_resource = Some(resource);
self
}
#[cfg(test)]
fn with_inspect_root(mut self, root: &'a inspect::Node) -> Self {
self.inspect_root = Some(root);
self
}
pub async fn build(self) -> Result<Rc<SyscallHandler>, Error> {
// If a CPU resource was not provided, query component_manager for it.
let cpu_resource = match self.cpu_resource {
Some(resource) => resource,
None => {
let proxy =
fuchsia_component::client::connect_to_protocol::<fkernel::CpuResourceMarker>()?;
proxy.get().await?
}
};
// Optionally use the default inspect root node
let inspect_root = self.inspect_root.unwrap_or(inspect::component::inspector().root());
let inspect_data = InspectData::new(inspect_root, "SyscallHandler");
Ok(Rc::new(SyscallHandler { cpu_resource, inspect: inspect_data }))
}
}
/// Struct for handling syscalls.
pub struct SyscallHandler {
cpu_resource: zx::Resource,
inspect: InspectData,
}
impl SyscallHandler {
fn handle_get_num_cpus(&self) -> MessageResult {
// There are no assumptions made by this unsafe block; it is only unsafe due to FFI.
let num_cpus = unsafe { sys::zx_system_get_num_cpus() };
Ok(MessageReturn::GetNumCpus(num_cpus))
}
fn handle_set_cpu_performance_info(
&self,
new_info: &Vec<sys::zx_cpu_performance_info_t>,
) -> MessageResult {
// The unsafe block assumes only that the pointer to `new_info` agrees with the count
// argument that follows.
let status = unsafe {
sys::zx_system_set_performance_info(
self.cpu_resource.raw_handle(),
sys::ZX_CPU_PERF_SCALE,
new_info.as_ptr() as *const u8,
new_info.len(),
)
};
if let Err(e) = zx::Status::ok(status) {
self.inspect.set_performance_info_error_count.add(1);
let error_string = e.to_string();
self.inspect.set_performance_info_last_error.set(&error_string);
let verbose_string = format!(
"{}: zx_system_set_performance_info returned error: {}",
self.name(),
&error_string
);
log::error!("{}", &verbose_string);
return Err(format_err!("{}", &verbose_string).into());
}
Ok(MessageReturn::SetCpuPerformanceInfo)
}
}
#[async_trait(?Send)]
impl Node for SyscallHandler {
fn name(&self) -> String {
"SyscallHandler".to_string()
}
async fn handle_message(&self, msg: &Message) -> MessageResult {
match msg {
Message::GetNumCpus => self.handle_get_num_cpus(),
Message::SetCpuPerformanceInfo(new_info) => {
self.handle_set_cpu_performance_info(new_info)
}
_ => Err(PowerManagerError::Unsupported),
}
}
}
struct InspectData {
_root_node: inspect::Node,
// Properties
set_performance_info_error_count: inspect::UintProperty,
set_performance_info_last_error: inspect::StringProperty,
}
impl InspectData {
fn new(parent: &inspect::Node, node_name: &str) -> Self {
let root_node = parent.create_child(node_name);
let set_performance_info = root_node.create_child("zx_system_set_performance_info");
let set_performance_info_error_count = set_performance_info.create_uint("error_count", 0);
let set_performance_info_last_error =
set_performance_info.create_string("last_error", "<None>");
root_node.record(set_performance_info);
Self {
_root_node: root_node,
set_performance_info_error_count,
set_performance_info_last_error,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use fuchsia_async as fasync;
use inspect::assert_data_tree;
// Tests that errors are logged to Inspect as expected.
#[fasync::run_singlethreaded(test)]
async fn test_inspect_data() {
let resource = zx::Handle::invalid().into();
let inspector = inspect::Inspector::new();
let builder = SyscallHandlerBuilder::new()
.with_cpu_resource(resource)
.with_inspect_root(inspector.root());
let handler = builder.build().await.unwrap();
// The test links against a fake implementation of zx_system_set_performance_info that is
// hard-coded to return ZX_ERR_BAD_HANDLE.
let status_string = zx::Status::BAD_HANDLE.to_string();
match handler.handle_message(&Message::SetCpuPerformanceInfo(Vec::new())).await {
Ok(_) => panic!("Expected to receive an error"),
Err(PowerManagerError::GenericError(e)) => {
assert!(e.to_string().contains(&status_string));
}
Err(e) => panic!("Expected GenericError; received {:?}", e),
}
assert_data_tree!(
inspector,
root: {
SyscallHandler: {
zx_system_set_performance_info: {
error_count: 1u64,
last_error: status_string,
}
}
}
);
}
}