summary refs log tree commit diff
diff options
context:
space:
mode:
authorXiong Zhang <xiong.y.zhang@intel.corp-partner.google.com>2019-09-04 16:51:57 +0800
committerCommit Bot <commit-bot@chromium.org>2019-10-28 06:10:01 +0000
commitb19987d93f1f3498e5e1c949ae10dcb93233828b (patch)
tree278e0e55385dc2b9a74be18cb4fd087655605b25
parentf9815ee26f4452b67ef6e79cf3a4c623851bb620 (diff)
downloadcrosvm-b19987d93f1f3498e5e1c949ae10dcb93233828b.tar
crosvm-b19987d93f1f3498e5e1c949ae10dcb93233828b.tar.gz
crosvm-b19987d93f1f3498e5e1c949ae10dcb93233828b.tar.bz2
crosvm-b19987d93f1f3498e5e1c949ae10dcb93233828b.tar.lz
crosvm-b19987d93f1f3498e5e1c949ae10dcb93233828b.tar.xz
crosvm-b19987d93f1f3498e5e1c949ae10dcb93233828b.tar.zst
crosvm-b19987d93f1f3498e5e1c949ae10dcb93233828b.zip
vfio: Intx support
When hw reports it could support INTX, this patch enable it by passing
irqfd into vfio kernel.

Then once hw intx happens, the vfio kernel irq handler receives and
handles it, the handler will trigger irqfd and kvm injects the interrupt
into guest.

BUG=chromium:992270
TEST=None

Change-Id: I8b200174a91183b7324b0044fde13b44c751d4d7
Signed-off-by: Xiong Zhang <xiong.y.zhang@intel.corp-partner.google.com>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1813457
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
-rw-r--r--devices/src/pci/vfio_pci.rs58
-rw-r--r--devices/src/vfio.rs121
2 files changed, 163 insertions, 16 deletions
diff --git a/devices/src/pci/vfio_pci.rs b/devices/src/pci/vfio_pci.rs
index 645ee19..af70aef 100644
--- a/devices/src/pci/vfio_pci.rs
+++ b/devices/src/pci/vfio_pci.rs
@@ -15,7 +15,9 @@ use vfio_sys::*;
 use crate::pci::pci_device::{Error as PciDeviceError, PciDevice};
 use crate::pci::PciInterruptPin;
 
-use crate::vfio::VfioDevice;
+use crate::vfio::{VfioDevice, VfioIrqType};
+
+const PCI_INTERRUPT_PIN: u32 = 0x3D;
 
 struct VfioPciConfig {
     device: Arc<VfioDevice>,
@@ -225,6 +227,7 @@ pub struct VfioPciDevice {
     mmio_regions: Vec<MmioInfo>,
     io_regions: Vec<IoInfo>,
     msi_cap: Option<VfioMsiCap>,
+    irq_type: Option<VfioIrqType>,
 }
 
 impl VfioPciDevice {
@@ -243,6 +246,7 @@ impl VfioPciDevice {
             mmio_regions: Vec::new(),
             io_regions: Vec::new(),
             msi_cap,
+            irq_type: None,
         }
     }
 
@@ -259,6 +263,45 @@ impl VfioPciDevice {
 
         None
     }
+
+    fn enable_intx(&mut self) {
+        if self.interrupt_evt.is_none() || self.interrupt_resample_evt.is_none() {
+            return;
+        }
+
+        if let Some(ref interrupt_evt) = self.interrupt_evt {
+            if let Err(e) = self.device.irq_enable(interrupt_evt, VfioIrqType::Intx) {
+                error!("Intx enable failed: {}", e);
+                return;
+            }
+            if let Some(ref irq_resample_evt) = self.interrupt_resample_evt {
+                if let Err(e) = self.device.irq_mask(VfioIrqType::Intx) {
+                    error!("Intx mask failed: {}", e);
+                    self.disable_intx();
+                    return;
+                }
+                if let Err(e) = self.device.resample_virq_enable(irq_resample_evt) {
+                    error!("resample enable failed: {}", e);
+                    self.disable_intx();
+                    return;
+                }
+                if let Err(e) = self.device.irq_unmask(VfioIrqType::Intx) {
+                    error!("Intx unmask failed: {}", e);
+                    self.disable_intx();
+                    return;
+                }
+            }
+        }
+
+        self.irq_type = Some(VfioIrqType::Intx);
+    }
+
+    fn disable_intx(&mut self) {
+        if let Err(e) = self.device.irq_disable(VfioIrqType::Intx) {
+            error!("Intx disable failed: {}", e);
+        }
+        self.irq_type = None;
+    }
 }
 
 impl PciDevice for VfioPciDevice {
@@ -286,12 +329,16 @@ impl PciDevice for VfioPciDevice {
         irq_evt: EventFd,
         irq_resample_evt: EventFd,
         irq_num: u32,
-        irq_pin: PciInterruptPin,
+        _irq_pin: PciInterruptPin,
     ) {
         self.config.write_config_byte(irq_num as u8, 0x3C);
-        self.config.write_config_byte(irq_pin as u8 + 1, 0x3D);
         self.interrupt_evt = Some(irq_evt);
         self.interrupt_resample_evt = Some(irq_resample_evt);
+
+        // enable INTX
+        if self.config.read_config_byte(PCI_INTERRUPT_PIN) > 0 {
+            self.enable_intx();
+        }
     }
 
     fn allocate_io_bars(
@@ -420,12 +467,13 @@ impl PciDevice for VfioPciDevice {
                 if let Some(ref interrupt_evt) = self.interrupt_evt {
                     match msi_cap.write_msi_reg(start, data) {
                         Some(VfioMsiChange::Enable) => {
-                            if let Err(e) = self.device.msi_enable(interrupt_evt) {
+                            if let Err(e) = self.device.irq_enable(interrupt_evt, VfioIrqType::Msi)
+                            {
                                 error!("{}", e);
                             }
                         }
                         Some(VfioMsiChange::Disable) => {
-                            if let Err(e) = self.device.msi_disable() {
+                            if let Err(e) = self.device.irq_disable(VfioIrqType::Msi) {
                                 error!("{}", e);
                             }
                         }
diff --git a/devices/src/vfio.rs b/devices/src/vfio.rs
index cdaadca..501a0bb 100644
--- a/devices/src/vfio.rs
+++ b/devices/src/vfio.rs
@@ -38,8 +38,10 @@ pub enum VfioError {
     InvalidPath,
     IommuDmaMap(Error),
     IommuDmaUnmap(Error),
-    VfioMsiEnable(Error),
-    VfioMsiDisable(Error),
+    VfioIrqEnable(Error),
+    VfioIrqDisable(Error),
+    VfioIrqUnmask(Error),
+    VfioIrqMask(Error),
 }
 
 impl fmt::Display for VfioError {
@@ -61,8 +63,10 @@ impl fmt::Display for VfioError {
             VfioError::InvalidPath => write!(f,"invalid file path"),
             VfioError::IommuDmaMap(e) => write!(f, "failed to add guest memory map into iommu table: {}", e),
             VfioError::IommuDmaUnmap(e) => write!(f, "failed to remove guest memory map from iommu table: {}", e),
-            VfioError::VfioMsiEnable(e) => write!(f, "failed to enable vfio deviece's MSI: {}", e),
-            VfioError::VfioMsiDisable(e) => write!(f, "failed to disable vfio deviece's MSI: {}", e),
+            VfioError::VfioIrqEnable(e) => write!(f, "failed to enable vfio deviece's irq: {}", e),
+            VfioError::VfioIrqDisable(e) => write!(f, "failed to disable vfio deviece's irq: {}", e),
+            VfioError::VfioIrqUnmask(e) => write!(f, "failed to unmask vfio deviece's irq: {}", e),
+            VfioError::VfioIrqMask(e) => write!(f, "failed to mask vfio deviece's irq: {}", e),
         }
     }
 }
@@ -269,6 +273,13 @@ impl AsRawFd for VfioGroup {
     }
 }
 
+/// Vfio Irq type used to enable/disable/mask/unmask vfio irq
+pub enum VfioIrqType {
+    Intx,
+    Msi,
+    Msix,
+}
+
 struct VfioRegion {
     flags: u32,
     size: u64,
@@ -310,12 +321,16 @@ impl VfioDevice {
         })
     }
 
-    /// enable vfio device's MSI and associate EventFd with this MSI
-    pub fn msi_enable(&self, fd: &EventFd) -> Result<(), VfioError> {
+    /// enable vfio device's irq and associate Irqfd EventFd with device
+    pub fn irq_enable(&self, fd: &EventFd, irq_type: VfioIrqType) -> Result<(), VfioError> {
         let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(1);
         irq_set[0].argsz = (mem::size_of::<vfio_irq_set>() + mem::size_of::<u32>()) as u32;
         irq_set[0].flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
-        irq_set[0].index = VFIO_PCI_MSI_IRQ_INDEX;
+        match irq_type {
+            VfioIrqType::Intx => irq_set[0].index = VFIO_PCI_INTX_IRQ_INDEX,
+            VfioIrqType::Msi => irq_set[0].index = VFIO_PCI_MSI_IRQ_INDEX,
+            VfioIrqType::Msix => irq_set[0].index = VFIO_PCI_MSIX_IRQ_INDEX,
+        }
         irq_set[0].start = 0;
         irq_set[0].count = 1;
 
@@ -331,24 +346,108 @@ impl VfioDevice {
         // 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 {
-            Err(VfioError::VfioMsiEnable(get_error()))
+            Err(VfioError::VfioIrqEnable(get_error()))
         } else {
             Ok(())
         }
     }
 
-    pub fn msi_disable(&self) -> Result<(), VfioError> {
+    /// When intx is enabled, irqfd is used to trigger a level interrupt into guest, resample irqfd
+    /// is used to get guest EOI notification.
+    /// When host hw generates interrupt, vfio irq handler in host kernel receive and handle it,
+    /// this handler disable hw irq first, then trigger irqfd to inject interrupt into guest. When
+    /// resample irqfd is triggered by guest EOI, vfio kernel could enable hw irq, so hw could
+    /// generate another interrupts.
+    /// This function enable resample irqfd and let vfio kernel could get EOI notification.
+    ///
+    /// fd: should be resample IrqFd.
+    pub fn resample_virq_enable(&self, fd: &EventFd) -> Result<(), VfioError> {
+        let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(1);
+        irq_set[0].argsz = (mem::size_of::<vfio_irq_set>() + mem::size_of::<u32>()) as u32;
+        irq_set[0].flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_UNMASK;
+        irq_set[0].index = VFIO_PCI_INTX_IRQ_INDEX;
+        irq_set[0].start = 0;
+        irq_set[0].count = 1;
+
+        {
+            // irq_set.data could be none, bool or fd according to flags, so irq_set.data
+            // is u8 default, here irq_set.data is fd as u32, so 4 default u8 are combined
+            // together as u32. It is safe as enough space is reserved through
+            // vec_with_array_field(u32)<1>.
+            let fds = unsafe { irq_set[0].data.as_mut_slice(4) };
+            fds.copy_from_slice(&fd.as_raw_fd().to_le_bytes()[..]);
+        }
+
+        // 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 {
+            Err(VfioError::VfioIrqEnable(get_error()))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// disable vfio device's irq and disconnect Irqfd EventFd with device
+    pub fn irq_disable(&self, irq_type: VfioIrqType) -> Result<(), VfioError> {
         let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(0);
         irq_set[0].argsz = mem::size_of::<vfio_irq_set>() as u32;
         irq_set[0].flags = VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_TRIGGER;
-        irq_set[0].index = VFIO_PCI_MSI_IRQ_INDEX;
+        match irq_type {
+            VfioIrqType::Intx => irq_set[0].index = VFIO_PCI_INTX_IRQ_INDEX,
+            VfioIrqType::Msi => irq_set[0].index = VFIO_PCI_MSI_IRQ_INDEX,
+            VfioIrqType::Msix => irq_set[0].index = VFIO_PCI_MSIX_IRQ_INDEX,
+        }
         irq_set[0].start = 0;
         irq_set[0].count = 0;
 
         // 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 {
-            Err(VfioError::VfioMsiDisable(get_error()))
+            Err(VfioError::VfioIrqDisable(get_error()))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Unmask vfio device irq
+    pub fn irq_unmask(&self, irq_type: VfioIrqType) -> Result<(), VfioError> {
+        let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(0);
+        irq_set[0].argsz = mem::size_of::<vfio_irq_set>() as u32;
+        irq_set[0].flags = VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_UNMASK;
+        match irq_type {
+            VfioIrqType::Intx => irq_set[0].index = VFIO_PCI_INTX_IRQ_INDEX,
+            VfioIrqType::Msi => irq_set[0].index = VFIO_PCI_MSI_IRQ_INDEX,
+            VfioIrqType::Msix => irq_set[0].index = VFIO_PCI_MSIX_IRQ_INDEX,
+        }
+        irq_set[0].start = 0;
+        irq_set[0].count = 1;
+
+        // 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 {
+            Err(VfioError::VfioIrqUnmask(get_error()))
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Mask vfio device irq
+    pub fn irq_mask(&self, irq_type: VfioIrqType) -> Result<(), VfioError> {
+        let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(0);
+        irq_set[0].argsz = mem::size_of::<vfio_irq_set>() as u32;
+        irq_set[0].flags = VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_MASK;
+        match irq_type {
+            VfioIrqType::Intx => irq_set[0].index = VFIO_PCI_INTX_IRQ_INDEX,
+            VfioIrqType::Msi => irq_set[0].index = VFIO_PCI_MSI_IRQ_INDEX,
+            VfioIrqType::Msix => irq_set[0].index = VFIO_PCI_MSIX_IRQ_INDEX,
+        }
+        irq_set[0].start = 0;
+        irq_set[0].count = 1;
+
+        // 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 {
+            Err(VfioError::VfioIrqMask(get_error()))
         } else {
             Ok(())
         }