1#[cfg(feature = "fdf_driver")]
15use crate::fuchsia_backends::{
16 FuchsiaClock, FuchsiaGpio, FuchsiaI2c, FuchsiaReset, FuchsiaSerial, FuchsiaSpi, LiveBackend,
17};
18#[cfg(feature = "fdf_driver")]
19use fidl_fuchsia_hardware_clock as fclock;
20#[cfg(feature = "fdf_driver")]
21use fidl_fuchsia_hardware_gpio as fgpio;
22#[cfg(feature = "fdf_driver")]
23use fidl_fuchsia_hardware_i2c as fi2c;
24#[cfg(feature = "fdf_driver")]
25use fidl_fuchsia_hardware_reset as freset;
26#[cfg(feature = "fdf_driver")]
27use fidl_fuchsia_hardware_serial as fserial;
28#[cfg(feature = "fdf_driver")]
29use fidl_fuchsia_hardware_spi as fspi;
30#[cfg(feature = "fdf_driver")]
31use fidl_next_fuchsia_hardware_platform_device as fpdev;
32#[cfg(feature = "fdf_driver")]
33use lab_proxy_core::access_policy::{
34 MmioResource, ProtocolCeiling, ResourceCeiling, ResourceId, ResourceKind,
35};
36use lab_proxy_core::hardware_backend::{BackendError, MmioBackend};
37#[cfg(feature = "fdf_driver")]
38use lab_proxy_core::provider::ProvidedResources;
39#[cfg(feature = "fdf_driver")]
40use log::info;
41#[cfg(feature = "fdf_driver")]
42use mmio::Mmio as _;
43#[cfg(feature = "fdf_driver")]
44use mmio::region::MmioRegion;
45#[cfg(feature = "fdf_driver")]
46use mmio::vmo::{VmoMapping, VmoMemory};
47#[cfg(feature = "fdf_driver")]
48use pdev::PlatformDevice;
49
50pub const PROVIDER: &str = "platform";
52
53#[cfg(not(feature = "fdf_driver"))]
54struct RawVmoMapping {
55 map_addr: usize,
56 map_size: usize,
57 base_addr: usize,
58 len: usize,
59}
60
61#[cfg(not(feature = "fdf_driver"))]
62impl RawVmoMapping {
63 fn map(offset: usize, size: usize, vmo: zx::Vmo) -> Result<Self, zx::Status> {
64 if size == 0 || size > isize::MAX as usize {
65 return Err(zx::Status::OUT_OF_RANGE);
66 }
67 let page_size = zx::system_get_page_size() as usize;
68 let page_offset = offset % page_size;
69 let aligned_offset = (offset - page_offset) as u64;
70 let map_size = size
71 .checked_add(page_offset)
72 .ok_or(zx::Status::OUT_OF_RANGE)?
73 .next_multiple_of(page_size);
74
75 let info = vmo.info()?;
76 if aligned_offset.saturating_add(map_size as u64) > info.size_bytes {
77 return Err(zx::Status::OUT_OF_RANGE);
78 }
79
80 let root_self = fuchsia_runtime::vmar_root_self();
81 let map_addr = root_self.map(
82 0,
83 &vmo,
84 aligned_offset,
85 map_size,
86 zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE | zx::VmarFlags::MAP_RANGE,
87 )?;
88 Ok(Self { map_addr, map_size, base_addr: map_addr + page_offset, len: size })
89 }
90
91 fn len(&self) -> usize {
92 self.len
93 }
94
95 fn try_load32(&self, offset: usize) -> Result<u32, BackendError> {
96 if !offset.is_multiple_of(4) || offset.checked_add(4).is_none_or(|end| end > self.len) {
97 return Err(BackendError::Fault);
98 }
99 let ptr = std::ptr::with_exposed_provenance::<u32>(self.base_addr + offset);
100 Ok(unsafe { std::ptr::read_volatile(ptr) })
102 }
103
104 fn try_store32(&mut self, offset: usize, value: u32) -> Result<(), BackendError> {
105 if !offset.is_multiple_of(4) || offset.checked_add(4).is_none_or(|end| end > self.len) {
106 return Err(BackendError::Fault);
107 }
108 let ptr = std::ptr::with_exposed_provenance_mut::<u32>(self.base_addr + offset);
109 unsafe { std::ptr::write_volatile(ptr, value) };
111 Ok(())
112 }
113}
114
115#[cfg(not(feature = "fdf_driver"))]
116impl Drop for RawVmoMapping {
117 fn drop(&mut self) {
118 let root_self = fuchsia_runtime::vmar_root_self();
119 let _ = unsafe { root_self.unmap(self.map_addr, self.map_size) };
122 }
123}
124
125pub struct MappedMmio {
127 #[cfg(feature = "fdf_driver")]
128 region: MmioRegion<VmoMemory>,
129 #[cfg(not(feature = "fdf_driver"))]
130 region: RawVmoMapping,
131}
132
133impl MappedMmio {
134 #[cfg(feature = "fdf_driver")]
136 pub fn from_region(region: MmioRegion<VmoMemory>) -> Self {
137 Self { region }
138 }
139
140 pub fn from_vmo(vmo: &zx::Vmo, offset: usize, size: usize) -> Result<Self, zx::Status> {
147 let dup_vmo = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
148 #[cfg(feature = "fdf_driver")]
149 {
150 let cache_policy = dup_vmo.info()?.cache_policy();
151 let region = VmoMapping::map_with_cache_policy(offset, size, dup_vmo, cache_policy)?;
152 Ok(Self { region })
153 }
154 #[cfg(not(feature = "fdf_driver"))]
155 {
156 let region = RawVmoMapping::map(offset, size, dup_vmo)?;
157 Ok(Self { region })
158 }
159 }
160
161 pub fn len(&self) -> usize {
163 self.region.len()
164 }
165
166 pub fn is_empty(&self) -> bool {
168 self.region.len() == 0
169 }
170}
171
172impl std::fmt::Debug for MappedMmio {
173 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174 f.debug_struct("MappedMmio").field("len", &self.region.len()).finish()
175 }
176}
177
178impl MmioBackend for MappedMmio {
179 fn read32(&mut self, offset: u64) -> Result<u32, BackendError> {
180 let offset = usize::try_from(offset).map_err(|_| BackendError::Fault)?;
181 self.region.try_load32(offset).map_err(|_| BackendError::Fault)
182 }
183
184 fn write32(&mut self, offset: u64, value: u32) -> Result<(), BackendError> {
185 let offset = usize::try_from(offset).map_err(|_| BackendError::Fault)?;
186 self.region.try_store32(offset, value).map_err(|_| BackendError::Fault)
187 }
188
189 fn barrier(&mut self) {
190 std::sync::atomic::fence(std::sync::atomic::Ordering::SeqCst);
191 }
192}
193
194impl lab_proxy_core::protocol_resource_adapter::ResourceBackend for MappedMmio {
195 fn read32(&mut self, offset: u64) -> Result<u32, BackendError> {
196 MmioBackend::read32(self, offset)
197 }
198
199 fn write32(&mut self, offset: u64, value: u32) -> Result<(), BackendError> {
200 MmioBackend::write32(self, offset, value)
201 }
202
203 fn barrier(&mut self) {
204 MmioBackend::barrier(self);
205 }
206}
207
208#[cfg(feature = "fdf_driver")]
211#[derive(Debug)]
212pub enum AcquireError {
213 Mmio {
215 id: u32,
217 },
218 Irq {
220 id: u32,
222 },
223}
224
225#[cfg(feature = "fdf_driver")]
226impl std::fmt::Display for AcquireError {
227 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228 match self {
229 AcquireError::Mmio { id } => write!(f, "MMIO {id} could not be fetched or mapped"),
230 AcquireError::Irq { id } => write!(f, "IRQ {id} could not be fetched"),
231 }
232 }
233}
234
235#[cfg(feature = "fdf_driver")]
236fn connect_instance(
237 context: &fdf_component::DriverContext,
238 instance: &str,
239) -> Option<fidl_next::Client<fpdev::Device>> {
240 let service = context
241 .incoming
242 .service::<fdf_component::ServiceInstance<fpdev::Service>>()
243 .instance(instance)
244 .connect_next()
245 .ok()?;
246 let (client_end, server_end) = fidl_next::fuchsia::create_channel();
247 service.device(server_end).ok()?;
248 Some(client_end.spawn())
249}
250
251#[cfg(feature = "fdf_driver")]
255pub async fn acquire(
256 context: &fdf_component::DriverContext,
257 node_identity: &str,
258) -> Result<(ProvidedResources<LiveBackend>, Vec<(ResourceId, zx::Interrupt)>), AcquireError> {
259 let mut bundle = ProvidedResources::empty(PROVIDER, node_identity);
260 let mut acquired_irqs = Vec::new();
261 let mut next_id: ResourceId = 0;
262
263 let mut connected_pdev = None;
265 for instance in ["pdev", "default"] {
266 let Some(pdev) = connect_instance(context, instance) else { continue };
267 match pdev.get_node_device_info().await {
268 Ok(Ok(device_info)) => {
269 connected_pdev = Some((
270 pdev,
271 device_info.mmio_count.unwrap_or(0),
272 device_info.irq_count.unwrap_or(0),
273 ));
274 break;
275 }
276 Ok(Err(status)) => {
277 info!("get_node_device_info on instance {instance} returned {status:?}");
278 }
279 Err(error) => {
280 info!("platform device instance {instance} not served ({error:?})");
281 }
282 }
283 }
284
285 if let Some((pdev, mmio_count, irq_count)) = connected_pdev {
286 for id in 0..mmio_count {
287 let region = pdev.map_mmio_by_id(id).await.map_err(|error| {
288 info!("mapping MMIO {id} failed: {error:?}");
289 AcquireError::Mmio { id }
290 })?;
291 let size = region.len() as u64;
292 let res_id = next_id;
293 next_id += 1;
294 bundle.resources.insert(res_id, MmioResource::mmio(format!("mmio{id}"), size, size));
295 bundle.ceiling.insert(
296 res_id,
297 ResourceCeiling {
298 hard_denied: vec![],
299 allow_unknown_reads: true,
300 allow_poll: true,
301 writable_registers: vec![],
302 protocol: None,
303 allow_interrupt: false,
304 },
305 );
306 bundle.backends.insert(res_id, LiveBackend::Mmio(MappedMmio { region }));
307 }
308
309 for id in 0..irq_count {
310 let irq = pdev
311 .get_interrupt_by_id(id, 0)
312 .await
313 .map_err(|error| {
314 info!("getting IRQ {id} failed: {error:?}");
315 AcquireError::Irq { id }
316 })?
317 .map_err(|status| {
318 info!("getting IRQ {id} returned status {status:?}");
319 AcquireError::Irq { id }
320 })?
321 .irq;
322 let res_id = next_id;
323 next_id += 1;
324 bundle.resources.insert(res_id, MmioResource::interrupt(format!("irq{id}")));
325 bundle.ceiling.insert(
326 res_id,
327 ResourceCeiling {
328 hard_denied: vec![],
329 allow_unknown_reads: false,
330 allow_poll: false,
331 writable_registers: vec![],
332 protocol: Some(ProtocolCeiling::default_for(ResourceKind::Interrupt)),
333 allow_interrupt: true,
334 },
335 );
336 bundle.backends.insert(res_id, LiveBackend::Interrupt);
337 acquired_irqs.push((res_id, irq));
338 }
339 }
340
341 for instance in ["default"] {
343 if let Ok(service) =
344 context.incoming.service_marker(fgpio::ServiceMarker).instance(&instance).connect()
345 {
346 if let Ok(proxy) = service.connect_to_device_sync() {
347 match proxy.read(zx::MonotonicInstant::INFINITE) {
348 Ok(_) => {
349 let res_id = next_id;
350 next_id += 1;
351 let name = if instance == "default" {
352 "gpio0".to_string()
353 } else {
354 format!("gpio-{instance}")
355 };
356 info!("acquired GPIO resource {name} (id {res_id})");
357 bundle.resources.insert(res_id, MmioResource::gpio(name));
358 bundle.ceiling.insert(
359 res_id,
360 ResourceCeiling {
361 hard_denied: vec![],
362 allow_unknown_reads: false,
363 allow_poll: false,
364 writable_registers: vec![],
365 protocol: Some(ProtocolCeiling::default_for(ResourceKind::Gpio)),
366 allow_interrupt: false,
367 },
368 );
369 bundle.backends.insert(res_id, LiveBackend::Gpio(FuchsiaGpio::new(proxy)));
370 }
371 Err(e) => {
372 info!("GPIO instance {instance} probe failed: {e:?}");
373 }
374 }
375 }
376 }
377 }
378
379 for instance in ["default"] {
381 if let Ok(service) =
382 context.incoming.service_marker(fi2c::ServiceMarker).instance(&instance).connect()
383 {
384 if let Ok(proxy) = service.connect_to_device_sync() {
385 match proxy.transfer(&[], zx::MonotonicInstant::INFINITE) {
387 Ok(Err(status)) if status == zx::Status::INVALID_ARGS.into_raw() => {
388 let res_id = next_id;
389 next_id += 1;
390 let name = if instance == "default" {
391 "i2c0".to_string()
392 } else {
393 format!("i2c-{instance}")
394 };
395 info!("acquired I2C resource {name} (id {res_id})");
396 bundle.resources.insert(res_id, MmioResource::i2c(name));
397 bundle.ceiling.insert(
398 res_id,
399 ResourceCeiling {
400 hard_denied: vec![],
401 allow_unknown_reads: false,
402 allow_poll: false,
403 writable_registers: vec![],
404 protocol: Some(ProtocolCeiling::default_for(ResourceKind::I2c)),
405 allow_interrupt: false,
406 },
407 );
408 bundle.backends.insert(res_id, LiveBackend::I2c(FuchsiaI2c::new(proxy)));
409 }
410 Ok(Ok(_)) => {
411 let res_id = next_id;
412 next_id += 1;
413 let name = if instance == "default" {
414 "i2c0".to_string()
415 } else {
416 format!("i2c-{instance}")
417 };
418 info!("acquired I2C resource {name} (id {res_id})");
419 bundle.resources.insert(res_id, MmioResource::i2c(name));
420 bundle.ceiling.insert(
421 res_id,
422 ResourceCeiling {
423 hard_denied: vec![],
424 allow_unknown_reads: false,
425 allow_poll: false,
426 writable_registers: vec![],
427 protocol: Some(ProtocolCeiling::default_for(ResourceKind::I2c)),
428 allow_interrupt: false,
429 },
430 );
431 bundle.backends.insert(res_id, LiveBackend::I2c(FuchsiaI2c::new(proxy)));
432 }
433 res => {
434 info!("I2C instance {instance} probe returned: {res:?}");
435 }
436 }
437 }
438 }
439 }
440
441 for instance in ["default"] {
443 if let Ok(service) =
444 context.incoming.service_marker(fspi::ServiceMarker).instance(&instance).connect()
445 {
446 if let Ok(proxy) = service.connect_to_device_sync() {
447 match proxy.can_assert_cs(zx::MonotonicInstant::INFINITE) {
449 Ok(_) => {
450 let res_id = next_id;
451 next_id += 1;
452 let name = if instance == "default" {
453 "spi0".to_string()
454 } else {
455 format!("spi-{instance}")
456 };
457 info!("acquired SPI resource {name} (id {res_id})");
458 bundle.resources.insert(res_id, MmioResource::spi(name));
459 bundle.ceiling.insert(
460 res_id,
461 ResourceCeiling {
462 hard_denied: vec![],
463 allow_unknown_reads: false,
464 allow_poll: false,
465 writable_registers: vec![],
466 protocol: Some(ProtocolCeiling::default_for(ResourceKind::Spi)),
467 allow_interrupt: false,
468 },
469 );
470 bundle.backends.insert(res_id, LiveBackend::Spi(FuchsiaSpi::new(proxy)));
471 }
472 Err(e) => {
473 info!("SPI instance {instance} probe returned: {e:?}");
474 }
475 }
476 }
477 }
478 }
479
480 for instance in ["default"] {
482 if let Ok(service) =
483 context.incoming.service_marker(fclock::ServiceMarker).instance(&instance).connect()
484 {
485 if let Ok(proxy) = service.connect_to_clock_sync() {
486 match proxy.is_enabled(zx::MonotonicInstant::INFINITE) {
487 Ok(_) => {
488 let res_id = next_id;
489 next_id += 1;
490 let name = if instance == "default" {
491 "clock0".to_string()
492 } else {
493 format!("clock-{instance}")
494 };
495 info!("acquired Clock resource {name} (id {res_id})");
496 bundle.resources.insert(res_id, MmioResource::clock(name));
497 bundle.ceiling.insert(
498 res_id,
499 ResourceCeiling {
500 hard_denied: vec![],
501 allow_unknown_reads: false,
502 allow_poll: false,
503 writable_registers: vec![],
504 protocol: Some(ProtocolCeiling::default_for(ResourceKind::Clock)),
505 allow_interrupt: false,
506 },
507 );
508 bundle
509 .backends
510 .insert(res_id, LiveBackend::Clock(FuchsiaClock::new(proxy)));
511 }
512 Err(e) => {
513 info!("Clock instance {instance} probe returned: {e:?}");
514 }
515 }
516 }
517 }
518 }
519
520 for instance in ["default"] {
522 if let Ok(service) =
523 context.incoming.service_marker(freset::ServiceMarker).instance(&instance).connect()
524 {
525 if let Ok(proxy) = service.connect_to_reset_sync() {
526 match proxy.status(zx::MonotonicInstant::INFINITE) {
527 Ok(_) => {
528 let res_id = next_id;
529 next_id += 1;
530 let name = if instance == "default" {
531 "reset0".to_string()
532 } else {
533 format!("reset-{instance}")
534 };
535 info!("acquired Reset resource {name} (id {res_id})");
536 bundle.resources.insert(res_id, MmioResource::reset(name));
537 bundle.ceiling.insert(
538 res_id,
539 ResourceCeiling {
540 hard_denied: vec![],
541 allow_unknown_reads: false,
542 allow_poll: false,
543 writable_registers: vec![],
544 protocol: Some(ProtocolCeiling::default_for(ResourceKind::Reset)),
545 allow_interrupt: false,
546 },
547 );
548 bundle
549 .backends
550 .insert(res_id, LiveBackend::Reset(FuchsiaReset::new(proxy)));
551 }
552 Err(e) => {
553 info!("Reset instance {instance} probe returned: {e:?}");
554 }
555 }
556 }
557 }
558 }
559
560 for instance in ["default"] {
562 if let Ok(service) =
563 context.incoming.service_marker(fserial::ServiceMarker).instance(&instance).connect()
564 {
565 if let Ok(proxy) = service.connect_to_device_sync() {
566 match proxy.get_class(zx::MonotonicInstant::INFINITE) {
567 Ok(_) => {
568 let res_id = next_id;
569 next_id += 1;
570 let name = if instance == "default" {
571 "serial0".to_string()
572 } else {
573 format!("serial-{instance}")
574 };
575 info!("acquired Serial resource {name} (id {res_id})");
576 bundle.resources.insert(res_id, MmioResource::serial(name));
577 bundle.ceiling.insert(
578 res_id,
579 ResourceCeiling {
580 hard_denied: vec![],
581 allow_unknown_reads: false,
582 allow_poll: false,
583 writable_registers: vec![],
584 protocol: Some(ProtocolCeiling::default_for(ResourceKind::Serial)),
585 allow_interrupt: false,
586 },
587 );
588 bundle
589 .backends
590 .insert(res_id, LiveBackend::Serial(FuchsiaSerial::new(proxy)));
591 }
592 Err(e) => {
593 info!("Serial instance {instance} probe returned: {e:?}");
594 }
595 }
596 }
597 }
598 }
599
600 if bundle.resources.is_empty() {
601 info!("no hardware resources offered; serving zero resources");
602 return Ok((ProvidedResources::empty("none", node_identity), acquired_irqs));
603 }
604
605 info!("acquired {} total hardware resource(s)", bundle.resources.len());
606 Ok((bundle, acquired_irqs))
607}
608
609#[cfg(test)]
610mod tests {
611 use super::*;
612 use zx::CachePolicy;
613
614 #[test]
615 fn mapped_mmio_from_vmo_shares_memory_independently_of_driver_mapping() {
616 const SIZE: usize = 4096;
617 let vmo = zx::Vmo::create(SIZE as u64).unwrap();
618 let driver_vmo = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
619
620 let mut driver_region =
622 VmoMapping::map_with_cache_policy(0, SIZE, driver_vmo, CachePolicy::Cached).unwrap();
623 assert_eq!(vmo.info().unwrap().num_mappings, 1);
624
625 driver_region.try_store32(0x10, 0xCAFE_BABE).unwrap();
627
628 let mut lab_mmio = MappedMmio::from_vmo(&vmo, 0, SIZE).unwrap();
630 assert_eq!(lab_mmio.len(), SIZE);
631 assert!(!lab_mmio.is_empty());
632 assert_eq!(vmo.info().unwrap().num_mappings, 2);
633
634 assert_eq!(MmioBackend::read32(&mut lab_mmio, 0x10), Ok(0xCAFE_BABE));
636
637 assert_eq!(MmioBackend::write32(&mut lab_mmio, 0x20, 0x1234_5678), Ok(()));
639 assert_eq!(driver_region.try_load32(0x20), Ok(0x1234_5678));
640
641 drop(lab_mmio);
643 assert_eq!(vmo.info().unwrap().num_mappings, 1);
644 assert_eq!(driver_region.try_load32(0x10), Ok(0xCAFE_BABE));
645 assert_eq!(driver_region.try_load32(0x20), Ok(0x1234_5678));
646 }
647
648 #[test]
649 fn mapped_mmio_from_vmo_rejects_out_of_range_size() {
650 let vmo = zx::Vmo::create(4096).unwrap();
651 let err = MappedMmio::from_vmo(&vmo, 0, 8192).unwrap_err();
652 assert_eq!(err, zx::Status::OUT_OF_RANGE);
653 }
654}