[PATCH 08/12] iommu: qcom_iommu: halt the micro-MMU while programming context banks

From: Dmitry Baryshkov

Date: Sun Aug 09 2026 - 16:17:33 EST


The context bank registers are reprogrammed underneath a live translation
front-end. This happens at attach time, and again on every runtime resume
for instances that have to replay their context bank state after power
collapse. Rewriting SCTLR, TTBR0, TCR and the MAIRs while the micro-MMU
keeps accepting client transactions races the in-flight traffic against
the new configuration.

The hardware provides a handshake for exactly this: setting HALT_REQ in
MICRO_MMU_CTRL, at the start of the implementation-defined register
space, stops new transactions from being accepted and reports IDLE once
the outstanding ones have retired. Downstream drives it through a
qcom,iommu-enable-halt property.

Add the handshake, under a per-instance flag as the handshake is not
wanted on every instance, and bound the wait rather than spinning
forever, so that an unresponsive front-end degrades to a diagnostic
instead of a hang.

The halt is confined to context bank programming. Applying it around the
global reset as well resets the SoC before the console comes up,
reproducibly, on an APQ8074 DragonBoard.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@xxxxxxxxxxxxxxxx>
---
drivers/iommu/arm/arm-smmu/qcom_iommu.c | 44 +++++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)

diff --git a/drivers/iommu/arm/arm-smmu/qcom_iommu.c b/drivers/iommu/arm/arm-smmu/qcom_iommu.c
index 8e9488c47d5c..f27f8722ee64 100644
--- a/drivers/iommu/arm/arm-smmu/qcom_iommu.c
+++ b/drivers/iommu/arm/arm-smmu/qcom_iommu.c
@@ -37,6 +37,14 @@
/* GR1 sits one 4K page above GR0 on the msm8974 QSMMU */
#define QCOM_IOMMU_GR1 0x1000

+/* The implementation-defined space sits two 4K pages above GR0 */
+#define QCOM_IOMMU_IMPL_DEF 0x2000
+
+/* Micro-MMU control, at the start of the implementation-defined space */
+#define QCOM_IOMMU_MICRO_MMU_CTRL (QCOM_IOMMU_IMPL_DEF + 0x000)
+#define MICRO_MMU_CTRL_HALT_REQ BIT(2)
+#define MICRO_MMU_CTRL_IDLE BIT(3)
+
/* Redirect all cacheable requests to the L2 slave port */
#define QCOM_IOMMU_ACTLR_BPRC (BIT(28) | BIT(29) | BIT(30))

@@ -66,6 +74,8 @@ struct qcom_iommu_cfg {
bool no_afe;
/* context banks lose their state over GDSC power collapse */
bool ctx_restore;
+ /* the micro-MMU must be halted while its registers are programmed */
+ bool halt;
const struct qcom_iommu_sid *sids; /* one SMR slot per entry */
unsigned int num_sids;
};
@@ -339,9 +349,41 @@ static int qcom_iommu_reset_ns(struct qcom_iommu_dev *qcom_iommu)
return 0;
}

+/*
+ * Halting the micro-MMU quiesces the translation front-end: it stops new
+ * client transactions being accepted and waits for the outstanding ones to
+ * retire, so that the context bank registers can be reprogrammed without
+ * in-flight traffic racing the change.
+ */
+static void qcom_iommu_halt(struct qcom_iommu_dev *qcom_iommu)
+{
+ void __iomem *reg = qcom_iommu->global_base + QCOM_IOMMU_MICRO_MMU_CTRL;
+ u32 val;
+
+ if (!qcom_iommu->cfg || !qcom_iommu->cfg->halt)
+ return;
+
+ writel_relaxed(readl_relaxed(reg) | MICRO_MMU_CTRL_HALT_REQ, reg);
+
+ if (readl_poll_timeout(reg, val, val & MICRO_MMU_CTRL_IDLE, 0, 100000))
+ dev_err(qcom_iommu->dev, "timeout waiting for micro-MMU halt\n");
+}
+
+static void qcom_iommu_unhalt(struct qcom_iommu_dev *qcom_iommu)
+{
+ void __iomem *reg = qcom_iommu->global_base + QCOM_IOMMU_MICRO_MMU_CTRL;
+
+ if (!qcom_iommu->cfg || !qcom_iommu->cfg->halt)
+ return;
+
+ writel_relaxed(readl_relaxed(reg) & ~MICRO_MMU_CTRL_HALT_REQ, reg);
+}
+
static void qcom_iommu_program_ctx(struct qcom_iommu_dev *qcom_iommu,
struct qcom_iommu_ctx *ctx)
{
+ qcom_iommu_halt(qcom_iommu);
+
/* Disable context bank before programming */
iommu_writel(ctx, ARM_SMMU_CB_SCTLR, 0);

@@ -369,6 +411,8 @@ static void qcom_iommu_program_ctx(struct qcom_iommu_dev *qcom_iommu,

/* SCTLR */
iommu_writel(ctx, ARM_SMMU_CB_SCTLR, ctx->sctlr);
+
+ qcom_iommu_unhalt(qcom_iommu);
}

static int qcom_iommu_init_domain(struct iommu_domain *domain,

--
2.47.3