blob: 6f1ce64fd31a65332945f07da54d144db15b9b51 [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, MessageReturn};
use crate::node::Node;
use crate::shutdown_request::ShutdownRequest;
use crate::utils::connect_to_driver;
use anyhow::{format_err, Error};
use async_trait::async_trait;
use fidl_fuchsia_hardware_input::{DeviceMarker as LidMarker, DeviceProxy as LidProxy};
use fuchsia_async::OnSignals;
use fuchsia_fs::{open_directory_in_namespace, OpenFlags};
use fuchsia_inspect::{self as inspect, NumericProperty, Property};
use fuchsia_inspect_contrib::{inspect_log, nodes::BoundedListNode};
use fuchsia_vfs_watcher as vfs;
use fuchsia_zircon as zx;
use futures::{
future::{FutureExt, LocalBoxFuture},
stream::FuturesUnordered,
TryStreamExt,
};
use log::*;
use serde_derive::Deserialize;
use serde_json as json;
use std::{
cell::RefCell,
collections::HashMap,
path::{Path, PathBuf},
rc::Rc,
};
/// Node: LidShutdown
///
/// Summary: Responds to lid closed events from devices with a lid sensor by waiting for a report
/// using the input FIDL protocol.
///
/// Handles Messages: N/A
///
/// Sends Messages:
/// - SystemShutdown
///
/// FIDL dependencies:
/// - fuchsia.hardware.input: the node uses this protocol to wait on reports from the
/// lid device
/// The lid sensor is not a real HID device however this descriptor is defined in the lid driver
/// and will be used to send lid HID reports for any ACPI lid sensor.
const HID_LID_DESCRIPTOR: [u8; 9] = [
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x80, // Usage (System Control)
0xA1, 0x01, // Collection (Application)
0x0A, 0xFF, 0x01, // Usage (0x01FF, unique to lid reports)
];
// Lid closed HID report
const LID_CLOSED: u8 = 0x0;
static INPUT_DEVICES_DIRECTORY: &str = "/dev/class/input";
pub struct LidShutdownBuilder<'a> {
proxy: Option<LidProxy>,
lid_report_event: Option<zx::Event>,
system_shutdown_node: Rc<dyn Node>,
inspect_root: Option<&'a inspect::Node>,
}
impl<'a> LidShutdownBuilder<'a> {
pub fn new(system_shutdown_node: Rc<dyn Node>) -> Self {
LidShutdownBuilder {
proxy: None,
lid_report_event: None,
system_shutdown_node,
inspect_root: None,
}
}
#[cfg(test)]
pub fn new_with_event_and_proxy(
proxy: LidProxy,
lid_report_event: zx::Event,
system_shutdown_node: Rc<dyn Node>,
) -> Self {
Self {
proxy: Some(proxy),
lid_report_event: Some(lid_report_event),
system_shutdown_node,
inspect_root: None,
}
}
pub fn new_from_json(json_data: json::Value, nodes: &HashMap<String, Rc<dyn Node>>) -> Self {
#[derive(Deserialize)]
struct Dependencies {
system_shutdown_node: String,
}
#[derive(Deserialize)]
struct JsonData {
dependencies: Dependencies,
}
let data: JsonData = json::from_value(json_data).unwrap();
Self::new(nodes[&data.dependencies.system_shutdown_node].clone())
}
#[cfg(test)]
pub fn with_inspect_root(mut self, root: &'a inspect::Node) -> Self {
self.inspect_root = Some(root);
self
}
pub async fn build<'b>(
self,
futures_out: &FuturesUnordered<LocalBoxFuture<'b, ()>>,
) -> Result<Rc<LidShutdown>, Error> {
// In tests use the default proxy.
let proxy = match self.proxy {
Some(proxy) => proxy,
None => Self::find_lid_sensor().await?,
};
// In tests use the default event.
let report_event = match self.lid_report_event {
Some(report_event) => report_event,
None => match proxy.get_reports_event().await {
Ok((_, report_event)) => report_event,
Err(_e) => return Err(format_err!("Could not get report event.")),
},
};
// In tests use the default inspect root node
let inspect_root = self.inspect_root.unwrap_or(inspect::component::inspector().root());
let node = Rc::new(LidShutdown {
proxy,
report_event,
system_shutdown_node: self.system_shutdown_node,
inspect: InspectData::new(inspect_root, "LidShutdown".to_string()),
});
futures_out.push(node.clone().watch_lid());
Ok(node)
}
/// Checks all the input devices until the lid sensor is found.
async fn find_lid_sensor() -> Result<LidProxy, Error> {
let dir_proxy =
open_directory_in_namespace(INPUT_DEVICES_DIRECTORY, OpenFlags::RIGHT_READABLE)?;
let mut watcher = vfs::Watcher::new(dir_proxy).await?;
while let Some(msg) = watcher.try_next().await? {
match msg.event {
vfs::WatchEvent::EXISTING | vfs::WatchEvent::ADD_FILE => {
match Self::open_sensor(&msg.filename).await {
Ok(device) => return Ok(device),
_ => (),
}
}
_ => (),
}
}
Err(format_err!("No lid device found"))
}
/// Opens the sensor's device file. Returns the device if the correct HID
/// report descriptor is found.
async fn open_sensor(filename: &PathBuf) -> Result<LidProxy, Error> {
let path = Path::new(INPUT_DEVICES_DIRECTORY).join(filename);
let device = connect_to_driver::<LidMarker>(&String::from(
path.to_str().ok_or(format_err!("Could not read path {:?}", path))?,
))
.await?;
if let Ok(device_descriptor) = device.get_report_desc().await {
if device_descriptor.len() < HID_LID_DESCRIPTOR.len() {
return Err(format_err!("Short HID header"));
}
let device_header = &device_descriptor[0..HID_LID_DESCRIPTOR.len()];
if device_header == HID_LID_DESCRIPTOR {
return Ok(device);
} else {
return Err(format_err!("Device is not lid sensor"));
}
}
Err(format_err!("Could not get device HID report descriptor"))
}
}
pub struct LidShutdown {
proxy: LidProxy,
/// Event that will signal |USER_0| when a report is in the lid device's report FIFO.
report_event: zx::Event,
/// Node to provide the system shutdown functionality via the SystemShutdown message.
system_shutdown_node: Rc<dyn Node>,
/// A struct for managing Component Inspection data
inspect: InspectData,
}
impl LidShutdown {
fn watch_lid<'a>(self: Rc<Self>) -> LocalBoxFuture<'a, ()> {
async move {
loop {
self.watch_lid_inner().await;
}
}
.boxed_local()
}
/// Watches the lid device for reports.
async fn watch_lid_inner(&self) {
match OnSignals::new(&self.report_event, zx::Signals::USER_0).await {
Err(e) => error!("Could not wait for lid event: {:?}", e),
_ => match self.check_report().await {
Ok(()) => (),
Err(e) => {
self.inspect.read_errors.add(1);
self.inspect.last_read_error.set(format!("{}", e).as_str());
error!("Failed to read lid report: {}", e)
}
},
};
}
/// Reads the report from the lid sensor and sends shutdown signal if lid is closed.
async fn check_report(&self) -> Result<(), Error> {
let (status, report, _time) = self.proxy.read_report().await?;
let status = zx::Status::from_raw(status);
if status != zx::Status::OK {
return Err(format_err!("Error reading report {}", status));
}
if report.len() != 1 {
return Err(format_err!("Expected single byte report, found {:?}", report));
}
self.inspect.log_lid_report(format!("{:?}", report));
let report = report[0];
if report == LID_CLOSED {
info!("Lid closed. Shutting down...");
self.send_message(
&self.system_shutdown_node,
&Message::SystemShutdown(ShutdownRequest::PowerOff),
)
.await
.map_err(|e| format_err!("Failed to shut down the system: {:?}", e))?;
}
Ok(())
}
}
#[async_trait(?Send)]
impl Node for LidShutdown {
fn name(&self) -> String {
"LidShutdown".to_string()
}
async fn handle_message(&self, _msg: &Message) -> Result<MessageReturn, PowerManagerError> {
Err(PowerManagerError::Unsupported)
}
}
struct InspectData {
lid_reports: RefCell<BoundedListNode>,
read_errors: inspect::UintProperty,
last_read_error: inspect::StringProperty,
}
impl InspectData {
/// Number of inspect samples to store in the `lid_reports` BoundedListNode.
// Store the last 60 lid reports
const NUM_INSPECT_LID_REPORTS: usize = 60;
fn new(parent: &inspect::Node, name: String) -> Self {
// Create a local root node and properties
let root = parent.create_child(name);
let lid_reports = RefCell::new(BoundedListNode::new(
root.create_child("lid_reports"),
Self::NUM_INSPECT_LID_REPORTS,
));
let read_errors = root.create_uint("read_lid_report_error_count", 0);
let last_read_error = root.create_string("last_read_error", "");
// Pass ownership of the new node to the parent node, otherwise it'll be dropped
parent.record(root);
InspectData { lid_reports, read_errors, last_read_error }
}
fn log_lid_report(&self, lid_report: String) {
inspect_log!(self.lid_reports.borrow_mut(), lid_report: lid_report);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::mock_node::{create_dummy_node, MessageMatcher, MockNodeMaker};
use crate::{msg_eq, msg_ok_return};
use fidl_fuchsia_hardware_input as finput;
use fuchsia_async as fasync;
use fuchsia_inspect::testing::TreeAssertion;
use fuchsia_zircon::{AsHandleRef, HandleBased};
use inspect::assert_data_tree;
const LID_OPEN: u8 = 0x1;
/// Spawns a new task that acts as a fake device driver for testing purposes. The driver only
/// handles requests for ReadReport - trying to send any other requests to it is a bug.
/// Each ReadReport responds with the |lid_report| specified.
fn setup_fake_driver(lid_report: u8) -> LidProxy {
let (proxy, mut stream) = fidl::endpoints::create_proxy_and_stream::<LidMarker>().unwrap();
fasync::Task::local(async move {
while let Ok(req) = stream.try_next().await {
match req {
Some(finput::DeviceRequest::ReadReport { responder }) => {
let _ = responder.send(zx::Status::OK.into_raw(), &[lid_report], 0 as i64);
}
_ => assert!(false),
}
}
})
.detach();
proxy
}
/// 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": "LidShutdown",
"name": "lid_shutdown",
"dependencies": {
"system_shutdown_node": "shutdown",
},
});
let mut nodes: HashMap<String, Rc<dyn Node>> = HashMap::new();
nodes.insert("shutdown".to_string(), create_dummy_node());
let _ = LidShutdownBuilder::new_from_json(json_data, &nodes);
}
/// Tests that when the node receives a signal on its |report_event|, it checks for a lid
/// report and, on reception of a lid closed report, it triggers a system shutdown.
#[fasync::run_singlethreaded(test)]
async fn test_triggered_shutdown() {
let mut mock_maker = MockNodeMaker::new();
let shutdown_node = mock_maker.make(
"Shutdown",
vec![(
msg_eq!(SystemShutdown(ShutdownRequest::PowerOff)),
msg_ok_return!(SystemShutdown),
)],
);
let event = zx::Event::create().unwrap();
let node = LidShutdownBuilder::new_with_event_and_proxy(
setup_fake_driver(LID_CLOSED),
event.duplicate_handle(zx::Rights::BASIC).unwrap(),
shutdown_node,
)
.build(&FuturesUnordered::new())
.await
.unwrap();
event
.signal_handle(zx::Signals::NONE, zx::Signals::USER_0)
.expect("Failed to signal event");
node.watch_lid_inner().await;
// When mock_maker goes out of scope, it verifies the ShutdownNode received the shutdown
// request.
}
/// Tests that when the node receives a signal on its |report_event|, it checks for a lid
/// report and, on reception of a lid open report, it does NOT trigger a system shutdown.
#[fasync::run_singlethreaded(test)]
async fn test_event_handling() {
let mut mock_maker = MockNodeMaker::new();
let shutdown_node = mock_maker.make(
"Shutdown",
vec![], // the shutdown node is not expected to receive any messages
);
let event = zx::Event::create().unwrap();
let node = LidShutdownBuilder::new_with_event_and_proxy(
setup_fake_driver(LID_OPEN),
event.duplicate_handle(zx::Rights::BASIC).unwrap(),
shutdown_node,
)
.build(&FuturesUnordered::new())
.await
.unwrap();
event
.signal_handle(zx::Signals::NONE, zx::Signals::USER_0)
.expect("Failed to signal event");
node.watch_lid_inner().await;
// When mock_maker will verify that the ShutdownNode receives no messages until it goes
// out of scope.
}
/// Tests that an unsupported message is handled gracefully and an error is returned.
#[fasync::run_singlethreaded(test)]
async fn test_unsupported_msg() {
let mut mock_maker = MockNodeMaker::new();
let shutdown_node = mock_maker.make("Shutdown", vec![]);
let node_futures = FuturesUnordered::new();
let node = LidShutdownBuilder::new_with_event_and_proxy(
setup_fake_driver(LID_CLOSED),
zx::Event::create().unwrap(),
shutdown_node,
)
.build(&node_futures)
.await
.unwrap();
match node.handle_message(&Message::GetCpuLoads).await {
Err(PowerManagerError::Unsupported) => {}
e => panic!("Unexpected return value: {:?}", e),
}
}
#[test]
fn test_loop_is_not_blocked() {
let mut executor = fasync::TestExecutor::new().unwrap();
let mut mock_maker = MockNodeMaker::new();
let shutdown_node = mock_maker.make("Shutdown", vec![]);
let node_futures = FuturesUnordered::new();
let node = executor
.run_singlethreaded(
LidShutdownBuilder::new_with_event_and_proxy(
setup_fake_driver(LID_OPEN),
zx::Event::create().unwrap(),
shutdown_node,
)
.build(&node_futures),
)
.unwrap();
let mut future = node.watch_lid();
let _ = executor.run_until_stalled(&mut future);
// If watch_lid() blocks waiting for the event, it will block forever (because the event
// will never come), meaning that this test will time out. Otherwise run_until_stalled()
// will return immediately and the test will pass.
}
/// Tests for the presence and correctness of dynamically-added inspect data
#[fasync::run_singlethreaded(test)]
async fn test_inspect_data() {
let lid_state = LID_OPEN;
let inspector = inspect::Inspector::new();
let mut mock_maker = MockNodeMaker::new();
let shutdown_node = mock_maker.make("Shutdown", vec![]);
let event = zx::Event::create().unwrap();
let node_futures = FuturesUnordered::new();
let node = LidShutdownBuilder::new_with_event_and_proxy(
setup_fake_driver(lid_state),
event.duplicate_handle(zx::Rights::BASIC).unwrap(),
shutdown_node,
)
.with_inspect_root(inspector.root())
.build(&node_futures)
.await
.unwrap();
// The node will read the current temperature and log the sample into Inspect. Read enough
// samples to test that the correct number of samples are logged and older ones are dropped.
for _ in 0..InspectData::NUM_INSPECT_LID_REPORTS + 10 {
event
.signal_handle(zx::Signals::NONE, zx::Signals::USER_0)
.expect("Failed to signal event");
node.watch_lid_inner().await;
}
let mut root = TreeAssertion::new("LidShutdown", false);
let mut lid_reports = TreeAssertion::new("lid_reports", true);
// Since we read 10 more samples than our limit allows, the first 10 should be dropped. So
// test that the sample numbering starts at 10 and continues for the expected number of
// samples.
for i in 10..InspectData::NUM_INSPECT_LID_REPORTS + 10 {
let mut sample_child = TreeAssertion::new(&i.to_string(), true);
sample_child
.add_property_assertion("lid_report", Box::new(format!("{:?}", [lid_state])));
sample_child.add_property_assertion("@time", Box::new(inspect::testing::AnyProperty));
lid_reports.add_child_assertion(sample_child);
}
root.add_child_assertion(lid_reports);
assert_data_tree!(inspector, root: { root, });
}
}