* [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it
@ 2026-07-06 16:26 Jason Gunthorpe
2026-07-06 16:26 ` [PATCH v2 1/8] iommu/arm-smmu-v3: Pass the parameters for the invalidation in a struct Jason Gunthorpe
` (8 more replies)
0 siblings, 9 replies; 24+ messages in thread
From: Jason Gunthorpe @ 2026-07-06 16:26 UTC (permalink / raw)
To: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon
Cc: David Matlack, Pasha Tatashin, patches, Pranjal Shrivastava,
Samiullah Khawaja, Mostafa Saleh
[ This is part of the patch pile to move SMMUv3 over to the generic page
table, the precursor patches have been merged now:
1) Organize the SMMUv3 invalidation flow so iommupt can use it
2) Use the generic iommu page table for SMMUv3
The whole branch is here:
https://github.com/jgunthorpe/linux/commits/iommu_pt_arm64/
]
iommupt has a design that focuses on building a single iommu_iotlb_gather
for arbitary batches of map/unmap operations. The gather uses the free
list and it captures invalidations of tables, leaves and supports mixed
levels.
The introduction of PT_FEAT_DETAILED_GATHER provides some additional
information that is useful for ARM: the damage bitmaps for the table and
level changes.
Prior to switching SMMUv3 over to use iommupt prepare for this by
reworking the internal invalidation to work on the same data format that
iommupt will produce. Bridge the invalidations generated by io-pgtable
into the new format. The conversion is simple enough, io-pgtable generates
invalidation operations that have only a single set bit in
table_levels_bitmap/leaf_levels_bitmap, so we can convert the io-pgtable
provided size into the proper level leaf or table bit.
When iommupt uses this mechanism it will fill in full bitmaps reflecting
the union of all invalidations contained in the gather, and this series
provides an implementation that can work this way.
Like the other drivers the general algorithm focuses on trying to issue a
single command per gather or at most 512 single invalidations. If that
isn't possible then it falls back to full invalidation. Since table and
leaf invalidation are combined together there is no waste of invaliding
tables prior to performing a full invalidation.
On its own this provides value as the invalidation has a number of
rough spots:
- Non-leaf invalidation actually expands into a TLBI for every
translation granule because the inner logic doesn't special case the
walk vs leaf condition. Now that a table_levels_bitmap is used to
describe the walk invalidation it properly generates a RIL with optimal
TTL or only one single invalidation.
- RIL doesn't calculate perfect hints for SVA because the SVA rules are
different from the io-pgtable-arm rules that the RIL algorithm works
with. SVA can now express the combined leaf and table invalidation that
the MM callback represents and get the right TTL, with an optimization
for the common 4k only scenario.
- RIL didn't generate a single invalidation like VT-d and AMD do,
instead it tries to generate an exact coverage with many
smaller invalidations. Switch it to match the other drivers single
range approach for performance and consistency. Since ARM has a much
more flexible range definition the over invalidation is far smaller
than other systems.
The approach is to introduce a new struct arm_smmu_tlbi which
describes the invalidation, pre-compute into the tlbi the single and
range commands from the start/last and bitmaps, and then apply the
correct pre-computed command to each of items in the invalidation
list.
The RIL and single calculations are revised to use the new bitmaps
and accurately generate TTL/stride/etc.
Some of this design is to support another series to remove the batch on
the stack. Now that we have the invalidation list and the tlbi it is
simple to just expand the invs list directly into commands instead of
using the temporary on-stack batch array. Eventually removing batch will
save ~1k of stack usage here.
v2:
- Rebase to v7.2-rc1
v1: https://lore.kernel.org/all/0-v1-5b1ac97a5403+6588f-smmu_tlbi_jgg@nvidia.com/
Jason Gunthorpe (8):
iommu/arm-smmu-v3: Pass the parameters for the invalidation in a
struct
iommu/arm-smmu-v3: Move pgsize out of arm_smmu_inv
iommu/arm-smmu-v3: Optimize range invalidation for latency
iommu/arm-smmu-v3: Keep track in the arm_smmu_invs if RIL is used
iommu/arm-smmu-v3: Precompute the invalidation commands
iommu/arm-smmu-v3: Populate the tlbi at the top of the call chain
iommu/arm-smmu-v3: Change how the tlbi describes the invalidation
iommu/arm-smmu-v3: Support the DS expansion of RIL's SCALE
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 32 +-
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c | 30 +-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 439 ++++++++++++------
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 54 ++-
4 files changed, 382 insertions(+), 173 deletions(-)
base-commit: 4c73a6222c248384513c4f465e547df80b280a06
--
2.43.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v2 1/8] iommu/arm-smmu-v3: Pass the parameters for the invalidation in a struct
2026-07-06 16:26 [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it Jason Gunthorpe
@ 2026-07-06 16:26 ` Jason Gunthorpe
2026-07-07 3:04 ` Nicolin Chen
2026-07-07 11:18 ` Mostafa Saleh
2026-07-06 16:26 ` [PATCH v2 2/8] iommu/arm-smmu-v3: Move pgsize out of arm_smmu_inv Jason Gunthorpe
` (7 subsequent siblings)
8 siblings, 2 replies; 24+ messages in thread
From: Jason Gunthorpe @ 2026-07-06 16:26 UTC (permalink / raw)
To: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon
Cc: David Matlack, Pasha Tatashin, patches, Pranjal Shrivastava,
Samiullah Khawaja, Mostafa Saleh
These parameters go to a lot of different functions and the next
patches will add more. Put them into a struct to keep things tidy.
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 75 +++++++++++----------
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 7 ++
2 files changed, 46 insertions(+), 36 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 a10affb483a4fe..0c875771b46d2f 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2298,8 +2298,8 @@ static irqreturn_t arm_smmu_combined_irq_handler(int irq, void *dev)
return IRQ_WAKE_THREAD;
}
-static struct arm_smmu_cmd
-arm_smmu_atc_inv_to_cmd(u32 sid, int ssid, unsigned long iova, size_t size)
+static struct arm_smmu_cmd arm_smmu_atc_inv_to_cmd(u32 sid, int ssid,
+ struct arm_smmu_tlbi *tlbi)
{
size_t log2_span;
size_t span_mask;
@@ -2321,8 +2321,8 @@ arm_smmu_atc_inv_to_cmd(u32 sid, int ssid, unsigned long iova, size_t size)
* This has the unpleasant side-effect of invalidating all PASID-tagged
* ATC entries within the address range.
*/
- page_start = iova >> inval_grain_shift;
- page_end = (iova + size - 1) >> inval_grain_shift;
+ page_start = tlbi->iova >> inval_grain_shift;
+ page_end = (tlbi->iova + tlbi->size - 1) >> inval_grain_shift;
/*
* In an ATS Invalidate Request, the address must be aligned on the
@@ -2397,20 +2397,23 @@ static void arm_smmu_tlb_inv_context(void *cookie)
static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
struct arm_smmu_cmdq_batch *cmds,
- struct arm_smmu_cmd *cmd, bool leaf,
- unsigned long iova, size_t size,
- size_t granule, size_t pgsize)
+ struct arm_smmu_cmd *cmd,
+ struct arm_smmu_tlbi *tlbi,
+ size_t pgsize)
{
- unsigned long end = iova + size, num_pages = 0, tg = pgsize;
+ size_t inv_range = tlbi->iopte_granule;
+ unsigned long iova = tlbi->iova;
+ unsigned long end = iova + tlbi->size;
+ unsigned long num_pages = 0;
+ unsigned int tg = pgsize;
u64 orig_data0 = cmd->data[0];
- size_t inv_range = granule;
u8 ttl = 0, tg_enc = 0;
- if (WARN_ON_ONCE(!size))
+ if (WARN_ON_ONCE(!tlbi->size))
return;
if (smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
- num_pages = size >> tg;
+ num_pages = tlbi->size >> tg;
/* Convert page size of 12,14,16 (log2) to 1,2,3 */
tg_enc = (tg - 10) / 2;
@@ -2423,8 +2426,8 @@ static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
* want to use a range command, so avoid the SVA corner case
* where both scale and num could be 0 as well.
*/
- if (leaf)
- ttl = 4 - ((ilog2(granule) - 3) / (tg - 3));
+ if (tlbi->leaf_only)
+ ttl = 4 - ((ilog2(tlbi->iopte_granule) - 3) / (tg - 3));
else if ((num_pages & CMDQ_TLBI_RANGE_NUM_MAX) == 1)
num_pages++;
}
@@ -2462,7 +2465,7 @@ static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
* command and something would be very broken if iova had them
* set.
*/
- cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, leaf) |
+ cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
(iova & ~GENMASK_U64(11, 0));
@@ -2472,13 +2475,13 @@ static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
}
}
-static bool arm_smmu_inv_size_too_big(struct arm_smmu_device *smmu, size_t size,
- size_t granule)
+static bool arm_smmu_inv_size_too_big(struct arm_smmu_device *smmu,
+ struct arm_smmu_tlbi *tlbi)
{
size_t max_tlbi_ops;
/* 0 size means invalidate all */
- if (!size || size == SIZE_MAX)
+ if (!tlbi->size || tlbi->size == SIZE_MAX)
return true;
if (smmu->features & ARM_SMMU_FEAT_RANGE_INV)
@@ -2491,19 +2494,17 @@ static bool arm_smmu_inv_size_too_big(struct arm_smmu_device *smmu, size_t size,
* invalidation feature, where there can be too many per-granule TLBIs,
* resulting in a soft lockup.
*/
- max_tlbi_ops = 1 << (ilog2(granule) - 3);
- return size >= max_tlbi_ops * granule;
+ max_tlbi_ops = 1 << (ilog2(tlbi->iopte_granule) - 3);
+ return tlbi->size >= max_tlbi_ops * tlbi->iopte_granule;
}
/* Used by non INV_TYPE_ATS* invalidations */
static void arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
struct arm_smmu_cmdq_batch *cmds,
struct arm_smmu_cmd *cmd,
- bool leaf,
- unsigned long iova, size_t size,
- unsigned int granule)
+ struct arm_smmu_tlbi *tlbi)
{
- if (arm_smmu_inv_size_too_big(inv->smmu, size, granule)) {
+ if (arm_smmu_inv_size_too_big(inv->smmu, tlbi)) {
struct arm_smmu_cmd nsize_cmd = *cmd;
u64p_replace_bits(&nsize_cmd.data[0], inv->nsize_opcode,
@@ -2512,8 +2513,7 @@ static void arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
return;
}
- arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, leaf,
- iova, size, granule, inv->pgsize);
+ arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, tlbi, inv->pgsize);
}
static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
@@ -2532,9 +2532,8 @@ static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
return false;
}
-static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
- unsigned long iova, size_t size,
- unsigned int granule, bool leaf)
+static void __arm_smmu_domain_inv_range(struct arm_smmu_tlbi *tlbi,
+ struct arm_smmu_invs *invs)
{
struct arm_smmu_cmdq_batch cmds = {};
struct arm_smmu_inv *cur;
@@ -2564,18 +2563,16 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
case INV_TYPE_S1_ASID:
cmd = arm_smmu_make_cmd_tlbi(cur->size_opcode,
cur->id, 0);
- arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, leaf,
- iova, size, granule);
+ arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, tlbi);
break;
case INV_TYPE_S2_VMID:
cmd = arm_smmu_make_cmd_tlbi(cur->size_opcode,
0, cur->id);
- arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, leaf,
- iova, size, granule);
+ arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, tlbi);
break;
case INV_TYPE_S2_VMID_S1_CLEAR:
/* CMDQ_OP_TLBI_S12_VMALL already flushed S1 entries */
- if (arm_smmu_inv_size_too_big(cur->smmu, size, granule))
+ if (arm_smmu_inv_size_too_big(cur->smmu, tlbi))
break;
arm_smmu_cmdq_batch_add_cmd(
smmu, &cmds,
@@ -2586,7 +2583,7 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
arm_smmu_cmdq_batch_add_cmd(
smmu, &cmds,
arm_smmu_atc_inv_to_cmd(cur->id, cur->ssid,
- iova, size));
+ tlbi));
break;
case INV_TYPE_ATS_FULL:
arm_smmu_cmdq_batch_add_cmd(
@@ -2617,6 +2614,12 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
unsigned long iova, size_t size,
unsigned int granule, bool leaf)
{
+ struct arm_smmu_tlbi tlbi = {
+ .iova = iova,
+ .size = size,
+ .iopte_granule = granule,
+ .leaf_only = leaf,
+ };
struct arm_smmu_invs *invs;
/*
@@ -2657,10 +2660,10 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
unsigned long flags;
read_lock_irqsave(&invs->rwlock, flags);
- __arm_smmu_domain_inv_range(invs, iova, size, granule, leaf);
+ __arm_smmu_domain_inv_range(&tlbi, invs);
read_unlock_irqrestore(&invs->rwlock, flags);
} else {
- __arm_smmu_domain_inv_range(invs, iova, size, granule, leaf);
+ __arm_smmu_domain_inv_range(&tlbi, invs);
}
rcu_read_unlock();
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 c909c9a88538bf..a364d847c22a92 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -802,6 +802,13 @@ static inline struct arm_smmu_invs *arm_smmu_invs_alloc(size_t num_invs)
return new_invs;
}
+struct arm_smmu_tlbi {
+ unsigned long iova;
+ size_t size;
+ unsigned int iopte_granule;
+ bool leaf_only;
+};
+
struct arm_smmu_evtq {
struct arm_smmu_queue q;
struct iopf_queue *iopf;
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v2 2/8] iommu/arm-smmu-v3: Move pgsize out of arm_smmu_inv
2026-07-06 16:26 [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it Jason Gunthorpe
2026-07-06 16:26 ` [PATCH v2 1/8] iommu/arm-smmu-v3: Pass the parameters for the invalidation in a struct Jason Gunthorpe
@ 2026-07-06 16:26 ` Jason Gunthorpe
2026-07-07 3:57 ` Nicolin Chen
2026-07-07 11:24 ` Mostafa Saleh
2026-07-06 16:26 ` [PATCH v2 3/8] iommu/arm-smmu-v3: Optimize range invalidation for latency Jason Gunthorpe
` (6 subsequent siblings)
8 siblings, 2 replies; 24+ messages in thread
From: Jason Gunthorpe @ 2026-07-06 16:26 UTC (permalink / raw)
To: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon
Cc: David Matlack, Pasha Tatashin, patches, Pranjal Shrivastava,
Samiullah Khawaja, Mostafa Saleh
pgsize is a constant property of the domain, it is the base translation
granule of the page table (4k, 16k, 64k) in log2.
Store it to the struct arm_smmu_domain based on how the page table was created.
Add smmu_domain to the tlbi and just get tg from the domain.
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
---
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 1 +
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 27 ++++++++-----------
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 3 ++-
3 files changed, 14 insertions(+), 17 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 1ed8a6f29dc445..5d4dde3d1cfe87 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
@@ -334,6 +334,7 @@ struct iommu_domain *arm_smmu_sva_domain_alloc(struct device *dev,
* ARM_SMMU_FEAT_RANGE_INV is present
*/
smmu_domain->domain.pgsize_bitmap = PAGE_SIZE;
+ smmu_domain->tgsz_lg2 = PAGE_SHIFT;
smmu_domain->stage = ARM_SMMU_DOMAIN_SVA;
smmu_domain->smmu = smmu;
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 0c875771b46d2f..d22012466e3965 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2398,14 +2398,13 @@ static void arm_smmu_tlb_inv_context(void *cookie)
static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
struct arm_smmu_cmdq_batch *cmds,
struct arm_smmu_cmd *cmd,
- struct arm_smmu_tlbi *tlbi,
- size_t pgsize)
+ struct arm_smmu_tlbi *tlbi)
{
size_t inv_range = tlbi->iopte_granule;
unsigned long iova = tlbi->iova;
unsigned long end = iova + tlbi->size;
unsigned long num_pages = 0;
- unsigned int tg = pgsize;
+ unsigned int tg = tlbi->smmu_domain->tgsz_lg2;
u64 orig_data0 = cmd->data[0];
u8 ttl = 0, tg_enc = 0;
@@ -2513,7 +2512,7 @@ static void arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
return;
}
- arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, tlbi, inv->pgsize);
+ arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, tlbi);
}
static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
@@ -2615,6 +2614,7 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
unsigned int granule, bool leaf)
{
struct arm_smmu_tlbi tlbi = {
+ .smmu_domain = smmu_domain,
.iova = iova,
.size = size,
.iopte_granule = granule,
@@ -2870,6 +2870,7 @@ static int arm_smmu_domain_finalise(struct arm_smmu_domain *smmu_domain,
return -ENOMEM;
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;
smmu_domain->domain.geometry.force_aperture = true;
if (enable_dirty && smmu_domain->stage == ARM_SMMU_DOMAIN_S1)
@@ -3104,15 +3105,13 @@ static void arm_smmu_disable_iopf(struct arm_smmu_master *master,
static struct arm_smmu_inv *
arm_smmu_master_build_inv(struct arm_smmu_master *master,
- enum arm_smmu_inv_type type, u32 id, ioasid_t ssid,
- size_t pgsize)
+ enum arm_smmu_inv_type type, u32 id, ioasid_t ssid)
{
struct arm_smmu_invs *build_invs = master->build_invs;
struct arm_smmu_inv *cur, inv = {
.smmu = master->smmu,
.type = type,
.id = id,
- .pgsize = pgsize,
};
if (WARN_ON(build_invs->num_invs >= build_invs->max_invs))
@@ -3164,28 +3163,24 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,
ioasid_t ssid, struct arm_smmu_domain *smmu_domain)
{
const bool nesting = smmu_domain->nest_parent;
- size_t pgsize = 0, i;
+ size_t i;
iommu_group_mutex_assert(master->dev);
master->build_invs->num_invs = 0;
- /* Range-based invalidation requires the leaf pgsize for calculation */
- if (master->smmu->features & ARM_SMMU_FEAT_RANGE_INV)
- pgsize = __ffs(smmu_domain->domain.pgsize_bitmap);
-
switch (smmu_domain->stage) {
case ARM_SMMU_DOMAIN_SVA:
case ARM_SMMU_DOMAIN_S1:
if (!arm_smmu_master_build_inv(master, INV_TYPE_S1_ASID,
smmu_domain->cd.asid,
- IOMMU_NO_PASID, pgsize))
+ IOMMU_NO_PASID))
return NULL;
break;
case ARM_SMMU_DOMAIN_S2:
if (!arm_smmu_master_build_inv(master, INV_TYPE_S2_VMID,
smmu_domain->s2_cfg.vmid,
- IOMMU_NO_PASID, pgsize))
+ IOMMU_NO_PASID))
return NULL;
break;
default:
@@ -3197,7 +3192,7 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,
if (nesting) {
if (!arm_smmu_master_build_inv(
master, INV_TYPE_S2_VMID_S1_CLEAR,
- smmu_domain->s2_cfg.vmid, IOMMU_NO_PASID, 0))
+ smmu_domain->s2_cfg.vmid, IOMMU_NO_PASID))
return NULL;
}
@@ -3208,7 +3203,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, 0))
+ master->streams[i].id, ssid))
return NULL;
}
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 a364d847c22a92..2fc695817671fe 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -734,7 +734,6 @@ struct arm_smmu_inv {
u8 nsize_opcode;
u32 id; /* ASID or VMID or SID */
union {
- size_t pgsize; /* ARM_SMMU_FEAT_RANGE_INV */
u32 ssid; /* INV_TYPE_ATS */
};
@@ -803,6 +802,7 @@ static inline struct arm_smmu_invs *arm_smmu_invs_alloc(size_t num_invs)
}
struct arm_smmu_tlbi {
+ struct arm_smmu_domain *smmu_domain;
unsigned long iova;
size_t size;
unsigned int iopte_granule;
@@ -1050,6 +1050,7 @@ struct arm_smmu_domain {
spinlock_t devices_lock;
bool enforce_cache_coherency : 1;
bool nest_parent : 1;
+ u8 tgsz_lg2;
struct mmu_notifier mmu_notifier;
};
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v2 3/8] iommu/arm-smmu-v3: Optimize range invalidation for latency
2026-07-06 16:26 [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it Jason Gunthorpe
2026-07-06 16:26 ` [PATCH v2 1/8] iommu/arm-smmu-v3: Pass the parameters for the invalidation in a struct Jason Gunthorpe
2026-07-06 16:26 ` [PATCH v2 2/8] iommu/arm-smmu-v3: Move pgsize out of arm_smmu_inv Jason Gunthorpe
@ 2026-07-06 16:26 ` Jason Gunthorpe
2026-07-07 7:27 ` Nicolin Chen
2026-07-07 11:45 ` Mostafa Saleh
2026-07-06 16:26 ` [PATCH v2 4/8] iommu/arm-smmu-v3: Keep track in the arm_smmu_invs if RIL is used Jason Gunthorpe
` (5 subsequent siblings)
8 siblings, 2 replies; 24+ messages in thread
From: Jason Gunthorpe @ 2026-07-06 16:26 UTC (permalink / raw)
To: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon
Cc: David Matlack, Pasha Tatashin, patches, Pranjal Shrivastava,
Samiullah Khawaja, Mostafa Saleh
The server IOMMU drivers focus on invalidation latency by default,
over-invalidating if necessary, to round the invalidation range up to a
single command. I think this represents a trade off for DMA non-FQ and SVA
where stalling the operation is overall worse than re-loading the IOTLB.
For instance AMD and VT-d both round the range up to the largest aligned
power of two and invalidate that. This causes over-invalidation but that
is preferred on real HW over trying to issue a number of smaller
range invalidations.
Only if a para-virtualizating hypervisor is detected do they switch to
using more accurate invalidation. This also triggers using
iommu_iotlb_gather_is_disjoint() (ie PT_FEAT_FLUSH_RANGE_NO_GAPS) to
remove over invalidation from the gather. A pvIOMMU has a hypervisor that
will walk the IOPTEs and resync them. Over invalidation, especially
significant over invalidation, can incur a big latency cost reloading alot
of page table. x86 IOMMUs have aligned range restrictions so there are
some pretty nasty corner cases that can trigger huge over invalidation.
Currently SMMUv3 doesn't support detecting a hypervisor, and it
unconditionally runs in a NO_GAPS mode. This makes some sense for the
single invalidation flow where there is little reason to push single
commands across a gap.
When we get to RIL hardware, this doesn't look so good. On real HW the
best option is the same as x86: issue a single RIL per gather and optimize
for latency. SMMUv3 has a significant advantage as its RIL does not have
alignment limitations so it's single-command over-invalidation is capped
at < 1/32 of the gather's size, making it much more suitable for a
pvIOMMU.
However even with RIL SMMUv3 still uses NO_GAPS and it breaks down the
gather into several exactly sized RILs to avoid any over-invalidation,
costing latency on real HW.
When the HW has RIL support follow the x86 approach in SMMUv3 and
calculate a single RIL per gather that will cover the required
invalidation.
Calculate the smallest SCALE such that NUM can cover the range to minimize
over-invalidation. Always use a RIL command if RIL is possible working
around the spec limitations to form a valid one. If RIL is not possible
then do full invalidation.
At least one invalidation errata is avoided by 'always use RIL'.
Since the normal path is now the only one with a loop, split them into two
functions and fold a simplified version of arm_smmu_inv_size_too_big()
directly into the normal flow in a way that directly limits the number of
single invalidation commands generated, again focusing on controlling
latency.
The end result is any gather is converted into either:
- One invalidate all
- One range invalidate op
- At most 512 single invalidation ops
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 257 ++++++++++++--------
1 file changed, 153 insertions(+), 104 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 d22012466e3965..1ad642e09eb92d 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2395,124 +2395,166 @@ static void arm_smmu_tlb_inv_context(void *cookie)
arm_smmu_domain_inv(smmu_domain);
}
-static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
+/*
+ * Check address alignment for TTL hint per SMMUv3 F.b Section 4.4.1.
+ * Address bits below the alignment must be zero, otherwise UNPREDICTABLE.
+ */
+static bool arm_smmu_ttl_addr_aligned(u64 address, unsigned int tg,
+ unsigned int ttl)
+{
+ unsigned int pgsz_lg2 = (tg - 3) * (3 - ttl) + tg;
+
+ return !(address & GENMASK_U64(pgsz_lg2 - 1, 0));
+}
+
+static void arm_smmu_cmdq_batch_add_ril(struct arm_smmu_device *smmu,
+ struct arm_smmu_cmdq_batch *cmds,
+ struct arm_smmu_cmd *cmd, bool leaf,
+ u64 address, unsigned int num,
+ unsigned int scale, u8 ttl, u8 tg_enc)
+{
+ cmd->data[0] |= FIELD_PREP(CMDQ_TLBI_0_NUM, num) |
+ FIELD_PREP(CMDQ_TLBI_0_SCALE, scale);
+ cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, leaf) |
+ FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
+ FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) | address;
+ arm_smmu_cmdq_batch_add_cmd_p(smmu, cmds, cmd);
+}
+
+/*
+ * Issue a single range TLBI command covering [iova, iova+size). Returns true if
+ * successful, false if the range is too large for a single command.
+ *
+ * The algorithm finds the smallest SCALE where the range (in tg-sized pages)
+ * fits in the 5-bit NUM field (max 32 units of 2^SCALE pages). This may widen
+ * the invalidation range.
+ */
+static bool arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
struct arm_smmu_cmdq_batch *cmds,
struct arm_smmu_cmd *cmd,
struct arm_smmu_tlbi *tlbi)
{
- size_t inv_range = tlbi->iopte_granule;
- unsigned long iova = tlbi->iova;
- unsigned long end = iova + tlbi->size;
- unsigned long num_pages = 0;
- unsigned int tg = tlbi->smmu_domain->tgsz_lg2;
- u64 orig_data0 = cmd->data[0];
- u8 ttl = 0, tg_enc = 0;
+ unsigned int tg_lg2 = tlbi->smmu_domain->tgsz_lg2;
+ u64 cur_tg = tlbi->iova >> tg_lg2;
+ u64 last_tg = (tlbi->iova + tlbi->size - 1) >> tg_lg2;
+ u64 num_tg = last_tg - cur_tg + 1;
+ u8 tg_enc = (tg_lg2 - 10) / 2;
+ unsigned int scale;
+ u8 ttl = 0;
- if (WARN_ON_ONCE(!tlbi->size))
- return;
-
- if (smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
- num_pages = tlbi->size >> tg;
-
- /* Convert page size of 12,14,16 (log2) to 1,2,3 */
- tg_enc = (tg - 10) / 2;
-
- /*
- * Determine what level the granule is at. For non-leaf, both
- * io-pgtable and SVA pass a nominal last-level granule because
- * they don't know what level(s) actually apply, so ignore that
- * and leave TTL=0. However for various errata reasons we still
- * want to use a range command, so avoid the SVA corner case
- * where both scale and num could be 0 as well.
- */
- if (tlbi->leaf_only)
- ttl = 4 - ((ilog2(tlbi->iopte_granule) - 3) / (tg - 3));
- else if ((num_pages & CMDQ_TLBI_RANGE_NUM_MAX) == 1)
- num_pages++;
- }
-
- while (iova < end) {
- if (smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
- /*
- * On each iteration of the loop, the range is 5 bits
- * worth of the aligned size remaining.
- * The range in pages is:
- *
- * range = (num_pages & (0x1f << __ffs(num_pages)))
- */
- unsigned long scale, num;
-
- /* Determine the power of 2 multiple number of pages */
- scale = __ffs(num_pages);
-
- /* Determine how many chunks of 2^scale size we have */
- num = (num_pages >> scale) & CMDQ_TLBI_RANGE_NUM_MAX;
-
- cmd->data[0] = orig_data0 |
- FIELD_PREP(CMDQ_TLBI_0_NUM, num - 1) |
- FIELD_PREP(CMDQ_TLBI_0_SCALE, scale);
-
- /* range is num * 2^scale * pgsize */
- inv_range = num << (scale + tg);
-
- /* Clear out the lower order bits for the next iteration */
- num_pages -= num << scale;
- }
-
- /*
- * IPA has fewer bits than VA, but they are reserved in the
- * command and something would be very broken if iova had them
- * set.
- */
- cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
- FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
- FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
- (iova & ~GENMASK_U64(11, 0));
-
- arm_smmu_cmdq_batch_add_cmd_p(smmu, cmds, cmd);
- iova += inv_range;
- }
-}
-
-static bool arm_smmu_inv_size_too_big(struct arm_smmu_device *smmu,
- struct arm_smmu_tlbi *tlbi)
-{
- size_t max_tlbi_ops;
-
- /* 0 size means invalidate all */
- if (!tlbi->size || tlbi->size == SIZE_MAX)
- return true;
-
- if (smmu->features & ARM_SMMU_FEAT_RANGE_INV)
+ if (!tlbi->size)
return false;
/*
- * Borrowed from the MAX_TLBI_OPS in arch/arm64/include/asm/tlbflush.h,
- * this is used as a threshold to replace "size_opcode" commands with a
- * single "nsize_opcode" command, when SMMU doesn't implement the range
- * invalidation feature, where there can be too many per-granule TLBIs,
- * resulting in a soft lockup.
+ * Determine what level the granule is at. For non-leaf, both
+ * io-pgtable and SVA pass a nominal last-level granule because they
+ * don't know what level(s) actually apply, so leave TTL=0.
*/
- max_tlbi_ops = 1 << (ilog2(tlbi->iopte_granule) - 3);
- return tlbi->size >= max_tlbi_ops * tlbi->iopte_granule;
+ if (tlbi->leaf_only)
+ ttl = 4 - ((ilog2(tlbi->iopte_granule) - 3) / (tg_lg2 - 3));
+
+ /*
+ * SMMUv3 F.b Section 4.4.1: TG!=0, NUM==0, SCALE==0, TTL==0 is
+ * Reserved and causes CERROR_ILL. Single page uses NUM=0, SCALE=0 with
+ * a TTL hint to target only the exact leaf entry.
+ */
+ if (num_tg == 1) {
+ if (!ttl)
+ ttl = 3;
+ arm_smmu_cmdq_batch_add_ril(smmu, cmds, cmd, tlbi->leaf_only,
+ cur_tg << tg_lg2, 0, 0, ttl,
+ tg_enc);
+ return true;
+ }
+
+ /*
+ * There are at most 5 possible values for NUM based on SCALE. The
+ * highest NUM is at the lowest SCALE where:
+ * ceil(num_tg / 2^SCALE) <= 32
+ * scale >= ceil(log2(num_tg / 32))
+ * The lowest value is 1 where 2^SCALE covers the whole range. Pick the
+ * highest since it trivially also gives the tightest range.
+ *
+ * Unlike other IOMMUs the spec doesn't have any alignment requirements
+ * on the address beyond it must be aligned to tg (so long as TTL=0)
+ */
+ scale = fls64((num_tg - 1) / 32);
+ if (scale > 31) {
+ /*
+ * Range too large for a single command, use full invalidation.
+ */
+ return false;
+ }
+
+ /* 16K granule TTL=1 is reserved (Section 4.4.1) */
+ if (tg_lg2 == 14 && ttl == 1)
+ ttl = 0;
+
+ /* Verify address alignment for the TTL hint */
+ if (ttl && !arm_smmu_ttl_addr_aligned(cur_tg << tg_lg2, tg_lg2, ttl))
+ ttl = 0;
+
+ arm_smmu_cmdq_batch_add_ril(smmu, cmds, cmd, tlbi->leaf_only,
+ cur_tg << tg_lg2,
+ DIV_ROUND_UP_ULL(num_tg, 1ULL << scale) - 1,
+ scale, ttl, tg_enc);
+ return true;
}
-/* Used by non INV_TYPE_ATS* invalidations */
-static void arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
+/*
+ * One TLBI command per IOTLB entry, assuming the entries are all at least
+ * iopte_granule sized. Returns false if too many commands would be needed which
+ * indicates too high a latency. The threshold is similar to MAX_DVM_OPS in
+ * arch/arm64/include/asm/tlbflush.h for the 4k PAGE_SIZE.
+ */
+static bool arm_smmu_cmdq_batch_add_single(struct arm_smmu_device *smmu,
+ struct arm_smmu_cmdq_batch *cmds,
+ struct arm_smmu_cmd *cmd,
+ struct arm_smmu_tlbi *tlbi)
+{
+ unsigned long num_ops = tlbi->size / tlbi->iopte_granule;
+ unsigned long iova = tlbi->iova;
+ unsigned long i;
+
+ if (!num_ops || num_ops > 512)
+ return false;
+
+ for (i = 0; i < num_ops; i++) {
+ cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
+ (iova & ~GENMASK_U64(11, 0));
+ arm_smmu_cmdq_batch_add_cmd_p(smmu, cmds, cmd);
+ iova += tlbi->iopte_granule;
+ }
+ return true;
+}
+
+static void arm_smmu_inv_all_cmd(struct arm_smmu_inv *inv,
+ struct arm_smmu_cmdq_batch *cmds,
+ struct arm_smmu_cmd *cmd)
+{
+ u64p_replace_bits(&cmd->data[0], inv->nsize_opcode, CMDQ_0_OP);
+ arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds, cmd);
+}
+
+/*
+ * Used by non INV_TYPE_ATS* invalidations. Returns true if it fell back to
+ * full invalidation using nsize_opcode.
+ */
+static bool arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
struct arm_smmu_cmdq_batch *cmds,
struct arm_smmu_cmd *cmd,
struct arm_smmu_tlbi *tlbi)
{
- if (arm_smmu_inv_size_too_big(inv->smmu, tlbi)) {
- struct arm_smmu_cmd nsize_cmd = *cmd;
-
- u64p_replace_bits(&nsize_cmd.data[0], inv->nsize_opcode,
- CMDQ_0_OP);
- arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds, &nsize_cmd);
- return;
+ if (inv->smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
+ if (arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, tlbi))
+ return false;
+ } else {
+ if (arm_smmu_cmdq_batch_add_single(inv->smmu, cmds, cmd, tlbi))
+ return false;
}
- arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, tlbi);
+ arm_smmu_inv_all_cmd(inv, cmds, cmd);
+ return true;
}
static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
@@ -2535,6 +2577,7 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_tlbi *tlbi,
struct arm_smmu_invs *invs)
{
struct arm_smmu_cmdq_batch cmds = {};
+ bool used_s12_vmall = false;
struct arm_smmu_inv *cur;
struct arm_smmu_inv *end;
@@ -2567,11 +2610,17 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_tlbi *tlbi,
case INV_TYPE_S2_VMID:
cmd = arm_smmu_make_cmd_tlbi(cur->size_opcode,
0, cur->id);
- arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, tlbi);
+ used_s12_vmall = arm_smmu_inv_to_cmdq_batch(cur, &cmds,
+ &cmd, tlbi);
break;
case INV_TYPE_S2_VMID_S1_CLEAR:
- /* CMDQ_OP_TLBI_S12_VMALL already flushed S1 entries */
- if (arm_smmu_inv_size_too_big(cur->smmu, tlbi))
+ /*
+ * S2_VMID used CMDQ_OP_TLBI_S12_VMALL which already
+ * flushed S1 entries. These two types always come in
+ * pairs and arm_smmu_inv_cmp() ensures that they are
+ * consecutive in the list.
+ */
+ if (used_s12_vmall)
break;
arm_smmu_cmdq_batch_add_cmd(
smmu, &cmds,
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v2 4/8] iommu/arm-smmu-v3: Keep track in the arm_smmu_invs if RIL is used
2026-07-06 16:26 [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it Jason Gunthorpe
` (2 preceding siblings ...)
2026-07-06 16:26 ` [PATCH v2 3/8] iommu/arm-smmu-v3: Optimize range invalidation for latency Jason Gunthorpe
@ 2026-07-06 16:26 ` Jason Gunthorpe
2026-07-07 7:27 ` Nicolin Chen
2026-07-07 11:46 ` Mostafa Saleh
2026-07-06 16:26 ` [PATCH v2 5/8] iommu/arm-smmu-v3: Precompute the invalidation commands Jason Gunthorpe
` (4 subsequent siblings)
8 siblings, 2 replies; 24+ messages in thread
From: Jason Gunthorpe @ 2026-07-06 16:26 UTC (permalink / raw)
To: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon
Cc: David Matlack, Pasha Tatashin, patches, Pranjal Shrivastava,
Samiullah Khawaja, Mostafa Saleh
Summarize if any of the inv entries will use RIL. The next patch will use
this to avoid RIL pre-calculations unless RIL is being used by the
invalidation.
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
---
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c | 30 +++++++++----------
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 18 ++++++++---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 2 ++
3 files changed, 31 insertions(+), 19 deletions(-)
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 add671363c828c..785dd21bd68b7a 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
@@ -655,37 +655,37 @@ static void arm_smmu_v3_invs_test_verify(struct kunit *test,
static struct arm_smmu_invs invs1 = {
.num_invs = 3,
- .inv = { { .type = INV_TYPE_S2_VMID, .id = 1, },
- { .type = INV_TYPE_S2_VMID_S1_CLEAR, .id = 1, },
- { .type = INV_TYPE_ATS, .id = 3, }, },
+ .inv = { { .smmu = &smmu, .type = INV_TYPE_S2_VMID, .id = 1, },
+ { .smmu = &smmu, .type = INV_TYPE_S2_VMID_S1_CLEAR, .id = 1, },
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 3, }, },
};
static struct arm_smmu_invs invs2 = {
.num_invs = 3,
- .inv = { { .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
- { .type = INV_TYPE_ATS, .id = 4, },
- { .type = INV_TYPE_ATS, .id = 5, }, },
+ .inv = { { .smmu = &smmu, .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 4, },
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 5, }, },
};
static struct arm_smmu_invs invs3 = {
.num_invs = 3,
- .inv = { { .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
- { .type = INV_TYPE_ATS, .id = 5, }, /* recover a trash */
- { .type = INV_TYPE_ATS, .id = 6, }, },
+ .inv = { { .smmu = &smmu, .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 5, }, /* recover a trash */
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 6, }, },
};
static struct arm_smmu_invs invs4 = {
.num_invs = 3,
- .inv = { { .type = INV_TYPE_ATS, .id = 10, .ssid = 1 },
- { .type = INV_TYPE_ATS, .id = 10, .ssid = 3 },
- { .type = INV_TYPE_ATS, .id = 12, .ssid = 1 }, },
+ .inv = { { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 1 },
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 3 },
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 12, .ssid = 1 }, },
};
static struct arm_smmu_invs invs5 = {
.num_invs = 3,
- .inv = { { .type = INV_TYPE_ATS, .id = 10, .ssid = 2 },
- { .type = INV_TYPE_ATS, .id = 10, .ssid = 3 }, /* duplicate */
- { .type = INV_TYPE_ATS, .id = 12, .ssid = 2 }, },
+ .inv = { { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 2 },
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 3 }, /* duplicate */
+ { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 12, .ssid = 2 }, },
};
static void arm_smmu_v3_invs_test(struct kunit *test)
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 1ad642e09eb92d..02323fd7709f07 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -988,6 +988,18 @@ static inline int arm_smmu_invs_iter_next_cmp(struct arm_smmu_invs *invs_l,
return arm_smmu_inv_cmp(cur_l, &invs_r->inv[next_r]);
}
+static void arm_smmu_invs_update_caps(struct arm_smmu_invs *invs,
+ const struct arm_smmu_inv *inv)
+{
+ if (arm_smmu_inv_is_ats(inv))
+ invs->has_ats = true;
+
+ if (!(inv->smmu->features & ARM_SMMU_FEAT_RANGE_INV))
+ return;
+
+ invs->has_range_inv = true;
+}
+
/**
* arm_smmu_invs_for_each_cmp - Iterate over two sorted arrays computing for
* arm_smmu_invs_merge() or arm_smmu_invs_unref()
@@ -1058,8 +1070,7 @@ struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs,
*/
if (new != new_invs->inv)
WARN_ON_ONCE(arm_smmu_inv_cmp(new - 1, new) == 1);
- if (arm_smmu_inv_is_ats(new))
- new_invs->has_ats = true;
+ arm_smmu_invs_update_caps(new_invs, new);
new++;
}
@@ -1169,8 +1180,7 @@ struct arm_smmu_invs *arm_smmu_invs_purge(struct arm_smmu_invs *invs)
arm_smmu_invs_for_each_entry(invs, i, inv) {
new_invs->inv[num_invs] = *inv;
- if (arm_smmu_inv_is_ats(inv))
- new_invs->has_ats = true;
+ arm_smmu_invs_update_caps(new_invs, inv);
num_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 2fc695817671fe..3ef55f8af63d90 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -755,6 +755,7 @@ static inline bool arm_smmu_inv_is_ats(const struct arm_smmu_inv *inv)
* Must not be greater than @num_invs
* @rwlock: optional rwlock to fence ATS operations
* @has_ats: flag if the array contains an INV_TYPE_ATS or INV_TYPE_ATS_FULL
+ * @has_range_inv: flag if any entry's SMMU supports range invalidation
* @rcu: rcu head for kfree_rcu()
* @inv: flexible invalidation array
*
@@ -784,6 +785,7 @@ struct arm_smmu_invs {
size_t num_trashes;
rwlock_t rwlock;
bool has_ats;
+ bool has_range_inv;
struct rcu_head rcu;
struct arm_smmu_inv inv[] __counted_by(max_invs);
};
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v2 5/8] iommu/arm-smmu-v3: Precompute the invalidation commands
2026-07-06 16:26 [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it Jason Gunthorpe
` (3 preceding siblings ...)
2026-07-06 16:26 ` [PATCH v2 4/8] iommu/arm-smmu-v3: Keep track in the arm_smmu_invs if RIL is used Jason Gunthorpe
@ 2026-07-06 16:26 ` Jason Gunthorpe
2026-07-07 11:52 ` Mostafa Saleh
2026-07-06 16:26 ` [PATCH v2 6/8] iommu/arm-smmu-v3: Populate the tlbi at the top of the call chain Jason Gunthorpe
` (3 subsequent siblings)
8 siblings, 1 reply; 24+ messages in thread
From: Jason Gunthorpe @ 2026-07-06 16:26 UTC (permalink / raw)
To: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon
Cc: David Matlack, Pasha Tatashin, patches, Pranjal Shrivastava,
Samiullah Khawaja, Mostafa Saleh
Store the required cmd data in the tlbi and just copy it out when
processing each item in the invs list. The cmd form only depends on
if the instance supports RIL or not, otherwise it is always the same.
This avoids redundant calculations for each invs entry.
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 132 +++++++++++---------
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 11 ++
2 files changed, 81 insertions(+), 62 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 02323fd7709f07..fbe3e5dc42f964 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2417,32 +2417,15 @@ static bool arm_smmu_ttl_addr_aligned(u64 address, unsigned int tg,
return !(address & GENMASK_U64(pgsz_lg2 - 1, 0));
}
-static void arm_smmu_cmdq_batch_add_ril(struct arm_smmu_device *smmu,
- struct arm_smmu_cmdq_batch *cmds,
- struct arm_smmu_cmd *cmd, bool leaf,
- u64 address, unsigned int num,
- unsigned int scale, u8 ttl, u8 tg_enc)
-{
- cmd->data[0] |= FIELD_PREP(CMDQ_TLBI_0_NUM, num) |
- FIELD_PREP(CMDQ_TLBI_0_SCALE, scale);
- cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, leaf) |
- FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
- FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) | address;
- arm_smmu_cmdq_batch_add_cmd_p(smmu, cmds, cmd);
-}
-
/*
- * Issue a single range TLBI command covering [iova, iova+size). Returns true if
- * successful, false if the range is too large for a single command.
+ * Generate a single range TLBI command covering [iova, iova+size). Sets
+ * use_full_inv if the range is too large for a single command.
*
* The algorithm finds the smallest SCALE where the range (in tg-sized pages)
* fits in the 5-bit NUM field (max 32 units of 2^SCALE pages). This may widen
* the invalidation range.
*/
-static bool arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
- struct arm_smmu_cmdq_batch *cmds,
- struct arm_smmu_cmd *cmd,
- struct arm_smmu_tlbi *tlbi)
+static void arm_smmu_tlbi_calc_range(struct arm_smmu_tlbi *tlbi)
{
unsigned int tg_lg2 = tlbi->smmu_domain->tgsz_lg2;
u64 cur_tg = tlbi->iova >> tg_lg2;
@@ -2452,9 +2435,6 @@ static bool arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
unsigned int scale;
u8 ttl = 0;
- if (!tlbi->size)
- return false;
-
/*
* Determine what level the granule is at. For non-leaf, both
* io-pgtable and SVA pass a nominal last-level granule because they
@@ -2471,10 +2451,13 @@ static bool arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
if (num_tg == 1) {
if (!ttl)
ttl = 3;
- arm_smmu_cmdq_batch_add_ril(smmu, cmds, cmd, tlbi->leaf_only,
- cur_tg << tg_lg2, 0, 0, ttl,
- tg_enc);
- return true;
+ tlbi->range.data0 = 0;
+ tlbi->range.data1 =
+ FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
+ FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
+ FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
+ (cur_tg << tg_lg2);
+ return;
}
/*
@@ -2493,7 +2476,8 @@ static bool arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
/*
* Range too large for a single command, use full invalidation.
*/
- return false;
+ tlbi->range.use_full_inv = true;
+ return;
}
/* 16K granule TTL=1 is reserved (Section 4.4.1) */
@@ -2504,38 +2488,31 @@ static bool arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
if (ttl && !arm_smmu_ttl_addr_aligned(cur_tg << tg_lg2, tg_lg2, ttl))
ttl = 0;
- arm_smmu_cmdq_batch_add_ril(smmu, cmds, cmd, tlbi->leaf_only,
- cur_tg << tg_lg2,
- DIV_ROUND_UP_ULL(num_tg, 1ULL << scale) - 1,
- scale, ttl, tg_enc);
- return true;
+ tlbi->range.data0 =
+ FIELD_PREP(CMDQ_TLBI_0_NUM,
+ DIV_ROUND_UP_ULL(num_tg, 1ULL << scale) - 1) |
+ FIELD_PREP(CMDQ_TLBI_0_SCALE, scale);
+ tlbi->range.data1 = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
+ FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
+ FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
+ (cur_tg << tg_lg2);
}
/*
* One TLBI command per IOTLB entry, assuming the entries are all at least
- * iopte_granule sized. Returns false if too many commands would be needed which
- * indicates too high a latency. The threshold is similar to MAX_DVM_OPS in
- * arch/arm64/include/asm/tlbflush.h for the 4k PAGE_SIZE.
+ * iopte_granule sized. Sets use_full_inv if too many commands would be needed
+ * which indicates too high a latency. The threshold is similar to MAX_DVM_OPS
+ * in arch/arm64/include/asm/tlbflush.h for the 4k PAGE_SIZE.
*/
-static bool arm_smmu_cmdq_batch_add_single(struct arm_smmu_device *smmu,
- struct arm_smmu_cmdq_batch *cmds,
- struct arm_smmu_cmd *cmd,
- struct arm_smmu_tlbi *tlbi)
+static void arm_smmu_tlbi_calc_single(struct arm_smmu_tlbi *tlbi)
{
unsigned long num_ops = tlbi->size / tlbi->iopte_granule;
- unsigned long iova = tlbi->iova;
- unsigned long i;
- if (!num_ops || num_ops > 512)
- return false;
-
- for (i = 0; i < num_ops; i++) {
- cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
- (iova & ~GENMASK_U64(11, 0));
- arm_smmu_cmdq_batch_add_cmd_p(smmu, cmds, cmd);
- iova += tlbi->iopte_granule;
+ if (!num_ops || num_ops > 512) {
+ tlbi->single.use_full_inv = true;
+ return;
}
- return true;
+ tlbi->single.num = num_ops;
}
static void arm_smmu_inv_all_cmd(struct arm_smmu_inv *inv,
@@ -2555,16 +2532,32 @@ static bool arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
struct arm_smmu_cmd *cmd,
struct arm_smmu_tlbi *tlbi)
{
+ u64 iova = tlbi->iova;
+ unsigned int i;
+
if (inv->smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
- if (arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, tlbi))
- return false;
- } else {
- if (arm_smmu_cmdq_batch_add_single(inv->smmu, cmds, cmd, tlbi))
- return false;
+ if (tlbi->range.use_full_inv) {
+ arm_smmu_inv_all_cmd(inv, cmds, cmd);
+ return true;
+ }
+ cmd->data[0] |= tlbi->range.data0;
+ cmd->data[1] = tlbi->range.data1;
+ arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds, cmd);
+ return false;
}
- arm_smmu_inv_all_cmd(inv, cmds, cmd);
- return true;
+ if (tlbi->single.use_full_inv) {
+ arm_smmu_inv_all_cmd(inv, cmds, cmd);
+ return true;
+ }
+
+ for (i = 0; i < tlbi->single.num; i++) {
+ cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
+ (iova & ~GENMASK_U64(11, 0));
+ iova += tlbi->iopte_granule;
+ arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds, cmd);
+ }
+ return false;
}
static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
@@ -2583,8 +2576,8 @@ static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
return false;
}
-static void __arm_smmu_domain_inv_range(struct arm_smmu_tlbi *tlbi,
- struct arm_smmu_invs *invs)
+static void arm_smmu_domain_tlbi_inv(struct arm_smmu_tlbi *tlbi,
+ struct arm_smmu_invs *invs)
{
struct arm_smmu_cmdq_batch cmds = {};
bool used_s12_vmall = false;
@@ -2681,6 +2674,13 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
};
struct arm_smmu_invs *invs;
+ if (!size || size == SIZE_MAX) {
+ tlbi.single.use_full_inv = true;
+ tlbi.range.use_full_inv = true;
+ } else {
+ arm_smmu_tlbi_calc_single(&tlbi);
+ }
+
/*
* An invalidation request must follow some IOPTE change and then load
* an invalidation array. In the meantime, a domain attachment mutates
@@ -2711,6 +2711,14 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
rcu_read_lock();
invs = rcu_dereference(smmu_domain->invs);
+ /* Only precaculate RIL if it will be used. */
+ if (invs->has_range_inv) {
+ if (!tlbi.range.use_full_inv)
+ arm_smmu_tlbi_calc_range(&tlbi);
+ } else {
+ tlbi.range.use_full_inv = true;
+ }
+
/*
* Avoid locking unless ATS is being used. No ATC invalidation can be
* going on after a domain is detached.
@@ -2719,10 +2727,10 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
unsigned long flags;
read_lock_irqsave(&invs->rwlock, flags);
- __arm_smmu_domain_inv_range(&tlbi, invs);
+ arm_smmu_domain_tlbi_inv(&tlbi, invs);
read_unlock_irqrestore(&invs->rwlock, flags);
} else {
- __arm_smmu_domain_inv_range(&tlbi, invs);
+ arm_smmu_domain_tlbi_inv(&tlbi, invs);
}
rcu_read_unlock();
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 3ef55f8af63d90..9d262ef6076225 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -809,6 +809,17 @@ struct arm_smmu_tlbi {
size_t size;
unsigned int iopte_granule;
bool leaf_only;
+
+ struct {
+ bool use_full_inv;
+ u16 num;
+ } single;
+
+ struct {
+ bool use_full_inv;
+ u32 data0;
+ u64 data1;
+ } range;
};
struct arm_smmu_evtq {
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v2 6/8] iommu/arm-smmu-v3: Populate the tlbi at the top of the call chain
2026-07-06 16:26 [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it Jason Gunthorpe
` (4 preceding siblings ...)
2026-07-06 16:26 ` [PATCH v2 5/8] iommu/arm-smmu-v3: Precompute the invalidation commands Jason Gunthorpe
@ 2026-07-06 16:26 ` Jason Gunthorpe
2026-07-07 11:57 ` Mostafa Saleh
2026-07-06 16:26 ` [PATCH v2 7/8] iommu/arm-smmu-v3: Change how the tlbi describes the invalidation Jason Gunthorpe
` (2 subsequent siblings)
8 siblings, 1 reply; 24+ messages in thread
From: Jason Gunthorpe @ 2026-07-06 16:26 UTC (permalink / raw)
To: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon
Cc: David Matlack, Pasha Tatashin, patches, Pranjal Shrivastava,
Samiullah Khawaja, Mostafa Saleh
Each of these has their own unique situation, populate the tlbi right
at the top and pass it into arm_smmu_domain_inv_range(). They will
diverge further when the iommupt invalidation scheme is introduced.
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
---
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 21 ++++----
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 51 +++++++++----------
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 13 +++--
3 files changed, 45 insertions(+), 40 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 5d4dde3d1cfe87..e6001913e2b043 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
@@ -139,16 +139,19 @@ static void arm_smmu_mm_arch_invalidate_secondary_tlbs(struct mmu_notifier *mn,
{
struct arm_smmu_domain *smmu_domain =
container_of(mn, struct arm_smmu_domain, mmu_notifier);
- size_t size;
+ struct arm_smmu_tlbi tlbi = {
+ .smmu_domain = smmu_domain,
+ .iova = start,
+ /*
+ * The mm_types defines vm_end as the first byte after the end
+ * address, different from IOMMU subsystem using the last
+ * address of an address range.
+ */
+ .size = end - start,
+ .iopte_granule = PAGE_SIZE,
+ };
- /*
- * The mm_types defines vm_end as the first byte after the end address,
- * different from IOMMU subsystem using the last address of an address
- * range. So do a simple translation here by calculating size correctly.
- */
- size = end - start;
-
- arm_smmu_domain_inv_range(smmu_domain, start, size, PAGE_SIZE, false);
+ arm_smmu_domain_tlbi(&tlbi);
}
static void arm_smmu_mm_release(struct mmu_notifier *mn, struct mm_struct *mm)
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 fbe3e5dc42f964..2e477f15080148 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2661,25 +2661,12 @@ static void arm_smmu_domain_tlbi_inv(struct arm_smmu_tlbi *tlbi,
}
}
-void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
- unsigned long iova, size_t size,
- unsigned int granule, bool leaf)
+void arm_smmu_domain_tlbi(struct arm_smmu_tlbi *tlbi)
{
- struct arm_smmu_tlbi tlbi = {
- .smmu_domain = smmu_domain,
- .iova = iova,
- .size = size,
- .iopte_granule = granule,
- .leaf_only = leaf,
- };
struct arm_smmu_invs *invs;
- if (!size || size == SIZE_MAX) {
- tlbi.single.use_full_inv = true;
- tlbi.range.use_full_inv = true;
- } else {
- arm_smmu_tlbi_calc_single(&tlbi);
- }
+ if (!tlbi->single.use_full_inv)
+ arm_smmu_tlbi_calc_single(tlbi);
/*
* An invalidation request must follow some IOPTE change and then load
@@ -2709,14 +2696,14 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
smp_mb();
rcu_read_lock();
- invs = rcu_dereference(smmu_domain->invs);
+ invs = rcu_dereference(tlbi->smmu_domain->invs);
/* Only precaculate RIL if it will be used. */
if (invs->has_range_inv) {
- if (!tlbi.range.use_full_inv)
- arm_smmu_tlbi_calc_range(&tlbi);
+ if (!tlbi->range.use_full_inv)
+ arm_smmu_tlbi_calc_range(tlbi);
} else {
- tlbi.range.use_full_inv = true;
+ tlbi->range.use_full_inv = true;
}
/*
@@ -2727,10 +2714,10 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
unsigned long flags;
read_lock_irqsave(&invs->rwlock, flags);
- arm_smmu_domain_tlbi_inv(&tlbi, invs);
+ arm_smmu_domain_tlbi_inv(tlbi, invs);
read_unlock_irqrestore(&invs->rwlock, flags);
} else {
- arm_smmu_domain_tlbi_inv(&tlbi, invs);
+ arm_smmu_domain_tlbi_inv(tlbi, invs);
}
rcu_read_unlock();
@@ -2750,8 +2737,14 @@ static void arm_smmu_tlb_inv_walk(unsigned long iova, size_t size,
size_t granule, void *cookie)
{
struct arm_smmu_domain *smmu_domain = cookie;
+ struct arm_smmu_tlbi tlbi = {
+ .smmu_domain = smmu_domain,
+ .iova = iova,
+ .size = size,
+ .iopte_granule = granule,
+ };
- arm_smmu_domain_inv_range(smmu_domain, iova, size, granule, false);
+ arm_smmu_domain_tlbi(&tlbi);
}
static const struct iommu_flush_ops arm_smmu_flush_ops = {
@@ -4018,14 +4011,18 @@ static void arm_smmu_flush_iotlb_all(struct iommu_domain *domain)
static void arm_smmu_iotlb_sync(struct iommu_domain *domain,
struct iommu_iotlb_gather *gather)
{
- struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
+ struct arm_smmu_tlbi tlbi = {
+ .smmu_domain = to_smmu_domain(domain),
+ .iova = gather->start,
+ .size = gather->end - gather->start + 1,
+ .iopte_granule = gather->pgsize,
+ .leaf_only = true,
+ };
if (!gather->pgsize)
return;
- arm_smmu_domain_inv_range(smmu_domain, gather->start,
- gather->end - gather->start + 1,
- gather->pgsize, true);
+ arm_smmu_domain_tlbi(&tlbi);
}
static phys_addr_t
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 9d262ef6076225..5f97d1a63ebbfd 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -1169,13 +1169,18 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
struct arm_smmu_domain *smmu_domain, ioasid_t pasid,
struct arm_smmu_cd *cd, struct iommu_domain *old);
-void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
- unsigned long iova, size_t size,
- unsigned int granule, bool leaf);
+void arm_smmu_domain_tlbi(struct arm_smmu_tlbi *tlbi);
static inline void arm_smmu_domain_inv(struct arm_smmu_domain *smmu_domain)
{
- arm_smmu_domain_inv_range(smmu_domain, 0, 0, 0, false);
+ /* Prefilled for invalidate all */
+ struct arm_smmu_tlbi tlbi = {
+ .smmu_domain = smmu_domain,
+ .single.use_full_inv = true,
+ .range.use_full_inv = true,
+ };
+
+ arm_smmu_domain_tlbi(&tlbi);
}
void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu,
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v2 7/8] iommu/arm-smmu-v3: Change how the tlbi describes the invalidation
2026-07-06 16:26 [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it Jason Gunthorpe
` (5 preceding siblings ...)
2026-07-06 16:26 ` [PATCH v2 6/8] iommu/arm-smmu-v3: Populate the tlbi at the top of the call chain Jason Gunthorpe
@ 2026-07-06 16:26 ` Jason Gunthorpe
2026-07-06 18:00 ` Robin Murphy
2026-07-06 16:26 ` [PATCH v2 8/8] iommu/arm-smmu-v3: Support the DS expansion of RIL's SCALE Jason Gunthorpe
2026-07-07 12:25 ` [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it Mostafa Saleh
8 siblings, 1 reply; 24+ messages in thread
From: Jason Gunthorpe @ 2026-07-06 16:26 UTC (permalink / raw)
To: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon
Cc: David Matlack, Pasha Tatashin, patches, Pranjal Shrivastava,
Samiullah Khawaja, Mostafa Saleh
The RIL logic has long had a FIXME that there is not enough
information to properly compute the RIL. There is also subtly not
enough information to properly compute the single stride either.
Change tlbi to use the information format that iommupt is going to
use for ARM. This prepares the invalidation code to support iommupt
and fixes two small limitations with the current code.
iommupt is designed to accumulate all invalidation into a single
gather, then the iommu driver should issue a small number of commands
to execute the gather to control invalidation latency. This is in
contrast to io-pgtable-arm.c which generates many gather flushes and
direct walk cache flushes as it progresses.
To accommodate this the gather will accumulate "damage" in bitmaps,
one for leaf changes and one for table changes. This is enough
information for SMMUv3 to compute the proper stride for single
invalidation and to generate ideal hints for range invalidation.
Change the inner workings of the tlbi process to directly use this
new-style gather description with the idea that the iommupt
conversion will just direct assign the gather fields to the tlbi.
Rework the three places creating the tlbi to express their needs in
terms of the new bitmaps.
1) Simple iotlb invalidation always gets a single range of leaf
levels, so it can set a single leaf bit
2) Walk invalidation always gets a single table level so it can set a
single table bit.
This corrects a weakness in the existing design where single
invalidation would walk the entire table level issuing 4k
invalidations, now it will just push a single invalidation.
3) SVA invalidation has no idea what the MM did, so it will set all
the bits in the bitmaps.
This corrects another weakness where the RIL invalidation logic
was generating hints assuming the #2 rules which isn't correct
for SVA.
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
---
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 28 ++-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 163 +++++++++++++-----
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 20 ++-
3 files changed, 157 insertions(+), 54 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 e6001913e2b043..0e670c92469b2f 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
@@ -139,17 +139,33 @@ static void arm_smmu_mm_arch_invalidate_secondary_tlbs(struct mmu_notifier *mn,
{
struct arm_smmu_domain *smmu_domain =
container_of(mn, struct arm_smmu_domain, mmu_notifier);
+ unsigned int tg_lg2 = smmu_domain->tgsz_lg2;
struct arm_smmu_tlbi tlbi = {
.smmu_domain = smmu_domain,
- .iova = start,
+ .start = start,
+ .last = end - 1,
/*
- * The mm_types defines vm_end as the first byte after the end
- * address, different from IOMMU subsystem using the last
- * address of an address range.
+ * No information comes from the mm, assume the worst case that
+ * it changed every table level. The way this is hooked into the
+ * mm is tricky, the range won't be expanded to include an
+ * entire table level if one was removed like the iommu gather
+ * does. Thus even if this is a 4k invalidation it may be
+ * including any table level too.
*/
- .size = end - start,
- .iopte_granule = PAGE_SIZE,
+ .table_levels_bitmap = 0xfe,
};
+ unsigned int pmd_lg2sz = (tg_lg2 - 3) * 1 + tg_lg2;
+
+ /*
+ * If the size is small then we can infer the invalidation is PTE only
+ * and set the PTE level only. Otherwise it could be some other
+ * combination so just set them all. This allows RIL to use TTL=3 in
+ * cases of PTE only changes.
+ */
+ if (end - start < BIT_U64(pmd_lg2sz))
+ tlbi.leaf_levels_bitmap = 1;
+ else
+ tlbi.leaf_levels_bitmap = 0xff;
arm_smmu_domain_tlbi(&tlbi);
}
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 2e477f15080148..a15eaa99c24121 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -2331,8 +2331,8 @@ static struct arm_smmu_cmd arm_smmu_atc_inv_to_cmd(u32 sid, int ssid,
* This has the unpleasant side-effect of invalidating all PASID-tagged
* ATC entries within the address range.
*/
- page_start = tlbi->iova >> inval_grain_shift;
- page_end = (tlbi->iova + tlbi->size - 1) >> inval_grain_shift;
+ page_start = tlbi->start >> inval_grain_shift;
+ page_end = tlbi->last >> inval_grain_shift;
/*
* In an ATS Invalidate Request, the address must be aligned on the
@@ -2418,7 +2418,49 @@ static bool arm_smmu_ttl_addr_aligned(u64 address, unsigned int tg,
}
/*
- * Generate a single range TLBI command covering [iova, iova+size). Sets
+ * Compute the TTL hint from leaf/table level bitmaps. 0 ttlt means no hint
+ * invalidate all levels.
+ */
+static unsigned int arm_smmu_compute_ttl(u8 leaf_bitmap, u8 table_bitmap,
+ unsigned int tg)
+{
+ int ttl;
+
+ if (leaf_bitmap) {
+ if (is_power_of_2(leaf_bitmap))
+ ttl = 3 - (int)__ffs(leaf_bitmap);
+ else
+ ttl = 0;
+
+ if (table_bitmap) {
+ int table_ttl = 3 - (int)__ffs(table_bitmap) + 1;
+
+ /*
+ * A RIL invalidation with !leaf_only clears out all
+ * table levels above the leaf level ttl only.
+ */
+ if (table_ttl > ttl)
+ ttl = 0;
+ }
+ } else if (table_bitmap) {
+ ttl = 3 - (int)__ffs(table_bitmap) + 1;
+ } else {
+ /* Both bitmaps zero is not allowed */
+ return 0;
+ }
+
+ /* 16K granule, ARM TTL=1 is reserved (SMMUv3 F.b Section 4.4.1) */
+ if (tg == 14 && ttl == 1)
+ return 0;
+
+ /* ARM levels -1 and 0 cannot be hinted */
+ if (ttl <= 0 || ttl > 3)
+ return 0;
+ return ttl;
+}
+
+/*
+ * Generate a single range TLBI command covering [start, last]. Sets
* use_full_inv if the range is too large for a single command.
*
* The algorithm finds the smallest SCALE where the range (in tg-sized pages)
@@ -2428,20 +2470,13 @@ static bool arm_smmu_ttl_addr_aligned(u64 address, unsigned int tg,
static void arm_smmu_tlbi_calc_range(struct arm_smmu_tlbi *tlbi)
{
unsigned int tg_lg2 = tlbi->smmu_domain->tgsz_lg2;
- u64 cur_tg = tlbi->iova >> tg_lg2;
- u64 last_tg = (tlbi->iova + tlbi->size - 1) >> tg_lg2;
+ unsigned int ttl = arm_smmu_compute_ttl(
+ tlbi->leaf_levels_bitmap, tlbi->table_levels_bitmap, tg_lg2);
+ u64 cur_tg = tlbi->start >> tg_lg2;
+ u64 last_tg = tlbi->last >> tg_lg2;
u64 num_tg = last_tg - cur_tg + 1;
u8 tg_enc = (tg_lg2 - 10) / 2;
unsigned int scale;
- u8 ttl = 0;
-
- /*
- * Determine what level the granule is at. For non-leaf, both
- * io-pgtable and SVA pass a nominal last-level granule because they
- * don't know what level(s) actually apply, so leave TTL=0.
- */
- if (tlbi->leaf_only)
- ttl = 4 - ((ilog2(tlbi->iopte_granule) - 3) / (tg_lg2 - 3));
/*
* SMMUv3 F.b Section 4.4.1: TG!=0, NUM==0, SCALE==0, TTL==0 is
@@ -2449,14 +2484,18 @@ static void arm_smmu_tlbi_calc_range(struct arm_smmu_tlbi *tlbi)
* a TTL hint to target only the exact leaf entry.
*/
if (num_tg == 1) {
- if (!ttl)
+ /*
+ * The two io-pgtable ops filling the tlbi won't generate ttl=0.
+ * sva sets constants for single page that give ttl=3
+ */
+ if (WARN_ON(!ttl))
ttl = 3;
tlbi->range.data0 = 0;
- tlbi->range.data1 =
- FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
- FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
- FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
- (cur_tg << tg_lg2);
+ tlbi->range.data1 = FIELD_PREP(CMDQ_TLBI_1_LEAF,
+ !tlbi->table_levels_bitmap) |
+ FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
+ FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
+ (cur_tg << tg_lg2);
return;
}
@@ -2480,10 +2519,6 @@ static void arm_smmu_tlbi_calc_range(struct arm_smmu_tlbi *tlbi)
return;
}
- /* 16K granule TTL=1 is reserved (Section 4.4.1) */
- if (tg_lg2 == 14 && ttl == 1)
- ttl = 0;
-
/* Verify address alignment for the TTL hint */
if (ttl && !arm_smmu_ttl_addr_aligned(cur_tg << tg_lg2, tg_lg2, ttl))
ttl = 0;
@@ -2492,27 +2527,52 @@ static void arm_smmu_tlbi_calc_range(struct arm_smmu_tlbi *tlbi)
FIELD_PREP(CMDQ_TLBI_0_NUM,
DIV_ROUND_UP_ULL(num_tg, 1ULL << scale) - 1) |
FIELD_PREP(CMDQ_TLBI_0_SCALE, scale);
- tlbi->range.data1 = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
- FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
- FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
- (cur_tg << tg_lg2);
+ tlbi->range.data1 =
+ FIELD_PREP(CMDQ_TLBI_1_LEAF, !tlbi->table_levels_bitmap) |
+ FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
+ FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
+ (cur_tg << tg_lg2);
}
/*
- * One TLBI command per IOTLB entry, assuming the entries are all at least
- * iopte_granule sized. Sets use_full_inv if too many commands would be needed
- * which indicates too high a latency. The threshold is similar to MAX_DVM_OPS
- * in arch/arm64/include/asm/tlbflush.h for the 4k PAGE_SIZE.
+ * Compute the stride for non-RIL single-page invalidation. Returns the log2
+ * stride of the lowest affected level. Single invalidation removes all IOPTEs
+ * that contain the IOVA invalidated, and we can reliably assume that the
+ * architected page size and table sizes (not contiguous!) are reflected in the
+ * IOTLB. Thus if there is a 2M leaf entry we only need to issue a single IOTLB
+ * invalidation within that 2M IOVA.
+ */
+static u8 arm_smmu_tlbi_calc_stride(struct arm_smmu_tlbi *tlbi)
+{
+ unsigned int tg_lg2 = tlbi->smmu_domain->tgsz_lg2;
+ u8 combined = tlbi->table_levels_bitmap | tlbi->leaf_levels_bitmap;
+
+ if (!combined)
+ return U8_MAX;
+ return (tg_lg2 - 3) * __ffs(combined) + tg_lg2;
+}
+
+/*
+ * One TLBI command per stride-sized entry. Sets use_full_inv if too many
+ * commands would be needed. The threshold is similar to MAX_DVM_OPS in
+ * arch/arm64/include/asm/tlbflush.h.
*/
static void arm_smmu_tlbi_calc_single(struct arm_smmu_tlbi *tlbi)
{
- unsigned long num_ops = tlbi->size / tlbi->iopte_granule;
+ u8 stride_lg2 = arm_smmu_tlbi_calc_stride(tlbi);
+ unsigned long num_ops;
+ if (stride_lg2 == U8_MAX) {
+ tlbi->single.use_full_inv = true;
+ return;
+ }
+ num_ops = (tlbi->last - tlbi->start + 1) >> stride_lg2;
if (!num_ops || num_ops > 512) {
tlbi->single.use_full_inv = true;
return;
}
tlbi->single.num = num_ops;
+ tlbi->single.stride_lg2 = stride_lg2;
}
static void arm_smmu_inv_all_cmd(struct arm_smmu_inv *inv,
@@ -2532,7 +2592,7 @@ static bool arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
struct arm_smmu_cmd *cmd,
struct arm_smmu_tlbi *tlbi)
{
- u64 iova = tlbi->iova;
+ u64 iova = tlbi->start;
unsigned int i;
if (inv->smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
@@ -2552,9 +2612,10 @@ static bool arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
}
for (i = 0; i < tlbi->single.num; i++) {
- cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
+ cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF,
+ !tlbi->table_levels_bitmap) |
(iova & ~GENMASK_U64(11, 0));
- iova += tlbi->iopte_granule;
+ iova += BIT_U64(tlbi->single.stride_lg2);
arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds, cmd);
}
return false;
@@ -2733,15 +2794,21 @@ static void arm_smmu_tlb_inv_page_nosync(struct iommu_iotlb_gather *gather,
iommu_iotlb_gather_add_page(domain, gather, iova, granule);
}
+/*
+ * Called by io-pgtable-arm.c for each single table level it wants to remove.
+ * size is the size of the table level and granule is the tg in bytes.
+ */
static void arm_smmu_tlb_inv_walk(unsigned long iova, size_t size,
size_t granule, void *cookie)
{
struct arm_smmu_domain *smmu_domain = cookie;
+ unsigned int tg_lg2 = smmu_domain->tgsz_lg2;
struct arm_smmu_tlbi tlbi = {
.smmu_domain = smmu_domain,
- .iova = iova,
- .size = size,
- .iopte_granule = granule,
+ .start = iova,
+ .last = iova + size - 1,
+ .table_levels_bitmap =
+ BIT((ilog2(size) - tg_lg2) / (tg_lg2 - 3)),
};
arm_smmu_domain_tlbi(&tlbi);
@@ -4008,15 +4075,23 @@ static void arm_smmu_flush_iotlb_all(struct iommu_domain *domain)
arm_smmu_tlb_inv_context(smmu_domain);
}
+/*
+ * Called by io-pgtable-arm.c for each run of same pgsize leaf only
+ * invalidation. If it has to change to a different leaf level then it flushes
+ * the gather and starts a fresh one. Thus this always targets only a single
+ * leaf level.
+ */
static void arm_smmu_iotlb_sync(struct iommu_domain *domain,
struct iommu_iotlb_gather *gather)
{
+ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
+ unsigned int tg = smmu_domain->tgsz_lg2;
struct arm_smmu_tlbi tlbi = {
- .smmu_domain = to_smmu_domain(domain),
- .iova = gather->start,
- .size = gather->end - gather->start + 1,
- .iopte_granule = gather->pgsize,
- .leaf_only = true,
+ .smmu_domain = smmu_domain,
+ .start = gather->start,
+ .last = gather->end,
+ .leaf_levels_bitmap =
+ BIT((ilog2(gather->pgsize) - tg) / (tg - 3)),
};
if (!gather->pgsize)
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 5f97d1a63ebbfd..b57205a83128e9 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -805,14 +805,26 @@ static inline struct arm_smmu_invs *arm_smmu_invs_alloc(size_t num_invs)
struct arm_smmu_tlbi {
struct arm_smmu_domain *smmu_domain;
- unsigned long iova;
- size_t size;
- unsigned int iopte_granule;
- bool leaf_only;
+ unsigned long start;
+ unsigned long last;
+ /*
+ * Level bitmaps use iommupt numbering: bit 0 is the leaf-only level
+ * (ARM level 3), bit 1 is the next level up (ARM level 2), etc. These
+ * match the iommu_iotlb_gather.pt fields. Each set bit indicates a
+ * change at that level. The contiguous hint has no effect on
+ * invalidation processing because HW can ignore the hint.
+ *
+ * If leaf_levels_bitmap is 0 then this is a walk cache only
+ * invalidation. If table_levels_bitmap is 0 then this is a leaf only
+ * invalidation.
+ */
+ u8 leaf_levels_bitmap;
+ u8 table_levels_bitmap;
struct {
bool use_full_inv;
u16 num;
+ u8 stride_lg2;
} single;
struct {
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v2 8/8] iommu/arm-smmu-v3: Support the DS expansion of RIL's SCALE
2026-07-06 16:26 [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it Jason Gunthorpe
` (6 preceding siblings ...)
2026-07-06 16:26 ` [PATCH v2 7/8] iommu/arm-smmu-v3: Change how the tlbi describes the invalidation Jason Gunthorpe
@ 2026-07-06 16:26 ` Jason Gunthorpe
2026-07-07 12:25 ` [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it Mostafa Saleh
8 siblings, 0 replies; 24+ messages in thread
From: Jason Gunthorpe @ 2026-07-06 16:26 UTC (permalink / raw)
To: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon
Cc: David Matlack, Pasha Tatashin, patches, Pranjal Shrivastava,
Samiullah Khawaja, Mostafa Saleh
If DS is supported then SCALE can go up to 39. Detect the IDR and compute
a scale max that is compatible for the entire invs list.
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 16 +++++++++++++---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 6 +++++-
2 files changed, 18 insertions(+), 4 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 a15eaa99c24121..5087603ea18e62 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -991,6 +991,8 @@ static inline int arm_smmu_invs_iter_next_cmp(struct arm_smmu_invs *invs_l,
static void arm_smmu_invs_update_caps(struct arm_smmu_invs *invs,
const struct arm_smmu_inv *inv)
{
+ unsigned int scale_max;
+
if (arm_smmu_inv_is_ats(inv))
invs->has_ats = true;
@@ -998,6 +1000,9 @@ static void arm_smmu_invs_update_caps(struct arm_smmu_invs *invs,
return;
invs->has_range_inv = true;
+ scale_max = (inv->smmu->features & ARM_SMMU_FEAT_DS) ? 39 : 31;
+ if (!invs->range_inv_scale_max || scale_max < invs->range_inv_scale_max)
+ invs->range_inv_scale_max = scale_max;
}
/**
@@ -2467,7 +2472,8 @@ static unsigned int arm_smmu_compute_ttl(u8 leaf_bitmap, u8 table_bitmap,
* fits in the 5-bit NUM field (max 32 units of 2^SCALE pages). This may widen
* the invalidation range.
*/
-static void arm_smmu_tlbi_calc_range(struct arm_smmu_tlbi *tlbi)
+static void arm_smmu_tlbi_calc_range(struct arm_smmu_tlbi *tlbi,
+ unsigned int scale_max)
{
unsigned int tg_lg2 = tlbi->smmu_domain->tgsz_lg2;
unsigned int ttl = arm_smmu_compute_ttl(
@@ -2511,7 +2517,7 @@ static void arm_smmu_tlbi_calc_range(struct arm_smmu_tlbi *tlbi)
* on the address beyond it must be aligned to tg (so long as TTL=0)
*/
scale = fls64((num_tg - 1) / 32);
- if (scale > 31) {
+ if (scale > scale_max) {
/*
* Range too large for a single command, use full invalidation.
*/
@@ -2762,7 +2768,8 @@ void arm_smmu_domain_tlbi(struct arm_smmu_tlbi *tlbi)
/* Only precaculate RIL if it will be used. */
if (invs->has_range_inv) {
if (!tlbi->range.use_full_inv)
- arm_smmu_tlbi_calc_range(tlbi);
+ arm_smmu_tlbi_calc_range(tlbi,
+ invs->range_inv_scale_max);
} else {
tlbi->range.use_full_inv = true;
}
@@ -5235,6 +5242,9 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
/* Maximum number of outstanding stalls */
smmu->evtq.max_stalls = FIELD_GET(IDR5_STALL_MAX, reg);
+ if (reg & IDR5_DS)
+ smmu->features |= ARM_SMMU_FEAT_DS;
+
/* Page sizes */
if (reg & IDR5_GRAN64K)
smmu->pgsize_bitmap |= SZ_64K | SZ_512M;
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 b57205a83128e9..95186cf84e9abb 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -64,6 +64,7 @@ struct arm_vsmmu;
#define ARM_SMMU_IDR5 0x14
#define IDR5_STALL_MAX GENMASK(31, 16)
+#define IDR5_DS (1 << 7)
#define IDR5_GRAN64K (1 << 6)
#define IDR5_GRAN16K (1 << 5)
#define IDR5_GRAN4K (1 << 4)
@@ -415,7 +416,7 @@ struct arm_smmu_cmd {
#define CMDQ_TLBI_0_NUM GENMASK_ULL(16, 12)
#define CMDQ_TLBI_RANGE_NUM_MAX 31
-#define CMDQ_TLBI_0_SCALE GENMASK_ULL(24, 20)
+#define CMDQ_TLBI_0_SCALE GENMASK_ULL(25, 20)
#define CMDQ_TLBI_0_VMID GENMASK_ULL(47, 32)
#define CMDQ_TLBI_0_ASID GENMASK_ULL(63, 48)
#define CMDQ_TLBI_1_LEAF (1UL << 0)
@@ -756,6 +757,7 @@ static inline bool arm_smmu_inv_is_ats(const struct arm_smmu_inv *inv)
* @rwlock: optional rwlock to fence ATS operations
* @has_ats: flag if the array contains an INV_TYPE_ATS or INV_TYPE_ATS_FULL
* @has_range_inv: flag if any entry's SMMU supports range invalidation
+ * @range_inv_scale_max: max SCALE usable by all range-capable SMMUs
* @rcu: rcu head for kfree_rcu()
* @inv: flexible invalidation array
*
@@ -786,6 +788,7 @@ struct arm_smmu_invs {
rwlock_t rwlock;
bool has_ats;
bool has_range_inv;
+ u8 range_inv_scale_max;
struct rcu_head rcu;
struct arm_smmu_inv inv[] __counted_by(max_invs);
};
@@ -953,6 +956,7 @@ struct arm_smmu_device {
#define ARM_SMMU_FEAT_HD (1 << 22)
#define ARM_SMMU_FEAT_S2FWB (1 << 23)
#define ARM_SMMU_FEAT_BBML2 (1 << 24)
+#define ARM_SMMU_FEAT_DS (1 << 25)
u32 features;
#define ARM_SMMU_OPT_SKIP_PREFETCH (1 << 0)
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH v2 7/8] iommu/arm-smmu-v3: Change how the tlbi describes the invalidation
2026-07-06 16:26 ` [PATCH v2 7/8] iommu/arm-smmu-v3: Change how the tlbi describes the invalidation Jason Gunthorpe
@ 2026-07-06 18:00 ` Robin Murphy
2026-07-06 19:45 ` Jason Gunthorpe
0 siblings, 1 reply; 24+ messages in thread
From: Robin Murphy @ 2026-07-06 18:00 UTC (permalink / raw)
To: Jason Gunthorpe, iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Will Deacon
Cc: David Matlack, Pasha Tatashin, patches, Pranjal Shrivastava,
Samiullah Khawaja, Mostafa Saleh
On 2026-07-06 5:26 pm, Jason Gunthorpe wrote:
[...]
> /*
> - * Generate a single range TLBI command covering [iova, iova+size). Sets
> + * Compute the TTL hint from leaf/table level bitmaps. 0 ttlt means no hint
> + * invalidate all levels.
> + */
> +static unsigned int arm_smmu_compute_ttl(u8 leaf_bitmap, u8 table_bitmap,
> + unsigned int tg)
> +{
> + int ttl;
> +
> + if (leaf_bitmap) {
> + if (is_power_of_2(leaf_bitmap))
> + ttl = 3 - (int)__ffs(leaf_bitmap);
> + else
> + ttl = 0;
> +
> + if (table_bitmap) {
> + int table_ttl = 3 - (int)__ffs(table_bitmap) + 1;
> +
> + /*
> + * A RIL invalidation with !leaf_only clears out all
> + * table levels above the leaf level ttl only.
> + */
> + if (table_ttl > ttl)
> + ttl = 0;
> + }
> + } else if (table_bitmap) {
> + ttl = 3 - (int)__ffs(table_bitmap) + 1;
Maybe I'm misunderstanding what table_bitmap represents, but whichever way:
- if this case means purely changes to table (i.e. non-leaf) PTEs
themselves, then calculating any leaf level is pretty pointless.
- conversely if it means to an invalidate an entire table worth of leaf
PTEs at once, then L1 tables could contain a mix of both L2 and L3
leaves, so a single level is not necessarily sufficient.
- at best, if it's the latter but you'd be generating separate
invalidations for each individual sub-table from the bottom up, such
that there would only be exactly one table_bitmap level per
invalidation, isn't that pretty inefficient?
> + } else {
> + /* Both bitmaps zero is not allowed */
> + return 0;
> + }
> +
> + /* 16K granule, ARM TTL=1 is reserved (SMMUv3 F.b Section 4.4.1) */
> + if (tg == 14 && ttl == 1)
> + return 0;
It's reserved in the absence of LPA2, i.e. when DS=0 (side note, please
refer to an up-to-date version of the architecture - F.b is pretty old
by now) because the 16K format can only have L1 block entries when using
52-bit VA. If between the caller and the code above we can calculate
that a block entry exists where it cannot, then something is wrong and
needs fixing properly.
> + /* ARM levels -1 and 0 cannot be hinted */
> + if (ttl <= 0 || ttl > 3)
> + return 0;
Similarly, no format allows blocks at level -1, so again if that check
ever did anything we'd already have bigger problems. In the remaining
case, 4KB with 52-bit VA *does* permit blocks at level 0, but it should
hopefully be obvious why that doesn't need special treatment here either...
Thanks,
Robin.
> + return ttl;
> +}
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 7/8] iommu/arm-smmu-v3: Change how the tlbi describes the invalidation
2026-07-06 18:00 ` Robin Murphy
@ 2026-07-06 19:45 ` Jason Gunthorpe
0 siblings, 0 replies; 24+ messages in thread
From: Jason Gunthorpe @ 2026-07-06 19:45 UTC (permalink / raw)
To: Robin Murphy
Cc: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Will Deacon, David Matlack, Pasha Tatashin,
patches, Pranjal Shrivastava, Samiullah Khawaja, Mostafa Saleh
On Mon, Jul 06, 2026 at 07:00:15PM +0100, Robin Murphy wrote:
> On 2026-07-06 5:26 pm, Jason Gunthorpe wrote:
> [...]
> > /*
> > - * Generate a single range TLBI command covering [iova, iova+size). Sets
> > + * Compute the TTL hint from leaf/table level bitmaps. 0 ttlt means no hint
> > + * invalidate all levels.
> > + */
> > +static unsigned int arm_smmu_compute_ttl(u8 leaf_bitmap, u8 table_bitmap,
> > + unsigned int tg)
> > +{
> > + int ttl;
> > +
> > + if (leaf_bitmap) {
> > + if (is_power_of_2(leaf_bitmap))
> > + ttl = 3 - (int)__ffs(leaf_bitmap);
> > + else
> > + ttl = 0;
> > +
> > + if (table_bitmap) {
> > + int table_ttl = 3 - (int)__ffs(table_bitmap) + 1;
> > +
> > + /*
> > + * A RIL invalidation with !leaf_only clears out all
> > + * table levels above the leaf level ttl only.
> > + */
> > + if (table_ttl > ttl)
> > + ttl = 0;
> > + }
> > + } else if (table_bitmap) {
> > + ttl = 3 - (int)__ffs(table_bitmap) + 1;
>
> Maybe I'm misunderstanding what table_bitmap represents, but whichever way:
> - if this case means purely changes to table (i.e. non-leaf) PTEs
> themselves, then calculating any leaf level is pretty pointless.
Yeah, it means this.
RIL doesn't have a table-only mode, it always includes leaf
invalidation. So my reasoning is the best RIL to form has a TTL which
hints the fewest leaves, meaning the leaf level below the target
tables.
I guess 0/no-hint is the only other choice, do you think it is better?
FWIW, a future feature I've talked about to remove unused tables would
generate table only gathers, but currently it should be impossible.
> > + /* 16K granule, ARM TTL=1 is reserved (SMMUv3 F.b Section 4.4.1) */
> > + if (tg == 14 && ttl == 1)
> > + return 0;
>
> It's reserved in the absence of LPA2, i.e. when DS=0 (side note, please
> refer to an up-to-date version of the architecture - F.b is pretty old by
> now)
Oh, I see the note is ment to be read that TTL=0b01 is legal when DS=1
and works normally..
> VA. If between the caller and the code above we can calculate that a block
> entry exists where it cannot, then something is wrong and needs fixing
> properly.
Yes for leaves, but, the above is mixing tables into this as well, so
at this point there can be ttl's for any point in the tree except the
top most level, hence the check.
If table-only changes to use TTL=0 then this could be a WARN_ON to
detect malformed gathers.
> > + /* ARM levels -1 and 0 cannot be hinted */
> > + if (ttl <= 0 || ttl > 3)
> > + return 0;
>
> Similarly, no format allows blocks at level -1, so again if that check ever
> did anything we'd already have bigger problems. In the remaining case, 4KB
> with 52-bit VA *does* permit blocks at level 0, but it should hopefully be
> obvious why that doesn't need special treatment here either...
Yes, the -1 test can be WARN_ON as it is a malformed gather.
The 0 level is just a comment than it isn't actually a hint
anymore. I'll adjust them
Thanks,
Jason
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 1/8] iommu/arm-smmu-v3: Pass the parameters for the invalidation in a struct
2026-07-06 16:26 ` [PATCH v2 1/8] iommu/arm-smmu-v3: Pass the parameters for the invalidation in a struct Jason Gunthorpe
@ 2026-07-07 3:04 ` Nicolin Chen
2026-07-07 11:18 ` Mostafa Saleh
1 sibling, 0 replies; 24+ messages in thread
From: Nicolin Chen @ 2026-07-07 3:04 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon, David Matlack,
Pasha Tatashin, patches, Pranjal Shrivastava, Samiullah Khawaja,
Mostafa Saleh
On Mon, Jul 06, 2026 at 01:26:38PM -0300, Jason Gunthorpe wrote:
> These parameters go to a lot of different functions and the next
> patches will add more. Put them into a struct to keep things tidy.
>
> Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 2/8] iommu/arm-smmu-v3: Move pgsize out of arm_smmu_inv
2026-07-06 16:26 ` [PATCH v2 2/8] iommu/arm-smmu-v3: Move pgsize out of arm_smmu_inv Jason Gunthorpe
@ 2026-07-07 3:57 ` Nicolin Chen
2026-07-07 11:24 ` Mostafa Saleh
1 sibling, 0 replies; 24+ messages in thread
From: Nicolin Chen @ 2026-07-07 3:57 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon, David Matlack,
Pasha Tatashin, patches, Pranjal Shrivastava, Samiullah Khawaja,
Mostafa Saleh
On Mon, Jul 06, 2026 at 01:26:39PM -0300, Jason Gunthorpe wrote:
> pgsize is a constant property of the domain, it is the base translation
> granule of the page table (4k, 16k, 64k) in log2.
>
> Store it to the struct arm_smmu_domain based on how the page table was created.
Checkpatch warns this for "> 75 chars".
> Add smmu_domain to the tlbi and just get tg from the domain.
>
> Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
> 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 a364d847c22a92..2fc695817671fe 100644
> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
> @@ -734,7 +734,6 @@ struct arm_smmu_inv {
> u8 nsize_opcode;
> u32 id; /* ASID or VMID or SID */
> union {
> - size_t pgsize; /* ARM_SMMU_FEAT_RANGE_INV */
> u32 ssid; /* INV_TYPE_ATS */
> };
So, union has only "ssid" now. Should we unwrap it?
Nicolin
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 3/8] iommu/arm-smmu-v3: Optimize range invalidation for latency
2026-07-06 16:26 ` [PATCH v2 3/8] iommu/arm-smmu-v3: Optimize range invalidation for latency Jason Gunthorpe
@ 2026-07-07 7:27 ` Nicolin Chen
2026-07-07 11:45 ` Mostafa Saleh
1 sibling, 0 replies; 24+ messages in thread
From: Nicolin Chen @ 2026-07-07 7:27 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon, David Matlack,
Pasha Tatashin, patches, Pranjal Shrivastava, Samiullah Khawaja,
Mostafa Saleh
On Mon, Jul 06, 2026 at 01:26:40PM -0300, Jason Gunthorpe wrote:
> + /*
> + * There are at most 5 possible values for NUM based on SCALE. The
Mind elaborating the "at most 5 possible values for NUM"?
> + * highest NUM is at the lowest SCALE where:
> + * ceil(num_tg / 2^SCALE) <= 32
> + * scale >= ceil(log2(num_tg / 32))
> + * The lowest value is 1 where 2^SCALE covers the whole range. Pick the
> + * highest since it trivially also gives the tightest range.
The spec only mentions:
Range = ((NUM+1) * 2^SCALE) * Translation_Granule_Size
Since CMDQ_TLBI_RANGE_NUM_MAX + 1 is 32, so we can get:
num_tg <= 32 * 2^SCALE
.. and then get that "ceil(num_tg / 2^SCALE) <= 32".
Maybe worth adding the first two steps to help readability?
Nicolin
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 4/8] iommu/arm-smmu-v3: Keep track in the arm_smmu_invs if RIL is used
2026-07-06 16:26 ` [PATCH v2 4/8] iommu/arm-smmu-v3: Keep track in the arm_smmu_invs if RIL is used Jason Gunthorpe
@ 2026-07-07 7:27 ` Nicolin Chen
2026-07-07 11:46 ` Mostafa Saleh
1 sibling, 0 replies; 24+ messages in thread
From: Nicolin Chen @ 2026-07-07 7:27 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon, David Matlack,
Pasha Tatashin, patches, Pranjal Shrivastava, Samiullah Khawaja,
Mostafa Saleh
On Mon, Jul 06, 2026 at 01:26:41PM -0300, Jason Gunthorpe wrote:
> Summarize if any of the inv entries will use RIL. The next patch will use
> this to avoid RIL pre-calculations unless RIL is being used by the
> invalidation.
>
> Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 1/8] iommu/arm-smmu-v3: Pass the parameters for the invalidation in a struct
2026-07-06 16:26 ` [PATCH v2 1/8] iommu/arm-smmu-v3: Pass the parameters for the invalidation in a struct Jason Gunthorpe
2026-07-07 3:04 ` Nicolin Chen
@ 2026-07-07 11:18 ` Mostafa Saleh
1 sibling, 0 replies; 24+ messages in thread
From: Mostafa Saleh @ 2026-07-07 11:18 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon, David Matlack,
Pasha Tatashin, patches, Pranjal Shrivastava, Samiullah Khawaja
On Mon, Jul 06, 2026 at 01:26:38PM -0300, Jason Gunthorpe wrote:
> These parameters go to a lot of different functions and the next
> patches will add more. Put them into a struct to keep things tidy.
>
> Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
Reviewed-by: Mostafa Saleh <smostafa@google.com>
Thanks,
Mostafa
> ---
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 75 +++++++++++----------
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 7 ++
> 2 files changed, 46 insertions(+), 36 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 a10affb483a4fe..0c875771b46d2f 100644
> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> @@ -2298,8 +2298,8 @@ static irqreturn_t arm_smmu_combined_irq_handler(int irq, void *dev)
> return IRQ_WAKE_THREAD;
> }
>
> -static struct arm_smmu_cmd
> -arm_smmu_atc_inv_to_cmd(u32 sid, int ssid, unsigned long iova, size_t size)
> +static struct arm_smmu_cmd arm_smmu_atc_inv_to_cmd(u32 sid, int ssid,
> + struct arm_smmu_tlbi *tlbi)
> {
> size_t log2_span;
> size_t span_mask;
> @@ -2321,8 +2321,8 @@ arm_smmu_atc_inv_to_cmd(u32 sid, int ssid, unsigned long iova, size_t size)
> * This has the unpleasant side-effect of invalidating all PASID-tagged
> * ATC entries within the address range.
> */
> - page_start = iova >> inval_grain_shift;
> - page_end = (iova + size - 1) >> inval_grain_shift;
> + page_start = tlbi->iova >> inval_grain_shift;
> + page_end = (tlbi->iova + tlbi->size - 1) >> inval_grain_shift;
>
> /*
> * In an ATS Invalidate Request, the address must be aligned on the
> @@ -2397,20 +2397,23 @@ static void arm_smmu_tlb_inv_context(void *cookie)
>
> static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
> struct arm_smmu_cmdq_batch *cmds,
> - struct arm_smmu_cmd *cmd, bool leaf,
> - unsigned long iova, size_t size,
> - size_t granule, size_t pgsize)
> + struct arm_smmu_cmd *cmd,
> + struct arm_smmu_tlbi *tlbi,
> + size_t pgsize)
> {
> - unsigned long end = iova + size, num_pages = 0, tg = pgsize;
> + size_t inv_range = tlbi->iopte_granule;
> + unsigned long iova = tlbi->iova;
> + unsigned long end = iova + tlbi->size;
> + unsigned long num_pages = 0;
> + unsigned int tg = pgsize;
> u64 orig_data0 = cmd->data[0];
> - size_t inv_range = granule;
> u8 ttl = 0, tg_enc = 0;
>
> - if (WARN_ON_ONCE(!size))
> + if (WARN_ON_ONCE(!tlbi->size))
> return;
>
> if (smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
> - num_pages = size >> tg;
> + num_pages = tlbi->size >> tg;
>
> /* Convert page size of 12,14,16 (log2) to 1,2,3 */
> tg_enc = (tg - 10) / 2;
> @@ -2423,8 +2426,8 @@ static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
> * want to use a range command, so avoid the SVA corner case
> * where both scale and num could be 0 as well.
> */
> - if (leaf)
> - ttl = 4 - ((ilog2(granule) - 3) / (tg - 3));
> + if (tlbi->leaf_only)
> + ttl = 4 - ((ilog2(tlbi->iopte_granule) - 3) / (tg - 3));
> else if ((num_pages & CMDQ_TLBI_RANGE_NUM_MAX) == 1)
> num_pages++;
> }
> @@ -2462,7 +2465,7 @@ static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
> * command and something would be very broken if iova had them
> * set.
> */
> - cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, leaf) |
> + cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, tlbi->leaf_only) |
> FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |
> FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |
> (iova & ~GENMASK_U64(11, 0));
> @@ -2472,13 +2475,13 @@ static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
> }
> }
>
> -static bool arm_smmu_inv_size_too_big(struct arm_smmu_device *smmu, size_t size,
> - size_t granule)
> +static bool arm_smmu_inv_size_too_big(struct arm_smmu_device *smmu,
> + struct arm_smmu_tlbi *tlbi)
> {
> size_t max_tlbi_ops;
>
> /* 0 size means invalidate all */
> - if (!size || size == SIZE_MAX)
> + if (!tlbi->size || tlbi->size == SIZE_MAX)
> return true;
>
> if (smmu->features & ARM_SMMU_FEAT_RANGE_INV)
> @@ -2491,19 +2494,17 @@ static bool arm_smmu_inv_size_too_big(struct arm_smmu_device *smmu, size_t size,
> * invalidation feature, where there can be too many per-granule TLBIs,
> * resulting in a soft lockup.
> */
> - max_tlbi_ops = 1 << (ilog2(granule) - 3);
> - return size >= max_tlbi_ops * granule;
> + max_tlbi_ops = 1 << (ilog2(tlbi->iopte_granule) - 3);
> + return tlbi->size >= max_tlbi_ops * tlbi->iopte_granule;
> }
>
> /* Used by non INV_TYPE_ATS* invalidations */
> static void arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
> struct arm_smmu_cmdq_batch *cmds,
> struct arm_smmu_cmd *cmd,
> - bool leaf,
> - unsigned long iova, size_t size,
> - unsigned int granule)
> + struct arm_smmu_tlbi *tlbi)
> {
> - if (arm_smmu_inv_size_too_big(inv->smmu, size, granule)) {
> + if (arm_smmu_inv_size_too_big(inv->smmu, tlbi)) {
> struct arm_smmu_cmd nsize_cmd = *cmd;
>
> u64p_replace_bits(&nsize_cmd.data[0], inv->nsize_opcode,
> @@ -2512,8 +2513,7 @@ static void arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
> return;
> }
>
> - arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, leaf,
> - iova, size, granule, inv->pgsize);
> + arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, tlbi, inv->pgsize);
> }
>
> static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
> @@ -2532,9 +2532,8 @@ static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
> return false;
> }
>
> -static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
> - unsigned long iova, size_t size,
> - unsigned int granule, bool leaf)
> +static void __arm_smmu_domain_inv_range(struct arm_smmu_tlbi *tlbi,
> + struct arm_smmu_invs *invs)
> {
> struct arm_smmu_cmdq_batch cmds = {};
> struct arm_smmu_inv *cur;
> @@ -2564,18 +2563,16 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
> case INV_TYPE_S1_ASID:
> cmd = arm_smmu_make_cmd_tlbi(cur->size_opcode,
> cur->id, 0);
> - arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, leaf,
> - iova, size, granule);
> + arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, tlbi);
> break;
> case INV_TYPE_S2_VMID:
> cmd = arm_smmu_make_cmd_tlbi(cur->size_opcode,
> 0, cur->id);
> - arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, leaf,
> - iova, size, granule);
> + arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, tlbi);
> break;
> case INV_TYPE_S2_VMID_S1_CLEAR:
> /* CMDQ_OP_TLBI_S12_VMALL already flushed S1 entries */
> - if (arm_smmu_inv_size_too_big(cur->smmu, size, granule))
> + if (arm_smmu_inv_size_too_big(cur->smmu, tlbi))
> break;
> arm_smmu_cmdq_batch_add_cmd(
> smmu, &cmds,
> @@ -2586,7 +2583,7 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
> arm_smmu_cmdq_batch_add_cmd(
> smmu, &cmds,
> arm_smmu_atc_inv_to_cmd(cur->id, cur->ssid,
> - iova, size));
> + tlbi));
> break;
> case INV_TYPE_ATS_FULL:
> arm_smmu_cmdq_batch_add_cmd(
> @@ -2617,6 +2614,12 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
> unsigned long iova, size_t size,
> unsigned int granule, bool leaf)
> {
> + struct arm_smmu_tlbi tlbi = {
> + .iova = iova,
> + .size = size,
> + .iopte_granule = granule,
> + .leaf_only = leaf,
> + };
> struct arm_smmu_invs *invs;
>
> /*
> @@ -2657,10 +2660,10 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
> unsigned long flags;
>
> read_lock_irqsave(&invs->rwlock, flags);
> - __arm_smmu_domain_inv_range(invs, iova, size, granule, leaf);
> + __arm_smmu_domain_inv_range(&tlbi, invs);
> read_unlock_irqrestore(&invs->rwlock, flags);
> } else {
> - __arm_smmu_domain_inv_range(invs, iova, size, granule, leaf);
> + __arm_smmu_domain_inv_range(&tlbi, invs);
> }
>
> rcu_read_unlock();
> 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 c909c9a88538bf..a364d847c22a92 100644
> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
> @@ -802,6 +802,13 @@ static inline struct arm_smmu_invs *arm_smmu_invs_alloc(size_t num_invs)
> return new_invs;
> }
>
> +struct arm_smmu_tlbi {
> + unsigned long iova;
> + size_t size;
> + unsigned int iopte_granule;
> + bool leaf_only;
> +};
> +
> struct arm_smmu_evtq {
> struct arm_smmu_queue q;
> struct iopf_queue *iopf;
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 2/8] iommu/arm-smmu-v3: Move pgsize out of arm_smmu_inv
2026-07-06 16:26 ` [PATCH v2 2/8] iommu/arm-smmu-v3: Move pgsize out of arm_smmu_inv Jason Gunthorpe
2026-07-07 3:57 ` Nicolin Chen
@ 2026-07-07 11:24 ` Mostafa Saleh
1 sibling, 0 replies; 24+ messages in thread
From: Mostafa Saleh @ 2026-07-07 11:24 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon, David Matlack,
Pasha Tatashin, patches, Pranjal Shrivastava, Samiullah Khawaja
On Mon, Jul 06, 2026 at 01:26:39PM -0300, Jason Gunthorpe wrote:
> pgsize is a constant property of the domain, it is the base translation
> granule of the page table (4k, 16k, 64k) in log2.
>
> Store it to the struct arm_smmu_domain based on how the page table was created.
>
> Add smmu_domain to the tlbi and just get tg from the domain.
>
> Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
> ---
> .../iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 1 +
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 27 ++++++++-----------
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 3 ++-
> 3 files changed, 14 insertions(+), 17 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 1ed8a6f29dc445..5d4dde3d1cfe87 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
> @@ -334,6 +334,7 @@ struct iommu_domain *arm_smmu_sva_domain_alloc(struct device *dev,
> * ARM_SMMU_FEAT_RANGE_INV is present
> */
> smmu_domain->domain.pgsize_bitmap = PAGE_SIZE;
> + smmu_domain->tgsz_lg2 = PAGE_SHIFT;
> smmu_domain->stage = ARM_SMMU_DOMAIN_SVA;
> smmu_domain->smmu = smmu;
>
> 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 0c875771b46d2f..d22012466e3965 100644
> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> @@ -2398,14 +2398,13 @@ static void arm_smmu_tlb_inv_context(void *cookie)
> static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
> struct arm_smmu_cmdq_batch *cmds,
> struct arm_smmu_cmd *cmd,
> - struct arm_smmu_tlbi *tlbi,
> - size_t pgsize)
> + struct arm_smmu_tlbi *tlbi)
> {
> size_t inv_range = tlbi->iopte_granule;
> unsigned long iova = tlbi->iova;
> unsigned long end = iova + tlbi->size;
> unsigned long num_pages = 0;
> - unsigned int tg = pgsize;
> + unsigned int tg = tlbi->smmu_domain->tgsz_lg2;
> u64 orig_data0 = cmd->data[0];
> u8 ttl = 0, tg_enc = 0;
>
> @@ -2513,7 +2512,7 @@ static void arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
> return;
> }
>
> - arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, tlbi, inv->pgsize);
> + arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, tlbi);
> }
>
> static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
> @@ -2615,6 +2614,7 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
> unsigned int granule, bool leaf)
> {
> struct arm_smmu_tlbi tlbi = {
> + .smmu_domain = smmu_domain,
> .iova = iova,
> .size = size,
> .iopte_granule = granule,
> @@ -2870,6 +2870,7 @@ static int arm_smmu_domain_finalise(struct arm_smmu_domain *smmu_domain,
> return -ENOMEM;
>
> 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;
> smmu_domain->domain.geometry.force_aperture = true;
> if (enable_dirty && smmu_domain->stage == ARM_SMMU_DOMAIN_S1)
> @@ -3104,15 +3105,13 @@ static void arm_smmu_disable_iopf(struct arm_smmu_master *master,
>
> static struct arm_smmu_inv *
> arm_smmu_master_build_inv(struct arm_smmu_master *master,
> - enum arm_smmu_inv_type type, u32 id, ioasid_t ssid,
> - size_t pgsize)
> + enum arm_smmu_inv_type type, u32 id, ioasid_t ssid)
> {
> struct arm_smmu_invs *build_invs = master->build_invs;
> struct arm_smmu_inv *cur, inv = {
> .smmu = master->smmu,
> .type = type,
> .id = id,
> - .pgsize = pgsize,
> };
>
> if (WARN_ON(build_invs->num_invs >= build_invs->max_invs))
> @@ -3164,28 +3163,24 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,
> ioasid_t ssid, struct arm_smmu_domain *smmu_domain)
> {
> const bool nesting = smmu_domain->nest_parent;
> - size_t pgsize = 0, i;
> + size_t i;
>
> iommu_group_mutex_assert(master->dev);
>
> master->build_invs->num_invs = 0;
>
> - /* Range-based invalidation requires the leaf pgsize for calculation */
> - if (master->smmu->features & ARM_SMMU_FEAT_RANGE_INV)
> - pgsize = __ffs(smmu_domain->domain.pgsize_bitmap);
> -
> switch (smmu_domain->stage) {
> case ARM_SMMU_DOMAIN_SVA:
> case ARM_SMMU_DOMAIN_S1:
> if (!arm_smmu_master_build_inv(master, INV_TYPE_S1_ASID,
> smmu_domain->cd.asid,
> - IOMMU_NO_PASID, pgsize))
> + IOMMU_NO_PASID))
> return NULL;
> break;
> case ARM_SMMU_DOMAIN_S2:
> if (!arm_smmu_master_build_inv(master, INV_TYPE_S2_VMID,
> smmu_domain->s2_cfg.vmid,
> - IOMMU_NO_PASID, pgsize))
> + IOMMU_NO_PASID))
> return NULL;
> break;
> default:
> @@ -3197,7 +3192,7 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,
> if (nesting) {
> if (!arm_smmu_master_build_inv(
> master, INV_TYPE_S2_VMID_S1_CLEAR,
> - smmu_domain->s2_cfg.vmid, IOMMU_NO_PASID, 0))
> + smmu_domain->s2_cfg.vmid, IOMMU_NO_PASID))
> return NULL;
> }
>
> @@ -3208,7 +3203,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, 0))
> + master->streams[i].id, ssid))
> return NULL;
> }
>
> 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 a364d847c22a92..2fc695817671fe 100644
> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
> @@ -734,7 +734,6 @@ struct arm_smmu_inv {
> u8 nsize_opcode;
> u32 id; /* ASID or VMID or SID */
> union {
> - size_t pgsize; /* ARM_SMMU_FEAT_RANGE_INV */
> u32 ssid; /* INV_TYPE_ATS */
> };
>
> @@ -803,6 +802,7 @@ static inline struct arm_smmu_invs *arm_smmu_invs_alloc(size_t num_invs)
> }
>
> struct arm_smmu_tlbi {
> + struct arm_smmu_domain *smmu_domain;
The smmu_domain is only used for the tgsz_lg2, can’t we add that
directly instead? Decoupling the invalidation form the domain would
make it easier for KVM to re-use the code.
I see by the end of the series the only difference would be
arm_smmu_domain_tlbi() which need to the domain for the RCU, but that
can be passed instead as all the callers are have the domain already.
Thanks,
Mostafa
> unsigned long iova;
> size_t size;
> unsigned int iopte_granule;
> @@ -1050,6 +1050,7 @@ struct arm_smmu_domain {
> spinlock_t devices_lock;
> bool enforce_cache_coherency : 1;
> bool nest_parent : 1;
> + u8 tgsz_lg2;
>
> struct mmu_notifier mmu_notifier;
> };
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 3/8] iommu/arm-smmu-v3: Optimize range invalidation for latency
2026-07-06 16:26 ` [PATCH v2 3/8] iommu/arm-smmu-v3: Optimize range invalidation for latency Jason Gunthorpe
2026-07-07 7:27 ` Nicolin Chen
@ 2026-07-07 11:45 ` Mostafa Saleh
1 sibling, 0 replies; 24+ messages in thread
From: Mostafa Saleh @ 2026-07-07 11:45 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon, David Matlack,
Pasha Tatashin, patches, Pranjal Shrivastava, Samiullah Khawaja
On Mon, Jul 06, 2026 at 01:26:40PM -0300, Jason Gunthorpe wrote:
> The server IOMMU drivers focus on invalidation latency by default,
> over-invalidating if necessary, to round the invalidation range up to a
> single command. I think this represents a trade off for DMA non-FQ and SVA
> where stalling the operation is overall worse than re-loading the IOTLB.
>
> For instance AMD and VT-d both round the range up to the largest aligned
> power of two and invalidate that. This causes over-invalidation but that
> is preferred on real HW over trying to issue a number of smaller
> range invalidations.
>
> Only if a para-virtualizating hypervisor is detected do they switch to
> using more accurate invalidation. This also triggers using
> iommu_iotlb_gather_is_disjoint() (ie PT_FEAT_FLUSH_RANGE_NO_GAPS) to
> remove over invalidation from the gather. A pvIOMMU has a hypervisor that
> will walk the IOPTEs and resync them. Over invalidation, especially
> significant over invalidation, can incur a big latency cost reloading alot
> of page table. x86 IOMMUs have aligned range restrictions so there are
> some pretty nasty corner cases that can trigger huge over invalidation.
>
> Currently SMMUv3 doesn't support detecting a hypervisor, and it
> unconditionally runs in a NO_GAPS mode. This makes some sense for the
> single invalidation flow where there is little reason to push single
> commands across a gap.
>
> When we get to RIL hardware, this doesn't look so good. On real HW the
> best option is the same as x86: issue a single RIL per gather and optimize
> for latency. SMMUv3 has a significant advantage as its RIL does not have
> alignment limitations so it's single-command over-invalidation is capped
> at < 1/32 of the gather's size, making it much more suitable for a
> pvIOMMU.
>
> However even with RIL SMMUv3 still uses NO_GAPS and it breaks down the
> gather into several exactly sized RILs to avoid any over-invalidation,
> costing latency on real HW.
>
> When the HW has RIL support follow the x86 approach in SMMUv3 and
> calculate a single RIL per gather that will cover the required
> invalidation.
>
> Calculate the smallest SCALE such that NUM can cover the range to minimize
> over-invalidation. Always use a RIL command if RIL is possible working
> around the spec limitations to form a valid one. If RIL is not possible
> then do full invalidation.
>
That may be beneficial for servers, but I am not sure about other use
cases, we already know that the invalidated entries are unmapped
and not used. However, over invalidating might impact live DMA which
would be bad for workloads sensitive to translation latency (as
embedded cameras, displays for example). Maybe this can be configured
instead (via cmdline)
> At least one invalidation errata is avoided by 'always use RIL'.
>
Can you please clarify what that means?
> Since the normal path is now the only one with a loop, split them into two
> functions and fold a simplified version of arm_smmu_inv_size_too_big()
> directly into the normal flow in a way that directly limits the number of
> single invalidation commands generated, again focusing on controlling
> latency.
>
> The end result is any gather is converted into either:
> - One invalidate all
> - One range invalidate op
> - At most 512 single invalidation ops
>
> Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
> ---
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 257 ++++++++++++--------
> 1 file changed, 153 insertions(+), 104 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 d22012466e3965..1ad642e09eb92d 100644
> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> @@ -2395,124 +2395,166 @@ static void arm_smmu_tlb_inv_context(void *cookie)
> arm_smmu_domain_inv(smmu_domain);
> }
>
[...]
> + scale = fls64((num_tg - 1) / 32);
> + if (scale > 31) {
> + /*
> + * Range too large for a single command, use full invalidation.
> + */
> + return false;
> + }
> +
> + /* 16K granule TTL=1 is reserved (Section 4.4.1) */
> + if (tg_lg2 == 14 && ttl == 1)
> + ttl = 0;
> +
> + /* Verify address alignment for the TTL hint */
> + if (ttl && !arm_smmu_ttl_addr_aligned(cur_tg << tg_lg2, tg_lg2, ttl))
> + ttl = 0;
> +
> + arm_smmu_cmdq_batch_add_ril(smmu, cmds, cmd, tlbi->leaf_only,
> + cur_tg << tg_lg2,
> + DIV_ROUND_UP_ULL(num_tg, 1ULL << scale) - 1,
> + scale, ttl, tg_enc);
> + return true;
> }
>
> -/* Used by non INV_TYPE_ATS* invalidations */
> -static void arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
> +/*
> + * One TLBI command per IOTLB entry, assuming the entries are all at least
> + * iopte_granule sized. Returns false if too many commands would be needed which
> + * indicates too high a latency. The threshold is similar to MAX_DVM_OPS in
> + * arch/arm64/include/asm/tlbflush.h for the 4k PAGE_SIZE.
> + */
> +static bool arm_smmu_cmdq_batch_add_single(struct arm_smmu_device *smmu,
> + struct arm_smmu_cmdq_batch *cmds,
> + struct arm_smmu_cmd *cmd,
> + struct arm_smmu_tlbi *tlbi)
> +{
> + unsigned long num_ops = tlbi->size / tlbi->iopte_granule;
> + unsigned long iova = tlbi->iova;
> + unsigned long i;
> +
> + if (!num_ops || num_ops > 512)
Is there a reason that was added instead of keeping the old formula?
Thanks,
Mostafa
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 4/8] iommu/arm-smmu-v3: Keep track in the arm_smmu_invs if RIL is used
2026-07-06 16:26 ` [PATCH v2 4/8] iommu/arm-smmu-v3: Keep track in the arm_smmu_invs if RIL is used Jason Gunthorpe
2026-07-07 7:27 ` Nicolin Chen
@ 2026-07-07 11:46 ` Mostafa Saleh
1 sibling, 0 replies; 24+ messages in thread
From: Mostafa Saleh @ 2026-07-07 11:46 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon, David Matlack,
Pasha Tatashin, patches, Pranjal Shrivastava, Samiullah Khawaja
On Mon, Jul 06, 2026 at 01:26:41PM -0300, Jason Gunthorpe wrote:
> Summarize if any of the inv entries will use RIL. The next patch will use
> this to avoid RIL pre-calculations unless RIL is being used by the
> invalidation.
>
> Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
Reviewed-by: Mostafa Saleh <smostafa@google.com>
Thanks,
Mostafa
> ---
> .../iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c | 30 +++++++++----------
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 18 ++++++++---
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 2 ++
> 3 files changed, 31 insertions(+), 19 deletions(-)
>
> 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 add671363c828c..785dd21bd68b7a 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
> @@ -655,37 +655,37 @@ static void arm_smmu_v3_invs_test_verify(struct kunit *test,
>
> static struct arm_smmu_invs invs1 = {
> .num_invs = 3,
> - .inv = { { .type = INV_TYPE_S2_VMID, .id = 1, },
> - { .type = INV_TYPE_S2_VMID_S1_CLEAR, .id = 1, },
> - { .type = INV_TYPE_ATS, .id = 3, }, },
> + .inv = { { .smmu = &smmu, .type = INV_TYPE_S2_VMID, .id = 1, },
> + { .smmu = &smmu, .type = INV_TYPE_S2_VMID_S1_CLEAR, .id = 1, },
> + { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 3, }, },
> };
>
> static struct arm_smmu_invs invs2 = {
> .num_invs = 3,
> - .inv = { { .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
> - { .type = INV_TYPE_ATS, .id = 4, },
> - { .type = INV_TYPE_ATS, .id = 5, }, },
> + .inv = { { .smmu = &smmu, .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
> + { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 4, },
> + { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 5, }, },
> };
>
> static struct arm_smmu_invs invs3 = {
> .num_invs = 3,
> - .inv = { { .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
> - { .type = INV_TYPE_ATS, .id = 5, }, /* recover a trash */
> - { .type = INV_TYPE_ATS, .id = 6, }, },
> + .inv = { { .smmu = &smmu, .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
> + { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 5, }, /* recover a trash */
> + { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 6, }, },
> };
>
> static struct arm_smmu_invs invs4 = {
> .num_invs = 3,
> - .inv = { { .type = INV_TYPE_ATS, .id = 10, .ssid = 1 },
> - { .type = INV_TYPE_ATS, .id = 10, .ssid = 3 },
> - { .type = INV_TYPE_ATS, .id = 12, .ssid = 1 }, },
> + .inv = { { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 1 },
> + { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 3 },
> + { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 12, .ssid = 1 }, },
> };
>
> static struct arm_smmu_invs invs5 = {
> .num_invs = 3,
> - .inv = { { .type = INV_TYPE_ATS, .id = 10, .ssid = 2 },
> - { .type = INV_TYPE_ATS, .id = 10, .ssid = 3 }, /* duplicate */
> - { .type = INV_TYPE_ATS, .id = 12, .ssid = 2 }, },
> + .inv = { { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 2 },
> + { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 3 }, /* duplicate */
> + { .smmu = &smmu, .type = INV_TYPE_ATS, .id = 12, .ssid = 2 }, },
> };
>
> static void arm_smmu_v3_invs_test(struct kunit *test)
> 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 1ad642e09eb92d..02323fd7709f07 100644
> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> @@ -988,6 +988,18 @@ static inline int arm_smmu_invs_iter_next_cmp(struct arm_smmu_invs *invs_l,
> return arm_smmu_inv_cmp(cur_l, &invs_r->inv[next_r]);
> }
>
> +static void arm_smmu_invs_update_caps(struct arm_smmu_invs *invs,
> + const struct arm_smmu_inv *inv)
> +{
> + if (arm_smmu_inv_is_ats(inv))
> + invs->has_ats = true;
> +
> + if (!(inv->smmu->features & ARM_SMMU_FEAT_RANGE_INV))
> + return;
> +
> + invs->has_range_inv = true;
> +}
> +
> /**
> * arm_smmu_invs_for_each_cmp - Iterate over two sorted arrays computing for
> * arm_smmu_invs_merge() or arm_smmu_invs_unref()
> @@ -1058,8 +1070,7 @@ struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs,
> */
> if (new != new_invs->inv)
> WARN_ON_ONCE(arm_smmu_inv_cmp(new - 1, new) == 1);
> - if (arm_smmu_inv_is_ats(new))
> - new_invs->has_ats = true;
> + arm_smmu_invs_update_caps(new_invs, new);
> new++;
> }
>
> @@ -1169,8 +1180,7 @@ struct arm_smmu_invs *arm_smmu_invs_purge(struct arm_smmu_invs *invs)
>
> arm_smmu_invs_for_each_entry(invs, i, inv) {
> new_invs->inv[num_invs] = *inv;
> - if (arm_smmu_inv_is_ats(inv))
> - new_invs->has_ats = true;
> + arm_smmu_invs_update_caps(new_invs, inv);
> num_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 2fc695817671fe..3ef55f8af63d90 100644
> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
> @@ -755,6 +755,7 @@ static inline bool arm_smmu_inv_is_ats(const struct arm_smmu_inv *inv)
> * Must not be greater than @num_invs
> * @rwlock: optional rwlock to fence ATS operations
> * @has_ats: flag if the array contains an INV_TYPE_ATS or INV_TYPE_ATS_FULL
> + * @has_range_inv: flag if any entry's SMMU supports range invalidation
> * @rcu: rcu head for kfree_rcu()
> * @inv: flexible invalidation array
> *
> @@ -784,6 +785,7 @@ struct arm_smmu_invs {
> size_t num_trashes;
> rwlock_t rwlock;
> bool has_ats;
> + bool has_range_inv;
> struct rcu_head rcu;
> struct arm_smmu_inv inv[] __counted_by(max_invs);
> };
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 5/8] iommu/arm-smmu-v3: Precompute the invalidation commands
2026-07-06 16:26 ` [PATCH v2 5/8] iommu/arm-smmu-v3: Precompute the invalidation commands Jason Gunthorpe
@ 2026-07-07 11:52 ` Mostafa Saleh
2026-07-07 14:58 ` Jason Gunthorpe
0 siblings, 1 reply; 24+ messages in thread
From: Mostafa Saleh @ 2026-07-07 11:52 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon, David Matlack,
Pasha Tatashin, patches, Pranjal Shrivastava, Samiullah Khawaja
On Mon, Jul 06, 2026 at 01:26:42PM -0300, Jason Gunthorpe wrote:
> Store the required cmd data in the tlbi and just copy it out when
> processing each item in the invs list. The cmd form only depends on
> if the instance supports RIL or not, otherwise it is always the same.
>
> This avoids redundant calculations for each invs entry.
I do not understand how does this avoids redundant calculation?
This would be the case if the domain shares multiple SMMUs,
otherwise, a range TLB invalidation should be unique and can not be
reused.
And in that case I am not sure that would be noticeable with modern
CPUs/compilers.
Thanks,
Mostafa
>
> Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
> ---
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 132 +++++++++++---------
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 11 ++
> 2 files changed, 81 insertions(+), 62 deletions(-)
>
> 2.43.0
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 6/8] iommu/arm-smmu-v3: Populate the tlbi at the top of the call chain
2026-07-06 16:26 ` [PATCH v2 6/8] iommu/arm-smmu-v3: Populate the tlbi at the top of the call chain Jason Gunthorpe
@ 2026-07-07 11:57 ` Mostafa Saleh
0 siblings, 0 replies; 24+ messages in thread
From: Mostafa Saleh @ 2026-07-07 11:57 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon, David Matlack,
Pasha Tatashin, patches, Pranjal Shrivastava, Samiullah Khawaja
On Mon, Jul 06, 2026 at 01:26:43PM -0300, Jason Gunthorpe wrote:
> Each of these has their own unique situation, populate the tlbi right
> at the top and pass it into arm_smmu_domain_inv_range(). They will
> diverge further when the iommupt invalidation scheme is introduced.
>
> Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
> ---
> .../iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 21 ++++----
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 51 +++++++++----------
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 13 +++--
> 3 files changed, 45 insertions(+), 40 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 5d4dde3d1cfe87..e6001913e2b043 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
> @@ -139,16 +139,19 @@ static void arm_smmu_mm_arch_invalidate_secondary_tlbs(struct mmu_notifier *mn,
> {
> struct arm_smmu_domain *smmu_domain =
> container_of(mn, struct arm_smmu_domain, mmu_notifier);
> - size_t size;
> + struct arm_smmu_tlbi tlbi = {
> + .smmu_domain = smmu_domain,
> + .iova = start,
> + /*
> + * The mm_types defines vm_end as the first byte after the end
> + * address, different from IOMMU subsystem using the last
> + * address of an address range.
> + */
> + .size = end - start,
> + .iopte_granule = PAGE_SIZE,
> + };
>
> - /*
> - * The mm_types defines vm_end as the first byte after the end address,
> - * different from IOMMU subsystem using the last address of an address
> - * range. So do a simple translation here by calculating size correctly.
> - */
> - size = end - start;
> -
> - arm_smmu_domain_inv_range(smmu_domain, start, size, PAGE_SIZE, false);
> + arm_smmu_domain_tlbi(&tlbi);
> }
>
> static void arm_smmu_mm_release(struct mmu_notifier *mn, struct mm_struct *mm)
> 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 fbe3e5dc42f964..2e477f15080148 100644
> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> @@ -2661,25 +2661,12 @@ static void arm_smmu_domain_tlbi_inv(struct arm_smmu_tlbi *tlbi,
> }
> }
>
> -void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
> - unsigned long iova, size_t size,
> - unsigned int granule, bool leaf)
> +void arm_smmu_domain_tlbi(struct arm_smmu_tlbi *tlbi)
> {
> - struct arm_smmu_tlbi tlbi = {
> - .smmu_domain = smmu_domain,
> - .iova = iova,
> - .size = size,
> - .iopte_granule = granule,
> - .leaf_only = leaf,
> - };
> struct arm_smmu_invs *invs;
>
> - if (!size || size == SIZE_MAX) {
> - tlbi.single.use_full_inv = true;
> - tlbi.range.use_full_inv = true;
> - } else {
> - arm_smmu_tlbi_calc_single(&tlbi);
> - }
> + if (!tlbi->single.use_full_inv)
> + arm_smmu_tlbi_calc_single(tlbi);
>
> /*
> * An invalidation request must follow some IOPTE change and then load
The rest of the comment still refers to the old name
arm_smmu_domain_inv_range()
> @@ -2709,14 +2696,14 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
> smp_mb();
>
> rcu_read_lock();
> - invs = rcu_dereference(smmu_domain->invs);
> + invs = rcu_dereference(tlbi->smmu_domain->invs);
>
> /* Only precaculate RIL if it will be used. */
> if (invs->has_range_inv) {
> - if (!tlbi.range.use_full_inv)
> - arm_smmu_tlbi_calc_range(&tlbi);
> + if (!tlbi->range.use_full_inv)
> + arm_smmu_tlbi_calc_range(tlbi);
> } else {
> - tlbi.range.use_full_inv = true;
> + tlbi->range.use_full_inv = true;
> }
>
> /*
> @@ -2727,10 +2714,10 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
> unsigned long flags;
>
> read_lock_irqsave(&invs->rwlock, flags);
> - arm_smmu_domain_tlbi_inv(&tlbi, invs);
> + arm_smmu_domain_tlbi_inv(tlbi, invs);
> read_unlock_irqrestore(&invs->rwlock, flags);
> } else {
> - arm_smmu_domain_tlbi_inv(&tlbi, invs);
> + arm_smmu_domain_tlbi_inv(tlbi, invs);
> }
>
> rcu_read_unlock();
> @@ -2750,8 +2737,14 @@ static void arm_smmu_tlb_inv_walk(unsigned long iova, size_t size,
> size_t granule, void *cookie)
> {
> struct arm_smmu_domain *smmu_domain = cookie;
> + struct arm_smmu_tlbi tlbi = {
> + .smmu_domain = smmu_domain,
> + .iova = iova,
> + .size = size,
> + .iopte_granule = granule,
> + };
>
> - arm_smmu_domain_inv_range(smmu_domain, iova, size, granule, false);
> + arm_smmu_domain_tlbi(&tlbi);
> }
>
> static const struct iommu_flush_ops arm_smmu_flush_ops = {
> @@ -4018,14 +4011,18 @@ static void arm_smmu_flush_iotlb_all(struct iommu_domain *domain)
> static void arm_smmu_iotlb_sync(struct iommu_domain *domain,
> struct iommu_iotlb_gather *gather)
> {
> - struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
> + struct arm_smmu_tlbi tlbi = {
> + .smmu_domain = to_smmu_domain(domain),
> + .iova = gather->start,
> + .size = gather->end - gather->start + 1,
> + .iopte_granule = gather->pgsize,
> + .leaf_only = true,
> + };
>
> if (!gather->pgsize)
> return;
>
> - arm_smmu_domain_inv_range(smmu_domain, gather->start,
> - gather->end - gather->start + 1,
> - gather->pgsize, true);
> + arm_smmu_domain_tlbi(&tlbi);
> }
>
> static phys_addr_t
> 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 9d262ef6076225..5f97d1a63ebbfd 100644
> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
> @@ -1169,13 +1169,18 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
> struct arm_smmu_domain *smmu_domain, ioasid_t pasid,
> struct arm_smmu_cd *cd, struct iommu_domain *old);
>
> -void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
> - unsigned long iova, size_t size,
> - unsigned int granule, bool leaf);
> +void arm_smmu_domain_tlbi(struct arm_smmu_tlbi *tlbi);
>
> static inline void arm_smmu_domain_inv(struct arm_smmu_domain *smmu_domain)
> {
> - arm_smmu_domain_inv_range(smmu_domain, 0, 0, 0, false);
> + /* Prefilled for invalidate all */
> + struct arm_smmu_tlbi tlbi = {
> + .smmu_domain = smmu_domain,
> + .single.use_full_inv = true,
> + .range.use_full_inv = true,
Those were introduced last patch, but I am wondering if use_full_inv
should be a common field instead of having it in both single and range.
Thanks,
Mostafa
> + };
> +
> + arm_smmu_domain_tlbi(&tlbi);
> }
>
> void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu,
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it
2026-07-06 16:26 [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it Jason Gunthorpe
` (7 preceding siblings ...)
2026-07-06 16:26 ` [PATCH v2 8/8] iommu/arm-smmu-v3: Support the DS expansion of RIL's SCALE Jason Gunthorpe
@ 2026-07-07 12:25 ` Mostafa Saleh
2026-07-07 15:00 ` Jason Gunthorpe
8 siblings, 1 reply; 24+ messages in thread
From: Mostafa Saleh @ 2026-07-07 12:25 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon, David Matlack,
Pasha Tatashin, patches, Pranjal Shrivastava, Samiullah Khawaja
On Mon, Jul 06, 2026 at 01:26:37PM -0300, Jason Gunthorpe wrote:
> [ This is part of the patch pile to move SMMUv3 over to the generic page
> table, the precursor patches have been merged now:
> 1) Organize the SMMUv3 invalidation flow so iommupt can use it
> 2) Use the generic iommu page table for SMMUv3
>
> The whole branch is here:
> https://github.com/jgunthorpe/linux/commits/iommu_pt_arm64/
> ]
>
> iommupt has a design that focuses on building a single iommu_iotlb_gather
> for arbitary batches of map/unmap operations. The gather uses the free
> list and it captures invalidations of tables, leaves and supports mixed
> levels.
>
> The introduction of PT_FEAT_DETAILED_GATHER provides some additional
> information that is useful for ARM: the damage bitmaps for the table and
> level changes.
>
> Prior to switching SMMUv3 over to use iommupt prepare for this by
> reworking the internal invalidation to work on the same data format that
> iommupt will produce. Bridge the invalidations generated by io-pgtable
> into the new format. The conversion is simple enough, io-pgtable generates
> invalidation operations that have only a single set bit in
> table_levels_bitmap/leaf_levels_bitmap, so we can convert the io-pgtable
> provided size into the proper level leaf or table bit.
>
> When iommupt uses this mechanism it will fill in full bitmaps reflecting
> the union of all invalidations contained in the gather, and this series
> provides an implementation that can work this way.
>
> Like the other drivers the general algorithm focuses on trying to issue a
> single command per gather or at most 512 single invalidations. If that
> isn't possible then it falls back to full invalidation. Since table and
> leaf invalidation are combined together there is no waste of invaliding
> tables prior to performing a full invalidation.
>
> On its own this provides value as the invalidation has a number of
> rough spots:
>
> - Non-leaf invalidation actually expands into a TLBI for every
> translation granule because the inner logic doesn't special case the
> walk vs leaf condition. Now that a table_levels_bitmap is used to
> describe the walk invalidation it properly generates a RIL with optimal
> TTL or only one single invalidation.
>
> - RIL doesn't calculate perfect hints for SVA because the SVA rules are
> different from the io-pgtable-arm rules that the RIL algorithm works
> with. SVA can now express the combined leaf and table invalidation that
> the MM callback represents and get the right TTL, with an optimization
> for the common 4k only scenario.
>
> - RIL didn't generate a single invalidation like VT-d and AMD do,
> instead it tries to generate an exact coverage with many
> smaller invalidations. Switch it to match the other drivers single
> range approach for performance and consistency. Since ARM has a much
> more flexible range definition the over invalidation is far smaller
> than other systems.
>
> The approach is to introduce a new struct arm_smmu_tlbi which
> describes the invalidation, pre-compute into the tlbi the single and
> range commands from the start/last and bitmaps, and then apply the
> correct pre-computed command to each of items in the invalidation
> list.
>
> The RIL and single calculations are revised to use the new bitmaps
> and accurately generate TTL/stride/etc.
>
> Some of this design is to support another series to remove the batch on
> the stack. Now that we have the invalidation list and the tlbi it is
> simple to just expand the invs list directly into commands instead of
> using the temporary on-stack batch array. Eventually removing batch will
> save ~1k of stack usage here.
>
> v2:
> - Rebase to v7.2-rc1
> v1: https://lore.kernel.org/all/0-v1-5b1ac97a5403+6588f-smmu_tlbi_jgg@nvidia.com/
>
> Jason Gunthorpe (8):
> iommu/arm-smmu-v3: Pass the parameters for the invalidation in a
> struct
> iommu/arm-smmu-v3: Move pgsize out of arm_smmu_inv
> iommu/arm-smmu-v3: Optimize range invalidation for latency
> iommu/arm-smmu-v3: Keep track in the arm_smmu_invs if RIL is used
> iommu/arm-smmu-v3: Precompute the invalidation commands
> iommu/arm-smmu-v3: Populate the tlbi at the top of the call chain
> iommu/arm-smmu-v3: Change how the tlbi describes the invalidation
> iommu/arm-smmu-v3: Support the DS expansion of RIL's SCALE
I quickly went through the series, and left few comments.
As discussed before regarding the TLB invalidation logic sharing
with pKVM [1], I still think after these changes both drivers can
share some logic on the level arm_smmu_tlbi_calc_range() (+ tweaks).
I am planning to post my series by this week, I will keep it based
on upstream and I can rework my series later if this one gets merged
first.
Thanks,
Mostafa
[1] https://lore.kernel.org/all/20260501111928.259252-5-smostafa@google.com/
>
> .../iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 32 +-
> .../iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c | 30 +-
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 439 ++++++++++++------
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 54 ++-
> 4 files changed, 382 insertions(+), 173 deletions(-)
>
>
> base-commit: 4c73a6222c248384513c4f465e547df80b280a06
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 5/8] iommu/arm-smmu-v3: Precompute the invalidation commands
2026-07-07 11:52 ` Mostafa Saleh
@ 2026-07-07 14:58 ` Jason Gunthorpe
0 siblings, 0 replies; 24+ messages in thread
From: Jason Gunthorpe @ 2026-07-07 14:58 UTC (permalink / raw)
To: Mostafa Saleh
Cc: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon, David Matlack,
Pasha Tatashin, patches, Pranjal Shrivastava, Samiullah Khawaja
On Tue, Jul 07, 2026 at 11:52:25AM +0000, Mostafa Saleh wrote:
> On Mon, Jul 06, 2026 at 01:26:42PM -0300, Jason Gunthorpe wrote:
> > Store the required cmd data in the tlbi and just copy it out when
> > processing each item in the invs list. The cmd form only depends on
> > if the instance supports RIL or not, otherwise it is always the same.
> >
> > This avoids redundant calculations for each invs entry.
>
> I do not understand how does this avoids redundant calculation?
> This would be the case if the domain shares multiple SMMUs,
> otherwise, a range TLB invalidation should be unique and can not be
> reused.
Right, if there are multiple SMMUs for the domain then currently it
recomputes the range for every one.
At the moment I think we can't have multiple SMMUs per domain but
Nioclin has a patch series changing that
Jason
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it
2026-07-07 12:25 ` [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it Mostafa Saleh
@ 2026-07-07 15:00 ` Jason Gunthorpe
0 siblings, 0 replies; 24+ messages in thread
From: Jason Gunthorpe @ 2026-07-07 15:00 UTC (permalink / raw)
To: Mostafa Saleh
Cc: iommu, Joerg Roedel (AMD), Jean-Philippe Brucker,
linux-arm-kernel, Robin Murphy, Will Deacon, David Matlack,
Pasha Tatashin, patches, Pranjal Shrivastava, Samiullah Khawaja
On Tue, Jul 07, 2026 at 12:25:08PM +0000, Mostafa Saleh wrote:
> I quickly went through the series, and left few comments.
> As discussed before regarding the TLB invalidation logic sharing
> with pKVM [1],
Great, thanks
> I still think after these changes both drivers can
> share some logic on the level arm_smmu_tlbi_calc_range() (+ tweaks).
Yeah, I'm sure, but it still would be nice to share more than just
that..
Jason
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2026-07-07 15:00 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-06 16:26 [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it Jason Gunthorpe
2026-07-06 16:26 ` [PATCH v2 1/8] iommu/arm-smmu-v3: Pass the parameters for the invalidation in a struct Jason Gunthorpe
2026-07-07 3:04 ` Nicolin Chen
2026-07-07 11:18 ` Mostafa Saleh
2026-07-06 16:26 ` [PATCH v2 2/8] iommu/arm-smmu-v3: Move pgsize out of arm_smmu_inv Jason Gunthorpe
2026-07-07 3:57 ` Nicolin Chen
2026-07-07 11:24 ` Mostafa Saleh
2026-07-06 16:26 ` [PATCH v2 3/8] iommu/arm-smmu-v3: Optimize range invalidation for latency Jason Gunthorpe
2026-07-07 7:27 ` Nicolin Chen
2026-07-07 11:45 ` Mostafa Saleh
2026-07-06 16:26 ` [PATCH v2 4/8] iommu/arm-smmu-v3: Keep track in the arm_smmu_invs if RIL is used Jason Gunthorpe
2026-07-07 7:27 ` Nicolin Chen
2026-07-07 11:46 ` Mostafa Saleh
2026-07-06 16:26 ` [PATCH v2 5/8] iommu/arm-smmu-v3: Precompute the invalidation commands Jason Gunthorpe
2026-07-07 11:52 ` Mostafa Saleh
2026-07-07 14:58 ` Jason Gunthorpe
2026-07-06 16:26 ` [PATCH v2 6/8] iommu/arm-smmu-v3: Populate the tlbi at the top of the call chain Jason Gunthorpe
2026-07-07 11:57 ` Mostafa Saleh
2026-07-06 16:26 ` [PATCH v2 7/8] iommu/arm-smmu-v3: Change how the tlbi describes the invalidation Jason Gunthorpe
2026-07-06 18:00 ` Robin Murphy
2026-07-06 19:45 ` Jason Gunthorpe
2026-07-06 16:26 ` [PATCH v2 8/8] iommu/arm-smmu-v3: Support the DS expansion of RIL's SCALE Jason Gunthorpe
2026-07-07 12:25 ` [PATCH v2 0/8] Organize the SMMUv3 invalidation flow so iommupt can use it Mostafa Saleh
2026-07-07 15:00 ` Jason Gunthorpe
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox