Skip to main content

hyperlight_common/virtq/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 The Hyperlight Authors.
3
4//! Packed Virtqueue Implementation
5//!
6//! This module provides a high-level API for virtio packed virtqueues, built on top of
7//! the lower-level ring primitives. It implements the VIRTIO 1.1+ packed ring format
8//! with proper memory ordering and event suppression support.
9//!
10//! # Architecture
11//!
12//! The implementation is split into layers:
13//!
14//! - **High-level API** ([`VirtqProducer`], [`VirtqConsumer`]): Manages buffer allocation,
15//!   chain lifecycle, and notification decisions. This is the recommended API
16//!   for most use cases.
17//!
18//! - **Ring primitives** ([`RingProducer`], [`RingConsumer`]): Low-level descriptor ring
19//!   operations with explicit buffer chain management. Use this when you need full control
20//!   over buffer layouts or custom allocation strategies.
21//!
22//! - **Descriptor and event types** ([`Descriptor`], [`EventSuppression`]): Raw virtio
23//!   data structures for direct memory manipulation.
24//!
25//! # Quick Start
26//!
27//! ## Single Readable/Writable Chain
28//!
29//! ```ignore
30//! // Producer (driver) side - build and submit a send chain
31//! let mut chain = producer.chain()
32//!     .readable(64)
33//!     .writable(128)
34//!     .build()?;
35//! chain.write_all(b"request data")?;
36//! let token = producer.submit(chain)?;
37//! // ... wait for notification ...
38//! if let Some(used) = producer.poll()? {
39//!     match used {
40//!         UsedChain::Data(_, segments) => process(segments),
41//!         UsedChain::Ack(_) => {}
42//!     }
43//! }
44//!
45//! // Consumer (device) side - receive a chain and reply/ack it
46//! if let Some((chain, reply)) = consumer.poll(max_recv_len)? {
47//!     let request = chain.to_bytes();
48//!     match reply {
49//!         ReplyChain::Writable(mut wc) => {
50//!             let response = handle(request);
51//!             wc.write_all(&response)?;
52//!             consumer.complete(wc)?;
53//!         }
54//!         ReplyChain::Ack(ack) => {
55//!             consumer.complete(ack)?;
56//!         }
57//!     }
58//! }
59//!
60//! // Multiple pending completions (no borrow on consumer)
61//! let mut pending = Vec::new();
62//! while let Some((chain, reply)) = consumer.poll(max_recv_len)? {
63//!     pending.push((process(chain), reply));
64//! }
65//! for (result, reply) in pending {
66//!     consumer.complete(reply)?;
67//! }
68//! ```
69//!
70//! ## Multiple Chains
71//!
72//! Each submit checks event suppression and notifies independently. Use
73//! [`VirtqProducer::batch`] when a higher-level protocol wants to publish
74//! multiple chains and kick the queue once.
75//!
76//! ```ignore
77//! let mut batch = producer.batch();
78//! for data in entries {
79//!     let mut chain = batch.chain()
80//!         .readable(data.len())
81//!         .writable(64)
82//!         .build()?;
83//!     chain.write_all(data)?;
84//!     batch.submit(chain)?;
85//! }
86//! batch.finish()?;
87//! ```
88//!
89//! ## Completion Batching with Event Suppression
90//!
91//! To receive a single notification when multiple requests complete:
92//!
93//! ```ignore
94//! // Submit chains
95//! for data in entries {
96//!     let mut chain = producer.chain()
97//!         .readable(data.len())
98//!         .writable(64)
99//!         .build()?;
100//!     chain.write_all(data)?;
101//!     producer.submit(chain)?;
102//! }
103//!
104//! // Tell device: "notify me only after completing past this cursor"
105//! let cursor = producer.used_cursor();
106//! producer.set_used_suppression(SuppressionKind::Descriptor(cursor))?;
107//!
108//! // Wait for single notification, then drain all responses
109//! producer.drain(|used| {
110//!     if let UsedChain::Data(token, data) = used {
111//!         handle_response(token, data);
112//!     }
113//! })?;
114//! ```
115//!
116//! # Event Suppression
117//!
118//! Both sides can control when they want to be notified using [`SuppressionKind`]:
119//!
120//! - [`SuppressionKind::Enable`]: Always notify (default, lowest latency)
121//! - [`SuppressionKind::Disable`]: Never notify (polling mode, lowest overhead)
122//! - [`SuppressionKind::Descriptor`]: Notify at specific ring position (batching)
123//!
124//! See [`VirtqProducer::set_used_suppression`] and [`VirtqConsumer::set_avail_suppression`].
125//!
126//! # Low-Level API
127//!
128//! For advanced use cases, the ring module exposes lower-level primitives:
129//!
130//! - [`RingProducer`] / [`RingConsumer`]: Direct ring access with [`BufferChain`] submission
131//! - [`BufferChainBuilder`]: Construct scatter-gather buffer lists
132//! - [`RingCursor`]: Track ring positions for event suppression
133//!
134//! Example using low-level API:
135//!
136//! ```ignore
137//! let chain = BufferChainBuilder::new()
138//!     .readable(header_addr, header_len)
139//!     .readable(data_addr, data_len)
140//!     .writable(response_addr, response_len)
141//!     .build()?;
142//!
143//! let result = ring_producer.submit_available_with_notify(&chain)?;
144//! if result.notify {
145//!     kick_device();
146//! }
147//! ```
148
149mod access;
150mod buffer;
151mod consumer;
152mod desc;
153mod event;
154pub mod msg;
155mod pool;
156mod producer;
157mod ring;
158
159#[cfg(all(test, loom))]
160mod concurrency;
161
162use core::num::NonZeroU16;
163
164pub use access::*;
165pub use buffer::*;
166pub use consumer::*;
167pub use desc::*;
168pub use event::*;
169pub use pool::*;
170pub use producer::*;
171pub use ring::*;
172use thiserror::Error;
173
174/// A trait for notifying the consumer about virtqueue events.
175pub trait Notifier {
176    fn notify(&self, stats: QueueStats);
177}
178
179/// Errors that can occur in the virtqueue operations.
180#[derive(Error, Debug)]
181pub enum VirtqError {
182    #[error("Ring error: {0}")]
183    RingError(RingError),
184    #[error("Allocation error: {0}")]
185    Alloc(AllocError),
186    #[error("Ring or pool temporarily full")]
187    Backpressure,
188    #[error("Allocation exceeds pool capacity")]
189    OutOfMemory,
190    #[error("Invalid chain received")]
191    BadChain,
192    #[error("Payload data too large: received {recv} bytes, limit {limit} bytes")]
193    PayloadTooLarge { recv: usize, limit: usize },
194    #[error("Reply data too large for allocated buffer")]
195    ReplyTooLarge,
196    #[error("Internal state error")]
197    InvalidState,
198    #[error("Memory write error")]
199    MemoryWriteError,
200    #[error("Memory read error")]
201    MemoryReadError,
202    #[error("No payload segment in this chain")]
203    NoPayloadSegment,
204}
205
206impl VirtqError {
207    /// Check if this error is transient or unrecoverable.
208    #[inline(always)]
209    pub fn is_transient(&self) -> bool {
210        matches!(self, Self::Backpressure)
211    }
212}
213
214impl From<RingError> for VirtqError {
215    fn from(e: RingError) -> Self {
216        match e {
217            RingError::WouldBlock => Self::Backpressure,
218            other => Self::RingError(other),
219        }
220    }
221}
222
223impl From<AllocError> for VirtqError {
224    fn from(e: AllocError) -> Self {
225        match e {
226            AllocError::NoSpace => Self::Backpressure,
227            AllocError::OutOfMemory => Self::OutOfMemory,
228            other => Self::Alloc(other),
229        }
230    }
231}
232
233/// Layout of a packed virtqueue ring in shared memory.
234///
235/// Describes the memory addresses for the descriptor table and event suppression
236/// structures. Use [`from_base`](Self::from_base) to compute the layout from a
237/// base address, or [`query_size`](Self::query_size) to determine memory requirements.
238///
239/// # Memory Layout
240///
241/// The packed ring consists of:
242/// 1. Descriptor table: `num_descs` × 16 bytes, aligned to 16 bytes
243/// 2. Driver event suppression: 4 bytes, aligned to 4 bytes
244/// 3. Device event suppression: 4 bytes, aligned to 4 bytes
245#[derive(Clone, Copy, Debug)]
246pub struct Layout {
247    /// Packed ring descriptor table base in shared memory.
248    desc_table_addr: u64,
249    /// Number of descriptors (ring size, must be power of 2).
250    desc_table_len: u16,
251    /// Driver-written event suppression area in shared memory.
252    drv_evt_addr: u64,
253    /// Device-written event suppression area in shared memory.
254    dev_evt_addr: u64,
255}
256
257#[inline]
258const fn align_up(val: usize, align: usize) -> usize {
259    val.next_multiple_of(align)
260}
261
262impl Layout {
263    /// Create a Layout from a base address and number of descriptors.
264    ///
265    /// The base address must be aligned to `Descriptor::ALIGN`.
266    /// The number of descriptors must be a power of 2.
267    /// The memory region starting at `base` must be at least `Layout::query_size(num_descs)` bytes.
268    ///
269    /// # Safety
270    /// - `base` must be valid for `Layout::query_size(num_descs)` bytes.
271    /// - `base` must be aligned to `Descriptor::ALIGN`.
272    /// - Memory must remain valid for the lifetime of the ring.
273    pub const unsafe fn from_base(base: u64, num_descs: NonZeroU16) -> Result<Self, RingError> {
274        let num_descs = num_descs.get() as usize;
275        if !num_descs.is_power_of_two() {
276            return Err(RingError::InvalidLayout);
277        }
278
279        if !base.is_multiple_of(Descriptor::ALIGN as u64) {
280            return Err(RingError::InvalidLayout);
281        }
282
283        if base
284            .checked_add(Layout::query_size(num_descs) as u64)
285            .is_none()
286        {
287            return Err(RingError::InvalidLayout);
288        }
289
290        let desc_size = num_descs * Descriptor::SIZE;
291        let event_size = EventSuppression::SIZE;
292        let event_align = EventSuppression::ALIGN;
293
294        let drv_evt_offset = align_up(desc_size, event_align);
295        let dev_evt_offset = align_up(drv_evt_offset + event_size, event_align);
296
297        Ok(Self {
298            desc_table_addr: base,
299            desc_table_len: num_descs as u16,
300            drv_evt_addr: base + drv_evt_offset as u64,
301            dev_evt_addr: base + dev_evt_offset as u64,
302        })
303    }
304
305    /// Packed ring descriptor table base in shared memory.
306    pub const fn desc_table_addr(&self) -> u64 {
307        self.desc_table_addr
308    }
309
310    /// Number of descriptors in the ring.
311    pub const fn desc_table_len(&self) -> u16 {
312        self.desc_table_len
313    }
314
315    /// Driver-written event suppression area in shared memory.
316    pub const fn drv_evt_addr(&self) -> u64 {
317        self.drv_evt_addr
318    }
319
320    /// Device-written event suppression area in shared memory.
321    pub const fn dev_evt_addr(&self) -> u64 {
322        self.dev_evt_addr
323    }
324
325    /// Calculate the memory size needed for a ring with `num_descs` descriptors,
326    /// accounting for alignment requirements.
327    pub const fn query_size(num_descs: usize) -> usize {
328        let desc_size = num_descs * Descriptor::SIZE;
329        let event_size = EventSuppression::SIZE;
330        let event_align = EventSuppression::ALIGN;
331
332        // desc table at offset 0, then aligned events
333        let drv_evt_offset = align_up(desc_size, event_align);
334        let dev_evt_offset = align_up(drv_evt_offset + event_size, event_align);
335
336        dev_evt_offset + event_size
337    }
338}
339
340/// Statistics about the current virtqueue state.
341///
342/// Provided to the [`Notifier`] when sending notifications, allowing
343/// the notifier to make decisions based on queue pressure.
344#[derive(Debug, Clone, Copy)]
345pub struct QueueStats {
346    /// Number of free descriptor slots available.
347    pub num_free: usize,
348    /// Number of descriptors currently in-flight (submitted but not completed).
349    pub num_inflight: usize,
350}
351
352/// Event suppression mode for controlling when notifications are sent.
353///
354/// This configures when the other side should signal (interrupt/kick) us
355/// about new data. Used to optimize batching and reduce interrupt overhead.
356#[derive(Debug, Clone, Copy, PartialEq, Eq)]
357pub enum SuppressionKind {
358    /// Always signal after each operation (default behavior).
359    Enable,
360    /// Never signal.
361    Disable,
362    /// Signal only when reaching a specific descriptor position.
363    Descriptor(RingCursor),
364}
365
366/// A token representing a sent chain in the virtqueue.
367///
368/// Tokens uniquely identify in-flight requests and are used to correlate
369/// requests with their responses.
370#[derive(Copy, Clone, Debug, PartialEq, Eq)]
371pub struct Token {
372    /// Monotonically increasing generation counter.
373    pub seq: u32,
374    /// Descriptor ID this token maps to.
375    pub id: u16,
376}
377
378impl From<BufferElement> for Allocation {
379    fn from(value: BufferElement) -> Self {
380        Allocation {
381            addr: value.addr,
382            len: value.len as usize,
383        }
384    }
385}
386
387const _: () = {
388    #[allow(clippy::panic)]
389    #[allow(clippy::unwrap_used)]
390    const fn verify_layout(num_descs: usize) {
391        let base = 0x1000u64;
392
393        // Safety: base is aligned and we're only checking layout math
394        let layout =
395            match unsafe { Layout::from_base(base, NonZeroU16::new(num_descs as u16).unwrap()) } {
396                Ok(l) => l,
397                Err(_) => panic!("from_base failed"),
398            };
399
400        let expected_size = Layout::query_size(num_descs);
401
402        assert!(layout.desc_table_addr() == base);
403        assert!(layout.desc_table_len() as usize == num_descs);
404        assert!(
405            layout
406                .drv_evt_addr()
407                .is_multiple_of(EventSuppression::ALIGN as u64)
408        );
409        assert!(
410            layout
411                .dev_evt_addr()
412                .is_multiple_of(EventSuppression::ALIGN as u64)
413        );
414
415        // Events don't overlap with descriptor table
416        let desc_end = base + (num_descs * Descriptor::SIZE) as u64;
417        assert!(layout.drv_evt_addr() >= desc_end);
418        assert!(layout.dev_evt_addr() >= layout.drv_evt_addr() + EventSuppression::SIZE as u64);
419
420        // Total size from query_size covers entire layout
421        let layout_end = layout.dev_evt_addr() + EventSuppression::SIZE as u64;
422        assert!(base + expected_size as u64 == layout_end);
423    }
424
425    unsafe {
426        assert!(Layout::from_base(u64::MAX, NonZeroU16::new(1).unwrap()).is_err());
427    }
428
429    verify_layout(1);
430    verify_layout(2);
431    verify_layout(4);
432    verify_layout(8);
433    verify_layout(16);
434    verify_layout(32);
435    verify_layout(64);
436    verify_layout(128);
437    verify_layout(256);
438    verify_layout(512);
439    verify_layout(1024);
440};
441
442/// Shared test utilities for virtqueue tests.
443#[cfg(test)]
444pub(crate) mod test_utils {
445    use alloc::collections::BTreeMap;
446    use alloc::sync::Arc;
447    use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
448    use std::sync::Mutex;
449
450    use super::*;
451    use crate::virtq::ring::tests::{OwnedRing, TestMem};
452
453    /// Simple notifier that tracks notification count.
454    #[derive(Debug, Clone)]
455    pub(crate) struct TestNotifier {
456        pub(crate) count: Arc<AtomicUsize>,
457    }
458
459    impl TestNotifier {
460        pub(crate) fn new() -> Self {
461            Self {
462                count: Arc::new(AtomicUsize::new(0)),
463            }
464        }
465
466        pub(crate) fn notification_count(&self) -> usize {
467            self.count.load(Ordering::Relaxed)
468        }
469    }
470
471    impl Notifier for TestNotifier {
472        fn notify(&self, _stats: QueueStats) {
473            self.count.fetch_add(1, Ordering::Relaxed);
474        }
475    }
476
477    /// Simple test buffer pool that allocates from a range.
478    #[derive(Clone)]
479    pub(crate) struct TestPool {
480        base: u64,
481        next: Arc<AtomicU64>,
482        size: usize,
483        max_alloc_len: usize,
484        allocations: Arc<Mutex<BTreeMap<u64, usize>>>,
485    }
486
487    impl TestPool {
488        pub(crate) fn new(base: u64, size: usize) -> Self {
489            Self {
490                base,
491                next: Arc::new(AtomicU64::new(base)),
492                size,
493                max_alloc_len: usize::MAX,
494                allocations: Arc::new(Mutex::new(BTreeMap::new())),
495            }
496        }
497
498        pub(crate) fn new_with_max_alloc_len(base: u64, size: usize, max_alloc_len: usize) -> Self {
499            Self {
500                base,
501                next: Arc::new(AtomicU64::new(base)),
502                size,
503                max_alloc_len,
504                allocations: Arc::new(Mutex::new(BTreeMap::new())),
505            }
506        }
507    }
508
509    impl BufferProvider for TestPool {
510        fn max_alloc_len(&self) -> usize {
511            self.max_alloc_len
512        }
513
514        fn alloc(&self, len: usize) -> Result<Allocation, AllocError> {
515            if len == 0 {
516                return Err(AllocError::InvalidArg);
517            }
518
519            let addr = self.next.fetch_add(len as u64, Ordering::Relaxed);
520            let end = addr + len as u64;
521            if end > self.base + self.size as u64 {
522                return Err(AllocError::NoSpace);
523            }
524            self.allocations
525                .lock()
526                .expect("poisoned mutex")
527                .insert(addr, len);
528            Ok(Allocation { addr, len })
529        }
530
531        fn dealloc(&self, addr: u64) -> Result<(), AllocError> {
532            self.allocations
533                .lock()
534                .expect("poisoned mutex")
535                .remove(&addr)
536                .map(|_| ())
537                .ok_or(AllocError::InvalidFree(addr, 0))
538        }
539    }
540
541    type TestProducer = VirtqProducer<TestMem, TestNotifier, TestPool>;
542    type TestConsumer = VirtqConsumer<TestMem, TestNotifier>;
543
544    /// Create test infrastructure: a producer, consumer, and notifier backed
545    /// by the supplied [`OwnedRing`].
546    pub(crate) fn make_test_producer(
547        ring: &OwnedRing,
548    ) -> (TestProducer, TestConsumer, TestNotifier) {
549        let layout = ring.layout();
550        let mem = ring.mem();
551
552        // Pool needs to be in memory accessible via mem - use memory after ring layout
553        let pool_base = mem.base_addr() + Layout::query_size(ring.len()) as u64 + 0x100;
554        let pool = TestPool::new(pool_base, 0x8000);
555        let notifier = TestNotifier::new();
556
557        let producer = VirtqProducer::new(layout, mem.clone(), notifier.clone(), pool);
558        let consumer = VirtqConsumer::new(layout, mem, notifier.clone());
559
560        (producer, consumer, notifier)
561    }
562}
563
564#[cfg(test)]
565mod tests {
566    use alloc::sync::Arc;
567    use core::sync::atomic::{AtomicUsize, Ordering};
568
569    use super::*;
570    use crate::virtq::ring::tests::{TestMem, make_ring};
571    use crate::virtq::test_utils::*;
572
573    /// Helper: build and submit a readable+writable chain using the chain() builder.
574    fn send_readwrite(
575        producer: &mut VirtqProducer<TestMem, TestNotifier, TestPool>,
576        entry_data: &[u8],
577        used_cap: usize,
578    ) -> Token {
579        let mut se = producer
580            .chain()
581            .readable(entry_data.len())
582            .writable(used_cap)
583            .build()
584            .unwrap();
585        se.write_all(entry_data).unwrap();
586        producer.submit(se).unwrap()
587    }
588
589    fn poll_received(
590        consumer: &mut VirtqConsumer<TestMem, TestNotifier>,
591    ) -> (RecvChain, ReplyChain<TestMem>) {
592        consumer.poll(1024).unwrap().unwrap()
593    }
594
595    #[test]
596    fn test_submit_notifies() {
597        let ring = make_ring(16);
598        let (mut producer, mut consumer, notifier) = make_test_producer(&ring);
599
600        let initial_count = notifier.notification_count();
601
602        let token = send_readwrite(&mut producer, b"hello", 64);
603        assert!(notifier.notification_count() > initial_count);
604
605        let (recv, _reply) = poll_received(&mut consumer);
606        assert_eq!(recv.token(), token);
607    }
608
609    #[test]
610    fn test_multiple_submits() {
611        let ring = make_ring(16);
612        let (mut producer, mut consumer, _notifier) = make_test_producer(&ring);
613
614        let tok1 = send_readwrite(&mut producer, b"request1", 64);
615        let tok2 = send_readwrite(&mut producer, b"request2", 64);
616        let tok3 = send_readwrite(&mut producer, b"request3", 64);
617
618        // Consumer sees all requests
619        for _ in 0..3 {
620            let (_recv, reply) = poll_received(&mut consumer);
621            consumer.complete(reply).unwrap();
622        }
623
624        // All completions available
625        let used1 = producer.poll().unwrap().unwrap();
626        let used2 = producer.poll().unwrap().unwrap();
627        let used3 = producer.poll().unwrap().unwrap();
628        assert!(
629            [used1.token(), used2.token(), used3.token()].contains(&tok1)
630                && [used1.token(), used2.token(), used3.token()].contains(&tok2)
631                && [used1.token(), used2.token(), used3.token()].contains(&tok3)
632        );
633    }
634
635    #[test]
636    fn test_completion_batching_with_suppression() {
637        let ring = make_ring(16);
638        let (mut producer, mut consumer, _notifier) = make_test_producer(&ring);
639
640        // Submit entries
641        let tok1 = send_readwrite(&mut producer, b"req1", 64);
642        let tok2 = send_readwrite(&mut producer, b"req2", 64);
643        let tok3 = send_readwrite(&mut producer, b"req3", 64);
644
645        // Set up reply batching via used suppression
646        let cursor = producer.used_cursor();
647        producer
648            .set_used_suppression(SuppressionKind::Descriptor(cursor))
649            .unwrap();
650
651        // Consumer processes requests
652        for _ in 0..3 {
653            let (_recv, reply) = poll_received(&mut consumer);
654            let ReplyChain::Writable(mut wc) = reply else {
655                panic!("expected writable reply");
656            };
657            wc.write_all(b"used-data").unwrap();
658            consumer.complete(wc).unwrap();
659        }
660
661        // Producer can drain all responses
662        let mut responses = Vec::new();
663        producer
664            .drain(|reply| {
665                responses.push(reply.token());
666            })
667            .unwrap();
668
669        assert_eq!(responses.len(), 3);
670        assert!(responses.contains(&tok1));
671        assert!(responses.contains(&tok2));
672        assert!(responses.contains(&tok3));
673    }
674
675    #[test]
676    fn test_notifier_receives_context() {
677        #[derive(Debug, Clone)]
678        struct CtxNotifier {
679            last_num_free: Arc<AtomicUsize>,
680            last_num_inflight: Arc<AtomicUsize>,
681            count: Arc<AtomicUsize>,
682        }
683
684        impl Notifier for CtxNotifier {
685            fn notify(&self, stats: QueueStats) {
686                self.last_num_free.store(stats.num_free, Ordering::Relaxed);
687                self.last_num_inflight
688                    .store(stats.num_inflight, Ordering::Relaxed);
689                self.count.fetch_add(1, Ordering::Relaxed);
690            }
691        }
692
693        let ring = make_ring(16);
694        let layout = ring.layout();
695        let mem = ring.mem();
696        let pool_base = mem.base_addr() + Layout::query_size(ring.len()) as u64 + 0x100;
697        let pool = TestPool::new(pool_base, 0x8000);
698        let notifier = CtxNotifier {
699            last_num_free: Arc::new(AtomicUsize::new(0)),
700            last_num_inflight: Arc::new(AtomicUsize::new(0)),
701            count: Arc::new(AtomicUsize::new(0)),
702        };
703
704        let mut producer = VirtqProducer::new(layout, mem, notifier.clone(), pool);
705
706        let mut se = producer.chain().readable(4).writable(32).build().unwrap();
707        se.write_all(b"test").unwrap();
708        producer.submit(se).unwrap();
709        assert_eq!(notifier.count.load(Ordering::Relaxed), 1);
710        assert!(notifier.last_num_inflight.load(Ordering::Relaxed) > 0);
711    }
712
713    #[test]
714    fn test_chain_batch() {
715        let ring = make_ring(16);
716        let (mut producer, mut consumer, notifier) = make_test_producer(&ring);
717
718        let initial_count = notifier.notification_count();
719
720        // First readable chain
721        let mut se1 = producer.chain().readable(64).writable(128).build().unwrap();
722        se1.write_all(b"first-ent").unwrap();
723        let _tok1 = producer.submit(se1).unwrap();
724
725        // Write-based recv
726        let mut se2 = producer.chain().readable(64).writable(64).build().unwrap();
727        se2.write_all(b"copy-ent").unwrap();
728        let _tok2 = producer.submit(se2).unwrap();
729
730        // Completion-only chain
731        let se3 = producer.chain().writable(32).build().unwrap();
732        let tok3 = producer.submit(se3).unwrap();
733
734        // Each submit may notify independently
735        assert!(notifier.notification_count() > initial_count);
736
737        // Consumer sees all three entries
738        let (recv1, reply1) = poll_received(&mut consumer);
739        assert_eq!(recv1.to_bytes().as_ref(), b"first-ent");
740        consumer.complete(reply1).unwrap();
741
742        let (recv2, reply2) = poll_received(&mut consumer);
743        assert_eq!(recv2.to_bytes().as_ref(), b"copy-ent");
744        consumer.complete(reply2).unwrap();
745
746        let (_recv3, reply3) = poll_received(&mut consumer);
747        let ReplyChain::Writable(mut wc) = reply3 else {
748            panic!("expected writable reply");
749        };
750        wc.write_all(b"resp").unwrap();
751        consumer.complete(wc).unwrap();
752
753        // Drain completions
754        let _ = producer.poll().unwrap().unwrap();
755        let _ = producer.poll().unwrap().unwrap();
756
757        let used = producer.poll().unwrap().unwrap();
758        assert_eq!(used.token(), tok3);
759        assert_eq!(used.to_bytes().unwrap().as_ref(), b"resp");
760    }
761
762    #[test]
763    fn test_chain_write_send() {
764        let ring = make_ring(16);
765        let (mut producer, mut consumer, _notifier) = make_test_producer(&ring);
766
767        let mut se = producer.chain().readable(64).writable(128).build().unwrap();
768        se.write_all(b"hello").unwrap();
769        let token = producer.submit(se).unwrap();
770
771        // Consumer sees the data
772        let (recv, reply) = poll_received(&mut consumer);
773        assert_eq!(recv.token(), token);
774        assert_eq!(recv.to_bytes().as_ref(), b"hello");
775
776        // Write response
777        let ReplyChain::Writable(mut wc) = reply else {
778            panic!("expected writable reply");
779        };
780        wc.write_all(b"world").unwrap();
781        consumer.complete(wc).unwrap();
782        let used = producer.poll().unwrap().unwrap();
783        assert_eq!(used.to_bytes().unwrap().as_ref(), b"world");
784    }
785
786    #[test]
787    fn test_full_round_trip() {
788        let ring = make_ring(16);
789        let (mut producer, mut consumer, _notifier) = make_test_producer(&ring);
790
791        // Send an recv
792        let token = send_readwrite(&mut producer, b"round-trip-recv", 128);
793
794        // Consumer receives and responds
795        let (recv, reply) = poll_received(&mut consumer);
796        assert_eq!(recv.token(), token);
797        assert_eq!(recv.to_bytes().as_ref(), b"round-trip-recv");
798
799        let ReplyChain::Writable(mut wc) = reply else {
800            panic!("expected writable reply");
801        };
802        assert!(wc.capacity() >= 128);
803        wc.write_all(b"round-trip-rsp").unwrap();
804        consumer.complete(wc).unwrap();
805
806        // Producer gets the reply
807        let used = producer.poll().unwrap().unwrap();
808        assert_eq!(used.token(), token);
809        assert_eq!(used.to_bytes().unwrap().as_ref(), b"round-trip-rsp");
810    }
811
812    #[test]
813    fn test_cancel_submits_zero_length() {
814        let ring = make_ring(16);
815        let (mut producer, mut consumer, _notifier) = make_test_producer(&ring);
816
817        let token = send_readwrite(&mut producer, b"recv-data", 64);
818
819        let (_recv, reply) = poll_received(&mut consumer);
820        consumer.complete(reply).unwrap();
821
822        let used = producer.poll().unwrap().unwrap();
823        assert_eq!(used.token(), token);
824        assert_eq!(used.to_bytes().unwrap().len(), 0);
825        assert!(used.to_bytes().unwrap().is_empty());
826    }
827
828    #[test]
829    fn test_hold_reply_and_complete() {
830        let ring = make_ring(16);
831        let (mut producer, mut consumer, _notifier) = make_test_producer(&ring);
832
833        let token = send_readwrite(&mut producer, b"deferred", 64);
834
835        // Poll and hold the reply
836        let (recv, reply) = poll_received(&mut consumer);
837        assert_eq!(recv.token(), token);
838        assert_eq!(recv.to_bytes().as_ref(), b"deferred");
839
840        let ReplyChain::Writable(mut wc) = reply else {
841            panic!("expected writable reply");
842        };
843        wc.write_all(b"deferred-used").unwrap();
844        consumer.complete(wc).unwrap();
845
846        let used = producer.poll().unwrap().unwrap();
847        assert_eq!(used.token(), token);
848        assert_eq!(used.to_bytes().unwrap().as_ref(), b"deferred-used");
849    }
850
851    #[test]
852    fn test_concurrent_pending_replies() {
853        let ring = make_ring(16);
854        let (mut producer, mut consumer, _notifier) = make_test_producer(&ring);
855
856        let tok1 = send_readwrite(&mut producer, b"first", 64);
857        let tok2 = send_readwrite(&mut producer, b"second", 64);
858
859        // Poll both
860        let (recv1, reply1) = poll_received(&mut consumer);
861        assert_eq!(recv1.token(), tok1);
862        assert_eq!(recv1.to_bytes().as_ref(), b"first");
863
864        let (recv2, reply2) = poll_received(&mut consumer);
865        assert_eq!(recv2.token(), tok2);
866        assert_eq!(recv2.to_bytes().as_ref(), b"second");
867
868        // Complete second first (out of order)
869        let ReplyChain::Writable(mut wc2) = reply2 else {
870            panic!("expected writable");
871        };
872        wc2.write_all(b"resp2").unwrap();
873        consumer.complete(wc2).unwrap();
874
875        let ReplyChain::Writable(mut wc1) = reply1 else {
876            panic!("expected writable");
877        };
878        wc1.write_all(b"resp1").unwrap();
879        consumer.complete(wc1).unwrap();
880
881        let used1 = producer.poll().unwrap().unwrap();
882        let used2 = producer.poll().unwrap().unwrap();
883        let mut responses: Vec<_> = vec![
884            (used1.token(), used1.to_bytes().unwrap().to_vec()),
885            (used2.token(), used2.to_bytes().unwrap().to_vec()),
886        ];
887        responses.sort_by_key(|(t, _)| t.seq);
888
889        let expected_first = responses.iter().find(|(t, _)| *t == tok1).unwrap();
890        let expected_second = responses.iter().find(|(t, _)| *t == tok2).unwrap();
891        assert_eq!(&expected_first.1[..], b"resp1");
892        assert_eq!(&expected_second.1[..], b"resp2");
893    }
894
895    /// Helper: submit a read-only chain (readable data, no writable reply).
896    fn send_readonly(
897        producer: &mut VirtqProducer<TestMem, TestNotifier, TestPool>,
898        entry_data: &[u8],
899    ) -> Token {
900        let mut se = producer.chain().readable(entry_data.len()).build().unwrap();
901        se.write_all(entry_data).unwrap();
902        producer.submit(se).unwrap()
903    }
904
905    #[test]
906    fn test_reclaim_frees_ring_slots() {
907        let ring = make_ring(4);
908        let (mut producer, mut consumer, _) = make_test_producer(&ring);
909
910        // Fill the ring with ReadOnly entries
911        send_readonly(&mut producer, b"a");
912        send_readonly(&mut producer, b"b");
913        send_readonly(&mut producer, b"c");
914        send_readonly(&mut producer, b"d");
915
916        // Ring is now full - next submit should fail with Backpressure
917        let mut se = producer.chain().readable(1).build().unwrap();
918        se.write_all(b"e").unwrap();
919        let res = producer.submit(se);
920        assert!(
921            matches!(res, Err(VirtqError::Backpressure)),
922            "expected Backpressure from full ring"
923        );
924
925        // Consumer acks all entries
926        while let Some(result) = consumer.poll(1024).unwrap() {
927            let (_, reply) = result;
928            consumer.complete(reply).unwrap();
929        }
930
931        // Reclaim should free ring slots without losing data
932        let count = producer.reclaim().unwrap();
933        assert_eq!(count, 4, "expected 4 reclaimed entries");
934
935        // Ring should have space now
936        send_readonly(&mut producer, b"e");
937    }
938
939    #[test]
940    fn test_reclaim_buffers_rw_completions() {
941        let ring = make_ring(4);
942        let (mut producer, mut consumer, _) = make_test_producer(&ring);
943
944        // Submit a ReadWrite recv
945        let tok = send_readwrite(&mut producer, b"request", 64);
946
947        // Consumer processes and writes response
948        let (_, reply) = poll_received(&mut consumer);
949        let ReplyChain::Writable(mut wc) = reply else {
950            panic!("expected writable");
951        };
952        wc.write_all(b"response-data").unwrap();
953        consumer.complete(wc).unwrap();
954
955        // Reclaim buffers the reply (doesn't discard it)
956        let count = producer.reclaim().unwrap();
957        assert_eq!(count, 1);
958
959        // poll() should return the buffered reply
960        let used = producer.poll().unwrap().unwrap();
961        assert_eq!(used.token(), tok);
962        assert_eq!(used.to_bytes().unwrap().as_ref(), b"response-data");
963    }
964
965    #[test]
966    fn test_reclaim_discards_readonly_completions() {
967        let ring = make_ring(8);
968        let (mut producer, mut consumer, _) = make_test_producer(&ring);
969
970        // Submit 3 entries: RO, RW, RO
971        let _tok_ro1 = send_readonly(&mut producer, b"log1");
972        let tok_rw = send_readwrite(&mut producer, b"call", 64);
973        let _tok_ro2 = send_readonly(&mut producer, b"log2");
974
975        // Consumer processes all 3
976        let (_, reply1) = poll_received(&mut consumer);
977        consumer.complete(reply1).unwrap(); // ack RO
978
979        let (_, reply2) = poll_received(&mut consumer);
980        let ReplyChain::Writable(mut wc) = reply2 else {
981            panic!("expected writable");
982        };
983        wc.write_all(b"result").unwrap();
984        consumer.complete(wc).unwrap(); // complete RW
985
986        let (_, reply3) = poll_received(&mut consumer);
987        consumer.complete(reply3).unwrap(); // ack RO
988
989        // Reclaim all 3 - RO completions are discarded, only RW is buffered
990        let count = producer.reclaim().unwrap();
991        assert_eq!(count, 3);
992
993        // poll() returns only the RW reply
994        let used = producer.poll().unwrap().unwrap();
995        assert_eq!(used.token(), tok_rw);
996        assert_eq!(used.to_bytes().unwrap().as_ref(), b"result");
997
998        // No more - RO completions were discarded
999        assert!(producer.poll().unwrap().is_none());
1000    }
1001
1002    #[test]
1003    fn test_reclaim_mixed_with_poll() {
1004        let ring = make_ring(8);
1005        let (mut producer, mut consumer, _) = make_test_producer(&ring);
1006
1007        // Submit and complete 2 entries
1008        send_readonly(&mut producer, b"x");
1009        let tok_rw = send_readwrite(&mut producer, b"y", 64);
1010
1011        let (_, reply1) = poll_received(&mut consumer);
1012        consumer.complete(reply1).unwrap();
1013
1014        let (_, reply2) = poll_received(&mut consumer);
1015        let ReplyChain::Writable(mut wc) = reply2 else {
1016            panic!("expected writable");
1017        };
1018        wc.write_all(b"reply").unwrap();
1019        consumer.complete(wc).unwrap();
1020
1021        // poll() consumes first recv directly from ring
1022        let used1 = producer.poll().unwrap().unwrap();
1023        assert!(matches!(used1, UsedChain::Ack(_)));
1024
1025        // reclaim() buffers second recv
1026        let count = producer.reclaim().unwrap();
1027        assert_eq!(count, 1);
1028
1029        // poll() returns the buffered one
1030        let used2 = producer.poll().unwrap().unwrap();
1031        assert_eq!(used2.token(), tok_rw);
1032        assert_eq!(used2.to_bytes().unwrap().as_ref(), b"reply");
1033    }
1034
1035    /// reclaim + submit must not cause token collisions.
1036    #[test]
1037    fn test_reclaim_submit_no_token_collision() {
1038        let ring = make_ring(8);
1039        let (mut producer, mut consumer, _) = make_test_producer(&ring);
1040
1041        // Submit and complete a ReadOnly recv
1042        let tok_old = send_readonly(&mut producer, b"log");
1043
1044        let (_, reply) = poll_received(&mut consumer);
1045        consumer.complete(reply).unwrap();
1046
1047        let count = producer.reclaim().unwrap();
1048        assert_eq!(count, 1);
1049
1050        // Submit a new ReadWrite recv - may reuse the same descriptor ID
1051        let tok_new = send_readwrite(&mut producer, b"call", 64);
1052
1053        // Tokens must differ even if the descriptor ID was recycled
1054        assert_ne!(
1055            tok_old, tok_new,
1056            "tokens must be unique across reclaim/submit cycles"
1057        );
1058
1059        // Complete the ReadWrite recv
1060        let (_, reply) = poll_received(&mut consumer);
1061        let ReplyChain::Writable(mut wc) = reply else {
1062            panic!("expected writable");
1063        };
1064        wc.write_all(b"result").unwrap();
1065        consumer.complete(wc).unwrap();
1066
1067        // Poll returns only the RW reply (RO was discarded by reclaim)
1068        let used = producer.poll().unwrap().unwrap();
1069        assert_eq!(used.token(), tok_new);
1070        assert_eq!(used.to_bytes().unwrap().as_ref(), b"result");
1071
1072        // No stale RO reply in the queue
1073        assert!(producer.poll().unwrap().is_none());
1074    }
1075
1076    /// Verify that repeated oneshot submit/reclaim cycles do not accumulate pending completions.
1077    #[test]
1078    fn test_reclaim_readonly_does_not_leak_pending() {
1079        let ring = make_ring(4);
1080        let (mut producer, mut consumer, _) = make_test_producer(&ring);
1081
1082        for _ in 0..10 {
1083            // Fill the ring
1084            for _ in 0..4 {
1085                send_readonly(&mut producer, b"msg");
1086            }
1087
1088            // Consumer acks all
1089            while let Some(result) = consumer.poll(1024).unwrap() {
1090                let (_, reply) = result;
1091                consumer.complete(reply).unwrap();
1092            }
1093
1094            // Reclaim frees ring slots; empty completions are discarded
1095            let count = producer.reclaim().unwrap();
1096            assert_eq!(count, 4);
1097
1098            // No completions should be buffered in pending
1099            assert!(
1100                producer.poll().unwrap().is_none(),
1101                "pending should be empty after reclaiming RO entries"
1102            );
1103        }
1104    }
1105}