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