iommu/arm-smmu: support mapping before enabling S1 translations

For performance reasons there are clients who would like to move
from stage 1 bypass to stage 1 enabled without having to stop their
device.

Currently clients need to stop their device because they have to
create the required stage 1 mappings before re-enabling the device.
Add the new DOMAIN_ATTR_EARLY_MAP domain attribute to allow clients
to create stage 1 mappings after attaching but before enabling
stage 1 translations.

If the clients set the DOMAIN_ATTR_EARLY_MAP domain attribute to 1
before attaching then then once they attach the SMMU driver won't
enable stage 1 translations. This gives the client the opportunity to
create the required early mappings (for example using iommu_map).
When the client has finished creating the necessary early mappings
the client can then set the DOMAIN_ATTR_EARLY_MAP domain attribute
to 0, this will in turn enable stage 1 translations.

Change-Id: I9e95c5d2130f1d371e201eac69dec140cc773b1f
Signed-off-by: Liam Mark <lmark@codeaurora.org>
Signed-off-by: Patrick Daly <pdaly@codeaurora.org>
diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c
index 5a10a02..d0a8065 100644
--- a/drivers/iommu/arm-smmu.c
+++ b/drivers/iommu/arm-smmu.c
@@ -548,6 +548,8 @@
 static int arm_smmu_arch_init(struct arm_smmu_device *smmu);
 static void arm_smmu_arch_device_reset(struct arm_smmu_device *smmu);
 
+static int arm_smmu_enable_s1_translations(struct arm_smmu_domain *smmu_domain);
+
 static struct arm_smmu_domain *to_smmu_domain(struct iommu_domain *dom)
 {
 	return container_of(dom, struct arm_smmu_domain, domain);
@@ -1424,8 +1426,10 @@
 
 	/* SCTLR */
 	reg = SCTLR_CFCFG | SCTLR_CFIE | SCTLR_CFRE | SCTLR_AFE | SCTLR_TRE;
-	if (!(smmu_domain->attributes & (1 << DOMAIN_ATTR_S1_BYPASS)) ||
-	    !stage1)
+
+	if ((!(smmu_domain->attributes & (1 << DOMAIN_ATTR_S1_BYPASS)) &&
+	     !(smmu_domain->attributes & (1 << DOMAIN_ATTR_EARLY_MAP))) ||
+								!stage1)
 		reg |= SCTLR_M;
 	if (stage1)
 		reg |= SCTLR_S1_ASIDPNE;
@@ -2578,6 +2582,11 @@
 				   (1 << DOMAIN_ATTR_USE_UPSTREAM_HINT));
 		ret = 0;
 		break;
+	case DOMAIN_ATTR_EARLY_MAP:
+		*((int *)data) = !!(smmu_domain->attributes
+				    & (1 << DOMAIN_ATTR_EARLY_MAP));
+		ret = 0;
+		break;
 	default:
 		return -ENODEV;
 	}
@@ -2716,6 +2725,24 @@
 				1 << DOMAIN_ATTR_USE_UPSTREAM_HINT;
 		ret = 0;
 		break;
+	case DOMAIN_ATTR_EARLY_MAP: {
+		int early_map = *((int *)data);
+
+		ret = 0;
+		if (early_map) {
+			smmu_domain->attributes |=
+						1 << DOMAIN_ATTR_EARLY_MAP;
+		} else {
+			if (smmu_domain->smmu)
+				ret = arm_smmu_enable_s1_translations(
+								smmu_domain);
+
+			if (!ret)
+				smmu_domain->attributes &=
+					~(1 << DOMAIN_ATTR_EARLY_MAP);
+		}
+		break;
+	}
 	default:
 		ret = -ENODEV;
 	}
@@ -2738,6 +2765,27 @@
 	return iommu_fwspec_add_ids(dev, &fwid, 1);
 }
 
+static int arm_smmu_enable_s1_translations(struct arm_smmu_domain *smmu_domain)
+{
+	struct arm_smmu_cfg *cfg = &smmu_domain->cfg;
+	struct arm_smmu_device *smmu = smmu_domain->smmu;
+	void __iomem *cb_base;
+	u32 reg;
+	int ret;
+
+	cb_base = ARM_SMMU_CB_BASE(smmu) + ARM_SMMU_CB(smmu, cfg->cbndx);
+	ret = arm_smmu_power_on(smmu->pwr);
+	if (ret)
+		return ret;
+
+	reg = readl_relaxed(cb_base + ARM_SMMU_CB_SCTLR);
+	reg |= SCTLR_M;
+
+	writel_relaxed(reg, cb_base + ARM_SMMU_CB_SCTLR);
+	arm_smmu_power_off(smmu->pwr);
+	return ret;
+}
+
 static void arm_smmu_trigger_fault(struct iommu_domain *domain,
 					unsigned long flags)
 {