| /* |
| * Copyright (c) 2006, Intel Corporation. |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms and conditions of the GNU General Public License, |
| * version 2, as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License along with |
| * this program; if not, write to the Free Software Foundation, Inc., 59 Temple |
| * Place - Suite 330, Boston, MA 02111-1307 USA. |
| * |
| * Copyright (C) 2006-2008 Intel Corporation |
| * Copyright IBM Corporation, 2008 |
| * Author: Allen M. Kay <allen.m.kay@intel.com> |
| * Author: Weidong Han <weidong.han@intel.com> |
| * Author: Ben-Ami Yassour <benami@il.ibm.com> |
| */ |
| |
| #include <linux/list.h> |
| #include <linux/kvm_host.h> |
| #include <linux/pci.h> |
| #include <linux/dmar.h> |
| #include <linux/intel-iommu.h> |
| |
| static int kvm_iommu_unmap_memslots(struct kvm *kvm); |
| static void kvm_iommu_put_pages(struct kvm *kvm, |
| gfn_t base_gfn, unsigned long npages); |
| |
| int kvm_iommu_map_pages(struct kvm *kvm, |
| gfn_t base_gfn, unsigned long npages) |
| { |
| gfn_t gfn = base_gfn; |
| pfn_t pfn; |
| int i, r; |
| struct dmar_domain *domain = kvm->arch.intel_iommu_domain; |
| |
| /* check if iommu exists and in use */ |
| if (!domain) |
| return 0; |
| |
| r = -EINVAL; |
| for (i = 0; i < npages; i++) { |
| /* check if already mapped */ |
| pfn = (pfn_t)intel_iommu_iova_to_pfn(domain, |
| gfn_to_gpa(gfn)); |
| if (pfn && !is_mmio_pfn(pfn)) |
| continue; |
| |
| pfn = gfn_to_pfn(kvm, gfn); |
| if (!is_mmio_pfn(pfn)) { |
| r = intel_iommu_page_mapping(domain, |
| gfn_to_gpa(gfn), |
| pfn_to_hpa(pfn), |
| PAGE_SIZE, |
| DMA_PTE_READ | |
| DMA_PTE_WRITE); |
| if (r) { |
| printk(KERN_DEBUG "kvm_iommu_map_pages:" |
| "iommu failed to map pfn=%lx\n", pfn); |
| goto unmap_pages; |
| } |
| } else { |
| printk(KERN_DEBUG "kvm_iommu_map_page:" |
| "invalid pfn=%lx\n", pfn); |
| goto unmap_pages; |
| } |
| gfn++; |
| } |
| return 0; |
| |
| unmap_pages: |
| kvm_iommu_put_pages(kvm, base_gfn, i); |
| return r; |
| } |
| |
| static int kvm_iommu_map_memslots(struct kvm *kvm) |
| { |
| 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); |
| if (r) |
| break; |
| } |
| up_read(&kvm->slots_lock); |
| return r; |
| } |
| |
| int kvm_iommu_map_guest(struct kvm *kvm, |
| struct kvm_assigned_dev_kernel *assigned_dev) |
| { |
| struct pci_dev *pdev = NULL; |
| int r; |
| |
| if (!intel_iommu_found()) { |
| printk(KERN_ERR "%s: intel iommu not found\n", __func__); |
| return -ENODEV; |
| } |
| |
| printk(KERN_DEBUG "VT-d direct map: host bdf = %x:%x:%x\n", |
| assigned_dev->host_busnr, |
| PCI_SLOT(assigned_dev->host_devfn), |
| PCI_FUNC(assigned_dev->host_devfn)); |
| |
| pdev = assigned_dev->dev; |
| |
| 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, |
| pdev->bus->number, pdev->devfn); |
| |
| r = intel_iommu_context_mapping(kvm->arch.intel_iommu_domain, |
| pdev); |
| if (r) { |
| printk(KERN_ERR "Domain context map for %s failed", |
| pci_name(pdev)); |
| goto out_unmap; |
| } |
| return 0; |
| |
| out_unmap: |
| kvm_iommu_unmap_memslots(kvm); |
| return r; |
| } |
| |
| static void kvm_iommu_put_pages(struct kvm *kvm, |
| gfn_t base_gfn, unsigned long npages) |
| { |
| gfn_t gfn = base_gfn; |
| pfn_t pfn; |
| struct dmar_domain *domain = kvm->arch.intel_iommu_domain; |
| 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 int kvm_iommu_unmap_memslots(struct kvm *kvm) |
| { |
| 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); |
| } |
| up_read(&kvm->slots_lock); |
| |
| return 0; |
| } |
| |
| int kvm_iommu_unmap_guest(struct kvm *kvm) |
| { |
| struct kvm_assigned_dev_kernel *entry; |
| struct dmar_domain *domain = kvm->arch.intel_iommu_domain; |
| |
| /* check if iommu exists and in use */ |
| if (!domain) |
| return 0; |
| |
| 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(domain, entry->host_busnr, |
| entry->host_devfn); |
| } |
| kvm_iommu_unmap_memslots(kvm); |
| intel_iommu_domain_exit(domain); |
| return 0; |
| } |