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