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 /// Returns true if the MMIO region has a length of 0.
64 fn is_empty(&self) -> bool {
65 self.len() == 0
66 }
67
68 /// Computes the first offset within this region that is aligned to `align`.
69 ///
70 /// See the trait-level documentation for more information on offsets and alignment.
71 ///
72 /// # Panics
73 /// If `align` is not a power of 2.
74 fn align_offset(&self, align: usize) -> usize;
75
76 /// Loads one byte from this MMIO region at the given offset.
77 ///
78 /// See the trait-level documentation for information about offset requirements.
79 ///
80 /// # Errors
81 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
82 fn try_load8(&self, offset: usize) -> Result<u8, MmioError>;
83
84 /// Loads one byte from this MMIO region at the given offset.
85 ///
86 /// See the trait-level documentation for information about offset requirements.
87 ///
88 /// # Panics
89 /// - If the load would exceed the bounds of this MMIO region.
90 ///
91 /// See [Mmio::try_load8] for a non-panicking version.
92 fn load8(&self, offset: usize) -> u8 {
93 self.try_load8(offset).unwrap()
94 }
95
96 /// Loads two bytes from this MMIO region at the given offset.
97 ///
98 /// See the trait-level documentation for information about offset requirements.
99 ///
100 /// # Errors
101 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
102 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
103 fn try_load16(&self, offset: usize) -> Result<u16, MmioError>;
104
105 /// Loads two bytes from this MMIO region at the given offset.
106 ///
107 /// See the trait-level documentation for information about offset requirements.
108 ///
109 /// # Panics
110 /// - If the load would exceed the bounds of this MMIO region.
111 /// - If the offset is not suitably aligned.
112 ///
113 /// See [Mmio::try_load16] for a non-panicking version.
114 fn load16(&self, offset: usize) -> u16 {
115 self.try_load16(offset).unwrap()
116 }
117
118 /// Loads four bytes from this MMIO region at the given offset.
119 ///
120 /// See the trait-level documentation for information about offset requirements.
121 ///
122 /// # Errors
123 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
124 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
125 fn try_load32(&self, offset: usize) -> Result<u32, MmioError>;
126
127 /// Loads four bytes from this MMIO region at the given offset.
128 ///
129 /// See the trait-level documentation for information about offset requirements.
130 ///
131 /// # Panics
132 /// - If the load would exceed the bounds of this MMIO region.
133 /// - If the offset is not suitably aligned.
134 ///
135 /// See [Mmio::try_load32] for a non-panicking version.
136 fn load32(&self, offset: usize) -> u32 {
137 self.try_load32(offset).unwrap()
138 }
139
140 /// Loads eight bytes from this MMIO region at the given offset.
141 ///
142 /// See the trait-level documentation for information about offset requirements.
143 ///
144 /// # Errors
145 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
146 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
147 fn try_load64(&self, offset: usize) -> Result<u64, MmioError>;
148
149 /// Loads eight bytes from this MMIO region at the given offset.
150 ///
151 /// See the trait-level documentation for information about offset requirements.
152 ///
153 /// # Panics
154 /// - If the load would exceed the bounds of this MMIO region.
155 /// - If the offset is not suitably aligned.
156 ///
157 /// See [Mmio::try_load64] for a non-panicking version.
158 fn load64(&self, offset: usize) -> u64 {
159 self.try_load64(offset).unwrap()
160 }
161
162 /// Stores one byte to this MMIO region at the given offset.
163 ///
164 /// See the trait-level documentation for information about offset requirements.
165 ///
166 /// # Errors
167 /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
168 fn try_store8(&mut self, offset: usize, value: u8) -> Result<(), MmioError>;
169
170 /// Stores one byte to this MMIO region at the given offset.
171 ///
172 /// See the trait-level documentation for information about offset requirements.
173 ///
174 /// # Panics
175 /// - If the store would exceed the bounds of this MMIO region.
176 ///
177 /// See [Mmio::try_store8] for a non-panicking version.
178 fn store8(&mut self, offset: usize, value: u8) {
179 self.try_store8(offset, value).unwrap();
180 }
181
182 /// Stores two bytes to this MMIO region at the given offset.
183 ///
184 /// See the trait-level documentation for information about offset requirements.
185 ///
186 /// # Errors
187 /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
188 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
189 fn try_store16(&mut self, offset: usize, value: u16) -> Result<(), MmioError>;
190
191 /// Stores two bytes to this MMIO region at the given offset.
192 ///
193 /// See the trait-level documentation for information about offset requirements.
194 ///
195 /// # Panics
196 /// - If the store would exceed the bounds of this MMIO region.
197 /// - If the offset is not suitably aligned.
198 ///
199 /// See [Mmio::try_store16] for a non-panicking version.
200 fn store16(&mut self, offset: usize, value: u16) {
201 self.try_store16(offset, value).unwrap();
202 }
203
204 /// Stores four bytes to this MMIO region at the given offset.
205 ///
206 /// See the trait-level documentation for information about offset requirements.
207 ///
208 /// # Errors
209 /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
210 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
211 fn try_store32(&mut self, offset: usize, value: u32) -> Result<(), MmioError>;
212
213 /// Stores four bytes to this MMIO region at the given offset.
214 ///
215 /// See the trait-level documentation for information about offset requirements.
216 ///
217 /// # Panics
218 /// - If the store would exceed the bounds of this MMIO region.
219 /// - If the offset is not suitably aligned.
220 ///
221 /// See [Mmio::try_store32] for a non-panicking version.
222 fn store32(&mut self, offset: usize, value: u32) {
223 self.try_store32(offset, value).unwrap();
224 }
225
226 /// Stores eight bytes to this MMIO region at the given offset.
227 ///
228 /// See the trait-level documentation for information about offset requirements.
229 ///
230 /// # Errors
231 /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
232 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
233 fn try_store64(&mut self, offset: usize, value: u64) -> Result<(), MmioError>;
234
235 /// Stores eight bytes to this MMIO region at the given offset.
236 ///
237 /// See the trait-level documentation for information about offset requirements.
238 ///
239 /// # Panics
240 /// - If the store would exceed the bounds of this MMIO region.
241 /// - If the offset is not suitably aligned.
242 ///
243 /// See [Mmio::try_store64] for a non-panicking version.
244 fn store64(&mut self, offset: usize, value: u64) {
245 self.try_store64(offset, value).unwrap();
246 }
247}
248
249/// An MMIO region from which ownership of disjoint sub-regions can be split off.
250pub trait MmioSplit: Mmio + Sized {
251 /// Splits this Mmio region into two at the given mid-point and returns the left-hand-side.
252 /// This MMIO region's bounds are updated to contain only the right-hand-side.
253 ///
254 /// # Errors
255 /// - [MmioError::OutOfRange]: if `mid > self.len()`.
256 fn try_split_off(&mut self, mid: usize) -> Result<Self, MmioError>;
257
258 /// Splits this MMIO region into two at the given mid-point and returns the left-hand-side.
259 /// This MMIO region's bounds are updated to contain only the right-hand-side.
260 ///
261 /// # Panics
262 /// - If `mid > self.len()`.
263 fn split_off(&mut self, mid: usize) -> Self {
264 self.try_split_off(mid).unwrap()
265 }
266}
267
268/// Implemented for the fundamental types supported by all [Mmio] implementations.
269///
270/// This is a sealed trait, implemented for the types: u8, u16, u32, u64.
271pub trait MmioOperand:
272 BitAnd<Output = Self>
273 + BitOr<Output = Self>
274 + Not<Output = Self>
275 + sealed::MmioOperand
276 + PartialEq
277 + Copy
278 + Debug
279 + Display
280 + Sized
281{
282 /// Loads a value of the this type from the MMIO region at the given offset.
283 ///
284 /// # Errors
285 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
286 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
287 fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError>;
288
289 /// Stores a value of this type to the MMIO region at the given offset.
290 ///
291 /// # Errors
292 /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
293 /// - [MmioError::Unaligned: if the offset is not suitably aligned.
294 fn try_store<M: Mmio + ?Sized>(
295 mmio: &mut M,
296 offset: usize,
297 value: Self,
298 ) -> Result<(), MmioError>;
299}
300
301mod sealed {
302 /// Internal sealed MmioOperand trait as the types implementing this must match the types in
303 /// the loadn/storen trait methods.
304 pub trait MmioOperand {}
305
306 impl MmioOperand for u8 {}
307 impl MmioOperand for u16 {}
308 impl MmioOperand for u32 {}
309 impl MmioOperand for u64 {}
310}
311
312impl MmioOperand for u8 {
313 fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
314 mmio.try_load8(offset)
315 }
316
317 fn try_store<M: Mmio + ?Sized>(
318 mmio: &mut M,
319 offset: usize,
320 value: Self,
321 ) -> Result<(), MmioError> {
322 mmio.try_store8(offset, value)
323 }
324}
325
326impl MmioOperand for u16 {
327 fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
328 mmio.try_load16(offset)
329 }
330
331 fn try_store<M: Mmio + ?Sized>(
332 mmio: &mut M,
333 offset: usize,
334 value: Self,
335 ) -> Result<(), MmioError> {
336 mmio.try_store16(offset, value)
337 }
338}
339
340impl MmioOperand for u32 {
341 fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
342 mmio.try_load32(offset)
343 }
344
345 fn try_store<M: Mmio + ?Sized>(
346 mmio: &mut M,
347 offset: usize,
348 value: Self,
349 ) -> Result<(), MmioError> {
350 mmio.try_store32(offset, value)
351 }
352}
353
354impl MmioOperand for u64 {
355 fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
356 mmio.try_load64(offset)
357 }
358
359 fn try_store<M: Mmio + ?Sized>(
360 mmio: &mut M,
361 offset: usize,
362 value: Self,
363 ) -> Result<(), MmioError> {
364 mmio.try_store64(offset, value)
365 }
366}
367
368/// This trait extends [Mmio] with some useful utilities. There is a blanket implementation for all
369/// types implementing [Mmio].
370///
371/// Functions may go into this trait instead of [Mmio] if any of the following is true:
372/// - their behavior shouldn't differ across Mmio implementations
373/// - they would make Mmio not dyn compatible
374/// - they would introduce an unnecessary burden on [Mmio] implementers
375pub trait MmioExt: Mmio {
376 /// Check that the given offset into this MMIO region would be suitable aligned for type `T`.
377 /// There is no guarantee that this offset is within the bounds of the MMIO region or that
378 /// there would be sufficient capacity to hold a `T` at that offset. See
379 /// [MmioExt::check_suitable_for].
380 ///
381 /// Returns [MmioError::Unaligned] if the offset os not suitably aligned.
382 fn check_aligned_for<T>(&self, offset: usize) -> Result<(), MmioError> {
383 let align = align_of::<T>();
384 let align_offset = self.align_offset(align);
385 // An offset is aligned if (offset = align_offset + i * align) for some i.
386 if offset.wrapping_sub(align_offset).is_multiple_of(align) {
387 Ok(())
388 } else {
389 Err(MmioError::Unaligned)
390 }
391 }
392
393 /// Checks that the given offset into this MMIO region has sufficient capacity to hold a value
394 /// of type `T`. There is no guarantee that the offset is suitably aligned. See
395 /// [MmioExt::check_capacity_for].
396 fn check_capacity_for<T>(&self, offset: usize) -> Result<(), MmioError> {
397 let capacity_at_offset = self.len().checked_sub(offset).ok_or(MmioError::OutOfRange)?;
398 if capacity_at_offset >= size_of::<T>() { Ok(()) } else { Err(MmioError::OutOfRange) }
399 }
400
401 /// Checks that the given offset into this MMIO rgion is suitably aligned and has sufficient
402 /// capacity for a value of type `T`.
403 fn check_suitable_for<T>(&self, offset: usize) -> Result<(), MmioError> {
404 self.check_aligned_for::<T>(offset)?;
405 self.check_capacity_for::<T>(offset)?;
406 Ok(())
407 }
408
409 /// Loads an [MmioOperand] from the MMIO region at the given offset.
410 ///
411 /// # Errors
412 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
413 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
414 fn try_load<T: MmioOperand>(&self, offset: usize) -> Result<T, MmioError> {
415 T::try_load(self, offset)
416 }
417
418 /// Loads an [MmioOperand] from the MMIO region at the given offset.
419 ///
420 /// # Panics
421 /// If `self.check_suitable_for::<T>(offset)` would fail.
422 ///
423 /// See [MmioExt::try_load] for a non-panicking version.
424 fn load<T: MmioOperand>(&self, offset: usize) -> T {
425 self.try_load(offset).unwrap()
426 }
427
428 /// Stores an [MmioOperand] to the MMIO region at the given offset.
429 ///
430 /// # Errors
431 /// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
432 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
433 fn try_store<T: MmioOperand>(&mut self, offset: usize, value: T) -> Result<(), MmioError> {
434 T::try_store(self, offset, value)
435 }
436
437 /// Stores an [MmioOperand] to the MMIO region at the given offset.
438 ///
439 /// # Panics
440 /// If `self.check_suitable_for::<T>(offset)` would fail.
441 ///
442 /// See [MmioExt::try_store] for a non-panicking version.
443 fn store<T: MmioOperand>(&mut self, offset: usize, value: T) {
444 self.try_store(offset, value).unwrap()
445 }
446
447 /// Loads an [MmioOperand] value from an MMIO region at the given offset, returning only the bits
448 /// set in the given mask.
449 ///
450 /// # Errors
451 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
452 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
453 fn try_masked_load<T: MmioOperand>(&self, offset: usize, mask: T) -> Result<T, MmioError> {
454 self.try_load::<T>(offset).map(|v| v & mask)
455 }
456
457 /// Loads an [MmioOperand] value from an MMIO region at the given offset, returning only the bits
458 /// set in the given mask.
459 ///
460 /// # Panics
461 /// If `self.check_suitable_for::<T>(offset)` would fail.
462 ///
463 /// See [MmioExt::try_masked_load] for a non-panicking version.
464 fn masked_load<T: MmioOperand>(&self, offset: usize, mask: T) -> T {
465 self.try_masked_load::<T>(offset, mask).unwrap()
466 }
467
468 /// Updates the value in the MMIO region at the given offset, only modifying the bits set in
469 /// the given mask.
470 ///
471 /// This operation performs a read-modify-write in order to only modify the bits matching the
472 /// mask. As this is performed as a load from device memory followed by a store to device
473 /// memory, the device may change state in between these operations.
474 ///
475 /// Callers must ensure that the this sequence of operations is valid for the device they're
476 /// accessing and their use case.
477 ///
478 /// # Errors
479 /// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
480 /// - [MmioError::Unaligned]: if the offset is not suitably aligned.
481 fn try_masked_modify<T: MmioOperand>(
482 &mut self,
483 offset: usize,
484 mask: T,
485 value: T,
486 ) -> Result<(), MmioError> {
487 let current = self.try_load::<T>(offset)?;
488 let unchanged_bits = current & !mask;
489 let changed_bits = value & mask;
490 self.try_store(offset, unchanged_bits | changed_bits)
491 }
492
493 /// Updates the value in the MMIO region at the given offset, only modifying the bits set in
494 /// the given mask.
495 ///
496 /// This operation performs a read-modify-write in order to only modify the bits matching the
497 /// mask. As this is performed as a load from device memory followed by a store to device
498 /// memory, the device may change state in between these operations.
499 ///
500 /// Callers must ensure that the this sequence of operations is valid for the device they're
501 /// accessing and their use case.
502 ///
503 /// # Panics
504 ///
505 /// If `self.check_suitable_for::<T>(offset)` would fail.
506 ///
507 /// See [MmioExt::try_masked_modify] for a non-panicking version.
508 fn masked_modify<T: MmioOperand>(&mut self, offset: usize, mask: T, value: T) {
509 self.try_masked_modify(offset, mask, value).unwrap()
510 }
511}
512
513impl<M: Mmio> MmioExt for M {}