Skip to main content

safe_mmio/backend/
mmio_ops.rs

1// Copyright 2026 The safe-mmio Authors.
2// This project is dual-licensed under Apache 2.0 and MIT terms.
3// See LICENSE-APACHE and LICENSE-MIT for details.
4
5use core::ptr::NonNull;
6use zerocopy::{FromBytes, Immutable, IntoBytes};
7
8fn convert<T: Immutable + IntoBytes, U: FromBytes>(value: T) -> U {
9    U::read_from_bytes(value.as_bytes()).unwrap()
10}
11
12/// Trait for custom MMIO read/write implementations.
13///
14/// Per-size methods are used because MMIO access width matters at the hardware level (e.g. the
15/// GHCB protocol needs to know the exact access size for VMGEXIT calls).
16///
17/// The pointer is guaranteed to be properly aligned for its type and to point to valid MMIO address
18/// space.
19pub trait MmioOps {
20    /// Perform an 8-bit MMIO read.
21    ///
22    /// # Safety
23    ///
24    /// `src` must be a valid, aligned pointer to MMIO address space.
25    unsafe fn read_u8(src: *const u8) -> u8;
26
27    /// Perform a 16-bit MMIO read.
28    ///
29    /// # Safety
30    ///
31    /// `src` must be a valid, aligned pointer to MMIO address space.
32    unsafe fn read_u16(src: *const u16) -> u16;
33
34    /// Perform a 32-bit MMIO read.
35    ///
36    /// # Safety
37    ///
38    /// `src` must be a valid, aligned pointer to MMIO address space.
39    unsafe fn read_u32(src: *const u32) -> u32;
40
41    /// Perform a 64-bit MMIO read.
42    ///
43    /// # Safety
44    ///
45    /// `src` must be a valid, aligned pointer to MMIO address space.
46    unsafe fn read_u64(src: *const u64) -> u64;
47
48    /// Perform an 8-bit MMIO write.
49    ///
50    /// # Safety
51    ///
52    /// `dst` must be a valid, aligned pointer to MMIO address space.
53    unsafe fn write_u8(dst: *mut u8, value: u8);
54
55    /// Perform a 16-bit MMIO write.
56    ///
57    /// # Safety
58    ///
59    /// `dst` must be a valid, aligned pointer to MMIO address space.
60    unsafe fn write_u16(dst: *mut u16, value: u16);
61
62    /// Perform a 32-bit MMIO write.
63    ///
64    /// # Safety
65    ///
66    /// `dst` must be a valid, aligned pointer to MMIO address space.
67    unsafe fn write_u32(dst: *mut u32, value: u32);
68
69    /// Perform a 64-bit MMIO write.
70    ///
71    /// # Safety
72    ///
73    /// `dst` must be a valid, aligned pointer to MMIO address space.
74    unsafe fn write_u64(dst: *mut u64, value: u64);
75
76    /// Performs an MMIO read and returns the value.
77    ///
78    /// # Safety
79    ///
80    /// The pointer must be valid to perform an MMIO read from.
81    unsafe fn read<T: FromBytes + IntoBytes>(ptr: NonNull<T>) -> T {
82        // SAFETY: ptr is a valid, aligned pointer to MMIO address space. The implementor
83        // provides correctly-functioning primitive MMIO operations. For sizes 1/2/4/8 we perform
84        // a single access; for larger sizes we split into chunks.
85        unsafe {
86            match size_of::<T>() {
87                1 => convert(Self::read_u8(ptr.cast().as_ptr())),
88                2 => convert(Self::read_u16(ptr.cast().as_ptr())),
89                4 => convert(Self::read_u32(ptr.cast().as_ptr())),
90                8 => convert(Self::read_u64(ptr.cast().as_ptr())),
91                _ => {
92                    let mut value = T::new_zeroed();
93                    Self::read_slice(ptr.cast(), value.as_mut_bytes());
94                    value
95                }
96            }
97        }
98    }
99
100    /// Reads from MMIO by splitting into naturally-sized chunks.
101    ///
102    /// # Safety
103    ///
104    /// `ptr` must be valid for MMIO reads spanning `slice.len()` bytes.
105    unsafe fn read_slice(ptr: NonNull<u8>, slice: &mut [u8]) {
106        if let Some((first, rest)) = slice.split_at_mut_checked(8)
107            && ptr.cast::<u64>().is_aligned()
108        {
109            // SAFETY: Caller guarantees ptr is valid for the full slice length and we just checked
110            // that it is properly aligned for u64.
111            unsafe {
112                Self::read_u64(ptr.cast().as_ptr()).write_to(first).unwrap();
113                Self::read_slice(ptr.add(8), rest);
114            }
115        } else if let Some((first, rest)) = slice.split_at_mut_checked(4)
116            && ptr.cast::<u32>().is_aligned()
117        {
118            // SAFETY: Caller guarantees ptr is valid for the full slice length and we just checked
119            // that it is properly aligned for u32.
120            unsafe {
121                Self::read_u32(ptr.cast().as_ptr()).write_to(first).unwrap();
122                Self::read_slice(ptr.add(4), rest);
123            }
124        } else if let Some((first, rest)) = slice.split_at_mut_checked(2)
125            && ptr.cast::<u16>().is_aligned()
126        {
127            // SAFETY: Caller guarantees ptr is valid for the full slice length and we just checked
128            // that it is properly aligned for u16.
129            unsafe {
130                Self::read_u16(ptr.cast().as_ptr()).write_to(first).unwrap();
131                Self::read_slice(ptr.add(2), rest);
132            }
133        } else if let [first, rest @ ..] = slice {
134            // SAFETY: Caller guarantees ptr is valid for the full slice length.
135            unsafe {
136                *first = Self::read_u8(ptr.as_ptr());
137                Self::read_slice(ptr.add(1), rest);
138            }
139        }
140    }
141
142    /// Writes to MMIO by splitting into naturally-sized chunks.
143    ///
144    /// # Safety
145    ///
146    /// `ptr` must be valid for MMIO writes spanning `slice.len()` bytes.
147    unsafe fn write_slice(ptr: NonNull<u8>, slice: &[u8]) {
148        if let Some((first, rest)) = slice.split_at_checked(8)
149            && ptr.cast::<u64>().is_aligned()
150        {
151            // SAFETY: Caller guarantees ptr is valid for the full slice length and we just checked
152            // that it is properly aligned for u64.
153            unsafe {
154                Self::write_u64(ptr.cast().as_ptr(), u64::read_from_bytes(first).unwrap());
155                Self::write_slice(ptr.add(8), rest);
156            }
157        } else if let Some((first, rest)) = slice.split_at_checked(4)
158            && ptr.cast::<u32>().is_aligned()
159        {
160            // SAFETY: Caller guarantees ptr is valid for the full slice length and we just checked
161            // that it is properly aligned for u32.
162            unsafe {
163                Self::write_u32(ptr.cast().as_ptr(), u32::read_from_bytes(first).unwrap());
164                Self::write_slice(ptr.add(4), rest);
165            }
166        } else if let Some((first, rest)) = slice.split_at_checked(2)
167            && ptr.cast::<u16>().is_aligned()
168        {
169            // SAFETY: Caller guarantees ptr is valid for the full slice length and we just checked
170            // that it is properly aligned for u16.
171            unsafe {
172                Self::write_u16(ptr.cast().as_ptr(), u16::read_from_bytes(first).unwrap());
173                Self::write_slice(ptr.add(2), rest);
174            }
175        } else if let [first, rest @ ..] = slice {
176            // SAFETY: Caller guarantees ptr is valid for the full slice length.
177            unsafe {
178                Self::write_u8(ptr.as_ptr(), *first);
179                Self::write_slice(ptr.add(1), rest);
180            }
181        }
182    }
183
184    /// Performs an MMIO write of the given value.
185    ///
186    /// # Safety
187    ///
188    /// `ptr` must be valid to perform an MMIO write to.
189    unsafe fn write<T: Immutable + IntoBytes>(ptr: NonNull<T>, value: T) {
190        // SAFETY: ptr is a valid, aligned pointer to MMIO address space. For sizes 1/2/4/8 we
191        // perform a single access; for larger sizes we split into chunks.
192        unsafe {
193            match size_of::<T>() {
194                1 => Self::write_u8(ptr.cast().as_ptr(), value.as_bytes()[0]),
195                2 => Self::write_u16(ptr.cast().as_ptr(), convert(value)),
196                4 => Self::write_u32(ptr.cast().as_ptr(), convert(value)),
197                8 => Self::write_u64(ptr.cast().as_ptr(), convert(value)),
198                _ => Self::write_slice(ptr.cast(), value.as_bytes()),
199            }
200        }
201    }
202}