use std::sync::Arc;
use std::sync::atomic::{Ordering, fence};
use virtio_bindings::virtio_ring;
use crate::QueueConfig;
use crate::guest_mem::GuestMemWriter;
use crate::queue::flags;
const VRING_AVAIL_F_NO_INTERRUPT: u16 = virtio_ring::VRING_AVAIL_F_NO_INTERRUPT as u16;
#[derive(Debug, Clone, Copy)]
pub struct VirtqDesc {
pub addr: u64,
pub len: u32,
pub flags: u16,
pub next: u16,
}
impl VirtqDesc {
#[must_use]
pub const fn is_write(&self) -> bool {
self.flags & flags::WRITE != 0
}
#[must_use]
pub const fn has_next(&self) -> bool {
self.flags & flags::NEXT != 0
}
}
#[derive(Debug)]
pub struct DescChain {
pub head_idx: u16,
pub descriptors: Vec<VirtqDesc>,
}
pub struct ChainIter<'a> {
queue: &'a SplitQueue,
idx: u16,
ttl: u16,
done: bool,
}
impl Iterator for ChainIter<'_> {
type Item = VirtqDesc;
fn next(&mut self) -> Option<VirtqDesc> {
if self.done || self.ttl == 0 || self.idx >= self.queue.size {
return None;
}
self.ttl -= 1;
let desc = self.queue.read_descriptor(self.idx);
if desc.has_next() {
self.idx = desc.next;
} else {
self.done = true;
}
Some(desc)
}
}
pub struct SplitQueue {
mem: Arc<GuestMemWriter>,
queue_idx: u16,
size: u16,
desc_gpa: u64,
avail_gpa: u64,
used_gpa: u64,
last_avail_idx: u16,
used_idx: u16,
event_idx: bool,
}
impl SplitQueue {
#[must_use]
pub fn new(
mem: Arc<GuestMemWriter>,
queue_idx: u16,
cfg: &QueueConfig,
event_idx: bool,
) -> Self {
let used_idx = mem.read_u16(cfg.used_addr as usize + 2);
Self {
mem,
queue_idx,
size: cfg.size,
desc_gpa: cfg.desc_addr,
avail_gpa: cfg.avail_addr,
used_gpa: cfg.used_addr,
last_avail_idx: 0,
used_idx,
event_idx,
}
}
#[must_use]
pub const fn last_avail_idx(&self) -> u16 {
self.last_avail_idx
}
pub const fn set_last_avail_idx(&mut self, idx: u16) {
self.last_avail_idx = idx;
}
#[must_use]
pub const fn queue_idx(&self) -> u16 {
self.queue_idx
}
#[must_use]
pub const fn size(&self) -> u16 {
self.size
}
#[must_use]
pub fn mem(&self) -> &GuestMemWriter {
&self.mem
}
pub fn set_event_idx(&mut self, enabled: bool) {
self.event_idx = enabled;
}
fn avail_idx(&self) -> u16 {
self.mem.read_u16(self.avail_gpa as usize + 2)
}
fn avail_ring_entry(&self, pos: u16) -> u16 {
let off = self.avail_gpa as usize + 4 + (pos % self.size) as usize * 2;
self.mem.read_u16(off)
}
fn read_descriptor(&self, idx: u16) -> VirtqDesc {
let base = self.desc_gpa as usize + idx as usize * 16;
VirtqDesc {
addr: self.mem.read_u64(base),
len: self.mem.read_u32(base + 8),
flags: self.mem.read_u16(base + 12),
next: self.mem.read_u16(base + 14),
}
}
#[must_use]
pub fn has_avail(&self) -> bool {
self.avail_idx() != self.last_avail_idx
}
pub fn pop_avail(&mut self) -> Option<DescChain> {
let head_idx = self.next_avail_head()?;
let descriptors = self.chain_iter(head_idx).collect();
Some(DescChain {
head_idx,
descriptors,
})
}
pub fn next_avail_head(&mut self) -> Option<u16> {
let avail_idx = self.avail_idx();
if avail_idx == self.last_avail_idx {
return None;
}
fence(Ordering::Acquire);
let head_idx = self.avail_ring_entry(self.last_avail_idx);
self.last_avail_idx = self.last_avail_idx.wrapping_add(1);
Some(head_idx)
}
#[must_use]
pub fn chain_iter(&self, head: u16) -> ChainIter<'_> {
ChainIter {
queue: self,
idx: head,
ttl: self.size,
done: false,
}
}
fn write_used_entry(&self, head_idx: u16, len: u32) {
let off = self.used_gpa as usize + 4 + (self.used_idx % self.size) as usize * 8;
self.mem.write_u32(off, u32::from(head_idx));
self.mem.write_u32(off + 4, len);
}
pub fn push_used(&mut self, head_idx: u16, len: u32) -> bool {
let old_used = self.used_idx;
self.write_used_entry(head_idx, len);
self.used_idx = self.used_idx.wrapping_add(1);
fence(Ordering::Release);
self.mem
.write_u16(self.used_gpa as usize + 2, self.used_idx);
self.should_notify(old_used, self.used_idx)
}
pub fn push_used_batch(&mut self, completions: &[(u16, u32)]) -> bool {
if completions.is_empty() {
return false;
}
let old_used = self.used_idx;
for &(head_idx, len) in completions {
self.write_used_entry(head_idx, len);
self.used_idx = self.used_idx.wrapping_add(1);
}
fence(Ordering::Release);
self.mem
.write_u16(self.used_gpa as usize + 2, self.used_idx);
self.should_notify(old_used, self.used_idx)
}
pub fn write_avail_event(&self) {
let off = self.used_gpa as usize + 4 + self.size as usize * 8;
self.mem.write_u16(off, self.last_avail_idx);
}
pub fn write_avail_event_current(&self) {
let avail_idx = self.avail_idx();
fence(Ordering::Release);
let off = self.used_gpa as usize + 4 + self.size as usize * 8;
self.mem.write_u16(off, avail_idx);
}
#[must_use]
pub fn enable_notification(&self) -> bool {
self.write_avail_event();
fence(Ordering::SeqCst);
self.avail_idx() != self.last_avail_idx
}
fn should_notify(&self, old_used: u16, new_used: u16) -> bool {
if old_used == new_used {
return false;
}
if self.event_idx {
fence(Ordering::SeqCst);
let used_event = self
.mem
.read_u16(self.avail_gpa as usize + 4 + self.size as usize * 2);
vring_need_event(used_event, new_used, old_used)
} else {
let avail_flags = self.mem.read_u16(self.avail_gpa as usize);
avail_flags & VRING_AVAIL_F_NO_INTERRUPT == 0
}
}
}
unsafe impl Send for SplitQueue {}
unsafe impl Sync for SplitQueue {}
fn vring_need_event(event_idx: u16, new_idx: u16, old_idx: u16) -> bool {
new_idx.wrapping_sub(event_idx).wrapping_sub(1) < new_idx.wrapping_sub(old_idx)
}
#[cfg(test)]
mod tests {
use super::*;
const RAM: usize = 0x1_0000; const SIZE: u16 = 8;
const DESC_OFF: u64 = 0x1000;
const AVAIL_OFF: u64 = 0x2000;
const USED_OFF: u64 = 0x3000;
const DATA_OFF: u64 = 0x4000;
struct TestRam {
buf: Vec<u8>,
gpa_base: u64,
}
impl TestRam {
fn new(gpa_base: u64) -> Self {
Self {
buf: vec![0u8; RAM],
gpa_base,
}
}
fn mem(&mut self) -> Arc<GuestMemWriter> {
unsafe {
Arc::new(GuestMemWriter::new(
self.buf.as_mut_ptr(),
self.buf.len(),
self.gpa_base as usize,
))
}
}
fn cfg(&self) -> QueueConfig {
QueueConfig {
desc_addr: self.gpa_base + DESC_OFF,
avail_addr: self.gpa_base + AVAIL_OFF,
used_addr: self.gpa_base + USED_OFF,
size: SIZE,
ready: true,
gpa_base: self.gpa_base,
}
}
fn off(&self, gpa: u64) -> usize {
(gpa - self.gpa_base) as usize
}
fn w16(&mut self, gpa: u64, v: u16) {
let o = self.off(gpa);
self.buf[o..o + 2].copy_from_slice(&v.to_le_bytes());
}
fn r16(&self, gpa: u64) -> u16 {
let o = (gpa - self.gpa_base) as usize;
u16::from_le_bytes([self.buf[o], self.buf[o + 1]])
}
fn r32(&self, gpa: u64) -> u32 {
let o = (gpa - self.gpa_base) as usize;
u32::from_le_bytes([
self.buf[o],
self.buf[o + 1],
self.buf[o + 2],
self.buf[o + 3],
])
}
fn write_desc(&mut self, idx: u16, addr: u64, len: u32, flags: u16, next: u16) {
let base = self.gpa_base + DESC_OFF + u64::from(idx) * 16;
let o = self.off(base);
self.buf[o..o + 8].copy_from_slice(&addr.to_le_bytes());
self.buf[o + 8..o + 12].copy_from_slice(&len.to_le_bytes());
self.buf[o + 12..o + 14].copy_from_slice(&flags.to_le_bytes());
self.buf[o + 14..o + 16].copy_from_slice(&next.to_le_bytes());
}
fn set_avail(&mut self, pos: u16, head: u16) {
self.w16(self.gpa_base + AVAIL_OFF + 4 + u64::from(pos) * 2, head);
}
fn set_avail_idx(&mut self, idx: u16) {
self.w16(self.gpa_base + AVAIL_OFF + 2, idx);
}
fn set_used_event(&mut self, v: u16) {
self.w16(self.gpa_base + AVAIL_OFF + 4 + u64::from(SIZE) * 2, v);
}
fn set_avail_flags(&mut self, v: u16) {
self.w16(self.gpa_base + AVAIL_OFF, v);
}
fn used_idx(&self) -> u16 {
self.r16(self.gpa_base + USED_OFF + 2)
}
fn used_entry(&self, slot: u16) -> (u32, u32) {
let base = self.gpa_base + USED_OFF + 4 + u64::from(slot) * 8;
(self.r32(base), self.r32(base + 4))
}
fn avail_event(&self) -> u16 {
self.r16(self.gpa_base + USED_OFF + 4 + u64::from(SIZE) * 8)
}
}
fn queue(ram: &mut TestRam, event_idx: bool) -> SplitQueue {
let cfg = ram.cfg();
SplitQueue::new(ram.mem(), 0, &cfg, event_idx)
}
#[test]
fn pop_single_descriptor() {
let mut ram = TestRam::new(0);
ram.write_desc(0, ram.gpa_base + DATA_OFF, 256, 0, 0);
ram.set_avail(0, 0);
ram.set_avail_idx(1);
let mut q = queue(&mut ram, false);
assert!(q.has_avail());
let chain = q.pop_avail().unwrap();
assert_eq!(chain.head_idx, 0);
assert_eq!(chain.descriptors.len(), 1);
assert_eq!(chain.descriptors[0].addr, ram.gpa_base + DATA_OFF);
assert_eq!(chain.descriptors[0].len, 256);
assert!(!q.has_avail());
}
#[test]
fn pop_descriptor_chain() {
let mut ram = TestRam::new(0);
ram.write_desc(0, ram.gpa_base + DATA_OFF, 16, flags::NEXT, 1);
ram.write_desc(
1,
ram.gpa_base + DATA_OFF + 16,
16,
flags::NEXT | flags::WRITE,
2,
);
ram.write_desc(2, ram.gpa_base + DATA_OFF + 32, 16, flags::WRITE, 0);
ram.set_avail(0, 0);
ram.set_avail_idx(1);
let mut q = queue(&mut ram, false);
let chain = q.pop_avail().unwrap();
assert_eq!(chain.descriptors.len(), 3);
assert!(!chain.descriptors[0].is_write());
assert!(chain.descriptors[1].is_write());
assert!(!chain.descriptors[2].has_next());
}
#[test]
fn cyclic_chain_terminates() {
let mut ram = TestRam::new(0);
ram.write_desc(0, ram.gpa_base + DATA_OFF, 16, flags::NEXT, 1);
ram.write_desc(1, ram.gpa_base + DATA_OFF + 16, 16, flags::NEXT, 0);
ram.set_avail(0, 0);
ram.set_avail_idx(1);
let mut q = queue(&mut ram, false);
let chain = q.pop_avail().unwrap();
assert!(chain.descriptors.len() <= SIZE as usize);
}
#[test]
fn out_of_range_next_stops_walk() {
let mut ram = TestRam::new(0);
ram.write_desc(0, ram.gpa_base + DATA_OFF, 16, flags::NEXT, 99);
ram.set_avail(0, 0);
ram.set_avail_idx(1);
let mut q = queue(&mut ram, false);
let chain = q.pop_avail().unwrap();
assert_eq!(chain.descriptors.len(), 1);
}
#[test]
fn chain_iter_walks_allocation_free() {
let mut ram = TestRam::new(0);
ram.write_desc(0, ram.gpa_base + DATA_OFF, 16, flags::NEXT, 1);
ram.write_desc(
1,
ram.gpa_base + DATA_OFF + 16,
32,
flags::NEXT | flags::WRITE,
2,
);
ram.write_desc(2, ram.gpa_base + DATA_OFF + 48, 64, flags::WRITE, 0);
ram.set_avail(0, 0);
ram.set_avail_idx(1);
let mut q = queue(&mut ram, false);
let head = q.next_avail_head().unwrap();
assert_eq!(head, 0);
let descs: Vec<_> = q.chain_iter(head).collect();
assert_eq!(descs.len(), 3);
assert_eq!(descs[0].len, 16);
assert!(!descs[0].is_write());
assert!(descs[1].is_write());
assert_eq!(descs[2].len, 64);
assert!(!descs[2].has_next());
assert!(q.next_avail_head().is_none());
}
#[test]
fn chain_iter_cycle_terminates() {
let mut ram = TestRam::new(0);
ram.write_desc(0, ram.gpa_base + DATA_OFF, 16, flags::NEXT, 1);
ram.write_desc(1, ram.gpa_base + DATA_OFF + 16, 16, flags::NEXT, 0);
ram.set_avail(0, 0);
ram.set_avail_idx(1);
let mut q = queue(&mut ram, false);
let head = q.next_avail_head().unwrap();
assert!(q.chain_iter(head).count() <= SIZE as usize);
}
#[test]
fn push_used_writes_entry_and_idx() {
let mut ram = TestRam::new(0);
let mut q = queue(&mut ram, false);
let notify = q.push_used(5, 1024);
assert!(notify); assert_eq!(ram.used_idx(), 1);
assert_eq!(ram.used_entry(0), (5, 1024));
}
#[test]
fn push_used_batch_single_idx_update() {
let mut ram = TestRam::new(0);
let mut q = queue(&mut ram, false);
let notify = q.push_used_batch(&[(0, 100), (1, 200), (2, 300)]);
assert!(notify);
assert_eq!(ram.used_idx(), 3);
assert_eq!(ram.used_entry(0), (0, 100));
assert_eq!(ram.used_entry(2), (2, 300));
}
#[test]
fn push_used_batch_empty_is_noop() {
let mut ram = TestRam::new(0);
let mut q = queue(&mut ram, false);
assert!(!q.push_used_batch(&[]));
assert_eq!(ram.used_idx(), 0);
}
#[test]
fn no_interrupt_flag_suppresses_without_event_idx() {
let mut ram = TestRam::new(0);
ram.set_avail_flags(VRING_AVAIL_F_NO_INTERRUPT);
let mut q = queue(&mut ram, false);
assert!(!q.push_used(0, 16));
}
#[test]
fn event_idx_suppresses_until_event_reached() {
let mut ram = TestRam::new(0);
ram.set_used_event(2);
let mut q = queue(&mut ram, true);
assert!(!q.push_used(0, 16));
assert!(!q.push_used(1, 16));
assert!(q.push_used(2, 16));
}
#[test]
fn write_avail_event_publishes_last_avail() {
let mut ram = TestRam::new(0);
ram.write_desc(0, ram.gpa_base + DATA_OFF, 16, 0, 0);
ram.set_avail(0, 0);
ram.set_avail_idx(1);
let mut q = queue(&mut ram, true);
let _ = q.pop_avail();
q.write_avail_event();
assert_eq!(ram.avail_event(), 1); }
#[test]
fn write_avail_event_current_publishes_guest_avail_idx() {
let mut ram = TestRam::new(0);
ram.write_desc(0, ram.gpa_base + DATA_OFF, 16, 0, 0);
ram.set_avail(0, 0);
ram.set_avail_idx(5);
let mut q = queue(&mut ram, true);
let _ = q.pop_avail();
q.write_avail_event_current();
assert_eq!(ram.avail_event(), 5);
}
#[test]
fn nonzero_gpa_base_resolves_correctly() {
let mut ram = TestRam::new(0x4000_0000);
ram.write_desc(0, ram.gpa_base + DATA_OFF, 512, 0, 0);
ram.set_avail(0, 0);
ram.set_avail_idx(1);
let mut q = queue(&mut ram, false);
let chain = q.pop_avail().unwrap();
assert_eq!(chain.descriptors[0].addr, 0x4000_0000 + DATA_OFF);
assert_eq!(chain.descriptors[0].len, 512);
q.push_used(0, 512);
assert_eq!(ram.used_idx(), 1);
assert_eq!(ram.used_entry(0), (0, 512));
}
#[test]
fn vring_need_event_formula() {
assert!(vring_need_event(0, 1, 0));
assert!(!vring_need_event(3, 6, 5));
assert!(vring_need_event(65535, 0, 65534));
}
}