mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2024-12-22 21:55:20 +00:00
vhost_user_backend: Add new crate
The purpose of this new crate is to provide a common library to all vhost-user backend implementations. The more is handled by this library, the less duplication will need to happen in each vhost-user daemon. This crate relies a lot on vhost_rs, vm-memory and vm-virtio crates. Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
This commit is contained in:
parent
b5ee9212c1
commit
2e2cad91ae
23
vhost_user_backend/Cargo.toml
Normal file
23
vhost_user_backend/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "vhost_user_backend"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
pci_support = ["vm-virtio/pci_support"]
|
||||
mmio_support = ["vm-virtio/mmio_support"]
|
||||
|
||||
[dependencies]
|
||||
epoll = "4.1.0"
|
||||
libc = "0.2.60"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
||||
|
||||
[dependencies.vhost_rs]
|
||||
path = "../vhost_rs"
|
||||
features = ["vhost-user-slave"]
|
||||
|
||||
[workspace]
|
778
vhost_user_backend/src/lib.rs
Normal file
778
vhost_user_backend/src/lib.rs
Normal file
@ -0,0 +1,778 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::num::Wrapping;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use vhost_rs::vhost_user::message::{
|
||||
VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures,
|
||||
VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState,
|
||||
VHOST_USER_CONFIG_OFFSET, VHOST_USER_CONFIG_SIZE,
|
||||
};
|
||||
use vhost_rs::vhost_user::{
|
||||
Error as VhostUserError, Result as VhostUserResult, SlaveReqHandler, VhostUserSlaveReqHandler,
|
||||
};
|
||||
use vm_memory::guest_memory::FileOffset;
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
use vm_virtio::{DescriptorChain, Queue};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Errors related to vhost-user daemon.
|
||||
pub enum Error {
|
||||
/// Failed creating vhost-user slave handler.
|
||||
CreateSlaveReqHandler(VhostUserError),
|
||||
/// Failed starting daemon thread.
|
||||
StartDaemon(io::Error),
|
||||
/// Failed waiting for daemon thread.
|
||||
WaitDaemon(std::boxed::Box<dyn std::any::Any + std::marker::Send>),
|
||||
/// Failed handling a vhost-user request.
|
||||
HandleRequest(VhostUserError),
|
||||
/// Could not find the mapping from memory regions.
|
||||
MissingMemoryMapping,
|
||||
/// Failed to create epoll file descriptor.
|
||||
EpollCreateFd(io::Error),
|
||||
/// Failed to add a file descriptor to the epoll handler.
|
||||
EpollCtl(io::Error),
|
||||
/// Failed while waiting for events.
|
||||
EpollWait(io::Error),
|
||||
/// Failed to signal used queue.
|
||||
SignalUsedQueue(io::Error),
|
||||
}
|
||||
|
||||
/// Result of vhost-user daemon operations.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// This trait must be implemented by the caller in order to provide backend
|
||||
/// specific implementation.
|
||||
pub trait VhostUserBackend: Send + Sync + 'static {
|
||||
/// Number of queues.
|
||||
fn num_queues(&self) -> usize;
|
||||
|
||||
/// Depth of each queue.
|
||||
fn max_queue_size(&self) -> usize;
|
||||
|
||||
/// Virtio features.
|
||||
fn features(&self) -> u64;
|
||||
|
||||
/// This function gets called if the backend registered some additional
|
||||
/// listeners onto specific file descriptors. The library can handle
|
||||
/// virtqueues on its own, but does not know what to do with events
|
||||
/// happening on custom listeners.
|
||||
fn handle_event(&self, device_event: u16, evset: epoll::Events) -> Result<bool>;
|
||||
|
||||
/// This function is responsible for the actual processing that needs to
|
||||
/// happen when one of the virtqueues is available.
|
||||
fn process_queue(
|
||||
&self,
|
||||
q_idx: u16,
|
||||
avail_desc: &DescriptorChain,
|
||||
mem: &GuestMemoryMmap,
|
||||
) -> Result<u32>;
|
||||
|
||||
/// Get virtio device configuration.
|
||||
fn get_config(&self, offset: u32, size: u32) -> Vec<u8>;
|
||||
|
||||
/// Set virtio device configuration.
|
||||
fn set_config(&self, offset: u32, buf: &[u8]);
|
||||
}
|
||||
|
||||
/// This structure is the public API the backend is allowed to interact with
|
||||
/// in order to run a fully functional vhost-user daemon.
|
||||
pub struct VhostUserDaemon<S: VhostUserBackend> {
|
||||
name: String,
|
||||
sock_path: String,
|
||||
handler: Arc<Mutex<VhostUserHandler<S>>>,
|
||||
vring_handler: Arc<Mutex<VringEpollHandler<S>>>,
|
||||
main_thread: Option<thread::JoinHandle<Result<()>>>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VhostUserDaemon<S> {
|
||||
/// Create the daemon instance, providing the backend implementation of
|
||||
/// VhostUserBackend.
|
||||
/// Under the hood, this will start a dedicated thread responsible for
|
||||
/// listening onto registered event. Those events can be vring events or
|
||||
/// custom events from the backend, but they get to be registered later
|
||||
/// during the sequence.
|
||||
pub fn new(name: String, sock_path: String, backend: S) -> Result<Self> {
|
||||
let handler = Arc::new(Mutex::new(VhostUserHandler::new(backend)));
|
||||
let vring_handler = handler.lock().unwrap().get_vring_handler();
|
||||
|
||||
Ok(VhostUserDaemon {
|
||||
name,
|
||||
sock_path,
|
||||
handler,
|
||||
vring_handler,
|
||||
main_thread: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Connect to the vhost-user socket and run a dedicated thread handling
|
||||
/// all requests coming through this socket. This runs in an infinite loop
|
||||
/// that should be terminating once the other end of the socket (the VMM)
|
||||
/// disconnects.
|
||||
pub fn start(&mut self) -> Result<()> {
|
||||
let mut slave_handler =
|
||||
SlaveReqHandler::connect(self.sock_path.as_str(), self.handler.clone())
|
||||
.map_err(Error::CreateSlaveReqHandler)?;
|
||||
let handle = thread::Builder::new()
|
||||
.name(self.name.clone())
|
||||
.spawn(move || loop {
|
||||
slave_handler
|
||||
.handle_request()
|
||||
.map_err(Error::HandleRequest)?;
|
||||
})
|
||||
.map_err(Error::StartDaemon)?;
|
||||
|
||||
self.main_thread = Some(handle);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wait for the thread handling the vhost-user socket connection to
|
||||
/// terminate.
|
||||
pub fn wait(&mut self) -> Result<()> {
|
||||
if let Some(handle) = self.main_thread.take() {
|
||||
let _ = handle.join().map_err(Error::WaitDaemon)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Register a custom event only meaningful to the caller. When this event
|
||||
/// is later triggered, and because only the caller knows what to do about
|
||||
/// it, the backend implementation of `handle_event` will be called.
|
||||
/// This lets entire control to the caller about what needs to be done for
|
||||
/// this special event, without forcing it to run its own dedicated epoll
|
||||
/// loop for it.
|
||||
pub fn register_listener(
|
||||
&self,
|
||||
fd: RawFd,
|
||||
ev_type: epoll::Events,
|
||||
data: u64,
|
||||
) -> result::Result<(), io::Error> {
|
||||
self.vring_handler
|
||||
.lock()
|
||||
.unwrap()
|
||||
.register_listener(fd, ev_type, data)
|
||||
}
|
||||
|
||||
/// Unregister a custom event. If the custom event is triggered after this
|
||||
/// function has been called, nothing will happen as it will be removed
|
||||
/// from the list of file descriptors the epoll loop is listening to.
|
||||
pub fn unregister_listener(
|
||||
&self,
|
||||
fd: RawFd,
|
||||
ev_type: epoll::Events,
|
||||
data: u64,
|
||||
) -> result::Result<(), io::Error> {
|
||||
self.vring_handler
|
||||
.lock()
|
||||
.unwrap()
|
||||
.unregister_listener(fd, ev_type, data)
|
||||
}
|
||||
|
||||
/// Trigger the processing of a virtqueue. This function is meant to be
|
||||
/// used by the caller whenever it might need some available queues to
|
||||
/// send data back to the guest.
|
||||
/// A concrete example is a backend registering one extra listener for
|
||||
/// data that needs to be sent to the guest. When the associated event
|
||||
/// is triggered, the backend will be invoked through its `handle_event`
|
||||
/// implementation. And in this case, the way to handle the event is to
|
||||
/// call into `process_queue` to let it invoke the backend implementation
|
||||
/// of `process_queue`. With this twisted trick, all common parts related
|
||||
/// to the virtqueues can remain part of the library.
|
||||
pub fn process_queue(&self, q_idx: u16) -> Result<()> {
|
||||
self.vring_handler.lock().unwrap().process_queue(q_idx)
|
||||
}
|
||||
}
|
||||
|
||||
struct AddrMapping {
|
||||
vmm_addr: u64,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
struct Memory {
|
||||
mappings: Vec<AddrMapping>,
|
||||
}
|
||||
|
||||
struct Vring {
|
||||
queue: Queue,
|
||||
kick: Option<EventFd>,
|
||||
call: Option<EventFd>,
|
||||
err: Option<EventFd>,
|
||||
started: bool,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl Clone for Vring {
|
||||
fn clone(&self) -> Self {
|
||||
let kick = if let Some(c) = &self.kick {
|
||||
Some(c.try_clone().unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let call = if let Some(c) = &self.call {
|
||||
Some(c.try_clone().unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let err = if let Some(e) = &self.err {
|
||||
Some(e.try_clone().unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Vring {
|
||||
queue: self.queue.clone(),
|
||||
kick,
|
||||
call,
|
||||
err,
|
||||
started: self.started,
|
||||
enabled: self.enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Vring {
|
||||
fn new(max_queue_size: u16) -> Self {
|
||||
Vring {
|
||||
queue: Queue::new(max_queue_size),
|
||||
kick: None,
|
||||
call: None,
|
||||
err: None,
|
||||
started: false,
|
||||
enabled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VringEpollHandler<S: VhostUserBackend> {
|
||||
backend: Arc<S>,
|
||||
vrings: Arc<Mutex<Vec<Vring>>>,
|
||||
mem: Option<GuestMemoryMmap>,
|
||||
epoll_fd: RawFd,
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VringEpollHandler<S> {
|
||||
fn update_memory(&mut self, mem: Option<GuestMemoryMmap>) {
|
||||
self.mem = mem;
|
||||
}
|
||||
|
||||
fn process_queue(&self, q_idx: u16) -> Result<()> {
|
||||
let vring = &mut self.vrings.lock().unwrap()[q_idx as usize];
|
||||
let mut used_desc_heads = vec![(0, 0); vring.queue.size as usize];
|
||||
let mut used_count = 0;
|
||||
if let Some(mem) = &self.mem {
|
||||
for avail_desc in vring.queue.iter(&mem) {
|
||||
let used_len = self
|
||||
.backend
|
||||
.process_queue(q_idx, &avail_desc, &mem)
|
||||
.unwrap();
|
||||
|
||||
used_desc_heads[used_count] = (avail_desc.index, used_len);
|
||||
used_count += 1;
|
||||
}
|
||||
|
||||
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||
vring.queue.add_used(&mem, desc_index, len);
|
||||
}
|
||||
}
|
||||
|
||||
if used_count > 0 {
|
||||
if let Some(call) = &vring.call {
|
||||
return call.write(1).map_err(Error::SignalUsedQueue);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, device_event: u16, evset: epoll::Events) -> Result<bool> {
|
||||
let num_queues = self.vrings.lock().unwrap().len();
|
||||
match device_event as usize {
|
||||
x if x < num_queues => {
|
||||
if let Some(kick) = &self.vrings.lock().unwrap()[device_event as usize].kick {
|
||||
kick.read().unwrap();
|
||||
}
|
||||
|
||||
self.process_queue(device_event).unwrap();
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
_ => self.backend.handle_event(device_event, evset),
|
||||
}
|
||||
}
|
||||
|
||||
fn register_vring_listener(&self, q_idx: usize) -> result::Result<(), io::Error> {
|
||||
if let Some(fd) = &self.vrings.lock().unwrap()[q_idx].kick {
|
||||
self.register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, q_idx as u64)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn unregister_vring_listener(&self, q_idx: usize) -> result::Result<(), io::Error> {
|
||||
if let Some(fd) = &self.vrings.lock().unwrap()[q_idx].kick {
|
||||
self.unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, q_idx as u64)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn register_listener(
|
||||
&self,
|
||||
fd: RawFd,
|
||||
ev_type: epoll::Events,
|
||||
data: u64,
|
||||
) -> result::Result<(), io::Error> {
|
||||
epoll::ctl(
|
||||
self.epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||
fd,
|
||||
epoll::Event::new(ev_type, data),
|
||||
)
|
||||
}
|
||||
|
||||
fn unregister_listener(
|
||||
&self,
|
||||
fd: RawFd,
|
||||
ev_type: epoll::Events,
|
||||
data: u64,
|
||||
) -> result::Result<(), io::Error> {
|
||||
epoll::ctl(
|
||||
self.epoll_fd,
|
||||
epoll::ControlOptions::EPOLL_CTL_DEL,
|
||||
fd,
|
||||
epoll::Event::new(ev_type, data),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct VringWorker<S: VhostUserBackend> {
|
||||
handler: Arc<Mutex<VringEpollHandler<S>>>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VringWorker<S> {
|
||||
fn run(&self, epoll_fd: RawFd) -> Result<()> {
|
||||
const EPOLL_EVENTS_LEN: usize = 100;
|
||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||
|
||||
'epoll: loop {
|
||||
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
// It's well defined from the epoll_wait() syscall
|
||||
// documentation that the epoll loop can be interrupted
|
||||
// before any of the requested events occurred or the
|
||||
// timeout expired. In both those cases, epoll_wait()
|
||||
// returns an error of type EINTR, but this should not
|
||||
// be considered as a regular error. Instead it is more
|
||||
// appropriate to retry, by calling into epoll_wait().
|
||||
continue;
|
||||
}
|
||||
return Err(Error::EpollWait(e));
|
||||
}
|
||||
};
|
||||
|
||||
for event in events.iter().take(num_events) {
|
||||
let evset = match epoll::Events::from_bits(event.events) {
|
||||
Some(evset) => evset,
|
||||
None => {
|
||||
let evbits = event.events;
|
||||
println!("epoll: ignoring unknown event set: 0x{:x}", evbits);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let ev_type = event.data as u16;
|
||||
|
||||
if self.handler.lock().unwrap().handle_event(ev_type, evset)? {
|
||||
break 'epoll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct VhostUserHandler<S: VhostUserBackend> {
|
||||
backend: Arc<S>,
|
||||
vring_handler: Arc<Mutex<VringEpollHandler<S>>>,
|
||||
owned: bool,
|
||||
features_acked: bool,
|
||||
acked_features: u64,
|
||||
acked_protocol_features: u64,
|
||||
num_queues: usize,
|
||||
max_queue_size: usize,
|
||||
memory: Option<Memory>,
|
||||
vrings: Arc<Mutex<Vec<Vring>>>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VhostUserHandler<S> {
|
||||
fn new(backend: S) -> Self {
|
||||
let num_queues = backend.num_queues();
|
||||
let max_queue_size = backend.max_queue_size();
|
||||
|
||||
let arc_backend = Arc::new(backend);
|
||||
let vrings = Arc::new(Mutex::new(vec![
|
||||
Vring::new(max_queue_size as u16);
|
||||
num_queues
|
||||
]));
|
||||
// Create the epoll file descriptor
|
||||
let epoll_fd = epoll::create(true).unwrap();
|
||||
|
||||
let vring_handler = Arc::new(Mutex::new(VringEpollHandler {
|
||||
backend: arc_backend.clone(),
|
||||
vrings: vrings.clone(),
|
||||
mem: None,
|
||||
epoll_fd,
|
||||
}));
|
||||
let worker = VringWorker {
|
||||
handler: vring_handler.clone(),
|
||||
};
|
||||
|
||||
thread::Builder::new()
|
||||
.name("vring_epoll_handler".to_string())
|
||||
.spawn(move || worker.run(epoll_fd))
|
||||
.unwrap();
|
||||
|
||||
VhostUserHandler {
|
||||
backend: arc_backend,
|
||||
vring_handler,
|
||||
owned: false,
|
||||
features_acked: false,
|
||||
acked_features: 0,
|
||||
acked_protocol_features: 0,
|
||||
num_queues,
|
||||
max_queue_size,
|
||||
memory: None,
|
||||
vrings,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_vring_handler(&self) -> Arc<Mutex<VringEpollHandler<S>>> {
|
||||
self.vring_handler.clone()
|
||||
}
|
||||
|
||||
fn vmm_va_to_gpa(&self, vmm_va: u64) -> Result<u64> {
|
||||
if let Some(memory) = &self.memory {
|
||||
for mapping in memory.mappings.iter() {
|
||||
if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size {
|
||||
return Ok(vmm_va - mapping.vmm_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::MissingMemoryMapping)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
||||
fn set_owner(&mut self) -> VhostUserResult<()> {
|
||||
if self.owned {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
}
|
||||
self.owned = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_owner(&mut self) -> VhostUserResult<()> {
|
||||
self.owned = false;
|
||||
self.features_acked = false;
|
||||
self.acked_features = 0;
|
||||
self.acked_protocol_features = 0;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_features(&mut self) -> VhostUserResult<u64> {
|
||||
Ok(self.backend.features())
|
||||
}
|
||||
|
||||
fn set_features(&mut self, features: u64) -> VhostUserResult<()> {
|
||||
if !self.owned || self.features_acked {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
} else if (features & !self.backend.features()) != 0 {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
self.acked_features = features;
|
||||
self.features_acked = true;
|
||||
|
||||
// If VHOST_USER_F_PROTOCOL_FEATURES has not been negotiated,
|
||||
// the ring is initialized in an enabled state.
|
||||
// If VHOST_USER_F_PROTOCOL_FEATURES has been negotiated,
|
||||
// the ring is initialized in a disabled state. Client must not
|
||||
// pass data to/from the backend until ring is enabled by
|
||||
// VHOST_USER_SET_VRING_ENABLE with parameter 1, or after it has
|
||||
// been disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0.
|
||||
let vring_enabled =
|
||||
self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0;
|
||||
for vring in self.vrings.lock().unwrap().iter_mut() {
|
||||
vring.enabled = vring_enabled;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_protocol_features(&mut self) -> VhostUserResult<VhostUserProtocolFeatures> {
|
||||
Ok(VhostUserProtocolFeatures::all())
|
||||
}
|
||||
|
||||
fn set_protocol_features(&mut self, features: u64) -> VhostUserResult<()> {
|
||||
// Note: slave that reported VHOST_USER_F_PROTOCOL_FEATURES must
|
||||
// support this message even before VHOST_USER_SET_FEATURES was
|
||||
// called.
|
||||
self.acked_protocol_features = features;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_mem_table(
|
||||
&mut self,
|
||||
ctx: &[VhostUserMemoryRegion],
|
||||
fds: &[RawFd],
|
||||
) -> VhostUserResult<()> {
|
||||
// We need to create tuple of ranges from the list of VhostUserMemoryRegion
|
||||
// that we get from the caller.
|
||||
let mut regions: Vec<(GuestAddress, usize, Option<FileOffset>)> = Vec::new();
|
||||
let mut mappings: Vec<AddrMapping> = Vec::new();
|
||||
|
||||
for (idx, region) in ctx.iter().enumerate() {
|
||||
let g_addr = GuestAddress(region.guest_phys_addr);
|
||||
let len = (region.memory_size + region.mmap_offset) as usize;
|
||||
let file = unsafe { File::from_raw_fd(fds[idx]) };
|
||||
let f_off = FileOffset::new(file, 0);
|
||||
|
||||
regions.push((g_addr, len, Some(f_off)));
|
||||
mappings.push(AddrMapping {
|
||||
vmm_addr: region.user_addr,
|
||||
size: region.memory_size + region.mmap_offset,
|
||||
});
|
||||
}
|
||||
|
||||
let mem = GuestMemoryMmap::with_files(regions).unwrap();
|
||||
self.vring_handler.lock().unwrap().update_memory(Some(mem));
|
||||
self.memory = Some(Memory { mappings });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_queue_num(&mut self) -> VhostUserResult<u64> {
|
||||
Ok(self.num_queues as u64)
|
||||
}
|
||||
|
||||
fn set_vring_num(&mut self, index: u32, num: u32) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues || num == 0 || num as usize > self.max_queue_size {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
self.vrings.lock().unwrap()[index as usize].queue.size = num as u16;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_addr(
|
||||
&mut self,
|
||||
index: u32,
|
||||
_flags: VhostUserVringAddrFlags,
|
||||
descriptor: u64,
|
||||
used: u64,
|
||||
available: u64,
|
||||
_log: u64,
|
||||
) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
if self.memory.is_some() {
|
||||
let desc_table = self.vmm_va_to_gpa(descriptor).unwrap();
|
||||
let avail_ring = self.vmm_va_to_gpa(available).unwrap();
|
||||
let used_ring = self.vmm_va_to_gpa(used).unwrap();
|
||||
self.vrings.lock().unwrap()[index as usize].queue.desc_table = GuestAddress(desc_table);
|
||||
self.vrings.lock().unwrap()[index as usize].queue.avail_ring = GuestAddress(avail_ring);
|
||||
self.vrings.lock().unwrap()[index as usize].queue.used_ring = GuestAddress(used_ring);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(VhostUserError::InvalidParam)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_vring_base(&mut self, index: u32, base: u32) -> VhostUserResult<()> {
|
||||
self.vrings.lock().unwrap()[index as usize].queue.next_avail = Wrapping(base as u16);
|
||||
self.vrings.lock().unwrap()[index as usize].queue.next_used = Wrapping(base as u16);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_vring_base(&mut self, index: u32) -> VhostUserResult<VhostUserVringState> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
// Quotation from vhost-user spec:
|
||||
// Client must start ring upon receiving a kick (that is, detecting
|
||||
// that file descriptor is readable) on the descriptor specified by
|
||||
// VHOST_USER_SET_VRING_KICK, and stop ring upon receiving
|
||||
// VHOST_USER_GET_VRING_BASE.
|
||||
self.vrings.lock().unwrap()[index as usize].started = false;
|
||||
self.vring_handler
|
||||
.lock()
|
||||
.unwrap()
|
||||
.unregister_vring_listener(index as usize)
|
||||
.unwrap();
|
||||
|
||||
let next_avail = self.vrings.lock().unwrap()[index as usize]
|
||||
.queue
|
||||
.next_avail
|
||||
.0 as u16;
|
||||
|
||||
Ok(VhostUserVringState::new(index, u32::from(next_avail)))
|
||||
}
|
||||
|
||||
fn set_vring_kick(&mut self, index: u8, fd: Option<RawFd>) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
if self.vrings.lock().unwrap()[index as usize].kick.is_some() {
|
||||
// Close file descriptor set by previous operations.
|
||||
let _ = unsafe {
|
||||
libc::close(
|
||||
self.vrings.lock().unwrap()[index as usize]
|
||||
.kick
|
||||
.take()
|
||||
.unwrap()
|
||||
.as_raw_fd(),
|
||||
)
|
||||
};
|
||||
}
|
||||
self.vrings.lock().unwrap()[index as usize].kick =
|
||||
Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) });;
|
||||
|
||||
// Quotation from vhost-user spec:
|
||||
// Client must start ring upon receiving a kick (that is, detecting
|
||||
// that file descriptor is readable) on the descriptor specified by
|
||||
// VHOST_USER_SET_VRING_KICK, and stop ring upon receiving
|
||||
// VHOST_USER_GET_VRING_BASE.
|
||||
//
|
||||
// So we should add fd to event monitor(select, poll, epoll) here.
|
||||
self.vrings.lock().unwrap()[index as usize].started = true;
|
||||
self.vring_handler
|
||||
.lock()
|
||||
.unwrap()
|
||||
.register_vring_listener(index as usize)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_call(&mut self, index: u8, fd: Option<RawFd>) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
if self.vrings.lock().unwrap()[index as usize].call.is_some() {
|
||||
// Close file descriptor set by previous operations.
|
||||
let _ = unsafe {
|
||||
libc::close(
|
||||
self.vrings.lock().unwrap()[index as usize]
|
||||
.call
|
||||
.take()
|
||||
.unwrap()
|
||||
.as_raw_fd(),
|
||||
)
|
||||
};
|
||||
}
|
||||
self.vrings.lock().unwrap()[index as usize].call =
|
||||
Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_err(&mut self, index: u8, fd: Option<RawFd>) -> VhostUserResult<()> {
|
||||
if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
if self.vrings.lock().unwrap()[index as usize].err.is_some() {
|
||||
// Close file descriptor set by previous operations.
|
||||
let _ = unsafe {
|
||||
libc::close(
|
||||
self.vrings.lock().unwrap()[index as usize]
|
||||
.err
|
||||
.take()
|
||||
.unwrap()
|
||||
.as_raw_fd(),
|
||||
)
|
||||
};
|
||||
}
|
||||
self.vrings.lock().unwrap()[index as usize].err =
|
||||
Some(unsafe { EventFd::from_raw_fd(fd.unwrap()) });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_enable(&mut self, index: u32, enable: bool) -> VhostUserResult<()> {
|
||||
// This request should be handled only when VHOST_USER_F_PROTOCOL_FEATURES
|
||||
// has been negotiated.
|
||||
if self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0 {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
} else if index as usize >= self.num_queues {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
// Slave must not pass data to/from the backend until ring is
|
||||
// enabled by VHOST_USER_SET_VRING_ENABLE with parameter 1,
|
||||
// or after it has been disabled by VHOST_USER_SET_VRING_ENABLE
|
||||
// with parameter 0.
|
||||
self.vrings.lock().unwrap()[index as usize].enabled = enable;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_config(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
_flags: VhostUserConfigFlags,
|
||||
) -> VhostUserResult<Vec<u8>> {
|
||||
if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
} else if offset < VHOST_USER_CONFIG_OFFSET
|
||||
|| offset >= VHOST_USER_CONFIG_SIZE
|
||||
|| size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET
|
||||
|| size + offset > VHOST_USER_CONFIG_SIZE
|
||||
{
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
Ok(self.backend.get_config(offset, size))
|
||||
}
|
||||
|
||||
fn set_config(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
buf: &[u8],
|
||||
_flags: VhostUserConfigFlags,
|
||||
) -> VhostUserResult<()> {
|
||||
let size = buf.len() as u32;
|
||||
if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
} else if offset < VHOST_USER_CONFIG_OFFSET
|
||||
|| offset >= VHOST_USER_CONFIG_SIZE
|
||||
|| size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET
|
||||
|| size + offset > VHOST_USER_CONFIG_SIZE
|
||||
{
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
self.backend.set_config(offset, buf);
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -232,8 +232,8 @@ pub struct Queue {
|
||||
/// Guest physical address of the used ring
|
||||
pub used_ring: GuestAddress,
|
||||
|
||||
next_avail: Wrapping<u16>,
|
||||
next_used: Wrapping<u16>,
|
||||
pub next_avail: Wrapping<u16>,
|
||||
pub next_used: Wrapping<u16>,
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
|
Loading…
Reference in New Issue
Block a user