iommu-sva calls us when an mm is modified. Perform the required ATC
invalidations.

Signed-off-by: Jean-Philippe Brucker <jean-phili...@linaro.org>
---
v4->v5: more comments
---
 drivers/iommu/arm-smmu-v3.c | 70 ++++++++++++++++++++++++++++++-------
 1 file changed, 58 insertions(+), 12 deletions(-)

diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c
index 6640c2ac2a7c5..c4bffb14461aa 100644
--- a/drivers/iommu/arm-smmu-v3.c
+++ b/drivers/iommu/arm-smmu-v3.c
@@ -2375,6 +2375,20 @@ arm_smmu_atc_inv_to_cmd(int ssid, unsigned long iova, 
size_t size,
        size_t inval_grain_shift = 12;
        unsigned long page_start, page_end;
 
+       /*
+        * ATS and PASID:
+        *
+        * If substream_valid is clear, the PCIe TLP is sent without a PASID
+        * prefix. In that case all ATC entries within the address range are
+        * invalidated, including those that were requested with a PASID! There
+        * is no way to invalidate only entries without PASID.
+        *
+        * When using STRTAB_STE_1_S1DSS_SSID0 (reserving CD 0 for non-PASID
+        * traffic), translation requests without PASID create ATC entries
+        * without PASID, which must be invalidated with substream_valid clear.
+        * This has the unpleasant side-effect of invalidating all PASID-tagged
+        * ATC entries within the address range.
+        */
        *cmd = (struct arm_smmu_cmdq_ent) {
                .opcode                 = CMDQ_OP_ATC_INV,
                .substream_valid        = !!ssid,
@@ -2418,12 +2432,12 @@ arm_smmu_atc_inv_to_cmd(int ssid, unsigned long iova, 
size_t size,
        cmd->atc.size   = log2_span;
 }
 
-static int arm_smmu_atc_inv_master(struct arm_smmu_master *master)
+static int arm_smmu_atc_inv_master(struct arm_smmu_master *master, int ssid)
 {
        int i;
        struct arm_smmu_cmdq_ent cmd;
 
-       arm_smmu_atc_inv_to_cmd(0, 0, 0, &cmd);
+       arm_smmu_atc_inv_to_cmd(ssid, 0, 0, &cmd);
 
        for (i = 0; i < master->num_sids; i++) {
                cmd.atc.sid = master->sids[i];
@@ -2934,7 +2948,7 @@ static void arm_smmu_disable_ats(struct arm_smmu_master 
*master)
         * ATC invalidation via the SMMU.
         */
        wmb();
-       arm_smmu_atc_inv_master(master);
+       arm_smmu_atc_inv_master(master, 0);
        atomic_dec(&smmu_domain->nr_ats_masters);
 }
 
@@ -3131,7 +3145,22 @@ arm_smmu_iova_to_phys(struct iommu_domain *domain, 
dma_addr_t iova)
 static void arm_smmu_mm_invalidate(struct device *dev, int pasid, void *entry,
                                   unsigned long iova, size_t size)
 {
-       /* TODO: Invalidate ATC */
+       int i;
+       struct arm_smmu_cmdq_ent cmd;
+       struct arm_smmu_cmdq_batch cmds = {};
+       struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+
+       if (!master->ats_enabled)
+               return;
+
+       arm_smmu_atc_inv_to_cmd(pasid, iova, size, &cmd);
+
+       for (i = 0; i < master->num_sids; i++) {
+               cmd.atc.sid = master->sids[i];
+               arm_smmu_cmdq_batch_add(master->smmu, &cmds, &cmd);
+       }
+
+       arm_smmu_cmdq_batch_submit(master->smmu, &cmds);
 }
 
 static int arm_smmu_mm_attach(struct device *dev, int pasid, void *entry,
@@ -3168,26 +3197,43 @@ static void arm_smmu_mm_clear(struct device *dev, int 
pasid, void *entry)
         * for this ASID, so we need to do it manually.
         */
        arm_smmu_tlb_inv_asid(smmu_domain->smmu, cd->asid);
-
-       /* TODO: invalidate ATC */
+       arm_smmu_atc_inv_domain(smmu_domain, pasid, 0, 0);
 }
 
 static void arm_smmu_mm_detach(struct device *dev, int pasid, void *entry,
                               bool detach_domain, bool cleared)
 {
        struct arm_smmu_ctx_desc *cd = entry;
+       struct arm_smmu_master *master = dev_iommu_priv_get(dev);
        struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
        struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
 
-       if (detach_domain) {
+       if (detach_domain)
                arm_smmu_write_ctx_desc(smmu_domain, pasid, NULL);
 
-               if (!cleared)
-                       /* See comment in arm_smmu_mm_clear() */
-                       arm_smmu_tlb_inv_asid(smmu_domain->smmu, cd->asid);
-       }
+       /*
+        * If we went through clear(), we've already invalidated, and no new TLB
+        * entry can have been formed.
+        */
+       if (cleared)
+               return;
+
+       if (detach_domain) {
+               /* See comment in arm_smmu_mm_clear() */
+               arm_smmu_tlb_inv_asid(smmu_domain->smmu, cd->asid);
+               arm_smmu_atc_inv_domain(smmu_domain, pasid, 0, 0);
 
-       /* TODO: invalidate ATC */
+       } else if (master->ats_enabled) {
+               /*
+                * There are more devices bound with this PASID in this domain,
+                * so we cannot yet clear the PASID entry, and this device could
+                * create new ATC entries. Invalidate the ATC for the sake of
+                * it. On unbinding the last device we'll properly invalidate
+                * all ATCs in the domain. Alternatively, an early detach_dev()
+                * on this device will also flush the ATC.
+                */
+               arm_smmu_atc_inv_master(master, pasid);
+       }
 }
 
 static void *arm_smmu_mm_alloc(struct mm_struct *mm)
-- 
2.26.0

_______________________________________________
iommu mailing list
iommu@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/iommu

Reply via email to