]> git.ipfire.org Git - thirdparty/kernel/linux.git/blobdiff - drivers/iommu/intel/iommu.c
Merge tag 'iommu-updates-v6.7' of git://git.kernel.org/pub/scm/linux/kernel/git/joro...
[thirdparty/kernel/linux.git] / drivers / iommu / intel / iommu.c
index d5d191a71fe0d595e59f8de641fb0c9a1f1ffa28..3531b956556c7df268ee32a5053a755d5ce0630b 100644 (file)
@@ -282,7 +282,6 @@ static LIST_HEAD(dmar_satc_units);
 #define for_each_rmrr_units(rmrr) \
        list_for_each_entry(rmrr, &dmar_rmrr_units, list)
 
-static void device_block_translation(struct device *dev);
 static void intel_iommu_domain_free(struct iommu_domain *domain);
 
 int dmar_disabled = !IS_ENABLED(CONFIG_INTEL_IOMMU_DEFAULT_ON);
@@ -300,6 +299,7 @@ static int iommu_skip_te_disable;
 #define IDENTMAP_AZALIA                4
 
 const struct iommu_ops intel_iommu_ops;
+const struct iommu_dirty_ops intel_dirty_ops;
 
 static bool translation_pre_enabled(struct intel_iommu *iommu)
 {
@@ -560,7 +560,7 @@ static unsigned long domain_super_pgsize_bitmap(struct dmar_domain *domain)
 }
 
 /* Some capabilities may be different across iommus */
-static void domain_update_iommu_cap(struct dmar_domain *domain)
+void domain_update_iommu_cap(struct dmar_domain *domain)
 {
        domain_update_iommu_coherency(domain);
        domain->iommu_superpage = domain_update_iommu_superpage(domain, NULL);
@@ -1778,8 +1778,7 @@ static struct dmar_domain *alloc_domain(unsigned int type)
        return domain;
 }
 
-static int domain_attach_iommu(struct dmar_domain *domain,
-                              struct intel_iommu *iommu)
+int domain_attach_iommu(struct dmar_domain *domain, struct intel_iommu *iommu)
 {
        struct iommu_domain_info *info, *curr;
        unsigned long ndomains;
@@ -1828,8 +1827,7 @@ err_unlock:
        return ret;
 }
 
-static void domain_detach_iommu(struct dmar_domain *domain,
-                               struct intel_iommu *iommu)
+void domain_detach_iommu(struct dmar_domain *domain, struct intel_iommu *iommu)
 {
        struct iommu_domain_info *info;
 
@@ -2196,6 +2194,11 @@ __domain_mapping(struct dmar_domain *domain, unsigned long iov_pfn,
        if ((prot & (DMA_PTE_READ|DMA_PTE_WRITE)) == 0)
                return -EINVAL;
 
+       if (!(prot & DMA_PTE_WRITE) && domain->nested_parent) {
+               pr_err_ratelimited("Read-only mapping is disallowed on the domain which serves as the parent in a nested configuration, due to HW errata (ERRATA_772415_SPR17)\n");
+               return -EINVAL;
+       }
+
        attr = prot & (DMA_PTE_READ | DMA_PTE_WRITE | DMA_PTE_SNP);
        attr |= DMA_FL_PTE_PRESENT;
        if (domain->use_first_level) {
@@ -3958,7 +3961,7 @@ static void dmar_remove_one_dev_info(struct device *dev)
  * all DMA requests without PASID from the device are blocked. If the page
  * table has been set, clean up the data structures.
  */
-static void device_block_translation(struct device *dev)
+void device_block_translation(struct device *dev)
 {
        struct device_domain_info *info = dev_iommu_priv_get(dev);
        struct intel_iommu *iommu = info->iommu;
@@ -4056,14 +4059,62 @@ static struct iommu_domain *intel_iommu_domain_alloc(unsigned type)
        return NULL;
 }
 
+static struct iommu_domain *
+intel_iommu_domain_alloc_user(struct device *dev, u32 flags,
+                             struct iommu_domain *parent,
+                             const struct iommu_user_data *user_data)
+{
+       struct device_domain_info *info = dev_iommu_priv_get(dev);
+       bool dirty_tracking = flags & IOMMU_HWPT_ALLOC_DIRTY_TRACKING;
+       bool nested_parent = flags & IOMMU_HWPT_ALLOC_NEST_PARENT;
+       struct intel_iommu *iommu = info->iommu;
+       struct iommu_domain *domain;
+
+       /* Must be NESTING domain */
+       if (parent) {
+               if (!nested_supported(iommu) || flags)
+                       return ERR_PTR(-EOPNOTSUPP);
+               return intel_nested_domain_alloc(parent, user_data);
+       }
+
+       if (flags &
+           (~(IOMMU_HWPT_ALLOC_NEST_PARENT | IOMMU_HWPT_ALLOC_DIRTY_TRACKING)))
+               return ERR_PTR(-EOPNOTSUPP);
+       if (nested_parent && !nested_supported(iommu))
+               return ERR_PTR(-EOPNOTSUPP);
+       if (user_data || (dirty_tracking && !ssads_supported(iommu)))
+               return ERR_PTR(-EOPNOTSUPP);
+
+       /*
+        * domain_alloc_user op needs to fully initialize a domain before
+        * return, so uses iommu_domain_alloc() here for simple.
+        */
+       domain = iommu_domain_alloc(dev->bus);
+       if (!domain)
+               return ERR_PTR(-ENOMEM);
+
+       if (nested_parent)
+               to_dmar_domain(domain)->nested_parent = true;
+
+       if (dirty_tracking) {
+               if (to_dmar_domain(domain)->use_first_level) {
+                       iommu_domain_free(domain);
+                       return ERR_PTR(-EOPNOTSUPP);
+               }
+               domain->dirty_ops = &intel_dirty_ops;
+       }
+
+       return domain;
+}
+
 static void intel_iommu_domain_free(struct iommu_domain *domain)
 {
        if (domain != &si_domain->domain)
                domain_exit(to_dmar_domain(domain));
 }
 
-static int prepare_domain_attach_device(struct iommu_domain *domain,
-                                       struct device *dev)
+int prepare_domain_attach_device(struct iommu_domain *domain,
+                                struct device *dev)
 {
        struct dmar_domain *dmar_domain = to_dmar_domain(domain);
        struct intel_iommu *iommu;
@@ -4076,6 +4127,9 @@ static int prepare_domain_attach_device(struct iommu_domain *domain,
        if (dmar_domain->force_snooping && !ecap_sc_support(iommu->ecap))
                return -EINVAL;
 
+       if (domain->dirty_ops && !ssads_supported(iommu))
+               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))
@@ -4330,6 +4384,8 @@ static bool intel_iommu_capable(struct device *dev, enum iommu_cap cap)
                return dmar_platform_optin();
        case IOMMU_CAP_ENFORCE_CACHE_COHERENCY:
                return ecap_sc_support(info->iommu->ecap);
+       case IOMMU_CAP_DIRTY_TRACKING:
+               return ssads_supported(info->iommu);
        default:
                return false;
        }
@@ -4732,6 +4788,9 @@ static int intel_iommu_set_dev_pasid(struct iommu_domain *domain,
        if (!pasid_supported(iommu) || dev_is_real_dma_subdevice(dev))
                return -EOPNOTSUPP;
 
+       if (domain->dirty_ops)
+               return -EINVAL;
+
        if (context_copied(iommu, info->bus, info->devfn))
                return -EBUSY;
 
@@ -4786,6 +4845,7 @@ static void *intel_iommu_hw_info(struct device *dev, u32 *length, u32 *type)
        if (!vtd)
                return ERR_PTR(-ENOMEM);
 
+       vtd->flags = IOMMU_HW_INFO_VTD_ERRATA_772415_SPR17;
        vtd->cap_reg = iommu->cap;
        vtd->ecap_reg = iommu->ecap;
        *length = sizeof(*vtd);
@@ -4793,11 +4853,89 @@ static void *intel_iommu_hw_info(struct device *dev, u32 *length, u32 *type)
        return vtd;
 }
 
+static int intel_iommu_set_dirty_tracking(struct iommu_domain *domain,
+                                         bool enable)
+{
+       struct dmar_domain *dmar_domain = to_dmar_domain(domain);
+       struct device_domain_info *info;
+       int ret;
+
+       spin_lock(&dmar_domain->lock);
+       if (dmar_domain->dirty_tracking == enable)
+               goto out_unlock;
+
+       list_for_each_entry(info, &dmar_domain->devices, link) {
+               ret = intel_pasid_setup_dirty_tracking(info->iommu,
+                                                      info->domain, info->dev,
+                                                      IOMMU_NO_PASID, enable);
+               if (ret)
+                       goto err_unwind;
+       }
+
+       dmar_domain->dirty_tracking = enable;
+out_unlock:
+       spin_unlock(&dmar_domain->lock);
+
+       return 0;
+
+err_unwind:
+       list_for_each_entry(info, &dmar_domain->devices, link)
+               intel_pasid_setup_dirty_tracking(info->iommu, dmar_domain,
+                                                info->dev, IOMMU_NO_PASID,
+                                                dmar_domain->dirty_tracking);
+       spin_unlock(&dmar_domain->lock);
+       return ret;
+}
+
+static int intel_iommu_read_and_clear_dirty(struct iommu_domain *domain,
+                                           unsigned long iova, size_t size,
+                                           unsigned long flags,
+                                           struct iommu_dirty_bitmap *dirty)
+{
+       struct dmar_domain *dmar_domain = to_dmar_domain(domain);
+       unsigned long end = iova + size - 1;
+       unsigned long pgsize;
+
+       /*
+        * IOMMUFD core calls into a dirty tracking disabled domain without an
+        * IOVA bitmap set in order to clean dirty bits in all PTEs that might
+        * have occurred when we stopped dirty tracking. This ensures that we
+        * never inherit dirtied bits from a previous cycle.
+        */
+       if (!dmar_domain->dirty_tracking && dirty->bitmap)
+               return -EINVAL;
+
+       do {
+               struct dma_pte *pte;
+               int lvl = 0;
+
+               pte = pfn_to_dma_pte(dmar_domain, iova >> VTD_PAGE_SHIFT, &lvl,
+                                    GFP_ATOMIC);
+               pgsize = level_size(lvl) << VTD_PAGE_SHIFT;
+               if (!pte || !dma_pte_present(pte)) {
+                       iova += pgsize;
+                       continue;
+               }
+
+               if (dma_sl_pte_test_and_clear_dirty(pte, flags))
+                       iommu_dirty_bitmap_record(dirty, iova, pgsize);
+               iova += pgsize;
+       } while (iova < end);
+
+       return 0;
+}
+
+const struct iommu_dirty_ops intel_dirty_ops = {
+       .set_dirty_tracking = intel_iommu_set_dirty_tracking,
+       .read_and_clear_dirty = intel_iommu_read_and_clear_dirty,
+};
+
 const struct iommu_ops intel_iommu_ops = {
        .blocked_domain         = &blocking_domain,
        .capable                = intel_iommu_capable,
        .hw_info                = intel_iommu_hw_info,
        .domain_alloc           = intel_iommu_domain_alloc,
+       .domain_alloc_user      = intel_iommu_domain_alloc_user,
        .probe_device           = intel_iommu_probe_device,
        .probe_finalize         = intel_iommu_probe_finalize,
        .release_device         = intel_iommu_release_device,