Skip to main content

arcbox_virtio_core/
guest_mem.rs

1//! Guest memory accessor for direct virtqueue manipulation.
2//!
3//! Provides safe-ish wrappers around a raw pointer into the guest RAM
4//! mmap. All methods accept guest physical addresses (GPAs) and
5//! translate them to host offsets by subtracting `gpa_base`.
6
7/// Raw pointer wrapper for guest physical memory access.
8///
9/// Backed by the VM-lifetime mmap of guest RAM. Device-owned descriptor
10/// buffers are exclusive to the device per the VirtIO spec — the
11/// guest will not touch them until the used ring advances.
12///
13/// All public methods accept guest physical addresses (GPAs) and translate
14/// them to slice offsets by subtracting `gpa_base`.
15pub struct GuestMemWriter {
16    ptr: *mut u8,
17    len: usize,
18    /// GPA of the start of guest RAM. Subtracted from every GPA argument
19    /// to obtain the host pointer offset within `ptr..ptr+len`.
20    gpa_base: usize,
21}
22
23// SAFETY: The pointer originates from a VM-lifetime mmap. The worker
24// thread writes only to descriptor buffers (device-owned) and the used
25// ring (with Release fences). No concurrent mutation from the guest is
26// possible for device-owned buffers per the VirtIO spec.
27unsafe impl Send for GuestMemWriter {}
28unsafe impl Sync for GuestMemWriter {}
29
30impl GuestMemWriter {
31    /// Creates a new writer from the DeviceManager's guest memory.
32    ///
33    /// `ptr` must point to the host mapping of guest RAM, which starts
34    /// at GPA `gpa_base`. `len` is the size of that mapping in bytes.
35    ///
36    /// # Safety
37    /// `ptr` must be valid for `len` bytes for the lifetime of the VM.
38    pub unsafe fn new(ptr: *mut u8, len: usize, gpa_base: usize) -> Self {
39        Self { ptr, len, gpa_base }
40    }
41
42    /// Translates a GPA to a host pointer offset, returning `None` if the
43    /// GPA falls below `gpa_base` (invalid) or the range exceeds the
44    /// mapped region.
45    pub fn gpa_to_offset(&self, gpa: usize, access_len: usize) -> Option<usize> {
46        let off = gpa.checked_sub(self.gpa_base)?;
47        let end = off.checked_add(access_len)?;
48        if end > self.len {
49            return None;
50        }
51        Some(off)
52    }
53
54    /// Returns a mutable slice into guest memory at the given GPA range.
55    ///
56    /// # Safety
57    /// Caller must ensure no other reference (mutable or shared) to the
58    /// same GPA range exists for the lifetime of the returned slice.
59    /// In practice, VirtIO descriptor ownership guarantees this — each
60    /// descriptor buffer is exclusive to the device that owns it.
61    #[allow(clippy::mut_from_ref)] // intentional: unsafe fn documents the aliasing contract
62    pub unsafe fn slice_mut(&self, gpa: usize, len: usize) -> Option<&mut [u8]> {
63        let off = self.gpa_to_offset(gpa, len)?;
64        // SAFETY: `gpa_to_offset` validated bounds within the allocation.
65        unsafe { Some(std::slice::from_raw_parts_mut(self.ptr.add(off), len)) }
66    }
67
68    /// Returns an immutable slice into guest memory at the given GPA range.
69    pub fn slice(&self, gpa: usize, len: usize) -> Option<&[u8]> {
70        let off = self.gpa_to_offset(gpa, len)?;
71        // SAFETY: `gpa_to_offset` validated bounds within the allocation.
72        unsafe { Some(std::slice::from_raw_parts(self.ptr.add(off), len)) }
73    }
74
75    /// Reads a little-endian `u16` from the given GPA. Returns 0 on
76    /// out-of-bounds access.
77    pub fn read_u16(&self, gpa: usize) -> u16 {
78        let Some(off) = self.gpa_to_offset(gpa, 2) else {
79            return 0;
80        };
81        // SAFETY: `gpa_to_offset` validated bounds within the allocation.
82        unsafe {
83            let p = self.ptr.add(off);
84            u16::from_le_bytes([*p, *p.add(1)])
85        }
86    }
87
88    /// Writes a little-endian `u16` to the given GPA. No-op on
89    /// out-of-bounds access.
90    pub fn write_u16(&self, gpa: usize, val: u16) {
91        let Some(off) = self.gpa_to_offset(gpa, 2) else {
92            return;
93        };
94        let bytes = val.to_le_bytes();
95        // SAFETY: `gpa_to_offset` validated bounds within the allocation.
96        unsafe {
97            let p = self.ptr.add(off);
98            *p = bytes[0];
99            *p.add(1) = bytes[1];
100        }
101    }
102
103    /// Writes a little-endian `u32` to the given GPA. No-op on
104    /// out-of-bounds access.
105    pub fn write_u32(&self, gpa: usize, val: u32) {
106        let Some(off) = self.gpa_to_offset(gpa, 4) else {
107            return;
108        };
109        let bytes = val.to_le_bytes();
110        // SAFETY: `gpa_to_offset` validated bounds within the allocation.
111        unsafe {
112            let p = self.ptr.add(off);
113            *p = bytes[0];
114            *p.add(1) = bytes[1];
115            *p.add(2) = bytes[2];
116            *p.add(3) = bytes[3];
117        }
118    }
119
120    /// Writes a single byte to the given GPA. No-op on out-of-bounds
121    /// access.
122    pub fn write_byte(&self, gpa: usize, val: u8) {
123        let Some(off) = self.gpa_to_offset(gpa, 1) else {
124            return;
125        };
126        // SAFETY: `gpa_to_offset` validated bounds within the allocation.
127        unsafe { *self.ptr.add(off) = val };
128    }
129
130    /// Returns the raw pointer to the start of guest memory.
131    pub fn ptr(&self) -> *mut u8 {
132        self.ptr
133    }
134
135    /// Returns the total length (in bytes) of the guest memory region.
136    pub fn len(&self) -> usize {
137        self.len
138    }
139
140    /// Returns `true` if the guest memory region has zero length.
141    pub fn is_empty(&self) -> bool {
142        self.len == 0
143    }
144
145    /// Returns the GPA base address of the guest memory region.
146    pub fn gpa_base(&self) -> usize {
147        self.gpa_base
148    }
149}