AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/06/29 11:40 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "KVM",
    "ACPI"
  ],
  "FocusSymbols": [
    "__kvm_arch_dirty_log_clear",
    "__kvm_arch_dirty_ring_clear",
    "dirty_bit_clear",
    "hacdbsirq_handler",
    "kvm_mmu_split_huge_pages",
    "kvm_get_dirty_log_protect",
    "kvm_clear_dirty_log_protect",
    "kvm_dirty_ring_reset"
  ],
  "Reasoning": "The patch introduces hardware dirty bit tracking (HDBSS) and cleaning (HACDBS) for ARM64 KVM. It modifies core KVM memory management and dirty logging logic, adding new ioctl paths and interrupt handlers. This is functional and reachable code that should be fuzzed.",
  "WorthFuzzing": true
}

1/1 2026/06/29 11:40 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 6c5867176f710476b28b136db5c83931a75ebe88\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Mon Jun 29 11:40:53 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/arch/arm64/include/asm/acpi.h b/arch/arm64/include/asm/acpi.h\nindex 8a54ca6ba60282..883315e9d79d98 100644\n--- a/arch/arm64/include/asm/acpi.h\n+++ b/arch/arm64/include/asm/acpi.h\n@@ -45,6 +45,9 @@\n \n #define ACPI_MADT_GICC_TRBE  (offsetof(struct acpi_madt_generic_interrupt, \\\n \ttrbe_interrupt) + sizeof(u16))\n+\n+#define ACPI_MADT_GICC_HACDBSIRQ  (offsetof(struct acpi_madt_generic_interrupt, \\\n+\thacdbsirq_gsi) + sizeof(u32))\n /*\n  * Arm® Functional Fixed Hardware Specification Version 1.2.\n  * Table 2: Arm Architecture context loss flags\ndiff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h\nindex a57870fa96db5b..620ae4cddb760a 100644\n--- a/arch/arm64/include/asm/cpufeature.h\n+++ b/arch/arm64/include/asm/cpufeature.h\n@@ -863,6 +863,16 @@ static inline bool system_supports_haft(void)\n \treturn cpus_have_final_cap(ARM64_HAFT);\n }\n \n+static inline bool system_supports_hdbss(void)\n+{\n+\treturn cpus_have_final_cap(ARM64_HAS_HDBSS);\n+}\n+\n+static inline bool system_supports_hacdbs(void)\n+{\n+\treturn cpus_have_final_cap(ARM64_HACDBS);\n+}\n+\n static __always_inline bool system_supports_mpam(void)\n {\n \treturn alternative_has_cap_unlikely(ARM64_MPAM);\ndiff --git a/arch/arm64/include/asm/kvm_dirty_bit.h b/arch/arm64/include/asm/kvm_dirty_bit.h\nnew file mode 100644\nindex 00000000000000..d76c109937d8da\n--- /dev/null\n+++ b/arch/arm64/include/asm/kvm_dirty_bit.h\n@@ -0,0 +1,67 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (C) 2026 ARM Ltd.\n+ * Author: Leonardo Bras \u003cleo.bras@arm.com\u003e\n+ */\n+\n+#ifndef __ARM64_KVM_DIRTY_BIT_H__\n+#define __ARM64_KVM_DIRTY_BIT_H__\n+\n+#include \u003casm/kvm_pgtable.h\u003e\n+\n+enum hacdbs_status {\n+\tHACDBS_OFF,\n+\tHACDBS_IDLE,\n+\tHACDBS_RUNNING,\n+\tHACDBS_ERROR\n+};\n+\n+struct hacdbs {\n+\tenum hacdbs_status status;\n+\tint size;\n+};\n+\n+DECLARE_PER_CPU(struct hacdbs, hacdbs_pcp);\n+\n+void __init kvm_hacdbs_init(void);\n+void kvm_hacdbs_cpu_up(void);\n+void kvm_hacdbs_cpu_down(void);\n+\n+int __kvm_arch_dirty_log_clear(struct kvm *kvm,\n+\t\t\t       struct kvm_memory_slot *memslot,\n+\t\t\t       struct kvm_clear_dirty_log *log,\n+\t\t\t       unsigned long *bitmap,\n+\t\t\t       bool *flush);\n+\n+int __kvm_arch_dirty_ring_clear(struct kvm *kvm, struct kvm_dirty_ring *ring,\n+\t\t\t\tint *nr_entries_reset);\n+\n+static inline bool kvm_arch_dirty_clear_enabled(struct kvm *kvm)\n+{\n+\treturn this_cpu_read(hacdbs_pcp.status) == HACDBS_IDLE \u0026\u0026\n+\t       (kvm-\u003earch.mmu.pgt-\u003eflags \u0026 KVM_PGTABLE_S2_DBM);\n+}\n+\n+static inline int kvm_arch_dirty_log_clear(struct kvm *kvm,\n+\t\t\t\t\t   struct kvm_memory_slot *memslot,\n+\t\t\t\t\t   struct kvm_clear_dirty_log *log,\n+\t\t\t\t\t   unsigned long *bitmap,\n+\t\t\t\t\t   bool *flush)\n+{\n+\tif (!kvm_arch_dirty_clear_enabled(kvm))\n+\t\treturn -EPERM;\n+\n+\treturn __kvm_arch_dirty_log_clear(kvm, memslot, log, bitmap, flush);\n+}\n+\n+static inline int kvm_arch_dirty_ring_clear(struct kvm *kvm,\n+\t\t\t\t\t    struct kvm_dirty_ring *ring,\n+\t\t\t\t\t    int *nr_entries_reset)\n+{\n+\tif (!kvm_arch_dirty_clear_enabled(kvm))\n+\t\treturn -EPERM;\n+\n+\treturn __kvm_arch_dirty_ring_clear(kvm, ring, nr_entries_reset);\n+}\n+\n+#endif /* __ARM64_KVM_DIRTY_BIT_H__ */\ndiff --git a/arch/arm64/include/asm/kvm_pgtable.h b/arch/arm64/include/asm/kvm_pgtable.h\nindex 41a8687938eb6b..646ff88e02583f 100644\n--- a/arch/arm64/include/asm/kvm_pgtable.h\n+++ b/arch/arm64/include/asm/kvm_pgtable.h\n@@ -93,6 +93,8 @@ typedef u64 kvm_pte_t;\n \n #define KVM_PTE_LEAF_ATTR_HI_S2_XN\tGENMASK(54, 53)\n \n+#define KVM_PTE_LEAF_ATTR_HI_S2_DBM\tBIT(51)\n+\n #define KVM_PTE_LEAF_ATTR_HI_S1_GP\tBIT(50)\n \n #define KVM_PTE_LEAF_ATTR_S2_PERMS\t(KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R | \\\n@@ -253,6 +255,7 @@ struct kvm_pgtable_mm_ops {\n enum kvm_pgtable_stage2_flags {\n \tKVM_PGTABLE_S2_IDMAP\t\t\t= BIT(0),\n \tKVM_PGTABLE_S2_AS_S1\t\t\t= BIT(1),\n+\tKVM_PGTABLE_S2_DBM\t\t\t= BIT(2),\n };\n \n /**\ndiff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c\nindex 9a22df0c5120f3..62f56bbd0a65c8 100644\n--- a/arch/arm64/kernel/cpufeature.c\n+++ b/arch/arm64/kernel/cpufeature.c\n@@ -523,6 +523,7 @@ static const struct arm64_ftr_bits ftr_id_aa64mmfr3[] = {\n static const struct arm64_ftr_bits ftr_id_aa64mmfr4[] = {\n \tS_ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR4_EL1_E2H0_SHIFT, 4, 0),\n \tARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR4_EL1_NV_frac_SHIFT, 4, 0),\n+\tARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR4_EL1_HACDBS_SHIFT, 4, 0),\n \tARM64_FTR_END,\n };\n \n@@ -2131,6 +2132,11 @@ static bool hvhe_possible(const struct arm64_cpu_capabilities *entry,\n \treturn arm64_test_sw_feature_override(ARM64_SW_FEATURE_OVERRIDE_HVHE);\n }\n \n+static bool has_vhe_hdbss(const struct arm64_cpu_capabilities *entry, int cope)\n+{\n+\treturn is_kernel_in_hyp_mode() \u0026\u0026 has_cpuid_feature(entry, cope);\n+}\n+\n bool cpu_supports_bbml2_noabort(void)\n {\n \t/*\n@@ -2766,6 +2772,13 @@ static const struct arm64_cpu_capabilities arm64_features[] = {\n \t\tARM64_CPUID_FIELDS(ID_AA64MMFR1_EL1, HAFDBS, DBM)\n \t},\n #endif\n+\t{\n+\t\t.desc = \"Hardware dirty bit Cleaning\",\n+\t\t.type = ARM64_CPUCAP_SYSTEM_FEATURE,\n+\t\t.capability = ARM64_HACDBS,\n+\t\t.matches = has_cpuid_feature,\n+\t\tARM64_CPUID_FIELDS(ID_AA64MMFR4_EL1, HACDBS, IMP)\n+\t},\n #ifdef CONFIG_ARM64_HAFT\n \t{\n \t\t.desc = \"Hardware managed Access Flag for Table Descriptors\",\n@@ -2781,6 +2794,13 @@ static const struct arm64_cpu_capabilities arm64_features[] = {\n \t\tARM64_CPUID_FIELDS(ID_AA64MMFR1_EL1, HAFDBS, HAFT)\n \t},\n #endif\n+\t{\n+\t\t.desc = \"Hardware Dirty state tracking structure (HDBSS)\",\n+\t\t.type = ARM64_CPUCAP_SYSTEM_FEATURE,\n+\t\t.capability = ARM64_HAS_HDBSS,\n+\t\t.matches = has_vhe_hdbss,\n+\t\tARM64_CPUID_FIELDS(ID_AA64MMFR1_EL1, HAFDBS, HDBSS)\n+\t},\n \t{\n \t\t.desc = \"CRC32 instructions\",\n \t\t.capability = ARM64_HAS_CRC32,\ndiff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig\nindex 449154f9a4852d..db8487bf738b78 100644\n--- a/arch/arm64/kvm/Kconfig\n+++ b/arch/arm64/kvm/Kconfig\n@@ -34,6 +34,7 @@ menuconfig KVM\n \tselect HAVE_KVM_IRQ_BYPASS\n \tselect HAVE_KVM_READONLY_MEM\n \tselect HAVE_KVM_VCPU_RUN_PID_CHANGE\n+\tselect HAVE_KVM_HW_DIRTY_BIT if ACPI\n \tselect SCHED_INFO\n \tselect GUEST_PERF_EVENTS if PERF_EVENTS\n \tselect KVM_GUEST_MEMFD\ndiff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile\nindex 59612d2f277c1d..6faacd857346b3 100644\n--- a/arch/arm64/kvm/Makefile\n+++ b/arch/arm64/kvm/Makefile\n@@ -24,7 +24,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \\\n \t vgic/vgic-mmio.o vgic/vgic-mmio-v2.o \\\n \t vgic/vgic-mmio-v3.o vgic/vgic-kvm-device.o \\\n \t vgic/vgic-its.o vgic/vgic-debug.o vgic/vgic-v3-nested.o \\\n-\t vgic/vgic-v5.o\n+\t vgic/vgic-v5.o dirty_bit.o\n \n kvm-$(CONFIG_HW_PERF_EVENTS)  += pmu-emul.o pmu.o\n kvm-$(CONFIG_ARM64_PTR_AUTH)  += pauth.o\ndiff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c\nindex 50adfff75be82e..dc1a4629aaeb98 100644\n--- a/arch/arm64/kvm/arm.c\n+++ b/arch/arm64/kvm/arm.c\n@@ -42,6 +42,7 @@\n #include \u003casm/kvm_nested.h\u003e\n #include \u003casm/kvm_pkvm.h\u003e\n #include \u003casm/kvm_ptrauth.h\u003e\n+#include \u003casm/kvm_dirty_bit.h\u003e\n #include \u003casm/sections.h\u003e\n #include \u003casm/stacktrace/nvhe.h\u003e\n \n@@ -2307,6 +2308,7 @@ int kvm_arch_enable_virtualization_cpu(void)\n \n \tkvm_vgic_cpu_up();\n \tkvm_timer_cpu_up();\n+\tkvm_hacdbs_cpu_up();\n \n \tpreempt_enable();\n \n@@ -2315,6 +2317,7 @@ int kvm_arch_enable_virtualization_cpu(void)\n \n void kvm_arch_disable_virtualization_cpu(void)\n {\n+\tkvm_hacdbs_cpu_down();\n \tkvm_timer_cpu_down();\n \tkvm_vgic_cpu_down();\n \n@@ -2481,6 +2484,8 @@ static int __init init_subsystems(void)\n \tif (err)\n \t\tgoto out;\n \n+\tkvm_hacdbs_init();\n+\n \tkvm_register_perf_callbacks();\n \n \terr = kvm_hyp_trace_init();\ndiff --git a/arch/arm64/kvm/dirty_bit.c b/arch/arm64/kvm/dirty_bit.c\nnew file mode 100644\nindex 00000000000000..19289ea73d96d9\n--- /dev/null\n+++ b/arch/arm64/kvm/dirty_bit.c\n@@ -0,0 +1,411 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Copyright (C) 2026 ARM Ltd.\n+ * Author: Leonardo Bras \u003cleo.bras@arm.com\u003e\n+ */\n+\n+#include \u003casm/kvm_dirty_bit.h\u003e\n+#include \u003casm/kvm_mmu.h\u003e\n+#include \u003clinux/kconfig.h\u003e\n+#include \u003clinux/acpi.h\u003e\n+\n+DEFINE_PER_CPU(struct hacdbs, hacdbs_pcp) = {\n+\t.status = HACDBS_OFF,\n+\t.size = 0,\n+};\n+\n+/* HDBSS entry field definitions */\n+#define HDBSS_ENTRY_VALID BIT(0)\n+#define HDBSS_ENTRY_TTWL_SHIFT (1)\n+#define HDBSS_ENTRY_TTWL_MASK (GENMASK(3, 1))\n+#define HDBSS_ENTRY_TTWL(x) \\\n+\t(((x) \u003c\u003c HDBSS_ENTRY_TTWL_SHIFT) \u0026 HDBSS_ENTRY_TTWL_MASK)\n+#define HDBSS_ENTRY_TTWL_RESV HDBSS_ENTRY_TTWL(-4)\n+#define HDBSS_ENTRY_IPA GENMASK_ULL(55, 12)\n+\n+static __ro_after_init int hacdbsirq = -1;\n+\n+static void hacdbs_start(u64 *hw_entries, int size)\n+{\n+\tu64 br;\n+\t/* Each entry is 8 bytes */\n+\tint size_b = size * sizeof(hw_entries[0]);\n+\tint size_p2 = max(roundup_pow_of_two(size_b), PAGE_SIZE);\n+\n+\t/* If not using the full size of the array, put a stop entry at the end */\n+\tif (size_b \u003c size_p2)\n+\t\thw_entries[size] = HDBSS_ENTRY_VALID | HDBSS_ENTRY_TTWL_RESV;\n+\n+\tsysreg_clear_set_s(SYS_HACDBSCONS_EL2,\n+\t\t\t   HACDBSCONS_EL2_ERR_REASON | HACDBSCONS_EL2_INDEX, 0);\n+\n+\tbr = (virt_to_phys(hw_entries) \u0026 HACDBSBR_EL2_BADDR_MASK) |\n+\t     FIELD_PREP(HACDBSBR_EL2_SZ, ilog2(size_p2) - 12) |\n+\t     FIELD_PREP(HACDBSBR_EL2_EN, 1);\n+\n+\tthis_cpu_write(hacdbs_pcp.status, HACDBS_RUNNING);\n+\tthis_cpu_write(hacdbs_pcp.size, size);\n+\twrite_sysreg_s(br, SYS_HACDBSBR_EL2);\n+\tisb();\n+}\n+\n+static int hacdbs_stop(void)\n+{\n+\twrite_sysreg_s(0, SYS_HACDBSBR_EL2);\n+\tisb();\n+\n+\tif (this_cpu_read(hacdbs_pcp.status) == HACDBS_ERROR) {\n+\t\t/* In case of error, HACDBSCONS_EL2.INDEX should point the faulty entry */\n+\t\tu64 cons = read_sysreg_s(SYS_HACDBSCONS_EL2);\n+\t\tint idx = FIELD_GET(HACDBSCONS_EL2_INDEX, cons);\n+\n+\t\tthis_cpu_write(hacdbs_pcp.status, HACDBS_IDLE);\n+\n+\t\treturn idx;\n+\t}\n+\n+\treturn this_cpu_read(hacdbs_pcp.size);\n+}\n+\n+/*\n+ * Clears dirty-bits for an array of pages (hw_entries) using HACDBS\n+ * Returns the number of items cleaned from the array. If returns value \u003c size,\n+ *\tthere was an error in the processing.\n+ */\n+static int dirty_bit_clear(struct kvm *kvm, u64 *hw_entries, int size)\n+{\n+\tu64 hcr_el2;\n+\tint ret;\n+\n+\tpreempt_disable();\n+\n+\thcr_el2 = read_sysreg(HCR_EL2);\n+\twrite_sysreg(hcr_el2 | HCR_EL2_VM, HCR_EL2);\n+\t__load_stage2(\u0026kvm-\u003earch.mmu);\n+\n+\thacdbs_start(hw_entries, size);\n+\n+\tdo {\n+\t\twfi();\n+\t} while (this_cpu_read(hacdbs_pcp.status) == HACDBS_RUNNING);\n+\n+\tret = hacdbs_stop();\n+\n+\twrite_sysreg(hcr_el2, HCR_EL2);\n+\tisb();\n+\n+\t/*\n+\t * No DSB is needed here, as kvm_flush_remote_tlbs_memslot() that happens\n+\t * later in generic dirty-cleaning code already performs a DSB before\n+\t * doing the TLBI.\n+\t */\n+\n+\tpreempt_enable();\n+\n+\treturn ret;\n+}\n+\n+static inline void hdbss_to_bitmap(u64 *hdbss_array, int start, int end,\n+\t\t\t\t   unsigned long *dirty_bitmap,\n+\t\t\t\t   unsigned long long offset)\n+{\n+\tu64 w = (gpa_to_gfn(hdbss_array[start]) - offset) / BITS_PER_LONG;\n+\tu64 mask = 0;\n+\tint idx = start;\n+\n+\tdo {\n+\t\tu64 entry = (gpa_to_gfn(hdbss_array[idx]) - offset);\n+\n+\t\tif (entry / BITS_PER_LONG == w) {\n+\t\t\tmask |= BIT(entry % BITS_PER_LONG);\n+\t\t} else {\n+\t\t\tatomic_long_or(mask, (atomic_long_t *)\u0026dirty_bitmap[w]);\n+\t\t\tw = entry / BITS_PER_LONG;\n+\t\t\tmask = BIT(entry % BITS_PER_LONG);\n+\t\t}\n+\t} while (++idx \u003c end);\n+\tatomic_long_or(mask, (atomic_long_t *)\u0026dirty_bitmap[w]);\n+}\n+\n+static inline int mask_to_hdbss(unsigned long *mask, u64 *hw_entries, const gfn_t offset,\n+\t\t\t\tu64 ttwl, int idx, int entries_sz)\n+{\n+\twhile (idx \u003c entries_sz) {\n+\t\tint j = __ffs(*mask);\n+\t\tu64 a = gfn_to_gpa(offset + j);\n+\n+\t\thw_entries[idx++] = (a \u0026 HDBSS_ENTRY_IPA) |\n+\t\t\t\t    ttwl |\n+\t\t\t\t    HDBSS_ENTRY_VALID;\n+\n+\t\t*mask \u0026= ~BIT(j);\n+\t\tif (!*mask)\n+\t\t\tbreak;\n+\t}\n+\n+\treturn idx;\n+}\n+\n+int __kvm_arch_dirty_log_clear(struct kvm *kvm,\n+\t\t\t       struct kvm_memory_slot *memslot,\n+\t\t\t       struct kvm_clear_dirty_log *log,\n+\t\t\t       unsigned long *bitmap,\n+\t\t\t       bool *flush)\n+{\n+\tint ret = 0;\n+\tint idx = 0;\n+\tunsigned long *dirty_bitmap = memslot-\u003edirty_bitmap;\n+\tu64 *hw_entries;\n+\tconst int entries_sz = PAGE_SIZE / sizeof(*hw_entries);\n+\tu64 ttwl;\n+\tu64 start, end;\n+\tgfn_t base_gfn;\n+\tgpa_t split_end = 0;\n+\n+\thw_entries = kmalloc_objs(u64, entries_sz, GFP_KERNEL);\n+\tif (!hw_entries)\n+\t\treturn -ENOMEM;\n+\n+\tttwl = HDBSS_ENTRY_TTWL(KVM_PGTABLE_LAST_LEVEL);\n+\n+\tif (log) {\n+\t\tstart = log-\u003efirst_page / BITS_PER_LONG;\n+\t\tend = start + DIV_ROUND_UP(log-\u003enum_pages, BITS_PER_LONG);\n+\t\tbase_gfn = memslot-\u003ebase_gfn + log-\u003efirst_page % BITS_PER_LONG;\n+\t} else {\n+\t\tstart = 0;\n+\t\tend = kvm_dirty_bitmap_bytes(memslot) / sizeof(long);\n+\t\tbase_gfn = memslot-\u003ebase_gfn;\n+\t}\n+\n+\twrite_lock(\u0026kvm-\u003emmu_lock);\n+\n+\tfor (unsigned long i = start; i \u003c end; i++) {\n+\t\tunsigned long mask;\n+\t\tgfn_t offset;\n+\t\tatomic_long_t *p;\n+\n+\t\tif (log) { /* Clean only what is in the input bitmap */\n+\t\t\tmask = bitmap[i];\n+\t\t\tif (!mask)\n+\t\t\t\tcontinue;\n+\n+\t\t\tp = (atomic_long_t *)\u0026dirty_bitmap[i];\n+\t\t\tmask \u0026= atomic_long_fetch_andnot(mask, p);\n+\t\t} else { /* Clean everything */\n+\t\t\tif (!dirty_bitmap[i])\n+\t\t\t\tcontinue;\n+\n+\t\t\tmask = xchg(\u0026dirty_bitmap[i], 0);\n+\t\t\tbitmap[i] = mask;\n+\t\t}\n+\n+\t\tif (!mask)\n+\t\t\tcontinue;\n+\n+\t\toffset = base_gfn + i * BITS_PER_LONG;\n+\n+\t\tif (kvm_dirty_log_manual_protect_and_init_set(kvm) \u0026\u0026\n+\t\t    (offset + BITS_PER_LONG \u003e split_end)) {\n+\t\t\tgpa_t start = gfn_to_gpa(offset + __ffs(mask));\n+\t\t\tgpa_t end = gfn_to_gpa(offset + __fls(mask) + 1);\n+\n+\t\t\tkvm_mmu_split_huge_pages(kvm, start, end);\n+\t\t\tsplit_end = gpa_to_gfn(ALIGN_DOWN(end, PMD_SIZE) + PMD_SIZE - 1);\n+\t\t}\n+\n+\t\tdo {\n+\t\t\tidx = mask_to_hdbss(\u0026mask, hw_entries, offset, ttwl, idx, entries_sz);\n+\t\t\tif (idx \u003e= entries_sz) {\n+\t\t\t\tret = dirty_bit_clear(kvm, hw_entries, idx);\n+\t\t\t\t*flush = *flush || ret \u003e 0;\n+\t\t\t\tif (ret != idx) {\n+\t\t\t\t\t/* Save bits not converted back to bitmap */\n+\t\t\t\t\tatomic_long_or(mask, (atomic_long_t *)\u0026dirty_bitmap[i]);\n+\t\t\t\t\tgoto out_err;\n+\t\t\t\t}\n+\t\t\t\tidx = 0;\n+\t\t\t}\n+\t\t} while (mask);\n+\t}\n+\n+\tif (idx != 0) {\n+\t\tret = dirty_bit_clear(kvm, hw_entries, idx);\n+\t\t*flush = *flush || ret \u003e 0;\n+\t}\n+out_err:\n+\tif (unlikely(ret != idx)) {\n+\t\t/*\n+\t\t * In case there is an error and not all entries in HACDBS get\n+\t\t * cleaned, we have to mark the dirty bits back in the bitmap,\n+\t\t * as that will be used by the software routine.\n+\t\t *\n+\t\t * Entries should be in order, since they were extraxed from\n+\t\t * the dirty-bitmap, so batching the atomic writes is efficient.\n+\t\t */\n+\n+\t\tif (ret \u003c idx)\n+\t\t\thdbss_to_bitmap(hw_entries, ret, idx, dirty_bitmap, memslot-\u003ebase_gfn);\n+\n+\t\tret = -EAGAIN;\n+\t}\n+\n+\twrite_unlock(\u0026kvm-\u003emmu_lock);\n+\tkfree(hw_entries);\n+\n+\treturn ret;\n+}\n+\n+int __kvm_arch_dirty_ring_clear(struct kvm *kvm, struct kvm_dirty_ring *ring,\n+\t\t\t\tint *nr_entries_reset)\n+{\n+\tu64 *hw_entries;\n+\tu64 slot_offset = 0;\n+\tu64 ttwl;\n+\tint i, ret;\n+\tu32 slot = -1;\n+\n+\tif (signal_pending(current))\n+\t\treturn -EINTR;\n+\n+\tttwl = HDBSS_ENTRY_TTWL(KVM_PGTABLE_LAST_LEVEL);\n+\n+\thw_entries = kmalloc(max(ring-\u003esize * sizeof(u64), PAGE_SIZE), GFP_KERNEL);\n+\tif (!hw_entries)\n+\t\treturn -ENOMEM;\n+\n+\tfor (i = 0; i \u003c ring-\u003esize; i++) {\n+\t\tstruct kvm_dirty_gfn *entry;\n+\t\tgfn_t gfn;\n+\n+\t\tentry = \u0026ring-\u003edirty_gfns[(ring-\u003ereset_index + i) \u0026\n+\t\t\t\t\t  (ring-\u003esize - 1)];\n+\n+\t\tif (!kvm_dirty_gfn_harvested(entry))\n+\t\t\tbreak;\n+\n+\t\tif (entry-\u003eslot != slot) {\n+\t\t\tstruct kvm_memory_slot *memslot;\n+\n+\t\t\tmemslot = kvm_dirty_ring_get_memslot(kvm, entry-\u003eslot);\n+\t\t\tslot = entry-\u003eslot;\n+\t\t\tslot_offset = memslot-\u003ebase_gfn;\n+\t\t}\n+\n+\t\tgfn = slot_offset + entry-\u003eoffset;\n+\n+\t\thw_entries[i] = (gfn_to_gpa(gfn) \u0026 HDBSS_ENTRY_IPA) |\n+\t\t\t\tttwl | HDBSS_ENTRY_VALID;\n+\t}\n+\n+\tret = dirty_bit_clear(kvm, hw_entries, i);\n+\n+\t/* Set as invalid all successfully cleaned entries */\n+\tfor (int j = 0; j \u003c ret; j++) {\n+\t\tstruct kvm_dirty_gfn *entry;\n+\n+\t\tentry = \u0026ring-\u003edirty_gfns[(ring-\u003ereset_index + j) \u0026\n+\t\t\t\t\t  (ring-\u003esize - 1)];\n+\n+\t\tkvm_dirty_gfn_set_invalid(entry);\n+\t}\n+\n+\t/* In case of error, try software cleaning from the faulting entry */\n+\tring-\u003ereset_index += ret;\n+\t*nr_entries_reset += ret;\n+\n+\tkfree(hw_entries);\n+\n+\tif (ret \u003c i)\n+\t\treturn -EFAULT;\n+\n+\treturn ret;\n+}\n+\n+static irqreturn_t hacdbsirq_handler(int irq, void *pcpu)\n+{\n+\tu64 cons = read_sysreg_s(SYS_HACDBSCONS_EL2);\n+\tunsigned long err = FIELD_GET(HACDBSCONS_EL2_ERR_REASON, cons);\n+\n+\tswitch (err) {\n+\tcase HACDBSCONS_EL2_ERR_REASON_NOF:\n+\t\tthis_cpu_write(hacdbs_pcp.status, HACDBS_IDLE);\n+\t\tbreak;\n+\tcase HACDBSCONS_EL2_ERR_REASON_IPAHACF:\n+\t\t/* When size not a power of two \u003e= 4k, exit with reserved TTLW */\n+\t\tint index = FIELD_GET(HACDBSCONS_EL2_INDEX, cons);\n+\n+\t\tif (index \u003e= this_cpu_read(hacdbs_pcp.size)) {\n+\t\t\tthis_cpu_write(hacdbs_pcp.status, HACDBS_IDLE);\n+\t\t\tbreak;\n+\t\t}\n+\t\tfallthrough;\n+\tcase HACDBSCONS_EL2_ERR_REASON_STRUCTF:\n+\tcase HACDBSCONS_EL2_ERR_REASON_IPAF:\n+\t\tthis_cpu_write(hacdbs_pcp.status, HACDBS_ERROR);\n+\t\tbreak;\n+\t}\n+\n+\treturn IRQ_HANDLED;\n+}\n+\n+void kvm_hacdbs_cpu_up(void)\n+{\n+\tif (hacdbsirq \u003c 0)\n+\t\treturn;\n+\n+\tenable_percpu_irq(hacdbsirq, IRQ_TYPE_LEVEL_HIGH);\n+\tthis_cpu_write(hacdbs_pcp.status, HACDBS_IDLE);\n+}\n+\n+void kvm_hacdbs_cpu_down(void)\n+{\n+\tif (hacdbsirq \u003c 0)\n+\t\treturn;\n+\n+\tdisable_percpu_irq(hacdbsirq);\n+\tthis_cpu_write(hacdbs_pcp.status, HACDBS_OFF);\n+}\n+\n+#ifdef CONFIG_ACPI\n+static int __init hacdbs_acpi_get_irq(void)\n+{\n+\tstruct acpi_madt_generic_interrupt *gicc;\n+\tu32 gsi;\n+\tint irq;\n+\n+\tgicc = acpi_cpu_get_madt_gicc(smp_processor_id());\n+\tif (gicc-\u003eheader.length \u003c ACPI_MADT_GICC_HACDBSIRQ)\n+\t\treturn -ENXIO;\n+\n+\tgsi =  gicc-\u003ehacdbsirq_gsi;\n+\n+\tirq = acpi_register_gsi(NULL, gsi, ACPI_LEVEL_SENSITIVE, ACPI_ACTIVE_HIGH);\n+\tif (irq \u003c 0) {\n+\t\tpr_warn(\"ACPI: Unable to register HACDBS interrupt: %d\\n\", gsi);\n+\t\treturn -ENXIO;\n+\t}\n+\n+\treturn irq;\n+}\n+#else\n+#define hacdbs_acpi_get_irq() (-ENXIO)\n+#endif\n+\n+void __init kvm_hacdbs_init(void)\n+{\n+\tint irq;\n+\n+\t/* FEAT_HACDBS is only supported if Linux runs in EL2 (VHE) */\n+\tif (!system_supports_hacdbs() || !is_kernel_in_hyp_mode())\n+\t\treturn;\n+\n+\tirq = hacdbs_acpi_get_irq();\n+\tif (irq \u003c 0)\n+\t\treturn;\n+\n+\tif (request_percpu_irq(irq, hacdbsirq_handler, \"HACDBSIRQ\", \u0026hacdbs_pcp) \u003c 0)\n+\t\treturn;\n+\n+\thacdbsirq = irq;\n+}\ndiff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c\nindex 91a7dfad668660..e16729f0b7bd24 100644\n--- a/arch/arm64/kvm/hyp/pgtable.c\n+++ b/arch/arm64/kvm/hyp/pgtable.c\n@@ -731,9 +731,13 @@ static int stage2_set_prot_attr(struct kvm_pgtable *pgt, enum kvm_pgtable_prot p\n \tif (prot \u0026 KVM_PGTABLE_PROT_R)\n \t\tattr |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R;\n \n-\tif (prot \u0026 KVM_PGTABLE_PROT_W)\n+\tif (prot \u0026 KVM_PGTABLE_PROT_W) {\n \t\tattr |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W;\n \n+\t\tif (pgt-\u003eflags \u0026 KVM_PGTABLE_S2_DBM)\n+\t\t\tattr |= KVM_PTE_LEAF_ATTR_HI_S2_DBM;\n+\t}\n+\n \tif (!kvm_lpa2_is_enabled())\n \t\tattr |= FIELD_PREP(KVM_PTE_LEAF_ATTR_LO_S2_SH, sh);\n \n@@ -1367,9 +1371,13 @@ int kvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr,\n \tif (prot \u0026 KVM_PGTABLE_PROT_R)\n \t\tset |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R;\n \n-\tif (prot \u0026 KVM_PGTABLE_PROT_W)\n+\tif (prot \u0026 KVM_PGTABLE_PROT_W) {\n \t\tset |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W;\n \n+\t\tif (pgt-\u003eflags \u0026 KVM_PGTABLE_S2_DBM)\n+\t\t\tset |= KVM_PTE_LEAF_ATTR_HI_S2_DBM;\n+\t}\n+\n \tret = stage2_set_xn_attr(prot, \u0026xn);\n \tif (ret)\n \t\treturn ret;\n@@ -1585,6 +1593,9 @@ int __kvm_pgtable_stage2_init(struct kvm_pgtable *pgt, struct kvm_s2_mmu *mmu,\n \tif (!pgt-\u003epgd)\n \t\treturn -ENOMEM;\n \n+\tif (system_supports_hdbss())\n+\t\tflags |= KVM_PGTABLE_S2_DBM;\n+\n \tpgt-\u003eia_bits\t\t= ia_bits;\n \tpgt-\u003estart_level\t= start_level;\n \tpgt-\u003emm_ops\t\t= mm_ops;\ndiff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c\nindex 6c941aaa10c639..2f9d90c35668b9 100644\n--- a/arch/arm64/kvm/mmu.c\n+++ b/arch/arm64/kvm/mmu.c\n@@ -117,8 +117,8 @@ static bool need_split_memcache_topup_or_resched(struct kvm *kvm)\n \treturn kvm_mmu_memory_cache_nr_free_objects(cache) \u003c min;\n }\n \n-static int kvm_mmu_split_huge_pages(struct kvm *kvm, phys_addr_t addr,\n-\t\t\t\t    phys_addr_t end)\n+int kvm_mmu_split_huge_pages(struct kvm *kvm, phys_addr_t addr,\n+\t\t\t     phys_addr_t end)\n {\n \tstruct kvm_mmu_memory_cache *cache;\n \tstruct kvm_pgtable *pgt;\n@@ -1027,8 +1027,12 @@ int kvm_init_stage2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu, unsigned long t\n \tfor_each_possible_cpu(cpu)\n \t\t*per_cpu_ptr(mmu-\u003elast_vcpu_ran, cpu) = -1;\n \n-\t /* The eager page splitting is disabled by default */\n-\tmmu-\u003esplit_page_chunk_size = KVM_ARM_EAGER_SPLIT_CHUNK_SIZE_DEFAULT;\n+\t /* The eager page splitting is disabled by default if system has no HDBSS */\n+\tif (system_supports_hdbss())\n+\t\tmmu-\u003esplit_page_chunk_size = 256 * 1024 * PAGE_SIZE;\n+\telse\n+\t\tmmu-\u003esplit_page_chunk_size = KVM_ARM_EAGER_SPLIT_CHUNK_SIZE_DEFAULT;\n+\n \tmmu-\u003esplit_page_cache.gfp_zero = __GFP_ZERO;\n \n \tmmu-\u003epgd_phys = __pa(pgt-\u003epgd);\ndiff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps\nindex 9b85a84f6fd494..bd2c0bb98da61e 100644\n--- a/arch/arm64/tools/cpucaps\n+++ b/arch/arm64/tools/cpucaps\n@@ -69,8 +69,10 @@ HAS_VA52\n HAS_VIRT_HOST_EXTN\n HAS_WFXT\n HAS_XNX\n+HAS_HDBSS\n HAFT\n HW_DBM\n+HACDBS\n KVM_HVHE\n KVM_PROTECTED_MODE\n MISMATCHED_CACHE_TYPE\ndiff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg\nindex bc1788b1662b76..7b7c3d6a0f0309 100644\n--- a/arch/arm64/tools/sysreg\n+++ b/arch/arm64/tools/sysreg\n@@ -4634,6 +4634,36 @@ Field\t7:6\tSL0\n Field\t5:0\tT0SZ\n EndSysreg\n \n+Sysreg\tHACDBSBR_EL2\t3\t4\t2\t3\t4\n+Res0\t63:56\n+Field\t55:12\tBADDR\n+Field\t11\tEN\n+Res0\t10:4\n+UnsignedEnum\t3:0\tSZ\n+\t0b0000\t4K\n+\t0b0001\t8K\n+\t0b0010\t16K\n+\t0b0011\t32K\n+\t0b0100\t64K\n+\t0b0101\t128K\n+\t0b0110\t256K\n+\t0b0111\t512K\n+\t0b1000\t1M\n+\t0b1001\t2M\n+EndEnum\n+EndSysreg\n+\n+Sysreg\tHACDBSCONS_EL2\t3\t4\t2\t3\t5\n+UnsignedEnum\t63:62\tERR_REASON\n+\t0b00\tNOF\n+\t0b01\tSTRUCTF\n+\t0b10\tIPAF\n+\t0b11\tIPAHACF\n+EndEnum\n+Res0\t61:19\n+Field\t18:0\tINDEX\n+EndSysreg\n+\n Sysreg\tGCSCR_EL2\t3\t4\t2\t5\t0\n Fields\tGCSCR_ELx\n EndSysreg\ndiff --git a/include/acpi/actbl2.h b/include/acpi/actbl2.h\nindex baef525367b5c4..eaefb494ef5972 100644\n--- a/include/acpi/actbl2.h\n+++ b/include/acpi/actbl2.h\n@@ -1449,6 +1449,7 @@ struct acpi_madt_generic_interrupt {\n \tu16 trbe_interrupt;\t/* ACPI 6.5 */\n \tu16 iaffid;\t\t/* ACPI 6.7 */\n \tu32 irs_id;\n+\tu32 hacdbsirq_gsi;\t/* ACPI 6.X */\n };\n \n /* Masks for Flags field above */\ndiff --git a/include/linux/kvm_dirty_bit.h b/include/linux/kvm_dirty_bit.h\nnew file mode 100644\nindex 00000000000000..8492979d694e70\n--- /dev/null\n+++ b/include/linux/kvm_dirty_bit.h\n@@ -0,0 +1,34 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/*\n+ * Copyright (C) 2026 ARM Ltd.\n+ * Author: Leonardo Bras \u003cleo.bras@arm.com\u003e\n+ */\n+\n+#ifndef __KVM_DIRTY_BIT_H__\n+#define __KVM_DIRTY_BIT_H__\n+\n+#ifndef CONFIG_HAVE_KVM_HW_DIRTY_BIT\n+\n+static inline int kvm_arch_dirty_log_clear(struct kvm *kvm,\n+\t\t\t\t\t   struct kvm_memory_slot *memslot,\n+\t\t\t\t\t   struct kvm_clear_dirty_log *log,\n+\t\t\t\t\t   unsigned long *bitmap,\n+\t\t\t\t\t   bool *flush)\n+{\n+\treturn -ENXIO;\n+}\n+\n+static inline int kvm_arch_dirty_ring_clear(struct kvm *kvm,\n+\t\t\t\t\t    struct kvm_dirty_ring *ring,\n+\t\t\t\t\t    int *nr_entries_reset)\n+{\n+\treturn -ENXIO;\n+}\n+\n+#else /* CONFIG_HAVE_KVM_HW_DIRTY_BIT */\n+\n+#include \u003casm/kvm_dirty_bit.h\u003e\n+\n+#endif /* CONFIG_HAVE_KVM_HW_DIRTY_BIT */\n+\n+#endif /* __KVM_DIRTY_BIT_H__ */\ndiff --git a/include/linux/kvm_dirty_ring.h b/include/linux/kvm_dirty_ring.h\nindex eb10d87adf7d59..190d97fce4a42e 100644\n--- a/include/linux/kvm_dirty_ring.h\n+++ b/include/linux/kvm_dirty_ring.h\n@@ -84,6 +84,18 @@ void kvm_dirty_ring_push(struct kvm_vcpu *vcpu, u32 slot, u64 offset);\n \n bool kvm_dirty_ring_check_request(struct kvm_vcpu *vcpu);\n \n+static inline bool kvm_dirty_gfn_harvested(struct kvm_dirty_gfn *gfn)\n+{\n+\treturn smp_load_acquire(\u0026gfn-\u003eflags) \u0026 KVM_DIRTY_GFN_F_RESET;\n+}\n+\n+static inline void kvm_dirty_gfn_set_invalid(struct kvm_dirty_gfn *gfn)\n+{\n+\tsmp_store_release(\u0026gfn-\u003eflags, 0);\n+}\n+\n+struct kvm_memory_slot *kvm_dirty_ring_get_memslot(struct kvm *kvm, u32 slot);\n+\n /* for use in vm_operations_struct */\n struct page *kvm_dirty_ring_get_page(struct kvm_dirty_ring *ring, u32 offset);\n \ndiff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h\nindex ab8cfaec82d31e..7ea6ed7ce203aa 100644\n--- a/include/linux/kvm_host.h\n+++ b/include/linux/kvm_host.h\n@@ -1669,6 +1669,9 @@ bool kvm_arch_vcpu_preempted_in_kernel(struct kvm_vcpu *vcpu);\n void kvm_arch_pre_destroy_vm(struct kvm *kvm);\n void kvm_arch_create_vm_debugfs(struct kvm *kvm);\n \n+int kvm_mmu_split_huge_pages(struct kvm *kvm, phys_addr_t addr,\n+\t\t\t     phys_addr_t end);\n+\n #ifndef __KVM_HAVE_ARCH_VM_ALLOC\n /*\n  * All architectures that want to use vzalloc currently also\ndiff --git a/virt/kvm/Kconfig b/virt/kvm/Kconfig\nindex 794976b88c6f97..f8757b5b84b3e0 100644\n--- a/virt/kvm/Kconfig\n+++ b/virt/kvm/Kconfig\n@@ -20,6 +20,9 @@ config HAVE_KVM_IRQ_ROUTING\n config HAVE_KVM_DIRTY_RING\n        bool\n \n+config HAVE_KVM_HW_DIRTY_BIT\n+       bool\n+\n # Only strongly ordered architectures can select this, as it doesn't\n # put any explicit constraint on userspace ordering. They can also\n # select the _ACQ_REL version.\ndiff --git a/virt/kvm/dirty_ring.c b/virt/kvm/dirty_ring.c\nindex 572b854edf740d..fe4e7da6cc4a6a 100644\n--- a/virt/kvm/dirty_ring.c\n+++ b/virt/kvm/dirty_ring.c\n@@ -8,6 +8,7 @@\n #include \u003clinux/kvm.h\u003e\n #include \u003clinux/vmalloc.h\u003e\n #include \u003clinux/kvm_dirty_ring.h\u003e\n+#include \u003clinux/kvm_dirty_bit.h\u003e\n #include \u003ctrace/events/kvm.h\u003e\n #include \"kvm_mm.h\"\n \n@@ -50,18 +51,30 @@ static bool kvm_dirty_ring_full(struct kvm_dirty_ring *ring)\n \treturn kvm_dirty_ring_used(ring) \u003e= ring-\u003esize;\n }\n \n-static void kvm_reset_dirty_gfn(struct kvm *kvm, u32 slot, u64 offset, u64 mask)\n+static inline struct kvm_memory_slot *\n+__kvm_dirty_ring_get_memslot(struct kvm *kvm, u32 slot)\n {\n-\tstruct kvm_memory_slot *memslot;\n \tint as_id, id;\n \n \tas_id = slot \u003e\u003e 16;\n \tid = (u16)slot;\n \n \tif (as_id \u003e= kvm_arch_nr_memslot_as_ids(kvm) || id \u003e= KVM_USER_MEM_SLOTS)\n-\t\treturn;\n+\t\treturn 0;\n+\n+\treturn id_to_memslot(__kvm_memslots(kvm, as_id), id);\n+}\n \n-\tmemslot = id_to_memslot(__kvm_memslots(kvm, as_id), id);\n+struct kvm_memory_slot *kvm_dirty_ring_get_memslot(struct kvm *kvm, u32 slot)\n+{\n+\treturn __kvm_dirty_ring_get_memslot(kvm, slot);\n+}\n+\n+static void kvm_reset_dirty_gfn(struct kvm *kvm, u32 slot, u64 offset, u64 mask)\n+{\n+\tstruct kvm_memory_slot *memslot;\n+\n+\tmemslot = __kvm_dirty_ring_get_memslot(kvm, slot);\n \n \tif (!memslot || offset \u003e= memslot-\u003enpages ||\n \t    offset + __fls(mask) \u003e= memslot-\u003enpages)\n@@ -88,21 +101,11 @@ int kvm_dirty_ring_alloc(struct kvm *kvm, struct kvm_dirty_ring *ring,\n \treturn 0;\n }\n \n-static inline void kvm_dirty_gfn_set_invalid(struct kvm_dirty_gfn *gfn)\n-{\n-\tsmp_store_release(\u0026gfn-\u003eflags, 0);\n-}\n-\n static inline void kvm_dirty_gfn_set_dirtied(struct kvm_dirty_gfn *gfn)\n {\n \tgfn-\u003eflags = KVM_DIRTY_GFN_F_DIRTY;\n }\n \n-static inline bool kvm_dirty_gfn_harvested(struct kvm_dirty_gfn *gfn)\n-{\n-\treturn smp_load_acquire(\u0026gfn-\u003eflags) \u0026 KVM_DIRTY_GFN_F_RESET;\n-}\n-\n int kvm_dirty_ring_reset(struct kvm *kvm, struct kvm_dirty_ring *ring,\n \t\t\t int *nr_entries_reset)\n {\n@@ -131,6 +134,9 @@ int kvm_dirty_ring_reset(struct kvm *kvm, struct kvm_dirty_ring *ring,\n \t */\n \tlockdep_assert_held(\u0026kvm-\u003eslots_lock);\n \n+\tif (kvm_arch_dirty_ring_clear(kvm, ring, nr_entries_reset) \u003e= 0)\n+\t\treturn 0;\n+\n \twhile (likely((*nr_entries_reset) \u003c INT_MAX)) {\n \t\tif (signal_pending(current))\n \t\t\treturn -EINTR;\ndiff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c\nindex e44c20c049610d..a25b8902cdfc17 100644\n--- a/virt/kvm/kvm_main.c\n+++ b/virt/kvm/kvm_main.c\n@@ -65,6 +65,7 @@\n #include \u003ctrace/events/kvm.h\u003e\n \n #include \u003clinux/kvm_dirty_ring.h\u003e\n+#include \u003clinux/kvm_dirty_bit.h\u003e\n \n \n /* Worst case buffer size needed for holding an integer. */\n@@ -2262,6 +2263,10 @@ static int kvm_get_dirty_log_protect(struct kvm *kvm, struct kvm_dirty_log *log)\n \t\tdirty_bitmap_buffer = kvm_second_dirty_bitmap(memslot);\n \t\tmemset(dirty_bitmap_buffer, 0, n);\n \n+\t\tif (kvm_arch_dirty_log_clear(kvm, memslot, NULL,\n+\t\t\t\t\t     dirty_bitmap_buffer, \u0026flush) \u003e= 0)\n+\t\t\tgoto out;\n+\n \t\tKVM_MMU_LOCK(kvm);\n \t\tfor (i = 0; i \u003c n / sizeof(long); i++) {\n \t\t\tunsigned long mask;\n@@ -2281,6 +2286,7 @@ static int kvm_get_dirty_log_protect(struct kvm *kvm, struct kvm_dirty_log *log)\n \t\tKVM_MMU_UNLOCK(kvm);\n \t}\n \n+out:\n \tif (flush)\n \t\tkvm_flush_remote_tlbs_memslot(kvm, memslot);\n \n@@ -2373,6 +2379,10 @@ static int kvm_clear_dirty_log_protect(struct kvm *kvm,\n \tif (copy_from_user(dirty_bitmap_buffer, log-\u003edirty_bitmap, n))\n \t\treturn -EFAULT;\n \n+\tif (kvm_arch_dirty_log_clear(kvm, memslot, log, dirty_bitmap_buffer,\n+\t\t\t\t     \u0026flush) \u003e= 0)\n+\t\tgoto out;\n+\n \tKVM_MMU_LOCK(kvm);\n \tfor (offset = log-\u003efirst_page, i = offset / BITS_PER_LONG,\n \t\t n = DIV_ROUND_UP(log-\u003enum_pages, BITS_PER_LONG); n--;\n@@ -2392,12 +2402,13 @@ static int kvm_clear_dirty_log_protect(struct kvm *kvm,\n \t\t*/\n \t\tif (mask) {\n \t\t\tflush = true;\n+\n \t\t\tkvm_arch_mmu_enable_log_dirty_pt_masked(kvm, memslot,\n \t\t\t\t\t\t\t\toffset, mask);\n \t\t}\n \t}\n \tKVM_MMU_UNLOCK(kvm);\n-\n+out:\n \tif (flush)\n \t\tkvm_flush_remote_tlbs_memslot(kvm, memslot);\n \n"
}

2/1 2026/06/29 11:40 agent patch-evaluator
1m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "KVM",
    "ACPI"
  ],
  "FocusSymbols": [
    "__kvm_arch_dirty_log_clear",
    "__kvm_arch_dirty_ring_clear",
    "dirty_bit_clear",
    "hacdbsirq_handler",
    "kvm_mmu_split_huge_pages",
    "kvm_get_dirty_log_protect",
    "kvm_clear_dirty_log_protect",
    "kvm_dirty_ring_reset"
  ],
  "Reasoning": "The patch introduces hardware dirty bit tracking (HDBSS) and cleaning (HACDBS) for ARM64 KVM. It modifies core KVM memory management and dirty logging logic, adding new ioctl paths and interrupt handlers. This is functional and reachable code that should be fuzzed.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a '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:
For your convenience, here is the diff of the changes:
commit 6c5867176f710476b28b136db5c83931a75ebe88
Author: syz-cluster <triage@syzkaller.com>
Date:   Mon Jun 29 11:40:53 2026 +0000

    syz-cluster: applied patch under review

diff --git a/arch/arm64/include/asm/acpi.h b/arch/arm64/include/asm/acpi.h
index 8a54ca6ba60282..883315e9d79d98 100644
--- a/arch/arm64/include/asm/acpi.h
+++ b/arch/arm64/include/asm/acpi.h
@@ -45,6 +45,9 @@
 
 #define ACPI_MADT_GICC_TRBE  (offsetof(struct acpi_madt_generic_interrupt, \
 	trbe_interrupt) + sizeof(u16))
+
+#define ACPI_MADT_GICC_HACDBSIRQ  (offsetof(struct acpi_madt_generic_interrupt, \
+	hacdbsirq_gsi) + sizeof(u32))
 /*
  * Arm® Functional Fixed Hardware Specification Version 1.2.
  * Table 2: Arm Architecture context loss flags
diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h
index a57870fa96db5b..620ae4cddb760a 100644
--- a/arch/arm64/include/asm/cpufeature.h
+++ b/arch/arm64/include/asm/cpufeature.h
@@ -863,6 +863,16 @@ static inline bool system_supports_haft(void)
 	return cpus_have_final_cap(ARM64_HAFT);
 }
 
+static inline bool system_supports_hdbss(void)
+{
+	return cpus_have_final_cap(ARM64_HAS_HDBSS);
+}
+
+static inline bool system_supports_hacdbs(void)
+{
+	return cpus_have_final_cap(ARM64_HACDBS);
+}
+
 static __always_inline bool system_supports_mpam(void)
 {
 	return alternative_has_cap_unlikely(ARM64_MPAM);
diff --git a/arch/arm64/include/asm/kvm_dirty_bit.h b/arch/arm64/include/asm/kvm_dirty_bit.h
new file mode 100644
index 00000000000000..d76c109937d8da
--- /dev/null
+++ b/arch/arm64/include/asm/kvm_dirty_bit.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 ARM Ltd.
+ * Author: Leonardo Bras <leo.bras@arm.com>
+ */
+
+#ifndef __ARM64_KVM_DIRTY_BIT_H__
+#define __ARM64_KVM_DIRTY_BIT_H__
+
+#include <asm/kvm_pgtable.h>
+
+enum hacdbs_status {
+	HACDBS_OFF,
+	HACDBS_IDLE,
+	HACDBS_RUNNING,
+	HACDBS_ERROR
+};
+
+struct hacdbs {
+	enum hacdbs_status status;
+	int size;
+};
+
+DECLARE_PER_CPU(struct hacdbs, hacdbs_pcp);
+
+void __init kvm_hacdbs_init(void);
+void kvm_hacdbs_cpu_up(void);
+void kvm_hacdbs_cpu_down(void);
+
+int __kvm_arch_dirty_log_clear(struct kvm *kvm,
+			       struct kvm_memory_slot *memslot,
+			       struct kvm_clear_dirty_log *log,
+			       unsigned long *bitmap,
+			       bool *flush);
+
+int __kvm_arch_dirty_ring_clear(struct kvm *kvm, struct kvm_dirty_ring *ring,
+				int *nr_entries_reset);
+
+static inline bool kvm_arch_dirty_clear_enabled(struct kvm *kvm)
+{
+	return this_cpu_read(hacdbs_pcp.status) == HACDBS_IDLE &&
+	       (kvm->arch.mmu.pgt->flags & KVM_PGTABLE_S2_DBM);
+}
+
+static inline int kvm_arch_dirty_log_clear(struct kvm *kvm,
+					   struct kvm_memory_slot *memslot,
+					   struct kvm_clear_dirty_log *log,
+					   unsigned long *bitmap,
+					   bool *flush)
+{
+	if (!kvm_arch_dirty_clear_enabled(kvm))
+		return -EPERM;
+
+	return __kvm_arch_dirty_log_clear(kvm, memslot, log, bitmap, flush);
+}
+
+static inline int kvm_arch_dirty_ring_clear(struct kvm *kvm,
+					    struct kvm_dirty_ring *ring,
+					    int *nr_entries_reset)
+{
+	if (!kvm_arch_dirty_clear_enabled(kvm))
+		return -EPERM;
+
+	return __kvm_arch_dirty_ring_clear(kvm, ring, nr_entries_reset);
+}
+
+#endif /* __ARM64_KVM_DIRTY_BIT_H__ */
diff --git a/arch/arm64/include/asm/kvm_pgtable.h b/arch/arm64/include/asm/kvm_pgtable.h
index 41a8687938eb6b..646ff88e02583f 100644
--- a/arch/arm64/include/asm/kvm_pgtable.h
+++ b/arch/arm64/include/asm/kvm_pgtable.h
@@ -93,6 +93,8 @@ typedef u64 kvm_pte_t;
 
 #define KVM_PTE_LEAF_ATTR_HI_S2_XN	GENMASK(54, 53)
 
+#define KVM_PTE_LEAF_ATTR_HI_S2_DBM	BIT(51)
+
 #define KVM_PTE_LEAF_ATTR_HI_S1_GP	BIT(50)
 
 #define KVM_PTE_LEAF_ATTR_S2_PERMS	(KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R | \
@@ -253,6 +255,7 @@ struct kvm_pgtable_mm_ops {
 enum kvm_pgtable_stage2_flags {
 	KVM_PGTABLE_S2_IDMAP			= BIT(0),
 	KVM_PGTABLE_S2_AS_S1			= BIT(1),
+	KVM_PGTABLE_S2_DBM			= BIT(2),
 };
 
 /**
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index 9a22df0c5120f3..62f56bbd0a65c8 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -523,6 +523,7 @@ static const struct arm64_ftr_bits ftr_id_aa64mmfr3[] = {
 static const struct arm64_ftr_bits ftr_id_aa64mmfr4[] = {
 	S_ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR4_EL1_E2H0_SHIFT, 4, 0),
 	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR4_EL1_NV_frac_SHIFT, 4, 0),
+	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64MMFR4_EL1_HACDBS_SHIFT, 4, 0),
 	ARM64_FTR_END,
 };
 
@@ -2131,6 +2132,11 @@ static bool hvhe_possible(const struct arm64_cpu_capabilities *entry,
 	return arm64_test_sw_feature_override(ARM64_SW_FEATURE_OVERRIDE_HVHE);
 }
 
+static bool has_vhe_hdbss(const struct arm64_cpu_capabilities *entry, int cope)
+{
+	return is_kernel_in_hyp_mode() && has_cpuid_feature(entry, cope);
+}
+
 bool cpu_supports_bbml2_noabort(void)
 {
 	/*
@@ -2766,6 +2772,13 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
 		ARM64_CPUID_FIELDS(ID_AA64MMFR1_EL1, HAFDBS, DBM)
 	},
 #endif
+	{
+		.desc = "Hardware dirty bit Cleaning",
+		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
+		.capability = ARM64_HACDBS,
+		.matches = has_cpuid_feature,
+		ARM64_CPUID_FIELDS(ID_AA64MMFR4_EL1, HACDBS, IMP)
+	},
 #ifdef CONFIG_ARM64_HAFT
 	{
 		.desc = "Hardware managed Access Flag for Table Descriptors",
@@ -2781,6 +2794,13 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
 		ARM64_CPUID_FIELDS(ID_AA64MMFR1_EL1, HAFDBS, HAFT)
 	},
 #endif
+	{
+		.desc = "Hardware Dirty state tracking structure (HDBSS)",
+		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
+		.capability = ARM64_HAS_HDBSS,
+		.matches = has_vhe_hdbss,
+		ARM64_CPUID_FIELDS(ID_AA64MMFR1_EL1, HAFDBS, HDBSS)
+	},
 	{
 		.desc = "CRC32 instructions",
 		.capability = ARM64_HAS_CRC32,
diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig
index 449154f9a4852d..db8487bf738b78 100644
--- a/arch/arm64/kvm/Kconfig
+++ b/arch/arm64/kvm/Kconfig
@@ -34,6 +34,7 @@ menuconfig KVM
 	select HAVE_KVM_IRQ_BYPASS
 	select HAVE_KVM_READONLY_MEM
 	select HAVE_KVM_VCPU_RUN_PID_CHANGE
+	select HAVE_KVM_HW_DIRTY_BIT if ACPI
 	select SCHED_INFO
 	select GUEST_PERF_EVENTS if PERF_EVENTS
 	select KVM_GUEST_MEMFD
diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
index 59612d2f277c1d..6faacd857346b3 100644
--- a/arch/arm64/kvm/Makefile
+++ b/arch/arm64/kvm/Makefile
@@ -24,7 +24,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
 	 vgic/vgic-mmio.o vgic/vgic-mmio-v2.o \
 	 vgic/vgic-mmio-v3.o vgic/vgic-kvm-device.o \
 	 vgic/vgic-its.o vgic/vgic-debug.o vgic/vgic-v3-nested.o \
-	 vgic/vgic-v5.o
+	 vgic/vgic-v5.o dirty_bit.o
 
 kvm-$(CONFIG_HW_PERF_EVENTS)  += pmu-emul.o pmu.o
 kvm-$(CONFIG_ARM64_PTR_AUTH)  += pauth.o
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 50adfff75be82e..dc1a4629aaeb98 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -42,6 +42,7 @@
 #include <asm/kvm_nested.h>
 #include <asm/kvm_pkvm.h>
 #include <asm/kvm_ptrauth.h>
+#include <asm/kvm_dirty_bit.h>
 #include <asm/sections.h>
 #include <asm/stacktrace/nvhe.h>
 
@@ -2307,6 +2308,7 @@ int kvm_arch_enable_virtualization_cpu(void)
 
 	kvm_vgic_cpu_up();
 	kvm_timer_cpu_up();
+	kvm_hacdbs_cpu_up();
 
 	preempt_enable();
 
@@ -2315,6 +2317,7 @@ int kvm_arch_enable_virtualization_cpu(void)
 
 void kvm_arch_disable_virtualization_cpu(void)
 {
+	kvm_hacdbs_cpu_down();
 	kvm_timer_cpu_down();
 	kvm_vgic_cpu_down();
 
@@ -2481,6 +2484,8 @@ static int __init init_subsystems(void)
 	if (err)
 		goto out;
 
+	kvm_hacdbs_init();
+
 	kvm_register_perf_callbacks();
 
 	err = kvm_hyp_trace_init();
diff --git a/arch/arm64/kvm/dirty_bit.c b/arch/arm64/kvm/dirty_bit.c
new file mode 100644
index 00000000000000..19289ea73d96d9
--- /dev/null
+++ b/arch/arm64/kvm/dirty_bit.c
@@ -0,0 +1,411 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 ARM Ltd.
+ * Author: Leonardo Bras <leo.bras@arm.com>
+ */
+
+#include <asm/kvm_dirty_bit.h>
+#include <asm/kvm_mmu.h>
+#include <linux/kconfig.h>
+#include <linux/acpi.h>
+
+DEFINE_PER_CPU(struct hacdbs, hacdbs_pcp) = {
+	.status = HACDBS_OFF,
+	.size = 0,
+};
+
+/* HDBSS entry field definitions */
+#define HDBSS_ENTRY_VALID BIT(0)
+#define HDBSS_ENTRY_TTWL_SHIFT (1)
+#define HDBSS_ENTRY_TTWL_MASK (GENMASK(3, 1))
+#define HDBSS_ENTRY_TTWL(x) \
+	(((x) << HDBSS_ENTRY_TTWL_SHIFT) & HDBSS_ENTRY_TTWL_MASK)
+#define HDBSS_ENTRY_TTWL_RESV HDBSS_ENTRY_TTWL(-4)
+#define HDBSS_ENTRY_IPA GENMASK_ULL(55, 12)
+
+static __ro_after_init int hacdbsirq = -1;
+
+static void hacdbs_start(u64 *hw_entries, int size)
+{
+	u64 br;
+	/* Each entry is 8 bytes */
+	int size_b = size * sizeof(hw_entries[0]);
+	int size_p2 = max(roundup_pow_of_two(size_b), PAGE_SIZE);
+
+	/* If not using the full size of the array, put a stop entry at the end */
+	if (size_b < size_p2)
+		hw_entries[size] = HDBSS_ENTRY_VALID | HDBSS_ENTRY_TTWL_RESV;
+
+	sysreg_clear_set_s(SYS_HACDBSCONS_EL2,
+			   HACDBSCONS_EL2_ERR_REASON | HACDBSCONS_EL2_INDEX, 0);
+
+	br = (virt_to_phys(hw_entries) & HACDBSBR_EL2_BADDR_MASK) |
+	     FIELD_PREP(HACDBSBR_EL2_SZ, ilog2(size_p2) - 12) |
+	     FIELD_PREP(HACDBSBR_EL2_EN, 1);
+
+	this_cpu_write(hacdbs_pcp.status, HACDBS_RUNNING);
+	this_cpu_write(hacdbs_pcp.size, size);
+	write_sysreg_s(br, SYS_HACDBSBR_EL2);
+	isb();
+}
+
+static int hacdbs_stop(void)
+{
+	write_sysreg_s(0, SYS_HACDBSBR_EL2);
+	isb();
+
+	if (this_cpu_read(hacdbs_pcp.status) == HACDBS_ERROR) {
+		/* In case of error, HACDBSCONS_EL2.INDEX should point the faulty entry */
+		u64 cons = read_sysreg_s(SYS_HACDBSCONS_EL2);
+		int idx = FIELD_GET(HACDBSCONS_EL2_INDEX, cons);
+
+		this_cpu_write(hacdbs_pcp.status, HACDBS_IDLE);
+
+		return idx;
+	}
+
+	return this_cpu_read(hacdbs_pcp.size);
+}
+
+/*
+ * Clears dirty-bits for an array of pages (hw_entries) using HACDBS
+ * Returns the number of items cleaned from the array. If returns value < size,
+ *	there was an error in the processing.
+ */
+static int dirty_bit_clear(struct kvm *kvm, u64 *hw_entries, int size)
+{
+	u64 hcr_el2;
+	int ret;
+
+	preempt_disable();
+
+	hcr_el2 = read_sysreg(HCR_EL2);
+	write_sysreg(hcr_el2 | HCR_EL2_VM, HCR_EL2);
+	__load_stage2(&kvm->arch.mmu);
+
+	hacdbs_start(hw_entries, size);
+
+	do {
+		wfi();
+	} while (this_cpu_read(hacdbs_pcp.status) == HACDBS_RUNNING);
+
+	ret = hacdbs_stop();
+
+	write_sysreg(hcr_el2, HCR_EL2);
+	isb();
+
+	/*
+	 * No DSB is needed here, as kvm_flush_remote_tlbs_memslot() that happens
+	 * later in generic dirty-cleaning code already performs a DSB before
+	 * doing the TLBI.
+	 */
+
+	preempt_enable();
+
+	return ret;
+}
+
+static inline void hdbss_to_bitmap(u64 *hdbss_array, int start, int end,
+				   unsigned long *dirty_bitmap,
+				   unsigned long long offset)
+{
+	u64 w = (gpa_to_gfn(hdbss_array[start]) - offset) / BITS_PER_LONG;
+	u64 mask = 0;
+	int idx = start;
+
+	do {
+		u64 entry = (gpa_to_gfn(hdbss_array[idx]) - offset);
+
+		if (entry / BITS_PER_LONG == w) {
+			mask |= BIT(entry % BITS_PER_LONG);
+		} else {
+			atomic_long_or(mask, (atomic_long_t *)&dirty_bitmap[w]);
+			w = entry / BITS_PER_LONG;
+			mask = BIT(entry % BITS_PER_LONG);
+		}
+	} while (++idx < end);
+	atomic_long_or(mask, (atomic_long_t *)&dirty_bitmap[w]);
+}
+
+static inline int mask_to_hdbss(unsigned long *mask, u64 *hw_entries, const gfn_t offset,
+				u64 ttwl, int idx, int entries_sz)
+{
+	while (idx < entries_sz) {
+		int j = __ffs(*mask);
+		u64 a = gfn_to_gpa(offset + j);
+
+		hw_entries[idx++] = (a & HDBSS_ENTRY_IPA) |
+				    ttwl |
+				    HDBSS_ENTRY_VALID;
+
+		*mask &= ~BIT(j);
+		if (!*mask)
+			break;
+	}
+
+	return idx;
+}
+
+int __kvm_arch_dirty_log_clear(struct kvm *kvm,
+			       struct kvm_memory_slot *memslot,
+			       struct kvm_clear_dirty_log *log,
+			       unsigned long *bitmap,
+			       bool *flush)
+{
+	int ret = 0;
+	int idx = 0;
+	unsigned long *dirty_bitmap = memslot->dirty_bitmap;
+	u64 *hw_entries;
+	const int entries_sz = PAGE_SIZE / sizeof(*hw_entries);
+	u64 ttwl;
+	u64 start, end;
+	gfn_t base_gfn;
+	gpa_t split_end = 0;
+
+	hw_entries = kmalloc_objs(u64, entries_sz, GFP_KERNEL);
+	if (!hw_entries)
+		return -ENOMEM;
+
+	ttwl = HDBSS_ENTRY_TTWL(KVM_PGTABLE_LAST_LEVEL);
+
+	if (log) {
+		start = log->first_page / BITS_PER_LONG;
+		end = start + DIV_ROUND_UP(log->num_pages, BITS_PER_LONG);
+		base_gfn = memslot->base_gfn + log->first_page % BITS_PER_LONG;
+	} else {
+		start = 0;
+		end = kvm_dirty_bitmap_bytes(memslot) / sizeof(long);
+		base_gfn = memslot->base_gfn;
+	}
+
+	write_lock(&kvm->mmu_lock);
+
+	for (unsigned long i = start; i < end; i++) {
+		unsigned long mask;
+		gfn_t offset;
+		atomic_long_t *p;
+
+		if (log) { /* Clean only what is in the input bitmap */
+			mask = bitmap[i];
+			if (!mask)
+				continue;
+
+			p = (atomic_long_t *)&dirty_bitmap[i];
+			mask &= atomic_long_fetch_andnot(mask, p);
+		} else { /* Clean everything */
+			if (!dirty_bitmap[i])
+				continue;
+
+			mask = xchg(&dirty_bitmap[i], 0);
+			bitmap[i] = mask;
+		}
+
+		if (!mask)
+			continue;
+
+		offset = base_gfn + i * BITS_PER_LONG;
+
+		if (kvm_dirty_log_manual_protect_and_init_set(kvm) &&
+		    (offset + BITS_PER_LONG > split_end)) {
+			gpa_t start = gfn_to_gpa(offset + __ffs(mask));
+			gpa_t end = gfn_to_gpa(offset + __fls(mask) + 1);
+
+			kvm_mmu_split_huge_pages(kvm, start, end);
+			split_end = gpa_to_gfn(ALIGN_DOWN(end, PMD_SIZE) + PMD_SIZE - 1);
+		}
+
+		do {
+			idx = mask_to_hdbss(&mask, hw_entries, offset, ttwl, idx, entries_sz);
+			if (idx >= entries_sz) {
+				ret = dirty_bit_clear(kvm, hw_entries, idx);
+				*flush = *flush || ret > 0;
+				if (ret != idx) {
+					/* Save bits not converted back to bitmap */
+					atomic_long_or(mask, (atomic_long_t *)&dirty_bitmap[i]);
+					goto out_err;
+				}
+				idx = 0;
+			}
+		} while (mask);
+	}
+
+	if (idx != 0) {
+		ret = dirty_bit_clear(kvm, hw_entries, idx);
+		*flush = *flush || ret > 0;
+	}
+out_err:
+	if (unlikely(ret != idx)) {
+		/*
+		 * In case there is an error and not all entries in HACDBS get
+		 * cleaned, we have to mark the dirty bits back in the bitmap,
+		 * as that will be used by the software routine.
+		 *
+		 * Entries should be in order, since they were extraxed from
+		 * the dirty-bitmap, so batching the atomic writes is efficient.
+		 */
+
+		if (ret < idx)
+			hdbss_to_bitmap(hw_entries, ret, idx, dirty_bitmap, memslot->base_gfn);
+
+		ret = -EAGAIN;
+	}
+
+	write_unlock(&kvm->mmu_lock);
+	kfree(hw_entries);
+
+	return ret;
+}
+
+int __kvm_arch_dirty_ring_clear(struct kvm *kvm, struct kvm_dirty_ring *ring,
+				int *nr_entries_reset)
+{
+	u64 *hw_entries;
+	u64 slot_offset = 0;
+	u64 ttwl;
+	int i, ret;
+	u32 slot = -1;
+
+	if (signal_pending(current))
+		return -EINTR;
+
+	ttwl = HDBSS_ENTRY_TTWL(KVM_PGTABLE_LAST_LEVEL);
+
+	hw_entries = kmalloc(max(ring->size * sizeof(u64), PAGE_SIZE), GFP_KERNEL);
+	if (!hw_entries)
+		return -ENOMEM;
+
+	for (i = 0; i < ring->size; i++) {
+		struct kvm_dirty_gfn *entry;
+		gfn_t gfn;
+
+		entry = &ring->dirty_gfns[(ring->reset_index + i) &
+					  (ring->size - 1)];
+
+		if (!kvm_dirty_gfn_harvested(entry))
+			break;
+
+		if (entry->slot != slot) {
+			struct kvm_memory_slot *memslot;
+
+			memslot = kvm_dirty_ring_get_memslot(kvm, entry->slot);
+			slot = entry->slot;
+			slot_offset = memslot->base_gfn;
+		}
+
+		gfn = slot_offset + entry->offset;
+
+		hw_entries[i] = (gfn_to_gpa(gfn) & HDBSS_ENTRY_IPA) |
+				ttwl | HDBSS_ENTRY_VALID;
+	}
+
+	ret = dirty_bit_clear(kvm, hw_entries, i);
+
+	/* Set as invalid all successfully cleaned entries */
+	for (int j = 0; j < ret; j++) {
+		struct kvm_dirty_gfn *entry;
+
+		entry = &ring->dirty_gfns[(ring->reset_index + j) &
+					  (ring->size - 1)];
+
+		kvm_dirty_gfn_set_invalid(entry);
+	}
+
+	/* In case of error, try software cleaning from the faulting entry */
+	ring->reset_index += ret;
+	*nr_entries_reset += ret;
+
+	kfree(hw_entries);
+
+	if (ret < i)
+		return -EFAULT;
+
+	return ret;
+}
+
+static irqreturn_t hacdbsirq_handler(int irq, void *pcpu)
+{
+	u64 cons = read_sysreg_s(SYS_HACDBSCONS_EL2);
+	unsigned long err = FIELD_GET(HACDBSCONS_EL2_ERR_REASON, cons);
+
+	switch (err) {
+	case HACDBSCONS_EL2_ERR_REASON_NOF:
+		this_cpu_write(hacdbs_pcp.status, HACDBS_IDLE);
+		break;
+	case HACDBSCONS_EL2_ERR_REASON_IPAHACF:
+		/* When size not a power of two >= 4k, exit with reserved TTLW */
+		int index = FIELD_GET(HACDBSCONS_EL2_INDEX, cons);
+
+		if (index >= this_cpu_read(hacdbs_pcp.size)) {
+			this_cpu_write(hacdbs_pcp.status, HACDBS_IDLE);
+			break;
+		}
+		fallthrough;
+	case HACDBSCONS_EL2_ERR_REASON_STRUCTF:
+	case HACDBSCONS_EL2_ERR_REASON_IPAF:
+		this_cpu_write(hacdbs_pcp.status, HACDBS_ERROR);
+		break;
+	}
+
+	return IRQ_HANDLED;
+}
+
+void kvm_hacdbs_cpu_up(void)
+{
+	if (hacdbsirq < 0)
+		return;
+
+	enable_percpu_irq(hacdbsirq, IRQ_TYPE_LEVEL_HIGH);
+	this_cpu_write(hacdbs_pcp.status, HACDBS_IDLE);
+}
+
+void kvm_hacdbs_cpu_down(void)
+{
+	if (hacdbsirq < 0)
+		return;
+
+	disable_percpu_irq(hacdbsirq);
+	this_cpu_write(hacdbs_pcp.status, HACDBS_OFF);
+}
+
+#ifdef CONFIG_ACPI
+static int __init hacdbs_acpi_get_irq(void)
+{
+	struct acpi_madt_generic_interrupt *gicc;
+	u32 gsi;
+	int irq;
+
+	gicc = acpi_cpu_get_madt_gicc(smp_processor_id());
+	if (gicc->header.length < ACPI_MADT_GICC_HACDBSIRQ)
+		return -ENXIO;
+
+	gsi =  gicc->hacdbsirq_gsi;
+
+	irq = acpi_register_gsi(NULL, gsi, ACPI_LEVEL_SENSITIVE, ACPI_ACTIVE_HIGH);
+	if (irq < 0) {
+		pr_warn("ACPI: Unable to register HACDBS interrupt: %d\n", gsi);
+		return -ENXIO;
+	}
+
+	return irq;
+}
+#else
+#define hacdbs_acpi_get_irq() (-ENXIO)
+#endif
+
+void __init kvm_hacdbs_init(void)
+{
+	int irq;
+
+	/* FEAT_HACDBS is only supported if Linux runs in EL2 (VHE) */
+	if (!system_supports_hacdbs() || !is_kernel_in_hyp_mode())
+		return;
+
+	irq = hacdbs_acpi_get_irq();
+	if (irq < 0)
+		return;
+
+	if (request_percpu_irq(irq, hacdbsirq_handler, "HACDBSIRQ", &hacdbs_pcp) < 0)
+		return;
+
+	hacdbsirq = irq;
+}
diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c
index 91a7dfad668660..e16729f0b7bd24 100644
--- a/arch/arm64/kvm/hyp/pgtable.c
+++ b/arch/arm64/kvm/hyp/pgtable.c
@@ -731,9 +731,13 @@ static int stage2_set_prot_attr(struct kvm_pgtable *pgt, enum kvm_pgtable_prot p
 	if (prot & KVM_PGTABLE_PROT_R)
 		attr |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R;
 
-	if (prot & KVM_PGTABLE_PROT_W)
+	if (prot & KVM_PGTABLE_PROT_W) {
 		attr |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W;
 
+		if (pgt->flags & KVM_PGTABLE_S2_DBM)
+			attr |= KVM_PTE_LEAF_ATTR_HI_S2_DBM;
+	}
+
 	if (!kvm_lpa2_is_enabled())
 		attr |= FIELD_PREP(KVM_PTE_LEAF_ATTR_LO_S2_SH, sh);
 
@@ -1367,9 +1371,13 @@ int kvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr,
 	if (prot & KVM_PGTABLE_PROT_R)
 		set |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_R;
 
-	if (prot & KVM_PGTABLE_PROT_W)
+	if (prot & KVM_PGTABLE_PROT_W) {
 		set |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W;
 
+		if (pgt->flags & KVM_PGTABLE_S2_DBM)
+			set |= KVM_PTE_LEAF_ATTR_HI_S2_DBM;
+	}
+
 	ret = stage2_set_xn_attr(prot, &xn);
 	if (ret)
 		return ret;
@@ -1585,6 +1593,9 @@ int __kvm_pgtable_stage2_init(struct kvm_pgtable *pgt, struct kvm_s2_mmu *mmu,
 	if (!pgt->pgd)
 		return -ENOMEM;
 
+	if (system_supports_hdbss())
+		flags |= KVM_PGTABLE_S2_DBM;
+
 	pgt->ia_bits		= ia_bits;
 	pgt->start_level	= start_level;
 	pgt->mm_ops		= mm_ops;
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 6c941aaa10c639..2f9d90c35668b9 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -117,8 +117,8 @@ static bool need_split_memcache_topup_or_resched(struct kvm *kvm)
 	return kvm_mmu_memory_cache_nr_free_objects(cache) < min;
 }
 
-static int kvm_mmu_split_huge_pages(struct kvm *kvm, phys_addr_t addr,
-				    phys_addr_t end)
+int kvm_mmu_split_huge_pages(struct kvm *kvm, phys_addr_t addr,
+			     phys_addr_t end)
 {
 	struct kvm_mmu_memory_cache *cache;
 	struct kvm_pgtable *pgt;
@@ -1027,8 +1027,12 @@ int kvm_init_stage2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu, unsigned long t
 	for_each_possible_cpu(cpu)
 		*per_cpu_ptr(mmu->last_vcpu_ran, cpu) = -1;
 
-	 /* The eager page splitting is disabled by default */
-	mmu->split_page_chunk_size = KVM_ARM_EAGER_SPLIT_CHUNK_SIZE_DEFAULT;
+	 /* The eager page splitting is disabled by default if system has no HDBSS */
+	if (system_supports_hdbss())
+		mmu->split_page_chunk_size = 256 * 1024 * PAGE_SIZE;
+	else
+		mmu->split_page_chunk_size = KVM_ARM_EAGER_SPLIT_CHUNK_SIZE_DEFAULT;
+
 	mmu->split_page_cache.gfp_zero = __GFP_ZERO;
 
 	mmu->pgd_phys = __pa(pgt->pgd);
diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps
index 9b85a84f6fd494..bd2c0bb98da61e 100644
--- a/arch/arm64/tools/cpucaps
+++ b/arch/arm64/tools/cpucaps
@@ -69,8 +69,10 @@ HAS_VA52
 HAS_VIRT_HOST_EXTN
 HAS_WFXT
 HAS_XNX
+HAS_HDBSS
 HAFT
 HW_DBM
+HACDBS
 KVM_HVHE
 KVM_PROTECTED_MODE
 MISMATCHED_CACHE_TYPE
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index bc1788b1662b76..7b7c3d6a0f0309 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -4634,6 +4634,36 @@ Field	7:6	SL0
 Field	5:0	T0SZ
 EndSysreg
 
+Sysreg	HACDBSBR_EL2	3	4	2	3	4
+Res0	63:56
+Field	55:12	BADDR
+Field	11	EN
+Res0	10:4
+UnsignedEnum	3:0	SZ
+	0b0000	4K
+	0b0001	8K
+	0b0010	16K
+	0b0011	32K
+	0b0100	64K
+	0b0101	128K
+	0b0110	256K
+	0b0111	512K
+	0b1000	1M
+	0b1001	2M
+EndEnum
+EndSysreg
+
+Sysreg	HACDBSCONS_EL2	3	4	2	3	5
+UnsignedEnum	63:62	ERR_REASON
+	0b00	NOF
+	0b01	STRUCTF
+	0b10	IPAF
+	0b11	IPAHACF
+EndEnum
+Res0	61:19
+Field	18:0	INDEX
+EndSysreg
+
 Sysreg	GCSCR_EL2	3	4	2	5	0
 Fields	GCSCR_ELx
 EndSysreg
diff --git a/include/acpi/actbl2.h b/include/acpi/actbl2.h
index baef525367b5c4..eaefb494ef5972 100644
--- a/include/acpi/actbl2.h
+++ b/include/acpi/actbl2.h
@@ -1449,6 +1449,7 @@ struct acpi_madt_generic_interrupt {
 	u16 trbe_interrupt;	/* ACPI 6.5 */
 	u16 iaffid;		/* ACPI 6.7 */
 	u32 irs_id;
+	u32 hacdbsirq_gsi;	/* ACPI 6.X */
 };
 
 /* Masks for Flags field above */
diff --git a/include/linux/kvm_dirty_bit.h b/include/linux/kvm_dirty_bit.h
new file mode 100644
index 00000000000000..8492979d694e70
--- /dev/null
+++ b/include/linux/kvm_dirty_bit.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 ARM Ltd.
+ * Author: Leonardo Bras <leo.bras@arm.com>
+ */
+
+#ifndef __KVM_DIRTY_BIT_H__
+#define __KVM_DIRTY_BIT_H__
+
+#ifndef CONFIG_HAVE_KVM_HW_DIRTY_BIT
+
+static inline int kvm_arch_dirty_log_clear(struct kvm *kvm,
+					   struct kvm_memory_slot *memslot,
+					   struct kvm_clear_dirty_log *log,
+					   unsigned long *bitmap,
+					   bool *flush)
+{
+	return -ENXIO;
+}
+
+static inline int kvm_arch_dirty_ring_clear(struct kvm *kvm,
+					    struct kvm_dirty_ring *ring,
+					    int *nr_entries_reset)
+{
+	return -ENXIO;
+}
+
+#else /* CONFIG_HAVE_KVM_HW_DIRTY_BIT */
+
+#include <asm/kvm_dirty_bit.h>
+
+#endif /* CONFIG_HAVE_KVM_HW_DIRTY_BIT */
+
+#endif /* __KVM_DIRTY_BIT_H__ */
diff --git a/include/linux/kvm_dirty_ring.h b/include/linux/kvm_dirty_ring.h
index eb10d87adf7d59..190d97fce4a42e 100644
--- a/include/linux/kvm_dirty_ring.h
+++ b/include/linux/kvm_dirty_ring.h
@@ -84,6 +84,18 @@ void kvm_dirty_ring_push(struct kvm_vcpu *vcpu, u32 slot, u64 offset);
 
 bool kvm_dirty_ring_check_request(struct kvm_vcpu *vcpu);
 
+static inline bool kvm_dirty_gfn_harvested(struct kvm_dirty_gfn *gfn)
+{
+	return smp_load_acquire(&gfn->flags) & KVM_DIRTY_GFN_F_RESET;
+}
+
+static inline void kvm_dirty_gfn_set_invalid(struct kvm_dirty_gfn *gfn)
+{
+	smp_store_release(&gfn->flags, 0);
+}
+
+struct kvm_memory_slot *kvm_dirty_ring_get_memslot(struct kvm *kvm, u32 slot);
+
 /* for use in vm_operations_struct */
 struct page *kvm_dirty_ring_get_page(struct kvm_dirty_ring *ring, u32 offset);
 
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index ab8cfaec82d31e..7ea6ed7ce203aa 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -1669,6 +1669,9 @@ bool kvm_arch_vcpu_preempted_in_kernel(struct kvm_vcpu *vcpu);
 void kvm_arch_pre_destroy_vm(struct kvm *kvm);
 void kvm_arch_create_vm_debugfs(struct kvm *kvm);
 
+int kvm_mmu_split_huge_pages(struct kvm *kvm, phys_addr_t addr,
+			     phys_addr_t end);
+
 #ifndef __KVM_HAVE_ARCH_VM_ALLOC
 /*
  * All architectures that want to use vzalloc currently also
diff --git a/virt/kvm/Kconfig b/virt/kvm/Kconfig
index 794976b88c6f97..f8757b5b84b3e0 100644
--- a/virt/kvm/Kconfig
+++ b/virt/kvm/Kconfig
@@ -20,6 +20,9 @@ config HAVE_KVM_IRQ_ROUTING
 config HAVE_KVM_DIRTY_RING
        bool
 
+config HAVE_KVM_HW_DIRTY_BIT
+       bool
+
 # Only strongly ordered architectures can select this, as it doesn't
 # put any explicit constraint on userspace ordering. They can also
 # select the _ACQ_REL version.
diff --git a/virt/kvm/dirty_ring.c b/virt/kvm/dirty_ring.c
index 572b854edf740d..fe4e7da6cc4a6a 100644
--- a/virt/kvm/dirty_ring.c
+++ b/virt/kvm/dirty_ring.c
@@ -8,6 +8,7 @@
 #include <linux/kvm.h>
 #include <linux/vmalloc.h>
 #include <linux/kvm_dirty_ring.h>
+#include <linux/kvm_dirty_bit.h>
 #include <trace/events/kvm.h>
 #include "kvm_mm.h"
 
@@ -50,18 +51,30 @@ static bool kvm_dirty_ring_full(struct kvm_dirty_ring *ring)
 	return kvm_dirty_ring_used(ring) >= ring->size;
 }
 
-static void kvm_reset_dirty_gfn(struct kvm *kvm, u32 slot, u64 offset, u64 mask)
+static inline struct kvm_memory_slot *
+__kvm_dirty_ring_get_memslot(struct kvm *kvm, u32 slot)
 {
-	struct kvm_memory_slot *memslot;
 	int as_id, id;
 
 	as_id = slot >> 16;
 	id = (u16)slot;
 
 	if (as_id >= kvm_arch_nr_memslot_as_ids(kvm) || id >= KVM_USER_MEM_SLOTS)
-		return;
+		return 0;
+
+	return id_to_memslot(__kvm_memslots(kvm, as_id), id);
+}
 
-	memslot = id_to_memslot(__kvm_memslots(kvm, as_id), id);
+struct kvm_memory_slot *kvm_dirty_ring_get_memslot(struct kvm *kvm, u32 slot)
+{
+	return __kvm_dirty_ring_get_memslot(kvm, slot);
+}
+
+static void kvm_reset_dirty_gfn(struct kvm *kvm, u32 slot, u64 offset, u64 mask)
+{
+	struct kvm_memory_slot *memslot;
+
+	memslot = __kvm_dirty_ring_get_memslot(kvm, slot);
 
 	if (!memslot || offset >= memslot->npages ||
 	    offset + __fls(mask) >= memslot->npages)
@@ -88,21 +101,11 @@ int kvm_dirty_ring_alloc(struct kvm *kvm, struct kvm_dirty_ring *ring,
 	return 0;
 }
 
-static inline void kvm_dirty_gfn_set_invalid(struct kvm_dirty_gfn *gfn)
-{
-	smp_store_release(&gfn->flags, 0);
-}
-
 static inline void kvm_dirty_gfn_set_dirtied(struct kvm_dirty_gfn *gfn)
 {
 	gfn->flags = KVM_DIRTY_GFN_F_DIRTY;
 }
 
-static inline bool kvm_dirty_gfn_harvested(struct kvm_dirty_gfn *gfn)
-{
-	return smp_load_acquire(&gfn->flags) & KVM_DIRTY_GFN_F_RESET;
-}
-
 int kvm_dirty_ring_reset(struct kvm *kvm, struct kvm_dirty_ring *ring,
 			 int *nr_entries_reset)
 {
@@ -131,6 +134,9 @@ int kvm_dirty_ring_reset(struct kvm *kvm, struct kvm_dirty_ring *ring,
 	 */
 	lockdep_assert_held(&kvm->slots_lock);
 
+	if (kvm_arch_dirty_ring_clear(kvm, ring, nr_entries_reset) >= 0)
+		return 0;
+
 	while (likely((*nr_entries_reset) < INT_MAX)) {
 		if (signal_pending(current))
 			return -EINTR;
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index e44c20c049610d..a25b8902cdfc17 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -65,6 +65,7 @@
 #include <trace/events/kvm.h>
 
 #include <linux/kvm_dirty_ring.h>
+#include <linux/kvm_dirty_bit.h>
 
 
 /* Worst case buffer size needed for holding an integer. */
@@ -2262,6 +2263,10 @@ static int kvm_get_dirty_log_protect(struct kvm *kvm, struct kvm_dirty_log *log)
 		dirty_bitmap_buffer = kvm_second_dirty_bitmap(memslot);
 		memset(dirty_bitmap_buffer, 0, n);
 
+		if (kvm_arch_dirty_log_clear(kvm, memslot, NULL,
+					     dirty_bitmap_buffer, &flush) >= 0)
+			goto out;
+
 		KVM_MMU_LOCK(kvm);
 		for (i = 0; i < n / sizeof(long); i++) {
 			unsigned long mask;
@@ -2281,6 +2286,7 @@ static int kvm_get_dirty_log_protect(struct kvm *kvm, struct kvm_dirty_log *log)
 		KVM_MMU_UNLOCK(kvm);
 	}
 
+out:
 	if (flush)
 		kvm_flush_remote_tlbs_memslot(kvm, memslot);
 
@@ -2373,6 +2379,10 @@ static int kvm_clear_dirty_log_protect(struct kvm *kvm,
 	if (copy_from_user(dirty_bitmap_buffer, log->dirty_bitmap, n))
 		return -EFAULT;
 
+	if (kvm_arch_dirty_log_clear(kvm, memslot, log, dirty_bitmap_buffer,
+				     &flush) >= 0)
+		goto out;
+
 	KVM_MMU_LOCK(kvm);
 	for (offset = log->first_page, i = offset / BITS_PER_LONG,
 		 n = DIV_ROUND_UP(log->num_pages, BITS_PER_LONG); n--;
@@ -2392,12 +2402,13 @@ static int kvm_clear_dirty_log_protect(struct kvm *kvm,
 		*/
 		if (mask) {
 			flush = true;
+
 			kvm_arch_mmu_enable_log_dirty_pt_masked(kvm, memslot,
 								offset, mask);
 		}
 	}
 	KVM_MMU_UNLOCK(kvm);
-
+out:
 	if (flush)
 		kvm_flush_remote_tlbs_memslot(kvm, memslot);
 

3/2 2026/06/29 11:40 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15376
							output: 26
							thoughts: 747
						

Thoughts:
**My Analysis of the ARM64 KVM Hardware Dirty Bit Cleaning Patch**

Alright, let's break this down. We have a new patch introducing Hardware Assisted Clean Dirty Bit Support (HACDBS) for ARM64 KVM. It's clearly adding the infrastructure for hardware-assisted dirty bit tracking (HDBSS) and cleaning (HACDBS), and it looks functional. The first order of business is definitely fuzzing to ensure robustness.

The patch itself is introducing a handful of new files, `arch/arm64/kvm/dirty_bit.c`, `arch/arm64/include/asm/kvm_dirty_bit.h`, and `include/linux/kvm_dirty_bit.h`, which are all standard for feature additions. Then we have modifications in existing files that include `arch/arm64/kvm/arm.c`, `arch/arm64/kvm/hyp/pgtable.c`, `arch/arm64/kvm/mmu.c`, `virt/kvm/dirty_ring.c`, and `virt/kvm/kvm_main.c`. Makes sense, they need to hook into the existing kvm architecture.

Now, let's drill down into the key functions. I see a core set of functions in `arch/arm64/kvm/dirty_bit.c` that are the heart of this: `hacdbs_start`, `hacdbs_stop`, `dirty_bit_clear`, `hdbss_to_bitmap`, `mask_to_hdbss`, `__kvm_arch_dirty_log_clear`, `__kvm_arch_dirty_ring_clear`, `hacdbsirq_handler`, `kvm_hacdbs_cpu_up`, `kvm_hacdbs_cpu_down`, `hacdbs_acpi_get_irq`, and `kvm_hacdbs_init`. This looks like the main entry points, the setup, and the control functions.

The other modified files look like support functions:

*   `virt/kvm/dirty_ring.c`:  `__kvm_dirty_ring_get_memslot` and `kvm_dirty_ring_get_memslot`.  These handle memslot management within the dirty ring.
*   `arch/arm64/kvm/hyp/pgtable.c`: `stage2_set_prot_attr`, `kvm_pgtable_stage2_relax_perms`, and `__kvm_pgtable_stage2_init`. This is dealing with the page table operations and the new permissions.
*   `arch/arm64/kvm/mmu.c`: `kvm_mmu_split_huge_pages` and `kvm_init_stage2_mmu`. This seems related to the MMU and potentially the handling of large pages and stage2 MMU initialization with the new HDBSS/HACDBS functionality.
*   `virt/kvm/kvm_main.c`: `kvm_get_dirty_log_protect` and `kvm_clear_dirty_log_protect`. These functions seem to control the access or permissions for the dirty log.

Now, let's look at configuration. The dependency on `ACPI` being enabled, through `select HAVE_KVM_HW_DIRTY_BIT if ACPI` is important. Also, the `hacdbs_acpi_get_irq` function is conditionally compiled using `#ifdef CONFIG_ACPI`. So if ACPI isn't enabled, this new dirty bit feature won't work. We need ACPI support enabled, and KVM of course. Time to look around for how HAVE_KVM_HW_DIRTY_BIT is used elsewhere, too.



4/2 2026/06/29 11:41 tool grepper
0m Args:
{
  "Expression": "HAVE_KVM_HW_DIRTY_BIT"
}

Results:
{
  "Output": "arch/arm64/kvm/Kconfig=20=menuconfig KVM\n--\narch/arm64/kvm/Kconfig-36-\tselect HAVE_KVM_VCPU_RUN_PID_CHANGE\narch/arm64/kvm/Kconfig:37:\tselect HAVE_KVM_HW_DIRTY_BIT if ACPI\narch/arm64/kvm/Kconfig-38-\tselect SCHED_INFO\n--\ninclude/linux/kvm_dirty_bit.h-9-\ninclude/linux/kvm_dirty_bit.h:10:#ifndef CONFIG_HAVE_KVM_HW_DIRTY_BIT\ninclude/linux/kvm_dirty_bit.h-11-\n--\ninclude/linux/kvm_dirty_bit.h=21=static inline int kvm_arch_dirty_ring_clear(struct kvm *kvm,\n--\ninclude/linux/kvm_dirty_bit.h-27-\ninclude/linux/kvm_dirty_bit.h:28:#else /* CONFIG_HAVE_KVM_HW_DIRTY_BIT */\ninclude/linux/kvm_dirty_bit.h-29-\n--\ninclude/linux/kvm_dirty_bit.h-31-\ninclude/linux/kvm_dirty_bit.h:32:#endif /* CONFIG_HAVE_KVM_HW_DIRTY_BIT */\ninclude/linux/kvm_dirty_bit.h-33-\n--\nvirt/kvm/Kconfig=20=config HAVE_KVM_DIRTY_RING\n--\nvirt/kvm/Kconfig-22-\nvirt/kvm/Kconfig:23:config HAVE_KVM_HW_DIRTY_BIT\nvirt/kvm/Kconfig-24-       bool\n"
}

5/2 2026/06/29 11:41 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17430
							output: 61
							thoughts: 450
						

Thoughts:
**Analyzing KVM Hardware Dirty Bit Configuration**

Okay, so I'm looking at this `HAVE_KVM_HW_DIRTY_BIT` configuration option. It's a hidden bool, selected by the `KVM` module, and it's conditional on `ACPI` being enabled. Right away, I see we need to make sure both `KVM` and `ACPI` are included in the `EnableConfigs` list. No brainer.

Now, let's zero in on the key functions involved here to see what we're working with. I'm focusing on:

*   `__kvm_arch_dirty_log_clear`
*   `__kvm_arch_dirty_ring_clear`
*   `dirty_bit_clear`
*   `hacdbs_start`
*   `hacdbs_stop`
*   `hacdbsirq_handler`
*   `kvm_hacdbs_init`
*   `kvm_hacdbs_cpu_up`
*   `kvm_hacdbs_cpu_down`
*   `hdbss_to_bitmap`
*   `mask_to_hdbss`
*   `__kvm_dirty_ring_get_memslot`
*   `kvm_dirty_ring_get_memslot`

Alright, let's quickly classify these for scope. Confirmed, `dirty_bit_clear` is static. Looks like `hacdbs_start` and `hacdbs_stop` are static too. `hdbss_to_bitmap` and `mask_to_hdbss` are static inline, good. `hacdbsirq_handler` is static as well. On the other hand, the global or exported ones are: `__kvm_arch_dirty_log_clear`, `__kvm_arch_dirty_ring_clear`, `kvm_hacdbs_init`, `kvm_hacdbs_cpu_up`, `kvm_hacdbs_cpu_down`, and `kvm_dirty_ring_get_memslot`. `__kvm_dirty_ring_get_memslot` is static inline.

Finally, I should take a peek at `arch/arm64/kvm/dirty_bit.c` just to make sure I haven't missed anything else relevant there. Gotta be thorough.



6/2 2026/06/29 11:41 tool grepper
0m Args:
{
  "Expression": "^([a-z_A-Z0-9]+ )+[a-z_A-Z0-9]+\\(",
  "PathPrefix": "arch/arm64/kvm/dirty_bit.c"
}

Results:
{
  "Output": "arch/arm64/kvm/dirty_bit.c=26=static __ro_after_init int hacdbsirq = -1;\narch/arm64/kvm/dirty_bit.c-27-\narch/arm64/kvm/dirty_bit.c:28:static void hacdbs_start(u64 *hw_entries, int size)\narch/arm64/kvm/dirty_bit.c-29-{\n--\narch/arm64/kvm/dirty_bit.c-51-\narch/arm64/kvm/dirty_bit.c:52:static int hacdbs_stop(void)\narch/arm64/kvm/dirty_bit.c-53-{\n--\narch/arm64/kvm/dirty_bit.c-74- */\narch/arm64/kvm/dirty_bit.c:75:static int dirty_bit_clear(struct kvm *kvm, u64 *hw_entries, int size)\narch/arm64/kvm/dirty_bit.c-76-{\n--\narch/arm64/kvm/dirty_bit.c-107-\narch/arm64/kvm/dirty_bit.c:108:static inline void hdbss_to_bitmap(u64 *hdbss_array, int start, int end,\narch/arm64/kvm/dirty_bit.c-109-\t\t\t\t   unsigned long *dirty_bitmap,\n--\narch/arm64/kvm/dirty_bit.c-129-\narch/arm64/kvm/dirty_bit.c:130:static inline int mask_to_hdbss(unsigned long *mask, u64 *hw_entries, const gfn_t offset,\narch/arm64/kvm/dirty_bit.c-131-\t\t\t\tu64 ttwl, int idx, int entries_sz)\n--\narch/arm64/kvm/dirty_bit.c-148-\narch/arm64/kvm/dirty_bit.c:149:int __kvm_arch_dirty_log_clear(struct kvm *kvm,\narch/arm64/kvm/dirty_bit.c-150-\t\t\t       struct kvm_memory_slot *memslot,\n--\narch/arm64/kvm/dirty_bit.c-258-\narch/arm64/kvm/dirty_bit.c:259:int __kvm_arch_dirty_ring_clear(struct kvm *kvm, struct kvm_dirty_ring *ring,\narch/arm64/kvm/dirty_bit.c-260-\t\t\t\tint *nr_entries_reset)\n--\narch/arm64/kvm/dirty_bit.c-324-\narch/arm64/kvm/dirty_bit.c:325:static irqreturn_t hacdbsirq_handler(int irq, void *pcpu)\narch/arm64/kvm/dirty_bit.c-326-{\n--\narch/arm64/kvm/dirty_bit.c-351-\narch/arm64/kvm/dirty_bit.c:352:void kvm_hacdbs_cpu_up(void)\narch/arm64/kvm/dirty_bit.c-353-{\n--\narch/arm64/kvm/dirty_bit.c-360-\narch/arm64/kvm/dirty_bit.c:361:void kvm_hacdbs_cpu_down(void)\narch/arm64/kvm/dirty_bit.c-362-{\n--\narch/arm64/kvm/dirty_bit.c-370-#ifdef CONFIG_ACPI\narch/arm64/kvm/dirty_bit.c:371:static int __init hacdbs_acpi_get_irq(void)\narch/arm64/kvm/dirty_bit.c-372-{\n--\narch/arm64/kvm/dirty_bit.c-394-\narch/arm64/kvm/dirty_bit.c:395:void __init kvm_hacdbs_init(void)\narch/arm64/kvm/dirty_bit.c-396-{\n"
}

7/2 2026/06/29 11:41 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19561
							output: 42
							thoughts: 200
						

Thoughts:
**Focusing on Key KVM Dirty Log Functions**

Okay, so I'm trying to wrap my head around the KVM dirty log implementation and figure out the critical functions. Right off the bat, I see a handful of functions that look important: `__kvm_arch_dirty_log_clear`, `__kvm_arch_dirty_ring_clear`, `dirty_bit_clear`, those seem obvious for clearing and managing the dirty log. Then there's `hacdbs_start`, `hacdbs_stop`, and `hacdbsirq_handler`, which seem related to the hardware-assisted dirty bit tracking, which is definitely something I need to understand better. And of course, the conversions between dirty bitmaps and hardware dirty bit structures: `hdbss_to_bitmap`, `mask_to_hdbss`. Those are essential. Finally, I see the functions `kvm_dirty_ring_get_memslot` and `__kvm_dirty_ring_get_memslot`. Wait a second… isn't `__kvm_dirty_ring_get_memslot` declared as static inline? That might make it a little less useful as a symbol to track directly. I'll need to double check how the inlining is handled by the compiler. But, `kvm_dirty_ring_get_memslot` is global, so I think that is still safe. Let's see... I'm going to quickly check `virt/kvm/dirty_ring.c` to confirm where `kvm_dirty_ring_get_memslot` is defined and how it is used. That will help me understand its role better.



8/2 2026/06/29 11:41 tool grepper
0m Args:
{
  "Expression": "kvm_dirty_ring_get_memslot",
  "PathPrefix": "virt/kvm/dirty_ring.c"
}

Results:
{
  "Output": "virt/kvm/dirty_ring.c=54=static inline struct kvm_memory_slot *\nvirt/kvm/dirty_ring.c:55:__kvm_dirty_ring_get_memslot(struct kvm *kvm, u32 slot)\nvirt/kvm/dirty_ring.c-56-{\n--\nvirt/kvm/dirty_ring.c-67-\nvirt/kvm/dirty_ring.c:68:struct kvm_memory_slot *kvm_dirty_ring_get_memslot(struct kvm *kvm, u32 slot)\nvirt/kvm/dirty_ring.c-69-{\nvirt/kvm/dirty_ring.c:70:\treturn __kvm_dirty_ring_get_memslot(kvm, slot);\nvirt/kvm/dirty_ring.c-71-}\n--\nvirt/kvm/dirty_ring.c=73=static void kvm_reset_dirty_gfn(struct kvm *kvm, u32 slot, u64 offset, u64 mask)\n--\nvirt/kvm/dirty_ring.c-76-\nvirt/kvm/dirty_ring.c:77:\tmemslot = __kvm_dirty_ring_get_memslot(kvm, slot);\nvirt/kvm/dirty_ring.c-78-\n"
}

9/2 2026/06/29 11:41 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20513
							output: 56
							thoughts: 47
						

Thoughts:
Okay, let's dive into this. Here's how I'd approach this task, as if these were my internal thoughts:

**Analyzing ARM64 KVM Stage 2 Page Table Protection**

Right, let's start by zeroing in on the core of how stage 2 page table permissions are managed within the ARM64 KVM implementation. My initial focus has to be on `arch/arm64/kvm/hyp/pgtable.c`. That's where I expect to find the crucial functions for setting and relaxing protection attributes.

Specifically, I need to understand `stage2_set_prot_attr`. I'm keen to see how the permissions for the guest's virtual addresses are translated and enforced at the hypervisor level. I'll need to look at how this function manipulates the page table entries, what flags are being set (e.g., read, write, execute permissions, and possibly cache attributes). Understanding the logic here is vital for security and performance.

Following that, I'll examine `kvm_pgtable_stage2_relax_perms`. The name alone tells me this function likely has something to do with weakening permissions, perhaps to enable specific functionality within the guest or to avoid issues when translating or manipulating data. I'm especially interested in the scenarios where the relaxation occurs. Are there specific memory regions or under particular conditions where this happens? Are there any security considerations or trade-offs? My experience tells me that relaxing protections introduces potential vulnerability.

My next step will be to explore these functions in the context of the larger code base, but this is where I'll get my initial insight. This will be an important place to start understanding how guest virtual memory is managed and secured within the ARM64 KVM hypervisor.



10/2 2026/06/29 11:41 tool grepper
0m Args:
{
  "Expression": "stage2_set_prot_attr|kvm_pgtable_stage2_relax_perms",
  "PathPrefix": "arch/arm64/kvm/hyp/pgtable.c"
}

Results:
{
  "Output": "arch/arm64/kvm/hyp/pgtable.c=677=static int stage2_set_xn_attr(enum kvm_pgtable_prot prot, kvm_pte_t *attr)\n--\narch/arm64/kvm/hyp/pgtable.c-701-\narch/arm64/kvm/hyp/pgtable.c:702:static int stage2_set_prot_attr(struct kvm_pgtable *pgt, enum kvm_pgtable_prot prot,\narch/arm64/kvm/hyp/pgtable.c-703-\t\t\t\tkvm_pte_t *ptep)\n--\narch/arm64/kvm/hyp/pgtable.c=1098=int kvm_pgtable_stage2_map(struct kvm_pgtable *pgt, u64 addr, u64 size,\n--\narch/arm64/kvm/hyp/pgtable.c-1119-\narch/arm64/kvm/hyp/pgtable.c:1120:\tret = stage2_set_prot_attr(pgt, prot, \u0026map_data.attr);\narch/arm64/kvm/hyp/pgtable.c-1121-\tif (ret)\n--\narch/arm64/kvm/hyp/pgtable.c=1345=bool kvm_pgtable_stage2_test_clear_young(struct kvm_pgtable *pgt, u64 addr,\n--\narch/arm64/kvm/hyp/pgtable.c-1360-\narch/arm64/kvm/hyp/pgtable.c:1361:int kvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr,\narch/arm64/kvm/hyp/pgtable.c-1362-\t\t\t\t   enum kvm_pgtable_prot prot, enum kvm_pgtable_walk_flags flags)\n--\narch/arm64/kvm/hyp/pgtable.c=1423=kvm_pte_t *kvm_pgtable_stage2_create_unlinked(struct kvm_pgtable *pgt,\n--\narch/arm64/kvm/hyp/pgtable.c-1457-\narch/arm64/kvm/hyp/pgtable.c:1458:\tret = stage2_set_prot_attr(pgt, prot, \u0026map_data.attr);\narch/arm64/kvm/hyp/pgtable.c-1459-\tif (ret)\n"
}

11/2 2026/06/29 11:41 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21640
							output: 44
							thoughts: 53
						

Thoughts:
Here's my take on the thought process:

**KVM Permission Relaxation: A Logical Starting Point**

Okay, so I'm thinking about how to approach this... `kvm_pgtable_stage2_relax_perms` feels like a strong contender as the starting point. It's in the realm of permissions, and we're looking to relax something, right? Makes sense to begin with a function name that hints at that.

Now, where was that huge page split code again? Ah, right! We should check `arch/arm64/kvm/mmu.c`, in particular for the function `kvm_mmu_split_huge_pages`. That's where all the interesting stuff concerning huge pages is. This is probably a good place to look for any logic related to the potential changes. Time to dive in.



12/2 2026/06/29 11:41 tool grepper
0m Args:
{
  "Expression": "kvm_mmu_split_huge_pages",
  "PathPrefix": "arch/arm64/kvm/mmu.c"
}

Results:
{
  "Output": "arch/arm64/kvm/mmu.c=106=static bool need_split_memcache_topup_or_resched(struct kvm *kvm)\n--\narch/arm64/kvm/mmu.c-119-\narch/arm64/kvm/mmu.c:120:int kvm_mmu_split_huge_pages(struct kvm *kvm, phys_addr_t addr,\narch/arm64/kvm/mmu.c-121-\t\t\t     phys_addr_t end)\n--\narch/arm64/kvm/mmu.c=1299=static void kvm_mmu_split_memory_region(struct kvm *kvm, int slot)\n--\narch/arm64/kvm/mmu.c-1313-\twrite_lock(\u0026kvm-\u003emmu_lock);\narch/arm64/kvm/mmu.c:1314:\tkvm_mmu_split_huge_pages(kvm, start, end);\narch/arm64/kvm/mmu.c-1315-\twrite_unlock(\u0026kvm-\u003emmu_lock);\n--\narch/arm64/kvm/mmu.c=1329=void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,\n--\narch/arm64/kvm/mmu.c-1349-\tif (kvm_dirty_log_manual_protect_and_init_set(kvm))\narch/arm64/kvm/mmu.c:1350:\t\tkvm_mmu_split_huge_pages(kvm, start, end);\narch/arm64/kvm/mmu.c-1351-\n"
}

13/2 2026/06/29 11:41 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22375
							output: 52
							thoughts: 62
						

Thoughts:
Okay, here's my interpretation of those thoughts, tailored for an expert audience, as if they were my own:

**Initial Assessment of a Potential Strategy**

Right, thinking about this... `kvm_mmu_split_huge_pages` is looking like a solid lead. My initial intuition is leaning towards it. I'm starting to visualize the implications and possibilities within that context.

Now, where do I need to go to start digging in? Let me take a look at `virt/kvm/kvm_main.c`. Specifically, I need to understand how the dirty log protection mechanisms interact with what I'm looking at. I'll focus on `kvm_get_dirty_log_protect` and `kvm_clear_dirty_log_protect` to get a handle on what's going on with the dirty page tracking. I'll need to see how splitting huge pages impacts those functions, and vice-versa. Understanding the locking and the general flow is critical here.



14/2 2026/06/29 11:41 tool grepper
0m Args:
{
  "Expression": "kvm_get_dirty_log_protect|kvm_clear_dirty_log_protect",
  "PathPrefix": "virt/kvm/kvm_main.c"
}

Results:
{
  "Output": "virt/kvm/kvm_main.c=2198=EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_get_dirty_log);\n--\nvirt/kvm/kvm_main.c-2201-/**\nvirt/kvm/kvm_main.c:2202: * kvm_get_dirty_log_protect - get a snapshot of dirty pages\nvirt/kvm/kvm_main.c-2203- *\tand reenable dirty page tracking for the corresponding pages.\n--\nvirt/kvm/kvm_main.c-2221- */\nvirt/kvm/kvm_main.c:2222:static int kvm_get_dirty_log_protect(struct kvm *kvm, struct kvm_dirty_log *log)\nvirt/kvm/kvm_main.c-2223-{\n--\nvirt/kvm/kvm_main.c-2257-\t\t * kvm_get_dirty_log, but hopefully all architecture\nvirt/kvm/kvm_main.c:2258:\t\t * transition to kvm_get_dirty_log_protect and kvm_get_dirty_log\nvirt/kvm/kvm_main.c-2259-\t\t * can be eliminated.\n--\nvirt/kvm/kvm_main.c-2304- * Steps 1-4 below provide general overview of dirty page logging. See\nvirt/kvm/kvm_main.c:2305: * kvm_get_dirty_log_protect() function description for additional details.\nvirt/kvm/kvm_main.c-2306- *\nvirt/kvm/kvm_main.c:2307: * We call kvm_get_dirty_log_protect() to handle steps 1-3, upon return we\nvirt/kvm/kvm_main.c-2308- * always flush the TLB (step 4) even if previous step failed  and the dirty\n--\nvirt/kvm/kvm_main.c=2318=static int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,\n--\nvirt/kvm/kvm_main.c-2324-\nvirt/kvm/kvm_main.c:2325:\tr = kvm_get_dirty_log_protect(kvm, log);\nvirt/kvm/kvm_main.c-2326-\n--\nvirt/kvm/kvm_main.c-2331-/**\nvirt/kvm/kvm_main.c:2332: * kvm_clear_dirty_log_protect - clear dirty bits in the bitmap\nvirt/kvm/kvm_main.c-2333- *\tand reenable dirty page tracking for the corresponding pages.\n--\nvirt/kvm/kvm_main.c-2336- */\nvirt/kvm/kvm_main.c:2337:static int kvm_clear_dirty_log_protect(struct kvm *kvm,\nvirt/kvm/kvm_main.c-2338-\t\t\t\t       struct kvm_clear_dirty_log *log)\n--\nvirt/kvm/kvm_main.c=2418=static int kvm_vm_ioctl_clear_dirty_log(struct kvm *kvm,\n--\nvirt/kvm/kvm_main.c-2424-\nvirt/kvm/kvm_main.c:2425:\tr = kvm_clear_dirty_log_protect(kvm, log);\nvirt/kvm/kvm_main.c-2426-\n"
}

15/2 2026/06/29 11:41 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 23612
							output: 38
							thoughts: 112
						

Thoughts:
**Analyzing KVM Dirty Log and Ring Reset Mechanisms**

Okay, so I'm looking at this code and trying to get a handle on the dirty log and ring reset implementations within KVM. Right off the bat, I see `kvm_get_dirty_log_protect` and `kvm_clear_dirty_log_protect` are declared as static, which is typical for helper functions. Let me think, so these are helper functions. Ah, and here are the relevant ioctl handlers – `kvm_vm_ioctl_get_dirty_log` and `kvm_vm_ioctl_clear_dirty_log`. They likely wrap the core functionality. I also see that `kvm_dirty_ring_reset` is an ioctl handler as well. Right, I need to see how `kvm_dirty_ring_reset` functions. It looks like `kvm_dirty_ring_reset` is defined in `virt/kvm/dirty_ring.c`, so I should start there to understand the reset procedure. Let's dig in and figure out how it works.



16/2 2026/06/29 11:41 tool grepper
0m Args:
{
  "Expression": "kvm_dirty_ring_reset",
  "PathPrefix": "virt/kvm/dirty_ring.c"
}

Results:
{
  "Output": "virt/kvm/dirty_ring.c=104=static inline void kvm_dirty_gfn_set_dirtied(struct kvm_dirty_gfn *gfn)\n--\nvirt/kvm/dirty_ring.c-108-\nvirt/kvm/dirty_ring.c:109:int kvm_dirty_ring_reset(struct kvm *kvm, struct kvm_dirty_ring *ring,\nvirt/kvm/dirty_ring.c-110-\t\t\t int *nr_entries_reset)\n--\nvirt/kvm/dirty_ring.c-219-\nvirt/kvm/dirty_ring.c:220:\ttrace_kvm_dirty_ring_reset(ring);\nvirt/kvm/dirty_ring.c-221-\n"
}

17/2 2026/06/29 11:41 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 24202
							output: 39
							thoughts: 574
						

Thoughts:
**Analyzing the Dirty Log and Related KVM Functions**

Okay, so I'm digging into this code, specifically around the dirty log mechanism within KVM. My initial focus is on the functions that are directly involved: `__kvm_arch_dirty_log_clear`, `__kvm_arch_dirty_ring_clear`, `kvm_mmu_split_huge_pages`, `kvm_dirty_ring_reset`, `kvm_vm_ioctl_get_dirty_log`, and `kvm_vm_ioctl_clear_dirty_log`, and `kvm_pgtable_stage2_relax_perms`. The key here is to understand how the dirty log is managed and cleared.

I had to double-check `kvm_vm_ioctl_get_dirty_log` and `kvm_vm_ioctl_clear_dirty_log`, since those IOCTLs can be quite tricky. Ah, right, the actual modifications are hitting the *protect* versions: `kvm_get_dirty_log_protect` and `kvm_clear_dirty_log_protect`. No problem; even though those are static, they are part of the critical path, so I need to include them in my analysis.

Let me refresh my memory of `arch/arm64/kvm/dirty_bit.c`. I recall that `dirty_bit_clear` is static, but it's *the* core clearing logic. Also, I need to keep in mind the related functions `hacdbs_start`, `hacdbs_stop`, and `hacdbsirq_handler` as they are integral to the hardware-assisted dirty bit tracking functionality.

Now, let's nail down a more refined list. Considering the core operations, I'll definitely include: `__kvm_arch_dirty_log_clear`, `__kvm_arch_dirty_ring_clear`, `dirty_bit_clear`, `hacdbsirq_handler`, `kvm_mmu_split_huge_pages`, `kvm_get_dirty_log_protect`, `kvm_clear_dirty_log_protect`, and `kvm_dirty_ring_reset`.

I can see that my initial list can be trimmed a bit. Focusing on the central logic, I will refine it to: `__kvm_arch_dirty_log_clear`, `__kvm_arch_dirty_ring_clear`, `dirty_bit_clear`, `hacdbsirq_handler`, and `kvm_mmu_split_huge_pages`.

I need to confirm the use of static functions. It's perfectly fine to include them because they are essential to how the feature is implemented. Also, `dirty_bit_clear` is unique so it is unlikely to have any name collision. Good. Back to `arch/arm64/kvm/dirty_bit.c` to see how it works.



18/2 2026/06/29 11:41 tool grepper
0m Args:
{
  "Expression": "dirty_bit_clear",
  "PathPrefix": "arch/arm64/kvm/dirty_bit.c"
}

Results:
{
  "Output": "arch/arm64/kvm/dirty_bit.c=52=static int hacdbs_stop(void)\n--\narch/arm64/kvm/dirty_bit.c-74- */\narch/arm64/kvm/dirty_bit.c:75:static int dirty_bit_clear(struct kvm *kvm, u64 *hw_entries, int size)\narch/arm64/kvm/dirty_bit.c-76-{\n--\narch/arm64/kvm/dirty_bit.c=149=int __kvm_arch_dirty_log_clear(struct kvm *kvm,\n--\narch/arm64/kvm/dirty_bit.c-219-\t\t\tif (idx \u003e= entries_sz) {\narch/arm64/kvm/dirty_bit.c:220:\t\t\t\tret = dirty_bit_clear(kvm, hw_entries, idx);\narch/arm64/kvm/dirty_bit.c-221-\t\t\t\t*flush = *flush || ret \u003e 0;\n--\narch/arm64/kvm/dirty_bit.c-232-\tif (idx != 0) {\narch/arm64/kvm/dirty_bit.c:233:\t\tret = dirty_bit_clear(kvm, hw_entries, idx);\narch/arm64/kvm/dirty_bit.c-234-\t\t*flush = *flush || ret \u003e 0;\n--\narch/arm64/kvm/dirty_bit.c=259=int __kvm_arch_dirty_ring_clear(struct kvm *kvm, struct kvm_dirty_ring *ring,\n--\narch/arm64/kvm/dirty_bit.c-300-\narch/arm64/kvm/dirty_bit.c:301:\tret = dirty_bit_clear(kvm, hw_entries, i);\narch/arm64/kvm/dirty_bit.c-302-\n"
}

19/2 2026/06/29 11:41 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25955
							output: 43
							thoughts: 122
						

Thoughts:
**Kernel Configuration Deep Dive**

Okay, so I'm looking at these kernel configurations and the dependencies are pretty straightforward. It's clear that to get this functionality, I absolutely need `KVM` and `ACPI` enabled. I see that the `HAVE_KVM_HW_DIRTY_BIT` is automatically selected *by* `KVM` if `ACPI` is also configured. That makes sense, so those two are the core requirements.

Now, I always like to make sure I haven't missed anything obvious. I need to make sure I understand any other implicit dependencies. Ah, right, of course, `CONFIG_ARM64` is definitely going to be implied in this configuration. And, I see that `CONFIG_HAVE_KVM_HW_DIRTY_BIT` itself is a hidden config, so it's not something I'll be directly setting. I should make a note of that for when I set up the build.

Okay, just to be absolutely certain, let me double-check that `arch/arm64/kvm/Kconfig` file again, make sure I didn't miss something. Let's make sure I didn't skip over anything.



20/2 2026/06/29 11:41 tool grepper
0m Args:
{
  "Expression": "HAVE_KVM_HW_DIRTY_BIT",
  "PathPrefix": "arch/arm64/kvm/Kconfig"
}

Results:
{
  "Output": "arch/arm64/kvm/Kconfig=20=menuconfig KVM\n--\narch/arm64/kvm/Kconfig-36-\tselect HAVE_KVM_VCPU_RUN_PID_CHANGE\narch/arm64/kvm/Kconfig:37:\tselect HAVE_KVM_HW_DIRTY_BIT if ACPI\narch/arm64/kvm/Kconfig-38-\tselect SCHED_INFO\n"
}

21/2 2026/06/29 11:41 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 26499
							output: 197
							thoughts: 218
						

Thoughts:
**My Assessment of the KVM/ACPI Patch for ARM64**

Alright, let's break this down. My initial assessment points to `KVM` and `ACPI` as the key players here. This is a functional area ripe for fuzzing.

My primary takeaway is that this patch is introducing hardware dirty bit tracking and cleaning (HDBSS and HACDBS, respectively) specifically for ARM64 within the KVM framework. This means we're looking at modifications to core KVM memory management and dirty logging logic. Based on the presence of these new interrupt handlers and ioctl paths, it means there are new points of interaction and potential vulnerabilities.

The core of the fuzzing target seems to be centered around several functions: `__kvm_arch_dirty_log_clear`, `__kvm_arch_dirty_ring_clear`, `dirty_bit_clear`, `hacdbsirq_handler`, `kvm_mmu_split_huge_pages`, `kvm_get_dirty_log_protect`, `kvm_clear_dirty_log_protect` and `kvm_dirty_ring_reset`. They are directly involved in handling the low-level dirty bit management. They should be prioritized during fuzzing.

Therefore, this patch is marked as "WorthFuzzing" because the modifications introduce new, reachable code paths that interact closely with hardware. The `KVM` and `ACPI` configs are activated, and the enumerated functions will form the core of my fuzzing campaigns.



22/2 2026/06/29 11:42 tool set-results
0m Args:
{
  "EnableConfigs": [
    "KVM",
    "ACPI"
  ],
  "FocusSymbols": [
    "__kvm_arch_dirty_log_clear",
    "__kvm_arch_dirty_ring_clear",
    "dirty_bit_clear",
    "hacdbsirq_handler",
    "kvm_mmu_split_huge_pages",
    "kvm_get_dirty_log_protect",
    "kvm_clear_dirty_log_protect",
    "kvm_dirty_ring_reset"
  ],
  "Reasoning": "The patch introduces hardware dirty bit tracking (HDBSS) and cleaning (HACDBS) for ARM64 KVM. It modifies core KVM memory management and dirty logging logic, adding new ioctl paths and interrupt handlers. This is functional and reachable code that should be fuzzed.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "KVM",
    "ACPI"
  ],
  "FocusSymbols": [
    "__kvm_arch_dirty_log_clear",
    "__kvm_arch_dirty_ring_clear",
    "dirty_bit_clear",
    "hacdbsirq_handler",
    "kvm_mmu_split_huge_pages",
    "kvm_get_dirty_log_protect",
    "kvm_clear_dirty_log_protect",
    "kvm_dirty_ring_reset"
  ],
  "Reasoning": "The patch introduces hardware dirty bit tracking (HDBSS) and cleaning (HACDBS) for ARM64 KVM. It modifies core KVM memory management and dirty logging logic, adding new ioctl paths and interrupt handlers. This is functional and reachable code that should be fuzzed.",
  "WorthFuzzing": true
}

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)