Skip to main content

axvirtio_common/queue/
descriptor.rs

1use alloc::vec::Vec;
2
3use axvm_types::GuestPhysAddr;
4
5use crate::{
6    VirtioDeviceID,
7    constants::*,
8    error::{VirtioError, VirtioResult},
9    memory::GuestMemory,
10};
11
12/// VirtIO queue descriptor structure.
13///
14/// This structure represents the memory layout of a single descriptor
15/// in the descriptor table according to the VirtIO specification. It is
16/// a C-compatible data structure that directly maps to guest memory.
17///
18/// Each descriptor describes a buffer in guest memory that can be used
19/// for device I/O operations. Descriptors can be chained together using
20/// the NEXT flag to describe scatter-gather buffers.
21///
22/// This structure is used by `DescriptorTable` to read/write individual
23/// descriptors in guest memory through the guest memory accessor.
24#[repr(C)]
25#[derive(Debug, Clone, Copy)]
26pub struct VirtQueueDesc {
27    /// Address (guest-physical)
28    pub base_addr: GuestPhysAddr,
29    /// Length
30    pub len: u32,
31    /// Flags
32    pub flags: u16,
33    /// Next descriptor index (if VIRTQ_DESC_F_NEXT is set)
34    pub next: u16,
35}
36
37impl VirtQueueDesc {
38    /// Create a new descriptor
39    pub fn new(base_addr: GuestPhysAddr, len: u32, flags: u16, next: u16) -> Self {
40        Self {
41            base_addr,
42            len,
43            flags,
44            next,
45        }
46    }
47
48    /// Check if this descriptor has the NEXT flag
49    pub fn has_next(&self) -> bool {
50        (self.flags & VIRTQ_DESC_F_NEXT) != 0
51    }
52
53    /// Check if this descriptor is writable
54    pub fn is_write(&self) -> bool {
55        (self.flags & VIRTQ_DESC_F_WRITE) != 0
56    }
57
58    /// Check if this descriptor is indirect
59    pub fn is_indirect(&self) -> bool {
60        (self.flags & VIRTQ_DESC_F_INDIRECT) != 0
61    }
62
63    /// Get the guest physical address
64    pub fn guest_addr(&self) -> GuestPhysAddr {
65        self.base_addr
66    }
67
68    /// Set the next flag
69    pub fn set_next(&mut self, has_next: bool) {
70        if has_next {
71            self.flags |= VIRTQ_DESC_F_NEXT;
72        } else {
73            self.flags &= !VIRTQ_DESC_F_NEXT;
74        }
75    }
76
77    /// Set the write flag
78    pub fn set_write(&mut self, is_write: bool) {
79        if is_write {
80            self.flags |= VIRTQ_DESC_F_WRITE;
81        } else {
82            self.flags &= !VIRTQ_DESC_F_WRITE;
83        }
84    }
85
86    /// Set the write flag (alias for compatibility)
87    pub fn set_write_only(&mut self, is_write: bool) {
88        self.set_write(is_write);
89    }
90
91    /// Check if this descriptor is write-only (alias for compatibility)
92    pub fn is_write_only(&self) -> bool {
93        self.is_write()
94    }
95
96    /// Set the indirect flag
97    pub fn set_indirect(&mut self, is_indirect: bool) {
98        if is_indirect {
99            self.flags |= VIRTQ_DESC_F_INDIRECT;
100        } else {
101            self.flags &= !VIRTQ_DESC_F_INDIRECT;
102        }
103    }
104}
105
106/// A fully validated, device-agnostic VirtIO descriptor chain.
107///
108/// The common queue layer hands complete, direction-tagged chains to device
109/// implementations (block, net, ...) so that each device can interpret its own
110/// wire layout without leaking protocol specifics into the queue code.
111///
112/// Direction follows the VirtIO convention:
113/// - `readable` descriptors are device-read (driver-written, not `WRITE`).
114/// - `writable` descriptors are device-write (driver-read, `WRITE` set).
115#[derive(Debug, Clone)]
116pub struct DescriptorChain {
117    /// Head descriptor index of this chain (the value read from the avail ring).
118    head: u16,
119    /// Descriptors in chain order, starting at `head`.
120    descriptors: Vec<VirtQueueDesc>,
121}
122
123impl DescriptorChain {
124    /// Construct a chain from its head index and ordered descriptors.
125    pub fn new(head: u16, descriptors: Vec<VirtQueueDesc>) -> Self {
126        Self { head, descriptors }
127    }
128
129    /// The head descriptor index (avail-ring entry value).
130    pub fn head(&self) -> u16 {
131        self.head
132    }
133
134    /// All descriptors in chain order.
135    pub fn descriptors(&self) -> &[VirtQueueDesc] {
136        &self.descriptors
137    }
138
139    /// Number of descriptors in the chain.
140    pub fn len(&self) -> usize {
141        self.descriptors.len()
142    }
143
144    /// Whether the chain is empty.
145    pub fn is_empty(&self) -> bool {
146        self.descriptors.is_empty()
147    }
148
149    /// Device-readable descriptors (driver-written, no `VIRTQ_DESC_F_WRITE`).
150    pub fn readable(&self) -> impl Iterator<Item = &VirtQueueDesc> {
151        self.descriptors.iter().filter(|d| !d.is_write())
152    }
153
154    /// Device-writable descriptors (`VIRTQ_DESC_F_WRITE` set).
155    pub fn writable(&self) -> impl Iterator<Item = &VirtQueueDesc> {
156        self.descriptors.iter().filter(|d| d.is_write())
157    }
158
159    /// Total bytes across device-readable descriptors, checked against overflow.
160    pub fn readable_len(&self) -> VirtioResult<usize> {
161        sum_descriptor_lens(self.readable().map(|d| d.len as usize))
162    }
163
164    /// Total bytes across device-writable descriptors, checked against overflow.
165    pub fn writable_len(&self) -> VirtioResult<usize> {
166        sum_descriptor_lens(self.writable().map(|d| d.len as usize))
167    }
168}
169
170/// Checked sum of descriptor lengths; fails on overflow rather than wrapping.
171fn sum_descriptor_lens(lens: impl Iterator<Item = usize>) -> VirtioResult<usize> {
172    let mut total = 0usize;
173    for v in lens {
174        total = total.checked_add(v).ok_or(VirtioError::InvalidDescriptor)?;
175    }
176    Ok(total)
177}
178
179/// Descriptor table management structure.
180///
181/// This structure provides a high-level interface for managing the VirtIO
182/// descriptor table in guest memory. It wraps the guest memory accessor and
183/// provides methods to read/write individual descriptors and follow descriptor
184/// chains.
185///
186/// Relationship with VirtQueueDesc:
187/// - VirtQueueDesc defines the memory layout of a single descriptor
188/// - DescriptorTable uses VirtQueueDesc to access descriptors in guest memory
189/// - DescriptorTable manages the entire descriptor table and provides operations
190///   for descriptor chains, validation, and buffer management
191///
192/// Memory Layout:
193/// ```text
194/// base_addr -> +-------------------+
195///              | VirtQueueDesc[0]  |  (addr + len + flags + next)
196///              +-------------------+
197///              | VirtQueueDesc[1]  |  (addr + len + flags + next)
198///              +-------------------+
199///              | ...               |
200///              +-------------------+
201///              | VirtQueueDesc[n-1]|  (addr + len + flags + next)
202///              +-------------------+
203/// ```
204///
205/// Descriptor chains are formed by setting the NEXT flag and the next field
206/// to link descriptors together, allowing scatter-gather I/O operations.
207#[derive(Debug, Clone)]
208pub struct DescriptorTable {
209    /// Base address of the descriptor table
210    pub base_addr: GuestPhysAddr,
211    /// Number of descriptors
212    pub size: u16,
213}
214
215impl DescriptorTable {
216    /// Create a new descriptor table
217    pub const fn new(base_addr: GuestPhysAddr, size: u16) -> Self {
218        Self { base_addr, size }
219    }
220
221    /// Get the address of a specific descriptor
222    pub fn desc_addr(&self, index: u16) -> Option<GuestPhysAddr> {
223        if index >= self.size {
224            return None;
225        }
226
227        let offset = index as usize * core::mem::size_of::<VirtQueueDesc>();
228        Some(self.base_addr + offset)
229    }
230
231    /// Size in bytes of the descriptor table for a queue of `size` descriptors.
232    ///
233    /// Owns the `size * sizeof(VirtQueueDesc)` math so ring-region derivation
234    /// (`VirtioQueue::ring_regions`) cannot drift from the per-descriptor
235    /// address computation.
236    pub(crate) const fn layout_size(size: u16) -> usize {
237        size as usize * core::mem::size_of::<VirtQueueDesc>()
238    }
239
240    /// Calculate the total size of the descriptor table
241    pub fn total_size(&self) -> usize {
242        Self::layout_size(self.size)
243    }
244
245    /// Check if the descriptor table is valid
246    pub fn is_valid(&self) -> bool {
247        self.base_addr.as_usize() != 0 && self.size > 0
248    }
249
250    /// Read a descriptor from the table
251    pub fn read_desc(
252        &self,
253        index: u16,
254        memory: &mut dyn GuestMemory,
255    ) -> VirtioResult<VirtQueueDesc> {
256        if !self.is_valid() {
257            return Err(VirtioError::QueueNotReady);
258        }
259
260        let desc_addr = self.desc_addr(index).ok_or(VirtioError::InvalidQueue)?;
261
262        let mut bytes = [0u8; 16];
263        memory.read(desc_addr, &mut bytes)?;
264        Ok(VirtQueueDesc {
265            base_addr: GuestPhysAddr::from(
266                u64::from_le_bytes(bytes[0..8].try_into().unwrap()) as usize
267            ),
268            len: u32::from_le_bytes(bytes[8..12].try_into().unwrap()),
269            flags: u16::from_le_bytes(bytes[12..14].try_into().unwrap()),
270            next: u16::from_le_bytes(bytes[14..16].try_into().unwrap()),
271        })
272    }
273
274    /// Write a descriptor to the table
275    pub fn write_desc(
276        &self,
277        index: u16,
278        desc: &VirtQueueDesc,
279        memory: &mut dyn GuestMemory,
280    ) -> VirtioResult<()> {
281        if !self.is_valid() {
282            return Err(VirtioError::QueueNotReady);
283        }
284
285        let desc_addr = self.desc_addr(index).ok_or(VirtioError::InvalidQueue)?;
286
287        let mut bytes = [0u8; 16];
288        bytes[0..8].copy_from_slice(&(desc.base_addr.as_usize() as u64).to_le_bytes());
289        bytes[8..12].copy_from_slice(&desc.len.to_le_bytes());
290        bytes[12..14].copy_from_slice(&desc.flags.to_le_bytes());
291        bytes[14..16].copy_from_slice(&desc.next.to_le_bytes());
292        memory.write(desc_addr, &bytes)?;
293
294        Ok(())
295    }
296
297    /// Follow a descriptor chain starting from the given index
298    pub fn follow_chain(
299        &self,
300        head_index: u16,
301        memory: &mut dyn GuestMemory,
302    ) -> VirtioResult<Vec<VirtQueueDesc>> {
303        if !self.is_valid() {
304            return Err(VirtioError::QueueNotReady);
305        }
306
307        let mut descriptors = Vec::new();
308        let mut current_index = head_index;
309
310        loop {
311            if current_index >= self.size {
312                return Err(VirtioError::InvalidQueue);
313            }
314
315            let desc = self.read_desc(current_index, memory)?;
316            descriptors.push(desc);
317
318            if !desc.has_next() {
319                break;
320            }
321
322            current_index = desc.next;
323
324            // Prevent infinite loops
325            if descriptors.len() > self.size as usize {
326                return Err(VirtioError::InvalidQueue);
327            }
328        }
329
330        Ok(descriptors)
331    }
332
333    /// Build a fully validated, device-agnostic [`DescriptorChain`] from a head
334    /// index.
335    ///
336    /// Validation performed (all guest-provided input is untrusted):
337    /// - `head` and every `next` index must be `< size`.
338    /// - `VIRTQ_DESC_F_INDIRECT` is rejected (indirect descriptors are not
339    ///   negotiated in the first version).
340    /// - `base_addr + len` must not overflow.
341    /// - The chain may reference at most `size` descriptors; a longer walk
342    ///   indicates a cycle or a corrupted `next` field.
343    pub fn descriptor_chain(
344        &self,
345        head: u16,
346        memory: &mut dyn GuestMemory,
347    ) -> VirtioResult<DescriptorChain> {
348        if !self.is_valid() {
349            return Err(VirtioError::QueueNotReady);
350        }
351        if head >= self.size {
352            return Err(VirtioError::InvalidDescriptor);
353        }
354
355        let mut descriptors = Vec::new();
356        let mut current = head;
357        loop {
358            if current >= self.size {
359                return Err(VirtioError::InvalidDescriptor);
360            }
361            let desc = self.read_desc(current, memory)?;
362            if desc.is_indirect() {
363                return Err(VirtioError::NotSupported);
364            }
365            if desc
366                .base_addr
367                .as_usize()
368                .checked_add(desc.len as usize)
369                .is_none()
370            {
371                return Err(VirtioError::InvalidDescriptor);
372            }
373            descriptors.push(desc);
374            if !desc.has_next() {
375                break;
376            }
377            current = desc.next;
378            // A chain referencing more than `size` descriptors is a cycle or
379            // corruption. Bounding the walk also guarantees termination.
380            if descriptors.len() > self.size as usize {
381                return Err(VirtioError::InvalidDescriptor);
382            }
383        }
384
385        Ok(DescriptorChain::new(head, descriptors))
386    }
387
388    /// Get the total length of a descriptor chain
389    pub fn chain_length(&self, head_index: u16, memory: &mut dyn GuestMemory) -> VirtioResult<u32> {
390        let descriptors = self.follow_chain(head_index, memory)?;
391        Ok(descriptors.iter().map(|desc| desc.len).sum())
392    }
393
394    /// Check if a descriptor chain is valid
395    pub fn validate_chain(
396        &self,
397        head_index: u16,
398        memory: &mut dyn GuestMemory,
399    ) -> VirtioResult<bool> {
400        let descriptors = self.follow_chain(head_index, memory)?;
401
402        // Basic validation: at least one descriptor
403        if descriptors.is_empty() {
404            return Ok(false);
405        }
406
407        // Check for proper flag usage
408        for (i, desc) in descriptors.iter().enumerate() {
409            // Last descriptor should not have NEXT flag
410            if i == descriptors.len() - 1 && desc.has_next() {
411                return Ok(false);
412            }
413
414            // Non-last descriptors should have NEXT flag
415            if i < descriptors.len() - 1 && !desc.has_next() {
416                return Ok(false);
417            }
418        }
419
420        Ok(true)
421    }
422
423    /// Get data buffer descriptors (excluding first and last)
424    pub fn get_data_buffers(
425        &self,
426        head_index: u16,
427        device_type: VirtioDeviceID,
428        memory: &mut dyn GuestMemory,
429    ) -> VirtioResult<Vec<(GuestPhysAddr, usize, bool)>> {
430        let descriptors = self.follow_chain(head_index, memory)?;
431
432        if descriptors.len() < 2 && device_type == VirtioDeviceID::Block {
433            return Ok(Vec::new());
434        }
435
436        let mut buffers = Vec::new();
437        if device_type == VirtioDeviceID::Block {
438            for desc in &descriptors[1..descriptors.len() - 1] {
439                buffers.push((desc.base_addr, desc.len as usize, desc.is_write()));
440            }
441        } else {
442            for desc in &descriptors {
443                buffers.push((desc.base_addr, desc.len as usize, desc.is_write()));
444            }
445        }
446
447        Ok(buffers)
448    }
449
450    /// Get the status descriptor address (last descriptor)
451    pub fn get_status_addr(
452        &self,
453        head_index: u16,
454        memory: &mut dyn GuestMemory,
455    ) -> VirtioResult<GuestPhysAddr> {
456        let descriptors = self.follow_chain(head_index, memory)?;
457
458        if descriptors.is_empty() {
459            return Err(VirtioError::InvalidQueue);
460        }
461
462        let status_desc = &descriptors[descriptors.len() - 1];
463        // The status descriptor must be writable and at least 1 byte long
464        if !status_desc.is_write() || status_desc.len < 1 {
465            return Err(VirtioError::InvalidQueue);
466        }
467
468        Ok(status_desc.base_addr)
469    }
470}
471
472#[cfg(test)]
473mod tests {
474    use alloc::vec;
475
476    use ax_memory_addr::PhysAddr;
477    use axaddrspace::GuestMemoryAccessor;
478
479    use super::*;
480
481    #[derive(Clone)]
482    struct TestTranslator {
483        base_host_ptr: usize,
484    }
485
486    impl GuestMemoryAccessor for TestTranslator {
487        fn translate_and_get_limit(&self, guest_addr: GuestPhysAddr) -> Option<(PhysAddr, usize)> {
488            let offset = guest_addr.as_usize();
489            Some((PhysAddr::from(self.base_host_ptr + offset), usize::MAX))
490        }
491    }
492
493    #[test]
494    fn status_descriptor_len_must_be_at_least_one() {
495        // Allocate a backing buffer to simulate host memory
496        let mut mem = vec![0u8; 4096];
497        let base_ptr = mem.as_mut_ptr() as usize;
498        let translator = TestTranslator {
499            base_host_ptr: base_ptr,
500        };
501        let mut memory = crate::AddressSpaceMemory::new(&translator);
502
503        // Create a descriptor table at a non-zero guest base within our backing buffer
504        let base = GuestPhysAddr::from(0x10usize);
505        let table = DescriptorTable::new(base, 2);
506
507        // Build a 2-descriptor chain: desc0 -> desc1
508        let mut d0 = VirtQueueDesc::new(GuestPhysAddr::from(0x100usize), 16, 0, 1);
509        d0.set_next(true);
510        let mut d1 = VirtQueueDesc::new(GuestPhysAddr::from(0x200usize), 0, 0, 0);
511        d1.set_write(true); // status descriptor must be write-only for device
512        d1.set_next(false);
513
514        table.write_desc(0, &d0, &mut memory).unwrap();
515        table.write_desc(1, &d1, &mut memory).unwrap();
516
517        // len == 0 should be invalid
518        let err = table.get_status_addr(0, &mut memory).unwrap_err();
519        assert!(matches!(err, VirtioError::InvalidQueue));
520
521        // Fix len to 1, now it should pass
522        let mut d1_ok = d1;
523        d1_ok.len = 1;
524        table.write_desc(1, &d1_ok, &mut memory).unwrap();
525        let ok_addr = table.get_status_addr(0, &mut memory).unwrap();
526        assert_eq!(ok_addr.as_usize(), 0x200);
527    }
528
529    #[test]
530    fn layout_size_counts_descriptors() {
531        // 4 descriptors * 16 bytes (VirtQueueDesc) = 64.
532        assert_eq!(DescriptorTable::layout_size(4), 64);
533        // Boundary: the largest queue size still counts every descriptor.
534        assert_eq!(DescriptorTable::layout_size(256), 4096);
535    }
536}