Skip to main content

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