[PATCH RFC 2/4] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master
From: Peng Fan (OSS) <hidden>
Date: 2026-09-16 15:08:34
Also in:
linux-iommu, lkml
Subsystem:
arm smmu drivers, iommu subsystem, the rest · Maintainers:
Will Deacon, Joerg Roedel, Linus Torvalds
From: Peng Fan <peng.fan@nxp.com> When arm_smmu_insert_master() encounters a SID already owned by a different master, instead of failing with -ENODEV, increment the canonical stream's ref_count and mark both masters as shared_sid. A per-SID shared_link list tracks all co-sharing streams so that ownership can be transferred when the canonical owner is removed. When arm_smmu_remove_master() removes a master: - Owning master with ref_count > 1: transfer the XArray entry to the next sharer via list_first_entry + xa_store, and clear shared_sid on the successor when ref_count drops to 1. - Owning master with ref_count == 1: xa_erase (sole owner). - Non-owning sharer: list_del + ref_count--. Clear shared_sid on the canonical owner when ref_count drops to 1. New fields: - arm_smmu_stream: ref_count, ste_installed, shared_link (list_head) - arm_smmu_master: shared_sid (disables SVA, stall, IOPF) Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Peng Fan <peng.fan@nxp.com> --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 88 +++++++++++++++++++++++------ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 23 ++++++++ 2 files changed, 94 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 65e448a69a019..f53e1871426a5 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c@@ -4095,6 +4095,7 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu, new_stream->id = fwspec->ids[i]; new_stream->master = master; + INIT_LIST_HEAD(&new_stream->shared_link); } /* Put the ids into order for sorted to_merge/to_unref arrays */
@@ -4118,31 +4119,46 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu, existing = xa_load(&smmu->streams, sid); if (existing) { - dev_warn(master->dev, - "Aliasing StreamID 0x%x (from %s) unsupported, expect DMA to be broken\n", - sid, dev_name(existing->master->dev)); - ret = -ENODEV; - break; + /* + * Another master already owns this SID. Bump the + * refcount, mark both masters as sharing, and link + * our stream so ownership can be transferred later. + */ + existing->ref_count++; + existing->master->shared_sid = true; + master->shared_sid = true; + list_add_tail(&new_stream->shared_link, + &existing->shared_link); + } else { + new_stream->ref_count = 1; + new_stream->ste_installed = false; + ret = xa_err(xa_store(&smmu->streams, sid, new_stream, + GFP_KERNEL)); + if (ret) + break; } - - /* - * xa_store() returns the old entry (void *) on success - * or an ERR_PTR on allocation failure. Use xa_err() to - * convert to a standard errno. - */ - ret = xa_err(xa_store(&smmu->streams, sid, new_stream, - GFP_KERNEL)); - if (ret) - break; } if (ret) { + /* Undo any successful insertions / refcount bumps */ for (i--; i >= 0; i--) { + struct arm_smmu_stream *existing; u32 sid = master->streams[i].id; if (i > 0 && master->streams[i - 1].id == sid) continue; - xa_erase(&smmu->streams, sid); + + existing = xa_load(&smmu->streams, sid); + if (!existing) + continue; + if (existing->master == master) { + xa_erase(&smmu->streams, sid); + } else { + list_del_init(&master->streams[i].shared_link); + existing->ref_count--; + if (existing->ref_count == 1) + existing->master->shared_sid = false; + } } kfree(master->streams); kfree(master->build_invs);
@@ -4163,11 +4179,49 @@ static void arm_smmu_remove_master(struct arm_smmu_master *master) mutex_lock(&smmu->streams_mutex); for (i = 0; i < master->num_streams; i++) { u32 sid = master->streams[i].id; + struct arm_smmu_stream *stream; /* Skip intra-master duplicate SIDs */ if (i > 0 && master->streams[i - 1].id == sid) continue; - xa_erase(&smmu->streams, sid); + + stream = xa_load(&smmu->streams, sid); + if (!stream) + continue; + + if (stream->master == master) { + /* + * This master owns the canonical XArray entry. + * Erase when the last reference drops; otherwise + * transfer ownership to the next sharer. + */ + stream->ref_count--; + if (stream->ref_count == 0) { + xa_erase(&smmu->streams, sid); + } else { + struct arm_smmu_stream *next; + + next = list_first_entry(&stream->shared_link, + struct arm_smmu_stream, + shared_link); + list_del(&stream->shared_link); + next->ref_count = stream->ref_count; + next->ste_installed = stream->ste_installed; + xa_store(&smmu->streams, sid, next, + GFP_KERNEL); + if (next->ref_count == 1) + next->master->shared_sid = false; + } + } else { + /* + * Non-owning sharer: unlink from the shared list + * and drop the refcount on the canonical entry. + */ + list_del_init(&master->streams[i].shared_link); + stream->ref_count--; + if (stream->ref_count == 1) + stream->master->shared_sid = false; + } } mutex_unlock(&smmu->streams_mutex);
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 97dc97ac704d9..cf245b5de2bd2 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h@@ -976,6 +976,24 @@ struct arm_smmu_device { struct arm_smmu_stream { u32 id; struct arm_smmu_master *master; + /* + * ref_count > 1 means multiple masters share this SID. Protected by + * smmu->streams_mutex. + */ + unsigned int ref_count; + /* + * When ref_count > 1 the STE has already been written by the first + * master; subsequent masters must skip the write. + */ + bool ste_installed; + /* + * Links all arm_smmu_stream objects that share the same SID across + * different masters. The canonical (XArray-stored) entry is the list + * head; non-owning sharers are linked into it. Used to transfer + * XArray ownership when the current owner is removed. + * Protected by smmu->streams_mutex. + */ + struct list_head shared_link; }; struct arm_smmu_vmaster {
@@ -1024,6 +1042,11 @@ struct arm_smmu_master { bool ste_ats_enabled : 1; bool stall_enabled; bool ats_always_on; + /* + * True when at least one of this master's SIDs is shared with another + * master. SVA, stall and IOPF are disabled for such masters. + */ + bool shared_sid; unsigned int ssid_bits; unsigned int iopf_refcount; };
--
2.34.1