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