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}