vhost-device/vhost-device-template/src/vhu_foo.rs
Manos Pitsidianakis c0e29d0124 template: enable workspace-wide lints
Signed-off-by: Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
2025-08-04 12:30:43 +02:00

494 lines
15 KiB
Rust

// vhost device foo
//
// Copyright 2023 Linaro Ltd. All Rights Reserved.
// Viresh Kumar <viresh.kumar@linaro.org>
//
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use std::{
convert,
io::{self, Result as IoResult},
};
use log::{info, warn};
use thiserror::Error as ThisError;
use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
use vhost_user_backend::{VhostUserBackendMut, VringRwLock, VringT};
use virtio_bindings::bindings::{
virtio_config::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_1},
virtio_ring::{VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC},
};
use virtio_queue::{DescriptorChain, QueueOwnedT};
use vm_memory::{GuestAddressSpace, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap};
use vmm_sys_util::{
epoll::EventSet,
eventfd::{EventFd, EFD_NONBLOCK},
};
use crate::FooInfo;
const QUEUE_SIZE: usize = 1024;
const NUM_QUEUES: usize = 1;
type Result<T> = std::result::Result<T, Error>;
#[derive(Copy, Clone, Debug, Eq, PartialEq, ThisError)]
/// Errors related to vhost-device-foo daemon.
pub(crate) enum Error {
#[error("Failed to handle event, didn't match EPOLLIN")]
HandleEventNotEpollIn,
#[error("Failed to handle unknown event")]
HandleEventUnknown,
#[error("Descriptor not found")]
DescriptorNotFound,
#[error("Failed to send notification")]
NotificationFailed,
#[error("Failed to create new EventFd")]
EventFdFailed,
}
impl convert::From<Error> for io::Error {
fn from(e: Error) -> Self {
io::Error::other(e)
}
}
pub(crate) struct VhostUserFooBackend {
info: FooInfo,
event_idx: bool,
pub exit_event: EventFd,
mem: Option<GuestMemoryLoadGuard<GuestMemoryMmap>>,
}
type FooDescriptorChain = DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap<()>>>;
impl VhostUserFooBackend {
pub fn new(info: FooInfo) -> Result<Self> {
Ok(VhostUserFooBackend {
info,
event_idx: false,
exit_event: EventFd::new(EFD_NONBLOCK).map_err(|_| Error::EventFdFailed)?,
mem: None,
})
}
/// Process the requests in the vring and dispatch replies
fn process_requests(
&mut self,
requests: Vec<FooDescriptorChain>,
_vring: &VringRwLock,
) -> Result<()> {
if requests.is_empty() {
info!("No pending requests");
return Ok(());
}
// Iterate over each FOO request.
//
// The layout of the various structures, to be read from and written into the
// descriptor buffers, is defined in the Virtio specification for each
// protocol.
for desc_chain in requests.clone() {
let counter = self.info.counter();
let descriptors: Vec<_> = desc_chain.clone().collect();
info!(
"Request number: {} contains {} descriptors",
counter,
descriptors.len(),
);
for (i, desc) in descriptors.iter().enumerate() {
let perm = if desc.is_write_only() {
"write only"
} else {
"read only"
};
// We now can iterate over the set of descriptors and process the messages.
// There will be a number of read only descriptors containing
// messages as defined by the specification. If any replies are
// needed, the driver should have placed one or more writable
// descriptors at the end for the device to use to reply.
info!("Length of the {} descriptor@{} is: {}", perm, i, desc.len());
}
}
Ok(())
}
/// Process the requests in the vring and dispatch replies
fn process_queue(&mut self, vring: &VringRwLock) -> Result<()> {
// Collect all pending requests
let requests: Vec<_> = vring
.get_mut()
.get_queue_mut()
.iter(self.mem.as_ref().unwrap().clone())
.map_err(|_| Error::DescriptorNotFound)?
.collect();
if self.process_requests(requests, vring).is_ok() {
// Send notification once all the requests are processed
vring
.signal_used_queue()
.map_err(|_| Error::NotificationFailed)?;
}
Ok(())
}
}
/// VhostUserBackendMut trait methods
impl VhostUserBackendMut for VhostUserFooBackend {
type Vring = VringRwLock;
type Bitmap = ();
fn num_queues(&self) -> usize {
NUM_QUEUES
}
fn max_queue_size(&self) -> usize {
QUEUE_SIZE
}
fn features(&self) -> u64 {
// this matches the current libvhost defaults except VHOST_F_LOG_ALL
(1 << VIRTIO_F_VERSION_1)
| (1 << VIRTIO_F_NOTIFY_ON_EMPTY)
| (1 << VIRTIO_RING_F_INDIRECT_DESC)
| (1 << VIRTIO_RING_F_EVENT_IDX)
// Protocol features are optional and must not be defined unless required and must be
// accompanied by the supporting PROTOCOL_FEATURES bits in features.
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
}
fn protocol_features(&self) -> VhostUserProtocolFeatures {
VhostUserProtocolFeatures::MQ
}
fn set_event_idx(&mut self, enabled: bool) {
self.event_idx = enabled;
}
fn update_memory(&mut self, mem: GuestMemoryAtomic<GuestMemoryMmap>) -> IoResult<()> {
self.mem = Some(mem.memory());
Ok(())
}
fn handle_event(
&mut self,
device_event: u16,
evset: EventSet,
vrings: &[VringRwLock],
_thread_id: usize,
) -> IoResult<()> {
if evset != EventSet::IN {
return Err(Error::HandleEventNotEpollIn.into());
}
match device_event {
0 => {
let vring = &vrings[0];
if self.event_idx {
// vm-virtio's Queue implementation only checks avail_index
// once, so to properly support EVENT_IDX we need to keep
// calling process_queue() until it stops finding new
// requests on the queue.
loop {
vring.disable_notification().unwrap();
self.process_queue(vring)?;
if !vring.enable_notification().unwrap() {
break;
}
}
} else {
// Without EVENT_IDX, a single call is enough.
self.process_queue(vring)?;
}
}
_ => {
warn!("unhandled device_event: {device_event}");
return Err(Error::HandleEventUnknown.into());
}
}
Ok(())
}
fn exit_event(&self, _thread_index: usize) -> Option<EventFd> {
self.exit_event.try_clone().ok()
}
}
#[cfg(test)]
mod tests {
use std::mem::size_of;
use vhost_user_backend::{VhostUserBackendMut, VringRwLock, VringT};
use virtio_bindings::bindings::virtio_ring::{VRING_DESC_F_NEXT, VRING_DESC_F_WRITE};
use virtio_queue::{
desc::{split::Descriptor as SplitDescriptor, RawDescriptor},
mock::MockSplitQueue,
Queue,
};
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemoryAtomic, GuestMemoryMmap};
use super::*;
#[derive(Copy, Clone, Default)]
#[repr(C)]
struct VirtioFooOutHdr {
a: u16,
b: u16,
c: u32,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for VirtioFooOutHdr {}
#[derive(Copy, Clone, Default)]
#[repr(C)]
struct VirtioFooInHdr {
d: u8,
}
// SAFETY: The layout of the structure is fixed and can be initialized by
// reading its content from byte array.
unsafe impl ByteValued for VirtioFooInHdr {}
fn init() -> (
VhostUserFooBackend,
GuestMemoryAtomic<GuestMemoryMmap>,
VringRwLock,
) {
let backend = VhostUserFooBackend::new(FooInfo::new()).unwrap();
let mem = GuestMemoryAtomic::new(
GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(),
);
let vring = VringRwLock::new(mem.clone(), 16).unwrap();
(backend, mem, vring)
}
// Prepares a single chain of descriptors
fn prepare_descriptors(
mut next_addr: u64,
mem: &GuestMemoryLoadGuard<GuestMemoryMmap<()>>,
buf: &mut [u8],
) -> Vec<SplitDescriptor> {
let mut descriptors = Vec::new();
let mut index = 0;
// Out header descriptor
let out_hdr = VirtioFooOutHdr {
a: 0x10,
b: 0x11,
c: 0x20,
};
let desc_out = SplitDescriptor::new(
next_addr,
size_of::<VirtioFooOutHdr>() as u32,
VRING_DESC_F_NEXT as u16,
index + 1,
);
next_addr += u64::from(desc_out.len());
index += 1;
mem.write_obj::<VirtioFooOutHdr>(out_hdr, desc_out.addr())
.unwrap();
descriptors.push(desc_out);
// Buf descriptor: optional
if !buf.is_empty() {
let desc_buf = SplitDescriptor::new(
next_addr,
buf.len() as u32,
(VRING_DESC_F_WRITE | VRING_DESC_F_NEXT) as u16,
index + 1,
);
next_addr += u64::from(desc_buf.len());
mem.write(buf, desc_buf.addr()).unwrap();
descriptors.push(desc_buf);
}
// In response descriptor
let desc_in = SplitDescriptor::new(
next_addr,
size_of::<VirtioFooInHdr>() as u32,
VRING_DESC_F_WRITE as u16,
0,
);
descriptors.push(desc_in);
descriptors
}
// Prepares a single chain of descriptors
fn prepare_desc_chain(buf: &mut [u8]) -> (VhostUserFooBackend, VringRwLock) {
let (mut backend, mem, vring) = init();
let mem_handle = mem.memory();
let vq = MockSplitQueue::new(&*mem_handle, 16);
let next_addr = vq.desc_table().total_size() + 0x100;
let descriptors = prepare_descriptors(next_addr, &mem_handle, buf);
vq.build_desc_chain(
&descriptors
.into_iter()
.map(RawDescriptor::from)
.collect::<Vec<RawDescriptor>>(),
)
.unwrap();
// Put the descriptor index 0 in the first available ring position.
mem_handle
.write_obj(0u16, vq.avail_addr().unchecked_add(4))
.unwrap();
// Set `avail_idx` to 1.
mem_handle
.write_obj(1u16, vq.avail_addr().unchecked_add(2))
.unwrap();
vring.set_queue_size(16);
vring
.set_queue_info(vq.desc_table_addr().0, vq.avail_addr().0, vq.used_addr().0)
.unwrap();
vring.set_queue_ready(true);
backend.update_memory(mem).unwrap();
(backend, vring)
}
// Prepares a chain of descriptors
fn prepare_desc_chains(
mem: &GuestMemoryAtomic<GuestMemoryMmap>,
buf: &mut [u8],
) -> FooDescriptorChain {
let mem_handle = mem.memory();
let vq = MockSplitQueue::new(&*mem_handle, 16);
let next_addr = vq.desc_table().total_size() + 0x100;
let descriptors = prepare_descriptors(next_addr, &mem_handle, buf);
for (idx, desc) in descriptors.iter().enumerate() {
vq.desc_table()
.store(idx as u16, RawDescriptor::from(*desc))
.unwrap();
}
// Put the descriptor index 0 in the first available ring position.
mem_handle
.write_obj(0u16, vq.avail_addr().unchecked_add(4))
.unwrap();
// Set `avail_idx` to 1.
mem_handle
.write_obj(1u16, vq.avail_addr().unchecked_add(2))
.unwrap();
// Create descriptor chain from pre-filled memory
vq.create_queue::<Queue>()
.unwrap()
.iter(mem_handle)
.unwrap()
.next()
.unwrap()
}
#[test]
fn process_requests_no_desc() {
let (mut backend, _, vring) = init();
// Descriptor chain size zero, shouldn't fail
backend
.process_requests(Vec::<FooDescriptorChain>::new(), &vring)
.unwrap();
}
#[test]
fn process_request_single() {
// Single valid descriptor
let mut buf: Vec<u8> = vec![0; 30];
let (mut backend, vring) = prepare_desc_chain(&mut buf);
backend.process_queue(&vring).unwrap();
}
#[test]
fn process_requests_multi() {
// Multiple valid descriptors
let (mut backend, mem, vring) = init();
let mut bufs: Vec<Vec<u8>> = vec![vec![0; 30]; 6];
let desc_chains = vec![
prepare_desc_chains(&mem, &mut bufs[0]),
prepare_desc_chains(&mem, &mut bufs[1]),
prepare_desc_chains(&mem, &mut bufs[2]),
prepare_desc_chains(&mem, &mut bufs[3]),
prepare_desc_chains(&mem, &mut bufs[4]),
prepare_desc_chains(&mem, &mut bufs[5]),
];
backend
.process_requests(desc_chains.clone(), &vring)
.unwrap();
}
#[test]
fn verify_backend() {
let info = FooInfo::new();
let mut backend = VhostUserFooBackend::new(info).unwrap();
assert_eq!(backend.num_queues(), NUM_QUEUES);
assert_eq!(backend.max_queue_size(), QUEUE_SIZE);
assert_eq!(backend.features(), 0x171000000);
assert_eq!(backend.protocol_features(), VhostUserProtocolFeatures::MQ);
assert_eq!(backend.queues_per_thread(), vec![0xffff_ffff]);
assert_eq!(backend.get_config(0, 0), vec![]);
backend.set_event_idx(true);
assert!(backend.event_idx);
assert!(backend.exit_event(0).is_some());
let mem = GuestMemoryAtomic::new(
GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(),
);
backend.update_memory(mem.clone()).unwrap();
let vring = VringRwLock::new(mem, 0x1000).unwrap();
vring.set_queue_info(0x100, 0x200, 0x300).unwrap();
vring.set_queue_ready(true);
assert_eq!(
backend
.handle_event(0, EventSet::OUT, &[vring.clone()], 0)
.unwrap_err()
.kind(),
io::ErrorKind::Other
);
assert_eq!(
backend
.handle_event(1, EventSet::IN, &[vring.clone()], 0)
.unwrap_err()
.kind(),
io::ErrorKind::Other
);
// Hit the loop part
backend.set_event_idx(true);
backend
.handle_event(0, EventSet::IN, &[vring.clone()], 0)
.unwrap();
// Hit the non-loop part
backend.set_event_idx(false);
backend.handle_event(0, EventSet::IN, &[vring], 0).unwrap();
}
}