| // 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); |
| } |
| } |