blob: e3663a615afb37607a0fad66653d0e467f6f1f18 [file] [log] [blame]
// Copyright 2025 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.
#![deny(missing_docs)]
//! PlatformDevice interface.
use fidl::{Persistable, Serializable};
use fidl_fuchsia_hardware_platform_device as pdev_fidl;
use log::error;
use mmio::region::MmioRegion;
use mmio::vmo::{VmoMapping, VmoMemory};
use std::future::Future;
use zx_status::Status;
/// PlatformDevice interface.
pub trait PlatformDevice {
/// The type of the [Mmio] implementation returned by this platform device.
type Mmio;
/// Maps an MMIO region by its id.
fn map_mmio_by_id(&self, id: u32) -> impl Future<Output = Result<Self::Mmio, Status>>;
/// Maps MMIO memory by its name.
fn map_mmio_by_name(&self, name: &str) -> impl Future<Output = Result<Self::Mmio, Status>>;
/// Gets typed metadata associated with this platform device.
fn get_typed_metadata<T: Persistable + Serializable>(
&self,
) -> impl Future<Output = Result<T, Status>>;
}
impl PlatformDevice for pdev_fidl::DeviceProxy {
type Mmio = MmioRegion<VmoMemory>;
async fn map_mmio_by_id(&self, id: u32) -> Result<Self::Mmio, Status> {
let mmio = self
.get_mmio_by_id(id)
.await
.map_err(|err| {
error!("Could not get mmio for id {id}: {err}");
Status::INTERNAL
})?
.map_err(Status::from_raw)?;
map_mmio(mmio)
}
async fn map_mmio_by_name(&self, name: &str) -> Result<Self::Mmio, Status> {
let mmio = self
.get_mmio_by_name(name)
.await
.map_err(|err| {
error!("Could not get mmio for name {name}: {err}");
Status::INTERNAL
})?
.map_err(Status::from_raw)?;
map_mmio(mmio)
}
async fn get_typed_metadata<T: Persistable + Serializable>(&self) -> Result<T, Status> {
let name = T::SERIALIZABLE_NAME;
let metadata = self
.get_metadata(name)
.await
.map_err(|err| {
error!("Failed to get metadata from pdev: {name} {err}");
Status::INTERNAL
})?
.map_err(Status::from_raw)?;
fidl::unpersist(&metadata).map_err(|err| {
error!("Failed to parse pdev metadata: {err}");
Status::INVALID_ARGS
})
}
}
fn map_mmio(mmio: pdev_fidl::Mmio) -> Result<MmioRegion<VmoMemory>, Status> {
let (Some(vmo), Some(offset), Some(size)) = (mmio.vmo, mmio.offset, mmio.size) else {
error!("Mmio device missing vmo, offset or size");
return Err(Status::INTERNAL);
};
let offset = offset as usize;
let size = size as usize;
let mmio = VmoMapping::map(offset, size, vmo).map_err(|err| {
error!("Failed to map Mmio memory for vmo: {err}");
Status::INTERNAL
})?;
Ok(mmio)
}
#[cfg(test)]
mod tests {
use super::*;
use fidl_test_metadata::{IntMetadata, Metadata};
use fuchsia_async::Task;
use futures_util::TryStreamExt;
use mmio::Mmio;
use std::collections::HashMap;
use zx::{Vmo, VmoOp};
struct TestServer {
mmios: Vec<(&'static str, Option<pdev_fidl::Mmio>)>,
metadata: HashMap<&'static str, Vec<u8>>,
}
impl TestServer {
fn new() -> Self {
Self { mmios: Vec::new(), metadata: HashMap::new() }
}
fn append_mmio(&mut self, name: &'static str, vmo: Vmo, offset: usize, size: usize) {
self.mmios.push((
name,
Some(pdev_fidl::Mmio {
offset: Some(offset as u64),
size: Some(size as u64),
vmo: Some(vmo),
..Default::default()
}),
));
}
fn set_typed_metadata<T: Persistable + Serializable>(&mut self, metadata: &T) {
let bytes = fidl::persist(metadata).unwrap();
self.metadata.insert(T::SERIALIZABLE_NAME, bytes);
}
async fn handle_requests(
&mut self,
mut requests: pdev_fidl::DeviceRequestStream,
) -> Result<(), fidl::Error> {
while let Some(req) = requests.try_next().await? {
match req {
pdev_fidl::DeviceRequest::GetMmioById { index, responder } => {
responder.send(self.take_mmio_by_id(index).map_err(Status::into_raw))?;
}
pdev_fidl::DeviceRequest::GetMmioByName { name, responder } => {
responder.send(self.take_mmio_by_name(&name).map_err(Status::into_raw))?;
}
pdev_fidl::DeviceRequest::GetMetadata { id, responder } => {
responder.send(self.get_metadata(&id).map_err(Status::into_raw))?;
}
_ => {
unreachable!("not used by tests")
}
}
}
Ok(())
}
fn take_mmio_by_id(&mut self, id: u32) -> Result<pdev_fidl::Mmio, Status> {
self.mmios
.get_mut(id as usize)
.ok_or(Status::NOT_FOUND)?
.1
.take()
.ok_or(Status::ALREADY_BOUND)
}
fn take_mmio_by_name(&mut self, name: &str) -> Result<pdev_fidl::Mmio, Status> {
self.mmios
.iter_mut()
.find(|(n, _)| *n == name)
.ok_or(Status::NOT_FOUND)?
.1
.take()
.ok_or(Status::ALREADY_BOUND)
}
fn get_metadata(&self, id: &str) -> Result<&[u8], Status> {
self.metadata.get(id).map(|v| v.as_slice()).ok_or(Status::NOT_FOUND)
}
fn run(mut self) -> (pdev_fidl::DeviceProxy, Task<Result<(), fidl::Error>>) {
let (proxy, stream) =
fidl::endpoints::create_proxy_and_stream::<pdev_fidl::DeviceMarker>();
let server = Task::local(async move { self.handle_requests(stream).await });
(proxy, server)
}
}
#[fuchsia::test]
async fn test_pdev() {
let mut server = TestServer::new();
let vmo = Vmo::create(4096).unwrap();
vmo.op_range(VmoOp::ZERO, 0, 4096).unwrap();
server.append_mmio("zero", vmo, 0, 4096);
// Prepare the MMIO region.
let vmo = Vmo::create(1024).unwrap();
for i in 0..256 {
vmo.write(&((i as u32).to_le_bytes()), (i * size_of::<u32>()) as u64).unwrap();
}
server.append_mmio("dev", vmo, 32 * size_of::<u32>(), 16);
server.set_typed_metadata(&Metadata {
test_field: Some("foo".to_string()),
..Default::default()
});
let (client, server) = server.run();
let mmio = client.map_mmio_by_id(1).await.unwrap();
assert_eq!(client.map_mmio_by_id(1).await.err(), Some(Status::ALREADY_BOUND));
assert_eq!(client.map_mmio_by_id(2).await.err(), Some(Status::NOT_FOUND));
assert_eq!(client.map_mmio_by_name("dev").await.err(), Some(Status::ALREADY_BOUND));
assert_eq!(mmio.load32(0), 32);
let mmio = client.map_mmio_by_name("zero").await.unwrap();
assert_eq!(mmio.len(), 4096);
assert_eq!(mmio.load64(128), 0);
assert_eq!(
client.get_typed_metadata::<Metadata>().await.unwrap(),
Metadata { test_field: Some("foo".to_string()), ..Default::default() }
);
assert_eq!(client.get_typed_metadata::<IntMetadata>().await.err(), Some(Status::NOT_FOUND));
let _ = server.abort().await;
}
}