* [PATCH v8 01/12] iommu/arm-smmu-v3: Refactor arm_smmu_setup_irqs
2026-06-01 21:58 [PATCH v8 00/10] iommu/arm-smmu-v3: Implement Runtime/System Sleep ops Pranjal Shrivastava
@ 2026-06-01 21:58 ` Pranjal Shrivastava
2026-06-01 21:58 ` [PATCH v8 02/12] iommu/arm-smmu-v3: Add a helper to drain cmd queues Pranjal Shrivastava
` (11 subsequent siblings)
12 siblings, 0 replies; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-01 21:58 UTC (permalink / raw)
To: iommu
Cc: Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Nicolin Chen, Daniel Mentz, Ashish Mhetre,
linux-arm-kernel, Pranjal Shrivastava
Refactor arm_smmu_setup_irqs by splitting it into two parts, one for
registering interrupt handlers and the other one for enabling interrupt
generation in the hardware. This refactor helps in re-initialization of
hardware interrupts as part of a subsequent patch that enables runtime
power management for the arm-smmu-v3 driver.
Reviewed-by: Mostafa Saleh <smostafa@google.com>
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 47 +++++++++++++--------
1 file changed, 30 insertions(+), 17 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 8ce3e801eda3..3dad2c2d3283 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4601,14 +4601,32 @@ static void arm_smmu_setup_unique_irqs(struct arm_smmu_device *smmu)
}
}
+static void arm_smmu_enable_irqs(struct arm_smmu_device *smmu)
+{
+ int ret;
+ u32 irqen_flags = IRQ_CTRL_EVTQ_IRQEN | IRQ_CTRL_GERROR_IRQEN;
+
+ if (smmu->features & ARM_SMMU_FEAT_PRI)
+ irqen_flags |= IRQ_CTRL_PRIQ_IRQEN;
+
+ ret = arm_smmu_write_reg_sync(smmu, irqen_flags,
+ ARM_SMMU_IRQ_CTRL, ARM_SMMU_IRQ_CTRLACK);
+ if (ret)
+ dev_warn(smmu->dev, "failed to enable irqs\n");
+}
+
+static int arm_smmu_disable_irqs(struct arm_smmu_device *smmu)
+{
+ return arm_smmu_write_reg_sync(smmu, 0, ARM_SMMU_IRQ_CTRL,
+ ARM_SMMU_IRQ_CTRLACK);
+}
+
static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu)
{
int ret, irq;
- u32 irqen_flags = IRQ_CTRL_EVTQ_IRQEN | IRQ_CTRL_GERROR_IRQEN;
/* Disable IRQs first */
- ret = arm_smmu_write_reg_sync(smmu, 0, ARM_SMMU_IRQ_CTRL,
- ARM_SMMU_IRQ_CTRLACK);
+ ret = arm_smmu_disable_irqs(smmu);
if (ret) {
dev_err(smmu->dev, "failed to disable irqs\n");
return ret;
@@ -4630,15 +4648,6 @@ static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu)
} else
arm_smmu_setup_unique_irqs(smmu);
- if (smmu->features & ARM_SMMU_FEAT_PRI)
- irqen_flags |= IRQ_CTRL_PRIQ_IRQEN;
-
- /* Enable interrupt generation on the SMMU */
- ret = arm_smmu_write_reg_sync(smmu, irqen_flags,
- ARM_SMMU_IRQ_CTRL, ARM_SMMU_IRQ_CTRLACK);
- if (ret)
- dev_warn(smmu->dev, "failed to enable irqs\n");
-
return 0;
}
@@ -4779,11 +4788,8 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
}
}
- ret = arm_smmu_setup_irqs(smmu);
- if (ret) {
- dev_err(smmu->dev, "failed to setup irqs\n");
- return ret;
- }
+ /* Enable interrupt generation on the SMMU */
+ arm_smmu_enable_irqs(smmu);
if (is_kdump_kernel())
enables &= ~(CR0_EVTQEN | CR0_PRIQEN);
@@ -5417,6 +5423,13 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
/* Check for RMRs and install bypass STEs if any */
arm_smmu_rmr_install_bypass_ste(smmu);
+ /* Setup interrupt handlers */
+ ret = arm_smmu_setup_irqs(smmu);
+ if (ret) {
+ dev_err(smmu->dev, "failed to setup irqs\n");
+ goto err_free_iopf;
+ }
+
/* Reset the device */
ret = arm_smmu_device_reset(smmu);
if (ret)
--
2.54.0.1013.g208068f2d8-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v8 02/12] iommu/arm-smmu-v3: Add a helper to drain cmd queues
2026-06-01 21:58 [PATCH v8 00/10] iommu/arm-smmu-v3: Implement Runtime/System Sleep ops Pranjal Shrivastava
2026-06-01 21:58 ` [PATCH v8 01/12] iommu/arm-smmu-v3: Refactor arm_smmu_setup_irqs Pranjal Shrivastava
@ 2026-06-01 21:58 ` Pranjal Shrivastava
2026-06-02 0:12 ` Nicolin Chen
2026-06-02 5:21 ` Daniel Mentz
2026-06-01 21:59 ` [PATCH v8 03/12] iommu/tegra241-cmdqv: Add a helper to drain VCMDQs Pranjal Shrivastava
` (10 subsequent siblings)
12 siblings, 2 replies; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-01 21:58 UTC (permalink / raw)
To: iommu
Cc: Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Nicolin Chen, Daniel Mentz, Ashish Mhetre,
linux-arm-kernel, Pranjal Shrivastava
Before we suspend SMMU, we want to ensure that all commands (especially
ATC_INV) have been flushed by the CMDQ, i.e. the CMDQs are empty.
Add a helper function that polls till the queues are dained.
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 34 +++++++++++++++++++++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 3 ++
2 files changed, 37 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 3dad2c2d3283..0e77ef1e4523 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -884,6 +884,40 @@ static int arm_smmu_cmdq_batch_submit(struct arm_smmu_device *smmu,
cmds->num, true);
}
+int arm_smmu_queue_poll_until_empty(struct arm_smmu_device *smmu,
+ struct arm_smmu_queue *q)
+{
+ struct arm_smmu_queue_poll qp;
+ struct arm_smmu_ll_queue *llq = &q->llq;
+ int ret = 0;
+
+ queue_poll_init(smmu, &qp);
+ do {
+ if (queue_empty(llq))
+ break;
+
+ ret = queue_poll(&qp);
+ WRITE_ONCE(llq->cons, readl_relaxed(q->cons_reg));
+
+ } while (!ret);
+
+ return ret;
+}
+
+static int arm_smmu_drain_queues(struct arm_smmu_device *smmu)
+{
+ int ret;
+
+ /*
+ * Since this is only called from the suspend callback where
+ * exclusive access is ensured as CMDQ_PROD_STOP_FLAG blocks new
+ * command submissions to the cmdq, we can skip the cmdq locking.
+ */
+ ret = arm_smmu_queue_poll_until_empty(smmu, &smmu->cmdq.q);
+
+ return ret;
+}
+
static void arm_smmu_page_response(struct device *dev, struct iopf_fault *unused,
struct iommu_page_response *resp)
{
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index 16353596e08a..c855ab4962ed 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -1165,6 +1165,9 @@ int arm_smmu_init_one_queue(struct arm_smmu_device *smmu,
int arm_smmu_cmdq_init(struct arm_smmu_device *smmu,
struct arm_smmu_cmdq *cmdq);
+int arm_smmu_queue_poll_until_empty(struct arm_smmu_device *smmu,
+ struct arm_smmu_queue *q);
+
static inline bool arm_smmu_master_canwbs(struct arm_smmu_master *master)
{
return dev_iommu_fwspec_get(master->dev)->flags &
--
2.54.0.1013.g208068f2d8-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v8 02/12] iommu/arm-smmu-v3: Add a helper to drain cmd queues
2026-06-01 21:58 ` [PATCH v8 02/12] iommu/arm-smmu-v3: Add a helper to drain cmd queues Pranjal Shrivastava
@ 2026-06-02 0:12 ` Nicolin Chen
2026-06-02 3:28 ` Pranjal Shrivastava
2026-06-02 5:21 ` Daniel Mentz
1 sibling, 1 reply; 29+ messages in thread
From: Nicolin Chen @ 2026-06-02 0:12 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Daniel Mentz, Ashish Mhetre, linux-arm-kernel
On Mon, Jun 01, 2026 at 09:58:59PM +0000, Pranjal Shrivastava wrote:
> +int arm_smmu_queue_poll_until_empty(struct arm_smmu_device *smmu,
> + struct arm_smmu_queue *q)
> +{
> + struct arm_smmu_queue_poll qp;
> + struct arm_smmu_ll_queue *llq = &q->llq;
> + int ret = 0;
> +
> + queue_poll_init(smmu, &qp);
> + do {
> + if (queue_empty(llq))
> + break;
> +
> + ret = queue_poll(&qp);
> + WRITE_ONCE(llq->cons, readl_relaxed(q->cons_reg));
> +
> + } while (!ret);
> +
> + return ret;
> +}
Hmm, this is not the unified arm_smmu_drain_queue() that you sent
me lately? Do you want me to change this function to that version
in my series? I thought it could go in with yours.
Nicolin
^ permalink raw reply [flat|nested] 29+ messages in thread* Re: [PATCH v8 02/12] iommu/arm-smmu-v3: Add a helper to drain cmd queues
2026-06-02 0:12 ` Nicolin Chen
@ 2026-06-02 3:28 ` Pranjal Shrivastava
0 siblings, 0 replies; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-02 3:28 UTC (permalink / raw)
To: Nicolin Chen
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Daniel Mentz, Ashish Mhetre, linux-arm-kernel
On Mon, Jun 01, 2026 at 05:12:39PM -0700, Nicolin Chen wrote:
> On Mon, Jun 01, 2026 at 09:58:59PM +0000, Pranjal Shrivastava wrote:
> > +int arm_smmu_queue_poll_until_empty(struct arm_smmu_device *smmu,
> > + struct arm_smmu_queue *q)
> > +{
> > + struct arm_smmu_queue_poll qp;
> > + struct arm_smmu_ll_queue *llq = &q->llq;
> > + int ret = 0;
> > +
> > + queue_poll_init(smmu, &qp);
> > + do {
> > + if (queue_empty(llq))
> > + break;
> > +
> > + ret = queue_poll(&qp);
> > + WRITE_ONCE(llq->cons, readl_relaxed(q->cons_reg));
> > +
> > + } while (!ret);
> > +
> > + return ret;
> > +}
>
> Hmm, this is not the unified arm_smmu_drain_queue() that you sent
> me lately? Do you want me to change this function to that version
> in my series? I thought it could go in with yours.
Yes.. I thought it could go with your series
If I were to include the full unified version here, I'd essentially have
to include changes from the PRI support, which is still in its early
stages. I'm keen to keep this RPM series standalone to avoid coupling
its progress to the PRI review cycle.
Anyway it's just a clean up?
Praan
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v8 02/12] iommu/arm-smmu-v3: Add a helper to drain cmd queues
2026-06-01 21:58 ` [PATCH v8 02/12] iommu/arm-smmu-v3: Add a helper to drain cmd queues Pranjal Shrivastava
2026-06-02 0:12 ` Nicolin Chen
@ 2026-06-02 5:21 ` Daniel Mentz
1 sibling, 0 replies; 29+ messages in thread
From: Daniel Mentz @ 2026-06-02 5:21 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Nicolin Chen, Ashish Mhetre, linux-arm-kernel
On Mon, Jun 1, 2026 at 2:59 PM Pranjal Shrivastava <praan@google.com> wrote:
> +static int arm_smmu_drain_queues(struct arm_smmu_device *smmu)
> +{
> + int ret;
> +
> + /*
> + * Since this is only called from the suspend callback where
> + * exclusive access is ensured as CMDQ_PROD_STOP_FLAG blocks new
> + * command submissions to the cmdq, we can skip the cmdq locking.
> + */
From what I understand, the purpose of this lock is to ensure that
only one thread updates smmu->cmdq.q.cons. The flag
CMDQ_PROD_STOP_FLAG might block new threads from submitting commands
to the command queue. However, other threads might have already passed
that point and are about to update smmu->cmdq.q.cons. Hence, this
assumption might not be safe.
> + ret = arm_smmu_queue_poll_until_empty(smmu, &smmu->cmdq.q);
> +
> + return ret;
> +}
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v8 03/12] iommu/tegra241-cmdqv: Add a helper to drain VCMDQs
2026-06-01 21:58 [PATCH v8 00/10] iommu/arm-smmu-v3: Implement Runtime/System Sleep ops Pranjal Shrivastava
2026-06-01 21:58 ` [PATCH v8 01/12] iommu/arm-smmu-v3: Refactor arm_smmu_setup_irqs Pranjal Shrivastava
2026-06-01 21:58 ` [PATCH v8 02/12] iommu/arm-smmu-v3: Add a helper to drain cmd queues Pranjal Shrivastava
@ 2026-06-01 21:59 ` Pranjal Shrivastava
2026-06-01 21:59 ` [PATCH v8 04/12] iommu/tegra241-cmdqv: Restore PROD and CONS after resume Pranjal Shrivastava
` (9 subsequent siblings)
12 siblings, 0 replies; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-01 21:59 UTC (permalink / raw)
To: iommu
Cc: Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Nicolin Chen, Daniel Mentz, Ashish Mhetre,
linux-arm-kernel, Pranjal Shrivastava
The tegra241-cmdqv driver supports vCMDQs which need to be drained
before suspending the SMMU. The current driver implementation only uses
VINTF0 for vCMDQs owned by the kernel which need to be drained. Add a
helper that drains all the enabled vCMDQs under VINTF0.
Add another function ptr to arm_smmu_impl_ops to drain implementation
specified queues and call it within `arm_smmu_drain_queues`.
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 7 +++++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 1 +
.../iommu/arm/arm-smmu-v3/tegra241-cmdqv.c | 27 +++++++++++++++++++
3 files changed, 35 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 0e77ef1e4523..8682be5060ed 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -915,6 +915,13 @@ static int arm_smmu_drain_queues(struct arm_smmu_device *smmu)
*/
ret = arm_smmu_queue_poll_until_empty(smmu, &smmu->cmdq.q);
+ if (ret)
+ goto out;
+
+ /* Drain all implementation-specific queues */
+ if (smmu->impl_ops && smmu->impl_ops->drain_queues)
+ ret = smmu->impl_ops->drain_queues(smmu);
+out:
return ret;
}
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index c855ab4962ed..24d5e28eea88 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -885,6 +885,7 @@ struct arm_smmu_impl_ops {
size_t (*get_viommu_size)(enum iommu_viommu_type viommu_type);
int (*vsmmu_init)(struct arm_vsmmu *vsmmu,
const struct iommu_user_data *user_data);
+ int (*drain_queues)(struct arm_smmu_device *smmu);
};
/* An SMMUv3 instance */
diff --git a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
index 67be62a6e764..cb1e75e4ee91 100644
--- a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
+++ b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
@@ -414,6 +414,32 @@ tegra241_cmdqv_get_cmdq(struct arm_smmu_device *smmu,
return &vcmdq->cmdq;
}
+static int tegra241_cmdqv_drain_vintf0_lvcmdqs(struct arm_smmu_device *smmu)
+{
+ struct tegra241_cmdqv *cmdqv =
+ container_of(smmu, struct tegra241_cmdqv, smmu);
+ struct tegra241_vintf *vintf = cmdqv->vintfs[0];
+ int ret = 0;
+ u16 lidx;
+
+ /* Kernel only uses VINTF0. Return if it's disabled */
+ if (!READ_ONCE(vintf->enabled))
+ return 0;
+
+ for (lidx = 0; lidx < cmdqv->num_lvcmdqs_per_vintf; lidx++) {
+ struct tegra241_vcmdq *vcmdq = vintf->lvcmdqs[lidx];
+
+ if (!vcmdq || !READ_ONCE(vcmdq->enabled))
+ continue;
+
+ ret = arm_smmu_queue_poll_until_empty(smmu, &vcmdq->cmdq.q);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
/* HW Reset Functions */
/*
@@ -845,6 +871,7 @@ static struct arm_smmu_impl_ops tegra241_cmdqv_impl_ops = {
.get_secondary_cmdq = tegra241_cmdqv_get_cmdq,
.device_reset = tegra241_cmdqv_hw_reset,
.device_remove = tegra241_cmdqv_remove,
+ .drain_queues = tegra241_cmdqv_drain_vintf0_lvcmdqs,
/* For user-space use */
.hw_info = tegra241_cmdqv_hw_info,
.get_viommu_size = tegra241_cmdqv_get_vintf_size,
--
2.54.0.1013.g208068f2d8-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v8 04/12] iommu/tegra241-cmdqv: Restore PROD and CONS after resume
2026-06-01 21:58 [PATCH v8 00/10] iommu/arm-smmu-v3: Implement Runtime/System Sleep ops Pranjal Shrivastava
` (2 preceding siblings ...)
2026-06-01 21:59 ` [PATCH v8 03/12] iommu/tegra241-cmdqv: Add a helper to drain VCMDQs Pranjal Shrivastava
@ 2026-06-01 21:59 ` Pranjal Shrivastava
2026-06-01 21:59 ` [PATCH v8 05/12] iommu/arm-smmu-v3: Cache and restore MSI config Pranjal Shrivastava
` (8 subsequent siblings)
12 siblings, 0 replies; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-01 21:59 UTC (permalink / raw)
To: iommu
Cc: Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Nicolin Chen, Daniel Mentz, Ashish Mhetre,
linux-arm-kernel, Pranjal Shrivastava
From: Ashish Mhetre <amhetre@nvidia.com>
PROD and CONS indices for vcmdqs are getting set to 0 after resume.
Because of this the vcmdq is not consuming commands after resume.
Fix this by restoring PROD and CONS indices after resume from
saved pointers.
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Ashish Mhetre <amhetre@nvidia.com>
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
index cb1e75e4ee91..866cae7b73e5 100644
--- a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
+++ b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
@@ -511,6 +511,8 @@ static int tegra241_vcmdq_hw_init(struct tegra241_vcmdq *vcmdq)
/* Configure and enable VCMDQ */
writeq_relaxed(vcmdq->cmdq.q.q_base, REG_VCMDQ_PAGE1(vcmdq, BASE));
+ writel_relaxed(vcmdq->cmdq.q.llq.prod, REG_VCMDQ_PAGE0(vcmdq, PROD));
+ writel_relaxed(vcmdq->cmdq.q.llq.cons, REG_VCMDQ_PAGE0(vcmdq, CONS));
ret = vcmdq_write_config(vcmdq, VCMDQ_EN);
if (ret) {
--
2.54.0.1013.g208068f2d8-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v8 05/12] iommu/arm-smmu-v3: Cache and restore MSI config
2026-06-01 21:58 [PATCH v8 00/10] iommu/arm-smmu-v3: Implement Runtime/System Sleep ops Pranjal Shrivastava
` (3 preceding siblings ...)
2026-06-01 21:59 ` [PATCH v8 04/12] iommu/tegra241-cmdqv: Restore PROD and CONS after resume Pranjal Shrivastava
@ 2026-06-01 21:59 ` Pranjal Shrivastava
2026-06-01 21:59 ` [PATCH v8 06/12] iommu/arm-smmu-v3: Handle gerror during suspend Pranjal Shrivastava
` (7 subsequent siblings)
12 siblings, 0 replies; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-01 21:59 UTC (permalink / raw)
To: iommu
Cc: Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Nicolin Chen, Daniel Mentz, Ashish Mhetre,
linux-arm-kernel, Pranjal Shrivastava
The SMMU's MSI configuration registers (*_IRQ_CFGn) containing target
address, data and memory attributes lose their state when the SMMU is
powered down. We'll need to cache and restore their contents to ensure
that MSIs work after the system resumes.
To address this, cache the original `msi_msg` within the `msi_desc`
when the configuration is first written by `arm_smmu_write_msi_msg`.
This primarily includes the target address and data since the memory
attributes are fixed.
Introduce a new helper `arm_smmu_resume_msis` which will later be called
during the driver's resume callback. The helper would retrieve the
cached MSI message for each relevant interrupt (evtq, gerr, priq) via
get_cached_msi_msg & re-config the registers via arm_smmu_write_msi_msg.
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 37 +++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 8682be5060ed..93cee32f6c99 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4551,6 +4551,9 @@ static void arm_smmu_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
struct arm_smmu_device *smmu = dev_get_drvdata(dev);
phys_addr_t *cfg = arm_smmu_msi_cfg[desc->msi_index];
+ /* Cache the msi_msg for resume */
+ desc->msg = *msg;
+
doorbell = (((u64)msg->address_hi) << 32) | msg->address_lo;
doorbell &= MSI_CFG0_ADDR_MASK;
@@ -4559,6 +4562,40 @@ static void arm_smmu_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
writel_relaxed(ARM_SMMU_MEMATTR_DEVICE_nGnRE, smmu->base + cfg[2]);
}
+static void arm_smmu_resume_msi(struct arm_smmu_device *smmu,
+ unsigned int irq, const char *name)
+{
+ struct msi_desc *desc;
+ struct msi_msg msg;
+
+ if (!irq)
+ return;
+
+ desc = irq_get_msi_desc(irq);
+ if (!desc) {
+ dev_err(smmu->dev, "Failed to resume msi: %s", name);
+ return;
+ }
+
+ get_cached_msi_msg(irq, &msg);
+ arm_smmu_write_msi_msg(desc, &msg);
+}
+
+static void arm_smmu_resume_msis(struct arm_smmu_device *smmu)
+{
+ if (!(smmu->features & ARM_SMMU_FEAT_MSI))
+ return;
+
+ if (!dev_get_msi_domain(smmu->dev))
+ return;
+
+ arm_smmu_resume_msi(smmu, smmu->gerr_irq, "gerror");
+ arm_smmu_resume_msi(smmu, smmu->evtq.q.irq, "evtq");
+
+ if (smmu->features & ARM_SMMU_FEAT_PRI)
+ arm_smmu_resume_msi(smmu, smmu->priq.q.irq, "priq");
+}
+
static void arm_smmu_setup_msis(struct arm_smmu_device *smmu)
{
int ret, nvec = ARM_SMMU_MAX_MSIS;
--
2.54.0.1013.g208068f2d8-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v8 06/12] iommu/arm-smmu-v3: Handle gerror during suspend
2026-06-01 21:58 [PATCH v8 00/10] iommu/arm-smmu-v3: Implement Runtime/System Sleep ops Pranjal Shrivastava
` (4 preceding siblings ...)
2026-06-01 21:59 ` [PATCH v8 05/12] iommu/arm-smmu-v3: Cache and restore MSI config Pranjal Shrivastava
@ 2026-06-01 21:59 ` Pranjal Shrivastava
2026-06-02 0:15 ` Nicolin Chen
2026-06-01 21:59 ` [PATCH v8 07/12] iommu/arm-smmu-v3: Add CMDQ_PROD_STOP_FLAG to gate CMDQ submissions Pranjal Shrivastava
` (6 subsequent siblings)
12 siblings, 1 reply; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-01 21:59 UTC (permalink / raw)
To: iommu
Cc: Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Nicolin Chen, Daniel Mentz, Ashish Mhetre,
linux-arm-kernel, Pranjal Shrivastava, Jason Gunthorpe
The GERROR register's state might be lost when the SMMU is powered down
during runtime suspend. Handle any pending errors before suspending.
Refactor the gerror handling logic into a helper function and invoke it
from the runtime suspend callback after disabling the SMMU. This ensures
that any late-breaking gerrors are logged and ack'ed before the hardware
state is lost.
Suggested-by: Jason Gunthorpe <jgg@nvidia.com>
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 93cee32f6c99..6c8631e2f153 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2257,10 +2257,10 @@ static irqreturn_t arm_smmu_priq_thread(int irq, void *dev)
static int arm_smmu_device_disable(struct arm_smmu_device *smmu);
-static irqreturn_t arm_smmu_gerror_handler(int irq, void *dev)
+/* Lockless; must ensure that there are no concurrent callers */
+static irqreturn_t arm_smmu_handle_gerror(struct arm_smmu_device *smmu)
{
u32 gerror, gerrorn, active;
- struct arm_smmu_device *smmu = dev;
gerror = readl_relaxed(smmu->base + ARM_SMMU_GERROR);
gerrorn = readl_relaxed(smmu->base + ARM_SMMU_GERRORN);
@@ -2300,9 +2300,17 @@ static irqreturn_t arm_smmu_gerror_handler(int irq, void *dev)
arm_smmu_cmdq_skip_err(smmu);
writel(gerror, smmu->base + ARM_SMMU_GERRORN);
+
return IRQ_HANDLED;
}
+static irqreturn_t arm_smmu_gerror_handler(int irq, void *dev)
+{
+ struct arm_smmu_device *smmu = dev;
+
+ return arm_smmu_handle_gerror(smmu);
+}
+
static irqreturn_t arm_smmu_combined_irq_thread(int irq, void *dev)
{
struct arm_smmu_device *smmu = dev;
--
2.54.0.1013.g208068f2d8-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v8 06/12] iommu/arm-smmu-v3: Handle gerror during suspend
2026-06-01 21:59 ` [PATCH v8 06/12] iommu/arm-smmu-v3: Handle gerror during suspend Pranjal Shrivastava
@ 2026-06-02 0:15 ` Nicolin Chen
2026-06-02 3:31 ` Pranjal Shrivastava
0 siblings, 1 reply; 29+ messages in thread
From: Nicolin Chen @ 2026-06-02 0:15 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Daniel Mentz, Ashish Mhetre, linux-arm-kernel,
Jason Gunthorpe
On Mon, Jun 01, 2026 at 09:59:03PM +0000, Pranjal Shrivastava wrote:
> @@ -2300,9 +2300,17 @@ static irqreturn_t arm_smmu_gerror_handler(int irq, void *dev)
> arm_smmu_cmdq_skip_err(smmu);
>
> writel(gerror, smmu->base + ARM_SMMU_GERRORN);
> +
> return IRQ_HANDLED;
> }
This seems unnecessary.
Nicolin
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v8 06/12] iommu/arm-smmu-v3: Handle gerror during suspend
2026-06-02 0:15 ` Nicolin Chen
@ 2026-06-02 3:31 ` Pranjal Shrivastava
0 siblings, 0 replies; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-02 3:31 UTC (permalink / raw)
To: Nicolin Chen
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Daniel Mentz, Ashish Mhetre, linux-arm-kernel,
Jason Gunthorpe
On Mon, Jun 01, 2026 at 05:15:39PM -0700, Nicolin Chen wrote:
> On Mon, Jun 01, 2026 at 09:59:03PM +0000, Pranjal Shrivastava wrote:
> > @@ -2300,9 +2300,17 @@ static irqreturn_t arm_smmu_gerror_handler(int irq, void *dev)
> > arm_smmu_cmdq_skip_err(smmu);
> >
> > writel(gerror, smmu->base + ARM_SMMU_GERRORN);
> > +
> > return IRQ_HANDLED;
> > }
>
> This seems unnecessary.
Will remove,
Thanks,
Praan
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v8 07/12] iommu/arm-smmu-v3: Add CMDQ_PROD_STOP_FLAG to gate CMDQ submissions
2026-06-01 21:58 [PATCH v8 00/10] iommu/arm-smmu-v3: Implement Runtime/System Sleep ops Pranjal Shrivastava
` (5 preceding siblings ...)
2026-06-01 21:59 ` [PATCH v8 06/12] iommu/arm-smmu-v3: Handle gerror during suspend Pranjal Shrivastava
@ 2026-06-01 21:59 ` Pranjal Shrivastava
2026-06-01 21:59 ` [PATCH v8 08/12] iommu/tegra241-cmdqv: Add a helper to quiesce VCMDQs Pranjal Shrivastava
` (5 subsequent siblings)
12 siblings, 0 replies; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-01 21:59 UTC (permalink / raw)
To: iommu
Cc: Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Nicolin Chen, Daniel Mentz, Ashish Mhetre,
linux-arm-kernel, Pranjal Shrivastava
Introduce a new bit flag, CMDQ_PROD_STOP_FLAG (bit 30), in the command
queue's producer index to safely gate command submissions during device
suspension.
The flag embeds the suspend state directly into the existing global state
The flag checked in the cmpxchg loop in arm_smmu_cmdq_issue_cmdlist(),
which acts as a Point of Commitment, ensuring that no indices are
reserved or committed once the SMMU begins suspending.
This prevents a situation of "abandoned batches" where indices are
incremented but commands are never written, which would otherwise
lead to timeout during the drain poll.
Update queue_inc_prod_n() to preserve this flag during index
calculations, ensuring that any in-flight commands that successfully
passed the point of commitment can proceed to completion while the
flag remains set.
Suggested-by: Daniel Mentz <danielmentz@google.com>
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 20 +++++++++++++++++++-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 2 ++
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 6c8631e2f153..c4315bdde7d5 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -210,7 +210,8 @@ static int queue_sync_prod_in(struct arm_smmu_queue *q)
static u32 queue_inc_prod_n(struct arm_smmu_ll_queue *q, int n)
{
u32 prod = (Q_WRP(q, q->prod) | Q_IDX(q, q->prod)) + n;
- return Q_OVF(q->prod) | Q_WRP(q, prod) | Q_IDX(q, prod);
+
+ return Q_OVF(q->prod) | Q_STOP(q->prod) | Q_WRP(q, prod) | Q_IDX(q, prod);
}
static void queue_poll_init(struct arm_smmu_device *smmu,
@@ -718,8 +719,25 @@ int arm_smmu_cmdq_issue_cmdlist(struct arm_smmu_device *smmu,
do {
u64 old;
+ /*
+ * If the SMMU is suspended/suspending, any new CMDs are elided.
+ * This loop is the Point of Commitment. If we haven't cmpxchg'd
+ * our new indices yet, we can safely bail. Once the indices are
+ * committed, we MUST write valid commands to those slots to
+ * avoid indefinite polling in the drain function.
+ */
+ if (Q_STOP(llq.prod)) {
+ local_irq_restore(flags);
+ return 0;
+ }
+
while (!queue_has_space(&llq, n + sync)) {
local_irq_restore(flags);
+
+ /* Avoid waiting for space if the SMMU is suspending */
+ if (Q_STOP(READ_ONCE(cmdq->q.llq.prod)))
+ return 0;
+
if (arm_smmu_cmdq_poll_until_not_full(smmu, cmdq, &llq))
dev_err_ratelimited(smmu->dev, "CMDQ timeout\n");
local_irq_save(flags);
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index 24d5e28eea88..a3c8417c87d8 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -389,6 +389,8 @@ static inline unsigned int arm_smmu_cdtab_l2_idx(unsigned int ssid)
#define CMDQ_ERR_CERROR_ATC_INV_IDX 3
#define CMDQ_PROD_OWNED_FLAG Q_OVERFLOW_FLAG
+#define CMDQ_PROD_STOP_FLAG (1U << 30)
+#define Q_STOP(p) ((p) & CMDQ_PROD_STOP_FLAG)
struct arm_smmu_cmd {
u64 data[CMDQ_ENT_DWORDS];
--
2.54.0.1013.g208068f2d8-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v8 08/12] iommu/tegra241-cmdqv: Add a helper to quiesce VCMDQs
2026-06-01 21:58 [PATCH v8 00/10] iommu/arm-smmu-v3: Implement Runtime/System Sleep ops Pranjal Shrivastava
` (6 preceding siblings ...)
2026-06-01 21:59 ` [PATCH v8 07/12] iommu/arm-smmu-v3: Add CMDQ_PROD_STOP_FLAG to gate CMDQ submissions Pranjal Shrivastava
@ 2026-06-01 21:59 ` Pranjal Shrivastava
2026-06-02 0:14 ` Nicolin Chen
2026-06-01 21:59 ` [PATCH v8 09/12] iommu/arm-smmu-v3: Implement pm_runtime & system sleep ops Pranjal Shrivastava
` (4 subsequent siblings)
12 siblings, 1 reply; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-01 21:59 UTC (permalink / raw)
To: iommu
Cc: Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Nicolin Chen, Daniel Mentz, Ashish Mhetre,
linux-arm-kernel, Pranjal Shrivastava
The tegra241-cmdqv driver supports vCMDQs which need to be quiesced using
the STOP_FLAG as well. The current driver implementation only uses VINTF0
for vCMDQs owned by the kernel which need to be stopped. Add a helper
that sets the CMDQ_PROD_STOP_FLAG on the vCMDQs as well.
Consolidate this logic by renaming the implementation hook to
quiesce_and_drain_queues and ensuring that the tegra241-cmdqv driver
gates all active local virtual queues before starting the drain loop.
Additionally, clear the STOP_FLAG in tegra241_vcmdq_hw_init() as a part
of tegra241_cmdqv_hw_reset.
Suggested-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 4 +--
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 2 +-
.../iommu/arm/arm-smmu-v3/tegra241-cmdqv.c | 32 +++++++++++++++++--
3 files changed, 33 insertions(+), 5 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index c4315bdde7d5..d31e50b64b50 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -937,8 +937,8 @@ static int arm_smmu_drain_queues(struct arm_smmu_device *smmu)
goto out;
/* Drain all implementation-specific queues */
- if (smmu->impl_ops && smmu->impl_ops->drain_queues)
- ret = smmu->impl_ops->drain_queues(smmu);
+ if (smmu->impl_ops && smmu->impl_ops->quiesce_and_drain_queues)
+ ret = smmu->impl_ops->quiesce_and_drain_queues(smmu);
out:
return ret;
}
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index a3c8417c87d8..719e499c72ce 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -887,7 +887,7 @@ struct arm_smmu_impl_ops {
size_t (*get_viommu_size)(enum iommu_viommu_type viommu_type);
int (*vsmmu_init)(struct arm_vsmmu *vsmmu,
const struct iommu_user_data *user_data);
- int (*drain_queues)(struct arm_smmu_device *smmu);
+ int (*quiesce_and_drain_queues)(struct arm_smmu_device *smmu);
};
/* An SMMUv3 instance */
diff --git a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
index 866cae7b73e5..536aeeb63202 100644
--- a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
+++ b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
@@ -414,6 +414,28 @@ tegra241_cmdqv_get_cmdq(struct arm_smmu_device *smmu,
return &vcmdq->cmdq;
}
+static void tegra241_cmdqv_quiesce_vintf0_lvcmdqs(struct arm_smmu_device *smmu)
+{
+ struct tegra241_cmdqv *cmdqv =
+ container_of(smmu, struct tegra241_cmdqv, smmu);
+ struct tegra241_vintf *vintf = cmdqv->vintfs[0];
+ u16 lidx;
+
+ if (!READ_ONCE(vintf->enabled))
+ return;
+
+ for (lidx = 0; lidx < cmdqv->num_lvcmdqs_per_vintf; lidx++) {
+ struct tegra241_vcmdq *vcmdq = vintf->lvcmdqs[lidx];
+
+ if (!vcmdq || !READ_ONCE(vcmdq->enabled))
+ continue;
+
+ atomic_or(CMDQ_PROD_STOP_FLAG, &vcmdq->cmdq.q.llq.atomic.prod);
+ }
+
+ return;
+}
+
static int tegra241_cmdqv_drain_vintf0_lvcmdqs(struct arm_smmu_device *smmu)
{
struct tegra241_cmdqv *cmdqv =
@@ -426,6 +448,8 @@ static int tegra241_cmdqv_drain_vintf0_lvcmdqs(struct arm_smmu_device *smmu)
if (!READ_ONCE(vintf->enabled))
return 0;
+ tegra241_cmdqv_quiesce_vintf0_lvcmdqs(smmu);
+
for (lidx = 0; lidx < cmdqv->num_lvcmdqs_per_vintf; lidx++) {
struct tegra241_vcmdq *vcmdq = vintf->lvcmdqs[lidx];
@@ -511,9 +535,13 @@ static int tegra241_vcmdq_hw_init(struct tegra241_vcmdq *vcmdq)
/* Configure and enable VCMDQ */
writeq_relaxed(vcmdq->cmdq.q.q_base, REG_VCMDQ_PAGE1(vcmdq, BASE));
- writel_relaxed(vcmdq->cmdq.q.llq.prod, REG_VCMDQ_PAGE0(vcmdq, PROD));
+ writel_relaxed(vcmdq->cmdq.q.llq.prod & CMDQ_PROD_IDX_MASK,
+ REG_VCMDQ_PAGE0(vcmdq, PROD));
writel_relaxed(vcmdq->cmdq.q.llq.cons, REG_VCMDQ_PAGE0(vcmdq, CONS));
+ /* Clear the CMDQ_PROD_STOP_FLAG */
+ atomic_andnot(CMDQ_PROD_STOP_FLAG, &vcmdq->cmdq.q.llq.atomic.prod);
+
ret = vcmdq_write_config(vcmdq, VCMDQ_EN);
if (ret) {
dev_err(vcmdq->cmdqv->dev,
@@ -873,7 +901,7 @@ static struct arm_smmu_impl_ops tegra241_cmdqv_impl_ops = {
.get_secondary_cmdq = tegra241_cmdqv_get_cmdq,
.device_reset = tegra241_cmdqv_hw_reset,
.device_remove = tegra241_cmdqv_remove,
- .drain_queues = tegra241_cmdqv_drain_vintf0_lvcmdqs,
+ .quiesce_and_drain_queues = tegra241_cmdqv_drain_vintf0_lvcmdqs,
/* For user-space use */
.hw_info = tegra241_cmdqv_hw_info,
.get_viommu_size = tegra241_cmdqv_get_vintf_size,
--
2.54.0.1013.g208068f2d8-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v8 08/12] iommu/tegra241-cmdqv: Add a helper to quiesce VCMDQs
2026-06-01 21:59 ` [PATCH v8 08/12] iommu/tegra241-cmdqv: Add a helper to quiesce VCMDQs Pranjal Shrivastava
@ 2026-06-02 0:14 ` Nicolin Chen
2026-06-02 3:37 ` Pranjal Shrivastava
0 siblings, 1 reply; 29+ messages in thread
From: Nicolin Chen @ 2026-06-02 0:14 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Daniel Mentz, Ashish Mhetre, linux-arm-kernel
On Mon, Jun 01, 2026 at 09:59:05PM +0000, Pranjal Shrivastava wrote:
> @@ -887,7 +887,7 @@ struct arm_smmu_impl_ops {
> size_t (*get_viommu_size)(enum iommu_viommu_type viommu_type);
> int (*vsmmu_init)(struct arm_vsmmu *vsmmu,
> const struct iommu_user_data *user_data);
> - int (*drain_queues)(struct arm_smmu_device *smmu);
> + int (*quiesce_and_drain_queues)(struct arm_smmu_device *smmu);
Hmm? What's the point in PATCH-3 adding drain_queues then?
Nicolin
^ permalink raw reply [flat|nested] 29+ messages in thread* Re: [PATCH v8 08/12] iommu/tegra241-cmdqv: Add a helper to quiesce VCMDQs
2026-06-02 0:14 ` Nicolin Chen
@ 2026-06-02 3:37 ` Pranjal Shrivastava
2026-06-02 5:59 ` Nicolin Chen
0 siblings, 1 reply; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-02 3:37 UTC (permalink / raw)
To: Nicolin Chen
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Daniel Mentz, Ashish Mhetre, linux-arm-kernel
On Mon, Jun 01, 2026 at 05:14:24PM -0700, Nicolin Chen wrote:
> On Mon, Jun 01, 2026 at 09:59:05PM +0000, Pranjal Shrivastava wrote:
> > @@ -887,7 +887,7 @@ struct arm_smmu_impl_ops {
> > size_t (*get_viommu_size)(enum iommu_viommu_type viommu_type);
> > int (*vsmmu_init)(struct arm_vsmmu *vsmmu,
> > const struct iommu_user_data *user_data);
> > - int (*drain_queues)(struct arm_smmu_device *smmu);
> > + int (*quiesce_and_drain_queues)(struct arm_smmu_device *smmu);
>
> Hmm? What's the point in PATCH-3 adding drain_queues then?
Well, you suggested in the previous version that STOP_FLAG needs to be
set for the secondary CMDQs too
All we're doing here is renaming this function as it also sets the
STOP_FLAG on impl-specific CMDQs now. We can still call it drain_queues
if you prefer not renaming this.
On the other hand, we can add a dedicated queisce_queues() op too that's
called before draining queues. I found that to be unnecessary as I'd rather
have one impl-specific op handle everything for impl-specific queues.
Thanks,
Praan
^ permalink raw reply [flat|nested] 29+ messages in thread* Re: [PATCH v8 08/12] iommu/tegra241-cmdqv: Add a helper to quiesce VCMDQs
2026-06-02 3:37 ` Pranjal Shrivastava
@ 2026-06-02 5:59 ` Nicolin Chen
2026-06-02 6:21 ` Pranjal Shrivastava
0 siblings, 1 reply; 29+ messages in thread
From: Nicolin Chen @ 2026-06-02 5:59 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Daniel Mentz, Ashish Mhetre, linux-arm-kernel
On Tue, Jun 02, 2026 at 03:37:43AM +0000, Pranjal Shrivastava wrote:
> On Mon, Jun 01, 2026 at 05:14:24PM -0700, Nicolin Chen wrote:
> > On Mon, Jun 01, 2026 at 09:59:05PM +0000, Pranjal Shrivastava wrote:
> > > @@ -887,7 +887,7 @@ struct arm_smmu_impl_ops {
> > > size_t (*get_viommu_size)(enum iommu_viommu_type viommu_type);
> > > int (*vsmmu_init)(struct arm_vsmmu *vsmmu,
> > > const struct iommu_user_data *user_data);
> > > - int (*drain_queues)(struct arm_smmu_device *smmu);
> > > + int (*quiesce_and_drain_queues)(struct arm_smmu_device *smmu);
> >
> > Hmm? What's the point in PATCH-3 adding drain_queues then?
>
> Well, you suggested in the previous version that STOP_FLAG needs to be
> set for the secondary CMDQs too
>
> All we're doing here is renaming this function as it also sets the
> STOP_FLAG on impl-specific CMDQs now. We can still call it drain_queues
> if you prefer not renaming this.
>
> On the other hand, we can add a dedicated queisce_queues() op too that's
> called before draining queues. I found that to be unnecessary as I'd rather
> have one impl-specific op handle everything for impl-specific queues.
The renaming is very confusing... I'd leave it as drain_queues.
Arguably,
atomic_or(CMDQ_PROD_STOP_FLAG, &vcmdq->cmdq.q.llq.atomic.prod);
could be even stuffed into tegra241_cmdqv_drain_vintf0_lvcmdqs(),
where there is a for loop already.
Nicolin
^ permalink raw reply [flat|nested] 29+ messages in thread* Re: [PATCH v8 08/12] iommu/tegra241-cmdqv: Add a helper to quiesce VCMDQs
2026-06-02 5:59 ` Nicolin Chen
@ 2026-06-02 6:21 ` Pranjal Shrivastava
2026-06-02 6:29 ` Nicolin Chen
0 siblings, 1 reply; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-02 6:21 UTC (permalink / raw)
To: Nicolin Chen
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Daniel Mentz, Ashish Mhetre, linux-arm-kernel
On Mon, Jun 01, 2026 at 10:59:33PM -0700, Nicolin Chen wrote:
> On Tue, Jun 02, 2026 at 03:37:43AM +0000, Pranjal Shrivastava wrote:
> > On Mon, Jun 01, 2026 at 05:14:24PM -0700, Nicolin Chen wrote:
> > > On Mon, Jun 01, 2026 at 09:59:05PM +0000, Pranjal Shrivastava wrote:
> > > > @@ -887,7 +887,7 @@ struct arm_smmu_impl_ops {
> > > > size_t (*get_viommu_size)(enum iommu_viommu_type viommu_type);
> > > > int (*vsmmu_init)(struct arm_vsmmu *vsmmu,
> > > > const struct iommu_user_data *user_data);
> > > > - int (*drain_queues)(struct arm_smmu_device *smmu);
> > > > + int (*quiesce_and_drain_queues)(struct arm_smmu_device *smmu);
> > >
> > > Hmm? What's the point in PATCH-3 adding drain_queues then?
> >
> > Well, you suggested in the previous version that STOP_FLAG needs to be
> > set for the secondary CMDQs too
> >
> > All we're doing here is renaming this function as it also sets the
> > STOP_FLAG on impl-specific CMDQs now. We can still call it drain_queues
> > if you prefer not renaming this.
> >
> > On the other hand, we can add a dedicated queisce_queues() op too that's
> > called before draining queues. I found that to be unnecessary as I'd rather
> > have one impl-specific op handle everything for impl-specific queues.
>
> The renaming is very confusing... I'd leave it as drain_queues.
Ack.
>
> Arguably,
> atomic_or(CMDQ_PROD_STOP_FLAG, &vcmdq->cmdq.q.llq.atomic.prod);
> could be even stuffed into tegra241_cmdqv_drain_vintf0_lvcmdqs(),
> where there is a for loop already.
That would be slow IMO, because we'd set the the STOP_FLAG on ONE queue,
then wait for it to drain and repeat this pattern for all vCMDQs.
Ideally, we should set the STOP_FLAG on all vCMDQs first and then wait
for them to drain.
Thanks,
Praan
^ permalink raw reply [flat|nested] 29+ messages in thread* Re: [PATCH v8 08/12] iommu/tegra241-cmdqv: Add a helper to quiesce VCMDQs
2026-06-02 6:21 ` Pranjal Shrivastava
@ 2026-06-02 6:29 ` Nicolin Chen
0 siblings, 0 replies; 29+ messages in thread
From: Nicolin Chen @ 2026-06-02 6:29 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Daniel Mentz, Ashish Mhetre, linux-arm-kernel
On Tue, Jun 02, 2026 at 06:21:45AM +0000, Pranjal Shrivastava wrote:
> On Mon, Jun 01, 2026 at 10:59:33PM -0700, Nicolin Chen wrote:
> > On Tue, Jun 02, 2026 at 03:37:43AM +0000, Pranjal Shrivastava wrote:
> > > On Mon, Jun 01, 2026 at 05:14:24PM -0700, Nicolin Chen wrote:
> > Arguably,
> > atomic_or(CMDQ_PROD_STOP_FLAG, &vcmdq->cmdq.q.llq.atomic.prod);
> > could be even stuffed into tegra241_cmdqv_drain_vintf0_lvcmdqs(),
> > where there is a for loop already.
>
> That would be slow IMO, because we'd set the the STOP_FLAG on ONE queue,
> then wait for it to drain and repeat this pattern for all vCMDQs.
> Ideally, we should set the STOP_FLAG on all vCMDQs first and then wait
> for them to drain.
That sounds plausible. Please leave a note for the double loops.
Nicolin
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v8 09/12] iommu/arm-smmu-v3: Implement pm_runtime & system sleep ops
2026-06-01 21:58 [PATCH v8 00/10] iommu/arm-smmu-v3: Implement Runtime/System Sleep ops Pranjal Shrivastava
` (7 preceding siblings ...)
2026-06-01 21:59 ` [PATCH v8 08/12] iommu/tegra241-cmdqv: Add a helper to quiesce VCMDQs Pranjal Shrivastava
@ 2026-06-01 21:59 ` Pranjal Shrivastava
2026-06-02 5:25 ` Daniel Mentz
2026-06-01 21:59 ` [PATCH v8 10/12] iommu/arm-smmu-v3: Enable pm_runtime and setup devlinks Pranjal Shrivastava
` (3 subsequent siblings)
12 siblings, 1 reply; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-01 21:59 UTC (permalink / raw)
To: iommu
Cc: Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Nicolin Chen, Daniel Mentz, Ashish Mhetre,
linux-arm-kernel, Pranjal Shrivastava
Implement pm_runtime and system sleep ops for arm-smmu-v3.
The suspend callback configures the SMMU to abort new transactions,
disables the main translation unit and then drains the command queue
to ensure completion of any in-flight commands. A software gate
(STOP_FLAG) and synchronization barriers are used to quiesce the command
submission pipeline and ensure state consistency before power-off.
To prevent software metadata flags from leaking into physical registers
or polluting the tracking pointer, a newly introduced bitmask
(CMDQ_PROD_IDX_MASK) is applied to all register writes and tracking
updates.
The resume callback restores the MSI configuration and performs a full
device reset via `arm_smmu_device_reset` to bring the SMMU back to an
operational state. The MSIs are cached during the msi_write and are
restored during the resume operation by using the helper. The STOP_FLAG
is cleared only after the CMDQ is enabled in hardware.
Suggested-by: Daniel Mentz <danielmentz@google.com>
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 172 +++++++++++++++++++-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 18 ++
2 files changed, 188 insertions(+), 2 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index d31e50b64b50..542de3a3173a 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -28,6 +28,7 @@
#include <linux/platform_device.h>
#include <linux/sort.h>
#include <linux/string_choices.h>
+#include <linux/pm_runtime.h>
#include <kunit/visibility.h>
#include <uapi/linux/iommufd.h>
@@ -110,6 +111,40 @@ static const char * const event_class_str[] = {
static int arm_smmu_alloc_cd_tables(struct arm_smmu_master *master);
static bool arm_smmu_ats_supported(struct arm_smmu_master *master);
+/* Runtime PM helpers */
+__maybe_unused static int arm_smmu_rpm_get(struct arm_smmu_device *smmu)
+{
+ int ret;
+
+ if (!pm_runtime_enabled(smmu->dev))
+ return 0;
+
+ ret = pm_runtime_resume_and_get(smmu->dev);
+ if (ret < 0) {
+ dev_err(smmu->dev, "failed to resume device: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+__maybe_unused static void arm_smmu_rpm_put(struct arm_smmu_device *smmu)
+{
+ int ret;
+
+ if (!pm_runtime_enabled(smmu->dev))
+ return;
+
+ ret = pm_runtime_put_autosuspend(smmu->dev);
+ if (ret < 0)
+ dev_err(smmu->dev, "failed to suspend device: %d\n", ret);
+}
+
+static inline u32 arm_smmu_cmdq_owner_prod_idx(struct arm_smmu_cmdq *cmdq)
+{
+ return atomic_read(&cmdq->owner_prod) & CMDQ_PROD_IDX_MASK;
+}
+
static void parse_driver_options(struct arm_smmu_device *smmu)
{
int i = 0;
@@ -789,7 +824,8 @@ int arm_smmu_cmdq_issue_cmdlist(struct arm_smmu_device *smmu,
/* b. Stop gathering work by clearing the owned flag */
prod = atomic_fetch_andnot_relaxed(CMDQ_PROD_OWNED_FLAG,
&cmdq->q.llq.atomic.prod);
- prod &= ~CMDQ_PROD_OWNED_FLAG;
+ /* Strip all metadata flags */
+ prod &= CMDQ_PROD_IDX_MASK;
/*
* c. Wait for any gathered work to be written to the queue.
@@ -4828,7 +4864,8 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
/* Command queue */
writeq_relaxed(smmu->cmdq.q.q_base, smmu->base + ARM_SMMU_CMDQ_BASE);
- writel_relaxed(smmu->cmdq.q.llq.prod, smmu->base + ARM_SMMU_CMDQ_PROD);
+ writel_relaxed(smmu->cmdq.q.llq.prod & CMDQ_PROD_IDX_MASK,
+ smmu->base + ARM_SMMU_CMDQ_PROD);
writel_relaxed(smmu->cmdq.q.llq.cons, smmu->base + ARM_SMMU_CMDQ_CONS);
enables = CR0_CMDQEN;
@@ -4839,6 +4876,10 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
return ret;
}
+ /* Clear any flags from the previous life */
+ atomic_andnot(CMDQ_PROD_STOP_FLAG, &smmu->cmdq.owner_prod);
+ atomic_andnot(CMDQ_PROD_STOP_FLAG, &smmu->cmdq.q.llq.atomic.prod);
+
/* Invalidate any cached configuration */
arm_smmu_cmdq_issue_cmd_with_sync(smmu, arm_smmu_make_cmd_cfgi_all());
@@ -4898,6 +4939,21 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu)
if (is_kdump_kernel())
enables &= ~(CR0_EVTQEN | CR0_PRIQEN);
+ /*
+ * While the SMMU was suspended, concurrent CPU threads may have
+ * updated in-memory structures (such as STEs, CDs, and PTEs).
+ * Any invalidations corresponding to those updates were safely
+ * elided because the command queue was stopped (STOP_FLAG == 1).
+ *
+ * Since the reset invalidate-all commands above have fully cleared
+ * the HW TLBs and config caches, the SMMU will fetch these descriptors
+ * directly from RAM as soon as translation is enabled.
+ *
+ * Add a memory barrier to collect all prior RAM writes to ensure the
+ * SMMU sees a consistent view of memory before translation is enabled.
+ */
+ smp_mb();
+
/* Enable the SMMU interface */
enables |= CR0_SMMUEN;
ret = arm_smmu_write_reg_sync(smmu, enables, ARM_SMMU_CR0,
@@ -5580,6 +5636,117 @@ static void arm_smmu_device_shutdown(struct platform_device *pdev)
arm_smmu_device_disable(smmu);
}
+static int __maybe_unused arm_smmu_runtime_suspend(struct device *dev)
+{
+ struct arm_smmu_device *smmu = dev_get_drvdata(dev);
+ struct arm_smmu_cmdq *cmdq = &smmu->cmdq;
+ int timeout = ARM_SMMU_SUSPEND_TIMEOUT_US;
+ u32 enables, target;
+ int ret;
+
+ /* Abort all transactions before disable to avoid spurious bypass */
+ arm_smmu_update_gbpa(smmu, GBPA_ABORT, 0);
+
+ /* Disable the SMMU via CR0.EN and all queues except CMDQ */
+ enables = CR0_CMDQEN;
+ ret = arm_smmu_write_reg_sync(smmu, enables, ARM_SMMU_CR0, ARM_SMMU_CR0ACK);
+ if (ret) {
+ dev_err(smmu->dev, "failed to disable SMMU\n");
+ return ret;
+ }
+
+ /*
+ * At this point the SMMU is completely disabled and won't access
+ * any translation/config structures, even speculative accesses
+ * aren't performed as per the IHI0070 spec (section 6.3.9.6).
+ */
+
+ /* Mark the CMDQ to stop and get the target index before the stop */
+ target = atomic_fetch_or_relaxed(CMDQ_PROD_STOP_FLAG, &cmdq->q.llq.atomic.prod);
+ target &= CMDQ_PROD_IDX_MASK;
+
+
+ /* Wait for the last committed owner to reach the hardware */
+ while ((arm_smmu_cmdq_owner_prod_idx(cmdq) != target) && --timeout)
+ udelay(1);
+
+ /*
+ * Entering suspend implies no active clients. A timeout here
+ * indicates a fatal CMDQ lockup or hardware stall. We proceed
+ * anyway to prioritize memory safety (avoiding stale TLBs)
+ */
+ if (!timeout)
+ dev_err(smmu->dev, "cmdq owner wait timeout, (check runtime PM + devlinks)\n");
+
+ /* Drain the CMDQs */
+ ret = arm_smmu_drain_queues(smmu);
+ if (ret)
+ dev_warn(smmu->dev, "failed to drain queues, forcing suspend\n");
+
+ /* Wait for cmdq->lock == 0 to ensure last CMDQ_CONS_REG is written */
+ timeout = ARM_SMMU_SUSPEND_TIMEOUT_US;
+ while (atomic_read(&cmdq->lock) != 0 && --timeout)
+ udelay(1);
+
+ /* Timing out here implies misconfigured Runtime PM or broken devlinks */
+ if (!timeout)
+ dev_err(smmu->dev, "cmdq lock != 0, forcing suspend. Polling CPUs may fault.\n");
+
+ /* Disable everything */
+ arm_smmu_device_disable(smmu);
+
+ /* Handle any pending gerrors before powering down */
+ arm_smmu_handle_gerror(smmu);
+
+ /* Avoid consuming stale commands if we timed-out to drain the queues */
+ if (ret || !timeout)
+ cmdq->q.llq.cons = cmdq->q.llq.prod & CMDQ_PROD_IDX_MASK;
+
+ dev_dbg(dev, "suspended smmu\n");
+
+ return 0;
+}
+
+static int __maybe_unused arm_smmu_runtime_resume(struct device *dev)
+{
+ struct arm_smmu_device *smmu = dev_get_drvdata(dev);
+ int ret;
+
+ /* Re-configure MSIs */
+ arm_smmu_resume_msis(smmu);
+
+ /* Clears the CMDQ_PROD_STOP_FLAG as well */
+ ret = arm_smmu_device_reset(smmu);
+ if (ret)
+ dev_err(dev, "failed to reset during resume operation: %d\n", ret);
+
+ dev_dbg(dev, "resumed smmu\n");
+
+ return ret;
+}
+
+static int __maybe_unused arm_smmu_pm_suspend(struct device *dev)
+{
+ if (pm_runtime_suspended(dev))
+ return 0;
+
+ return arm_smmu_runtime_suspend(dev);
+}
+
+static int __maybe_unused arm_smmu_pm_resume(struct device *dev)
+{
+ if (pm_runtime_suspended(dev))
+ return 0;
+
+ return arm_smmu_runtime_resume(dev);
+}
+
+static const struct dev_pm_ops arm_smmu_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(arm_smmu_pm_suspend, arm_smmu_pm_resume)
+ SET_RUNTIME_PM_OPS(arm_smmu_runtime_suspend,
+ arm_smmu_runtime_resume, NULL)
+};
+
static const struct of_device_id arm_smmu_of_match[] = {
{ .compatible = "arm,smmu-v3", },
{ },
@@ -5596,6 +5763,7 @@ static struct platform_driver arm_smmu_driver = {
.driver = {
.name = "arm-smmu-v3",
.of_match_table = arm_smmu_of_match,
+ .pm = &arm_smmu_pm_ops,
.suppress_bind_attrs = true,
},
.probe = arm_smmu_device_probe,
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index 719e499c72ce..51a3281a1e46 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -392,6 +392,9 @@ static inline unsigned int arm_smmu_cdtab_l2_idx(unsigned int ssid)
#define CMDQ_PROD_STOP_FLAG (1U << 30)
#define Q_STOP(p) ((p) & CMDQ_PROD_STOP_FLAG)
+/* Mask out software-only metadata flags to get the pure queue index/wrap bits */
+#define CMDQ_PROD_IDX_MASK ~(CMDQ_PROD_STOP_FLAG | CMDQ_PROD_OWNED_FLAG)
+
struct arm_smmu_cmd {
u64 data[CMDQ_ENT_DWORDS];
};
@@ -655,11 +658,14 @@ arm_smmu_make_cmd_tlbi(enum arm_smmu_cmdq_opcode op, u16 asid, u16 vmid)
/* High-level queue structures */
#define ARM_SMMU_POLL_TIMEOUT_US 1000000 /* 1s! */
+#define ARM_SMMU_SUSPEND_TIMEOUT_US 1000000 /* 1s! */
#define ARM_SMMU_POLL_SPIN_COUNT 10
#define MSI_IOVA_BASE 0x8000000
#define MSI_IOVA_LENGTH 0x100000
+#define RPM_AUTOSUSPEND_DELAY_MS 15
+
struct arm_smmu_ll_queue {
union {
u64 val;
@@ -1217,6 +1223,18 @@ int arm_smmu_cmdq_issue_cmdlist(struct arm_smmu_device *smmu,
struct arm_smmu_cmd *cmds, int n,
bool sync);
+/*
+ * Lockless pre-check to elide invalidations if SMMU is suspended.
+ * Races with concurrent suspend are benign: the cmpxchg loop in
+ * arm_smmu_cmdq_issue_cmdlist() acts as the true commit point.
+ * If we lose the race, that loop observes Q_STOP == 1 and safely
+ * drops the command. If we win, the suspend thread waits for us.
+ */
+static inline bool arm_smmu_can_elide(struct arm_smmu_device *smmu)
+{
+ return !!Q_STOP(READ_ONCE(smmu->cmdq.q.llq.prod));
+}
+
#ifdef CONFIG_ARM_SMMU_V3_SVA
bool arm_smmu_sva_supported(struct arm_smmu_device *smmu);
void arm_smmu_sva_notifier_synchronize(void);
--
2.54.0.1013.g208068f2d8-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v8 09/12] iommu/arm-smmu-v3: Implement pm_runtime & system sleep ops
2026-06-01 21:59 ` [PATCH v8 09/12] iommu/arm-smmu-v3: Implement pm_runtime & system sleep ops Pranjal Shrivastava
@ 2026-06-02 5:25 ` Daniel Mentz
0 siblings, 0 replies; 29+ messages in thread
From: Daniel Mentz @ 2026-06-02 5:25 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Nicolin Chen, Ashish Mhetre, linux-arm-kernel
On Mon, Jun 1, 2026 at 2:59 PM Pranjal Shrivastava <praan@google.com> wrote:
> + /* Drain the CMDQs */
> + ret = arm_smmu_drain_queues(smmu);
> + if (ret)
> + dev_warn(smmu->dev, "failed to drain queues, forcing suspend\n");
Can arm_smmu_drain_queues() be called after cmdq->lock is 0? I think
that way we can be sure that no other thread updates smmu->cmdq.q.cons
> +
> + /* Wait for cmdq->lock == 0 to ensure last CMDQ_CONS_REG is written */
> + timeout = ARM_SMMU_SUSPEND_TIMEOUT_US;
> + while (atomic_read(&cmdq->lock) != 0 && --timeout)
> + udelay(1);
> +
> + /* Timing out here implies misconfigured Runtime PM or broken devlinks */
> + if (!timeout)
> + dev_err(smmu->dev, "cmdq lock != 0, forcing suspend. Polling CPUs may fault.\n");
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v8 10/12] iommu/arm-smmu-v3: Enable pm_runtime and setup devlinks
2026-06-01 21:58 [PATCH v8 00/10] iommu/arm-smmu-v3: Implement Runtime/System Sleep ops Pranjal Shrivastava
` (8 preceding siblings ...)
2026-06-01 21:59 ` [PATCH v8 09/12] iommu/arm-smmu-v3: Implement pm_runtime & system sleep ops Pranjal Shrivastava
@ 2026-06-01 21:59 ` Pranjal Shrivastava
2026-06-01 21:59 ` [PATCH v8 11/12] iommu/arm-smmu-v3: Invoke pm_runtime before hw access Pranjal Shrivastava
` (2 subsequent siblings)
12 siblings, 0 replies; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-01 21:59 UTC (permalink / raw)
To: iommu
Cc: Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Nicolin Chen, Daniel Mentz, Ashish Mhetre,
linux-arm-kernel, Pranjal Shrivastava
Enable PM runtime for SMMUs having a power-domain during smmu probe.
Add a devlink between the clients and SMMU device. The absence of a
power domain effectively disables runtime power management.
Reviewed-by: Mostafa Saleh <smostafa@google.com>
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 542de3a3173a..24a1bcc8974f 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4243,6 +4243,9 @@ static struct iommu_device *arm_smmu_probe_device(struct device *dev)
pci_prepare_ats(to_pci_dev(dev), stu);
}
+ device_link_add(dev, smmu->dev,
+ DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER);
+
return &smmu->iommu;
err_free_master:
@@ -5607,6 +5610,13 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
goto err_free_sysfs;
}
+ if (dev->pm_domain) {
+ pm_runtime_set_active(dev);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_autosuspend_delay(dev, RPM_AUTOSUSPEND_DELAY_MS);
+ pm_runtime_enable(dev);
+ }
+
return 0;
err_free_sysfs:
--
2.54.0.1013.g208068f2d8-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread* [PATCH v8 11/12] iommu/arm-smmu-v3: Invoke pm_runtime before hw access
2026-06-01 21:58 [PATCH v8 00/10] iommu/arm-smmu-v3: Implement Runtime/System Sleep ops Pranjal Shrivastava
` (9 preceding siblings ...)
2026-06-01 21:59 ` [PATCH v8 10/12] iommu/arm-smmu-v3: Enable pm_runtime and setup devlinks Pranjal Shrivastava
@ 2026-06-01 21:59 ` Pranjal Shrivastava
2026-06-02 0:24 ` Nicolin Chen
2026-06-01 21:59 ` [PATCH v8 12/12] iommu/arm-smmu-v3: Add KUnit unit tests for Runtime PM Pranjal Shrivastava
2026-06-02 6:03 ` [PATCH v8 00/10] iommu/arm-smmu-v3: Implement Runtime/System Sleep ops Nicolin Chen
12 siblings, 1 reply; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-01 21:59 UTC (permalink / raw)
To: iommu
Cc: Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Nicolin Chen, Daniel Mentz, Ashish Mhetre,
linux-arm-kernel, Pranjal Shrivastava
Invoke the pm_runtime helpers at all places before accessing the hw.
The idea is to invoke runtime_pm helpers at common points which are used
by exposed ops or interrupt handlers. TLB and CFG invalidations are
elided if the SMMU is suspended by observing the CMDQ_PROD_STOP_FLAG.
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
.../arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 10 +-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 101 ++++++++++++++++--
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 5 +-
3 files changed, 108 insertions(+), 8 deletions(-)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c
index 1e9f7d2de344..2d729034e3bb 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c
@@ -15,6 +15,11 @@ void *arm_smmu_hw_info(struct device *dev, u32 *length,
struct iommu_hw_info_arm_smmuv3 *info;
u32 __iomem *base_idr;
unsigned int i;
+ int ret;
+
+ ret = arm_smmu_rpm_get(master->smmu);
+ if (ret < 0)
+ return ERR_PTR(-EIO);
if (*type != IOMMU_HW_INFO_TYPE_DEFAULT &&
*type != IOMMU_HW_INFO_TYPE_ARM_SMMUV3) {
@@ -24,8 +29,10 @@ void *arm_smmu_hw_info(struct device *dev, u32 *length,
}
info = kzalloc_obj(*info);
- if (!info)
+ if (!info) {
+ arm_smmu_rpm_put(master->smmu);
return ERR_PTR(-ENOMEM);
+ }
base_idr = master->smmu->base + ARM_SMMU_IDR0;
for (i = 0; i <= 5; i++)
@@ -36,6 +43,7 @@ void *arm_smmu_hw_info(struct device *dev, u32 *length,
*length = sizeof(*info);
*type = IOMMU_HW_INFO_TYPE_ARM_SMMUV3;
+ arm_smmu_rpm_put(master->smmu);
return info;
}
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 24a1bcc8974f..2e260f85b8fd 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -112,7 +112,7 @@ static int arm_smmu_alloc_cd_tables(struct arm_smmu_master *master);
static bool arm_smmu_ats_supported(struct arm_smmu_master *master);
/* Runtime PM helpers */
-__maybe_unused static int arm_smmu_rpm_get(struct arm_smmu_device *smmu)
+int arm_smmu_rpm_get(struct arm_smmu_device *smmu)
{
int ret;
@@ -128,7 +128,7 @@ __maybe_unused static int arm_smmu_rpm_get(struct arm_smmu_device *smmu)
return 0;
}
-__maybe_unused static void arm_smmu_rpm_put(struct arm_smmu_device *smmu)
+void arm_smmu_rpm_put(struct arm_smmu_device *smmu)
{
int ret;
@@ -983,7 +983,9 @@ static void arm_smmu_page_response(struct device *dev, struct iopf_fault *unused
struct iommu_page_response *resp)
{
struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+ struct arm_smmu_device *smmu = master->smmu;
u8 resume_resp;
+ int ret;
if (WARN_ON(!master->stall_enabled))
return;
@@ -1001,6 +1003,25 @@ static void arm_smmu_page_response(struct device *dev, struct iopf_fault *unused
break;
}
+ /*
+ * The SMMU is guaranteed to be active via device_link if any master is
+ * active. Furthermore, on suspend we set GBPA to abort, flushing any
+ * pending stalled transactions.
+ *
+ * Receiving a page fault while suspended implies a bug in the power
+ * dependency chain or a stale event. Since the SMMU is powered down
+ * and the command queue is inaccessible, we cannot issue the
+ * RESUME command and must drop it.
+ */
+ if (arm_smmu_cmdq_can_elide(smmu)) {
+ dev_err(smmu->dev, "Ignoring page fault while suspended\n");
+ return;
+ }
+
+ ret = arm_smmu_rpm_get(smmu);
+ if (ret < 0)
+ return;
+
arm_smmu_cmdq_issue_cmd(master->smmu,
arm_smmu_make_cmd_resume(master->streams[0].id,
resp->grpid,
@@ -1011,6 +1032,7 @@ static void arm_smmu_page_response(struct device *dev, struct iopf_fault *unused
* terminated... at some point in the future. PRI_RESP is fire and
* forget.
*/
+ arm_smmu_rpm_put(smmu);
}
/* Invalidation array manipulation functions */
@@ -1536,7 +1558,6 @@ static void arm_smmu_sync_cd(struct arm_smmu_master *master,
smmu, &cmds,
arm_smmu_make_cmd_cfgi_cd(master->streams[i].id, ssid,
leaf));
-
arm_smmu_cmdq_batch_submit(smmu, &cmds);
}
@@ -1828,9 +1849,9 @@ static void arm_smmu_ste_writer_sync_entry(struct arm_smmu_entry_writer *writer)
{
struct arm_smmu_ste_writer *ste_writer =
container_of(writer, struct arm_smmu_ste_writer, writer);
+ struct arm_smmu_device *smmu = writer->master->smmu;
- arm_smmu_cmdq_issue_cmd_with_sync(
- writer->master->smmu,
+ arm_smmu_cmdq_issue_cmd_with_sync(smmu,
arm_smmu_make_cmd_cfgi_ste(ste_writer->sid, true));
}
@@ -2230,6 +2251,7 @@ static void arm_smmu_dump_event(struct arm_smmu_device *smmu, u64 *raw,
static irqreturn_t arm_smmu_evtq_thread(int irq, void *dev)
{
+ int ret;
u64 evt[EVTQ_ENT_DWORDS];
struct arm_smmu_event event = {0};
struct arm_smmu_device *smmu = dev;
@@ -2238,6 +2260,10 @@ static irqreturn_t arm_smmu_evtq_thread(int irq, void *dev)
static DEFINE_RATELIMIT_STATE(rs, DEFAULT_RATELIMIT_INTERVAL,
DEFAULT_RATELIMIT_BURST);
+ ret = arm_smmu_rpm_get(smmu);
+ if (ret < 0)
+ return IRQ_NONE;
+
do {
while (!queue_remove_raw(q, evt)) {
arm_smmu_decode_event(smmu, evt, &event);
@@ -2258,6 +2284,7 @@ static irqreturn_t arm_smmu_evtq_thread(int irq, void *dev)
/* Sync our overflow flag, as we believe we're up to speed */
queue_sync_cons_ovf(q);
+ arm_smmu_rpm_put(smmu);
return IRQ_HANDLED;
}
@@ -2295,6 +2322,11 @@ static irqreturn_t arm_smmu_priq_thread(int irq, void *dev)
struct arm_smmu_queue *q = &smmu->priq.q;
struct arm_smmu_ll_queue *llq = &q->llq;
u64 evt[PRIQ_ENT_DWORDS];
+ int ret;
+
+ ret = arm_smmu_rpm_get(smmu);
+ if (ret < 0)
+ return IRQ_NONE;
do {
while (!queue_remove_raw(q, evt))
@@ -2306,6 +2338,7 @@ static irqreturn_t arm_smmu_priq_thread(int irq, void *dev)
/* Sync our overflow flag, as we believe we're up to speed */
queue_sync_cons_ovf(q);
+ arm_smmu_rpm_put(smmu);
return IRQ_HANDLED;
}
@@ -2361,8 +2394,33 @@ static irqreturn_t arm_smmu_handle_gerror(struct arm_smmu_device *smmu)
static irqreturn_t arm_smmu_gerror_handler(int irq, void *dev)
{
struct arm_smmu_device *smmu = dev;
+ irqreturn_t ret;
+
+ /*
+ * Global Errors are only processed if the SMMU is active.
+ *
+ * If the STOP_FLAG is set (can_elide == true), the hardware is
+ * either already disabled or in the process of being disabled.
+ * Any errors captured during the quiesce/drain phase will be
+ * handled by the explicit arm_smmu_handle_gerror() call at the
+ * end of arm_smmu_runtime_suspend() callback. On resume, the
+ * STOP_FLAG is cleared before interrupts are re-enabled, ensuring
+ * no valid errors are missed.
+ *
+ * A lockless check is favoured here over a dynamic PM core check
+ * since the runtime_pm_get_if_active would return false during
+ * transient states like RPM_RESUMING & ignore level-triggered
+ * interrupts.
+ */
+ if (arm_smmu_cmdq_can_elide(smmu)) {
+ dev_err(smmu->dev,
+ "Ignoring gerror interrupt because the SMMU is suspended\n");
+ return IRQ_NONE;
+ }
+
+ ret = arm_smmu_handle_gerror(smmu);
- return arm_smmu_handle_gerror(smmu);
+ return ret;
}
static irqreturn_t arm_smmu_combined_irq_thread(int irq, void *dev)
@@ -2445,6 +2503,10 @@ static int arm_smmu_atc_inv_master(struct arm_smmu_master *master,
struct arm_smmu_cmd cmd;
struct arm_smmu_cmdq_batch cmds;
+ /* Shouldn't hit the WARN if there's no devlink inconsistency */
+ if (WARN_ON_ONCE(arm_smmu_cmdq_can_elide(master->smmu)))
+ return 0;
+
cmd = arm_smmu_make_cmd_atc_inv_all(0, IOMMU_NO_PASID);
arm_smmu_cmdq_batch_init_cmd(master->smmu, &cmds, &cmd);
for (i = 0; i < master->num_streams; i++)
@@ -2690,7 +2752,16 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
if (cmds.num &&
(next == end || arm_smmu_invs_end_batch(cur, next))) {
+
+ /*
+ * Concurrent suspend races are benign: the cmdq allocation cmpxchg
+ * loop acts as the serialization point to safely drop the batch
+ * without MMIO accesses. Concurrent resume is caught by the HW
+ * reset cache invalidation, ensuring state consistency.
+ */
arm_smmu_cmdq_batch_submit(smmu, &cmds);
+
+ /* Drop this batch to ensure the next one's fresh */
cmds.num = 0;
}
cur = next;
@@ -2703,6 +2774,9 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
{
struct arm_smmu_invs *invs;
+ if (arm_smmu_cmdq_can_elide(smmu_domain->smmu))
+ return;
+
/*
* An invalidation request must follow some IOPTE change and then load
* an invalidation array. In the meantime, a domain attachment mutates
@@ -5631,10 +5705,19 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
static void arm_smmu_device_remove(struct platform_device *pdev)
{
struct arm_smmu_device *smmu = platform_get_drvdata(pdev);
+ int ret;
iommu_device_unregister(&smmu->iommu);
iommu_device_sysfs_remove(&smmu->iommu);
+
+ ret = arm_smmu_rpm_get(smmu);
+ if (ret < 0)
+ goto free_iopf;
+
arm_smmu_device_disable(smmu);
+ arm_smmu_rpm_put(smmu);
+
+free_iopf:
iopf_queue_free(smmu->evtq.iopf);
ida_destroy(&smmu->vmid_map);
}
@@ -5642,8 +5725,14 @@ static void arm_smmu_device_remove(struct platform_device *pdev)
static void arm_smmu_device_shutdown(struct platform_device *pdev)
{
struct arm_smmu_device *smmu = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = arm_smmu_rpm_get(smmu);
+ if (ret < 0)
+ return;
arm_smmu_device_disable(smmu);
+ arm_smmu_rpm_put(smmu);
}
static int __maybe_unused arm_smmu_runtime_suspend(struct device *dev)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index 51a3281a1e46..0bd5ad2670e0 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -1230,11 +1230,14 @@ int arm_smmu_cmdq_issue_cmdlist(struct arm_smmu_device *smmu,
* If we lose the race, that loop observes Q_STOP == 1 and safely
* drops the command. If we win, the suspend thread waits for us.
*/
-static inline bool arm_smmu_can_elide(struct arm_smmu_device *smmu)
+static inline bool arm_smmu_cmdq_can_elide(struct arm_smmu_device *smmu)
{
return !!Q_STOP(READ_ONCE(smmu->cmdq.q.llq.prod));
}
+int arm_smmu_rpm_get(struct arm_smmu_device *smmu);
+void arm_smmu_rpm_put(struct arm_smmu_device *smmu);
+
#ifdef CONFIG_ARM_SMMU_V3_SVA
bool arm_smmu_sva_supported(struct arm_smmu_device *smmu);
void arm_smmu_sva_notifier_synchronize(void);
--
2.54.0.1013.g208068f2d8-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v8 11/12] iommu/arm-smmu-v3: Invoke pm_runtime before hw access
2026-06-01 21:59 ` [PATCH v8 11/12] iommu/arm-smmu-v3: Invoke pm_runtime before hw access Pranjal Shrivastava
@ 2026-06-02 0:24 ` Nicolin Chen
2026-06-02 3:59 ` Pranjal Shrivastava
0 siblings, 1 reply; 29+ messages in thread
From: Nicolin Chen @ 2026-06-02 0:24 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Daniel Mentz, Ashish Mhetre, linux-arm-kernel
On Mon, Jun 01, 2026 at 09:59:08PM +0000, Pranjal Shrivastava wrote:
> -static inline bool arm_smmu_can_elide(struct arm_smmu_device *smmu)
> +static inline bool arm_smmu_cmdq_can_elide(struct arm_smmu_device *smmu)
> {
> return !!Q_STOP(READ_ONCE(smmu->cmdq.q.llq.prod));
> }
Can it be named perfectly at the beginning?
And again, should it check Q_STOP in vcmdq?
Nicolin
^ permalink raw reply [flat|nested] 29+ messages in thread* Re: [PATCH v8 11/12] iommu/arm-smmu-v3: Invoke pm_runtime before hw access
2026-06-02 0:24 ` Nicolin Chen
@ 2026-06-02 3:59 ` Pranjal Shrivastava
2026-06-02 5:51 ` Nicolin Chen
0 siblings, 1 reply; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-02 3:59 UTC (permalink / raw)
To: Nicolin Chen
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Daniel Mentz, Ashish Mhetre, linux-arm-kernel
On Mon, Jun 01, 2026 at 05:24:18PM -0700, Nicolin Chen wrote:
> On Mon, Jun 01, 2026 at 09:59:08PM +0000, Pranjal Shrivastava wrote:
> > -static inline bool arm_smmu_can_elide(struct arm_smmu_device *smmu)
> > +static inline bool arm_smmu_cmdq_can_elide(struct arm_smmu_device *smmu)
> > {
> > return !!Q_STOP(READ_ONCE(smmu->cmdq.q.llq.prod));
> > }
>
> Can it be named perfectly at the beginning?
Ack.
>
> And again, should it check Q_STOP in vcmdq?
I don't think so.. the primary CMDQ's STOP_FLAG should act as a proxy for
the SMMU's global power state. Since all queues (primary and secondary)
are gated synchronously during suspend after SMMUEN=0, and ungated before
SMMUEN=1 (i.e.before the caching can begin). Checking the primary queue
should suffice..
We can also decide to drop the can_elide checks from invs_array and
atc_inv_master altogether, leaving gerror & page_response the only
remaining call-sites, both of which don't use secondary queues.
Praan
^ permalink raw reply [flat|nested] 29+ messages in thread* Re: [PATCH v8 11/12] iommu/arm-smmu-v3: Invoke pm_runtime before hw access
2026-06-02 3:59 ` Pranjal Shrivastava
@ 2026-06-02 5:51 ` Nicolin Chen
2026-06-02 6:24 ` Pranjal Shrivastava
0 siblings, 1 reply; 29+ messages in thread
From: Nicolin Chen @ 2026-06-02 5:51 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Daniel Mentz, Ashish Mhetre, linux-arm-kernel
On Tue, Jun 02, 2026 at 03:59:52AM +0000, Pranjal Shrivastava wrote:
> On Mon, Jun 01, 2026 at 05:24:18PM -0700, Nicolin Chen wrote:
> > On Mon, Jun 01, 2026 at 09:59:08PM +0000, Pranjal Shrivastava wrote:
> > > -static inline bool arm_smmu_can_elide(struct arm_smmu_device *smmu)
> > > +static inline bool arm_smmu_cmdq_can_elide(struct arm_smmu_device *smmu)
> > > {
> > > return !!Q_STOP(READ_ONCE(smmu->cmdq.q.llq.prod));
> > > }
> >
> > Can it be named perfectly at the beginning?
>
> Ack.
>
> >
> > And again, should it check Q_STOP in vcmdq?
>
> I don't think so.. the primary CMDQ's STOP_FLAG should act as a proxy for
> the SMMU's global power state. Since all queues (primary and secondary)
> are gated synchronously during suspend after SMMUEN=0, and ungated before
> SMMUEN=1 (i.e.before the caching can begin). Checking the primary queue
> should suffice..
Should leave a note inline to elaborate why we only check primary
cmdq only.
[...]
> We can also decide to drop the can_elide checks from invs_array and
> atc_inv_master altogether, leaving gerror & page_response the only
> remaining call-sites, both of which don't use secondary queues.
That's not true.. In the host mode, any command will be issued to
vcmdq. IOW, primary queue might not be used at all.
Nicolin
^ permalink raw reply [flat|nested] 29+ messages in thread* Re: [PATCH v8 11/12] iommu/arm-smmu-v3: Invoke pm_runtime before hw access
2026-06-02 5:51 ` Nicolin Chen
@ 2026-06-02 6:24 ` Pranjal Shrivastava
0 siblings, 0 replies; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-02 6:24 UTC (permalink / raw)
To: Nicolin Chen
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Daniel Mentz, Ashish Mhetre, linux-arm-kernel
On Mon, Jun 01, 2026 at 10:51:34PM -0700, Nicolin Chen wrote:
> On Tue, Jun 02, 2026 at 03:59:52AM +0000, Pranjal Shrivastava wrote:
> > On Mon, Jun 01, 2026 at 05:24:18PM -0700, Nicolin Chen wrote:
> > > On Mon, Jun 01, 2026 at 09:59:08PM +0000, Pranjal Shrivastava wrote:
> > > > -static inline bool arm_smmu_can_elide(struct arm_smmu_device *smmu)
> > > > +static inline bool arm_smmu_cmdq_can_elide(struct arm_smmu_device *smmu)
> > > > {
> > > > return !!Q_STOP(READ_ONCE(smmu->cmdq.q.llq.prod));
> > > > }
> > >
> > > Can it be named perfectly at the beginning?
> >
> > Ack.
> >
> > >
> > > And again, should it check Q_STOP in vcmdq?
> >
> > I don't think so.. the primary CMDQ's STOP_FLAG should act as a proxy for
> > the SMMU's global power state. Since all queues (primary and secondary)
> > are gated synchronously during suspend after SMMUEN=0, and ungated before
> > SMMUEN=1 (i.e.before the caching can begin). Checking the primary queue
> > should suffice..
>
> Should leave a note inline to elaborate why we only check primary
> cmdq only.
Ack.
>
> [...]
> > We can also decide to drop the can_elide checks from invs_array and
> > atc_inv_master altogether, leaving gerror & page_response the only
> > remaining call-sites, both of which don't use secondary queues.
>
> That's not true.. In the host mode, any command will be issued to
> vcmdq. IOW, primary queue might not be used at all.
>
Alright, I see the supports_cmd is a Guest-only thing. I'll add not to
the can_elide helper.
Praan
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v8 12/12] iommu/arm-smmu-v3: Add KUnit unit tests for Runtime PM
2026-06-01 21:58 [PATCH v8 00/10] iommu/arm-smmu-v3: Implement Runtime/System Sleep ops Pranjal Shrivastava
` (10 preceding siblings ...)
2026-06-01 21:59 ` [PATCH v8 11/12] iommu/arm-smmu-v3: Invoke pm_runtime before hw access Pranjal Shrivastava
@ 2026-06-01 21:59 ` Pranjal Shrivastava
2026-06-02 6:03 ` [PATCH v8 00/10] iommu/arm-smmu-v3: Implement Runtime/System Sleep ops Nicolin Chen
12 siblings, 0 replies; 29+ messages in thread
From: Pranjal Shrivastava @ 2026-06-01 21:59 UTC (permalink / raw)
To: iommu
Cc: Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Nicolin Chen, Daniel Mentz, Ashish Mhetre,
linux-arm-kernel, Pranjal Shrivastava
Introduce kunit selftests to verify the Runtime PM elision gating,
post-suspend elisions and progress on resumption under active
invalidation load. Simulate concurrent HW suspension using a timer.
Mock all HW registers and CMDQ buffers by allocating them on RAM.
Make the mock CMDQ self-consuming to avoid hitting queue_full scenarios.
Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c | 169 ++++++++++++++++++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 1 +
2 files changed, 170 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
index add671363c82..04c65920c147 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
@@ -3,6 +3,10 @@
* Copyright 2024 Google LLC.
*/
#include <kunit/test.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
#include <linux/io-pgtable.h>
#include "arm-smmu-v3.h"
@@ -771,6 +775,169 @@ static void arm_smmu_v3_invs_test(struct kunit *test)
kfree(test_b);
}
+struct arm_smmu_mock_cmdq {
+ u32 mock_prod_reg;
+};
+
+/* Helper to allocate a self-consuming mock cmdq */
+static void arm_smmu_v3_test_init_mock_cmdq(struct kunit *test,
+ struct arm_smmu_device *smmu,
+ struct arm_smmu_mock_cmdq *mock)
+{
+ struct arm_smmu_cmdq *cmdq = &smmu->cmdq;
+ unsigned long *mock_valid_map;
+ u64 *mock_base;
+
+ mock_base = kunit_kzalloc(test, 1024 * sizeof(struct arm_smmu_cmd), GFP_KERNEL);
+ mock_valid_map = kunit_kzalloc(test, BITS_TO_LONGS(1024) * sizeof(long), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, mock_base);
+ KUNIT_ASSERT_NOT_NULL(test, mock_valid_map);
+
+ smmu->features = 0;
+ /* 1024 entries */
+ cmdq->q.llq.max_n_shift = 10;
+ cmdq->q.ent_dwords = CMDQ_ENT_DWORDS;
+ cmdq->q.base = (__le64 *)mock_base;
+ cmdq->valid_map = (atomic_long_t *)mock_valid_map;
+
+ /* Self-Consuming, prod == cons always ensures queue empty */
+ cmdq->q.prod_reg = (__force u32 __iomem *)&mock->mock_prod_reg;
+ cmdq->q.cons_reg = (__force u32 __iomem *)&mock->mock_prod_reg;
+
+ atomic_set(&cmdq->q.llq.atomic.prod, 0);
+ atomic_set(&cmdq->q.llq.atomic.cons, 0);
+ atomic_set(&cmdq->owner_prod, 0);
+ mock->mock_prod_reg = 0;
+}
+
+struct arm_smmu_test_timer_context {
+ struct arm_smmu_device *smmu;
+ struct timer_list timer;
+ bool suspended;
+};
+
+static void arm_smmu_v3_test_rpm_timer_callback(struct timer_list *t)
+{
+ struct arm_smmu_test_timer_context *ctx =
+ timer_container_of(ctx, t, timer);
+ struct arm_smmu_cmdq *cmdq = &ctx->smmu->cmdq;
+
+ /* Simulate a concurrent suspend event interrupting the invalidations */
+ atomic_or(CMDQ_PROD_STOP_FLAG, &cmdq->q.llq.atomic.prod);
+ WRITE_ONCE(ctx->suspended, true);
+}
+
+/*
+ * Verify SMMU PM Runtime gating, elision, and post-suspend resumption
+ * safety sequentially under active stress.
+ */
+static void arm_smmu_v3_rpm_test_stress_race(struct kunit *test)
+{
+ struct arm_smmu_cmd cmd = arm_smmu_make_cmd_cfgi_all();
+ struct arm_smmu_test_timer_context timer_ctx = {0};
+ struct arm_smmu_device mock_smmu = smmu;
+ struct arm_smmu_cmdq *cmdq = &mock_smmu.cmdq;
+ struct arm_smmu_mock_cmdq mock = {0};
+ u32 stopped_prod;
+ int i;
+
+ arm_smmu_v3_test_init_mock_cmdq(test, &mock_smmu, &mock);
+
+ timer_ctx.smmu = &mock_smmu;
+
+ timer_setup(&timer_ctx.timer, arm_smmu_v3_test_rpm_timer_callback, 0);
+ mod_timer(&timer_ctx.timer, jiffies + msecs_to_jiffies(10));
+
+ /* Execute the unmap storm until the timer triggers */
+ while (!READ_ONCE(timer_ctx.suspended)) {
+ if (arm_smmu_cmdq_issue_cmdlist(&mock_smmu, cmdq, &cmd, 1, false))
+ break;
+ usleep_range(50, 100);
+ }
+
+ timer_delete_sync(&timer_ctx.timer);
+
+ /* Establish the post-storm prod_reg index */
+ stopped_prod = mock.mock_prod_reg;
+
+ /*
+ * Attempt multiple unmaps while the SMMU is disabled (STOP_GATE is set)
+ * Every single invalidation must get elided and return 0. The prod_reg
+ * shall remain completely frozen after all of these submissions.
+ */
+ for (i = 0; i < 1000; i++) {
+ if (arm_smmu_cmdq_issue_cmdlist(&mock_smmu, cmdq, &cmd, 1, false))
+ break;
+ }
+ KUNIT_EXPECT_EQ(test, stopped_prod, mock.mock_prod_reg);
+
+ /*
+ * Clear the STOP_FLAG (resume the SMMU). A new invalidation must
+ * now successfully commit prod_idx & move the prod_reg by exactly 1.
+ */
+ atomic_andnot(CMDQ_PROD_STOP_FLAG, &cmdq->q.llq.atomic.prod);
+ KUNIT_EXPECT_EQ(test, 0, arm_smmu_cmdq_issue_cmdlist(&mock_smmu, cmdq, &cmd, 1, false));
+ KUNIT_EXPECT_EQ(test, stopped_prod + 1, mock.mock_prod_reg);
+}
+
+struct arm_smmu_test_kthread_context {
+ struct arm_smmu_device *smmu;
+ int error;
+};
+
+static int arm_smmu_v3_test_kthread_worker(void *data)
+{
+ struct arm_smmu_cmd cmd = arm_smmu_make_cmd_cfgi_all();
+ struct arm_smmu_test_kthread_context *ctx = data;
+ struct arm_smmu_cmdq *cmdq = &ctx->smmu->cmdq;
+
+ while (!kthread_should_stop()) {
+ if (arm_smmu_cmdq_issue_cmdlist(ctx->smmu, cmdq, &cmd, 1, false))
+ WRITE_ONCE(ctx->error, 1);
+ usleep_range(50, 100);
+ }
+ return 0;
+}
+
+static void arm_smmu_v3_rpm_test_kthread_race(struct kunit *test)
+{
+ struct arm_smmu_test_kthread_context ctx = {0};
+ struct arm_smmu_device mock_smmu = smmu;
+ struct arm_smmu_mock_cmdq mock = {0};
+ struct task_struct *thread1, *thread2;
+ u32 stopped_prod;
+
+ ctx.smmu = &mock_smmu;
+ arm_smmu_v3_test_init_mock_cmdq(test, &mock_smmu, &mock);
+
+ thread1 = kthread_run(arm_smmu_v3_test_kthread_worker, &ctx, "smmu_w1");
+ thread2 = kthread_run(arm_smmu_v3_test_kthread_worker, &ctx, "smmu_w2");
+ if (IS_ERR(thread1) || IS_ERR(thread2)) {
+ if (!IS_ERR(thread1)) kthread_stop(thread1);
+ if (!IS_ERR(thread2)) kthread_stop(thread2);
+ return;
+ }
+
+ usleep_range(1000, 2000);
+
+ /* Gate the CMDQ */
+ atomic_or(CMDQ_PROD_STOP_FLAG, &mock_smmu.cmdq.q.llq.atomic.prod);
+ stopped_prod = mock.mock_prod_reg;
+
+ usleep_range(1000, 2000);
+ KUNIT_EXPECT_EQ(test, stopped_prod, mock.mock_prod_reg);
+
+ /* Open the gate */
+ atomic_andnot(CMDQ_PROD_STOP_FLAG, &mock_smmu.cmdq.q.llq.atomic.prod);
+ usleep_range(1000, 2000);
+ KUNIT_EXPECT_NE(test, stopped_prod, mock.mock_prod_reg);
+
+ kthread_stop(thread1);
+ kthread_stop(thread2);
+ KUNIT_EXPECT_EQ(test, 0, ctx.error);
+}
+
static struct kunit_case arm_smmu_v3_test_cases[] = {
KUNIT_CASE(arm_smmu_v3_write_ste_test_bypass_to_abort),
KUNIT_CASE(arm_smmu_v3_write_ste_test_abort_to_bypass),
@@ -797,6 +964,8 @@ static struct kunit_case arm_smmu_v3_test_cases[] = {
KUNIT_CASE(arm_smmu_v3_write_cd_test_sva_clear),
KUNIT_CASE(arm_smmu_v3_write_cd_test_sva_release),
KUNIT_CASE(arm_smmu_v3_invs_test),
+ KUNIT_CASE(arm_smmu_v3_rpm_test_stress_race),
+ KUNIT_CASE(arm_smmu_v3_rpm_test_kthread_race),
{},
};
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 2e260f85b8fd..d72c891e75ba 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -873,6 +873,7 @@ int arm_smmu_cmdq_issue_cmdlist(struct arm_smmu_device *smmu,
local_irq_restore(flags);
return ret;
}
+EXPORT_SYMBOL_IF_KUNIT(arm_smmu_cmdq_issue_cmdlist);
static int arm_smmu_cmdq_issue_cmd_p(struct arm_smmu_device *smmu,
struct arm_smmu_cmd *cmd, bool sync)
--
2.54.0.1013.g208068f2d8-goog
^ permalink raw reply related [flat|nested] 29+ messages in thread* Re: [PATCH v8 00/10] iommu/arm-smmu-v3: Implement Runtime/System Sleep ops
2026-06-01 21:58 [PATCH v8 00/10] iommu/arm-smmu-v3: Implement Runtime/System Sleep ops Pranjal Shrivastava
` (11 preceding siblings ...)
2026-06-01 21:59 ` [PATCH v8 12/12] iommu/arm-smmu-v3: Add KUnit unit tests for Runtime PM Pranjal Shrivastava
@ 2026-06-02 6:03 ` Nicolin Chen
12 siblings, 0 replies; 29+ messages in thread
From: Nicolin Chen @ 2026-06-02 6:03 UTC (permalink / raw)
To: Pranjal Shrivastava
Cc: iommu, Will Deacon, Joerg Roedel, Robin Murphy, Jason Gunthorpe,
Mostafa Saleh, Daniel Mentz, Ashish Mhetre, linux-arm-kernel
On Mon, Jun 01, 2026 at 09:58:57PM +0000, Pranjal Shrivastava wrote:
> [v8]
> - Centralized elision logic: dropped redundant checks from invalidation
> call-sites; elision is now authoritative within the CMDQ layer.
> - Renamed elision helper to arm_smmu_cmdq_can_elide() and preserved it
> only for diagnostic/safety paths (ATC, GERROR, Page Response).
> - Consolidated implementation-specific gating and draining into a
> unified quiesce_and_drain_queues callback.
> - Updated tegra241-cmdqv to gate virtual queues before draining,
> addressing non-deterministic timeouts from guest-side submissions.
> - Re-ordered probe sequence to enable pm_runtime only at the end,
> aligning with SMMUv2 and simplifying error paths.
> - Refactored KUnit tests for better, addressed Nicolin's comments & added
> a multi-threaded kthread race test.
> - Refactored RPM helpers to use early-return patterns for improved clarity.
> - Collected R-bs from Nicolin.
FWIW, v8 is still missing a base-commit and Sashiko fails to review:
https://sashiko.dev/#/patchset/20260601215909.3958732-1-praan%40google.com
Please make sure v9 patches are formatted with "--base=" and a public
commit.
Nicolin
^ permalink raw reply [flat|nested] 29+ messages in thread