* [PATCH RFC v3 1/3] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master
2026-09-30 11:25 [PATCH RFC v3 0/3] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
@ 2026-09-30 11:25 ` Peng Fan (OSS)
2026-09-30 18:11 ` Nicolin Chen
2026-09-30 11:25 ` [PATCH RFC v3 2/3] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group Peng Fan (OSS)
2026-09-30 11:25 ` [PATCH RFC v3 3/3] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
2 siblings, 1 reply; 5+ messages in thread
From: Peng Fan (OSS) @ 2026-09-30 11:25 UTC (permalink / raw)
To: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
Jean-Philippe Brucker, Nicolin Chen, Jason Gunthorpe
Cc: linux-arm-kernel, iommu, linux-kernel, Peng Fan
From: Peng Fan <peng.fan@nxp.com>
Some SoCs have a limited number of IOMMU Stream IDs and hardware
that inherently shares them. For example, the NXP i.MX95 has only
64 SIDs (6-bit internal bus) serving 64+ DMA initiators across
separate IP blocks - MMC, SD, NET, PCI, DMA, NPU, DSP, DISPLAY
and more. Genuinely distinct platform devices share a SID by
hardware design because the SID space is exhausted.
When arm_smmu_insert_master() encounters a SID already owned by a
different single-SID master, instead of failing with -ENODEV, link
the new master into the existing stream's shared_masters list.
The stream structure is shared directly: the RB tree entry remains
the same, and all sharing masters are tracked via list_head.
On removal, if the departing master owns the canonical stream,
transfer ownership to the next sharer.
Sharing is restricted to single-SID masters (num_streams == 1)
since multi-SID sharing would require more complex tracking.
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 76 +++++++++++++++++++++++------
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 9 ++++
2 files changed, 70 insertions(+), 15 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..940f34465c0fa 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 */
@@ -4115,7 +4128,10 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
new_stream->id = fwspec->ids[i];
new_stream->master = master;
+ new_stream->ste_installed = false;
+ INIT_LIST_HEAD(&new_stream->shared_masters);
}
+ INIT_LIST_HEAD(&master->shared_masters_elm);
/* Put the ids into order for sorted to_merge/to_unref arrays */
sort(master->streams, master->num_streams,
@@ -4136,23 +4152,29 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
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;
+ struct arm_smmu_stream *existing_stream =
+ rb_entry(existing, struct arm_smmu_stream, node);
/* Bridged PCI devices may end up with duplicated IDs */
- if (existing_master == master)
+ if (existing_stream->master == master)
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) {
+ dev_warn(master->dev,
+ "Shared StreamID 0x%x not supported for multi-SID masters\n",
+ sid);
+ ret = -ENODEV;
+ break;
+ }
+
+ list_add_tail(&master->shared_masters_elm,
+ &existing_stream->shared_masters);
+ continue;
}
}
if (ret) {
+ list_del_init(&master->shared_masters_elm);
for (i--; i >= 0; i--)
rb_erase(&master->streams[i].node, &smmu->streams);
kfree(master->streams);
@@ -4167,14 +4189,38 @@ 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);
+ list_del_init(&master->shared_masters_elm);
+ for (i = 0; i < master->num_streams; i++) {
+ u32 sid = master->streams[i].id;
+ struct arm_smmu_stream *stream;
+
+ if (i > 0 && master->streams[i - 1].id == sid)
+ continue;
+
+ stream = arm_smmu_find_stream(smmu, sid);
+ if (!stream)
+ continue;
+
+ if (stream->master != master)
+ continue;
+
+ if (list_empty(&stream->shared_masters)) {
+ rb_erase(&stream->node, &smmu->streams);
+ } else {
+ struct arm_smmu_master *next;
+
+ next = list_first_entry(&stream->shared_masters,
+ struct arm_smmu_master,
+ shared_masters_elm);
+ list_del_init(&next->shared_masters_elm);
+ stream->master = next;
+ }
+ }
mutex_unlock(&smmu->streams_mutex);
kfree(master->streams);
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..ea0aa345fb673 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,10 @@ struct arm_smmu_stream {
u32 id;
struct arm_smmu_master *master;
struct rb_node node;
+ /* Set when the STE has been programmed for this stream. */
+ bool ste_installed;
+ /* List of masters sharing this SID. Empty unless shared. */
+ struct list_head shared_masters;
};
struct arm_smmu_vmaster {
@@ -1017,6 +1021,11 @@ struct arm_smmu_master {
bool ste_ats_enabled : 1;
bool stall_enabled;
bool ats_always_on;
+ /*
+ * Links into arm_smmu_stream.shared_masters. Supports num_streams == 1
+ * only. Empty if SID is not shared.
+ */
+ struct list_head shared_masters_elm;
unsigned int ssid_bits;
unsigned int iopf_refcount;
};
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH RFC v3 2/3] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group
2026-09-30 11:25 [PATCH RFC v3 0/3] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
2026-09-30 11:25 ` [PATCH RFC v3 1/3] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master Peng Fan (OSS)
@ 2026-09-30 11:25 ` Peng Fan (OSS)
2026-09-30 11:25 ` [PATCH RFC v3 3/3] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
2 siblings, 0 replies; 5+ messages in thread
From: Peng Fan (OSS) @ 2026-09-30 11:25 UTC (permalink / raw)
To: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
Jean-Philippe Brucker, Nicolin Chen, Jason Gunthorpe
Cc: linux-arm-kernel, iommu, linux-kernel, 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.
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 940f34465c0fa..2e13cf0cb8882 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4363,19 +4363,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] 5+ messages in thread* [PATCH RFC v3 3/3] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating
2026-09-30 11:25 [PATCH RFC v3 0/3] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
2026-09-30 11:25 ` [PATCH RFC v3 1/3] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master Peng Fan (OSS)
2026-09-30 11:25 ` [PATCH RFC v3 2/3] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group Peng Fan (OSS)
@ 2026-09-30 11:25 ` Peng Fan (OSS)
2 siblings, 0 replies; 5+ messages in thread
From: Peng Fan (OSS) @ 2026-09-30 11:25 UTC (permalink / raw)
To: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
Jean-Philippe Brucker, Nicolin Chen, Jason Gunthorpe
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, 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.
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 | 44 ++++++++++++++++++++--
3 files changed, 44 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..73fb5fad1c181 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 2e13cf0cb8882..b192efa679561 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) {
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread