[PATCH RFC 2/4] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master
From: Peng Fan (OSS)
Date: Wed Sep 16 2026 - 11:11:40 EST
From: Peng Fan <peng.fan@xxxxxxx>
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@xxxxxxx>
---
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