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