Currently, ASID is allocated per smmu_domain, stored in the domain, and
freed with the domain.
Practically, ASID is only used in a CD as an iotlb tag. Therefore, ASID
doesn't really follow the life cycle of a domain but domain attachment.
On the other hand, the CD carrying ASID is installed to a device's STE.
This applies to the VMID as well, which is installed in an STE directly.
Since a device can only have one ASID per SSID and one VMID per SID, add
an ASID array and VMID in the arm_smmu_master structure, to decouple the
ASID/VMID from the domain structure.
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 4 +++
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c | 13 ++++++---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 28 +++++++++++++++++++
3 files changed, 41 insertions(+), 4 deletions(-)
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 b275673c03ce..e21e95936b05 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -953,6 +953,8 @@ struct arm_smmu_master {
bool stall_enabled;
unsigned int ssid_bits;
unsigned int iopf_refcount;
+ /* Store allocated ASID[1 << ssid_bits] and VMID */
+ u16 *asid, vmid;
};
/* SMMU private data for an IOMMU domain */
@@ -1117,11 +1119,13 @@ static inline bool arm_smmu_master_canwbs(struct arm_smmu_master *master)
* @new_invs: for new domain, this is the new invs array to update domain->invs;
* for old domain, this is the master->build_invs to pass in as the
* to_unref argument to an arm_smmu_invs_unref() call
+ * @iotlb_tag: copy of the first entry in the build_invs for the domain
*/
struct arm_smmu_inv_state {
struct arm_smmu_invs __rcu **invs_ptr;
struct arm_smmu_invs *old_invs;
struct arm_smmu_invs *new_invs;
+ struct arm_smmu_inv iotlb_tag;
};
struct arm_smmu_attach_state {
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 e4bdb4cfdacd..ead0d84cc9a0 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
@@ -449,7 +449,8 @@ static void arm_smmu_v3_test_cd_expect_hitless_transition(
num_syncs_expected, true);
}
-static void arm_smmu_test_make_s1_cd(struct arm_smmu_cd *cd, unsigned int asid)
+static void arm_smmu_test_make_s1_cd(struct kunit *test, struct arm_smmu_cd *cd,
+ unsigned int asid)
{
struct arm_smmu_master master = {
.smmu = &smmu,
@@ -471,6 +472,10 @@ static void arm_smmu_test_make_s1_cd(struct arm_smmu_cd *cd, unsigned int asid)
io_pgtable.cfg.arm_lpae_s1_cfg.tcr.tsz = 4;
io_pgtable.cfg.arm_lpae_s1_cfg.mair = 0xabcdef012345678ULL;
+ master.asid = kunit_kzalloc(test, sizeof(*master.asid), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, master.asid);
+
+ master.asid[IOMMU_NO_PASID] = asid;
arm_smmu_make_s1_cd(cd, &master, &smmu_domain, IOMMU_NO_PASID);
}
@@ -479,7 +484,7 @@ static void arm_smmu_v3_write_cd_test_s1_clear(struct kunit *test)
struct arm_smmu_cd cd = {};
struct arm_smmu_cd cd_2;
- arm_smmu_test_make_s1_cd(&cd_2, 1997);
+ arm_smmu_test_make_s1_cd(test, &cd_2, 1997);
arm_smmu_v3_test_cd_expect_non_hitless_transition(
test, &cd, &cd_2, NUM_EXPECTED_SYNCS(2));
arm_smmu_v3_test_cd_expect_non_hitless_transition(
@@ -491,8 +496,8 @@ static void arm_smmu_v3_write_cd_test_s1_change_asid(struct kunit *test)
struct arm_smmu_cd cd = {};
struct arm_smmu_cd cd_2;
- arm_smmu_test_make_s1_cd(&cd, 778);
- arm_smmu_test_make_s1_cd(&cd_2, 1997);
+ arm_smmu_test_make_s1_cd(test, &cd, 778);
+ arm_smmu_test_make_s1_cd(test, &cd_2, 1997);
arm_smmu_v3_test_cd_expect_hitless_transition(test, &cd, &cd_2,
NUM_EXPECTED_SYNCS(1));
arm_smmu_v3_test_cd_expect_hitless_transition(test, &cd_2, &cd,
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 8a2b7064d29b..1bf7b7233109 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -3391,6 +3391,7 @@ static int arm_smmu_attach_prepare_invs(struct arm_smmu_attach_state *state,
arm_smmu_invs_merge(invst->old_invs, build_invs);
if (IS_ERR(invst->new_invs))
return PTR_ERR(invst->new_invs);
+ invst->iotlb_tag = build_invs->inv[0];
}
if (old_smmu_domain) {
@@ -3440,6 +3441,11 @@ arm_smmu_install_new_domain_invs(struct arm_smmu_attach_state *state)
*/
smp_mb();
kfree_rcu(invst->old_invs, rcu);
+
+ if (invst->iotlb_tag.type == INV_TYPE_S1_ASID)
+ state->master->asid[state->ssid] = invst->iotlb_tag.id;
+ else
+ state->master->vmid = invst->iotlb_tag.id;
}
/*
@@ -3471,8 +3477,11 @@ static void arm_smmu_inv_flush_iotlb_tag(struct arm_smmu_inv *inv)
static void
arm_smmu_install_old_domain_invs(struct arm_smmu_attach_state *state)
{
+ struct arm_smmu_inv *new_iotlb_tag = &state->new_domain_invst.iotlb_tag;
+ struct arm_smmu_inv *old_iotlb_tag = &state->old_domain_invst.iotlb_tag;
struct arm_smmu_inv_state *invst = &state->old_domain_invst;
struct arm_smmu_invs *old_invs = invst->old_invs;
+ struct arm_smmu_master *master = state->master;
struct arm_smmu_invs *new_invs;
lockdep_assert_held(&arm_smmu_asid_lock);
@@ -3482,6 +3491,7 @@ arm_smmu_install_old_domain_invs(struct arm_smmu_attach_state *state)
arm_smmu_invs_unref(old_invs, invst->new_invs,
arm_smmu_inv_flush_iotlb_tag);
+ *old_iotlb_tag = invst->new_invs->inv[0];
new_invs = arm_smmu_invs_purge(old_invs);
if (!new_invs)
@@ -3506,6 +3516,14 @@ arm_smmu_install_old_domain_invs(struct arm_smmu_attach_state *state)
*/
smp_mb();
kfree_rcu(old_invs, rcu);
+
+ /* Make sure we don't clear the stored new iotlb tag */
+ if (!new_iotlb_tag->id) {
+ if (old_iotlb_tag->type == INV_TYPE_S1_ASID)
+ cmpxchg(&master->asid[state->ssid], old_iotlb_tag->id, 0);
+ else
+ cmpxchg(&master->vmid, old_iotlb_tag->id, 0);
+ }
}
/*
@@ -4286,6 +4304,13 @@ static struct iommu_device *arm_smmu_probe_device(struct device *dev)
master->ssid_bits = min_t(u8, master->ssid_bits,
CTXDESC_LINEAR_CDMAX);
+ master->asid = kcalloc(1 << master->ssid_bits, sizeof(*master->asid),
+ GFP_KERNEL);
+ if (!master->asid) {
+ ret = -ENOMEM;
+ goto err_disable_pasid;
+ }
+
if ((smmu->features & ARM_SMMU_FEAT_STALLS &&
device_property_read_bool(dev, "dma-can-stall")) ||
smmu->features & ARM_SMMU_FEAT_STALL_FORCE)
@@ -4299,6 +4324,9 @@ static struct iommu_device *arm_smmu_probe_device(struct device *dev)
return &smmu->iommu;
+err_disable_pasid:
+ arm_smmu_disable_pasid(master);
+ arm_smmu_remove_master(master);
err_free_master:
kfree(master);
return ERR_PTR(ret);
--
2.43.0
On Thu, Dec 18, 2025 at 12:26:49PM -0800, Nicolin Chen wrote: > Currently, ASID is allocated per smmu_domain, stored in the domain, and > freed with the domain. > > Practically, ASID is only used in a CD as an iotlb tag. Therefore, ASID > doesn't really follow the life cycle of a domain but domain attachment. > > On the other hand, the CD carrying ASID is installed to a device's STE. > > This applies to the VMID as well, which is installed in an STE directly. > > Since a device can only have one ASID per SSID and one VMID per SID, add > an ASID array and VMID in the arm_smmu_master structure, to decouple the > ASID/VMID from the domain structure. I don't think this is entirely right.. When a S1 is attached to a master the master needs to store the VMID it is using, as the VMID is global to the STE and effectively becomes global to the master. But the ASID should be stored in the invalidation list of the domain. Jus search the list for the (instance, vmid) pair of the master to get back the right ASID. We don't need to store it again in another list, that's confusing. Jason
On Fri, Dec 19, 2025 at 11:16:08AM -0400, Jason Gunthorpe wrote: > On Thu, Dec 18, 2025 at 12:26:49PM -0800, Nicolin Chen wrote: > > Currently, ASID is allocated per smmu_domain, stored in the domain, and > > freed with the domain. > > > > Practically, ASID is only used in a CD as an iotlb tag. Therefore, ASID > > doesn't really follow the life cycle of a domain but domain attachment. > > > > On the other hand, the CD carrying ASID is installed to a device's STE. > > > > This applies to the VMID as well, which is installed in an STE directly. > > > > Since a device can only have one ASID per SSID and one VMID per SID, add > > an ASID array and VMID in the arm_smmu_master structure, to decouple the > > ASID/VMID from the domain structure. > > I don't think this is entirely right.. > > When a S1 is attached to a master the master needs to store the VMID > it is using, as the VMID is global to the STE and effectively becomes > global to the master. > > But the ASID should be stored in the invalidation list of the domain. Hmm, you mean the nested case (host VMID + guest ASID), right? But in that case, the guest-level ASID isn't needed anywhere in the host driver? The ASID should be configured in a guest-level CD. And the invalidation command directly coming from the guest is already filled with the guest-level ASID. Also, the invalidation array stores either ASID or VMID, there is no case of storing both, right? > Jus search the list for the (instance, vmid) pair of the master to get > back the right ASID. > > We don't need to store it again in another list, that's confusing. Okay. I was trying to get rid of the extra for loop searching for an ID in the array. That's why I had the alloc_id/free_id ops too, so everything would be done in existing two loops in the merge/unref functions. Nicolin
© 2016 - 2026 Red Hat, Inc.