virtual machine domain may own multiple devices from different iommus, and agaw may be different across iommus. Because one domain used one VT-d page table, it's necessary to check if agaw is sufficient for mapped memory.
Signed-off-by: Weidong Han <[EMAIL PROTECTED]>
---
drivers/pci/intel-iommu.c | 63 +++++++++++++++++++++++++++++++++++++++++
include/linux/dma_remapping.h | 1 +
2 files changed, 64 insertions(+), 0 deletions(-)
diff --git a/drivers/pci/intel-iommu.c b/drivers/pci/intel-iommu.c
index 0db77e2..a8c2e58 100644
--- a/drivers/pci/intel-iommu.c
+++ b/drivers/pci/intel-iommu.c
@@ -2552,6 +2552,25 @@ 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)
+{
+ struct dmar_drhd_unit *drhd;
+ struct intel_iommu *iommu;
+ int min_agaw = domain->agaw;
+
+ for_each_drhd_unit(drhd) {
+ if (drhd->ignored)
+ continue;
+ iommu = drhd->iommu;
+
+ if (test_bit(iommu->seq_id, &domain->iommu_bmp))
+ if (min_agaw > iommu->agaw)
+ min_agaw = iommu->agaw;
+ }
+
+ return min_agaw;
+}
+
static struct dmar_domain *iommu_alloc_vm_domain(void)
{
struct dmar_domain *domain;
@@ -2563,6 +2582,7 @@ static struct dmar_domain *iommu_alloc_vm_domain(void)
domain->id = vm_domid++;
domain->iommu_count = 0;
domain->iommu_coherency = 0;
+ domain->max_addr = 0;
memset(&domain->iommu_bmp, 0, sizeof(unsigned long));
domain->flags = DOMAIN_FLAG_VIRTUAL_MACHINE;
@@ -2675,6 +2695,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 */
@@ -2690,6 +2713,21 @@ int intel_iommu_attach_device(struct dmar_domain *domain,
}
}
+ iommu = device_find_matched_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;
@@ -2709,7 +2747,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;
}
@@ -2724,6 +2784,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);
diff --git a/include/linux/dma_remapping.h b/include/linux/dma_remapping.h
index f6925c5..880fa17 100644
--- a/include/linux/dma_remapping.h
+++ b/include/linux/dma_remapping.h
@@ -187,6 +187,7 @@ struct dmar_domain {
int iommu_coherency;/* iommu access is coherent or not */
int iommu_count; /* reference count of iommu */
+ u64 max_addr; /* maximum mapped address */
};
/* PCI domain-device relationship */
--
1.5.1
0011-check-agaw-is-sufficient-for-mapped-memory.patch
Description: 0011-check-agaw-is-sufficient-for-mapped-memory.patch
