use alloc::sync::Arc;
use axaddrspace::GuestMemoryAccessor;
use axvm_types::GuestPhysAddr;
use mbarrier::mb;
use crate::{
constants::*,
error::{VirtioError, VirtioResult},
memory::GuestMemory,
};
#[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 {
self.base_addr + Self::layout_size(self.size) - 2
}
pub(crate) const fn layout_size(size: u16) -> usize {
core::mem::size_of::<VirtQueueUsed>()
+ (size as usize) * core::mem::size_of::<VirtqUsedElem>()
+ 2
}
pub fn total_size(&self) -> usize {
Self::layout_size(self.size)
}
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<()> {
let accessor = self.accessor.clone();
let mut memory = crate::AddressSpaceMemory::new(&*accessor);
self.add_used_with_memory(id, len, &mut memory)
}
pub fn add_used_with_memory(
&mut self,
id: u32,
len: u32,
memory: &mut dyn GuestMemory,
) -> VirtioResult<()> {
self.add_used_with_memory_and_barrier(id, len, memory, mb)
}
fn add_used_with_memory_and_barrier(
&mut self,
id: u32,
len: u32,
memory: &mut dyn GuestMemory,
barrier: impl FnOnce(),
) -> 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);
let mut bytes = [0u8; 8];
bytes[0..4].copy_from_slice(&used_elem.id.to_le_bytes());
bytes[4..8].copy_from_slice(&used_elem.len.to_le_bytes());
memory.write(elem_addr, &bytes)?;
self.used_idx = self.used_idx.wrapping_add(1);
barrier();
self.write_used_idx_with_memory(memory)?;
Ok(())
}
pub fn write_used_idx(&self) -> VirtioResult<()> {
let mut memory = crate::AddressSpaceMemory::new(&*self.accessor);
self.write_used_idx_with_memory(&mut memory)
}
pub fn write_used_idx_with_memory(&self, memory: &mut dyn GuestMemory) -> VirtioResult<()> {
if !self.is_valid() {
return Err(VirtioError::QueueNotReady);
}
let idx_addr = self.base_addr + 2;
memory.write(idx_addr, &self.used_idx.to_le_bytes())?;
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(())
}
pub(crate) fn set_notification_with_memory(
&self,
suppress: bool,
memory: &mut dyn GuestMemory,
) -> VirtioResult<()> {
if !self.is_valid() {
return Err(VirtioError::QueueNotReady);
}
let flags = if suppress { VIRTQ_USED_F_NO_NOTIFY } else { 0 };
memory.write(self.base_addr, &flags.to_le_bytes())
}
pub(crate) fn write_avail_event_with_memory(
&self,
event: u16,
memory: &mut dyn GuestMemory,
) -> VirtioResult<()> {
if !self.is_valid() {
return Err(VirtioError::QueueNotReady);
}
memory.write(self.avail_event_addr(), &event.to_le_bytes())
}
}
#[cfg(test)]
mod tests {
use alloc::{rc::Rc, vec::Vec};
use core::cell::RefCell;
use axvm_types::GuestPhysAddr;
use super::*;
use crate::{GuestMemory, NoGuestMemoryAccessor};
struct RecordingMemory {
events: Rc<RefCell<Vec<&'static str>>>,
}
impl GuestMemory for RecordingMemory {
fn read(&mut self, _: GuestPhysAddr, _: &mut [u8]) -> VirtioResult<()> {
Err(VirtioError::InvalidAddress)
}
fn write(&mut self, address: GuestPhysAddr, _: &[u8]) -> VirtioResult<()> {
self.events
.borrow_mut()
.push(if address.as_usize() == 0x1002 {
"used_idx"
} else {
"used_elem"
});
Ok(())
}
}
#[test]
fn publishes_used_element_before_used_index() {
let events = Rc::new(RefCell::new(Vec::new()));
let mut memory = RecordingMemory {
events: events.clone(),
};
let mut ring = UsedRing::new(
GuestPhysAddr::from(0x1000),
1,
alloc::sync::Arc::new(NoGuestMemoryAccessor),
);
ring.add_used_with_memory_and_barrier(7, 11, &mut memory, || {
events.borrow_mut().push("barrier");
})
.unwrap();
assert_eq!(&*events.borrow(), &["used_elem", "barrier", "used_idx"]);
}
#[test]
fn layout_size_counts_header_elements_and_footer() {
assert_eq!(UsedRing::<NoGuestMemoryAccessor>::layout_size(4), 38);
assert_eq!(
UsedRing::<NoGuestMemoryAccessor>::layout_size(256),
4 + 256 * 8 + 2
);
}
}