use crate::constants::*;
use crate::error::{VirtioError, VirtioResult};
use alloc::sync::Arc;
use axaddrspace::GuestMemoryAccessor;
use axaddrspace::GuestPhysAddr;
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct VirtQueueAvail {
pub flags: u16,
pub idx: u16,
}
impl VirtQueueAvail {
pub fn new() -> Self {
Self { flags: 0, idx: 0 }
}
pub fn no_interrupt(&self) -> bool {
(self.flags & VIRTQ_AVAIL_F_NO_INTERRUPT) != 0
}
pub fn set_no_interrupt(&mut self, no_interrupt: bool) {
if no_interrupt {
self.flags |= VIRTQ_AVAIL_F_NO_INTERRUPT;
} else {
self.flags &= !VIRTQ_AVAIL_F_NO_INTERRUPT;
}
}
}
#[derive(Debug, Clone)]
pub struct AvailableRing<T: GuestMemoryAccessor + Clone> {
pub base_addr: GuestPhysAddr,
pub size: u16,
pub last_avail_idx: u16,
accessor: Arc<T>,
}
impl<T: GuestMemoryAccessor + Clone> AvailableRing<T> {
pub fn new(base_addr: GuestPhysAddr, size: u16, accessor: Arc<T>) -> Self {
Self {
base_addr,
size,
last_avail_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::<VirtQueueAvail>()
}
pub fn ring_entry_addr(&self, index: u16) -> Option<GuestPhysAddr> {
if index >= self.size {
return None;
}
let offset = core::mem::size_of::<VirtQueueAvail>() + (index as usize * 2);
Some(self.base_addr + offset)
}
pub fn used_event_addr(&self) -> GuestPhysAddr {
let offset = core::mem::size_of::<VirtQueueAvail>() + (self.size as usize * 2);
self.base_addr + offset
}
pub fn total_size(&self) -> usize {
core::mem::size_of::<VirtQueueAvail>() + (self.size as usize * 2) + 2
}
pub fn is_valid(&self) -> bool {
self.base_addr.as_usize() != 0 && self.size > 0
}
pub fn has_new_avail(&self, current_idx: u16) -> bool {
current_idx != self.last_avail_idx
}
pub fn update_last_avail_idx(&mut self, idx: u16) {
self.last_avail_idx = idx;
}
pub fn read_avail_header(&self) -> VirtioResult<VirtQueueAvail> {
if !self.is_valid() {
return Err(VirtioError::QueueNotReady);
}
self.accessor
.read_obj(self.base_addr)
.map_err(|_| VirtioError::InvalidAddress)
}
pub fn write_avail_header(&self, header: &VirtQueueAvail) -> VirtioResult<()> {
if !self.is_valid() {
return Err(VirtioError::QueueNotReady);
}
self.accessor
.write_obj(self.base_addr, header)
.map_err(|_| VirtioError::InvalidAddress)
}
pub fn read_avail_idx(&self) -> VirtioResult<u16> {
if !self.is_valid() {
return Err(VirtioError::QueueNotReady);
}
let idx_addr = self.base_addr + 2;
self.accessor
.read_obj(idx_addr)
.map_err(|_| VirtioError::InvalidAddress)
}
pub fn get_avail_idx(&self) -> VirtioResult<u16> {
self.read_avail_idx()
}
pub fn read_avail_ring_entry(&self, ring_index: u16) -> VirtioResult<u16> {
if !self.is_valid() {
return Err(VirtioError::QueueNotReady);
}
let entry_addr = self
.ring_entry_addr(ring_index % self.size)
.ok_or(VirtioError::InvalidQueue)?;
self.accessor
.read_obj(entry_addr)
.map_err(|_| VirtioError::InvalidAddress)
}
pub fn write_avail_ring_entry(&self, ring_index: u16, desc_index: u16) -> VirtioResult<()> {
if !self.is_valid() {
return Err(VirtioError::QueueNotReady);
}
let entry_addr = self
.ring_entry_addr(ring_index % self.size)
.ok_or(VirtioError::InvalidQueue)?;
self.accessor
.write_obj(entry_addr, desc_index)
.map_err(|_| VirtioError::InvalidAddress)?;
Ok(())
}
pub fn get_available_count(&self) -> VirtioResult<u16> {
let current_idx = self.read_avail_idx()?;
Ok(current_idx.wrapping_sub(self.last_avail_idx))
}
pub fn interrupts_suppressed(&self) -> VirtioResult<bool> {
let header = self.read_avail_header()?;
Ok(header.no_interrupt())
}
pub fn set_interrupt_suppression(&self, suppress: bool) -> VirtioResult<()> {
let mut header = self.read_avail_header()?;
header.set_no_interrupt(suppress);
self.write_avail_header(&header)?;
Ok(())
}
pub fn read_used_event(&self) -> VirtioResult<u16> {
if !self.is_valid() {
return Err(VirtioError::QueueNotReady);
}
let event_addr = self.used_event_addr();
self.accessor
.read_obj(event_addr)
.map_err(|_| VirtioError::InvalidAddress)
}
pub fn write_used_event(&self, event: u16) -> VirtioResult<()> {
if !self.is_valid() {
return Err(VirtioError::QueueNotReady);
}
let event_addr = self.used_event_addr();
self.accessor
.write_obj(event_addr, event)
.map_err(|_| VirtioError::InvalidAddress)
}
}