Skip to main content

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}