[PATCH v2 2/2] iommu/ipmmu-vmsa: Keep shared micro-TLBs enabled while in use

From: Koichiro Den

Date: Wed Nov 04 2026 - 22:57:05 EST


PCI functions can share a micro-TLB. ipmmu_release_device() disables it
unconditionally, so removing one function makes DMA from the others
bypass translation even though they remain attached to a paging domain.

Leave micro-TLBs unchanged on device release, as other devices may still
use the domain. Instead, disable the micro-TLBs that still select its
context when that context is destroyed. Check the recorded context ID so
freeing an old domain does not disable a micro-TLB already switched to
another one.

Cc: stable@xxxxxxxxxxxxxxx
Fixes: 24dfb197c3b7 ("iommu/ipmmu-vmsa: Call arm_iommu_release_mapping() in release path")
Suggested-by: Robin Murphy <robin.murphy@xxxxxxx>
Signed-off-by: Koichiro Den <den@xxxxxxxxxxxxx>
---
Changes in v2:
- Disable micro-TLBs still selecting a context when that context is
destroyed, rather than reference-counting their users. (Robin)
- Remove the per-device state and restore the original identity-attach
behavior accordingly.

drivers/iommu/ipmmu-vmsa.c | 36 +++++++++++++-----------------------
1 file changed, 13 insertions(+), 23 deletions(-)

diff --git a/drivers/iommu/ipmmu-vmsa.c b/drivers/iommu/ipmmu-vmsa.c
index 99586c68ef35..f13131916b7c 100644
--- a/drivers/iommu/ipmmu-vmsa.c
+++ b/drivers/iommu/ipmmu-vmsa.c
@@ -281,11 +281,6 @@ static void ipmmu_utlb_enable(struct ipmmu_vmsa_domain *domain,
{
struct ipmmu_vmsa_device *mmu = domain->mmu;

- /*
- * TODO: Reference-count the microTLB as several bus masters can be
- * connected to the same microTLB.
- */
-
/* TODO: What should we set the ASID to ? */
ipmmu_imuasid_write(mmu, utlb, 0);
/* TODO: Do we need to flush the microTLB ? */
@@ -466,9 +461,18 @@ static int ipmmu_domain_init_context(struct ipmmu_vmsa_domain *domain)

static void ipmmu_domain_destroy_context(struct ipmmu_vmsa_domain *domain)
{
- if (!domain->mmu)
+ struct ipmmu_vmsa_device *mmu = domain->mmu;
+ unsigned int i;
+
+ if (!mmu)
return;

+ /* Released devices may have left micro-TLBs selecting this context. */
+ for (i = 0; i < mmu->features->num_utlbs; i++) {
+ if (mmu->utlb_ctx[i] == domain->context_id)
+ ipmmu_utlb_disable(domain, i);
+ }
+
/*
* Disable the context. Flush the TLB as required when modifying the
* context registers.
@@ -477,7 +481,7 @@ static void ipmmu_domain_destroy_context(struct ipmmu_vmsa_domain *domain)
*/
ipmmu_ctx_write_all(domain, IMCTR, IMCTR_FLUSH);
ipmmu_tlb_sync(domain);
- ipmmu_domain_free_context(domain->mmu->root, domain->context_id);
+ ipmmu_domain_free_context(mmu->root, domain->context_id);
}

/* -----------------------------------------------------------------------------
@@ -580,10 +584,6 @@ static void ipmmu_domain_free(struct iommu_domain *io_domain)
{
struct ipmmu_vmsa_domain *domain = to_vmsa_domain(io_domain);

- /*
- * Free the domain resources. We assume that all devices have already
- * been detached.
- */
ipmmu_domain_destroy_context(domain);
free_io_pgtable_ops(domain->iop);
kfree(domain);
@@ -863,18 +863,8 @@ static void ipmmu_probe_finalize(struct device *dev)

static void ipmmu_release_device(struct device *dev)
{
- struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
- struct ipmmu_vmsa_device *mmu = to_ipmmu(dev);
- unsigned int i;
-
- for (i = 0; i < fwspec->num_ids; ++i) {
- unsigned int utlb = fwspec->ids[i];
-
- ipmmu_imuctr_write(mmu, utlb, 0);
- mmu->utlb_ctx[utlb] = IPMMU_CTX_INVALID;
- }
-
- arm_iommu_release_mapping(mmu->mapping);
+ /* Micro-TLBs may be shared. Domain teardown handles their cleanup. */
+ arm_iommu_release_mapping(to_ipmmu(dev)->mapping);
}

static const struct iommu_ops ipmmu_ops = {
--
2.54.0