blob: aebff4284e83958802a365e4dcdb2d58ef094575 [file]
// 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 fdf_component::DriverError;
use fidl::{Persistable, Serializable};
use fidl_next_fuchsia_hardware_platform_device as fpdev;
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, DriverError>>;
/// Maps MMIO memory by its name.
fn map_mmio_by_name(&self, name: &str)
-> impl Future<Output = Result<Self::Mmio, DriverError>>;
/// Gets typed metadata associated with this platform device.
fn get_typed_metadata<T: Persistable + Serializable>(
&self,
) -> impl Future<Output = Result<T, DriverError>>;
/// Gets deserialized metadata associated with this platform device using default ID.
fn get_deserialized_metadata<T: serde::de::DeserializeOwned>(
&self,
) -> impl Future<Output = Result<T, DriverError>>;
}
impl PlatformDevice for fidl_next::Client<fpdev::Device> {
type Mmio = MmioRegion<VmoMemory>;
async fn map_mmio_by_id(&self, id: u32) -> Result<Self::Mmio, DriverError> {
let mmio =
self.get_mmio_by_id(id).await?.map_err(|s| s.err().unwrap_or(Status::INTERNAL))?;
Ok(map_mmio(mmio)?)
}
async fn map_mmio_by_name(&self, name: &str) -> Result<Self::Mmio, DriverError> {
let mmio =
self.get_mmio_by_name(name).await?.map_err(|s| s.err().unwrap_or(Status::INTERNAL))?;
Ok(map_mmio(mmio)?)
}
async fn get_typed_metadata<T: Persistable + Serializable>(&self) -> Result<T, DriverError> {
let name = T::SERIALIZABLE_NAME;
let metadata_res =
self.get_metadata(name).await?.map_err(|s| s.err().unwrap_or(Status::INTERNAL))?;
fidl::unpersist(&metadata_res.metadata).map_err(|err| {
error!("Failed to parse pdev metadata: {err}");
DriverError::Status(Status::INVALID_ARGS)
})
}
async fn get_deserialized_metadata<T: serde::de::DeserializeOwned>(
&self,
) -> Result<T, DriverError> {
let name = "fuchsia.driver.metadata.Dictionary";
let metadata_res =
self.get_metadata(name).await?.map_err(|s| s.err().unwrap_or(Status::INTERNAL))?;
let dict: fidl_fuchsia_driver_metadata::Dictionary =
fidl::unpersist(&metadata_res.metadata).map_err(|err| {
error!("Failed to unpersist dictionary: {err}");
DriverError::Status(Status::INVALID_ARGS)
})?;
fdf_metadata::from_dictionary(dict).map_err(|err| {
error!("Failed to deserialize config from dictionary: {err:?}");
DriverError::Status(Status::INVALID_ARGS)
})
}
}
/// Extension trait for [`DriverContext`] to simplify connecting to a platform device in a driver's
/// start routine.
pub trait PdevExt {
/// Connects to the platform device ("pdev") in the incoming namespace.
fn connect_to_pdev(&self) -> Result<fidl_next::Client<fpdev::Device>, DriverError>;
}
impl PdevExt for fdf_component::DriverContext {
fn connect_to_pdev(&self) -> Result<fidl_next::Client<fpdev::Device>, DriverError> {
let service = self
.incoming
.service::<fdf_component::ServiceInstance<fpdev::Service>>()
.instance("pdev")
.connect_next()?;
let (client_end, server_end) = fidl_next::fuchsia::create_channel();
service.device(server_end)?;
Ok(client_end.spawn())
}
}
fn map_mmio(mmio: fpdev::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_next::{Request, Responder};
use fidl_test_metadata::{IntMetadata, Metadata};
use fuchsia_async::Task;
use mmio::Mmio;
use std::collections::HashMap;
use zx::{Vmo, VmoOp};
struct TestServer {
mmios: Vec<(&'static str, Option<fpdev::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(fpdev::Mmio {
offset: Some(offset as u64),
size: Some(size as u64),
vmo: Some(vmo),
}),
));
}
fn set_typed_metadata<T: Persistable + Serializable>(&mut self, metadata: &T) {
let bytes = fidl::persist(metadata).unwrap();
self.metadata.insert(T::SERIALIZABLE_NAME, bytes);
}
fn take_mmio_by_id(&mut self, id: u32) -> Result<fpdev::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<fpdev::Mmio, Status> {
self.mmios
.iter_mut()
.find(|(n, _)| *n == name)
.ok_or(Status::NOT_FOUND)?
.1
.take()
.ok_or(Status::ALREADY_BOUND)
}
fn read_metadata(&self, id: &str) -> Result<&[u8], Status> {
self.metadata.get(id).map(|v| v.as_slice()).ok_or(Status::NOT_FOUND)
}
fn run(
self,
) -> (
fidl_next::Client<fpdev::Device>,
Task<Result<(), fidl_next::ProtocolError<zx::Status>>>,
) {
let (client_end, server_end) = fidl_next::fuchsia::create_channel::<fpdev::Device>();
let client = client_end.spawn();
let server = Task::local(async move {
let dispatcher = fidl_next::ServerDispatcher::new(server_end);
dispatcher.run_local(self).await.map(|_| ())
});
(client, server)
}
}
impl fpdev::DeviceLocalServerHandler for TestServer {
async fn get_mmio_by_id(
&mut self,
request: Request<fpdev::device::GetMmioById>,
responder: Responder<fpdev::device::GetMmioById>,
) {
let index = request.payload().index;
match self.take_mmio_by_id(index) {
Ok(mmio) => {
let _ = responder.respond(mmio).await;
}
Err(status) => {
let _ = responder.respond_err(status).await;
}
}
}
async fn get_mmio_by_name(
&mut self,
request: Request<fpdev::device::GetMmioByName>,
responder: Responder<fpdev::device::GetMmioByName>,
) {
let name = &request.payload().name;
match self.take_mmio_by_name(name) {
Ok(mmio) => {
let _ = responder.respond(mmio).await;
}
Err(status) => {
let _ = responder.respond_err(status).await;
}
}
}
async fn get_interrupt_by_id(
&mut self,
_request: Request<fpdev::device::GetInterruptById>,
_responder: Responder<fpdev::device::GetInterruptById>,
) {
unimplemented!("not used by tests");
}
async fn get_interrupt_by_name(
&mut self,
_request: Request<fpdev::device::GetInterruptByName>,
_responder: Responder<fpdev::device::GetInterruptByName>,
) {
unimplemented!("not used by tests");
}
async fn get_bti_by_id(
&mut self,
_request: Request<fpdev::device::GetBtiById>,
_responder: Responder<fpdev::device::GetBtiById>,
) {
unimplemented!("not used by tests");
}
async fn get_bti_by_name(
&mut self,
_request: Request<fpdev::device::GetBtiByName>,
_responder: Responder<fpdev::device::GetBtiByName>,
) {
unimplemented!("not used by tests");
}
async fn get_smc_by_id(
&mut self,
_request: Request<fpdev::device::GetSmcById>,
_responder: Responder<fpdev::device::GetSmcById>,
) {
unimplemented!("not used by tests");
}
async fn get_smc_by_name(
&mut self,
_request: Request<fpdev::device::GetSmcByName>,
_responder: Responder<fpdev::device::GetSmcByName>,
) {
unimplemented!("not used by tests");
}
async fn get_power_configuration(
&mut self,
_responder: Responder<fpdev::device::GetPowerConfiguration>,
) {
unimplemented!("not used by tests");
}
async fn get_node_device_info(
&mut self,
_responder: Responder<fpdev::device::GetNodeDeviceInfo>,
) {
unimplemented!("not used by tests");
}
async fn get_board_info(&mut self, _responder: Responder<fpdev::device::GetBoardInfo>) {
unimplemented!("not used by tests");
}
async fn get_metadata(
&mut self,
request: Request<fpdev::device::GetMetadata>,
responder: Responder<fpdev::device::GetMetadata>,
) {
let id = &request.payload().id;
match self.read_metadata(id) {
Ok(metadata) => {
let _ = responder.respond(metadata).await;
}
Err(status) => {
let _ = responder.respond_err(status).await;
}
}
}
}
#[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().map(|e| e.log_to_status()),
Some(Status::ALREADY_BOUND)
);
assert_eq!(
client.map_mmio_by_id(2).await.err().map(|e| e.log_to_status()),
Some(Status::NOT_FOUND)
);
assert_eq!(
client.map_mmio_by_name("dev").await.err().map(|e| e.log_to_status()),
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().map(|e| e.log_to_status()),
Some(Status::NOT_FOUND)
);
let _ = server.abort().await;
}
}