[PATCH 04/12] iommu: qcom_iommu: support the short-descriptor pagetable format
Dmitry Baryshkov <[email protected]>
| Newsgroups | dev.linux.lists.iommu,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <20260809-msm8974-iommu-upstream-v1-4-87f5cd492560@oss.qualcomm.com> |
The MSM8974-generation QSMMU does not walk the LPAE tables this driver hardcodes: every access faults with an access flag fault despite AF being set in the descriptors, and with SCTLR.AFE cleared the walks still return garbage translations (observed on the APQ8074 dragonboard GPU, which locks up on its first ringbuffer fetch). The downstream kernel only ever used the short-descriptor format on MSM8974; its LPAE support was reserved for the newer APQ8084 IP / TZ revision. Introduce a per-instance configuration structure, selected through the of_device_id match data, carrying a pagetable format field and an AFE quirk flag. For the ARM_V7S format, program the ASID through CONTEXTIDR instead of the TTBR0 ASID field, reuse the MAIR register offsets for PRRR/NMRR, restrict the output address size to 32 bits and keep SCTLR.AFE disabled like the downstream driver does when the no_afe flag is set. No instance sets a configuration yet. Assisted-by: Claude:claude-fable-5 Signed-off-by: Dmitry Baryshkov <[email protected]> --- drivers/iommu/arm/Kconfig | 1 + drivers/iommu/arm/arm-smmu/qcom_iommu.c | 54 +++++++++++++++++++++++++++------ 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/drivers/iommu/arm/Kconfig b/drivers/iommu/arm/Kconfig index 5fac08b89dee..596e643961c3 100644 --- a/drivers/iommu/arm/Kconfig +++ b/drivers/iommu/arm/Kconfig @@ -138,6 +138,7 @@ config QCOM_IOMMU select QCOM_SCM select IOMMU_API select IOMMU_IO_PGTABLE_LPAE + select IOMMU_IO_PGTABLE_ARMV7S select ARM_DMA_USE_IOMMU help Support for IOMMU on certain Qualcomm SoCs. diff --git a/drivers/iommu/arm/arm-smmu/qcom_iommu.c b/drivers/iommu/arm/arm-smmu/qcom_iommu.c index 5bbb51d9bd65..77c01f217d7e 100644 --- a/drivers/iommu/arm/arm-smmu/qcom_iommu.c +++ b/drivers/iommu/arm/arm-smmu/qcom_iommu.c @@ -43,10 +43,18 @@ enum qcom_iommu_clk { struct qcom_iommu_ctx; +/* Per-instance configuration, absent on msm8916-style instances */ +struct qcom_iommu_cfg { + enum io_pgtable_fmt fmt; + /* the walker faults on the AF bit despite it being set */ + bool no_afe; +}; + struct qcom_iommu_dev { /* IOMMU core code handle */ struct iommu_device iommu; struct device *dev; + const struct qcom_iommu_cfg *cfg; struct clk_bulk_data clks[CLK_NUM]; void __iomem *local_base; u32 sec_id; @@ -54,6 +62,11 @@ struct qcom_iommu_dev { struct qcom_iommu_ctx *ctxs[]; /* indexed by asid */ }; +static enum io_pgtable_fmt qcom_iommu_pgtbl_fmt(struct qcom_iommu_dev *qcom_iommu) +{ + return qcom_iommu->cfg ? qcom_iommu->cfg->fmt : ARM_32_LPAE_S1; +} + struct qcom_iommu_ctx { struct device *dev; void __iomem *base; @@ -65,6 +78,7 @@ struct qcom_iommu_ctx { u64 ttbr0; u32 tcr[2]; u32 mair[2]; + u32 contextidr; u32 sctlr; }; @@ -232,8 +246,11 @@ static void qcom_iommu_program_ctx(struct qcom_iommu_dev *qcom_iommu, iommu_writeq(ctx, ARM_SMMU_CB_TTBR0, ctx->ttbr0); iommu_writeq(ctx, ARM_SMMU_CB_TTBR1, 0); - /* TCR */ - iommu_writel(ctx, ARM_SMMU_CB_TCR2, ctx->tcr[1]); + /* TCR; the v7s ASID lives in CONTEXTIDR instead of TTBR0 */ + if (qcom_iommu_pgtbl_fmt(qcom_iommu) == ARM_V7S) + iommu_writel(ctx, ARM_SMMU_CB_CONTEXTIDR, ctx->contextidr); + else + iommu_writel(ctx, ARM_SMMU_CB_TCR2, ctx->tcr[1]); iommu_writel(ctx, ARM_SMMU_CB_TCR, ctx->tcr[0]); /* MAIRs (stage-1 only) */ @@ -252,6 +269,7 @@ static int qcom_iommu_init_domain(struct iommu_domain *domain, struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev); struct io_pgtable_ops *pgtbl_ops; struct io_pgtable_cfg pgtbl_cfg; + enum io_pgtable_fmt fmt; int i, ret = 0; u32 reg; @@ -259,10 +277,12 @@ static int qcom_iommu_init_domain(struct iommu_domain *domain, if (qcom_domain->iommu) goto out_unlock; + fmt = qcom_iommu_pgtbl_fmt(qcom_iommu); + pgtbl_cfg = (struct io_pgtable_cfg) { .pgsize_bitmap = domain->pgsize_bitmap, .ias = 32, - .oas = 40, + .oas = fmt == ARM_V7S ? 32 : 40, .tlb = &qcom_flush_ops, .iommu_dev = qcom_iommu->dev, }; @@ -270,7 +290,7 @@ static int qcom_iommu_init_domain(struct iommu_domain *domain, qcom_domain->iommu = qcom_iommu; qcom_domain->fwspec = fwspec; - pgtbl_ops = alloc_io_pgtable_ops(ARM_32_LPAE_S1, &pgtbl_cfg, qcom_domain); + pgtbl_ops = alloc_io_pgtable_ops(fmt, &pgtbl_cfg, qcom_domain); if (!pgtbl_ops) { dev_err(qcom_iommu->dev, "failed to allocate pagetable ops\n"); ret = -ENOMEM; @@ -298,12 +318,22 @@ static int qcom_iommu_init_domain(struct iommu_domain *domain, continue; } - ctx->ttbr0 = pgtbl_cfg.arm_lpae_s1_cfg.ttbr | - FIELD_PREP(ARM_SMMU_TTBRn_ASID, ctx->asid); - ctx->tcr[0] = arm_smmu_lpae_tcr(&pgtbl_cfg) | ARM_SMMU_TCR_EAE; - ctx->tcr[1] = arm_smmu_lpae_tcr2(&pgtbl_cfg); - ctx->mair[0] = pgtbl_cfg.arm_lpae_s1_cfg.mair; - ctx->mair[1] = pgtbl_cfg.arm_lpae_s1_cfg.mair >> 32; + if (fmt == ARM_V7S) { + ctx->ttbr0 = pgtbl_cfg.arm_v7s_cfg.ttbr; + ctx->tcr[0] = pgtbl_cfg.arm_v7s_cfg.tcr; + ctx->tcr[1] = 0; + /* PRRR/NMRR share the MAIR0/MAIR1 offsets */ + ctx->mair[0] = pgtbl_cfg.arm_v7s_cfg.prrr; + ctx->mair[1] = pgtbl_cfg.arm_v7s_cfg.nmrr; + ctx->contextidr = ctx->asid; + } else { + ctx->ttbr0 = pgtbl_cfg.arm_lpae_s1_cfg.ttbr | + FIELD_PREP(ARM_SMMU_TTBRn_ASID, ctx->asid); + ctx->tcr[0] = arm_smmu_lpae_tcr(&pgtbl_cfg) | ARM_SMMU_TCR_EAE; + ctx->tcr[1] = arm_smmu_lpae_tcr2(&pgtbl_cfg); + ctx->mair[0] = pgtbl_cfg.arm_lpae_s1_cfg.mair; + ctx->mair[1] = pgtbl_cfg.arm_lpae_s1_cfg.mair >> 32; + } reg = ARM_SMMU_SCTLR_CFIE | ARM_SMMU_SCTLR_CFRE | ARM_SMMU_SCTLR_AFE | ARM_SMMU_SCTLR_TRE | @@ -313,6 +343,9 @@ static int qcom_iommu_init_domain(struct iommu_domain *domain, if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) reg |= ARM_SMMU_SCTLR_E; + if (qcom_iommu->cfg && qcom_iommu->cfg->no_afe) + reg &= ~ARM_SMMU_SCTLR_AFE; + ctx->sctlr = reg; qcom_iommu_program_ctx(qcom_iommu, ctx); @@ -808,6 +841,7 @@ static int qcom_iommu_device_probe(struct platform_device *pdev) return -ENOMEM; qcom_iommu->max_asid = max_asid; qcom_iommu->dev = dev; + qcom_iommu->cfg = of_device_get_match_data(dev); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res) { -- 2.47.3