* [PATCH RFC v5 1/6] iommu/arm-smmu-v3: Don't rb_erase() a never-inserted stream node
2026-10-06 12:19 [PATCH RFC v5 0/6] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
@ 2026-10-06 12:19 ` Peng Fan (OSS)
2026-10-06 12:19 ` [PATCH RFC v5 2/6] iommu/arm-smmu-v3: Allocate streams individually Peng Fan (OSS)
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Peng Fan (OSS) @ 2026-10-06 12:19 UTC (permalink / raw)
To: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
Jean-Philippe Brucker, Nicolin Chen, Jason Gunthorpe,
Thierry Reding, Krishna Reddy, Jonathan Hunter, Jason Gunthorpe
Cc: linux-arm-kernel, iommu, linux-kernel, linux-tegra, Peng Fan,
Mostafa Saleh
From: Nicolin Chen <nicolinc@nvidia.com>
arm_smmu_insert_master() skips inserting a stream whose StreamID duplicates
one the same master already owns (bridged PCI devices can present duplicate
IDs), leaving that master->streams[i].node zeroed and unlinked from the
smmu->streams rb-tree.
Both the insert error-rollback loop and arm_smmu_remove_master() then call
rb_erase() on every master->streams[i].node unconditionally. rb_erase() on
a zeroed node sees a NULL parent, treats the node as the tree root and sets
root->rb_node = NULL, silently emptying the whole SID tree and breaking SID
lookups (and DMA) for every other master on the SMMU.
Mark each node with RB_CLEAR_NODE() after sort_nonatomic() reorders the
array, since sorting relocates the entries and would leave the earlier
self-referential RB_CLEAR_NODE() pointer stale. An un-inserted node then
stays RB_EMPTY_NODE() and is skipped in both erase loops; inserted nodes
are linked by rb_find_add() and erased as before.
Fixes: b00d24997a11 ("iommu/arm-smmu-v3: Fix iommu_device_probe bug due to duplicated stream ids")
Assisted-by: LLM
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
Reviewed-by: Mostafa Saleh <smostafa@google.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 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 98f105798b89a..dba6c2942af8f 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4122,6 +4122,13 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
sizeof(master->streams[0]), arm_smmu_stream_id_cmp,
NULL);
+ /*
+ * Clear after sorting: RB_CLEAR_NODE() records the node's own address,
+ * which sort_nonatomic() invalidates by relocating the entries.
+ */
+ for (i = 0; i < fwspec->num_ids; i++)
+ RB_CLEAR_NODE(&master->streams[i].node);
+
mutex_lock(&smmu->streams_mutex);
for (i = 0; i < fwspec->num_ids; i++) {
struct arm_smmu_stream *new_stream = &master->streams[i];
@@ -4154,7 +4161,9 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
if (ret) {
for (i--; i >= 0; i--)
- rb_erase(&master->streams[i].node, &smmu->streams);
+ if (!RB_EMPTY_NODE(&master->streams[i].node))
+ rb_erase(&master->streams[i].node,
+ &smmu->streams);
kfree(master->streams);
kfree(master->build_invs);
}
@@ -4174,7 +4183,8 @@ static void arm_smmu_remove_master(struct arm_smmu_master *master)
mutex_lock(&smmu->streams_mutex);
for (i = 0; i < fwspec->num_ids; i++)
- rb_erase(&master->streams[i].node, &smmu->streams);
+ if (!RB_EMPTY_NODE(&master->streams[i].node))
+ rb_erase(&master->streams[i].node, &smmu->streams);
mutex_unlock(&smmu->streams_mutex);
kfree(master->streams);
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH RFC v5 2/6] iommu/arm-smmu-v3: Allocate streams individually
2026-10-06 12:19 [PATCH RFC v5 0/6] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
2026-10-06 12:19 ` [PATCH RFC v5 1/6] iommu/arm-smmu-v3: Don't rb_erase() a never-inserted stream node Peng Fan (OSS)
@ 2026-10-06 12:19 ` Peng Fan (OSS)
2026-10-06 12:19 ` [PATCH RFC v5 3/6] iommu/arm-smmu-v3: Delay stream allocation to inside the mutex Peng Fan (OSS)
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Peng Fan (OSS) @ 2026-10-06 12:19 UTC (permalink / raw)
To: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
Jean-Philippe Brucker, Nicolin Chen, Jason Gunthorpe,
Thierry Reding, Krishna Reddy, Jonathan Hunter, Jason Gunthorpe
Cc: linux-arm-kernel, iommu, linux-kernel, linux-tegra, Peng Fan
From: Peng Fan <peng.fan@nxp.com>
Change master->streams from an embedded array of struct arm_smmu_stream
to an array of pointers, with each stream individually allocated.
Prepare for shared-SID support where multiple masters will point to the
same stream object. With embedded structs, sharing requires duplicating
stream state and manually keeping fields like ste_installed in sync.
With individually allocated streams, a sharing master can simply point to
the existing stream.
The sort comparator is updated to dereference the pointer indirection.
No functional change.
Suggested-by: Nicolin Chen <nicolinc@nvidia.com>
Link: https://lore.kernel.org/linux-iommu/arG6hmng3NddGEHm@nvidia.com/
Assisted-by: LLM
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 2 +-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 52 ++++++++++++++--------
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 2 +-
drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c | 2 +-
4 files changed, 37 insertions(+), 21 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..0934a6bbd3e08 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
@@ -294,7 +294,7 @@ static int arm_vsmmu_vsid_to_sid(struct arm_vsmmu *vsmmu, u32 vsid, u32 *sid)
/* At this moment, iommufd only supports PCI device that has one SID */
if (sid)
- *sid = master->streams[0].id;
+ *sid = master->streams[0]->id;
unlock:
xa_unlock(&vsmmu->core.vdevs);
return ret;
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 dba6c2942af8f..9d34eac196a65 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -972,7 +972,7 @@ static void arm_smmu_page_response(struct device *dev, struct iopf_fault *unused
}
arm_smmu_cmdq_issue_cmd(master->smmu,
- arm_smmu_make_cmd_resume(master->streams[0].id,
+ arm_smmu_make_cmd_resume(master->streams[0]->id,
resp->grpid,
resume_resp));
/*
@@ -1504,7 +1504,7 @@ static void arm_smmu_sync_cd(struct arm_smmu_master *master,
for (i = 0; i < master->num_streams; i++)
arm_smmu_cmdq_batch_add_cmd(
smmu, &cmds,
- arm_smmu_make_cmd_cfgi_cd(master->streams[i].id, ssid,
+ arm_smmu_make_cmd_cfgi_cd(master->streams[i]->id, ssid,
leaf));
arm_smmu_cmdq_batch_submit(smmu, &cmds);
@@ -2431,7 +2431,7 @@ static int arm_smmu_atc_inv_master(struct arm_smmu_master *master,
for (i = 0; i < master->num_streams; i++)
arm_smmu_cmdq_batch_add_cmd(
master->smmu, &cmds,
- arm_smmu_make_cmd_atc_inv_all(master->streams[i].id,
+ arm_smmu_make_cmd_atc_inv_all(master->streams[i]->id,
ssid));
return arm_smmu_cmdq_batch_submit(master->smmu, &cmds);
@@ -2978,13 +2978,13 @@ void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master,
STRTAB_STE_1_EATS_TRANS;
for (i = 0; i < master->num_streams; ++i) {
- u32 sid = master->streams[i].id;
+ u32 sid = master->streams[i]->id;
struct arm_smmu_ste *step =
arm_smmu_get_step_for_sid(smmu, sid);
/* Bridged PCI devices may end up with duplicated IDs */
for (j = 0; j < i; j++)
- if (master->streams[j].id == sid)
+ if (master->streams[j]->id == sid)
break;
if (j < i)
continue;
@@ -3276,7 +3276,7 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,
*/
if (!arm_smmu_master_build_inv(
master, nesting ? INV_TYPE_ATS_FULL : INV_TYPE_ATS,
- master->streams[i].id, ssid, 0))
+ master->streams[i]->id, ssid, 0))
return NULL;
}
@@ -4078,10 +4078,10 @@ static int arm_smmu_init_sid_strtab(struct arm_smmu_device *smmu, u32 sid)
static int arm_smmu_stream_id_cmp(const void *_l, const void *_r)
{
- const typeof_member(struct arm_smmu_stream, id) *l = _l;
- const typeof_member(struct arm_smmu_stream, id) *r = _r;
+ const struct arm_smmu_stream * const *l = _l;
+ const struct arm_smmu_stream * const *r = _r;
- return cmp_int(*l, *r);
+ return cmp_int((*l)->id, (*r)->id);
}
static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
@@ -4111,10 +4111,16 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
}
for (i = 0; i < fwspec->num_ids; i++) {
- struct arm_smmu_stream *new_stream = &master->streams[i];
+ struct arm_smmu_stream *new_stream;
+ new_stream = kzalloc_obj(*new_stream, GFP_KERNEL);
+ if (!new_stream) {
+ ret = -ENOMEM;
+ goto out_free_streams;
+ }
new_stream->id = fwspec->ids[i];
new_stream->master = master;
+ master->streams[i] = new_stream;
}
/* Put the ids into order for sorted to_merge/to_unref arrays */
@@ -4127,11 +4133,11 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
* which sort_nonatomic() invalidates by relocating the entries.
*/
for (i = 0; i < fwspec->num_ids; i++)
- RB_CLEAR_NODE(&master->streams[i].node);
+ RB_CLEAR_NODE(&master->streams[i]->node);
mutex_lock(&smmu->streams_mutex);
for (i = 0; i < fwspec->num_ids; i++) {
- struct arm_smmu_stream *new_stream = &master->streams[i];
+ struct arm_smmu_stream *new_stream = master->streams[i];
struct rb_node *existing;
u32 sid = new_stream->id;
@@ -4161,14 +4167,21 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
if (ret) {
for (i--; i >= 0; i--)
- if (!RB_EMPTY_NODE(&master->streams[i].node))
- rb_erase(&master->streams[i].node,
+ if (!RB_EMPTY_NODE(&master->streams[i]->node))
+ rb_erase(&master->streams[i]->node,
&smmu->streams);
- kfree(master->streams);
- kfree(master->build_invs);
+ mutex_unlock(&smmu->streams_mutex);
+ goto out_free_streams;
}
mutex_unlock(&smmu->streams_mutex);
+ return 0;
+
+out_free_streams:
+ for (i = 0; i < master->num_streams; i++)
+ kfree(master->streams[i]);
+ kfree(master->streams);
+ kfree(master->build_invs);
return ret;
}
@@ -4183,10 +4196,13 @@ static void arm_smmu_remove_master(struct arm_smmu_master *master)
mutex_lock(&smmu->streams_mutex);
for (i = 0; i < fwspec->num_ids; i++)
- if (!RB_EMPTY_NODE(&master->streams[i].node))
- rb_erase(&master->streams[i].node, &smmu->streams);
+ if (!RB_EMPTY_NODE(&master->streams[i]->node))
+ rb_erase(&master->streams[i]->node, &smmu->streams);
mutex_unlock(&smmu->streams_mutex);
+ for (i = 0; i < master->num_streams; i++)
+ kfree(master->streams[i]);
+
kfree(master->streams);
kfree(master->build_invs);
}
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..ba430078cbce9 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -1000,7 +1000,7 @@ struct arm_smmu_event {
struct arm_smmu_master {
struct arm_smmu_device *smmu;
struct device *dev;
- struct arm_smmu_stream *streams;
+ struct arm_smmu_stream **streams;
/*
* Scratch memory for a to_merge or to_unref array to build a per-domain
* invalidation array. It'll be pre-allocated with enough enries for all
diff --git a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
index 6644075c1431e..bc62a3d5a63f9 100644
--- a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
+++ b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
@@ -1257,7 +1257,7 @@ static int tegra241_vintf_init_vsid(struct iommufd_vdevice *vdev)
struct arm_smmu_master *master = dev_iommu_priv_get(dev);
struct tegra241_vintf *vintf = viommu_to_vintf(vdev->viommu);
struct tegra241_vintf_sid *vsid = vdev_to_vsid(vdev);
- struct arm_smmu_stream *stream = &master->streams[0];
+ struct arm_smmu_stream *stream = master->streams[0];
u64 virt_sid = vdev->virt_id;
int sidx;
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH RFC v5 3/6] iommu/arm-smmu-v3: Delay stream allocation to inside the mutex
2026-10-06 12:19 [PATCH RFC v5 0/6] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
2026-10-06 12:19 ` [PATCH RFC v5 1/6] iommu/arm-smmu-v3: Don't rb_erase() a never-inserted stream node Peng Fan (OSS)
2026-10-06 12:19 ` [PATCH RFC v5 2/6] iommu/arm-smmu-v3: Allocate streams individually Peng Fan (OSS)
@ 2026-10-06 12:19 ` Peng Fan (OSS)
2026-10-06 12:19 ` [PATCH RFC v5 4/6] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master Peng Fan (OSS)
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Peng Fan (OSS) @ 2026-10-06 12:19 UTC (permalink / raw)
To: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
Jean-Philippe Brucker, Nicolin Chen, Jason Gunthorpe,
Thierry Reding, Krishna Reddy, Jonathan Hunter, Jason Gunthorpe
Cc: linux-arm-kernel, iommu, linux-kernel, linux-tegra, Peng Fan
From: Peng Fan <peng.fan@nxp.com>
Move arm_smmu_stream allocation from upfront (before the mutex) into
the mutex-protected loop in arm_smmu_insert_master(). Instead of
pre-allocating all stream objects and then inserting them into the RB
tree, first look up whether the SID already exists in the tree. Only
allocate and insert a new stream when no existing entry is found, then
avoid unnecessary allocations when bridged PCI devices produce duplicated
IDs. Prepare the code for a subsequent patch that will reuse existing
streams when stream IDs are shared across masters.
The sort is also moved after the mutex section, since streams are now
populated inside the loop rather than beforehand.
Assisted-by: LLM
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 102 +++++++++++++++-------------
1 file changed, 53 insertions(+), 49 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 9d34eac196a65..69c2c3596b06a 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4110,52 +4110,29 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
return -ENOMEM;
}
- for (i = 0; i < fwspec->num_ids; i++) {
- struct arm_smmu_stream *new_stream;
-
- new_stream = kzalloc_obj(*new_stream, GFP_KERNEL);
- if (!new_stream) {
- ret = -ENOMEM;
- goto out_free_streams;
- }
- new_stream->id = fwspec->ids[i];
- new_stream->master = master;
- master->streams[i] = new_stream;
- }
-
- /* Put the ids into order for sorted to_merge/to_unref arrays */
- sort(master->streams, master->num_streams,
- sizeof(master->streams[0]), arm_smmu_stream_id_cmp,
- NULL);
-
- /*
- * Clear after sorting: RB_CLEAR_NODE() records the node's own address,
- * which sort_nonatomic() invalidates by relocating the entries.
- */
- for (i = 0; i < fwspec->num_ids; i++)
- RB_CLEAR_NODE(&master->streams[i]->node);
-
mutex_lock(&smmu->streams_mutex);
for (i = 0; i < fwspec->num_ids; i++) {
- struct arm_smmu_stream *new_stream = master->streams[i];
+ struct arm_smmu_stream *stream;
struct rb_node *existing;
- u32 sid = new_stream->id;
+ u32 sid = fwspec->ids[i];
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);
+ existing = rb_find(&sid, &smmu->streams,
+ arm_smmu_streams_cmp_key);
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)
+ if (existing_master == master) {
+ master->streams[i] = rb_entry(existing,
+ struct arm_smmu_stream, node);
continue;
+ }
dev_warn(master->dev,
"Aliasing StreamID 0x%x (from %s) unsupported, expect DMA to be broken\n",
@@ -4163,45 +4140,72 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
ret = -ENODEV;
break;
}
+
+ stream = kzalloc_obj(*stream, GFP_KERNEL);
+ if (!stream) {
+ ret = -ENOMEM;
+ break;
+ }
+ stream->id = sid;
+ stream->master = master;
+
+ rb_find_add(&stream->node, &smmu->streams,
+ arm_smmu_streams_cmp_node);
+ master->streams[i] = stream;
}
if (ret) {
- for (i--; i >= 0; i--)
- if (!RB_EMPTY_NODE(&master->streams[i]->node))
- rb_erase(&master->streams[i]->node,
- &smmu->streams);
+ for (i--; i >= 0; i--) {
+ int j;
+
+ if (!master->streams[i])
+ continue;
+ /* Skip duplicated SID pointers already freed */
+ for (j = 0; j < i; j++)
+ if (master->streams[j] == master->streams[i])
+ break;
+ if (j < i)
+ continue;
+ rb_erase(&master->streams[i]->node, &smmu->streams);
+ kfree(master->streams[i]);
+ }
mutex_unlock(&smmu->streams_mutex);
- goto out_free_streams;
+ kfree(master->streams);
+ kfree(master->build_invs);
+ return ret;
}
mutex_unlock(&smmu->streams_mutex);
- return 0;
+ /* Put the ids into order for sorted to_merge/to_unref arrays */
+ sort(master->streams, master->num_streams,
+ sizeof(master->streams[0]), arm_smmu_stream_id_cmp,
+ NULL);
-out_free_streams:
- for (i = 0; i < master->num_streams; i++)
- kfree(master->streams[i]);
- kfree(master->streams);
- kfree(master->build_invs);
- return ret;
+ return 0;
}
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++)
- if (!RB_EMPTY_NODE(&master->streams[i]->node))
- rb_erase(&master->streams[i]->node, &smmu->streams);
- mutex_unlock(&smmu->streams_mutex);
+ for (i = 0; i < master->num_streams; i++) {
+ int j;
- for (i = 0; i < master->num_streams; i++)
+ /* Skip duplicated SID pointers already freed */
+ for (j = 0; j < i; j++)
+ if (master->streams[j] == master->streams[i])
+ break;
+ if (j < i)
+ continue;
+ rb_erase(&master->streams[i]->node, &smmu->streams);
kfree(master->streams[i]);
+ }
+ mutex_unlock(&smmu->streams_mutex);
kfree(master->streams);
kfree(master->build_invs);
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH RFC v5 4/6] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master
2026-10-06 12:19 [PATCH RFC v5 0/6] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
` (2 preceding siblings ...)
2026-10-06 12:19 ` [PATCH RFC v5 3/6] iommu/arm-smmu-v3: Delay stream allocation to inside the mutex Peng Fan (OSS)
@ 2026-10-06 12:19 ` Peng Fan (OSS)
2026-10-06 12:19 ` [PATCH RFC v5 5/6] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group Peng Fan (OSS)
2026-10-06 12:19 ` [PATCH RFC v5 6/6] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
5 siblings, 0 replies; 7+ messages in thread
From: Peng Fan (OSS) @ 2026-10-06 12:19 UTC (permalink / raw)
To: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
Jean-Philippe Brucker, Nicolin Chen, Jason Gunthorpe,
Thierry Reding, Krishna Reddy, Jonathan Hunter, Jason Gunthorpe
Cc: linux-arm-kernel, iommu, linux-kernel, linux-tegra, Peng Fan
From: Peng Fan <peng.fan@nxp.com>
On SoCs like NXP i.MX95 where the SMMU has a limited number of Stream
IDs (e.g. 6-bit bus = 64 SIDs), multiple DMA-capable devices are
assigned the same Stream ID by hardware design.
Refactor arm_smmu_find_master() into arm_smmu_find_stream() to look up
streams by SID inside the lock. When a second master attempts to use a
SID that is already claimed, link the masters together via the stream's
shared_masters list instead of rejecting the probe.
Sharing is restricted to single-SID masters (num_streams == 1) to keep
the STE lifecycle simple. Multi-SID sharing is explicitly rejected.
On removal, a shared master detaches from the shared_masters list. The
stream and its RB tree entry are kept alive until the last master using
that SID is removed.
Suggested-by: Nicolin Chen <nicolinc@nvidia.com>
Link: https://lore.kernel.org/linux-iommu/arG6hmng3NddGEHm@nvidia.com/
Assisted-by: LLM
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 92 ++++++++++++++++++++---------
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 3 +
2 files changed, 66 insertions(+), 29 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 69c2c3596b06a..d306e4af90ef0 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2044,8 +2044,8 @@ static int arm_smmu_streams_cmp_node(struct rb_node *lhs,
&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)
+static struct arm_smmu_stream *
+arm_smmu_find_stream(struct arm_smmu_device *smmu, u32 sid)
{
struct rb_node *node;
@@ -2054,7 +2054,20 @@ arm_smmu_find_master(struct arm_smmu_device *smmu, u32 sid)
node = rb_find(&sid, &smmu->streams, arm_smmu_streams_cmp_key);
if (!node)
return NULL;
- return rb_entry(node, struct arm_smmu_stream, node)->master;
+ return rb_entry(node, struct arm_smmu_stream, node);
+}
+
+static struct arm_smmu_master *
+arm_smmu_find_master(struct arm_smmu_device *smmu, u32 sid)
+{
+ struct arm_smmu_stream *stream;
+
+ stream = arm_smmu_find_stream(smmu, sid);
+ if (!stream)
+ return NULL;
+ if (!list_empty(&stream->shared_masters))
+ return NULL;
+ return stream->master;
}
/* IRQ and event handlers */
@@ -4109,36 +4122,41 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
kfree(master->streams);
return -ENOMEM;
}
+ INIT_LIST_HEAD(&master->shared_masters_elm);
mutex_lock(&smmu->streams_mutex);
for (i = 0; i < fwspec->num_ids; i++) {
struct arm_smmu_stream *stream;
- struct rb_node *existing;
u32 sid = fwspec->ids[i];
ret = arm_smmu_init_sid_strtab(smmu, sid);
if (ret)
break;
- existing = rb_find(&sid, &smmu->streams,
- arm_smmu_streams_cmp_key);
- if (existing) {
- struct arm_smmu_master *existing_master =
- rb_entry(existing, struct arm_smmu_stream, node)
- ->master;
-
+ stream = arm_smmu_find_stream(smmu, sid);
+ if (stream) {
/* Bridged PCI devices may end up with duplicated IDs */
- if (existing_master == master) {
- master->streams[i] = rb_entry(existing,
- struct arm_smmu_stream, node);
+ if (stream->master == master) {
+ master->streams[i] = stream;
continue;
}
- 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;
+ if (master->num_streams != 1 ||
+ stream->master->num_streams != 1) {
+ dev_warn(master->dev,
+ "Shared StreamID 0x%x not supported for multi-SID masters\n",
+ sid);
+ ret = -ENODEV;
+ break;
+ }
+
+ if (list_empty(&stream->shared_masters))
+ list_add_tail(&stream->master->shared_masters_elm,
+ &stream->shared_masters);
+ list_add_tail(&master->shared_masters_elm,
+ &stream->shared_masters);
+ master->streams[i] = stream;
+ continue;
}
stream = kzalloc_obj(*stream, GFP_KERNEL);
@@ -4148,6 +4166,7 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
}
stream->id = sid;
stream->master = master;
+ INIT_LIST_HEAD(&stream->shared_masters);
rb_find_add(&stream->node, &smmu->streams,
arm_smmu_streams_cmp_node);
@@ -4155,6 +4174,7 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
}
if (ret) {
+ list_del_init(&master->shared_masters_elm);
for (i--; i >= 0; i--) {
int j;
@@ -4193,17 +4213,31 @@ static void arm_smmu_remove_master(struct arm_smmu_master *master)
return;
mutex_lock(&smmu->streams_mutex);
- for (i = 0; i < master->num_streams; i++) {
- int j;
+ if (!list_empty(&master->shared_masters_elm)) {
+ struct arm_smmu_stream *stream = master->streams[0];
- /* Skip duplicated SID pointers already freed */
- for (j = 0; j < i; j++)
- if (master->streams[j] == master->streams[i])
- break;
- if (j < i)
- continue;
- rb_erase(&master->streams[i]->node, &smmu->streams);
- kfree(master->streams[i]);
+ list_del_init(&master->shared_masters_elm);
+ if (list_is_singular(&stream->shared_masters)) {
+ struct arm_smmu_master *last;
+
+ last = list_first_entry(&stream->shared_masters,
+ struct arm_smmu_master,
+ shared_masters_elm);
+ list_del_init(&last->shared_masters_elm);
+ }
+ } else {
+ for (i = 0; i < master->num_streams; i++) {
+ int j;
+
+ /* Skip duplicated SID pointers already freed */
+ for (j = 0; j < i; j++)
+ if (master->streams[j] == master->streams[i])
+ break;
+ if (j < i)
+ continue;
+ rb_erase(&master->streams[i]->node, &smmu->streams);
+ kfree(master->streams[i]);
+ }
}
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 ba430078cbce9..2ed7a66c8b0b8 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -969,6 +969,8 @@ struct arm_smmu_stream {
u32 id;
struct arm_smmu_master *master;
struct rb_node node;
+ bool ste_installed;
+ struct list_head shared_masters;
};
struct arm_smmu_vmaster {
@@ -1017,6 +1019,7 @@ struct arm_smmu_master {
bool ste_ats_enabled : 1;
bool stall_enabled;
bool ats_always_on;
+ struct list_head shared_masters_elm;
unsigned int ssid_bits;
unsigned int iopf_refcount;
};
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH RFC v5 5/6] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group
2026-10-06 12:19 [PATCH RFC v5 0/6] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
` (3 preceding siblings ...)
2026-10-06 12:19 ` [PATCH RFC v5 4/6] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master Peng Fan (OSS)
@ 2026-10-06 12:19 ` Peng Fan (OSS)
2026-10-06 12:19 ` [PATCH RFC v5 6/6] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
5 siblings, 0 replies; 7+ messages in thread
From: Peng Fan (OSS) @ 2026-10-06 12:19 UTC (permalink / raw)
To: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
Jean-Philippe Brucker, Nicolin Chen, Jason Gunthorpe,
Thierry Reding, Krishna Reddy, Jonathan Hunter, Jason Gunthorpe
Cc: linux-arm-kernel, iommu, linux-kernel, linux-tegra, Peng Fan
From: Peng Fan <peng.fan@nxp.com>
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.
Detect SID aliasing at group-assignment time by looking up the RB
tree for existing owners. arm_smmu_device_group() is called before
arm_smmu_insert_master(), so any RB tree hit belongs to a
different, already-probed master.
Assisted-by: LLM
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 33 ++++++++++++++++++++---------
1 file changed, 23 insertions(+), 10 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 d306e4af90ef0..c4cfe609ae93d 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4381,19 +4381,32 @@ 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;
+
+ mutex_lock(&smmu->streams_mutex);
+ for (i = 0; i < fwspec->num_ids; i++) {
+ struct arm_smmu_stream *stream;
+
+ stream = arm_smmu_find_stream(smmu, fwspec->ids[i]);
+ if (!stream)
+ continue;
+
+ group = iommu_group_get(stream->master->dev);
+ break;
+ }
+ mutex_unlock(&smmu->streams_mutex);
+
+ if (group)
+ return group;
- /*
- * 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 (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] 7+ messages in thread* [PATCH RFC v5 6/6] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating
2026-10-06 12:19 [PATCH RFC v5 0/6] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
` (4 preceding siblings ...)
2026-10-06 12:19 ` [PATCH RFC v5 5/6] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group Peng Fan (OSS)
@ 2026-10-06 12:19 ` Peng Fan (OSS)
5 siblings, 0 replies; 7+ messages in thread
From: Peng Fan (OSS) @ 2026-10-06 12:19 UTC (permalink / raw)
To: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
Jean-Philippe Brucker, Nicolin Chen, Jason Gunthorpe,
Thierry Reding, Krishna Reddy, Jonathan Hunter, Jason Gunthorpe
Cc: linux-arm-kernel, iommu, linux-kernel, linux-tegra, 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, skip the ABORT STE write while other
masters still share the SID.
Fault events on shared SIDs cannot be attributed to a specific
master, so arm_smmu_find_master() returns NULL when the stream's
shared_masters list is non-empty.
Disable SVA, IOPF/stall, and vSMMU nesting for shared-SID masters
since all three require unambiguous SID-to-device mapping. Clear
stall_enabled at probe time so the STE is not programmed with the
stall bit, which would cause unrecoverable bus lockups since
CMD_RESUME cannot be issued without a unique SID-to-device mapping.
Assisted-by: LLM
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 2 +-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 3 ++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 47 ++++++++++++++++++++--
3 files changed, 47 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 0934a6bbd3e08..9f13154f832c1 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
@@ -309,7 +309,7 @@ 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.
*/
- if (master->num_streams != 1)
+ if (master->num_streams != 1 || !list_empty(&master->shared_masters_elm))
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..54a0a65669ac2 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,9 @@ static int arm_smmu_sva_set_dev_pasid(struct iommu_domain *domain,
if (!(master->smmu->features & ARM_SMMU_FEAT_SVA))
return -EOPNOTSUPP;
+ if (!list_empty(&master->shared_masters_elm))
+ 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 c4cfe609ae93d..bf09c02538381 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2977,11 +2977,36 @@ arm_smmu_get_step_for_sid(struct arm_smmu_device *smmu, u32 sid)
}
}
+static bool arm_smmu_is_shared_sid(struct arm_smmu_master *master)
+{
+ return !list_empty(&master->shared_masters_elm);
+}
+
+static bool arm_smmu_skip_shared_ste(struct arm_smmu_device *smmu,
+ u32 sid, bool is_abort)
+{
+ struct arm_smmu_stream *stream;
+
+ lockdep_assert_held(&smmu->streams_mutex);
+
+ stream = arm_smmu_find_stream(smmu, sid);
+ if (!stream || list_empty(&stream->shared_masters))
+ return false;
+
+ if (is_abort)
+ return true;
+ if (stream->ste_installed)
+ return true;
+ stream->ste_installed = true;
+ return false;
+}
+
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])) ==
@@ -2990,10 +3015,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++)
@@ -3002,6 +3029,16 @@ void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master,
if (j < i)
continue;
+ if (arm_smmu_is_shared_sid(master)) {
+ mutex_lock(&smmu->streams_mutex);
+ if (arm_smmu_skip_shared_ste(smmu, sid, is_abort)) {
+ mutex_unlock(&smmu->streams_mutex);
+ continue;
+ }
+ mutex_unlock(&smmu->streams_mutex);
+ }
+
+ step = arm_smmu_get_step_for_sid(smmu, sid);
arm_smmu_write_ste(master, sid, step, target);
}
}
@@ -3149,8 +3186,7 @@ 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)
+ if (master->num_streams != 1 || arm_smmu_is_shared_sid(master))
return -EOPNOTSUPP;
if (master->iopf_refcount) {
@@ -4331,6 +4367,9 @@ static struct iommu_device *arm_smmu_probe_device(struct device *dev)
smmu->features & ARM_SMMU_FEAT_STALL_FORCE)
master->stall_enabled = true;
+ if (arm_smmu_is_shared_sid(master))
+ master->stall_enabled = false;
+
ret = arm_smmu_master_prepare_ats(master);
if (ret)
goto err_disable_pasid;
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread