Skip to main content

arcbox_virtio_core/
queue_guest.rs

1//! VirtQueue operations over guest physical memory.
2//!
3//! When running with a manual-execution hypervisor (KVM, Hypervisor.framework),
4//! VirtIO queues live in guest RAM. This module provides zero-copy access to
5//! descriptor tables, available rings, and used rings via direct host pointer
6//! arithmetic.
7
8use std::sync::atomic::{Ordering, fence};
9
10/// A VirtIO split virtqueue backed by guest physical memory.
11///
12/// The queue addresses (desc, avail, used) are guest physical addresses
13/// set by the guest driver during device initialization. The `ram_base`
14/// pointer is the host virtual address corresponding to GPA 0.
15pub struct GuestMemoryVirtQueue {
16    /// Queue index within the device.
17    queue_idx: u16,
18    /// Queue size (number of descriptors).
19    size: u16,
20    /// GPA of the descriptor table.
21    desc_gpa: u64,
22    /// GPA of the available ring.
23    avail_gpa: u64,
24    /// GPA of the used ring.
25    used_gpa: u64,
26    /// Host base pointer (GPA 0 maps here).
27    ram_base: *mut u8,
28    /// Total guest RAM size (for bounds checking).
29    ram_size: usize,
30    /// Last processed available ring index.
31    last_avail_idx: u16,
32    /// Current used ring index.
33    used_idx: u16,
34    /// Whether event index feature is negotiated.
35    event_idx: bool,
36}
37
38// SAFETY: The ram_base pointer is valid for the lifetime of the VM and
39// synchronized through the vCPU exit/enter barrier. Only one thread
40// processes a given queue at a time (the vCPU thread that received
41// the QUEUE_NOTIFY exit).
42unsafe impl Send for GuestMemoryVirtQueue {}
43unsafe impl Sync for GuestMemoryVirtQueue {}
44
45/// A descriptor from the virtqueue descriptor table.
46#[derive(Debug, Clone, Copy)]
47pub struct VirtqDesc {
48    /// Guest physical address of the buffer.
49    pub addr: u64,
50    /// Length of the buffer.
51    pub len: u32,
52    /// Descriptor flags (NEXT, WRITE, INDIRECT).
53    pub flags: u16,
54    /// Next descriptor index (if NEXT flag set).
55    pub next: u16,
56}
57
58impl VirtqDesc {
59    /// Whether this descriptor is writable by the device.
60    pub fn is_write(&self) -> bool {
61        self.flags & crate::queue::flags::WRITE != 0
62    }
63
64    /// Whether there is a next descriptor in the chain.
65    pub fn has_next(&self) -> bool {
66        self.flags & crate::queue::flags::NEXT != 0
67    }
68}
69
70/// A chain of descriptors representing a single I/O request.
71pub struct DescriptorChain {
72    /// Index of the head descriptor.
73    pub head_idx: u16,
74    /// All descriptors in the chain.
75    pub descriptors: Vec<VirtqDesc>,
76}
77
78impl GuestMemoryVirtQueue {
79    /// Creates a new guest memory virtqueue.
80    ///
81    /// # Safety
82    ///
83    /// `ram_base` must point to a valid allocation of at least `ram_size` bytes
84    /// that remains valid for the lifetime of this queue.
85    pub unsafe fn new(
86        queue_idx: u16,
87        size: u16,
88        desc_gpa: u64,
89        avail_gpa: u64,
90        used_gpa: u64,
91        ram_base: *mut u8,
92        ram_size: usize,
93    ) -> Self {
94        Self {
95            queue_idx,
96            size,
97            desc_gpa,
98            avail_gpa,
99            used_gpa,
100            ram_base,
101            ram_size,
102            last_avail_idx: 0,
103            used_idx: 0,
104            event_idx: false,
105        }
106    }
107
108    /// Enables event index feature (VIRTIO_F_EVENT_IDX).
109    pub fn set_event_idx(&mut self, enabled: bool) {
110        self.event_idx = enabled;
111    }
112
113    /// Returns the queue index.
114    pub fn queue_idx(&self) -> u16 {
115        self.queue_idx
116    }
117
118    /// Translates a GPA to a host pointer. Returns None if out of bounds.
119    fn gpa_to_ptr(&self, gpa: u64) -> Option<*mut u8> {
120        let offset = gpa as usize;
121        if offset < self.ram_size {
122            Some(unsafe { self.ram_base.add(offset) })
123        } else {
124            None
125        }
126    }
127
128    /// Reads a u16 from guest memory at the given GPA.
129    fn read_u16(&self, gpa: u64) -> Option<u16> {
130        let ptr = self.gpa_to_ptr(gpa)?;
131        if gpa as usize + 2 > self.ram_size {
132            return None;
133        }
134        // SAFETY: bounds checked above, alignment not required for MMIO memory.
135        Some(unsafe { (ptr as *const u16).read_unaligned() })
136    }
137
138    /// Reads a u32 from guest memory.
139    fn read_u32(&self, gpa: u64) -> Option<u32> {
140        let ptr = self.gpa_to_ptr(gpa)?;
141        if gpa as usize + 4 > self.ram_size {
142            return None;
143        }
144        Some(unsafe { (ptr as *const u32).read_unaligned() })
145    }
146
147    /// Reads a u64 from guest memory.
148    fn read_u64(&self, gpa: u64) -> Option<u64> {
149        let ptr = self.gpa_to_ptr(gpa)?;
150        if gpa as usize + 8 > self.ram_size {
151            return None;
152        }
153        Some(unsafe { (ptr as *const u64).read_unaligned() })
154    }
155
156    /// Writes a u16 to guest memory.
157    fn write_u16(&self, gpa: u64, val: u16) -> bool {
158        if let Some(ptr) = self.gpa_to_ptr(gpa) {
159            if gpa as usize + 2 <= self.ram_size {
160                unsafe { (ptr as *mut u16).write_unaligned(val) };
161                return true;
162            }
163        }
164        false
165    }
166
167    /// Writes a u32 to guest memory.
168    fn write_u32(&self, gpa: u64, val: u32) -> bool {
169        if let Some(ptr) = self.gpa_to_ptr(gpa) {
170            if gpa as usize + 4 <= self.ram_size {
171                unsafe { (ptr as *mut u32).write_unaligned(val) };
172                return true;
173            }
174        }
175        false
176    }
177
178    /// Reads a descriptor from the descriptor table.
179    fn read_descriptor(&self, idx: u16) -> Option<VirtqDesc> {
180        if idx >= self.size {
181            return None;
182        }
183        // Each descriptor is 16 bytes: addr(8) + len(4) + flags(2) + next(2)
184        let desc_offset = self.desc_gpa + u64::from(idx) * 16;
185        Some(VirtqDesc {
186            addr: self.read_u64(desc_offset)?,
187            len: self.read_u32(desc_offset + 8)?,
188            flags: self.read_u16(desc_offset + 12)?,
189            next: self.read_u16(desc_offset + 14)?,
190        })
191    }
192
193    /// Reads the available ring index.
194    fn avail_idx(&self) -> u16 {
195        // avail ring layout: flags(2) + idx(2) + ring[size](2*size) + used_event(2)
196        self.read_u16(self.avail_gpa + 2).unwrap_or(0)
197    }
198
199    /// Reads the available ring entry at position `pos`.
200    fn avail_ring_entry(&self, pos: u16) -> u16 {
201        let offset = self.avail_gpa + 4 + u64::from(pos % self.size) * 2;
202        self.read_u16(offset).unwrap_or(0)
203    }
204
205    /// Returns whether there are available descriptors to process.
206    pub fn has_avail(&self) -> bool {
207        fence(Ordering::Acquire);
208        self.avail_idx() != self.last_avail_idx
209    }
210
211    /// Pops the next available descriptor chain.
212    pub fn pop_avail(&mut self) -> Option<DescriptorChain> {
213        fence(Ordering::Acquire);
214
215        let avail_idx = self.avail_idx();
216        if avail_idx == self.last_avail_idx {
217            return None;
218        }
219
220        let head_idx = self.avail_ring_entry(self.last_avail_idx);
221        self.last_avail_idx = self.last_avail_idx.wrapping_add(1);
222
223        // Walk the descriptor chain
224        let mut descriptors = Vec::new();
225        let mut idx = head_idx;
226        let mut count = 0u16;
227
228        loop {
229            if count >= self.size {
230                tracing::warn!("Descriptor chain loop detected in queue {}", self.queue_idx);
231                break;
232            }
233
234            let desc = self.read_descriptor(idx)?;
235            descriptors.push(desc);
236            count += 1;
237
238            if !desc.has_next() {
239                break;
240            }
241            idx = desc.next;
242        }
243
244        Some(DescriptorChain {
245            head_idx,
246            descriptors,
247        })
248    }
249
250    /// Pushes a used buffer notification.
251    pub fn push_used(&mut self, head_idx: u16, len: u32) {
252        // used ring layout: flags(2) + idx(2) + ring[size](id(4)+len(4)*size) + avail_event(2)
253        let used_ring_offset = self.used_gpa + 4 + u64::from(self.used_idx % self.size) * 8;
254        self.write_u32(used_ring_offset, u32::from(head_idx));
255        self.write_u32(used_ring_offset + 4, len);
256
257        self.used_idx = self.used_idx.wrapping_add(1);
258
259        fence(Ordering::Release);
260        // Write the used index
261        self.write_u16(self.used_gpa + 2, self.used_idx);
262    }
263
264    /// Pushes multiple used buffers with a single index update.
265    pub fn push_used_batch(&mut self, completions: &[(u16, u32)]) {
266        for &(head_idx, len) in completions {
267            let used_ring_offset = self.used_gpa + 4 + u64::from(self.used_idx % self.size) * 8;
268            self.write_u32(used_ring_offset, u32::from(head_idx));
269            self.write_u32(used_ring_offset + 4, len);
270            self.used_idx = self.used_idx.wrapping_add(1);
271        }
272
273        fence(Ordering::Release);
274        self.write_u16(self.used_gpa + 2, self.used_idx);
275    }
276
277    /// Reads bytes from a guest buffer into a host Vec.
278    pub fn read_buffer(&self, gpa: u64, len: u32) -> Option<Vec<u8>> {
279        let len = len as usize;
280        let ptr = self.gpa_to_ptr(gpa)?;
281        if gpa as usize + len > self.ram_size {
282            return None;
283        }
284        let mut buf = vec![0u8; len];
285        unsafe {
286            std::ptr::copy_nonoverlapping(ptr, buf.as_mut_ptr(), len);
287        }
288        Some(buf)
289    }
290
291    /// Writes bytes from a host buffer into guest memory.
292    pub fn write_buffer(&self, gpa: u64, data: &[u8]) -> bool {
293        if let Some(ptr) = self.gpa_to_ptr(gpa) {
294            if gpa as usize + data.len() <= self.ram_size {
295                unsafe {
296                    std::ptr::copy_nonoverlapping(data.as_ptr(), ptr, data.len());
297                }
298                return true;
299            }
300        }
301        false
302    }
303
304    /// Returns a raw host pointer for zero-copy access to a guest buffer.
305    ///
306    /// # Safety
307    ///
308    /// The caller must ensure no concurrent modifications to the same guest
309    /// memory region and that the returned pointer is not used after the
310    /// queue (and its backing RAM) is dropped.
311    pub unsafe fn guest_slice(&self, gpa: u64, len: usize) -> Option<&[u8]> {
312        let ptr = self.gpa_to_ptr(gpa)?;
313        if gpa as usize + len > self.ram_size {
314            return None;
315        }
316        // SAFETY: bounds checked above, caller guarantees no concurrent writes.
317        Some(unsafe { std::slice::from_raw_parts(ptr, len) })
318    }
319
320    /// Returns a mutable raw host pointer for zero-copy write access.
321    ///
322    /// # Safety
323    ///
324    /// Same requirements as `guest_slice`, plus exclusive access guarantee.
325    pub unsafe fn guest_slice_mut(&mut self, gpa: u64, len: usize) -> Option<&mut [u8]> {
326        let ptr = self.gpa_to_ptr(gpa)?;
327        if gpa as usize + len > self.ram_size {
328            return None;
329        }
330        // SAFETY: bounds checked above, caller guarantees exclusive access.
331        Some(unsafe { std::slice::from_raw_parts_mut(ptr, len) })
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338    use std::alloc::{Layout, alloc_zeroed, dealloc};
339
340    /// Size of our fake guest RAM for tests (64 KiB).
341    const TEST_RAM_SIZE: usize = 64 * 1024;
342    /// Queue size used in tests.
343    const TEST_QUEUE_SIZE: u16 = 16;
344
345    // Layout constants for the virtqueue structures within the test RAM.
346    // Descriptor table starts at GPA 0x1000.
347    const DESC_TABLE_GPA: u64 = 0x1000;
348    // Available ring starts at GPA 0x2000.
349    const AVAIL_RING_GPA: u64 = 0x2000;
350    // Used ring starts at GPA 0x3000.
351    const USED_RING_GPA: u64 = 0x3000;
352    // Data buffer region starts at GPA 0x4000.
353    const DATA_BUFFER_GPA: u64 = 0x4000;
354
355    /// RAII wrapper for test guest memory allocation.
356    struct TestGuestMemory {
357        ptr: *mut u8,
358        layout: Layout,
359    }
360
361    impl TestGuestMemory {
362        fn new() -> Self {
363            let layout = Layout::from_size_align(TEST_RAM_SIZE, 4096).unwrap();
364            let ptr = unsafe { alloc_zeroed(layout) };
365            assert!(!ptr.is_null(), "Failed to allocate test guest memory");
366            Self { ptr, layout }
367        }
368
369        fn as_mut_ptr(&self) -> *mut u8 {
370            self.ptr
371        }
372
373        /// Writes a u16 at the given GPA offset within the test RAM.
374        fn write_u16(&self, gpa: u64, val: u16) {
375            let offset = gpa as usize;
376            assert!(offset + 2 <= TEST_RAM_SIZE);
377            unsafe {
378                (self.ptr.add(offset) as *mut u16).write_unaligned(val);
379            }
380        }
381
382        /// Writes a u32 at the given GPA offset.
383        fn write_u32(&self, gpa: u64, val: u32) {
384            let offset = gpa as usize;
385            assert!(offset + 4 <= TEST_RAM_SIZE);
386            unsafe {
387                (self.ptr.add(offset) as *mut u32).write_unaligned(val);
388            }
389        }
390
391        /// Writes a u64 at the given GPA offset.
392        fn write_u64(&self, gpa: u64, val: u64) {
393            let offset = gpa as usize;
394            assert!(offset + 8 <= TEST_RAM_SIZE);
395            unsafe {
396                (self.ptr.add(offset) as *mut u64).write_unaligned(val);
397            }
398        }
399
400        /// Reads a u16 from the given GPA offset.
401        fn read_u16(&self, gpa: u64) -> u16 {
402            let offset = gpa as usize;
403            assert!(offset + 2 <= TEST_RAM_SIZE);
404            unsafe { (self.ptr.add(offset) as *const u16).read_unaligned() }
405        }
406
407        /// Reads a u32 from the given GPA offset.
408        fn read_u32(&self, gpa: u64) -> u32 {
409            let offset = gpa as usize;
410            assert!(offset + 4 <= TEST_RAM_SIZE);
411            unsafe { (self.ptr.add(offset) as *const u32).read_unaligned() }
412        }
413
414        /// Writes a descriptor into the descriptor table at the given index.
415        fn write_descriptor(&self, idx: u16, addr: u64, len: u32, flags: u16, next: u16) {
416            let base = DESC_TABLE_GPA + u64::from(idx) * 16;
417            self.write_u64(base, addr);
418            self.write_u32(base + 8, len);
419            self.write_u16(base + 12, flags);
420            self.write_u16(base + 14, next);
421        }
422
423        /// Sets the available ring index.
424        fn set_avail_idx(&self, idx: u16) {
425            // avail ring: flags(2) + idx(2)
426            self.write_u16(AVAIL_RING_GPA + 2, idx);
427        }
428
429        /// Sets an entry in the available ring.
430        fn set_avail_ring_entry(&self, pos: u16, desc_idx: u16) {
431            let offset = AVAIL_RING_GPA + 4 + u64::from(pos % TEST_QUEUE_SIZE) * 2;
432            self.write_u16(offset, desc_idx);
433        }
434
435        /// Writes data bytes at a GPA.
436        fn write_bytes(&self, gpa: u64, data: &[u8]) {
437            let offset = gpa as usize;
438            assert!(offset + data.len() <= TEST_RAM_SIZE);
439            unsafe {
440                std::ptr::copy_nonoverlapping(data.as_ptr(), self.ptr.add(offset), data.len());
441            }
442        }
443
444        /// Reads data bytes from a GPA.
445        fn read_bytes(&self, gpa: u64, len: usize) -> Vec<u8> {
446            let offset = gpa as usize;
447            assert!(offset + len <= TEST_RAM_SIZE);
448            let mut buf = vec![0u8; len];
449            unsafe {
450                std::ptr::copy_nonoverlapping(self.ptr.add(offset), buf.as_mut_ptr(), len);
451            }
452            buf
453        }
454    }
455
456    impl Drop for TestGuestMemory {
457        fn drop(&mut self) {
458            unsafe {
459                dealloc(self.ptr, self.layout);
460            }
461        }
462    }
463
464    /// Creates a GuestMemoryVirtQueue backed by the test memory.
465    fn create_test_queue(mem: &TestGuestMemory) -> GuestMemoryVirtQueue {
466        unsafe {
467            GuestMemoryVirtQueue::new(
468                0,
469                TEST_QUEUE_SIZE,
470                DESC_TABLE_GPA,
471                AVAIL_RING_GPA,
472                USED_RING_GPA,
473                mem.as_mut_ptr(),
474                TEST_RAM_SIZE,
475            )
476        }
477    }
478
479    // ======================================================================
480    // Descriptor chain walking
481    // ======================================================================
482
483    #[test]
484    fn test_single_descriptor_chain() {
485        let mem = TestGuestMemory::new();
486
487        // Write a single descriptor (no NEXT flag)
488        mem.write_descriptor(0, DATA_BUFFER_GPA, 256, 0, 0);
489
490        // Make it available
491        mem.set_avail_ring_entry(0, 0);
492        mem.set_avail_idx(1);
493
494        let mut queue = create_test_queue(&mem);
495
496        assert!(queue.has_avail());
497        let chain = queue.pop_avail().unwrap();
498        assert_eq!(chain.head_idx, 0);
499        assert_eq!(chain.descriptors.len(), 1);
500        assert_eq!(chain.descriptors[0].addr, DATA_BUFFER_GPA);
501        assert_eq!(chain.descriptors[0].len, 256);
502        assert!(!chain.descriptors[0].has_next());
503    }
504
505    #[test]
506    fn test_chained_descriptors() {
507        let mem = TestGuestMemory::new();
508
509        // Chain: desc 0 -> desc 1 -> desc 2
510        let next_flag = crate::queue::flags::NEXT;
511        mem.write_descriptor(0, DATA_BUFFER_GPA, 128, next_flag, 1);
512        mem.write_descriptor(1, DATA_BUFFER_GPA + 128, 256, next_flag, 2);
513        mem.write_descriptor(2, DATA_BUFFER_GPA + 384, 512, 0, 0);
514
515        mem.set_avail_ring_entry(0, 0);
516        mem.set_avail_idx(1);
517
518        let mut queue = create_test_queue(&mem);
519        let chain = queue.pop_avail().unwrap();
520
521        assert_eq!(chain.head_idx, 0);
522        assert_eq!(chain.descriptors.len(), 3);
523        assert_eq!(chain.descriptors[0].addr, DATA_BUFFER_GPA);
524        assert_eq!(chain.descriptors[0].len, 128);
525        assert!(chain.descriptors[0].has_next());
526        assert_eq!(chain.descriptors[1].addr, DATA_BUFFER_GPA + 128);
527        assert_eq!(chain.descriptors[1].len, 256);
528        assert!(chain.descriptors[1].has_next());
529        assert_eq!(chain.descriptors[2].addr, DATA_BUFFER_GPA + 384);
530        assert_eq!(chain.descriptors[2].len, 512);
531        assert!(!chain.descriptors[2].has_next());
532    }
533
534    #[test]
535    fn test_write_descriptor_flag() {
536        let mem = TestGuestMemory::new();
537
538        // Descriptor with WRITE flag (device-writable buffer)
539        let write_flag = crate::queue::flags::WRITE;
540        mem.write_descriptor(0, DATA_BUFFER_GPA, 1024, write_flag, 0);
541
542        mem.set_avail_ring_entry(0, 0);
543        mem.set_avail_idx(1);
544
545        let mut queue = create_test_queue(&mem);
546        let chain = queue.pop_avail().unwrap();
547
548        assert!(chain.descriptors[0].is_write());
549        assert!(!chain.descriptors[0].has_next());
550    }
551
552    // ======================================================================
553    // pop_avail / push_used ring operations
554    // ======================================================================
555
556    #[test]
557    fn test_pop_avail_empty() {
558        let mem = TestGuestMemory::new();
559        let mut queue = create_test_queue(&mem);
560
561        // No descriptors available
562        assert!(!queue.has_avail());
563        assert!(queue.pop_avail().is_none());
564    }
565
566    #[test]
567    fn test_pop_avail_multiple() {
568        let mem = TestGuestMemory::new();
569
570        // Set up 3 independent descriptors
571        mem.write_descriptor(0, DATA_BUFFER_GPA, 100, 0, 0);
572        mem.write_descriptor(1, DATA_BUFFER_GPA + 0x100, 200, 0, 0);
573        mem.write_descriptor(2, DATA_BUFFER_GPA + 0x200, 300, 0, 0);
574
575        mem.set_avail_ring_entry(0, 0);
576        mem.set_avail_ring_entry(1, 1);
577        mem.set_avail_ring_entry(2, 2);
578        mem.set_avail_idx(3);
579
580        let mut queue = create_test_queue(&mem);
581
582        // Pop all three
583        for i in 0..3 {
584            assert!(queue.has_avail());
585            let chain = queue.pop_avail().unwrap();
586            assert_eq!(chain.head_idx, i);
587            assert_eq!(chain.descriptors.len(), 1);
588        }
589
590        // No more available
591        assert!(!queue.has_avail());
592        assert!(queue.pop_avail().is_none());
593    }
594
595    #[test]
596    fn test_push_used_single() {
597        let mem = TestGuestMemory::new();
598        let mut queue = create_test_queue(&mem);
599
600        queue.push_used(5, 1024);
601
602        // Verify used ring: used index should be 1
603        let used_idx = mem.read_u16(USED_RING_GPA + 2);
604        assert_eq!(used_idx, 1);
605
606        // Verify used ring entry: id=5, len=1024
607        let used_id = mem.read_u32(USED_RING_GPA + 4);
608        let used_len = mem.read_u32(USED_RING_GPA + 8);
609        assert_eq!(used_id, 5);
610        assert_eq!(used_len, 1024);
611    }
612
613    #[test]
614    fn test_push_used_wrapping() {
615        let mem = TestGuestMemory::new();
616        let mut queue = create_test_queue(&mem);
617
618        // Push more than queue size entries to verify wrapping
619        for i in 0..TEST_QUEUE_SIZE + 2 {
620            queue.push_used(i, i as u32 * 100);
621        }
622
623        let used_idx = mem.read_u16(USED_RING_GPA + 2);
624        assert_eq!(used_idx, TEST_QUEUE_SIZE + 2);
625    }
626
627    #[test]
628    fn test_pop_and_push_roundtrip() {
629        let mem = TestGuestMemory::new();
630
631        // Set up a descriptor
632        mem.write_descriptor(3, DATA_BUFFER_GPA, 512, 0, 0);
633        mem.set_avail_ring_entry(0, 3);
634        mem.set_avail_idx(1);
635
636        let mut queue = create_test_queue(&mem);
637
638        // Pop the available descriptor
639        let chain = queue.pop_avail().unwrap();
640        assert_eq!(chain.head_idx, 3);
641
642        // Push it to the used ring
643        queue.push_used(chain.head_idx, 512);
644
645        // Verify used ring
646        let used_idx = mem.read_u16(USED_RING_GPA + 2);
647        assert_eq!(used_idx, 1);
648        let used_id = mem.read_u32(USED_RING_GPA + 4);
649        assert_eq!(used_id, 3);
650    }
651
652    // ======================================================================
653    // Batch push
654    // ======================================================================
655
656    #[test]
657    fn test_push_used_batch() {
658        let mem = TestGuestMemory::new();
659        let mut queue = create_test_queue(&mem);
660
661        let completions = [(0, 100), (1, 200), (2, 300)];
662        queue.push_used_batch(&completions);
663
664        // Verify used index
665        let used_idx = mem.read_u16(USED_RING_GPA + 2);
666        assert_eq!(used_idx, 3);
667
668        // Verify each entry
669        for (i, &(id, len)) in completions.iter().enumerate() {
670            let entry_offset = USED_RING_GPA + 4 + (i as u64) * 8;
671            let entry_id = mem.read_u32(entry_offset);
672            let entry_len = mem.read_u32(entry_offset + 4);
673            assert_eq!(entry_id, u32::from(id));
674            assert_eq!(entry_len, len);
675        }
676    }
677
678    #[test]
679    fn test_push_used_batch_empty() {
680        let mem = TestGuestMemory::new();
681        let mut queue = create_test_queue(&mem);
682
683        queue.push_used_batch(&[]);
684
685        // Used index should still be 0
686        let used_idx = mem.read_u16(USED_RING_GPA + 2);
687        assert_eq!(used_idx, 0);
688    }
689
690    // ======================================================================
691    // Bounds checking
692    // ======================================================================
693
694    #[test]
695    fn test_gpa_out_of_bounds_read_buffer() {
696        let mem = TestGuestMemory::new();
697        let queue = create_test_queue(&mem);
698
699        // Try to read from a GPA beyond RAM size
700        let result = queue.read_buffer(TEST_RAM_SIZE as u64, 100);
701        assert!(result.is_none());
702    }
703
704    #[test]
705    fn test_gpa_out_of_bounds_write_buffer() {
706        let mem = TestGuestMemory::new();
707        let queue = create_test_queue(&mem);
708
709        // Try to write to a GPA beyond RAM size
710        let data = [0xAA; 100];
711        let result = queue.write_buffer(TEST_RAM_SIZE as u64, &data);
712        assert!(!result);
713    }
714
715    #[test]
716    fn test_gpa_partial_out_of_bounds() {
717        let mem = TestGuestMemory::new();
718        let queue = create_test_queue(&mem);
719
720        // Start within bounds, but extends beyond
721        let gpa = (TEST_RAM_SIZE - 10) as u64;
722        let result = queue.read_buffer(gpa, 100);
723        assert!(result.is_none());
724    }
725
726    #[test]
727    fn test_guest_slice_out_of_bounds() {
728        let mem = TestGuestMemory::new();
729        let mut queue = create_test_queue(&mem);
730
731        let result = unsafe { queue.guest_slice(TEST_RAM_SIZE as u64, 1) };
732        assert!(result.is_none());
733
734        let result = unsafe { queue.guest_slice_mut(TEST_RAM_SIZE as u64, 1) };
735        assert!(result.is_none());
736    }
737
738    // ======================================================================
739    // read_buffer / write_buffer
740    // ======================================================================
741
742    #[test]
743    fn test_read_buffer() {
744        let mem = TestGuestMemory::new();
745        let queue = create_test_queue(&mem);
746
747        // Write test data into guest memory
748        let test_data = b"Hello, VirtIO!";
749        mem.write_bytes(DATA_BUFFER_GPA, test_data);
750
751        // Read it back via the queue
752        let result = queue.read_buffer(DATA_BUFFER_GPA, test_data.len() as u32);
753        assert!(result.is_some());
754        assert_eq!(result.unwrap(), test_data);
755    }
756
757    #[test]
758    fn test_write_buffer() {
759        let mem = TestGuestMemory::new();
760        let queue = create_test_queue(&mem);
761
762        // Write data via the queue
763        let test_data = b"Device response";
764        let success = queue.write_buffer(DATA_BUFFER_GPA, test_data);
765        assert!(success);
766
767        // Verify by reading from the raw memory
768        let readback = mem.read_bytes(DATA_BUFFER_GPA, test_data.len());
769        assert_eq!(readback, test_data);
770    }
771
772    #[test]
773    fn test_read_write_buffer_roundtrip() {
774        let mem = TestGuestMemory::new();
775        let queue = create_test_queue(&mem);
776
777        // Write some data
778        let original = vec![0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE];
779        let success = queue.write_buffer(DATA_BUFFER_GPA, &original);
780        assert!(success);
781
782        // Read it back
783        let readback = queue.read_buffer(DATA_BUFFER_GPA, original.len() as u32);
784        assert_eq!(readback.unwrap(), original);
785    }
786
787    #[test]
788    fn test_guest_slice_read() {
789        let mem = TestGuestMemory::new();
790        let queue = create_test_queue(&mem);
791
792        let test_data = b"zero-copy read";
793        mem.write_bytes(DATA_BUFFER_GPA, test_data);
794
795        let slice = unsafe { queue.guest_slice(DATA_BUFFER_GPA, test_data.len()) };
796        assert!(slice.is_some());
797        assert_eq!(slice.unwrap(), test_data);
798    }
799
800    #[test]
801    fn test_guest_slice_mut_write() {
802        let mem = TestGuestMemory::new();
803        let mut queue = create_test_queue(&mem);
804
805        let slice = unsafe { queue.guest_slice_mut(DATA_BUFFER_GPA, 5) };
806        assert!(slice.is_some());
807        let slice = slice.unwrap();
808        slice.copy_from_slice(b"ABCDE");
809
810        // Verify via raw memory read
811        let readback = mem.read_bytes(DATA_BUFFER_GPA, 5);
812        assert_eq!(readback, b"ABCDE");
813    }
814
815    // ======================================================================
816    // Queue index and event_idx accessors
817    // ======================================================================
818
819    #[test]
820    fn test_queue_idx_accessor() {
821        let mem = TestGuestMemory::new();
822        let queue = unsafe {
823            GuestMemoryVirtQueue::new(
824                7,
825                TEST_QUEUE_SIZE,
826                DESC_TABLE_GPA,
827                AVAIL_RING_GPA,
828                USED_RING_GPA,
829                mem.as_mut_ptr(),
830                TEST_RAM_SIZE,
831            )
832        };
833        assert_eq!(queue.queue_idx(), 7);
834    }
835
836    #[test]
837    fn test_event_idx_toggle() {
838        let mem = TestGuestMemory::new();
839        let mut queue = create_test_queue(&mem);
840
841        assert!(!queue.event_idx);
842        queue.set_event_idx(true);
843        assert!(queue.event_idx);
844        queue.set_event_idx(false);
845        assert!(!queue.event_idx);
846    }
847
848    // ======================================================================
849    // Descriptor chain loop detection
850    // ======================================================================
851
852    #[test]
853    fn test_descriptor_chain_loop_terminates() {
854        let mem = TestGuestMemory::new();
855
856        // Create a loop: desc 0 -> desc 1 -> desc 0
857        let next_flag = crate::queue::flags::NEXT;
858        mem.write_descriptor(0, DATA_BUFFER_GPA, 64, next_flag, 1);
859        mem.write_descriptor(1, DATA_BUFFER_GPA + 64, 64, next_flag, 0);
860
861        mem.set_avail_ring_entry(0, 0);
862        mem.set_avail_idx(1);
863
864        let mut queue = create_test_queue(&mem);
865        let chain = queue.pop_avail().unwrap();
866
867        // Should stop after at most `size` descriptors (loop detection)
868        assert!(chain.descriptors.len() <= TEST_QUEUE_SIZE as usize);
869    }
870}