[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