Skip to main content

pdev/
lib.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4#![deny(missing_docs)]
5//! PlatformDevice interface.
6
7use fidl::{Persistable, Serializable};
8use fidl_fuchsia_hardware_platform_device as fpdev;
9use log::error;
10use mmio::region::MmioRegion;
11use mmio::vmo::{VmoMapping, VmoMemory};
12use std::future::Future;
13use zx_status::Status;
14
15/// PlatformDevice interface.
16pub trait PlatformDevice {
17    /// The type of the [Mmio] implementation returned by this platform device.
18    type Mmio;
19
20    /// Maps an MMIO region by its id.
21    fn map_mmio_by_id(&self, id: u32) -> impl Future<Output = Result<Self::Mmio, Status>>;
22
23    /// Maps MMIO memory by its name.
24    fn map_mmio_by_name(&self, name: &str) -> impl Future<Output = Result<Self::Mmio, Status>>;
25
26    /// Gets typed metadata associated with this platform device.
27    fn get_typed_metadata<T: Persistable + Serializable>(
28        &self,
29    ) -> impl Future<Output = Result<T, Status>>;
30
31    /// Gets deserialized metadata associated with this platform device using default ID.
32    fn get_deserialized_metadata<T: serde::de::DeserializeOwned>(
33        &self,
34    ) -> impl Future<Output = Result<T, Status>>;
35}
36
37impl PlatformDevice for fpdev::DeviceProxy {
38    type Mmio = MmioRegion<VmoMemory>;
39
40    async fn map_mmio_by_id(&self, id: u32) -> Result<Self::Mmio, Status> {
41        let mmio = self
42            .get_mmio_by_id(id)
43            .await
44            .map_err(|err| {
45                error!("Could not get mmio for id {id}: {err}");
46                Status::INTERNAL
47            })?
48            .map_err(Status::from_raw)?;
49        map_mmio(mmio)
50    }
51
52    async fn map_mmio_by_name(&self, name: &str) -> Result<Self::Mmio, Status> {
53        let mmio = self
54            .get_mmio_by_name(name)
55            .await
56            .map_err(|err| {
57                error!("Could not get mmio for name {name}: {err}");
58                Status::INTERNAL
59            })?
60            .map_err(Status::from_raw)?;
61        map_mmio(mmio)
62    }
63
64    async fn get_typed_metadata<T: Persistable + Serializable>(&self) -> Result<T, Status> {
65        let name = T::SERIALIZABLE_NAME;
66        let metadata = self
67            .get_metadata(name)
68            .await
69            .map_err(|err| {
70                error!("Failed to get metadata from pdev: {name} {err}");
71                Status::INTERNAL
72            })?
73            .map_err(Status::from_raw)?;
74        fidl::unpersist(&metadata).map_err(|err| {
75            error!("Failed to parse pdev metadata: {err}");
76            Status::INVALID_ARGS
77        })
78    }
79
80    async fn get_deserialized_metadata<T: serde::de::DeserializeOwned>(&self) -> Result<T, Status> {
81        let name = "fuchsia.driver.metadata.Dictionary";
82        let metadata = self
83            .get_metadata(name)
84            .await
85            .map_err(|err| {
86                error!("Failed to get metadata from pdev: {name} {err}");
87                Status::INTERNAL
88            })?
89            .map_err(Status::from_raw)?;
90        let dict: fidl_fuchsia_driver_metadata::Dictionary =
91            fidl::unpersist(&metadata).map_err(|err| {
92                error!("Failed to unpersist dictionary: {err}");
93                Status::INVALID_ARGS
94            })?;
95        fdf_metadata::from_dictionary(dict).map_err(|err| {
96            error!("Failed to deserialize config from dictionary: {err:?}");
97            Status::INVALID_ARGS
98        })
99    }
100}
101
102/// Extension trait for [`DriverContext`] to simplify connecting to a platform device in a driver's
103/// start routine.
104pub trait PdevExt {
105    /// Connects to the platform device ("pdev") in the incoming namespace.
106    fn connect_to_pdev(&self) -> Result<fpdev::DeviceProxy, fdf_component::DriverError>;
107}
108
109impl PdevExt for fdf_component::DriverContext {
110    fn connect_to_pdev(&self) -> Result<fpdev::DeviceProxy, fdf_component::DriverError> {
111        Ok(self
112            .incoming
113            .service_marker(fpdev::ServiceMarker)
114            .instance("pdev")
115            .connect()?
116            .connect_to_device()?)
117    }
118}
119
120fn map_mmio(mmio: fpdev::Mmio) -> Result<MmioRegion<VmoMemory>, Status> {
121    let (Some(vmo), Some(offset), Some(size)) = (mmio.vmo, mmio.offset, mmio.size) else {
122        error!("Mmio device missing vmo, offset or size");
123        return Err(Status::INTERNAL);
124    };
125    let offset = offset as usize;
126    let size = size as usize;
127
128    let mmio = VmoMapping::map(offset, size, vmo).map_err(|err| {
129        error!("Failed to map Mmio memory for vmo: {err}");
130        Status::INTERNAL
131    })?;
132    Ok(mmio)
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use fidl_test_metadata::{IntMetadata, Metadata};
139    use fuchsia_async::Task;
140    use futures_util::TryStreamExt;
141    use mmio::Mmio;
142    use std::collections::HashMap;
143    use zx::{Vmo, VmoOp};
144
145    struct TestServer {
146        mmios: Vec<(&'static str, Option<fpdev::Mmio>)>,
147        metadata: HashMap<&'static str, Vec<u8>>,
148    }
149
150    impl TestServer {
151        fn new() -> Self {
152            Self { mmios: Vec::new(), metadata: HashMap::new() }
153        }
154
155        fn append_mmio(&mut self, name: &'static str, vmo: Vmo, offset: usize, size: usize) {
156            self.mmios.push((
157                name,
158                Some(fpdev::Mmio {
159                    offset: Some(offset as u64),
160                    size: Some(size as u64),
161                    vmo: Some(vmo),
162                    ..Default::default()
163                }),
164            ));
165        }
166
167        fn set_typed_metadata<T: Persistable + Serializable>(&mut self, metadata: &T) {
168            let bytes = fidl::persist(metadata).unwrap();
169            self.metadata.insert(T::SERIALIZABLE_NAME, bytes);
170        }
171
172        async fn handle_requests(
173            &mut self,
174            mut requests: fpdev::DeviceRequestStream,
175        ) -> Result<(), fidl::Error> {
176            while let Some(req) = requests.try_next().await? {
177                match req {
178                    fpdev::DeviceRequest::GetMmioById { index, responder } => {
179                        responder.send(self.take_mmio_by_id(index).map_err(Status::into_raw))?;
180                    }
181                    fpdev::DeviceRequest::GetMmioByName { name, responder } => {
182                        responder.send(self.take_mmio_by_name(&name).map_err(Status::into_raw))?;
183                    }
184                    fpdev::DeviceRequest::GetMetadata { id, responder } => {
185                        responder.send(self.get_metadata(&id).map_err(Status::into_raw))?;
186                    }
187                    _ => {
188                        unreachable!("not used by tests")
189                    }
190                }
191            }
192            Ok(())
193        }
194
195        fn take_mmio_by_id(&mut self, id: u32) -> Result<fpdev::Mmio, Status> {
196            self.mmios
197                .get_mut(id as usize)
198                .ok_or(Status::NOT_FOUND)?
199                .1
200                .take()
201                .ok_or(Status::ALREADY_BOUND)
202        }
203
204        fn take_mmio_by_name(&mut self, name: &str) -> Result<fpdev::Mmio, Status> {
205            self.mmios
206                .iter_mut()
207                .find(|(n, _)| *n == name)
208                .ok_or(Status::NOT_FOUND)?
209                .1
210                .take()
211                .ok_or(Status::ALREADY_BOUND)
212        }
213
214        fn get_metadata(&self, id: &str) -> Result<&[u8], Status> {
215            self.metadata.get(id).map(|v| v.as_slice()).ok_or(Status::NOT_FOUND)
216        }
217
218        fn run(mut self) -> (fpdev::DeviceProxy, Task<Result<(), fidl::Error>>) {
219            let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<fpdev::DeviceMarker>();
220            let server = Task::local(async move { self.handle_requests(stream).await });
221            (proxy, server)
222        }
223    }
224
225    #[fuchsia::test]
226    async fn test_pdev() {
227        let mut server = TestServer::new();
228
229        let vmo = Vmo::create(4096).unwrap();
230        vmo.op_range(VmoOp::ZERO, 0, 4096).unwrap();
231        server.append_mmio("zero", vmo, 0, 4096);
232
233        // Prepare the MMIO region.
234        let vmo = Vmo::create(1024).unwrap();
235        for i in 0..256 {
236            vmo.write(&((i as u32).to_le_bytes()), (i * size_of::<u32>()) as u64).unwrap();
237        }
238        server.append_mmio("dev", vmo, 32 * size_of::<u32>(), 16);
239
240        server.set_typed_metadata(&Metadata {
241            test_field: Some("foo".to_string()),
242            ..Default::default()
243        });
244
245        let (client, server) = server.run();
246
247        let mmio = client.map_mmio_by_id(1).await.unwrap();
248        assert_eq!(client.map_mmio_by_id(1).await.err(), Some(Status::ALREADY_BOUND));
249        assert_eq!(client.map_mmio_by_id(2).await.err(), Some(Status::NOT_FOUND));
250        assert_eq!(client.map_mmio_by_name("dev").await.err(), Some(Status::ALREADY_BOUND));
251
252        assert_eq!(mmio.load32(0), 32);
253
254        let mmio = client.map_mmio_by_name("zero").await.unwrap();
255        assert_eq!(mmio.len(), 4096);
256        assert_eq!(mmio.load64(128), 0);
257
258        assert_eq!(
259            client.get_typed_metadata::<Metadata>().await.unwrap(),
260            Metadata { test_field: Some("foo".to_string()), ..Default::default() }
261        );
262
263        assert_eq!(client.get_typed_metadata::<IntMetadata>().await.err(), Some(Status::NOT_FOUND));
264
265        let _ = server.abort().await;
266    }
267}