]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - drivers/iommu/intel-iommu.c
iommu/vt-d: Reserve a domain id for FL and PT modes
[linux.git] / drivers / iommu / intel-iommu.c
index f3ccf025108b4c377f2f9bc6de80e8222b817a01..9818aaf2d0f73a830f34d6cc170986ee22259bae 100644 (file)
@@ -290,49 +290,6 @@ static inline void context_clear_entry(struct context_entry *context)
        context->hi = 0;
 }
 
-/*
- * 0: readable
- * 1: writable
- * 2-6: reserved
- * 7: super page
- * 8-10: available
- * 11: snoop behavior
- * 12-63: Host physcial address
- */
-struct dma_pte {
-       u64 val;
-};
-
-static inline void dma_clear_pte(struct dma_pte *pte)
-{
-       pte->val = 0;
-}
-
-static inline u64 dma_pte_addr(struct dma_pte *pte)
-{
-#ifdef CONFIG_64BIT
-       return pte->val & VTD_PAGE_MASK;
-#else
-       /* Must have a full atomic 64-bit read */
-       return  __cmpxchg64(&pte->val, 0ULL, 0ULL) & VTD_PAGE_MASK;
-#endif
-}
-
-static inline bool dma_pte_present(struct dma_pte *pte)
-{
-       return (pte->val & 3) != 0;
-}
-
-static inline bool dma_pte_superpage(struct dma_pte *pte)
-{
-       return (pte->val & DMA_PTE_LARGE_PAGE);
-}
-
-static inline int first_pte_in_page(struct dma_pte *pte)
-{
-       return !((unsigned long)pte & ~VTD_PAGE_MASK);
-}
-
 /*
  * This domain is a statically identity mapping domain.
  *     1. This domain creats a static 1:1 mapping to all usable memory.
@@ -405,38 +362,16 @@ static int dmar_map_gfx = 1;
 static int dmar_forcedac;
 static int intel_iommu_strict;
 static int intel_iommu_superpage = 1;
-static int intel_iommu_ecs = 1;
-static int intel_iommu_pasid28;
+static int intel_iommu_sm = 1;
 static int iommu_identity_mapping;
 
 #define IDENTMAP_ALL           1
 #define IDENTMAP_GFX           2
 #define IDENTMAP_AZALIA                4
 
-/* Broadwell and Skylake have broken ECS support — normal so-called "second
- * level" translation of DMA requests-without-PASID doesn't actually happen
- * unless you also set the NESTE bit in an extended context-entry. Which of
- * course means that SVM doesn't work because it's trying to do nested
- * translation of the physical addresses it finds in the process page tables,
- * through the IOVA->phys mapping found in the "second level" page tables.
- *
- * The VT-d specification was retroactively changed to change the definition
- * of the capability bits and pretend that Broadwell/Skylake never happened...
- * but unfortunately the wrong bit was changed. It's ECS which is broken, but
- * for some reason it was the PASID capability bit which was redefined (from
- * bit 28 on BDW/SKL to bit 40 in future).
- *
- * So our test for ECS needs to eschew those implementations which set the old
- * PASID capabiity bit 28, since those are the ones on which ECS is broken.
- * Unless we are working around the 'pasid28' limitations, that is, by putting
- * the device into passthrough mode for normal DMA and thus masking the bug.
- */
-#define ecs_enabled(iommu) (intel_iommu_ecs && ecap_ecs(iommu->ecap) && \
-                           (intel_iommu_pasid28 || !ecap_broken_pasid(iommu->ecap)))
-/* PASID support is thus enabled if ECS is enabled and *either* of the old
- * or new capability bits are set. */
-#define pasid_enabled(iommu) (ecs_enabled(iommu) &&                    \
-                             (ecap_pasid(iommu->ecap) || ecap_broken_pasid(iommu->ecap)))
+#define sm_supported(iommu)    (intel_iommu_sm && ecap_smts((iommu)->ecap))
+#define pasid_supported(iommu) (sm_supported(iommu) &&                 \
+                                ecap_pasid((iommu)->ecap))
 
 int intel_iommu_gfx_mapped;
 EXPORT_SYMBOL_GPL(intel_iommu_gfx_mapped);
@@ -447,21 +382,24 @@ static LIST_HEAD(device_domain_list);
 
 /*
  * Iterate over elements in device_domain_list and call the specified
- * callback @fn against each element. This helper should only be used
- * in the context where the device_domain_lock has already been holden.
+ * callback @fn against each element.
  */
 int for_each_device_domain(int (*fn)(struct device_domain_info *info,
                                     void *data), void *data)
 {
        int ret = 0;
+       unsigned long flags;
        struct device_domain_info *info;
 
-       assert_spin_locked(&device_domain_lock);
+       spin_lock_irqsave(&device_domain_lock, flags);
        list_for_each_entry(info, &device_domain_list, global) {
                ret = fn(info, data);
-               if (ret)
+               if (ret) {
+                       spin_unlock_irqrestore(&device_domain_lock, flags);
                        return ret;
+               }
        }
+       spin_unlock_irqrestore(&device_domain_lock, flags);
 
        return 0;
 }
@@ -516,15 +454,9 @@ static int __init intel_iommu_setup(char *str)
                } else if (!strncmp(str, "sp_off", 6)) {
                        pr_info("Disable supported super page\n");
                        intel_iommu_superpage = 0;
-               } else if (!strncmp(str, "ecs_off", 7)) {
-                       printk(KERN_INFO
-                               "Intel-IOMMU: disable extended context table support\n");
-                       intel_iommu_ecs = 0;
-               } else if (!strncmp(str, "pasid28", 7)) {
-                       printk(KERN_INFO
-                               "Intel-IOMMU: enable pre-production PASID support\n");
-                       intel_iommu_pasid28 = 1;
-                       iommu_identity_mapping |= IDENTMAP_GFX;
+               } else if (!strncmp(str, "sm_off", 6)) {
+                       pr_info("Intel-IOMMU: disable scalable mode support\n");
+                       intel_iommu_sm = 0;
                } else if (!strncmp(str, "tboot_noforce", 13)) {
                        printk(KERN_INFO
                                "Intel-IOMMU: not forcing on after tboot. This could expose security risk for tboot\n");
@@ -771,7 +703,7 @@ struct context_entry *iommu_context_addr(struct intel_iommu *iommu, u8 bus,
        u64 *entry;
 
        entry = &root->lo;
-       if (ecs_enabled(iommu)) {
+       if (sm_supported(iommu)) {
                if (devfn >= 0x80) {
                        devfn -= 0x80;
                        entry = &root->hi;
@@ -913,7 +845,7 @@ static void free_context_table(struct intel_iommu *iommu)
                if (context)
                        free_pgtable_page(context);
 
-               if (!ecs_enabled(iommu))
+               if (!sm_supported(iommu))
                        continue;
 
                context = iommu_context_addr(iommu, i, 0x80, 0);
@@ -1265,8 +1197,6 @@ static void iommu_set_root_entry(struct intel_iommu *iommu)
        unsigned long flag;
 
        addr = virt_to_phys(iommu->root_entry);
-       if (ecs_enabled(iommu))
-               addr |= DMA_RTADDR_RTT;
 
        raw_spin_lock_irqsave(&iommu->register_lock, flag);
        dmar_writeq(iommu->reg + DMAR_RTADDR_REG, addr);
@@ -1691,6 +1621,16 @@ static int iommu_init_domains(struct intel_iommu *iommu)
         */
        set_bit(0, iommu->domain_ids);
 
+       /*
+        * Vt-d spec rev3.0 (section 6.2.3.1) requires that each pasid
+        * entry for first-level or pass-through translation modes should
+        * be programmed with a domain id different from those used for
+        * second-level or nested translation. We reserve a domain id for
+        * this purpose.
+        */
+       if (sm_supported(iommu))
+               set_bit(FLPT_DEFAULT_DID, iommu->domain_ids);
+
        return 0;
 }
 
@@ -1755,7 +1695,7 @@ static void free_dmar_iommu(struct intel_iommu *iommu)
        free_context_table(iommu);
 
 #ifdef CONFIG_INTEL_IOMMU_SVM
-       if (pasid_enabled(iommu)) {
+       if (pasid_supported(iommu)) {
                if (ecap_prs(iommu->ecap))
                        intel_svm_finish_prq(iommu);
                intel_svm_exit(iommu);
@@ -2044,7 +1984,7 @@ static int domain_context_mapping_one(struct dmar_domain *domain,
         * than default.  Unnecessary for PT mode.
         */
        if (translation != CONTEXT_TT_PASS_THROUGH) {
-               for (agaw = domain->agaw; agaw != iommu->agaw; agaw--) {
+               for (agaw = domain->agaw; agaw > iommu->agaw; agaw--) {
                        ret = -ENOMEM;
                        pgd = phys_to_virt(dma_pte_addr(pgd));
                        if (!dma_pte_present(pgd))
@@ -2058,7 +1998,7 @@ static int domain_context_mapping_one(struct dmar_domain *domain,
                        translation = CONTEXT_TT_MULTI_LEVEL;
 
                context_set_address_root(context, virt_to_phys(pgd));
-               context_set_address_width(context, iommu->agaw);
+               context_set_address_width(context, agaw);
        } else {
                /*
                 * In pass through mode, AW must be programmed to
@@ -2464,8 +2404,8 @@ static struct dmar_domain *dmar_insert_one_dev_info(struct intel_iommu *iommu,
                    dmar_find_matched_atsr_unit(pdev))
                        info->ats_supported = 1;
 
-               if (ecs_enabled(iommu)) {
-                       if (pasid_enabled(iommu)) {
+               if (sm_supported(iommu)) {
+                       if (pasid_supported(iommu)) {
                                int features = pci_pasid_features(pdev);
                                if (features >= 0)
                                        info->pasid_supported = features | 1;
@@ -2511,16 +2451,18 @@ static struct dmar_domain *dmar_insert_one_dev_info(struct intel_iommu *iommu,
        list_add(&info->global, &device_domain_list);
        if (dev)
                dev->archdata.iommu = info;
+       spin_unlock_irqrestore(&device_domain_lock, flags);
 
-       if (dev && dev_is_pci(dev) && info->pasid_supported) {
+       /* PASID table is mandatory for a PCI device in scalable mode. */
+       if (dev && dev_is_pci(dev) && sm_supported(iommu)) {
                ret = intel_pasid_alloc_table(dev);
                if (ret) {
-                       pr_warn("No pasid table for %s, pasid disabled\n",
-                               dev_name(dev));
-                       info->pasid_supported = 0;
+                       pr_err("PASID table allocation for %s failed\n",
+                              dev_name(dev));
+                       dmar_remove_one_dev_info(domain, dev);
+                       return NULL;
                }
        }
-       spin_unlock_irqrestore(&device_domain_lock, flags);
 
        if (dev && domain_context_mapping(domain, dev)) {
                pr_err("Domain context map for %s failed\n", dev_name(dev));
@@ -3277,7 +3219,7 @@ static int __init init_dmars(void)
                 * We need to ensure the system pasid table is no bigger
                 * than the smallest supported.
                 */
-               if (pasid_enabled(iommu)) {
+               if (pasid_supported(iommu)) {
                        u32 temp = 2 << ecap_pss(iommu->ecap);
 
                        intel_pasid_max_id = min_t(u32, temp,
@@ -3338,7 +3280,7 @@ static int __init init_dmars(void)
                if (!ecap_pass_through(iommu->ecap))
                        hw_pass_through = 0;
 #ifdef CONFIG_INTEL_IOMMU_SVM
-               if (pasid_enabled(iommu))
+               if (pasid_supported(iommu))
                        intel_svm_init(iommu);
 #endif
        }
@@ -3442,7 +3384,7 @@ static int __init init_dmars(void)
                iommu_flush_write_buffer(iommu);
 
 #ifdef CONFIG_INTEL_IOMMU_SVM
-               if (pasid_enabled(iommu) && ecap_prs(iommu->ecap)) {
+               if (pasid_supported(iommu) && ecap_prs(iommu->ecap)) {
                        ret = intel_svm_enable_prq(iommu);
                        if (ret)
                                goto free_iommu;
@@ -4331,7 +4273,7 @@ static int intel_iommu_add(struct dmar_drhd_unit *dmaru)
                goto out;
 
 #ifdef CONFIG_INTEL_IOMMU_SVM
-       if (pasid_enabled(iommu))
+       if (pasid_supported(iommu))
                intel_svm_init(iommu);
 #endif
 
@@ -4348,7 +4290,7 @@ static int intel_iommu_add(struct dmar_drhd_unit *dmaru)
        iommu_flush_write_buffer(iommu);
 
 #ifdef CONFIG_INTEL_IOMMU_SVM
-       if (pasid_enabled(iommu) && ecap_prs(iommu->ecap)) {
+       if (pasid_supported(iommu) && ecap_prs(iommu->ecap)) {
                ret = intel_svm_enable_prq(iommu);
                if (ret)
                        goto disable_iommu;