use crate::constants::*;
use crate::error::{VirtioError, VirtioResult};
use alloc::sync::Arc;
use axaddrspace::{GuestMemoryAccessor, GuestPhysAddr};
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct VirtqUsedElem {
pub id: u32,
pub len: u32,
}
impl VirtqUsedElem {
pub fn new(id: u32, len: u32) -> Self {
Self { id, len }
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct VirtQueueUsed {
pub flags: u16,
pub idx: u16,
}
impl VirtQueueUsed {
pub fn new() -> Self {
Self { flags: 0, idx: 0 }
}
pub fn no_notify(&self) -> bool {
(self.flags & VIRTQ_USED_F_NO_NOTIFY) != 0
}
pub fn set_no_notify(&mut self, no_notify: bool) {
if no_notify {
self.flags |= VIRTQ_USED_F_NO_NOTIFY;
} else {
self.flags &= !VIRTQ_USED_F_NO_NOTIFY;
}
}
}
#[derive(Debug, Clone)]
pub struct UsedRing<T: GuestMemoryAccessor + Clone> {
pub base_addr: GuestPhysAddr,
pub size: u16,
pub used_idx: u16,
accessor: Arc<T>,
}
impl<T: GuestMemoryAccessor + Clone> UsedRing<T> {
pub fn new(base_addr: GuestPhysAddr, size: u16, accessor: Arc<T>) -> Self {
Self {
base_addr,
size,
used_idx: 0,
accessor,
}
}
pub fn header_addr(&self) -> GuestPhysAddr {
self.base_addr
}
pub fn ring_addr(&self) -> GuestPhysAddr {
self.base_addr + core::mem::size_of::<VirtQueueUsed>()
}
pub fn ring_entry_addr(&self, index: u16) -> Option<GuestPhysAddr> {
if index >= self.size {
return None;
}
let offset = core::mem::size_of::<VirtQueueUsed>()
+ (index as usize * core::mem::size_of::<VirtqUsedElem>());
Some(self.base_addr + offset)
}
pub fn avail_event_addr(&self) -> GuestPhysAddr {
let offset = core::mem::size_of::<VirtQueueUsed>()
+ (self.size as usize * core::mem::size_of::<VirtqUsedElem>());
self.base_addr + offset
}
pub fn total_size(&self) -> usize {
core::mem::size_of::<VirtQueueUsed>()
+ (self.size as usize * core::mem::size_of::<VirtqUsedElem>())
+ 2
}
pub fn is_valid(&self) -> bool {
self.base_addr.as_usize() != 0 && self.size > 0
}
pub fn add_used(&mut self, id: u32, len: u32) -> VirtioResult<()> {
if !self.is_valid() {
return Err(VirtioError::QueueNotReady);
}
let ring_index = self.used_idx % self.size;
let elem_addr = self
.ring_entry_addr(ring_index)
.ok_or(VirtioError::InvalidQueue)?;
let used_elem = VirtqUsedElem::new(id, len);
self.accessor
.write_obj(elem_addr, used_elem)
.map_err(|_| VirtioError::InvalidAddress)?;
self.used_idx = self.used_idx.wrapping_add(1);
self.write_used_idx()?;
Ok(())
}
pub fn write_used_idx(&self) -> VirtioResult<()> {
if !self.is_valid() {
return Err(VirtioError::QueueNotReady);
}
let idx_addr = self.base_addr + 2;
self.accessor
.write_obj(idx_addr, self.used_idx)
.map_err(|_| VirtioError::InvalidAddress)?;
Ok(())
}
pub fn read_used_header(&self) -> VirtioResult<VirtQueueUsed> {
if !self.is_valid() {
return Err(VirtioError::QueueNotReady);
}
self.accessor
.read_obj(self.base_addr)
.map_err(|_| VirtioError::InvalidAddress)
}
pub fn write_used_header(&self, header: &VirtQueueUsed) -> VirtioResult<()> {
if !self.is_valid() {
return Err(VirtioError::QueueNotReady);
}
self.accessor
.write_obj(self.base_addr, *header)
.map_err(|_| VirtioError::InvalidAddress)
}
pub fn get_used_idx(&self) -> u16 {
self.used_idx
}
pub fn set_used_idx(&mut self, idx: u16) {
self.used_idx = idx;
}
pub fn should_notify(&self) -> VirtioResult<bool> {
if !self.is_valid() {
return Err(VirtioError::QueueNotReady);
}
let header = self.read_used_header()?;
Ok(!header.no_notify())
}
pub fn set_notification(&self, suppress: bool) -> VirtioResult<()> {
if !self.is_valid() {
return Err(VirtioError::QueueNotReady);
}
let mut header = self.read_used_header()?;
header.set_no_notify(suppress);
self.write_used_header(&header)?;
Ok(())
}
}