AI Trajectory


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

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "All modifications in this patch series are strictly confined to the RISC-V architecture (arch/riscv/), specifically the RISC-V KVM Stage-2 VMID management. They cannot be built or executed on the target architecture (amd64).",
  "WorthFuzzing": false
}

1/1 2026/09/10 17:50 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 5fc35ccc5c7d9d4c73494b548c88de85fde348c4\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Sep 10 17:50:24 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/arch/riscv/include/asm/kvm_gstage.h b/arch/riscv/include/asm/kvm_gstage.h\nindex aaf080ba1b77a..c61a1bbb272f4 100644\n--- a/arch/riscv/include/asm/kvm_gstage.h\n+++ b/arch/riscv/include/asm/kvm_gstage.h\n@@ -8,7 +8,7 @@\n #define __RISCV_KVM_GSTAGE_H_\n \n #include \u003clinux/kvm_types.h\u003e\n-\n+#include \u003casm/kvm_vmid.h\u003e\n struct kvm_gstage {\n \tstruct kvm *kvm;\n \tunsigned long flags;\n@@ -108,7 +108,8 @@ static inline void kvm_riscv_gstage_init(struct kvm_gstage *gstage, struct kvm *\n {\n \tgstage-\u003ekvm = kvm;\n \tgstage-\u003eflags = 0;\n-\tgstage-\u003evmid = READ_ONCE(kvm-\u003earch.vmid.vmid);\n+\tgstage-\u003evmid =\n+\t\tkvm_riscv_gstage_vmid_hwid(atomic64_read(\u0026kvm-\u003earch.vmid.id));\n \tgstage-\u003epgd = kvm-\u003earch.pgd;\n \tgstage-\u003epgd_levels = kvm-\u003earch.pgd_levels;\n }\ndiff --git a/arch/riscv/include/asm/kvm_vmid.h b/arch/riscv/include/asm/kvm_vmid.h\nindex db61b0525a8d0..ddd7cc125c3f6 100644\n--- a/arch/riscv/include/asm/kvm_vmid.h\n+++ b/arch/riscv/include/asm/kvm_vmid.h\n@@ -6,21 +6,42 @@\n #ifndef __RISCV_KVM_VMID_H_\n #define __RISCV_KVM_VMID_H_\n \n+#include \u003clinux/atomic.h\u003e\n #include \u003clinux/kvm_types.h\u003e\n \n struct kvm_vmid {\n \t/*\n-\t * Writes to vmid_version and vmid happen with vmid_lock held\n-\t * whereas reads happen without any lock held.\n+\t * Software VMID:\n+\t *\n+\t *   [ generation | hardware VMID ]\n+\t *\n+\t * Keep the identity 64-bit on RV32 so that the generation does not\n+\t * wrap at the native word size.  Zero denotes an unallocated VMID.\n+\t *\n+\t * Only the low hardware VMID bits may be written to HGATP\n+\t * or used as a hardware fence VMID.\n \t */\n-\tunsigned long vmid_version;\n-\tunsigned long vmid;\n+\tatomic64_t id;\n };\n \n void __init kvm_riscv_gstage_vmid_detect(void);\n unsigned long kvm_riscv_gstage_vmid_bits(void);\n+\n+int __init kvm_riscv_gstage_vmid_alloc_init(void);\n+void kvm_riscv_gstage_vmid_alloc_free(void);\n+\n int kvm_riscv_gstage_vmid_init(struct kvm *kvm);\n+\n+unsigned long kvm_riscv_gstage_vmid_hwid(u64 vmid);\n+\n bool kvm_riscv_gstage_vmid_ver_changed(struct kvm_vmid *vmid);\n+\n void kvm_riscv_gstage_vmid_update(struct kvm_vcpu *vcpu);\n \n+/*\n+ * Invalidate the current CPU's fast-path VMID state while preserving\n+ * the old identity conservatively in reserved_vmids.\n+ */\n+void kvm_riscv_gstage_vmid_cpu_invalidate(void);\n+\n #endif\ndiff --git a/arch/riscv/kvm/main.c b/arch/riscv/kvm/main.c\nindex 89568ccce01de..d6c6be4df61b1 100644\n--- a/arch/riscv/kvm/main.c\n+++ b/arch/riscv/kvm/main.c\n@@ -16,7 +16,7 @@\n #include \u003casm/kvm_nacl.h\u003e\n #include \u003casm/sbi.h\u003e\n #include \u003casm/kvm_vcpu_vector.h\u003e\n-\n+#include \u003casm/kvm_vmid.h\u003e\n static DEFINE_PER_CPU(bool, kvm_riscv_virtualization_enabled);\n \n DEFINE_STATIC_KEY_FALSE(kvm_riscv_vsstage_tlb_no_gpa);\n@@ -84,6 +84,7 @@ int kvm_arch_enable_virtualization_cpu(void)\n \n void kvm_arch_disable_virtualization_cpu(void)\n {\n+\tkvm_riscv_gstage_vmid_cpu_invalidate();\n \tkvm_riscv_aia_disable();\n \tkvm_riscv_csr_cleanup();\n \tkvm_riscv_nacl_disable();\n@@ -112,6 +113,7 @@ static int kvm_riscv_cpu_pm_notifier(struct notifier_block *self, unsigned long\n \t\t * is enabled on this CPU.\n \t\t */\n \t\tif (__this_cpu_read(kvm_riscv_virtualization_enabled)) {\n+\t\t\tkvm_riscv_gstage_vmid_cpu_invalidate();\n \t\t\tkvm_riscv_aia_pm_enter();\n \t\t\tkvm_riscv_csr_cleanup();\n \t\t}\n@@ -130,6 +132,7 @@ static struct notifier_block kvm_riscv_cpu_pm_nb = {\n static void kvm_riscv_teardown(void)\n {\n \tkvm_riscv_aia_exit();\n+\tkvm_riscv_gstage_vmid_alloc_free();\n \tkvm_riscv_nacl_exit();\n \tkvm_riscv_v_exit();\n \tkvm_unregister_perf_callbacks();\n@@ -181,8 +184,15 @@ static int __init riscv_kvm_init(void)\n \n \tkvm_riscv_gstage_vmid_detect();\n \n+\trc = kvm_riscv_gstage_vmid_alloc_init();\n+\tif (rc) {\n+\t\tkvm_riscv_nacl_exit();\n+\t\treturn rc;\n+\t}\n+\n \trc = kvm_riscv_aia_init();\n \tif (rc \u0026\u0026 rc != -ENODEV) {\n+\t\tkvm_riscv_gstage_vmid_alloc_free();\n \t\tkvm_riscv_nacl_exit();\n \t\treturn rc;\n \t}\ndiff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c\nindex 6035b5ec95039..3c8fb6009c0a5 100644\n--- a/arch/riscv/kvm/mmu.c\n+++ b/arch/riscv/kvm/mmu.c\n@@ -15,7 +15,7 @@\n #include \u003clinux/sched/signal.h\u003e\n #include \u003casm/kvm_mmu.h\u003e\n #include \u003casm/kvm_nacl.h\u003e\n-\n+#include \u003casm/kvm_vmid.h\u003e\n static bool __read_mostly eager_page_split = true;\n module_param(eager_page_split, bool, 0644);\n \n@@ -799,10 +799,11 @@ void kvm_riscv_mmu_free_pgd(struct kvm *kvm)\n void kvm_riscv_mmu_update_hgatp(struct kvm_vcpu *vcpu)\n {\n \tstruct kvm_arch *ka = \u0026vcpu-\u003ekvm-\u003earch;\n+\tu64 vmid = atomic64_read(\u0026ka-\u003evmid.id);\n \tunsigned long hgatp = kvm_riscv_gstage_mode(ka-\u003epgd_levels)\n \t\t\t      \u003c\u003c HGATP_MODE_SHIFT;\n \n-\thgatp |= (READ_ONCE(ka-\u003evmid.vmid) \u003c\u003c HGATP_VMID_SHIFT) \u0026 HGATP_VMID;\n+\thgatp |= (kvm_riscv_gstage_vmid_hwid(vmid) \u003c\u003c HGATP_VMID_SHIFT) \u0026 HGATP_VMID;\n \thgatp |= (ka-\u003epgd_phys \u003e\u003e PAGE_SHIFT) \u0026 HGATP_PPN;\n \n \tncsr_write(CSR_HGATP, hgatp);\ndiff --git a/arch/riscv/kvm/tlb.c b/arch/riscv/kvm/tlb.c\nindex 2ae34632cdcb1..b85e5ba2198aa 100644\n--- a/arch/riscv/kvm/tlb.c\n+++ b/arch/riscv/kvm/tlb.c\n@@ -224,7 +224,7 @@ void kvm_riscv_local_tlb_sanitize(struct kvm_vcpu *vcpu)\n \t * entries by VMID whenever underlying Host CPU changes for a VCPU.\n \t */\n \n-\tvmid = READ_ONCE(vcpu-\u003ekvm-\u003earch.vmid.vmid);\n+\tvmid = kvm_riscv_gstage_vmid_hwid(atomic64_read(\u0026vcpu-\u003ekvm-\u003earch.vmid.id));\n \tkvm_riscv_local_hfence_gvma_vmid_all(vmid);\n \n \t/*\n@@ -244,7 +244,7 @@ void kvm_riscv_fence_i_process(struct kvm_vcpu *vcpu)\n void kvm_riscv_tlb_flush_process(struct kvm_vcpu *vcpu)\n {\n \tstruct kvm_vmid *v = \u0026vcpu-\u003ekvm-\u003earch.vmid;\n-\tunsigned long vmid = READ_ONCE(v-\u003evmid);\n+\tunsigned long vmid = kvm_riscv_gstage_vmid_hwid(atomic64_read(\u0026v-\u003eid));\n \n \tif (kvm_riscv_nacl_available())\n \t\tnacl_hfence_gvma_vmid_all(nacl_shmem(), vmid);\n@@ -255,7 +255,7 @@ void kvm_riscv_tlb_flush_process(struct kvm_vcpu *vcpu)\n void kvm_riscv_hfence_vvma_all_process(struct kvm_vcpu *vcpu)\n {\n \tstruct kvm_vmid *v = \u0026vcpu-\u003ekvm-\u003earch.vmid;\n-\tunsigned long vmid = READ_ONCE(v-\u003evmid);\n+\tunsigned long vmid = kvm_riscv_gstage_vmid_hwid(atomic64_read(\u0026v-\u003eid));\n \n \tif (kvm_riscv_nacl_available())\n \t\tnacl_hfence_vvma_all(nacl_shmem(), vmid);\n@@ -531,8 +531,11 @@ void kvm_riscv_hfence_vvma_all(struct kvm *kvm,\n \n int kvm_arch_flush_remote_tlbs_range(struct kvm *kvm, gfn_t gfn, u64 nr_pages)\n {\n+\tunsigned long vmid =\n+\t\tkvm_riscv_gstage_vmid_hwid(atomic64_read(\u0026kvm-\u003earch.vmid.id));\n+\n \tkvm_riscv_hfence_gvma_vmid_gpa(kvm, -1UL, 0,\n \t\t\t\t       gfn \u003c\u003c PAGE_SHIFT, nr_pages \u003c\u003c PAGE_SHIFT,\n-\t\t\t\t       PAGE_SHIFT, READ_ONCE(kvm-\u003earch.vmid.vmid));\n+\t\t\t\t       PAGE_SHIFT, vmid);\n \treturn 0;\n }\ndiff --git a/arch/riscv/kvm/vcpu.c b/arch/riscv/kvm/vcpu.c\nindex e062ca19f9d8f..85542c1a5ec3f 100644\n--- a/arch/riscv/kvm/vcpu.c\n+++ b/arch/riscv/kvm/vcpu.c\n@@ -943,14 +943,14 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)\n \t\t\tcontinue;\n \t\tret = 1;\n \n-\t\tkvm_riscv_gstage_vmid_update(vcpu);\n-\n \t\tret = kvm_riscv_check_vcpu_requests(vcpu);\n \t\tif (ret \u003c= 0)\n \t\t\tcontinue;\n \n \t\tpreempt_disable();\n \n+\t\tkvm_riscv_gstage_vmid_update(vcpu);\n+\n \t\t/* Update AIA HW state before entering guest */\n \t\tret = kvm_riscv_vcpu_aia_update(vcpu);\n \t\tif (ret \u003c= 0) {\ndiff --git a/arch/riscv/kvm/vcpu_sbi_replace.c b/arch/riscv/kvm/vcpu_sbi_replace.c\nindex 506a510b6bff3..1be386a78e27f 100644\n--- a/arch/riscv/kvm/vcpu_sbi_replace.c\n+++ b/arch/riscv/kvm/vcpu_sbi_replace.c\n@@ -104,7 +104,7 @@ static int kvm_sbi_ext_rfence_handler(struct kvm_vcpu *vcpu, struct kvm_run *run\n \t\tkvm_riscv_vcpu_pmu_incr_fw(vcpu, SBI_PMU_FW_FENCE_I_SENT);\n \t\tbreak;\n \tcase SBI_EXT_RFENCE_REMOTE_SFENCE_VMA:\n-\t\tvmid = READ_ONCE(vcpu-\u003ekvm-\u003earch.vmid.vmid);\n+\t\tvmid = kvm_riscv_gstage_vmid_hwid(atomic64_read(\u0026vcpu-\u003ekvm-\u003earch.vmid.id));\n \t\tif ((cp-\u003ea2 == 0 \u0026\u0026 cp-\u003ea3 == 0) || cp-\u003ea3 == -1UL)\n \t\t\tkvm_riscv_hfence_vvma_all(vcpu-\u003ekvm, hbase, hmask, vmid);\n \t\telse\n@@ -113,7 +113,7 @@ static int kvm_sbi_ext_rfence_handler(struct kvm_vcpu *vcpu, struct kvm_run *run\n \t\tkvm_riscv_vcpu_pmu_incr_fw(vcpu, SBI_PMU_FW_HFENCE_VVMA_SENT);\n \t\tbreak;\n \tcase SBI_EXT_RFENCE_REMOTE_SFENCE_VMA_ASID:\n-\t\tvmid = READ_ONCE(vcpu-\u003ekvm-\u003earch.vmid.vmid);\n+\t\tvmid = kvm_riscv_gstage_vmid_hwid(atomic64_read(\u0026vcpu-\u003ekvm-\u003earch.vmid.id));\n \t\tif ((cp-\u003ea2 == 0 \u0026\u0026 cp-\u003ea3 == 0) || cp-\u003ea3 == -1UL)\n \t\t\tkvm_riscv_hfence_vvma_asid_all(vcpu-\u003ekvm, hbase, hmask,\n \t\t\t\t\t\t       cp-\u003ea4, vmid);\ndiff --git a/arch/riscv/kvm/vcpu_sbi_v01.c b/arch/riscv/kvm/vcpu_sbi_v01.c\nindex de544ea3f28dc..ca3efe63c1cf1 100644\n--- a/arch/riscv/kvm/vcpu_sbi_v01.c\n+++ b/arch/riscv/kvm/vcpu_sbi_v01.c\n@@ -88,14 +88,14 @@ static int kvm_sbi_ext_v01_handler(struct kvm_vcpu *vcpu, struct kvm_run *run,\n \t\tif (cp-\u003ea7 == SBI_EXT_0_1_REMOTE_FENCE_I)\n \t\t\tkvm_riscv_fence_i(vcpu-\u003ekvm, hbase, hmask);\n \t\telse if (cp-\u003ea7 == SBI_EXT_0_1_REMOTE_SFENCE_VMA) {\n-\t\t\tvmid = READ_ONCE(vcpu-\u003ekvm-\u003earch.vmid.vmid);\n+\t\t\tvmid = kvm_riscv_gstage_vmid_hwid(atomic64_read(\u0026vcpu-\u003ekvm-\u003earch.vmid.id));\n \t\t\tif (cp-\u003ea1 == 0 \u0026\u0026 cp-\u003ea2 == 0)\n \t\t\t\tkvm_riscv_hfence_vvma_all(vcpu-\u003ekvm, hbase, hmask, vmid);\n \t\t\telse\n \t\t\t\tkvm_riscv_hfence_vvma_gva(vcpu-\u003ekvm, hbase, hmask, cp-\u003ea1,\n \t\t\t\t\t\t\t  cp-\u003ea2, PAGE_SHIFT, vmid);\n \t\t} else {\n-\t\t\tvmid = READ_ONCE(vcpu-\u003ekvm-\u003earch.vmid.vmid);\n+\t\t\tvmid = kvm_riscv_gstage_vmid_hwid(atomic64_read(\u0026vcpu-\u003ekvm-\u003earch.vmid.id));\n \t\t\tif (cp-\u003ea1 == 0 \u0026\u0026 cp-\u003ea2 == 0)\n \t\t\t\tkvm_riscv_hfence_vvma_asid_all(vcpu-\u003ekvm, hbase, hmask,\n \t\t\t\t\t\t\t       cp-\u003ea3, vmid);\ndiff --git a/arch/riscv/kvm/vmid.c b/arch/riscv/kvm/vmid.c\nindex c15bdb1dd8bef..7b86a6fbe4a69 100644\n--- a/arch/riscv/kvm/vmid.c\n+++ b/arch/riscv/kvm/vmid.c\n@@ -6,11 +6,15 @@\n  *     Anup Patel \u003canup.patel@wdc.com\u003e\n  */\n \n+#include \u003clinux/bitmap.h\u003e\n #include \u003clinux/bitops.h\u003e\n #include \u003clinux/cpumask.h\u003e\n #include \u003clinux/errno.h\u003e\n #include \u003clinux/err.h\u003e\n #include \u003clinux/module.h\u003e\n+#include \u003clinux/percpu.h\u003e\n+#include \u003clinux/preempt.h\u003e\n+#include \u003clinux/slab.h\u003e\n #include \u003clinux/smp.h\u003e\n #include \u003clinux/kvm_host.h\u003e\n #include \u003casm/csr.h\u003e\n@@ -18,26 +22,54 @@\n #include \u003casm/kvm_tlb.h\u003e\n #include \u003casm/kvm_vmid.h\u003e\n \n-static unsigned long vmid_version = 1;\n-static unsigned long vmid_next;\n+static atomic64_t vmid_generation;\n+static unsigned long *vmid_map;\n static unsigned long vmid_bits __ro_after_init;\n-static DEFINE_SPINLOCK(vmid_lock);\n+static unsigned long vmid_cur_idx = 1;\n+\n+static DEFINE_RAW_SPINLOCK(vmid_lock);\n+\n+static DEFINE_PER_CPU(atomic64_t, active_vmids);\n+static DEFINE_PER_CPU(u64, reserved_vmids);\n+\n+static cpumask_t tlb_flush_pending;\n+\n+#define VMID_FIRST_VERSION\tBIT_ULL(vmid_bits)\n+#define NUM_VMIDS\t\tBIT(vmid_bits)\n+#define VMID_HW_MASK\t\t(VMID_FIRST_VERSION - 1)\n+\n+static bool vmid_gen_match(u64 vmid)\n+{\n+\treturn !((vmid ^ atomic64_read(\u0026vmid_generation)) \u003e\u003e vmid_bits);\n+}\n+\n+unsigned long kvm_riscv_gstage_vmid_hwid(u64 vmid)\n+{\n+\tif (!vmid_bits)\n+\t\treturn 0;\n+\n+\treturn (unsigned long)(vmid \u0026 VMID_HW_MASK);\n+}\n \n void __init kvm_riscv_gstage_vmid_detect(void)\n {\n-\t/* Figure-out number of VMID bits in HW */\n+\t/* Figure out the number of VMID bits supported by hardware. */\n \tcsr_write(CSR_HGATP, (kvm_riscv_gstage_mode(kvm_riscv_gstage_max_pgd_levels) \u003c\u003c\n-\t\t\t      HGATP_MODE_SHIFT) | HGATP_VMID);\n+\t\t   HGATP_MODE_SHIFT) | HGATP_VMID);\n \tvmid_bits = csr_read(CSR_HGATP);\n \tvmid_bits = (vmid_bits \u0026 HGATP_VMID) \u003e\u003e HGATP_VMID_SHIFT;\n \tvmid_bits = fls_long(vmid_bits);\n \tcsr_write(CSR_HGATP, 0);\n \n-\t/* We polluted local TLB so flush all guest TLB */\n+\t/* Flush the local guest TLB after probing HGATP. */\n \tkvm_riscv_local_hfence_gvma_all();\n \n-\t/* We don't use VMID bits if they are not sufficient */\n-\tif ((1UL \u003c\u003c vmid_bits) \u003c num_possible_cpus())\n+\t/*\n+\t * VMID 0 is reserved.  During rollover every possible CPU may\n+\t * reserve one hardware VMID, and we still need at least one VMID\n+\t * available for a new allocation.\n+\t */\n+\tif (vmid_bits \u0026\u0026 NUM_VMIDS - 1 \u003c= num_possible_cpus())\n \t\tvmid_bits = 0;\n }\n \n@@ -46,80 +78,298 @@ unsigned long kvm_riscv_gstage_vmid_bits(void)\n \treturn vmid_bits;\n }\n \n+int __init kvm_riscv_gstage_vmid_alloc_init(void)\n+{\n+\tint cpu;\n+\n+\tif (!vmid_bits)\n+\t\treturn 0;\n+\n+\tvmid_map = bitmap_zalloc(NUM_VMIDS, GFP_KERNEL);\n+\tif (!vmid_map)\n+\t\treturn -ENOMEM;\n+\n+\tvmid_cur_idx = 1;\n+\n+\t/* Hardware VMID 0 is reserved. */\n+\t__set_bit(0, vmid_map);\n+\n+\tatomic64_set(\u0026vmid_generation, VMID_FIRST_VERSION);\n+\n+\tfor_each_possible_cpu(cpu) {\n+\t\tatomic64_set(\u0026per_cpu(active_vmids, cpu), 0);\n+\t\tper_cpu(reserved_vmids, cpu) = 0;\n+\t}\n+\n+\t/*\n+\t * Every CPU performs an initial local invalidation before its\n+\t * first VMID activation.\n+\t */\n+\tcpumask_copy(\u0026tlb_flush_pending, cpu_possible_mask);\n+\n+\treturn 0;\n+}\n+\n+void kvm_riscv_gstage_vmid_alloc_free(void)\n+{\n+\tbitmap_free(vmid_map);\n+\tvmid_map = NULL;\n+}\n+\n int kvm_riscv_gstage_vmid_init(struct kvm *kvm)\n {\n-\t/* Mark the initial VMID and VMID version invalid */\n-\tkvm-\u003earch.vmid.vmid_version = 0;\n-\tkvm-\u003earch.vmid.vmid = 0;\n+\tatomic64_set(\u0026kvm-\u003earch.vmid.id, 0);\n \n \treturn 0;\n }\n \n bool kvm_riscv_gstage_vmid_ver_changed(struct kvm_vmid *vmid)\n {\n+\tu64 id;\n+\n \tif (!vmid_bits)\n \t\treturn false;\n \n-\treturn unlikely(READ_ONCE(vmid-\u003evmid_version) !=\n-\t\t\tREAD_ONCE(vmid_version));\n+\tid = atomic64_read(\u0026vmid-\u003eid);\n+\n+\treturn unlikely(!vmid_gen_match(id));\n }\n \n-static void __local_hfence_gvma_all(void *info)\n+/*\n+ * Called with vmid_lock held after vmid_generation has already been\n+ * advanced.\n+ *\n+ * No remote CPU is interrupted here.  Instead, preserve every VMID\n+ * which may still be used by a CPU and queue a local invalidation for\n+ * that CPU's next VMID activation.\n+ */\n+static void flush_context(void)\n {\n-\tkvm_riscv_local_hfence_gvma_all();\n+\tu64 vmid;\n+\tint cpu;\n+\n+\tbitmap_zero(vmid_map, NUM_VMIDS);\n+\t__set_bit(0, vmid_map);\n+\n+\tfor_each_possible_cpu(cpu) {\n+\t\tvmid = atomic64_xchg(\u0026per_cpu(active_vmids, cpu), 0);\n+\n+\t\t/*\n+\t\t * The CPU may already have been caught by an earlier rollover\n+\t\t * without performing another activation since then.  In that\n+\t\t * case reserved_vmids is the only record of the old context.\n+\t\t */\n+\t\tif (!vmid)\n+\t\t\tvmid = per_cpu(reserved_vmids, cpu);\n+\n+\t\tif (vmid)\n+\t\t\t__set_bit(kvm_riscv_gstage_vmid_hwid(vmid),\n+\t\t\t\t  vmid_map);\n+\n+\t\tper_cpu(reserved_vmids, cpu) = vmid;\n+\t}\n+\n+\tcpumask_copy(\u0026tlb_flush_pending, cpu_possible_mask);\n+}\n+\n+/*\n+ * Update every reserved copy of an old software VMID.\n+ *\n+ * Do not stop after the first match: the same VM may have been active\n+ * on more than one CPU when rollover occurred.\n+ */\n+static bool check_update_reserved_vmid(u64 old_vmid, u64 new_vmid)\n+{\n+\tbool hit = false;\n+\tint cpu;\n+\n+\tfor_each_possible_cpu(cpu) {\n+\t\tif (per_cpu(reserved_vmids, cpu) == old_vmid) {\n+\t\t\tper_cpu(reserved_vmids, cpu) = new_vmid;\n+\t\t\thit = true;\n+\t\t}\n+\t}\n+\n+\treturn hit;\n+}\n+\n+/*\n+ * Allocate/promote a software VMID.\n+ *\n+ * vmid_lock must be held by the caller.\n+ */\n+static u64 new_vmid_locked(struct kvm_vmid *kvm_vmid)\n+{\n+\tu64 vmid = atomic64_read(\u0026kvm_vmid-\u003eid);\n+\tu64 generation = atomic64_read(\u0026vmid_generation);\n+\tu64 new_vmid;\n+\tunsigned long idx;\n+\n+\tif (vmid) {\n+\t\tnew_vmid = generation |\n+\t\t\t   kvm_riscv_gstage_vmid_hwid(vmid);\n+\n+\t\t/*\n+\t\t * The old VMID is still protected by one or more CPUs.\n+\t\t * Keep the same hardware VMID and only promote generation.\n+\t\t */\n+\t\tif (check_update_reserved_vmid(vmid, new_vmid))\n+\t\t\treturn new_vmid;\n+\n+\t\t/*\n+\t\t * The VM had a VMID in an older generation.  Reuse the same\n+\t\t * hardware number if it has not already been claimed.\n+\t\t */\n+\t\tidx = kvm_riscv_gstage_vmid_hwid(vmid);\n+\t\tif (!__test_and_set_bit(idx, vmid_map))\n+\t\t\treturn new_vmid;\n+\t}\n+\n+\t/*\n+\t * Find a free VMID in the current generation.\n+\t */\n+\tidx = find_next_zero_bit(vmid_map, NUM_VMIDS, vmid_cur_idx);\n+\tif (idx != NUM_VMIDS)\n+\t\tgoto set_vmid;\n+\n+\t/*\n+\t * No free VMID.  Start a new generation, preserve all CPU-local\n+\t * users, and defer each CPU's flush until its next activation.\n+\t */\n+\tgeneration += VMID_FIRST_VERSION;\n+\n+\t/* Software VMID 0 is reserved as the invalid identifier. */\n+\tif (unlikely(!generation))\n+\t\tgeneration = VMID_FIRST_VERSION;\n+\n+\tatomic64_xchg(\u0026vmid_generation, generation);\n+\tflush_context();\n+\n+\t/*\n+\t * NUM_VMIDS - 1 \u003e num_possible_cpus(), therefore rollover must\n+\t * leave at least one allocatable hardware VMID.\n+\t */\n+\tidx = find_next_zero_bit(vmid_map, NUM_VMIDS, 1);\n+\n+set_vmid:\n+\t__set_bit(idx, vmid_map);\n+\tvmid_cur_idx = idx;\n+\n+\treturn generation | idx;\n }\n \n void kvm_riscv_gstage_vmid_update(struct kvm_vcpu *vcpu)\n {\n-\tunsigned long i;\n+\tstruct kvm_vmid *kvm_vmid = \u0026vcpu-\u003ekvm-\u003earch.vmid;\n+\tatomic64_t *active;\n \tstruct kvm_vcpu *v;\n-\tstruct kvm_vmid *vmid = \u0026vcpu-\u003ekvm-\u003earch.vmid;\n+\tunsigned long flags;\n+\tu64 vmid;\n+\tu64 old_active_vmid;\n+\tunsigned long i;\n+\tunsigned int cpu;\n+\tbool vmid_changed = false;\n \n-\tif (!kvm_riscv_gstage_vmid_ver_changed(vmid))\n+\tif (!vmid_bits)\n \t\treturn;\n \n-\tspin_lock(\u0026vmid_lock);\n+\t/*\n+\t * active_vmids and tlb_flush_pending are per-CPU admission state.\n+\t * They must refer to the same CPU for the complete activation.\n+\t */\n+\tpreempt_disable();\n+\n+\tcpu = smp_processor_id();\n+\tactive = this_cpu_ptr(\u0026active_vmids);\n+\n+\tvmid = atomic64_read(\u0026kvm_vmid-\u003eid);\n+\told_active_vmid = atomic64_read(active);\n \n \t/*\n-\t * We need to re-check the vmid_version here to ensure that if\n-\t * another vcpu already allocated a valid vmid for this vm.\n+\t * Fast path.\n+\t *\n+\t * The cmpxchg races with flush_context()'s xchg on the same\n+\t * per-CPU atomic.  Either this activation is captured by rollover,\n+\t * or rollover clears active first and the cmpxchg fails.\n \t */\n-\tif (!kvm_riscv_gstage_vmid_ver_changed(vmid)) {\n-\t\tspin_unlock(\u0026vmid_lock);\n-\t\treturn;\n+\tif (old_active_vmid \u0026\u0026\n+\t    vmid_gen_match(vmid) \u0026\u0026\n+\t    atomic64_cmpxchg(active, old_active_vmid, vmid) ==\n+\t    old_active_vmid)\n+\t\tgoto out;\n+\n+\traw_spin_lock_irqsave(\u0026vmid_lock, flags);\n+\n+\t/*\n+\t * Re-read under the allocator lock because another vCPU of this VM\n+\t * may already have promoted or allocated the shared VMID.\n+\t */\n+\tvmid = atomic64_read(\u0026kvm_vmid-\u003eid);\n+\n+\tif (!vmid_gen_match(vmid)) {\n+\t\tvmid = new_vmid_locked(kvm_vmid);\n+\t\tatomic64_set(\u0026kvm_vmid-\u003eid, vmid);\n+\t\tvmid_changed = true;\n \t}\n \n-\t/* First user of a new VMID version? */\n-\tif (unlikely(vmid_next == 0)) {\n-\t\tWRITE_ONCE(vmid_version, READ_ONCE(vmid_version) + 1);\n-\t\tvmid_next = 1;\n+\t/*\n+\t * Even if another vCPU already updated the shared VMID, this CPU\n+\t * must still discharge its own rollover flush obligation.\n+\t */\n+\tif (cpumask_test_and_clear_cpu(cpu, \u0026tlb_flush_pending))\n+\t\tkvm_riscv_local_hfence_gvma_all();\n \n-\t\t/*\n-\t\t * We ran out of VMIDs so we increment vmid_version and\n-\t\t * start assigning VMIDs from 1.\n-\t\t *\n-\t\t * This also means existing VMIDs assignment to all Guest\n-\t\t * instances is invalid and we have force VMID re-assignement\n-\t\t * for all Guest instances. The Guest instances that were not\n-\t\t * running will automatically pick-up new VMIDs because will\n-\t\t * call kvm_riscv_gstage_vmid_update() whenever they enter\n-\t\t * in-kernel run loop. For Guest instances that are already\n-\t\t * running, we force VM exits on all host CPUs using IPI and\n-\t\t * flush all Guest TLBs.\n-\t\t */\n-\t\ton_each_cpu_mask(cpu_online_mask, __local_hfence_gvma_all,\n-\t\t\t\t NULL, 1);\n+\t/*\n+\t * Publish the CPU-local active identity only after the required\n+\t * local invalidation is complete.\n+\t */\n+\tatomic64_set(active, vmid);\n+\n+\traw_spin_unlock_irqrestore(\u0026vmid_lock, flags);\n+\n+\t/*\n+\t * A new software VMID may contain a different hardware VMID, so\n+\t * request an HGATP update for every vCPU in the VM.\n+\t */\n+\tif (vmid_changed) {\n+\t\tkvm_for_each_vcpu(i, v, vcpu-\u003ekvm)\n+\t\t\tkvm_make_request(KVM_REQ_UPDATE_HGATP, v);\n \t}\n \n-\tvmid-\u003evmid = vmid_next;\n-\tvmid_next++;\n-\tvmid_next \u0026= (1 \u003c\u003c vmid_bits) - 1;\n+out:\n+\tpreempt_enable();\n+}\n+\n+/*\n+ * CPU virtualization/CSR state is being discarded.\n+ *\n+ * Withdraw fast-path eligibility, but conservatively preserve the last\n+ * identity instead of making its hardware VMID immediately reusable.\n+ */\n+void kvm_riscv_gstage_vmid_cpu_invalidate(void)\n+{\n+\tunsigned long flags;\n+\tu64 vmid;\n+\tunsigned int cpu;\n \n-\tWRITE_ONCE(vmid-\u003evmid_version, READ_ONCE(vmid_version));\n+\tif (!vmid_bits)\n+\t\treturn;\n \n-\tspin_unlock(\u0026vmid_lock);\n+\tlockdep_assert_preemption_disabled();\n+\n+\tcpu = smp_processor_id();\n+\n+\traw_spin_lock_irqsave(\u0026vmid_lock, flags);\n+\n+\tvmid = atomic64_xchg(this_cpu_ptr(\u0026active_vmids), 0);\n+\n+\tif (vmid)\n+\t\tper_cpu(reserved_vmids, cpu) = vmid;\n+\n+\t/*\n+\t * If active was already zero, retain the old reserved entry.\n+\t */\n+\tcpumask_set_cpu(cpu, \u0026tlb_flush_pending);\n \n-\t/* Request G-stage page table update for all VCPUs */\n-\tkvm_for_each_vcpu(i, v, vcpu-\u003ekvm)\n-\t\tkvm_make_request(KVM_REQ_UPDATE_HGATP, v);\n+\traw_spin_unlock_irqrestore(\u0026vmid_lock, flags);\n }\n"
}

2/1 2026/09/10 17:50 agent patch-evaluator
0m Model:
core

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "All modifications in this patch series are strictly confined to the RISC-V architecture (arch/riscv/), specifically the RISC-V KVM Stage-2 VMID management. They cannot be built or executed on the target architecture (amd64).",
  "WorthFuzzing": false
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 5fc35ccc5c7d9d4c73494b548c88de85fde348c4
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Sep 10 17:50:24 2026 +0000

    syz-cluster: applied patch under review

diff --git a/arch/riscv/include/asm/kvm_gstage.h b/arch/riscv/include/asm/kvm_gstage.h
index aaf080ba1b77a..c61a1bbb272f4 100644
--- a/arch/riscv/include/asm/kvm_gstage.h
+++ b/arch/riscv/include/asm/kvm_gstage.h
@@ -8,7 +8,7 @@
 #define __RISCV_KVM_GSTAGE_H_
 
 #include <linux/kvm_types.h>
-
+#include <asm/kvm_vmid.h>
 struct kvm_gstage {
 	struct kvm *kvm;
 	unsigned long flags;
@@ -108,7 +108,8 @@ static inline void kvm_riscv_gstage_init(struct kvm_gstage *gstage, struct kvm *
 {
 	gstage->kvm = kvm;
 	gstage->flags = 0;
-	gstage->vmid = READ_ONCE(kvm->arch.vmid.vmid);
+	gstage->vmid =
+		kvm_riscv_gstage_vmid_hwid(atomic64_read(&kvm->arch.vmid.id));
 	gstage->pgd = kvm->arch.pgd;
 	gstage->pgd_levels = kvm->arch.pgd_levels;
 }
diff --git a/arch/riscv/include/asm/kvm_vmid.h b/arch/riscv/include/asm/kvm_vmid.h
index db61b0525a8d0..ddd7cc125c3f6 100644
--- a/arch/riscv/include/asm/kvm_vmid.h
+++ b/arch/riscv/include/asm/kvm_vmid.h
@@ -6,21 +6,42 @@
 #ifndef __RISCV_KVM_VMID_H_
 #define __RISCV_KVM_VMID_H_
 
+#include <linux/atomic.h>
 #include <linux/kvm_types.h>
 
 struct kvm_vmid {
 	/*
-	 * Writes to vmid_version and vmid happen with vmid_lock held
-	 * whereas reads happen without any lock held.
+	 * Software VMID:
+	 *
+	 *   [ generation | hardware VMID ]
+	 *
+	 * Keep the identity 64-bit on RV32 so that the generation does not
+	 * wrap at the native word size.  Zero denotes an unallocated VMID.
+	 *
+	 * Only the low hardware VMID bits may be written to HGATP
+	 * or used as a hardware fence VMID.
 	 */
-	unsigned long vmid_version;
-	unsigned long vmid;
+	atomic64_t id;
 };
 
 void __init kvm_riscv_gstage_vmid_detect(void);
 unsigned long kvm_riscv_gstage_vmid_bits(void);
+
+int __init kvm_riscv_gstage_vmid_alloc_init(void);
+void kvm_riscv_gstage_vmid_alloc_free(void);
+
 int kvm_riscv_gstage_vmid_init(struct kvm *kvm);
+
+unsigned long kvm_riscv_gstage_vmid_hwid(u64 vmid);
+
 bool kvm_riscv_gstage_vmid_ver_changed(struct kvm_vmid *vmid);
+
 void kvm_riscv_gstage_vmid_update(struct kvm_vcpu *vcpu);
 
+/*
+ * Invalidate the current CPU's fast-path VMID state while preserving
+ * the old identity conservatively in reserved_vmids.
+ */
+void kvm_riscv_gstage_vmid_cpu_invalidate(void);
+
 #endif
diff --git a/arch/riscv/kvm/main.c b/arch/riscv/kvm/main.c
index 89568ccce01de..d6c6be4df61b1 100644
--- a/arch/riscv/kvm/main.c
+++ b/arch/riscv/kvm/main.c
@@ -16,7 +16,7 @@
 #include <asm/kvm_nacl.h>
 #include <asm/sbi.h>
 #include <asm/kvm_vcpu_vector.h>
-
+#include <asm/kvm_vmid.h>
 static DEFINE_PER_CPU(bool, kvm_riscv_virtualization_enabled);
 
 DEFINE_STATIC_KEY_FALSE(kvm_riscv_vsstage_tlb_no_gpa);
@@ -84,6 +84,7 @@ int kvm_arch_enable_virtualization_cpu(void)
 
 void kvm_arch_disable_virtualization_cpu(void)
 {
+	kvm_riscv_gstage_vmid_cpu_invalidate();
 	kvm_riscv_aia_disable();
 	kvm_riscv_csr_cleanup();
 	kvm_riscv_nacl_disable();
@@ -112,6 +113,7 @@ static int kvm_riscv_cpu_pm_notifier(struct notifier_block *self, unsigned long
 		 * is enabled on this CPU.
 		 */
 		if (__this_cpu_read(kvm_riscv_virtualization_enabled)) {
+			kvm_riscv_gstage_vmid_cpu_invalidate();
 			kvm_riscv_aia_pm_enter();
 			kvm_riscv_csr_cleanup();
 		}
@@ -130,6 +132,7 @@ static struct notifier_block kvm_riscv_cpu_pm_nb = {
 static void kvm_riscv_teardown(void)
 {
 	kvm_riscv_aia_exit();
+	kvm_riscv_gstage_vmid_alloc_free();
 	kvm_riscv_nacl_exit();
 	kvm_riscv_v_exit();
 	kvm_unregister_perf_callbacks();
@@ -181,8 +184,15 @@ static int __init riscv_kvm_init(void)
 
 	kvm_riscv_gstage_vmid_detect();
 
+	rc = kvm_riscv_gstage_vmid_alloc_init();
+	if (rc) {
+		kvm_riscv_nacl_exit();
+		return rc;
+	}
+
 	rc = kvm_riscv_aia_init();
 	if (rc && rc != -ENODEV) {
+		kvm_riscv_gstage_vmid_alloc_free();
 		kvm_riscv_nacl_exit();
 		return rc;
 	}
diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
index 6035b5ec95039..3c8fb6009c0a5 100644
--- a/arch/riscv/kvm/mmu.c
+++ b/arch/riscv/kvm/mmu.c
@@ -15,7 +15,7 @@
 #include <linux/sched/signal.h>
 #include <asm/kvm_mmu.h>
 #include <asm/kvm_nacl.h>
-
+#include <asm/kvm_vmid.h>
 static bool __read_mostly eager_page_split = true;
 module_param(eager_page_split, bool, 0644);
 
@@ -799,10 +799,11 @@ void kvm_riscv_mmu_free_pgd(struct kvm *kvm)
 void kvm_riscv_mmu_update_hgatp(struct kvm_vcpu *vcpu)
 {
 	struct kvm_arch *ka = &vcpu->kvm->arch;
+	u64 vmid = atomic64_read(&ka->vmid.id);
 	unsigned long hgatp = kvm_riscv_gstage_mode(ka->pgd_levels)
 			      << HGATP_MODE_SHIFT;
 
-	hgatp |= (READ_ONCE(ka->vmid.vmid) << HGATP_VMID_SHIFT) & HGATP_VMID;
+	hgatp |= (kvm_riscv_gstage_vmid_hwid(vmid) << HGATP_VMID_SHIFT) & HGATP_VMID;
 	hgatp |= (ka->pgd_phys >> PAGE_SHIFT) & HGATP_PPN;
 
 	ncsr_write(CSR_HGATP, hgatp);
diff --git a/arch/riscv/kvm/tlb.c b/arch/riscv/kvm/tlb.c
index 2ae34632cdcb1..b85e5ba2198aa 100644
--- a/arch/riscv/kvm/tlb.c
+++ b/arch/riscv/kvm/tlb.c
@@ -224,7 +224,7 @@ void kvm_riscv_local_tlb_sanitize(struct kvm_vcpu *vcpu)
 	 * entries by VMID whenever underlying Host CPU changes for a VCPU.
 	 */
 
-	vmid = READ_ONCE(vcpu->kvm->arch.vmid.vmid);
+	vmid = kvm_riscv_gstage_vmid_hwid(atomic64_read(&vcpu->kvm->arch.vmid.id));
 	kvm_riscv_local_hfence_gvma_vmid_all(vmid);
 
 	/*
@@ -244,7 +244,7 @@ void kvm_riscv_fence_i_process(struct kvm_vcpu *vcpu)
 void kvm_riscv_tlb_flush_process(struct kvm_vcpu *vcpu)
 {
 	struct kvm_vmid *v = &vcpu->kvm->arch.vmid;
-	unsigned long vmid = READ_ONCE(v->vmid);
+	unsigned long vmid = kvm_riscv_gstage_vmid_hwid(atomic64_read(&v->id));
 
 	if (kvm_riscv_nacl_available())
 		nacl_hfence_gvma_vmid_all(nacl_shmem(), vmid);
@@ -255,7 +255,7 @@ void kvm_riscv_tlb_flush_process(struct kvm_vcpu *vcpu)
 void kvm_riscv_hfence_vvma_all_process(struct kvm_vcpu *vcpu)
 {
 	struct kvm_vmid *v = &vcpu->kvm->arch.vmid;
-	unsigned long vmid = READ_ONCE(v->vmid);
+	unsigned long vmid = kvm_riscv_gstage_vmid_hwid(atomic64_read(&v->id));
 
 	if (kvm_riscv_nacl_available())
 		nacl_hfence_vvma_all(nacl_shmem(), vmid);
@@ -531,8 +531,11 @@ void kvm_riscv_hfence_vvma_all(struct kvm *kvm,
 
 int kvm_arch_flush_remote_tlbs_range(struct kvm *kvm, gfn_t gfn, u64 nr_pages)
 {
+	unsigned long vmid =
+		kvm_riscv_gstage_vmid_hwid(atomic64_read(&kvm->arch.vmid.id));
+
 	kvm_riscv_hfence_gvma_vmid_gpa(kvm, -1UL, 0,
 				       gfn << PAGE_SHIFT, nr_pages << PAGE_SHIFT,
-				       PAGE_SHIFT, READ_ONCE(kvm->arch.vmid.vmid));
+				       PAGE_SHIFT, vmid);
 	return 0;
 }
diff --git a/arch/riscv/kvm/vcpu.c b/arch/riscv/kvm/vcpu.c
index e062ca19f9d8f..85542c1a5ec3f 100644
--- a/arch/riscv/kvm/vcpu.c
+++ b/arch/riscv/kvm/vcpu.c
@@ -943,14 +943,14 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
 			continue;
 		ret = 1;
 
-		kvm_riscv_gstage_vmid_update(vcpu);
-
 		ret = kvm_riscv_check_vcpu_requests(vcpu);
 		if (ret <= 0)
 			continue;
 
 		preempt_disable();
 
+		kvm_riscv_gstage_vmid_update(vcpu);
+
 		/* Update AIA HW state before entering guest */
 		ret = kvm_riscv_vcpu_aia_update(vcpu);
 		if (ret <= 0) {
diff --git a/arch/riscv/kvm/vcpu_sbi_replace.c b/arch/riscv/kvm/vcpu_sbi_replace.c
index 506a510b6bff3..1be386a78e27f 100644
--- a/arch/riscv/kvm/vcpu_sbi_replace.c
+++ b/arch/riscv/kvm/vcpu_sbi_replace.c
@@ -104,7 +104,7 @@ static int kvm_sbi_ext_rfence_handler(struct kvm_vcpu *vcpu, struct kvm_run *run
 		kvm_riscv_vcpu_pmu_incr_fw(vcpu, SBI_PMU_FW_FENCE_I_SENT);
 		break;
 	case SBI_EXT_RFENCE_REMOTE_SFENCE_VMA:
-		vmid = READ_ONCE(vcpu->kvm->arch.vmid.vmid);
+		vmid = kvm_riscv_gstage_vmid_hwid(atomic64_read(&vcpu->kvm->arch.vmid.id));
 		if ((cp->a2 == 0 && cp->a3 == 0) || cp->a3 == -1UL)
 			kvm_riscv_hfence_vvma_all(vcpu->kvm, hbase, hmask, vmid);
 		else
@@ -113,7 +113,7 @@ static int kvm_sbi_ext_rfence_handler(struct kvm_vcpu *vcpu, struct kvm_run *run
 		kvm_riscv_vcpu_pmu_incr_fw(vcpu, SBI_PMU_FW_HFENCE_VVMA_SENT);
 		break;
 	case SBI_EXT_RFENCE_REMOTE_SFENCE_VMA_ASID:
-		vmid = READ_ONCE(vcpu->kvm->arch.vmid.vmid);
+		vmid = kvm_riscv_gstage_vmid_hwid(atomic64_read(&vcpu->kvm->arch.vmid.id));
 		if ((cp->a2 == 0 && cp->a3 == 0) || cp->a3 == -1UL)
 			kvm_riscv_hfence_vvma_asid_all(vcpu->kvm, hbase, hmask,
 						       cp->a4, vmid);
diff --git a/arch/riscv/kvm/vcpu_sbi_v01.c b/arch/riscv/kvm/vcpu_sbi_v01.c
index de544ea3f28dc..ca3efe63c1cf1 100644
--- a/arch/riscv/kvm/vcpu_sbi_v01.c
+++ b/arch/riscv/kvm/vcpu_sbi_v01.c
@@ -88,14 +88,14 @@ static int kvm_sbi_ext_v01_handler(struct kvm_vcpu *vcpu, struct kvm_run *run,
 		if (cp->a7 == SBI_EXT_0_1_REMOTE_FENCE_I)
 			kvm_riscv_fence_i(vcpu->kvm, hbase, hmask);
 		else if (cp->a7 == SBI_EXT_0_1_REMOTE_SFENCE_VMA) {
-			vmid = READ_ONCE(vcpu->kvm->arch.vmid.vmid);
+			vmid = kvm_riscv_gstage_vmid_hwid(atomic64_read(&vcpu->kvm->arch.vmid.id));
 			if (cp->a1 == 0 && cp->a2 == 0)
 				kvm_riscv_hfence_vvma_all(vcpu->kvm, hbase, hmask, vmid);
 			else
 				kvm_riscv_hfence_vvma_gva(vcpu->kvm, hbase, hmask, cp->a1,
 							  cp->a2, PAGE_SHIFT, vmid);
 		} else {
-			vmid = READ_ONCE(vcpu->kvm->arch.vmid.vmid);
+			vmid = kvm_riscv_gstage_vmid_hwid(atomic64_read(&vcpu->kvm->arch.vmid.id));
 			if (cp->a1 == 0 && cp->a2 == 0)
 				kvm_riscv_hfence_vvma_asid_all(vcpu->kvm, hbase, hmask,
 							       cp->a3, vmid);
diff --git a/arch/riscv/kvm/vmid.c b/arch/riscv/kvm/vmid.c
index c15bdb1dd8bef..7b86a6fbe4a69 100644
--- a/arch/riscv/kvm/vmid.c
+++ b/arch/riscv/kvm/vmid.c
@@ -6,11 +6,15 @@
  *     Anup Patel <anup.patel@wdc.com>
  */
 
+#include <linux/bitmap.h>
 #include <linux/bitops.h>
 #include <linux/cpumask.h>
 #include <linux/errno.h>
 #include <linux/err.h>
 #include <linux/module.h>
+#include <linux/percpu.h>
+#include <linux/preempt.h>
+#include <linux/slab.h>
 #include <linux/smp.h>
 #include <linux/kvm_host.h>
 #include <asm/csr.h>
@@ -18,26 +22,54 @@
 #include <asm/kvm_tlb.h>
 #include <asm/kvm_vmid.h>
 
-static unsigned long vmid_version = 1;
-static unsigned long vmid_next;
+static atomic64_t vmid_generation;
+static unsigned long *vmid_map;
 static unsigned long vmid_bits __ro_after_init;
-static DEFINE_SPINLOCK(vmid_lock);
+static unsigned long vmid_cur_idx = 1;
+
+static DEFINE_RAW_SPINLOCK(vmid_lock);
+
+static DEFINE_PER_CPU(atomic64_t, active_vmids);
+static DEFINE_PER_CPU(u64, reserved_vmids);
+
+static cpumask_t tlb_flush_pending;
+
+#define VMID_FIRST_VERSION	BIT_ULL(vmid_bits)
+#define NUM_VMIDS		BIT(vmid_bits)
+#define VMID_HW_MASK		(VMID_FIRST_VERSION - 1)
+
+static bool vmid_gen_match(u64 vmid)
+{
+	return !((vmid ^ atomic64_read(&vmid_generation)) >> vmid_bits);
+}
+
+unsigned long kvm_riscv_gstage_vmid_hwid(u64 vmid)
+{
+	if (!vmid_bits)
+		return 0;
+
+	return (unsigned long)(vmid & VMID_HW_MASK);
+}
 
 void __init kvm_riscv_gstage_vmid_detect(void)
 {
-	/* Figure-out number of VMID bits in HW */
+	/* Figure out the number of VMID bits supported by hardware. */
 	csr_write(CSR_HGATP, (kvm_riscv_gstage_mode(kvm_riscv_gstage_max_pgd_levels) <<
-			      HGATP_MODE_SHIFT) | HGATP_VMID);
+		   HGATP_MODE_SHIFT) | HGATP_VMID);
 	vmid_bits = csr_read(CSR_HGATP);
 	vmid_bits = (vmid_bits & HGATP_VMID) >> HGATP_VMID_SHIFT;
 	vmid_bits = fls_long(vmid_bits);
 	csr_write(CSR_HGATP, 0);
 
-	/* We polluted local TLB so flush all guest TLB */
+	/* Flush the local guest TLB after probing HGATP. */
 	kvm_riscv_local_hfence_gvma_all();
 
-	/* We don't use VMID bits if they are not sufficient */
-	if ((1UL << vmid_bits) < num_possible_cpus())
+	/*
+	 * VMID 0 is reserved.  During rollover every possible CPU may
+	 * reserve one hardware VMID, and we still need at least one VMID
+	 * available for a new allocation.
+	 */
+	if (vmid_bits && NUM_VMIDS - 1 <= num_possible_cpus())
 		vmid_bits = 0;
 }
 
@@ -46,80 +78,298 @@ unsigned long kvm_riscv_gstage_vmid_bits(void)
 	return vmid_bits;
 }
 
+int __init kvm_riscv_gstage_vmid_alloc_init(void)
+{
+	int cpu;
+
+	if (!vmid_bits)
+		return 0;
+
+	vmid_map = bitmap_zalloc(NUM_VMIDS, GFP_KERNEL);
+	if (!vmid_map)
+		return -ENOMEM;
+
+	vmid_cur_idx = 1;
+
+	/* Hardware VMID 0 is reserved. */
+	__set_bit(0, vmid_map);
+
+	atomic64_set(&vmid_generation, VMID_FIRST_VERSION);
+
+	for_each_possible_cpu(cpu) {
+		atomic64_set(&per_cpu(active_vmids, cpu), 0);
+		per_cpu(reserved_vmids, cpu) = 0;
+	}
+
+	/*
+	 * Every CPU performs an initial local invalidation before its
+	 * first VMID activation.
+	 */
+	cpumask_copy(&tlb_flush_pending, cpu_possible_mask);
+
+	return 0;
+}
+
+void kvm_riscv_gstage_vmid_alloc_free(void)
+{
+	bitmap_free(vmid_map);
+	vmid_map = NULL;
+}
+
 int kvm_riscv_gstage_vmid_init(struct kvm *kvm)
 {
-	/* Mark the initial VMID and VMID version invalid */
-	kvm->arch.vmid.vmid_version = 0;
-	kvm->arch.vmid.vmid = 0;
+	atomic64_set(&kvm->arch.vmid.id, 0);
 
 	return 0;
 }
 
 bool kvm_riscv_gstage_vmid_ver_changed(struct kvm_vmid *vmid)
 {
+	u64 id;
+
 	if (!vmid_bits)
 		return false;
 
-	return unlikely(READ_ONCE(vmid->vmid_version) !=
-			READ_ONCE(vmid_version));
+	id = atomic64_read(&vmid->id);
+
+	return unlikely(!vmid_gen_match(id));
 }
 
-static void __local_hfence_gvma_all(void *info)
+/*
+ * Called with vmid_lock held after vmid_generation has already been
+ * advanced.
+ *
+ * No remote CPU is interrupted here.  Instead, preserve every VMID
+ * which may still be used by a CPU and queue a local invalidation for
+ * that CPU's next VMID activation.
+ */
+static void flush_context(void)
 {
-	kvm_riscv_local_hfence_gvma_all();
+	u64 vmid;
+	int cpu;
+
+	bitmap_zero(vmid_map, NUM_VMIDS);
+	__set_bit(0, vmid_map);
+
+	for_each_possible_cpu(cpu) {
+		vmid = atomic64_xchg(&per_cpu(active_vmids, cpu), 0);
+
+		/*
+		 * The CPU may already have been caught by an earlier rollover
+		 * without performing another activation since then.  In that
+		 * case reserved_vmids is the only record of the old context.
+		 */
+		if (!vmid)
+			vmid = per_cpu(reserved_vmids, cpu);
+
+		if (vmid)
+			__set_bit(kvm_riscv_gstage_vmid_hwid(vmid),
+				  vmid_map);
+
+		per_cpu(reserved_vmids, cpu) = vmid;
+	}
+
+	cpumask_copy(&tlb_flush_pending, cpu_possible_mask);
+}
+
+/*
+ * Update every reserved copy of an old software VMID.
+ *
+ * Do not stop after the first match: the same VM may have been active
+ * on more than one CPU when rollover occurred.
+ */
+static bool check_update_reserved_vmid(u64 old_vmid, u64 new_vmid)
+{
+	bool hit = false;
+	int cpu;
+
+	for_each_possible_cpu(cpu) {
+		if (per_cpu(reserved_vmids, cpu) == old_vmid) {
+			per_cpu(reserved_vmids, cpu) = new_vmid;
+			hit = true;
+		}
+	}
+
+	return hit;
+}
+
+/*
+ * Allocate/promote a software VMID.
+ *
+ * vmid_lock must be held by the caller.
+ */
+static u64 new_vmid_locked(struct kvm_vmid *kvm_vmid)
+{
+	u64 vmid = atomic64_read(&kvm_vmid->id);
+	u64 generation = atomic64_read(&vmid_generation);
+	u64 new_vmid;
+	unsigned long idx;
+
+	if (vmid) {
+		new_vmid = generation |
+			   kvm_riscv_gstage_vmid_hwid(vmid);
+
+		/*
+		 * The old VMID is still protected by one or more CPUs.
+		 * Keep the same hardware VMID and only promote generation.
+		 */
+		if (check_update_reserved_vmid(vmid, new_vmid))
+			return new_vmid;
+
+		/*
+		 * The VM had a VMID in an older generation.  Reuse the same
+		 * hardware number if it has not already been claimed.
+		 */
+		idx = kvm_riscv_gstage_vmid_hwid(vmid);
+		if (!__test_and_set_bit(idx, vmid_map))
+			return new_vmid;
+	}
+
+	/*
+	 * Find a free VMID in the current generation.
+	 */
+	idx = find_next_zero_bit(vmid_map, NUM_VMIDS, vmid_cur_idx);
+	if (idx != NUM_VMIDS)
+		goto set_vmid;
+
+	/*
+	 * No free VMID.  Start a new generation, preserve all CPU-local
+	 * users, and defer each CPU's flush until its next activation.
+	 */
+	generation += VMID_FIRST_VERSION;
+
+	/* Software VMID 0 is reserved as the invalid identifier. */
+	if (unlikely(!generation))
+		generation = VMID_FIRST_VERSION;
+
+	atomic64_xchg(&vmid_generation, generation);
+	flush_context();
+
+	/*
+	 * NUM_VMIDS - 1 > num_possible_cpus(), therefore rollover must
+	 * leave at least one allocatable hardware VMID.
+	 */
+	idx = find_next_zero_bit(vmid_map, NUM_VMIDS, 1);
+
+set_vmid:
+	__set_bit(idx, vmid_map);
+	vmid_cur_idx = idx;
+
+	return generation | idx;
 }
 
 void kvm_riscv_gstage_vmid_update(struct kvm_vcpu *vcpu)
 {
-	unsigned long i;
+	struct kvm_vmid *kvm_vmid = &vcpu->kvm->arch.vmid;
+	atomic64_t *active;
 	struct kvm_vcpu *v;
-	struct kvm_vmid *vmid = &vcpu->kvm->arch.vmid;
+	unsigned long flags;
+	u64 vmid;
+	u64 old_active_vmid;
+	unsigned long i;
+	unsigned int cpu;
+	bool vmid_changed = false;
 
-	if (!kvm_riscv_gstage_vmid_ver_changed(vmid))
+	if (!vmid_bits)
 		return;
 
-	spin_lock(&vmid_lock);
+	/*
+	 * active_vmids and tlb_flush_pending are per-CPU admission state.
+	 * They must refer to the same CPU for the complete activation.
+	 */
+	preempt_disable();
+
+	cpu = smp_processor_id();
+	active = this_cpu_ptr(&active_vmids);
+
+	vmid = atomic64_read(&kvm_vmid->id);
+	old_active_vmid = atomic64_read(active);
 
 	/*
-	 * We need to re-check the vmid_version here to ensure that if
-	 * another vcpu already allocated a valid vmid for this vm.
+	 * Fast path.
+	 *
+	 * The cmpxchg races with flush_context()'s xchg on the same
+	 * per-CPU atomic.  Either this activation is captured by rollover,
+	 * or rollover clears active first and the cmpxchg fails.
 	 */
-	if (!kvm_riscv_gstage_vmid_ver_changed(vmid)) {
-		spin_unlock(&vmid_lock);
-		return;
+	if (old_active_vmid &&
+	    vmid_gen_match(vmid) &&
+	    atomic64_cmpxchg(active, old_active_vmid, vmid) ==
+	    old_active_vmid)
+		goto out;
+
+	raw_spin_lock_irqsave(&vmid_lock, flags);
+
+	/*
+	 * Re-read under the allocator lock because another vCPU of this VM
+	 * may already have promoted or allocated the shared VMID.
+	 */
+	vmid = atomic64_read(&kvm_vmid->id);
+
+	if (!vmid_gen_match(vmid)) {
+		vmid = new_vmid_locked(kvm_vmid);
+		atomic64_set(&kvm_vmid->id, vmid);
+		vmid_changed = true;
 	}
 
-	/* First user of a new VMID version? */
-	if (unlikely(vmid_next == 0)) {
-		WRITE_ONCE(vmid_version, READ_ONCE(vmid_version) + 1);
-		vmid_next = 1;
+	/*
+	 * Even if another vCPU already updated the shared VMID, this CPU
+	 * must still discharge its own rollover flush obligation.
+	 */
+	if (cpumask_test_and_clear_cpu(cpu, &tlb_flush_pending))
+		kvm_riscv_local_hfence_gvma_all();
 
-		/*
-		 * We ran out of VMIDs so we increment vmid_version and
-		 * start assigning VMIDs from 1.
-		 *
-		 * This also means existing VMIDs assignment to all Guest
-		 * instances is invalid and we have force VMID re-assignement
-		 * for all Guest instances. The Guest instances that were not
-		 * running will automatically pick-up new VMIDs because will
-		 * call kvm_riscv_gstage_vmid_update() whenever they enter
-		 * in-kernel run loop. For Guest instances that are already
-		 * running, we force VM exits on all host CPUs using IPI and
-		 * flush all Guest TLBs.
-		 */
-		on_each_cpu_mask(cpu_online_mask, __local_hfence_gvma_all,
-				 NULL, 1);
+	/*
+	 * Publish the CPU-local active identity only after the required
+	 * local invalidation is complete.
+	 */
+	atomic64_set(active, vmid);
+
+	raw_spin_unlock_irqrestore(&vmid_lock, flags);
+
+	/*
+	 * A new software VMID may contain a different hardware VMID, so
+	 * request an HGATP update for every vCPU in the VM.
+	 */
+	if (vmid_changed) {
+		kvm_for_each_vcpu(i, v, vcpu->kvm)
+			kvm_make_request(KVM_REQ_UPDATE_HGATP, v);
 	}
 
-	vmid->vmid = vmid_next;
-	vmid_next++;
-	vmid_next &= (1 << vmid_bits) - 1;
+out:
+	preempt_enable();
+}
+
+/*
+ * CPU virtualization/CSR state is being discarded.
+ *
+ * Withdraw fast-path eligibility, but conservatively preserve the last
+ * identity instead of making its hardware VMID immediately reusable.
+ */
+void kvm_riscv_gstage_vmid_cpu_invalidate(void)
+{
+	unsigned long flags;
+	u64 vmid;
+	unsigned int cpu;
 
-	WRITE_ONCE(vmid->vmid_version, READ_ONCE(vmid_version));
+	if (!vmid_bits)
+		return;
 
-	spin_unlock(&vmid_lock);
+	lockdep_assert_preemption_disabled();
+
+	cpu = smp_processor_id();
+
+	raw_spin_lock_irqsave(&vmid_lock, flags);
+
+	vmid = atomic64_xchg(this_cpu_ptr(&active_vmids), 0);
+
+	if (vmid)
+		per_cpu(reserved_vmids, cpu) = vmid;
+
+	/*
+	 * If active was already zero, retain the old reserved entry.
+	 */
+	cpumask_set_cpu(cpu, &tlb_flush_pending);
 
-	/* Request G-stage page table update for all VCPUs */
-	kvm_for_each_vcpu(i, v, vcpu->kvm)
-		kvm_make_request(KVM_REQ_UPDATE_HGATP, v);
+	raw_spin_unlock_irqrestore(&vmid_lock, flags);
 }

3/2 2026/09/10 17:50 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 11821
							output: 83
							thoughts: 649
						

Thoughts:
**Analysis of Patch Applicability for Fuzzing**

Okay, let's see. First, I need to understand what this patch touches and whether it's relevant for fuzzing on our target architecture, which is `amd64`. I see that the patch modifies files exclusively within the `arch/riscv/` directory. Specifically, it involves changes to `kvm_gstage.h`, `kvm_vmid.h`, `main.c`, `mmu.c`, `tlb.c`, `vcpu.c`, `vcpu_sbi_replace.c`, `vcpu_sbi_v01.c`, and `vmid.c` – all RISC-V KVM implementation details.

Now, I recall the rules. We only *don't* fuzz if the changes fall into certain categories. The relevant rule here is about unreachable code paths, specifically code exclusive to architectures other than our target. And that's precisely what we have. These RISC-V specific files are definitely not going to be compiled or executed on `amd64`. They're completely isolated to the RISC-V architecture. I'm going to double-check that by listing all the files:

1.  `arch/riscv/include/asm/kvm_gstage.h`
2.  `arch/riscv/include/asm/kvm_vmid.h`
3.  `arch/riscv/kvm/main.c`
4.  `arch/riscv/kvm/mmu.c`
5.  `arch/riscv/kvm/tlb.c`
6.  `arch/riscv/kvm/vcpu.c`
7.  `arch/riscv/kvm/vcpu_sbi_replace.c`
8.  `arch/riscv/kvm/vcpu_sbi_v01.c`
9.  `arch/riscv/kvm/vmid.c`

Yes, all nine files are undeniably within `arch/riscv/`. On `amd64`, these files will remain untouched, not built, and completely unreachable.

Therefore, the verdict is clear. This patch is irrelevant for fuzzing on `amd64`. `WorthFuzzing` is `false`. There are no specific configurations to enable, nor any specific symbols that are useful, so `EnableConfigs` and `FocusSymbols` are empty. The reasoning is solid: the changes are RISC-V specific, and that's not our target.

I'm comfortable with this. It's a straightforward case. Let's do a quick final check... yep, everything looks good. Now, let's call `set-results`.



4/2 2026/09/10 17:50 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "All modifications in this patch series are strictly confined to the RISC-V architecture (arch/riscv/), specifically the RISC-V KVM Stage-2 VMID management. They cannot be built or executed on the target architecture (amd64).",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "All modifications in this patch series are strictly confined to the RISC-V architecture (arch/riscv/), specifically the RISC-V KVM Stage-2 VMID management. They cannot be built or executed on the target architecture (amd64).",
  "WorthFuzzing": false
}

5/1 2026/09/10 17:50 action If
0m Args:
{
  "WorthFuzzing": false
}

Results:
null

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