| // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause |
| |
| //! Utilities used by unit tests and benchmarks for mocking the driver side |
| //! of the virtio protocol. |
| |
| use std::marker::PhantomData; |
| use std::mem::size_of; |
| |
| use vm_memory::{ |
| Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestUsize, |
| }; |
| |
| use crate::defs::{VIRTQ_AVAIL_ELEMENT_SIZE, VIRTQ_AVAIL_RING_HEADER_SIZE}; |
| use crate::{Descriptor, DescriptorChain, Error, Queue, QueueOwnedT, QueueT, VirtqUsedElem}; |
| use std::fmt::{self, Debug, Display}; |
| use virtio_bindings::bindings::virtio_ring::{VRING_DESC_F_INDIRECT, VRING_DESC_F_NEXT}; |
| |
| /// Mock related errors. |
| #[derive(Debug)] |
| pub enum MockError { |
| /// Cannot create the Queue object due to invalid parameters. |
| InvalidQueueParams(Error), |
| /// Invalid Ref index |
| InvalidIndex, |
| /// Invalid next avail |
| InvalidNextAvail, |
| /// Guest memory errors |
| GuestMem(GuestMemoryError), |
| } |
| |
| impl Display for MockError { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| use self::MockError::*; |
| |
| match self { |
| InvalidQueueParams(_) => write!(f, "cannot create queue due to invalid parameter"), |
| InvalidIndex => write!( |
| f, |
| "invalid index for pointing to an address in a region when defining a Ref object" |
| ), |
| InvalidNextAvail => write!( |
| f, |
| "invalid next available descriptor chain head in the queue" |
| ), |
| GuestMem(e) => write!(f, "guest memory error: {}", e), |
| } |
| } |
| } |
| |
| impl std::error::Error for MockError {} |
| |
| /// Wrapper struct used for accessing a particular address of a GuestMemory area. |
| pub struct Ref<'a, M, T> { |
| mem: &'a M, |
| addr: GuestAddress, |
| phantom: PhantomData<*const T>, |
| } |
| |
| impl<'a, M: GuestMemory, T: ByteValued> Ref<'a, M, T> { |
| fn new(mem: &'a M, addr: GuestAddress) -> Self { |
| Ref { |
| mem, |
| addr, |
| phantom: PhantomData, |
| } |
| } |
| |
| /// Read an object of type T from the underlying memory found at self.addr. |
| pub fn load(&self) -> T { |
| self.mem.read_obj(self.addr).unwrap() |
| } |
| |
| /// Write an object of type T from the underlying memory found at self.addr. |
| pub fn store(&self, val: T) { |
| self.mem.write_obj(val, self.addr).unwrap() |
| } |
| } |
| |
| /// Wrapper struct used for accessing a subregion of a GuestMemory area. |
| pub struct ArrayRef<'a, M, T> { |
| mem: &'a M, |
| addr: GuestAddress, |
| len: usize, |
| phantom: PhantomData<*const T>, |
| } |
| |
| impl<'a, M: GuestMemory, T: ByteValued> ArrayRef<'a, M, T> { |
| fn new(mem: &'a M, addr: GuestAddress, len: usize) -> Self { |
| ArrayRef { |
| mem, |
| addr, |
| len, |
| phantom: PhantomData, |
| } |
| } |
| |
| /// Return a `Ref` object pointing to an address defined by a particular |
| /// index offset in the region. |
| pub fn ref_at(&self, index: usize) -> Result<Ref<'a, M, T>, MockError> { |
| if index >= self.len { |
| return Err(MockError::InvalidIndex); |
| } |
| |
| let addr = self |
| .addr |
| .checked_add((index * size_of::<T>()) as u64) |
| .unwrap(); |
| |
| Ok(Ref::new(self.mem, addr)) |
| } |
| } |
| |
| /// Represents a virtio queue ring. The only difference between the used and available rings, |
| /// is the ring element type. |
| pub struct SplitQueueRing<'a, M, T: ByteValued> { |
| flags: Ref<'a, M, u16>, |
| // The value stored here should more precisely be a `Wrapping<u16>`, but that would require a |
| // `ByteValued` impl for this type, which is not provided in vm-memory. Implementing the trait |
| // here would require defining a wrapper for `Wrapping<u16>` and that would be too much for a |
| // mock framework that is only used in tests. |
| idx: Ref<'a, M, u16>, |
| ring: ArrayRef<'a, M, T>, |
| // `used_event` for `AvailRing`, `avail_event` for `UsedRing`. |
| event: Ref<'a, M, u16>, |
| } |
| |
| impl<'a, M: GuestMemory, T: ByteValued> SplitQueueRing<'a, M, T> { |
| /// Create a new `SplitQueueRing` instance |
| pub fn new(mem: &'a M, base: GuestAddress, len: u16) -> Self { |
| let event_addr = base |
| .checked_add(4) |
| .and_then(|a| a.checked_add((size_of::<u16>() * len as usize) as u64)) |
| .unwrap(); |
| |
| let split_queue_ring = SplitQueueRing { |
| flags: Ref::new(mem, base), |
| idx: Ref::new(mem, base.checked_add(2).unwrap()), |
| ring: ArrayRef::new(mem, base.checked_add(4).unwrap(), len as usize), |
| event: Ref::new(mem, event_addr), |
| }; |
| |
| split_queue_ring.flags.store(0); |
| split_queue_ring.idx.store(0); |
| split_queue_ring.event.store(0); |
| |
| split_queue_ring |
| } |
| |
| /// Return the starting address of the `SplitQueueRing`. |
| pub fn start(&self) -> GuestAddress { |
| self.ring.addr |
| } |
| |
| /// Return the end address of the `SplitQueueRing`. |
| pub fn end(&self) -> GuestAddress { |
| self.start() |
| .checked_add(self.ring.len as GuestUsize) |
| .unwrap() |
| } |
| |
| /// Return a reference to the idx field. |
| pub fn idx(&self) -> &Ref<'a, M, u16> { |
| &self.idx |
| } |
| |
| /// Return a reference to the ring field. |
| pub fn ring(&self) -> &ArrayRef<'a, M, T> { |
| &self.ring |
| } |
| } |
| |
| /// The available ring is used by the driver to offer buffers to the device. |
| pub type AvailRing<'a, M> = SplitQueueRing<'a, M, u16>; |
| /// The used ring is where the device returns buffers once it is done with them. |
| pub type UsedRing<'a, M> = SplitQueueRing<'a, M, VirtqUsedElem>; |
| |
| /// Refers to the buffers the driver is using for the device. |
| pub struct DescriptorTable<'a, M> { |
| table: ArrayRef<'a, M, Descriptor>, |
| len: u16, |
| free_descriptors: Vec<u16>, |
| } |
| |
| impl<'a, M: GuestMemory> DescriptorTable<'a, M> { |
| /// Create a new `DescriptorTable` instance |
| pub fn new(mem: &'a M, addr: GuestAddress, len: u16) -> Self { |
| let table = ArrayRef::new(mem, addr, len as usize); |
| let free_descriptors = (0..len).rev().collect(); |
| |
| DescriptorTable { |
| table, |
| len, |
| free_descriptors, |
| } |
| } |
| |
| /// Read one descriptor from the specified index. |
| pub fn load(&self, index: u16) -> Result<Descriptor, MockError> { |
| self.table |
| .ref_at(index as usize) |
| .map(|load_ref| load_ref.load()) |
| } |
| |
| /// Write one descriptor at the specified index. |
| pub fn store(&self, index: u16, value: Descriptor) -> Result<(), MockError> { |
| self.table |
| .ref_at(index as usize) |
| .map(|store_ref| store_ref.store(value)) |
| } |
| |
| /// Return the total size of the DescriptorTable in bytes. |
| pub fn total_size(&self) -> u64 { |
| (self.len as usize * size_of::<Descriptor>()) as u64 |
| } |
| |
| /// Create a chain of descriptors. |
| pub fn build_chain(&mut self, len: u16) -> Result<u16, MockError> { |
| let indices = self |
| .free_descriptors |
| .iter() |
| .copied() |
| .rev() |
| .take(usize::from(len)) |
| .collect::<Vec<_>>(); |
| |
| assert_eq!(indices.len(), len as usize); |
| |
| for (pos, index_value) in indices.iter().copied().enumerate() { |
| // Addresses and lens constant for now. |
| let mut desc = Descriptor::new(0x1000, 0x1000, 0, 0); |
| |
| // It's not the last descriptor in the chain. |
| if pos < indices.len() - 1 { |
| desc.set_flags(VRING_DESC_F_NEXT as u16); |
| desc.set_next(indices[pos + 1]); |
| } else { |
| desc.set_flags(0); |
| } |
| self.store(index_value, desc)?; |
| } |
| |
| Ok(indices[0]) |
| } |
| } |
| |
| trait GuestAddressExt { |
| fn align_up(&self, x: GuestUsize) -> GuestAddress; |
| } |
| |
| impl GuestAddressExt for GuestAddress { |
| fn align_up(&self, x: GuestUsize) -> GuestAddress { |
| Self((self.0 + (x - 1)) & !(x - 1)) |
| } |
| } |
| |
| /// A mock version of the virtio queue implemented from the perspective of the driver. |
| pub struct MockSplitQueue<'a, M> { |
| mem: &'a M, |
| len: u16, |
| desc_table_addr: GuestAddress, |
| desc_table: DescriptorTable<'a, M>, |
| avail_addr: GuestAddress, |
| avail: AvailRing<'a, M>, |
| used_addr: GuestAddress, |
| used: UsedRing<'a, M>, |
| indirect_addr: GuestAddress, |
| } |
| |
| impl<'a, M: GuestMemory> MockSplitQueue<'a, M> { |
| /// Create a new `MockSplitQueue` instance with 0 as the default guest |
| /// physical starting address. |
| pub fn new(mem: &'a M, len: u16) -> Self { |
| Self::create(mem, GuestAddress(0), len) |
| } |
| |
| /// Create a new `MockSplitQueue` instance. |
| pub fn create(mem: &'a M, start: GuestAddress, len: u16) -> Self { |
| const AVAIL_ALIGN: GuestUsize = 2; |
| const USED_ALIGN: GuestUsize = 4; |
| |
| let desc_table_addr = start; |
| let desc_table = DescriptorTable::new(mem, desc_table_addr, len); |
| |
| let avail_addr = start |
| .checked_add(16 * len as GuestUsize) |
| .unwrap() |
| .align_up(AVAIL_ALIGN); |
| let avail = AvailRing::new(mem, avail_addr, len); |
| |
| let used_addr = avail.end().align_up(USED_ALIGN); |
| let used = UsedRing::new(mem, used_addr, len); |
| |
| let indirect_addr = GuestAddress(0x3000_0000); |
| |
| MockSplitQueue { |
| mem, |
| len, |
| desc_table_addr, |
| desc_table, |
| avail_addr, |
| avail, |
| used_addr, |
| used, |
| indirect_addr, |
| } |
| } |
| |
| /// Return the starting address of the queue. |
| pub fn start(&self) -> GuestAddress { |
| self.desc_table_addr |
| } |
| |
| /// Return the end address of the queue. |
| pub fn end(&self) -> GuestAddress { |
| self.used.end() |
| } |
| |
| /// Descriptor table accessor. |
| pub fn desc_table(&self) -> &DescriptorTable<'a, M> { |
| &self.desc_table |
| } |
| |
| /// Available ring accessor. |
| pub fn avail(&self) -> &AvailRing<M> { |
| &self.avail |
| } |
| |
| /// Used ring accessor. |
| pub fn used(&self) -> &UsedRing<M> { |
| &self.used |
| } |
| |
| /// Return the starting address of the descriptor table. |
| pub fn desc_table_addr(&self) -> GuestAddress { |
| self.desc_table_addr |
| } |
| |
| /// Return the starting address of the available ring. |
| pub fn avail_addr(&self) -> GuestAddress { |
| self.avail_addr |
| } |
| |
| /// Return the starting address of the used ring. |
| pub fn used_addr(&self) -> GuestAddress { |
| self.used_addr |
| } |
| |
| fn update_avail_idx(&mut self, value: u16) -> Result<(), MockError> { |
| let avail_idx = self.avail.idx.load(); |
| self.avail.ring.ref_at(avail_idx as usize)?.store(value); |
| self.avail.idx.store(avail_idx.wrapping_add(1)); |
| Ok(()) |
| } |
| |
| fn alloc_indirect_chain(&mut self, len: u16) -> Result<GuestAddress, MockError> { |
| // To simplify things for now, we round up the table len as a multiple of 16. When this is |
| // no longer the case, we should make sure the starting address of the descriptor table |
| // we're creating below is properly aligned. |
| |
| let table_len = if len % 16 == 0 { |
| len |
| } else { |
| 16 * (len / 16 + 1) |
| }; |
| |
| let mut table = DescriptorTable::new(self.mem, self.indirect_addr, table_len); |
| let head_decriptor_index = table.build_chain(len)?; |
| // When building indirect descriptor tables, the descriptor at index 0 is supposed to be |
| // first in the resulting chain. Just making sure our logic actually makes that happen. |
| assert_eq!(head_decriptor_index, 0); |
| |
| let table_addr = self.indirect_addr; |
| self.indirect_addr = self.indirect_addr.checked_add(table.total_size()).unwrap(); |
| Ok(table_addr) |
| } |
| |
| /// Add a descriptor chain to the table. |
| pub fn add_chain(&mut self, len: u16) -> Result<(), MockError> { |
| self.desc_table |
| .build_chain(len) |
| .and_then(|head_idx| self.update_avail_idx(head_idx)) |
| } |
| |
| /// Add an indirect descriptor chain to the table. |
| pub fn add_indirect_chain(&mut self, len: u16) -> Result<(), MockError> { |
| let head_idx = self.desc_table.build_chain(1)?; |
| |
| // We just allocate the indirect table and forget about it for now. |
| let indirect_addr = self.alloc_indirect_chain(len)?; |
| |
| let mut desc = self.desc_table.load(head_idx)?; |
| desc.set_flags(VRING_DESC_F_INDIRECT as u16); |
| desc.set_addr(indirect_addr.raw_value()); |
| desc.set_len(u32::from(len) * size_of::<Descriptor>() as u32); |
| |
| self.desc_table.store(head_idx, desc)?; |
| self.update_avail_idx(head_idx) |
| } |
| |
| /// Creates a new `Queue`, using the underlying memory regions represented |
| /// by the `MockSplitQueue`. |
| pub fn create_queue<Q: QueueT>(&self) -> Result<Q, Error> { |
| let mut q = Q::new(self.len)?; |
| q.set_size(self.len); |
| q.set_ready(true); |
| // we cannot directly set the u64 address, we need to compose it from low & high. |
| q.set_desc_table_address( |
| Some(self.desc_table_addr.0 as u32), |
| Some((self.desc_table_addr.0 >> 32) as u32), |
| ); |
| q.set_avail_ring_address( |
| Some(self.avail_addr.0 as u32), |
| Some((self.avail_addr.0 >> 32) as u32), |
| ); |
| q.set_used_ring_address( |
| Some(self.used_addr.0 as u32), |
| Some((self.used_addr.0 >> 32) as u32), |
| ); |
| Ok(q) |
| } |
| |
| /// Writes multiple descriptor chains to the memory object of the queue, at the beginning of |
| /// the descriptor table, and returns the first `DescriptorChain` available. |
| pub fn build_multiple_desc_chains( |
| &self, |
| descs: &[Descriptor], |
| ) -> Result<DescriptorChain<&M>, MockError> { |
| self.add_desc_chains(descs, 0)?; |
| self.create_queue::<Queue>() |
| .map_err(MockError::InvalidQueueParams)? |
| .iter(self.mem) |
| .map_err(MockError::InvalidQueueParams)? |
| .next() |
| .ok_or(MockError::InvalidNextAvail) |
| } |
| |
| /// Writes a single descriptor chain to the memory object of the queue, at the beginning of the |
| /// descriptor table, and returns the associated `DescriptorChain` object. |
| // This method ensures the next flags and values are set properly for the desired chain, but |
| // keeps the other characteristics of the input descriptors (`addr`, `len`, other flags). |
| // TODO: make this function work with a generic queue. For now that's not possible because |
| // we cannot create the descriptor chain from an iterator as iterator is not implemented for |
| // a generic T, just for `Queue`. |
| pub fn build_desc_chain(&self, descs: &[Descriptor]) -> Result<DescriptorChain<&M>, MockError> { |
| let mut modified_descs: Vec<Descriptor> = Vec::with_capacity(descs.len()); |
| for (idx, desc) in descs.iter().enumerate() { |
| let (flags, next) = if idx == descs.len() - 1 { |
| // Clear the NEXT flag if it was set. The value of the next field of the |
| // Descriptor doesn't matter at this point. |
| (desc.flags() & !VRING_DESC_F_NEXT as u16, 0) |
| } else { |
| // Ensure that the next flag is set and that we are referring the following |
| // descriptor. This ignores any value is actually present in `desc.next`. |
| (desc.flags() | VRING_DESC_F_NEXT as u16, idx as u16 + 1) |
| }; |
| modified_descs.push(Descriptor::new(desc.addr().0, desc.len(), flags, next)); |
| } |
| self.build_multiple_desc_chains(&modified_descs[..]) |
| } |
| |
| /// Adds descriptor chains to the memory object of the queue. |
| // `descs` represents a slice of `Descriptor` objects which are used to populate the chains, and |
| // `offset` is the index in the descriptor table where the chains should be added. |
| // The descriptor chain related information is written in memory starting with address 0. |
| // The `addr` fields of the input descriptors should start at a sufficiently |
| // greater location (i.e. 1MiB, or `0x10_0000`). |
| pub fn add_desc_chains(&self, descs: &[Descriptor], offset: u16) -> Result<(), MockError> { |
| let mut new_entries = 0; |
| let avail_idx: u16 = self |
| .mem |
| .read_obj::<u16>(self.avail_addr().unchecked_add(2)) |
| .map(u16::from_le) |
| .map_err(MockError::GuestMem)?; |
| |
| for (idx, desc) in descs.iter().enumerate() { |
| let i = idx as u16 + offset; |
| self.desc_table().store(i, *desc)?; |
| |
| if idx == 0 || descs[idx - 1].flags() & VRING_DESC_F_NEXT as u16 != 1 { |
| // Update the available ring position. |
| self.mem |
| .write_obj( |
| u16::to_le(i), |
| self.avail_addr().unchecked_add( |
| VIRTQ_AVAIL_RING_HEADER_SIZE |
| + (avail_idx + new_entries) as u64 * VIRTQ_AVAIL_ELEMENT_SIZE, |
| ), |
| ) |
| .map_err(MockError::GuestMem)?; |
| new_entries += 1; |
| } |
| } |
| |
| // Increment `avail_idx`. |
| self.mem |
| .write_obj( |
| u16::to_le(avail_idx + new_entries), |
| self.avail_addr().unchecked_add(2), |
| ) |
| .map_err(MockError::GuestMem)?; |
| |
| Ok(()) |
| } |
| } |