Linux Tegra architecture development
 help / color / mirror / Atom feed
* [PATCH RFC v8 0/9] iommu/arm-smmu-v3: Support shared Stream IDs
@ 2026-11-13 13:27 Peng Fan (OSS)
  2026-11-13 13:27 ` [PATCH RFC v8 1/9] iommu/arm-smmu-v3: Allocate streams individually Peng Fan (OSS)
                   ` (8 more replies)
  0 siblings, 9 replies; 12+ messages in thread
From: Peng Fan (OSS) @ 2026-11-13 13:27 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 v7:
- Add a locking patch that serializes shared-SID membership changes with
  attach/PASID/ATS/vSMMU using arm_smmu_asid_lock. The lock order remains
  asid_lock -> streams_mutex; arm_smmu_set_pasid() rechecks membership
  under asid_lock, and the vSMMU checks in iommufd and Tegra241 vINTF are
  moved under asid_lock.

- Add a PASID teardown patch that quiesces the PASID before mmu_notifier
  release can unlink it. Clear the CD and invalidate ATS before removing
  master_domain, use a no-account CD write from arm_smmu_mm_release(), and
  re-clear the CD without accounting after unlinking. This closes the
  exit_mmap() use-after-free window and the used_ssids resurrection race.

- Fold the CD[0] allocation fix into the per-domain CD table patch:
  allocate CD[0] as part of arm_smmu_master_alloc_cd_tables(), factor the
  L2 allocation into arm_smmu_alloc_cd_ptr_l2(), and free/reset the new
  table if CD[0] allocation fails. A later identity/blocked/stage-2 attach
  can no longer find an allocated table with a missing CD[0] and warn in
  arm_smmu_clear_cd().

- Fold the in_ste fix into the per-domain CD table patch. in_ste now means
  the STE actually walks the private CD table, not merely that the STE is
  S1-translate. This removes the "first" special case from set_pasid and
  moves the redundant-STE-write guard back into arm_smmu_update_ste().

- Separate the per-device release domain from the group-wide blocked
  domain. A shared SID skips the release STE only while other sharers
  remain; a group-wide blocked attach still programs the abort STE.

- Complete the vSMMU gating for shared SIDs. In addition to
  arm_vsmmu_vdevice_init(), check arm_smmu_master_shares_sid() in
  tegra241_vintf_init_vsid(), arm_smmu_get_viommu_size(), and
  arm_smmu_attach_dev_nested(), and reject sharing with an existing master
  that already has a vmaster.

- Split ATS capability from ATS support. arm_smmu_ats_capable() describes
  hardware/PCIe capability; arm_smmu_ats_supported() also requires that
  the SID is not shared. Shared ATS-capable devices are prepared through
  pci_prepare_ats() but ATS is not enabled. Existing masters with ATS
  enabled, ATS-always-on, PASID, or vSMMU state reject new sharers.

- Reject shared-SID configurations that cannot be supported safely:
  multi-SID masters, stalling on either side, incoming ATS-required
  devices, and PCI/non-PCI SID aliasing.

- Resolve stall_enabled before insert_master() and use
  master->stall_enabled directly for CD.S, STE.S1STALLD, and STE.S2S.
  Remove the redundant arm_smmu_master_stall_enabled() helper and rename
  arm_smmu_is_shared_sid() to arm_smmu_master_shares_sid().

- Do not propagate attach_dev() failures when dropping the last PASID.
  The S1 RID domain now uses the same ignore-return policy as the old
  identity/blocked downgrade path; the private CD table is kept for reuse.

- Propagate arm_smmu_attach_prepare() failures from the identity, blocked,
  and release attach paths instead of installing a partially prepared STE.

- Fold the CD table allocation check into the free helper, add
  arm_smmu_master_needs_private_cdtab(), drop the trivial master/domain
  free wrappers in favour of arm_smmu_free_cd_tables(), and rename the CD
  helpers:
    arm_smmu_master_alloc_cd_tables()
    arm_smmu_domain_alloc_cd_tables()
    arm_smmu_master_get_cd_ptr()
    arm_smmu_get_cd_ptr()

- Restore the normal group return style, remove the unused downgrade
  boolean, and keep the S1/identity/blocked handling under the equivalent
  !ats_always_on gate.

Changes since RFC v6:
- Add "Plumb the CD table into the CD helpers" as a preparation patch, then
  introduce a per-domain CD table for non-PASID S1 streams. Normal non-PASID
  devices in one domain now share a single CD table; private tables are kept
  only for PASID, ATS-always-on, and stalling devices.
- Rework shared-SID STE handling. With the domain-owned CD table, all sharers
  build the same S1ContextPtr, so any sharer may program a normal domain STE.
  Release/blocked STE writes are skipped while the SID is shared, and the last
  master tears the STE down.
- Keep CD.S per-device rather than deriving it from the SMMU stall capability.
  Stall-capable masters use their private CD table so CD.S, STE.S1STALLD and
  STE.S2S remain consistent.
- Add arm_smmu_master_stall_enabled() and use it uniformly. Reject sharing if
  either master wants stalling, and resolve the stall policy before
  insert_master() so the incoming master's policy is visible.
- Reject sharing when the existing master has ATS, PASID, or stall enabled.
  Disable ATS, PASID, SVA, vSMMU and IOPF for shared SIDs.
- Link to v6: https://lore.kernel.org/linux-iommu/20261011-smmu-shared-sid-v6-0-cb76b89dc612@nxp.com/

Changes since RFC v5:
- 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-3 are preparatory refactoring:

  - allocate stream objects individually so multiple masters can point to
    the same stream;
  - delay stream allocation into the streams_mutex-protected insertion
    loop, reusing an existing stream when the SID is already present;
  - plumb struct arm_smmu_ctx_desc_cfg through the CD helpers instead of
    always deriving it from the master.

Patch 4 moves non-PASID stage-1 streams to a per-domain CD table. The
private per-master CD table is retained only for PASID substreams,
ATS-always-on/S1DSS, and the stall model. It also allocates CD[0] together
with the private table and tracks private-table use in in_ste.

Patch 5 and patch 6 add the shared-SID object model:

  - link masters that use the same SID through stream->shared_masters;
  - keep the stream and its RB-tree entry alive until the last master goes
    away;
  - transfer stream ownership when the owner is removed;
  - group aliasing devices into the same IOMMU group.

Patch 7 wires up shared-SID STE programming and feature gating:

  - separate the per-device release domain from the group-wide blocked
    domain, so a single-device release cannot abort a shared STE while
    other sharers remain;
  - let any sharer program the common normal-domain STE;
  - disable PASID, ATS, vSMMU, SVA, and IOPF/stall for shared SIDs;
  - prepare ATS for shared ATS-capable devices without enabling it;
  - reject ATS-required devices, PCI/non-PCI mixing, multi-SID masters,
    and stalling configurations.

Patch 8 serializes shared-SID membership changes with the attach/PASID/ATS
and vSMMU paths using arm_smmu_asid_lock, keeping the lock order as
arm_smmu_asid_lock -> streams_mutex.

Patch 9 fixes the PASID teardown versus mmu_notifier release race: clear
the CD and invalidate ATS before unlinking master_domain, use a no-account
CD write in arm_smmu_mm_release(), and re-clear the CD after unlinking.
It also avoids propagating attach_dev() failures from the last-PASID
downgrade path, as required by the IOMMU core.

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

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
Peng Fan (9):
      iommu/arm-smmu-v3: Allocate streams individually
      iommu/arm-smmu-v3: Delay stream allocation to inside the mutex
      iommu/arm-smmu-v3: Plumb the CD table into the CD helpers
      iommu/arm-smmu-v3: Use a per-domain CD table for non-PASID streams
      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-smmu-v3: Serialize shared-SID changes with attach
      iommu/arm-smmu-v3: Quiesce PASID before mm release unlinks

 .../iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c    |  26 +-
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c    |  11 +-
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c   |   8 +-
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c        | 739 ++++++++++++++++-----
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h        |  27 +-
 drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c     |   9 +-
 6 files changed, 632 insertions(+), 188 deletions(-)
---
base-commit: aaa2f954c21b205a0bcd0961e5b2be4e82e58455
change-id: 20261112-b4-smmu-shared-sid-ver8-d6e35dddd5a9

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


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

* [PATCH RFC v8 1/9] iommu/arm-smmu-v3: Allocate streams individually
  2026-11-13 13:27 [PATCH RFC v8 0/9] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
@ 2026-11-13 13:27 ` Peng Fan (OSS)
  2026-11-13 13:27 ` [PATCH RFC v8 2/9] iommu/arm-smmu-v3: Delay stream allocation to inside the mutex Peng Fan (OSS)
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 12+ messages in thread
From: Peng Fan (OSS) @ 2026-11-13 13:27 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/
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 e68b46485095..853276dd5453 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 100b34f07032..87ae1acb6146 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 bb048e08d613..56ed6ad1dd52 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 710a4c694b94..8166cece943c 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] 12+ messages in thread

* [PATCH RFC v8 2/9] iommu/arm-smmu-v3: Delay stream allocation to inside the mutex
  2026-11-13 13:27 [PATCH RFC v8 0/9] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
  2026-11-13 13:27 ` [PATCH RFC v8 1/9] iommu/arm-smmu-v3: Allocate streams individually Peng Fan (OSS)
@ 2026-11-13 13:27 ` Peng Fan (OSS)
  2026-11-13 13:27 ` [PATCH RFC v8 3/9] iommu/arm-smmu-v3: Plumb the CD table into the CD helpers Peng Fan (OSS)
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 12+ messages in thread
From: Peng Fan (OSS) @ 2026-11-13 13:27 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>
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
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 87ae1acb6146..339b94f2a8a4 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] 12+ messages in thread

* [PATCH RFC v8 3/9] iommu/arm-smmu-v3: Plumb the CD table into the CD helpers
  2026-11-13 13:27 [PATCH RFC v8 0/9] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
  2026-11-13 13:27 ` [PATCH RFC v8 1/9] iommu/arm-smmu-v3: Allocate streams individually Peng Fan (OSS)
  2026-11-13 13:27 ` [PATCH RFC v8 2/9] iommu/arm-smmu-v3: Delay stream allocation to inside the mutex Peng Fan (OSS)
@ 2026-11-13 13:27 ` Peng Fan (OSS)
  2026-11-13 13:27 ` [PATCH RFC v8 4/9] iommu/arm-smmu-v3: Use a per-domain CD table for non-PASID streams Peng Fan (OSS)
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 12+ messages in thread
From: Peng Fan (OSS) @ 2026-11-13 13:27 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>

Refactor the CD table allocation, freeing, lookup and STE-building helpers
to take a struct arm_smmu_ctx_desc_cfg pointer instead of always deriving
it from struct arm_smmu_master. Add master wrappers so existing callers
keep the same behaviour.

Prepare for domain-owned CD tables, where non-PASID streams will point
their STE at a CD table stored in the IOMMU domain rather than in the
master.

No functional change.

Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c  |  4 +-
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c |  3 +-
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c      | 64 +++++++++++++++---------
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h      |  7 +--
 4 files changed, 48 insertions(+), 30 deletions(-)

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 faeec656da9e..7f89bb7a2e9c 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
@@ -25,7 +25,7 @@ arm_smmu_update_s1_domain_cd_entry(struct arm_smmu_domain *smmu_domain)
 		struct arm_smmu_master *master = master_domain->master;
 		struct arm_smmu_cd *cdptr;
 
-		cdptr = arm_smmu_get_cd_ptr(master, master_domain->ssid);
+		cdptr = arm_smmu_master_get_cd_ptr(master, master_domain->ssid);
 		if (WARN_ON(!cdptr))
 			continue;
 
@@ -173,7 +173,7 @@ static void arm_smmu_mm_release(struct mmu_notifier *mn, struct mm_struct *mm)
 		struct arm_smmu_cd target;
 		struct arm_smmu_cd *cdptr;
 
-		cdptr = arm_smmu_get_cd_ptr(master, master_domain->ssid);
+		cdptr = arm_smmu_master_get_cd_ptr(master, master_domain->ssid);
 		if (WARN_ON(!cdptr))
 			continue;
 		arm_smmu_make_sva_cd(&target, master, NULL,
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
index 5a4e45e15125..864d9f2350bc 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
@@ -215,7 +215,8 @@ static void arm_smmu_test_make_cdtable_ste(struct arm_smmu_ste *ste,
 		.stall_enabled = stall_enabled,
 	};
 
-	arm_smmu_make_cdtable_ste(ste, &master, ats_enabled, s1dss);
+	arm_smmu_make_cdtable_ste(ste, &master, &master.cd_table, ats_enabled,
+				  s1dss);
 	if (feat & ARM_SMMU_MASTER_TEST_NESTED) {
 		struct arm_smmu_ste s2ste;
 		int i;
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 339b94f2a8a4..b24a4684202e 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -122,7 +122,7 @@ static const char * const event_class_str[] = {
 	[3] = "Reserved",
 };
 
-static int arm_smmu_alloc_cd_tables(struct arm_smmu_master *master);
+static int arm_smmu_master_alloc_cd_tables(struct arm_smmu_master *master);
 static bool arm_smmu_ats_supported(struct arm_smmu_master *master);
 
 static void parse_driver_options(struct arm_smmu_device *smmu)
@@ -1545,11 +1545,10 @@ static dma_addr_t arm_smmu_cd_l1_get_desc(const struct arm_smmu_cdtab_l1 *src)
 	return le64_to_cpu(src->l2ptr) & CTXDESC_L1_DESC_L2PTR_MASK;
 }
 
-struct arm_smmu_cd *arm_smmu_get_cd_ptr(struct arm_smmu_master *master,
-					u32 ssid)
+static struct arm_smmu_cd *
+arm_smmu_get_cd_ptr(struct arm_smmu_ctx_desc_cfg *cd_table, u32 ssid)
 {
 	struct arm_smmu_cdtab_l2 *l2;
-	struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;
 
 	if (!arm_smmu_cdtab_allocated(cd_table))
 		return NULL;
@@ -1563,6 +1562,12 @@ struct arm_smmu_cd *arm_smmu_get_cd_ptr(struct arm_smmu_master *master,
 	return &l2->cds[arm_smmu_cdtab_l2_idx(ssid)];
 }
 
+struct arm_smmu_cd *arm_smmu_master_get_cd_ptr(struct arm_smmu_master *master,
+					u32 ssid)
+{
+	return arm_smmu_get_cd_ptr(&master->cd_table, ssid);
+}
+
 static struct arm_smmu_cd *arm_smmu_alloc_cd_ptr(struct arm_smmu_master *master,
 						 u32 ssid)
 {
@@ -1573,7 +1578,7 @@ static struct arm_smmu_cd *arm_smmu_alloc_cd_ptr(struct arm_smmu_master *master,
 	iommu_group_mutex_assert(master->dev);
 
 	if (!arm_smmu_cdtab_allocated(cd_table)) {
-		if (arm_smmu_alloc_cd_tables(master))
+		if (arm_smmu_master_alloc_cd_tables(master))
 			return NULL;
 	}
 
@@ -1595,7 +1600,7 @@ static struct arm_smmu_cd *arm_smmu_alloc_cd_ptr(struct arm_smmu_master *master,
 			arm_smmu_sync_cd(master, ssid, false);
 		}
 	}
-	return arm_smmu_get_cd_ptr(master, ssid);
+	return arm_smmu_get_cd_ptr(cd_table, ssid);
 }
 
 struct arm_smmu_cd_writer {
@@ -1712,7 +1717,7 @@ void arm_smmu_clear_cd(struct arm_smmu_master *master, ioasid_t ssid)
 
 	if (!arm_smmu_cdtab_allocated(&master->cd_table))
 		return;
-	cdptr = arm_smmu_get_cd_ptr(master, ssid);
+	cdptr = arm_smmu_master_get_cd_ptr(master, ssid);
 	if (!cdptr) {
 		/* Only ats_always_on allows a NULL CD on default substream */
 		WARN_ON(!master->ats_always_on || ssid);
@@ -1721,15 +1726,15 @@ void arm_smmu_clear_cd(struct arm_smmu_master *master, ioasid_t ssid)
 	arm_smmu_write_cd_entry(master, ssid, cdptr, &target);
 }
 
-static int arm_smmu_alloc_cd_tables(struct arm_smmu_master *master)
+static int arm_smmu_alloc_cd_tables(struct arm_smmu_device *smmu,
+				    struct arm_smmu_ctx_desc_cfg *cd_table,
+				    u8 ssid_bits, bool ats_always_on)
 {
 	int ret;
 	size_t l1size;
 	size_t max_contexts;
-	struct arm_smmu_device *smmu = master->smmu;
-	struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;
 
-	cd_table->s1cdmax = master->ssid_bits;
+	cd_table->s1cdmax = ssid_bits;
 
 	/*
 	 * When a device doesn't support PASID (non default SSID), ssid_bits is
@@ -1744,7 +1749,7 @@ static int arm_smmu_alloc_cd_tables(struct arm_smmu_master *master)
 	 * Set S1CDMAX no lower than 1. This would add a dummy substream in the
 	 * CD table but it should never be used by an actual CD.
 	 */
-	if (master->ats_always_on)
+	if (ats_always_on)
 		cd_table->s1cdmax = max_t(u8, cd_table->s1cdmax, 1);
 	max_contexts = 1 << cd_table->s1cdmax;
 
@@ -1786,11 +1791,20 @@ static int arm_smmu_alloc_cd_tables(struct arm_smmu_master *master)
 	return ret;
 }
 
-static void arm_smmu_free_cd_tables(struct arm_smmu_master *master)
+static int arm_smmu_master_alloc_cd_tables(struct arm_smmu_master *master)
+{
+	return arm_smmu_alloc_cd_tables(master->smmu, &master->cd_table,
+					master->ssid_bits,
+					master->ats_always_on);
+}
+
+static void arm_smmu_free_cd_tables(struct arm_smmu_device *smmu,
+				    struct arm_smmu_ctx_desc_cfg *cd_table)
 {
 	int i;
-	struct arm_smmu_device *smmu = master->smmu;
-	struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;
+
+	if (!arm_smmu_cdtab_allocated(cd_table))
+		return;
 
 	if (cd_table->s1fmt != STRTAB_STE_0_S1FMT_LINEAR) {
 		for (i = 0; i < cd_table->l2.num_l1_ents; i++) {
@@ -1900,10 +1914,10 @@ EXPORT_SYMBOL_IF_KUNIT(arm_smmu_make_bypass_ste);
 
 VISIBLE_IF_KUNIT
 void arm_smmu_make_cdtable_ste(struct arm_smmu_ste *target,
-			       struct arm_smmu_master *master, bool ats_enabled,
-			       unsigned int s1dss)
+			       struct arm_smmu_master *master,
+			       struct arm_smmu_ctx_desc_cfg *cd_table,
+			       bool ats_enabled, unsigned int s1dss)
 {
-	struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;
 	struct arm_smmu_device *smmu = master->smmu;
 
 	memset(target, 0, sizeof(*target));
@@ -3865,7 +3879,8 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev,
 		arm_smmu_make_s1_cd(&target_cd, master, smmu_domain);
 		arm_smmu_write_cd_entry(master, IOMMU_NO_PASID, cdptr,
 					&target_cd);
-		arm_smmu_make_cdtable_ste(&target, master, state.ats_enabled,
+		arm_smmu_make_cdtable_ste(&target, master, &master->cd_table,
+					  state.ats_enabled,
 					  STRTAB_STE_1_S1DSS_SSID0);
 		arm_smmu_install_ste_for_dev(master, &target);
 		break;
@@ -3932,7 +3947,8 @@ static void arm_smmu_update_ste(struct arm_smmu_master *master,
 	 * the S1DSS is correct and this will just update the EATS. Otherwise it
 	 * installs the entire thing. This will be hitless.
 	 */
-	arm_smmu_make_cdtable_ste(&ste, master, ats_enabled, s1dss);
+	arm_smmu_make_cdtable_ste(&ste, master, &master->cd_table, ats_enabled,
+				  s1dss);
 	arm_smmu_install_ste_for_dev(master, &ste);
 }
 
@@ -4065,7 +4081,8 @@ static void arm_smmu_attach_dev_ste(struct iommu_domain *domain,
 		 */
 		state.cd_needs_ats = true;
 		arm_smmu_attach_prepare(&state, domain);
-		arm_smmu_make_cdtable_ste(ste, master, state.ats_enabled, s1dss);
+		arm_smmu_make_cdtable_ste(ste, master, &master->cd_table,
+					  state.ats_enabled, s1dss);
 	} else {
 		arm_smmu_attach_prepare(&state, domain);
 	}
@@ -4436,7 +4453,7 @@ static int arm_smmu_master_prepare_ats(struct arm_smmu_master *master)
 
 	master->ats_always_on = true;
 
-	return arm_smmu_alloc_cd_tables(master);
+	return arm_smmu_master_alloc_cd_tables(master);
 }
 
 static struct iommu_device *arm_smmu_probe_device(struct device *dev)
@@ -4509,8 +4526,7 @@ static void arm_smmu_release_device(struct device *dev)
 
 	arm_smmu_disable_pasid(master);
 	arm_smmu_remove_master(master);
-	if (arm_smmu_cdtab_allocated(&master->cd_table))
-		arm_smmu_free_cd_tables(master);
+	arm_smmu_free_cd_tables(master->smmu, &master->cd_table);
 	kfree(master);
 }
 
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 56ed6ad1dd52..b02163a48c97 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -1143,8 +1143,9 @@ void arm_smmu_get_cd_used(const __le64 *ent, __le64 *used_bits);
 void arm_smmu_make_bypass_ste(struct arm_smmu_device *smmu,
 			      struct arm_smmu_ste *target);
 void arm_smmu_make_cdtable_ste(struct arm_smmu_ste *target,
-			       struct arm_smmu_master *master, bool ats_enabled,
-			       unsigned int s1dss);
+			       struct arm_smmu_master *master,
+			       struct arm_smmu_ctx_desc_cfg *cd_table,
+			       bool ats_enabled, unsigned int s1dss);
 void arm_smmu_make_sva_cd(struct arm_smmu_cd *target,
 			  struct arm_smmu_master *master, struct mm_struct *mm,
 			  u16 asid);
@@ -1193,7 +1194,7 @@ static inline void arm_smmu_domain_free(struct arm_smmu_domain *smmu_domain)
 }
 
 void arm_smmu_clear_cd(struct arm_smmu_master *master, ioasid_t ssid);
-struct arm_smmu_cd *arm_smmu_get_cd_ptr(struct arm_smmu_master *master,
+struct arm_smmu_cd *arm_smmu_master_get_cd_ptr(struct arm_smmu_master *master,
 					u32 ssid);
 void arm_smmu_make_s1_cd(struct arm_smmu_cd *target,
 			 struct arm_smmu_master *master,

-- 
2.34.1


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

* [PATCH RFC v8 4/9] iommu/arm-smmu-v3: Use a per-domain CD table for non-PASID streams
  2026-11-13 13:27 [PATCH RFC v8 0/9] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
                   ` (2 preceding siblings ...)
  2026-11-13 13:27 ` [PATCH RFC v8 3/9] iommu/arm-smmu-v3: Plumb the CD table into the CD helpers Peng Fan (OSS)
@ 2026-11-13 13:27 ` Peng Fan (OSS)
  2026-11-13 19:01   ` Nicolin Chen
  2026-11-13 13:27 ` [PATCH RFC v8 5/9] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master Peng Fan (OSS)
                   ` (4 subsequent siblings)
  8 siblings, 1 reply; 12+ messages in thread
From: Peng Fan (OSS) @ 2026-11-13 13:27 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>

The CD describes the domain context, so non-PASID devices attached to the
same S1 domain do not need private copies of it. Store a CD table in
struct arm_smmu_domain and make non-PASID streams point their STE at that
table.

The per-master CD table is now used when a device needs substreams (PASID),
for ats_always_on devices which need a CD table to use S1DSS, or when the
device uses the stall model. On the first PASID attach, seed the private
table's default substream from the RID domain and switch the STE to the
private table; after the last PASID detaches, switch the STE back to the
domain table (or the native bypass/abort STE). The private table is
retained for reuse and freed at device release.

CD.S is a property of the CD and must agree with STE.S1STALLD. On an SMMU
with STALL_MODEL==0b00, CD.S=1 together with STE.S1STALLD=1 is an illegal
CD and the SMMU aborts the stream. A shared domain CD therefore cannot
serve both stalling and terminating devices. Keep stall-enabled devices on
their private CD table, where CD.S can be set for them, while terminating
devices continue to share the domain table with CD.S=0.

Prepare for devices sharing a StreamID to naturally build the same
S1ContextPtr for the same domain, instead of depending on the stream
owner's private CD table.

Allocate CD[0] as part of the private CD table allocation and track
whether the STE actually walks the private table in in_ste. This keeps a
failed L2 allocation from leaving a partially allocated table behind, and
lets teardown skip redundant STE rewrites.

Suggested-by: Robin Murphy <robin.murphy@arm.com>
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 244 ++++++++++++++++++++++------
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h |   8 +
 2 files changed, 202 insertions(+), 50 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 b24a4684202e..091119e584ed 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -123,6 +123,8 @@ static const char * const event_class_str[] = {
 };
 
 static int arm_smmu_master_alloc_cd_tables(struct arm_smmu_master *master);
+static void arm_smmu_free_cd_tables(struct arm_smmu_device *smmu,
+				    struct arm_smmu_ctx_desc_cfg *cd_table);
 static bool arm_smmu_ats_supported(struct arm_smmu_master *master);
 
 static void parse_driver_options(struct arm_smmu_device *smmu)
@@ -1568,11 +1570,31 @@ struct arm_smmu_cd *arm_smmu_master_get_cd_ptr(struct arm_smmu_master *master,
 	return arm_smmu_get_cd_ptr(&master->cd_table, ssid);
 }
 
+static int arm_smmu_alloc_cd_ptr_l2(struct arm_smmu_master *master, u32 ssid)
+{
+	struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;
+	unsigned int idx = arm_smmu_cdtab_l1_idx(ssid);
+	struct arm_smmu_cdtab_l2 **l2ptr = &cd_table->l2.l2ptrs[idx];
+	dma_addr_t l2ptr_dma;
+
+	if (*l2ptr)
+		return 0;
+
+	*l2ptr = dma_alloc_coherent(master->smmu->dev, sizeof(**l2ptr),
+				    &l2ptr_dma, GFP_KERNEL);
+	if (!*l2ptr)
+		return -ENOMEM;
+
+	arm_smmu_write_cd_l1_desc(&cd_table->l2.l1tab[idx], l2ptr_dma);
+	/* An invalid L1CD can be cached */
+	arm_smmu_sync_cd(master, ssid, false);
+	return 0;
+}
+
 static struct arm_smmu_cd *arm_smmu_alloc_cd_ptr(struct arm_smmu_master *master,
 						 u32 ssid)
 {
 	struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;
-	struct arm_smmu_device *smmu = master->smmu;
 
 	might_sleep();
 	iommu_group_mutex_assert(master->dev);
@@ -1582,24 +1604,10 @@ static struct arm_smmu_cd *arm_smmu_alloc_cd_ptr(struct arm_smmu_master *master,
 			return NULL;
 	}
 
-	if (cd_table->s1fmt == STRTAB_STE_0_S1FMT_64K_L2) {
-		unsigned int idx = arm_smmu_cdtab_l1_idx(ssid);
-		struct arm_smmu_cdtab_l2 **l2ptr = &cd_table->l2.l2ptrs[idx];
-
-		if (!*l2ptr) {
-			dma_addr_t l2ptr_dma;
-
-			*l2ptr = dma_alloc_coherent(smmu->dev, sizeof(**l2ptr),
-						    &l2ptr_dma, GFP_KERNEL);
-			if (!*l2ptr)
-				return NULL;
+	if (cd_table->s1fmt == STRTAB_STE_0_S1FMT_64K_L2 &&
+	    arm_smmu_alloc_cd_ptr_l2(master, ssid))
+		return NULL;
 
-			arm_smmu_write_cd_l1_desc(&cd_table->l2.l1tab[idx],
-						  l2ptr_dma);
-			/* An invalid L1CD can be cached */
-			arm_smmu_sync_cd(master, ssid, false);
-		}
-	}
 	return arm_smmu_get_cd_ptr(cd_table, ssid);
 }
 
@@ -1793,9 +1801,22 @@ static int arm_smmu_alloc_cd_tables(struct arm_smmu_device *smmu,
 
 static int arm_smmu_master_alloc_cd_tables(struct arm_smmu_master *master)
 {
-	return arm_smmu_alloc_cd_tables(master->smmu, &master->cd_table,
-					master->ssid_bits,
-					master->ats_always_on);
+	int ret;
+
+	ret = arm_smmu_alloc_cd_tables(master->smmu, &master->cd_table,
+				       master->ssid_bits,
+				       master->ats_always_on);
+	if (ret)
+		return ret;
+
+	if (master->cd_table.s1fmt == STRTAB_STE_0_S1FMT_64K_L2) {
+		ret = arm_smmu_alloc_cd_ptr_l2(master, IOMMU_NO_PASID);
+		if (ret) {
+			arm_smmu_free_cd_tables(master->smmu, &master->cd_table);
+			memset(&master->cd_table, 0, sizeof(master->cd_table));
+		}
+	}
+	return ret;
 }
 
 static void arm_smmu_free_cd_tables(struct arm_smmu_device *smmu,
@@ -1830,6 +1851,12 @@ static void arm_smmu_free_cd_tables(struct arm_smmu_device *smmu,
 	}
 }
 
+static int arm_smmu_domain_alloc_cd_tables(struct arm_smmu_domain *smmu_domain)
+{
+	return arm_smmu_alloc_cd_tables(smmu_domain->smmu,
+					&smmu_domain->cd_table, 0, false);
+}
+
 /* Stream table manipulation functions */
 static void arm_smmu_write_strtab_l1_desc(struct arm_smmu_strtab_l1 *dst,
 					  dma_addr_t l2ptr_dma)
@@ -3042,6 +3069,9 @@ static void arm_smmu_domain_free_paging(struct iommu_domain *domain)
 		mutex_lock(&arm_smmu_asid_lock);
 		xa_erase(&arm_smmu_asid_xa, smmu_domain->cd.asid);
 		mutex_unlock(&arm_smmu_asid_lock);
+
+		arm_smmu_free_cd_tables(smmu_domain->smmu,
+					&smmu_domain->cd_table);
 	} else {
 		struct arm_smmu_s2_cfg *cfg = &smmu_domain->s2_cfg;
 		if (cfg->vmid)
@@ -3133,6 +3163,15 @@ static int arm_smmu_domain_finalise(struct arm_smmu_domain *smmu_domain,
 	if (!pgtbl_ops)
 		return -ENOMEM;
 
+	smmu_domain->smmu = smmu;
+	if (smmu_domain->stage == ARM_SMMU_DOMAIN_S1) {
+		ret = arm_smmu_domain_alloc_cd_tables(smmu_domain);
+		if (ret) {
+			free_io_pgtable_ops(pgtbl_ops);
+			return ret;
+		}
+	}
+
 	smmu_domain->domain.pgsize_bitmap = pgtbl_cfg.pgsize_bitmap;
 	smmu_domain->tgsz_lg2 = __ffs(pgtbl_cfg.pgsize_bitmap);
 	smmu_domain->domain.geometry.aperture_end = (1UL << pgtbl_cfg.ias) - 1;
@@ -3142,12 +3181,14 @@ static int arm_smmu_domain_finalise(struct arm_smmu_domain *smmu_domain,
 
 	ret = finalise_stage_fn(smmu, smmu_domain);
 	if (ret < 0) {
+		if (smmu_domain->stage == ARM_SMMU_DOMAIN_S1)
+			arm_smmu_free_cd_tables(smmu_domain->smmu,
+						&smmu_domain->cd_table);
 		free_io_pgtable_ops(pgtbl_ops);
 		return ret;
 	}
 
 	smmu_domain->pgtbl_ops = pgtbl_ops;
-	smmu_domain->smmu = smmu;
 	return 0;
 }
 
@@ -3171,12 +3212,17 @@ void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master,
 {
 	int i, j;
 	struct arm_smmu_device *smmu = master->smmu;
+	u64 ste = le64_to_cpu(target->data[0]);
 
 	master->cd_table.in_ste =
-		FIELD_GET(STRTAB_STE_0_CFG, le64_to_cpu(target->data[0])) ==
-		STRTAB_STE_0_CFG_S1_TRANS;
+		FIELD_GET(STRTAB_STE_0_CFG, ste) ==
+			STRTAB_STE_0_CFG_S1_TRANS &&
+		(ste & STRTAB_STE_0_S1CTXPTR_MASK) ==
+			(master->cd_table.cdtab_dma &
+			 STRTAB_STE_0_S1CTXPTR_MASK);
 	master->ste_ats_enabled =
-		FIELD_GET(STRTAB_STE_1_EATS, le64_to_cpu(target->data[1])) ==
+		FIELD_GET(STRTAB_STE_1_EATS,
+			  le64_to_cpu(target->data[1])) ==
 		STRTAB_STE_1_EATS_TRANS;
 
 	for (i = 0; i < master->num_streams; ++i) {
@@ -3827,6 +3873,20 @@ void arm_smmu_attach_commit(struct arm_smmu_attach_state *state)
 	master->ats_enabled = state->ats_enabled;
 }
 
+static bool arm_smmu_master_needs_private_cdtab(struct arm_smmu_master *master)
+{
+	/* ATS-always-on needs S1DSS/EATS, which requires a private CD table */
+	if (master->ats_always_on)
+		return true;
+
+	/* CD.S is a per-CD property and cannot be shared with terminating devices */
+	if (master->stall_enabled)
+		return true;
+
+	/* PASID CDs live in the private table */
+	return arm_smmu_ssids_in_use(&master->cd_table);
+}
+
 static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev,
 			       struct iommu_domain *old_domain)
 {
@@ -3840,6 +3900,7 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev,
 		.ssid = IOMMU_NO_PASID,
 	};
 	struct arm_smmu_master *master;
+	struct arm_smmu_ctx_desc_cfg *cd_table = NULL;
 	struct arm_smmu_cd *cdptr;
 
 	if (!fwspec)
@@ -3852,11 +3913,25 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev,
 		return -EINVAL;
 
 	if (smmu_domain->stage == ARM_SMMU_DOMAIN_S1) {
-		cdptr = arm_smmu_alloc_cd_ptr(master, IOMMU_NO_PASID);
+		/*
+		 * Non-PASID devices use the domain's shared CD table. The
+		 * private table is needed for substreams (PASID), for
+		 * ATS-always-on (S1DSS), and for the stall model: CD.S is a
+		 * per-CD property and cannot be shared by devices that
+		 * terminate faults.
+		 */
+		if (arm_smmu_master_needs_private_cdtab(master)) {
+			cd_table = &master->cd_table;
+			cdptr = arm_smmu_alloc_cd_ptr(master, IOMMU_NO_PASID);
+		} else {
+			cd_table = &smmu_domain->cd_table;
+			cdptr = arm_smmu_get_cd_ptr(cd_table, IOMMU_NO_PASID);
+		}
 		if (!cdptr)
 			return -ENOMEM;
-	} else if (arm_smmu_ssids_in_use(&master->cd_table))
+	} else if (arm_smmu_ssids_in_use(&master->cd_table)) {
 		return -EBUSY;
+	}
 
 	/*
 	 * Prevent arm_smmu_share_asid() from trying to change the ASID
@@ -3879,7 +3954,7 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev,
 		arm_smmu_make_s1_cd(&target_cd, master, smmu_domain);
 		arm_smmu_write_cd_entry(master, IOMMU_NO_PASID, cdptr,
 					&target_cd);
-		arm_smmu_make_cdtable_ste(&target, master, &master->cd_table,
+		arm_smmu_make_cdtable_ste(&target, master, cd_table,
 					  state.ats_enabled,
 					  STRTAB_STE_1_S1DSS_SSID0);
 		arm_smmu_install_ste_for_dev(master, &target);
@@ -3930,22 +4005,33 @@ static void arm_smmu_update_ste(struct arm_smmu_master *master,
 				struct iommu_domain *sid_domain,
 				bool ats_enabled)
 {
-	unsigned int s1dss = STRTAB_STE_1_S1DSS_TERMINATE;
+	unsigned int s1dss;
 	struct arm_smmu_ste ste;
 
-	if (master->cd_table.in_ste && master->ste_ats_enabled == ats_enabled)
+	/*
+	 * A repeat PASID attach leaves the STE on the private table with the
+	 * same S1DSS/EATS. arm_smmu_write_ste() would otherwise issue a
+	 * redundant CMD_PREFETCH_CFG for every stream.
+	 */
+	if (master->cd_table.in_ste &&
+	    master->ste_ats_enabled == ats_enabled)
 		return;
 
 	if (sid_domain->type == IOMMU_DOMAIN_IDENTITY)
 		s1dss = STRTAB_STE_1_S1DSS_BYPASS;
-	else
-		WARN_ON(sid_domain->type != IOMMU_DOMAIN_BLOCKED);
+	else if (sid_domain->type == IOMMU_DOMAIN_BLOCKED)
+		s1dss = STRTAB_STE_1_S1DSS_TERMINATE;
+	else if (sid_domain->type & __IOMMU_DOMAIN_PAGING)
+		s1dss = STRTAB_STE_1_S1DSS_SSID0;
+	else {
+		WARN_ON(1);
+		return;
+	}
 
 	/*
-	 * Change the STE into a cdtable one with SID IDENTITY/BLOCKED behavior
-	 * using s1dss if necessary. If the cd_table is already installed then
-	 * the S1DSS is correct and this will just update the EATS. Otherwise it
-	 * installs the entire thing. This will be hitless.
+	 * The PASID entries live in the private per-master CD table, so make
+	 * sure the STE points there even when the RID domain was previously
+	 * using the domain's shared CD table.
 	 */
 	arm_smmu_make_cdtable_ste(&ste, master, &master->cd_table, ats_enabled,
 				  s1dss);
@@ -3963,7 +4049,8 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
 		.ssid = pasid,
 		.old_domain = old,
 	};
-	struct arm_smmu_cd *cdptr;
+	struct arm_smmu_cd *cdptr, *cd0ptr = NULL;
+	bool first;
 	int ret;
 
 	/* The core code validates pasid */
@@ -3971,15 +4058,23 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
 	if (smmu_domain->smmu != master->smmu)
 		return -EINVAL;
 
-	if (!master->cd_table.in_ste &&
+	if (!((sid_domain->type & __IOMMU_DOMAIN_PAGING) &&
+	      to_smmu_domain(sid_domain)->stage == ARM_SMMU_DOMAIN_S1) &&
 	    sid_domain->type != IOMMU_DOMAIN_IDENTITY &&
 	    sid_domain->type != IOMMU_DOMAIN_BLOCKED)
 		return -EINVAL;
 
+	first = !arm_smmu_ssids_in_use(&master->cd_table);
 	cdptr = arm_smmu_alloc_cd_ptr(master, pasid);
 	if (!cdptr)
 		return -ENOMEM;
 
+	if (first) {
+		cd0ptr = arm_smmu_master_get_cd_ptr(master, IOMMU_NO_PASID);
+		if (WARN_ON(!cd0ptr))
+			return -ENXIO;
+	}
+
 	mutex_lock(&arm_smmu_asid_lock);
 	ret = arm_smmu_attach_prepare(&state, &smmu_domain->domain);
 	if (ret) {
@@ -3995,7 +4090,30 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
 	cd->data[0] |= cpu_to_le64(
 		FIELD_PREP(CTXDESC_CD_0_ASID, smmu_domain->cd.asid));
 
+	/*
+	 * The private CD table is taking over the STE now. Make sure its
+	 * default substream matches the RID domain before the STE points at
+	 * it, since non-PASID traffic follows CD[0].
+	 */
+	if (first) {
+		if ((sid_domain->type & __IOMMU_DOMAIN_PAGING) &&
+		    to_smmu_domain(sid_domain)->stage == ARM_SMMU_DOMAIN_S1) {
+			struct arm_smmu_cd cd0;
+
+			arm_smmu_make_s1_cd(&cd0, master,
+					    to_smmu_domain(sid_domain));
+			arm_smmu_write_cd_entry(master, IOMMU_NO_PASID,
+						cd0ptr, &cd0);
+		} else if (sid_domain->type == IOMMU_DOMAIN_IDENTITY ||
+			   sid_domain->type == IOMMU_DOMAIN_BLOCKED) {
+			arm_smmu_clear_cd(master, IOMMU_NO_PASID);
+		} else {
+			WARN_ON(1);
+		}
+	}
+
 	arm_smmu_write_cd_entry(master, pasid, cdptr, cd);
+
 	arm_smmu_update_ste(master, sid_domain, state.ats_enabled);
 
 	arm_smmu_attach_commit(&state);
@@ -4015,35 +4133,61 @@ static int arm_smmu_blocking_set_dev_pasid(struct iommu_domain *new_domain,
 		.old_domain = old_domain,
 		.ssid = pasid,
 	};
+	int ret = 0;
 
 	mutex_lock(&arm_smmu_asid_lock);
 	arm_smmu_attach_prepare_invs(&state, NULL);
+	/*
+	 * Unlink the PASID from the domain before invalidating its CD.
+	 * arm_smmu_mm_release() walks this list with only devices_lock held
+	 * and writes a valid, faulting CD; unlinking first prevents it from
+	 * resurrecting used_ssids after the last PASID is detached.
+	 */
+	state.old_master_domain = arm_smmu_remove_master_domain(
+		master, &smmu_domain->domain, pasid);
 	arm_smmu_clear_cd(master, pasid);
 	if (master->ats_enabled)
 		arm_smmu_atc_inv_master(master, pasid);
-	state.old_master_domain = arm_smmu_remove_master_domain(
-		master, &smmu_domain->domain, pasid);
 	arm_smmu_install_old_domain_invs(&state);
 	mutex_unlock(&arm_smmu_asid_lock);
 	arm_smmu_attach_release(&state);
 
 	/*
-	 * When the last user of the CD table goes away downgrade the STE back
-	 * to a non-cd_table one, by re-attaching its sid_domain.
+	 * When the last user of the CD table goes away, switch the STE back to
+	 * the RID domain's CD table (S1) or to the native bypass/abort STE
+	 * (identity/blocked). Do not call attach_dev() for an S1 paging domain:
+	 * this is a teardown path where allocation failures cannot be
+	 * propagated. The private CD table is kept allocated for reuse and
+	 * freed when the device goes away.
 	 */
 	if (!arm_smmu_ssids_in_use(&master->cd_table)) {
 		struct iommu_domain *sid_domain =
 			iommu_driver_get_domain_for_dev(master->dev);
-		bool ats_always_on = master->ats_always_on &&
-				     sid_domain->type != IOMMU_DOMAIN_BLOCKED;
-		bool downgrade = sid_domain->type == IOMMU_DOMAIN_IDENTITY ||
-				 sid_domain->type == IOMMU_DOMAIN_BLOCKED;
 
-		if (!ats_always_on && downgrade)
-			sid_domain->ops->attach_dev(sid_domain, dev,
-						    sid_domain);
+		if (!master->ats_always_on ||
+		    sid_domain->type == IOMMU_DOMAIN_BLOCKED) {
+			if ((sid_domain->type & __IOMMU_DOMAIN_PAGING) &&
+			    to_smmu_domain(sid_domain)->stage ==
+				    ARM_SMMU_DOMAIN_S1) {
+				struct arm_smmu_ste ste;
+				struct arm_smmu_domain *sid_smmu_domain =
+					to_smmu_domain(sid_domain);
+
+				mutex_lock(&arm_smmu_asid_lock);
+				arm_smmu_make_cdtable_ste(
+					&ste, master, &sid_smmu_domain->cd_table,
+					master->ats_enabled,
+					STRTAB_STE_1_S1DSS_SSID0);
+				arm_smmu_install_ste_for_dev(master, &ste);
+				mutex_unlock(&arm_smmu_asid_lock);
+			} else if (sid_domain->type == IOMMU_DOMAIN_IDENTITY ||
+				   sid_domain->type == IOMMU_DOMAIN_BLOCKED) {
+				ret = sid_domain->ops->attach_dev(sid_domain, dev,
+								  sid_domain);
+			}
+		}
 	}
-	return 0;
+	return ret;
 }
 
 static void arm_smmu_attach_dev_ste(struct iommu_domain *domain,
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 b02163a48c97..90ba843989bb 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -879,6 +879,7 @@ struct arm_smmu_ctx_desc_cfg {
 	};
 	dma_addr_t			cdtab_dma;
 	unsigned int			used_ssids;
+	/* The STE walks this private CD table */
 	u8				in_ste;
 	u8				s1fmt;
 	/* log2 of the maximum number of CDs supported by this table */
@@ -1091,6 +1092,13 @@ struct arm_smmu_domain {
 		struct arm_smmu_s2_cfg		s2_cfg;
 	};
 
+	/*
+	 * CD table shared by all non-PASID devices attached to this S1
+	 * domain. A master uses a private table only when it needs
+	 * substreams (PASID) or ATS-always-on/S1DSS.
+	 */
+	struct arm_smmu_ctx_desc_cfg	cd_table;
+
 	struct iommu_domain		domain;
 
 	struct arm_smmu_invs __rcu	*invs;

-- 
2.34.1


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

* [PATCH RFC v8 5/9] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master
  2026-11-13 13:27 [PATCH RFC v8 0/9] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
                   ` (3 preceding siblings ...)
  2026-11-13 13:27 ` [PATCH RFC v8 4/9] iommu/arm-smmu-v3: Use a per-domain CD table for non-PASID streams Peng Fan (OSS)
@ 2026-11-13 13:27 ` Peng Fan (OSS)
  2026-11-13 13:27 ` [PATCH RFC v8 6/9] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group Peng Fan (OSS)
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 12+ messages in thread
From: Peng Fan (OSS) @ 2026-11-13 13:27 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 SCU configuration.

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.
Sharing is also rejected if the existing master has stall enabled, since
dynamically clearing stall_enabled without reprogramming the hardware
STE would leave the stall bit active and risk an unrecoverable bus
lockup when CMD_RESUME cannot be issued.

On removal, arm_smmu_free_master_streams() handles both the shared and
non-shared cases: 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/
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 55 ++++++++++++++++++++++++-----
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h |  2 ++
 2 files changed, 49 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 091119e584ed..f954dc7361c4 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2138,6 +2138,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;
 }
 
@@ -4462,10 +4464,26 @@ static void arm_smmu_free_master_streams(struct arm_smmu_master *master)
 	lockdep_assert_held(&smmu->streams_mutex);
 
 	for (i = 0; i < master->num_streams; i++) {
-		if (!master->streams[i])
+		struct arm_smmu_stream *stream = master->streams[i];
+
+		if (!stream)
+			continue;
+
+		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(&master->streams[i]->node, &smmu->streams);
-		kfree(master->streams[i]);
+		}
+		rb_erase(&stream->node, &smmu->streams);
+		kfree(stream);
 	}
 }
 
@@ -4493,6 +4511,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++) {
@@ -4509,11 +4528,30 @@ 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 (stream->master->stall_enabled) {
+				dev_warn(master->dev,
+					 "Shared StreamID 0x%x rejected: existing master has stall enabled\n",
+					 sid);
+				ret = -EBUSY;
+				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);
@@ -4523,6 +4561,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);
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 90ba843989bb..444b88cc67c5 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -1021,6 +1021,7 @@ struct arm_smmu_stream {
 	u32				id;
 	struct arm_smmu_master		*master;
 	struct rb_node			node;
+	struct list_head		shared_masters;
 };
 
 struct arm_smmu_vmaster {
@@ -1069,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] 12+ messages in thread

* [PATCH RFC v8 6/9] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group
  2026-11-13 13:27 [PATCH RFC v8 0/9] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
                   ` (4 preceding siblings ...)
  2026-11-13 13:27 ` [PATCH RFC v8 5/9] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master Peng Fan (OSS)
@ 2026-11-13 13:27 ` Peng Fan (OSS)
  2026-11-13 13:27 ` [PATCH RFC v8 7/9] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 12+ messages in thread
From: Peng Fan (OSS) @ 2026-11-13 13:27 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 because the STE
can only point to one CD table at a time. With the domain-owned CD table,
non-PASID devices in that domain share that table directly.

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.

Faults on a shared SID cannot be attributed to one device, so they are
logged by the SMMU driver but not reported to a device driver. Sharing
across PCI and non-PCI masters is rejected to avoid bypassing the PCI
topology grouping.

Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 27 +++++++++++++++++++++------
 1 file changed, 21 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 f954dc7361c4..20cbad032846 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4736,13 +4736,28 @@ 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 || stream->master == master)
+			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

-- 
2.34.1


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

* [PATCH RFC v8 7/9] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating
  2026-11-13 13:27 [PATCH RFC v8 0/9] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
                   ` (5 preceding siblings ...)
  2026-11-13 13:27 ` [PATCH RFC v8 6/9] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group Peng Fan (OSS)
@ 2026-11-13 13:27 ` Peng Fan (OSS)
  2026-11-13 19:09   ` Nicolin Chen
  2026-11-13 13:27 ` [PATCH RFC v8 8/9] iommu/arm-smmu-v3: Serialize shared-SID changes with attach Peng Fan (OSS)
  2026-11-13 13:27 ` [PATCH RFC v8 9/9] iommu/arm-smmu-v3: Quiesce PASID before mm release unlinks Peng Fan (OSS)
  8 siblings, 1 reply; 12+ messages in thread
From: Peng Fan (OSS) @ 2026-11-13 13:27 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>

With the per-domain CD table, every non-PASID sharer of a SID builds the
same normal-domain STE: same S1ContextPtr, S1DSS and S1CDMAX. Disable
PASID and ATS for shared SIDs as well, so the remaining STE fields are
common too, and let any sharer program the STE. This also handles the
case where one device's driver attaches before the stream owner's driver:
the first attach now installs a valid STE instead of leaving the boot or
firmware STE in place.

A per-device release must not abort a shared STE while other sharers
remain, otherwise it would break their ongoing DMA. Use a dedicated
release domain instead of inferring release from the STE contents:
group-wide BLOCKED attaches still program the abort STE, while the
release domain skips writes until the last master no longer shares the
SID.

Disable SVA, PASID, vSMMU nesting, ATS and IOPF/stall for shared-SID
masters since all of them require an unambiguous SID-to-device mapping.
Reject sharing at insert_master() time if either master wants stalling,
if the incoming master requires ATS, if the existing master has PASID,
ATS or vSMMU state enabled, or if a PCI and a non-PCI master would share
the SID. Other ATS-capable shared devices have their ATS capability
prepared, but ATS is not enabled while the SID is shared.

Resolve stall_enabled before insert_master(), and use master->stall_enabled
directly for CD.S, STE.S1STALLD and STE.S2S. A shared master can never
have stalling enabled.

Add arm_smmu_master_shares_sid() as an inline helper in arm-smmu-v3.h so
that arm-smmu-v3-sva.c and arm-smmu-v3-iommufd.c can use it without
repeating the list_empty() pattern.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 .../iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c    |   7 +-
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c    |   3 +
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c   |   5 +
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c        | 236 ++++++++++++++++-----
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h        |   5 +
 drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c     |   2 +-
 6 files changed, 200 insertions(+), 58 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 853276dd5453..1fa512a655d2 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
@@ -168,6 +168,8 @@ static int arm_smmu_attach_dev_nested(struct iommu_domain *domain,
 
 	if (nested_domain->vsmmu->smmu != master->smmu)
 		return -EINVAL;
+	if (arm_smmu_master_shares_sid(master))
+		return -EOPNOTSUPP;
 	if (arm_smmu_ssids_in_use(&master->cd_table))
 		return -EBUSY;
 
@@ -310,7 +312,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 || arm_smmu_master_shares_sid(master))
 		return -EOPNOTSUPP;
 	return 0;
 }
@@ -545,6 +547,9 @@ size_t arm_smmu_get_viommu_size(struct device *dev,
 	struct arm_smmu_master *master = dev_iommu_priv_get(dev);
 	struct arm_smmu_device *smmu = master->smmu;
 
+	if (arm_smmu_master_shares_sid(master))
+		return 0;
+
 	if (!(smmu->features & ARM_SMMU_FEAT_NESTING))
 		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 7f89bb7a2e9c..f65c60652c0d 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 (arm_smmu_master_shares_sid(master))
+		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-test.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
index 864d9f2350bc..a3fbcd88675a 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
@@ -215,6 +215,7 @@ static void arm_smmu_test_make_cdtable_ste(struct arm_smmu_ste *ste,
 		.stall_enabled = stall_enabled,
 	};
 
+	INIT_LIST_HEAD(&master.shared_masters_elm);
 	arm_smmu_make_cdtable_ste(ste, &master, &master.cd_table, ats_enabled,
 				  s1dss);
 	if (feat & ARM_SMMU_MASTER_TEST_NESTED) {
@@ -351,6 +352,7 @@ static void arm_smmu_test_make_s2_ste(struct arm_smmu_ste *ste,
 		.pgtbl_ops = &io_pgtable.ops,
 	};
 
+	INIT_LIST_HEAD(&master.shared_masters_elm);
 	io_pgtable.cfg.arm_lpae_s2_cfg.vttbr = 0xdaedbeefdeadbeefULL;
 	io_pgtable.cfg.arm_lpae_s2_cfg.vtcr.ps = 1;
 	io_pgtable.cfg.arm_lpae_s2_cfg.vtcr.tg = 2;
@@ -506,6 +508,7 @@ static void arm_smmu_test_make_s1_cd(struct arm_smmu_cd *cd, unsigned int asid)
 		},
 	};
 
+	INIT_LIST_HEAD(&master.shared_masters_elm);
 	io_pgtable.cfg.arm_lpae_s1_cfg.ttbr = 0xdaedbeefdeadbeefULL;
 	io_pgtable.cfg.arm_lpae_s1_cfg.tcr.ips = 1;
 	io_pgtable.cfg.arm_lpae_s1_cfg.tcr.tg = 2;
@@ -549,6 +552,7 @@ static void arm_smmu_test_make_sva_cd(struct arm_smmu_cd *cd, unsigned int asid)
 		.smmu = &smmu,
 	};
 
+	INIT_LIST_HEAD(&master.shared_masters_elm);
 	arm_smmu_make_sva_cd(cd, &master, &sva_mm, asid);
 }
 
@@ -559,6 +563,7 @@ static void arm_smmu_test_make_sva_release_cd(struct arm_smmu_cd *cd,
 		.smmu = &smmu,
 	};
 
+	INIT_LIST_HEAD(&master.shared_masters_elm);
 	arm_smmu_make_sva_cd(cd, &master, NULL, asid);
 }
 
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 20cbad032846..790a6e54c24a 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -3209,28 +3209,18 @@ arm_smmu_get_step_for_sid(struct arm_smmu_device *smmu, u32 sid)
 	}
 }
 
-void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master,
-				  const struct arm_smmu_ste *target)
+static void
+arm_smmu_install_ste_for_dev_flags(struct arm_smmu_master *master,
+				   const struct arm_smmu_ste *target,
+				   bool skip_shared)
 {
 	int i, j;
+	bool written = false;
 	struct arm_smmu_device *smmu = master->smmu;
-	u64 ste = le64_to_cpu(target->data[0]);
-
-	master->cd_table.in_ste =
-		FIELD_GET(STRTAB_STE_0_CFG, ste) ==
-			STRTAB_STE_0_CFG_S1_TRANS &&
-		(ste & STRTAB_STE_0_S1CTXPTR_MASK) ==
-			(master->cd_table.cdtab_dma &
-			 STRTAB_STE_0_S1CTXPTR_MASK);
-	master->ste_ats_enabled =
-		FIELD_GET(STRTAB_STE_1_EATS,
-			  le64_to_cpu(target->data[1])) ==
-		STRTAB_STE_1_EATS_TRANS;
 
 	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++)
@@ -3239,11 +3229,43 @@ void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master,
 		if (j < i)
 			continue;
 
+		/*
+		 * A shared STE represents the group's domain, not a single
+		 * master. The release domain is attached to one device at a
+		 * time while the other sharers must keep translating; leave
+		 * the STE untouched until the last master is released.
+		 */
+		if (skip_shared && arm_smmu_master_shares_sid(master))
+			continue;
+
+		step = arm_smmu_get_step_for_sid(smmu, sid);
 		arm_smmu_write_ste(master, sid, step, target);
+		written = true;
+	}
+
+	if (written) {
+		u64 ste = le64_to_cpu(target->data[0]);
+
+		master->cd_table.in_ste =
+			FIELD_GET(STRTAB_STE_0_CFG, ste) ==
+				STRTAB_STE_0_CFG_S1_TRANS &&
+			(ste & STRTAB_STE_0_S1CTXPTR_MASK) ==
+				(master->cd_table.cdtab_dma &
+				 STRTAB_STE_0_S1CTXPTR_MASK);
+		master->ste_ats_enabled =
+			FIELD_GET(STRTAB_STE_1_EATS,
+				  le64_to_cpu(target->data[1])) ==
+			STRTAB_STE_1_EATS_TRANS;
 	}
 }
 
-static bool arm_smmu_ats_supported(struct arm_smmu_master *master)
+void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master,
+				  const struct arm_smmu_ste *target)
+{
+	arm_smmu_install_ste_for_dev_flags(master, target, false);
+}
+
+static bool arm_smmu_ats_capable(struct arm_smmu_master *master)
 {
 	struct device *dev = master->dev;
 	struct arm_smmu_device *smmu = master->smmu;
@@ -3258,6 +3280,18 @@ static bool arm_smmu_ats_supported(struct arm_smmu_master *master)
 	return dev_is_pci(dev) && pci_ats_supported(to_pci_dev(dev));
 }
 
+static bool arm_smmu_ats_supported(struct arm_smmu_master *master)
+{
+	return !arm_smmu_master_shares_sid(master) &&
+	       arm_smmu_ats_capable(master);
+}
+
+static bool arm_smmu_master_pasid_enabled(struct arm_smmu_master *master)
+{
+	return dev_is_pci(master->dev) &&
+	       to_pci_dev(master->dev)->pasid_enabled;
+}
+
 static void arm_smmu_enable_ats(struct arm_smmu_master *master)
 {
 	size_t stu;
@@ -3386,8 +3420,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_master_shares_sid(master))
 		return -EOPNOTSUPP;
 
 	if (master->iopf_refcount) {
@@ -3994,6 +4027,9 @@ static int arm_smmu_s1_set_dev_pasid(struct iommu_domain *domain,
 	if (smmu_domain->stage != ARM_SMMU_DOMAIN_S1)
 		return -EINVAL;
 
+	if (arm_smmu_master_shares_sid(master))
+		return -EOPNOTSUPP;
+
 	/*
 	 * We can read cd.asid outside the lock because arm_smmu_set_pasid()
 	 * will fix it
@@ -4192,11 +4228,11 @@ static int arm_smmu_blocking_set_dev_pasid(struct iommu_domain *new_domain,
 	return ret;
 }
 
-static void arm_smmu_attach_dev_ste(struct iommu_domain *domain,
-				    struct iommu_domain *old_domain,
-				    struct device *dev,
-				    struct arm_smmu_ste *ste,
-				    unsigned int s1dss)
+static int arm_smmu_attach_dev_ste(struct iommu_domain *domain,
+				   struct iommu_domain *old_domain,
+				   struct device *dev,
+				   struct arm_smmu_ste *ste,
+				   unsigned int s1dss, bool release)
 {
 	struct arm_smmu_master *master = dev_iommu_priv_get(dev);
 	struct arm_smmu_attach_state state = {
@@ -4206,6 +4242,7 @@ static void arm_smmu_attach_dev_ste(struct iommu_domain *domain,
 	};
 	bool ats_always_on = master->ats_always_on &&
 			     s1dss != STRTAB_STE_1_S1DSS_TERMINATE;
+	int ret;
 
 	/*
 	 * Do not allow any ASID to be changed while are working on the STE,
@@ -4226,13 +4263,17 @@ static void arm_smmu_attach_dev_ste(struct iommu_domain *domain,
 		 * and allows for ATS on the RID to work.
 		 */
 		state.cd_needs_ats = true;
-		arm_smmu_attach_prepare(&state, domain);
+		ret = arm_smmu_attach_prepare(&state, domain);
+		if (ret)
+			goto out_unlock;
 		arm_smmu_make_cdtable_ste(ste, master, &master->cd_table,
 					  state.ats_enabled, s1dss);
 	} else {
-		arm_smmu_attach_prepare(&state, domain);
+		ret = arm_smmu_attach_prepare(&state, domain);
+		if (ret)
+			goto out_unlock;
 	}
-	arm_smmu_install_ste_for_dev(master, ste);
+	arm_smmu_install_ste_for_dev_flags(master, ste, release);
 	arm_smmu_attach_commit(&state);
 	mutex_unlock(&arm_smmu_asid_lock);
 	arm_smmu_attach_release(&state);
@@ -4243,6 +4284,11 @@ static void arm_smmu_attach_dev_ste(struct iommu_domain *domain,
 	 * descriptor from arm_smmu_share_asid().
 	 */
 	arm_smmu_clear_cd(master, IOMMU_NO_PASID);
+	return 0;
+
+out_unlock:
+	mutex_unlock(&arm_smmu_asid_lock);
+	return ret;
 }
 
 static int arm_smmu_attach_dev_identity(struct iommu_domain *domain,
@@ -4254,9 +4300,8 @@ static int arm_smmu_attach_dev_identity(struct iommu_domain *domain,
 
 	arm_smmu_master_clear_vmaster(master);
 	arm_smmu_make_bypass_ste(master->smmu, &ste);
-	arm_smmu_attach_dev_ste(domain, old_domain, dev, &ste,
-				STRTAB_STE_1_S1DSS_BYPASS);
-	return 0;
+	return arm_smmu_attach_dev_ste(domain, old_domain, dev, &ste,
+				       STRTAB_STE_1_S1DSS_BYPASS, false);
 }
 
 static const struct iommu_domain_ops arm_smmu_identity_ops = {
@@ -4277,9 +4322,8 @@ static int arm_smmu_attach_dev_blocked(struct iommu_domain *domain,
 
 	arm_smmu_master_clear_vmaster(master);
 	arm_smmu_make_abort_ste(&ste);
-	arm_smmu_attach_dev_ste(domain, old_domain, dev, &ste,
-				STRTAB_STE_1_S1DSS_TERMINATE);
-	return 0;
+	return arm_smmu_attach_dev_ste(domain, old_domain, dev, &ste,
+				       STRTAB_STE_1_S1DSS_TERMINATE, false);
 }
 
 static const struct iommu_domain_ops arm_smmu_blocked_ops = {
@@ -4292,6 +4336,37 @@ static struct iommu_domain arm_smmu_blocked_domain = {
 	.ops = &arm_smmu_blocked_ops,
 };
 
+static int arm_smmu_attach_dev_release(struct iommu_domain *domain,
+				       struct device *dev,
+				       struct iommu_domain *old_domain)
+{
+	struct arm_smmu_ste ste;
+	struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+	bool require_direct = dev->iommu && dev->iommu->require_direct;
+	unsigned int s1dss;
+
+	arm_smmu_master_clear_vmaster(master);
+	if (require_direct) {
+		arm_smmu_make_bypass_ste(master->smmu, &ste);
+		s1dss = STRTAB_STE_1_S1DSS_BYPASS;
+	} else {
+		arm_smmu_make_abort_ste(&ste);
+		s1dss = STRTAB_STE_1_S1DSS_TERMINATE;
+	}
+
+	return arm_smmu_attach_dev_ste(domain, old_domain, dev, &ste, s1dss,
+				       true);
+}
+
+static const struct iommu_domain_ops arm_smmu_release_ops = {
+	.attach_dev = arm_smmu_attach_dev_release,
+};
+
+static struct iommu_domain arm_smmu_release_domain = {
+	.type = IOMMU_DOMAIN_BLOCKED,
+	.ops = &arm_smmu_release_ops,
+};
+
 static struct iommu_domain *
 arm_smmu_domain_alloc_paging_flags(struct device *dev, u32 flags,
 				   const struct iommu_user_data *user_data)
@@ -4537,9 +4612,40 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
 				break;
 			}
 
-			if (stream->master->stall_enabled) {
+			if (dev_is_pci(master->dev) !=
+			    dev_is_pci(stream->master->dev)) {
+				dev_warn(master->dev,
+					 "Shared StreamID 0x%x across PCI and non-PCI masters unsupported\n",
+					 sid);
+				ret = -ENODEV;
+				break;
+			}
+
+			if (dev_is_pci(master->dev) &&
+			    pci_ats_required(to_pci_dev(master->dev))) {
+				dev_warn(master->dev,
+					 "Shared StreamID 0x%x rejected: incoming master requires ATS\n",
+					 sid);
+				ret = -EBUSY;
+				break;
+			}
+
+			if (stream->master->stall_enabled ||
+			    master->stall_enabled) {
+				dev_warn(master->dev,
+					 "Shared StreamID 0x%x rejected: stall enabled\n",
+					 sid);
+				ret = -EBUSY;
+				break;
+			}
+
+			if (stream->master->ats_always_on ||
+			    stream->master->ats_enabled ||
+			    arm_smmu_master_pasid_enabled(stream->master) ||
+			    arm_smmu_ssids_in_use(&stream->master->cd_table) ||
+			    stream->master->vmaster) {
 				dev_warn(master->dev,
-					 "Shared StreamID 0x%x rejected: existing master has stall enabled\n",
+					 "Shared StreamID 0x%x rejected: existing master has PASID/ATS/vSMMU enabled\n",
 					 sid);
 				ret = -EBUSY;
 				break;
@@ -4612,7 +4718,7 @@ static int arm_smmu_master_prepare_ats(struct arm_smmu_master *master)
 		return 0;
 	pdev = to_pci_dev(master->dev);
 
-	if (!arm_smmu_ats_supported(master)) {
+	if (!arm_smmu_ats_capable(master)) {
 		if (pci_ats_required(pdev)) {
 			dev_err_once(master->dev, "SMMU doesn't support ATS\n");
 			return -EOPNOTSUPP;
@@ -4624,6 +4730,9 @@ static int arm_smmu_master_prepare_ats(struct arm_smmu_master *master)
 	if (ret || !pci_ats_required(pdev))
 		return ret;
 
+	if (arm_smmu_master_shares_sid(master))
+		return -EOPNOTSUPP;
+
 	/*
 	 * S1DSS is required for ATS to be always on for identity domain cases.
 	 * However, the S1DSS field is ignored if !IDR0_S1P or !IDR1_SSIDSIZE.
@@ -4661,32 +4770,47 @@ static struct iommu_device *arm_smmu_probe_device(struct device *dev)
 	master->smmu = smmu;
 	dev_iommu_priv_set(dev, master);
 
-	ret = arm_smmu_insert_master(smmu, master);
-	if (ret)
-		goto err_free_master;
-
-	device_property_read_u32(dev, "pasid-num-bits", &master->ssid_bits);
-	master->ssid_bits = min(smmu->ssid_bits, master->ssid_bits);
-
 	/*
-	 * Note that PASID must be enabled before, and disabled after ATS:
-	 * PCI Express Base 4.0r1.0 - 10.5.1.3 ATS Control Register
-	 *
-	 *   Behavior is undefined if this bit is Set and the value of the PASID
-	 *   Enable, Execute Requested Enable, or Privileged Mode Requested bits
-	 *   are changed.
+	 * Resolve the stall policy before inserting the master. StreamID
+	 * sharing is rejected when either device wants stalling, and the
+	 * insert path needs to know the incoming master's policy.
 	 */
-	arm_smmu_enable_pasid(master);
-
-	if (!(smmu->features & ARM_SMMU_FEAT_2_LVL_CDTAB))
-		master->ssid_bits = min_t(u8, master->ssid_bits,
-					  CTXDESC_LINEAR_CDMAX);
-
 	if ((smmu->features & ARM_SMMU_FEAT_STALLS &&
 	     device_property_read_bool(dev, "dma-can-stall")) ||
 	    smmu->features & ARM_SMMU_FEAT_STALL_FORCE)
 		master->stall_enabled = true;
 
+	ret = arm_smmu_insert_master(smmu, master);
+	if (ret)
+		goto err_free_master;
+
+	if (arm_smmu_master_shares_sid(master)) {
+		/*
+		 * A shared StreamID cannot use PASID because all sharers must
+		 * point at the same, domain-owned CD table.
+		 */
+		master->ssid_bits = 0;
+	} else {
+		device_property_read_u32(dev, "pasid-num-bits",
+					 &master->ssid_bits);
+		master->ssid_bits = min(smmu->ssid_bits, master->ssid_bits);
+
+		/*
+		 * Note that PASID must be enabled before, and disabled after
+		 * ATS:
+		 * PCI Express Base 4.0r1.0 - 10.5.1.3 ATS Control Register
+		 *
+		 *   Behavior is undefined if this bit is Set and the value of
+		 *   the PASID Enable, Execute Requested Enable, or Privileged
+		 *   Mode Requested bits are changed.
+		 */
+		arm_smmu_enable_pasid(master);
+
+		if (!(smmu->features & ARM_SMMU_FEAT_2_LVL_CDTAB))
+			master->ssid_bits = min_t(u8, master->ssid_bits,
+						  CTXDESC_LINEAR_CDMAX);
+	}
+
 	ret = arm_smmu_master_prepare_ats(master);
 	if (ret)
 		goto err_disable_pasid;
@@ -4811,7 +4935,7 @@ static int arm_smmu_def_domain_type(struct device *dev)
 static const struct iommu_ops arm_smmu_ops = {
 	.identity_domain	= &arm_smmu_identity_domain,
 	.blocked_domain		= &arm_smmu_blocked_domain,
-	.release_domain		= &arm_smmu_blocked_domain,
+	.release_domain		= &arm_smmu_release_domain,
 	.capable		= arm_smmu_capable,
 	.hw_info		= arm_smmu_hw_info,
 	.domain_alloc_sva       = arm_smmu_sva_domain_alloc,
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 444b88cc67c5..cd659590dfe5 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -1242,6 +1242,11 @@ int arm_smmu_init_one_queue(struct arm_smmu_device *smmu,
 int arm_smmu_cmdq_init(struct arm_smmu_device *smmu,
 		       struct arm_smmu_cmdq *cmdq);
 
+static inline bool arm_smmu_master_shares_sid(struct arm_smmu_master *master)
+{
+	return !list_empty(&master->shared_masters_elm);
+}
+
 static inline bool arm_smmu_master_canwbs(struct arm_smmu_master *master)
 {
 	return dev_iommu_fwspec_get(master->dev)->flags &
diff --git a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
index 8166cece943c..ffb0ac93a4f4 100644
--- a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
+++ b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
@@ -1264,7 +1264,7 @@ static int tegra241_vintf_init_vsid(struct iommufd_vdevice *vdev)
 	if (virt_sid > FIELD_MAX(VINTF_SID_MATCH_VIRT_SID))
 		return -EINVAL;
 
-	if (master->num_streams != 1)
+	if (master->num_streams != 1 || arm_smmu_master_shares_sid(master))
 		return -EOPNOTSUPP;
 
 	/* Find an empty pair of SID_REPLACE and SID_MATCH */

-- 
2.34.1


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

* [PATCH RFC v8 8/9] iommu/arm-smmu-v3: Serialize shared-SID changes with attach
  2026-11-13 13:27 [PATCH RFC v8 0/9] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
                   ` (6 preceding siblings ...)
  2026-11-13 13:27 ` [PATCH RFC v8 7/9] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
@ 2026-11-13 13:27 ` Peng Fan (OSS)
  2026-11-13 13:27 ` [PATCH RFC v8 9/9] iommu/arm-smmu-v3: Quiesce PASID before mm release unlinks Peng Fan (OSS)
  8 siblings, 0 replies; 12+ messages in thread
From: Peng Fan (OSS) @ 2026-11-13 13:27 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>

Shared SID membership is modified under smmu->streams_mutex, but the
attach, PASID, ATS and vSMMU paths read it while holding the group mutex
or arm_smmu_asid_lock. A second device can start sharing a SID while the
first master is attaching, so the first master may select a private CD
table or enable a feature that is incompatible with sharing.

Take arm_smmu_asid_lock around insert_master() and remove_master() so
sharing changes serialize with the attach/PASID/ATS paths, which already
hold it. Keep the lock order asid_lock -> streams_mutex.

Recheck shared membership under asid_lock in the PASID attach path and
move the vSMMU membership checks under asid_lock. The incoming master's
own probe setup is still serialized by iommu_probe_device_lock.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 .../iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c    | 27 +++++++++++++++++-----
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c        | 14 +++++++++++
 drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c     |  9 +++++++-
 3 files changed, 43 insertions(+), 7 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 1fa512a655d2..10c5f9605152 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
@@ -168,12 +168,16 @@ static int arm_smmu_attach_dev_nested(struct iommu_domain *domain,
 
 	if (nested_domain->vsmmu->smmu != master->smmu)
 		return -EINVAL;
-	if (arm_smmu_master_shares_sid(master))
-		return -EOPNOTSUPP;
-	if (arm_smmu_ssids_in_use(&master->cd_table))
-		return -EBUSY;
 
 	mutex_lock(&arm_smmu_asid_lock);
+	if (arm_smmu_master_shares_sid(master)) {
+		mutex_unlock(&arm_smmu_asid_lock);
+		return -EOPNOTSUPP;
+	}
+	if (arm_smmu_ssids_in_use(&master->cd_table)) {
+		mutex_unlock(&arm_smmu_asid_lock);
+		return -EBUSY;
+	}
 	/*
 	 * The VM has to control the actual ATS state at the PCI device because
 	 * we forward the invalidations directly from the VM. If the VM doesn't
@@ -312,8 +316,15 @@ 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 || arm_smmu_master_shares_sid(master))
+	if (master->num_streams != 1)
+		return -EOPNOTSUPP;
+
+	mutex_lock(&arm_smmu_asid_lock);
+	if (arm_smmu_master_shares_sid(master)) {
+		mutex_unlock(&arm_smmu_asid_lock);
 		return -EOPNOTSUPP;
+	}
+	mutex_unlock(&arm_smmu_asid_lock);
 	return 0;
 }
 
@@ -547,8 +558,12 @@ size_t arm_smmu_get_viommu_size(struct device *dev,
 	struct arm_smmu_master *master = dev_iommu_priv_get(dev);
 	struct arm_smmu_device *smmu = master->smmu;
 
-	if (arm_smmu_master_shares_sid(master))
+	mutex_lock(&arm_smmu_asid_lock);
+	if (arm_smmu_master_shares_sid(master)) {
+		mutex_unlock(&arm_smmu_asid_lock);
 		return 0;
+	}
+	mutex_unlock(&arm_smmu_asid_lock);
 
 	if (!(smmu->features & ARM_SMMU_FEAT_NESTING))
 		return 0;
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 790a6e54c24a..5a6bed0ca7f1 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4114,6 +4114,11 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
 	}
 
 	mutex_lock(&arm_smmu_asid_lock);
+	if (arm_smmu_master_shares_sid(master)) {
+		mutex_unlock(&arm_smmu_asid_lock);
+		return -EOPNOTSUPP;
+	}
+
 	ret = arm_smmu_attach_prepare(&state, &smmu_domain->domain);
 	if (ret) {
 		mutex_unlock(&arm_smmu_asid_lock);
@@ -4588,6 +4593,11 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
 	}
 	INIT_LIST_HEAD(&master->shared_masters_elm);
 
+	/*
+	 * Serialize sharing changes with attach/PASID/ATS updates, which
+	 * hold arm_smmu_asid_lock. Lock order is asid_lock -> streams_mutex.
+	 */
+	mutex_lock(&arm_smmu_asid_lock);
 	mutex_lock(&smmu->streams_mutex);
 	for (i = 0, n = 0; i < fwspec->num_ids; i++) {
 		struct arm_smmu_stream *stream;
@@ -4678,11 +4688,13 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
 	if (ret) {
 		arm_smmu_free_master_streams(master);
 		mutex_unlock(&smmu->streams_mutex);
+		mutex_unlock(&arm_smmu_asid_lock);
 		kfree(master->streams);
 		kfree(master->build_invs);
 		return ret;
 	}
 	mutex_unlock(&smmu->streams_mutex);
+	mutex_unlock(&arm_smmu_asid_lock);
 
 	/* Put the ids into order for sorted to_merge/to_unref arrays */
 	sort(master->streams, master->num_streams,
@@ -4699,9 +4711,11 @@ static void arm_smmu_remove_master(struct arm_smmu_master *master)
 	if (!smmu || !master->streams)
 		return;
 
+	mutex_lock(&arm_smmu_asid_lock);
 	mutex_lock(&smmu->streams_mutex);
 	arm_smmu_free_master_streams(master);
 	mutex_unlock(&smmu->streams_mutex);
+	mutex_unlock(&arm_smmu_asid_lock);
 
 	kfree(master->streams);
 	kfree(master->build_invs);
diff --git a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
index ffb0ac93a4f4..3bcecdc00a86 100644
--- a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
+++ b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c
@@ -1264,9 +1264,16 @@ static int tegra241_vintf_init_vsid(struct iommufd_vdevice *vdev)
 	if (virt_sid > FIELD_MAX(VINTF_SID_MATCH_VIRT_SID))
 		return -EINVAL;
 
-	if (master->num_streams != 1 || arm_smmu_master_shares_sid(master))
+	if (master->num_streams != 1)
 		return -EOPNOTSUPP;
 
+	mutex_lock(&arm_smmu_asid_lock);
+	if (arm_smmu_master_shares_sid(master)) {
+		mutex_unlock(&arm_smmu_asid_lock);
+		return -EOPNOTSUPP;
+	}
+	mutex_unlock(&arm_smmu_asid_lock);
+
 	/* Find an empty pair of SID_REPLACE and SID_MATCH */
 	sidx = ida_alloc_max(&vintf->sids, vintf->cmdqv->num_sids_per_vintf - 1,
 			     GFP_KERNEL);

-- 
2.34.1


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

* [PATCH RFC v8 9/9] iommu/arm-smmu-v3: Quiesce PASID before mm release unlinks
  2026-11-13 13:27 [PATCH RFC v8 0/9] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
                   ` (7 preceding siblings ...)
  2026-11-13 13:27 ` [PATCH RFC v8 8/9] iommu/arm-smmu-v3: Serialize shared-SID changes with attach Peng Fan (OSS)
@ 2026-11-13 13:27 ` Peng Fan (OSS)
  8 siblings, 0 replies; 12+ messages in thread
From: Peng Fan (OSS) @ 2026-11-13 13:27 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>

arm_smmu_blocking_set_dev_pasid() unlinked the PASID master_domain
before clearing the CD and invalidating the ATC. arm_smmu_mm_release()
only takes devices_lock, so it could miss the removed entry, return after
arm_smmu_domain_inv(), and let exit_mmap() free page tables while the
PASID CD was still valid and could be re-fetched.

Clear the CD and invalidate ATS before unlinking the master_domain, so
mm_release either sees the entry and writes a faulting CD itself, or
sees it removed only after the CD was invalidated.

mm_release must not change used_ssids while quiescing, otherwise a write
racing with clear_cd() can resurrect the PASID count. Add a no-account
CD write for mm_release, and re-clear the CD without accounting after
unlinking to drop a faulting CD written in the race window.

For the last-PASID downgrade, do not propagate attach_dev() failures:
the IOMMU core requires set_dev_pasid() not to fail in this path. The
private CD table is kept allocated for reuse.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c |  4 +-
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c     | 95 ++++++++++++++++---------
 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h     |  7 +-
 3 files changed, 67 insertions(+), 39 deletions(-)

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 f65c60652c0d..23dbe6ff479d 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
@@ -178,8 +178,8 @@ static void arm_smmu_mm_release(struct mmu_notifier *mn, struct mm_struct *mm)
 			continue;
 		arm_smmu_make_sva_cd(&target, master, NULL,
 				     smmu_domain->cd.asid);
-		arm_smmu_write_cd_entry(master, master_domain->ssid, cdptr,
-					&target);
+		arm_smmu_write_cd_entry_no_account(master, master_domain->ssid,
+						   cdptr, &target);
 	}
 	spin_unlock_irqrestore(&smmu_domain->devices_lock, flags);
 
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 5a6bed0ca7f1..f22a3b6455a5 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -1675,6 +1675,26 @@ void arm_smmu_write_cd_entry(struct arm_smmu_master *master, int ssid,
 	arm_smmu_write_entry(&cd_writer.writer, cdptr->data, target->data);
 }
 
+/*
+ * Quiesce the CD without changing the PASID accounting. mmu_notifier release
+ * writes a valid, faulting CD while the detach path owns the used_ssids
+ * transition, so it must not resurrect the PASID count.
+ */
+void arm_smmu_write_cd_entry_no_account(struct arm_smmu_master *master, int ssid,
+					struct arm_smmu_cd *cdptr,
+					const struct arm_smmu_cd *target)
+{
+	struct arm_smmu_cd_writer cd_writer = {
+		.writer = {
+			.ops = &arm_smmu_cd_writer_ops,
+			.master = master,
+		},
+		.ssid = ssid,
+	};
+
+	arm_smmu_write_entry(&cd_writer.writer, cdptr->data, target->data);
+}
+
 void arm_smmu_make_s1_cd(struct arm_smmu_cd *target,
 			 struct arm_smmu_master *master,
 			 struct arm_smmu_domain *smmu_domain)
@@ -1734,6 +1754,23 @@ void arm_smmu_clear_cd(struct arm_smmu_master *master, ioasid_t ssid)
 	arm_smmu_write_cd_entry(master, ssid, cdptr, &target);
 }
 
+static void arm_smmu_clear_cd_no_account(struct arm_smmu_master *master,
+					 ioasid_t ssid)
+{
+	struct arm_smmu_cd target = {};
+	struct arm_smmu_cd *cdptr;
+
+	if (!arm_smmu_cdtab_allocated(&master->cd_table))
+		return;
+	cdptr = arm_smmu_master_get_cd_ptr(master, ssid);
+	if (!cdptr) {
+		/* Only ats_always_on allows a NULL CD on default substream */
+		WARN_ON(!master->ats_always_on || ssid);
+		return;
+	}
+	arm_smmu_write_cd_entry_no_account(master, ssid, cdptr, &target);
+}
+
 static int arm_smmu_alloc_cd_tables(struct arm_smmu_device *smmu,
 				    struct arm_smmu_ctx_desc_cfg *cd_table,
 				    u8 ssid_bits, bool ats_always_on)
@@ -4176,61 +4213,49 @@ static int arm_smmu_blocking_set_dev_pasid(struct iommu_domain *new_domain,
 		.old_domain = old_domain,
 		.ssid = pasid,
 	};
-	int ret = 0;
 
 	mutex_lock(&arm_smmu_asid_lock);
 	arm_smmu_attach_prepare_invs(&state, NULL);
 	/*
-	 * Unlink the PASID from the domain before invalidating its CD.
-	 * arm_smmu_mm_release() walks this list with only devices_lock held
-	 * and writes a valid, faulting CD; unlinking first prevents it from
-	 * resurrecting used_ssids after the last PASID is detached.
+	 * Invalidate the CD before unlinking the PASID from the domain.
+	 * arm_smmu_mm_release() walks this list with only devices_lock held:
+	 * if it sees the master_domain it writes a valid, faulting CD; if it
+	 * does not, the CD has already been invalidated here. Either way the
+	 * page tables can be freed after mm_release() returns.
 	 */
-	state.old_master_domain = arm_smmu_remove_master_domain(
-		master, &smmu_domain->domain, pasid);
 	arm_smmu_clear_cd(master, pasid);
 	if (master->ats_enabled)
 		arm_smmu_atc_inv_master(master, pasid);
+	state.old_master_domain = arm_smmu_remove_master_domain(
+		master, &smmu_domain->domain, pasid);
+	/*
+	 * mm_release() may have written a faulting CD in the window above.
+	 * Drop it without touching used_ssids so the CD is invalid again and
+	 * a later PASID attach accounts for the install correctly.
+	 */
+	arm_smmu_clear_cd_no_account(master, pasid);
 	arm_smmu_install_old_domain_invs(&state);
 	mutex_unlock(&arm_smmu_asid_lock);
 	arm_smmu_attach_release(&state);
 
 	/*
 	 * When the last user of the CD table goes away, switch the STE back to
-	 * the RID domain's CD table (S1) or to the native bypass/abort STE
-	 * (identity/blocked). Do not call attach_dev() for an S1 paging domain:
-	 * this is a teardown path where allocation failures cannot be
-	 * propagated. The private CD table is kept allocated for reuse and
-	 * freed when the device goes away.
+	 * the RID domain's shared CD table (S1) or to the native bypass/abort
+	 * STE (identity/blocked), by re-attaching its sid_domain. Do not
+	 * propagate attach_dev() failures from this teardown path. The private
+	 * CD table is kept allocated for reuse and freed when the device goes
+	 * away.
 	 */
 	if (!arm_smmu_ssids_in_use(&master->cd_table)) {
 		struct iommu_domain *sid_domain =
 			iommu_driver_get_domain_for_dev(master->dev);
+		bool ats_always_on = master->ats_always_on &&
+				     sid_domain->type != IOMMU_DOMAIN_BLOCKED;
 
-		if (!master->ats_always_on ||
-		    sid_domain->type == IOMMU_DOMAIN_BLOCKED) {
-			if ((sid_domain->type & __IOMMU_DOMAIN_PAGING) &&
-			    to_smmu_domain(sid_domain)->stage ==
-				    ARM_SMMU_DOMAIN_S1) {
-				struct arm_smmu_ste ste;
-				struct arm_smmu_domain *sid_smmu_domain =
-					to_smmu_domain(sid_domain);
-
-				mutex_lock(&arm_smmu_asid_lock);
-				arm_smmu_make_cdtable_ste(
-					&ste, master, &sid_smmu_domain->cd_table,
-					master->ats_enabled,
-					STRTAB_STE_1_S1DSS_SSID0);
-				arm_smmu_install_ste_for_dev(master, &ste);
-				mutex_unlock(&arm_smmu_asid_lock);
-			} else if (sid_domain->type == IOMMU_DOMAIN_IDENTITY ||
-				   sid_domain->type == IOMMU_DOMAIN_BLOCKED) {
-				ret = sid_domain->ops->attach_dev(sid_domain, dev,
-								  sid_domain);
-			}
-		}
+		if (!ats_always_on)
+			sid_domain->ops->attach_dev(sid_domain, dev, sid_domain);
 	}
-	return ret;
+	return 0;
 }
 
 static int arm_smmu_attach_dev_ste(struct iommu_domain *domain,
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 cd659590dfe5..79623d8283f5 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -1096,8 +1096,8 @@ struct arm_smmu_domain {
 
 	/*
 	 * CD table shared by all non-PASID devices attached to this S1
-	 * domain. A master uses a private table only when it needs
-	 * substreams (PASID) or ATS-always-on/S1DSS.
+	 * domain. A master uses a private table when it needs substreams
+	 * (PASID), ATS-always-on/S1DSS, or the stall model.
 	 */
 	struct arm_smmu_ctx_desc_cfg	cd_table;
 
@@ -1212,6 +1212,9 @@ void arm_smmu_make_s1_cd(struct arm_smmu_cd *target,
 void arm_smmu_write_cd_entry(struct arm_smmu_master *master, int ssid,
 			     struct arm_smmu_cd *cdptr,
 			     const struct arm_smmu_cd *target);
+void arm_smmu_write_cd_entry_no_account(struct arm_smmu_master *master, int ssid,
+					struct arm_smmu_cd *cdptr,
+					const struct arm_smmu_cd *target);
 
 int arm_smmu_set_pasid(struct arm_smmu_master *master,
 		       struct arm_smmu_domain *smmu_domain, ioasid_t pasid,

-- 
2.34.1


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

* Re: [PATCH RFC v8 4/9] iommu/arm-smmu-v3: Use a per-domain CD table for non-PASID streams
  2026-11-13 13:27 ` [PATCH RFC v8 4/9] iommu/arm-smmu-v3: Use a per-domain CD table for non-PASID streams Peng Fan (OSS)
@ 2026-11-13 19:01   ` Nicolin Chen
  0 siblings, 0 replies; 12+ messages in thread
From: Nicolin Chen @ 2026-11-13 19:01 UTC (permalink / raw)
  To: Peng Fan (OSS), Robin Murphy
  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 Fri, Nov 13, 2026 at 09:27:16PM +0800, Peng Fan (OSS) wrote:
> +static int arm_smmu_alloc_cd_ptr_l2(struct arm_smmu_master *master, u32 ssid)
> +{
> +	struct arm_smmu_ctx_desc_cfg *cd_table = &master->cd_table;
> +	unsigned int idx = arm_smmu_cdtab_l1_idx(ssid);
> +	struct arm_smmu_cdtab_l2 **l2ptr = &cd_table->l2.l2ptrs[idx];
> +	dma_addr_t l2ptr_dma;
> +
> +	if (*l2ptr)
> +		return 0;
> +
> +	*l2ptr = dma_alloc_coherent(master->smmu->dev, sizeof(**l2ptr),
> +				    &l2ptr_dma, GFP_KERNEL);
> +	if (!*l2ptr)
> +		return -ENOMEM;
> +
> +	arm_smmu_write_cd_l1_desc(&cd_table->l2.l1tab[idx], l2ptr_dma);
> +	/* An invalid L1CD can be cached */
> +	arm_smmu_sync_cd(master, ssid, false);
> +	return 0;
> +}

This patch is a bit too big. Move this into a prep patch please.

> @@ -1793,9 +1801,22 @@ static int arm_smmu_alloc_cd_tables(struct arm_smmu_device *smmu,
>  
>  static int arm_smmu_master_alloc_cd_tables(struct arm_smmu_master *master)
>  {
> -	return arm_smmu_alloc_cd_tables(master->smmu, &master->cd_table,
> -					master->ssid_bits,
> -					master->ats_always_on);
> +	int ret;
> +
> +	ret = arm_smmu_alloc_cd_tables(master->smmu, &master->cd_table,
> +				       master->ssid_bits,
> +				       master->ats_always_on);
> +	if (ret)
> +		return ret;
> +
> +	if (master->cd_table.s1fmt == STRTAB_STE_0_S1FMT_64K_L2) {
> +		ret = arm_smmu_alloc_cd_ptr_l2(master, IOMMU_NO_PASID);
> +		if (ret) {
> +			arm_smmu_free_cd_tables(master->smmu, &master->cd_table);
> +			memset(&master->cd_table, 0, sizeof(master->cd_table));
> +		}

It'd be helpful to have a line of inline comments stating why this
is needed here.

> @@ -1830,6 +1851,12 @@ static void arm_smmu_free_cd_tables(struct arm_smmu_device *smmu,
>  	}
>  }
>  
> +static int arm_smmu_domain_alloc_cd_tables(struct arm_smmu_domain *smmu_domain)
> +{
> +	return arm_smmu_alloc_cd_tables(smmu_domain->smmu,

Hmm, that's not good. We are trying to decouple smmu_domain from an
SMMU instance so that one smmu_domain can be shared across multiple
SMMU instances.

Tying one instance to a domain is apparently going against it..

Instead, we would need an {smmu, cd_table, refcount} list. And it'd
make thing more complicated than letting master own CD table as-is.

Not sure if it's still worth doing so. Robin?

> @@ -3971,15 +4058,23 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
>  	if (smmu_domain->smmu != master->smmu)
>  		return -EINVAL;
>  
> -	if (!master->cd_table.in_ste &&
> +	if (!((sid_domain->type & __IOMMU_DOMAIN_PAGING) &&
> +	      to_smmu_domain(sid_domain)->stage == ARM_SMMU_DOMAIN_S1) &&
>  	    sid_domain->type != IOMMU_DOMAIN_IDENTITY &&
>  	    sid_domain->type != IOMMU_DOMAIN_BLOCKED)
>  		return -EINVAL;
>  
> +	first = !arm_smmu_ssids_in_use(&master->cd_table);
>  	cdptr = arm_smmu_alloc_cd_ptr(master, pasid);
>  	if (!cdptr)
>  		return -ENOMEM;
>  
> +	if (first) {
> +		cd0ptr = arm_smmu_master_get_cd_ptr(master, IOMMU_NO_PASID);
> +		if (WARN_ON(!cd0ptr))
> +			return -ENXIO;

You don't need "first" at all, given ...

>  	mutex_lock(&arm_smmu_asid_lock);
>  	ret = arm_smmu_attach_prepare(&state, &smmu_domain->domain);
>  	if (ret) {
> @@ -3995,7 +4090,30 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
>  	cd->data[0] |= cpu_to_le64(
>  		FIELD_PREP(CTXDESC_CD_0_ASID, smmu_domain->cd.asid));
>  
> +	/*
> +	 * The private CD table is taking over the STE now. Make sure its
> +	 * default substream matches the RID domain before the STE points at
> +	 * it, since non-PASID traffic follows CD[0].
> +	 */
> +	if (first) {
> +		if ((sid_domain->type & __IOMMU_DOMAIN_PAGING) &&
> +		    to_smmu_domain(sid_domain)->stage == ARM_SMMU_DOMAIN_S1) {
> +			struct arm_smmu_cd cd0;
> +
> +			arm_smmu_make_s1_cd(&cd0, master,
> +					    to_smmu_domain(sid_domain));
> +			arm_smmu_write_cd_entry(master, IOMMU_NO_PASID,
> +						cd0ptr, &cd0);

... there is only one place using the cd0ptr.

> @@ -4015,35 +4133,61 @@ static int arm_smmu_blocking_set_dev_pasid(struct iommu_domain *new_domain,
[...] 
>  	/*
> -	 * When the last user of the CD table goes away downgrade the STE back
> -	 * to a non-cd_table one, by re-attaching its sid_domain.
> +	 * When the last user of the CD table goes away, switch the STE back to
> +	 * the RID domain's CD table (S1) or to the native bypass/abort STE
> +	 * (identity/blocked). Do not call attach_dev() for an S1 paging domain:
> +	 * this is a teardown path where allocation failures cannot be
> +	 * propagated. The private CD table is kept allocated for reuse and
> +	 * freed when the device goes away.
>  	 */
>  	if (!arm_smmu_ssids_in_use(&master->cd_table)) {
>  		struct iommu_domain *sid_domain =
>  			iommu_driver_get_domain_for_dev(master->dev);
> -		bool ats_always_on = master->ats_always_on &&
> -				     sid_domain->type != IOMMU_DOMAIN_BLOCKED;
> -		bool downgrade = sid_domain->type == IOMMU_DOMAIN_IDENTITY ||
> -				 sid_domain->type == IOMMU_DOMAIN_BLOCKED;
>  
> -		if (!ats_always_on && downgrade)
> -			sid_domain->ops->attach_dev(sid_domain, dev,
> -						    sid_domain);
> +		if (!master->ats_always_on ||
> +		    sid_domain->type == IOMMU_DOMAIN_BLOCKED) {
> +			if ((sid_domain->type & __IOMMU_DOMAIN_PAGING) &&
> +			    to_smmu_domain(sid_domain)->stage ==
> +				    ARM_SMMU_DOMAIN_S1) {
> +				struct arm_smmu_ste ste;
> +				struct arm_smmu_domain *sid_smmu_domain =
> +					to_smmu_domain(sid_domain);
> +
> +				mutex_lock(&arm_smmu_asid_lock);
> +				arm_smmu_make_cdtable_ste(
> +					&ste, master, &sid_smmu_domain->cd_table,
> +					master->ats_enabled,
> +					STRTAB_STE_1_S1DSS_SSID0);
> +				arm_smmu_install_ste_for_dev(master, &ste);
> +				mutex_unlock(&arm_smmu_asid_lock);
> +			} else if (sid_domain->type == IOMMU_DOMAIN_IDENTITY ||
> +				   sid_domain->type == IOMMU_DOMAIN_BLOCKED) {
> +				ret = sid_domain->ops->attach_dev(sid_domain, dev,
> +								  sid_domain);

I commented in v7 that attach_dev shouldn't propagate the ret..

Although your patch-9 clean it back to the right form, I think
there should be a way to prepare things cleanly so that you can
make a change here in one shot.

Nicolin

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

* Re: [PATCH RFC v8 7/9] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating
  2026-11-13 13:27 ` [PATCH RFC v8 7/9] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
@ 2026-11-13 19:09   ` Nicolin Chen
  0 siblings, 0 replies; 12+ messages in thread
From: Nicolin Chen @ 2026-11-13 19:09 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 Fri, Nov 13, 2026 at 09:27:19PM +0800, Peng Fan (OSS) wrote:
> From: Peng Fan <peng.fan@nxp.com>
> 
> With the per-domain CD table, every non-PASID sharer of a SID builds the
> same normal-domain STE: same S1ContextPtr, S1DSS and S1CDMAX. Disable
> PASID and ATS for shared SIDs as well, so the remaining STE fields are
> common too, and let any sharer program the STE. This also handles the
> case where one device's driver attaches before the stream owner's driver:
> the first attach now installs a valid STE instead of leaving the boot or
> firmware STE in place.
> 
> A per-device release must not abort a shared STE while other sharers
> remain, otherwise it would break their ongoing DMA. Use a dedicated
> release domain instead of inferring release from the STE contents:
> group-wide BLOCKED attaches still program the abort STE, while the
> release domain skips writes until the last master no longer shares the
> SID.
> 
> Disable SVA, PASID, vSMMU nesting, ATS and IOPF/stall for shared-SID
> masters since all of them require an unambiguous SID-to-device mapping.
> Reject sharing at insert_master() time if either master wants stalling,
> if the incoming master requires ATS, if the existing master has PASID,
> ATS or vSMMU state enabled, or if a PCI and a non-PCI master would share
> the SID. Other ATS-capable shared devices have their ATS capability
> prepared, but ATS is not enabled while the SID is shared.
> 
> Resolve stall_enabled before insert_master(), and use master->stall_enabled
> directly for CD.S, STE.S1STALLD and STE.S2S. A shared master can never
> have stalling enabled.
> 
> Add arm_smmu_master_shares_sid() as an inline helper in arm-smmu-v3.h so
> that arm-smmu-v3-sva.c and arm-smmu-v3-iommufd.c can use it without
> repeating the list_empty() pattern.
> 
> Signed-off-by: Peng Fan <peng.fan@nxp.com>

I am asking a bigger question in PATCH-4 for Robin to confirm,
yet Sashiko gave a few scary findings against patch 7-9.

Would you please run Sashiko locally and address them as much
as possible, before sending a new version?

Nicolin

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

end of thread, other threads:[~2026-11-13 21:09 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-13 13:27 [PATCH RFC v8 0/9] iommu/arm-smmu-v3: Support shared Stream IDs Peng Fan (OSS)
2026-11-13 13:27 ` [PATCH RFC v8 1/9] iommu/arm-smmu-v3: Allocate streams individually Peng Fan (OSS)
2026-11-13 13:27 ` [PATCH RFC v8 2/9] iommu/arm-smmu-v3: Delay stream allocation to inside the mutex Peng Fan (OSS)
2026-11-13 13:27 ` [PATCH RFC v8 3/9] iommu/arm-smmu-v3: Plumb the CD table into the CD helpers Peng Fan (OSS)
2026-11-13 13:27 ` [PATCH RFC v8 4/9] iommu/arm-smmu-v3: Use a per-domain CD table for non-PASID streams Peng Fan (OSS)
2026-11-13 19:01   ` Nicolin Chen
2026-11-13 13:27 ` [PATCH RFC v8 5/9] iommu/arm-smmu-v3: Support shared SIDs in insert/remove_master Peng Fan (OSS)
2026-11-13 13:27 ` [PATCH RFC v8 6/9] iommu/arm-smmu-v3: Group aliasing devices into the same IOMMU group Peng Fan (OSS)
2026-11-13 13:27 ` [PATCH RFC v8 7/9] iommu/arm-smmu-v3: Wire up shared-SID STE ordering and feature gating Peng Fan (OSS)
2026-11-13 19:09   ` Nicolin Chen
2026-11-13 13:27 ` [PATCH RFC v8 8/9] iommu/arm-smmu-v3: Serialize shared-SID changes with attach Peng Fan (OSS)
2026-11-13 13:27 ` [PATCH RFC v8 9/9] iommu/arm-smmu-v3: Quiesce PASID before mm release unlinks Peng Fan (OSS)

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