162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2013 Red Hat
462306a36Sopenharmony_ci * Author: Rob Clark <robdclark@gmail.com>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/adreno-smmu-priv.h>
862306a36Sopenharmony_ci#include <linux/io-pgtable.h>
962306a36Sopenharmony_ci#include "msm_drv.h"
1062306a36Sopenharmony_ci#include "msm_mmu.h"
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_cistruct msm_iommu {
1362306a36Sopenharmony_ci	struct msm_mmu base;
1462306a36Sopenharmony_ci	struct iommu_domain *domain;
1562306a36Sopenharmony_ci	atomic_t pagetables;
1662306a36Sopenharmony_ci};
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define to_msm_iommu(x) container_of(x, struct msm_iommu, base)
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistruct msm_iommu_pagetable {
2162306a36Sopenharmony_ci	struct msm_mmu base;
2262306a36Sopenharmony_ci	struct msm_mmu *parent;
2362306a36Sopenharmony_ci	struct io_pgtable_ops *pgtbl_ops;
2462306a36Sopenharmony_ci	const struct iommu_flush_ops *tlb;
2562306a36Sopenharmony_ci	struct device *iommu_dev;
2662306a36Sopenharmony_ci	unsigned long pgsize_bitmap;	/* Bitmap of page sizes in use */
2762306a36Sopenharmony_ci	phys_addr_t ttbr;
2862306a36Sopenharmony_ci	u32 asid;
2962306a36Sopenharmony_ci};
3062306a36Sopenharmony_cistatic struct msm_iommu_pagetable *to_pagetable(struct msm_mmu *mmu)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	return container_of(mmu, struct msm_iommu_pagetable, base);
3362306a36Sopenharmony_ci}
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci/* based on iommu_pgsize() in iommu.c: */
3662306a36Sopenharmony_cistatic size_t calc_pgsize(struct msm_iommu_pagetable *pagetable,
3762306a36Sopenharmony_ci			   unsigned long iova, phys_addr_t paddr,
3862306a36Sopenharmony_ci			   size_t size, size_t *count)
3962306a36Sopenharmony_ci{
4062306a36Sopenharmony_ci	unsigned int pgsize_idx, pgsize_idx_next;
4162306a36Sopenharmony_ci	unsigned long pgsizes;
4262306a36Sopenharmony_ci	size_t offset, pgsize, pgsize_next;
4362306a36Sopenharmony_ci	unsigned long addr_merge = paddr | iova;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	/* Page sizes supported by the hardware and small enough for @size */
4662306a36Sopenharmony_ci	pgsizes = pagetable->pgsize_bitmap & GENMASK(__fls(size), 0);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	/* Constrain the page sizes further based on the maximum alignment */
4962306a36Sopenharmony_ci	if (likely(addr_merge))
5062306a36Sopenharmony_ci		pgsizes &= GENMASK(__ffs(addr_merge), 0);
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	/* Make sure we have at least one suitable page size */
5362306a36Sopenharmony_ci	BUG_ON(!pgsizes);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	/* Pick the biggest page size remaining */
5662306a36Sopenharmony_ci	pgsize_idx = __fls(pgsizes);
5762306a36Sopenharmony_ci	pgsize = BIT(pgsize_idx);
5862306a36Sopenharmony_ci	if (!count)
5962306a36Sopenharmony_ci		return pgsize;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	/* Find the next biggest support page size, if it exists */
6262306a36Sopenharmony_ci	pgsizes = pagetable->pgsize_bitmap & ~GENMASK(pgsize_idx, 0);
6362306a36Sopenharmony_ci	if (!pgsizes)
6462306a36Sopenharmony_ci		goto out_set_count;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	pgsize_idx_next = __ffs(pgsizes);
6762306a36Sopenharmony_ci	pgsize_next = BIT(pgsize_idx_next);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	/*
7062306a36Sopenharmony_ci	 * There's no point trying a bigger page size unless the virtual
7162306a36Sopenharmony_ci	 * and physical addresses are similarly offset within the larger page.
7262306a36Sopenharmony_ci	 */
7362306a36Sopenharmony_ci	if ((iova ^ paddr) & (pgsize_next - 1))
7462306a36Sopenharmony_ci		goto out_set_count;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	/* Calculate the offset to the next page size alignment boundary */
7762306a36Sopenharmony_ci	offset = pgsize_next - (addr_merge & (pgsize_next - 1));
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	/*
8062306a36Sopenharmony_ci	 * If size is big enough to accommodate the larger page, reduce
8162306a36Sopenharmony_ci	 * the number of smaller pages.
8262306a36Sopenharmony_ci	 */
8362306a36Sopenharmony_ci	if (offset + pgsize_next <= size)
8462306a36Sopenharmony_ci		size = offset;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ciout_set_count:
8762306a36Sopenharmony_ci	*count = size >> pgsize_idx;
8862306a36Sopenharmony_ci	return pgsize;
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_cistatic int msm_iommu_pagetable_unmap(struct msm_mmu *mmu, u64 iova,
9262306a36Sopenharmony_ci		size_t size)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	struct msm_iommu_pagetable *pagetable = to_pagetable(mmu);
9562306a36Sopenharmony_ci	struct io_pgtable_ops *ops = pagetable->pgtbl_ops;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	while (size) {
9862306a36Sopenharmony_ci		size_t unmapped, pgsize, count;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci		pgsize = calc_pgsize(pagetable, iova, iova, size, &count);
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci		unmapped = ops->unmap_pages(ops, iova, pgsize, count, NULL);
10362306a36Sopenharmony_ci		if (!unmapped)
10462306a36Sopenharmony_ci			break;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci		iova += unmapped;
10762306a36Sopenharmony_ci		size -= unmapped;
10862306a36Sopenharmony_ci	}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	iommu_flush_iotlb_all(to_msm_iommu(pagetable->parent)->domain);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	return (size == 0) ? 0 : -EINVAL;
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_cistatic int msm_iommu_pagetable_map(struct msm_mmu *mmu, u64 iova,
11662306a36Sopenharmony_ci		struct sg_table *sgt, size_t len, int prot)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	struct msm_iommu_pagetable *pagetable = to_pagetable(mmu);
11962306a36Sopenharmony_ci	struct io_pgtable_ops *ops = pagetable->pgtbl_ops;
12062306a36Sopenharmony_ci	struct scatterlist *sg;
12162306a36Sopenharmony_ci	u64 addr = iova;
12262306a36Sopenharmony_ci	unsigned int i;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	for_each_sgtable_sg(sgt, sg, i) {
12562306a36Sopenharmony_ci		size_t size = sg->length;
12662306a36Sopenharmony_ci		phys_addr_t phys = sg_phys(sg);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci		while (size) {
12962306a36Sopenharmony_ci			size_t pgsize, count, mapped = 0;
13062306a36Sopenharmony_ci			int ret;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci			pgsize = calc_pgsize(pagetable, addr, phys, size, &count);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci			ret = ops->map_pages(ops, addr, phys, pgsize, count,
13562306a36Sopenharmony_ci					     prot, GFP_KERNEL, &mapped);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci			/* map_pages could fail after mapping some of the pages,
13862306a36Sopenharmony_ci			 * so update the counters before error handling.
13962306a36Sopenharmony_ci			 */
14062306a36Sopenharmony_ci			phys += mapped;
14162306a36Sopenharmony_ci			addr += mapped;
14262306a36Sopenharmony_ci			size -= mapped;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci			if (ret) {
14562306a36Sopenharmony_ci				msm_iommu_pagetable_unmap(mmu, iova, addr - iova);
14662306a36Sopenharmony_ci				return -EINVAL;
14762306a36Sopenharmony_ci			}
14862306a36Sopenharmony_ci		}
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	return 0;
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_cistatic void msm_iommu_pagetable_destroy(struct msm_mmu *mmu)
15562306a36Sopenharmony_ci{
15662306a36Sopenharmony_ci	struct msm_iommu_pagetable *pagetable = to_pagetable(mmu);
15762306a36Sopenharmony_ci	struct msm_iommu *iommu = to_msm_iommu(pagetable->parent);
15862306a36Sopenharmony_ci	struct adreno_smmu_priv *adreno_smmu =
15962306a36Sopenharmony_ci		dev_get_drvdata(pagetable->parent->dev);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	/*
16262306a36Sopenharmony_ci	 * If this is the last attached pagetable for the parent,
16362306a36Sopenharmony_ci	 * disable TTBR0 in the arm-smmu driver
16462306a36Sopenharmony_ci	 */
16562306a36Sopenharmony_ci	if (atomic_dec_return(&iommu->pagetables) == 0)
16662306a36Sopenharmony_ci		adreno_smmu->set_ttbr0_cfg(adreno_smmu->cookie, NULL);
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	free_io_pgtable_ops(pagetable->pgtbl_ops);
16962306a36Sopenharmony_ci	kfree(pagetable);
17062306a36Sopenharmony_ci}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ciint msm_iommu_pagetable_params(struct msm_mmu *mmu,
17362306a36Sopenharmony_ci		phys_addr_t *ttbr, int *asid)
17462306a36Sopenharmony_ci{
17562306a36Sopenharmony_ci	struct msm_iommu_pagetable *pagetable;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	if (mmu->type != MSM_MMU_IOMMU_PAGETABLE)
17862306a36Sopenharmony_ci		return -EINVAL;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	pagetable = to_pagetable(mmu);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	if (ttbr)
18362306a36Sopenharmony_ci		*ttbr = pagetable->ttbr;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	if (asid)
18662306a36Sopenharmony_ci		*asid = pagetable->asid;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	return 0;
18962306a36Sopenharmony_ci}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_cistruct iommu_domain_geometry *msm_iommu_get_geometry(struct msm_mmu *mmu)
19262306a36Sopenharmony_ci{
19362306a36Sopenharmony_ci	struct msm_iommu *iommu = to_msm_iommu(mmu);
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	return &iommu->domain->geometry;
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_cistatic const struct msm_mmu_funcs pagetable_funcs = {
19962306a36Sopenharmony_ci		.map = msm_iommu_pagetable_map,
20062306a36Sopenharmony_ci		.unmap = msm_iommu_pagetable_unmap,
20162306a36Sopenharmony_ci		.destroy = msm_iommu_pagetable_destroy,
20262306a36Sopenharmony_ci};
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_cistatic void msm_iommu_tlb_flush_all(void *cookie)
20562306a36Sopenharmony_ci{
20662306a36Sopenharmony_ci	struct msm_iommu_pagetable *pagetable = cookie;
20762306a36Sopenharmony_ci	struct adreno_smmu_priv *adreno_smmu;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	if (!pm_runtime_get_if_in_use(pagetable->iommu_dev))
21062306a36Sopenharmony_ci		return;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	adreno_smmu = dev_get_drvdata(pagetable->parent->dev);
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	pagetable->tlb->tlb_flush_all((void *)adreno_smmu->cookie);
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	pm_runtime_put_autosuspend(pagetable->iommu_dev);
21762306a36Sopenharmony_ci}
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_cistatic void msm_iommu_tlb_flush_walk(unsigned long iova, size_t size,
22062306a36Sopenharmony_ci		size_t granule, void *cookie)
22162306a36Sopenharmony_ci{
22262306a36Sopenharmony_ci	struct msm_iommu_pagetable *pagetable = cookie;
22362306a36Sopenharmony_ci	struct adreno_smmu_priv *adreno_smmu;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	if (!pm_runtime_get_if_in_use(pagetable->iommu_dev))
22662306a36Sopenharmony_ci		return;
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	adreno_smmu = dev_get_drvdata(pagetable->parent->dev);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	pagetable->tlb->tlb_flush_walk(iova, size, granule, (void *)adreno_smmu->cookie);
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	pm_runtime_put_autosuspend(pagetable->iommu_dev);
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_cistatic void msm_iommu_tlb_add_page(struct iommu_iotlb_gather *gather,
23662306a36Sopenharmony_ci		unsigned long iova, size_t granule, void *cookie)
23762306a36Sopenharmony_ci{
23862306a36Sopenharmony_ci}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_cistatic const struct iommu_flush_ops tlb_ops = {
24162306a36Sopenharmony_ci	.tlb_flush_all = msm_iommu_tlb_flush_all,
24262306a36Sopenharmony_ci	.tlb_flush_walk = msm_iommu_tlb_flush_walk,
24362306a36Sopenharmony_ci	.tlb_add_page = msm_iommu_tlb_add_page,
24462306a36Sopenharmony_ci};
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_cistatic int msm_fault_handler(struct iommu_domain *domain, struct device *dev,
24762306a36Sopenharmony_ci		unsigned long iova, int flags, void *arg);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_cistruct msm_mmu *msm_iommu_pagetable_create(struct msm_mmu *parent)
25062306a36Sopenharmony_ci{
25162306a36Sopenharmony_ci	struct adreno_smmu_priv *adreno_smmu = dev_get_drvdata(parent->dev);
25262306a36Sopenharmony_ci	struct msm_iommu *iommu = to_msm_iommu(parent);
25362306a36Sopenharmony_ci	struct msm_iommu_pagetable *pagetable;
25462306a36Sopenharmony_ci	const struct io_pgtable_cfg *ttbr1_cfg = NULL;
25562306a36Sopenharmony_ci	struct io_pgtable_cfg ttbr0_cfg;
25662306a36Sopenharmony_ci	int ret;
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	/* Get the pagetable configuration from the domain */
25962306a36Sopenharmony_ci	if (adreno_smmu->cookie)
26062306a36Sopenharmony_ci		ttbr1_cfg = adreno_smmu->get_ttbr1_cfg(adreno_smmu->cookie);
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	/*
26362306a36Sopenharmony_ci	 * If you hit this WARN_ONCE() you are probably missing an entry in
26462306a36Sopenharmony_ci	 * qcom_smmu_impl_of_match[] in arm-smmu-qcom.c
26562306a36Sopenharmony_ci	 */
26662306a36Sopenharmony_ci	if (WARN_ONCE(!ttbr1_cfg, "No per-process page tables"))
26762306a36Sopenharmony_ci		return ERR_PTR(-ENODEV);
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	pagetable = kzalloc(sizeof(*pagetable), GFP_KERNEL);
27062306a36Sopenharmony_ci	if (!pagetable)
27162306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	msm_mmu_init(&pagetable->base, parent->dev, &pagetable_funcs,
27462306a36Sopenharmony_ci		MSM_MMU_IOMMU_PAGETABLE);
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	/* Clone the TTBR1 cfg as starting point for TTBR0 cfg: */
27762306a36Sopenharmony_ci	ttbr0_cfg = *ttbr1_cfg;
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	/* The incoming cfg will have the TTBR1 quirk enabled */
28062306a36Sopenharmony_ci	ttbr0_cfg.quirks &= ~IO_PGTABLE_QUIRK_ARM_TTBR1;
28162306a36Sopenharmony_ci	ttbr0_cfg.tlb = &tlb_ops;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	pagetable->pgtbl_ops = alloc_io_pgtable_ops(ARM_64_LPAE_S1,
28462306a36Sopenharmony_ci		&ttbr0_cfg, pagetable);
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	if (!pagetable->pgtbl_ops) {
28762306a36Sopenharmony_ci		kfree(pagetable);
28862306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
28962306a36Sopenharmony_ci	}
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	/*
29262306a36Sopenharmony_ci	 * If this is the first pagetable that we've allocated, send it back to
29362306a36Sopenharmony_ci	 * the arm-smmu driver as a trigger to set up TTBR0
29462306a36Sopenharmony_ci	 */
29562306a36Sopenharmony_ci	if (atomic_inc_return(&iommu->pagetables) == 1) {
29662306a36Sopenharmony_ci		ret = adreno_smmu->set_ttbr0_cfg(adreno_smmu->cookie, &ttbr0_cfg);
29762306a36Sopenharmony_ci		if (ret) {
29862306a36Sopenharmony_ci			free_io_pgtable_ops(pagetable->pgtbl_ops);
29962306a36Sopenharmony_ci			kfree(pagetable);
30062306a36Sopenharmony_ci			return ERR_PTR(ret);
30162306a36Sopenharmony_ci		}
30262306a36Sopenharmony_ci	}
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	/* Needed later for TLB flush */
30562306a36Sopenharmony_ci	pagetable->parent = parent;
30662306a36Sopenharmony_ci	pagetable->tlb = ttbr1_cfg->tlb;
30762306a36Sopenharmony_ci	pagetable->iommu_dev = ttbr1_cfg->iommu_dev;
30862306a36Sopenharmony_ci	pagetable->pgsize_bitmap = ttbr0_cfg.pgsize_bitmap;
30962306a36Sopenharmony_ci	pagetable->ttbr = ttbr0_cfg.arm_lpae_s1_cfg.ttbr;
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	/*
31262306a36Sopenharmony_ci	 * TODO we would like each set of page tables to have a unique ASID
31362306a36Sopenharmony_ci	 * to optimize TLB invalidation.  But iommu_flush_iotlb_all() will
31462306a36Sopenharmony_ci	 * end up flushing the ASID used for TTBR1 pagetables, which is not
31562306a36Sopenharmony_ci	 * what we want.  So for now just use the same ASID as TTBR1.
31662306a36Sopenharmony_ci	 */
31762306a36Sopenharmony_ci	pagetable->asid = 0;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	return &pagetable->base;
32062306a36Sopenharmony_ci}
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_cistatic int msm_fault_handler(struct iommu_domain *domain, struct device *dev,
32362306a36Sopenharmony_ci		unsigned long iova, int flags, void *arg)
32462306a36Sopenharmony_ci{
32562306a36Sopenharmony_ci	struct msm_iommu *iommu = arg;
32662306a36Sopenharmony_ci	struct msm_mmu *mmu = &iommu->base;
32762306a36Sopenharmony_ci	struct adreno_smmu_priv *adreno_smmu = dev_get_drvdata(iommu->base.dev);
32862306a36Sopenharmony_ci	struct adreno_smmu_fault_info info, *ptr = NULL;
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	if (adreno_smmu->get_fault_info) {
33162306a36Sopenharmony_ci		adreno_smmu->get_fault_info(adreno_smmu->cookie, &info);
33262306a36Sopenharmony_ci		ptr = &info;
33362306a36Sopenharmony_ci	}
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	if (iommu->base.handler)
33662306a36Sopenharmony_ci		return iommu->base.handler(iommu->base.arg, iova, flags, ptr);
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	pr_warn_ratelimited("*** fault: iova=%16lx, flags=%d\n", iova, flags);
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	if (mmu->funcs->resume_translation)
34162306a36Sopenharmony_ci		mmu->funcs->resume_translation(mmu);
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	return 0;
34462306a36Sopenharmony_ci}
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_cistatic void msm_iommu_resume_translation(struct msm_mmu *mmu)
34762306a36Sopenharmony_ci{
34862306a36Sopenharmony_ci	struct adreno_smmu_priv *adreno_smmu = dev_get_drvdata(mmu->dev);
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	if (adreno_smmu->resume_translation)
35162306a36Sopenharmony_ci		adreno_smmu->resume_translation(adreno_smmu->cookie, true);
35262306a36Sopenharmony_ci}
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_cistatic void msm_iommu_detach(struct msm_mmu *mmu)
35562306a36Sopenharmony_ci{
35662306a36Sopenharmony_ci	struct msm_iommu *iommu = to_msm_iommu(mmu);
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	iommu_detach_device(iommu->domain, mmu->dev);
35962306a36Sopenharmony_ci}
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_cistatic int msm_iommu_map(struct msm_mmu *mmu, uint64_t iova,
36262306a36Sopenharmony_ci		struct sg_table *sgt, size_t len, int prot)
36362306a36Sopenharmony_ci{
36462306a36Sopenharmony_ci	struct msm_iommu *iommu = to_msm_iommu(mmu);
36562306a36Sopenharmony_ci	size_t ret;
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	/* The arm-smmu driver expects the addresses to be sign extended */
36862306a36Sopenharmony_ci	if (iova & BIT_ULL(48))
36962306a36Sopenharmony_ci		iova |= GENMASK_ULL(63, 49);
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	ret = iommu_map_sgtable(iommu->domain, iova, sgt, prot);
37262306a36Sopenharmony_ci	WARN_ON(!ret);
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	return (ret == len) ? 0 : -EINVAL;
37562306a36Sopenharmony_ci}
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_cistatic int msm_iommu_unmap(struct msm_mmu *mmu, uint64_t iova, size_t len)
37862306a36Sopenharmony_ci{
37962306a36Sopenharmony_ci	struct msm_iommu *iommu = to_msm_iommu(mmu);
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	if (iova & BIT_ULL(48))
38262306a36Sopenharmony_ci		iova |= GENMASK_ULL(63, 49);
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	iommu_unmap(iommu->domain, iova, len);
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	return 0;
38762306a36Sopenharmony_ci}
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_cistatic void msm_iommu_destroy(struct msm_mmu *mmu)
39062306a36Sopenharmony_ci{
39162306a36Sopenharmony_ci	struct msm_iommu *iommu = to_msm_iommu(mmu);
39262306a36Sopenharmony_ci	iommu_domain_free(iommu->domain);
39362306a36Sopenharmony_ci	kfree(iommu);
39462306a36Sopenharmony_ci}
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_cistatic const struct msm_mmu_funcs funcs = {
39762306a36Sopenharmony_ci		.detach = msm_iommu_detach,
39862306a36Sopenharmony_ci		.map = msm_iommu_map,
39962306a36Sopenharmony_ci		.unmap = msm_iommu_unmap,
40062306a36Sopenharmony_ci		.destroy = msm_iommu_destroy,
40162306a36Sopenharmony_ci		.resume_translation = msm_iommu_resume_translation,
40262306a36Sopenharmony_ci};
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_cistruct msm_mmu *msm_iommu_new(struct device *dev, unsigned long quirks)
40562306a36Sopenharmony_ci{
40662306a36Sopenharmony_ci	struct iommu_domain *domain;
40762306a36Sopenharmony_ci	struct msm_iommu *iommu;
40862306a36Sopenharmony_ci	int ret;
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_ci	domain = iommu_domain_alloc(dev->bus);
41162306a36Sopenharmony_ci	if (!domain)
41262306a36Sopenharmony_ci		return NULL;
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	iommu_set_pgtable_quirks(domain, quirks);
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	iommu = kzalloc(sizeof(*iommu), GFP_KERNEL);
41762306a36Sopenharmony_ci	if (!iommu) {
41862306a36Sopenharmony_ci		iommu_domain_free(domain);
41962306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
42062306a36Sopenharmony_ci	}
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	iommu->domain = domain;
42362306a36Sopenharmony_ci	msm_mmu_init(&iommu->base, dev, &funcs, MSM_MMU_IOMMU);
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	atomic_set(&iommu->pagetables, 0);
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	ret = iommu_attach_device(iommu->domain, dev);
42862306a36Sopenharmony_ci	if (ret) {
42962306a36Sopenharmony_ci		iommu_domain_free(domain);
43062306a36Sopenharmony_ci		kfree(iommu);
43162306a36Sopenharmony_ci		return ERR_PTR(ret);
43262306a36Sopenharmony_ci	}
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	return &iommu->base;
43562306a36Sopenharmony_ci}
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_cistruct msm_mmu *msm_iommu_gpu_new(struct device *dev, struct msm_gpu *gpu, unsigned long quirks)
43862306a36Sopenharmony_ci{
43962306a36Sopenharmony_ci	struct adreno_smmu_priv *adreno_smmu = dev_get_drvdata(dev);
44062306a36Sopenharmony_ci	struct msm_iommu *iommu;
44162306a36Sopenharmony_ci	struct msm_mmu *mmu;
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	mmu = msm_iommu_new(dev, quirks);
44462306a36Sopenharmony_ci	if (IS_ERR_OR_NULL(mmu))
44562306a36Sopenharmony_ci		return mmu;
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	iommu = to_msm_iommu(mmu);
44862306a36Sopenharmony_ci	iommu_set_fault_handler(iommu->domain, msm_fault_handler, iommu);
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	/* Enable stall on iommu fault: */
45162306a36Sopenharmony_ci	if (adreno_smmu->set_stall)
45262306a36Sopenharmony_ci		adreno_smmu->set_stall(adreno_smmu->cookie, true);
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci	return mmu;
45562306a36Sopenharmony_ci}
456