summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWeidong Han <weidong.han@intel.com>2008-12-08 23:10:23 +0800
committerJoerg Roedel <joerg.roedel@amd.com>2009-01-03 14:02:19 +0100
commitfe40f1e020d0923f5f35ca15f02a206c75a28053 (patch)
treee81c64559e6189f9ce8c3f19d13b6bd3c1aa92d9
parentfaa3d6f5ffe7bf60ebfd0d36513fbcda0eb0ea1a (diff)
Check agaw is sufficient for mapped memory
When domain is related to multiple iommus, need to check if the minimum agaw is sufficient for the mapped memory Signed-off-by: Weidong Han <weidong.han@intel.com> Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
-rw-r--r--drivers/pci/intel-iommu.c61
1 files changed, 61 insertions, 0 deletions
diff --git a/drivers/pci/intel-iommu.c b/drivers/pci/intel-iommu.c
index f1380269cab..772fb22e1be 100644
--- a/drivers/pci/intel-iommu.c
+++ b/drivers/pci/intel-iommu.c
@@ -230,6 +230,7 @@ struct dmar_domain {
int iommu_coherency;/* indicate coherency of iommu access */
int iommu_count; /* reference count of iommu */
spinlock_t iommu_lock; /* protect iommu set in domain */
+ u64 max_addr; /* maximum mapped address */
};
/* PCI domain-device relationship */
@@ -2849,6 +2850,22 @@ static void vm_domain_remove_all_dev_info(struct dmar_domain *domain)
/* domain id for virtual machine, it won't be set in context */
static unsigned long vm_domid;
+static int vm_domain_min_agaw(struct dmar_domain *domain)
+{
+ int i;
+ int min_agaw = domain->agaw;
+
+ i = find_first_bit(&domain->iommu_bmp, g_num_of_iommus);
+ for (; i < g_num_of_iommus; ) {
+ if (min_agaw > g_iommus[i]->agaw)
+ min_agaw = g_iommus[i]->agaw;
+
+ i = find_next_bit(&domain->iommu_bmp, g_num_of_iommus, i+1);
+ }
+
+ return min_agaw;
+}
+
static struct dmar_domain *iommu_alloc_vm_domain(void)
{
struct dmar_domain *domain;
@@ -2883,6 +2900,7 @@ static int vm_domain_init(struct dmar_domain *domain, int guest_width)
domain->iommu_count = 0;
domain->iommu_coherency = 0;
+ domain->max_addr = 0;
/* always allocate the top pgd */
domain->pgd = (struct dma_pte *)alloc_pgtable_page();
@@ -2974,6 +2992,9 @@ EXPORT_SYMBOL_GPL(intel_iommu_free_domain);
int intel_iommu_attach_device(struct dmar_domain *domain,
struct pci_dev *pdev)
{
+ struct intel_iommu *iommu;
+ int addr_width;
+ u64 end;
int ret;
/* normally pdev is not mapped */
@@ -2989,6 +3010,21 @@ int intel_iommu_attach_device(struct dmar_domain *domain,
}
}
+ iommu = device_to_iommu(pdev->bus->number, pdev->devfn);
+ if (!iommu)
+ return -ENODEV;
+
+ /* check if this iommu agaw is sufficient for max mapped address */
+ addr_width = agaw_to_width(iommu->agaw);
+ end = DOMAIN_MAX_ADDR(addr_width);
+ end = end & VTD_PAGE_MASK;
+ if (end < domain->max_addr) {
+ printk(KERN_ERR "%s: iommu agaw (%d) is not "
+ "sufficient for the mapped address (%llx)\n",
+ __func__, iommu->agaw, domain->max_addr);
+ return -EFAULT;
+ }
+
ret = domain_context_mapping(domain, pdev);
if (ret)
return ret;
@@ -3008,7 +3044,29 @@ EXPORT_SYMBOL_GPL(intel_iommu_detach_device);
int intel_iommu_map_address(struct dmar_domain *domain, dma_addr_t iova,
u64 hpa, size_t size, int prot)
{
+ u64 max_addr;
+ int addr_width;
int ret;
+
+ max_addr = (iova & VTD_PAGE_MASK) + VTD_PAGE_ALIGN(size);
+ if (domain->max_addr < max_addr) {
+ int min_agaw;
+ u64 end;
+
+ /* check if minimum agaw is sufficient for mapped address */
+ min_agaw = vm_domain_min_agaw(domain);
+ addr_width = agaw_to_width(min_agaw);
+ end = DOMAIN_MAX_ADDR(addr_width);
+ end = end & VTD_PAGE_MASK;
+ if (end < max_addr) {
+ printk(KERN_ERR "%s: iommu agaw (%d) is not "
+ "sufficient for the mapped address (%llx)\n",
+ __func__, min_agaw, max_addr);
+ return -EFAULT;
+ }
+ domain->max_addr = max_addr;
+ }
+
ret = domain_page_mapping(domain, iova, hpa, size, prot);
return ret;
}
@@ -3023,6 +3081,9 @@ void intel_iommu_unmap_address(struct dmar_domain *domain,
base = iova & VTD_PAGE_MASK;
size = VTD_PAGE_ALIGN(size);
dma_pte_clear_range(domain, base, base + size);
+
+ if (domain->max_addr == base + size)
+ domain->max_addr = base;
}
EXPORT_SYMBOL_GPL(intel_iommu_unmap_address);