mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH RFC 0/4] iommu/arm-smmu-v3: Support shared Stream IDs
@ 2026-09-16 15:12 Peng Fan (OSS)
  2026-09-16 15:12 ` [PATCH RFC 1/4] iommu/arm-smmu-v3: Convert streams from RB tree to XArray Peng Fan (OSS)
                   ` (4 more replies)
  0 siblings, 5 replies; 7+ messages in thread
From: Peng Fan (OSS) @ 2026-09-16 15:12 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

Some SoCs have a limited number of IOMMU Stream IDs (SIDs) and
hardware that inherently shares them. For example, the NXP i.MX95
eDMA controller has 64 channels where each TX/RX pair is assigned
a single SID by the hardware - the two channel devices are distinct
from Linux's perspective but present the same SID to the SMMU.

The current ARM SMMUv3 driver rejects this with:
  "Aliasing StreamID unsupported, expect DMA to be broken"

I took comments in [1] and implement this patchset and only want to
support a very simple case that multiple platform devices share one SID.

This series adds support for multiple masters to share a single
SID, allowing per-channel DMA devices behind an IOMMU without
requiring one SID per channel.

Patch 1 converts the streams RB tree to an XArray for O(1) SID
lookup, simplifying duplicate-SID handling.

Patch 2 adds reference counting to streams so that multiple
masters can share a SID. Insert increments the refcount; remove
either transfers ownership or decrements. A per-SID linked list
tracks co-sharing masters.

Patch 3 places devices that share a SID into the same IOMMU
group, ensuring they share a single IOMMU domain.

Patch 4 wires up STE write ordering (first master writes, last
master tears down) and gates features that require unambiguous
SID-to-device mapping: SVA, IOPF/stall, and vSMMU nesting are
disabled for shared-SID masters.

[1] https://lore.kernel.org/linux-iommu/DU0PR04MB94172CB3F138AD39E9B6DEEF887F9@DU0PR04MB9417.eurprd04.prod.outlook.com/

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
Peng Fan (4):
      iommu/arm-smmu-v3: Convert streams from RB tree to XArray
      iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master
      iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group
      iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating

 .../iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c    |   4 +-
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c    |   7 +
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c        | 265 ++++++++++++++++-----
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h        |  34 ++-
 4 files changed, 247 insertions(+), 63 deletions(-)
---
base-commit: e6e35979777d646fe3c7c94dca7dd32fb25d45f4
change-id: 20260916-smmu-shared-sid-e488f6c4d4f0

Best regards,
--  
Peng Fan <peng.fan@nxp.com>


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH RFC 1/4] iommu/arm-smmu-v3: Convert streams from RB tree to XArray
  2026-09-16 15:12 [PATCH RFC 0/4] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
@ 2026-09-16 15:12 ` Peng Fan (OSS)
  2026-09-16 15:12 ` [PATCH RFC 2/4] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master Peng Fan (OSS)
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 7+ messages in thread
From: Peng Fan (OSS) @ 2026-09-16 15:12 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] 7+ messages in thread

* [PATCH RFC 2/4] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master
  2026-09-16 15:12 [PATCH RFC 0/4] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
  2026-09-16 15:12 ` [PATCH RFC 1/4] iommu/arm-smmu-v3: Convert streams from RB tree to XArray Peng Fan (OSS)
@ 2026-09-16 15:12 ` Peng Fan (OSS)
  2026-09-16 15:12 ` [PATCH RFC 3/4] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group Peng Fan (OSS)
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 7+ messages in thread
From: Peng Fan (OSS) @ 2026-09-16 15:12 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>

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] 7+ messages in thread

* [PATCH RFC 3/4] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group
  2026-09-16 15:12 [PATCH RFC 0/4] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
  2026-09-16 15:12 ` [PATCH RFC 1/4] iommu/arm-smmu-v3: Convert streams from RB tree to XArray Peng Fan (OSS)
  2026-09-16 15:12 ` [PATCH RFC 2/4] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master Peng Fan (OSS)
@ 2026-09-16 15:12 ` Peng Fan (OSS)
  2026-09-16 15:12 ` [PATCH RFC 4/4] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
  2026-09-16 16:10 ` [PATCH RFC 0/4] iommu/arm-smmu-v3: Support shared Stream IDs Jason Gunthorpe
  4 siblings, 0 replies; 7+ messages in thread
From: Peng Fan (OSS) @ 2026-09-16 15:12 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>

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] 7+ messages in thread

* [PATCH RFC 4/4] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating
  2026-09-16 15:12 [PATCH RFC 0/4] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
                   ` (2 preceding siblings ...)
  2026-09-16 15:12 ` [PATCH RFC 3/4] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group Peng Fan (OSS)
@ 2026-09-16 15:12 ` Peng Fan (OSS)
  2026-09-16 16:10 ` [PATCH RFC 0/4] iommu/arm-smmu-v3: Support shared Stream IDs Jason Gunthorpe
  4 siblings, 0 replies; 7+ messages in thread
From: Peng Fan (OSS) @ 2026-09-16 15:12 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>

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>
---
 .../iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c    |  4 +-
 drivers/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] 7+ messages in thread

* Re: [PATCH RFC 0/4] iommu/arm-smmu-v3: Support shared Stream IDs
  2026-09-16 15:12 [PATCH RFC 0/4] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
                   ` (3 preceding siblings ...)
  2026-09-16 15:12 ` [PATCH RFC 4/4] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
@ 2026-09-16 16:10 ` Jason Gunthorpe
  2026-09-17  1:33   ` Peng Fan
  4 siblings, 1 reply; 7+ messages in thread
From: Jason Gunthorpe @ 2026-09-16 16:10 UTC (permalink / raw)
  To: Peng Fan (OSS)
  Cc: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
	Jean-Philippe Brucker, linux-arm-kernel, iommu, linux-kernel,
	Peng Fan

On Wed, Sep 16, 2026 at 11:12:33PM +0800, Peng Fan (OSS) wrote:
> Some SoCs have a limited number of IOMMU Stream IDs (SIDs) and
> hardware that inherently shares them. For example, the NXP i.MX95
> eDMA controller has 64 channels where each TX/RX pair is assigned
> a single SID by the hardware - the two channel devices are distinct
> from Linux's perspective but present the same SID to the SMMU.

Is that a reflection of poor DT modelling though?

Why must a TX/RX *PAIR* have two platform_devices nodes?

Fix it there and you don't need any of this? Or is there more?

Jason

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH RFC 0/4] iommu/arm-smmu-v3: Support shared Stream IDs
  2026-09-16 16:10 ` [PATCH RFC 0/4] iommu/arm-smmu-v3: Support shared Stream IDs Jason Gunthorpe
@ 2026-09-17  1:33   ` Peng Fan
  0 siblings, 0 replies; 7+ messages in thread
From: Peng Fan @ 2026-09-17  1:33 UTC (permalink / raw)
  To: Jason Gunthorpe
  Cc: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
	Jean-Philippe Brucker, linux-arm-kernel, iommu, linux-kernel,
	Peng Fan

Hi Jason,

On Wed, Sep 16, 2026 at 01:10:18PM -0300, Jason Gunthorpe wrote:
>On Wed, Sep 16, 2026 at 11:12:33PM +0800, Peng Fan (OSS) wrote:
>> Some SoCs have a limited number of IOMMU Stream IDs (SIDs) and
>> hardware that inherently shares them. For example, the NXP i.MX95
>> eDMA controller has 64 channels where each TX/RX pair is assigned
>> a single SID by the hardware - the two channel devices are distinct
>> from Linux's perspective but present the same SID to the SMMU.
>
>Is that a reflection of poor DT modelling though?
>
>Why must a TX/RX *PAIR* have two platform_devices nodes?
>
>Fix it there and you don't need any of this? Or is there more?

I think there may be a misunderstanding about the device topology here.

There is only one platform device node for the eDMA controller
(dma-controller@42000000). There are no separate platform device nodes per
channel pair.

What happens instead:
The fsl-edma driver probes the single platform device. During
dmaenginem_async_device_register(), the dmaengine core calls
__dma_async_device_channel_register() for each channel, which creates a
struct dma_chan_dev containing a struct device - registered via
device_register() with class = &dma_devclass and parent = edma_platform_dev
(see drivers/dma/dmaengine.c line 1115-1125)

For a 64-channel eDMA, this creates 64 channel devices: dma0chan[0-63]
These channel devices are not platform devices. They are class devices under
the dma device class. They have no DT node, no of_node. They are purely
software constructs created by the dmaengine framework.

The iommu-map property sits on the eDMA controller's DT node. At channel
allocation time (xlate), the driver calls of_dma_configure_id(chan_dev,
edma_np, true, &chan_id) to look up the channel index in the eDMA node's
iommu-map and attach an IOMMU domain to that specific channel device. The
dmaengine framework's dmaengine_get_dma_device() API
(which checks chan->dev->chan_dma_dev) then returns the per-channel device
instead of the parent platform device, so DMA clients map buffers through
the correct IOMMU context.

The shared-SID situation arises because in hardware, each TX/RX channel
pair presents the same Stream ID to the SMMU. So dma0chan0 and dma0chan1
both get configured with SID 0x30 via:
iommu-map = <2  &smmu 0x30 1>,
            <3  &smmu 0x30 1>,
            ...
Both channel devices end up in the same IOMMU group, but currently the SMMU
driver rejects the second device trying to register the same SID. That is
what this series fixes.

And merging TX and RX into a single channel device is not possible -
the dmaengine framework allocates one dma_chan (and thus one dma_chan_dev) per
direction. SPI/I2C/UART clients call dma_request_chan() separately
for "tx" and "rx", each returning an independent channel with its own device.
The two channels have different source ids (e.g. 83 for TX, 84 for RX) and
are independently programmable hardware resources - they only share a SID per
hardware design.

one more point to support SID sharing is that i.MX95 only support 64 SIDs,
however there are more than 64 DMA initiators. 

Hope this explains well.

Thanks,
Peng

>
>Jason
>
>

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-09-17  1:29 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-16 15:12 [PATCH RFC 0/4] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
2026-09-16 15:12 ` [PATCH RFC 1/4] iommu/arm-smmu-v3: Convert streams from RB tree to XArray Peng Fan (OSS)
2026-09-16 15:12 ` [PATCH RFC 2/4] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master Peng Fan (OSS)
2026-09-16 15:12 ` [PATCH RFC 3/4] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group Peng Fan (OSS)
2026-09-16 15:12 ` [PATCH RFC 4/4] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
2026-09-16 16:10 ` [PATCH RFC 0/4] iommu/arm-smmu-v3: Support shared Stream IDs Jason Gunthorpe
2026-09-17  1:33   ` Peng Fan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®