AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/09/18 18:32 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch modifies documentation and the ARM SMMUv3 driver (drivers/iommu/arm/arm-smmu-v3/). The ARM_SMMU_V3 config option depends strictly on ARM64, making this code structurally unreachable and unbuildable on the target architecture (amd64).",
  "WorthFuzzing": false
}

1/1 2026/09/18 18:32 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 7d5f0e7a108e89270e7c489c2c69695e85b7ad9d\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Sep 18 18:32:58 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst\nindex ac3248b9f2f3b..68018bf75b791 100644\n--- a/Documentation/arch/arm64/silicon-errata.rst\n+++ b/Documentation/arch/arm64/silicon-errata.rst\n@@ -271,7 +271,8 @@ stable kernels.\n +----------------+-----------------+-----------------+-----------------------------+\n | ARM            | MMU L1          | #3878312        | N/A                         |\n +----------------+-----------------+-----------------+-----------------------------+\n-| ARM            | MMU S3          | #3995052        | N/A                         |\n+| ARM            | MMU S3          | #3995052,       | N/A                         |\n+|                |                 | #3673557        |                             |\n +----------------+-----------------+-----------------+-----------------------------+\n | ARM            | GIC-700         | #2941627        | ARM64_ERRATUM_2941627       |\n +----------------+-----------------+-----------------+-----------------------------+\ndiff --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\nindex 0a429c64fbf3e..fc9622cdbac80 100644\n--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c\n+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c\n@@ -140,16 +140,36 @@ static void arm_smmu_mm_arch_invalidate_secondary_tlbs(struct mmu_notifier *mn,\n {\n \tstruct arm_smmu_domain *smmu_domain =\n \t\tcontainer_of(mn, struct arm_smmu_domain, mmu_notifier);\n-\tsize_t size;\n+\tu8 tgsz_lg2 = smmu_domain-\u003etgsz_lg2;\n+\tstruct arm_smmu_tlbi tlbi = {\n+\t\t.tgsz_lg2 = smmu_domain-\u003etgsz_lg2,\n+\t\t.start = start,\n+\t\t.last = end - 1,\n+\t\t/*\n+\t\t * No information comes from the mm, assume the worst case that\n+\t\t * it changed every table level. The way this is hooked into the\n+\t\t * mm is tricky, the range won't be expanded to include an\n+\t\t * entire table level if one was removed like the iommu gather\n+\t\t * does. Thus even if this is a 4k invalidation it may be\n+\t\t * including any table level too.\n+\t\t */\n+\t\t.table_levels_bitmap = 0xfe,\n+\t};\n+\tu8 pmd_lg2sz = arm_smmu_pt_level_to_lg2sz(tgsz_lg2, 1);\n \n \t/*\n-\t * The mm_types defines vm_end as the first byte after the end address,\n-\t * different from IOMMU subsystem using the last address of an address\n-\t * range. So do a simple translation here by calculating size correctly.\n+\t * If the size is small then we can infer the invalidation is PTE only\n+\t * and set the PTE level only. Otherwise it could be some other\n+\t * combination so just set them all. This allows RIL to use TTL=3 in\n+\t * cases of PTE only changes. The mm must not try to partially\n+\t * invalidate pmd/etc.\n \t */\n-\tsize = end - start;\n+\tif (end - start \u003c BIT_U64(pmd_lg2sz))\n+\t\ttlbi.leaf_levels_bitmap = 1;\n+\telse\n+\t\ttlbi.leaf_levels_bitmap = 0xff;\n \n-\tarm_smmu_domain_inv_range(smmu_domain, start, size, PAGE_SIZE, false);\n+\tarm_smmu_domain_tlbi(\u0026tlbi, smmu_domain);\n }\n \n static void arm_smmu_mm_release(struct mmu_notifier *mn, struct mm_struct *mm)\n@@ -215,6 +235,13 @@ bool arm_smmu_sva_supported(struct arm_smmu_device *smmu)\n \tif (system_supports_haft())\n \t\tfeat_mask |= ARM_SMMU_FEAT_HAFT;\n \n+\t/*\n+\t * The workaround for ARM_SMMU_OPT_FULL_CONT_RIL requires range\n+\t * invalidation support.\n+\t */\n+\tif (smmu-\u003eoptions \u0026 ARM_SMMU_OPT_FULL_CONT_RIL)\n+\t\tfeat_mask |= ARM_SMMU_FEAT_RANGE_INV;\n+\n \tif ((smmu-\u003efeatures \u0026 feat_mask) != feat_mask)\n \t\treturn false;\n \n@@ -338,6 +365,7 @@ struct iommu_domain *arm_smmu_sva_domain_alloc(struct device *dev,\n \t * ARM_SMMU_FEAT_RANGE_INV is present\n \t */\n \tsmmu_domain-\u003edomain.pgsize_bitmap = PAGE_SIZE;\n+\tsmmu_domain-\u003etgsz_lg2 = PAGE_SHIFT;\n \tsmmu_domain-\u003estage = ARM_SMMU_DOMAIN_SVA;\n \tsmmu_domain-\u003esmmu = smmu;\n \ndiff --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\nindex add671363c828..ca734c1410641 100644\n--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c\n+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c\n@@ -24,7 +24,9 @@ struct arm_smmu_test_writer {\n static struct arm_smmu_ste bypass_ste;\n static struct arm_smmu_ste abort_ste;\n static struct arm_smmu_device smmu = {\n-\t.features = ARM_SMMU_FEAT_STALLS | ARM_SMMU_FEAT_ATTR_TYPES_OVR\n+\t.features = ARM_SMMU_FEAT_STALLS | ARM_SMMU_FEAT_ATTR_TYPES_OVR |\n+\t\t    ARM_SMMU_FEAT_RANGE_INV,\n+\t.options = ARM_SMMU_OPT_FULL_CONT_RIL,\n };\n static struct mm_struct sva_mm = {\n \t.pgd = (void *)0xdaedbeefdeadbeefULL,\n@@ -645,6 +647,8 @@ static void arm_smmu_v3_invs_test_verify(struct kunit *test,\n {\n \tKUNIT_EXPECT_EQ(test, invs-\u003enum_invs, num_invs);\n \tKUNIT_EXPECT_EQ(test, invs-\u003enum_trashes, num_trashes);\n+\tKUNIT_EXPECT_EQ(test, invs-\u003erange_inv_scale_max, 31);\n+\tKUNIT_EXPECT_TRUE(test, invs-\u003ehas_full_cont_ril);\n \twhile (num_invs--) {\n \t\tKUNIT_EXPECT_EQ(test, invs-\u003einv[num_invs].id, ids[num_invs]);\n \t\tKUNIT_EXPECT_EQ(test, READ_ONCE(invs-\u003einv[num_invs].users),\n@@ -655,37 +659,37 @@ static void arm_smmu_v3_invs_test_verify(struct kunit *test,\n \n static struct arm_smmu_invs invs1 = {\n \t.num_invs = 3,\n-\t.inv = { { .type = INV_TYPE_S2_VMID, .id = 1, },\n-\t\t { .type = INV_TYPE_S2_VMID_S1_CLEAR, .id = 1, },\n-\t\t { .type = INV_TYPE_ATS, .id = 3, }, },\n+\t.inv = { { .smmu = \u0026smmu, .type = INV_TYPE_S2_VMID, .id = 1, },\n+\t\t { .smmu = \u0026smmu, .type = INV_TYPE_S2_VMID_S1_CLEAR, .id = 1, },\n+\t\t { .smmu = \u0026smmu, .type = INV_TYPE_ATS, .id = 3, }, },\n };\n \n static struct arm_smmu_invs invs2 = {\n \t.num_invs = 3,\n-\t.inv = { { .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */\n-\t\t { .type = INV_TYPE_ATS, .id = 4, },\n-\t\t { .type = INV_TYPE_ATS, .id = 5, }, },\n+\t.inv = { { .smmu = \u0026smmu, .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */\n+\t\t { .smmu = \u0026smmu, .type = INV_TYPE_ATS, .id = 4, },\n+\t\t { .smmu = \u0026smmu, .type = INV_TYPE_ATS, .id = 5, }, },\n };\n \n static struct arm_smmu_invs invs3 = {\n \t.num_invs = 3,\n-\t.inv = { { .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */\n-\t\t { .type = INV_TYPE_ATS, .id = 5, }, /* recover a trash */\n-\t\t { .type = INV_TYPE_ATS, .id = 6, }, },\n+\t.inv = { { .smmu = \u0026smmu, .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */\n+\t\t { .smmu = \u0026smmu, .type = INV_TYPE_ATS, .id = 5, }, /* recover a trash */\n+\t\t { .smmu = \u0026smmu, .type = INV_TYPE_ATS, .id = 6, }, },\n };\n \n static struct arm_smmu_invs invs4 = {\n \t.num_invs = 3,\n-\t.inv = { { .type = INV_TYPE_ATS, .id = 10, .ssid = 1 },\n-\t\t { .type = INV_TYPE_ATS, .id = 10, .ssid = 3 },\n-\t\t { .type = INV_TYPE_ATS, .id = 12, .ssid = 1 }, },\n+\t.inv = { { .smmu = \u0026smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 1 },\n+\t\t { .smmu = \u0026smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 3 },\n+\t\t { .smmu = \u0026smmu, .type = INV_TYPE_ATS, .id = 12, .ssid = 1 }, },\n };\n \n static struct arm_smmu_invs invs5 = {\n \t.num_invs = 3,\n-\t.inv = { { .type = INV_TYPE_ATS, .id = 10, .ssid = 2 },\n-\t\t { .type = INV_TYPE_ATS, .id = 10, .ssid = 3 }, /* duplicate */\n-\t\t { .type = INV_TYPE_ATS, .id = 12, .ssid = 2 }, },\n+\t.inv = { { .smmu = \u0026smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 2 },\n+\t\t { .smmu = \u0026smmu, .type = INV_TYPE_ATS, .id = 10, .ssid = 3 }, /* duplicate */\n+\t\t { .smmu = \u0026smmu, .type = INV_TYPE_ATS, .id = 12, .ssid = 2 }, },\n };\n \n static void arm_smmu_v3_invs_test(struct kunit *test)\n@@ -705,6 +709,8 @@ static void arm_smmu_v3_invs_test(struct kunit *test)\n \t/* New array */\n \ttest_a = arm_smmu_invs_alloc(0);\n \tKUNIT_EXPECT_EQ(test, test_a-\u003enum_invs, 0);\n+\tKUNIT_EXPECT_EQ(test, test_a-\u003erange_inv_scale_max, 0);\n+\tKUNIT_EXPECT_FALSE(test, test_a-\u003ehas_full_cont_ril);\n \n \t/* Test1: merge invs1 (new array) */\n \ttest_b = arm_smmu_invs_merge(test_a, \u0026invs1);\ndiff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c\nindex 5732f3ba0122d..3747cac6353a9 100644\n--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c\n+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c\n@@ -1053,6 +1053,25 @@ static inline int arm_smmu_invs_iter_next_cmp(struct arm_smmu_invs *invs_l,\n \treturn arm_smmu_inv_cmp(cur_l, \u0026invs_r-\u003einv[next_r]);\n }\n \n+static void arm_smmu_invs_update_caps(struct arm_smmu_invs *invs,\n+\t\t\t\t      const struct arm_smmu_inv *inv)\n+{\n+\tif (arm_smmu_inv_is_ats(inv))\n+\t\tinvs-\u003ehas_ats = true;\n+\n+\tif (inv-\u003esmmu-\u003efeatures \u0026 ARM_SMMU_FEAT_RANGE_INV) {\n+\t\tunsigned int scale_max;\n+\n+\t\tif (inv-\u003esmmu-\u003eoptions \u0026 ARM_SMMU_OPT_FULL_CONT_RIL)\n+\t\t\tinvs-\u003ehas_full_cont_ril = true;\n+\n+\t\tscale_max = (inv-\u003esmmu-\u003efeatures \u0026 ARM_SMMU_FEAT_DS) ? 39 : 31;\n+\t\tif (!invs-\u003erange_inv_scale_max ||\n+\t\t    scale_max \u003c invs-\u003erange_inv_scale_max)\n+\t\t\tinvs-\u003erange_inv_scale_max = scale_max;\n+\t}\n+}\n+\n /**\n  * arm_smmu_invs_for_each_cmp - Iterate over two sorted arrays computing for\n  *                              arm_smmu_invs_merge() or arm_smmu_invs_unref()\n@@ -1123,8 +1142,7 @@ struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs,\n \t\t */\n \t\tif (new != new_invs-\u003einv)\n \t\t\tWARN_ON_ONCE(arm_smmu_inv_cmp(new - 1, new) == 1);\n-\t\tif (arm_smmu_inv_is_ats(new))\n-\t\t\tnew_invs-\u003ehas_ats = true;\n+\t\tarm_smmu_invs_update_caps(new_invs, new);\n \t\tnew++;\n \t}\n \n@@ -1234,8 +1252,7 @@ struct arm_smmu_invs *arm_smmu_invs_purge(struct arm_smmu_invs *invs)\n \n \tarm_smmu_invs_for_each_entry(invs, i, inv) {\n \t\tnew_invs-\u003einv[num_invs] = *inv;\n-\t\tif (arm_smmu_inv_is_ats(inv))\n-\t\t\tnew_invs-\u003ehas_ats = true;\n+\t\tarm_smmu_invs_update_caps(new_invs, inv);\n \t\tnum_invs++;\n \t}\n \n@@ -2363,8 +2380,8 @@ static irqreturn_t arm_smmu_combined_irq_handler(int irq, void *dev)\n \treturn IRQ_WAKE_THREAD;\n }\n \n-static struct arm_smmu_cmd\n-arm_smmu_atc_inv_to_cmd(u32 sid, int ssid, unsigned long iova, size_t size)\n+static struct arm_smmu_cmd arm_smmu_atc_inv_to_cmd(u32 sid, int ssid,\n+\t\t\t\t\t\t   struct arm_smmu_tlbi *tlbi)\n {\n \tsize_t log2_span;\n \tsize_t span_mask;\n@@ -2386,8 +2403,8 @@ arm_smmu_atc_inv_to_cmd(u32 sid, int ssid, unsigned long iova, size_t size)\n \t * This has the unpleasant side-effect of invalidating all PASID-tagged\n \t * ATC entries within the address range.\n \t */\n-\tpage_start\t= iova \u003e\u003e inval_grain_shift;\n-\tpage_end\t= (iova + size - 1) \u003e\u003e inval_grain_shift;\n+\tpage_start = tlbi-\u003estart \u003e\u003e inval_grain_shift;\n+\tpage_end = tlbi-\u003elast \u003e\u003e inval_grain_shift;\n \n \t/*\n \t * In an ATS Invalidate Request, the address must be aligned on the\n@@ -2460,126 +2477,310 @@ static void arm_smmu_tlb_inv_context(void *cookie)\n \tarm_smmu_domain_inv(smmu_domain);\n }\n \n-static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,\n-\t\t\t\t\t  struct arm_smmu_cmdq_batch *cmds,\n-\t\t\t\t\t  struct arm_smmu_cmd *cmd, bool leaf,\n-\t\t\t\t\t  unsigned long iova, size_t size,\n-\t\t\t\t\t  size_t granule, size_t pgsize)\n+/*\n+ * Check address alignment for TTL hint per SMMUv3 H.a Section 4.4.1.\n+ * Address bits below the alignment must be zero, otherwise UNPREDICTABLE.\n+ */\n+static bool arm_smmu_ttl_addr_aligned(u64 address, unsigned int tg,\n+\t\t\t\t      unsigned int ttl)\n {\n-\tunsigned long end = iova + size, num_pages = 0, tg = pgsize;\n-\tu64 orig_data0 = cmd-\u003edata[0];\n-\tsize_t inv_range = granule;\n-\tu8 ttl = 0, tg_enc = 0;\n+\tunsigned int pgsz_lg2 = arm_smmu_pt_level_to_lg2sz(tg, 3 - ttl);\n \n-\tif (WARN_ON_ONCE(!size))\n-\t\treturn;\n+\treturn !(address \u0026 GENMASK_U64(pgsz_lg2 - 1, 0));\n+}\n \n-\tif (smmu-\u003efeatures \u0026 ARM_SMMU_FEAT_RANGE_INV) {\n-\t\tnum_pages = size \u003e\u003e tg;\n+struct arm_smmu_ril_range {\n+\tu64 start_tg;\n+\t/* Normal integer, not encoded. 0 means 0.*/\n+\tu64 num;\n+\tunsigned int scale;\n+};\n \n-\t\t/* Convert page size of 12,14,16 (log2) to 1,2,3 */\n-\t\ttg_enc = (tg - 10) / 2;\n+static unsigned int arm_smmu_ril_calc_scale(u64 num_tg)\n+{\n+\treturn fls64((num_tg - 1) / (CMDQ_TLBI_RANGE_NUM_MAX + 1));\n+}\n \n-\t\t/*\n-\t\t * Determine what level the granule is at. For non-leaf, both\n-\t\t * io-pgtable and SVA pass a nominal last-level granule because\n-\t\t * they don't know what level(s) actually apply, so ignore that\n-\t\t * and leave TTL=0. However for various errata reasons we still\n-\t\t * want to use a range command, so avoid the SVA corner case\n-\t\t * where both scale and num could be 0 as well.\n-\t\t */\n-\t\tif (leaf)\n-\t\t\tttl = 4 - ((ilog2(granule) - 3) / (tg - 3));\n-\t\telse if ((num_pages \u0026 CMDQ_TLBI_RANGE_NUM_MAX) == 1)\n-\t\t\tnum_pages++;\n-\t}\n+static u64 arm_smmu_ril_calc_num(u64 num_tg, unsigned int scale)\n+{\n+\treturn DIV_ROUND_UP_ULL(num_tg, 1ULL \u003c\u003c scale);\n+}\n \n-\twhile (iova \u003c end) {\n-\t\tif (smmu-\u003efeatures \u0026 ARM_SMMU_FEAT_RANGE_INV) {\n-\t\t\t/*\n-\t\t\t * On each iteration of the loop, the range is 5 bits\n-\t\t\t * worth of the aligned size remaining.\n-\t\t\t * The range in pages is:\n-\t\t\t *\n-\t\t\t * range = (num_pages \u0026 (0x1f \u003c\u003c __ffs(num_pages)))\n-\t\t\t */\n-\t\t\tunsigned long scale, num;\n+/*\n+ * Initialize the smallest RIL covering num_tg and ending at last_tg.\n+ */\n+static struct arm_smmu_ril_range arm_smmu_ril_init_end(u64 last_tg, u64 num_tg)\n+{\n+\tstruct arm_smmu_ril_range ril = {};\n+\n+\tif (!num_tg)\n+\t\treturn ril;\n \n-\t\t\t/* Determine the power of 2 multiple number of pages */\n-\t\t\tscale = __ffs(num_pages);\n+\tril.scale = arm_smmu_ril_calc_scale(num_tg);\n+\tril.num = arm_smmu_ril_calc_num(num_tg, ril.scale);\n+\tril.start_tg = last_tg - ((ril.num \u003c\u003c ril.scale) - 1);\n+\treturn ril;\n+}\n \n-\t\t\t/* Determine how many chunks of 2^scale size we have */\n-\t\t\tnum = (num_pages \u003e\u003e scale) \u0026 CMDQ_TLBI_RANGE_NUM_MAX;\n+static void arm_smmu_tlbi_add_range_cmd(struct arm_smmu_tlbi *tlbi,\n+\t\t\t\t\tconst struct arm_smmu_ril_range *ril,\n+\t\t\t\t\tu8 ttl, u8 tg_enc)\n+{\n+\tstruct arm_smmu_cmd *cmd =\n+\t\t\u0026tlbi-\u003erange.cmds[tlbi-\u003erange.num_cmds++];\n+\tu64 iova = ril-\u003estart_tg \u003c\u003c tlbi-\u003etgsz_lg2;\n+\tunsigned int num = ril-\u003enum - 1;\n \n-\t\t\t/* Keep the pre-DS 5-bit truncation when scale \u003e 31 */\n-\t\t\tcmd-\u003edata[0] = orig_data0 |\n-\t\t\t\tFIELD_PREP(CMDQ_TLBI_0_NUM, num - 1) |\n-\t\t\t\tFIELD_PREP(CMDQ_TLBI_0_SCALE, scale \u0026 0x1f);\n+\t/* Verify address alignment for the TTL hint */\n+\tif (ttl \u0026\u0026 !arm_smmu_ttl_addr_aligned(iova, tlbi-\u003etgsz_lg2, ttl))\n+\t\tttl = 0;\n \n-\t\t\t/* range is num * 2^scale * pgsize */\n-\t\t\tinv_range = num \u003c\u003c (scale + tg);\n+\t/*\n+\t * SMMUv3 H.a Section 4.4.1: TG!=0, NUM==0, SCALE==0, TTL==0 is Reserved\n+\t * and causes CERROR_ILL. Single tg uses NUM=0, SCALE=0 with a TTL hint\n+\t * to target only the exact leaf entry.\n+\t *\n+\t * For a single tg invalidation a 0 TTL can come as a side of effect of\n+\t * the splitting.\n+\t *\n+\t * A single-TG invalidation cannot reach this point if it is part of a\n+\t * CONT group, so it is safe to transform it into a single invalidation.\n+\t * The ARM_SMMU_OPT_FULL_CONT_RIL errata does not apply.\n+\t */\n+\tif (!num \u0026\u0026 !ril-\u003escale \u0026\u0026 !ttl)\n+\t\ttg_enc = 0;\n \n-\t\t\t/* Clear out the lower order bits for the next iteration */\n-\t\t\tnum_pages -= num \u003c\u003c scale;\n+\tcmd-\u003edata[0] = FIELD_PREP(CMDQ_TLBI_0_NUM, num) |\n+\t\t       FIELD_PREP(CMDQ_TLBI_0_SCALE, ril-\u003escale);\n+\tcmd-\u003edata[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF,\n+\t\t\t\t  !tlbi-\u003etable_levels_bitmap) |\n+\t\t       FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |\n+\t\t       FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) | iova;\n+}\n+\n+static int arm_smmu_bitmap_to_level(u8 bitmap)\n+{\n+\treturn 3 - (int)__ffs(bitmap);\n+}\n+\n+/*\n+ * Compute the TTL hint from leaf/table level bitmaps. 0 ttl means no hint\n+ * invalidate all levels.\n+ */\n+static unsigned int arm_smmu_compute_ttl(u8 leaf_bitmap, u8 table_bitmap,\n+\t\t\t\t\t u8 tgsz_lg2)\n+{\n+\tint ttl;\n+\n+\tif (leaf_bitmap) {\n+\t\t/* If TTL is used then only leaves at the TTL are invalidated */\n+\t\tif (!is_power_of_2(leaf_bitmap))\n+\t\t\treturn 0;\n+\n+\t\tttl = arm_smmu_bitmap_to_level(leaf_bitmap);\n+\t\tif (table_bitmap) {\n+\t\t\tint table_ttl = arm_smmu_bitmap_to_level(table_bitmap) + 1;\n+\n+\t\t\t/*\n+\t\t\t * A RIL invalidation with !leaf_only clears out all\n+\t\t\t * table levels above the leaf level ttl only.\n+\t\t\t */\n+\t\t\tif (table_ttl \u003e ttl)\n+\t\t\t\treturn 0;\n \t\t}\n+\t} else if (table_bitmap) {\n+\t\t/*\n+\t\t * Table-only invalidation. Spec says:\n+\t\t *  For operations with Leaf=0, invalidation of cached Table\n+\t\t *  descriptors for the address and scope additionally occurs at\n+\t\t *  levels between the start of the walk and the level before\n+\t\t *  the last level given by TTL.\n+\t\t * Choose a TTL hint that covers the only target table\n+\t\t * descriptor levels.\n+\t\t */\n+\t\tttl = arm_smmu_bitmap_to_level(table_bitmap) + 1;\n \n \t\t/*\n-\t\t * IPA has fewer bits than VA, but they are reserved in the\n-\t\t * command and something would be very broken if iova had them\n-\t\t * set.\n+\t\t * 16K granule, ARM TTL=1 is reserved (SMMUv3 H.a Section\n+\t\t * 4.4.1.1) if DS=0, avoid it always for table invalidations\n+\t\t * since we don't know what instance this will be applied to\n+\t\t * yet.\n \t\t */\n-\t\tcmd-\u003edata[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF, leaf) |\n-\t\t\t       FIELD_PREP(CMDQ_TLBI_1_TTL, ttl) |\n-\t\t\t       FIELD_PREP(CMDQ_TLBI_1_TG, tg_enc) |\n-\t\t\t       (iova \u0026 ~GENMASK_U64(11, 0));\n+\t\tif (tgsz_lg2 == 14 \u0026\u0026 ttl == 1)\n+\t\t\treturn 0;\n+\t} else {\n+\t\t/* Both bitmaps zero is not allowed */\n+\t\tWARN_ON(true);\n+\t\treturn 0;\n+\t}\n+\n+\t/*\n+\t * Assumes the page table is formed properly and does not trigger the\n+\t * 16k TTL=1 condition for leaf-only unless DS is enabled.\n+\t *\n+\t * ARM level -1 never has a leaf so something has gone wrong. ARM Level\n+\t * 0 cannot be hinted because ttl=0 means no-hint.\n+\t */\n+\tif (WARN_ON(ttl \u003c 0))\n+\t\treturn 0;\n+\treturn ttl;\n+}\n+\n+/*\n+ * Generate up to two range TLBI command payloads covering [start, last]. Sets\n+ * use_full_inv if the range is too large to represent.\n+ *\n+ * Normally the first RIL is the largest representable span which does not\n+ * exceed the requested range. If necessary, the second RIL is the smallest\n+ * representable range covering the remainder and is anchored at the end. Any\n+ * excess coverage from the second RIL overlaps the first instead of exceeding\n+ * the requested range.\n+ *\n+ * For SVA on an invs containing an SMMU with ARM_SMMU_OPT_FULL_CONT_RIL,\n+ * produce only a single RIL and overinvalidate so any potential CONT is\n+ * covered by one command.\n+ */\n+static void arm_smmu_tlbi_calc_range(struct arm_smmu_tlbi *tlbi,\n+\t\t\t\t     bool single_ril,\n+\t\t\t\t     unsigned int scale_max)\n+{\n+\tu8 tgsz_lg2 = tlbi-\u003etgsz_lg2;\n+\tunsigned int ttl = arm_smmu_compute_ttl(\n+\t\ttlbi-\u003eleaf_levels_bitmap, tlbi-\u003etable_levels_bitmap, tgsz_lg2);\n+\tstruct arm_smmu_ril_range first = { .start_tg = tlbi-\u003estart \u003e\u003e\n+\t\t\t\t\t\t\ttgsz_lg2 };\n+\tu64 last_tg = tlbi-\u003elast \u003e\u003e tgsz_lg2;\n+\tu64 num_tg = last_tg - first.start_tg + 1;\n+\tu8 tg_enc = (tgsz_lg2 - 10) / 2;\n+\tstruct arm_smmu_ril_range trail;\n \n-\t\tarm_smmu_cmdq_batch_add_cmd_p(smmu, cmds, cmd);\n-\t\tiova += inv_range;\n+\t/*\n+\t * The spec defines the invalidated range as:\n+\t *   Range = ((NUM+1) * 2^SCALE) * Translation_Granule_Size\n+\t * NUM is 5 bits, so (NUM+1) covers 1..32 granules. Find the smallest\n+\t * SCALE at which a single command could cover num_tg.\n+\t *\n+\t * Unlike other IOMMUs the spec has no alignment requirement on the\n+\t * address beyond alignment to tg (so long as TTL=0).\n+\t */\n+\tfirst.scale = arm_smmu_ril_calc_scale(num_tg);\n+\tif (first.scale \u003e scale_max) {\n+\t\t/* Range too large for a single command do full invalidation */\n+\t\ttlbi-\u003erange.use_full_inv = true;\n+\t\treturn;\n+\t}\n+\n+\tif (single_ril) {\n+\t\t/*\n+\t\t * Produce a single invalidation by rounding up and disabling\n+\t\t * the trailer.\n+\t\t */\n+\t\tfirst.num = arm_smmu_ril_calc_num(num_tg, first.scale);\n+\t\ttrail.num = 0;\n+\t} else {\n+\t\t/*\n+\t\t * Produce two invalidations by rounding down and adding a\n+\t\t * second trailing RIL anchored at the end.\n+\t\t */\n+\t\tfirst.num = num_tg \u003e\u003e first.scale;\n+\t\ttrail = arm_smmu_ril_init_end(\n+\t\t\tlast_tg, num_tg - ((u64)first.num \u003c\u003c first.scale));\n \t}\n+\tarm_smmu_tlbi_add_range_cmd(tlbi, \u0026first, ttl, tg_enc);\n+\n+\tif (trail.num)\n+\t\tarm_smmu_tlbi_add_range_cmd(tlbi, \u0026trail, ttl, tg_enc);\n }\n \n-static bool arm_smmu_inv_size_too_big(struct arm_smmu_device *smmu, size_t size,\n-\t\t\t\t      size_t granule)\n+/*\n+ * Compute the stride for non-RIL single-page invalidation. Returns the log2\n+ * stride of the lowest affected level. Single invalidation removes all IOPTEs\n+ * that contain the IOVA invalidated, and we can reliably assume that the\n+ * architected page size and table sizes (not contiguous!) are reflected in the\n+ * IOTLB. Thus if there is a 2M leaf entry we only need to issue a single IOTLB\n+ * invalidation within that 2M IOVA.\n+ */\n+static u8 arm_smmu_tlbi_calc_stride(struct arm_smmu_tlbi *tlbi)\n {\n-\tsize_t max_tlbi_ops;\n+\tu8 combined = tlbi-\u003etable_levels_bitmap | tlbi-\u003eleaf_levels_bitmap;\n \n-\t/* 0 size means invalidate all */\n-\tif (!size || size == SIZE_MAX)\n-\t\treturn true;\n+\tif (WARN_ON(!combined))\n+\t\treturn U8_MAX;\n+\treturn arm_smmu_pt_level_to_lg2sz(tlbi-\u003etgsz_lg2, __ffs(combined));\n+}\n \n-\tif (smmu-\u003efeatures \u0026 ARM_SMMU_FEAT_RANGE_INV)\n-\t\treturn false;\n+/*\n+ * One TLBI command per stride-sized entry. Sets use_full_inv if too many\n+ * commands would be needed. The threshold is similar to MAX_DVM_OPS in\n+ * arch/arm64/include/asm/tlbflush.h.\n+ */\n+static void arm_smmu_tlbi_calc_single(struct arm_smmu_tlbi *tlbi)\n+{\n+\tu8 stride_lg2 = arm_smmu_tlbi_calc_stride(tlbi);\n+\tunsigned long num_ops;\n \n-\t/*\n-\t * Borrowed from the MAX_TLBI_OPS in arch/arm64/include/asm/tlbflush.h,\n-\t * this is used as a threshold to replace \"size_opcode\" commands with a\n-\t * single \"nsize_opcode\" command, when SMMU doesn't implement the range\n-\t * invalidation feature, where there can be too many per-granule TLBIs,\n-\t * resulting in a soft lockup.\n-\t */\n-\tmax_tlbi_ops = 1 \u003c\u003c (ilog2(granule) - 3);\n-\treturn size \u003e= max_tlbi_ops * granule;\n+\tif (stride_lg2 == U8_MAX) {\n+\t\ttlbi-\u003esingle.use_full_inv = true;\n+\t\treturn;\n+\t}\n+\tnum_ops = (tlbi-\u003elast - tlbi-\u003estart + 1) \u003e\u003e stride_lg2;\n+\tif (!num_ops || num_ops \u003e 512) {\n+\t\ttlbi-\u003esingle.use_full_inv = true;\n+\t\treturn;\n+\t}\n+\ttlbi-\u003esingle.num = num_ops;\n+\ttlbi-\u003esingle.stride_lg2 = stride_lg2;\n+}\n+\n+static void arm_smmu_inv_all_cmd(struct arm_smmu_inv *inv,\n+\t\t\t\t struct arm_smmu_cmdq_batch *cmds,\n+\t\t\t\t struct arm_smmu_cmd *cmd)\n+{\n+\tu64p_replace_bits(\u0026cmd-\u003edata[0], inv-\u003ensize_opcode, CMDQ_0_OP);\n+\tarm_smmu_cmdq_batch_add_cmd_p(inv-\u003esmmu, cmds, cmd);\n }\n \n-/* Used by non INV_TYPE_ATS* invalidations */\n-static void arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,\n+/*\n+ * Used by non INV_TYPE_ATS* invalidations. Returns true if it fell back to\n+ * full invalidation using nsize_opcode.\n+ */\n+static bool arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,\n \t\t\t\t       struct arm_smmu_cmdq_batch *cmds,\n \t\t\t\t       struct arm_smmu_cmd *cmd,\n-\t\t\t\t       bool leaf,\n-\t\t\t\t       unsigned long iova, size_t size,\n-\t\t\t\t       unsigned int granule)\n+\t\t\t\t       struct arm_smmu_tlbi *tlbi)\n {\n-\tif (arm_smmu_inv_size_too_big(inv-\u003esmmu, size, granule)) {\n-\t\tstruct arm_smmu_cmd nsize_cmd = *cmd;\n+\tu64 iova = tlbi-\u003estart;\n+\tunsigned int i;\n \n-\t\tu64p_replace_bits(\u0026nsize_cmd.data[0], inv-\u003ensize_opcode,\n-\t\t\t\t  CMDQ_0_OP);\n-\t\tarm_smmu_cmdq_batch_add_cmd_p(inv-\u003esmmu, cmds, \u0026nsize_cmd);\n-\t\treturn;\n+\tif (inv-\u003esmmu-\u003efeatures \u0026 ARM_SMMU_FEAT_RANGE_INV) {\n+\t\tif (tlbi-\u003erange.use_full_inv) {\n+\t\t\tarm_smmu_inv_all_cmd(inv, cmds, cmd);\n+\t\t\treturn true;\n+\t\t}\n+\t\tfor (i = 0; i \u003c tlbi-\u003erange.num_cmds; i++) {\n+\t\t\tstruct arm_smmu_cmd range_cmd = tlbi-\u003erange.cmds[i];\n+\n+\t\t\trange_cmd.data[0] |= cmd-\u003edata[0];\n+\t\t\trange_cmd.data[1] |= cmd-\u003edata[1];\n+\t\t\tarm_smmu_cmdq_batch_add_cmd_p(inv-\u003esmmu, cmds,\n+\t\t\t\t\t\t      \u0026range_cmd);\n+\t\t}\n+\t\treturn false;\n+\t}\n+\n+\tif (tlbi-\u003esingle.use_full_inv) {\n+\t\tarm_smmu_inv_all_cmd(inv, cmds, cmd);\n+\t\treturn true;\n \t}\n \n-\tarm_smmu_cmdq_batch_add_range(inv-\u003esmmu, cmds, cmd, leaf,\n-\t\t\t\t      iova, size, granule, inv-\u003epgsize);\n+\tfor (i = 0; i \u003c tlbi-\u003esingle.num; i++) {\n+\t\tcmd-\u003edata[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF,\n+\t\t\t\t\t  !tlbi-\u003etable_levels_bitmap) |\n+\t\t\t       (iova \u0026 ~GENMASK_U64(11, 0));\n+\t\tiova += BIT_U64(tlbi-\u003esingle.stride_lg2);\n+\t\tarm_smmu_cmdq_batch_add_cmd_p(inv-\u003esmmu, cmds, cmd);\n+\t}\n+\treturn false;\n }\n \n static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,\n@@ -2598,10 +2799,10 @@ static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,\n \treturn false;\n }\n \n-static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,\n-\t\t\t\t\tunsigned long iova, size_t size,\n-\t\t\t\t\tunsigned int granule, bool leaf)\n+static void arm_smmu_domain_tlbi_inv(struct arm_smmu_tlbi *tlbi,\n+\t\t\t\t     struct arm_smmu_invs *invs)\n {\n+\tstruct arm_smmu_inv *used_s12_vmall = NULL;\n \tstruct arm_smmu_cmdq_batch cmds = {};\n \tstruct arm_smmu_inv *cur;\n \tstruct arm_smmu_inv *end;\n@@ -2630,18 +2831,24 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,\n \t\tcase INV_TYPE_S1_ASID:\n \t\t\tcmd = arm_smmu_make_cmd_tlbi(cur-\u003esize_opcode,\n \t\t\t\t\t\t     cur-\u003eid, 0);\n-\t\t\tarm_smmu_inv_to_cmdq_batch(cur, \u0026cmds, \u0026cmd, leaf,\n-\t\t\t\t\t\t   iova, size, granule);\n+\t\t\tarm_smmu_inv_to_cmdq_batch(cur, \u0026cmds, \u0026cmd, tlbi);\n \t\t\tbreak;\n \t\tcase INV_TYPE_S2_VMID:\n \t\t\tcmd = arm_smmu_make_cmd_tlbi(cur-\u003esize_opcode,\n \t\t\t\t\t\t     0, cur-\u003eid);\n-\t\t\tarm_smmu_inv_to_cmdq_batch(cur, \u0026cmds, \u0026cmd, leaf,\n-\t\t\t\t\t\t   iova, size, granule);\n+\t\t\tif (arm_smmu_inv_to_cmdq_batch(cur, \u0026cmds, \u0026cmd, tlbi))\n+\t\t\t\tused_s12_vmall = cur + 1;\n \t\t\tbreak;\n \t\tcase INV_TYPE_S2_VMID_S1_CLEAR:\n-\t\t\t/* CMDQ_OP_TLBI_S12_VMALL already flushed S1 entries */\n-\t\t\tif (arm_smmu_inv_size_too_big(cur-\u003esmmu, size, granule))\n+\t\t\t/*\n+\t\t\t * S2_VMID used CMDQ_OP_TLBI_S12_VMALL which already\n+\t\t\t * flushed S1 entries. These two types always come in\n+\t\t\t * pairs and arm_smmu_inv_cmp() ensures that they are\n+\t\t\t * consecutive in the list for the same SMMU. There may\n+\t\t\t * be several pairings so check this is paired with the\n+\t\t\t * one that did the full invalidation.\n+\t\t\t */\n+\t\t\tif (used_s12_vmall == cur)\n \t\t\t\tbreak;\n \t\t\tarm_smmu_cmdq_batch_add_cmd(\n \t\t\t\tsmmu, \u0026cmds,\n@@ -2652,7 +2859,7 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,\n \t\t\tarm_smmu_cmdq_batch_add_cmd(\n \t\t\t\tsmmu, \u0026cmds,\n \t\t\t\tarm_smmu_atc_inv_to_cmd(cur-\u003eid, cur-\u003essid,\n-\t\t\t\t\t\t\tiova, size));\n+\t\t\t\t\t\t\ttlbi));\n \t\t\tbreak;\n \t\tcase INV_TYPE_ATS_FULL:\n \t\t\tarm_smmu_cmdq_batch_add_cmd(\n@@ -2679,12 +2886,14 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,\n \t}\n }\n \n-void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,\n-\t\t\t       unsigned long iova, size_t size,\n-\t\t\t       unsigned int granule, bool leaf)\n+void arm_smmu_domain_tlbi(struct arm_smmu_tlbi *tlbi,\n+\t\t\t  struct arm_smmu_domain *smmu_domain)\n {\n \tstruct arm_smmu_invs *invs;\n \n+\tif (!tlbi-\u003esingle.use_full_inv)\n+\t\tarm_smmu_tlbi_calc_single(tlbi);\n+\n \t/*\n \t * An invalidation request must follow some IOPTE change and then load\n \t * an invalidation array. In the meantime, a domain attachment mutates\n@@ -2702,7 +2911,7 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,\n \t *\n \t *  [CPU0]                        | [CPU1]\n \t *  change IOPTE on new domain:   |\n-\t *  arm_smmu_domain_inv_range() { | arm_smmu_install_new_domain_invs()\n+\t *  arm_smmu_domain_tlbi() {      | arm_smmu_install_new_domain_invs()\n \t *    smp_mb(); // ensures IOPTE  | arm_smmu_install_ste_for_dev {\n \t *              // seen by SMMU   |   dma_wmb(); // ensures invs update\n \t *    // load the updated invs    |              // before updating STE\n@@ -2715,6 +2924,20 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,\n \trcu_read_lock();\n \tinvs = rcu_dereference(smmu_domain-\u003einvs);\n \n+\t/*\n+\t * Only precalculate RIL if it will be used, invs generation ensures\n+\t * this matches the instances used for invalidation.\n+\t */\n+\tif (invs-\u003erange_inv_scale_max) {\n+\t\tif (!tlbi-\u003erange.use_full_inv) {\n+\t\t\tarm_smmu_tlbi_calc_range(\n+\t\t\t\ttlbi,\n+\t\t\t\tsmmu_domain-\u003estage == ARM_SMMU_DOMAIN_SVA \u0026\u0026\n+\t\t\t\t\tinvs-\u003ehas_full_cont_ril,\n+\t\t\t\tinvs-\u003erange_inv_scale_max);\n+\t\t}\n+\t}\n+\n \t/*\n \t * Avoid locking unless ATS is being used. No ATC invalidation can be\n \t * going on after a domain is detached.\n@@ -2723,10 +2946,10 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,\n \t\tunsigned long flags;\n \n \t\tread_lock_irqsave(\u0026invs-\u003erwlock, flags);\n-\t\t__arm_smmu_domain_inv_range(invs, iova, size, granule, leaf);\n+\t\tarm_smmu_domain_tlbi_inv(tlbi, invs);\n \t\tread_unlock_irqrestore(\u0026invs-\u003erwlock, flags);\n \t} else {\n-\t\t__arm_smmu_domain_inv_range(invs, iova, size, granule, leaf);\n+\t\tarm_smmu_domain_tlbi_inv(tlbi, invs);\n \t}\n \n \trcu_read_unlock();\n@@ -2742,12 +2965,28 @@ static void arm_smmu_tlb_inv_page_nosync(struct iommu_iotlb_gather *gather,\n \tiommu_iotlb_gather_add_page(domain, gather, iova, granule);\n }\n \n+/*\n+ * Called by io-pgtable-arm.c for each single table level it wants to remove.\n+ * size is the size of the table level and granule is the tg in bytes. This must\n+ * clear the walk cache and any leaves within the range.\n+ */\n static void arm_smmu_tlb_inv_walk(unsigned long iova, size_t size,\n \t\t\t\t  size_t granule, void *cookie)\n {\n \tstruct arm_smmu_domain *smmu_domain = cookie;\n+\tu8 tgsz_lg2 = smmu_domain-\u003etgsz_lg2;\n+\tstruct arm_smmu_tlbi tlbi = {\n+\t\t.tgsz_lg2 = smmu_domain-\u003etgsz_lg2,\n+\t\t.start = iova,\n+\t\t.last = iova + size - 1,\n+\t};\n+\tu8 table_levels =\n+\t\tBIT(arm_smmu_pt_lg2sz_to_level(tgsz_lg2, ilog2(size)));\n \n-\tarm_smmu_domain_inv_range(smmu_domain, iova, size, granule, false);\n+\ttlbi.table_levels_bitmap = table_levels;\n+\ttlbi.leaf_levels_bitmap = table_levels - 1;\n+\n+\tarm_smmu_domain_tlbi(\u0026tlbi, smmu_domain);\n }\n \n static const struct iommu_flush_ops arm_smmu_flush_ops = {\n@@ -2933,6 +3172,7 @@ static int arm_smmu_domain_finalise(struct arm_smmu_domain *smmu_domain,\n \t\treturn -ENOMEM;\n \n \tsmmu_domain-\u003edomain.pgsize_bitmap = pgtbl_cfg.pgsize_bitmap;\n+\tsmmu_domain-\u003etgsz_lg2 = __ffs(pgtbl_cfg.pgsize_bitmap);\n \tsmmu_domain-\u003edomain.geometry.aperture_end = (1UL \u003c\u003c pgtbl_cfg.ias) - 1;\n \tsmmu_domain-\u003edomain.geometry.force_aperture = true;\n \tif (enable_dirty \u0026\u0026 smmu_domain-\u003estage == ARM_SMMU_DOMAIN_S1)\n@@ -3172,15 +3412,13 @@ static void arm_smmu_disable_iopf(struct arm_smmu_master *master,\n \n static struct arm_smmu_inv *\n arm_smmu_master_build_inv(struct arm_smmu_master *master,\n-\t\t\t  enum arm_smmu_inv_type type, u32 id, ioasid_t ssid,\n-\t\t\t  size_t pgsize)\n+\t\t\t  enum arm_smmu_inv_type type, u32 id, ioasid_t ssid)\n {\n \tstruct arm_smmu_invs *build_invs = master-\u003ebuild_invs;\n \tstruct arm_smmu_inv *cur, inv = {\n \t\t.smmu = master-\u003esmmu,\n \t\t.type = type,\n \t\t.id = id,\n-\t\t.pgsize = pgsize,\n \t};\n \n \tif (WARN_ON(build_invs-\u003enum_invs \u003e= build_invs-\u003emax_invs))\n@@ -3232,28 +3470,24 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,\n \t\t\t   ioasid_t ssid, struct arm_smmu_domain *smmu_domain)\n {\n \tconst bool nesting = smmu_domain-\u003enest_parent;\n-\tsize_t pgsize = 0, i;\n+\tsize_t i;\n \n \tiommu_group_mutex_assert(master-\u003edev);\n \n \tmaster-\u003ebuild_invs-\u003enum_invs = 0;\n \n-\t/* Range-based invalidation requires the leaf pgsize for calculation */\n-\tif (master-\u003esmmu-\u003efeatures \u0026 ARM_SMMU_FEAT_RANGE_INV)\n-\t\tpgsize = __ffs(smmu_domain-\u003edomain.pgsize_bitmap);\n-\n \tswitch (smmu_domain-\u003estage) {\n \tcase ARM_SMMU_DOMAIN_SVA:\n \tcase ARM_SMMU_DOMAIN_S1:\n \t\tif (!arm_smmu_master_build_inv(master, INV_TYPE_S1_ASID,\n \t\t\t\t\t       smmu_domain-\u003ecd.asid,\n-\t\t\t\t\t       IOMMU_NO_PASID, pgsize))\n+\t\t\t\t\t       IOMMU_NO_PASID))\n \t\t\treturn NULL;\n \t\tbreak;\n \tcase ARM_SMMU_DOMAIN_S2:\n \t\tif (!arm_smmu_master_build_inv(master, INV_TYPE_S2_VMID,\n \t\t\t\t\t       smmu_domain-\u003es2_cfg.vmid,\n-\t\t\t\t\t       IOMMU_NO_PASID, pgsize))\n+\t\t\t\t\t       IOMMU_NO_PASID))\n \t\t\treturn NULL;\n \t\tbreak;\n \tdefault:\n@@ -3265,7 +3499,7 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,\n \tif (nesting) {\n \t\tif (!arm_smmu_master_build_inv(\n \t\t\t    master, INV_TYPE_S2_VMID_S1_CLEAR,\n-\t\t\t    smmu_domain-\u003es2_cfg.vmid, IOMMU_NO_PASID, 0))\n+\t\t\t    smmu_domain-\u003es2_cfg.vmid, IOMMU_NO_PASID))\n \t\t\treturn NULL;\n \t}\n \n@@ -3276,7 +3510,7 @@ arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled,\n \t\t */\n \t\tif (!arm_smmu_master_build_inv(\n \t\t\t    master, nesting ? INV_TYPE_ATS_FULL : INV_TYPE_ATS,\n-\t\t\t    master-\u003estreams[i].id, ssid, 0))\n+\t\t\t    master-\u003estreams[i].id, ssid))\n \t\t\treturn NULL;\n \t}\n \n@@ -4021,17 +4255,32 @@ static void arm_smmu_flush_iotlb_all(struct iommu_domain *domain)\n \t\tarm_smmu_tlb_inv_context(smmu_domain);\n }\n \n+/*\n+ * io-pgtable-arm.c calls this function either under\n+ * arm_smmu_tlb_inv_page_nosync() or via the normal iommu code to flush the\n+ * gather. Due to how iommu_iotlb_gather_add_page() works the gather will end up\n+ * with a single uniform pgsize leaf. If it has to change to a different leaf\n+ * level then it flushes the gather and starts a fresh one. Thus this always\n+ * targets only a single leaf level.\n+ */\n static void arm_smmu_iotlb_sync(struct iommu_domain *domain,\n \t\t\t\tstruct iommu_iotlb_gather *gather)\n {\n \tstruct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);\n+\tunsigned int tg = smmu_domain-\u003etgsz_lg2;\n+\tstruct arm_smmu_tlbi tlbi = {\n+\t\t.tgsz_lg2 = smmu_domain-\u003etgsz_lg2,\n+\t\t.start = gather-\u003estart,\n+\t\t.last = gather-\u003eend,\n+\t};\n \n-\tif (!gather-\u003epgsize)\n+\tif (WARN_ON(gather-\u003epgsize \u003c BIT(tg)))\n \t\treturn;\n \n-\tarm_smmu_domain_inv_range(smmu_domain, gather-\u003estart,\n-\t\t\t\t  gather-\u003eend - gather-\u003estart + 1,\n-\t\t\t\t  gather-\u003epgsize, true);\n+\ttlbi.leaf_levels_bitmap =\n+\t\tBIT(arm_smmu_pt_lg2sz_to_level(tg, ilog2(gather-\u003epgsize)));\n+\n+\tarm_smmu_domain_tlbi(\u0026tlbi, smmu_domain);\n }\n \n static phys_addr_t\n@@ -5009,11 +5258,19 @@ static void arm_smmu_device_iidr_probe(struct arm_smmu_device *smmu)\n \t\t\t\t/* Arm errata 2268618, 2812531 */\n \t\t\t\tsmmu-\u003efeatures \u0026= ~ARM_SMMU_FEAT_NESTING;\n \t\t\t}\n+\t\t\t/* Arm errata 3777127 */\n+\t\t\tsmmu-\u003eoptions |= ARM_SMMU_OPT_FULL_CONT_RIL;\n \t\t\tbreak;\n \t\tcase IIDR_PRODUCTID_ARM_MMU_L1:\n+\t\t\t/* Arm errata 3878312 */\n+\t\t\tsmmu-\u003efeatures \u0026= ~ARM_SMMU_FEAT_BTM;\n+\t\t\tbreak;\n \t\tcase IIDR_PRODUCTID_ARM_MMU_S3:\n-\t\t\t/* Arm errata 3878312/3995052 */\n+\t\t\t/* Arm errata 3995052 */\n \t\t\tsmmu-\u003efeatures \u0026= ~ARM_SMMU_FEAT_BTM;\n+\t\t\t/* Arm errata 3673557 */\n+\t\t\tif (variant \u003c 1 || (variant == 1 \u0026\u0026 revision \u003c 1))\n+\t\t\t\tsmmu-\u003eoptions |= ARM_SMMU_OPT_FULL_CONT_RIL;\n \t\t\tbreak;\n \t\t}\n \t\tbreak;\ndiff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h\nindex dd2fee2f560e6..65fa94e865149 100644\n--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h\n+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h\n@@ -736,10 +736,9 @@ struct arm_smmu_inv {\n \tu8 size_opcode;\n \tu8 nsize_opcode;\n \tu32 id; /* ASID or VMID or SID */\n-\tunion {\n-\t\tsize_t pgsize; /* ARM_SMMU_FEAT_RANGE_INV */\n-\t\tu32 ssid; /* INV_TYPE_ATS */\n-\t};\n+\n+\t/* Only used by INV_TYPE_ATS */\n+\tu32 ssid;\n \n \tint users; /* users=0 to mark as a trash to be purged */\n };\n@@ -759,6 +758,9 @@ static inline bool arm_smmu_inv_is_ats(const struct arm_smmu_inv *inv)\n  *               Must not be greater than @num_invs\n  * @rwlock: optional rwlock to fence ATS operations\n  * @has_ats: flag if the array contains an INV_TYPE_ATS or INV_TYPE_ATS_FULL\n+ * @has_full_cont_ril: flag if any entry's SMMU requires the CONT RIL workaround\n+ * @range_inv_scale_max: max SCALE usable by all range-capable SMMUs, or 0 if\n+ *                       no SMMU supports range invalidation\n  * @rcu: rcu head for kfree_rcu()\n  * @inv: flexible invalidation array\n  *\n@@ -788,6 +790,8 @@ struct arm_smmu_invs {\n \tsize_t num_trashes;\n \trwlock_t rwlock;\n \tbool has_ats;\n+\tbool has_full_cont_ril;\n+\tu8 range_inv_scale_max;\n \tstruct rcu_head rcu;\n \tstruct arm_smmu_inv inv[] __counted_by(max_invs);\n };\n@@ -805,6 +809,53 @@ static inline struct arm_smmu_invs *arm_smmu_invs_alloc(size_t num_invs)\n \treturn new_invs;\n }\n \n+/* Generic page-table level 0 is the leaf-only level. */\n+static inline unsigned int arm_smmu_pt_level_to_lg2sz(unsigned int tgsz_lg2,\n+\t\t\t\t\t\t      unsigned int level)\n+{\n+\treturn tgsz_lg2 + (tgsz_lg2 - ilog2(sizeof(u64))) * level;\n+}\n+\n+static inline unsigned int arm_smmu_pt_lg2sz_to_level(unsigned int tgsz_lg2,\n+\t\t\t\t\t\t      unsigned int lg2sz)\n+{\n+\treturn (lg2sz - tgsz_lg2) / (tgsz_lg2 - ilog2(sizeof(u64)));\n+}\n+\n+struct arm_smmu_tlbi {\n+\tunsigned long start;\n+\tunsigned long last;\n+\t/* Base Translation Granule of the page table */\n+\tu8 tgsz_lg2;\n+\t/*\n+\t * Level bitmaps use iommupt numbering: bit 0 is the leaf-only level\n+\t * (ARM level 3), bit 1 is the next level up (ARM level 2), etc. These\n+\t * match the iommu_iotlb_gather.pt fields. Each set bit indicates a\n+\t * change at that level. The contiguous hint has no effect on\n+\t * invalidation processing because HW can ignore the hint.\n+\t *\n+\t * The pair selects the invalidation scope:\n+\t *   table!=0, leaf==0 : walk cache only\n+\t *   table==0, leaf!=0 : leaves only\n+\t *   table!=0, leaf!=0 : walk cache + all leaves\n+\t *   table==0, leaf==0 : illegal\n+\t */\n+\tu8 leaf_levels_bitmap;\n+\tu8 table_levels_bitmap;\n+\n+\tstruct {\n+\t\tbool use_full_inv;\n+\t\tu16 num;\n+\t\tu8 stride_lg2;\n+\t} single;\n+\n+\tstruct {\n+\t\tbool use_full_inv;\n+\t\tu8 num_cmds;\n+\t\tstruct arm_smmu_cmd cmds[2];\n+\t} range;\n+};\n+\n struct arm_smmu_evtq {\n \tstruct arm_smmu_queue\t\tq;\n \tstruct iopf_queue\t\t*iopf;\n@@ -934,6 +985,8 @@ struct arm_smmu_device {\n #define ARM_SMMU_OPT_MSIPOLL\t\t(1 \u003c\u003c 2)\n #define ARM_SMMU_OPT_CMDQ_FORCE_SYNC\t(1 \u003c\u003c 3)\n #define ARM_SMMU_OPT_TEGRA241_CMDQV\t(1 \u003c\u003c 4)\n+/* RANGE_INV is mandatory and one RIL must fully span an invalidated CONT */\n+#define ARM_SMMU_OPT_FULL_CONT_RIL\t(1 \u003c\u003c 5)\n \tu32\t\t\t\toptions;\n \n \tstruct arm_smmu_cmdq\t\tcmdq;\n@@ -1049,6 +1102,7 @@ struct arm_smmu_domain {\n \tspinlock_t\t\t\tdevices_lock;\n \tbool\t\t\t\tenforce_cache_coherency : 1;\n \tbool\t\t\t\tnest_parent : 1;\n+\tu8\t\t\t\ttgsz_lg2;\n \n \tstruct mmu_notifier\t\tmmu_notifier;\n };\n@@ -1154,13 +1208,21 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,\n \t\t       struct arm_smmu_domain *smmu_domain, ioasid_t pasid,\n \t\t       struct arm_smmu_cd *cd, struct iommu_domain *old);\n \n-void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,\n-\t\t\t       unsigned long iova, size_t size,\n-\t\t\t       unsigned int granule, bool leaf);\n+void arm_smmu_domain_tlbi(struct arm_smmu_tlbi *tlbi,\n+\t\t\t  struct arm_smmu_domain *smmu_domain);\n \n static inline void arm_smmu_domain_inv(struct arm_smmu_domain *smmu_domain)\n {\n-\tarm_smmu_domain_inv_range(smmu_domain, 0, 0, 0, false);\n+\t/* Prefilled for invalidate all */\n+\tstruct arm_smmu_tlbi tlbi = {\n+\t\t.start = 0,\n+\t\t.last = ULONG_MAX,\n+\t\t.tgsz_lg2 = smmu_domain-\u003etgsz_lg2,\n+\t\t.single.use_full_inv = true,\n+\t\t.range.use_full_inv = true,\n+\t};\n+\n+\tarm_smmu_domain_tlbi(\u0026tlbi, smmu_domain);\n }\n \n void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu,\n"
}

2/1 2026/09/18 18:32 agent patch-evaluator
0m Model:
core

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch modifies documentation and the ARM SMMUv3 driver (drivers/iommu/arm/arm-smmu-v3/). The ARM_SMMU_V3 config option depends strictly on ARM64, making this code structurally unreachable and unbuildable on the target architecture (amd64).",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 7d5f0e7a108e89270e7c489c2c69695e85b7ad9d
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Sep 18 18:32:58 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst
index ac3248b9f2f3b..68018bf75b791 100644
--- a/Documentation/arch/arm64/silicon-errata.rst
+++ b/Documentation/arch/arm64/silicon-errata.rst
@@ -271,7 +271,8 @@ stable kernels.
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | MMU L1          | #3878312        | N/A                         |
 +----------------+-----------------+-----------------+-----------------------------+
-| ARM            | MMU S3          | #3995052        | N/A                         |
+| ARM            | MMU S3          | #3995052,       | N/A                         |
+|                |                 | #3673557        |                             |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | GIC-700         | #2941627        | ARM64_ERRATUM_2941627       |
 +----------------+-----------------+-----------------+-----------------------------+
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c
index 0a429c64fbf3e..fc9622cdbac80 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
@@ -140,16 +140,36 @@ 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;
+	u8 tgsz_lg2 = smmu_domain->tgsz_lg2;
+	struct arm_smmu_tlbi tlbi = {
+		.tgsz_lg2 = smmu_domain->tgsz_lg2,
+		.start = start,
+		.last = end - 1,
+		/*
+		 * 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.
+		 */
+		.table_levels_bitmap = 0xfe,
+	};
+	u8 pmd_lg2sz = arm_smmu_pt_level_to_lg2sz(tgsz_lg2, 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. So do a simple translation here by calculating size correctly.
+	 * 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. The mm must not try to partially
+	 * invalidate pmd/etc.
 	 */
-	size = end - start;
+	if (end - start < BIT_U64(pmd_lg2sz))
+		tlbi.leaf_levels_bitmap = 1;
+	else
+		tlbi.leaf_levels_bitmap = 0xff;
 
-	arm_smmu_domain_inv_range(smmu_domain, start, size, PAGE_SIZE, false);
+	arm_smmu_domain_tlbi(&tlbi, smmu_domain);
 }
 
 static void arm_smmu_mm_release(struct mmu_notifier *mn, struct mm_struct *mm)
@@ -215,6 +235,13 @@ bool arm_smmu_sva_supported(struct arm_smmu_device *smmu)
 	if (system_supports_haft())
 		feat_mask |= ARM_SMMU_FEAT_HAFT;
 
+	/*
+	 * The workaround for ARM_SMMU_OPT_FULL_CONT_RIL requires range
+	 * invalidation support.
+	 */
+	if (smmu->options & ARM_SMMU_OPT_FULL_CONT_RIL)
+		feat_mask |= ARM_SMMU_FEAT_RANGE_INV;
+
 	if ((smmu->features & feat_mask) != feat_mask)
 		return false;
 
@@ -338,6 +365,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-test.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
index add671363c828..ca734c1410641 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
@@ -24,7 +24,9 @@ struct arm_smmu_test_writer {
 static struct arm_smmu_ste bypass_ste;
 static struct arm_smmu_ste abort_ste;
 static struct arm_smmu_device smmu = {
-	.features = ARM_SMMU_FEAT_STALLS | ARM_SMMU_FEAT_ATTR_TYPES_OVR
+	.features = ARM_SMMU_FEAT_STALLS | ARM_SMMU_FEAT_ATTR_TYPES_OVR |
+		    ARM_SMMU_FEAT_RANGE_INV,
+	.options = ARM_SMMU_OPT_FULL_CONT_RIL,
 };
 static struct mm_struct sva_mm = {
 	.pgd = (void *)0xdaedbeefdeadbeefULL,
@@ -645,6 +647,8 @@ static void arm_smmu_v3_invs_test_verify(struct kunit *test,
 {
 	KUNIT_EXPECT_EQ(test, invs->num_invs, num_invs);
 	KUNIT_EXPECT_EQ(test, invs->num_trashes, num_trashes);
+	KUNIT_EXPECT_EQ(test, invs->range_inv_scale_max, 31);
+	KUNIT_EXPECT_TRUE(test, invs->has_full_cont_ril);
 	while (num_invs--) {
 		KUNIT_EXPECT_EQ(test, invs->inv[num_invs].id, ids[num_invs]);
 		KUNIT_EXPECT_EQ(test, READ_ONCE(invs->inv[num_invs].users),
@@ -655,37 +659,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)
@@ -705,6 +709,8 @@ static void arm_smmu_v3_invs_test(struct kunit *test)
 	/* New array */
 	test_a = arm_smmu_invs_alloc(0);
 	KUNIT_EXPECT_EQ(test, test_a->num_invs, 0);
+	KUNIT_EXPECT_EQ(test, test_a->range_inv_scale_max, 0);
+	KUNIT_EXPECT_FALSE(test, test_a->has_full_cont_ril);
 
 	/* Test1: merge invs1 (new array) */
 	test_b = arm_smmu_invs_merge(test_a, &invs1);
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 5732f3ba0122d..3747cac6353a9 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -1053,6 +1053,25 @@ 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) {
+		unsigned int scale_max;
+
+		if (inv->smmu->options & ARM_SMMU_OPT_FULL_CONT_RIL)
+			invs->has_full_cont_ril = 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;
+	}
+}
+
 /**
  * arm_smmu_invs_for_each_cmp - Iterate over two sorted arrays computing for
  *                              arm_smmu_invs_merge() or arm_smmu_invs_unref()
@@ -1123,8 +1142,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++;
 	}
 
@@ -1234,8 +1252,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++;
 	}
 
@@ -2363,8 +2380,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;
@@ -2386,8 +2403,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->start >> inval_grain_shift;
+	page_end = tlbi->last >> inval_grain_shift;
 
 	/*
 	 * In an ATS Invalidate Request, the address must be aligned on the
@@ -2460,126 +2477,310 @@ 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,
-					  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)
+/*
+ * Check address alignment for TTL hint per SMMUv3 H.a 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 long end = iova + size, num_pages = 0, tg = pgsize;
-	u64 orig_data0 = cmd->data[0];
-	size_t inv_range = granule;
-	u8 ttl = 0, tg_enc = 0;
+	unsigned int pgsz_lg2 = arm_smmu_pt_level_to_lg2sz(tg, 3 - ttl);
 
-	if (WARN_ON_ONCE(!size))
-		return;
+	return !(address & GENMASK_U64(pgsz_lg2 - 1, 0));
+}
 
-	if (smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
-		num_pages = size >> tg;
+struct arm_smmu_ril_range {
+	u64 start_tg;
+	/* Normal integer, not encoded. 0 means 0.*/
+	u64 num;
+	unsigned int scale;
+};
 
-		/* Convert page size of 12,14,16 (log2) to 1,2,3 */
-		tg_enc = (tg - 10) / 2;
+static unsigned int arm_smmu_ril_calc_scale(u64 num_tg)
+{
+	return fls64((num_tg - 1) / (CMDQ_TLBI_RANGE_NUM_MAX + 1));
+}
 
-		/*
-		 * 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 (leaf)
-			ttl = 4 - ((ilog2(granule) - 3) / (tg - 3));
-		else if ((num_pages & CMDQ_TLBI_RANGE_NUM_MAX) == 1)
-			num_pages++;
-	}
+static u64 arm_smmu_ril_calc_num(u64 num_tg, unsigned int scale)
+{
+	return DIV_ROUND_UP_ULL(num_tg, 1ULL << scale);
+}
 
-	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;
+/*
+ * Initialize the smallest RIL covering num_tg and ending at last_tg.
+ */
+static struct arm_smmu_ril_range arm_smmu_ril_init_end(u64 last_tg, u64 num_tg)
+{
+	struct arm_smmu_ril_range ril = {};
+
+	if (!num_tg)
+		return ril;
 
-			/* Determine the power of 2 multiple number of pages */
-			scale = __ffs(num_pages);
+	ril.scale = arm_smmu_ril_calc_scale(num_tg);
+	ril.num = arm_smmu_ril_calc_num(num_tg, ril.scale);
+	ril.start_tg = last_tg - ((ril.num << ril.scale) - 1);
+	return ril;
+}
 
-			/* Determine how many chunks of 2^scale size we have */
-			num = (num_pages >> scale) & CMDQ_TLBI_RANGE_NUM_MAX;
+static void arm_smmu_tlbi_add_range_cmd(struct arm_smmu_tlbi *tlbi,
+					const struct arm_smmu_ril_range *ril,
+					u8 ttl, u8 tg_enc)
+{
+	struct arm_smmu_cmd *cmd =
+		&tlbi->range.cmds[tlbi->range.num_cmds++];
+	u64 iova = ril->start_tg << tlbi->tgsz_lg2;
+	unsigned int num = ril->num - 1;
 
-			/* Keep the pre-DS 5-bit truncation when scale > 31 */
-			cmd->data[0] = orig_data0 |
-				FIELD_PREP(CMDQ_TLBI_0_NUM, num - 1) |
-				FIELD_PREP(CMDQ_TLBI_0_SCALE, scale & 0x1f);
+	/* Verify address alignment for the TTL hint */
+	if (ttl && !arm_smmu_ttl_addr_aligned(iova, tlbi->tgsz_lg2, ttl))
+		ttl = 0;
 
-			/* range is num * 2^scale * pgsize */
-			inv_range = num << (scale + tg);
+	/*
+	 * SMMUv3 H.a Section 4.4.1: TG!=0, NUM==0, SCALE==0, TTL==0 is Reserved
+	 * and causes CERROR_ILL. Single tg uses NUM=0, SCALE=0 with a TTL hint
+	 * to target only the exact leaf entry.
+	 *
+	 * For a single tg invalidation a 0 TTL can come as a side of effect of
+	 * the splitting.
+	 *
+	 * A single-TG invalidation cannot reach this point if it is part of a
+	 * CONT group, so it is safe to transform it into a single invalidation.
+	 * The ARM_SMMU_OPT_FULL_CONT_RIL errata does not apply.
+	 */
+	if (!num && !ril->scale && !ttl)
+		tg_enc = 0;
 
-			/* Clear out the lower order bits for the next iteration */
-			num_pages -= num << scale;
+	cmd->data[0] = FIELD_PREP(CMDQ_TLBI_0_NUM, num) |
+		       FIELD_PREP(CMDQ_TLBI_0_SCALE, ril->scale);
+	cmd->data[1] = 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) | iova;
+}
+
+static int arm_smmu_bitmap_to_level(u8 bitmap)
+{
+	return 3 - (int)__ffs(bitmap);
+}
+
+/*
+ * Compute the TTL hint from leaf/table level bitmaps. 0 ttl means no hint
+ * invalidate all levels.
+ */
+static unsigned int arm_smmu_compute_ttl(u8 leaf_bitmap, u8 table_bitmap,
+					 u8 tgsz_lg2)
+{
+	int ttl;
+
+	if (leaf_bitmap) {
+		/* If TTL is used then only leaves at the TTL are invalidated */
+		if (!is_power_of_2(leaf_bitmap))
+			return 0;
+
+		ttl = arm_smmu_bitmap_to_level(leaf_bitmap);
+		if (table_bitmap) {
+			int table_ttl = arm_smmu_bitmap_to_level(table_bitmap) + 1;
+
+			/*
+			 * A RIL invalidation with !leaf_only clears out all
+			 * table levels above the leaf level ttl only.
+			 */
+			if (table_ttl > ttl)
+				return 0;
 		}
+	} else if (table_bitmap) {
+		/*
+		 * Table-only invalidation. Spec says:
+		 *  For operations with Leaf=0, invalidation of cached Table
+		 *  descriptors for the address and scope additionally occurs at
+		 *  levels between the start of the walk and the level before
+		 *  the last level given by TTL.
+		 * Choose a TTL hint that covers the only target table
+		 * descriptor levels.
+		 */
+		ttl = arm_smmu_bitmap_to_level(table_bitmap) + 1;
 
 		/*
-		 * IPA has fewer bits than VA, but they are reserved in the
-		 * command and something would be very broken if iova had them
-		 * set.
+		 * 16K granule, ARM TTL=1 is reserved (SMMUv3 H.a Section
+		 * 4.4.1.1) if DS=0, avoid it always for table invalidations
+		 * since we don't know what instance this will be applied to
+		 * yet.
 		 */
-		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) |
-			       (iova & ~GENMASK_U64(11, 0));
+		if (tgsz_lg2 == 14 && ttl == 1)
+			return 0;
+	} else {
+		/* Both bitmaps zero is not allowed */
+		WARN_ON(true);
+		return 0;
+	}
+
+	/*
+	 * Assumes the page table is formed properly and does not trigger the
+	 * 16k TTL=1 condition for leaf-only unless DS is enabled.
+	 *
+	 * ARM level -1 never has a leaf so something has gone wrong. ARM Level
+	 * 0 cannot be hinted because ttl=0 means no-hint.
+	 */
+	if (WARN_ON(ttl < 0))
+		return 0;
+	return ttl;
+}
+
+/*
+ * Generate up to two range TLBI command payloads covering [start, last]. Sets
+ * use_full_inv if the range is too large to represent.
+ *
+ * Normally the first RIL is the largest representable span which does not
+ * exceed the requested range. If necessary, the second RIL is the smallest
+ * representable range covering the remainder and is anchored at the end. Any
+ * excess coverage from the second RIL overlaps the first instead of exceeding
+ * the requested range.
+ *
+ * For SVA on an invs containing an SMMU with ARM_SMMU_OPT_FULL_CONT_RIL,
+ * produce only a single RIL and overinvalidate so any potential CONT is
+ * covered by one command.
+ */
+static void arm_smmu_tlbi_calc_range(struct arm_smmu_tlbi *tlbi,
+				     bool single_ril,
+				     unsigned int scale_max)
+{
+	u8 tgsz_lg2 = tlbi->tgsz_lg2;
+	unsigned int ttl = arm_smmu_compute_ttl(
+		tlbi->leaf_levels_bitmap, tlbi->table_levels_bitmap, tgsz_lg2);
+	struct arm_smmu_ril_range first = { .start_tg = tlbi->start >>
+							tgsz_lg2 };
+	u64 last_tg = tlbi->last >> tgsz_lg2;
+	u64 num_tg = last_tg - first.start_tg + 1;
+	u8 tg_enc = (tgsz_lg2 - 10) / 2;
+	struct arm_smmu_ril_range trail;
 
-		arm_smmu_cmdq_batch_add_cmd_p(smmu, cmds, cmd);
-		iova += inv_range;
+	/*
+	 * The spec defines the invalidated range as:
+	 *   Range = ((NUM+1) * 2^SCALE) * Translation_Granule_Size
+	 * NUM is 5 bits, so (NUM+1) covers 1..32 granules. Find the smallest
+	 * SCALE at which a single command could cover num_tg.
+	 *
+	 * Unlike other IOMMUs the spec has no alignment requirement on the
+	 * address beyond alignment to tg (so long as TTL=0).
+	 */
+	first.scale = arm_smmu_ril_calc_scale(num_tg);
+	if (first.scale > scale_max) {
+		/* Range too large for a single command do full invalidation */
+		tlbi->range.use_full_inv = true;
+		return;
+	}
+
+	if (single_ril) {
+		/*
+		 * Produce a single invalidation by rounding up and disabling
+		 * the trailer.
+		 */
+		first.num = arm_smmu_ril_calc_num(num_tg, first.scale);
+		trail.num = 0;
+	} else {
+		/*
+		 * Produce two invalidations by rounding down and adding a
+		 * second trailing RIL anchored at the end.
+		 */
+		first.num = num_tg >> first.scale;
+		trail = arm_smmu_ril_init_end(
+			last_tg, num_tg - ((u64)first.num << first.scale));
 	}
+	arm_smmu_tlbi_add_range_cmd(tlbi, &first, ttl, tg_enc);
+
+	if (trail.num)
+		arm_smmu_tlbi_add_range_cmd(tlbi, &trail, ttl, tg_enc);
 }
 
-static bool arm_smmu_inv_size_too_big(struct arm_smmu_device *smmu, size_t size,
-				      size_t granule)
+/*
+ * 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)
 {
-	size_t max_tlbi_ops;
+	u8 combined = tlbi->table_levels_bitmap | tlbi->leaf_levels_bitmap;
 
-	/* 0 size means invalidate all */
-	if (!size || size == SIZE_MAX)
-		return true;
+	if (WARN_ON(!combined))
+		return U8_MAX;
+	return arm_smmu_pt_level_to_lg2sz(tlbi->tgsz_lg2, __ffs(combined));
+}
 
-	if (smmu->features & ARM_SMMU_FEAT_RANGE_INV)
-		return false;
+/*
+ * 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)
+{
+	u8 stride_lg2 = arm_smmu_tlbi_calc_stride(tlbi);
+	unsigned long num_ops;
 
-	/*
-	 * 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.
-	 */
-	max_tlbi_ops = 1 << (ilog2(granule) - 3);
-	return size >= max_tlbi_ops * granule;
+	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,
+				 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 */
-static void arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
+/*
+ * 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,
-				       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)) {
-		struct arm_smmu_cmd nsize_cmd = *cmd;
+	u64 iova = tlbi->start;
+	unsigned int i;
 
-		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 (tlbi->range.use_full_inv) {
+			arm_smmu_inv_all_cmd(inv, cmds, cmd);
+			return true;
+		}
+		for (i = 0; i < tlbi->range.num_cmds; i++) {
+			struct arm_smmu_cmd range_cmd = tlbi->range.cmds[i];
+
+			range_cmd.data[0] |= cmd->data[0];
+			range_cmd.data[1] |= cmd->data[1];
+			arm_smmu_cmdq_batch_add_cmd_p(inv->smmu, cmds,
+						      &range_cmd);
+		}
+		return false;
+	}
+
+	if (tlbi->single.use_full_inv) {
+		arm_smmu_inv_all_cmd(inv, cmds, cmd);
+		return true;
 	}
 
-	arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, leaf,
-				      iova, size, granule, inv->pgsize);
+	for (i = 0; i < tlbi->single.num; i++) {
+		cmd->data[1] = FIELD_PREP(CMDQ_TLBI_1_LEAF,
+					  !tlbi->table_levels_bitmap) |
+			       (iova & ~GENMASK_U64(11, 0));
+		iova += BIT_U64(tlbi->single.stride_lg2);
+		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,
@@ -2598,10 +2799,10 @@ 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_tlbi_inv(struct arm_smmu_tlbi *tlbi,
+				     struct arm_smmu_invs *invs)
 {
+	struct arm_smmu_inv *used_s12_vmall = NULL;
 	struct arm_smmu_cmdq_batch cmds = {};
 	struct arm_smmu_inv *cur;
 	struct arm_smmu_inv *end;
@@ -2630,18 +2831,24 @@ 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);
+			if (arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, tlbi))
+				used_s12_vmall = cur + 1;
 			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))
+			/*
+			 * 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 for the same SMMU. There may
+			 * be several pairings so check this is paired with the
+			 * one that did the full invalidation.
+			 */
+			if (used_s12_vmall == cur)
 				break;
 			arm_smmu_cmdq_batch_add_cmd(
 				smmu, &cmds,
@@ -2652,7 +2859,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(
@@ -2679,12 +2886,14 @@ static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
 	}
 }
 
-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_domain *smmu_domain)
 {
 	struct arm_smmu_invs *invs;
 
+	if (!tlbi->single.use_full_inv)
+		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
@@ -2702,7 +2911,7 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
 	 *
 	 *  [CPU0]                        | [CPU1]
 	 *  change IOPTE on new domain:   |
-	 *  arm_smmu_domain_inv_range() { | arm_smmu_install_new_domain_invs()
+	 *  arm_smmu_domain_tlbi() {      | arm_smmu_install_new_domain_invs()
 	 *    smp_mb(); // ensures IOPTE  | arm_smmu_install_ste_for_dev {
 	 *              // seen by SMMU   |   dma_wmb(); // ensures invs update
 	 *    // load the updated invs    |              // before updating STE
@@ -2715,6 +2924,20 @@ void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
 	rcu_read_lock();
 	invs = rcu_dereference(smmu_domain->invs);
 
+	/*
+	 * Only precalculate RIL if it will be used, invs generation ensures
+	 * this matches the instances used for invalidation.
+	 */
+	if (invs->range_inv_scale_max) {
+		if (!tlbi->range.use_full_inv) {
+			arm_smmu_tlbi_calc_range(
+				tlbi,
+				smmu_domain->stage == ARM_SMMU_DOMAIN_SVA &&
+					invs->has_full_cont_ril,
+				invs->range_inv_scale_max);
+		}
+	}
+
 	/*
 	 * Avoid locking unless ATS is being used. No ATC invalidation can be
 	 * going on after a domain is detached.
@@ -2723,10 +2946,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_tlbi_inv(tlbi, invs);
 		read_unlock_irqrestore(&invs->rwlock, flags);
 	} else {
-		__arm_smmu_domain_inv_range(invs, iova, size, granule, leaf);
+		arm_smmu_domain_tlbi_inv(tlbi, invs);
 	}
 
 	rcu_read_unlock();
@@ -2742,12 +2965,28 @@ 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. This must
+ * clear the walk cache and any leaves within the range.
+ */
 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;
+	u8 tgsz_lg2 = smmu_domain->tgsz_lg2;
+	struct arm_smmu_tlbi tlbi = {
+		.tgsz_lg2 = smmu_domain->tgsz_lg2,
+		.start = iova,
+		.last = iova + size - 1,
+	};
+	u8 table_levels =
+		BIT(arm_smmu_pt_lg2sz_to_level(tgsz_lg2, ilog2(size)));
 
-	arm_smmu_domain_inv_range(smmu_domain, iova, size, granule, false);
+	tlbi.table_levels_bitmap = table_levels;
+	tlbi.leaf_levels_bitmap = table_levels - 1;
+
+	arm_smmu_domain_tlbi(&tlbi, smmu_domain);
 }
 
 static const struct iommu_flush_ops arm_smmu_flush_ops = {
@@ -2933,6 +3172,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)
@@ -3172,15 +3412,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))
@@ -3232,28 +3470,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:
@@ -3265,7 +3499,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;
 	}
 
@@ -3276,7 +3510,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;
 	}
 
@@ -4021,17 +4255,32 @@ static void arm_smmu_flush_iotlb_all(struct iommu_domain *domain)
 		arm_smmu_tlb_inv_context(smmu_domain);
 }
 
+/*
+ * io-pgtable-arm.c calls this function either under
+ * arm_smmu_tlb_inv_page_nosync() or via the normal iommu code to flush the
+ * gather. Due to how iommu_iotlb_gather_add_page() works the gather will end up
+ * with a single uniform pgsize leaf. 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 = {
+		.tgsz_lg2 = smmu_domain->tgsz_lg2,
+		.start = gather->start,
+		.last = gather->end,
+	};
 
-	if (!gather->pgsize)
+	if (WARN_ON(gather->pgsize < BIT(tg)))
 		return;
 
-	arm_smmu_domain_inv_range(smmu_domain, gather->start,
-				  gather->end - gather->start + 1,
-				  gather->pgsize, true);
+	tlbi.leaf_levels_bitmap =
+		BIT(arm_smmu_pt_lg2sz_to_level(tg, ilog2(gather->pgsize)));
+
+	arm_smmu_domain_tlbi(&tlbi, smmu_domain);
 }
 
 static phys_addr_t
@@ -5009,11 +5258,19 @@ static void arm_smmu_device_iidr_probe(struct arm_smmu_device *smmu)
 				/* Arm errata 2268618, 2812531 */
 				smmu->features &= ~ARM_SMMU_FEAT_NESTING;
 			}
+			/* Arm errata 3777127 */
+			smmu->options |= ARM_SMMU_OPT_FULL_CONT_RIL;
 			break;
 		case IIDR_PRODUCTID_ARM_MMU_L1:
+			/* Arm errata 3878312 */
+			smmu->features &= ~ARM_SMMU_FEAT_BTM;
+			break;
 		case IIDR_PRODUCTID_ARM_MMU_S3:
-			/* Arm errata 3878312/3995052 */
+			/* Arm errata 3995052 */
 			smmu->features &= ~ARM_SMMU_FEAT_BTM;
+			/* Arm errata 3673557 */
+			if (variant < 1 || (variant == 1 && revision < 1))
+				smmu->options |= ARM_SMMU_OPT_FULL_CONT_RIL;
 			break;
 		}
 		break;
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index dd2fee2f560e6..65fa94e865149 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -736,10 +736,9 @@ struct arm_smmu_inv {
 	u8 size_opcode;
 	u8 nsize_opcode;
 	u32 id; /* ASID or VMID or SID */
-	union {
-		size_t pgsize; /* ARM_SMMU_FEAT_RANGE_INV */
-		u32 ssid; /* INV_TYPE_ATS */
-	};
+
+	/* Only used by INV_TYPE_ATS */
+	u32 ssid;
 
 	int users; /* users=0 to mark as a trash to be purged */
 };
@@ -759,6 +758,9 @@ 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_full_cont_ril: flag if any entry's SMMU requires the CONT RIL workaround
+ * @range_inv_scale_max: max SCALE usable by all range-capable SMMUs, or 0 if
+ *                       no SMMU supports range invalidation
  * @rcu: rcu head for kfree_rcu()
  * @inv: flexible invalidation array
  *
@@ -788,6 +790,8 @@ struct arm_smmu_invs {
 	size_t num_trashes;
 	rwlock_t rwlock;
 	bool has_ats;
+	bool has_full_cont_ril;
+	u8 range_inv_scale_max;
 	struct rcu_head rcu;
 	struct arm_smmu_inv inv[] __counted_by(max_invs);
 };
@@ -805,6 +809,53 @@ static inline struct arm_smmu_invs *arm_smmu_invs_alloc(size_t num_invs)
 	return new_invs;
 }
 
+/* Generic page-table level 0 is the leaf-only level. */
+static inline unsigned int arm_smmu_pt_level_to_lg2sz(unsigned int tgsz_lg2,
+						      unsigned int level)
+{
+	return tgsz_lg2 + (tgsz_lg2 - ilog2(sizeof(u64))) * level;
+}
+
+static inline unsigned int arm_smmu_pt_lg2sz_to_level(unsigned int tgsz_lg2,
+						      unsigned int lg2sz)
+{
+	return (lg2sz - tgsz_lg2) / (tgsz_lg2 - ilog2(sizeof(u64)));
+}
+
+struct arm_smmu_tlbi {
+	unsigned long start;
+	unsigned long last;
+	/* Base Translation Granule of the page table */
+	u8 tgsz_lg2;
+	/*
+	 * 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.
+	 *
+	 * The pair selects the invalidation scope:
+	 *   table!=0, leaf==0 : walk cache only
+	 *   table==0, leaf!=0 : leaves only
+	 *   table!=0, leaf!=0 : walk cache + all leaves
+	 *   table==0, leaf==0 : illegal
+	 */
+	u8 leaf_levels_bitmap;
+	u8 table_levels_bitmap;
+
+	struct {
+		bool use_full_inv;
+		u16 num;
+		u8 stride_lg2;
+	} single;
+
+	struct {
+		bool use_full_inv;
+		u8 num_cmds;
+		struct arm_smmu_cmd cmds[2];
+	} range;
+};
+
 struct arm_smmu_evtq {
 	struct arm_smmu_queue		q;
 	struct iopf_queue		*iopf;
@@ -934,6 +985,8 @@ struct arm_smmu_device {
 #define ARM_SMMU_OPT_MSIPOLL		(1 << 2)
 #define ARM_SMMU_OPT_CMDQ_FORCE_SYNC	(1 << 3)
 #define ARM_SMMU_OPT_TEGRA241_CMDQV	(1 << 4)
+/* RANGE_INV is mandatory and one RIL must fully span an invalidated CONT */
+#define ARM_SMMU_OPT_FULL_CONT_RIL	(1 << 5)
 	u32				options;
 
 	struct arm_smmu_cmdq		cmdq;
@@ -1049,6 +1102,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;
 };
@@ -1154,13 +1208,21 @@ 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,
+			  struct arm_smmu_domain *smmu_domain);
 
 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 = {
+		.start = 0,
+		.last = ULONG_MAX,
+		.tgsz_lg2 = smmu_domain->tgsz_lg2,
+		.single.use_full_inv = true,
+		.range.use_full_inv = true,
+	};
+
+	arm_smmu_domain_tlbi(&tlbi, smmu_domain);
 }
 
 void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu,

3/2 2026/09/18 18:32 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 18707
							output: 34
							thoughts: 220
						

Thoughts:
**Fuzzing Feasibility Assessment**

Okay, let's take a look at this patch series and determine if it's a good candidate for syzkaller fuzzing. First things first, target architecture is `amd64`. I need to ensure the modifications are relevant to this arch, so I should look over the files changed. I see:

*   `Documentation/arch/arm64/silicon-errata.rst`
*   `drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c`
*   `drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c`
*   `drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c`
*   `drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h`

Hmm, these files all seem to revolve around the ARM SMMU v3, which is a bit of a red flag. I need to quickly determine if any of these SMMU v3 files are compiled or even relevant on `amd64`. Time to dig into the Kconfig and see if `ARM_SMMU_V3` is even enabled or reachable in this context. If not, this patch set is likely a waste of fuzzing time.



4/2 2026/09/18 18:33 tool grepper
0m Args:
{
  "Expression": "config ARM_SMMU_V3",
  "PathPrefix": "drivers/iommu"
}

Results:
{
  "Output": "drivers/iommu/arm/Kconfig=74=endif\ndrivers/iommu/arm/Kconfig-75-\ndrivers/iommu/arm/Kconfig:76:config ARM_SMMU_V3\ndrivers/iommu/arm/Kconfig-77-\ttristate \"ARM Ltd. System MMU Version 3 (SMMUv3) Support\"\n--\ndrivers/iommu/arm/Kconfig=90=if ARM_SMMU_V3\ndrivers/iommu/arm/Kconfig:91:config ARM_SMMU_V3_SVA\ndrivers/iommu/arm/Kconfig-92-\tbool \"Shared Virtual Addressing support for the ARM SMMUv3\"\n--\ndrivers/iommu/arm/Kconfig-102-\ndrivers/iommu/arm/Kconfig:103:config ARM_SMMU_V3_IOMMUFD\ndrivers/iommu/arm/Kconfig-104-\tbool \"Enable IOMMUFD features for ARM SMMUv3 (EXPERIMENTAL)\"\n--\ndrivers/iommu/arm/Kconfig-111-\ndrivers/iommu/arm/Kconfig:112:config ARM_SMMU_V3_KUNIT_TEST\ndrivers/iommu/arm/Kconfig-113-\ttristate \"KUnit tests for arm-smmu-v3 driver\"  if !KUNIT_ALL_TESTS\n"
}

5/2 2026/09/18 18:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 19641
							output: 37
							thoughts: 36
						

Thoughts:
Okay, let's dive into this. Here's how I'd approach that kernel configuration investigation, summarized from my perspective:

**Investigating ARM_SMMU_V3 Dependencies**

Alright, so someone's asked me to look at the dependencies for `ARM_SMMU_V3`. My first instinct is always to go directly to the source. Specifically, the Kconfig file for that driver. Based on what I know about kernel organization, that's almost certainly going to be in `drivers/iommu/arm/Kconfig`. I know the configuration logic is going to be in there.

Now, to save some time, I am going to jump straight to line 76 (or thereabouts). I figure that if `ARM_SMMU_V3` is complex enough to merit some investigation, it's probably one of the more involved options, and might have some interesting conditions defining when it's enabled. Line 76 is a reasonable place to start looking for those critical dependencies. That's where I expect to find the meat of the matter - the conditions that enable or disable it, the other options it's tied to, and anything else I need to understand about this particular configuration option and its implications for the overall system.



6/2 2026/09/18 18:33 tool read-file
0m Args:
{
  "File": "drivers/iommu/arm/Kconfig",
  "FirstLine": 60,
  "LineCount": 50
}

Results:
{
  "Contents": "  60:\t\n  61:\tconfig ARM_SMMU_QCOM_DEBUG\n  62:\t\tbool \"ARM SMMU QCOM implementation defined debug support\"\n  63:\t\tdepends on ARM_SMMU_QCOM=y\n  64:\t\thelp\n  65:\t\t  Support for implementation specific debug features in ARM SMMU\n  66:\t\t  hardware found in QTI platforms. This include support for\n  67:\t\t  the Translation Buffer Units (TBU) that can be used to obtain\n  68:\t\t  additional information when debugging memory management issues\n  69:\t\t  like context faults.\n  70:\t\n  71:\t\t  Say Y here to enable debug for issues such as context faults\n  72:\t\t  or TLB sync timeouts which requires implementation defined\n  73:\t\t  register dumps.\n  74:\tendif\n  75:\t\n  76:\tconfig ARM_SMMU_V3\n  77:\t\ttristate \"ARM Ltd. System MMU Version 3 (SMMUv3) Support\"\n  78:\t\tdepends on ARM64\n  79:\t\tselect IOMMU_API\n  80:\t\tselect IOMMU_IO_PGTABLE_LPAE\n  81:\t\tselect GENERIC_MSI_IRQ\n  82:\t\tselect IOMMUFD_DRIVER if IOMMUFD\n  83:\t\thelp\n  84:\t\t  Support for implementations of the ARM System MMU architecture\n  85:\t\t  version 3 providing translation support to a PCIe root complex.\n  86:\t\n  87:\t\t  Say Y here if your system includes an IOMMU device implementing\n  88:\t\t  the ARM SMMUv3 architecture.\n  89:\t\n  90:\tif ARM_SMMU_V3\n  91:\tconfig ARM_SMMU_V3_SVA\n  92:\t\tbool \"Shared Virtual Addressing support for the ARM SMMUv3\"\n  93:\t\tselect IOMMU_SVA\n  94:\t\tselect IOMMU_IOPF\n  95:\t\tselect MMU_NOTIFIER\n  96:\t\thelp\n  97:\t\t  Support for sharing process address spaces with devices using the\n  98:\t\t  SMMUv3.\n  99:\t\n 100:\t\t  Say Y here if your system supports SVA extensions such as PCIe PASID\n 101:\t\t  and PRI.\n 102:\t\n 103:\tconfig ARM_SMMU_V3_IOMMUFD\n 104:\t\tbool \"Enable IOMMUFD features for ARM SMMUv3 (EXPERIMENTAL)\"\n 105:\t\tdepends on IOMMUFD\n 106:\t\thelp\n 107:\t\t  Support for IOMMUFD features intended to support virtual machines\n 108:\t\t  with accelerated virtual IOMMUs.\n 109:\t\n"
}

7/2 2026/09/18 18:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 20703
							output: 38
							thoughts: 94
						

Thoughts:
**Investigating ARM64 Dependency and AMD64 Compatibility**

Okay, line 78, that's it: "depends on ARM64"! That throws up a red flag immediately. I need to figure out if we can even *consider* enabling `ARM_SMMU_V3` on an amd64 (x86_64) architecture. My initial intuition says "probably not," but I need to be sure. I can't just dismiss it without a deeper look.

My immediate next thought is to broaden the scope a little bit and ask: are there *any* related configurations that might be more permissive? Could something like `ARM_SMMU_V3_KUNIT_TEST` have a more flexible dependency structure? Maybe there's a workaround or a subtle difference in the build that would permit at least *some* functionality. To get the data, I will go look at lines 110-125 of `drivers/iommu/arm/Kconfig` to see the dependencies there, and any clues about alternative configurations that can run on x86_64. Let's see what the Kconfig file has to say.



8/2 2026/09/18 18:33 tool read-file
0m Args:
{
  "File": "drivers/iommu/arm/Kconfig",
  "FirstLine": 110,
  "LineCount": 30
}

Results:
{
  "Contents": " 110:\t\t  Say Y here if you are doing development and testing on this feature.\n 111:\t\n 112:\tconfig ARM_SMMU_V3_KUNIT_TEST\n 113:\t\ttristate \"KUnit tests for arm-smmu-v3 driver\"  if !KUNIT_ALL_TESTS\n 114:\t\tdepends on KUNIT\n 115:\t\tdepends on ARM_SMMU_V3_SVA\n 116:\t\tdefault KUNIT_ALL_TESTS\n 117:\t\thelp\n 118:\t\t  Enable this option to unit-test arm-smmu-v3 driver functions.\n 119:\t\n 120:\t\t  If unsure, say N.\n 121:\t\n 122:\tconfig TEGRA241_CMDQV\n 123:\t\tbool \"NVIDIA Tegra241 CMDQ-V extension support for ARM SMMUv3\"\n 124:\t\thelp\n 125:\t\t  Support for NVIDIA CMDQ-Virtualization extension for ARM SMMUv3. The\n 126:\t\t  CMDQ-V extension is similar to v3.3 ECMDQ for multi command queues\n 127:\t\t  support, except with virtualization capabilities.\n 128:\t\n 129:\t\t  Say Y here if your system is NVIDIA Tegra241 (Grace) or it has the same\n 130:\t\t  CMDQ-V extension.\n 131:\tendif\n 132:\t\n 133:\tconfig QCOM_IOMMU\n 134:\t\t# Note: iommu drivers cannot (yet?) be built as modules\n 135:\t\tbool \"Qualcomm IOMMU Support\"\n 136:\t\tdepends on ARCH_QCOM || COMPILE_TEST\n 137:\t\tdepends on !GENERIC_ATOMIC64\t# for IOMMU_IO_PGTABLE_LPAE\n 138:\t\tselect QCOM_SCM\n 139:\t\tselect IOMMU_API\n"
}

9/2 2026/09/18 18:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 21606
							output: 43
							thoughts: 318
						

Thoughts:
**Analyzing the ARM SMMUv3 Configuration**

Okay, let's dive into this `ARM_SMMU_V3` configuration. Straight away, I see it's dependent on `ARM64`. No conditional compilation with `COMPILE_TEST` either. Interesting. Then I note that `ARM_SMMU_V3_SVA` is nested under `if ARM_SMMU_V3`. And naturally, `ARM_SMMU_V3_KUNIT_TEST` relies on `ARM_SMMU_V3_SVA` itself. So far, so good, a pretty standard dependency setup.

Now, let's quickly review the files this patch touches: documentation update for silicon errata, the SVA implementation, a test file, and the core implementation with a header. Standard.

Next, I need to check the Makefile. I need to understand how these pieces fit together from a build perspective.



10/2 2026/09/18 18:33 tool read-file
0m Args:
{
  "File": "drivers/iommu/arm/arm-smmu-v3/Makefile",
  "FirstLine": 1,
  "LineCount": 20
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0\n   2:\tobj-$(CONFIG_ARM_SMMU_V3) += arm_smmu_v3.o\n   3:\tarm_smmu_v3-y := arm-smmu-v3.o\n   4:\tarm_smmu_v3-$(CONFIG_ARM_SMMU_V3_IOMMUFD) += arm-smmu-v3-iommufd.o\n   5:\tarm_smmu_v3-$(CONFIG_ARM_SMMU_V3_SVA) += arm-smmu-v3-sva.o\n   6:\tarm_smmu_v3-$(CONFIG_TEGRA241_CMDQV) += tegra241-cmdqv.o\n   7:\t\n   8:\tobj-$(CONFIG_ARM_SMMU_V3_KUNIT_TEST) += arm-smmu-v3-test.o\n"
}

11/2 2026/09/18 18:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 22416
							output: 25
							thoughts: 28
						

Thoughts:
**Investigating `CONFIG_ARM_SMMU_V3` References**

Okay, so I need to make sure I've got a comprehensive understanding of how `CONFIG_ARM_SMMU_V3` is being used within the code base. My initial thought is to do a quick sweep and see if there are any other instances where this configuration option is either defined or referenced. I want to identify any potential dependencies or implications of this particular flag. I'm looking for anything that might shed light on its overall impact. It's a fundamental step in ensuring I have a solid grasp on how this feature interacts with the system.



12/2 2026/09/18 18:33 tool grepper
0m Args:
{
  "Expression": "config ARM_SMMU_V3\\b"
}

Results:
{
  "Output": "drivers/iommu/arm/Kconfig=74=endif\ndrivers/iommu/arm/Kconfig-75-\ndrivers/iommu/arm/Kconfig:76:config ARM_SMMU_V3\ndrivers/iommu/arm/Kconfig-77-\ttristate \"ARM Ltd. System MMU Version 3 (SMMUv3) Support\"\n"
}

13/2 2026/09/18 18:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 22708
							output: 17
							thoughts: 287
						

Thoughts:
**My Analysis of the Fuzzing Suitability**

Okay, let's see. The prompt is asking about fuzzing a patch, and the first thing I need to confirm is whether the modified code is even relevant to my target architecture, which is `amd64`. I need to rule out modifications that are clearly not applicable.

Right, so I checked if `ARM64` could somehow be selected on `amd64`, but that's a dead end. `ARM64` is a distinct architecture configuration. The instructions are very clear: I should return `WorthFuzzing=false` *if* the changes *exclusively* fall into certain negative criteria. One of those criteria is code paths specific to an architecture *other* than my target.

Now, let's look at the specific files. The commit touches `Documentation/arch/arm64/silicon-errata.rst` and code within `drivers/iommu/arm/arm-smmu-v3/`. That `arm64` directory, along with the very specific `arm-smmu-v3` driver, screams `ARM64` only! It's pretty straightforward, actually.

The documentation is non-executable and the driver seems highly specific to `ARM64`. With `amd64` being the target, it's pretty clear that all modified code is *exclusive* to `ARM64`.

I should probably double-check if there are any *other* files modified by the commit that I might have missed, but I'm pretty confident in my assessment at this point.



14/2 2026/09/18 18:33 tool grepper
0m Args:
{
  "Expression": "diff --git"
}

Results:
{
  "Output": "Documentation/bpf/drgn.rst=99=For example, to print ``struct bpf_prog_aux`` for BPF program id 53077:\n--\nDocumentation/bpf/drgn.rst-103-    % git diff\nDocumentation/bpf/drgn.rst:104:    diff --git a/tools/bpf_inspect.py b/tools/bpf_inspect.py\nDocumentation/bpf/drgn.rst-105-    index 650e228..aea2357 100755\n--\nDocumentation/dev-tools/checkuapi.rst=54=won't break userspace::\n--\nDocumentation/dev-tools/checkuapi.rst-69-      *  comp2_t is 24-bit with 5-bit base 2 exponent and 20 bit fraction\nDocumentation/dev-tools/checkuapi.rst:70:    diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h\nDocumentation/dev-tools/checkuapi.rst-71-    EOF\n--\nDocumentation/dev-tools/checkuapi.rst=310=Consider this change::\n--\nDocumentation/dev-tools/checkuapi.rst-312-    cat \u003c\u003c 'EOF' | patch -l -p1\nDocumentation/dev-tools/checkuapi.rst:313:    diff --git a/include/uapi/asm-generic/Kbuild b/include/uapi/asm-generic/Kbuild\nDocumentation/dev-tools/checkuapi.rst-314-    index ebb180aac74e..a9c88b0a8b3b 100644\n--\nDocumentation/doc-guide/contributing.rst=84=looked like this::\n--\nDocumentation/doc-guide/contributing.rst-102-\nDocumentation/doc-guide/contributing.rst:103:  diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c\nDocumentation/doc-guide/contributing.rst-104-  index 57f6944d65a6..00c9b80b3d33 100644\n--\nDocumentation/mm/transhuge.rst=73=change::\nDocumentation/mm/transhuge.rst-74-\nDocumentation/mm/transhuge.rst:75:\tdiff --git a/mm/mremap.c b/mm/mremap.c\nDocumentation/mm/transhuge.rst-76-\t--- a/mm/mremap.c\n--\nDocumentation/translations/it_IT/doc-guide/contributing.rst=91=patch risultante era questa::\n--\nDocumentation/translations/it_IT/doc-guide/contributing.rst-109-\nDocumentation/translations/it_IT/doc-guide/contributing.rst:110:  diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c\nDocumentation/translations/it_IT/doc-guide/contributing.rst-111-  index 57f6944d65a6..00c9b80b3d33 100644\n--\nDocumentation/translations/zh_CN/doc-guide/contributing.rst=36=C代码编译器发出的警告常常会被视为误报,从而导致出现了旨在让编译器闭嘴的补丁。\n--\nDocumentation/translations/zh_CN/doc-guide/contributing.rst-86-\nDocumentation/translations/zh_CN/doc-guide/contributing.rst:87:  diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c\nDocumentation/translations/zh_CN/doc-guide/contributing.rst-88-  index 57f6944d65a6..00c9b80b3d33 100644\n--\nscripts/checkpatch.pl=2671=sub process {\n--\nscripts/checkpatch.pl-2844-\t\t    ($line =~ /^rename (?:from|to) \\S+\\s*$/ ||\nscripts/checkpatch.pl:2845:\t\t     $line =~ /^diff --git a\\/[\\w\\/\\.\\_\\-]+ b\\/\\S+\\s*$/))) {\nscripts/checkpatch.pl-2846-\t\t\t$is_patch = 1;\n--\nscripts/checkpatch.pl-2902-\t\t# extract the filename as it passes\nscripts/checkpatch.pl:2903:\t\tif ($line =~ /^diff --git.*?(\\S+)$/) {\nscripts/checkpatch.pl-2904-\t\t\t$realfile = $1;\n--\nscripts/get_maintainer.pl=552=foreach my $file (@ARGV) {\n--\nscripts/get_maintainer.pl-601-\t\tpush(@files, $filename);\nscripts/get_maintainer.pl:602:\t    } elsif (m/^diff --git a\\/(\\S+) b\\/(\\S+)\\s*$/) {\nscripts/get_maintainer.pl-603-\t\tmy $filename1 = $1;\n"
}

15/2 2026/09/18 18:33 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 24638
							output: 97
							thoughts: 451
						

Thoughts:
**My Assessment of the Git Diff**

Okay, let me take a look at this patch. The diff includes changes to `Documentation/arch/arm64/silicon-errata.rst` and files within `drivers/iommu/arm/arm-smmu-v3/`. Immediately, I see that these are either documentation updates or relate to the ARM SMMUv3 driver. The Kconfig entry for `ARM_SMMU_V3` shows a dependency on `ARM64`. No compile-time tests are enabled for `ARM_SMMU_V3` either, which confirms my initial thoughts.

Given that the driver is specifically for ARM SMMUv3 hardware and depends on the ARM64 architecture, this code is completely unreachable on the x86-64 architecture, which is the target. The patch modifies documentation and this exclusively ARM64 code.

According to the rules defined in Section 2, changes limited to documentation or unreachable execution paths should result in `WorthFuzzing=false`. The updated code falls squarely into these categories. The patch only touches code that will not execute on the current architecture.

Therefore, `WorthFuzzing` should be `false`, `FocusSymbols` is empty, and `EnableConfigs` should also be empty. There's no ambiguity here.

Alright, I'm confident in this assessment. Time to call `set-results`.



16/2 2026/09/18 18:33 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch modifies documentation and the ARM SMMUv3 driver (drivers/iommu/arm/arm-smmu-v3/). The ARM_SMMU_V3 config option depends strictly on ARM64, making this code structurally unreachable and unbuildable on the target architecture (amd64).",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch modifies documentation and the ARM SMMUv3 driver (drivers/iommu/arm/arm-smmu-v3/). The ARM_SMMU_V3 config option depends strictly on ARM64, making this code structurally unreachable and unbuildable on the target architecture (amd64).",
  "WorthFuzzing": false
}

17/1 2026/09/18 18:33 action If
0m Args:
{
  "WorthFuzzing": false
}

Results:
null

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)