From 2cec3aad7f37bd0c58ed193613d750d97c741d51 Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Thu, 13 Jun 2019 22:13:35 +0200 Subject: [PATCH] vfio: VFIO API wrappers and helpers The Virtual Function I/O (VFIO) kernel subsystem exposes a vast and relatively complex userspace API. This commit abstracts and simplifies this API into both an internal and external API. The external API is to be consumed by VFIO device implementation through the VfioDevice structure. A VfioDevice instance can: - Enable and disable all interrupts (INTX, MSI and MSI-X) on the underlying VFIO device. - Read and write all of the VFIO device memory regions. - Set the system's IOMMU tables for the underlying device. Signed-off-by: Zhang, Xiong Y Signed-off-by: Chao Peng Signed-off-by: Samuel Ortiz --- vfio/Cargo.toml | 17 + vfio/src/lib.rs | 54 +++ vfio/src/vfio_device.rs | 734 ++++++++++++++++++++++++++++++++++++++++ vfio/src/vfio_ioctls.rs | 36 ++ 4 files changed, 841 insertions(+) create mode 100644 vfio/Cargo.toml create mode 100644 vfio/src/lib.rs create mode 100644 vfio/src/vfio_device.rs create mode 100644 vfio/src/vfio_ioctls.rs diff --git a/vfio/Cargo.toml b/vfio/Cargo.toml new file mode 100644 index 000000000..e9f2e91d3 --- /dev/null +++ b/vfio/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "vfio" +version = "0.0.1" +authors = ["The Cloud Hypervisor Authors"] + +[dependencies] +byteorder = ">=1.2.1" +kvm-bindings = "0.1" +kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" } +libc = ">=0.2.39" +log = "*" +vfio-bindings = { path = "../vfio-bindings" } +vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" } + +[dependencies.vm-memory] +git = "https://github.com/rust-vmm/vm-memory" +features = ["backend-mmap"] diff --git a/vfio/src/lib.rs b/vfio/src/lib.rs new file mode 100644 index 000000000..c7cc4e20c --- /dev/null +++ b/vfio/src/lib.rs @@ -0,0 +1,54 @@ +// Copyright © 2019 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// + +//#![deny(missing_docs)] +//! Virtual Function I/O (VFIO) API + +extern crate byteorder; +extern crate kvm_bindings; +extern crate kvm_ioctls; +extern crate log; +extern crate vfio_bindings; +extern crate vm_memory; +#[macro_use] +extern crate vmm_sys_util; + +mod vfio_device; +mod vfio_ioctls; + +use std::mem::size_of; + +pub use vfio_device::{VfioDevice, VfioError}; + +// Returns a `Vec` with a size in bytes at least as large as `size_in_bytes`. +fn vec_with_size_in_bytes(size_in_bytes: usize) -> Vec { + let rounded_size = (size_in_bytes + size_of::() - 1) / size_of::(); + let mut v = Vec::with_capacity(rounded_size); + for _ in 0..rounded_size { + v.push(T::default()) + } + v +} + +// The kvm API has many structs that resemble the following `Foo` structure: +// +// ``` +// #[repr(C)] +// struct Foo { +// some_data: u32 +// entries: __IncompleteArrayField<__u32>, +// } +// ``` +// +// In order to allocate such a structure, `size_of::()` would be too small because it would not +// include any space for `entries`. To make the allocation large enough while still being aligned +// for `Foo`, a `Vec` is created. Only the first element of `Vec` would actually be used +// as a `Foo`. The remaining memory in the `Vec` is for `entries`, which must be contiguous +// with `Foo`. This function is used to make the `Vec` with enough space for `count` entries. +pub fn vec_with_array_field(count: usize) -> Vec { + let element_space = count * size_of::(); + let vec_size_bytes = size_of::() + element_space; + vec_with_size_in_bytes(vec_size_bytes) +} diff --git a/vfio/src/vfio_device.rs b/vfio/src/vfio_device.rs new file mode 100644 index 000000000..0ee39a991 --- /dev/null +++ b/vfio/src/vfio_device.rs @@ -0,0 +1,734 @@ +// Copyright © 2019 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// +use crate::vec_with_array_field; +use byteorder::{ByteOrder, LittleEndian}; +use kvm_ioctls::*; +use std::collections::HashMap; +use std::ffi::CString; +use std::fmt; +use std::fs::{File, OpenOptions}; +use std::io; +use std::mem; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::os::unix::prelude::FileExt; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use std::u32; +use vfio_bindings::bindings::vfio::*; +use vfio_ioctls::*; +use vm_memory::{Address, GuestMemory, GuestMemoryMmap, GuestMemoryRegion}; +use vmm_sys_util::ioctl::*; +use vmm_sys_util::EventFd; + +#[derive(Debug)] +pub enum VfioError { + OpenContainer(io::Error), + OpenGroup(io::Error), + GetGroupStatus, + GroupViable, + VfioApiVersion, + VfioExtension, + VfioInvalidType, + VfioType1V2, + GroupSetContainer, + UnsetContainer, + ContainerSetIOMMU, + GroupGetDeviceFD, + CreateVfioKvmDevice(io::Error), + KvmSetDeviceAttr(io::Error), + VfioDeviceGetInfo, + VfioDeviceGetRegionInfo, + InvalidPath, + IommuDmaMap, + IommuDmaUnmap, + VfioDeviceGetIrqInfo, + VfioDeviceSetIrq, +} +pub type Result = std::result::Result; + +impl fmt::Display for VfioError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + VfioError::OpenContainer(e) => { + write!(f, "failed to open /dev/vfio/vfio container: {}", e) + } + VfioError::OpenGroup(e) => { + write!(f, "failed to open /dev/vfio/$group_num group: {}", e) + } + VfioError::GetGroupStatus => write!(f, "failed to get Group Status"), + VfioError::GroupViable => write!(f, "group is inviable"), + VfioError::VfioApiVersion => write!( + f, + "vfio API version doesn't match with VFIO_API_VERSION defined in vfio-bindings" + ), + VfioError::VfioExtension => write!(f, "failed to check VFIO extension"), + VfioError::VfioInvalidType => write!(f, "invalid VFIO type"), + VfioError::VfioType1V2 => { + write!(f, "container dones't support VfioType1V2 IOMMU driver type") + } + VfioError::GroupSetContainer => { + write!(f, "failed to add vfio group into vfio container") + } + VfioError::UnsetContainer => write!(f, "failed to unset vfio container"), + VfioError::ContainerSetIOMMU => write!( + f, + "failed to set container's IOMMU driver type as VfioType1V2" + ), + VfioError::GroupGetDeviceFD => write!(f, "failed to get vfio device fd"), + VfioError::CreateVfioKvmDevice(e) => { + write!(f, "failed to create KVM vfio device: {}", e) + } + VfioError::KvmSetDeviceAttr(e) => { + write!(f, "failed to set KVM vfio device's attribute: {}", e) + } + VfioError::VfioDeviceGetInfo => { + write!(f, "failed to get vfio device's info or info doesn't match") + } + VfioError::VfioDeviceGetRegionInfo => { + write!(f, "failed to get vfio device's region info") + } + VfioError::InvalidPath => write!(f, "invalid file path"), + VfioError::IommuDmaMap => write!(f, "failed to add guest memory map into iommu table"), + VfioError::IommuDmaUnmap => { + write!(f, "failed to remove guest memory map from iommu table") + } + VfioError::VfioDeviceGetIrqInfo => write!(f, "failed to get vfio device irq info"), + VfioError::VfioDeviceSetIrq => write!(f, "failed to set vfio deviece irq"), + } + } +} + +struct VfioContainer { + container: File, +} + +impl VfioContainer { + fn new() -> Result { + let container = OpenOptions::new() + .read(true) + .write(true) + .open("/dev/vfio/vfio") + .map_err(VfioError::OpenContainer)?; + + Ok(VfioContainer { container }) + } + + fn get_api_version(&self) -> i32 { + // Safe as file is vfio container fd and ioctl is defined by kernel. + unsafe { ioctl(self, VFIO_GET_API_VERSION()) } + } + + fn check_extension(&self, val: u32) -> Result<()> { + if val != VFIO_TYPE1_IOMMU && val != VFIO_TYPE1v2_IOMMU { + return Err(VfioError::VfioInvalidType); + } + + // Safe as file is vfio container and make sure val is valid. + let ret = unsafe { ioctl_with_val(self, VFIO_CHECK_EXTENSION(), val.into()) }; + if ret != 1 { + return Err(VfioError::VfioExtension); + } + + Ok(()) + } + + fn set_iommu(&self, val: u32) -> Result<()> { + if val != VFIO_TYPE1_IOMMU && val != VFIO_TYPE1v2_IOMMU { + return Err(VfioError::VfioInvalidType); + } + + // Safe as file is vfio container and make sure val is valid. + let ret = unsafe { ioctl_with_val(self, VFIO_SET_IOMMU(), val.into()) }; + if ret < 0 { + return Err(VfioError::ContainerSetIOMMU); + } + + Ok(()) + } + + fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> { + let dma_map = vfio_iommu_type1_dma_map { + argsz: mem::size_of::() as u32, + flags: VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE, + vaddr: user_addr, + iova, + size, + }; + + // Safe as file is vfio container, dma_map is constructed by us, and + // we check the return value + let ret = unsafe { ioctl_with_ref(self, VFIO_IOMMU_MAP_DMA(), &dma_map) }; + if ret != 0 { + return Err(VfioError::IommuDmaMap); + } + + Ok(()) + } + + fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> { + let mut dma_unmap = vfio_iommu_type1_dma_unmap { + argsz: mem::size_of::() as u32, + flags: 0, + iova, + size, + }; + + // Safe as file is vfio container, dma_unmap is constructed by us, and + // we check the return value + let ret = unsafe { ioctl_with_mut_ref(self, VFIO_IOMMU_UNMAP_DMA(), &mut dma_unmap) }; + if ret != 0 || dma_unmap.size != size { + return Err(VfioError::IommuDmaUnmap); + } + + Ok(()) + } +} + +impl AsRawFd for VfioContainer { + fn as_raw_fd(&self) -> RawFd { + self.container.as_raw_fd() + } +} + +struct VfioGroup { + group: File, + device: DeviceFd, + container: VfioContainer, +} + +impl VfioGroup { + fn new(id: u32, vm: &Arc) -> Result { + let group_path = Path::new("/dev/vfio").join(id.to_string()); + let group = OpenOptions::new() + .read(true) + .write(true) + .open(&group_path) + .map_err(VfioError::OpenGroup)?; + + let mut group_status = vfio_group_status { + argsz: mem::size_of::() as u32, + flags: 0, + }; + // Safe as we are the owner of group and group_status which are valid value. + let mut ret = + unsafe { ioctl_with_mut_ref(&group, VFIO_GROUP_GET_STATUS(), &mut group_status) }; + if ret < 0 { + return Err(VfioError::GetGroupStatus); + } + + if group_status.flags != VFIO_GROUP_FLAGS_VIABLE { + return Err(VfioError::GroupViable); + } + + let container = VfioContainer::new()?; + if container.get_api_version() as u32 != VFIO_API_VERSION { + return Err(VfioError::VfioApiVersion); + } + + container.check_extension(VFIO_TYPE1v2_IOMMU)?; + + // Safe as we are the owner of group and container_raw_fd which are valid value, + // and we verify the ret value + let container_raw_fd = container.as_raw_fd(); + ret = unsafe { ioctl_with_ref(&group, VFIO_GROUP_SET_CONTAINER(), &container_raw_fd) }; + if ret < 0 { + return Err(VfioError::GroupSetContainer); + } + + container.set_iommu(VFIO_TYPE1v2_IOMMU)?; + + let device = Self::kvm_device_add_group(vm, &group)?; + + Ok(VfioGroup { + group, + device, + container, + }) + } + + fn kvm_device_add_group(vm: &VmFd, group: &File) -> Result { + let mut vfio_dev = kvm_bindings::kvm_create_device { + type_: kvm_bindings::kvm_device_type_KVM_DEV_TYPE_VFIO, + fd: 0, + flags: 0, + }; + + let device_fd = vm + .create_device(&mut vfio_dev) + .map_err(VfioError::CreateVfioKvmDevice)?; + + let group_fd = group.as_raw_fd(); + let group_fd_ptr = &group_fd as *const i32; + let dev_attr = kvm_bindings::kvm_device_attr { + flags: 0, + group: kvm_bindings::KVM_DEV_VFIO_GROUP, + attr: u64::from(kvm_bindings::KVM_DEV_VFIO_GROUP_ADD), + addr: group_fd_ptr as u64, + }; + + device_fd + .set_device_attr(&dev_attr) + .map_err(VfioError::KvmSetDeviceAttr)?; + + Ok(device_fd) + } + + fn kvm_device_del_group(&self) -> std::result::Result<(), io::Error> { + let group_fd = self.as_raw_fd(); + let group_fd_ptr = &group_fd as *const i32; + let dev_attr = kvm_bindings::kvm_device_attr { + flags: 0, + group: kvm_bindings::KVM_DEV_VFIO_GROUP, + attr: u64::from(kvm_bindings::KVM_DEV_VFIO_GROUP_DEL), + addr: group_fd_ptr as u64, + }; + + self.device.set_device_attr(&dev_attr) + } + + fn unset_container(&self) -> std::result::Result<(), io::Error> { + let container_raw_fd = self.container.as_raw_fd(); + + // Safe as we are the owner of self and container_raw_fd which are valid value. + let ret = unsafe { ioctl_with_ref(self, VFIO_GROUP_UNSET_CONTAINER(), &container_raw_fd) }; + if ret < 0 { + return Err(io::Error::last_os_error()); + } + + Ok(()) + } + + fn get_device(&self, name: &Path) -> Result { + let uuid_osstr = name.file_name().ok_or(VfioError::InvalidPath)?; + let uuid_str = uuid_osstr.to_str().ok_or(VfioError::InvalidPath)?; + let path: CString = CString::new(uuid_str.as_bytes()).expect("CString::new() failed"); + let path_ptr = path.as_ptr(); + + // Safe as we are the owner of self and path_ptr which are valid value. + let fd = unsafe { ioctl_with_ptr(self, VFIO_GROUP_GET_DEVICE_FD(), path_ptr) }; + if fd < 0 { + return Err(VfioError::GroupGetDeviceFD); + } + + // Safe as fd is valid FD + let device = unsafe { File::from_raw_fd(fd) }; + + let mut dev_info = vfio_device_info { + argsz: mem::size_of::() as u32, + flags: 0, + num_regions: 0, + num_irqs: 0, + }; + // Safe as we are the owner of dev and dev_info which are valid value, + // and we verify the return value. + let ret = unsafe { ioctl_with_mut_ref(&device, VFIO_DEVICE_GET_INFO(), &mut dev_info) }; + if ret < 0 + || (dev_info.flags & VFIO_DEVICE_FLAGS_PCI) == 0 + || dev_info.num_regions < VFIO_PCI_CONFIG_REGION_INDEX + 1 + || dev_info.num_irqs < VFIO_PCI_MSIX_IRQ_INDEX + 1 + { + return Err(VfioError::VfioDeviceGetInfo); + } + + Ok(VfioDeviceInfo { + device, + flags: dev_info.flags, + num_regions: dev_info.num_regions, + num_irqs: dev_info.num_irqs, + }) + } +} + +impl AsRawFd for VfioGroup { + fn as_raw_fd(&self) -> RawFd { + self.group.as_raw_fd() + } +} + +impl Drop for VfioGroup { + fn drop(&mut self) { + match self.kvm_device_del_group() { + Ok(_) => {} + Err(e) => { + error!("Could not delete VFIO group: {:?}", e); + } + } + + match self.unset_container() { + Ok(_) => {} + Err(e) => { + error!("Could not unset container: {:?}", e); + } + } + } +} + +struct VfioRegion { + flags: u32, + size: u64, + offset: u64, +} + +struct VfioIrq { + flags: u32, + index: u32, + count: u32, +} + +struct VfioDeviceInfo { + device: File, + flags: u32, + num_regions: u32, + num_irqs: u32, +} + +impl VfioDeviceInfo { + fn get_irqs(&self) -> Result> { + let mut irqs: HashMap = HashMap::new(); + + for index in 0..self.num_irqs { + let mut irq_info = vfio_irq_info { + argsz: mem::size_of::() as u32, + flags: 0, + index, + count: 0, + }; + + let ret = unsafe { + ioctl_with_mut_ref(&self.device, VFIO_DEVICE_GET_IRQ_INFO(), &mut irq_info) + }; + if ret < 0 { + warn!("Could not get VFIO IRQ info for index {:}", index); + continue; + } + + let irq = VfioIrq { + flags: irq_info.flags, + index, + count: irq_info.count, + }; + + debug!("IRQ #{}", index); + debug!("\tflag 0x{:x}", irq.flags); + debug!("\tindex {}", irq.index); + debug!("\tcount {}", irq.count); + + irqs.insert(index, irq); + } + + Ok(irqs) + } + + fn get_regions(&self) -> Result> { + let mut regions: Vec = Vec::new(); + + for i in VFIO_PCI_BAR0_REGION_INDEX..self.num_regions { + let mut reg_info = vfio_region_info { + argsz: mem::size_of::() as u32, + flags: 0, + index: i, + cap_offset: 0, + size: 0, + offset: 0, + }; + // Safe as we are the owner of dev and reg_info which are valid value, + // and we verify the return value. + let ret = unsafe { + ioctl_with_mut_ref(&self.device, VFIO_DEVICE_GET_REGION_INFO(), &mut reg_info) + }; + if ret < 0 { + error!("Could not get region #{} info", i); + continue; + } + + let region = VfioRegion { + flags: reg_info.flags, + size: reg_info.size, + offset: reg_info.offset, + }; + + debug!("Region #{}", i); + debug!("\tflag 0x{:x}", region.flags); + debug!("\tsize 0x{:x}", region.size); + debug!("\toffset 0x{:x}", region.offset); + + regions.push(region); + } + + Ok(regions) + } +} + +/// Vfio device for exposing regions which could be read/write to kernel vfio device. +pub struct VfioDevice { + device: File, + flags: u32, + group: VfioGroup, + regions: Vec, + irqs: HashMap, + mem: GuestMemoryMmap, +} + +impl VfioDevice { + /// Create a new vfio device, then guest read/write on this device could be + /// transfered into kernel vfio. + /// sysfspath specify the vfio device path in sys file system. + pub fn new(sysfspath: &Path, vm: &Arc, mem: GuestMemoryMmap) -> Result { + let uuid_path: PathBuf = [sysfspath, Path::new("iommu_group")].iter().collect(); + let group_path = uuid_path.read_link().map_err(|_| VfioError::InvalidPath)?; + let group_osstr = group_path.file_name().ok_or(VfioError::InvalidPath)?; + let group_str = group_osstr.to_str().ok_or(VfioError::InvalidPath)?; + let group_id = group_str + .parse::() + .map_err(|_| VfioError::InvalidPath)?; + + let group = VfioGroup::new(group_id, vm)?; + let device_info = group.get_device(sysfspath)?; + let regions = device_info.get_regions()?; + let irqs = device_info.get_irqs()?; + + Ok(VfioDevice { + device: device_info.device, + flags: device_info.flags, + group, + regions, + irqs, + mem, + }) + } + + /// VFIO device reset. + /// Only if the device supports being reset. + pub fn reset(&self) { + if self.flags & VFIO_DEVICE_FLAGS_RESET != 0 { + unsafe { ioctl(self, VFIO_DEVICE_RESET()) }; + } + } + + /// Enables a VFIO device IRQs. + /// This maps a vector of EventFds to all VFIO managed interrupts. In other words, this + /// tells VFIO which EventFd to write into whenever one of the device interrupt vector + /// is triggered. + /// + /// # Arguments + /// + /// * `irq_index` - The type (INTX, MSI or MSI-X) of interrupts to enable. + /// * `event_fds` - The EventFds vector that matches all the supported VFIO interrupts. + pub fn enable_irq(&self, irq_index: u32, event_fds: Vec<&EventFd>) -> Result<()> { + let irq = self + .irqs + .get(&irq_index) + .ok_or(VfioError::VfioDeviceSetIrq)?; + if irq.count == 0 { + return Err(VfioError::VfioDeviceSetIrq); + } + + let mut irq_set = vec_with_array_field::(event_fds.len()); + irq_set[0].argsz = mem::size_of::() as u32 + + (event_fds.len() * mem::size_of::()) as u32; + irq_set[0].flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER; + irq_set[0].index = irq_index; + irq_set[0].start = 0; + irq_set[0].count = irq.count; + + { + // irq_set.data could be none, bool or fd according to flags, so irq_set.data + // is u8 default, here irq_set.data is a vector of fds as u32, so 4 default u8 + // are combined together as u32 for each fd. + // It is safe as enough space is reserved through + // vec_with_array_field(u32). + let fds = unsafe { + irq_set[0] + .data + .as_mut_slice(event_fds.len() * mem::size_of::()) + }; + for (index, event_fd) in event_fds.iter().enumerate() { + let fds_offset = index * mem::size_of::(); + let fd = &mut fds[fds_offset..fds_offset + mem::size_of::()]; + LittleEndian::write_u32(fd, event_fd.as_raw_fd() as u32); + } + } + + // Safe as we are the owner of self and irq_set which are valid value + let ret = unsafe { ioctl_with_ref(self, VFIO_DEVICE_SET_IRQS(), &irq_set[0]) }; + if ret < 0 { + return Err(VfioError::VfioDeviceSetIrq); + } + + Ok(()) + } + + /// Disables a VFIO device IRQs + /// + /// # Arguments + /// + /// * `irq_index` - The type (INTX, MSI or MSI-X) of interrupts to disable. + pub fn disable_irq(&self, irq_index: u32) -> Result<()> { + let irq = self + .irqs + .get(&irq_index) + .ok_or(VfioError::VfioDeviceSetIrq)?; + if irq.count == 0 { + return Err(VfioError::VfioDeviceSetIrq); + } + + let mut irq_set = vec_with_array_field::(0); + irq_set[0].argsz = mem::size_of::() as u32; + irq_set[0].flags = VFIO_IRQ_SET_ACTION_MASK; + irq_set[0].index = irq_index; + irq_set[0].start = 0; + irq_set[0].count = irq.count; + + // Safe as we are the owner of self and irq_set which are valid value + let ret = unsafe { ioctl_with_ref(self, VFIO_DEVICE_SET_IRQS(), &irq_set[0]) }; + if ret < 0 { + return Err(VfioError::VfioDeviceSetIrq); + } + + Ok(()) + } + + /// Wrapper to enable MSI IRQs. + pub fn enable_msi(&self, fds: Vec<&EventFd>) -> Result<()> { + self.enable_irq(VFIO_PCI_MSI_IRQ_INDEX, fds) + } + + /// Wrapper to disable MSI IRQs. + pub fn disable_msi(&self) -> Result<()> { + self.disable_irq(VFIO_PCI_MSI_IRQ_INDEX) + } + + /// Wrapper to enable MSI-X IRQs. + pub fn enable_msix(&self, fds: Vec<&EventFd>) -> Result<()> { + self.enable_irq(VFIO_PCI_MSIX_IRQ_INDEX, fds) + } + + /// Wrapper to disable MSI-X IRQs. + pub fn disable_msix(&self) -> Result<()> { + self.disable_irq(VFIO_PCI_MSIX_IRQ_INDEX) + } + + /// Read region's data from VFIO device into buf + /// index: region num + /// buf: data destination and buf length is read size + /// addr: offset in the region + pub fn region_read(&self, index: u32, buf: &mut [u8], addr: u64) { + let region: &VfioRegion; + match self.regions.get(index as usize) { + Some(v) => region = v, + None => { + warn!("region read with invalid index: {}", index); + return; + } + } + + let size = buf.len() as u64; + if size > region.size || addr + size > region.size { + warn!( + "region read with invalid parameter, add: {}, size: {}", + addr, size + ); + return; + } + + if let Err(e) = self.device.read_exact_at(buf, region.offset + addr) { + warn!( + "Failed to read region in index: {}, addr: {}, error: {}", + index, addr, e + ); + } + } + + /// write the data from buf into a vfio device region + /// index: region num + /// buf: data src and buf length is write size + /// addr: offset in the region + pub fn region_write(&self, index: u32, buf: &[u8], addr: u64) { + let stub: &VfioRegion; + match self.regions.get(index as usize) { + Some(v) => stub = v, + None => { + warn!("region write with invalid index: {}", index); + return; + } + } + + let size = buf.len() as u64; + if size > stub.size + || addr + size > stub.size + || (stub.flags & VFIO_REGION_INFO_FLAG_WRITE) == 0 + { + warn!( + "region write with invalid parameter, add: {}, size: {}", + addr, size + ); + return; + } + + if let Err(e) = self.device.write_all_at(buf, stub.offset + addr) { + warn!( + "Failed to write region in index: {}, addr: {}, error: {}", + index, addr, e + ); + } + } + + fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> { + self.group.container.vfio_dma_map(iova, size, user_addr) + } + + fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> { + self.group.container.vfio_dma_unmap(iova, size) + } + + /// Add all guest memory regions into vfio container's iommu table, + /// then vfio kernel driver could access guest memory from gfn + pub fn setup_dma_map(&self) -> Result<()> { + self.mem.with_regions(|_index, region| { + self.vfio_dma_map( + region.start_addr().raw_value(), + region.len() as u64, + region.as_ptr() as u64, + ) + })?; + Ok(()) + } + + /// remove all guest memory regions from vfio containers iommu table + /// then vfio kernel driver couldn't access this guest memory + pub fn unset_dma_map(&self) -> Result<()> { + self.mem.with_regions(|_index, region| { + self.vfio_dma_unmap(region.start_addr().raw_value(), region.len() as u64) + })?; + Ok(()) + } + + /// Return the maximum numner of interrupts a VFIO device can request. + /// This is used for pre-allocating the VFIO PCI routes. + pub fn max_interrupts(&self) -> u32 { + let mut max_interrupts = 0; + let irq_indexes = vec![ + VFIO_PCI_INTX_IRQ_INDEX, + VFIO_PCI_MSI_IRQ_INDEX, + VFIO_PCI_MSIX_IRQ_INDEX, + ]; + + for index in irq_indexes { + if let Some(irq_info) = self.irqs.get(&index) { + if irq_info.count > max_interrupts { + max_interrupts = irq_info.count; + } + } + } + + max_interrupts + } +} + +impl AsRawFd for VfioDevice { + fn as_raw_fd(&self) -> RawFd { + self.device.as_raw_fd() + } +} diff --git a/vfio/src/vfio_ioctls.rs b/vfio/src/vfio_ioctls.rs new file mode 100644 index 000000000..12280bcd5 --- /dev/null +++ b/vfio/src/vfio_ioctls.rs @@ -0,0 +1,36 @@ +// Copyright © 2019 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +use vfio_bindings::bindings::vfio::*; + +ioctl_io_nr!(VFIO_GET_API_VERSION, VFIO_TYPE, VFIO_BASE); +ioctl_io_nr!(VFIO_CHECK_EXTENSION, VFIO_TYPE, VFIO_BASE + 1); +ioctl_io_nr!(VFIO_SET_IOMMU, VFIO_TYPE, VFIO_BASE + 2); +ioctl_io_nr!(VFIO_GROUP_GET_STATUS, VFIO_TYPE, VFIO_BASE + 3); +ioctl_io_nr!(VFIO_GROUP_SET_CONTAINER, VFIO_TYPE, VFIO_BASE + 4); +ioctl_io_nr!(VFIO_GROUP_UNSET_CONTAINER, VFIO_TYPE, VFIO_BASE + 5); +ioctl_io_nr!(VFIO_GROUP_GET_DEVICE_FD, VFIO_TYPE, VFIO_BASE + 6); +ioctl_io_nr!(VFIO_DEVICE_GET_INFO, VFIO_TYPE, VFIO_BASE + 7); +ioctl_io_nr!(VFIO_DEVICE_GET_REGION_INFO, VFIO_TYPE, VFIO_BASE + 8); +ioctl_io_nr!(VFIO_DEVICE_GET_IRQ_INFO, VFIO_TYPE, VFIO_BASE + 9); +ioctl_io_nr!(VFIO_DEVICE_SET_IRQS, VFIO_TYPE, VFIO_BASE + 10); +ioctl_io_nr!(VFIO_DEVICE_RESET, VFIO_TYPE, VFIO_BASE + 11); +ioctl_io_nr!( + VFIO_DEVICE_GET_PCI_HOT_RESET_INFO, + VFIO_TYPE, + VFIO_BASE + 12 +); +ioctl_io_nr!(VFIO_DEVICE_PCI_HOT_RESET, VFIO_TYPE, VFIO_BASE + 13); +ioctl_io_nr!(VFIO_DEVICE_QUERY_GFX_PLANE, VFIO_TYPE, VFIO_BASE + 14); +ioctl_io_nr!(VFIO_DEVICE_GET_GFX_DMABUF, VFIO_TYPE, VFIO_BASE + 15); +ioctl_io_nr!(VFIO_DEVICE_IOEVENTFD, VFIO_TYPE, VFIO_BASE + 16); +ioctl_io_nr!(VFIO_IOMMU_GET_INFO, VFIO_TYPE, VFIO_BASE + 12); +ioctl_io_nr!(VFIO_IOMMU_MAP_DMA, VFIO_TYPE, VFIO_BASE + 13); +ioctl_io_nr!(VFIO_IOMMU_UNMAP_DMA, VFIO_TYPE, VFIO_BASE + 14); +ioctl_io_nr!(VFIO_IOMMU_ENABLE, VFIO_TYPE, VFIO_BASE + 15); +ioctl_io_nr!(VFIO_IOMMU_DISABLE, VFIO_TYPE, VFIO_BASE + 16);