[Rebased the patch due to my mmio's patch (commit: 0d679782) was checked in]
>From 9e68fc762358cc44cfec3968ac5ec65324ce04d7 Mon Sep 17 00:00:00 2001
From: Weidong Han <[EMAIL PROTECTED]>
Date: Mon, 6 Oct 2008 14:02:18 +0800
Subject: [PATCH] Support multiple device assignment to one guest
Current VT-d patches in kvm only support one device assignment to one
guest due to dmar_domain is per device.
In order to support multiple device assignemnt, this patch wraps
dmar_domain with a reference count (kvm_vtd_domain), and also adds a
pointer in kvm_assigned_dev_kernel to link to a kvm_vtd_domain.
Each dmar_domain owns one VT-d page table, in order to reduce page
tables and improve IOTLB utility, the devices assigned to the same guest
and under the same IOMMU share the same kvm_vtd_domain.
Signed-off-by: Weidong Han <[EMAIL PROTECTED]>
---
arch/x86/kvm/vtd.c | 198
+++++++++++++++++++++++++++----------------
arch/x86/kvm/x86.c | 22 +++--
drivers/pci/intel-iommu.c | 16 ++++
include/asm-x86/kvm_host.h | 1 -
include/linux/intel-iommu.h | 1 +
include/linux/kvm_host.h | 21 +++++
6 files changed, 177 insertions(+), 82 deletions(-)
diff --git a/arch/x86/kvm/vtd.c b/arch/x86/kvm/vtd.c
index a770874..7552f92 100644
--- a/arch/x86/kvm/vtd.c
+++ b/arch/x86/kvm/vtd.c
@@ -27,19 +27,40 @@
#include <linux/dmar.h>
#include <linux/intel-iommu.h>
-static int kvm_iommu_unmap_memslots(struct kvm *kvm);
+static void kvm_iommu_put_domain_pages(struct dmar_domain *domain,
+ gfn_t base_gfn, unsigned long
npages)
+{
+ gfn_t gfn = base_gfn;
+ pfn_t pfn;
+ int i;
+
+ for (i = 0; i < npages; i++) {
+ pfn = (pfn_t)intel_iommu_iova_to_pfn(domain,
+ gfn_to_gpa(gfn));
+ kvm_release_pfn_clean(pfn);
+ gfn++;
+ }
+}
+
static void kvm_iommu_put_pages(struct kvm *kvm,
- gfn_t base_gfn, unsigned long npages);
+ gfn_t base_gfn, unsigned long npages)
+{
+ struct kvm_assigned_dev_kernel *assigned_dev;
-int kvm_iommu_map_pages(struct kvm *kvm,
- gfn_t base_gfn, unsigned long npages)
+ list_for_each_entry(assigned_dev, &kvm->arch.assigned_dev_head,
list) {
+
kvm_iommu_put_domain_pages(assigned_dev->vtd_domain->domain,
+ base_gfn, npages);
+ }
+}
+
+static int kvm_iommu_map_domain_pages(struct kvm *kvm,
+ struct dmar_domain *domain,
+ gfn_t base_gfn, unsigned long
npages)
{
gfn_t gfn = base_gfn;
pfn_t pfn;
int i, r = 0;
- struct dmar_domain *domain = kvm->arch.intel_iommu_domain;
- /* check if iommu exists and in use */
if (!domain)
return 0;
@@ -58,7 +79,7 @@ int kvm_iommu_map_pages(struct kvm *kvm,
DMA_PTE_READ |
DMA_PTE_WRITE);
if (r) {
- printk(KERN_ERR "kvm_iommu_map_pages:"
+ printk(KERN_ERR "kvm_iommu_map_domain_pages:"
"iommu failed to map pfn=%lx\n", pfn);
goto unmap_pages;
}
@@ -67,18 +88,40 @@ int kvm_iommu_map_pages(struct kvm *kvm,
return 0;
unmap_pages:
- kvm_iommu_put_pages(kvm, base_gfn, i);
+ kvm_iommu_put_domain_pages(domain, base_gfn, i);
return r;
}
-static int kvm_iommu_map_memslots(struct kvm *kvm)
+int kvm_iommu_map_pages(struct kvm *kvm,
+ gfn_t base_gfn, unsigned long npages)
+{
+ int r = 0;
+ struct kvm_assigned_dev_kernel *assigned_dev;
+
+ list_for_each_entry(assigned_dev, &kvm->arch.assigned_dev_head,
list) {
+ r = kvm_iommu_map_domain_pages(kvm,
+ assigned_dev->vtd_domain->domain,
+ base_gfn, npages);
+ if (r)
+ goto unmap_pages;
+ }
+
+ return 0;
+
+unmap_pages:
+ kvm_iommu_put_pages(kvm, base_gfn, npages);
+ return r;
+}
+
+static int kvm_iommu_map_domain_memslots(struct kvm *kvm,
+ struct dmar_domain *domain)
{
int i, r;
down_read(&kvm->slots_lock);
for (i = 0; i < kvm->nmemslots; i++) {
- r = kvm_iommu_map_pages(kvm, kvm->memslots[i].base_gfn,
- kvm->memslots[i].npages);
+ r = kvm_iommu_map_domain_pages(kvm, domain,
+ kvm->memslots[i].base_gfn,
kvm->memslots[i].npages);
if (r)
break;
}
@@ -86,10 +129,23 @@ static int kvm_iommu_map_memslots(struct kvm *kvm)
return r;
}
+static void kvm_iommu_unmap_domain_memslots(struct kvm *kvm,
+ struct dmar_domain *domain)
+{
+ int i;
+ down_read(&kvm->slots_lock);
+ for (i = 0; i < kvm->nmemslots; i++) {
+ kvm_iommu_put_domain_pages(domain,
+ kvm->memslots[i].base_gfn,
kvm->memslots[i].npages);
+ }
+ up_read(&kvm->slots_lock);
+}
+
int kvm_iommu_map_guest(struct kvm *kvm,
struct kvm_assigned_dev_kernel *assigned_dev)
{
struct pci_dev *pdev = NULL;
+ struct kvm_vtd_domain *vtd_dom = NULL;
int r;
if (!intel_iommu_found()) {
@@ -103,89 +159,83 @@ int kvm_iommu_map_guest(struct kvm *kvm,
PCI_FUNC(assigned_dev->host_devfn));
pdev = assigned_dev->dev;
+ vtd_dom = assigned_dev->vtd_domain;
- if (pdev == NULL) {
- if (kvm->arch.intel_iommu_domain) {
-
intel_iommu_domain_exit(kvm->arch.intel_iommu_domain);
- kvm->arch.intel_iommu_domain = NULL;
- }
- return -ENODEV;
- }
-
- kvm->arch.intel_iommu_domain = intel_iommu_domain_alloc(pdev);
- if (!kvm->arch.intel_iommu_domain)
- return -ENODEV;
-
- r = kvm_iommu_map_memslots(kvm);
- if (r)
- goto out_unmap;
-
- intel_iommu_detach_dev(kvm->arch.intel_iommu_domain,
+ intel_iommu_detach_dev(vtd_dom->domain,
pdev->bus->number, pdev->devfn);
- r = intel_iommu_context_mapping(kvm->arch.intel_iommu_domain,
- pdev);
+ r = intel_iommu_context_mapping(vtd_dom->domain, pdev);
if (r) {
printk(KERN_ERR "Domain context map for %s failed",
pci_name(pdev));
- goto out_unmap;
+ return r;
}
- return 0;
-out_unmap:
- kvm_iommu_unmap_memslots(kvm);
- return r;
+ return 0;
}
-static void kvm_iommu_put_pages(struct kvm *kvm,
- gfn_t base_gfn, unsigned long npages)
+int kvm_iommu_unmap_guest(struct kvm *kvm)
{
- gfn_t gfn = base_gfn;
- pfn_t pfn;
- struct dmar_domain *domain = kvm->arch.intel_iommu_domain;
- int i;
+ struct kvm_assigned_dev_kernel *entry;
- for (i = 0; i < npages; i++) {
- pfn = (pfn_t)intel_iommu_iova_to_pfn(domain,
- gfn_to_gpa(gfn));
- kvm_release_pfn_clean(pfn);
- gfn++;
- }
+ list_for_each_entry(entry, &kvm->arch.assigned_dev_head, list)
{
+ printk(KERN_DEBUG "VT-d unmap: host bdf = %x:%x:%x\n",
+ entry->host_busnr,
+ PCI_SLOT(entry->host_devfn),
+ PCI_FUNC(entry->host_devfn));
+
+ /* detach kvm dmar domain */
+ intel_iommu_detach_dev(entry->vtd_domain->domain,
+ entry->host_busnr,
+ entry->host_devfn);
+ }
+
+ return 0;
}
-static int kvm_iommu_unmap_memslots(struct kvm *kvm)
+struct kvm_vtd_domain *get_kvm_vtd_domain(struct kvm *kvm,
+ struct pci_dev *pdev)
{
- int i;
- down_read(&kvm->slots_lock);
- for (i = 0; i < kvm->nmemslots; i++) {
- kvm_iommu_put_pages(kvm, kvm->memslots[i].base_gfn,
- kvm->memslots[i].npages);
+ struct kvm_vtd_domain *vtd_dom = NULL;
+ struct intel_iommu *iommu = NULL;
+ struct kvm_assigned_dev_kernel *assigned_dev;
+
+ iommu = intel_iommu_device_get_iommu(pdev);
+
+ list_for_each_entry(assigned_dev, &kvm->arch.assigned_dev_head,
list) {
+ if (iommu == assigned_dev->vtd_domain->domain->iommu)
+ return assigned_dev->vtd_domain;
}
- up_read(&kvm->slots_lock);
- return 0;
-}
+ vtd_dom = kzalloc(sizeof(struct kvm_vtd_domain), GFP_KERNEL);
+ if (!vtd_dom)
+ return NULL;
-int kvm_iommu_unmap_guest(struct kvm *kvm)
-{
- struct kvm_assigned_dev_kernel *entry;
- struct dmar_domain *domain = kvm->arch.intel_iommu_domain;
+ vtd_dom->domain = intel_iommu_domain_alloc(pdev);
+ if (!vtd_dom->domain) {
+ kfree(vtd_dom);
+ return NULL;
+ }
- /* check if iommu exists and in use */
- if (!domain)
- return 0;
+ if (kvm_iommu_map_domain_memslots(kvm, vtd_dom->domain)) {
+ kvm_iommu_unmap_domain_memslots(kvm, vtd_dom->domain);
+ intel_iommu_domain_exit(vtd_dom->domain);
+ kfree(vtd_dom);
+ return NULL;
+ }
- list_for_each_entry(entry, &kvm->arch.assigned_dev_head, list) {
- printk(KERN_DEBUG "VT-d unmap: host bdf = %x:%x:%x\n",
- entry->host_busnr,
- PCI_SLOT(entry->host_devfn),
- PCI_FUNC(entry->host_devfn));
+ return vtd_dom;
+}
- /* detach kvm dmar domain */
- intel_iommu_detach_dev(domain, entry->host_busnr,
- entry->host_devfn);
+void release_kvm_vtd_domain(struct kvm *kvm, struct kvm_vtd_domain
*vtd_dom)
+{
+ if (vtd_dom == NULL)
+ return;
+
+ if (--vtd_dom->dev_count == 0) {
+ kvm_iommu_unmap_domain_memslots(kvm, vtd_dom->domain);
+ intel_iommu_domain_exit(vtd_dom->domain);
+ kfree(vtd_dom);
+ vtd_dom = NULL;
}
- kvm_iommu_unmap_memslots(kvm);
- intel_iommu_domain_exit(domain);
- return 0;
}
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 675fcc1..a1d6ede 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -188,6 +188,8 @@ static void kvm_free_assigned_device(struct kvm
*kvm,
pci_disable_device(assigned_dev->dev);
pci_dev_put(assigned_dev->dev);
+ release_kvm_vtd_domain(kvm, assigned_dev->vtd_domain);
+
list_del(&assigned_dev->list);
kfree(assigned_dev);
}
@@ -280,7 +282,10 @@ static int kvm_vm_ioctl_assign_device(struct kvm
*kvm,
match = kvm_find_assigned_dev(&kvm->arch.assigned_dev_head,
assigned_dev->assigned_dev_id);
if (match) {
- /* device already assigned */
+ printk(KERN_ERR "%s: device (%x:%x.%x) is already
assigned\n",
+ __func__, match->host_busnr,
+ PCI_SLOT(match->host_devfn),
+ PCI_FUNC(match->host_devfn));
r = -EINVAL;
goto out;
}
@@ -317,21 +322,24 @@ static int kvm_vm_ioctl_assign_device(struct kvm
*kvm,
match->kvm = kvm;
- list_add(&match->list, &kvm->arch.assigned_dev_head);
-
if (assigned_dev->flags & KVM_DEV_ASSIGN_ENABLE_IOMMU) {
+ match->vtd_domain = get_kvm_vtd_domain(kvm, dev);
+ if (match->vtd_domain == NULL)
+ goto out_disable;
+ match->vtd_domain->dev_count++;
+
r = kvm_iommu_map_guest(kvm, match);
if (r)
- goto out_list_del;
+ goto out_disable;
}
+
+ list_add(&match->list, &kvm->arch.assigned_dev_head);
out:
mutex_unlock(&kvm->lock);
return r;
-out_list_del:
- list_del(&match->list);
- pci_release_regions(dev);
out_disable:
+ pci_release_regions(dev);
pci_disable_device(dev);
out_put:
pci_dev_put(dev);
diff --git a/drivers/pci/intel-iommu.c b/drivers/pci/intel-iommu.c
index 089ba3f..ccb2fd3 100644
--- a/drivers/pci/intel-iommu.c
+++ b/drivers/pci/intel-iommu.c
@@ -2561,3 +2561,19 @@ u64 intel_iommu_iova_to_pfn(struct dmar_domain
*domain, u64 iova)
return pfn >> PAGE_SHIFT_4K;
}
EXPORT_SYMBOL_GPL(intel_iommu_iova_to_pfn);
+
+struct intel_iommu *intel_iommu_device_get_iommu(struct pci_dev *pdev)
+{
+ struct dmar_drhd_unit *drhd;
+
+ drhd = dmar_find_matched_drhd_unit(pdev);
+ if (!drhd) {
+ printk(KERN_ERR "%s: cannot find drhd for %x:%x.%x\n",
+ __func__, pdev->bus->number,
+ PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn));
+ return NULL;
+ }
+
+ return drhd->iommu;
+}
+EXPORT_SYMBOL_GPL(intel_iommu_device_get_iommu);
diff --git a/include/asm-x86/kvm_host.h b/include/asm-x86/kvm_host.h
index 53995a8..a48dcfe 100644
--- a/include/asm-x86/kvm_host.h
+++ b/include/asm-x86/kvm_host.h
@@ -351,7 +351,6 @@ struct kvm_arch{
*/
struct list_head active_mmu_pages;
struct list_head assigned_dev_head;
- struct dmar_domain *intel_iommu_domain;
struct kvm_pic *vpic;
struct kvm_ioapic *vioapic;
struct kvm_pit *vpit;
diff --git a/include/linux/intel-iommu.h b/include/linux/intel-iommu.h
index 5fa9d26..7801aa4 100644
--- a/include/linux/intel-iommu.h
+++ b/include/linux/intel-iommu.h
@@ -350,6 +350,7 @@ int intel_iommu_page_mapping(struct dmar_domain
*domain, dma_addr_t iova,
void intel_iommu_detach_dev(struct dmar_domain *domain, u8 bus, u8
devfn);
struct dmar_domain *intel_iommu_find_domain(struct pci_dev *pdev);
u64 intel_iommu_iova_to_pfn(struct dmar_domain *domain, u64 iova);
+struct intel_iommu *intel_iommu_device_get_iommu(struct pci_dev *pdev);
#ifdef CONFIG_DMAR
int intel_iommu_found(void);
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 73b7c52..7a3e1b6 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -293,6 +293,11 @@ struct kvm_irq_ack_notifier {
void (*irq_acked)(struct kvm_irq_ack_notifier *kian);
};
+struct kvm_vtd_domain {
+ int dev_count; /* number of assigned devices */
+ struct dmar_domain *domain;
+};
+
struct kvm_assigned_dev_kernel {
struct kvm_irq_ack_notifier ack_notifier;
struct work_struct interrupt_work;
@@ -305,6 +310,7 @@ struct kvm_assigned_dev_kernel {
int irq_requested;
struct pci_dev *dev;
struct kvm *kvm;
+ struct kvm_vtd_domain *vtd_domain;
};
#ifdef CONFIG_DMAR
@@ -313,6 +319,9 @@ int kvm_iommu_map_pages(struct kvm *kvm, gfn_t
base_gfn,
int kvm_iommu_map_guest(struct kvm *kvm,
struct kvm_assigned_dev_kernel *assigned_dev);
int kvm_iommu_unmap_guest(struct kvm *kvm);
+struct kvm_vtd_domain *get_kvm_vtd_domain(struct kvm *kvm,
+ struct pci_dev *pdev);
+void release_kvm_vtd_domain(struct kvm *kvm, struct kvm_vtd_domain
*vtd_dom);
#else /* CONFIG_DMAR */
static inline int kvm_iommu_map_pages(struct kvm *kvm,
gfn_t base_gfn,
@@ -332,6 +341,18 @@ static inline int kvm_iommu_unmap_guest(struct kvm
*kvm)
{
return 0;
}
+
+static inline struct kvm_vtd_domain *get_kvm_vtd_domain(struct kvm
*kvm,
+ struct pci_dev
*pdev)
+{
+ return NULL;
+}
+
+static inline void release_kvm_vtd_domain(struct kvm *kvm,
+ struct kvm_vtd_domain
*vtd_dom)
+{
+ return;
+}
#endif /* CONFIG_DMAR */
static inline void kvm_guest_enter(void)
--
1.5.1
0001-Support-multiple-device-assignment-to-one-guest-v2.patch
Description: 0001-Support-multiple-device-assignment-to-one-guest-v2.patch
