hyperlight_common/virtq/access.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 The Hyperlight Authors.
3
4//! Memory Access Traits for Virtqueue Operations
5//!
6//! This module defines the [`MemOps`] trait that abstracts memory access patterns
7//! required by the virtqueue implementation. This allows the virtqueue code to
8//! work with different memory backends e.g. Host vs Guest.
9
10use alloc::sync::Arc;
11
12use bytemuck::Pod;
13
14/// Backend-provided memory access for virtqueue.
15///
16/// # Safety
17///
18/// Implementations must ensure that:
19/// - Addresses accepted by these methods are translated according to the
20/// backend's memory model.
21/// - Invalid or inaccessible addresses are reported with `Self::Error` rather
22/// than causing undefined behavior.
23/// - Memory ordering guarantees are upheld as documented.
24/// - Typed reads/writes and atomic operations honor alignment and initialized
25/// memory requirements for the translated addresses.
26///
27/// [`RingProducer`]: super::RingProducer
28/// [`RingConsumer`]: super::RingConsumer
29pub unsafe trait MemOps {
30 type Error;
31
32 /// Read bytes from physical memory.
33 ///
34 /// Used for reading buffer contents pointed to by descriptors.
35 ///
36 /// # Arguments
37 ///
38 /// * `addr` - Guest physical address to read from
39 /// * `dst` - Destination buffer to fill
40 ///
41 /// Implementations must return an error if `addr` cannot be read for
42 /// at least `dst.len()` bytes.
43 fn read(&self, addr: u64, dst: &mut [u8]) -> Result<(), Self::Error>;
44
45 /// Write bytes to physical memory.
46 ///
47 /// # Arguments
48 ///
49 /// * `addr` - address to write to
50 /// * `src` - Source data to write
51 ///
52 /// Implementations must return an error if `addr` cannot be written for
53 /// at least `src.len()` bytes.
54 fn write(&self, addr: u64, src: &[u8]) -> Result<(), Self::Error>;
55
56 /// Load a u16 with acquire semantics.
57 ///
58 /// Implementations must return an error if `addr` does not translate to a
59 /// valid, aligned `AtomicU16` in shared memory.
60 fn load_acquire(&self, addr: u64) -> Result<u16, Self::Error>;
61
62 /// Store a u16 with release semantics.
63 ///
64 /// Implementations must return an error if `addr` does not translate to a
65 /// valid, aligned `AtomicU16` in shared memory.
66 fn store_release(&self, addr: u64, val: u16) -> Result<(), Self::Error>;
67
68 /// Get a direct read-only slice into shared memory.
69 ///
70 /// # Safety
71 ///
72 /// The caller must ensure:
73 /// - `addr` is valid and points to at least `len` bytes.
74 /// - The memory region is not concurrently modified for the lifetime of
75 /// the returned slice. Caller must uphold this via protocol-level
76 /// synchronisation, e.g. descriptor ownership transfer.
77 ///
78 /// See also [`BufferOwner`]: super::BufferOwner
79 unsafe fn as_slice(&self, addr: u64, len: usize) -> Result<&[u8], Self::Error>;
80
81 /// Get a direct mutable slice into shared memory.
82 ///
83 /// # Safety
84 ///
85 /// The caller must ensure:
86 /// - `addr` is valid and points to at least `len` bytes.
87 /// - No other references (shared or mutable) to this memory region exist
88 /// for the lifetime of the returned slice.
89 /// - Protocol-level synchronisation (e.g. descriptor ownership) guarantees
90 /// exclusive access.
91 #[allow(clippy::mut_from_ref)]
92 unsafe fn as_mut_slice(&self, addr: u64, len: usize) -> Result<&mut [u8], Self::Error>;
93
94 /// Read a Pod type at the given pointer.
95 ///
96 /// Implementations must return an error if `addr` is not valid, aligned,
97 /// and initialized for `T`.
98 fn read_val<T: Pod>(&self, addr: u64) -> Result<T, Self::Error> {
99 let mut val = T::zeroed();
100 let bytes = bytemuck::bytes_of_mut(&mut val);
101
102 self.read(addr, bytes)?;
103 Ok(val)
104 }
105
106 /// Write a Pod type at the given pointer.
107 ///
108 /// Implementations must return an error if `addr` is not valid and aligned
109 /// for `T`.
110 fn write_val<T: Pod>(&self, addr: u64, val: T) -> Result<(), Self::Error> {
111 let bytes = bytemuck::bytes_of(&val);
112 self.write(addr, bytes)?;
113 Ok(())
114 }
115}
116
117// SAFETY: Arc delegates all memory operations to the wrapped backend, preserving
118// that backend's MemOps contract.
119unsafe impl<T: MemOps> MemOps for Arc<T> {
120 type Error = T::Error;
121
122 fn read(&self, addr: u64, dst: &mut [u8]) -> Result<(), Self::Error> {
123 (**self).read(addr, dst)
124 }
125
126 fn write(&self, addr: u64, src: &[u8]) -> Result<(), Self::Error> {
127 (**self).write(addr, src)
128 }
129
130 fn load_acquire(&self, addr: u64) -> Result<u16, Self::Error> {
131 (**self).load_acquire(addr)
132 }
133
134 fn store_release(&self, addr: u64, val: u16) -> Result<(), Self::Error> {
135 (**self).store_release(addr, val)
136 }
137
138 unsafe fn as_slice(&self, addr: u64, len: usize) -> Result<&[u8], Self::Error> {
139 unsafe { (**self).as_slice(addr, len) }
140 }
141
142 #[allow(clippy::mut_from_ref)]
143 unsafe fn as_mut_slice(&self, addr: u64, len: usize) -> Result<&mut [u8], Self::Error> {
144 unsafe { (**self).as_mut_slice(addr, len) }
145 }
146}