blob: 6bf36b6cc6fac26d1bf43c05b943b6c49af90a27 [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::{
common_utils::common::macros::{fx_err_and_bail, with_line},
ram::types::{SerializableBandwidthInfo, SerializableBandwidthMeasurementConfig},
};
use anyhow::Error;
// Auto generated fidl crate:
use fidl_fuchsia_hardware_ram_metrics::{BandwidthMeasurementConfig, DeviceMarker, DeviceProxy};
use fuchsia_syslog::macros::fx_log_err;
use glob::glob;
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
/// Perform Ram metrics operations.
///
/// Note this object is shared among all threads created by server.
///
#[derive(Debug)]
pub struct RamFacade {
proxy: RwLock<Option<DeviceProxy>>,
}
impl RamFacade {
pub fn new() -> Self {
Self { proxy: RwLock::new(None) }
}
#[cfg(test)]
fn new_with_proxy(proxy: DeviceProxy) -> Self {
Self { proxy: RwLock::new(Some(proxy)) }
}
/// Connect to a ram device.
/// Will connect to the first device found in the path /dev/class/aml-ram/
/// Returns the connection if successful, otherwise returns an error.
fn get_proxy(&self) -> Result<DeviceProxy, Error> {
let tag = "RamFacade::get_proxy";
let lock = self.proxy.upgradable_read();
if let Some(proxy) = lock.as_ref() {
Ok(proxy.clone())
} else {
let (proxy, server) = match fidl::endpoints::create_proxy::<DeviceMarker>() {
Ok(r) => r,
Err(e) => fx_err_and_bail!(
&with_line!(tag),
format_err!("Failed to get device proxy {:?}", e)
),
};
let found_path = glob("/dev/class/aml-ram/*")?.filter_map(|entry| entry.ok()).next();
match found_path {
Some(path) => {
fdio::service_connect(path.to_string_lossy().as_ref(), server.into_channel())?;
*RwLockUpgradableReadGuard::upgrade(lock) = Some(proxy.clone());
Ok(proxy)
}
None => fx_err_and_bail!(&with_line!(tag), format_err!("Failed to find device")),
}
}
}
/// Call the MeasureBandwidth interface of the first available ram device.
/// ser_config: configuration describing which channels to measure.
/// Returns bandwidth measurement results if successful, otherwise returns error.
/// See sdk/fidl/fuchsia.hardware.ram.metrics/metrics.fidl for more details on
/// the input and output of this function.
pub async fn measure_bandwidth(
&self,
ser_config: SerializableBandwidthMeasurementConfig,
) -> Result<SerializableBandwidthInfo, Error> {
let tag = "RamFacade::measure_bandwidth";
let mut config = BandwidthMeasurementConfig::from(ser_config);
match self.get_proxy()?.measure_bandwidth(&mut config).await? {
Ok(r) => Ok(SerializableBandwidthInfo::from(r)),
Err(e) => {
fx_err_and_bail!(&with_line!(tag), format_err!("MeasureBandwidth failed {:?}", e))
}
}
}
pub async fn get_ddr_windowing_results(&self) -> Result<u32, Error> {
let tag = "RamFacade::get_ddr_windowing_results";
match self.get_proxy()?.get_ddr_windowing_results().await? {
Ok(r) => Ok(r),
Err(e) => fx_err_and_bail!(
&with_line!(tag),
format_err!("GetDdrWindowingResults failed {:?}", e)
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ram::types::{
SerializableBandwidthInfo, SerializableBandwidthMeasurementConfig,
SerializableGrantedCyclesResult,
};
use fidl_fuchsia_hardware_ram_metrics::{
BandwidthInfo, BandwidthMeasurementConfig, DeviceRequest,
};
use fuchsia_zircon as zx;
use futures::{future::Future, join, stream::StreamExt};
use matches::assert_matches;
/// An arbitrary rigistry value, for tests.
const TEST_REG_VALUE: u32 = 33;
struct MockDeviceBuilder {
expected: Vec<Box<dyn FnOnce(DeviceRequest) + Send + 'static>>,
}
impl MockDeviceBuilder {
fn new() -> Self {
Self { expected: vec![] }
}
fn push(mut self, request: impl FnOnce(DeviceRequest) + Send + 'static) -> Self {
self.expected.push(Box::new(request));
self
}
fn expect_measure_bandwidth(
self,
val: SerializableBandwidthMeasurementConfig,
res: Result<SerializableBandwidthInfo, zx::zx_status_t>,
) -> Self {
self.push(move |req| match req {
DeviceRequest::MeasureBandwidth { config, responder } => {
assert_eq!(BandwidthMeasurementConfig::from(val), config);
responder
// Here we convert the result we passed in to the fidl version
.send(&mut res.map(|bandwidth_info| BandwidthInfo::from(bandwidth_info)))
.expect("failed to respond to MeasureBandwidth request")
}
req => panic!("unexpected request: {:?}", req),
})
}
fn expect_get_ddr_windowing_results(self, res: Result<u32, zx::zx_status_t>) -> Self {
self.push(move |req| match req {
DeviceRequest::GetDdrWindowingResults { responder } => {
responder.send(&mut res.clone()).unwrap()
}
req => panic!("unexpected request: {:?}", req),
})
}
fn build(self) -> (RamFacade, impl Future<Output = ()>) {
let (proxy, mut stream) =
fidl::endpoints::create_proxy_and_stream::<DeviceMarker>().unwrap();
let fut = async move {
for expected in self.expected {
expected(stream.next().await.unwrap().unwrap());
}
assert!(stream.next().await.is_none());
};
(RamFacade::new_with_proxy(proxy), fut)
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn measure_bandwidth_ok() {
let input_config = SerializableBandwidthMeasurementConfig {
cycles_to_measure: 10,
channels: [1, 2, 3, 4, 5, 6, 7, 8],
};
let output_info = SerializableBandwidthInfo {
timestamp: 123_456_789,
frequency: 5_000_000,
bytes_per_cycle: 3_000_000,
channels: [SerializableGrantedCyclesResult {
read_cycles: 0,
write_cycles: 0,
readwrite_cycles: 0,
}; 8],
total: SerializableGrantedCyclesResult {
read_cycles: 0,
write_cycles: 0,
readwrite_cycles: 0,
},
};
let (facade, device) = MockDeviceBuilder::new()
.expect_measure_bandwidth(
// Change this to be the input
input_config.clone(),
// change this to be result with the output
Ok(output_info.clone()),
)
.build();
let test = async move {
assert_matches!(
facade.measure_bandwidth(input_config).await,
Ok(info) if info == output_info);
};
join!(device, test);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn get_ddr_windowing_results_ok() {
let (facade, device) =
MockDeviceBuilder::new().expect_get_ddr_windowing_results(Ok(TEST_REG_VALUE)).build();
let test = async move {
assert_matches!(facade.get_ddr_windowing_results().await, Ok(TEST_REG_VALUE));
};
join!(device, test);
}
}