* [PATCH RFC v2 1/4] iommu/arm-smmu-v3: Convert streams from RB tree to XArray
2026-09-21 11:34 [PATCH RFC v2 0/4] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
@ 2026-09-21 11:35 ` Peng Fan (OSS)
2026-09-21 19:24 ` Nicolin Chen
2026-09-21 12:38 ` [PATCH RFC v2 2/4] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master Peng Fan (OSS)
` (2 subsequent siblings)
3 siblings, 1 reply; 6+ messages in thread
From: Peng Fan (OSS) @ 2026-09-21 11:35 UTC (permalink / raw)
To: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
Jean-Philippe Brucker, Jason Gunthorpe
Cc: linux-arm-kernel, iommu, linux-kernel, Peng Fan
From: Peng Fan <peng.fan@nxp.com>
Replace the smmu->streams RB tree with an XArray for SID ->
arm_smmu_stream lookups. The existing streams_mutex serialises all
accesses (both xa_store/xa_erase and xa_load), protecting the lifetime
of returned pointers against concurrent arm_smmu_remove_master()
without requiring RCU grace periods. A mutex (rather than xa_lock) is
needed because several paths sleep while the lock is held:
dmam_alloc_coherent(GFP_KERNEL) in arm_smmu_init_sid_strtab(), and
down_read() inside iommu_report_device_fault().
This removes the RB tree comparators, the rb_node from
arm_smmu_stream, and simplifies duplicate-SID handling for bridged PCI
devices.
No behavioural change intended; preparation for shared-SID support.
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 | 83 ++++++++++++++---------------
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 11 +++-
2 files changed, 48 insertions(+), 46 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 5732f3ba0122d..65e448a69a019 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2024,37 +2024,17 @@ static int arm_smmu_init_l2_strtab(struct arm_smmu_device *smmu, u32 sid)
return 0;
}
-static int arm_smmu_streams_cmp_key(const void *lhs, const struct rb_node *rhs)
-{
- struct arm_smmu_stream *stream_rhs =
- rb_entry(rhs, struct arm_smmu_stream, node);
- const u32 *sid_lhs = lhs;
-
- if (*sid_lhs < stream_rhs->id)
- return -1;
- if (*sid_lhs > stream_rhs->id)
- return 1;
- return 0;
-}
-
-static int arm_smmu_streams_cmp_node(struct rb_node *lhs,
- const struct rb_node *rhs)
-{
- return arm_smmu_streams_cmp_key(
- &rb_entry(lhs, struct arm_smmu_stream, node)->id, rhs);
-}
-
static struct arm_smmu_master *
arm_smmu_find_master(struct arm_smmu_device *smmu, u32 sid)
{
- struct rb_node *node;
+ struct arm_smmu_stream *stream;
lockdep_assert_held(&smmu->streams_mutex);
- node = rb_find(&sid, &smmu->streams, arm_smmu_streams_cmp_key);
- if (!node)
+ stream = xa_load(&smmu->streams, sid);
+ if (!stream)
return NULL;
- return rb_entry(node, struct arm_smmu_stream, node)->master;
+ return stream->master;
}
/* IRQ and event handlers */
@@ -4123,38 +4103,47 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
NULL);
mutex_lock(&smmu->streams_mutex);
- for (i = 0; i < fwspec->num_ids; i++) {
+ for (i = 0; i < master->num_streams; i++) {
struct arm_smmu_stream *new_stream = &master->streams[i];
- struct rb_node *existing;
+ struct arm_smmu_stream *existing;
u32 sid = new_stream->id;
ret = arm_smmu_init_sid_strtab(smmu, sid);
if (ret)
break;
- /* Insert into SID tree */
- existing = rb_find_add(&new_stream->node, &smmu->streams,
- arm_smmu_streams_cmp_node);
- if (existing) {
- struct arm_smmu_master *existing_master =
- rb_entry(existing, struct arm_smmu_stream, node)
- ->master;
-
- /* Bridged PCI devices may end up with duplicated IDs */
- if (existing_master == master)
- continue;
+ /* Bridged PCI devices may end up with duplicated IDs */
+ if (i > 0 && master->streams[i - 1].id == sid)
+ continue;
+ 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));
+ sid, dev_name(existing->master->dev));
ret = -ENODEV;
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) {
- for (i--; i >= 0; i--)
- rb_erase(&master->streams[i].node, &smmu->streams);
+ for (i--; i >= 0; i--) {
+ u32 sid = master->streams[i].id;
+
+ if (i > 0 && master->streams[i - 1].id == sid)
+ continue;
+ xa_erase(&smmu->streams, sid);
+ }
kfree(master->streams);
kfree(master->build_invs);
}
@@ -4167,14 +4156,19 @@ static void arm_smmu_remove_master(struct arm_smmu_master *master)
{
int i;
struct arm_smmu_device *smmu = master->smmu;
- struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(master->dev);
if (!smmu || !master->streams)
return;
mutex_lock(&smmu->streams_mutex);
- for (i = 0; i < fwspec->num_ids; i++)
- rb_erase(&master->streams[i].node, &smmu->streams);
+ for (i = 0; i < master->num_streams; i++) {
+ u32 sid = master->streams[i].id;
+
+ /* Skip intra-master duplicate SIDs */
+ if (i > 0 && master->streams[i - 1].id == sid)
+ continue;
+ xa_erase(&smmu->streams, sid);
+ }
mutex_unlock(&smmu->streams_mutex);
kfree(master->streams);
@@ -4602,7 +4596,7 @@ static int arm_smmu_init_structures(struct arm_smmu_device *smmu)
int ret;
mutex_init(&smmu->streams_mutex);
- smmu->streams = RB_ROOT;
+ xa_init(&smmu->streams);
ret = arm_smmu_init_queues(smmu);
if (ret)
@@ -5627,6 +5621,7 @@ static void arm_smmu_device_remove(struct platform_device *pdev)
iommu_device_unregister(&smmu->iommu);
iommu_device_sysfs_remove(&smmu->iommu);
+ xa_destroy(&smmu->streams);
}
static void arm_smmu_device_shutdown(struct platform_device *pdev)
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 dd2fee2f560e6..97dc97ac704d9 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -14,6 +14,7 @@
#include <linux/kernel.h>
#include <linux/mmzone.h>
#include <linux/sizes.h>
+#include <linux/xarray.h>
struct arm_smmu_device;
struct arm_vsmmu;
@@ -961,14 +962,20 @@ struct arm_smmu_device {
/* IOMMU core code handle */
struct iommu_device iommu;
- struct rb_root streams;
+ /*
+ * XArray of arm_smmu_stream, indexed by SID.
+ * All accesses (reads and writes) are serialised by streams_mutex.
+ * The mutex is held across xa_load() and all subsequent uses of the
+ * returned pointer to prevent use-after-free from concurrent
+ * arm_smmu_remove_master().
+ */
+ struct xarray streams;
struct mutex streams_mutex;
};
struct arm_smmu_stream {
u32 id;
struct arm_smmu_master *master;
- struct rb_node node;
};
struct arm_smmu_vmaster {
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH RFC v2 1/4] iommu/arm-smmu-v3: Convert streams from RB tree to XArray
2026-09-21 11:35 ` [PATCH RFC v2 1/4] iommu/arm-smmu-v3: Convert streams from RB tree to XArray Peng Fan (OSS)
@ 2026-09-21 19:24 ` Nicolin Chen
0 siblings, 0 replies; 6+ messages in thread
From: Nicolin Chen @ 2026-09-21 19:24 UTC (permalink / raw)
To: Peng Fan (OSS)
Cc: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
Jean-Philippe Brucker, Jason Gunthorpe, linux-arm-kernel, iommu,
linux-kernel, Peng Fan
On Mon, Sep 21, 2026 at 07:35:00PM +0800, Peng Fan (OSS) wrote:
> From: Peng Fan <peng.fan@nxp.com>
>
> Replace the smmu->streams RB tree with an XArray for SID ->
> arm_smmu_stream lookups. The existing streams_mutex serialises all
> accesses (both xa_store/xa_erase and xa_load), protecting the lifetime
> of returned pointers against concurrent arm_smmu_remove_master()
> without requiring RCU grace periods. A mutex (rather than xa_lock) is
> needed because several paths sleep while the lock is held:
> dmam_alloc_coherent(GFP_KERNEL) in arm_smmu_init_sid_strtab(), and
> down_read() inside iommu_report_device_fault().
The commit message reads very unconvincing.
The first paragraph starts with an action "replace" without giving
a clear reason for the action.
> This removes the RB tree comparators, the rb_node from
Then the second paragraph repeats the action...
> arm_smmu_stream, and simplifies duplicate-SID handling for bridged PCI
> devices.
... and slightly mentions "simplifies". Maybe it should write about
why this is necessary and how xarray would simplify vs RB tree?
Nicolin
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH RFC v2 2/4] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master
2026-09-21 11:34 [PATCH RFC v2 0/4] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
2026-09-21 11:35 ` [PATCH RFC v2 1/4] iommu/arm-smmu-v3: Convert streams from RB tree to XArray Peng Fan (OSS)
@ 2026-09-21 12:38 ` Peng Fan (OSS)
2026-09-21 12:40 ` [PATCH RFC v2 3/4] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group Peng Fan (OSS)
2026-09-21 12:41 ` [PATCH RFC v2 4/4] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
3 siblings, 0 replies; 6+ messages in thread
From: Peng Fan (OSS) @ 2026-09-21 12:38 UTC (permalink / raw)
To: will, robin.murphy, joro, jpb, jgg
Cc: linux-arm-kernel, iommu, linux-kernel, peng.fan
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
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH RFC v2 3/4] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group
2026-09-21 11:34 [PATCH RFC v2 0/4] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
2026-09-21 11:35 ` [PATCH RFC v2 1/4] iommu/arm-smmu-v3: Convert streams from RB tree to XArray Peng Fan (OSS)
2026-09-21 12:38 ` [PATCH RFC v2 2/4] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master Peng Fan (OSS)
@ 2026-09-21 12:40 ` Peng Fan (OSS)
2026-09-21 12:41 ` [PATCH RFC v2 4/4] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
3 siblings, 0 replies; 6+ messages in thread
From: Peng Fan (OSS) @ 2026-09-21 12:40 UTC (permalink / raw)
To: will, robin.murphy, joro, jpb, jgg
Cc: linux-arm-kernel, iommu, linux-kernel, peng.fan
From: Peng Fan <peng.fan@nxp.com>
arm_smmu_device_group() previously rejected SID aliasing with a comment
saying it was impractical. Now that the XArray makes SID lookup O(1),
detect aliasing at group-assignment time and place the new device into
the same IOMMU group as the master that already owns the SID.
This is a prerequisite for shared-SID support: devices sharing a SID
must share a single IOMMU domain and therefore a single cd_table,
because the STE can only point to one cd_table at a time.
arm_smmu_device_group() is called before arm_smmu_insert_master() adds
the current master's streams to the xarray, so any xarray hit here
belongs to a different, already-probed master. streams_mutex is held
to ensure the xarray is stable and the returned stream object is not
freed concurrently.
If a device has multiple SIDs that land in different existing groups, a
warning is emitted. The IOMMU core has no group-merge API, so this
cannot be fixed up and DMA isolation may be compromised.
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 | 61 ++++++++++++++++++---
1 file changed, 53 insertions(+), 8 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 f53e1871426a5..bb3ee25d10d6e 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4365,19 +4365,64 @@ static int arm_smmu_set_dirty_tracking(struct iommu_domain *domain,
static struct iommu_group *arm_smmu_device_group(struct device *dev)
{
- struct iommu_group *group;
+ struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+ struct arm_smmu_device *smmu = master->smmu;
+ struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
+ struct iommu_group *group = NULL;
+ int i;
/*
- * We don't support devices sharing stream IDs other than PCI RID
- * aliases, since the necessary ID-to-device lookup becomes rather
- * impractical given a potential sparse 32-bit stream ID space.
+ * If any of this device's SIDs are already owned by another master,
+ * place this device in the same IOMMU group as that master. This
+ * ensures that aliasing devices share a single domain and therefore
+ * a single cd_table, which is required because the STE can only
+ * point to one cd_table at a time.
+ *
+ * arm_smmu_device_group() is called before arm_smmu_insert_master()
+ * adds the current master's streams to the xarray, so any hit here
+ * belongs to a different, already-probed master.
+ *
+ * streams_mutex is held to ensure the xarray is stable and the
+ * returned stream object is not freed concurrently.
*/
+ mutex_lock(&smmu->streams_mutex);
+ for (i = 0; i < fwspec->num_ids; i++) {
+ struct arm_smmu_stream *stream =
+ xa_load(&smmu->streams, fwspec->ids[i]);
+ struct iommu_group *existing;
+
+ if (!stream)
+ continue;
+
+ existing = iommu_group_get(stream->master->dev);
+ if (!group) {
+ group = existing;
+ } else if (group != existing) {
+ /*
+ * This device has SIDs in two different groups.
+ * The IOMMU core has no group-merge API, so we
+ * cannot fix this up. Warn and keep the first
+ * group — the mismatched SID will still be
+ * inserted into the XArray and shared, but DMA
+ * isolation is compromised.
+ */
+ dev_warn(dev,
+ "SID 0x%x already in a different IOMMU group than SID 0x%x, expect broken isolation\n",
+ fwspec->ids[i], fwspec->ids[0]);
+ iommu_group_put(existing);
+ } else {
+ iommu_group_put(existing);
+ }
+ }
+ mutex_unlock(&smmu->streams_mutex);
+
+ if (group)
+ return group;
+
if (dev_is_pci(dev))
- group = pci_device_group(dev);
- else
- group = generic_device_group(dev);
+ return pci_device_group(dev);
- return group;
+ return generic_device_group(dev);
}
static int arm_smmu_of_xlate(struct device *dev,
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH RFC v2 4/4] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating
2026-09-21 11:34 [PATCH RFC v2 0/4] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
` (2 preceding siblings ...)
2026-09-21 12:40 ` [PATCH RFC v2 3/4] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group Peng Fan (OSS)
@ 2026-09-21 12:41 ` Peng Fan (OSS)
3 siblings, 0 replies; 6+ messages in thread
From: Peng Fan (OSS) @ 2026-09-21 12:41 UTC (permalink / raw)
To: will, robin.murphy, joro, jpb, jgg
Cc: linux-arm-kernel, iommu, linux-kernel, peng.fan
From: Peng Fan <peng.fan@nxp.com>
For shared SIDs, only the first master to attach writes the STE
(tracked by ste_installed under streams_mutex); subsequent masters
skip the write. On teardown, only the last master writes the ABORT
STE (ref_count == 1). This is handled by arm_smmu_skip_shared_ste().
Fault events on shared SIDs cannot be attributed to a specific master,
so arm_smmu_find_master() returns NULL when ref_count > 1.
Disable SVA, IOPF/stall, and vSMMU nesting for shared-SID masters
since all three require unambiguous SID-to-device mapping.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
.../arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 4 +-
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 7 +++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 59 +++++++++++++++++--
3 files changed, 65 insertions(+), 5 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 ab1078a97d801..258ca42917f07 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
@@ -308,8 +308,10 @@ static int arm_vsmmu_vdevice_init(struct iommufd_vdevice *vdev)
/*
* arm_vsmmu_vsid_to_sid() maps a vSID to master->streams[0] alone, so
* more streams would leave the rest stale and none reads out of bounds.
+ * Shared SIDs are also unsupported for vSMMU since the STE is shared
+ * between multiple masters.
*/
- if (master->num_streams != 1)
+ if (master->num_streams != 1 || master->shared_sid)
return -EOPNOTSUPP;
return 0;
}
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c
index 0a429c64fbf3e..43f8cc81bfdd1 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c
@@ -271,6 +271,13 @@ static int arm_smmu_sva_set_dev_pasid(struct iommu_domain *domain,
if (!(master->smmu->features & ARM_SMMU_FEAT_SVA))
return -EOPNOTSUPP;
+ /*
+ * SVA requires stall-based fault handling which cannot be supported
+ * when multiple devices share a SID (fault routing is ambiguous).
+ */
+ if (master->shared_sid)
+ return -EOPNOTSUPP;
+
/* Prevent arm_smmu_mm_release from being called while we are attaching */
if (!mmget_not_zero(domain->mm))
return -EINVAL;
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 bb3ee25d10d6e..13968191b1452 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2034,6 +2034,13 @@ arm_smmu_find_master(struct arm_smmu_device *smmu, u32 sid)
stream = xa_load(&smmu->streams, sid);
if (!stream)
return NULL;
+ /*
+ * For shared SIDs (ref_count > 1) we cannot determine which master
+ * triggered the fault, so return NULL to let the caller handle it
+ * as an unresolvable event.
+ */
+ if (stream->ref_count > 1)
+ return NULL;
return stream->master;
}
@@ -2944,11 +2951,43 @@ arm_smmu_get_step_for_sid(struct arm_smmu_device *smmu, u32 sid)
}
}
+/*
+ * For shared SIDs, check whether this master should skip the STE write.
+ *
+ * Setup path: only the first master writes the STE; subsequent masters
+ * skip because ste_installed is already true.
+ *
+ * Teardown path (ABORT): skip while other masters still share the SID
+ * (ref_count > 1). Only the last remaining master writes the ABORT STE.
+ *
+ * Returns true if the STE write should be skipped for this SID.
+ */
+static bool arm_smmu_skip_shared_ste(struct arm_smmu_device *smmu,
+ u32 sid, bool is_abort)
+{
+ struct arm_smmu_stream *stream;
+ bool skip = false;
+
+ mutex_lock(&smmu->streams_mutex);
+ stream = xa_load(&smmu->streams, sid);
+ if (stream) {
+ if (is_abort)
+ skip = stream->ref_count > 1;
+ else if (stream->ste_installed)
+ skip = true;
+ else
+ stream->ste_installed = true;
+ }
+ mutex_unlock(&smmu->streams_mutex);
+ return skip;
+}
+
void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master,
const struct arm_smmu_ste *target)
{
int i, j;
struct arm_smmu_device *smmu = master->smmu;
+ bool is_abort;
master->cd_table.in_ste =
FIELD_GET(STRTAB_STE_0_CFG, le64_to_cpu(target->data[0])) ==
@@ -2957,10 +2996,12 @@ void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master,
FIELD_GET(STRTAB_STE_1_EATS, le64_to_cpu(target->data[1])) ==
STRTAB_STE_1_EATS_TRANS;
+ is_abort = (FIELD_GET(STRTAB_STE_0_CFG, le64_to_cpu(target->data[0])) ==
+ STRTAB_STE_0_CFG_ABORT);
+
for (i = 0; i < master->num_streams; ++i) {
u32 sid = master->streams[i].id;
- struct arm_smmu_ste *step =
- arm_smmu_get_step_for_sid(smmu, sid);
+ struct arm_smmu_ste *step;
/* Bridged PCI devices may end up with duplicated IDs */
for (j = 0; j < i; j++)
@@ -2969,6 +3010,11 @@ void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master,
if (j < i)
continue;
+ if (master->shared_sid &&
+ arm_smmu_skip_shared_ste(smmu, sid, is_abort))
+ continue;
+
+ step = arm_smmu_get_step_for_sid(smmu, sid);
arm_smmu_write_ste(master, sid, step, target);
}
}
@@ -3116,8 +3162,13 @@ static int arm_smmu_enable_iopf(struct arm_smmu_master *master,
if (!master->stall_enabled)
return 0;
- /* We're not keeping track of SIDs in fault events */
- if (master->num_streams != 1)
+ /*
+ * We're not keeping track of SIDs in fault events, and stall/PRI
+ * cannot be supported when multiple devices share a SID because page
+ * fault responses are routed by RID/SID and we cannot distinguish
+ * which device triggered the fault.
+ */
+ if (master->num_streams != 1 || master->shared_sid)
return -EOPNOTSUPP;
if (master->iopf_refcount) {
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread