iommu/arm-smmu-v3: Add PRI support
Subsystems (3)
v2 cover iommu/arm-smmu-v3: Add PRI support
The SMMUv3 driver doesn't handle events on the PRI queue or respond to IOPF faults. This series adds the missing pieces, using the IOPF infrastructure, to convert PRI page requests into iopf_faults and issue CMDQ_OP_PRI_RESP.
The iopf_queue_flush_dev() contract requires the driver to first drain the hardware PRI queue and synchronize using a threaded IRQ handler before the IOPF software flush. This drove the additional commits compared to v1:
- arm_smmu_drain_queue_for_iopf() drains the hardware queue to the PROD snapshot
- arm_smmu_attach_release() moves the teardown outside the global lock
- synchronize_irq() closes the gap before the final flush
This is on Github:
https://github.com/nicolinc/iommufd/commits/smmuv3_pri-v2
FWIW, engineers on the NVIDIA side have managed to verify the PRI feature.
Changelog
v2:
* Allocate evtq.iopf for ARM_SMMU_FEAT_PRI
* Pick up Jean's PRI stubs and PRI export patches
* Enable PRI for PCI devices in arm_smmu_probe_device()
* Add arm_smmu_drain_queue_for_iopf() for EVTQ and PRIQ
* Add arm_smmu_attach_release() to rework the IOPF drain
* Add IOMMU_FAULT_PAGE_REQUEST_STALLS_TRANS for STALL mode
* Gate pci_enable_pri() on FEAT_PRI plus a non-NULL evtq.iopf
* Deny unrecognised-StreamID PRG_LAST in arm_smmu_handle_ppr()
* Disable PRI when no IRQ handler is registered (unique or combined IRQ)
v1:
https://lore.kernel.org/all/[email protected]/
Jean-Philippe Brucker (2):
PCI/ATS: Add PRI stubs
PCI/ATS: Export pci_enable_pri() and pci_reset_pri()
Malak Marrid (1):
iommu/arm-smmu-v3: Submit CMDQ_OP_PRI_RESP for IOPF event
Nicolin Chen (8):
iommu/arm-smmu-v3: Add arm_smmu_attach_release() iommu/arm-smmu-v3: Factor out __queue_empty() and __queue_consumed() iommu/arm-smmu-v3: Add arm_smmu_drain_queue_for_iopf() helper iommu/arm-smmu-v3: Drain in-flight fault handlers iommu/arm-smmu-v3: Support PRI Page Request in arm_smmu_handle_ppr() iommu/arm-smmu-v3: Disable PRI when no IRQ handler is registered iommu/arm-smmu-v3: Allocate IOPF queue for ARM_SMMU_FEAT_PRI iommu/arm-smmu-v3: Enable PRI for PCI device in
arm_smmu_probe_device()
drivers/iommu/arm/Kconfig | 1 + drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 3 + include/linux/iommu.h | 1 + include/linux/pci-ats.h | 5 + .../arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 1 + drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 244 +++++++++++++++--- drivers/pci/ats.c | 2 + 7 files changed, 221 insertions(+), 36 deletions(-)
base-commit: 74fa4c177ad09800b007cba043370c887bb1b4e3
Signature · 1 line
2.43.0
PATCH v2 1/11 iommu/arm-smmu-v3: Add arm_smmu_attach_release()
The IOPF teardown is done in arm_smmu_remove_master_domain() when releasing the master_domain on detach, under the global arm_smmu_asid_lock mutex. But the teardown must drain any in-flight IOPF (for the old domain), before the master_domain is freed via iopf_queue_flush_dev() calling flush_workqueue() that can block on user-faulting page-fault handlers. Doing so when holding the arm_smmu_asid_lock would stall any unrelated attachment in the system.
Split the teardown out of arm_smmu_remove_master_domain(), to a new helper arm_smmu_attach_release() that runs after arm_smmu_asid_lock is released.
Since no other device would use the old master_domain that is being freed, it's safe to move out of arm_smmu_asid_lock (still under the protection of iommu_group->mutex).
Note: this is a pure refactor; no functional change; it is a prerequisite to apply bug fix calling iopf_queue_flush_dev().
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 2 ++ .../arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 1 + drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 33 +++++++++++++++---- 3 files changed, 30 insertions(+), 6 deletions(-)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +2 -0
@@ -1197,12 +1197,14 @@ struct arm_smmu_attach_state {struct arm_smmu_vmaster *vmaster;struct arm_smmu_inv_state old_domain_invst;struct arm_smmu_inv_state new_domain_invst; +struct arm_smmu_master_domain *old_master_domain;bool ats_enabled; };int arm_smmu_attach_prepare (struct arm_smmu_attach_state *state,struct iommu_domain *new_domain);void arm_smmu_attach_commit (struct arm_smmu_attach_state *state); +void arm_smmu_attach_release (struct arm_smmu_attach_state *state);void arm_smmu_install_ste_for_dev (struct arm_smmu_master *master,const struct arm_smmu_ste *target);
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c +1 -0
@@ -191,6 +191,7 @@ static int arm_smmu_attach_dev_nested(struct iommu_domain *domain,arm_smmu_install_ste_for_dev (master, &ste);arm_smmu_attach_commit (&state);mutex_unlock (&arm_smmu_asid_lock); +arm_smmu_attach_release (&state);return 0 ; }
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +27 -6
@@ -3195,9 +3195,9 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,return master->build_invs; } -static void arm_smmu_remove_master_domain (struct arm_smmu_master *master, -struct iommu_domain *domain, -ioasid_t ssid) +static struct arm_smmu_master_domain * +arm_smmu_remove_master_domain (struct arm_smmu_master *master, +struct iommu_domain *domain,ioasid_t ssid) {struct arm_smmu_domain *smmu_domain =to_smmu_domain_devices (domain);struct arm_smmu_master_domain *master_domain;
@@ -3205,7 +3205,7 @@ static void arm_smmu_remove_master_domain(struct arm_smmu_master *master,unsigned long flags;if (!smmu_domain) -return ; +return NULL ;if (domain->type ==IOMMU_DOMAIN_NESTED ) nested_ats_flush =to_smmu_nested_domain (domain)->enable_ats;
@@ -3220,8 +3220,23 @@ static void arm_smmu_remove_master_domain(struct arm_smmu_master *master, }spin_unlock_irqrestore (&smmu_domain->devices_lock, flags); +/* arm_smmu_attach_release() will free it */ +return master_domain; +} + +/* Release the old master_domain detached by arm_smmu_remove_master_domain() */ +void arm_smmu_attach_release (struct arm_smmu_attach_state *state) +{ +struct arm_smmu_master_domain *master_domain = state->old_master_domain; +struct arm_smmu_master *master = state->master; + +iommu_group_mutex_assert (master->dev); + +if (!master_domain) +return ;arm_smmu_disable_iopf (master, master_domain);kfree (master_domain); + state->old_master_domain =NULL ; }/*
@@ -3519,7 +3534,8 @@ void arm_smmu_attach_commit(struct arm_smmu_attach_state *state)arm_smmu_atc_inv_master (master,IOMMU_NO_PASID ); } -arm_smmu_remove_master_domain (master, state->old_domain, state->ssid); + state->old_master_domain =arm_smmu_remove_master_domain ( + master, state->old_domain, state->ssid);arm_smmu_install_old_domain_invs (state); master->ats_enabled = state->ats_enabled; }
@@ -3594,6 +3610,7 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev,arm_smmu_attach_commit (&state);mutex_unlock (&arm_smmu_asid_lock); +arm_smmu_attach_release (&state);return 0 ; }
@@ -3694,6 +3711,7 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master, out_unlock:mutex_unlock (&arm_smmu_asid_lock); +arm_smmu_attach_release (&state);return ret; }
@@ -3714,9 +3732,11 @@ static int arm_smmu_blocking_set_dev_pasid(struct iommu_domain *new_domain,arm_smmu_clear_cd (master, pasid);if (master->ats_enabled)arm_smmu_atc_inv_master (master, pasid); -arm_smmu_remove_master_domain (master, &smmu_domain->domain, pasid); + state.old_master_domain =arm_smmu_remove_master_domain ( + master, &smmu_domain->domain, pasid);arm_smmu_install_old_domain_invs (&state);mutex_unlock (&arm_smmu_asid_lock); +arm_smmu_attach_release (&state);/* * When the last user of the CD table goes away downgrade the STE back
@@ -3774,6 +3794,7 @@ static void arm_smmu_attach_dev_ste(struct iommu_domain *domain,arm_smmu_install_ste_for_dev (master, ste);arm_smmu_attach_commit (&state);mutex_unlock (&arm_smmu_asid_lock); +arm_smmu_attach_release (&state);/* * This has to be done after removing the master from the
Signature · 1 line
2.43.0
PATCH v2 2/11 iommu/arm-smmu-v3: Factor out __queue_empty() and __queue_consumed()
In reply to Nicolin Chenqueue_empty() and queue_consumed() each compare a ring-position pair where one operand is fixed to a cached llq field (q->prod, q->cons).
A subsequent change will need the same ring-position comparisons against a CONS value read live from MMIO into a local, which does not live in q->llq.
Factor the ring-position checks in queue_empty() and queue_consumed() into __queue_empty() and __queue_consumed() primitives that accept both operands explicitly. queue_empty() and queue_consumed() become pass-through wrappers that pass the cached fields.
No functional change intended; it's a prerequisite to apply bug fix calling iopf_queue_flush_dev().
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 22 +++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +16 -6
@@ -146,18 +146,28 @@ static bool queue_full(struct arm_smmu_ll_queue *q)Q_WRP (q, q->prod) !=Q_WRP (q, q->cons); } +static bool __queue_empty (struct arm_smmu_ll_queue *q,u32 cons,u32 prod) +{ +return Q_IDX (q, prod) ==Q_IDX (q, cons) && +Q_WRP (q, prod) ==Q_WRP (q, cons); +} +static bool queue_empty (struct arm_smmu_ll_queue *q) { -return Q_IDX (q, q->prod) ==Q_IDX (q, q->cons) && -Q_WRP (q, q->prod) ==Q_WRP (q, q->cons); +return __queue_empty (q, q->cons, q->prod); +} + +static bool __queue_consumed (struct arm_smmu_ll_queue *q,u32 cons,u32 prod) +{ +return ((Q_WRP (q, cons) ==Q_WRP (q, prod)) && + (Q_IDX (q, cons) >Q_IDX (q, prod))) || + ((Q_WRP (q, cons) !=Q_WRP (q, prod)) && + (Q_IDX (q, cons) <=Q_IDX (q, prod))); }static bool queue_consumed (struct arm_smmu_ll_queue *q,u32 prod) { -return ((Q_WRP (q, q->cons) ==Q_WRP (q, prod)) && - (Q_IDX (q, q->cons) >Q_IDX (q, prod))) || - ((Q_WRP (q, q->cons) !=Q_WRP (q, prod)) && - (Q_IDX (q, q->cons) <=Q_IDX (q, prod))); +return __queue_consumed (q, q->cons, prod); }static void queue_sync_cons_out (struct arm_smmu_queue *q)
Signature · 1 line
2.43.0
PATCH v2 3/11 iommu/arm-smmu-v3: Add arm_smmu_drain_queue_for_iopf() helper
In reply to Nicolin ChenA poll-until-empty form does not converge on the shared EVTQ or PRIQ since other masters keep advancing PROD with unrelated traffic. A fixed snapshot bounds the wait even under sustained unrelated load, since the target does not move with subsequent enqueues.
Add a helper that drains an SMMU EVTQ or PRIQ up to a PROD snapshot taken on entry, waiting until CONS reaches the snapshot. Every entry already in the queue is consumed by the threaded IRQ handler before this returns. A subsequent change uses it from the IOPF attach-release path.
SMMUv3 guarantees that after a CMD_SYNC following STE/CD invalidation and any CMD_ATC_INV, no further entries tied to the pre-SYNC configuration will appear in either queue. A caller that has completed that sequence captures its entire in-flight cohort in the snapshot.
Read CONS from MMIO each iteration so the comparison does not rely on the cached llq->cons, which the threaded handler owns. Disable WFE since SMMU does not signal SEV on EVTQ/PRIQ PROD advance. Call cond_resched() inside the loop so this caller does not starve the threaded IRQ handler it waits on when both share a CPU on a non-preemptible kernel.
This is a prerequisite to apply bug fix calling iopf_queue_flush_dev().
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +27 -0
@@ -894,6 +894,33 @@ static int arm_smmu_cmdq_batch_submit(struct arm_smmu_device *smmu, cmds->num, true); } +/* Drain an SMMU EVTQ or PRIQ to a PROD snapshot taken on entry */ +static int arm_smmu_drain_queue_for_iopf (struct arm_smmu_device *smmu, +struct arm_smmu_queue *q) +{ +struct arm_smmu_queue_poll qp; +u32 prod, cons; +int ret =0 ; + +/* Snapshot PROD; entries [old_cons, prod) are the cohort to drain */ + prod =readl_relaxed (q->prod_reg); +queue_poll_init (smmu, &qp); + qp.wfe = false;/* No SEV on EVTQ/PRIQ PROD advance */ +/* Read MMIO each iteration; llq->cons is the threaded handler's */ +do { + cons =readl_relaxed (q->cons_reg); +if (__queue_empty (&q->llq, cons, prod) || +__queue_consumed (&q->llq, cons, prod)) +return 0 ; +cond_resched (); + }while (!(ret =queue_poll (&qp))); + +dev_warn_ratelimited (smmu->dev, +"queue drain timed out at prod=0x%x cons=0x%x\n" , + prod, cons); +return ret; +} +static void arm_smmu_page_response (struct device *dev,struct iopf_fault *unused,struct iommu_page_response *resp) {
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 03/11] iommu/arm-smmu-v3: Add arm_smmu_drain_queue_for_iopf() helper
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [High] Unbounded exponential udelay() in queue_poll() defeats cond_resched() and stalls the CPU. - [High]…
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Unbounded exponential udelay() in queue_poll() defeats cond_resched() and stalls the CPU.
- [High] Missing final queue state check after queue_poll() timeout leads to false positive timeouts.
- [High] Spurious success when the queue is completely full due to flawed __queue_consumed() logic.
- [High] Comparing a continuously advancing cons pointer against a stale prod snapshot using __queue_consumed() causes false timeouts under sustained traffic. --
commit 14bcb1387d4a8bb96abcbd316d76e0715f493ccd
Author: Nicolin Chen <[email protected]>
iommu/arm-smmu-v3: Add arm_smmu_drain_queue_for_iopf() helper
This patch adds a helper to drain an SMMU EVTQ or PRIQ up to a snapshot of the PROD register. It reads CONS from MMIO each iteration and calls cond_resched() to avoid starving the threaded IRQ handler.
Quoted 4 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +0
-0
[ ... ]
Quoted 8 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +7
-0@@ -894,6 +894,33 @@ static int arm_smmu_cmdq_batch_submit(struct arm_smmu_device *smmu, +/* Drain an SMMU EVTQ or PRIQ to a PROD snapshot taken on entry */ +static int arm_smmu_drain_queue_for_iopf (struct arm_smmu_device *smmu, +struct arm_smmu_queue *q) +{ +struct arm_smmu_queue_poll qp; +u32 prod, cons; +int ret =0 ;
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static int arm_smmu_cmdq_batch_submit(struct arm_smmu_device *smmu, · line 910
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +10
-0+ +/* Snapshot PROD; entries [old_cons, prod) are the cohort to drain */ + prod =readl_relaxed (q->prod_reg); +queue_poll_init (smmu, &qp); + qp.wfe = false;/* No SEV on EVTQ/PRIQ PROD advance */ +/* Read MMIO each iteration; llq->cons is the threaded handler's */ + do { + cons = readl_relaxed(q->cons_reg); + if (__queue_empty(&q->llq, cons, prod) || + __queue_consumed(&q->llq, cons, prod))
[Severity: High]
Does __queue_consumed() return true when the queue is completely full?
If the queue is full, Q_IDX(cons) == Q_IDX(prod) and Q_WRP(cons) != Q_WRP(prod). This matches the second half of the condition in __queue_consumed():
((Q_WRP(q, cons) != Q_WRP(q, prod)) && (Q_IDX(q, cons) <= Q_IDX(q, prod)))
If prod is snapshotted exactly when the hardware queue is full, this would exit immediately without draining anything.
[Severity: High]
Can the 1-bit wrap logic fail if cons advances more than a full queue size past the prod snapshot?
Since unrelated traffic continues to advance both prod and cons, if the polling loop is preempted and cons advances exactly two wraps ahead of the snapshot, the 1-bit counter will wrap.
This could make cons appear to be behind prod again, preventing the condition from converging and causing spurious timeouts under sustained load.
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static int arm_smmu_cmdq_batch_submit(struct arm_smmu_device *smmu, · line 916
Quoted 3 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +3
-0+return 0 ; +cond_resched (); + }while (!(ret =queue_poll (&qp)));
[Severity: High]
Will the exponential backoff in queue_poll() defeat the purpose of calling cond_resched()?
queue_poll() doubles qp->delay on each iteration, eventually calling udelay() with very large values (up to roughly 500ms given the 1s timeout). Since udelay() does not yield, this creates a long busy-wait that completely stalls the CPU.
If the IRQ handler shares this CPU on a non-preemptible kernel, could this prevent the handler from making progress and cause a soft lockup?
[Severity: High]
Is a final state check needed after queue_poll() returns a timeout?
If cond_resched() yields the CPU for a long time and the task is scheduled back after the 1-second timeout expires, queue_poll() will immediately return -ETIMEDOUT.
The loop will then exit without checking q->cons_reg again, even if the IRQ thread fully drained the queue during the sleep. Could this lead to false positive timeouts?
Quoted 6 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +6
-0+ +dev_warn_ratelimited (smmu->dev, +"queue drain timed out at prod=0x%x cons=0x%x\n" , + prod, cons); +return ret; +}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
Re: [PATCH v2 03/11] iommu/arm-smmu-v3: Add arm_smmu_drain_queue_for_iopf() helper
In reply to Nicolin ChenQuoted 5 lines · Nicolin Chen
A poll-until-empty form does not converge on the shared EVTQ or PRIQ since other masters keep advancing PROD with unrelated traffic. A fixed snapshot bounds the wait even under sustained unrelated load, since the target does not move with subsequent enqueues.
[...]
Quoted 39 lines
This is a prerequisite to apply bug fix calling
iopf_queue_flush_dev().Fixes cfea71aea921 ("iommu/arm-smmu-v3: Put iopf enablement in the domain attach path")Cc [email protected] # v6.16Assisted-by Claude:claude-opus-4-7Signed-off-by Nicolin Chen <[email protected]>--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+)drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +20
-0@@ -894,6 +894,33 @@ static int arm_smmu_cmdq_batch_submit(struct arm_smmu_device *smmu, cmds->num, true); } +/* Drain an SMMU EVTQ or PRIQ to a PROD snapshot taken on entry */ +static int arm_smmu_drain_queue_for_iopf (struct arm_smmu_device *smmu, +struct arm_smmu_queue *q) +{ +struct arm_smmu_queue_poll qp; +u32 prod, cons; +int ret =0 ; + +/* Snapshot PROD; entries [old_cons, prod) are the cohort to drain */ + prod =readl_relaxed (q->prod_reg); +queue_poll_init (smmu, &qp); + qp.wfe = false;/* No SEV on EVTQ/PRIQ PROD advance */ +/* Read MMIO each iteration; llq->cons is the threaded handler's */ +do { + cons =readl_relaxed (q->cons_reg); +if (__queue_empty (&q->llq, cons, prod) || +__queue_consumed (&q->llq, cons, prod)) +return 0 ; +cond_resched (); + }while (!(ret =queue_poll (&qp)));
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static int arm_smmu_cmdq_batch_submit(struct arm_smmu_device *smmu, · line 926
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +7
-0+ +dev_warn_ratelimited (smmu->dev, +"queue drain timed out at prod=0x%x cons=0x%x\n" , + prod, cons); +return ret; +} +static void arm_smmu_page_response (struct device *dev,struct iopf_fault *unused,struct iommu_page_response *resp) {
Hi Nicolin,
The following is a unified drain_queue helper, that also attempts to address Sashiko's concerns. This applies on top of my runtime PM v9 series [1], please see if it works for you:
Subject: [PATCH] iommu/arm-smmu-v3: Add a unified arm_smmu_drain_queue() helper
Introduce a unified arm_smmu_drain_queue() helper to replace queue specific polling loops. The helper handles both snapshot-based cohort draining (for IOPF) and full queue draining (required for Runtime PM).
The Runtime PM explicitly gates the CMDQ (via CMDQ_PROD_STOP_FLAG) prior to draining, this allows a single, snapshot-based target to natively handle all drain scenarios.
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 68 +++++++++++++++------ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 4 +- 2 files changed, 52 insertions(+), 20 deletions(-)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +50 -18
@@ -1057,39 +1057,70 @@ static int arm_smmu_cmdq_batch_submit(struct arm_smmu_device *smmu, cmds->num, true); } +static bool queue_consumed (struct arm_smmu_ll_queue *q,u32 cons,u32 target) +{ +if (cons == target) +return true; +if (Q_WRP (q, cons) ==Q_WRP (q, target)) +return Q_IDX (q, cons) >Q_IDX (q, target); +return Q_IDX (q, cons) <Q_IDX (q, target); +} +/* -* Currently, only used for CMDQ. -* TODO: Update to handle PRIQ & EVTQ when PRI support is added +* Drains a queue until the consumer reaches the provided 'prod' snapshot. +* For CMDQ, the consumer is the SMMU hardware (polled via MMIO). +* For EVTQ/PRIQ, the consumer is the CPU IRQ thread (polled via software llq). */ -int arm_smmu_queue_poll_until_empty (struct arm_smmu_device *smmu, -struct arm_smmu_queue *q) +int arm_smmu_drain_queue (struct arm_smmu_device *smmu, +struct arm_smmu_queue *q,u32 prod) {struct arm_smmu_queue_poll qp; -struct arm_smmu_ll_queue *llq = &q->llq; -int ret =0 ; +/* Secondary CMDQs don't support WFE*/ +bool is_cmdq = (q == &smmu->cmdq.q); +u32 cons;queue_poll_init (smmu, &qp); + qp.wfe = is_cmdq; + +do { +if (is_cmdq) { + cons =readl_relaxed (q->cons_reg); +WRITE_ONCE (q->llq.cons, cons); + }else { + cons =READ_ONCE (q->llq.cons); + } + +if (queue_consumed (&q->llq, cons, prod)) +return 0 ; + +cond_resched (); + }while (!queue_poll (&qp));/* -* The events are only generated when CMDQ becomes non-full. -* Thus, wfe can't be used here, mark it false explicitly. +* Check once more after timeout: cond_resched() could have delayed us +* until after the timer expired while the consumer finished draining. */ - qp.wfe = false; - -do { -WRITE_ONCE (llq->cons,readl_relaxed (q->cons_reg)); -if (queue_empty (llq)) -break ; +if (is_cmdq) { + cons =readl_relaxed (q->cons_reg); +WRITE_ONCE (q->llq.cons, cons); + }else { + cons =READ_ONCE (q->llq.cons); + } - ret =queue_poll (&qp); +if (queue_consumed (&q->llq, cons, prod)) +return 0 ; - }while (!ret); +dev_warn_ratelimited (smmu->dev, +"queue drain timed out at prod=0x%x cons=0x%x\n" , + prod, cons); -return ret; +return -ETIMEDOUT ; }static int arm_smmu_drain_queues (struct arm_smmu_device *smmu) { +struct arm_smmu_cmdq *cmdq = &smmu->cmdq; +u32 target;int ret;/*
@@ -1097,7 +1128,8 @@ static int arm_smmu_drain_queues(struct arm_smmu_device *smmu)* from contexts (like suspend) where the caller has already * ensured that new command submissions are fully closed. */ - ret =arm_smmu_queue_poll_until_empty (smmu, &smmu->cmdq.q); + target =atomic_read (&cmdq->q.llq.atomic.prod) &CMDQ_PROD_IDX_MASK ; + ret =arm_smmu_drain_queue (smmu, &cmdq->q, target);if (ret) goto out;
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +2 -2
@@ -1176,8 +1176,8 @@ 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); +int arm_smmu_drain_queue (struct arm_smmu_device *smmu, +struct arm_smmu_queue *q,u32 prod);static inline bool arm_smmu_master_canwbs (struct arm_smmu_master *master) {
Signature · 5 lines
[1] https://lore.kernel.org/all/[email protected]/ Thanks, Praan
Re: [PATCH v2 03/11] iommu/arm-smmu-v3: Add arm_smmu_drain_queue_for_iopf() helper
Quoted 13 lines · Pranjal Shrivastava
The following is a unified
drain_queuehelper, that also attempts to address Sashiko's concerns. This applies on top of my runtime PM v9 series [1], please see if it works for you:Subject: [PATCH] iommu/arm-smmu-v3: Add a unified
arm_smmu_drain_queue()helperIntroduce a unified
arm_smmu_drain_queue()helper to replace queue specific polling loops. The helper handles both snapshot-based cohort draining (for IOPF) and full queue draining (required for Runtime PM).The Runtime PM explicitly gates the CMDQ (via
CMDQ_PROD_STOP_FLAG) prior to draining, this allows a single, snapshot-based target to natively handle all drain scenarios.
[..]
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static int arm_smmu_cmdq_batch_submit(struct arm_smmu_device *smmu, · line 1083
Quoted 2 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +1
-0queue_poll_init (smmu, &qp); + qp.wfe = is_cmdq;
The callers of this drain() will be RPM and PRI, either of which needs busy-waiting. So, I have added a different drain() helper. I run Sashiko locally; it also gives a clean result.
Commit:
https://github.com/nicolinc/iommufd/commit/d10325ff40fd45c476bc98d484d8dd2cdc7fdf95
This will be in my pri-v3 series:
https://github.com/nicolinc/iommufd/commits/smmuv3_pri-v3
We have too many SMMUv3 under review now. So, I will post it once some of them are merged.
Nicolin
Re: [PATCH v2 03/11] iommu/arm-smmu-v3: Add arm_smmu_drain_queue_for_iopf() helper
Quoted 15 lines · Nicolin Chen
Quoted 6 lines · Pranjal Shrivastava
The following is a unified
drain_queuehelper, that also attempts to address Sashiko's concerns. This applies on top of my runtime PM v9 series [1], please see if it works for you:Subject: [PATCH] iommu/arm-smmu-v3: Add a unified
arm_smmu_drain_queue()helperQuoted 7 lines
Introduce a unified
arm_smmu_drain_queue()helper to replace queue specific polling loops. The helper handles both snapshot-based cohort draining (for IOPF) and full queue draining (required for Runtime PM).The Runtime PM explicitly gates the CMDQ (via
CMDQ_PROD_STOP_FLAG) prior to draining, this allows a single, snapshot-based target to natively handle all drain scenarios.
Quoted 8 lines
[..]
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static int arm_smmu_cmdq_batch_submit(struct arm_smmu_device *smmu, · line 1083
Quoted 2 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +1
-0queue_poll_init (smmu, &qp); + qp.wfe = is_cmdq;The callers of this
drain()will be RPM and PRI, either of which needs busy-waiting. So, I have added a differentdrain()helper. I run Sashiko locally; it also gives a clean result.
Ahh my bad. I meant to set qp.wfe = false, is_cmdq is a rebase remnant...
Quoted 3 lines
Commit:
https://github.com/nicolinc/iommufd/commit/d10325ff40fd45c476bc98d484d8dd2cdc7fdf95
Thanks for sharing this, looks good from the CMDQ side (since we call this when new CMDQ submissions are gated).
For PRIQ/EVTQ, my only worry was the HW producing more events, say Device A is detached (we call drain from attach_release) but Device B (still attached) starts an agressive DMA with multiple PRI requests/evts.
Maybe we should go back to snapshot based polling for that? Anyway, we can discuss this when the series is posted.
Quoted 7 lines
This will be in my pri-v3 series:
https://github.com/nicolinc/iommufd/commits/smmuv3_pri-v3We have too many SMMUv3 under review now. So, I will post it once some of them are merged.
Nicolin
Thanks,
Praan
Re: [PATCH v2 03/11] iommu/arm-smmu-v3: Add arm_smmu_drain_queue_for_iopf() helper
Quoted 5 lines · Pranjal Shrivastava
For PRIQ/EVTQ, my only worry was the HW producing more events, say Device A is detached (we call drain from
attach_release) but Device B (still attached) starts an agressive DMA with multiple PRI requests/evts.Maybe we should go back to snapshot based polling for that?
If you look closely, "pending" is the snapshot, regardless of the growing prod.
* With @until_empty == false (for EVTQ/PRIQ), exit once "drained" reaches its
* target: "pending" (i.e. prod0 - cons0, frozen at the entry time):
*
* cons0 cons prod0 (prod)
* |<---- drained ---->| | |
* ---+###################+=====================+=============+--->
* |<--------------- pending --------------->|
Nicolin
Re: [PATCH v2 03/11] iommu/arm-smmu-v3: Add arm_smmu_drain_queue_for_iopf() helper
Quoted 10 lines · Nicolin Chen
Quoted 5 lines · Pranjal Shrivastava
For PRIQ/EVTQ, my only worry was the HW producing more events, say Device A is detached (we call drain from
attach_release) but Device B (still attached) starts an agressive DMA with multiple PRI requests/evts.Maybe we should go back to snapshot based polling for that?
If you look closely, "pending" is the snapshot, regardless of the growing prod.
Quoted 8 lines
* With @until_empty == false (for EVTQ/PRIQ), exit once "drained" reaches its
* target: "pending" (i.e. prod0 - cons0, frozen at the entry time):
*
* cons0 cons prod0 (prod)
* |<---- drained ---->| | |
* ---+###################+=====================+=============+--->
* |<--------------- pending --------------->|
Right, but we write back cons to cons_reg only after we see queue_empty() We have a do { .. } while (!queue_empty(llq)); and then we call queue_sync_cons_ovf(q). Same for PRIQ handler.
IIUC the helper relies on cons_reg being updated:
cons = readl_relaxed(q->cons_reg);
drained += Q_POS(&q->llq, cons - prev);
Thus, if we get a storm of pri reqs / evts, we may never leave that loop and queue_sync_cons_ovf(q).
Let me know if I m missing anything?
Praan
Re: [PATCH v2 03/11] iommu/arm-smmu-v3: Add arm_smmu_drain_queue_for_iopf() helper
Quoted 24 lines · Pranjal Shrivastava
Quoted 10 lines · Nicolin Chen
Quoted 5 lines · Pranjal Shrivastava
For PRIQ/EVTQ, my only worry was the HW producing more events, say Device A is detached (we call drain from
attach_release) but Device B (still attached) starts an agressive DMA with multiple PRI requests/evts.Maybe we should go back to snapshot based polling for that?
If you look closely, "pending" is the snapshot, regardless of the growing prod.
Quoted 8 lines
* With @until_empty == false (for EVTQ/PRIQ), exit once "drained" reaches its
* target: "pending" (i.e. prod0 - cons0, frozen at the entry time):
*
* cons0 cons prod0 (prod)
* |<---- drained ---->| | |
* ---+###################+=====================+=============+--->
* |<--------------- pending --------------->|Right, but we write back cons to
cons_regonly after we seequeue_empty()We have a do { .. } while (!queue_empty(llq)); and then we callqueue_sync_cons_ovf(q). Same for PRIQ handler.
Quoted 7 lines
IIUC the helper relies on
cons_regbeing updated:cons =
readl_relaxed(q->cons_reg);
drained +=Q_POS(&q->llq, cons - prev);Thus, if we get a storm of pri reqs / evts, we may never leave that loop and
queue_sync_cons_ovf(q).
cons_reg is updated by queue_remove_raw() every iteration inside that loop.
queue_sync_cons_ovf() doesn't update cons_reg unless an overflow.
Nicolin
Re: [PATCH v2 03/11] iommu/arm-smmu-v3: Add arm_smmu_drain_queue_for_iopf() helper
Quoted 30 lines · Nicolin Chen
Quoted 29 lines · Pranjal Shrivastava
Quoted 10 lines · Nicolin Chen
Quoted 5 lines · Pranjal Shrivastava
For PRIQ/EVTQ, my only worry was the HW producing more events, say Device A is detached (we call drain from
attach_release) but Device B (still attached) starts an agressive DMA with multiple PRI requests/evts.Maybe we should go back to snapshot based polling for that?
If you look closely, "pending" is the snapshot, regardless of the growing prod.
Quoted 8 lines
* With @until_empty == false (for EVTQ/PRIQ), exit once "drained" reaches its
* target: "pending" (i.e. prod0 - cons0, frozen at the entry time):
*
* cons0 cons prod0 (prod)
* |<---- drained ---->| | |
* ---+###################+=====================+=============+--->
* |<--------------- pending --------------->|Right, but we write back cons to
cons_regonly after we seequeue_empty()We have a do { .. } while (!queue_empty(llq)); and then we callqueue_sync_cons_ovf(q). Same for PRIQ handler.IIUC the helper relies on
cons_regbeing updated:cons =
readl_relaxed(q->cons_reg);
drained +=Q_POS(&q->llq, cons - prev);
Quoted 8 lines
Quoted 2 lines
Thus, if we get a storm of pri reqs / evts, we may never leave that loop and
queue_sync_cons_ovf(q).
cons_regis updated byqueue_remove_raw()every iteration inside that loop.
queue_sync_cons_ovf()doesn't updatecons_regunless an overflow.
Ahh right, I was too focused on the storm *facepalm*. In that case the new helper LGTM.
Thanks,
Praan
PATCH v2 4/11 iommu/arm-smmu-v3: Drain in-flight fault handlers
In reply to Nicolin ChenWhen a device is switching away from a domain, either through a detach or a replace operation, in-flight stall events for the old domain might still be on the SMMU's hardware event queue or on the IOMMU core's IOPF queue. Thus, if the IOMMU core swaps the device's attach_handle and frees the old domain before those handlers complete, the IOPF work might hit use-after-free.
Two queues need to be drained: the SMMU hardware event queue and the IOMMU core IOPF software workqueue.
Poll the evtq so that pending IRQs won't let the threaded handler run after the drain and queue a fault against the freed master_domain.
Then, synchronize_irq() on the evtq. queue_remove_raw() advances MMIO CONS before the caller pushes an event to the IOPF queue, so the first step does not on its own guarantee every event has been queued. It must wait for the IRQ handler to finish to close that gap.
Lastly, invoke iopf_queue_flush_dev() to drain the IOPF workqueue.
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +19 -0
@@ -3267,10 +3267,29 @@ void arm_smmu_attach_release(struct arm_smmu_attach_state *state)struct arm_smmu_master_domain *master_domain = state->old_master_domain;struct arm_smmu_master *master = state->master; +lockdep_assert_not_held (&arm_smmu_asid_lock);iommu_group_mutex_assert (master->dev);if (!master_domain)return ; + +if (master_domain->using_iopf) { +struct arm_smmu_device *smmu = master->smmu; + +/* Drain the hardware eventq */ +if (master->stall_enabled) { +arm_smmu_drain_queue_for_iopf (smmu, &smmu->evtq.q); +/* Ensure pending events have reached the IOPF queue */ +if (smmu->evtq.q.irq) +synchronize_irq (smmu->evtq.q.irq); + } +/* Pending events might be in the combined_irq handler */ +if (smmu->combined_irq) +synchronize_irq (smmu->combined_irq); +/* Lastly, drain the IOPF queue */ +iopf_queue_flush_dev (master->dev); + } +arm_smmu_disable_iopf (master, master_domain);kfree (master_domain); state->old_master_domain =NULL ;
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 04/11] iommu/arm-smmu-v3: Drain in-flight fault handlers
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] The EVTQ drain logic introduced to fix a Use-After-Free vulnerability is flawed: it fails to fully…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The EVTQ drain logic introduced to fix a Use-After-Free vulnerability is flawed: it fails to fully prevent the UAF and introduces a new deadlock vector. --
commit 0c247207412e55b576df6d5bf6788056a0ed654c
Author: Nicolin Chen <[email protected]>
iommu/arm-smmu-v3: Drain in-flight fault handlers
This commit attempts to fix a use-after-free vulnerability by draining the SMMU hardware event queue and the IOMMU core IOPF software workqueue during a domain detach or replace operation. It introduces logic to poll the event queue and synchronize the IRQ handlers before flushing the IOPF queue.
Quoted 12 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +1
-0@@ -3267,10 +3267,29 @@ void arm_smmu_attach_release(struct arm_smmu_attach_state *state)struct arm_smmu_master_domain *master_domain = state->old_master_domain;struct arm_smmu_master *master = state->master; +lockdep_assert_not_held (&arm_smmu_asid_lock);iommu_group_mutex_assert (master->dev);if (!master_domain)return ;
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c void arm_smmu_attach_release(struct arm_smmu_attach_state *state) · line 3284
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +10
-0+ +if (master_domain->using_iopf) { +struct arm_smmu_device *smmu = master->smmu; + +/* Drain the hardware eventq */ +if (master->stall_enabled) { +arm_smmu_drain_queue_for_iopf (smmu, &smmu->evtq.q); +/* Ensure pending events have reached the IOPF queue */ +if (smmu->evtq.q.irq) +synchronize_irq (smmu->evtq.q.irq);
[Severity: High]
Since arm_smmu_attach_dev() installs the new Stream Table Entry (STE) before calling arm_smmu_attach_release(), the SMMU can start generating new faults under the new domain's configuration while this code executes.
Because the attach_dev driver callback hasn't returned yet, the IOMMU core hasn't updated the device's attach_handle.
Could the concurrent event queue threaded handler read the old attach_handle and route these new faults to the old domain's handler?
Since arm_smmu_drain_queue_for_iopf() only drains up to a snapshot of the queue producer index, wouldn't these newly generated faults remain queued against the old handle, leading to the same use-after-free this attempts to fix when the core frees the old handle?
Additionally, is it safe to call synchronize_irq() here while holding the iommu_group_mutex?
Since the event queue is shared across all devices on the SMMU, and the arm_smmu_evtq_thread() handler processes faults in an unbounded loop, could a continuous stream of faults from another device prevent the handler from exiting? This might cause synchronize_irq() to block indefinitely and deadlock the attach operation.
Quoted 2 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +2
-0+ } +/* Pending events might be in the combined_irq handler */
[ ... ]
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
PATCH v2 5/11 iommu/arm-smmu-v3: Submit CMDQ_OP_PRI_RESP for IOPF event
In reply to Nicolin ChenFrom: Malak Marrid <[email protected]>
To handle IOMMU_FAULT_PAGE_REQ from the PRI queue, arm_smmu_page_response() must issue a CMDQ_OP_PRI_RESP back to the SMMU.
However, either a stall event in the EVTQ or a PRI request in the PRIQ can surface to the IOPF infrastructure with fault.type == IOMMU_FAULT_PAGE_REQ, and a single master can in principle be both stall-capable and PRI-capable (e.g. FEAT_STALL_FORCE on a PCIe device with PRI), so master state is not a reliable discriminator.
Add IOMMU_FAULT_PAGE_REQUEST_STALLS_TRANS to the generic flags so the fault reporter can mark a page request that is holding the device's transaction:
arm_smmu_handle_event() sets it on STALL events arm_smmu_handle_ppr() leaves it clear for PRI events
Note: streams[0].id remains the RID because arm_smmu_enable_iopf() rejects num_streams != 1.
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 1 + include/linux/iommu.h | 1 + drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 75 +++++++++++++++------ 3 files changed, 58 insertions(+), 19 deletions(-)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +1 -0
@@ -1007,6 +1007,7 @@ struct arm_smmu_master {/* Locked by the iommu core using the group mutex */ struct arm_smmu_ctx_desc_cfg cd_table;unsigned int num_streams; + bool pri_enabled : 1; bool ats_enabled : 1; bool ste_ats_enabled : 1;bool stall_enabled;
include/linux/iommu.h +1 -0
@@ -76,6 +76,7 @@ struct iommu_fault_page_request {#define IOMMU_FAULT_PAGE_REQUEST_PASID_VALID (1 << 0)#define IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE (1 << 1)#define IOMMU_FAULT_PAGE_RESPONSE_NEEDS_PASID (1 << 2) +#define IOMMU_FAULT_PAGE_REQUEST_STALLS_TRANS (1 << 3)u32 flags;u32 pasid;u32 grpid;
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +56 -19
95 lines not on this page · raw message
Signature · 1 line
2.43.0
Robin Murphy
reviewerRe: [PATCH v2 05/11] iommu/arm-smmu-v3: Submit CMDQ_OP_PRI_RESP for IOPF event
Quoted 8 lines · Nicolin Chen
From: Malak Marrid <[email protected]>
To handle
IOMMU_FAULT_PAGE_REQfrom the PRI queue,arm_smmu_page_response()must issue aCMDQ_OP_PRI_RESPback to the SMMU.However, either a stall event in the EVTQ or a PRI request in the PRIQ can surface to the IOPF infrastructure with fault.type ==
IOMMU_FAULT_PAGE_REQ, and a single master can in principle be both stall-capable and PRI-capable
No, the SMMU architecture does all it can to specifically forbid this, see 3.12.1 and 16.4, it just can't be made architecturally ILLEGAL to enable stalls for PCIe devices because there's no strict architectural definition for what "a PCIe device" actually is. Similarly with the note in the definition of STE.EATS about the relationship with CD.S - the unwritten implication is that defining specific behaviours would only create an unreasonable burden for hardware validation, for the sake of something that nobody in their right mind should ever do anyway.
The expectation is that RCiEPs which do speak stallable non-PCIe bus protocols will not go to the effort of implementing ATS/PRI capabilities (not least because there's every chance that such protocols simply doesn't have that kind of transaction flow anyway). And conversely that it can be considered an egregious firmware (or system design) error to even claim (let alone force) stall capability for a real PCIe root port which may be deadlocked by blocking its requirement for free-flowing writes. Thus I think we could go so far as to refuse to handle any endpoint which did somehow claim both.
Thanks,
Robin.
Quoted 148 lines
(e.g.
FEAT_STALL_FORCEon a PCIe device with PRI), so master state is not a reliable discriminator.Add
IOMMU_FAULT_PAGE_REQUEST_STALLS_TRANSto the generic flags so the fault reporter can mark a page request that is holding the device's transaction:
arm_smmu_handle_event()sets it on STALL eventsarm_smmu_handle_ppr()leaves it clear for PRI eventsNote: streams[0].id remains the RID because
arm_smmu_enable_iopf()rejectsnum_streams!= 1.Co-developed-by Barak Biber <[email protected]>Signed-off-by Barak Biber <[email protected]>Co-developed-by Stefan Kaestle <[email protected]>Signed-off-by Stefan Kaestle <[email protected]>Signed-off-by Malak Marrid <[email protected]>Signed-off-by Nicolin Chen <[email protected]>--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 1 + include/linux/iommu.h | 1 + drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 75 +++++++++++++++------ 3 files changed, 58 insertions(+), 19 deletions(-)drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +1
-0@@ -1007,6 +1007,7 @@ struct arm_smmu_master {/* Locked by the iommu core using the group mutex */ struct arm_smmu_ctx_desc_cfg cd_table;unsigned int num_streams; + bool pri_enabled : 1; bool ats_enabled : 1; bool ste_ats_enabled : 1;bool stall_enabled;include/linux/iommu.h +1
-0@@ -76,6 +76,7 @@ struct iommu_fault_page_request {#define IOMMU_FAULT_PAGE_REQUEST_PASID_VALID (1 << 0)#define IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE (1 << 1)#define IOMMU_FAULT_PAGE_RESPONSE_NEEDS_PASID (1 << 2) +#define IOMMU_FAULT_PAGE_REQUEST_STALLS_TRANS (1 << 3)u32 flags;u32 pasid;u32 grpid;drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +56
-19@@ -921,32 +921,68 @@ static int arm_smmu_drain_queue_for_iopf(struct arm_smmu_device *smmu,return ret; } -static void arm_smmu_page_response (struct device *dev,struct iopf_fault *unused, +static void arm_smmu_page_response (struct device *dev,struct iopf_fault *evt,struct iommu_page_response *resp) {struct arm_smmu_master *master =dev_iommu_priv_get (dev); -u8 resume_resp; +struct arm_smmu_cmd cmd; +int sid; -if (WARN_ON (!master->stall_enabled)) +if (WARN_ON_ONCE (evt->fault.type !=IOMMU_FAULT_PAGE_REQ ))return ; -switch (resp->code) { -case IOMMU_PAGE_RESP_INVALID : -case IOMMU_PAGE_RESP_FAILURE : - resume_resp =CMDQ_RESUME_0_RESP_ABORT ; -break ; -case IOMMU_PAGE_RESP_SUCCESS : - resume_resp =CMDQ_RESUME_0_RESP_RETRY ; -break ; -default : - resume_resp =CMDQ_RESUME_0_RESP_TERM ; -break ; +/* IOPF is gated to num_streams == 1 in arm_smmu_enable_iopf() */ + sid = master->streams[0 ].id; + +if (evt->fault.prm.flags &IOMMU_FAULT_PAGE_REQUEST_STALLS_TRANS ) { +u8 resume_resp; + +if (WARN_ON_ONCE (!master->stall_enabled)) +return ; +switch (resp->code) { +case IOMMU_PAGE_RESP_INVALID : +case IOMMU_PAGE_RESP_FAILURE : + resume_resp =CMDQ_RESUME_0_RESP_ABORT ; +break ; +case IOMMU_PAGE_RESP_SUCCESS : + resume_resp =CMDQ_RESUME_0_RESP_RETRY ; +break ; +default : + resume_resp =CMDQ_RESUME_0_RESP_TERM ; +break ; + } + cmd =arm_smmu_make_cmd_resume (sid, resp->grpid, resume_resp); + }else { +enum pri_resp pri_resp; +bool ssv; + +if (WARN_ON_ONCE (!master->pri_enabled)) +return ; +/* PCIe allows only one PRG Response per group */ +if (!(evt->fault.prm.flags & +IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE )) +return ; +switch (resp->code) { +case IOMMU_PAGE_RESP_SUCCESS : + pri_resp =PRI_RESP_SUCC ; +break ; +case IOMMU_PAGE_RESP_FAILURE : + pri_resp =PRI_RESP_FAIL ; +break ; +case IOMMU_PAGE_RESP_INVALID : + pri_resp =PRI_RESP_DENY ; +break ; +default : +WARN_ON (true); +return ; + } + ssv = !!(evt->fault.prm.flags & +IOMMU_FAULT_PAGE_REQUEST_PASID_VALID ); + cmd =arm_smmu_make_cmd_pri_resp (sid, resp->pasid, ssv, + resp->grpid, pri_resp); } -arm_smmu_cmdq_issue_cmd (master->smmu, -arm_smmu_make_cmd_resume (master->streams[0 ].id, - resp->grpid, - resume_resp)); +arm_smmu_cmdq_issue_cmd (master->smmu, cmd);/* * Don't send a SYNC, it doesn't do anything for RESUME or PRI_RESP. * RESUME consumption guarantees that the stalled transaction will be @@ -2081,7 +2117,8 @@ static int arm_smmu_handle_event(struct arm_smmu_device *smmu, u64 *evt, flt->type =IOMMU_FAULT_PAGE_REQ ; flt->prm = (struct iommu_fault_page_request ){ - .flags =IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE , + .flags =IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE | +IOMMU_FAULT_PAGE_REQUEST_STALLS_TRANS , .grpid = event->stag, .perm = perm, .addr = event->iova,
Re: [PATCH v2 05/11] iommu/arm-smmu-v3: Submit CMDQ_OP_PRI_RESP for IOPF event
Quoted 19 lines · Robin Murphy
Quoted 8 lines · Nicolin Chen
From: Malak Marrid <[email protected]>
To handle
IOMMU_FAULT_PAGE_REQfrom the PRI queue,arm_smmu_page_response()must issue aCMDQ_OP_PRI_RESPback to the SMMU.However, either a stall event in the EVTQ or a PRI request in the PRIQ can surface to the IOPF infrastructure with fault.type ==
IOMMU_FAULT_PAGE_REQ, and a single master can in principle be both stall-capable and PRI-capableNo, the SMMU architecture does all it can to specifically forbid this, see 3.12.1 and 16.4, it just can't be made architecturally ILLEGAL to enable stalls for PCIe devices because there's no strict architectural definition for what "a PCIe device" actually is. Similarly with the note in the definition of STE.EATS about the relationship with
CD.S- the unwritten implication is that defining specific behaviours would only create an unreasonable burden for hardware validation, for the sake of something that nobody in their right mind should ever do anyway.
Quoted 9 lines
The expectation is that RCiEPs which do speak stallable non-PCIe bus protocols will not go to the effort of implementing ATS/PRI capabilities (not least because there's every chance that such protocols simply doesn't have that kind of transaction flow anyway). And conversely that it can be considered an egregious firmware (or system design) error to even claim (let alone force) stall capability for a real PCIe root port which may be deadlocked by blocking its requirement for free-flowing writes. Thus I think we could go so far as to refuse to handle any endpoint which did somehow claim both.
Oh, I missed that. This certainly can simplify things here.
I will fix it.
Thanks!
Nicolin
PATCH v2 6/11 iommu/arm-smmu-v3: Support PRI Page Request in arm_smmu_handle_ppr()
In reply to Nicolin ChenNow, arm_smmu_page_response() can issue CMDQ_OP_PRI_RESP for page requests from the IOPF infrastructure. Forward PRI requests from the priq into the IOPF infrastructure for PRI-enabled masters by building an iopf_fault from the priq entry and calling iommu_report_device_fault().
For an unrecognised StreamID or a master without master->pri_enabled, fall through to the existing "unexpected PRI request" log + LAST-page DENY path to release the credit per the PCIe PRI spec.
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 32 +++++++++++++++++++++ 1 file changed, 32 insertions(+)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +32 -0
@@ -2242,6 +2242,7 @@ static irqreturn_t arm_smmu_evtq_thread(int irq, void *dev)static void arm_smmu_handle_ppr (struct arm_smmu_device *smmu,u64 *evt) { +struct arm_smmu_master *master;u32 sid, ssid;u16 grpid;bool ssv, last;
@@ -2252,6 +2253,37 @@ static void arm_smmu_handle_ppr(struct arm_smmu_device *smmu, u64 *evt) last =FIELD_GET (PRIQ_0_PRG_LAST , evt[0 ]); grpid =FIELD_GET (PRIQ_1_PRG_IDX , evt[1 ]); +mutex_lock (&smmu->streams_mutex); + master =arm_smmu_find_master (smmu, sid); +if (master && master->pri_enabled) { +struct iopf_fault iopf_fault = {0 }; +struct iommu_fault *fault = &iopf_fault.fault; + + fault->type =IOMMU_FAULT_PAGE_REQ ; +if (last) + fault->prm.flags |=IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE ; +if (ssv) { + fault->prm.flags |= +IOMMU_FAULT_PAGE_REQUEST_PASID_VALID ; + fault->prm.pasid = ssid; + } + fault->prm.grpid = grpid; +if (evt[0 ] &PRIQ_0_PERM_READ ) + fault->prm.perm |=IOMMU_FAULT_PERM_READ ; +if (evt[0 ] &PRIQ_0_PERM_WRITE ) + fault->prm.perm |=IOMMU_FAULT_PERM_WRITE ; +if (evt[0 ] &PRIQ_0_PERM_EXEC ) + fault->prm.perm |=IOMMU_FAULT_PERM_EXEC ; +if (evt[0 ] &PRIQ_0_PERM_PRIV ) + fault->prm.perm |=IOMMU_FAULT_PERM_PRIV ; + fault->prm.addr =FIELD_GET (PRIQ_1_ADDR_MASK , evt[1 ]) <<12 ; + +iommu_report_device_fault (master->dev, &iopf_fault); +mutex_unlock (&smmu->streams_mutex); +return ; + } +mutex_unlock (&smmu->streams_mutex); +dev_info (smmu->dev,"unexpected PRI request received:\n" ); dev_info(smmu->dev, "\tsid 0x%08x.0x%05x: [%u%s] %sprivileged %s%s%s access at iova 0x%016llx\n",
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 06/11] iommu/arm-smmu-v3: Support PRI Page Request in arm_smmu_handle_ppr()
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Unbounded memory allocation in report_partial_fault() allows a malicious PCIe endpoint to exhaust kernel memory (OOM).
- [High] Unratelimited logging of unexpected PRI requests allows a malicious endpoint to flood the kernel log. --
commit 9904fc0d39f6cc48ddad4141a58759c0d2ae2811
Author: Nicolin Chen <[email protected]>
iommu/arm-smmu-v3: Support PRI Page Request in arm_smmu_handle_ppr()
Now, arm_smmu_page_response() can issue CMDQ_OP_PRI_RESP for page requests from the IOPF infrastructure. Forward PRI requests from the priq into the IOPF infrastructure for PRI-enabled masters by building an iopf_fault from the priq entry and calling iommu_report_device_fault().
Quoted 17 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +9
-0@@ -2252,6 +2253,37 @@ static void arm_smmu_handle_ppr(struct arm_smmu_device *smmu, u64 *evt) last =FIELD_GET (PRIQ_0_PRG_LAST , evt[0 ]); grpid =FIELD_GET (PRIQ_1_PRG_IDX , evt[1 ]); +mutex_lock (&smmu->streams_mutex); + master =arm_smmu_find_master (smmu, sid); +if (master && master->pri_enabled) { +struct iopf_fault iopf_fault = {0 }; +struct iommu_fault *fault = &iopf_fault.fault; + + fault->type =IOMMU_FAULT_PAGE_REQ ; +if (last) + fault->prm.flags |=IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE ;
[ ... ]
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static void arm_smmu_handle_ppr(struct arm_smmu_device *smmu, u64 *evt) · line 2281
Quoted 3 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +3
-0+ fault->prm.addr =FIELD_GET (PRIQ_1_ADDR_MASK , evt[1 ]) <<12 ; + +iommu_report_device_fault (master->dev, &iopf_fault);
[Severity: High]
This isn't a bug introduced by this patch, but does the path handling partial faults lack bounds checking?
When a PRI request lacks the LAST_PAGE flag, the fault is processed via report_partial_fault() in drivers/iommu/io-pgfault.c, which dynamically allocates memory for it:
drivers/iommu/io-pgfault.c:report_partial_fault() {
...
iopf = kzalloc_obj(*iopf);
if (!iopf)
return -ENOMEM;
iopf->fault = *fault;
mutex_lock(&fault_param->lock);
list_add(&iopf->list, &fault_param->partial);
mutex_unlock(&fault_param->lock);...
}
Is it possible for a compromised PCIe endpoint to exhaust kernel memory by spamming partial page requests with the PRG_LAST bit cleared, completely bypassing IOMMU queue limits and triggering an OOM condition?
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static void arm_smmu_handle_ppr(struct arm_smmu_device *smmu, u64 *evt) · line 2289
Quoted 8 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +5
-0+mutex_unlock (&smmu->streams_mutex); +return ; + } +mutex_unlock (&smmu->streams_mutex); +dev_info (smmu->dev,"unexpected PRI request received:\n" ); dev_info(smmu->dev, "\tsid 0x%08x.0x%05x: [%u%s] %sprivileged %s%s%s access at iova 0x%016llx\n",
[Severity: High]
This is a pre-existing issue, but could this logging lead to log flooding or a soft lockup?
Since this message is unratelimited and can be triggered by external device events in arm_smmu_handle_ppr(), an endpoint sending millions of invalid PRI requests for disabled or unrecognized Stream IDs could force the kernel to synchronously print to the log buffer.
Would it make sense to use dev_info_ratelimited() here to prevent CPU starvation and disk space exhaustion?
Quoted 1 line
sid, ssid, grpid, last ? "L" : "",
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
PATCH v2 7/11 iommu/arm-smmu-v3: Disable PRI when no IRQ handler is registered
In reply to Nicolin Chenarm_smmu_setup_irqs() has three failure paths that leave the priq without a handler: a missing priq IRQ line, devm_request_threaded_irq() failure on the priq IRQ, and devm_request_threaded_irq() failure on the combined IRQ. Each path warned but kept ARM_SMMU_FEAT_PRI set in smmu->features.
With FEAT_PRI still set, arm_smmu_probe_device() calls pci_enable_pri() on PCIe endpoints, which then issue PRI Page Requests that pile up in a priq with no drainer; arm_smmu_setup_irqs() also enables IRQ_CTRL_PRIQ_IRQEN against a handler that does not exist. Separately, arm_smmu_device_reset() has already enabled CR0_PRIQEN before calling arm_smmu_setup_irqs(), so the hardware queue stays enabled regardless of what setup_irqs decides.
Clear ARM_SMMU_FEAT_PRI from all three failure paths so subsequent code treats PRI as unavailable. And disable CR0_PRIQEN after the setup function returns.
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +11 -2
@@ -4738,11 +4738,14 @@ static void arm_smmu_setup_unique_irqs(struct arm_smmu_device *smmu)IRQF_ONESHOT ,"arm-smmu-v3-priq" , smmu); -if (ret <0 ) +if (ret <0 ) {dev_warn (smmu->dev,"failed to enable priq irq\n" ); + smmu->features &= ~ARM_SMMU_FEAT_PRI ; + } }else {dev_warn (smmu->dev,"no priq irq - PRI will be broken\n" ); + smmu->features &= ~ARM_SMMU_FEAT_PRI ; } } }
@@ -4771,8 +4774,10 @@ static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu) arm_smmu_combined_irq_thread,IRQF_ONESHOT , "arm-smmu-v3-combined-irq", smmu); -if (ret <0 ) +if (ret <0 ) {dev_warn (smmu->dev,"failed to enable combined irq\n" ); + smmu->features &= ~ARM_SMMU_FEAT_PRI ; + } }else arm_smmu_setup_unique_irqs (smmu);
@@ -4931,6 +4936,10 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu)return ret; } +/* arm_smmu_setup_irqs() might have unset the ARM_SMMU_FEAT_PRI */ +if (!(smmu->features &ARM_SMMU_FEAT_PRI )) + enables &= ~CR0_PRIQEN ; +if (is_kdump_kernel ()) enables &= ~(CR0_EVTQEN |CR0_PRIQEN );
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 07/11] iommu/arm-smmu-v3: Disable PRI when no IRQ handler is registered
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Updating multiple CR0 enable bits in a single write causes CONSTRAINED UNPREDICTABLE behavior.
Pre-existing issues:
- [High] The kdump kernel path forcibly disables the PRI queue in hardware but leaves the ARM_SMMU_FEAT_PRI software flag set.
- [High] Failure to enable IRQ generation in hardware bypasses the PRI disabling logic, leaving the PRI queue enabled without functioning interrupts.
- [High] SMMU interrupts (EVTQ and GERROR) are enabled in hardware even if the OS fails to register their IRQ handlers, leading to potential unhandled interrupts. --
commit cbf6d221eefa894b802ea8dddcf53abb97fab008
Author: Nicolin Chen <[email protected]>
iommu/arm-smmu-v3: Disable PRI when no IRQ handler is registered
This patch updates the SMMUv3 driver to clear the ARM_SMMU_FEAT_PRI flag when PRI queue IRQ setup fails. It also ensures that the PRI queue is disabled in the hardware CR0 register if the feature is no longer supported after IRQ setup.
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +1
-1@@ -4738,11 +4738,14 @@ static void arm_smmu_setup_unique_irqs(struct arm_smmu_device *smmu)IRQF_ONESHOT ,"arm-smmu-v3-priq" , smmu); -if (ret <0 ) +if (ret <0 ) {
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static void arm_smmu_setup_unique_irqs(struct arm_smmu_device *smmu) · line 4751
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +3
-0dev_warn (smmu->dev,"failed to enable priq irq\n" ); + smmu->features &= ~ARM_SMMU_FEAT_PRI ; + } }else {dev_warn (smmu->dev,"no priq irq - PRI will be broken\n" ); + smmu->features &= ~ARM_SMMU_FEAT_PRI ; } } }
[Severity: High]
This is a pre-existing issue, but EVTQ and GERROR interrupts are enabled in the hardware IRQ_CTRL register even if their IRQ handlers fail to register earlier in arm_smmu_setup_unique_irqs():
irq = smmu->evtq.q.irq;
if (irq) {
ret = devm_request_threaded_irq(...);
if (ret < 0)
dev_warn(smmu->dev, "failed to enable evtq irq\n");Does the unconditional propagation of EVTQ and GERROR enable bits to the hardware trigger persistent unhandled IRQ storms if registration fails?
[ ... ]
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu) · line 4783
Quoted 12 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +3
-1@@ -4771,8 +4774,10 @@ static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu) arm_smmu_combined_irq_thread,IRQF_ONESHOT , "arm-smmu-v3-combined-irq", smmu); -if (ret <0 ) +if (ret <0 ) {dev_warn (smmu->dev,"failed to enable combined irq\n" ); + smmu->features &= ~ARM_SMMU_FEAT_PRI ; + } }else arm_smmu_setup_unique_irqs (smmu);
[Severity: High]
This is a pre-existing issue, but if arm_smmu_write_reg_sync() fails to enable IRQ generation in hardware at the end of arm_smmu_setup_irqs(), the function still returns 0:
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;Does this silent fall-through bypass the PRI disabling logic in the caller and leave the hardware PRI queues enabled without functioning interrupt drainers, potentially causing unrecoverable PCIe bus stalls?
[ ... ]
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static int arm_smmu_device_reset(struct arm_smmu_device *smmu) · line 4944
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +4
-0@@ -4931,6 +4936,10 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu)return ret; } +/* arm_smmu_setup_irqs() might have unset the ARM_SMMU_FEAT_PRI */ +if (!(smmu->features &ARM_SMMU_FEAT_PRI )) + enables &= ~CR0_PRIQEN ; +if (is_kdump_kernel ()) enables &= ~(CR0_EVTQEN |CR0_PRIQEN );
[Severity: High]
This isn't a bug introduced by this patch, but the kdump kernel path forcibly disables the PRI queue in hardware here while leaving the ARM_SMMU_FEAT_PRI software flag set.
Does bypassing the clearance of ARM_SMMU_FEAT_PRI force active endpoint capabilities against disabled hardware queues, causing permanent SMMU aborts?
[Severity: High]
Further down in arm_smmu_device_reset(), the SMMU interface is enabled using the updated enables mask:
enables |= CR0_SMMUEN;
ret = arm_smmu_write_reg_sync(smmu, enables, ARM_SMMU_CR0,
ARM_SMMU_CR0ACK);Does updating multiple CR0 enable bits in a single write cause CONSTRAINED UNPREDICTABLE behavior?
By clearing CR0_PRIQEN above and setting CR0_SMMUEN simultaneously here, might this trigger an unpredictable hardware state? Would sequenced write-and-ACK patterns for each enable bit resolve this synchronization flaw?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
PATCH v2 8/11 iommu/arm-smmu-v3: Allocate IOPF queue for ARM_SMMU_FEAT_PRI
In reply to Nicolin Chenarm_smmu_init_queues() allocates smmu->evtq.iopf only when FEAT_SVA and ARM_SMMU_FEAT_STALLS are both advertised. A subsequent change will add PRI support on top of the IOPF infrastructure, so the IOPF workqueue must also exist on SMMUs supporting FEAT_PRI without FEAT_STALLS. Extend the allocation condition to include FEAT_PRI.
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +1 -1
@@ -4504,7 +4504,7 @@ static int arm_smmu_init_queues(struct arm_smmu_device *smmu)return ret;if ((smmu->features &ARM_SMMU_FEAT_SVA ) && - (smmu->features &ARM_SMMU_FEAT_STALLS )) { + (smmu->features & (ARM_SMMU_FEAT_STALLS |ARM_SMMU_FEAT_PRI ))) { smmu->evtq.iopf =iopf_queue_alloc (dev_name (smmu->dev));if (!smmu->evtq.iopf)return -ENOMEM ;
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 08/11] iommu/arm-smmu-v3: Allocate IOPF queue for ARM_SMMU_FEAT_PRI
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] The condition for allocating the IOPF queue incorrectly mandates ARM_SMMU_FEAT_SVA, leaving evtq.iopf NULL on platforms supporting STALLS or PRI but lacking SVA. This leads to a fatal NULL pointer dereference in arm_smmu_enable_iopf(). --
commit 4cb5a675156f4120c4c0531ad8a519bcc597afe7
Author: Nicolin Chen <[email protected]>
iommu/arm-smmu-v3: Allocate IOPF queue for ARM_SMMU_FEAT_PRI
arm_smmu_init_queues() allocates smmu->evtq.iopf only when FEAT_SVA and ARM_SMMU_FEAT_STALLS are both advertised. A subsequent change will add PRI support on top of the IOPF infrastructure, so the IOPF workqueue must also exist on SMMUs supporting FEAT_PRI without FEAT_STALLS. Extend the allocation condition to include FEAT_PRI.
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static int arm_smmu_init_queues(struct arm_smmu_device *smmu) · line 4507
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +1
-1@@ -4504,7 +4504,7 @@ static int arm_smmu_init_queues(struct arm_smmu_device *smmu)return ret;if ((smmu->features &ARM_SMMU_FEAT_SVA ) && - (smmu->features &ARM_SMMU_FEAT_STALLS )) { + (smmu->features & (ARM_SMMU_FEAT_STALLS |ARM_SMMU_FEAT_PRI ))) {
[Severity: Critical]
This is a pre-existing issue, but does mandating ARM_SMMU_FEAT_SVA leave evtq.iopf as NULL on platforms that support STALLS or PRI but lack SVA?
The SMMU driver sets master->stall_enabled = true if ARM_SMMU_FEAT_STALLS is present (e.g. when dma-can-stall is true), without requiring the runtime SVA feature bit (ARM_SMMU_FEAT_SVA).
If a userspace application (e.g., via IOMMUFD) creates an HWPT with IOMMU_HWPT_FAULT_ID_VALID and attaches a device with STALLS enabled but SVA unsupported, arm_smmu_enable_iopf() will be invoked.
Since it only performs a compile-time check for SVA (IS_ENABLED(CONFIG_ARM_SMMU_V3_SVA)), it proceeds to call:iopf_queue_add_device(master->smmu->evtq.iopf, master->dev)
Since evtq.iopf is NULL, will iopf_queue_add_device unconditionally dereference it at mutex_lock(&queue->lock) and cause a kernel panic?
Quoted 3 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +0
-0smmu->evtq.iopf =iopf_queue_alloc (dev_name (smmu->dev));if (!smmu->evtq.iopf)return -ENOMEM ;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
PATCH v2 9/11 PCI/ATS: Add PRI stubs
In reply to Nicolin ChenFrom: Jean-Philippe Brucker <[email protected]>
The SMMUv3 driver, which can be built without CONFIG_PCI, will soon gain support for PRI. Partially revert commit c6e9aefbf9db ("PCI/ATS: Remove unused PRI and PASID stubs") to re-introduce the PRI stubs, and avoid adding more #ifdefs to the SMMU driver.
--- include/linux/pci-ats.h | 5 +++++ 1 file changed, 5 insertions(+)
include/linux/pci-ats.h +5 -0
@@ -33,6 +33,11 @@ int pci_reset_pri(struct pci_dev *pdev);int pci_prg_resp_pasid_required (struct pci_dev *pdev);bool pci_pri_supported (struct pci_dev *pdev);#else +static inline int pci_enable_pri (struct pci_dev *pdev,u32 reqs) +{return -ENODEV ; } +static inline void pci_disable_pri (struct pci_dev *pdev) { } +static inline int pci_reset_pri (struct pci_dev *pdev) +{return -ENODEV ; }static inline bool pci_pri_supported (struct pci_dev *pdev) {return false; }#endif /* CONFIG_PCI_PRI */
Signature · 1 line
2.43.0
PATCH v2 10/11 PCI/ATS: Export pci_enable_pri() and pci_reset_pri()
In reply to Nicolin ChenFrom: Jean-Philippe Brucker <[email protected]>
The SMMUv3 driver, which is tristate, calls pci_enable_pri() and pci_reset_pri() from its probe path. Export them so the driver can be built as a module.
--- drivers/pci/ats.c | 2 ++ 1 file changed, 2 insertions(+)
drivers/pci/ats.c +2 -0
@@ -266,6 +266,7 @@ int pci_enable_pri(struct pci_dev *pdev, u32 reqs)return 0 ; } +EXPORT_SYMBOL_GPL (pci_enable_pri);/** * pci_disable_pri - Disable PRI capability
@@ -345,6 +346,7 @@ int pci_reset_pri(struct pci_dev *pdev)return 0 ; } +EXPORT_SYMBOL_GPL (pci_reset_pri);/** * pci_prg_resp_pasid_required - Return PRG Response PASID Required bit
Signature · 1 line
2.43.0
PATCH v2 11/11 iommu/arm-smmu-v3: Enable PRI for PCI device in arm_smmu_probe_device()
In reply to Nicolin ChenNow PRI requests can be correctly handled. Enable the PCI cap when probing a PCI device. Also flush the priq in arm_smmu_attach_release().
Set the per-device outstanding request budget to the full priq depth, same as intel-iommu's per-device PRQ_DEPTH choice. A fixed per-device cap won't prevent multiple PRI-capable devices from potentially exceeding the priq's capacity; priq overflow is recoverable per the SMMUv3 spec, and it is rare in practice.
select PCI_PRI in Kconfig like other IOMMUs, gated on PCI so the build can stay clean for non-PCI ARM SMMUv3 configurations.
--- drivers/iommu/arm/Kconfig | 1 + drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-)
drivers/iommu/arm/Kconfig +1 -0
@@ -79,6 +79,7 @@ config ARM_SMMU_V3 select IOMMU_API select IOMMU_IO_PGTABLE_LPAE select GENERIC_MSI_IRQ + select PCI_PRI if PCI select IOMMUFD_DRIVER if IOMMUFD help Support for implementations of the ARM System MMU architecture
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +19 -2
@@ -3149,7 +3149,7 @@ static int arm_smmu_enable_iopf(struct arm_smmu_master *master,* device-specific fault handlers and don't need IOPF, so this is not a * failure. */ -if (!master->stall_enabled) +if (!master->stall_enabled && !master->pri_enabled)return 0 ;/* We're not keeping track of SIDs in fault events */
@@ -3352,6 +3352,12 @@ void arm_smmu_attach_release(struct arm_smmu_attach_state *state)if (smmu->evtq.q.irq)synchronize_irq (smmu->evtq.q.irq); } +/* Drain the hardware priq */ +if (master->pri_enabled) { +arm_smmu_drain_queue_for_iopf (smmu, &smmu->priq.q); +if (smmu->priq.q.irq) +synchronize_irq (smmu->priq.q.irq); + }/* Pending events might be in the combined_irq handler */ if (smmu->combined_irq)synchronize_irq (smmu->combined_irq);
@@ -4282,8 +4288,17 @@ static struct iommu_device *arm_smmu_probe_device(struct device *dev)if (dev_is_pci (dev)) {unsigned int stu =__ffs (smmu->pgsize_bitmap); +struct pci_dev *pdev =to_pci_dev (dev); -pci_prepare_ats (to_pci_dev (dev), stu); +if (!pci_prepare_ats (pdev, stu) &&pci_pri_supported (pdev) && + (smmu->features &ARM_SMMU_FEAT_PRI ) && smmu->evtq.iopf) { +unsigned int reqs =1 << smmu->priq.q.llq.max_n_shift; + +if (!pci_reset_pri (pdev) && !pci_enable_pri (pdev, reqs)) + master->pri_enabled = true; +else +dev_warn (master->dev,"failed to enable PRI\n" ); + } }return &smmu->iommu;
@@ -4299,6 +4314,8 @@ static void arm_smmu_release_device(struct device *dev)WARN_ON (master->iopf_refcount); +if (master->pri_enabled) +pci_disable_pri (to_pci_dev (master->dev));arm_smmu_disable_pasid (master);arm_smmu_remove_master (master);if (arm_smmu_cdtab_allocated (&master->cd_table))
Signature · 1 line
2.43.0
Hi Nicolin Chen,
Quoted 59 lines · Nicolin Chen
The SMMUv3 driver doesn't handle events on the PRI queue or respond to IOPF faults. This series adds the missing pieces, using the IOPF infrastructure, to convert PRI page requests into
iopf_faultsand issueCMDQ_OP_PRI_RESP.The
iopf_queue_flush_dev()contract requires the driver to first drain the hardware PRI queue and synchronize using a threaded IRQ handler before the IOPF software flush. This drove the additional commits compared to v1:
-arm_smmu_drain_queue_for_iopf()drains the hardware queue to the PROD snapshot
-arm_smmu_attach_release()moves the teardown outside the global lock
-synchronize_irq()closes the gap before the final flushThis is on Github:
https://github.com/nicolinc/iommufd/commits/smmuv3_pri-v2FWIW, engineers on the NVIDIA side have managed to verify the PRI feature.
Changelog
v2:
* Allocate evtq.iopf forARM_SMMU_FEAT_PRI
* Pick up Jean's PRI stubs and PRI export patches
* Enable PRI for PCI devices inarm_smmu_probe_device()
* Addarm_smmu_drain_queue_for_iopf()for EVTQ and PRIQ
* Addarm_smmu_attach_release()to rework the IOPF drain
* AddIOMMU_FAULT_PAGE_REQUEST_STALLS_TRANSfor STALL mode
* Gatepci_enable_pri()onFEAT_PRIplus a non-NULL evtq.iopf
* Deny unrecognised-StreamIDPRG_LASTinarm_smmu_handle_ppr()
* Disable PRI when no IRQ handler is registered (unique or combined IRQ)
v1:
https://lore.kernel.org/all/[email protected]/Jean-Philippe Brucker (2):
PCI/ATS: Add PRI stubs
PCI/ATS: Exportpci_enable_pri()andpci_reset_pri()Malak Marrid (1):
iommu/arm-smmu-v3: SubmitCMDQ_OP_PRI_RESPfor IOPF eventNicolin Chen (8):
iommu/arm-smmu-v3: Addarm_smmu_attach_release()iommu/arm-smmu-v3: Factor out__queue_empty()and__queue_consumed()iommu/arm-smmu-v3: Addarm_smmu_drain_queue_for_iopf()helper iommu/arm-smmu-v3: Drain in-flight fault handlers iommu/arm-smmu-v3: Support PRI Page Request inarm_smmu_handle_ppr()iommu/arm-smmu-v3: Disable PRI when no IRQ handler is registered iommu/arm-smmu-v3: Allocate IOPF queue forARM_SMMU_FEAT_PRIiommu/arm-smmu-v3: Enable PRI for PCI device in
arm_smmu_probe_device()drivers/iommu/arm/Kconfig | 1 + drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 3 + include/linux/iommu.h | 1 + include/linux/pci-ats.h | 5 + .../arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 1 + drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 244 +++++++++++++++--- drivers/pci/ats.c | 2 + 7 files changed, 221 insertions(+), 36 deletions(-)
Quoted 1 line
base-commit:
74fa4c177ad09800b007cba043370c887bb1b4e3
One thing I noticed while reviewing: when arm_smmu_priq_thread() detects PRIQ overflow, partial faults (non-LAST pages stored via report_partial_fault()) whose LAST page was lost in the overflow remain permanently in iopf_param->partial. This is a monotonic memory leak — it grows with each overflow event.
Intel VT-d handles this in prq_event_thread() (drivers/iommu/intel/prq.c):
if (head == tail) {
iopf_queue_discard_partial(iommu->iopf_queue);
writel(DMA_PRS_PRO, iommu->reg + DMAR_PRS_REG);
}iopf_queue_discard_partial() was written for exactly this scenario. Could we add the same here arm_smmu_priq_thread() (drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c) ?
if (queue_sync_prod_in(q) == -EOVERFLOW) {
dev_err(smmu->dev, "PRIQ overflow detected -- requests lost\n");
+ iopf_queue_discard_partial(smmu->evtq.iopf);
}At this point all surviving entries have already been consumed by the loop above, so discarding unconditionally is safe — implicitly matching Intel's "head == tail" guard.
Best Regards,
Harsha Vardhan V
Quoted 16 lines · On Fri, Jun 26, 2026 at 08:24:31PM +0530, [email protected] wrote:
One thing I noticed while reviewing: when
arm_smmu_priq_thread()detects PRIQ overflow, partial faults (non-LAST pages stored viareport_partial_fault()) whose LAST page was lost in the overflow remain permanently iniopf_param->partial. This is a monotonic memory leak — it grows with each overflow event.Intel VT-d handles this in
prq_event_thread()(drivers/iommu/intel/prq.c):if (head == tail) { iopf_queue_discard_partial(iommu->iopf_queue); writel(DMA_PRS_PRO, iommu->reg + DMAR_PRS_REG); }
iopf_queue_discard_partial()was written for exactly this scenario. Could we add the same herearm_smmu_priq_thread()(drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c) ?
Quoted 8 lines
if (queue_sync_prod_in(q) == -EOVERFLOW) { dev_err(smmu->dev, "PRIQ overflow detected -- requests lost\n"); + iopf_queue_discard_partial(smmu->evtq.iopf); }At this point all surviving entries have already been consumed by the loop above, so discarding unconditionally is safe — implicitly matching Intel's "head == tail" guard.
I will fold it in.
Thanks!
Nicolin