aboutsummaryrefslogtreecommitdiff
path: root/src/vhost_kern/vdpa.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/vhost_kern/vdpa.rs')
-rw-r--r--src/vhost_kern/vdpa.rs560
1 files changed, 560 insertions, 0 deletions
diff --git a/src/vhost_kern/vdpa.rs b/src/vhost_kern/vdpa.rs
new file mode 100644
index 0000000..65e0123
--- /dev/null
+++ b/src/vhost_kern/vdpa.rs
@@ -0,0 +1,560 @@
+// Copyright (C) 2021 Red Hat, Inc. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
+
+//! Kernel-based vhost-vdpa backend.
+
+use std::fs::{File, OpenOptions};
+use std::io::Error as IOError;
+use std::os::raw::{c_uchar, c_uint};
+use std::os::unix::fs::OpenOptionsExt;
+use std::os::unix::io::{AsRawFd, RawFd};
+
+use vm_memory::GuestAddressSpace;
+use vmm_sys_util::eventfd::EventFd;
+use vmm_sys_util::fam::*;
+use vmm_sys_util::ioctl::{ioctl, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref};
+
+use super::vhost_binding::*;
+use super::{ioctl_result, Error, Result, VhostKernBackend, VhostKernFeatures};
+use crate::vdpa::*;
+use crate::{VhostAccess, VhostIotlbBackend, VhostIotlbMsg, VhostIotlbType, VringConfigData};
+
+// Implement the FamStruct trait for vhost_vdpa_config
+generate_fam_struct_impl!(
+ vhost_vdpa_config,
+ c_uchar,
+ buf,
+ c_uint,
+ len,
+ c_uint::MAX as usize
+);
+
+type VhostVdpaConfig = FamStructWrapper<vhost_vdpa_config>;
+
+/// Handle for running VHOST_VDPA ioctls.
+pub struct VhostKernVdpa<AS: GuestAddressSpace> {
+ fd: File,
+ mem: AS,
+ backend_features_acked: u64,
+}
+
+impl<AS: GuestAddressSpace> VhostKernVdpa<AS> {
+ /// Open a handle to a new VHOST-VDPA instance.
+ pub fn new(path: &str, mem: AS) -> Result<Self> {
+ Ok(VhostKernVdpa {
+ fd: OpenOptions::new()
+ .read(true)
+ .write(true)
+ .custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK)
+ .open(path)
+ .map_err(Error::VhostOpen)?,
+ mem,
+ backend_features_acked: 0,
+ })
+ }
+
+ /// Create a `VhostKernVdpa` object with given content.
+ pub fn with(fd: File, mem: AS, backend_features_acked: u64) -> Self {
+ VhostKernVdpa {
+ fd,
+ mem,
+ backend_features_acked,
+ }
+ }
+
+ /// Set the addresses for a given vring.
+ ///
+ /// # Arguments
+ /// * `queue_index` - Index of the queue to set addresses for.
+ /// * `config_data` - Vring config data, addresses of desc_table, avail_ring
+ /// and used_ring are in the guest address space.
+ pub fn set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()> {
+ if !self.is_valid(config_data) {
+ return Err(Error::InvalidQueue);
+ }
+
+ // vDPA backends expect IOVA (that can be mapped 1:1 with
+ // GPA when no IOMMU is involved).
+ let vring_addr = vhost_vring_addr {
+ index: queue_index as u32,
+ flags: config_data.flags,
+ desc_user_addr: config_data.desc_table_addr,
+ used_user_addr: config_data.used_ring_addr,
+ avail_user_addr: config_data.avail_ring_addr,
+ log_guest_addr: config_data.get_log_addr(),
+ };
+
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_ADDR(), &vring_addr) };
+ ioctl_result(ret, ())
+ }
+}
+
+impl<AS: GuestAddressSpace> VhostVdpa for VhostKernVdpa<AS> {
+ fn get_device_id(&self) -> Result<u32> {
+ let mut device_id: u32 = 0;
+
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ let ret = unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_DEVICE_ID(), &mut device_id) };
+ ioctl_result(ret, device_id)
+ }
+
+ fn get_status(&self) -> Result<u8> {
+ let mut status: u8 = 0;
+
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ let ret = unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_STATUS(), &mut status) };
+ ioctl_result(ret, status)
+ }
+
+ fn set_status(&self, status: u8) -> Result<()> {
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ let ret = unsafe { ioctl_with_ref(self, VHOST_VDPA_SET_STATUS(), &status) };
+ ioctl_result(ret, ())
+ }
+
+ fn get_config(&self, offset: u32, buffer: &mut [u8]) -> Result<()> {
+ let mut config = VhostVdpaConfig::new(buffer.len())
+ .map_err(|_| Error::IoctlError(IOError::from_raw_os_error(libc::ENOMEM)))?;
+
+ config.as_mut_fam_struct().off = offset;
+
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ let ret = unsafe {
+ ioctl_with_ptr(
+ self,
+ VHOST_VDPA_GET_CONFIG(),
+ config.as_mut_fam_struct_ptr(),
+ )
+ };
+
+ buffer.copy_from_slice(config.as_slice());
+
+ ioctl_result(ret, ())
+ }
+
+ fn set_config(&self, offset: u32, buffer: &[u8]) -> Result<()> {
+ let mut config = VhostVdpaConfig::new(buffer.len())
+ .map_err(|_| Error::IoctlError(IOError::from_raw_os_error(libc::ENOMEM)))?;
+
+ config.as_mut_fam_struct().off = offset;
+ config.as_mut_slice().copy_from_slice(buffer);
+
+ let ret =
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ unsafe { ioctl_with_ptr(self, VHOST_VDPA_SET_CONFIG(), config.as_fam_struct_ptr()) };
+ ioctl_result(ret, ())
+ }
+
+ fn set_vring_enable(&self, queue_index: usize, enabled: bool) -> Result<()> {
+ let vring_state = vhost_vring_state {
+ index: queue_index as u32,
+ num: enabled as u32,
+ };
+
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ let ret = unsafe { ioctl_with_ref(self, VHOST_VDPA_SET_VRING_ENABLE(), &vring_state) };
+ ioctl_result(ret, ())
+ }
+
+ fn get_vring_num(&self) -> Result<u16> {
+ let mut vring_num: u16 = 0;
+
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ let ret = unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_VRING_NUM(), &mut vring_num) };
+ ioctl_result(ret, vring_num)
+ }
+
+ fn set_config_call(&self, fd: &EventFd) -> Result<()> {
+ let event_fd: ::std::os::raw::c_int = fd.as_raw_fd();
+
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ let ret = unsafe { ioctl_with_ref(self, VHOST_VDPA_SET_CONFIG_CALL(), &event_fd) };
+ ioctl_result(ret, ())
+ }
+
+ fn get_iova_range(&self) -> Result<VhostVdpaIovaRange> {
+ let mut low_iova_range = vhost_vdpa_iova_range { first: 0, last: 0 };
+
+ let ret =
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_IOVA_RANGE(), &mut low_iova_range) };
+
+ let iova_range = VhostVdpaIovaRange {
+ first: low_iova_range.first,
+ last: low_iova_range.last,
+ };
+
+ ioctl_result(ret, iova_range)
+ }
+
+ fn get_config_size(&self) -> Result<u32> {
+ let mut config_size: u32 = 0;
+
+ let ret =
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_CONFIG_SIZE(), &mut config_size) };
+ ioctl_result(ret, config_size)
+ }
+
+ fn get_vqs_count(&self) -> Result<u32> {
+ let mut vqs_count: u32 = 0;
+
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ let ret = unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_VQS_COUNT(), &mut vqs_count) };
+ ioctl_result(ret, vqs_count)
+ }
+
+ fn get_group_num(&self) -> Result<u32> {
+ let mut group_num: u32 = 0;
+
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ let ret = unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_GROUP_NUM(), &mut group_num) };
+ ioctl_result(ret, group_num)
+ }
+
+ fn get_as_num(&self) -> Result<u32> {
+ let mut as_num: u32 = 0;
+
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ let ret = unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_AS_NUM(), &mut as_num) };
+ ioctl_result(ret, as_num)
+ }
+
+ fn get_vring_group(&self, queue_index: u32) -> Result<u32> {
+ let mut vring_state = vhost_vring_state {
+ index: queue_index,
+ ..Default::default()
+ };
+
+ let ret =
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ unsafe { ioctl_with_mut_ref(self, VHOST_VDPA_GET_VRING_GROUP(), &mut vring_state) };
+ ioctl_result(ret, vring_state.num)
+ }
+
+ fn set_group_asid(&self, group_index: u32, asid: u32) -> Result<()> {
+ let vring_state = vhost_vring_state {
+ index: group_index,
+ num: asid,
+ };
+
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ let ret = unsafe { ioctl_with_ref(self, VHOST_VDPA_GET_VRING_GROUP(), &vring_state) };
+ ioctl_result(ret, ())
+ }
+
+ fn suspend(&self) -> Result<()> {
+ // SAFETY: This ioctl is called on a valid vhost-vdpa fd and has its
+ // return value checked.
+ let ret = unsafe { ioctl(self, VHOST_VDPA_SUSPEND()) };
+ ioctl_result(ret, ())
+ }
+
+ fn dma_map(&self, iova: u64, size: u64, vaddr: *const u8, readonly: bool) -> Result<()> {
+ let iotlb = VhostIotlbMsg {
+ iova,
+ size,
+ userspace_addr: vaddr as u64,
+ perm: match readonly {
+ true => VhostAccess::ReadOnly,
+ false => VhostAccess::ReadWrite,
+ },
+ msg_type: VhostIotlbType::Update,
+ };
+
+ self.send_iotlb_msg(&iotlb)
+ }
+
+ fn dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
+ let iotlb = VhostIotlbMsg {
+ iova,
+ size,
+ msg_type: VhostIotlbType::Invalidate,
+ ..Default::default()
+ };
+
+ self.send_iotlb_msg(&iotlb)
+ }
+}
+
+impl<AS: GuestAddressSpace> VhostKernBackend for VhostKernVdpa<AS> {
+ type AS = AS;
+
+ fn mem(&self) -> &Self::AS {
+ &self.mem
+ }
+
+ /// Check whether the ring configuration is valid.
+ fn is_valid(&self, config_data: &VringConfigData) -> bool {
+ let queue_size = config_data.queue_size;
+ if queue_size > config_data.queue_max_size
+ || queue_size == 0
+ || (queue_size & (queue_size - 1)) != 0
+ {
+ return false;
+ }
+
+ // Since vDPA could be dealing with IOVAs corresponding to GVAs, it
+ // wouldn't make sense to go through the validation of the descriptor
+ // table address, available ring address and used ring address against
+ // the guest memory representation we have access to.
+
+ config_data.is_log_addr_valid()
+ }
+}
+
+impl<AS: GuestAddressSpace> AsRawFd for VhostKernVdpa<AS> {
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd.as_raw_fd()
+ }
+}
+
+impl<AS: GuestAddressSpace> VhostKernFeatures for VhostKernVdpa<AS> {
+ fn get_backend_features_acked(&self) -> u64 {
+ self.backend_features_acked
+ }
+
+ fn set_backend_features_acked(&mut self, features: u64) {
+ self.backend_features_acked = features;
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ const VHOST_VDPA_PATH: &str = "/dev/vhost-vdpa-0";
+
+ use std::alloc::{alloc, dealloc, Layout};
+ use vm_memory::{GuestAddress, GuestMemory, GuestMemoryMmap};
+ use vmm_sys_util::eventfd::EventFd;
+
+ use super::*;
+ use crate::{
+ VhostBackend, VhostUserDirtyLogRegion, VhostUserMemoryRegionInfo, VringConfigData,
+ };
+ use serial_test::serial;
+ use std::io::ErrorKind;
+
+ /// macro to skip test if vhost-vdpa device path is not found.
+ ///
+ /// vDPA simulators are available since Linux 5.7, but the CI may have
+ /// an older kernel, so for now we skip the test if we don't find
+ /// the device.
+ macro_rules! unwrap_not_found {
+ ( $e:expr ) => {
+ match $e {
+ Ok(v) => v,
+ Err(error) => match error {
+ Error::VhostOpen(ref e) if e.kind() == ErrorKind::NotFound => {
+ println!("Err: {:?} SKIPPED", e);
+ return;
+ }
+ e => panic!("Err: {:?}", e),
+ },
+ }
+ };
+ }
+
+ macro_rules! validate_ioctl {
+ ( $e:expr, $ref_value:expr ) => {
+ match $e {
+ Ok(v) => assert_eq!(v, $ref_value),
+ Err(error) => match error {
+ Error::IoctlError(e) if e.raw_os_error().unwrap() == libc::ENOTTY => {
+ println!("Err: {:?} SKIPPED", e);
+ }
+ e => panic!("Err: {:?}", e),
+ },
+ }
+ };
+ }
+
+ #[test]
+ #[serial]
+ fn test_vdpa_kern_new_device() {
+ let m = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10_0000)]).unwrap();
+ let vdpa = unwrap_not_found!(VhostKernVdpa::new(VHOST_VDPA_PATH, &m));
+
+ assert!(vdpa.as_raw_fd() >= 0);
+ assert!(vdpa.mem().find_region(GuestAddress(0x100)).is_some());
+ assert!(vdpa.mem().find_region(GuestAddress(0x10_0000)).is_none());
+ }
+
+ #[test]
+ #[serial]
+ fn test_vdpa_kern_is_valid() {
+ let m = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10_0000)]).unwrap();
+ let vdpa = unwrap_not_found!(VhostKernVdpa::new(VHOST_VDPA_PATH, &m));
+
+ let mut config = VringConfigData {
+ queue_max_size: 32,
+ queue_size: 32,
+ flags: 0,
+ desc_table_addr: 0x1000,
+ used_ring_addr: 0x2000,
+ avail_ring_addr: 0x3000,
+ log_addr: None,
+ };
+ assert!(vdpa.is_valid(&config));
+
+ config.queue_size = 0;
+ assert!(!vdpa.is_valid(&config));
+ config.queue_size = 31;
+ assert!(!vdpa.is_valid(&config));
+ config.queue_size = 33;
+ assert!(!vdpa.is_valid(&config));
+ }
+
+ #[test]
+ #[serial]
+ fn test_vdpa_kern_ioctls() {
+ let m = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10_0000)]).unwrap();
+ let vdpa = unwrap_not_found!(VhostKernVdpa::new(VHOST_VDPA_PATH, &m));
+
+ let features = vdpa.get_features().unwrap();
+ // VIRTIO_F_VERSION_1 (bit 32) should be set
+ assert_ne!(features & (1 << 32), 0);
+ vdpa.set_features(features).unwrap();
+
+ vdpa.set_owner().unwrap();
+
+ vdpa.set_mem_table(&[]).unwrap_err();
+
+ let region = VhostUserMemoryRegionInfo::new(
+ 0x0,
+ 0x10_0000,
+ m.get_host_address(GuestAddress(0x0)).unwrap() as u64,
+ 0,
+ -1,
+ );
+ vdpa.set_mem_table(&[region]).unwrap();
+
+ let device_id = vdpa.get_device_id().unwrap();
+ assert!(device_id > 0);
+
+ assert_eq!(vdpa.get_status().unwrap(), 0x0);
+ vdpa.set_status(0x1).unwrap();
+ assert_eq!(vdpa.get_status().unwrap(), 0x1);
+
+ let mut vec = vec![0u8; 8];
+ vdpa.get_config(0, &mut vec).unwrap();
+ vdpa.set_config(0, &vec).unwrap();
+
+ let eventfd = EventFd::new(0).unwrap();
+
+ // set_log_base() and set_log_fd() are not supported by vhost-vdpa
+ vdpa.set_log_base(
+ 0x4000,
+ Some(VhostUserDirtyLogRegion {
+ mmap_size: 0x1000,
+ mmap_offset: 0x10,
+ mmap_handle: 1,
+ }),
+ )
+ .unwrap_err();
+ vdpa.set_log_base(0x4000, None).unwrap_err();
+ vdpa.set_log_fd(eventfd.as_raw_fd()).unwrap_err();
+
+ let max_queues = vdpa.get_vring_num().unwrap();
+ vdpa.set_vring_num(0, max_queues + 1).unwrap_err();
+
+ vdpa.set_vring_num(0, 32).unwrap();
+
+ let config = VringConfigData {
+ queue_max_size: 32,
+ queue_size: 32,
+ flags: 0,
+ desc_table_addr: 0x1000,
+ used_ring_addr: 0x2000,
+ avail_ring_addr: 0x3000,
+ log_addr: None,
+ };
+ vdpa.set_vring_addr(0, &config).unwrap();
+ vdpa.set_vring_base(0, 1).unwrap();
+ vdpa.set_vring_call(0, &eventfd).unwrap();
+ vdpa.set_vring_kick(0, &eventfd).unwrap();
+ vdpa.set_vring_err(0, &eventfd).unwrap();
+
+ vdpa.set_config_call(&eventfd).unwrap();
+
+ let iova_range = vdpa.get_iova_range().unwrap();
+ // vDPA-block simulator returns [0, u64::MAX] range
+ assert_eq!(iova_range.first, 0);
+ assert_eq!(iova_range.last, u64::MAX);
+
+ let (config_size, vqs_count, group_num, as_num, vring_group) = if device_id == 1 {
+ (24, 3, 2, 2, 0)
+ } else if device_id == 2 {
+ (60, 1, 1, 1, 0)
+ } else {
+ panic!("Unexpected device id {}", device_id)
+ };
+
+ validate_ioctl!(vdpa.get_config_size(), config_size);
+ validate_ioctl!(vdpa.get_vqs_count(), vqs_count);
+ validate_ioctl!(vdpa.get_group_num(), group_num);
+ validate_ioctl!(vdpa.get_as_num(), as_num);
+ validate_ioctl!(vdpa.get_vring_group(0), vring_group);
+ validate_ioctl!(vdpa.set_group_asid(0, 12345), ());
+
+ if vdpa.get_backend_features().unwrap() & (1 << VHOST_BACKEND_F_SUSPEND)
+ == (1 << VHOST_BACKEND_F_SUSPEND)
+ {
+ validate_ioctl!(vdpa.suspend(), ());
+ }
+
+ assert_eq!(vdpa.get_vring_base(0).unwrap(), 1);
+
+ vdpa.set_vring_enable(0, true).unwrap();
+ vdpa.set_vring_enable(0, false).unwrap();
+ }
+
+ #[test]
+ #[serial]
+ fn test_vdpa_kern_dma() {
+ let m = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10_0000)]).unwrap();
+ let mut vdpa = unwrap_not_found!(VhostKernVdpa::new(VHOST_VDPA_PATH, &m));
+
+ let features = vdpa.get_features().unwrap();
+ // VIRTIO_F_VERSION_1 (bit 32) should be set
+ assert_ne!(features & (1 << 32), 0);
+ vdpa.set_features(features).unwrap();
+
+ let backend_features = vdpa.get_backend_features().unwrap();
+ assert_ne!(backend_features & (1 << VHOST_BACKEND_F_IOTLB_MSG_V2), 0);
+ vdpa.set_backend_features(backend_features).unwrap();
+
+ vdpa.set_owner().unwrap();
+
+ vdpa.dma_map(0xFFFF_0000, 0xFFFF, std::ptr::null::<u8>(), false)
+ .unwrap_err();
+
+ let layout = Layout::from_size_align(0xFFFF, 1).unwrap();
+
+ // SAFETY: Safe because layout has non-zero size.
+ let ptr = unsafe { alloc(layout) };
+
+ vdpa.dma_map(0xFFFF_0000, 0xFFFF, ptr, false).unwrap();
+ vdpa.dma_unmap(0xFFFF_0000, 0xFFFF).unwrap();
+
+ // SAFETY: Safe because `ptr` is allocated with the same allocator
+ // using the same `layout`.
+ unsafe { dealloc(ptr, layout) };
+ }
+}