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
254/// An MMIO region from which ownership of disjoint sub-regions can be split off.
255pub trait MmioSplit: Mmio + Sized {
256 /// Splits this Mmio region into two at the given mid-point and returns the left-hand-side.
257 /// This MMIO region's bounds are updated to contain only the right-hand-side.
258 ///
259 /// # Errors
260 /// - [MmioError::OutOfRange]: if `mid > self.len()`.
261 fn try_split_off(&mut self, mid: usize) -> Result<Self, MmioError>;
262
263 /// Splits this MMIO region into two at the given mid-point and returns the left-hand-side.
264 /// This MMIO region's bounds are updated to contain only the right-hand-side.
265 ///
266 /// # Panics
267 /// - If `mid > self.len()`.
268 fn split_off(&mut self, mid: usize) -> Self {
269 self.try_split_off(mid).unwrap()
270 }
271}
272
273/// Implemented for the fundamental types supported by all [Mmio] implementations.
274///
275/// This is a sealed trait, implemented for the types: u8, u16, u32, u64.
276pub trait MmioOperand:
277 BitAnd<Output = Self>
278 + BitOr<Output = Self>
279 + Not<Output = Self>
280 + sealed::MmioOperand
281 + PartialEq
282 + Copy
283 + Debug
284 + Display
285 + Sized
286{
287 /// Loads a value of the this type from the MMIO region at the given offset.
288 ///
289 /// # Errors
290 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
291 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
292 fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError>;
293
294 /// Stores a value of this type to the MMIO region at the given offset.
295 ///
296 /// # Errors
297 /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
298 /// - [MmioError::Unaligned: if the offset is not suitably aligned.
299 fn try_store<M: Mmio + ?Sized>(
300 mmio: &mut M,
301 offset: usize,
302 value: Self,
303 ) -> Result<(), MmioError>;
304}
305
306mod sealed {
307 /// Internal sealed MmioOperand trait as the types implementing this must match the types in
308 /// the loadn/storen trait methods.
309 pub trait MmioOperand {}
310
311 impl MmioOperand for u8 {}
312 impl MmioOperand for u16 {}
313 impl MmioOperand for u32 {}
314 impl MmioOperand for u64 {}
315}
316
317impl MmioOperand for u8 {
318 fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
319 mmio.try_load8(offset)
320 }
321
322 fn try_store<M: Mmio + ?Sized>(
323 mmio: &mut M,
324 offset: usize,
325 value: Self,
326 ) -> Result<(), MmioError> {
327 mmio.try_store8(offset, value)
328 }
329}
330
331impl MmioOperand for u16 {
332 fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
333 mmio.try_load16(offset)
334 }
335
336 fn try_store<M: Mmio + ?Sized>(
337 mmio: &mut M,
338 offset: usize,
339 value: Self,
340 ) -> Result<(), MmioError> {
341 mmio.try_store16(offset, value)
342 }
343}
344
345impl MmioOperand for u32 {
346 fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
347 mmio.try_load32(offset)
348 }
349
350 fn try_store<M: Mmio + ?Sized>(
351 mmio: &mut M,
352 offset: usize,
353 value: Self,
354 ) -> Result<(), MmioError> {
355 mmio.try_store32(offset, value)
356 }
357}
358
359impl MmioOperand for u64 {
360 fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
361 mmio.try_load64(offset)
362 }
363
364 fn try_store<M: Mmio + ?Sized>(
365 mmio: &mut M,
366 offset: usize,
367 value: Self,
368 ) -> Result<(), MmioError> {
369 mmio.try_store64(offset, value)
370 }
371}
372
373/// This trait extends [Mmio] with some useful utilities. There is a blanket implementation for all
374/// types implementing [Mmio].
375///
376/// Functions may go into this trait instead of [Mmio] if any of the following is true:
377/// - their behavior shouldn't differ across Mmio implementations
378/// - they would make Mmio not dyn compatible
379/// - they would introduce an unnecessary burden on [Mmio] implementers
380pub trait MmioExt: Mmio {
381 /// Check that the given offset into this MMIO region would be suitable aligned for type `T`.
382 /// There is no guarantee that this offset is within the bounds of the MMIO region or that
383 /// there would be sufficient capacity to hold a `T` at that offset. See
384 /// [MmioExt::check_suitable_for].
385 ///
386 /// Returns [MmioError::Unaligned] if the offset os not suitably aligned.
387 fn check_aligned_for<T>(&self, offset: usize) -> Result<(), MmioError> {
388 let align = align_of::<T>();
389 let align_offset = self.align_offset(align);
390 // An offset is aligned if (offset = align_offset + i * align) for some i.
391 if offset.wrapping_sub(align_offset).is_multiple_of(align) {
392 Ok(())
393 } else {
394 Err(MmioError::Unaligned)
395 }
396 }
397
398 /// Checks that the given offset into this MMIO region has sufficient capacity to hold a value
399 /// of type `T`. There is no guarantee that the offset is suitably aligned. See
400 /// [MmioExt::check_capacity_for].
401 fn check_capacity_for<T>(&self, offset: usize) -> Result<(), MmioError> {
402 let capacity_at_offset = self.len().checked_sub(offset).ok_or(MmioError::OutOfRange)?;
403 if capacity_at_offset >= size_of::<T>() { Ok(()) } else { Err(MmioError::OutOfRange) }
404 }
405
406 /// Checks that the given offset into this MMIO rgion is suitably aligned and has sufficient
407 /// capacity for a value of type `T`.
408 fn check_suitable_for<T>(&self, offset: usize) -> Result<(), MmioError> {
409 self.check_aligned_for::<T>(offset)?;
410 self.check_capacity_for::<T>(offset)?;
411 Ok(())
412 }
413
414 /// Loads an [MmioOperand] from the MMIO region at the given offset.
415 ///
416 /// # Errors
417 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
418 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
419 fn try_load<T: MmioOperand>(&self, offset: usize) -> Result<T, MmioError> {
420 T::try_load(self, offset)
421 }
422
423 /// Loads an [MmioOperand] from the MMIO region at the given offset.
424 ///
425 /// # Panics
426 /// If `self.check_suitable_for::<T>(offset)` would fail.
427 ///
428 /// See [MmioExt::try_load] for a non-panicking version.
429 fn load<T: MmioOperand>(&self, offset: usize) -> T {
430 self.try_load(offset).unwrap()
431 }
432
433 /// Stores an [MmioOperand] to the MMIO region at the given offset.
434 ///
435 /// # Errors
436 /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
437 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
438 fn try_store<T: MmioOperand>(&mut self, offset: usize, value: T) -> Result<(), MmioError> {
439 T::try_store(self, offset, value)
440 }
441
442 /// Stores an [MmioOperand] to the MMIO region at the given offset.
443 ///
444 /// # Panics
445 /// If `self.check_suitable_for::<T>(offset)` would fail.
446 ///
447 /// See [MmioExt::try_store] for a non-panicking version.
448 fn store<T: MmioOperand>(&mut self, offset: usize, value: T) {
449 self.try_store(offset, value).unwrap()
450 }
451
452 /// Loads an [MmioOperand] value from an MMIO region at the given offset, returning only the bits
453 /// set in the given mask.
454 ///
455 /// # Errors
456 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
457 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
458 fn try_masked_load<T: MmioOperand>(&self, offset: usize, mask: T) -> Result<T, MmioError> {
459 self.try_load::<T>(offset).map(|v| v & mask)
460 }
461
462 /// Loads an [MmioOperand] value from an MMIO region at the given offset, returning only the bits
463 /// set in the given mask.
464 ///
465 /// # Panics
466 /// If `self.check_suitable_for::<T>(offset)` would fail.
467 ///
468 /// See [MmioExt::try_masked_load] for a non-panicking version.
469 fn masked_load<T: MmioOperand>(&self, offset: usize, mask: T) -> T {
470 self.try_masked_load::<T>(offset, mask).unwrap()
471 }
472
473 /// Updates the value in the MMIO region at the given offset, only modifying the bits set in
474 /// the given mask.
475 ///
476 /// This operation performs a read-modify-write in order to only modify the bits matching the
477 /// mask. As this is performed as a load from device memory followed by a store to device
478 /// memory, the device may change state in between these operations.
479 ///
480 /// Callers must ensure that the this sequence of operations is valid for the device they're
481 /// accessing and their use case.
482 ///
483 /// # Errors
484 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
485 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
486 fn try_masked_modify<T: MmioOperand>(
487 &mut self,
488 offset: usize,
489 mask: T,
490 value: T,
491 ) -> Result<(), MmioError> {
492 let current = self.try_load::<T>(offset)?;
493 let unchanged_bits = current & !mask;
494 let changed_bits = value & mask;
495 self.try_store(offset, unchanged_bits | changed_bits)
496 }
497
498 /// Updates the value in the MMIO region at the given offset, only modifying the bits set in
499 /// the given mask.
500 ///
501 /// This operation performs a read-modify-write in order to only modify the bits matching the
502 /// mask. As this is performed as a load from device memory followed by a store to device
503 /// memory, the device may change state in between these operations.
504 ///
505 /// Callers must ensure that the this sequence of operations is valid for the device they're
506 /// accessing and their use case.
507 ///
508 /// # Panics
509 ///
510 /// If `self.check_suitable_for::<T>(offset)` would fail.
511 ///
512 /// See [MmioExt::try_masked_modify] for a non-panicking version.
513 fn masked_modify<T: MmioOperand>(&mut self, offset: usize, mask: T, value: T) {
514 self.try_masked_modify(offset, mask, value).unwrap()
515 }
516
517 /// Reads a [`ReadableRegister`] from the MMIO region.
518 fn read_reg<R: ReadableRegister>(&self) -> R {
519 R::read(self)
520 }
521
522 /// Writes a [`WritableRegister`] to the MMIO region.
523 fn write_reg<R: WritableRegister>(&mut self, reg: R) {
524 reg.write(self)
525 }
526
527 /// Reads a [`Register`] from the MMIO region, updates it with the given function, and writes it
528 /// back.
529 fn update_reg<R: ReadableRegister + WritableRegister, F>(&mut self, f: F)
530 where
531 F: FnOnce(&mut R),
532 {
533 let mut reg = self.read_reg::<R>();
534 f(&mut reg);
535 self.write_reg(reg);
536 }
537
538 /// Reads a [`ReadableIndexedRegister`] from the MMIO region at the given index.
539 fn read_index_reg<R: ReadableIndexedRegister>(&self, index: usize) -> R {
540 R::read_index(self, index)
541 }
542
543 /// Writes a [`WritableIndexedRegister`] to the MMIO region at the given index.
544 fn write_index_reg<R: WritableIndexedRegister>(&mut self, index: usize, reg: R) {
545 reg.write_index(self, index)
546 }
547
548 /// Reads an [`IndexedRegister`] from the MMIO region at the given index, updates it with the
549 /// given function, and writes it back.
550 fn update_index_reg<R: ReadableIndexedRegister + WritableIndexedRegister, F>(
551 &mut self,
552 index: usize,
553 f: F,
554 ) where
555 F: FnOnce(&mut R),
556 {
557 let mut reg = self.read_index_reg::<R>(index);
558 f(&mut reg);
559 self.write_index_reg(index, reg);
560 }
561
562 /// Returns a [`RegisterProxy`] to ergonomically read a single `Register`.
563 fn reg<R: Register>(&self) -> RegisterProxy<'_, Self, R> {
564 RegisterProxy::new(self)
565 }
566
567 /// Returns a [`RegisterProxyMut`] to ergonomically mutate a single `Register`.
568 fn reg_mut<R: Register>(&mut self) -> RegisterProxyMut<'_, Self, R> {
569 RegisterProxyMut::new(self)
570 }
571
572 /// Returns an [`IndexedRegisterProxy`] to ergonomically read a single `IndexedRegister`.
573 fn indexed_reg<R: IndexedRegister>(&self) -> IndexedRegisterProxy<'_, Self, R> {
574 IndexedRegisterProxy::new(self)
575 }
576
577 /// Returns an [`IndexedRegisterProxyMut`] to ergonomically mutate a single `IndexedRegister`.
578 fn indexed_reg_mut<R: IndexedRegister>(&mut self) -> IndexedRegisterProxyMut<'_, Self, R> {
579 IndexedRegisterProxyMut::new(self)
580 }
581}
582
583impl<M: Mmio + ?Sized> MmioExt for M {}