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}