#![allow(clippy::indexing_slicing)]
use core::sync::atomic::{Ordering, fence};
const VRING_DESC_F_WRITE: u16 = crate::bit!(1);
#[repr(C)]
#[derive(Copy, Clone)]
struct VirtqDesc {
addr: u64,
len: u32,
flags: u16,
next: u16,
}
impl VirtqDesc {
const ZERO: Self = Self { addr: 0, len: 0, flags: 0, next: 0 };
}
#[repr(C, align(16))]
struct DescTable<const N: usize>([VirtqDesc; N]);
impl<const N: usize> DescTable<N> {
const fn new() -> Self {
Self([VirtqDesc::ZERO; N])
}
}
#[repr(C, align(2))]
struct AvailRing<const N: usize> {
flags: u16,
idx: u16,
ring: [u16; N],
used_event: u16,
}
impl<const N: usize> AvailRing<N> {
const fn new() -> Self {
Self { flags: 0, idx: 0, ring: [0; N], used_event: 0 }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
struct VirtqUsedElem {
id: u32,
len: u32,
}
impl VirtqUsedElem {
const ZERO: Self = Self { id: 0, len: 0 };
}
#[repr(C, align(4))]
struct UsedRing<const N: usize> {
flags: u16,
idx: u16,
ring: [VirtqUsedElem; N],
avail_event: u16,
}
impl<const N: usize> UsedRing<N> {
const fn new() -> Self {
Self { flags: 0, idx: 0, ring: [VirtqUsedElem::ZERO; N], avail_event: 0 }
}
}
#[repr(C, align(16))]
pub(super) struct VirtQueue<const N: usize, const B: usize> {
desc: DescTable<N>,
avail: AvailRing<N>,
used: UsedRing<N>,
buffers: [[u8; B]; N],
last_used_idx: u16,
next_desc: u16,
rx_held: Option<(u16, u32)>,
}
impl<const N: usize, const B: usize> VirtQueue<N, B> {
const VALIDATE_QUEUE: () = {
assert!(N > 0, "VirtQueue: N must be greater than zero");
assert!(B > 0, "VirtQueue: B must be greater than zero");
assert!(N.is_power_of_two(), "VirtQueue: N must be a power of two");
assert!(N <= 0x8000, "VirtQueue: N must be <= 0x8000");
};
pub(super) const fn new() -> Self {
let () = Self::VALIDATE_QUEUE;
Self {
desc: DescTable::new(),
avail: AvailRing::new(),
used: UsedRing::new(),
buffers: [[0; B]; N],
last_used_idx: 0,
next_desc: 0,
rx_held: None,
}
}
pub(super) fn rx_clean(&mut self) {
for i in 0..N {
self.rx_post(i as u16);
}
}
pub(super) fn rx_peek(&mut self) -> Option<&[u8]> {
if self.rx_held.is_none() {
loop {
let (desc, len) = self.rx_pop()?;
if len == 0 {
self.rx_post(desc);
continue;
}
let len = core::cmp::min(len as usize, B) as u32;
self.rx_held = Some((desc, len));
break;
}
}
let (desc, len) = self.rx_held?;
let idx = (desc as usize) % N;
Some(&self.buffers[idx][..len as usize])
}
pub(super) fn rx_release(&mut self) {
if let Some((desc, _)) = self.rx_held.take() {
self.rx_post(desc);
}
}
fn rx_post(&mut self, desc_idx: u16) {
let idx = (desc_idx as usize) % N;
let addr = self.buffers[idx].as_ptr() as u64;
self.desc.0[idx] = VirtqDesc { addr, len: B as u32, flags: VRING_DESC_F_WRITE, next: 0 };
self.publish_avail(desc_idx);
}
fn rx_pop(&mut self) -> Option<(u16, u32)> {
fence(Ordering::SeqCst);
if self.last_used_idx == self.used.idx {
return None;
}
let elem_pos = (self.last_used_idx as usize) % N;
let elem = self.used.ring[elem_pos];
self.last_used_idx = self.last_used_idx.wrapping_add(1);
Some((elem.id as u16, elem.len))
}
pub(super) fn tx_try_submit(&mut self, data: &[u8]) -> Option<usize> {
if data.is_empty() {
return Some(0);
}
let desc_idx = (self.next_desc as usize) % N;
if self.desc.0[desc_idx].len != 0 {
self.tx_reap();
if self.desc.0[desc_idx].len != 0 {
return None;
}
}
let take = core::cmp::min(B, data.len());
self.buffers[desc_idx][..take].copy_from_slice(&data[..take]);
let buf_addr = self.buffers[desc_idx].as_ptr() as u64;
self.desc.0[desc_idx] = VirtqDesc { addr: buf_addr, len: take as u32, flags: 0, next: 0 };
self.publish_avail(desc_idx as u16);
self.next_desc = self.next_desc.wrapping_add(1);
Some(take)
}
fn tx_reap(&mut self) {
fence(Ordering::SeqCst);
let used_idx = self.used.idx;
while self.last_used_idx != used_idx {
let elem_pos = (self.last_used_idx as usize) % N;
let elem = self.used.ring[elem_pos];
let d = (elem.id as usize) % N;
self.desc.0[d].len = 0;
self.last_used_idx = self.last_used_idx.wrapping_add(1);
}
}
pub(super) fn desc_addr(&self) -> u64 {
self.desc.0.as_ptr() as u64
}
pub(super) fn avail_addr(&self) -> u64 {
core::ptr::from_ref(&self.avail) as u64
}
pub(super) fn used_addr(&self) -> u64 {
core::ptr::from_ref(&self.used) as u64
}
fn publish_avail(&mut self, desc_idx: u16) {
let avail_idx = self.avail.idx;
let ring_pos = (avail_idx as usize) % N;
self.avail.ring[ring_pos] = desc_idx;
fence(Ordering::SeqCst);
self.avail.idx = avail_idx.wrapping_add(1);
fence(Ordering::SeqCst);
}
}
#[cfg(test)]
#[coverage(off)]
impl<const N: usize, const B: usize> VirtQueue<N, B> {
pub(super) fn test_drain_tx(&mut self) -> alloc::vec::Vec<alloc::vec::Vec<u8>> {
use alloc::vec::Vec;
fence(Ordering::SeqCst);
let mut out = Vec::new();
while self.used.idx != self.avail.idx {
let pos = (self.used.idx as usize) % N;
let desc_idx = self.avail.ring[pos];
let i = (desc_idx as usize) % N;
let len = self.desc.0[i].len as usize;
out.push(self.buffers[i][..len].to_vec());
self.used.ring[pos] = VirtqUsedElem { id: desc_idx as u32, len: len as u32 };
fence(Ordering::SeqCst);
self.used.idx = self.used.idx.wrapping_add(1);
}
fence(Ordering::SeqCst);
out
}
pub(super) fn test_supply_rx(&mut self, data: &[u8]) -> Option<usize> {
fence(Ordering::SeqCst);
if self.used.idx == self.avail.idx {
return None;
}
let pos = (self.used.idx as usize) % N;
let desc_idx = self.avail.ring[pos];
let i = (desc_idx as usize) % N;
let take = core::cmp::min(B, data.len());
self.buffers[i][..take].copy_from_slice(&data[..take]);
self.used.ring[pos] = VirtqUsedElem { id: desc_idx as u32, len: take as u32 };
fence(Ordering::SeqCst);
self.used.idx = self.used.idx.wrapping_add(1);
fence(Ordering::SeqCst);
Some(take)
}
}