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 fdf_component::DriverError;
8use fidl::{Persistable, Serializable};
9use fidl_next_fuchsia_hardware_platform_device as fpdev;
10use log::{debug, error};
11use mmio::region::MmioRegion;
12use mmio::vmo::{VmoMapping, VmoMemory};
13use std::future::Future;
14use zx_status::Status;
15
16/// PlatformDevice interface.
17pub trait PlatformDevice {
18    /// The type of the [Mmio] implementation returned by this platform device.
19    type Mmio;
20
21    /// Maps an MMIO region by its id.
22    fn map_mmio_by_id(&self, id: u32) -> impl Future<Output = Result<Self::Mmio, DriverError>>;
23
24    /// Maps MMIO memory by its name.
25    fn map_mmio_by_name(&self, name: &str)
26    -> impl Future<Output = Result<Self::Mmio, DriverError>>;
27
28    /// Gets typed metadata associated with this platform device.
29    fn get_typed_metadata<T: Persistable + Serializable>(
30        &self,
31    ) -> impl Future<Output = Result<T, DriverError>>;
32
33    /// Gets deserialized metadata associated with this platform device using default ID.
34    fn get_deserialized_metadata<T: serde::de::DeserializeOwned>(
35        &self,
36    ) -> impl Future<Output = Result<T, DriverError>>;
37
38    /// Gets persisted metadata bytes associated with this platform device by metadata ID.
39    fn get_persisted_metadata_by_id(
40        &self,
41        metadata_id: &str,
42    ) -> impl Future<Output = Result<Vec<u8>, DriverError>>;
43
44    /// Gets metadata dictionary associated with this platform device by metadata ID.
45    fn get_dictionary_metadata(
46        &self,
47        metadata_id: &str,
48    ) -> impl Future<Output = Result<fidl_fuchsia_driver_metadata::Dictionary, DriverError>>;
49}
50
51impl PlatformDevice for fidl_next::Client<fpdev::Device> {
52    type Mmio = MmioRegion<VmoMemory>;
53
54    async fn map_mmio_by_id(&self, id: u32) -> Result<Self::Mmio, DriverError> {
55        let mmio = self.get_mmio_by_id(id).await??;
56        Ok(map_mmio(mmio)?)
57    }
58
59    async fn map_mmio_by_name(&self, name: &str) -> Result<Self::Mmio, DriverError> {
60        let mmio = self.get_mmio_by_name(name).await??;
61        Ok(map_mmio(mmio)?)
62    }
63
64    async fn get_typed_metadata<T: Persistable + Serializable>(&self) -> Result<T, DriverError> {
65        let name = T::SERIALIZABLE_NAME;
66        let metadata_res = self.get_metadata(name).await??;
67        fidl::unpersist(&metadata_res.metadata).map_err(|err| {
68            error!("Failed to parse pdev metadata: {err}");
69            DriverError::Status(Status::INVALID_ARGS)
70        })
71    }
72
73    async fn get_deserialized_metadata<T: serde::de::DeserializeOwned>(
74        &self,
75    ) -> Result<T, DriverError> {
76        let name = "fuchsia.driver.metadata.Dictionary";
77        let metadata_res = self.get_metadata(name).await??;
78        let dict: fidl_fuchsia_driver_metadata::Dictionary =
79            fidl::unpersist(&metadata_res.metadata).map_err(|err| {
80                error!("Failed to unpersist dictionary: {err}");
81                DriverError::Status(Status::INVALID_ARGS)
82            })?;
83        fdf_metadata::from_dictionary(dict).map_err(|err| {
84            error!("Failed to deserialize config from dictionary: {err:?}");
85            DriverError::Status(Status::INVALID_ARGS)
86        })
87    }
88
89    async fn get_persisted_metadata_by_id(
90        &self,
91        metadata_id: &str,
92    ) -> Result<Vec<u8>, DriverError> {
93        let metadata_res = self.get_metadata(metadata_id).await??;
94        Ok(metadata_res.metadata)
95    }
96
97    async fn get_dictionary_metadata(
98        &self,
99        metadata_id: &str,
100    ) -> Result<fidl_fuchsia_driver_metadata::Dictionary, DriverError> {
101        let bytes = self.get_persisted_metadata_by_id(metadata_id).await?;
102        fidl::unpersist(&bytes).map_err(|err| {
103            debug!("Failed to unpersist metadata dictionary for {}: {:?}", metadata_id, err);
104            DriverError::Status(Status::INVALID_ARGS)
105        })
106    }
107}
108
109/// Extension trait for [`DriverContext`] to simplify connecting to a platform device in a driver's
110/// start routine.
111pub trait PdevExt {
112    /// Connects to the platform device ("pdev") in the incoming namespace.
113    fn connect_to_pdev(&self) -> Result<fidl_next::Client<fpdev::Device>, DriverError>;
114}
115
116impl PdevExt for fdf_component::DriverContext {
117    fn connect_to_pdev(&self) -> Result<fidl_next::Client<fpdev::Device>, DriverError> {
118        let service = self
119            .incoming
120            .service::<fdf_component::ServiceInstance<fpdev::Service>>()
121            .instance("pdev")
122            .connect_next()?;
123        let (client_end, server_end) = fidl_next::fuchsia::create_channel();
124        service.device(server_end)?;
125        Ok(client_end.spawn())
126    }
127}
128
129fn map_mmio(mmio: fpdev::Mmio) -> Result<MmioRegion<VmoMemory>, Status> {
130    let (Some(vmo), Some(offset), Some(size)) = (mmio.vmo, mmio.offset, mmio.size) else {
131        error!("Mmio device missing vmo, offset or size");
132        return Err(Status::INTERNAL);
133    };
134    let offset = offset as usize;
135    let size = size as usize;
136
137    let mmio = VmoMapping::map(offset, size, vmo).map_err(|err| {
138        error!("Failed to map Mmio memory for vmo: {err}");
139        Status::INTERNAL
140    })?;
141    Ok(mmio)
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use fidl_next::{Request, Responder};
148    use fidl_test_metadata::{IntMetadata, Metadata};
149    use fuchsia_async::Task;
150    use mmio::Mmio;
151    use std::collections::HashMap;
152    use zx::{Vmo, VmoOp};
153
154    struct TestServer {
155        mmios: Vec<(&'static str, Option<fpdev::Mmio>)>,
156        metadata: HashMap<&'static str, Vec<u8>>,
157    }
158
159    impl TestServer {
160        fn new() -> Self {
161            Self { mmios: Vec::new(), metadata: HashMap::new() }
162        }
163
164        fn append_mmio(&mut self, name: &'static str, vmo: Vmo, offset: usize, size: usize) {
165            self.mmios.push((
166                name,
167                Some(fpdev::Mmio {
168                    offset: Some(offset as u64),
169                    size: Some(size as u64),
170                    vmo: Some(vmo),
171                }),
172            ));
173        }
174
175        fn set_typed_metadata<T: Persistable + Serializable>(&mut self, metadata: &T) {
176            let bytes = fidl::persist(metadata).unwrap();
177            self.metadata.insert(T::SERIALIZABLE_NAME, bytes);
178        }
179
180        fn take_mmio_by_id(&mut self, id: u32) -> Result<fpdev::Mmio, Status> {
181            self.mmios
182                .get_mut(id as usize)
183                .ok_or(Status::NOT_FOUND)?
184                .1
185                .take()
186                .ok_or(Status::ALREADY_BOUND)
187        }
188
189        fn take_mmio_by_name(&mut self, name: &str) -> Result<fpdev::Mmio, Status> {
190            self.mmios
191                .iter_mut()
192                .find(|(n, _)| *n == name)
193                .ok_or(Status::NOT_FOUND)?
194                .1
195                .take()
196                .ok_or(Status::ALREADY_BOUND)
197        }
198
199        fn read_metadata(&self, id: &str) -> Result<&[u8], Status> {
200            self.metadata.get(id).map(|v| v.as_slice()).ok_or(Status::NOT_FOUND)
201        }
202
203        fn run(
204            self,
205        ) -> (
206            fidl_next::Client<fpdev::Device>,
207            Task<Result<(), fidl_next::ProtocolError<zx::Status>>>,
208        ) {
209            let (client_end, server_end) = fidl_next::fuchsia::create_channel::<fpdev::Device>();
210            let client = client_end.spawn();
211            let server = Task::local(async move {
212                let dispatcher = fidl_next::ServerDispatcher::new(server_end);
213                dispatcher.run_local(self).await.map(|_| ())
214            });
215            (client, server)
216        }
217    }
218
219    impl fpdev::DeviceLocalServerHandler for TestServer {
220        async fn get_mmio_by_id(
221            &mut self,
222            request: Request<fpdev::device::GetMmioById>,
223            responder: Responder<fpdev::device::GetMmioById>,
224        ) {
225            let index = request.payload().index;
226            match self.take_mmio_by_id(index) {
227                Ok(mmio) => {
228                    let _ = responder.respond(mmio).await;
229                }
230                Err(status) => {
231                    let _ = responder.respond_err(status).await;
232                }
233            }
234        }
235
236        async fn get_mmio_by_name(
237            &mut self,
238            request: Request<fpdev::device::GetMmioByName>,
239            responder: Responder<fpdev::device::GetMmioByName>,
240        ) {
241            let name = &request.payload().name;
242            match self.take_mmio_by_name(name) {
243                Ok(mmio) => {
244                    let _ = responder.respond(mmio).await;
245                }
246                Err(status) => {
247                    let _ = responder.respond_err(status).await;
248                }
249            }
250        }
251
252        async fn get_interrupt_by_id(
253            &mut self,
254            _request: Request<fpdev::device::GetInterruptById>,
255            _responder: Responder<fpdev::device::GetInterruptById>,
256        ) {
257            unimplemented!("not used by tests");
258        }
259
260        async fn get_interrupt_by_name(
261            &mut self,
262            _request: Request<fpdev::device::GetInterruptByName>,
263            _responder: Responder<fpdev::device::GetInterruptByName>,
264        ) {
265            unimplemented!("not used by tests");
266        }
267
268        async fn get_bti_by_id(
269            &mut self,
270            _request: Request<fpdev::device::GetBtiById>,
271            _responder: Responder<fpdev::device::GetBtiById>,
272        ) {
273            unimplemented!("not used by tests");
274        }
275
276        async fn get_bti_by_name(
277            &mut self,
278            _request: Request<fpdev::device::GetBtiByName>,
279            _responder: Responder<fpdev::device::GetBtiByName>,
280        ) {
281            unimplemented!("not used by tests");
282        }
283
284        async fn get_smc_by_id(
285            &mut self,
286            _request: Request<fpdev::device::GetSmcById>,
287            _responder: Responder<fpdev::device::GetSmcById>,
288        ) {
289            unimplemented!("not used by tests");
290        }
291
292        async fn get_smc_by_name(
293            &mut self,
294            _request: Request<fpdev::device::GetSmcByName>,
295            _responder: Responder<fpdev::device::GetSmcByName>,
296        ) {
297            unimplemented!("not used by tests");
298        }
299
300        async fn get_power_configuration(
301            &mut self,
302            _responder: Responder<fpdev::device::GetPowerConfiguration>,
303        ) {
304            unimplemented!("not used by tests");
305        }
306
307        async fn get_node_device_info(
308            &mut self,
309            _responder: Responder<fpdev::device::GetNodeDeviceInfo>,
310        ) {
311            unimplemented!("not used by tests");
312        }
313
314        async fn get_board_info(&mut self, _responder: Responder<fpdev::device::GetBoardInfo>) {
315            unimplemented!("not used by tests");
316        }
317
318        async fn get_metadata(
319            &mut self,
320            request: Request<fpdev::device::GetMetadata>,
321            responder: Responder<fpdev::device::GetMetadata>,
322        ) {
323            let id = &request.payload().id;
324            match self.read_metadata(id) {
325                Ok(metadata) => {
326                    let _ = responder.respond(metadata).await;
327                }
328                Err(status) => {
329                    let _ = responder.respond_err(status).await;
330                }
331            }
332        }
333    }
334
335    #[fuchsia::test]
336    async fn test_pdev() {
337        let mut server = TestServer::new();
338
339        let vmo = Vmo::create(4096).unwrap();
340        vmo.op_range(VmoOp::ZERO, 0, 4096).unwrap();
341        server.append_mmio("zero", vmo, 0, 4096);
342
343        // Prepare the MMIO region.
344        let vmo = Vmo::create(1024).unwrap();
345        for i in 0..256 {
346            vmo.write(&((i as u32).to_le_bytes()), (i * size_of::<u32>()) as u64).unwrap();
347        }
348        server.append_mmio("dev", vmo, 32 * size_of::<u32>(), 16);
349
350        server.set_typed_metadata(&Metadata {
351            test_field: Some("foo".to_string()),
352            ..Default::default()
353        });
354
355        let dict = fidl_fuchsia_driver_metadata::Dictionary {
356            entries: Some(vec![fidl_fuchsia_driver_metadata::DictionaryEntry {
357                key: "test_key".to_string(),
358                value: fidl_fuchsia_driver_metadata::DictionaryValue::Str("test_val".to_string()),
359            }]),
360            ..Default::default()
361        };
362        let dict_bytes = fidl::persist(&dict).unwrap();
363        server.metadata.insert("test_dict_id", dict_bytes.clone());
364
365        let (client, server) = server.run();
366
367        let mmio = client.map_mmio_by_id(1).await.unwrap();
368        assert_eq!(
369            client.map_mmio_by_id(1).await.err().map(|e| e.log_to_status()),
370            Some(Status::ALREADY_BOUND)
371        );
372        assert_eq!(
373            client.map_mmio_by_id(2).await.err().map(|e| e.log_to_status()),
374            Some(Status::NOT_FOUND)
375        );
376        assert_eq!(
377            client.map_mmio_by_name("dev").await.err().map(|e| e.log_to_status()),
378            Some(Status::ALREADY_BOUND)
379        );
380
381        assert_eq!(mmio.load32(0), 32);
382
383        let mmio = client.map_mmio_by_name("zero").await.unwrap();
384        assert_eq!(mmio.len(), 4096);
385        assert_eq!(mmio.load64(128), 0);
386
387        assert_eq!(
388            client.get_typed_metadata::<Metadata>().await.unwrap(),
389            Metadata { test_field: Some("foo".to_string()), ..Default::default() }
390        );
391
392        assert_eq!(
393            client.get_typed_metadata::<IntMetadata>().await.err().map(|e| e.log_to_status()),
394            Some(Status::NOT_FOUND)
395        );
396
397        let raw_bytes = client.get_persisted_metadata_by_id("test_dict_id").await.unwrap();
398        assert_eq!(raw_bytes, dict_bytes);
399
400        let retrieved_dict = client.get_dictionary_metadata("test_dict_id").await.unwrap();
401        assert_eq!(retrieved_dict, dict);
402
403        // Probing for dictionary metadata when typed metadata is present fails gracefully.
404        assert_eq!(
405            client
406                .get_dictionary_metadata(Metadata::SERIALIZABLE_NAME)
407                .await
408                .err()
409                .map(|e| e.log_to_status()),
410            Some(Status::INVALID_ARGS)
411        );
412
413        let _ = server.abort().await;
414    }
415}