mmio/mmio.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.
4use crate::register::{
5 IndexedRegister, IndexedRegisterProxy, IndexedRegisterProxyMut, ReadableIndexedRegister,
6 ReadableRegister, Register, RegisterProxy, RegisterProxyMut, WritableIndexedRegister,
7 WritableRegister,
8};
9use core::fmt::{Debug, Display};
10use core::ops::{BitAnd, BitOr, Not};
11
12/// An error which may be encountered when performing operations on an MMIO region.
13#[derive(Copy, Clone, PartialEq, Eq, Debug, thiserror::Error)]
14pub enum MmioError {
15 /// An operation was attempted outside the bounds of an MMIO region.
16 #[error("An MMIO operation was out of range")]
17 OutOfRange,
18 /// An operation would be unaligned for the operand type at the given offset into the MMIO
19 /// region.
20 #[error("An MMIO operation was unaligned")]
21 Unaligned,
22}
23
24/// An `Mmio` implementation provides access to an MMIO region.
25///
26/// # Device memory
27/// For implementations that provide access to device memory, all memory must be accessed with
28/// volatile semantics. I/O instructions should not be coalesced or cached and should occur in
29/// program order.
30///
31/// Implementations are not required to provide any ordering guarantees between device memory
32/// access and other instructions. Furthermore there is no guarantee that any side-effects are
33/// visible to subsequent operations.
34///
35/// Callers who need guarantees around instruction ordering and side-effect visibility should
36/// insert the appropriate compiler fences and/or memory barriers.
37///
38/// # Safety
39/// Implementations of `Mmio` are required to be safe in a Rust sense, however loads may have
40/// side-effects and device memory may change outside of the control of the host.
41///
42/// # Offsets, Alignment and Capacity
43/// Mmio implementations provide access to device memory via offsets, not addresses. There are no
44/// requirements on the alignment of offset `0`.
45///
46/// When using the load and store operations, callers are required to provide offsets representing
47/// locations that are suitably aligned and fully within bounds of the location. Failure to do so
48/// does not cause any safety issues, but may result in errors for the checked loads and stores, or
49/// panics for the non-checked variants.
50///
51/// If these guarantees can't be provided statically for a given use case, the following functions
52/// can be used to ensure these requirements:
53///
54/// - [MmioExt::check_aligned_for<T>]: returns an [MmioError::Unaligned] error if the given offset is
55/// not suitably aligned.
56/// - [MmioExt::check_capacity_for<T>]: returns an [MmioError::OutOfRange] error if there is not
57/// sufficient capacity at the given offset.
58/// - [MmioExt::check_suitable_for<T>]: returns an [MmioError] if there is not sufficient capacity at
59/// the given offset or it is not suitably aligned.
60///
61/// # Dyn Compatibility
62/// This trait is dyn compatible. See the [MmioExt] trait for useful utilities that extend this
63/// trait.
64pub trait Mmio {
65 /// Returns the size in bytes of this MMIO region.
66 fn len(&self) -> usize;
67
68 /// Returns true if the MMIO region has a length of 0.
69 fn is_empty(&self) -> bool {
70 self.len() == 0
71 }
72
73 /// Computes the first offset within this region that is aligned to `align`.
74 ///
75 /// See the trait-level documentation for more information on offsets and alignment.
76 ///
77 /// # Panics
78 /// If `align` is not a power of 2.
79 fn align_offset(&self, align: usize) -> usize;
80
81 /// Loads one byte from this MMIO region at the given offset.
82 ///
83 /// See the trait-level documentation for information about offset requirements.
84 ///
85 /// # Errors
86 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
87 fn try_load8(&self, offset: usize) -> Result<u8, MmioError>;
88
89 /// Loads one byte from this MMIO region at the given offset.
90 ///
91 /// See the trait-level documentation for information about offset requirements.
92 ///
93 /// # Panics
94 /// - If the load would exceed the bounds of this MMIO region.
95 ///
96 /// See [Mmio::try_load8] for a non-panicking version.
97 fn load8(&self, offset: usize) -> u8 {
98 self.try_load8(offset).unwrap()
99 }
100
101 /// Loads two bytes from this MMIO region at the given offset.
102 ///
103 /// See the trait-level documentation for information about offset requirements.
104 ///
105 /// # Errors
106 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
107 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
108 fn try_load16(&self, offset: usize) -> Result<u16, MmioError>;
109
110 /// Loads two bytes from this MMIO region at the given offset.
111 ///
112 /// See the trait-level documentation for information about offset requirements.
113 ///
114 /// # Panics
115 /// - If the load would exceed the bounds of this MMIO region.
116 /// - If the offset is not suitably aligned.
117 ///
118 /// See [Mmio::try_load16] for a non-panicking version.
119 fn load16(&self, offset: usize) -> u16 {
120 self.try_load16(offset).unwrap()
121 }
122
123 /// Loads four bytes from this MMIO region at the given offset.
124 ///
125 /// See the trait-level documentation for information about offset requirements.
126 ///
127 /// # Errors
128 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
129 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
130 fn try_load32(&self, offset: usize) -> Result<u32, MmioError>;
131
132 /// Loads four bytes from this MMIO region at the given offset.
133 ///
134 /// See the trait-level documentation for information about offset requirements.
135 ///
136 /// # Panics
137 /// - If the load would exceed the bounds of this MMIO region.
138 /// - If the offset is not suitably aligned.
139 ///
140 /// See [Mmio::try_load32] for a non-panicking version.
141 fn load32(&self, offset: usize) -> u32 {
142 self.try_load32(offset).unwrap()
143 }
144
145 /// Loads eight bytes from this MMIO region at the given offset.
146 ///
147 /// See the trait-level documentation for information about offset requirements.
148 ///
149 /// # Errors
150 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
151 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
152 fn try_load64(&self, offset: usize) -> Result<u64, MmioError>;
153
154 /// Loads eight bytes from this MMIO region at the given offset.
155 ///
156 /// See the trait-level documentation for information about offset requirements.
157 ///
158 /// # Panics
159 /// - If the load would exceed the bounds of this MMIO region.
160 /// - If the offset is not suitably aligned.
161 ///
162 /// See [Mmio::try_load64] for a non-panicking version.
163 fn load64(&self, offset: usize) -> u64 {
164 self.try_load64(offset).unwrap()
165 }
166
167 /// Stores one byte to this MMIO region at the given offset.
168 ///
169 /// See the trait-level documentation for information about offset requirements.
170 ///
171 /// # Errors
172 /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
173 fn try_store8(&mut self, offset: usize, value: u8) -> Result<(), MmioError>;
174
175 /// Stores one byte to this MMIO region at the given offset.
176 ///
177 /// See the trait-level documentation for information about offset requirements.
178 ///
179 /// # Panics
180 /// - If the store would exceed the bounds of this MMIO region.
181 ///
182 /// See [Mmio::try_store8] for a non-panicking version.
183 fn store8(&mut self, offset: usize, value: u8) {
184 self.try_store8(offset, value).unwrap();
185 }
186
187 /// Stores two bytes to this MMIO region at the given offset.
188 ///
189 /// See the trait-level documentation for information about offset requirements.
190 ///
191 /// # Errors
192 /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
193 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
194 fn try_store16(&mut self, offset: usize, value: u16) -> Result<(), MmioError>;
195
196 /// Stores two bytes to this MMIO region at the given offset.
197 ///
198 /// See the trait-level documentation for information about offset requirements.
199 ///
200 /// # Panics
201 /// - If the store would exceed the bounds of this MMIO region.
202 /// - If the offset is not suitably aligned.
203 ///
204 /// See [Mmio::try_store16] for a non-panicking version.
205 fn store16(&mut self, offset: usize, value: u16) {
206 self.try_store16(offset, value).unwrap();
207 }
208
209 /// Stores four bytes to this MMIO region at the given offset.
210 ///
211 /// See the trait-level documentation for information about offset requirements.
212 ///
213 /// # Errors
214 /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
215 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
216 fn try_store32(&mut self, offset: usize, value: u32) -> Result<(), MmioError>;
217
218 /// Stores four bytes to this MMIO region at the given offset.
219 ///
220 /// See the trait-level documentation for information about offset requirements.
221 ///
222 /// # Panics
223 /// - If the store would exceed the bounds of this MMIO region.
224 /// - If the offset is not suitably aligned.
225 ///
226 /// See [Mmio::try_store32] for a non-panicking version.
227 fn store32(&mut self, offset: usize, value: u32) {
228 self.try_store32(offset, value).unwrap();
229 }
230
231 /// Stores eight bytes to this MMIO region at the given offset.
232 ///
233 /// See the trait-level documentation for information about offset requirements.
234 ///
235 /// # Errors
236 /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
237 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
238 fn try_store64(&mut self, offset: usize, value: u64) -> Result<(), MmioError>;
239
240 /// Stores eight bytes to this MMIO region at the given offset.
241 ///
242 /// See the trait-level documentation for information about offset requirements.
243 ///
244 /// # Panics
245 /// - If the store would exceed the bounds of this MMIO region.
246 /// - If the offset is not suitably aligned.
247 ///
248 /// See [Mmio::try_store64] for a non-panicking version.
249 fn store64(&mut self, offset: usize, value: u64) {
250 self.try_store64(offset, value).unwrap();
251 }
252
253 /// Issues a memory write barrier. It is guaranteed that all stores preceding this barrier will
254 /// appear to have happened before all stores following this barrier.
255 fn write_barrier(&self);
256}
257
258/// An MMIO region from which ownership of disjoint sub-regions can be split off.
259pub trait MmioSplit: Mmio + Sized {
260 /// Splits this Mmio region into two at the given mid-point and returns the left-hand-side.
261 /// This MMIO region's bounds are updated to contain only the right-hand-side.
262 ///
263 /// # Errors
264 /// - [MmioError::OutOfRange]: if `mid > self.len()`.
265 fn try_split_off(&mut self, mid: usize) -> Result<Self, MmioError>;
266
267 /// Splits this MMIO region into two at the given mid-point and returns the left-hand-side.
268 /// This MMIO region's bounds are updated to contain only the right-hand-side.
269 ///
270 /// # Panics
271 /// - If `mid > self.len()`.
272 fn split_off(&mut self, mid: usize) -> Self {
273 self.try_split_off(mid).unwrap()
274 }
275}
276
277/// Implemented for the fundamental types supported by all [Mmio] implementations.
278///
279/// This is a sealed trait, implemented for the types: u8, u16, u32, u64.
280pub trait MmioOperand:
281 BitAnd<Output = Self>
282 + BitOr<Output = Self>
283 + Not<Output = Self>
284 + sealed::MmioOperand
285 + PartialEq
286 + Copy
287 + Debug
288 + Display
289 + Sized
290{
291 /// Loads a value of the this type from the MMIO region at the given offset.
292 ///
293 /// # Errors
294 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
295 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
296 fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError>;
297
298 /// Stores a value of this type to the MMIO region at the given offset.
299 ///
300 /// # Errors
301 /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
302 /// - [MmioError::Unaligned: if the offset is not suitably aligned.
303 fn try_store<M: Mmio + ?Sized>(
304 mmio: &mut M,
305 offset: usize,
306 value: Self,
307 ) -> Result<(), MmioError>;
308}
309
310mod sealed {
311 /// Internal sealed MmioOperand trait as the types implementing this must match the types in
312 /// the loadn/storen trait methods.
313 pub trait MmioOperand {}
314
315 impl MmioOperand for u8 {}
316 impl MmioOperand for u16 {}
317 impl MmioOperand for u32 {}
318 impl MmioOperand for u64 {}
319}
320
321impl MmioOperand for u8 {
322 fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
323 mmio.try_load8(offset)
324 }
325
326 fn try_store<M: Mmio + ?Sized>(
327 mmio: &mut M,
328 offset: usize,
329 value: Self,
330 ) -> Result<(), MmioError> {
331 mmio.try_store8(offset, value)
332 }
333}
334
335impl MmioOperand for u16 {
336 fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
337 mmio.try_load16(offset)
338 }
339
340 fn try_store<M: Mmio + ?Sized>(
341 mmio: &mut M,
342 offset: usize,
343 value: Self,
344 ) -> Result<(), MmioError> {
345 mmio.try_store16(offset, value)
346 }
347}
348
349impl MmioOperand for u32 {
350 fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
351 mmio.try_load32(offset)
352 }
353
354 fn try_store<M: Mmio + ?Sized>(
355 mmio: &mut M,
356 offset: usize,
357 value: Self,
358 ) -> Result<(), MmioError> {
359 mmio.try_store32(offset, value)
360 }
361}
362
363impl MmioOperand for u64 {
364 fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
365 mmio.try_load64(offset)
366 }
367
368 fn try_store<M: Mmio + ?Sized>(
369 mmio: &mut M,
370 offset: usize,
371 value: Self,
372 ) -> Result<(), MmioError> {
373 mmio.try_store64(offset, value)
374 }
375}
376
377/// This trait extends [Mmio] with some useful utilities. There is a blanket implementation for all
378/// types implementing [Mmio].
379///
380/// Functions may go into this trait instead of [Mmio] if any of the following is true:
381/// - their behavior shouldn't differ across Mmio implementations
382/// - they would make Mmio not dyn compatible
383/// - they would introduce an unnecessary burden on [Mmio] implementers
384pub trait MmioExt: Mmio {
385 /// Check that the given offset into this MMIO region would be suitable aligned for type `T`.
386 /// There is no guarantee that this offset is within the bounds of the MMIO region or that
387 /// there would be sufficient capacity to hold a `T` at that offset. See
388 /// [MmioExt::check_suitable_for].
389 ///
390 /// Returns [MmioError::Unaligned] if the offset os not suitably aligned.
391 fn check_aligned_for<T>(&self, offset: usize) -> Result<(), MmioError> {
392 let align = align_of::<T>();
393 let align_offset = self.align_offset(align);
394 // An offset is aligned if (offset = align_offset + i * align) for some i.
395 if offset.wrapping_sub(align_offset).is_multiple_of(align) {
396 Ok(())
397 } else {
398 Err(MmioError::Unaligned)
399 }
400 }
401
402 /// Checks that the given offset into this MMIO region has sufficient capacity to hold a value
403 /// of type `T`. There is no guarantee that the offset is suitably aligned. See
404 /// [MmioExt::check_capacity_for].
405 fn check_capacity_for<T>(&self, offset: usize) -> Result<(), MmioError> {
406 let capacity_at_offset = self.len().checked_sub(offset).ok_or(MmioError::OutOfRange)?;
407 if capacity_at_offset >= size_of::<T>() { Ok(()) } else { Err(MmioError::OutOfRange) }
408 }
409
410 /// Checks that the given offset into this MMIO rgion is suitably aligned and has sufficient
411 /// capacity for a value of type `T`.
412 fn check_suitable_for<T>(&self, offset: usize) -> Result<(), MmioError> {
413 self.check_aligned_for::<T>(offset)?;
414 self.check_capacity_for::<T>(offset)?;
415 Ok(())
416 }
417
418 /// Loads an [MmioOperand] from the MMIO region at the given offset.
419 ///
420 /// # Errors
421 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
422 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
423 fn try_load<T: MmioOperand>(&self, offset: usize) -> Result<T, MmioError> {
424 T::try_load(self, offset)
425 }
426
427 /// Loads an [MmioOperand] from the MMIO region at the given offset.
428 ///
429 /// # Panics
430 /// If `self.check_suitable_for::<T>(offset)` would fail.
431 ///
432 /// See [MmioExt::try_load] for a non-panicking version.
433 fn load<T: MmioOperand>(&self, offset: usize) -> T {
434 self.try_load(offset).unwrap()
435 }
436
437 /// Stores an [MmioOperand] to the MMIO region at the given offset.
438 ///
439 /// # Errors
440 /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
441 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
442 fn try_store<T: MmioOperand>(&mut self, offset: usize, value: T) -> Result<(), MmioError> {
443 T::try_store(self, offset, value)
444 }
445
446 /// Stores an [MmioOperand] to the MMIO region at the given offset.
447 ///
448 /// # Panics
449 /// If `self.check_suitable_for::<T>(offset)` would fail.
450 ///
451 /// See [MmioExt::try_store] for a non-panicking version.
452 fn store<T: MmioOperand>(&mut self, offset: usize, value: T) {
453 self.try_store(offset, value).unwrap()
454 }
455
456 /// Loads an [MmioOperand] value from an MMIO region at the given offset, returning only the bits
457 /// set in the given mask.
458 ///
459 /// # Errors
460 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
461 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
462 fn try_masked_load<T: MmioOperand>(&self, offset: usize, mask: T) -> Result<T, MmioError> {
463 self.try_load::<T>(offset).map(|v| v & mask)
464 }
465
466 /// Loads an [MmioOperand] value from an MMIO region at the given offset, returning only the bits
467 /// set in the given mask.
468 ///
469 /// # Panics
470 /// If `self.check_suitable_for::<T>(offset)` would fail.
471 ///
472 /// See [MmioExt::try_masked_load] for a non-panicking version.
473 fn masked_load<T: MmioOperand>(&self, offset: usize, mask: T) -> T {
474 self.try_masked_load::<T>(offset, mask).unwrap()
475 }
476
477 /// Updates the value in the MMIO region at the given offset, only modifying the bits set in
478 /// the given mask.
479 ///
480 /// This operation performs a read-modify-write in order to only modify the bits matching the
481 /// mask. As this is performed as a load from device memory followed by a store to device
482 /// memory, the device may change state in between these operations.
483 ///
484 /// Callers must ensure that the this sequence of operations is valid for the device they're
485 /// accessing and their use case.
486 ///
487 /// # Errors
488 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
489 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
490 fn try_masked_modify<T: MmioOperand>(
491 &mut self,
492 offset: usize,
493 mask: T,
494 value: T,
495 ) -> Result<(), MmioError> {
496 let current = self.try_load::<T>(offset)?;
497 let unchanged_bits = current & !mask;
498 let changed_bits = value & mask;
499 self.try_store(offset, unchanged_bits | changed_bits)
500 }
501
502 /// Updates the value in the MMIO region at the given offset, only modifying the bits set in
503 /// the given mask.
504 ///
505 /// This operation performs a read-modify-write in order to only modify the bits matching the
506 /// mask. As this is performed as a load from device memory followed by a store to device
507 /// memory, the device may change state in between these operations.
508 ///
509 /// Callers must ensure that the this sequence of operations is valid for the device they're
510 /// accessing and their use case.
511 ///
512 /// # Panics
513 ///
514 /// If `self.check_suitable_for::<T>(offset)` would fail.
515 ///
516 /// See [MmioExt::try_masked_modify] for a non-panicking version.
517 fn masked_modify<T: MmioOperand>(&mut self, offset: usize, mask: T, value: T) {
518 self.try_masked_modify(offset, mask, value).unwrap()
519 }
520
521 /// Reads a [`ReadableRegister`] from the MMIO region.
522 fn read_reg<R: ReadableRegister>(&self) -> R {
523 R::read(self)
524 }
525
526 /// Writes a [`WritableRegister`] to the MMIO region.
527 fn write_reg<R: WritableRegister>(&mut self, reg: R) {
528 reg.write(self)
529 }
530
531 /// Reads a [`Register`] from the MMIO region, updates it with the given function, and writes it
532 /// back.
533 fn update_reg<R: ReadableRegister + WritableRegister, F>(&mut self, f: F)
534 where
535 F: FnOnce(&mut R),
536 {
537 let mut reg = self.read_reg::<R>();
538 f(&mut reg);
539 self.write_reg(reg);
540 }
541
542 /// Reads a [`ReadableIndexedRegister`] from the MMIO region at the given index.
543 fn read_index_reg<R: ReadableIndexedRegister>(&self, index: usize) -> R {
544 R::read_index(self, index)
545 }
546
547 /// Writes a [`WritableIndexedRegister`] to the MMIO region at the given index.
548 fn write_index_reg<R: WritableIndexedRegister>(&mut self, index: usize, reg: R) {
549 reg.write_index(self, index)
550 }
551
552 /// Reads an [`IndexedRegister`] from the MMIO region at the given index, updates it with the
553 /// given function, and writes it back.
554 fn update_index_reg<R: ReadableIndexedRegister + WritableIndexedRegister, F>(
555 &mut self,
556 index: usize,
557 f: F,
558 ) where
559 F: FnOnce(&mut R),
560 {
561 let mut reg = self.read_index_reg::<R>(index);
562 f(&mut reg);
563 self.write_index_reg(index, reg);
564 }
565
566 /// Returns a [`RegisterProxy`] to ergonomically read a single `Register`.
567 fn reg<R: Register>(&self) -> RegisterProxy<'_, Self, R> {
568 RegisterProxy::new(self)
569 }
570
571 /// Returns a [`RegisterProxyMut`] to ergonomically mutate a single `Register`.
572 fn reg_mut<R: Register>(&mut self) -> RegisterProxyMut<'_, Self, R> {
573 RegisterProxyMut::new(self)
574 }
575
576 /// Returns an [`IndexedRegisterProxy`] to ergonomically read a single `IndexedRegister`.
577 fn indexed_reg<R: IndexedRegister>(&self) -> IndexedRegisterProxy<'_, Self, R> {
578 IndexedRegisterProxy::new(self)
579 }
580
581 /// Returns an [`IndexedRegisterProxyMut`] to ergonomically mutate a single `IndexedRegister`.
582 fn indexed_reg_mut<R: IndexedRegister>(&mut self) -> IndexedRegisterProxyMut<'_, Self, R> {
583 IndexedRegisterProxyMut::new(self)
584 }
585}
586
587impl<M: Mmio + ?Sized> MmioExt for M {}