Skip to main content

hyperlight_common/virtq/
desc.rs

1/*
2Copyright 2026  The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17//! Virtqueue Descriptor Types
18//!
19//! This module defines the descriptor format for packed virtqueues as specified
20//! in VIRTIO 1.1+. Each descriptor represents a memory buffer in a scatter-gather
21//! list that the device will read from or write to.
22
23use bitflags::bitflags;
24use bytemuck::{Pod, Zeroable};
25
26use super::MemOps;
27
28bitflags! {
29    /// Descriptor flags as defined by VIRTIO specification.
30    ///
31    /// Note: The implementation never follows the indirect-table interpretation,
32    /// so INDIRECT bit is effectively ignored.
33    #[repr(transparent)]
34    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
35    pub struct DescFlags: u16 {
36        /// This marks a buffer as continuing via the next field.
37        const NEXT     = 1 << 0;
38        /// This marks a buffer as device write-only (otherwise device read-only).
39        const WRITE    = 1 << 1;
40        /// This means the buffer contains a list of buffer descriptors (unsupported here).
41        const INDIRECT = 1 << 2;
42        /// Available flag for packed virtqueue wrap counter.
43        const AVAIL    = 1 << 7;
44        /// Used flag for packed virtqueue wrap counter.
45        const USED     = 1 << 15;
46    }
47}
48
49impl DescFlags {
50    /// Was a descriptor carrying these flags made available by the driver in
51    /// the round identified by `wrap`?
52    #[inline]
53    pub fn is_avail(self, wrap: bool) -> bool {
54        let avail = self.contains(DescFlags::AVAIL);
55        let used = self.contains(DescFlags::USED);
56        avail == wrap && used != wrap
57    }
58
59    /// Was a descriptor carrying these flags marked used by the device in the
60    /// round identified by `wrap`?
61    #[inline]
62    pub fn is_used(self, wrap: bool) -> bool {
63        let avail = self.contains(DescFlags::AVAIL);
64        let used = self.contains(DescFlags::USED);
65        avail == wrap && used == wrap
66    }
67}
68
69#[repr(C)]
70#[derive(Clone, Copy, Debug, Pod, Zeroable, PartialEq, Eq, Hash)]
71pub struct Descriptor {
72    /// Physical address of the buffer.
73    pub addr: u64,
74    /// Length of the buffer in bytes.
75    /// For used descriptors, this contains bytes written by device.
76    pub len: u32,
77    /// Buffer ID - used to correlate completions with submissions.
78    /// All descriptors in a chain share the same ID.
79    pub id: u16,
80    /// Flags (NEXT, WRITE, INDIRECT, AVAIL, USED).
81    pub flags: u16,
82}
83
84const _: () = assert!(core::mem::size_of::<Descriptor>() == 16);
85const _: () = assert!(Descriptor::ALIGN == 16);
86const _: () = assert!(Descriptor::ADDR_OFFSET == 0);
87const _: () = assert!(Descriptor::LEN_OFFSET == 8);
88const _: () = assert!(Descriptor::ID_OFFSET == 12);
89const _: () = assert!(Descriptor::FLAGS_OFFSET == 14);
90
91impl Descriptor {
92    // VIRTIO spec requires 16-byte alignment for descriptors
93    pub const ALIGN: usize = 16;
94    pub const SIZE: usize = core::mem::size_of::<Self>();
95
96    pub const ADDR_OFFSET: usize = core::mem::offset_of!(Self, addr);
97    pub const LEN_OFFSET: usize = core::mem::offset_of!(Self, len);
98    pub const ID_OFFSET: usize = core::mem::offset_of!(Self, id);
99    pub const FLAGS_OFFSET: usize = core::mem::offset_of!(Self, flags);
100
101    pub fn new(addr: u64, len: u32, id: u16, flags: DescFlags) -> Self {
102        Self {
103            addr,
104            len,
105            id,
106            flags: flags.bits(),
107        }
108    }
109
110    /// Get flags as a [`DescFlags`] bitfield.
111    #[inline]
112    pub fn flags(&self) -> DescFlags {
113        DescFlags::from_bits_truncate(self.flags)
114    }
115
116    /// Did the driver make this descriptor available in the current driver round?
117    #[inline]
118    pub fn is_avail(&self, wrap: bool) -> bool {
119        self.flags().is_avail(wrap)
120    }
121
122    /// Did the device mark this descriptor used in the current device round?
123    #[inline]
124    pub fn is_used(&self, wrap: bool) -> bool {
125        self.flags().is_used(wrap)
126    }
127
128    /// Is this descriptor writable by the device?
129    #[inline]
130    pub fn is_writable(&self) -> bool {
131        self.flags().contains(DescFlags::WRITE)
132    }
133
134    /// Does this descriptor point to a next descriptor in the chain?
135    #[inline]
136    pub fn is_next(&self) -> bool {
137        self.flags().contains(DescFlags::NEXT)
138    }
139
140    /// Mark descriptor as available according to the driver's wrap bit.
141    /// As per the packed-virtqueue description:
142    /// - set AVAIL bit to `driver_wrap`
143    /// - set USED bit to `!driver_wrap` (inverse)
144    #[inline]
145    pub fn mark_avail(&mut self, wrap: bool) {
146        if wrap {
147            self.flags |= DescFlags::AVAIL.bits();
148            self.flags &= !DescFlags::USED.bits();
149        } else {
150            self.flags &= !DescFlags::AVAIL.bits();
151            self.flags |= DescFlags::USED.bits();
152        }
153    }
154
155    /// Mark descriptor as used according to the device's wrap bit.
156    /// As per spec: set both USED and AVAIL bits to match device_wrap
157    #[inline]
158    pub fn mark_used(&mut self, wrap: bool) {
159        if wrap {
160            self.flags |= DescFlags::USED.bits();
161            self.flags |= DescFlags::AVAIL.bits();
162        } else {
163            self.flags &= !DescFlags::USED.bits();
164            self.flags &= !DescFlags::AVAIL.bits();
165        }
166    }
167
168    /// Write a descriptor to memory with release semantics for flags at the given base pointer
169    ///
170    /// This is the primary synchronization point for publishing descriptors.
171    ///
172    /// # Invariant
173    ///
174    /// The caller must ensure that `addr` is valid for writes of Descriptor
175    pub fn write_release<M: MemOps>(&self, mem: &M, addr: u64) -> Result<(), M::Error> {
176        mem.write_val(addr + Self::ADDR_OFFSET as u64, self.addr)?;
177        mem.write_val(addr + Self::LEN_OFFSET as u64, self.len)?;
178        mem.write_val(addr + Self::ID_OFFSET as u64, self.id)?;
179        // Flags written last with release semantics
180        mem.store_release(addr + Self::FLAGS_OFFSET as u64, self.flags)?;
181        Ok(())
182    }
183
184    /// Acquire-load only the flags word - the packed-ring publish point -
185    /// without reading the descriptor body.
186    pub fn read_flags_acquire<M: MemOps>(mem: &M, addr: u64) -> Result<DescFlags, M::Error> {
187        let flags = mem.load_acquire(addr + Self::FLAGS_OFFSET as u64)?;
188        Ok(DescFlags::from_bits_truncate(flags))
189    }
190
191    /// Read the descriptor body (`addr`/`len`/`id`) and combine it with flags
192    /// already obtained from [`read_flags_acquire`](Self::read_flags_acquire).
193    pub fn read_body<M: MemOps>(mem: &M, addr: u64, flags: DescFlags) -> Result<Self, M::Error> {
194        let addr_val: u64 = mem.read_val(addr + Self::ADDR_OFFSET as u64)?;
195        let len: u32 = mem.read_val(addr + Self::LEN_OFFSET as u64)?;
196        let id: u16 = mem.read_val(addr + Self::ID_OFFSET as u64)?;
197
198        Ok(Self {
199            addr: addr_val,
200            len,
201            id,
202            flags: flags.bits(),
203        })
204    }
205}
206
207/// A table of descriptors stored in shared memory.
208#[derive(Debug, Clone, Copy)]
209pub struct DescTable {
210    base_addr: u64,
211    len: usize,
212}
213
214impl DescTable {
215    pub const DEFAULT_LEN: usize = 256;
216
217    /// Create a descriptor table from shared memory.
218    ///
219    /// # Safety
220    ///
221    /// - `base_addr` must be valid for reads and writes of `len` descriptors
222    /// - `base_addr` must be properly aligned for `Descriptor`
223    /// - `len` must not exceed `u16::MAX`
224    /// - memory must remain valid for the lifetime of this table
225    pub unsafe fn from_raw_parts(base_addr: u64, len: usize) -> Self {
226        debug_assert!(base_addr.is_multiple_of(Descriptor::ALIGN as u64));
227        debug_assert!(len <= u16::MAX as usize);
228
229        Self { base_addr, len }
230    }
231
232    /// Get view into descriptor at index or None if idx is out of bounds
233    pub fn desc_addr(&self, idx: u16) -> Option<u64> {
234        if idx >= self.len as u16 {
235            return None;
236        }
237
238        Some(self.base_addr + (idx as u64 * Descriptor::SIZE as u64))
239    }
240
241    /// Get number of descriptors in table
242    pub fn len(&self) -> usize {
243        self.len
244    }
245
246    /// Is the descriptor table empty?
247    pub fn is_empty(&self) -> bool {
248        self.len == 0
249    }
250
251    pub const fn default_len() -> usize {
252        Self::DEFAULT_LEN
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259
260    #[test]
261    fn mark_avail_sets_bits_correctly_wrap_true() {
262        let mut d = Descriptor::zeroed();
263        d.flags = DescFlags::WRITE.bits() | DescFlags::NEXT.bits();
264        d.mark_avail(true);
265        let f = d.flags();
266        assert!(f.contains(DescFlags::AVAIL));
267        assert!(!f.contains(DescFlags::USED));
268        assert!(f.contains(DescFlags::WRITE));
269        assert!(f.contains(DescFlags::NEXT));
270    }
271
272    #[test]
273    fn mark_avail_sets_bits_correctly_wrap_false() {
274        let mut d = Descriptor::zeroed();
275        d.mark_avail(false);
276        let f = d.flags();
277        assert!(!f.contains(DescFlags::AVAIL));
278        assert!(f.contains(DescFlags::USED));
279    }
280
281    #[test]
282    fn mark_used_sets_both_bits_match_wrap_true() {
283        let mut d = Descriptor::zeroed();
284        d.mark_used(true);
285        let f = d.flags();
286        assert!(f.contains(DescFlags::AVAIL));
287        assert!(f.contains(DescFlags::USED));
288    }
289
290    #[test]
291    fn mark_used_sets_both_bits_match_wrap_false() {
292        let mut d = Descriptor::zeroed();
293        d.mark_used(false);
294        let f = d.flags();
295        assert!(!f.contains(DescFlags::AVAIL));
296        assert!(!f.contains(DescFlags::USED));
297    }
298
299    #[test]
300    fn is_avail_and_is_used() {
301        let mut d = Descriptor::zeroed();
302        d.mark_avail(true);
303        assert!(d.is_avail(true));
304        assert!(!d.is_used(true));
305        d.mark_used(true);
306        assert!(d.is_used(true));
307        assert!(!d.is_avail(true));
308        d.mark_avail(false);
309        assert!(d.is_avail(false));
310        assert!(!d.is_used(false));
311        d.mark_used(false);
312        assert!(d.is_used(false));
313        assert!(!d.is_avail(false));
314    }
315
316    #[test]
317    fn writable_and_next_helpers() {
318        let mut d = Descriptor::zeroed();
319        d.flags = (DescFlags::WRITE | DescFlags::NEXT).bits();
320        assert!(d.is_writable());
321        assert!(d.is_next());
322        d.flags = 0;
323        assert!(!d.is_writable());
324        assert!(!d.is_next());
325    }
326
327    #[test]
328    fn avail_then_used_wrap_flip_sequence() {
329        let mut d = Descriptor::zeroed();
330        d.mark_avail(true);
331        assert!(d.is_avail(true));
332        d.mark_used(false);
333        assert!(d.is_used(false));
334        assert!(!d.is_avail(false));
335        d.mark_avail(true);
336        assert!(d.is_avail(true));
337    }
338
339    #[test]
340    fn desc_table_get_out_of_bounds() {
341        // Allocate with extra space to guarantee 16-byte alignment
342        // (Descriptor requires ALIGN=16 but repr(C) only gives 8).
343        let mut buf = vec![0u8; 4 * Descriptor::SIZE + Descriptor::ALIGN];
344        let base = buf.as_mut_ptr() as usize;
345        let aligned = (base + Descriptor::ALIGN - 1) & !(Descriptor::ALIGN - 1);
346        let table = unsafe { DescTable::from_raw_parts(aligned as u64, 4) };
347        assert!(table.desc_addr(3).is_some());
348        assert!(table.desc_addr(4).is_none());
349    }
350}