[RFC PATCH kvmtool 10/15] virtio-pci: translate MSIs with the virtual IOMMU

Jean-Philippe Brucker
2017-04-07T19:24:50Z

When the virtio device is behind a virtual IOMMU, the doorbell address
written into the MSI-X table by the guest is an IOVA, not a physical one.
When injecting an MSI, KVM needs a physical address to recognize the
doorbell and the associated IRQ chip. Translate the address given by the
guest into a physical one, and store it in a secondary table for easy
access.

Signed-off-by: Jean-Philippe Brucker <jean-philippe.brucker at arm.com>
---
 include/kvm/iommu.h      |  4 ++++
 include/kvm/virtio-pci.h |  1 +
 iommu.c                  | 23 +++++++++++++++++++++++
 virtio/pci.c             | 33 ++++++++++++++++++++++++---------
 4 files changed, 52 insertions(+), 9 deletions(-)

diff --git a/include/kvm/iommu.h b/include/kvm/iommu.h
index 4164ba20..8f87ce5a 100644
--- a/include/kvm/iommu.h
+++ b/include/kvm/iommu.h
@@ -70,4 +70,8 @@ int iommu_unmap(void *address_space, u64 virt_addr, u64 size,
int flags);
 u64 iommu_access(void *address_space, u64 addr, size_t size, size_t *out_size,
 		 int prot);

+struct msi_msg;
+
+int iommu_translate_msi(void *address_space, struct msi_msg *msi);
+
 #endif /* KVM_IOMMU_H */
diff --git a/include/kvm/virtio-pci.h b/include/kvm/virtio-pci.h
index 26772f74..cb5225d6 100644
--- a/include/kvm/virtio-pci.h
+++ b/include/kvm/virtio-pci.h
@@ -47,6 +47,7 @@ struct virtio_pci {
 	u32			msix_io_block;
 	u64			msix_pba;
 	struct msix_table	msix_table[VIRTIO_PCI_MAX_VQ + VIRTIO_PCI_MAX_CONFIG];
+	struct msi_msg		msix_msgs[VIRTIO_PCI_MAX_VQ + VIRTIO_PCI_MAX_CONFIG];

 	/* virtio queue */
 	u16			queue_selector;
diff --git a/iommu.c b/iommu.c
index 0a662404..c10a3f0b 100644
--- a/iommu.c
+++ b/iommu.c
@@ -5,6 +5,7 @@

 #include "kvm/iommu.h"
 #include "kvm/kvm.h"
+#include "kvm/msi.h"
 #include "kvm/mutex.h"
 #include "kvm/rbtree-interval.h"

@@ -160,3 +161,25 @@ out_unlock:

 	return out_addr;
 }
+
+int iommu_translate_msi(void *address_space, struct msi_msg *msg)
+{
+	size_t size = 4, out_size;
+	u64 addr = ((u64)msg->address_hi << 32) | msg->address_lo;
+
+	if (!address_space)
+		return 0;
+
+	addr = iommu_access(address_space, addr, size, &out_size,
+			    IOMMU_PROT_WRITE);
+
+	if (!addr || out_size != size) {
+		pr_err("could not translate MSI doorbell");
+		return -EFAULT;
+	}
+
+	msg->address_lo = addr & 0xffffffff;
+	msg->address_hi = addr >> 32;
+
+	return 0;
+}
diff --git a/virtio/pci.c b/virtio/pci.c
index 674d5143..88b1a129 100644
--- a/virtio/pci.c
+++ b/virtio/pci.c
@@ -156,6 +156,7 @@ static void update_msix_map(struct virtio_pci *vpci,
 			    struct msix_table *msix_entry, u32 vecnum)
 {
 	u32 gsi, i;
+	struct msi_msg *msg;

 	/* Find the GSI number used for that vector */
 	if (vecnum == vpci->config_vector) {
@@ -172,14 +173,20 @@ static void update_msix_map(struct virtio_pci *vpci,
 	if (gsi == 0)
 		return;

-	msix_entry = &msix_entry[vecnum];
-	irq__update_msix_route(vpci->kvm, gsi, &msix_entry->msg);
+	msg = &vpci->msix_msgs[vecnum];
+	*msg = msix_entry[vecnum].msg;
+
+	if (iommu_translate_msi(vpci->vdev->iotlb, msg))
+		return;
+
+	irq__update_msix_route(vpci->kvm, gsi, msg);
 }

 static bool virtio_pci__specific_io_out(struct kvm *kvm, struct virtio_device
*vdev, u16 port,
 					void *data, int size, int offset)
 {
 	struct virtio_pci *vpci = vdev->virtio;
+	struct msi_msg *msg;
 	u32 config_offset, vec;
 	int gsi;
 	int type = virtio__get_dev_specific_field(offset - 20,
virtio_pci__msix_enabled(vpci),
@@ -191,8 +198,12 @@ static bool virtio_pci__specific_io_out(struct kvm *kvm,
struct virtio_device *v
 			if (vec == VIRTIO_MSI_NO_VECTOR)
 				break;

-			gsi = irq__add_msix_route(kvm,
-						  &vpci->msix_table[vec].msg,
+			msg = &vpci->msix_msgs[vec];
+			*msg = vpci->msix_table[vec].msg;
+			if (iommu_translate_msi(vdev->iotlb, msg))
+				break;
+
+			gsi = irq__add_msix_route(kvm, msg,
 						  vpci->dev_hdr.dev_num << 3);
 			if (gsi >= 0) {
 				vpci->config_gsi = gsi;
@@ -210,8 +221,12 @@ static bool virtio_pci__specific_io_out(struct kvm *kvm,
struct virtio_device *v
 			if (vec == VIRTIO_MSI_NO_VECTOR)
 				break;

-			gsi = irq__add_msix_route(kvm,
-						  &vpci->msix_table[vec].msg,
+			msg = &vpci->msix_msgs[vec];
+			*msg = vpci->msix_table[vec].msg;
+			if (iommu_translate_msi(vdev->iotlb, msg))
+				break;
+
+			gsi = irq__add_msix_route(kvm, msg,
 						  vpci->dev_hdr.dev_num << 3);
 			if (gsi < 0) {
 				if (gsi == -ENXIO &&
@@ -328,9 +343,9 @@ static void virtio_pci__signal_msi(struct kvm *kvm, struct
virtio_pci *vpci,
 {
 	static int needs_devid = 0;
 	struct kvm_msi msi = {
-		.address_lo = vpci->msix_table[vec].msg.address_lo,
-		.address_hi = vpci->msix_table[vec].msg.address_hi,
-		.data = vpci->msix_table[vec].msg.data,
+		.address_lo = vpci->msix_msgs[vec].address_lo,
+		.address_hi = vpci->msix_msgs[vec].address_hi,
+		.data = vpci->msix_msgs[vec].data,
 	};

 	if (needs_devid == 0) {
--
2.12.1