From 9f86c03e527902af56fdc76f78361a08c1041d89 Mon Sep 17 00:00:00 2001
From: Weidong Han <weidong.han@intel.com>
Date: Thu, 3 Jul 2008 16:51:57 +0800
Subject: [PATCH] VT-d multiple devices assignment support


Signed-off-by: Weidong Han <weidong.han@intel.com>
---
 arch/x86/kvm/vtd.c         |  260 +++++++++++++++++++++++++++++---------------
 arch/x86/kvm/x86.c         |    4 +-
 include/asm-x86/kvm_host.h |   14 ++-
 3 files changed, 185 insertions(+), 93 deletions(-)

diff --git a/arch/x86/kvm/vtd.c b/arch/x86/kvm/vtd.c
index 4387c25..2365509 100644
--- a/arch/x86/kvm/vtd.c
+++ b/arch/x86/kvm/vtd.c
@@ -26,29 +26,32 @@
 #include <linux/intel-iommu.h>
 #include "vtd.h"
 
-int kvm_iommu_map_pages(struct kvm *kvm,
-	gfn_t base_gfn, unsigned long npages)
+DEFINE_SPINLOCK(kvm_vtd_domain_lock);
+
+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;
 	struct page *page;
 	int i, rc;
 
-	if (!kvm->arch.intel_iommu_domain)
+	if (!domain)
 		return -EFAULT;
 
-	printk(KERN_DEBUG "kvm_iommu_map_page: gpa = %lx\n",
+	printk(KERN_DEBUG "kvm_iommu_map_domain_page: gpa = %lx\n",
 		gfn << PAGE_SHIFT);
-	printk(KERN_DEBUG "kvm_iommu_map_page: hpa = %lx\n",
+	printk(KERN_DEBUG "kvm_iommu_map_domain_page: hpa = %lx\n",
 		gfn_to_pfn(kvm, base_gfn) << PAGE_SHIFT);
-	printk(KERN_DEBUG "kvm_iommu_map_page: size = %lx\n",
+	printk(KERN_DEBUG "kvm_iommu_map_domain_page: size = %lx\n",
 		npages*PAGE_SIZE);
 
 	for (i = 0; i < npages; i++) {
 		pfn = gfn_to_pfn(kvm, gfn);
 		if (pfn_valid(pfn)) {
 			rc = kvm_intel_iommu_page_mapping(
-				kvm->arch.intel_iommu_domain,
+				domain,
 				gfn << PAGE_SHIFT, pfn << PAGE_SHIFT,
 				PAGE_SIZE, DMA_PTE_READ | DMA_PTE_WRITE);
 			if (rc) {
@@ -64,25 +67,146 @@ int kvm_iommu_map_pages(struct kvm *kvm,
 	}
 	return 0;
 }
+
+int kvm_iommu_map_pages(struct kvm *kvm,
+			gfn_t base_gfn, unsigned long npages)
+{
+	struct kvm_vtd_domain *vtd_dom = NULL;
+
+	list_for_each_entry(vtd_dom, &kvm->arch.vtd_domains, list)
+		kvm_iommu_map_domain_pages(kvm, vtd_dom->domain,
+					   base_gfn, npages);
+
+	return 0;
+}
 EXPORT_SYMBOL_GPL(kvm_iommu_map_pages);
 
-static int kvm_iommu_map_memslots(struct kvm *kvm)
+static int kvm_iommu_put_domain_pages(struct kvm *kvm,
+				      struct dmar_domain *domain,
+				      gfn_t base_gfn, unsigned long npages)
+{
+	gfn_t gfn = base_gfn;
+	struct page *page;
+	int i;
+
+	if (!domain)
+		return -EFAULT;
+
+	printk(KERN_DEBUG "kvm_iommu_put_domain_pages: gpa = %lx\n",
+		gfn << PAGE_SHIFT);
+	printk(KERN_DEBUG "kvm_iommu_put_domain_pages: hpa = %lx\n",
+		gfn_to_pfn(kvm, gfn) << PAGE_SHIFT);
+	printk(KERN_DEBUG "kvm_iommu_put_domain_pages: size = %lx\n",
+		npages*PAGE_SIZE);
+
+	for (i = 0; i < npages; i++) {
+		page = gfn_to_page(kvm, gfn);
+		put_page(page);
+		gfn++;
+	}
+	return 0;
+}
+
+int kvm_iommu_put_pages(struct kvm *kvm,
+			gfn_t base_gfn, unsigned long npages)
+{
+	struct kvm_vtd_domain *vtd_dom = NULL;
+
+	list_for_each_entry(vtd_dom, &kvm->arch.vtd_domains, list)
+		kvm_iommu_put_domain_pages(kvm, vtd_dom->domain,
+					   base_gfn, npages);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(kvm_iommu_put_pages);
+
+static int kvm_iommu_map_domain_memslots(struct kvm *kvm,
+					 struct dmar_domain *domain)
 {
 	int i, rc;
 	for (i = 0; i < kvm->nmemslots; i++) {
-		rc = kvm_iommu_map_pages(kvm, kvm->memslots[i].base_gfn,
-				kvm->memslots[i].npages);
+		rc = kvm_iommu_map_domain_pages(kvm, domain,
+			 kvm->memslots[i].base_gfn, kvm->memslots[i].npages);
 		if (rc)
 			return rc;
 	}
 	return 0;
 }
 
-static int kvm_iommu_unmap_memslots(struct kvm *kvm);
+static int kvm_iommu_unmap_domain_memslots(struct kvm *kvm,
+					   struct dmar_domain *domain)
+{
+	int i, rc;
+	for (i = 0; i < kvm->nmemslots; i++) {
+		rc = kvm_iommu_put_domain_pages(kvm, domain,
+			kvm->memslots[i].base_gfn, kvm->memslots[i].npages);
+		if (rc)
+			return rc;
+	}
+	return 0;
+}
+
+static struct kvm_vtd_domain *find_kvm_vtd_domain(struct kvm *kvm,
+						  struct pci_dev *pdev)
+{
+	struct kvm_vtd_domain *vtd_dom;
+	struct kvm_vtd_pt_dev_list *vtd_dev;
+
+	list_for_each_entry(vtd_dom, &kvm->arch.vtd_domains, list) {
+		list_for_each_entry(vtd_dev, &vtd_dom->pci_pt_devices, list) {
+			if (vtd_dev->dev_info.busnr == pdev->bus->number &&
+			    vtd_dev->dev_info.devfn == pdev->devfn)
+				return vtd_dom;
+		}
+	}
+
+	return NULL;
+}
+
+static struct kvm_vtd_domain *get_kvm_vtd_domain(struct kvm *kvm,
+						 struct pci_dev *pdev)
+{
+	struct kvm_vtd_domain *vtd_dom;
+
+	vtd_dom = find_kvm_vtd_domain(kvm, pdev);
+	if (!vtd_dom) {
+		vtd_dom = kzalloc(sizeof(struct kvm_vtd_domain), GFP_KERNEL);
+		if (!vtd_dom)
+			return NULL;
+
+		INIT_LIST_HEAD(&vtd_dom->pci_pt_devices);
+		if (!vtd_dom->domain) {
+			vtd_dom->domain = kvm_intel_iommu_domain_alloc(pdev);
+			if (!vtd_dom->domain) {
+				kfree(vtd_dom);
+				return NULL;
+			}
+
+			if (kvm_iommu_map_domain_memslots(kvm,
+						  vtd_dom->domain)) {
+				kvm_iommu_unmap_domain_memslots(kvm,
+					vtd_dom->domain);
+				kvm_intel_iommu_domain_exit(vtd_dom->domain);
+				kfree(vtd_dom);
+				return NULL;
+			}
+		}
+
+		spin_lock(&kvm_vtd_domain_lock);
+		list_add(&vtd_dom->list, &kvm->arch.vtd_domains);
+		spin_unlock(&kvm_vtd_domain_lock);
+	}
+
+	return vtd_dom;
+}
+
 int kvm_iommu_map_guest(struct kvm *kvm,
-	struct kvm_pci_passthrough_dev *pci_pt_dev)
+			struct kvm_pci_passthrough_dev *pci_pt_dev)
 {
 	struct pci_dev *pdev = NULL;
+	struct kvm_vtd_domain *vtd_dom = NULL;
+	struct kvm_vtd_pt_dev_list *vtd_dev = NULL;
+
 
 	printk(KERN_DEBUG "kvm_iommu_map_guest: host bdf = %x:%x:%x\n",
 		pci_pt_dev->host.busnr,
@@ -94,104 +218,62 @@ int kvm_iommu_map_guest(struct kvm *kvm,
 			(pdev->devfn == pci_pt_dev->host.devfn))
 			goto found;
 	}
-	if (kvm->arch.intel_iommu_domain) {
-		kvm_intel_iommu_domain_exit(kvm->arch.intel_iommu_domain);
-		kvm->arch.intel_iommu_domain = NULL;
-	}
 	return -ENODEV;
 found:
-	kvm->arch.intel_iommu_domain = kvm_intel_iommu_domain_alloc(pdev);
-	if (kvm->arch.intel_iommu_domain == NULL)
-		printk(KERN_ERR "kvm_iommu_map_guest: domain == NULL\n");
-	else
-		printk("kvm_iommu_map_guest: domain = %p\n",
-		       kvm->arch.intel_iommu_domain);
-
-	if (kvm_iommu_map_memslots(kvm)) {
-		kvm_iommu_unmap_memslots(kvm);
+	vtd_dom = get_kvm_vtd_domain(kvm, pdev);
+	if (!vtd_dom)
 		return -EFAULT;
-	}
 
-	if (kvm_intel_iommu_context_mapping(
-		kvm->arch.intel_iommu_domain, pdev)) {
-		printk(KERN_ERR "Domain context map for %s failed",
-		       pci_name(pdev));
-		return -EFAULT;
-	}
-	return 0;
-}
-EXPORT_SYMBOL_GPL(kvm_iommu_map_guest);
+	vtd_dev = kzalloc(sizeof(struct kvm_vtd_pt_dev_list), GFP_KERNEL);
+	if (!vtd_dev)
+		return -ENOMEM;
+	vtd_dev->dev_info.busnr = pdev->bus->number;
+	vtd_dev->dev_info.devfn = pdev->devfn;
 
-static int kvm_iommu_put_pages(struct kvm *kvm,
-	gfn_t base_gfn, unsigned long npages)
-{
-	gfn_t gfn = base_gfn;
-	struct page *page;
-	int i;
+	spin_lock(&kvm_vtd_domain_lock);
+	list_add(&vtd_dev->list, &vtd_dom->pci_pt_devices);
+	spin_unlock(&kvm_vtd_domain_lock);
 
-	if (!kvm->arch.intel_iommu_domain)
+	/* detach device, because it may be used by host */
+	kvm_intel_iommu_detach_dev(vtd_dom->domain,
+				   pdev->bus->number, pdev->devfn);
+	if (kvm_intel_iommu_context_mapping(vtd_dom->domain, pdev)) {
+		printk(KERN_ERR "Domain context map for %s failed",
+		       pci_name(pdev));
 		return -EFAULT;
-
-	printk(KERN_DEBUG "kvm_iommu_put_pages: gpa = %lx\n",
-		gfn << PAGE_SHIFT);
-	printk(KERN_DEBUG "kvm_iommu_put_pages: hpa = %lx\n",
-		gfn_to_pfn(kvm, gfn) << PAGE_SHIFT);
-	printk(KERN_DEBUG "kvm_iommu_put_pages: size = %lx\n",
-		npages*PAGE_SIZE);
-
-	for (i = 0; i < npages; i++) {
-		page = gfn_to_page(kvm, gfn);
-		put_page(page);
-		gfn++;
 	}
-	return 0;
-}
 
-static int kvm_iommu_unmap_memslots(struct kvm *kvm)
-{
-	int i, rc;
-	for (i = 0; i < kvm->nmemslots; i++) {
-		rc = kvm_iommu_put_pages(kvm, kvm->memslots[i].base_gfn,
-				kvm->memslots[i].npages);
-		if (rc)
-			return rc;
-	}
 	return 0;
 }
+EXPORT_SYMBOL_GPL(kvm_iommu_map_guest);
 
 int kvm_iommu_unmap_guest(struct kvm *kvm)
 {
-	struct kvm_pci_pt_dev_list *entry;
-	struct pci_dev *pdev = NULL;
+	struct kvm_vtd_domain *vtd_dom;
+	struct kvm_vtd_pt_dev_list *vtd_dev;
+	struct list_head *pdev1, *pdev2;
 
-	if (!kvm->arch.intel_iommu_domain)
+	if (list_empty(&kvm->arch.vtd_domains))
 		return 0;
 
-	list_for_each_entry(entry, &kvm->arch.pci_pt_dev_head, list) {
-		printk(KERN_DEBUG "kvm_iommu_unmap_guest: %x:%x:%x\n",
-			entry->pt_dev.host.busnr,
-			PCI_SLOT(entry->pt_dev.host.devfn),
-			PCI_FUNC(entry->pt_dev.host.devfn));
+	spin_lock(&kvm_vtd_domain_lock);
 
-		for_each_pci_dev(pdev) {
-			if ((pdev->bus->number == entry->pt_dev.host.busnr) &&
-				(pdev->devfn == entry->pt_dev.host.devfn))
-				goto found;
-		}
-		return -ENODEV;
-found:
-		if (pdev == NULL) {
-			printk("kvm_iommu_unmap_guest: pdev == NULL\n");
-			return -EFAULT;
+	list_for_each_entry(vtd_dom, &kvm->arch.vtd_domains, list) {
+		list_for_each_safe(pdev1, pdev2, &vtd_dom->pci_pt_devices) {
+			vtd_dev = list_entry(pdev1,
+				struct kvm_vtd_pt_dev_list, list);
+			kvm_intel_iommu_detach_dev(vtd_dom->domain,
+				vtd_dev->dev_info.busnr,
+				vtd_dev->dev_info.devfn);
+			list_del(&vtd_dev->list);
+			kfree(vtd_dev);
 		}
 
-		/* detach kvm dmar domain */
-		kvm_intel_iommu_detach_dev(
-				kvm->arch.intel_iommu_domain,
-				pdev->bus->number, pdev->devfn);
+		kvm_iommu_unmap_domain_memslots(kvm, vtd_dom->domain);
+		kvm_intel_iommu_domain_exit(vtd_dom->domain);
 	}
-	kvm_iommu_unmap_memslots(kvm);
-	kvm_intel_iommu_domain_exit(kvm->arch.intel_iommu_domain);
+
+	spin_unlock(&kvm_vtd_domain_lock);
 	return 0;
 }
 EXPORT_SYMBOL_GPL(kvm_iommu_unmap_guest);
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 37e3a66..1bc41d9 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -386,9 +386,6 @@ static void kvm_free_pci_passthrough(struct kvm *kvm)
 		kfree(pci_pt_dev);
 	}
 	write_unlock_irqrestore(&kvm_pci_pt_lock, flags);
-
-	if (kvm_intel_iommu_found())
-		kvm->arch.intel_iommu_domain = NULL;
 }
 
 unsigned long segment_base(u16 selector)
@@ -4251,6 +4248,7 @@ struct  kvm *kvm_arch_create_vm(void)
 
 	INIT_LIST_HEAD(&kvm->arch.active_mmu_pages);
 	INIT_LIST_HEAD(&kvm->arch.pci_pt_dev_head);
+	INIT_LIST_HEAD(&kvm->arch.vtd_domains);
 
 	return kvm;
 }
diff --git a/include/asm-x86/kvm_host.h b/include/asm-x86/kvm_host.h
index a2f1e3e..00b43a3 100644
--- a/include/asm-x86/kvm_host.h
+++ b/include/asm-x86/kvm_host.h
@@ -343,6 +343,18 @@ struct kvm_pci_pt_dev_list {
 	struct kvm_pci_passthrough_dev_kernel pt_dev;
 };
 
+struct kvm_vtd_pt_dev_list {
+	struct list_head list;
+	struct kvm_pci_pt_info dev_info;
+};
+
+/* devices under the same iommu uses the same dmar_domain */
+struct kvm_vtd_domain {
+	struct list_head list;
+	struct list_head pci_pt_devices; /* assigned devices */
+	struct dmar_domain *domain; 	 /* pointer to domain */
+};
+
 struct kvm_arch{
 	int naliases;
 	struct kvm_mem_alias aliases[KVM_ALIAS_SLOTS];
@@ -356,7 +368,7 @@ struct kvm_arch{
 	 */
 	struct list_head active_mmu_pages;
 	struct list_head pci_pt_dev_head;
- 	struct dmar_domain *intel_iommu_domain;
+	struct list_head vtd_domains;
 	struct kvm_pic *vpic;
 	struct kvm_ioapic *vioapic;
 	struct kvm_pit *vpit;
-- 
1.5.1

