iommu/arm-smmu: Add support for slave side protection

For targets where we have no hypervisor, slave side protection mechanism
is used to provide buffer protection. Add functionality to make calls
into TZ for mapping/unmapping of buffers.

Change-Id: I845983a25b56ce1b34c90f91ac4a2cf6c4451813
Signed-off-by: Charan Teja Reddy <charante@codeaurora.org>
diff --git a/Documentation/devicetree/bindings/iommu/arm,smmu.txt b/Documentation/devicetree/bindings/iommu/arm,smmu.txt
index 2a7e161..78aa1d7 100644
--- a/Documentation/devicetree/bindings/iommu/arm,smmu.txt
+++ b/Documentation/devicetree/bindings/iommu/arm,smmu.txt
@@ -74,6 +74,9 @@
                   address size faults are due to a fundamental programming
                   error from which we don't care about recovering anyways.
 
+- qcom,tz-device-id : A string indicating the device ID for this SMMU known
+		  to TZ.  See msm_tz_smmu.c for a full list of mappings.
+
 - qcom,skip-init : Disable resetting configuration for all context banks
                   during device reset.  This is useful for targets where
                   some context banks are dedicated to other execution
diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile
index 620fc50..ca267a8 100644
--- a/drivers/iommu/Makefile
+++ b/drivers/iommu/Makefile
@@ -7,6 +7,7 @@
 obj-$(CONFIG_IOMMU_IO_PGTABLE_ARMV7S) += io-pgtable-arm-v7s.o
 obj-$(CONFIG_IOMMU_IO_PGTABLE_LPAE) += io-pgtable-arm.o
 obj-$(CONFIG_IOMMU_IOVA) += iova.o
+obj-$(CONFIG_MSM_TZ_SMMU) += io-pgtable-msm-secure.o
 obj-$(CONFIG_IOMMU_IO_PGTABLE_FAST) += io-pgtable-fast.o dma-mapping-fast.o
 obj-$(CONFIG_OF_IOMMU)	+= of_iommu.o
 obj-$(CONFIG_IOMMU_DEBUG) += iommu-debug.o
diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c
index 0be0779..76b8933 100644
--- a/drivers/iommu/arm-smmu.c
+++ b/drivers/iommu/arm-smmu.c
@@ -55,6 +55,8 @@
 #include <linux/remote_spinlock.h>
 #include <linux/ktime.h>
 #include <trace/events/iommu.h>
+#include <soc/qcom/msm_tz_smmu.h>
+#include <soc/qcom/scm.h>
 
 #include <linux/amba/bus.h>
 
@@ -174,6 +176,7 @@
 #define SMR_VALID			(1 << 31)
 #define SMR_MASK_SHIFT			16
 #define SMR_MASK_MASK			0x7FFF
+#define SID_MASK			0x7FFF
 #define SMR_ID_SHIFT			0
 
 #define ARM_SMMU_GR0_S2CR(n)		(0xc00 + ((n) << 2))
@@ -411,6 +414,7 @@
 
 	void __iomem			*base;
 	unsigned long			size;
+	phys_addr_t			phys_addr;
 	unsigned long			pgshift;
 
 #define ARM_SMMU_FEAT_COHERENT_WALK	(1 << 0)
@@ -478,6 +482,8 @@
 
 	struct arm_smmu_arch_ops	*arch_ops;
 	void				*archdata;
+
+	enum tz_smmu_device_id		sec_id;
 };
 
 enum arm_smmu_context_fmt {
@@ -529,6 +535,7 @@
 	enum arm_smmu_domain_stage	stage;
 	struct mutex			init_mutex; /* Protects smmu pointer */
 	u32 attributes;
+	bool				slave_side_secure;
 	u32				secure_vmid;
 	struct list_head		pte_info_list;
 	struct list_head		unassign_list;
@@ -588,6 +595,8 @@
 static struct iommu_gather_ops qsmmuv500_errata1_smmu_gather_ops;
 
 static bool arm_smmu_is_static_cb(struct arm_smmu_device *smmu);
+static bool arm_smmu_is_master_side_secure(struct arm_smmu_domain *smmu_domain);
+static bool arm_smmu_is_slave_side_secure(struct arm_smmu_domain *smmu_domain);
 
 static struct arm_smmu_domain *to_smmu_domain(struct iommu_domain *dom)
 {
@@ -615,6 +624,22 @@
 	return !!(smmu_domain->attributes & (1 << DOMAIN_ATTR_DYNAMIC));
 }
 
+static int arm_smmu_restore_sec_cfg(struct arm_smmu_device *smmu)
+{
+	int ret;
+	int scm_ret = 0;
+
+	if (!arm_smmu_is_static_cb(smmu))
+		return 0;
+
+	ret = scm_restore_sec_cfg(smmu->sec_id, 0x0, &scm_ret);
+	if (ret || scm_ret) {
+		pr_err("scm call IOMMU_SECURE_CFG failed\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
 static bool is_iommu_pt_coherent(struct arm_smmu_domain *smmu_domain)
 {
 	if (smmu_domain->attributes &
@@ -631,20 +656,32 @@
 	return smmu->options & ARM_SMMU_OPT_STATIC_CB;
 }
 
-static bool arm_smmu_is_domain_secure(struct arm_smmu_domain *smmu_domain)
+static bool arm_smmu_has_secure_vmid(struct arm_smmu_domain *smmu_domain)
 {
 	return (smmu_domain->secure_vmid != VMID_INVAL);
 }
 
+static bool arm_smmu_is_slave_side_secure(struct arm_smmu_domain *smmu_domain)
+{
+	return arm_smmu_has_secure_vmid(smmu_domain) &&
+			smmu_domain->slave_side_secure;
+}
+
+static bool arm_smmu_is_master_side_secure(struct arm_smmu_domain *smmu_domain)
+{
+	return arm_smmu_has_secure_vmid(smmu_domain)
+			&& !smmu_domain->slave_side_secure;
+}
+
 static void arm_smmu_secure_domain_lock(struct arm_smmu_domain *smmu_domain)
 {
-	if (arm_smmu_is_domain_secure(smmu_domain))
+	if (arm_smmu_is_master_side_secure(smmu_domain))
 		mutex_lock(&smmu_domain->assign_lock);
 }
 
 static void arm_smmu_secure_domain_unlock(struct arm_smmu_domain *smmu_domain)
 {
-	if (arm_smmu_is_domain_secure(smmu_domain))
+	if (arm_smmu_is_master_side_secure(smmu_domain))
 		mutex_unlock(&smmu_domain->assign_lock);
 }
 
@@ -1281,7 +1318,7 @@
 	void *page;
 	struct arm_smmu_domain *smmu_domain = cookie;
 
-	if (!arm_smmu_is_domain_secure(smmu_domain))
+	if (!arm_smmu_is_master_side_secure(smmu_domain))
 		return alloc_pages_exact(size, gfp_mask);
 
 	page = arm_smmu_secure_pool_remove(smmu_domain, size);
@@ -1304,7 +1341,7 @@
 {
 	struct arm_smmu_domain *smmu_domain = cookie;
 
-	if (!arm_smmu_is_domain_secure(smmu_domain)) {
+	if (!arm_smmu_is_master_side_secure(smmu_domain)) {
 		free_pages_exact(virt, size);
 		return;
 	}
@@ -1515,6 +1552,22 @@
 	return IRQ_HANDLED;
 }
 
+static int arm_smmu_set_pt_format(struct arm_smmu_domain *smmu_domain,
+				  struct io_pgtable_cfg *pgtbl_cfg)
+{
+	struct arm_smmu_device *smmu = smmu_domain->smmu;
+	struct arm_smmu_cfg *cfg = &smmu_domain->cfg;
+	int ret = 0;
+
+	if ((smmu->version > ARM_SMMU_V1) &&
+	    (cfg->fmt == ARM_SMMU_CTX_FMT_AARCH64) &&
+	    !arm_smmu_has_secure_vmid(smmu_domain) &&
+	    arm_smmu_is_static_cb(smmu)) {
+		ret = msm_tz_set_cb_format(smmu->sec_id, cfg->cbndx);
+	}
+	return ret;
+}
+
 static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain,
 				       struct io_pgtable_cfg *pgtbl_cfg)
 {
@@ -1825,14 +1878,27 @@
 		cfg->irptndx = cfg->cbndx;
 	}
 
-	smmu_domain->pgtbl_cfg = (struct io_pgtable_cfg) {
-		.quirks		= quirks,
-		.pgsize_bitmap	= smmu->pgsize_bitmap,
-		.ias		= ias,
-		.oas		= oas,
-		.tlb		= tlb,
-		.iommu_dev	= smmu->dev,
-	};
+	if (arm_smmu_is_slave_side_secure(smmu_domain)) {
+		smmu_domain->pgtbl_cfg = (struct io_pgtable_cfg) {
+			.quirks         = quirks,
+			.pgsize_bitmap  = smmu->pgsize_bitmap,
+			.arm_msm_secure_cfg = {
+				.sec_id = smmu->sec_id,
+				.cbndx = cfg->cbndx,
+			},
+			.iommu_dev      = smmu->dev,
+		};
+		fmt = ARM_MSM_SECURE;
+	} else  {
+		smmu_domain->pgtbl_cfg = (struct io_pgtable_cfg) {
+			.quirks		= quirks,
+			.pgsize_bitmap	= smmu->pgsize_bitmap,
+			.ias		= ias,
+			.oas		= oas,
+			.tlb		= tlb,
+			.iommu_dev	= smmu->dev,
+		};
+	}
 
 	smmu_domain->smmu = smmu;
 	smmu_domain->dev = dev;
@@ -1865,6 +1931,13 @@
 		/* Initialise the context bank with our page table cfg */
 		arm_smmu_init_context_bank(smmu_domain,
 						&smmu_domain->pgtbl_cfg);
+		/* for slave side secure, we may have to force the pagetable
+		 * format to V8L.
+		 */
+		ret = arm_smmu_set_pt_format(smmu_domain,
+					     &smmu_domain->pgtbl_cfg);
+		if (ret)
+			goto out_clear_smmu;
 
 		arm_smmu_arch_init_context_bank(smmu_domain, dev);
 
@@ -2173,6 +2246,8 @@
 	const struct iommu_gather_ops *tlb;
 
 	tlb = smmu_domain->pgtbl_cfg.tlb;
+	if (!tlb)
+		return;
 
 	mutex_lock(&smmu->stream_map_mutex);
 	for_each_cfg_sme(fwspec, i, idx) {
@@ -2249,7 +2324,7 @@
 	int source_vmid = VMID_HLOS;
 	struct arm_smmu_pte_info *pte_info, *temp;
 
-	if (!arm_smmu_is_domain_secure(smmu_domain))
+	if (!arm_smmu_is_master_side_secure(smmu_domain))
 		return ret;
 
 	list_for_each_entry(pte_info, &smmu_domain->pte_info_list, entry) {
@@ -2276,7 +2351,7 @@
 	int source_vmlist[2] = {VMID_HLOS, smmu_domain->secure_vmid};
 	struct arm_smmu_pte_info *pte_info, *temp;
 
-	if (!arm_smmu_is_domain_secure(smmu_domain))
+	if (!arm_smmu_is_master_side_secure(smmu_domain))
 		return;
 
 	list_for_each_entry(pte_info, &smmu_domain->unassign_list, entry) {
@@ -2300,7 +2375,14 @@
 	struct arm_smmu_domain *smmu_domain = cookie;
 	struct arm_smmu_pte_info *pte_info;
 
-	BUG_ON(!arm_smmu_is_domain_secure(smmu_domain));
+	if (smmu_domain->slave_side_secure ||
+	    !arm_smmu_has_secure_vmid(smmu_domain)) {
+		if (smmu_domain->slave_side_secure)
+			WARN(1, "slave side secure is enforced\n");
+		else
+			WARN(1, "Invalid VMID is set !!\n");
+		return;
+	}
 
 	pte_info = kzalloc(sizeof(struct arm_smmu_pte_info), GFP_ATOMIC);
 	if (!pte_info)
@@ -2316,7 +2398,14 @@
 	struct arm_smmu_domain *smmu_domain = cookie;
 	struct arm_smmu_pte_info *pte_info;
 
-	BUG_ON(!arm_smmu_is_domain_secure(smmu_domain));
+	if (smmu_domain->slave_side_secure ||
+	    !arm_smmu_has_secure_vmid(smmu_domain)) {
+		if (smmu_domain->slave_side_secure)
+			WARN(1, "slave side secure is enforced\n");
+		else
+			WARN(1, "Invalid VMID is set !!\n");
+		return -EINVAL;
+	}
 
 	pte_info = kzalloc(sizeof(struct arm_smmu_pte_info), GFP_ATOMIC);
 	if (!pte_info)
@@ -3294,7 +3383,20 @@
 
 	reg = readl_relaxed(impl_def1_base + IMPL_DEF1_MICRO_MMU_CTRL);
 	reg |= MICRO_MMU_CTRL_LOCAL_HALT_REQ;
-	writel_relaxed(reg, impl_def1_base + IMPL_DEF1_MICRO_MMU_CTRL);
+
+	if (arm_smmu_is_static_cb(smmu)) {
+		phys_addr_t impl_def1_base_phys = impl_def1_base - smmu->base +
+							smmu->phys_addr;
+
+		if (scm_io_write(impl_def1_base_phys +
+					IMPL_DEF1_MICRO_MMU_CTRL, reg)) {
+			dev_err(smmu->dev,
+				"scm_io_write fail. SMMU might not be halted");
+			return -EINVAL;
+		}
+	} else {
+		writel_relaxed(reg, impl_def1_base + IMPL_DEF1_MICRO_MMU_CTRL);
+	}
 
 	return wait ? qsmmuv2_wait_for_halt(smmu) : 0;
 }
@@ -3316,7 +3418,18 @@
 
 	reg = readl_relaxed(impl_def1_base + IMPL_DEF1_MICRO_MMU_CTRL);
 	reg &= ~MICRO_MMU_CTRL_LOCAL_HALT_REQ;
-	writel_relaxed(reg, impl_def1_base + IMPL_DEF1_MICRO_MMU_CTRL);
+
+	if (arm_smmu_is_static_cb(smmu)) {
+		phys_addr_t impl_def1_base_phys = impl_def1_base - smmu->base +
+							smmu->phys_addr;
+
+		if (scm_io_write(impl_def1_base_phys +
+				IMPL_DEF1_MICRO_MMU_CTRL, reg))
+			dev_err(smmu->dev,
+				"scm_io_write fail. SMMU might not be resumed");
+	} else {
+		writel_relaxed(reg, impl_def1_base + IMPL_DEF1_MICRO_MMU_CTRL);
+	}
 }
 
 static void qsmmuv2_device_reset(struct arm_smmu_device *smmu)
@@ -3549,6 +3662,9 @@
 			cb = smmu->s2crs[idx].cbndx;
 	}
 
+	if (cb >= 0 && arm_smmu_is_static_cb(smmu))
+		smmu_domain->slave_side_secure = true;
+
 	if (cb < 0 && !arm_smmu_is_static_cb(smmu)) {
 		mutex_unlock(&smmu->stream_map_mutex);
 		return __arm_smmu_alloc_bitmap(smmu->context_map,
@@ -3558,7 +3674,8 @@
 
 	for (i = 0; i < smmu->num_mapping_groups; i++) {
 		if (smmu->s2crs[i].cb_handoff && smmu->s2crs[i].cbndx == cb) {
-			smmu->s2crs[i].cb_handoff = false;
+			if (!arm_smmu_is_static_cb(smmu))
+				smmu->s2crs[i].cb_handoff = false;
 			smmu->s2crs[i].count -= 1;
 		}
 	}
@@ -3810,6 +3927,9 @@
 	bool cttw_dt, cttw_reg;
 	int i;
 
+	if (arm_smmu_restore_sec_cfg(smmu))
+		return -ENODEV;
+
 	dev_dbg(smmu->dev, "probing hardware configuration...\n");
 	dev_dbg(smmu->dev, "SMMUv%d with:\n",
 			smmu->version == ARM_SMMU_V2 ? 2 : 1);
@@ -3887,26 +4007,32 @@
 		 * bits are set, so check each one separately. We can reject
 		 * masters later if they try to claim IDs outside these masks.
 		 */
-		for (i = 0; i < size; i++) {
+		if (!arm_smmu_is_static_cb(smmu)) {
+			for (i = 0; i < size; i++) {
+				smr = readl_relaxed(
+					gr0_base + ARM_SMMU_GR0_SMR(i));
+				if (!(smr & SMR_VALID))
+					break;
+			}
+			if (i == size) {
+				dev_err(smmu->dev,
+					"Unable to compute streamid_masks\n");
+				return -ENODEV;
+			}
+
+			smr = smmu->streamid_mask << SMR_ID_SHIFT;
+			writel_relaxed(smr, gr0_base + ARM_SMMU_GR0_SMR(i));
 			smr = readl_relaxed(gr0_base + ARM_SMMU_GR0_SMR(i));
-			if (!(smr & SMR_VALID))
-				break;
-		}
-		if (i == size) {
-			dev_err(smmu->dev,
-				"Unable to compute streamid_masks\n");
-			return -ENODEV;
-		}
+			smmu->streamid_mask = smr >> SMR_ID_SHIFT;
 
-		smr = smmu->streamid_mask << SMR_ID_SHIFT;
-		writel_relaxed(smr, gr0_base + ARM_SMMU_GR0_SMR(i));
-		smr = readl_relaxed(gr0_base + ARM_SMMU_GR0_SMR(i));
-		smmu->streamid_mask = smr >> SMR_ID_SHIFT;
-
-		smr = smmu->streamid_mask << SMR_MASK_SHIFT;
-		writel_relaxed(smr, gr0_base + ARM_SMMU_GR0_SMR(i));
-		smr = readl_relaxed(gr0_base + ARM_SMMU_GR0_SMR(i));
-		smmu->smr_mask_mask = smr >> SMR_MASK_SHIFT;
+			smr = smmu->streamid_mask << SMR_MASK_SHIFT;
+			writel_relaxed(smr, gr0_base + ARM_SMMU_GR0_SMR(i));
+			smr = readl_relaxed(gr0_base + ARM_SMMU_GR0_SMR(i));
+			smmu->smr_mask_mask = smr >> SMR_MASK_SHIFT;
+		} else {
+			smmu->smr_mask_mask = SMR_MASK_MASK;
+			smmu->streamid_mask = SID_MASK;
+		}
 
 		/* Zero-initialised to mark as invalid */
 		smmu->smrs = devm_kcalloc(smmu->dev, size, sizeof(*smmu->smrs),
@@ -4070,7 +4196,23 @@
 };
 MODULE_DEVICE_TABLE(of, arm_smmu_of_match);
 
+#ifdef CONFIG_MSM_TZ_SMMU
+int register_iommu_sec_ptbl(void)
+{
+	struct device_node *np;
 
+	for_each_matching_node(np, arm_smmu_of_match)
+		if (of_find_property(np, "qcom,tz-device-id", NULL) &&
+				of_device_is_available(np))
+			break;
+	if (!np)
+		return -ENODEV;
+
+	of_node_put(np);
+
+	return msm_iommu_sec_pgtbl_init();
+}
+#endif
 static int arm_smmu_of_iommu_configure_fixup(struct device *dev, void *data)
 {
 	if (!dev->iommu_fwspec)
@@ -4124,6 +4266,8 @@
 	smmu->arch_ops = data->arch_ops;
 
 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res)
+		smmu->phys_addr = res->start;
 	smmu->base = devm_ioremap_resource(dev, res);
 	if (IS_ERR(smmu->base))
 		return PTR_ERR(smmu->base);
@@ -4175,6 +4319,7 @@
 	if (err)
 		goto out_exit_power_resources;
 
+	smmu->sec_id = msm_dev_to_device_id(dev);
 	err = arm_smmu_device_cfg_probe(smmu);
 	if (err)
 		goto out_power_off;
@@ -4302,6 +4447,9 @@
 		return ret;
 
 	ret = platform_driver_register(&arm_smmu_driver);
+#ifdef CONFIG_MSM_TZ_SMMU
+	ret = register_iommu_sec_ptbl();
+#endif
 	registered = !ret;
 	trace_smmu_init(ktime_us_delta(ktime_get(), cur));
 
diff --git a/drivers/iommu/io-pgtable-msm-secure.c b/drivers/iommu/io-pgtable-msm-secure.c
new file mode 100644
index 0000000..983b28b
--- /dev/null
+++ b/drivers/iommu/io-pgtable-msm-secure.c
@@ -0,0 +1,350 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt)	"io-pgtable-msm-secure: " fmt
+
+#include <linux/iommu.h>
+#include <linux/kernel.h>
+#include <linux/scatterlist.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <soc/qcom/scm.h>
+#include <linux/dma-mapping.h>
+#include <asm/cacheflush.h>
+
+#include "io-pgtable.h"
+
+#define IOMMU_SECURE_PTBL_SIZE  3
+#define IOMMU_SECURE_PTBL_INIT  4
+#define IOMMU_SECURE_MAP2_FLAT 0x12
+#define IOMMU_SECURE_UNMAP2_FLAT 0x13
+#define IOMMU_TLBINVAL_FLAG 0x00000001
+
+#define io_pgtable_to_data(x)						\
+	container_of((x), struct msm_secure_io_pgtable, iop)
+
+#define io_pgtable_ops_to_pgtable(x)					\
+	container_of((x), struct io_pgtable, ops)
+
+#define io_pgtable_ops_to_data(x)					\
+	io_pgtable_to_data(io_pgtable_ops_to_pgtable(x))
+
+struct msm_secure_io_pgtable {
+	struct io_pgtable iop;
+};
+
+int msm_iommu_sec_pgtbl_init(void)
+{
+	int psize[2] = {0, 0};
+	unsigned int spare = 0;
+	int ret, ptbl_ret = 0;
+	struct device dev = {0};
+	void *cpu_addr;
+	dma_addr_t paddr;
+	unsigned long attrs = 0;
+
+	if (is_scm_armv8()) {
+		struct scm_desc desc = {0};
+
+		desc.args[0] = spare;
+		desc.arginfo = SCM_ARGS(1);
+		ret = scm_call2(SCM_SIP_FNID(SCM_SVC_MP,
+				IOMMU_SECURE_PTBL_SIZE), &desc);
+		psize[0] = desc.ret[0];
+		psize[1] = desc.ret[1];
+		if (ret || psize[1]) {
+			pr_err("scm call IOMMU_SECURE_PTBL_SIZE failed\n");
+			return ret;
+		}
+	}
+
+	/* Now allocate memory for the secure page tables */
+	attrs = DMA_ATTR_NO_KERNEL_MAPPING;
+	dev.coherent_dma_mask = DMA_BIT_MASK(sizeof(dma_addr_t) * 8);
+	arch_setup_dma_ops(&dev, 0, 0, NULL, 1);
+	cpu_addr = dma_alloc_attrs(&dev, psize[0], &paddr, GFP_KERNEL, attrs);
+	if (!cpu_addr) {
+		pr_err("%s: Failed to allocate %d bytes for PTBL\n",
+				__func__, psize[0]);
+		return -ENOMEM;
+	}
+
+	if (is_scm_armv8()) {
+		struct scm_desc desc = {0};
+
+		desc.args[0] = paddr;
+		desc.args[1] = psize[0];
+		desc.args[2] = 0;
+		desc.arginfo = SCM_ARGS(3, SCM_RW, SCM_VAL, SCM_VAL);
+
+		ret = scm_call2(SCM_SIP_FNID(SCM_SVC_MP,
+				IOMMU_SECURE_PTBL_INIT), &desc);
+		ptbl_ret = desc.ret[0];
+
+		if (ret) {
+			pr_err("scm call IOMMU_SECURE_PTBL_INIT failed\n");
+			return ret;
+		}
+
+		if (ptbl_ret) {
+			pr_err("scm call IOMMU_SECURE_PTBL_INIT extended ret fail\n");
+			return ret;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(msm_iommu_sec_pgtbl_init);
+
+static int msm_secure_map(struct io_pgtable_ops *ops, unsigned long iova,
+			phys_addr_t paddr, size_t size, int iommu_prot)
+{
+	struct msm_secure_io_pgtable *data = io_pgtable_ops_to_data(ops);
+	struct io_pgtable_cfg *cfg = &data->iop.cfg;
+	void *flush_va, *flush_va_end;
+	struct scm_desc desc = {0};
+	int ret = -EINVAL;
+	u32 resp;
+
+	if (!IS_ALIGNED(iova, SZ_1M) || !IS_ALIGNED(paddr, SZ_1M) ||
+			!IS_ALIGNED(size, SZ_1M))
+		return -EINVAL;
+
+	desc.args[0] = virt_to_phys(&paddr);
+	desc.args[1] = 1;
+	desc.args[2] = size;
+	desc.args[3] = cfg->arm_msm_secure_cfg.sec_id;
+	desc.args[4] = cfg->arm_msm_secure_cfg.cbndx;
+	desc.args[5] = iova;
+	desc.args[6] = size;
+	desc.args[7] = 0;
+
+	flush_va = &paddr;
+	flush_va_end = (void *)
+		(((unsigned long) flush_va) + sizeof(phys_addr_t));
+
+	/*
+	 * Ensure that the buffer is in RAM by the time it gets to TZ
+	 */
+	dmac_clean_range(flush_va, flush_va_end);
+
+	desc.arginfo = SCM_ARGS(8, SCM_RW, SCM_VAL, SCM_VAL, SCM_VAL, SCM_VAL,
+				SCM_VAL, SCM_VAL, SCM_VAL);
+
+	if (is_scm_armv8()) {
+		ret = scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_MP,
+				IOMMU_SECURE_MAP2_FLAT), &desc);
+		resp = desc.ret[0];
+	}
+
+	if (ret || resp)
+		return -EINVAL;
+
+	return 0;
+}
+
+static dma_addr_t msm_secure_get_phys_addr(struct scatterlist *sg)
+{
+	/*
+	 * Try sg_dma_address first so that we can
+	 * map carveout regions that do not have a
+	 * struct page associated with them.
+	 */
+	dma_addr_t pa = sg_dma_address(sg);
+
+	if (pa == 0)
+		pa = sg_phys(sg);
+	return pa;
+}
+
+static int msm_secure_map_sg(struct io_pgtable_ops *ops, unsigned long iova,
+			   struct scatterlist *sg, unsigned int nents,
+			   int iommu_prot, size_t *size)
+{
+	struct msm_secure_io_pgtable *data = io_pgtable_ops_to_data(ops);
+	struct io_pgtable_cfg *cfg = &data->iop.cfg;
+	int ret = -EINVAL;
+	struct scatterlist *tmp, *sgiter;
+	dma_addr_t *pa_list = 0;
+	unsigned int cnt, offset = 0, chunk_offset = 0;
+	dma_addr_t pa;
+	void *flush_va, *flush_va_end;
+	unsigned long len = 0;
+	struct scm_desc desc = {0};
+	int i;
+	u32 resp;
+
+	for_each_sg(sg, tmp, nents, i)
+		len += tmp->length;
+
+	if (!IS_ALIGNED(iova, SZ_1M) || !IS_ALIGNED(len, SZ_1M))
+		return -EINVAL;
+
+	if (sg->length == len) {
+		cnt = 1;
+		pa = msm_secure_get_phys_addr(sg);
+		if (!IS_ALIGNED(pa, SZ_1M))
+			return -EINVAL;
+
+		desc.args[0] = virt_to_phys(&pa);
+		desc.args[1] = cnt;
+		desc.args[2] = len;
+		flush_va = &pa;
+	} else {
+		sgiter = sg;
+		if (!IS_ALIGNED(sgiter->length, SZ_1M))
+			return -EINVAL;
+		cnt = sg->length / SZ_1M;
+		while ((sgiter = sg_next(sgiter))) {
+			if (!IS_ALIGNED(sgiter->length, SZ_1M))
+				return -EINVAL;
+			cnt += sgiter->length / SZ_1M;
+		}
+
+		pa_list = kmalloc_array(cnt, sizeof(*pa_list), GFP_KERNEL);
+		if (!pa_list)
+			return -ENOMEM;
+
+		sgiter = sg;
+		cnt = 0;
+		pa = msm_secure_get_phys_addr(sgiter);
+		while (offset < len) {
+
+			if (!IS_ALIGNED(pa, SZ_1M)) {
+				kfree(pa_list);
+				return -EINVAL;
+			}
+
+			pa_list[cnt] = pa + chunk_offset;
+			chunk_offset += SZ_1M;
+			offset += SZ_1M;
+			cnt++;
+
+			if (chunk_offset >= sgiter->length && offset < len) {
+				chunk_offset = 0;
+				sgiter = sg_next(sgiter);
+				pa = msm_secure_get_phys_addr(sgiter);
+			}
+		}
+
+		desc.args[0] = virt_to_phys(pa_list);
+		desc.args[1] = cnt;
+		desc.args[2] = SZ_1M;
+		flush_va = pa_list;
+	}
+
+	desc.args[3] = cfg->arm_msm_secure_cfg.sec_id;
+	desc.args[4] = cfg->arm_msm_secure_cfg.cbndx;
+	desc.args[5] = iova;
+	desc.args[6] = len;
+	desc.args[7] = 0;
+
+	desc.arginfo = SCM_ARGS(8, SCM_RW, SCM_VAL, SCM_VAL, SCM_VAL, SCM_VAL,
+			SCM_VAL, SCM_VAL, SCM_VAL);
+
+	/*
+	 * Ensure that the buffer is in RAM by the time it gets to TZ
+	 */
+
+	flush_va_end = (void *) (((unsigned long) flush_va) +
+			(cnt * sizeof(*pa_list)));
+	dmac_clean_range(flush_va, flush_va_end);
+
+	if (is_scm_armv8()) {
+		ret = scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_MP,
+					 IOMMU_SECURE_MAP2_FLAT), &desc);
+		resp = desc.ret[0];
+
+		if (ret || resp)
+			ret = -EINVAL;
+		else
+			ret = len;
+	}
+
+	kfree(pa_list);
+	return ret;
+}
+
+static size_t msm_secure_unmap(struct io_pgtable_ops *ops, unsigned long iova,
+			  size_t len)
+{
+	struct msm_secure_io_pgtable *data = io_pgtable_ops_to_data(ops);
+	struct io_pgtable_cfg *cfg = &data->iop.cfg;
+	int ret = -EINVAL;
+	struct scm_desc desc = {0};
+
+	if (!IS_ALIGNED(iova, SZ_1M) || !IS_ALIGNED(len, SZ_1M))
+		return ret;
+
+	desc.args[0] = cfg->arm_msm_secure_cfg.sec_id;
+	desc.args[1] = cfg->arm_msm_secure_cfg.cbndx;
+	desc.args[2] = iova;
+	desc.args[3] = len;
+	desc.args[4] = IOMMU_TLBINVAL_FLAG;
+	desc.arginfo = SCM_ARGS(5);
+
+	if (is_scm_armv8()) {
+		ret = scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_MP,
+			IOMMU_SECURE_UNMAP2_FLAT), &desc);
+
+		if (!ret)
+			ret = len;
+	}
+	return ret;
+}
+
+static phys_addr_t msm_secure_iova_to_phys(struct io_pgtable_ops *ops,
+					 unsigned long iova)
+{
+	return -EINVAL;
+}
+
+static struct msm_secure_io_pgtable *
+msm_secure_alloc_pgtable_data(struct io_pgtable_cfg *cfg)
+{
+	struct msm_secure_io_pgtable *data;
+
+	data = kmalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return NULL;
+
+	data->iop.ops = (struct io_pgtable_ops) {
+		.map		= msm_secure_map,
+		.map_sg		= msm_secure_map_sg,
+		.unmap		= msm_secure_unmap,
+		.iova_to_phys	= msm_secure_iova_to_phys,
+	};
+
+	return data;
+}
+
+static struct io_pgtable *
+msm_secure_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie)
+{
+	struct msm_secure_io_pgtable *data =
+		msm_secure_alloc_pgtable_data(cfg);
+
+	return &data->iop;
+}
+
+static void msm_secure_free_pgtable(struct io_pgtable *iop)
+{
+	struct msm_secure_io_pgtable *data = io_pgtable_to_data(iop);
+
+	kfree(data);
+}
+
+struct io_pgtable_init_fns io_pgtable_arm_msm_secure_init_fns = {
+	.alloc	= msm_secure_alloc_pgtable,
+	.free	= msm_secure_free_pgtable,
+};
diff --git a/drivers/iommu/io-pgtable.c b/drivers/iommu/io-pgtable.c
index 541abb2..0e30cfb 100644
--- a/drivers/iommu/io-pgtable.c
+++ b/drivers/iommu/io-pgtable.c
@@ -44,6 +44,9 @@
 #ifdef CONFIG_IOMMU_IO_PGTABLE_FAST
 	[ARM_V8L_FAST] = &io_pgtable_av8l_fast_init_fns,
 #endif
+#ifdef CONFIG_MSM_TZ_SMMU
+	[ARM_MSM_SECURE] = &io_pgtable_arm_msm_secure_init_fns,
+#endif
 };
 
 static struct dentry *io_pgtable_top;
diff --git a/drivers/iommu/io-pgtable.h b/drivers/iommu/io-pgtable.h
index b35016e..2f5488c 100644
--- a/drivers/iommu/io-pgtable.h
+++ b/drivers/iommu/io-pgtable.h
@@ -3,6 +3,7 @@
 #include <linux/bitops.h>
 
 #include <linux/scatterlist.h>
+#include <soc/qcom/msm_tz_smmu.h>
 
 /*
  * Public API for use by IOMMU drivers
@@ -14,6 +15,7 @@
 	ARM_64_LPAE_S2,
 	ARM_V7S,
 	ARM_V8L_FAST,
+	ARM_MSM_SECURE,
 	IO_PGTABLE_NUM_FMTS,
 };
 
@@ -128,6 +130,11 @@
 			u64	mair[2];
 			void	*pmds;
 		} av8l_fast_cfg;
+
+		struct {
+			enum tz_smmu_device_id sec_id;
+			int cbndx;
+		} arm_msm_secure_cfg;
 	};
 };
 
@@ -211,6 +218,8 @@
 
 static inline void io_pgtable_tlb_flush_all(struct io_pgtable *iop)
 {
+	if (!iop->cfg.tlb)
+		return;
 	iop->cfg.tlb->tlb_flush_all(iop->cookie);
 	iop->tlb_sync_pending = true;
 }
@@ -218,12 +227,16 @@
 static inline void io_pgtable_tlb_add_flush(struct io_pgtable *iop,
 		unsigned long iova, size_t size, size_t granule, bool leaf)
 {
+	if (!iop->cfg.tlb)
+		return;
 	iop->cfg.tlb->tlb_add_flush(iova, size, granule, leaf, iop->cookie);
 	iop->tlb_sync_pending = true;
 }
 
 static inline void io_pgtable_tlb_sync(struct io_pgtable *iop)
 {
+	if (!iop->cfg.tlb)
+		return;
 	if (iop->tlb_sync_pending) {
 		iop->cfg.tlb->tlb_sync(iop->cookie);
 		iop->tlb_sync_pending = false;
@@ -248,6 +261,7 @@
 extern struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s2_init_fns;
 extern struct io_pgtable_init_fns io_pgtable_arm_v7s_init_fns;
 extern struct io_pgtable_init_fns io_pgtable_av8l_fast_init_fns;
+extern struct io_pgtable_init_fns io_pgtable_arm_msm_secure_init_fns;
 
 /**
  * io_pgtable_alloc_pages_exact:
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 3b37a37..31cf232 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -365,6 +365,16 @@
 	  is handled by transport plug-ins that can be individually enabled and
 	  configured separately.
 
+config MSM_TZ_SMMU
+	depends on ARCH_MSM8953
+	bool "Helper functions for SMMU configuration through TZ"
+	help
+	  Say 'Y' here for targets that need to call into TZ to configure
+	  SMMUs for any reason (for example, for errata workarounds or
+	  configuration of SMMU virtualization).
+
+	  If unsure, say N.
+
 config MSM_GLINK_LOOPBACK_SERVER
 	bool "Generic Link (G-Link) Loopback Server"
 	help
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index efa702f..b5c9f51 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -35,6 +35,7 @@
 obj-$(CONFIG_MSM_SMEM) += msm_smem.o smem_debug.o
 obj-$(CONFIG_MSM_SMD)	+= msm_smd.o smd_debug.o smd_private.o smd_init_dt.o smsm_debug.o
 obj-$(CONFIG_MSM_GLINK) += glink.o glink_debugfs.o glink_ssr.o
+obj-$(CONFIG_MSM_TZ_SMMU) += msm_tz_smmu.o
 obj-$(CONFIG_MSM_GLINK_LOOPBACK_SERVER) += glink_loopback_server.o
 obj-$(CONFIG_MSM_GLINK_SMEM_NATIVE_XPRT) += glink_smem_native_xprt.o
 obj-$(CONFIG_MSM_GLINK_SPI_XPRT) += glink_spi_xprt.o
diff --git a/drivers/soc/qcom/msm_tz_smmu.c b/drivers/soc/qcom/msm_tz_smmu.c
new file mode 100644
index 0000000..f608426
--- /dev/null
+++ b/drivers/soc/qcom/msm_tz_smmu.c
@@ -0,0 +1,130 @@
+/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <soc/qcom/scm.h>
+#include <soc/qcom/msm_tz_smmu.h>
+
+static const char * const device_id_mappings[] = {
+	[TZ_DEVICE_VIDEO] = "VIDEO",
+	[TZ_DEVICE_MDSS] = "MDSS",
+	[TZ_DEVICE_LPASS] = "LPASS",
+	[TZ_DEVICE_MDSS_BOOT] = "MDSS_BOOT",
+	[TZ_DEVICE_USB1_HS] = "USB1_HS",
+	[TZ_DEVICE_OCMEM] = "OCMEM",
+	[TZ_DEVICE_LPASS_CORE] = "LPASS_CORE",
+	[TZ_DEVICE_VPU] = "VPU",
+	[TZ_DEVICE_COPSS_SMMU] = "COPSS_SMMU",
+	[TZ_DEVICE_USB3_0] = "USB3_0",
+	[TZ_DEVICE_USB3_1] = "USB3_1",
+	[TZ_DEVICE_PCIE_0] = "PCIE_0",
+	[TZ_DEVICE_PCIE_1] = "PCIE_1",
+	[TZ_DEVICE_BCSS] = "BCSS",
+	[TZ_DEVICE_VCAP] = "VCAP",
+	[TZ_DEVICE_PCIE20] = "PCIE20",
+	[TZ_DEVICE_IPA] = "IPA",
+	[TZ_DEVICE_APPS] = "APPS",
+	[TZ_DEVICE_GPU] = "GPU",
+	[TZ_DEVICE_UFS] = "UFS",
+	[TZ_DEVICE_ICE] = "ICE",
+	[TZ_DEVICE_ROT] = "ROT",
+	[TZ_DEVICE_VFE] = "VFE",
+	[TZ_DEVICE_ANOC0] = "ANOC0",
+	[TZ_DEVICE_ANOC1] = "ANOC1",
+	[TZ_DEVICE_ANOC2] = "ANOC2",
+	[TZ_DEVICE_CPP] = "CPP",
+	[TZ_DEVICE_JPEG] = "JPEG",
+};
+
+#define MAX_DEVICE_ID_NAME_LEN 20
+
+#define TZ_SMMU_PREPARE_ATOS_ID 0x21
+#define TZ_SMMU_ATOS_START 1
+#define TZ_SMMU_ATOS_END 0
+
+#define SMMU_CHANGE_PAGETABLE_FORMAT    0X01
+
+enum tz_smmu_device_id msm_dev_to_device_id(struct device *dev)
+{
+	const char *device_id;
+	enum tz_smmu_device_id iter;
+
+	if (of_property_read_string(dev->of_node, "qcom,tz-device-id",
+					    &device_id)) {
+		dev_err(dev, "no qcom,device-id property\n");
+		return TZ_DEVICE_MAX;
+	}
+
+	for (iter = TZ_DEVICE_START; iter < TZ_DEVICE_MAX; iter++)
+		if (!strcmp(device_id_mappings[iter], device_id))
+			return iter;
+
+	return TZ_DEVICE_MAX;
+}
+
+static int __msm_tz_smmu_atos(struct device *dev, int cb_num, int operation)
+{
+	int ret;
+	struct scm_desc desc = {0};
+	enum tz_smmu_device_id devid = msm_dev_to_device_id(dev);
+
+	if (devid == TZ_DEVICE_MAX)
+		return -ENODEV;
+
+	desc.args[0] = devid;
+	desc.args[1] = cb_num;
+	desc.args[2] = operation;
+	desc.arginfo = SCM_ARGS(3, SCM_VAL, SCM_VAL, SCM_VAL);
+
+	ret = scm_call2(SCM_SIP_FNID(SCM_SVC_MP, TZ_SMMU_PREPARE_ATOS_ID),
+			&desc);
+	if (ret)
+		pr_info("%s: TZ SMMU ATOS %s failed, ret = %d\n",
+			__func__,
+			operation == TZ_SMMU_ATOS_START ? "start" : "end",
+			ret);
+	return ret;
+}
+
+int msm_tz_smmu_atos_start(struct device *dev, int cb_num)
+{
+	return __msm_tz_smmu_atos(dev, cb_num, TZ_SMMU_ATOS_START);
+}
+
+int msm_tz_smmu_atos_end(struct device *dev, int cb_num)
+{
+	return __msm_tz_smmu_atos(dev, cb_num, TZ_SMMU_ATOS_END);
+}
+
+int msm_tz_set_cb_format(enum tz_smmu_device_id sec_id, int cbndx)
+{
+	struct scm_desc desc = {0};
+	int ret = 0;
+
+	desc.args[0] = sec_id;
+	desc.args[1] = cbndx;
+	desc.args[2] = 1;	/* Enable */
+	desc.arginfo = SCM_ARGS(3, SCM_VAL, SCM_VAL, SCM_VAL);
+
+	ret = scm_call2(SCM_SIP_FNID(SCM_SVC_SMMU_PROGRAM,
+			SMMU_CHANGE_PAGETABLE_FORMAT), &desc);
+
+	if (ret) {
+		WARN(1, "Format change failed for CB %d with ret %d\n",
+		     cbndx, ret);
+		return ret;
+	}
+
+	return 0;
+}
diff --git a/include/soc/qcom/msm_tz_smmu.h b/include/soc/qcom/msm_tz_smmu.h
new file mode 100644
index 0000000..a83c9bd
--- /dev/null
+++ b/include/soc/qcom/msm_tz_smmu.h
@@ -0,0 +1,93 @@
+/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __MSM_TZ_SMMU_H__
+#define __MSM_TZ_SMMU_H__
+
+#include <linux/device.h>
+
+enum tz_smmu_device_id {
+	TZ_DEVICE_START = 0,
+	TZ_DEVICE_VIDEO = 0,
+	TZ_DEVICE_MDSS,
+	TZ_DEVICE_LPASS,
+	TZ_DEVICE_MDSS_BOOT,
+	TZ_DEVICE_USB1_HS,
+	TZ_DEVICE_OCMEM,
+	TZ_DEVICE_LPASS_CORE,
+	TZ_DEVICE_VPU,
+	TZ_DEVICE_COPSS_SMMU,
+	TZ_DEVICE_USB3_0,
+	TZ_DEVICE_USB3_1,
+	TZ_DEVICE_PCIE_0,
+	TZ_DEVICE_PCIE_1,
+	TZ_DEVICE_BCSS,
+	TZ_DEVICE_VCAP,
+	TZ_DEVICE_PCIE20,
+	TZ_DEVICE_IPA,
+	TZ_DEVICE_APPS,
+	TZ_DEVICE_GPU,
+	TZ_DEVICE_UFS,
+	TZ_DEVICE_ICE,
+	TZ_DEVICE_ROT,
+	TZ_DEVICE_VFE,
+	TZ_DEVICE_ANOC0,
+	TZ_DEVICE_ANOC1,
+	TZ_DEVICE_ANOC2,
+	TZ_DEVICE_CPP,
+	TZ_DEVICE_JPEG,
+	TZ_DEVICE_MAX,
+};
+
+#ifdef CONFIG_MSM_TZ_SMMU
+
+int msm_tz_smmu_atos_start(struct device *dev, int cb_num);
+int msm_tz_smmu_atos_end(struct device *dev, int cb_num);
+enum tz_smmu_device_id msm_dev_to_device_id(struct device *dev);
+int msm_tz_set_cb_format(enum tz_smmu_device_id sec_id, int cbndx);
+int msm_iommu_sec_pgtbl_init(void);
+int register_iommu_sec_ptbl(void);
+#else
+
+static inline int msm_tz_smmu_atos_start(struct device *dev, int cb_num)
+{
+	return 0;
+}
+
+static inline int msm_tz_smmu_atos_end(struct device *dev, int cb_num)
+{
+	return 0;
+}
+
+static inline enum tz_smmu_device_id msm_dev_to_device_id(struct device *dev)
+{
+	return -EINVAL;
+}
+
+static inline int msm_tz_set_cb_format(enum tz_smmu_device_id sec_id,
+					int cbndx)
+{
+	return -EINVAL;
+}
+
+static inline int msm_iommu_sec_pgtbl_init(void)
+{
+	return -EINVAL;
+}
+
+static inline int register_iommu_sec_ptbl(void)
+{
+	return -EINVAL;
+}
+#endif /* CONFIG_MSM_TZ_SMMU */
+
+#endif /* __MSM_TZ_SMMU_H__ */