axaddrspace/memory_accessor.rs
1// Copyright 2025 The Axvisor Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Unified guest memory access interface
16//!
17//! This module provides a safe and consistent way to access guest memory
18//! from VirtIO device implementations, handling address translation and
19//! memory safety concerns.
20use ax_memory_addr::PhysAddr;
21use axvm_types::GuestPhysAddr;
22
23use crate::{AddrSpaceError, AddrSpaceResult};
24
25/// A stateful accessor to the memory space of a guest
26pub trait GuestMemoryAccessor {
27 /// Translate a guest physical address to host physical address and get access limit
28 ///
29 /// Returns a tuple of (host_physical_address, accessible_size) if the translation
30 /// is successful. The accessible_size indicates how many bytes can be safely
31 /// accessed starting from the given guest address.
32 fn translate_and_get_limit(&self, guest_addr: GuestPhysAddr) -> Option<(PhysAddr, usize)>;
33
34 /// Read a value of type V from guest memory
35 ///
36 /// # Returns
37 ///
38 /// Returns an error when the address is unmapped or the region is too small.
39 /// - The guest address cannot be translated to a valid host address
40 /// - The accessible memory region starting from the guest address is smaller
41 /// than the size of type V (insufficient space for the read operation)
42 ///
43 /// # Safety
44 ///
45 /// This function uses volatile memory access to ensure the read operation
46 /// is not optimized away by the compiler, which is important for device
47 /// register access and shared memory scenarios.
48 fn read_obj<V: Copy>(&self, guest_addr: GuestPhysAddr) -> AddrSpaceResult<V> {
49 let (host_addr, limit) =
50 self.translate_and_get_limit(guest_addr)
51 .ok_or(AddrSpaceError::Unmapped {
52 address: guest_addr,
53 })?;
54
55 // Check if we have enough space to read the object
56 if limit < core::mem::size_of::<V>() {
57 return Err(AddrSpaceError::InsufficientAccess {
58 operation: "read guest object",
59 address: guest_addr,
60 requested: core::mem::size_of::<V>(),
61 available: limit,
62 });
63 }
64
65 unsafe {
66 let ptr = host_addr.as_usize() as *const V;
67 Ok(core::ptr::read_volatile(ptr))
68 }
69 }
70
71 /// Write a value of type V to guest memory
72 ///
73 /// # Returns
74 ///
75 /// Returns an error when the address is unmapped or the region is too small.
76 /// - The guest address cannot be translated to a valid host address
77 /// - The accessible memory region starting from the guest address is smaller
78 /// than the size of type V (insufficient space for the write operation)
79 ///
80 /// # Safety
81 ///
82 /// This function uses volatile memory access to ensure the write operation
83 /// is not optimized away by the compiler, which is important for device
84 /// register access and shared memory scenarios.
85 fn write_obj<V: Copy>(&self, guest_addr: GuestPhysAddr, val: V) -> AddrSpaceResult {
86 let (host_addr, limit) =
87 self.translate_and_get_limit(guest_addr)
88 .ok_or(AddrSpaceError::Unmapped {
89 address: guest_addr,
90 })?;
91
92 // Check if we have enough space to write the object
93 if limit < core::mem::size_of::<V>() {
94 return Err(AddrSpaceError::InsufficientAccess {
95 operation: "write guest object",
96 address: guest_addr,
97 requested: core::mem::size_of::<V>(),
98 available: limit,
99 });
100 }
101
102 unsafe {
103 let ptr = host_addr.as_usize() as *mut V;
104 core::ptr::write_volatile(ptr, val);
105 }
106 Ok(())
107 }
108
109 /// Read a buffer from guest memory
110 fn read_buffer(&self, guest_addr: GuestPhysAddr, buffer: &mut [u8]) -> AddrSpaceResult {
111 if buffer.is_empty() {
112 return Ok(());
113 }
114
115 let (host_addr, accessible_size) =
116 self.translate_and_get_limit(guest_addr)
117 .ok_or(AddrSpaceError::Unmapped {
118 address: guest_addr,
119 })?;
120
121 // Check if we can read the entire buffer from this accessible region
122 if accessible_size >= buffer.len() {
123 // Simple case: entire buffer fits within accessible region
124 unsafe {
125 let src_ptr = host_addr.as_usize() as *const u8;
126 core::ptr::copy_nonoverlapping(src_ptr, buffer.as_mut_ptr(), buffer.len());
127 }
128 return Ok(());
129 }
130
131 // Complex case: buffer spans multiple regions, handle region by region
132 let mut current_guest_addr = guest_addr;
133 let mut remaining_buffer = buffer;
134
135 while !remaining_buffer.is_empty() {
136 let (current_host_addr, current_accessible_size) = self
137 .translate_and_get_limit(current_guest_addr)
138 .ok_or(AddrSpaceError::Unmapped {
139 address: current_guest_addr,
140 })?;
141
142 if current_accessible_size == 0 {
143 return Err(AddrSpaceError::InsufficientAccess {
144 operation: "read guest buffer",
145 address: current_guest_addr,
146 requested: remaining_buffer.len(),
147 available: 0,
148 });
149 }
150
151 let bytes_to_read = remaining_buffer.len().min(current_accessible_size);
152
153 // Read from current accessible region
154 unsafe {
155 let src_ptr = current_host_addr.as_usize() as *const u8;
156 core::ptr::copy_nonoverlapping(
157 src_ptr,
158 remaining_buffer.as_mut_ptr(),
159 bytes_to_read,
160 );
161 }
162
163 // Move to next region
164 current_guest_addr = advance_guest_address(current_guest_addr, bytes_to_read)?;
165 remaining_buffer = &mut remaining_buffer[bytes_to_read..];
166 }
167
168 Ok(())
169 }
170
171 /// Write a buffer to guest memory
172 fn write_buffer(&self, guest_addr: GuestPhysAddr, buffer: &[u8]) -> AddrSpaceResult {
173 if buffer.is_empty() {
174 return Ok(());
175 }
176
177 let (host_addr, accessible_size) =
178 self.translate_and_get_limit(guest_addr)
179 .ok_or(AddrSpaceError::Unmapped {
180 address: guest_addr,
181 })?;
182
183 // Check if we can write the entire buffer to this accessible region
184 if accessible_size >= buffer.len() {
185 // Simple case: entire buffer fits within accessible region
186 unsafe {
187 let dst_ptr = host_addr.as_usize() as *mut u8;
188 core::ptr::copy_nonoverlapping(buffer.as_ptr(), dst_ptr, buffer.len());
189 }
190 return Ok(());
191 }
192
193 // Complex case: buffer spans multiple regions, handle region by region
194 let mut current_guest_addr = guest_addr;
195 let mut remaining_buffer = buffer;
196
197 while !remaining_buffer.is_empty() {
198 let (current_host_addr, current_accessible_size) = self
199 .translate_and_get_limit(current_guest_addr)
200 .ok_or(AddrSpaceError::Unmapped {
201 address: current_guest_addr,
202 })?;
203
204 if current_accessible_size == 0 {
205 return Err(AddrSpaceError::InsufficientAccess {
206 operation: "write guest buffer",
207 address: current_guest_addr,
208 requested: remaining_buffer.len(),
209 available: 0,
210 });
211 }
212
213 let bytes_to_write = remaining_buffer.len().min(current_accessible_size);
214
215 // Write to current accessible region
216 unsafe {
217 let dst_ptr = current_host_addr.as_usize() as *mut u8;
218 core::ptr::copy_nonoverlapping(remaining_buffer.as_ptr(), dst_ptr, bytes_to_write);
219 }
220
221 // Move to next region
222 current_guest_addr = advance_guest_address(current_guest_addr, bytes_to_write)?;
223 remaining_buffer = &remaining_buffer[bytes_to_write..];
224 }
225
226 Ok(())
227 }
228
229 /// Read a volatile value from guest memory (for device registers)
230 fn read_volatile<V: Copy>(&self, guest_addr: GuestPhysAddr) -> AddrSpaceResult<V> {
231 self.read_obj(guest_addr)
232 }
233
234 /// Write a volatile value to guest memory (for device registers)
235 fn write_volatile<V: Copy>(&self, guest_addr: GuestPhysAddr, val: V) -> AddrSpaceResult {
236 self.write_obj(guest_addr, val)
237 }
238}
239
240fn advance_guest_address(address: GuestPhysAddr, size: usize) -> AddrSpaceResult<GuestPhysAddr> {
241 let next = address
242 .as_usize()
243 .checked_add(size)
244 .ok_or(AddrSpaceError::AddressOverflow {
245 start: address.as_usize(),
246 size,
247 })?;
248 Ok(GuestPhysAddr::from_usize(next))
249}