162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * IOMMU API for QCOM secure IOMMUs. Somewhat based on arm-smmu.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2013 ARM Limited 662306a36Sopenharmony_ci * Copyright (C) 2017 Red Hat 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/atomic.h> 1062306a36Sopenharmony_ci#include <linux/bitfield.h> 1162306a36Sopenharmony_ci#include <linux/clk.h> 1262306a36Sopenharmony_ci#include <linux/delay.h> 1362306a36Sopenharmony_ci#include <linux/dma-mapping.h> 1462306a36Sopenharmony_ci#include <linux/err.h> 1562306a36Sopenharmony_ci#include <linux/interrupt.h> 1662306a36Sopenharmony_ci#include <linux/io.h> 1762306a36Sopenharmony_ci#include <linux/io-64-nonatomic-hi-lo.h> 1862306a36Sopenharmony_ci#include <linux/io-pgtable.h> 1962306a36Sopenharmony_ci#include <linux/iommu.h> 2062306a36Sopenharmony_ci#include <linux/iopoll.h> 2162306a36Sopenharmony_ci#include <linux/kconfig.h> 2262306a36Sopenharmony_ci#include <linux/init.h> 2362306a36Sopenharmony_ci#include <linux/mutex.h> 2462306a36Sopenharmony_ci#include <linux/of.h> 2562306a36Sopenharmony_ci#include <linux/of_platform.h> 2662306a36Sopenharmony_ci#include <linux/platform_device.h> 2762306a36Sopenharmony_ci#include <linux/pm.h> 2862306a36Sopenharmony_ci#include <linux/pm_runtime.h> 2962306a36Sopenharmony_ci#include <linux/firmware/qcom/qcom_scm.h> 3062306a36Sopenharmony_ci#include <linux/slab.h> 3162306a36Sopenharmony_ci#include <linux/spinlock.h> 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#include "arm-smmu.h" 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#define SMMU_INTR_SEL_NS 0x2000 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cienum qcom_iommu_clk { 3862306a36Sopenharmony_ci CLK_IFACE, 3962306a36Sopenharmony_ci CLK_BUS, 4062306a36Sopenharmony_ci CLK_TBU, 4162306a36Sopenharmony_ci CLK_NUM, 4262306a36Sopenharmony_ci}; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistruct qcom_iommu_ctx; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistruct qcom_iommu_dev { 4762306a36Sopenharmony_ci /* IOMMU core code handle */ 4862306a36Sopenharmony_ci struct iommu_device iommu; 4962306a36Sopenharmony_ci struct device *dev; 5062306a36Sopenharmony_ci struct clk_bulk_data clks[CLK_NUM]; 5162306a36Sopenharmony_ci void __iomem *local_base; 5262306a36Sopenharmony_ci u32 sec_id; 5362306a36Sopenharmony_ci u8 max_asid; 5462306a36Sopenharmony_ci struct qcom_iommu_ctx *ctxs[]; /* indexed by asid */ 5562306a36Sopenharmony_ci}; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistruct qcom_iommu_ctx { 5862306a36Sopenharmony_ci struct device *dev; 5962306a36Sopenharmony_ci void __iomem *base; 6062306a36Sopenharmony_ci bool secure_init; 6162306a36Sopenharmony_ci bool secured_ctx; 6262306a36Sopenharmony_ci u8 asid; /* asid and ctx bank # are 1:1 */ 6362306a36Sopenharmony_ci struct iommu_domain *domain; 6462306a36Sopenharmony_ci}; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistruct qcom_iommu_domain { 6762306a36Sopenharmony_ci struct io_pgtable_ops *pgtbl_ops; 6862306a36Sopenharmony_ci spinlock_t pgtbl_lock; 6962306a36Sopenharmony_ci struct mutex init_mutex; /* Protects iommu pointer */ 7062306a36Sopenharmony_ci struct iommu_domain domain; 7162306a36Sopenharmony_ci struct qcom_iommu_dev *iommu; 7262306a36Sopenharmony_ci struct iommu_fwspec *fwspec; 7362306a36Sopenharmony_ci}; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistatic struct qcom_iommu_domain *to_qcom_iommu_domain(struct iommu_domain *dom) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci return container_of(dom, struct qcom_iommu_domain, domain); 7862306a36Sopenharmony_ci} 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic const struct iommu_ops qcom_iommu_ops; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic struct qcom_iommu_dev * to_iommu(struct device *dev) 8362306a36Sopenharmony_ci{ 8462306a36Sopenharmony_ci struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci if (!fwspec || fwspec->ops != &qcom_iommu_ops) 8762306a36Sopenharmony_ci return NULL; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci return dev_iommu_priv_get(dev); 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistatic struct qcom_iommu_ctx * to_ctx(struct qcom_iommu_domain *d, unsigned asid) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci struct qcom_iommu_dev *qcom_iommu = d->iommu; 9562306a36Sopenharmony_ci if (!qcom_iommu) 9662306a36Sopenharmony_ci return NULL; 9762306a36Sopenharmony_ci return qcom_iommu->ctxs[asid]; 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistatic inline void 10162306a36Sopenharmony_ciiommu_writel(struct qcom_iommu_ctx *ctx, unsigned reg, u32 val) 10262306a36Sopenharmony_ci{ 10362306a36Sopenharmony_ci writel_relaxed(val, ctx->base + reg); 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic inline void 10762306a36Sopenharmony_ciiommu_writeq(struct qcom_iommu_ctx *ctx, unsigned reg, u64 val) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci writeq_relaxed(val, ctx->base + reg); 11062306a36Sopenharmony_ci} 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_cistatic inline u32 11362306a36Sopenharmony_ciiommu_readl(struct qcom_iommu_ctx *ctx, unsigned reg) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci return readl_relaxed(ctx->base + reg); 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cistatic inline u64 11962306a36Sopenharmony_ciiommu_readq(struct qcom_iommu_ctx *ctx, unsigned reg) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci return readq_relaxed(ctx->base + reg); 12262306a36Sopenharmony_ci} 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_cistatic void qcom_iommu_tlb_sync(void *cookie) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct qcom_iommu_domain *qcom_domain = cookie; 12762306a36Sopenharmony_ci struct iommu_fwspec *fwspec = qcom_domain->fwspec; 12862306a36Sopenharmony_ci unsigned i; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci for (i = 0; i < fwspec->num_ids; i++) { 13162306a36Sopenharmony_ci struct qcom_iommu_ctx *ctx = to_ctx(qcom_domain, fwspec->ids[i]); 13262306a36Sopenharmony_ci unsigned int val, ret; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci iommu_writel(ctx, ARM_SMMU_CB_TLBSYNC, 0); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci ret = readl_poll_timeout(ctx->base + ARM_SMMU_CB_TLBSTATUS, val, 13762306a36Sopenharmony_ci (val & 0x1) == 0, 0, 5000000); 13862306a36Sopenharmony_ci if (ret) 13962306a36Sopenharmony_ci dev_err(ctx->dev, "timeout waiting for TLB SYNC\n"); 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic void qcom_iommu_tlb_inv_context(void *cookie) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci struct qcom_iommu_domain *qcom_domain = cookie; 14662306a36Sopenharmony_ci struct iommu_fwspec *fwspec = qcom_domain->fwspec; 14762306a36Sopenharmony_ci unsigned i; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci for (i = 0; i < fwspec->num_ids; i++) { 15062306a36Sopenharmony_ci struct qcom_iommu_ctx *ctx = to_ctx(qcom_domain, fwspec->ids[i]); 15162306a36Sopenharmony_ci iommu_writel(ctx, ARM_SMMU_CB_S1_TLBIASID, ctx->asid); 15262306a36Sopenharmony_ci } 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci qcom_iommu_tlb_sync(cookie); 15562306a36Sopenharmony_ci} 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_cistatic void qcom_iommu_tlb_inv_range_nosync(unsigned long iova, size_t size, 15862306a36Sopenharmony_ci size_t granule, bool leaf, void *cookie) 15962306a36Sopenharmony_ci{ 16062306a36Sopenharmony_ci struct qcom_iommu_domain *qcom_domain = cookie; 16162306a36Sopenharmony_ci struct iommu_fwspec *fwspec = qcom_domain->fwspec; 16262306a36Sopenharmony_ci unsigned i, reg; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci reg = leaf ? ARM_SMMU_CB_S1_TLBIVAL : ARM_SMMU_CB_S1_TLBIVA; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci for (i = 0; i < fwspec->num_ids; i++) { 16762306a36Sopenharmony_ci struct qcom_iommu_ctx *ctx = to_ctx(qcom_domain, fwspec->ids[i]); 16862306a36Sopenharmony_ci size_t s = size; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci iova = (iova >> 12) << 12; 17162306a36Sopenharmony_ci iova |= ctx->asid; 17262306a36Sopenharmony_ci do { 17362306a36Sopenharmony_ci iommu_writel(ctx, reg, iova); 17462306a36Sopenharmony_ci iova += granule; 17562306a36Sopenharmony_ci } while (s -= granule); 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_cistatic void qcom_iommu_tlb_flush_walk(unsigned long iova, size_t size, 18062306a36Sopenharmony_ci size_t granule, void *cookie) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci qcom_iommu_tlb_inv_range_nosync(iova, size, granule, false, cookie); 18362306a36Sopenharmony_ci qcom_iommu_tlb_sync(cookie); 18462306a36Sopenharmony_ci} 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_cistatic void qcom_iommu_tlb_add_page(struct iommu_iotlb_gather *gather, 18762306a36Sopenharmony_ci unsigned long iova, size_t granule, 18862306a36Sopenharmony_ci void *cookie) 18962306a36Sopenharmony_ci{ 19062306a36Sopenharmony_ci qcom_iommu_tlb_inv_range_nosync(iova, granule, granule, true, cookie); 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_cistatic const struct iommu_flush_ops qcom_flush_ops = { 19462306a36Sopenharmony_ci .tlb_flush_all = qcom_iommu_tlb_inv_context, 19562306a36Sopenharmony_ci .tlb_flush_walk = qcom_iommu_tlb_flush_walk, 19662306a36Sopenharmony_ci .tlb_add_page = qcom_iommu_tlb_add_page, 19762306a36Sopenharmony_ci}; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_cistatic irqreturn_t qcom_iommu_fault(int irq, void *dev) 20062306a36Sopenharmony_ci{ 20162306a36Sopenharmony_ci struct qcom_iommu_ctx *ctx = dev; 20262306a36Sopenharmony_ci u32 fsr, fsynr; 20362306a36Sopenharmony_ci u64 iova; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci fsr = iommu_readl(ctx, ARM_SMMU_CB_FSR); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci if (!(fsr & ARM_SMMU_FSR_FAULT)) 20862306a36Sopenharmony_ci return IRQ_NONE; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci fsynr = iommu_readl(ctx, ARM_SMMU_CB_FSYNR0); 21162306a36Sopenharmony_ci iova = iommu_readq(ctx, ARM_SMMU_CB_FAR); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci if (!report_iommu_fault(ctx->domain, ctx->dev, iova, 0)) { 21462306a36Sopenharmony_ci dev_err_ratelimited(ctx->dev, 21562306a36Sopenharmony_ci "Unhandled context fault: fsr=0x%x, " 21662306a36Sopenharmony_ci "iova=0x%016llx, fsynr=0x%x, cb=%d\n", 21762306a36Sopenharmony_ci fsr, iova, fsynr, ctx->asid); 21862306a36Sopenharmony_ci } 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci iommu_writel(ctx, ARM_SMMU_CB_FSR, fsr); 22162306a36Sopenharmony_ci iommu_writel(ctx, ARM_SMMU_CB_RESUME, ARM_SMMU_RESUME_TERMINATE); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci return IRQ_HANDLED; 22462306a36Sopenharmony_ci} 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_cistatic int qcom_iommu_init_domain(struct iommu_domain *domain, 22762306a36Sopenharmony_ci struct qcom_iommu_dev *qcom_iommu, 22862306a36Sopenharmony_ci struct device *dev) 22962306a36Sopenharmony_ci{ 23062306a36Sopenharmony_ci struct qcom_iommu_domain *qcom_domain = to_qcom_iommu_domain(domain); 23162306a36Sopenharmony_ci struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev); 23262306a36Sopenharmony_ci struct io_pgtable_ops *pgtbl_ops; 23362306a36Sopenharmony_ci struct io_pgtable_cfg pgtbl_cfg; 23462306a36Sopenharmony_ci int i, ret = 0; 23562306a36Sopenharmony_ci u32 reg; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci mutex_lock(&qcom_domain->init_mutex); 23862306a36Sopenharmony_ci if (qcom_domain->iommu) 23962306a36Sopenharmony_ci goto out_unlock; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci pgtbl_cfg = (struct io_pgtable_cfg) { 24262306a36Sopenharmony_ci .pgsize_bitmap = qcom_iommu_ops.pgsize_bitmap, 24362306a36Sopenharmony_ci .ias = 32, 24462306a36Sopenharmony_ci .oas = 40, 24562306a36Sopenharmony_ci .tlb = &qcom_flush_ops, 24662306a36Sopenharmony_ci .iommu_dev = qcom_iommu->dev, 24762306a36Sopenharmony_ci }; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci qcom_domain->iommu = qcom_iommu; 25062306a36Sopenharmony_ci qcom_domain->fwspec = fwspec; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci pgtbl_ops = alloc_io_pgtable_ops(ARM_32_LPAE_S1, &pgtbl_cfg, qcom_domain); 25362306a36Sopenharmony_ci if (!pgtbl_ops) { 25462306a36Sopenharmony_ci dev_err(qcom_iommu->dev, "failed to allocate pagetable ops\n"); 25562306a36Sopenharmony_ci ret = -ENOMEM; 25662306a36Sopenharmony_ci goto out_clear_iommu; 25762306a36Sopenharmony_ci } 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci /* Update the domain's page sizes to reflect the page table format */ 26062306a36Sopenharmony_ci domain->pgsize_bitmap = pgtbl_cfg.pgsize_bitmap; 26162306a36Sopenharmony_ci domain->geometry.aperture_end = (1ULL << pgtbl_cfg.ias) - 1; 26262306a36Sopenharmony_ci domain->geometry.force_aperture = true; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci for (i = 0; i < fwspec->num_ids; i++) { 26562306a36Sopenharmony_ci struct qcom_iommu_ctx *ctx = to_ctx(qcom_domain, fwspec->ids[i]); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci if (!ctx->secure_init) { 26862306a36Sopenharmony_ci ret = qcom_scm_restore_sec_cfg(qcom_iommu->sec_id, ctx->asid); 26962306a36Sopenharmony_ci if (ret) { 27062306a36Sopenharmony_ci dev_err(qcom_iommu->dev, "secure init failed: %d\n", ret); 27162306a36Sopenharmony_ci goto out_clear_iommu; 27262306a36Sopenharmony_ci } 27362306a36Sopenharmony_ci ctx->secure_init = true; 27462306a36Sopenharmony_ci } 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci /* Secured QSMMU-500/QSMMU-v2 contexts cannot be programmed */ 27762306a36Sopenharmony_ci if (ctx->secured_ctx) { 27862306a36Sopenharmony_ci ctx->domain = domain; 27962306a36Sopenharmony_ci continue; 28062306a36Sopenharmony_ci } 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci /* Disable context bank before programming */ 28362306a36Sopenharmony_ci iommu_writel(ctx, ARM_SMMU_CB_SCTLR, 0); 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci /* Clear context bank fault address fault status registers */ 28662306a36Sopenharmony_ci iommu_writel(ctx, ARM_SMMU_CB_FAR, 0); 28762306a36Sopenharmony_ci iommu_writel(ctx, ARM_SMMU_CB_FSR, ARM_SMMU_FSR_FAULT); 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci /* TTBRs */ 29062306a36Sopenharmony_ci iommu_writeq(ctx, ARM_SMMU_CB_TTBR0, 29162306a36Sopenharmony_ci pgtbl_cfg.arm_lpae_s1_cfg.ttbr | 29262306a36Sopenharmony_ci FIELD_PREP(ARM_SMMU_TTBRn_ASID, ctx->asid)); 29362306a36Sopenharmony_ci iommu_writeq(ctx, ARM_SMMU_CB_TTBR1, 0); 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci /* TCR */ 29662306a36Sopenharmony_ci iommu_writel(ctx, ARM_SMMU_CB_TCR2, 29762306a36Sopenharmony_ci arm_smmu_lpae_tcr2(&pgtbl_cfg)); 29862306a36Sopenharmony_ci iommu_writel(ctx, ARM_SMMU_CB_TCR, 29962306a36Sopenharmony_ci arm_smmu_lpae_tcr(&pgtbl_cfg) | ARM_SMMU_TCR_EAE); 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci /* MAIRs (stage-1 only) */ 30262306a36Sopenharmony_ci iommu_writel(ctx, ARM_SMMU_CB_S1_MAIR0, 30362306a36Sopenharmony_ci pgtbl_cfg.arm_lpae_s1_cfg.mair); 30462306a36Sopenharmony_ci iommu_writel(ctx, ARM_SMMU_CB_S1_MAIR1, 30562306a36Sopenharmony_ci pgtbl_cfg.arm_lpae_s1_cfg.mair >> 32); 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci /* SCTLR */ 30862306a36Sopenharmony_ci reg = ARM_SMMU_SCTLR_CFIE | ARM_SMMU_SCTLR_CFRE | 30962306a36Sopenharmony_ci ARM_SMMU_SCTLR_AFE | ARM_SMMU_SCTLR_TRE | 31062306a36Sopenharmony_ci ARM_SMMU_SCTLR_M | ARM_SMMU_SCTLR_S1_ASIDPNE | 31162306a36Sopenharmony_ci ARM_SMMU_SCTLR_CFCFG; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) 31462306a36Sopenharmony_ci reg |= ARM_SMMU_SCTLR_E; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci iommu_writel(ctx, ARM_SMMU_CB_SCTLR, reg); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci ctx->domain = domain; 31962306a36Sopenharmony_ci } 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci mutex_unlock(&qcom_domain->init_mutex); 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci /* Publish page table ops for map/unmap */ 32462306a36Sopenharmony_ci qcom_domain->pgtbl_ops = pgtbl_ops; 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci return 0; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ciout_clear_iommu: 32962306a36Sopenharmony_ci qcom_domain->iommu = NULL; 33062306a36Sopenharmony_ciout_unlock: 33162306a36Sopenharmony_ci mutex_unlock(&qcom_domain->init_mutex); 33262306a36Sopenharmony_ci return ret; 33362306a36Sopenharmony_ci} 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_cistatic struct iommu_domain *qcom_iommu_domain_alloc(unsigned type) 33662306a36Sopenharmony_ci{ 33762306a36Sopenharmony_ci struct qcom_iommu_domain *qcom_domain; 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci if (type != IOMMU_DOMAIN_UNMANAGED && type != IOMMU_DOMAIN_DMA) 34062306a36Sopenharmony_ci return NULL; 34162306a36Sopenharmony_ci /* 34262306a36Sopenharmony_ci * Allocate the domain and initialise some of its data structures. 34362306a36Sopenharmony_ci * We can't really do anything meaningful until we've added a 34462306a36Sopenharmony_ci * master. 34562306a36Sopenharmony_ci */ 34662306a36Sopenharmony_ci qcom_domain = kzalloc(sizeof(*qcom_domain), GFP_KERNEL); 34762306a36Sopenharmony_ci if (!qcom_domain) 34862306a36Sopenharmony_ci return NULL; 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci mutex_init(&qcom_domain->init_mutex); 35162306a36Sopenharmony_ci spin_lock_init(&qcom_domain->pgtbl_lock); 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci return &qcom_domain->domain; 35462306a36Sopenharmony_ci} 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_cistatic void qcom_iommu_domain_free(struct iommu_domain *domain) 35762306a36Sopenharmony_ci{ 35862306a36Sopenharmony_ci struct qcom_iommu_domain *qcom_domain = to_qcom_iommu_domain(domain); 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci if (qcom_domain->iommu) { 36162306a36Sopenharmony_ci /* 36262306a36Sopenharmony_ci * NOTE: unmap can be called after client device is powered 36362306a36Sopenharmony_ci * off, for example, with GPUs or anything involving dma-buf. 36462306a36Sopenharmony_ci * So we cannot rely on the device_link. Make sure the IOMMU 36562306a36Sopenharmony_ci * is on to avoid unclocked accesses in the TLB inv path: 36662306a36Sopenharmony_ci */ 36762306a36Sopenharmony_ci pm_runtime_get_sync(qcom_domain->iommu->dev); 36862306a36Sopenharmony_ci free_io_pgtable_ops(qcom_domain->pgtbl_ops); 36962306a36Sopenharmony_ci pm_runtime_put_sync(qcom_domain->iommu->dev); 37062306a36Sopenharmony_ci } 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci kfree(qcom_domain); 37362306a36Sopenharmony_ci} 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_cistatic int qcom_iommu_attach_dev(struct iommu_domain *domain, struct device *dev) 37662306a36Sopenharmony_ci{ 37762306a36Sopenharmony_ci struct qcom_iommu_dev *qcom_iommu = to_iommu(dev); 37862306a36Sopenharmony_ci struct qcom_iommu_domain *qcom_domain = to_qcom_iommu_domain(domain); 37962306a36Sopenharmony_ci int ret; 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci if (!qcom_iommu) { 38262306a36Sopenharmony_ci dev_err(dev, "cannot attach to IOMMU, is it on the same bus?\n"); 38362306a36Sopenharmony_ci return -ENXIO; 38462306a36Sopenharmony_ci } 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci /* Ensure that the domain is finalized */ 38762306a36Sopenharmony_ci pm_runtime_get_sync(qcom_iommu->dev); 38862306a36Sopenharmony_ci ret = qcom_iommu_init_domain(domain, qcom_iommu, dev); 38962306a36Sopenharmony_ci pm_runtime_put_sync(qcom_iommu->dev); 39062306a36Sopenharmony_ci if (ret < 0) 39162306a36Sopenharmony_ci return ret; 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci /* 39462306a36Sopenharmony_ci * Sanity check the domain. We don't support domains across 39562306a36Sopenharmony_ci * different IOMMUs. 39662306a36Sopenharmony_ci */ 39762306a36Sopenharmony_ci if (qcom_domain->iommu != qcom_iommu) 39862306a36Sopenharmony_ci return -EINVAL; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci return 0; 40162306a36Sopenharmony_ci} 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_cistatic int qcom_iommu_map(struct iommu_domain *domain, unsigned long iova, 40462306a36Sopenharmony_ci phys_addr_t paddr, size_t pgsize, size_t pgcount, 40562306a36Sopenharmony_ci int prot, gfp_t gfp, size_t *mapped) 40662306a36Sopenharmony_ci{ 40762306a36Sopenharmony_ci int ret; 40862306a36Sopenharmony_ci unsigned long flags; 40962306a36Sopenharmony_ci struct qcom_iommu_domain *qcom_domain = to_qcom_iommu_domain(domain); 41062306a36Sopenharmony_ci struct io_pgtable_ops *ops = qcom_domain->pgtbl_ops; 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci if (!ops) 41362306a36Sopenharmony_ci return -ENODEV; 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci spin_lock_irqsave(&qcom_domain->pgtbl_lock, flags); 41662306a36Sopenharmony_ci ret = ops->map_pages(ops, iova, paddr, pgsize, pgcount, prot, GFP_ATOMIC, mapped); 41762306a36Sopenharmony_ci spin_unlock_irqrestore(&qcom_domain->pgtbl_lock, flags); 41862306a36Sopenharmony_ci return ret; 41962306a36Sopenharmony_ci} 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_cistatic size_t qcom_iommu_unmap(struct iommu_domain *domain, unsigned long iova, 42262306a36Sopenharmony_ci size_t pgsize, size_t pgcount, 42362306a36Sopenharmony_ci struct iommu_iotlb_gather *gather) 42462306a36Sopenharmony_ci{ 42562306a36Sopenharmony_ci size_t ret; 42662306a36Sopenharmony_ci unsigned long flags; 42762306a36Sopenharmony_ci struct qcom_iommu_domain *qcom_domain = to_qcom_iommu_domain(domain); 42862306a36Sopenharmony_ci struct io_pgtable_ops *ops = qcom_domain->pgtbl_ops; 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci if (!ops) 43162306a36Sopenharmony_ci return 0; 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci /* NOTE: unmap can be called after client device is powered off, 43462306a36Sopenharmony_ci * for example, with GPUs or anything involving dma-buf. So we 43562306a36Sopenharmony_ci * cannot rely on the device_link. Make sure the IOMMU is on to 43662306a36Sopenharmony_ci * avoid unclocked accesses in the TLB inv path: 43762306a36Sopenharmony_ci */ 43862306a36Sopenharmony_ci pm_runtime_get_sync(qcom_domain->iommu->dev); 43962306a36Sopenharmony_ci spin_lock_irqsave(&qcom_domain->pgtbl_lock, flags); 44062306a36Sopenharmony_ci ret = ops->unmap_pages(ops, iova, pgsize, pgcount, gather); 44162306a36Sopenharmony_ci spin_unlock_irqrestore(&qcom_domain->pgtbl_lock, flags); 44262306a36Sopenharmony_ci pm_runtime_put_sync(qcom_domain->iommu->dev); 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci return ret; 44562306a36Sopenharmony_ci} 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_cistatic void qcom_iommu_flush_iotlb_all(struct iommu_domain *domain) 44862306a36Sopenharmony_ci{ 44962306a36Sopenharmony_ci struct qcom_iommu_domain *qcom_domain = to_qcom_iommu_domain(domain); 45062306a36Sopenharmony_ci struct io_pgtable *pgtable = container_of(qcom_domain->pgtbl_ops, 45162306a36Sopenharmony_ci struct io_pgtable, ops); 45262306a36Sopenharmony_ci if (!qcom_domain->pgtbl_ops) 45362306a36Sopenharmony_ci return; 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci pm_runtime_get_sync(qcom_domain->iommu->dev); 45662306a36Sopenharmony_ci qcom_iommu_tlb_sync(pgtable->cookie); 45762306a36Sopenharmony_ci pm_runtime_put_sync(qcom_domain->iommu->dev); 45862306a36Sopenharmony_ci} 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_cistatic void qcom_iommu_iotlb_sync(struct iommu_domain *domain, 46162306a36Sopenharmony_ci struct iommu_iotlb_gather *gather) 46262306a36Sopenharmony_ci{ 46362306a36Sopenharmony_ci qcom_iommu_flush_iotlb_all(domain); 46462306a36Sopenharmony_ci} 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_cistatic phys_addr_t qcom_iommu_iova_to_phys(struct iommu_domain *domain, 46762306a36Sopenharmony_ci dma_addr_t iova) 46862306a36Sopenharmony_ci{ 46962306a36Sopenharmony_ci phys_addr_t ret; 47062306a36Sopenharmony_ci unsigned long flags; 47162306a36Sopenharmony_ci struct qcom_iommu_domain *qcom_domain = to_qcom_iommu_domain(domain); 47262306a36Sopenharmony_ci struct io_pgtable_ops *ops = qcom_domain->pgtbl_ops; 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci if (!ops) 47562306a36Sopenharmony_ci return 0; 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci spin_lock_irqsave(&qcom_domain->pgtbl_lock, flags); 47862306a36Sopenharmony_ci ret = ops->iova_to_phys(ops, iova); 47962306a36Sopenharmony_ci spin_unlock_irqrestore(&qcom_domain->pgtbl_lock, flags); 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ci return ret; 48262306a36Sopenharmony_ci} 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_cistatic bool qcom_iommu_capable(struct device *dev, enum iommu_cap cap) 48562306a36Sopenharmony_ci{ 48662306a36Sopenharmony_ci switch (cap) { 48762306a36Sopenharmony_ci case IOMMU_CAP_CACHE_COHERENCY: 48862306a36Sopenharmony_ci /* 48962306a36Sopenharmony_ci * Return true here as the SMMU can always send out coherent 49062306a36Sopenharmony_ci * requests. 49162306a36Sopenharmony_ci */ 49262306a36Sopenharmony_ci return true; 49362306a36Sopenharmony_ci case IOMMU_CAP_NOEXEC: 49462306a36Sopenharmony_ci return true; 49562306a36Sopenharmony_ci default: 49662306a36Sopenharmony_ci return false; 49762306a36Sopenharmony_ci } 49862306a36Sopenharmony_ci} 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_cistatic struct iommu_device *qcom_iommu_probe_device(struct device *dev) 50162306a36Sopenharmony_ci{ 50262306a36Sopenharmony_ci struct qcom_iommu_dev *qcom_iommu = to_iommu(dev); 50362306a36Sopenharmony_ci struct device_link *link; 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci if (!qcom_iommu) 50662306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci /* 50962306a36Sopenharmony_ci * Establish the link between iommu and master, so that the 51062306a36Sopenharmony_ci * iommu gets runtime enabled/disabled as per the master's 51162306a36Sopenharmony_ci * needs. 51262306a36Sopenharmony_ci */ 51362306a36Sopenharmony_ci link = device_link_add(dev, qcom_iommu->dev, DL_FLAG_PM_RUNTIME); 51462306a36Sopenharmony_ci if (!link) { 51562306a36Sopenharmony_ci dev_err(qcom_iommu->dev, "Unable to create device link between %s and %s\n", 51662306a36Sopenharmony_ci dev_name(qcom_iommu->dev), dev_name(dev)); 51762306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 51862306a36Sopenharmony_ci } 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci return &qcom_iommu->iommu; 52162306a36Sopenharmony_ci} 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_cistatic int qcom_iommu_of_xlate(struct device *dev, struct of_phandle_args *args) 52462306a36Sopenharmony_ci{ 52562306a36Sopenharmony_ci struct qcom_iommu_dev *qcom_iommu; 52662306a36Sopenharmony_ci struct platform_device *iommu_pdev; 52762306a36Sopenharmony_ci unsigned asid = args->args[0]; 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci if (args->args_count != 1) { 53062306a36Sopenharmony_ci dev_err(dev, "incorrect number of iommu params found for %s " 53162306a36Sopenharmony_ci "(found %d, expected 1)\n", 53262306a36Sopenharmony_ci args->np->full_name, args->args_count); 53362306a36Sopenharmony_ci return -EINVAL; 53462306a36Sopenharmony_ci } 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci iommu_pdev = of_find_device_by_node(args->np); 53762306a36Sopenharmony_ci if (WARN_ON(!iommu_pdev)) 53862306a36Sopenharmony_ci return -EINVAL; 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci qcom_iommu = platform_get_drvdata(iommu_pdev); 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci /* make sure the asid specified in dt is valid, so we don't have 54362306a36Sopenharmony_ci * to sanity check this elsewhere: 54462306a36Sopenharmony_ci */ 54562306a36Sopenharmony_ci if (WARN_ON(asid > qcom_iommu->max_asid) || 54662306a36Sopenharmony_ci WARN_ON(qcom_iommu->ctxs[asid] == NULL)) { 54762306a36Sopenharmony_ci put_device(&iommu_pdev->dev); 54862306a36Sopenharmony_ci return -EINVAL; 54962306a36Sopenharmony_ci } 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci if (!dev_iommu_priv_get(dev)) { 55262306a36Sopenharmony_ci dev_iommu_priv_set(dev, qcom_iommu); 55362306a36Sopenharmony_ci } else { 55462306a36Sopenharmony_ci /* make sure devices iommus dt node isn't referring to 55562306a36Sopenharmony_ci * multiple different iommu devices. Multiple context 55662306a36Sopenharmony_ci * banks are ok, but multiple devices are not: 55762306a36Sopenharmony_ci */ 55862306a36Sopenharmony_ci if (WARN_ON(qcom_iommu != dev_iommu_priv_get(dev))) { 55962306a36Sopenharmony_ci put_device(&iommu_pdev->dev); 56062306a36Sopenharmony_ci return -EINVAL; 56162306a36Sopenharmony_ci } 56262306a36Sopenharmony_ci } 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci return iommu_fwspec_add_ids(dev, &asid, 1); 56562306a36Sopenharmony_ci} 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_cistatic const struct iommu_ops qcom_iommu_ops = { 56862306a36Sopenharmony_ci .capable = qcom_iommu_capable, 56962306a36Sopenharmony_ci .domain_alloc = qcom_iommu_domain_alloc, 57062306a36Sopenharmony_ci .probe_device = qcom_iommu_probe_device, 57162306a36Sopenharmony_ci .device_group = generic_device_group, 57262306a36Sopenharmony_ci .of_xlate = qcom_iommu_of_xlate, 57362306a36Sopenharmony_ci .pgsize_bitmap = SZ_4K | SZ_64K | SZ_1M | SZ_16M, 57462306a36Sopenharmony_ci .default_domain_ops = &(const struct iommu_domain_ops) { 57562306a36Sopenharmony_ci .attach_dev = qcom_iommu_attach_dev, 57662306a36Sopenharmony_ci .map_pages = qcom_iommu_map, 57762306a36Sopenharmony_ci .unmap_pages = qcom_iommu_unmap, 57862306a36Sopenharmony_ci .flush_iotlb_all = qcom_iommu_flush_iotlb_all, 57962306a36Sopenharmony_ci .iotlb_sync = qcom_iommu_iotlb_sync, 58062306a36Sopenharmony_ci .iova_to_phys = qcom_iommu_iova_to_phys, 58162306a36Sopenharmony_ci .free = qcom_iommu_domain_free, 58262306a36Sopenharmony_ci } 58362306a36Sopenharmony_ci}; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_cistatic int qcom_iommu_sec_ptbl_init(struct device *dev) 58662306a36Sopenharmony_ci{ 58762306a36Sopenharmony_ci size_t psize = 0; 58862306a36Sopenharmony_ci unsigned int spare = 0; 58962306a36Sopenharmony_ci void *cpu_addr; 59062306a36Sopenharmony_ci dma_addr_t paddr; 59162306a36Sopenharmony_ci unsigned long attrs; 59262306a36Sopenharmony_ci static bool allocated = false; 59362306a36Sopenharmony_ci int ret; 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_ci if (allocated) 59662306a36Sopenharmony_ci return 0; 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci ret = qcom_scm_iommu_secure_ptbl_size(spare, &psize); 59962306a36Sopenharmony_ci if (ret) { 60062306a36Sopenharmony_ci dev_err(dev, "failed to get iommu secure pgtable size (%d)\n", 60162306a36Sopenharmony_ci ret); 60262306a36Sopenharmony_ci return ret; 60362306a36Sopenharmony_ci } 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci dev_info(dev, "iommu sec: pgtable size: %zu\n", psize); 60662306a36Sopenharmony_ci 60762306a36Sopenharmony_ci attrs = DMA_ATTR_NO_KERNEL_MAPPING; 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci cpu_addr = dma_alloc_attrs(dev, psize, &paddr, GFP_KERNEL, attrs); 61062306a36Sopenharmony_ci if (!cpu_addr) { 61162306a36Sopenharmony_ci dev_err(dev, "failed to allocate %zu bytes for pgtable\n", 61262306a36Sopenharmony_ci psize); 61362306a36Sopenharmony_ci return -ENOMEM; 61462306a36Sopenharmony_ci } 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci ret = qcom_scm_iommu_secure_ptbl_init(paddr, psize, spare); 61762306a36Sopenharmony_ci if (ret) { 61862306a36Sopenharmony_ci dev_err(dev, "failed to init iommu pgtable (%d)\n", ret); 61962306a36Sopenharmony_ci goto free_mem; 62062306a36Sopenharmony_ci } 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci allocated = true; 62362306a36Sopenharmony_ci return 0; 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_cifree_mem: 62662306a36Sopenharmony_ci dma_free_attrs(dev, psize, cpu_addr, paddr, attrs); 62762306a36Sopenharmony_ci return ret; 62862306a36Sopenharmony_ci} 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_cistatic int get_asid(const struct device_node *np) 63162306a36Sopenharmony_ci{ 63262306a36Sopenharmony_ci u32 reg, val; 63362306a36Sopenharmony_ci int asid; 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci /* read the "reg" property directly to get the relative address 63662306a36Sopenharmony_ci * of the context bank, and calculate the asid from that: 63762306a36Sopenharmony_ci */ 63862306a36Sopenharmony_ci if (of_property_read_u32_index(np, "reg", 0, ®)) 63962306a36Sopenharmony_ci return -ENODEV; 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci /* 64262306a36Sopenharmony_ci * Context banks are 0x1000 apart but, in some cases, the ASID 64362306a36Sopenharmony_ci * number doesn't match to this logic and needs to be passed 64462306a36Sopenharmony_ci * from the DT configuration explicitly. 64562306a36Sopenharmony_ci */ 64662306a36Sopenharmony_ci if (!of_property_read_u32(np, "qcom,ctx-asid", &val)) 64762306a36Sopenharmony_ci asid = val; 64862306a36Sopenharmony_ci else 64962306a36Sopenharmony_ci asid = reg / 0x1000; 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_ci return asid; 65262306a36Sopenharmony_ci} 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_cistatic int qcom_iommu_ctx_probe(struct platform_device *pdev) 65562306a36Sopenharmony_ci{ 65662306a36Sopenharmony_ci struct qcom_iommu_ctx *ctx; 65762306a36Sopenharmony_ci struct device *dev = &pdev->dev; 65862306a36Sopenharmony_ci struct qcom_iommu_dev *qcom_iommu = dev_get_drvdata(dev->parent); 65962306a36Sopenharmony_ci int ret, irq; 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ci ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); 66262306a36Sopenharmony_ci if (!ctx) 66362306a36Sopenharmony_ci return -ENOMEM; 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_ci ctx->dev = dev; 66662306a36Sopenharmony_ci platform_set_drvdata(pdev, ctx); 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_ci ctx->base = devm_platform_ioremap_resource(pdev, 0); 66962306a36Sopenharmony_ci if (IS_ERR(ctx->base)) 67062306a36Sopenharmony_ci return PTR_ERR(ctx->base); 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci irq = platform_get_irq(pdev, 0); 67362306a36Sopenharmony_ci if (irq < 0) 67462306a36Sopenharmony_ci return irq; 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci if (of_device_is_compatible(dev->of_node, "qcom,msm-iommu-v2-sec")) 67762306a36Sopenharmony_ci ctx->secured_ctx = true; 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_ci /* clear IRQs before registering fault handler, just in case the 68062306a36Sopenharmony_ci * boot-loader left us a surprise: 68162306a36Sopenharmony_ci */ 68262306a36Sopenharmony_ci if (!ctx->secured_ctx) 68362306a36Sopenharmony_ci iommu_writel(ctx, ARM_SMMU_CB_FSR, iommu_readl(ctx, ARM_SMMU_CB_FSR)); 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_ci ret = devm_request_irq(dev, irq, 68662306a36Sopenharmony_ci qcom_iommu_fault, 68762306a36Sopenharmony_ci IRQF_SHARED, 68862306a36Sopenharmony_ci "qcom-iommu-fault", 68962306a36Sopenharmony_ci ctx); 69062306a36Sopenharmony_ci if (ret) { 69162306a36Sopenharmony_ci dev_err(dev, "failed to request IRQ %u\n", irq); 69262306a36Sopenharmony_ci return ret; 69362306a36Sopenharmony_ci } 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_ci ret = get_asid(dev->of_node); 69662306a36Sopenharmony_ci if (ret < 0) { 69762306a36Sopenharmony_ci dev_err(dev, "missing reg property\n"); 69862306a36Sopenharmony_ci return ret; 69962306a36Sopenharmony_ci } 70062306a36Sopenharmony_ci 70162306a36Sopenharmony_ci ctx->asid = ret; 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_ci dev_dbg(dev, "found asid %u\n", ctx->asid); 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci qcom_iommu->ctxs[ctx->asid] = ctx; 70662306a36Sopenharmony_ci 70762306a36Sopenharmony_ci return 0; 70862306a36Sopenharmony_ci} 70962306a36Sopenharmony_ci 71062306a36Sopenharmony_cistatic void qcom_iommu_ctx_remove(struct platform_device *pdev) 71162306a36Sopenharmony_ci{ 71262306a36Sopenharmony_ci struct qcom_iommu_dev *qcom_iommu = dev_get_drvdata(pdev->dev.parent); 71362306a36Sopenharmony_ci struct qcom_iommu_ctx *ctx = platform_get_drvdata(pdev); 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_ci platform_set_drvdata(pdev, NULL); 71662306a36Sopenharmony_ci 71762306a36Sopenharmony_ci qcom_iommu->ctxs[ctx->asid] = NULL; 71862306a36Sopenharmony_ci} 71962306a36Sopenharmony_ci 72062306a36Sopenharmony_cistatic const struct of_device_id ctx_of_match[] = { 72162306a36Sopenharmony_ci { .compatible = "qcom,msm-iommu-v1-ns" }, 72262306a36Sopenharmony_ci { .compatible = "qcom,msm-iommu-v1-sec" }, 72362306a36Sopenharmony_ci { .compatible = "qcom,msm-iommu-v2-ns" }, 72462306a36Sopenharmony_ci { .compatible = "qcom,msm-iommu-v2-sec" }, 72562306a36Sopenharmony_ci { /* sentinel */ } 72662306a36Sopenharmony_ci}; 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_cistatic struct platform_driver qcom_iommu_ctx_driver = { 72962306a36Sopenharmony_ci .driver = { 73062306a36Sopenharmony_ci .name = "qcom-iommu-ctx", 73162306a36Sopenharmony_ci .of_match_table = ctx_of_match, 73262306a36Sopenharmony_ci }, 73362306a36Sopenharmony_ci .probe = qcom_iommu_ctx_probe, 73462306a36Sopenharmony_ci .remove_new = qcom_iommu_ctx_remove, 73562306a36Sopenharmony_ci}; 73662306a36Sopenharmony_ci 73762306a36Sopenharmony_cistatic bool qcom_iommu_has_secure_context(struct qcom_iommu_dev *qcom_iommu) 73862306a36Sopenharmony_ci{ 73962306a36Sopenharmony_ci struct device_node *child; 74062306a36Sopenharmony_ci 74162306a36Sopenharmony_ci for_each_child_of_node(qcom_iommu->dev->of_node, child) { 74262306a36Sopenharmony_ci if (of_device_is_compatible(child, "qcom,msm-iommu-v1-sec") || 74362306a36Sopenharmony_ci of_device_is_compatible(child, "qcom,msm-iommu-v2-sec")) { 74462306a36Sopenharmony_ci of_node_put(child); 74562306a36Sopenharmony_ci return true; 74662306a36Sopenharmony_ci } 74762306a36Sopenharmony_ci } 74862306a36Sopenharmony_ci 74962306a36Sopenharmony_ci return false; 75062306a36Sopenharmony_ci} 75162306a36Sopenharmony_ci 75262306a36Sopenharmony_cistatic int qcom_iommu_device_probe(struct platform_device *pdev) 75362306a36Sopenharmony_ci{ 75462306a36Sopenharmony_ci struct device_node *child; 75562306a36Sopenharmony_ci struct qcom_iommu_dev *qcom_iommu; 75662306a36Sopenharmony_ci struct device *dev = &pdev->dev; 75762306a36Sopenharmony_ci struct resource *res; 75862306a36Sopenharmony_ci struct clk *clk; 75962306a36Sopenharmony_ci int ret, max_asid = 0; 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci /* find the max asid (which is 1:1 to ctx bank idx), so we know how 76262306a36Sopenharmony_ci * many child ctx devices we have: 76362306a36Sopenharmony_ci */ 76462306a36Sopenharmony_ci for_each_child_of_node(dev->of_node, child) 76562306a36Sopenharmony_ci max_asid = max(max_asid, get_asid(child)); 76662306a36Sopenharmony_ci 76762306a36Sopenharmony_ci qcom_iommu = devm_kzalloc(dev, struct_size(qcom_iommu, ctxs, max_asid + 1), 76862306a36Sopenharmony_ci GFP_KERNEL); 76962306a36Sopenharmony_ci if (!qcom_iommu) 77062306a36Sopenharmony_ci return -ENOMEM; 77162306a36Sopenharmony_ci qcom_iommu->max_asid = max_asid; 77262306a36Sopenharmony_ci qcom_iommu->dev = dev; 77362306a36Sopenharmony_ci 77462306a36Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 77562306a36Sopenharmony_ci if (res) { 77662306a36Sopenharmony_ci qcom_iommu->local_base = devm_ioremap_resource(dev, res); 77762306a36Sopenharmony_ci if (IS_ERR(qcom_iommu->local_base)) 77862306a36Sopenharmony_ci return PTR_ERR(qcom_iommu->local_base); 77962306a36Sopenharmony_ci } 78062306a36Sopenharmony_ci 78162306a36Sopenharmony_ci clk = devm_clk_get(dev, "iface"); 78262306a36Sopenharmony_ci if (IS_ERR(clk)) { 78362306a36Sopenharmony_ci dev_err(dev, "failed to get iface clock\n"); 78462306a36Sopenharmony_ci return PTR_ERR(clk); 78562306a36Sopenharmony_ci } 78662306a36Sopenharmony_ci qcom_iommu->clks[CLK_IFACE].clk = clk; 78762306a36Sopenharmony_ci 78862306a36Sopenharmony_ci clk = devm_clk_get(dev, "bus"); 78962306a36Sopenharmony_ci if (IS_ERR(clk)) { 79062306a36Sopenharmony_ci dev_err(dev, "failed to get bus clock\n"); 79162306a36Sopenharmony_ci return PTR_ERR(clk); 79262306a36Sopenharmony_ci } 79362306a36Sopenharmony_ci qcom_iommu->clks[CLK_BUS].clk = clk; 79462306a36Sopenharmony_ci 79562306a36Sopenharmony_ci clk = devm_clk_get_optional(dev, "tbu"); 79662306a36Sopenharmony_ci if (IS_ERR(clk)) { 79762306a36Sopenharmony_ci dev_err(dev, "failed to get tbu clock\n"); 79862306a36Sopenharmony_ci return PTR_ERR(clk); 79962306a36Sopenharmony_ci } 80062306a36Sopenharmony_ci qcom_iommu->clks[CLK_TBU].clk = clk; 80162306a36Sopenharmony_ci 80262306a36Sopenharmony_ci if (of_property_read_u32(dev->of_node, "qcom,iommu-secure-id", 80362306a36Sopenharmony_ci &qcom_iommu->sec_id)) { 80462306a36Sopenharmony_ci dev_err(dev, "missing qcom,iommu-secure-id property\n"); 80562306a36Sopenharmony_ci return -ENODEV; 80662306a36Sopenharmony_ci } 80762306a36Sopenharmony_ci 80862306a36Sopenharmony_ci if (qcom_iommu_has_secure_context(qcom_iommu)) { 80962306a36Sopenharmony_ci ret = qcom_iommu_sec_ptbl_init(dev); 81062306a36Sopenharmony_ci if (ret) { 81162306a36Sopenharmony_ci dev_err(dev, "cannot init secure pg table(%d)\n", ret); 81262306a36Sopenharmony_ci return ret; 81362306a36Sopenharmony_ci } 81462306a36Sopenharmony_ci } 81562306a36Sopenharmony_ci 81662306a36Sopenharmony_ci platform_set_drvdata(pdev, qcom_iommu); 81762306a36Sopenharmony_ci 81862306a36Sopenharmony_ci pm_runtime_enable(dev); 81962306a36Sopenharmony_ci 82062306a36Sopenharmony_ci /* register context bank devices, which are child nodes: */ 82162306a36Sopenharmony_ci ret = devm_of_platform_populate(dev); 82262306a36Sopenharmony_ci if (ret) { 82362306a36Sopenharmony_ci dev_err(dev, "Failed to populate iommu contexts\n"); 82462306a36Sopenharmony_ci goto err_pm_disable; 82562306a36Sopenharmony_ci } 82662306a36Sopenharmony_ci 82762306a36Sopenharmony_ci ret = iommu_device_sysfs_add(&qcom_iommu->iommu, dev, NULL, 82862306a36Sopenharmony_ci dev_name(dev)); 82962306a36Sopenharmony_ci if (ret) { 83062306a36Sopenharmony_ci dev_err(dev, "Failed to register iommu in sysfs\n"); 83162306a36Sopenharmony_ci goto err_pm_disable; 83262306a36Sopenharmony_ci } 83362306a36Sopenharmony_ci 83462306a36Sopenharmony_ci ret = iommu_device_register(&qcom_iommu->iommu, &qcom_iommu_ops, dev); 83562306a36Sopenharmony_ci if (ret) { 83662306a36Sopenharmony_ci dev_err(dev, "Failed to register iommu\n"); 83762306a36Sopenharmony_ci goto err_pm_disable; 83862306a36Sopenharmony_ci } 83962306a36Sopenharmony_ci 84062306a36Sopenharmony_ci if (qcom_iommu->local_base) { 84162306a36Sopenharmony_ci pm_runtime_get_sync(dev); 84262306a36Sopenharmony_ci writel_relaxed(0xffffffff, qcom_iommu->local_base + SMMU_INTR_SEL_NS); 84362306a36Sopenharmony_ci pm_runtime_put_sync(dev); 84462306a36Sopenharmony_ci } 84562306a36Sopenharmony_ci 84662306a36Sopenharmony_ci return 0; 84762306a36Sopenharmony_ci 84862306a36Sopenharmony_cierr_pm_disable: 84962306a36Sopenharmony_ci pm_runtime_disable(dev); 85062306a36Sopenharmony_ci return ret; 85162306a36Sopenharmony_ci} 85262306a36Sopenharmony_ci 85362306a36Sopenharmony_cistatic void qcom_iommu_device_remove(struct platform_device *pdev) 85462306a36Sopenharmony_ci{ 85562306a36Sopenharmony_ci struct qcom_iommu_dev *qcom_iommu = platform_get_drvdata(pdev); 85662306a36Sopenharmony_ci 85762306a36Sopenharmony_ci pm_runtime_force_suspend(&pdev->dev); 85862306a36Sopenharmony_ci platform_set_drvdata(pdev, NULL); 85962306a36Sopenharmony_ci iommu_device_sysfs_remove(&qcom_iommu->iommu); 86062306a36Sopenharmony_ci iommu_device_unregister(&qcom_iommu->iommu); 86162306a36Sopenharmony_ci} 86262306a36Sopenharmony_ci 86362306a36Sopenharmony_cistatic int __maybe_unused qcom_iommu_resume(struct device *dev) 86462306a36Sopenharmony_ci{ 86562306a36Sopenharmony_ci struct qcom_iommu_dev *qcom_iommu = dev_get_drvdata(dev); 86662306a36Sopenharmony_ci 86762306a36Sopenharmony_ci return clk_bulk_prepare_enable(CLK_NUM, qcom_iommu->clks); 86862306a36Sopenharmony_ci} 86962306a36Sopenharmony_ci 87062306a36Sopenharmony_cistatic int __maybe_unused qcom_iommu_suspend(struct device *dev) 87162306a36Sopenharmony_ci{ 87262306a36Sopenharmony_ci struct qcom_iommu_dev *qcom_iommu = dev_get_drvdata(dev); 87362306a36Sopenharmony_ci 87462306a36Sopenharmony_ci clk_bulk_disable_unprepare(CLK_NUM, qcom_iommu->clks); 87562306a36Sopenharmony_ci 87662306a36Sopenharmony_ci return 0; 87762306a36Sopenharmony_ci} 87862306a36Sopenharmony_ci 87962306a36Sopenharmony_cistatic const struct dev_pm_ops qcom_iommu_pm_ops = { 88062306a36Sopenharmony_ci SET_RUNTIME_PM_OPS(qcom_iommu_suspend, qcom_iommu_resume, NULL) 88162306a36Sopenharmony_ci SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, 88262306a36Sopenharmony_ci pm_runtime_force_resume) 88362306a36Sopenharmony_ci}; 88462306a36Sopenharmony_ci 88562306a36Sopenharmony_cistatic const struct of_device_id qcom_iommu_of_match[] = { 88662306a36Sopenharmony_ci { .compatible = "qcom,msm-iommu-v1" }, 88762306a36Sopenharmony_ci { .compatible = "qcom,msm-iommu-v2" }, 88862306a36Sopenharmony_ci { /* sentinel */ } 88962306a36Sopenharmony_ci}; 89062306a36Sopenharmony_ci 89162306a36Sopenharmony_cistatic struct platform_driver qcom_iommu_driver = { 89262306a36Sopenharmony_ci .driver = { 89362306a36Sopenharmony_ci .name = "qcom-iommu", 89462306a36Sopenharmony_ci .of_match_table = qcom_iommu_of_match, 89562306a36Sopenharmony_ci .pm = &qcom_iommu_pm_ops, 89662306a36Sopenharmony_ci }, 89762306a36Sopenharmony_ci .probe = qcom_iommu_device_probe, 89862306a36Sopenharmony_ci .remove_new = qcom_iommu_device_remove, 89962306a36Sopenharmony_ci}; 90062306a36Sopenharmony_ci 90162306a36Sopenharmony_cistatic int __init qcom_iommu_init(void) 90262306a36Sopenharmony_ci{ 90362306a36Sopenharmony_ci int ret; 90462306a36Sopenharmony_ci 90562306a36Sopenharmony_ci ret = platform_driver_register(&qcom_iommu_ctx_driver); 90662306a36Sopenharmony_ci if (ret) 90762306a36Sopenharmony_ci return ret; 90862306a36Sopenharmony_ci 90962306a36Sopenharmony_ci ret = platform_driver_register(&qcom_iommu_driver); 91062306a36Sopenharmony_ci if (ret) 91162306a36Sopenharmony_ci platform_driver_unregister(&qcom_iommu_ctx_driver); 91262306a36Sopenharmony_ci 91362306a36Sopenharmony_ci return ret; 91462306a36Sopenharmony_ci} 91562306a36Sopenharmony_cidevice_initcall(qcom_iommu_init); 916