All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH RFC v6 0/5] iommu/arm-smmu-v3: Support shared Stream IDs
@ 2026-10-11 11:31 Peng Fan (OSS)
  2026-10-11 11:31 ` [PATCH RFC v6 1/5] iommu/arm-smmu-v3: Allocate streams individually Peng Fan (OSS)
                   ` (4 more replies)
  0 siblings, 5 replies; 17+ messages in thread
From: Peng Fan (OSS) @ 2026-10-11 11:31 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

Changes since RFC v5:
- Rebased on next-20261009 and drop Nicolin's patch
  ("iommu/arm-smmu-v3: Don't rb_erase() a never-inserted stream node") which
  was already in tree.
- Actively deduplicate Stream IDs during insertion instead of
  storing duplicate pointers. num_streams now reflects the unique
  count, eliminating pointer-equality dedup loops from all cleanup
  paths (Robin).
- Extracted arm_smmu_find_stream() into patch 2 instead of patch 3
  to clean up the find-before-allocate code (Nicolin).
- Extracted arm_smmu_free_master_streams() helper shared between
  error rollback and arm_smmu_remove_master() (Nicolin).
- Fixed remove_master to reassign stream->master when the current
  owner departs, preventing dangling pointer (Nicolin).
- Fixed remove_master to use a for loop iterating master->streams[]
  with per-stream shared vs non-shared handling (Nicolin).
- Reset ste_installed on ABORT so domain transitions (e.g. DMA ->
  VFIO) correctly re-write the STE instead of freezing it.
- Clear stall_enabled on the first master retroactively when
  sharing is detected in arm_smmu_insert_master(), not just on
  subsequent masters at probe time.  Prevents bus lockup when
  the first master probes before sharing is known.
- Link to v5: https://lore.kernel.org/linux-iommu/20261006-smmu-shared-sid-v5-0-169a59c671d3@nxp.com/

Changes since RFC v4:
- Include Nicolin's patch ("iommu/arm-smmu-v3: Don't rb_erase() a
  never-inserted stream node") to avoid conflicts in future.
  so the standalone patch 1 from v4 is dropped.
- Split old patch 2 (shared SID support) into two:
  - New patch 2: Convert streams from embedded array to individually
    allocated objects (struct arm_smmu_stream ** instead of *).
  - New patch 3: Move stream allocation inside the mutex and look up
    existing entries first (find-before-allocate), avoiding unnecessary
    allocation when the SID already exists.  Pure refactor, no
    functional change.
  - New patch 4: Add shared-SID logic on top of the refactored code.
- Duplicate-SID cleanup (bridged PCI) now uses pointer-equality dedup
  instead of RB_EMPTY_NODE checks, since streams are no longer
  pre-allocated with cleared nodes.
- Link to v4: https://lore.kernel.org/all/20261001-smmu-shared-sid-ver3-v4-0-06b34fb2e691@nxp.com/

Changes since RFC v3:
- Added a standalone bugfix for duplicate-SID rb_erase (new patch 1).
- Stream structures are now shared directly between masters
  instead of duplicating them (Nicolin).  No ref_count field;
  sharing is detected via list_empty(&stream->shared_masters).
- On owner removal, use rb_replace_node() + list_splice_init()
  to transfer the RB tree entry and shared_masters list to the
  next sharer, avoiding use-after-free.
- INIT_LIST_HEAD(&stream->shared_masters) moved after sort() to
  avoid list pointer corruption from bitwise memory swaps.
- Both sides of the sharing pair are checked for num_streams == 1.
- Link to v3: https://lore.kernel.org/all/20260930-smmu-shared-sid-v3-0-8abe6c1b4c20@nxp.com/

Changes since RFC v2:
- Dropped the RB tree to XArray conversion patch (Nicolin).
  The RB tree is retained as-is; a new arm_smmu_find_stream()
  helper provides stream-level lookup.
- No per-master shared_sid bool; replaced by
  list_empty(&master->shared_masters_elm) (Nicolin).
- Only SID-sharing for single SID device
- Follow Nicolin's suggestion https://lore.kernel.org/linux-iommu/arG6hmng3NddGEHm@nvidia.com/
- Link to v2: https://lore.kernel.org/linux-iommu/20260921-smmu-shared-sid-v2-0-4b656ce68178@nxp.com/

Changes since RFC v1:
- Revised cover letter to drop eDMA channel example (Jason).

Some SoCs have a limited number of IOMMU Stream IDs (SIDs) and
hardware that inherently shares them. For example, the NXP i.MX95
only has 64 SIDs, only 6 bits for SID in the system chip bus.
However there are many device IPs, such as SDHC, PCIE, NETC, GPU,
NPU, Display, VPU, ISP, ISI, JPEG, SATA, DMA and etc. Each Kind IP has
mutilple instances inside the SoC. So I introudce this patchset to
support shared SID.

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.

Patch 1 converts the per-master streams array from an embedded array
of structs to an array of individually allocated pointers.  This
prepares for subsequent patches where multiple masters will point
to the same stream object.

Patch 2 moves stream allocation from upfront into the mutex-protected
loop in arm_smmu_insert_master(), using a find-before-allocate pattern:
first look up whether the SID already exists in the RB tree, only
allocate and insert a new stream when no existing entry is found.
This is a pure refactor with no functional change.

Patch 3 adds shared-SID tracking so that when a second single-SID
master registers an already-owned SID, it joins the existing
stream rather than being rejected.  The stream structure is shared
directly between masters via a linked list.  On removal, a shared
master detaches from the list; the stream and its RB tree entry
are kept alive until the last master is removed.

Patch 4 places devices that share a SID into the same IOMMU
group, ensuring they share a single IOMMU domain (required
because one STE can only point to one cd_table).

Patch 5 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 (5):
      iommu/arm-smmu-v3: Allocate streams individually
      iommu/arm-smmu-v3: Delay stream allocation to inside the mutex
      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    |   3 +
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c        | 254 ++++++++++++++-------
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h        |   5 +-
 drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c     |   2 +-
 5 files changed, 185 insertions(+), 83 deletions(-)
---
base-commit: 581e50f62f59cba2e33c72ca119d3c3f797aec9d
change-id: 20260916-smmu-shared-sid-e488f6c4d4f0

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


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

* [PATCH RFC v6 1/5] iommu/arm-smmu-v3: Allocate streams individually
  2026-10-11 11:31 [PATCH RFC v6 0/5] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
@ 2026-10-11 11:31 ` Peng Fan (OSS)
  2026-10-11 11:31 ` [PATCH RFC v6 2/5] iommu/arm-smmu-v3: Delay stream allocation to inside the mutex Peng Fan (OSS)
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 17+ messages in thread
From: Peng Fan (OSS) @ 2026-10-11 11:31 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
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
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 e68b464850955..853276dd54536 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
@@ -295,7 +295,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 6d4520c473086..5e092f51faad0 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -977,7 +977,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));
 	/*
@@ -1522,7 +1522,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);
@@ -2463,7 +2463,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);
@@ -3155,13 +3155,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;
@@ -3447,7 +3447,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))
+			    master->streams[i]->id, ssid))
 			return NULL;
 	}
 
@@ -4276,10 +4276,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,
@@ -4309,10 +4309,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 */
@@ -4325,11 +4331,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;
 
@@ -4359,14 +4365,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;
 }
 
@@ -4381,10 +4394,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 bb048e08d613d..56ed6ad1dd523 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -1051,7 +1051,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 710a4c694b946..8166cece943c4 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 related	[flat|nested] 17+ messages in thread

* [PATCH RFC v6 2/5] iommu/arm-smmu-v3: Delay stream allocation to inside the mutex
  2026-10-11 11:31 [PATCH RFC v6 0/5] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
  2026-10-11 11:31 ` [PATCH RFC v6 1/5] iommu/arm-smmu-v3: Allocate streams individually Peng Fan (OSS)
@ 2026-10-11 11:31 ` Peng Fan (OSS)
  2026-10-12 20:36   ` Nicolin Chen
  2026-10-11 11:31 ` [PATCH RFC v6 3/5] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master Peng Fan (OSS)
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 17+ messages in thread
From: Peng Fan (OSS) @ 2026-10-11 11:31 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 via the
new arm_smmu_find_stream() helper. Only allocate and insert a new
stream when no existing entry is found.

Duplicated Stream IDs from bridged PCI devices are now actively
skipped rather than stored as duplicate pointers. A separate write
index (n) tracks only unique entries, and master->num_streams is set
to the deduplicated count. This eliminates the need for duplicate-
pointer tracking in all cleanup paths.

Extract arm_smmu_find_stream() from arm_smmu_find_master() to provide
stream-level lookup, which will also be used by subsequent shared-SID
patches.

Extract arm_smmu_free_master_streams() to share the stream cleanup
logic between the error rollback in arm_smmu_insert_master() and
arm_smmu_remove_master().

The sort is moved after the mutex section, since streams are now
populated inside the loop rather than beforehand.

Suggested-by: Nicolin Chen <nicolinc@nvidia.com>
Suggested-by: Robin Murphy <robin.murphy@arm.com>
Assisted-by: LLM
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 124 ++++++++++++++--------------
 1 file changed, 60 insertions(+), 64 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 5e092f51faad0..0ea755ea82192 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2076,8 +2076,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;
 
@@ -2086,7 +2086,18 @@ 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;
+	return stream->master;
 }
 
 /* IRQ and event handlers */
@@ -4282,10 +4293,25 @@ static int arm_smmu_stream_id_cmp(const void *_l, const void *_r)
 	return cmp_int((*l)->id, (*r)->id);
 }
 
+static void arm_smmu_free_master_streams(struct arm_smmu_master *master)
+{
+	struct arm_smmu_device *smmu = master->smmu;
+	int i;
+
+	lockdep_assert_held(&smmu->streams_mutex);
+
+	for (i = 0; i < master->num_streams; i++) {
+		if (!master->streams[i])
+			continue;
+		rb_erase(&master->streams[i]->node, &smmu->streams);
+		kfree(master->streams[i]);
+	}
+}
+
 static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
 				  struct arm_smmu_master *master)
 {
-	int i;
+	int i, n;
 	int ret = 0;
 	struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(master->dev);
 	bool ats_supported = dev_is_pci(master->dev) &&
@@ -4294,7 +4320,6 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
 	master->streams = kzalloc_objs(*master->streams, fwspec->num_ids);
 	if (!master->streams)
 		return -ENOMEM;
-	master->num_streams = fwspec->num_ids;
 
 	if (!ats_supported) {
 		/* Base case has 1 ASID entry or maximum 2 VMID entries */
@@ -4308,99 +4333,70 @@ 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 rb_node *existing;
-		u32 sid = new_stream->id;
+	for (i = 0, n = 0; i < fwspec->num_ids; i++) {
+		struct arm_smmu_stream *stream;
+		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);
-		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)
+			if (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));
+				 sid, dev_name(stream->master->dev));
 			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[n++] = stream;
 	}
+	master->num_streams = n;
 
 	if (ret) {
-		for (i--; i >= 0; i--)
-			if (!RB_EMPTY_NODE(&master->streams[i]->node))
-				rb_erase(&master->streams[i]->node,
-					 &smmu->streams);
+		arm_smmu_free_master_streams(master);
 		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);
+	arm_smmu_free_master_streams(master);
 	mutex_unlock(&smmu->streams_mutex);
 
-	for (i = 0; i < master->num_streams; i++)
-		kfree(master->streams[i]);
-
 	kfree(master->streams);
 	kfree(master->build_invs);
 }

-- 
2.34.1


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

* [PATCH RFC v6 3/5] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master
  2026-10-11 11:31 [PATCH RFC v6 0/5] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
  2026-10-11 11:31 ` [PATCH RFC v6 1/5] iommu/arm-smmu-v3: Allocate streams individually Peng Fan (OSS)
  2026-10-11 11:31 ` [PATCH RFC v6 2/5] iommu/arm-smmu-v3: Delay stream allocation to inside the mutex Peng Fan (OSS)
@ 2026-10-11 11:31 ` Peng Fan (OSS)
  2026-10-12 20:48   ` Nicolin Chen
  2026-10-11 11:31 ` [PATCH RFC v6 4/5] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group Peng Fan (OSS)
  2026-10-11 11:31 ` [PATCH RFC v6 5/5] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
  4 siblings, 1 reply; 17+ messages in thread
From: Peng Fan (OSS) @ 2026-10-11 11:31 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.

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. The stream object is shared directly between
masters — no duplication.

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 and
ownership transfers to the next sharer if the departing master was the
stream owner. The stream and its RB tree entry are kept alive until
the last master using that SID is removed.

Since fault events on shared SIDs cannot be attributed to a specific
master, arm_smmu_find_master() returns NULL when the stream's
shared_masters list is non-empty.

Suggested-by: Nicolin Chen <nicolinc@nvidia.com>
Link: https://lore.kernel.org/linux-iommu/Z3r-cBm00tVE4eKe@Asurada-Nvidia/
Assisted-by: LLM
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 47 +++++++++++++++++++++++++----
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h |  3 ++
 2 files changed, 44 insertions(+), 6 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 0ea755ea82192..351ccdfa765b4 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2097,6 +2097,8 @@ arm_smmu_find_master(struct arm_smmu_device *smmu, u32 sid)
 	stream = arm_smmu_find_stream(smmu, sid);
 	if (!stream)
 		return NULL;
+	if (!list_empty(&stream->shared_masters))
+		return NULL;
 	return stream->master;
 }
 
@@ -4332,6 +4334,7 @@ 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, n = 0; i < fwspec->num_ids; i++) {
@@ -4348,11 +4351,22 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
 			if (stream->master == master)
 				continue;
 
-			dev_warn(master->dev,
-				 "Aliasing StreamID 0x%x (from %s) unsupported, expect DMA to be broken\n",
-				 sid, dev_name(stream->master->dev));
-			ret = -ENODEV;
-			break;
+			if (fwspec->num_ids != 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[n++] = stream;
+			continue;
 		}
 
 		stream = kzalloc_obj(*stream, GFP_KERNEL);
@@ -4362,6 +4376,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);
@@ -4370,6 +4385,7 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
 	master->num_streams = n;
 
 	if (ret) {
+		list_del_init(&master->shared_masters_elm);
 		arm_smmu_free_master_streams(master);
 		mutex_unlock(&smmu->streams_mutex);
 		kfree(master->streams);
@@ -4388,13 +4404,32 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
 
 static void arm_smmu_remove_master(struct arm_smmu_master *master)
 {
+	int i;
 	struct arm_smmu_device *smmu = master->smmu;
 
 	if (!smmu || !master->streams)
 		return;
 
 	mutex_lock(&smmu->streams_mutex);
-	arm_smmu_free_master_streams(master);
+	for (i = 0; i < master->num_streams; i++) {
+		struct arm_smmu_stream *stream = master->streams[i];
+
+		if (!list_empty(&stream->shared_masters)) {
+			struct arm_smmu_master *next;
+
+			list_del_init(&master->shared_masters_elm);
+			next = list_first_entry(&stream->shared_masters,
+						struct arm_smmu_master,
+						shared_masters_elm);
+			if (stream->master == master)
+				stream->master = next;
+			if (list_is_singular(&stream->shared_masters))
+				list_del_init(&next->shared_masters_elm);
+			continue;
+		}
+		rb_erase(&stream->node, &smmu->streams);
+		kfree(stream);
+	}
 	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 56ed6ad1dd523..95da4ad354829 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -1020,6 +1020,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 {
@@ -1068,6 +1070,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 related	[flat|nested] 17+ messages in thread

* [PATCH RFC v6 4/5] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group
  2026-10-11 11:31 [PATCH RFC v6 0/5] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
                   ` (2 preceding siblings ...)
  2026-10-11 11:31 ` [PATCH RFC v6 3/5] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master Peng Fan (OSS)
@ 2026-10-11 11:31 ` Peng Fan (OSS)
  2026-10-12 12:24   ` Peng Fan
  2026-10-11 11:31 ` [PATCH RFC v6 5/5] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
  4 siblings, 1 reply; 17+ messages in thread
From: Peng Fan (OSS) @ 2026-10-11 11:31 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 351ccdfa765b4..328d580935b96 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4572,19 +4572,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 related	[flat|nested] 17+ messages in thread

* [PATCH RFC v6 5/5] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating
  2026-10-11 11:31 [PATCH RFC v6 0/5] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
                   ` (3 preceding siblings ...)
  2026-10-11 11:31 ` [PATCH RFC v6 4/5] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group Peng Fan (OSS)
@ 2026-10-11 11:31 ` Peng Fan (OSS)
  2026-10-12 13:23   ` Peng Fan
  4 siblings, 1 reply; 17+ messages in thread
From: Peng Fan (OSS) @ 2026-10-11 11:31 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, and reset ste_installed so the next
domain attach cycle re-writes the STE.

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 for both the new master and the existing first master
when sharing is detected in arm_smmu_insert_master(), so the STE
is never programmed with the stall bit.  This prevents
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        | 60 +++++++++++++++++-----
 3 files changed, 52 insertions(+), 13 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 853276dd54536..cccc42b13d89e 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
@@ -310,7 +310,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 faeec656da9e7..46d24f8eac830 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
@@ -281,6 +281,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 328d580935b96..d56fe512382dc 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -3154,11 +3154,38 @@ 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) {
+		stream->ste_installed = false;
+		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;
+	int i;
 	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])) ==
@@ -3167,18 +3194,23 @@ 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++)
-			if (master->streams[j]->id == sid)
-				break;
-		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);
 	}
 }
@@ -3326,8 +3358,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) {
@@ -4360,9 +4391,11 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
 				break;
 			}
 
-			if (list_empty(&stream->shared_masters))
+			if (list_empty(&stream->shared_masters)) {
 				list_add_tail(&stream->master->shared_masters_elm,
 					      &stream->shared_masters);
+				stream->master->stall_enabled = false;
+			}
 			list_add_tail(&master->shared_masters_elm,
 				      &stream->shared_masters);
 			master->streams[n++] = stream;
@@ -4522,6 +4555,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 related	[flat|nested] 17+ messages in thread

* Re: [PATCH RFC v6 4/5] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group
  2026-10-11 11:31 ` [PATCH RFC v6 4/5] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group Peng Fan (OSS)
@ 2026-10-12 12:24   ` Peng Fan
  2026-10-12 13:05     ` Robin Murphy
  0 siblings, 1 reply; 17+ messages in thread
From: Peng Fan @ 2026-10-12 12:24 UTC (permalink / raw)
  To: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
	Jean-Philippe Brucker, Nicolin Chen, Jason Gunthorpe,
	Thierry Reding, Krishna Reddy, Jonathan Hunter
  Cc: linux-arm-kernel, iommu, linux-kernel, linux-tegra, Peng Fan

On Sun, Oct 11, 2026 at 07:31:28PM +0800, Peng Fan (OSS) wrote:
>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.
As Sashiko pointed, I need correct as below:

Detect SID aliasing at group-assignment time by looking up the RB
tree for existing owners. Since probe_device() inserts the master
before device_group() is called, skip the device's own stream and
look for a different, already-probed master that shares the same
SID.

>
>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 351ccdfa765b4..328d580935b96 100644
>--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
>+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
>@@ -4572,19 +4572,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;

Not very sure here, should I use below as check condition?
  if (!stream || stream->master == master)
      continue?

Per my understanding stream->master will never equals to master,
but Sashiko concerns: Could this logic be adjusted to better handle the
device's own stream?

Thanks
Peng

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

* Re: [PATCH RFC v6 4/5] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group
  2026-10-12 12:24   ` Peng Fan
@ 2026-10-12 13:05     ` Robin Murphy
  0 siblings, 0 replies; 17+ messages in thread
From: Robin Murphy @ 2026-10-12 13:05 UTC (permalink / raw)
  To: Peng Fan, Will Deacon, Joerg Roedel (AMD), Jean-Philippe Brucker,
	Nicolin Chen, Jason Gunthorpe, Thierry Reding, Krishna Reddy,
	Jonathan Hunter
  Cc: linux-arm-kernel, iommu, linux-kernel, linux-tegra, Peng Fan

On 2026-10-12 1:24 pm, Peng Fan wrote:
> On Sun, Oct 11, 2026 at 07:31:28PM +0800, Peng Fan (OSS) wrote:
>> 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.
> As Sashiko pointed, I need correct as below:
> 
> Detect SID aliasing at group-assignment time by looking up the RB
> tree for existing owners. Since probe_device() inserts the master
> before device_group() is called, skip the device's own stream and
> look for a different, already-probed master that shares the same
> SID.
> 
>>
>> 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 351ccdfa765b4..328d580935b96 100644
>> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
>> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
>> @@ -4572,19 +4572,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;
> 
> Not very sure here, should I use below as check condition?
>    if (!stream || stream->master == master)
>        continue?

By this point, we must always find a valid stream for every ID, or 
probe_device() hasn't done its job correctly. The difference is if it 
only belongs to this device then it doesn't represent a group yet, 
otherwise if it is shared then the other device(s) must already have a 
group (since we're within the serialised part of probing) and we need to 
return that.

Thanks,
Robin.

> Per my understanding stream->master will never equals to master,
> but Sashiko concerns: Could this logic be adjusted to better handle the
> device's own stream?
> 
> Thanks
> Peng
> 
>> +
>> +		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] 17+ messages in thread

* Re: [PATCH RFC v6 5/5] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating
  2026-10-11 11:31 ` [PATCH RFC v6 5/5] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
@ 2026-10-12 13:23   ` Peng Fan
  2026-10-12 23:54     ` Nicolin Chen
  0 siblings, 1 reply; 17+ messages in thread
From: Peng Fan @ 2026-10-12 13:23 UTC (permalink / raw)
  To: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
	Jean-Philippe Brucker, Nicolin Chen, Jason Gunthorpe,
	Thierry Reding, Krishna Reddy, Jonathan Hunter
  Cc: linux-arm-kernel, iommu, linux-kernel, linux-tegra, Peng Fan

Hi Robin, Nicolin,

On Sun, Oct 11, 2026 at 07:31:29PM +0800, Peng Fan (OSS) wrote:
>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, and reset ste_installed so the next
>domain attach cycle re-writes the STE.
>
>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 for both the new master and the existing first master
>when sharing is detected in arm_smmu_insert_master(), so the STE
>is never programmed with the stall bit.  This prevents
>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        | 60 +++++++++++++++++-----
> 3 files changed, 52 insertions(+), 13 deletions(-)

...

>+
>+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) {
>+		stream->ste_installed = false;
>+		return true;
>+	}
>+	if (stream->ste_installed)
>+		return true;
>+	stream->ste_installed = true;
>+	return false;

sashiko has comments: https://sashiko.dev/#/patchset/20261011-smmu-shared-sid-v6-0-cb76b89dc612%40nxp.com

We just need to support a simple case that platform devices(single SID) which
could share there SIDs, such as MMC-0 and MMC-1 share one SID.

Shared-SID masters are restricted to platform devices sharing a single SID.
SVA, IOPF, and vSMMU nesting are all rejected for shared-SID masters (patch 5),
so VFIO passthrough and direct domain replacement scenarios do not apply.
The only domain transitions are the default DMA domain attach at probe time
and ABORT on unbind. Or I may miss something.

>+}
>+
> void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master,
> 				  const struct arm_smmu_ste *target)
> {
>-	int i, j;
>+	int i;
> 	struct arm_smmu_device *smmu = master->smmu;
>+	bool is_abort;
> 

....

>@@ -4360,9 +4391,11 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
> 				break;
> 			}
> 
>-			if (list_empty(&stream->shared_masters))
>+			if (list_empty(&stream->shared_masters)) {
> 				list_add_tail(&stream->master->shared_masters_elm,
> 					      &stream->shared_masters);
>+				stream->master->stall_enabled = false;

Sashiko comment:
https://sashiko.dev/#/patchset/20261011-smmu-shared-sid-v6-0-cb76b89dc612%40nxp.com

"
Does this dynamically modify the software stall state of the active first
master without reprogramming the active SMMU hardware STE?
When a second device sharing a SID is dynamically probed, this clears the
software stall_enabled flag for the existing active master. However, this
software state change is never actively flushed to the live hardware Context
Descriptor (CD), leaving the stall bit active in the SMMU hardware.
If the first device triggers a page fault while in this mismatched state,
the SMMU hardware will pause the transaction and generate a stall event.
Because arm_smmu_find_master() returns NULL for shared SIDs, won't
arm_smmu_handle_event() drop the event?
Alternatively, if a fault was already reported to the IOPF queue just before
the second device's probe, wouldn't arm_smmu_page_response() see the cleared
stall_enabled flag and return early?
In both cases, the required CMD_RESUME is never issued. Can this result in
a permanent, unrecoverable lockup of the SMMU bus? Should this sharing be
rejected if the existing master is already actively using SVA or IOPF?
"

I am thinking to simplify reject if stall_enabled is true:
 			if (list_empty(&stream->shared_masters)) {
+				if (stream->master->stall_enabled) {
+					dev_warn(master->dev,
+						 "Shared StreamID 0x%x rejected: existing master has stall enabled\n",
+						 sid);
+					ret = -EBUSY;
+					break;
+				}

Otherwise things will be a bit complicated.

May I get your suggestions?

Thanks
Peng


>+			}
> 			list_add_tail(&master->shared_masters_elm,
> 				      &stream->shared_masters);
> 			master->streams[n++] = stream;
>@@ -4522,6 +4555,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] 17+ messages in thread

* Re: [PATCH RFC v6 2/5] iommu/arm-smmu-v3: Delay stream allocation to inside the mutex
  2026-10-11 11:31 ` [PATCH RFC v6 2/5] iommu/arm-smmu-v3: Delay stream allocation to inside the mutex Peng Fan (OSS)
@ 2026-10-12 20:36   ` Nicolin Chen
  0 siblings, 0 replies; 17+ messages in thread
From: Nicolin Chen @ 2026-10-12 20:36 UTC (permalink / raw)
  To: Peng Fan (OSS)
  Cc: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
	Jean-Philippe Brucker, Jason Gunthorpe, Thierry Reding,
	Krishna Reddy, Jonathan Hunter, linux-arm-kernel, iommu,
	linux-kernel, linux-tegra, Peng Fan

On Sun, Oct 11, 2026 at 07:31:26PM +0800, Peng Fan (OSS) wrote:
> 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 via the
> new arm_smmu_find_stream() helper. Only allocate and insert a new
> stream when no existing entry is found.
> 
> Duplicated Stream IDs from bridged PCI devices are now actively
> skipped rather than stored as duplicate pointers. A separate write
> index (n) tracks only unique entries, and master->num_streams is set
> to the deduplicated count. This eliminates the need for duplicate-
> pointer tracking in all cleanup paths.
> 
> Extract arm_smmu_find_stream() from arm_smmu_find_master() to provide
> stream-level lookup, which will also be used by subsequent shared-SID
> patches.
> 
> Extract arm_smmu_free_master_streams() to share the stream cleanup
> logic between the error rollback in arm_smmu_insert_master() and
> arm_smmu_remove_master().
> 
> The sort is moved after the mutex section, since streams are now
> populated inside the loop rather than beforehand.
> 
> Suggested-by: Nicolin Chen <nicolinc@nvidia.com>
> Suggested-by: Robin Murphy <robin.murphy@arm.com>
> Assisted-by: LLM
> Signed-off-by: Peng Fan <peng.fan@nxp.com>

Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>

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

* Re: [PATCH RFC v6 3/5] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master
  2026-10-11 11:31 ` [PATCH RFC v6 3/5] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master Peng Fan (OSS)
@ 2026-10-12 20:48   ` Nicolin Chen
  0 siblings, 0 replies; 17+ messages in thread
From: Nicolin Chen @ 2026-10-12 20:48 UTC (permalink / raw)
  To: Peng Fan (OSS)
  Cc: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
	Jean-Philippe Brucker, Jason Gunthorpe, Thierry Reding,
	Krishna Reddy, Jonathan Hunter, linux-arm-kernel, iommu,
	linux-kernel, linux-tegra, Peng Fan

On Sun, Oct 11, 2026 at 07:31:27PM +0800, Peng Fan (OSS) wrote:
>  static void arm_smmu_remove_master(struct arm_smmu_master *master)
>  {
> +	int i;
>  	struct arm_smmu_device *smmu = master->smmu;
>  
>  	if (!smmu || !master->streams)
>  		return;
>  
>  	mutex_lock(&smmu->streams_mutex);
> -	arm_smmu_free_master_streams(master);
> +	for (i = 0; i < master->num_streams; i++) {
> +		struct arm_smmu_stream *stream = master->streams[i];
> +
> +		if (!list_empty(&stream->shared_masters)) {
> +			struct arm_smmu_master *next;
> +
> +			list_del_init(&master->shared_masters_elm);
> +			next = list_first_entry(&stream->shared_masters,
> +						struct arm_smmu_master,
> +						shared_masters_elm);
> +			if (stream->master == master)
> +				stream->master = next;
> +			if (list_is_singular(&stream->shared_masters))
> +				list_del_init(&next->shared_masters_elm);
> +			continue;
> +		}
> +		rb_erase(&stream->node, &smmu->streams);
> +		kfree(stream);
> +	}

Rather than removing arm_smmu_free_master_streams(), just fold all
of these into arm_smmu_free_master_streams(). The rollback path in
arm_smmu_insert_master() can still use the same helper:

arm_smmu_insert_master:
	[...]
	if (ret) {
		arm_smmu_free_master_streams(master);
		mutex_unlock(&smmu->streams_mutex);
		kfree(master->streams);
		[...]

arm_smmu_remove_master:
	[...]
	mutex_lock(&smmu->streams_mutex);
	arm_smmu_free_master_streams(master);
	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 56ed6ad1dd523..95da4ad354829 100644
> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
> @@ -1020,6 +1020,8 @@ struct arm_smmu_stream {
>  	u32				id;
>  	struct arm_smmu_master		*master;
>  	struct rb_node			node;
> +	bool				ste_installed;

ste_installed is unused; better move to the patch that uses it.

With these addressed,

Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>

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

* Re: [PATCH RFC v6 5/5] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating
  2026-10-12 13:23   ` Peng Fan
@ 2026-10-12 23:54     ` Nicolin Chen
  2026-10-13 11:22       ` Robin Murphy
  0 siblings, 1 reply; 17+ messages in thread
From: Nicolin Chen @ 2026-10-12 23:54 UTC (permalink / raw)
  To: Peng Fan
  Cc: Will Deacon, Robin Murphy, Joerg Roedel (AMD),
	Jean-Philippe Brucker, Jason Gunthorpe, Thierry Reding,
	Krishna Reddy, Jonathan Hunter, linux-arm-kernel, iommu,
	linux-kernel, linux-tegra, Peng Fan

On Mon, Oct 12, 2026 at 09:23:32PM +0800, Peng Fan wrote:
> >+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) {
> >+		stream->ste_installed = false;
> >+		return true;
> >+	}
> >+	if (stream->ste_installed)
> >+		return true;
> >+	stream->ste_installed = true;
> >+	return false;
> 
> sashiko has comments: https://sashiko.dev/#/patchset/20261011-smmu-shared-sid-v6-0-cb76b89dc612%40nxp.com
> 
> We just need to support a simple case that platform devices(single SID) which
> could share there SIDs, such as MMC-0 and MMC-1 share one SID.
> 
> Shared-SID masters are restricted to platform devices sharing a single SID.
> SVA, IOPF, and vSMMU nesting are all rejected for shared-SID masters (patch 5),
> so VFIO passthrough and direct domain replacement scenarios do not apply.
> The only domain transitions are the default DMA domain attach at probe time
> and ABORT on unbind. Or I may miss something.
 
Domain can be switched between DMA and IDENTITY via sysfs.

I think there should be a way to support that. But, without a concrete
use case, it might be easier to add this into the attach_dev callbacks
(except arm_smmu_attach_dev_blocked):

	if (arm_smmu_master_shares_sid(master) &&
	    old_domain && old_domain != domain &&
	    old_domain != &arm_smmu_blocked_domain)
		return -EBUSY;

And Sashiko's second remark does reveal a real bug:
"Additionally, when devices are detached from a domain, is_abort is true.
 arm_smmu_skip_shared_ste() clears ste_installed but unconditionally returns
 true for all masters. Will this cause the ABORT STE to never be written to
 hardware?"

Given that your use case always group SID-sharing masters in the same
iommu_group and attach/replace a domain on those masters together, I
think a simpler solution can be to always let stream->master install
the shared STE.

One pitfall would be the release domain's attach_dev, which would go
through arm_smmu_attach_dev_blocked() just like a blocked domain. So,
it has to skip correctly if the shared stream->master == master. This
means we might have to add a separate arm_smmu_attach_dev_release().

And I realized that there is another problem in your design: the CD
table is still allocated per master; see arm_smmu_alloc_cd_ptr(). If
STE is shared, its CD table must be shared too.

You'd need to redirect all the master->cd_table with something like:

static struct arm_smmu_master *arm_smmu_cd_owner(struct arm_smmu_master *master)
{
	if (arm_smmu_master_shares_sid(master)) {
		int i;

		for (i = 0; i < master->num_streams; i++) {
			struct arm_smmu_stream *stream = master->streams[i];

			if (!list_empty(&stream->shared_masters))
				return stream->master;
		}
	}

	return master;
}

> >@@ -4360,9 +4391,11 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
> I am thinking to simplify reject if stall_enabled is true:
>  			if (list_empty(&stream->shared_masters)) {
> +				if (stream->master->stall_enabled) {
> +					dev_warn(master->dev,
> +						 "Shared StreamID 0x%x rejected: existing master has stall enabled\n",
> +						 sid);
> +					ret = -EBUSY;
> +					break;
> +				}
 
I'd move it upwards: next to the existing "num_streams != 1" guard.

Nicolin

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

* Re: [PATCH RFC v6 5/5] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating
  2026-10-12 23:54     ` Nicolin Chen
@ 2026-10-13 11:22       ` Robin Murphy
  2026-10-13 12:44         ` Jason Gunthorpe
  0 siblings, 1 reply; 17+ messages in thread
From: Robin Murphy @ 2026-10-13 11:22 UTC (permalink / raw)
  To: Nicolin Chen, Peng Fan
  Cc: Will Deacon, Joerg Roedel (AMD), Jean-Philippe Brucker,
	Jason Gunthorpe, Thierry Reding, Krishna Reddy, Jonathan Hunter,
	linux-arm-kernel, iommu, linux-kernel, linux-tegra, Peng Fan

On 13/10/2026 12:54 am, Nicolin Chen wrote:
> On Mon, Oct 12, 2026 at 09:23:32PM +0800, Peng Fan wrote:
>>> +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) {
>>> +		stream->ste_installed = false;
>>> +		return true;
>>> +	}
>>> +	if (stream->ste_installed)
>>> +		return true;
>>> +	stream->ste_installed = true;
>>> +	return false;
>>
>> sashiko has comments: https://sashiko.dev/#/patchset/20261011-smmu-shared-sid-v6-0-cb76b89dc612%40nxp.com
>>
>> We just need to support a simple case that platform devices(single SID) which
>> could share there SIDs, such as MMC-0 and MMC-1 share one SID.
>>
>> Shared-SID masters are restricted to platform devices sharing a single SID.
>> SVA, IOPF, and vSMMU nesting are all rejected for shared-SID masters (patch 5),
>> so VFIO passthrough and direct domain replacement scenarios do not apply.
>> The only domain transitions are the default DMA domain attach at probe time
>> and ABORT on unbind. Or I may miss something.
>   
> Domain can be switched between DMA and IDENTITY via sysfs.
> 
> I think there should be a way to support that. But, without a concrete
> use case, it might be easier to add this into the attach_dev callbacks
> (except arm_smmu_attach_dev_blocked):
> 
> 	if (arm_smmu_master_shares_sid(master) &&
> 	    old_domain && old_domain != domain &&
> 	    old_domain != &arm_smmu_blocked_domain)
> 		return -EBUSY;
> 
> And Sashiko's second remark does reveal a real bug:
> "Additionally, when devices are detached from a domain, is_abort is true.
>   arm_smmu_skip_shared_ste() clears ste_installed but unconditionally returns
>   true for all masters. Will this cause the ABORT STE to never be written to
>   hardware?"
> 
> Given that your use case always group SID-sharing masters in the same
> iommu_group and attach/replace a domain on those masters together, I
> think a simpler solution can be to always let stream->master install
> the shared STE.
> 
> One pitfall would be the release domain's attach_dev, which would go
> through arm_smmu_attach_dev_blocked() just like a blocked domain. So,
> it has to skip correctly if the shared stream->master == master. This
> means we might have to add a separate arm_smmu_attach_dev_release().

Hmm, but in fact why are we worrying about skipping things at all? Yes 
originally trying to rewrite a "live" STE was a problem, but since 
Jason's redesign, isn't arm_smmu_write_ste() just going to see that the 
"new" entry is identical to what's already there and happily do nothing?

> And I realized that there is another problem in your design: the CD
> table is still allocated per master; see arm_smmu_alloc_cd_ptr(). If
> STE is shared, its CD table must be shared too.

If we're not going to support PASID for shared streams (which I suggest 
we don't...) than we shouldn't strictly need to worry about CD tables 
anyway. So rather than having to special-case CD table sharing for 
shared streams, perhaps it might make sense to generalise back to a 
"default" per-domain CD for non-PASID use, which can then get switched 
for the private per-stream cd_table if and when a device does enable PASID?

Thanks,
Robin.

> You'd need to redirect all the master->cd_table with something like:
> 
> static struct arm_smmu_master *arm_smmu_cd_owner(struct arm_smmu_master *master)
> {
> 	if (arm_smmu_master_shares_sid(master)) {
> 		int i;
> 
> 		for (i = 0; i < master->num_streams; i++) {
> 			struct arm_smmu_stream *stream = master->streams[i];
> 
> 			if (!list_empty(&stream->shared_masters))
> 				return stream->master;
> 		}
> 	}
> 
> 	return master;
> }
> 
>>> @@ -4360,9 +4391,11 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
>> I am thinking to simplify reject if stall_enabled is true:
>>   			if (list_empty(&stream->shared_masters)) {
>> +				if (stream->master->stall_enabled) {
>> +					dev_warn(master->dev,
>> +						 "Shared StreamID 0x%x rejected: existing master has stall enabled\n",
>> +						 sid);
>> +					ret = -EBUSY;
>> +					break;
>> +				}
>   
> I'd move it upwards: next to the existing "num_streams != 1" guard.
> 
> Nicolin



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

* Re: [PATCH RFC v6 5/5] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating
  2026-10-13 11:22       ` Robin Murphy
@ 2026-10-13 12:44         ` Jason Gunthorpe
  2026-10-13 14:46           ` Peng Fan
  2026-10-13 16:23           ` Robin Murphy
  0 siblings, 2 replies; 17+ messages in thread
From: Jason Gunthorpe @ 2026-10-13 12:44 UTC (permalink / raw)
  To: Robin Murphy
  Cc: Nicolin Chen, Peng Fan, Will Deacon, Joerg Roedel (AMD),
	Jean-Philippe Brucker, Thierry Reding, Krishna Reddy,
	Jonathan Hunter, linux-arm-kernel, iommu, linux-kernel,
	linux-tegra, Peng Fan

On Tue, Oct 13, 2026 at 12:22:06PM +0100, Robin Murphy wrote:
> If we're not going to support PASID for shared streams (which I suggest we
> don't...) than we shouldn't strictly need to worry about CD tables anyway.
> So rather than having to special-case CD table sharing for shared streams,
> perhaps it might make sense to generalise back to a "default" per-domain CD
> for non-PASID use, which can then get switched for the private per-stream
> cd_table if and when a device does enable PASID?

If we have stared STEs then the group becomes the per-STE data
structure not the master.. We don't have any way right now to do that,
but it would make some sense for the group to store the shared CD

Jason

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

* Re: [PATCH RFC v6 5/5] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating
  2026-10-13 12:44         ` Jason Gunthorpe
@ 2026-10-13 14:46           ` Peng Fan
  2026-10-13 15:14             ` Jason Gunthorpe
  2026-10-13 16:23           ` Robin Murphy
  1 sibling, 1 reply; 17+ messages in thread
From: Peng Fan @ 2026-10-13 14:46 UTC (permalink / raw)
  To: Jason Gunthorpe, Robin Murphy, Nicolin Chen
  Cc: Robin Murphy, Nicolin Chen, Will Deacon, Joerg Roedel (AMD),
	Jean-Philippe Brucker, Thierry Reding, Krishna Reddy,
	Jonathan Hunter, linux-arm-kernel, iommu, linux-kernel,
	linux-tegra, Peng Fan

Hi Nicolin, Robin, Jason

On Tue, Oct 13, 2026 at 09:44:00AM -0300, Jason Gunthorpe wrote:
>On Tue, Oct 13, 2026 at 12:22:06PM +0100, Robin Murphy wrote:
>> If we're not going to support PASID for shared streams (which I suggest we
>> don't...) than we shouldn't strictly need to worry about CD tables anyway.
>> So rather than having to special-case CD table sharing for shared streams,
>> perhaps it might make sense to generalise back to a "default" per-domain CD
>> for non-PASID use, which can then get switched for the private per-stream
>> cd_table if and when a device does enable PASID?
>
>If we have stared STEs then the group becomes the per-STE data
>structure not the master.. We don't have any way right now to do that,
>but it would make some sense for the group to store the shared CD

Thanks for the detailed feedback, reply you all here.

For the CD table question: since shared-SID masters are in the same
group and always use the same domain, the CD content is identical.
With skip_shared_ste(), only stream->master's STE (and thus its
cd_table) is active in hardware.  The other masters' cd_tables are
allocated but never referenced by the STE.

I see two options:

A) Keep skip_shared_ste() and add arm_smmu_cd_owner() to redirect
   all cd_table operations to stream->master. This is a minimal
   change that works for our use case (platform devices, no PASID,
   no domain switching).

B) Drop skip_shared_ste() entirely and share the CD table at the
   group or domain level. This is cleaner but a larger change.

I'm leaning towards (A) for now to keep the series focused on the
simple shared-SID case.  Would that be acceptable, or do you prefer
we go with (B) from the start?

Thanks,
Peng
>
>Jason
>
>

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

* Re: [PATCH RFC v6 5/5] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating
  2026-10-13 14:46           ` Peng Fan
@ 2026-10-13 15:14             ` Jason Gunthorpe
  0 siblings, 0 replies; 17+ messages in thread
From: Jason Gunthorpe @ 2026-10-13 15:14 UTC (permalink / raw)
  To: Peng Fan
  Cc: Robin Murphy, Nicolin Chen, Will Deacon, Joerg Roedel (AMD),
	Jean-Philippe Brucker, Thierry Reding, Krishna Reddy,
	Jonathan Hunter, linux-arm-kernel, iommu, linux-kernel,
	linux-tegra, Peng Fan

On Tue, Oct 13, 2026 at 10:46:28PM +0800, Peng Fan wrote:
> Hi Nicolin, Robin, Jason
> 
> On Tue, Oct 13, 2026 at 09:44:00AM -0300, Jason Gunthorpe wrote:
> >On Tue, Oct 13, 2026 at 12:22:06PM +0100, Robin Murphy wrote:
> >> If we're not going to support PASID for shared streams (which I suggest we
> >> don't...) than we shouldn't strictly need to worry about CD tables anyway.
> >> So rather than having to special-case CD table sharing for shared streams,
> >> perhaps it might make sense to generalise back to a "default" per-domain CD
> >> for non-PASID use, which can then get switched for the private per-stream
> >> cd_table if and when a device does enable PASID?
> >
> >If we have stared STEs then the group becomes the per-STE data
> >structure not the master.. We don't have any way right now to do that,
> >but it would make some sense for the group to store the shared CD
> 
> Thanks for the detailed feedback, reply you all here.
> 
> For the CD table question: since shared-SID masters are in the same
> group and always use the same domain, the CD content is identical.
> With skip_shared_ste(), only stream->master's STE (and thus its
> cd_table) is active in hardware.  The other masters' cd_tables are
> allocated but never referenced by the STE.
> 
> I see two options:
> 
> A) Keep skip_shared_ste() and add arm_smmu_cd_owner() to redirect
>    all cd_table operations to stream->master. This is a minimal
>    change that works for our use case (platform devices, no PASID,
>    no domain switching).
> 
> B) Drop skip_shared_ste() entirely and share the CD table at the
>    group or domain level. This is cleaner but a larger change.
> 
> I'm leaning towards (A) for now to keep the series focused on the
> simple shared-SID case.  Would that be acceptable, or do you prefer
> we go with (B) from the start?

I think the skip is in the wrong place perhaps?

The group is always guarenteed to have all the masters in it.

So we could split them into primary masters and secondary ones. The
secondary ones are all the devices thare share SIDs with the primary.

The group list is guarenteed to have all masters in it, so we can
simply fully skip attach from the secondary. It is guarenteed by the
group that a primary will have already attached to the STE with
exactly the same domain so the secondary just needs to do NOP.

If you do that then all these questions go away since there is only
ever one thing writing the STE, it is always stable and no issue.

This does leave a problem for master remove to switch the STE to one
of the secondaries, but that sounds like it could be managed through
the release domain mechanism, just nominate a secondary to be primary
and execute an attach on it in the normal way. The hitless STE
mechanism will make it work safely.

Jason

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

* Re: [PATCH RFC v6 5/5] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating
  2026-10-13 12:44         ` Jason Gunthorpe
  2026-10-13 14:46           ` Peng Fan
@ 2026-10-13 16:23           ` Robin Murphy
  1 sibling, 0 replies; 17+ messages in thread
From: Robin Murphy @ 2026-10-13 16:23 UTC (permalink / raw)
  To: Jason Gunthorpe
  Cc: Nicolin Chen, Peng Fan, Will Deacon, Joerg Roedel (AMD),
	Jean-Philippe Brucker, Thierry Reding, Krishna Reddy,
	Jonathan Hunter, linux-arm-kernel, iommu, linux-kernel,
	linux-tegra, Peng Fan

On 13/10/2026 1:44 pm, Jason Gunthorpe wrote:
> On Tue, Oct 13, 2026 at 12:22:06PM +0100, Robin Murphy wrote:
>> If we're not going to support PASID for shared streams (which I suggest we
>> don't...) than we shouldn't strictly need to worry about CD tables anyway.
>> So rather than having to special-case CD table sharing for shared streams,
>> perhaps it might make sense to generalise back to a "default" per-domain CD
>> for non-PASID use, which can then get switched for the private per-stream
>> cd_table if and when a device does enable PASID?
> 
> If we have stared STEs then the group becomes the per-STE data
> structure not the master.. We don't have any way right now to do that,
> but it would make some sense for the group to store the shared CD

That would be precisely the aforementioned special case that I'd hope to 
avoid. My point is that the CD represents the domain context, so we 
really shouldn't have to have multiple copies of that per-device _or_ 
per-group, _unless_ it is attached via a PASID, since in that case it 
does need to be copied into a per-device CD table (since the per-domain 
CD table was indeed the wrong move in hindsight).

So what I'm saying is that if we're going to make anything share CDs, 
then ideally we should just make all non-PASID devices always directly 
share the domain's own CD, then if we just enforce that any devices 
sharing StreamIDs must be non-PASID devices, it should all work out, we 
save some memory in the normal case, and never have to be concerned with 
rewriting any STE with a different S1ContextPtr for the _same_ domain 
except when enabling or disabling PASID for the (single) device owning 
that STE.

Thanks,
Robin.

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

end of thread, other threads:[~2026-10-13 16:23 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-11 11:31 [PATCH RFC v6 0/5] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
2026-10-11 11:31 ` [PATCH RFC v6 1/5] iommu/arm-smmu-v3: Allocate streams individually Peng Fan (OSS)
2026-10-11 11:31 ` [PATCH RFC v6 2/5] iommu/arm-smmu-v3: Delay stream allocation to inside the mutex Peng Fan (OSS)
2026-10-12 20:36   ` Nicolin Chen
2026-10-11 11:31 ` [PATCH RFC v6 3/5] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master Peng Fan (OSS)
2026-10-12 20:48   ` Nicolin Chen
2026-10-11 11:31 ` [PATCH RFC v6 4/5] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group Peng Fan (OSS)
2026-10-12 12:24   ` Peng Fan
2026-10-12 13:05     ` Robin Murphy
2026-10-11 11:31 ` [PATCH RFC v6 5/5] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
2026-10-12 13:23   ` Peng Fan
2026-10-12 23:54     ` Nicolin Chen
2026-10-13 11:22       ` Robin Murphy
2026-10-13 12:44         ` Jason Gunthorpe
2026-10-13 14:46           ` Peng Fan
2026-10-13 15:14             ` Jason Gunthorpe
2026-10-13 16:23           ` Robin Murphy

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.