]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
iommu/vt-d: Enhance compatibility check for paging domain attach
authorLu Baolu <baolu.lu@linux.intel.com>
Mon, 4 Nov 2024 01:40:23 +0000 (09:40 +0800)
committerJoerg Roedel <jroedel@suse.de>
Tue, 5 Nov 2024 12:32:16 +0000 (13:32 +0100)
The driver now supports domain_alloc_paging, ensuring that a valid device
pointer is provided whenever a paging domain is allocated. Additionally,
the dmar_domain attributes are set up at the time of allocation.

Consistent with the established semantics in the IOMMU core, if a domain is
attached to a device and found to be incompatible with the IOMMU hardware
capabilities, the operation will return an -EINVAL error. This implicitly
advises the caller to allocate a new domain for the device and attempt the
domain attachment again.

Rename prepare_domain_attach_device() to a more meaningful name.

Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Link: https://lore.kernel.org/r/20241021085125.192333-4-baolu.lu@linux.intel.com
Signed-off-by: Joerg Roedel <jroedel@suse.de>
drivers/iommu/intel/iommu.c
drivers/iommu/intel/iommu.h
drivers/iommu/intel/nested.c
drivers/iommu/intel/pasid.c

index dd158ff5fd45ebe5be279547e9197d6b6462c171..eeb341aafe3e689a939cd8595ae6cff61afc6e3c 100644 (file)
@@ -1606,7 +1606,7 @@ static int domain_context_mapping_one(struct dmar_domain *domain,
        int translation = CONTEXT_TT_MULTI_LEVEL;
        struct dma_pte *pgd = domain->pgd;
        struct context_entry *context;
-       int agaw, ret;
+       int ret;
 
        pr_debug("Set context mapping for %02x:%02x.%d\n",
                bus, PCI_SLOT(devfn), PCI_FUNC(devfn));
@@ -1623,27 +1623,15 @@ static int domain_context_mapping_one(struct dmar_domain *domain,
 
        copied_context_tear_down(iommu, context, bus, devfn);
        context_clear_entry(context);
-
        context_set_domain_id(context, did);
 
-       /*
-        * Skip top levels of page tables for iommu which has
-        * less agaw than default. Unnecessary for PT mode.
-        */
-       for (agaw = domain->agaw; agaw > iommu->agaw; agaw--) {
-               ret = -ENOMEM;
-               pgd = phys_to_virt(dma_pte_addr(pgd));
-               if (!dma_pte_present(pgd))
-                       goto out_unlock;
-       }
-
        if (info && info->ats_supported)
                translation = CONTEXT_TT_DEV_IOTLB;
        else
                translation = CONTEXT_TT_MULTI_LEVEL;
 
        context_set_address_root(context, virt_to_phys(pgd));
-       context_set_address_width(context, agaw);
+       context_set_address_width(context, domain->agaw);
        context_set_translation_type(context, translation);
        context_set_fault_enable(context);
        context_set_present(context);
@@ -1876,20 +1864,9 @@ static int domain_setup_first_level(struct intel_iommu *iommu,
                                    u32 pasid)
 {
        struct dma_pte *pgd = domain->pgd;
-       int agaw, level;
-       int flags = 0;
+       int level, flags = 0;
 
-       /*
-        * Skip top levels of page tables for iommu which has
-        * less agaw than default. Unnecessary for PT mode.
-        */
-       for (agaw = domain->agaw; agaw > iommu->agaw; agaw--) {
-               pgd = phys_to_virt(dma_pte_addr(pgd));
-               if (!dma_pte_present(pgd))
-                       return -ENOMEM;
-       }
-
-       level = agaw_to_level(agaw);
+       level = agaw_to_level(domain->agaw);
        if (level != 4 && level != 5)
                return -EINVAL;
 
@@ -3492,42 +3469,41 @@ static void intel_iommu_domain_free(struct iommu_domain *domain)
        domain_exit(dmar_domain);
 }
 
-int prepare_domain_attach_device(struct iommu_domain *domain,
-                                struct device *dev)
+int paging_domain_compatible(struct iommu_domain *domain, struct device *dev)
 {
        struct device_domain_info *info = dev_iommu_priv_get(dev);
        struct dmar_domain *dmar_domain = to_dmar_domain(domain);
        struct intel_iommu *iommu = info->iommu;
        int addr_width;
 
+       if (WARN_ON_ONCE(!(domain->type & __IOMMU_DOMAIN_PAGING)))
+               return -EPERM;
+
        if (dmar_domain->force_snooping && !ecap_sc_support(iommu->ecap))
                return -EINVAL;
 
        if (domain->dirty_ops && !ssads_supported(iommu))
                return -EINVAL;
 
+       if (dmar_domain->iommu_coherency !=
+                       iommu_paging_structure_coherency(iommu))
+               return -EINVAL;
+
+       if (dmar_domain->iommu_superpage !=
+                       iommu_superpage_capability(iommu, dmar_domain->use_first_level))
+               return -EINVAL;
+
+       if (dmar_domain->use_first_level &&
+           (!sm_supported(iommu) || !ecap_flts(iommu->ecap)))
+               return -EINVAL;
+
        /* check if this iommu agaw is sufficient for max mapped address */
        addr_width = agaw_to_width(iommu->agaw);
        if (addr_width > cap_mgaw(iommu->cap))
                addr_width = cap_mgaw(iommu->cap);
 
-       if (dmar_domain->max_addr > (1LL << addr_width))
+       if (dmar_domain->gaw > addr_width || dmar_domain->agaw > iommu->agaw)
                return -EINVAL;
-       dmar_domain->gaw = addr_width;
-
-       /*
-        * Knock out extra levels of page tables if necessary
-        */
-       while (iommu->agaw < dmar_domain->agaw) {
-               struct dma_pte *pte;
-
-               pte = dmar_domain->pgd;
-               if (dma_pte_present(pte)) {
-                       dmar_domain->pgd = phys_to_virt(dma_pte_addr(pte));
-                       iommu_free_page(pte);
-               }
-               dmar_domain->agaw--;
-       }
 
        if (sm_supported(iommu) && !dev_is_real_dma_subdevice(dev) &&
            context_copied(iommu, info->bus, info->devfn))
@@ -3543,7 +3519,7 @@ static int intel_iommu_attach_device(struct iommu_domain *domain,
 
        device_block_translation(dev);
 
-       ret = prepare_domain_attach_device(domain, dev);
+       ret = paging_domain_compatible(domain, dev);
        if (ret)
                return ret;
 
@@ -4214,7 +4190,7 @@ static int intel_iommu_set_dev_pasid(struct iommu_domain *domain,
        if (context_copied(iommu, info->bus, info->devfn))
                return -EBUSY;
 
-       ret = prepare_domain_attach_device(domain, dev);
+       ret = paging_domain_compatible(domain, dev);
        if (ret)
                return ret;
 
index 1497f3112b12cd69551ff7a6654052d4148a1c2b..b1928ca3aaa8718301e432068845e6f3da3d1b5a 100644 (file)
@@ -1230,8 +1230,7 @@ void __iommu_flush_iotlb(struct intel_iommu *iommu, u16 did, u64 addr,
 int domain_attach_iommu(struct dmar_domain *domain, struct intel_iommu *iommu);
 void domain_detach_iommu(struct dmar_domain *domain, struct intel_iommu *iommu);
 void device_block_translation(struct device *dev);
-int prepare_domain_attach_device(struct iommu_domain *domain,
-                                struct device *dev);
+int paging_domain_compatible(struct iommu_domain *domain, struct device *dev);
 void domain_update_iommu_cap(struct dmar_domain *domain);
 
 int dmar_ir_support(void);
index 433c58944401f9f4c15ba816d5e2b177a120bb18..96016bc40f94a6799b292dde0327cce627eb795f 100644 (file)
@@ -40,7 +40,7 @@ static int intel_nested_attach_dev(struct iommu_domain *domain,
         * The s2_domain will be used in nested translation, hence needs
         * to ensure the s2_domain is compatible with this IOMMU.
         */
-       ret = prepare_domain_attach_device(&dmar_domain->s2_domain->domain, dev);
+       ret = paging_domain_compatible(&dmar_domain->s2_domain->domain, dev);
        if (ret) {
                dev_err_ratelimited(dev, "s2 domain is not compatible\n");
                return ret;
index 2e5fa0a232999fad15ae91b8bb581f64307552b2..53157e1194f48649e4e6422261445ef793e144ca 100644 (file)
@@ -345,25 +345,6 @@ int intel_pasid_setup_first_level(struct intel_iommu *iommu,
        return 0;
 }
 
-/*
- * Skip top levels of page tables for iommu which has less agaw
- * than default. Unnecessary for PT mode.
- */
-static int iommu_skip_agaw(struct dmar_domain *domain,
-                          struct intel_iommu *iommu,
-                          struct dma_pte **pgd)
-{
-       int agaw;
-
-       for (agaw = domain->agaw; agaw > iommu->agaw; agaw--) {
-               *pgd = phys_to_virt(dma_pte_addr(*pgd));
-               if (!dma_pte_present(*pgd))
-                       return -EINVAL;
-       }
-
-       return agaw;
-}
-
 /*
  * Set up the scalable mode pasid entry for second only translation type.
  */
@@ -374,7 +355,6 @@ int intel_pasid_setup_second_level(struct intel_iommu *iommu,
        struct pasid_entry *pte;
        struct dma_pte *pgd;
        u64 pgd_val;
-       int agaw;
        u16 did;
 
        /*
@@ -388,12 +368,6 @@ int intel_pasid_setup_second_level(struct intel_iommu *iommu,
        }
 
        pgd = domain->pgd;
-       agaw = iommu_skip_agaw(domain, iommu, &pgd);
-       if (agaw < 0) {
-               dev_err(dev, "Invalid domain page table\n");
-               return -EINVAL;
-       }
-
        pgd_val = virt_to_phys(pgd);
        did = domain_id_iommu(domain, iommu);
 
@@ -412,7 +386,7 @@ int intel_pasid_setup_second_level(struct intel_iommu *iommu,
        pasid_clear_entry(pte);
        pasid_set_domain_id(pte, did);
        pasid_set_slptr(pte, pgd_val);
-       pasid_set_address_width(pte, agaw);
+       pasid_set_address_width(pte, domain->agaw);
        pasid_set_translation_type(pte, PASID_ENTRY_PGTT_SL_ONLY);
        pasid_set_fault_enable(pte);
        pasid_set_page_snoop(pte, !!ecap_smpwc(iommu->ecap));