iommu/arm-smmu: Keep track of S2CR state
authorRobin Murphy <robin.murphy@arm.com>
Mon, 12 Sep 2016 16:13:50 +0000 (17:13 +0100)
committerWill Deacon <will.deacon@arm.com>
Fri, 16 Sep 2016 08:34:18 +0000 (09:34 +0100)
Making S2CRs first-class citizens within the driver with a high-level
representation of their state offers a neat solution to a few problems:

Firstly, the information about which context a device's stream IDs are
associated with is already present by necessity in the S2CR. With that
state easily accessible we can refer directly to it and obviate the need
to track an IOMMU domain in each device's archdata (its earlier purpose
of enforcing correct attachment of multi-device groups now being handled
by the IOMMU core itself).

Secondly, the core API now deprecates explicit domain detach and expects
domain attach to move devices smoothly from one domain to another; for
SMMUv2, this notion maps directly to simply rewriting the S2CRs assigned
to the device. By giving the driver a suitable abstraction of those
S2CRs to work with, we can massively reduce the overhead of the current
heavy-handed "detach, free resources, reallocate resources, attach"
approach.

Thirdly, making the software state hardware-shaped and attached to the
SMMU instance once again makes suspend/resume of this register group
that much simpler to implement in future.

Signed-off-by: Robin Murphy <robin.murphy@arm.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
drivers/iommu/arm-smmu.c

index dfe13780ba54b9ea8f05fa265612bda297f6b2ef..69b6cab6542179b2a4e808d246d698492da7de96 100644 (file)
 #define S2CR_CBNDX_MASK                        0xff
 #define S2CR_TYPE_SHIFT                        16
 #define S2CR_TYPE_MASK                 0x3
-#define S2CR_TYPE_TRANS                        (0 << S2CR_TYPE_SHIFT)
-#define S2CR_TYPE_BYPASS               (1 << S2CR_TYPE_SHIFT)
-#define S2CR_TYPE_FAULT                        (2 << S2CR_TYPE_SHIFT)
+enum arm_smmu_s2cr_type {
+       S2CR_TYPE_TRANS,
+       S2CR_TYPE_BYPASS,
+       S2CR_TYPE_FAULT,
+};
 
 #define S2CR_PRIVCFG_SHIFT             24
-#define S2CR_PRIVCFG_UNPRIV            (2 << S2CR_PRIVCFG_SHIFT)
+#define S2CR_PRIVCFG_MASK              0x3
+enum arm_smmu_s2cr_privcfg {
+       S2CR_PRIVCFG_DEFAULT,
+       S2CR_PRIVCFG_DIPAN,
+       S2CR_PRIVCFG_UNPRIV,
+       S2CR_PRIVCFG_PRIV,
+};
 
 /* Context bank attribute registers */
 #define ARM_SMMU_GR1_CBAR(n)           (0x0 + ((n) << 2))
@@ -292,6 +300,16 @@ enum arm_smmu_implementation {
        CAVIUM_SMMUV2,
 };
 
+struct arm_smmu_s2cr {
+       enum arm_smmu_s2cr_type         type;
+       enum arm_smmu_s2cr_privcfg      privcfg;
+       u8                              cbndx;
+};
+
+#define s2cr_init_val (struct arm_smmu_s2cr){                          \
+       .type = disable_bypass ? S2CR_TYPE_FAULT : S2CR_TYPE_BYPASS,    \
+}
+
 struct arm_smmu_smr {
        u16                             mask;
        u16                             id;
@@ -346,6 +364,7 @@ struct arm_smmu_device {
        u16                             streamid_mask;
        u16                             smr_mask_mask;
        struct arm_smmu_smr             *smrs;
+       struct arm_smmu_s2cr            *s2crs;
 
        unsigned long                   va_size;
        unsigned long                   ipa_size;
@@ -1108,6 +1127,23 @@ static void arm_smmu_write_smr(struct arm_smmu_device *smmu, int idx)
        writel_relaxed(reg, ARM_SMMU_GR0(smmu) + ARM_SMMU_GR0_SMR(idx));
 }
 
+static void arm_smmu_write_s2cr(struct arm_smmu_device *smmu, int idx)
+{
+       struct arm_smmu_s2cr *s2cr = smmu->s2crs + idx;
+       u32 reg = (s2cr->type & S2CR_TYPE_MASK) << S2CR_TYPE_SHIFT |
+                 (s2cr->cbndx & S2CR_CBNDX_MASK) << S2CR_CBNDX_SHIFT |
+                 (s2cr->privcfg & S2CR_PRIVCFG_MASK) << S2CR_PRIVCFG_SHIFT;
+
+       writel_relaxed(reg, ARM_SMMU_GR0(smmu) + ARM_SMMU_GR0_S2CR(idx));
+}
+
+static void arm_smmu_write_sme(struct arm_smmu_device *smmu, int idx)
+{
+       arm_smmu_write_s2cr(smmu, idx);
+       if (smmu->smrs)
+               arm_smmu_write_smr(smmu, idx);
+}
+
 static int arm_smmu_master_alloc_smes(struct arm_smmu_device *smmu,
                                      struct arm_smmu_master_cfg *cfg)
 {
@@ -1158,6 +1194,23 @@ static void arm_smmu_master_free_smes(struct arm_smmu_device *smmu,
 {
        int i;
 
+       /*
+        * We *must* clear the S2CR first, because freeing the SMR means
+        * that it can be re-allocated immediately.
+        */
+       for (i = 0; i < cfg->num_streamids; ++i) {
+               int idx = cfg->smendx[i];
+
+               /* An IOMMU group is torn down by the first device to be removed */
+               if (idx == INVALID_SMENDX)
+                       return;
+
+               smmu->s2crs[idx] = s2cr_init_val;
+               arm_smmu_write_s2cr(smmu, idx);
+       }
+       /* Sync S2CR updates before touching anything else */
+       __iowmb();
+
        /* Invalidate the SMRs before freeing back to the allocator */
        for (i = 0; i < cfg->num_streamids; ++i) {
                if (smmu->smrs)
@@ -1170,9 +1223,16 @@ static void arm_smmu_master_free_smes(struct arm_smmu_device *smmu,
 static int arm_smmu_domain_add_master(struct arm_smmu_domain *smmu_domain,
                                      struct arm_smmu_master_cfg *cfg)
 {
-       int i, ret;
+       int i, ret = 0;
        struct arm_smmu_device *smmu = smmu_domain->smmu;
-       void __iomem *gr0_base = ARM_SMMU_GR0(smmu);
+       struct arm_smmu_s2cr *s2cr = smmu->s2crs;
+       enum arm_smmu_s2cr_type type = S2CR_TYPE_TRANS;
+       u8 cbndx = smmu_domain->cfg.cbndx;
+
+       if (cfg->smendx[0] == INVALID_SMENDX)
+               ret = arm_smmu_master_alloc_smes(smmu, cfg);
+       if (ret)
+               return ret;
 
        /*
         * FIXME: This won't be needed once we have IOMMU-backed DMA ops
@@ -1181,58 +1241,21 @@ static int arm_smmu_domain_add_master(struct arm_smmu_domain *smmu_domain,
         * and a PCI device (i.e. a PCI host controller)
         */
        if (smmu_domain->domain.type == IOMMU_DOMAIN_DMA)
-               return 0;
-
-       /* Devices in an IOMMU group may already be configured */
-       ret = arm_smmu_master_alloc_smes(smmu, cfg);
-       if (ret)
-               return ret == -EEXIST ? 0 : ret;
+               type = S2CR_TYPE_BYPASS;
 
-       for (i = 0; i < cfg->num_streamids; ++i) {
-               u32 idx, s2cr;
-
-               idx = cfg->smendx[i];
-               s2cr = S2CR_TYPE_TRANS | S2CR_PRIVCFG_UNPRIV |
-                      (smmu_domain->cfg.cbndx << S2CR_CBNDX_SHIFT);
-               writel_relaxed(s2cr, gr0_base + ARM_SMMU_GR0_S2CR(idx));
-       }
-
-       return 0;
-}
-
-static void arm_smmu_domain_remove_master(struct arm_smmu_domain *smmu_domain,
-                                         struct arm_smmu_master_cfg *cfg)
-{
-       int i;
-       struct arm_smmu_device *smmu = smmu_domain->smmu;
-       void __iomem *gr0_base = ARM_SMMU_GR0(smmu);
-
-       /*
-        * We *must* clear the S2CR first, because freeing the SMR means
-        * that it can be re-allocated immediately.
-        */
        for (i = 0; i < cfg->num_streamids; ++i) {
                int idx = cfg->smendx[i];
-               u32 reg = disable_bypass ? S2CR_TYPE_FAULT : S2CR_TYPE_BYPASS;
 
-               /* An IOMMU group is torn down by the first device to be removed */
-               if (idx == INVALID_SMENDX)
-                       return;
+               /* Devices in an IOMMU group may already be configured */
+               if (type == s2cr[idx].type && cbndx == s2cr[idx].cbndx)
+                       break;
 
-               writel_relaxed(reg, gr0_base + ARM_SMMU_GR0_S2CR(idx));
+               s2cr[idx].type = type;
+               s2cr[idx].privcfg = S2CR_PRIVCFG_UNPRIV;
+               s2cr[idx].cbndx = cbndx;
+               arm_smmu_write_s2cr(smmu, idx);
        }
-
-       arm_smmu_master_free_smes(smmu, cfg);
-}
-
-static void arm_smmu_detach_dev(struct device *dev,
-                               struct arm_smmu_master_cfg *cfg)
-{
-       struct iommu_domain *domain = dev->archdata.iommu;
-       struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
-
-       dev->archdata.iommu = NULL;
-       arm_smmu_domain_remove_master(smmu_domain, cfg);
+       return 0;
 }
 
 static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
@@ -1269,14 +1292,7 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
        if (!cfg)
                return -ENODEV;
 
-       /* Detach the dev from its current domain */
-       if (dev->archdata.iommu)
-               arm_smmu_detach_dev(dev, cfg);
-
-       ret = arm_smmu_domain_add_master(smmu_domain, cfg);
-       if (!ret)
-               dev->archdata.iommu = domain;
-       return ret;
+       return arm_smmu_domain_add_master(smmu_domain, cfg);
 }
 
 static int arm_smmu_map(struct iommu_domain *domain, unsigned long iova,
@@ -1477,6 +1493,12 @@ static int arm_smmu_add_device(struct device *dev)
 
 static void arm_smmu_remove_device(struct device *dev)
 {
+       struct arm_smmu_device *smmu = find_smmu_for_device(dev);
+       struct arm_smmu_master_cfg *cfg = find_smmu_master_cfg(dev);
+
+       if (smmu && cfg)
+               arm_smmu_master_free_smes(smmu, cfg);
+
        iommu_group_remove_device(dev);
 }
 
@@ -1582,12 +1604,8 @@ static void arm_smmu_device_reset(struct arm_smmu_device *smmu)
         * Reset stream mapping groups: Initial values mark all SMRn as
         * invalid and all S2CRn as bypass unless overridden.
         */
-       reg = disable_bypass ? S2CR_TYPE_FAULT : S2CR_TYPE_BYPASS;
-       for (i = 0; i < smmu->num_mapping_groups; ++i) {
-               if (smmu->smrs)
-                       arm_smmu_write_smr(smmu, i);
-               writel_relaxed(reg, gr0_base + ARM_SMMU_GR0_S2CR(i));
-       }
+       for (i = 0; i < smmu->num_mapping_groups; ++i)
+               arm_smmu_write_sme(smmu, i);
 
        /*
         * Before clearing ARM_MMU500_ACTLR_CPRE, need to
@@ -1676,6 +1694,7 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu)
        void __iomem *gr0_base = ARM_SMMU_GR0(smmu);
        u32 id;
        bool cttw_dt, cttw_reg;
+       int i;
 
        dev_notice(smmu->dev, "probing hardware configuration...\n");
        dev_notice(smmu->dev, "SMMUv%d with:\n",
@@ -1773,6 +1792,14 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu)
                           "\tstream matching with %lu register groups, mask 0x%x",
                           size, smmu->smr_mask_mask);
        }
+       /* s2cr->type == 0 means translation, so initialise explicitly */
+       smmu->s2crs = devm_kmalloc_array(smmu->dev, size, sizeof(*smmu->s2crs),
+                                        GFP_KERNEL);
+       if (!smmu->s2crs)
+               return -ENOMEM;
+       for (i = 0; i < size; i++)
+               smmu->s2crs[i] = s2cr_init_val;
+
        smmu->num_mapping_groups = size;
 
        if (smmu->version < ARM_SMMU_V2 || !(id & ID0_PTFS_NO_AARCH32)) {