// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved. // // StratoVirt is licensed under Mulan PSL v2. // You can use this software according to the terms and conditions of the Mulan // PSL v2. // You may obtain a copy of Mulan PSL v2 at: // http://license.coscl.org.cn/MulanPSL2 // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. use std::collections::HashMap; use std::ffi::CString; use std::fs::{File, OpenOptions}; use std::mem::size_of; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::prelude::FileExt; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, Weak}; use anyhow::{anyhow, bail, Context, Result}; use byteorder::{ByteOrder, LittleEndian}; use kvm_bindings::{ kvm_device_attr, KVM_DEV_VFIO_GROUP, KVM_DEV_VFIO_GROUP_ADD, KVM_DEV_VFIO_GROUP_DEL, }; use vfio_bindings::bindings::vfio; use vmm_sys_util::ioctl::{ ioctl, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref, ioctl_with_val, }; use vmm_sys_util::{ioctl_io_nr, ioctl_ioc_nr}; use super::{CONTAINERS, GROUPS, KVM_DEVICE_FD}; use crate::VfioError; use address_space::{AddressSpace, FlatRange, Listener, ListenerReqType, RegionIoEventFd}; /// Refer to VFIO in https://github.com/torvalds/linux/blob/master/include/uapi/linux/vfio.h const IOMMU_GROUP: &str = "iommu_group"; const GROUP_PATH: &str = "/dev/vfio"; const CONTAINER_PATH: &str = "/dev/vfio/vfio"; ioctl_io_nr!(VFIO_GET_API_VERSION, vfio::VFIO_TYPE, vfio::VFIO_BASE); ioctl_io_nr!( VFIO_CHECK_EXTENSION, vfio::VFIO_TYPE, vfio::VFIO_BASE + 0x01 ); ioctl_io_nr!(VFIO_SET_IOMMU, vfio::VFIO_TYPE, vfio::VFIO_BASE + 0x02); ioctl_io_nr!( VFIO_GROUP_GET_STATUS, vfio::VFIO_TYPE, vfio::VFIO_BASE + 0x03 ); ioctl_io_nr!( VFIO_GROUP_SET_CONTAINER, vfio::VFIO_TYPE, vfio::VFIO_BASE + 0x04 ); ioctl_io_nr!( VFIO_GROUP_UNSET_CONTAINER, vfio::VFIO_TYPE, vfio::VFIO_BASE + 0x05 ); ioctl_io_nr!( VFIO_GROUP_GET_DEVICE_FD, vfio::VFIO_TYPE, vfio::VFIO_BASE + 0x06 ); ioctl_io_nr!( VFIO_DEVICE_GET_INFO, vfio::VFIO_TYPE, vfio::VFIO_BASE + 0x07 ); ioctl_io_nr!( VFIO_DEVICE_GET_REGION_INFO, vfio::VFIO_TYPE, vfio::VFIO_BASE + 0x08 ); ioctl_io_nr!( VFIO_DEVICE_SET_IRQS, vfio::VFIO_TYPE, vfio::VFIO_BASE + 0x0a ); ioctl_io_nr!(VFIO_DEVICE_RESET, vfio::VFIO_TYPE, vfio::VFIO_BASE + 0x0b); ioctl_io_nr!(VFIO_IOMMU_MAP_DMA, vfio::VFIO_TYPE, vfio::VFIO_BASE + 0x0d); ioctl_io_nr!( VFIO_IOMMU_UNMAP_DMA, vfio::VFIO_TYPE, vfio::VFIO_BASE + 0x0e ); /// Vfio container class can hold one or more groups. In IOMMUs, page tables are shared between /// different groups, vfio container can reduce TLB thrashing and duplicate page tables. /// A container can be created by simply opening the `/dev/vfio/vfio` file. pub struct VfioContainer { /// `/dev/vfio/vfio` file fd, empowered by the attached groups. pub fd: File, /// A set of groups in the same container. pub groups: Mutex<HashMap<u32, Arc<VfioGroup>>>, // Whether enabled as a memory listener. enabled: bool, } impl VfioContainer { /// Create a VFIO container. /// /// Return Error if /// * Fail to open `/dev/vfio/vfio` file. /// * Fail to match container api version or extension. /// * Only support api version type1v2 IOMMU. pub fn new() -> Result<Self> { let fd = OpenOptions::new() .read(true) .write(true) .open(CONTAINER_PATH) .with_context(|| format!("Failed to open {} for VFIO container.", CONTAINER_PATH))?; // SAFETY: Called file is `/dev/vfio/vfio` fd and we check the return. let v = unsafe { ioctl(&fd, VFIO_GET_API_VERSION()) }; if v as u32 != vfio::VFIO_API_VERSION { return Err(anyhow!(VfioError::VfioIoctl( "VFIO_GET_API_VERSION".to_string(), std::io::Error::last_os_error(), ))); }; let ret = // SAFETY: Ioctl is safe. Called file is `/dev/vfio/vfio` fd and we check the return. unsafe { ioctl_with_val(&fd, VFIO_CHECK_EXTENSION(), vfio::VFIO_TYPE1v2_IOMMU.into()) }; if ret != 1 { return Err(anyhow!(VfioError::VfioIoctl( "VFIO_CHECK_EXTENSION".to_string(), std::io::Error::last_os_error(), ))); } Ok(VfioContainer { fd, groups: Mutex::new(HashMap::new()), enabled: false, }) } /// Set specific IOMMU type for the container. /// /// # Arguments /// /// * `val` - IOMMU type. /// /// Return Error if /// * Fail to match IOMMU type. /// * Fail to set container IOMMU. fn set_iommu(&self, val: u32) -> Result<()> { // SAFETY: Called container file is `/dev/vfio/vfio` fd and we check the return. let ret = unsafe { ioctl_with_val(&self.fd, VFIO_SET_IOMMU(), val.into()) }; if ret < 0 { return Err(anyhow!(VfioError::VfioIoctl( "VFIO_SET_IOMMU".to_string(), std::io::Error::last_os_error(), ))); } Ok(()) } /// Try to add a region of guest memory map into IOMMU table. /// /// # Arguments /// /// * `iova` - GPA of Guest memory region. /// * `size` - Region size. /// * `user_addr` - HVA of Guest memory region. /// /// Return Error if /// * Fail to map memory into IOMMU table. fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> { let map = vfio::vfio_iommu_type1_dma_map { argsz: size_of::<vfio::vfio_iommu_type1_dma_map>() as u32, flags: vfio::VFIO_DMA_MAP_FLAG_READ | vfio::VFIO_DMA_MAP_FLAG_WRITE, vaddr: user_addr, iova, size, }; // SAFETY: Called container file is `/dev/vfio/vfio` fd and we check the return. let ret = unsafe { ioctl_with_ref(&self.fd, VFIO_IOMMU_MAP_DMA(), &map) }; if ret != 0 { return Err(anyhow!(VfioError::VfioIoctl( "VFIO_IOMMU_MAP_DMA".to_string(), std::io::Error::last_os_error(), ))); } Ok(()) } /// Unmap DMA region for the "type1" IOMMU interface. /// /// # Arguments /// /// * `iova` - GPA of Guest memory region. /// * `size` - Region size. /// /// Return Error if /// * Fail to unmap DMA region. fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> { let unmap = vfio::vfio_iommu_type1_dma_unmap { argsz: size_of::<vfio::vfio_iommu_type1_dma_unmap>() as u32, flags: 0, iova, size, }; // SAFETY: Called container file is `/dev/vfio/vfio` fd and we check the return. let ret = unsafe { ioctl_with_ref(&self.fd, VFIO_IOMMU_UNMAP_DMA(), &unmap) }; if ret != 0 { return Err(anyhow!(VfioError::VfioIoctl( "VFIO_IOMMU_UNMAP_DMA".to_string(), std::io::Error::last_os_error(), ))); } Ok(()) } fn add_listener_region(&self, fr: &FlatRange) -> Result<()> { if fr.owner.region_type() != address_space::RegionType::Ram { return Ok(()); } let guest_phys_addr = fr.addr_range.base.raw_value(); let memory_size = fr.addr_range.size; let hva = match fr.owner.get_host_address() { Some(addr) => addr, None => bail!("Failed to get host address"), }; let userspace_addr = hva + fr.offset_in_region; Result::with_context( self.vfio_dma_map(guest_phys_addr, memory_size, userspace_addr), || { format!( "Failed to do dma map: gpa 0x{:x}, size 0x{:x}, hva 0x{:x}", guest_phys_addr, memory_size, userspace_addr ) }, )?; Ok(()) } fn del_listener_region(&self, fr: &FlatRange) -> Result<()> { if fr.owner.region_type() != address_space::RegionType::Ram { return Ok(()); } let guest_phys_addr = fr.addr_range.base.raw_value(); let size = fr.addr_range.size; Result::with_context(self.vfio_dma_unmap(guest_phys_addr, size), || { format!( "Failed to do dma unmap: gpa 0x{:x}, size 0x{:x}.", guest_phys_addr, size ) })?; Ok(()) } } impl Listener for VfioContainer { fn priority(&self) -> i32 { 0 } fn enabled(&self) -> bool { self.enabled } fn enable(&mut self) { self.enabled = true; } fn disable(&mut self) { self.enabled = false; } fn handle_request( &self, range: Option<&FlatRange>, _evtfd: Option<&RegionIoEventFd>, req_type: ListenerReqType, ) -> Result<()> { match req_type { ListenerReqType::AddRegion => { self.add_listener_region(range.unwrap())?; } ListenerReqType::DeleteRegion => { self.del_listener_region(range.unwrap())?; } _ => {} } Ok(()) } } /// Vfio group is a member of IOMMU group, which contains a set of devices isolated from all /// other devices in the system. /// A vfio group can be created by opening `/dev/vfio/$group_id`, where $group_id represents the /// IOMMU group number. pub struct VfioGroup { /// Group id. pub id: u32, /// `/dev/vfio/$group_id` file fd. pub fd: File, container: Weak<Mutex<VfioContainer>>, /// Devices in the group. pub devices: Mutex<HashMap<RawFd, Arc<Mutex<VfioDevice>>>>, } impl VfioGroup { fn new(group_id: u32) -> Result<Self> { let group_path = Path::new(GROUP_PATH).join(group_id.to_string()); let file = OpenOptions::new() .read(true) .write(true) .open(&group_path) .with_context(|| { format!( "Failed to open {} for iommu_group.", group_path.to_str().unwrap() ) })?; let mut status = vfio::vfio_group_status { argsz: size_of::<vfio::vfio_group_status>() as u32, flags: 0, }; // SAFETY: file is `iommu_group` fd, and we check the return. let ret = unsafe { ioctl_with_mut_ref(&file, VFIO_GROUP_GET_STATUS(), &mut status) }; if ret < 0 { return Err(anyhow!(VfioError::VfioIoctl( "VFIO_GROUP_GET_STATUS".to_string(), std::io::Error::last_os_error(), ))); } if status.flags != vfio::VFIO_GROUP_FLAGS_VIABLE { bail!( "Group is not viable, ensure all devices within the IOMMU group are bound to \ their VFIO bus driver." ); } Ok(VfioGroup { id: group_id, fd: file, container: Weak::new(), devices: Mutex::new(HashMap::new()), }) } /// Add group to kvm VFIO device. /// /// Return Error if /// * Fail to set group to kvm device. fn add_to_kvm_device(&self) -> Result<()> { let attr = kvm_device_attr { flags: 0, group: KVM_DEV_VFIO_GROUP, attr: u64::from(KVM_DEV_VFIO_GROUP_ADD), addr: &self.fd.as_raw_fd() as *const i32 as u64, }; match KVM_DEVICE_FD.lock().unwrap().as_ref() { Some(fd) => fd .set_device_attr(&attr) .with_context(|| "Failed to add group to kvm device.")?, None => bail!("Kvm device hasn't been created."), } Ok(()) } /// Delete group from kvm VFIO device. /// /// Return Error if /// * Fail to delete group. pub fn del_from_kvm_device(&self) -> Result<()> { let attr = kvm_device_attr { flags: 0, group: KVM_DEV_VFIO_GROUP, attr: u64::from(KVM_DEV_VFIO_GROUP_DEL), addr: &self.fd.as_raw_fd() as *const i32 as u64, }; match KVM_DEVICE_FD.lock().unwrap().as_ref() { Some(fd) => fd .set_device_attr(&attr) .with_context(|| "Failed to delete group from kvm device.")?, None => bail!("Kvm device hasn't been created."), } Ok(()) } fn set_container(&mut self, container: &Arc<Mutex<VfioContainer>>) -> Result<()> { let fd = &container.lock().unwrap().fd.as_raw_fd(); // SAFETY: group is the owner of file, and we check the return. let ret = unsafe { ioctl_with_ref(&self.fd, VFIO_GROUP_SET_CONTAINER(), fd) }; if ret < 0 { return Err(anyhow!(VfioError::VfioIoctl( "VFIO_GROUP_SET_CONTAINER".to_string(), std::io::Error::last_os_error(), ))); } self.container = Arc::downgrade(container); Ok(()) } fn unset_container(&mut self) { let container = self.container.upgrade().unwrap(); let fd = container.lock().unwrap().fd.as_raw_fd(); // SAFETY: self.fd was created in function new(). unsafe { ioctl_with_ref(&self.fd, VFIO_GROUP_UNSET_CONTAINER(), &fd) }; self.container = Weak::new(); } fn connect_container(&mut self, mem_as: &Arc<AddressSpace>) -> Result<()> { for (_fd, container) in CONTAINERS.lock().unwrap().iter() { if self.set_container(container).is_ok() { self.add_to_kvm_device()?; return Ok(()); } } // No containers existed or can not be attached to the existed containers. if self.container.upgrade().is_none() { let container = Arc::new(Mutex::new(VfioContainer::new()?)); self.set_container(&container)?; container .lock() .unwrap() .set_iommu(vfio::VFIO_TYPE1v2_IOMMU)?; let fd = container.lock().unwrap().fd.as_raw_fd(); CONTAINERS.lock().unwrap().insert(fd, container); } self.add_to_kvm_device()?; mem_as .register_listener(self.container.upgrade().unwrap()) .with_context(|| "Failed to register memory listener.")?; Ok(()) } } pub struct VfioDevInfo { pub num_irqs: u32, flags: u32, } /// Vfio device includes the group and container it belongs to, I/O regions and interrupt /// notifications info. pub struct VfioDevice { /// File descriptor for a VFIO device instance. pub fd: File, /// Identify the unique VFIO device. pub name: String, /// Vfio group the device belongs to. pub group: Weak<VfioGroup>, /// Vfio container the device belongs to. pub container: Weak<Mutex<VfioContainer>>, /// Information of the vfio device instance. pub dev_info: VfioDevInfo, /// Unmasked MSI-X vectors. pub nr_vectors: usize, } #[derive(Debug, Copy, Clone, Default)] pub struct MmapInfo { pub size: u64, pub offset: u64, } pub struct VfioRegion { // Size of device region. pub size: u64, // Offset of device region. pub region_offset: u64, // Region flags. pub flags: u32, // Region size and offset that can be mapped. pub mmaps: Vec<MmapInfo>, // Guest physical address. pub guest_phys_addr: u64, } #[repr(C)] #[derive(Debug, Default)] struct VfioRegionWithCap { region_info: vfio::vfio_region_info, cap_info: vfio::__IncompleteArrayField<u8>, } impl VfioDevice { pub fn new(path: &Path, mem_as: &Arc<AddressSpace>) -> Result<Arc<Mutex<Self>>> { if !path.exists() { bail!("No provided host PCI device, use -device vfio-pci,host=DDDD:BB:DD.F"); } let group = Self::vfio_get_group(path, mem_as).with_context(|| "Failed to get iommu group")?; let (name, fd) = Self::vfio_get_device(&group, path).with_context(|| "Failed to get vfio device")?; let dev_info = Self::get_dev_info(&fd).with_context(|| "Failed to get device info")?; let vfio_dev = Arc::new(Mutex::new(VfioDevice { fd, name, group: Arc::downgrade(&group), container: group.container.clone(), dev_info, nr_vectors: 0, })); group .devices .lock() .unwrap() .insert(vfio_dev.lock().unwrap().fd.as_raw_fd(), vfio_dev.clone()); Ok(vfio_dev) } fn vfio_get_group(dev_path: &Path, mem_as: &Arc<AddressSpace>) -> Result<Arc<VfioGroup>> { let iommu_group: PathBuf = [dev_path, Path::new(IOMMU_GROUP)] .iter() .collect::<PathBuf>() .read_link() .with_context(|| "Invalid iommu group path")?; let group_name = iommu_group .file_name() .with_context(|| "Invalid iommu group name")?; let mut group_id = 0; if let Some(n) = group_name.to_str() { group_id = n.parse::<u32>().with_context(|| "Invalid iommu group id")?; } if let Some(g) = GROUPS.lock().unwrap().get(&group_id) { return Ok(g.clone()); } let mut group = VfioGroup::new(group_id)?; if let Err(e) = group.connect_container(mem_as) { group.unset_container(); return Err(e); } let group = Arc::new(group); GROUPS.lock().unwrap().insert(group_id, group.clone()); group .container .upgrade() .unwrap() .lock() .unwrap() .groups .lock() .unwrap() .insert(group_id, group.clone()); Ok(group) } fn vfio_get_device(group: &VfioGroup, name: &Path) -> Result<(String, File)> { let mut dev_name: &str = ""; if let Some(n) = name.file_name() { dev_name = n.to_str().with_context(|| "Invalid device path")?; } for device in group.devices.lock().unwrap().iter() { if device.1.lock().unwrap().name == dev_name { bail!("Device {} is already attached", dev_name); } } let path: CString = CString::new(dev_name.as_bytes()) .with_context(|| "Failed to convert device name to CString type of data")?; let ptr = path.as_ptr(); // SAFETY: group is the owner of file and make sure ptr is valid. let fd = unsafe { ioctl_with_ptr(&group.fd, VFIO_GROUP_GET_DEVICE_FD(), ptr) }; if fd < 0 { return Err(anyhow!(VfioError::VfioIoctl( "VFIO_GROUP_GET_DEVICE_FD".to_string(), std::io::Error::last_os_error(), ))); } // SAFETY: We have verified that fd is a valid FD. let device = unsafe { File::from_raw_fd(fd) }; Ok((String::from(dev_name), device)) } fn get_dev_info(device: &File) -> Result<VfioDevInfo> { let mut dev_info = vfio::vfio_device_info { argsz: size_of::<vfio::vfio_device_info>() as u32, flags: 0, num_regions: 0, num_irqs: 0, }; // SAFETY: Device is the owner of file, and we will verify the result is valid. let ret = unsafe { ioctl_with_mut_ref(device, VFIO_DEVICE_GET_INFO(), &mut dev_info) }; if ret < 0 || (dev_info.flags & vfio::VFIO_DEVICE_FLAGS_PCI) == 0 || dev_info.num_regions < vfio::VFIO_PCI_CONFIG_REGION_INDEX + 1 || dev_info.num_irqs < vfio::VFIO_PCI_MSIX_IRQ_INDEX + 1 { return Err(anyhow!(VfioError::VfioIoctl( "VFIO_DEVICE_GET_INFO".to_string(), std::io::Error::last_os_error(), ))); } Ok(VfioDevInfo { num_irqs: dev_info.num_irqs, flags: dev_info.flags, }) } fn region_mmap_info(&self, info: vfio::vfio_region_info) -> Result<Vec<MmapInfo>> { let mut mmaps = Vec::new(); if info.flags & vfio::VFIO_REGION_INFO_FLAG_MMAP != 0 { mmaps.push(MmapInfo { size: info.size, offset: 0, }); let argsz = size_of::<vfio::vfio_region_info>() as u32; if info.flags & vfio::VFIO_REGION_INFO_FLAG_CAPS != 0 && info.argsz > argsz { let cap_size = (info.argsz - argsz) as usize; let mut new_info = array_to_vec::<VfioRegionWithCap, u8>(cap_size); new_info[0].region_info = info; // SAFETY: Device is the owner of file, and we will verify the result is valid. let ret = unsafe { ioctl_with_mut_ref( &self.fd, VFIO_DEVICE_GET_REGION_INFO(), &mut (new_info[0].region_info), ) }; if ret < 0 { return Err(anyhow!(VfioError::VfioIoctl( "VFIO_DEVICE_GET_REGION_INFO".to_string(), std::io::Error::last_os_error(), ))); } // SAFETY: We make sure there is enough memory space to convert cap info into // specific structure. let sparse = unsafe { new_info[0].cap_info.as_ptr() as *mut vfio::vfio_region_info_cap_sparse_mmap }; // SAFETY: sparse was created in this function and can be guaranteed now be null. if unsafe { (*sparse).header.id } == vfio::VFIO_REGION_INFO_CAP_SPARSE_MMAP as u16 { // SAFETY: The reason is same as above. let nr_areas = unsafe { (*sparse).nr_areas as usize }; let areas: &mut [vfio::vfio_region_sparse_mmap_area] = // SAFETY: The reason is same as above. unsafe { (*sparse).areas.as_mut_slice(nr_areas) }; mmaps = Vec::with_capacity(nr_areas); for area in areas.iter() { if area.size > 0 { mmaps.push(MmapInfo { size: area.size, offset: area.offset, }); } } } } } Ok(mmaps) } fn region_info(&self, index: u32) -> Result<vfio::vfio_region_info> { let argsz = size_of::<vfio::vfio_region_info>() as u32; let mut info = vfio::vfio_region_info { argsz, flags: 0, index, cap_offset: 0, size: 0, offset: 0, }; // SAFETY: Device is the owner of file, and we will verify the result is valid. let ret = unsafe { ioctl_with_mut_ref(&self.fd, VFIO_DEVICE_GET_REGION_INFO(), &mut info) }; if ret < 0 { return Err(anyhow!(VfioError::VfioIoctl( "VFIO_DEVICE_GET_REGION_INFO".to_string(), std::io::Error::last_os_error(), ))); } Ok(info) } pub fn get_regions_info(&self) -> Result<Vec<VfioRegion>> { let mut regions: Vec<VfioRegion> = Vec::new(); for index in vfio::VFIO_PCI_BAR0_REGION_INDEX..vfio::VFIO_PCI_ROM_REGION_INDEX { let info = self .region_info(index) .with_context(|| "Fail to get region info")?; let mut mmaps = Vec::new(); if info.size > 0 { mmaps = self .region_mmap_info(info) .with_context(|| "Fail to get region mmap info")?; } regions.push(VfioRegion { size: info.size, region_offset: info.offset, flags: info.flags, mmaps, guest_phys_addr: 0, }); } Ok(regions) } /// Read region information from VFIO device. /// /// # Arguments /// /// * `buf` - The destination that the data would be read to. /// * `region_offset` - Vfio device region offset from its device descriptor. /// * `addr` - Offset in the region to read data. pub fn read_region(&self, buf: &mut [u8], region_offset: u64, addr: u64) -> Result<()> { self.fd .read_exact_at(buf, region_offset + addr) .with_context(|| "Failed to read vfio region")?; Ok(()) } /// Write region information to VFIO device. /// /// # Arguments /// /// * `buf` - The data that would be written to. /// * `region_offset` - Vfio device region offset from its device descriptor. /// * `addr` - Offset in the region to write. pub fn write_region(&self, buf: &[u8], region_offset: u64, addr: u64) -> Result<()> { self.fd .write_all_at(buf, region_offset + addr) .with_context(|| "Failed to write vfio region")?; Ok(()) } /// Bind irqs to kvm interrupts. /// /// # Arguments /// /// * `irq_fds` - Irq fds that will be registered to kvm. /// * `start` - The start of subindexes being specified. pub fn enable_irqs(&mut self, irq_fds: Vec<RawFd>, start: u32) -> Result<()> { let mut irq_set = array_to_vec::<vfio::vfio_irq_set, u32>(irq_fds.len()); irq_set[0].argsz = (size_of::<vfio::vfio_irq_set>() + irq_fds.len() * size_of::<RawFd>()) as u32; irq_set[0].flags = vfio::VFIO_IRQ_SET_DATA_EVENTFD | vfio::VFIO_IRQ_SET_ACTION_TRIGGER; irq_set[0].index = vfio::VFIO_PCI_MSIX_IRQ_INDEX; irq_set[0].start = start; irq_set[0].count = irq_fds.len() as u32; // SAFETY: It is safe as enough memory space to save irq_set data. let data: &mut [u8] = unsafe { irq_set[0] .data .as_mut_slice(irq_fds.len() * size_of::<RawFd>()) }; LittleEndian::write_i32_into(irq_fds.as_slice(), data); // SAFETY: Device is the owner of file, and we will verify the result is valid. let ret = unsafe { ioctl_with_ref(&self.fd, VFIO_DEVICE_SET_IRQS(), &irq_set[0]) }; if ret < 0 { return Err(anyhow!(VfioError::VfioIoctl( "VFIO_DEVICE_SET_IRQS".to_string(), std::io::Error::last_os_error(), ))); } Ok(()) } /// Unbind irqs from kvm interrupts. /// /// # Arguments /// /// * `irq_fds` - Irq fds that will be registered to kvm. pub fn disable_irqs(&mut self) -> Result<()> { if self.nr_vectors == 0 { return Ok(()); } let mut irq_set = array_to_vec::<vfio::vfio_irq_set, u32>(0); irq_set[0].argsz = size_of::<vfio::vfio_irq_set>() as u32; irq_set[0].flags = vfio::VFIO_IRQ_SET_DATA_NONE | vfio::VFIO_IRQ_SET_ACTION_TRIGGER; irq_set[0].index = vfio::VFIO_PCI_MSIX_IRQ_INDEX; irq_set[0].start = 0u32; irq_set[0].count = 0u32; // SAFETY: Device is the owner of file, and we will verify the result is valid. let ret = unsafe { ioctl_with_ref(&self.fd, VFIO_DEVICE_SET_IRQS(), &irq_set[0]) }; if ret < 0 { return Err(anyhow!(VfioError::VfioIoctl( "VFIO_DEVICE_SET_IRQS".to_string(), std::io::Error::last_os_error(), ))); } self.nr_vectors = 0; Ok(()) } pub fn reset(&self) -> Result<()> { if self.dev_info.flags & vfio::VFIO_DEVICE_FLAGS_RESET != 0 { // SAFETY: Device is the owner of file, and we verify the device supports being reset. let ret = unsafe { ioctl(&self.fd, VFIO_DEVICE_RESET()) }; if ret < 0 { return Err(anyhow!(VfioError::VfioIoctl( "VFIO_DEVICE_RESET".to_string(), std::io::Error::last_os_error(), ))); } } Ok(()) } } /// In VFIO, there are several structures contains zero-length array, as follows: /// ``` /// use vfio_bindings::bindings::vfio::__IncompleteArrayField; /// struct Foo { /// info: u8, /// array: __IncompleteArrayField<u8>, /// } /// ``` /// Size_of::<Foo>() is too small to keep array data. Because array is zero-length array, we are not /// sure how much memory is required, and the array memory must be contiguous with info data. /// The function is used to allocate enough memory space for info and array data. fn array_to_vec<T: Default, F>(len: usize) -> Vec<T> { let round = (len * size_of::<F>() + 2 * size_of::<T>() - 1) / size_of::<T>(); let mut vec = Vec::with_capacity(round); for _ in 0..round { vec.push(T::default()); } vec } #[cfg(test)] mod tests { use crate::vfio_dev::array_to_vec; #[test] fn test_array_to_vec() { let vec1 = array_to_vec::<u8, u8>(1); assert_eq!(vec1.len(), 2); let vec2 = array_to_vec::<u16, u8>(2); assert_eq!(vec2.len(), 2); let vec3 = array_to_vec::<u8, u32>(2); assert_eq!(vec3.len(), 9); } }