AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/02 18:16 flow patch-triage
2m Args:
null

Results:
{
  "EnableConfigs": [
    "KVM"
  ],
  "FocusSymbols": [
    "kvm_init_nv_sysregs",
    "limit_nv_id_reg",
    "nvhcr_el2_visibility",
    "get_reg_fixed_bits",
    "fixup_nv_guest_exit"
  ],
  "Reasoning": "The patch implements support for ARM64 Nested Virtualization v3 (FEAT_NV3) and FEAT_NV2p1 in KVM. It modifies core KVM nested virtualization logic, including sysreg handling (e.g., NVHCR_EL2, CPTR_EL2, CNTHCTL_EL2), trap configuration, and guest exit fixups. This is reachable and functional code that can be tested by running KVM guests with nested virtualization enabled.",
  "WorthFuzzing": true
}

1/1 2026/07/02 18:16 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 08da62a3cedbf88ff3e68aac97bd1d0ba9a7db03\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Jul 2 18:16:15 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h\nindex a57870fa96db5b..a42683af79fb5d 100644\n--- a/arch/arm64/include/asm/cpufeature.h\n+++ b/arch/arm64/include/asm/cpufeature.h\n@@ -968,6 +968,7 @@ struct arm64_ftr_reg *get_arm64_ftr_reg(u32 sys_id);\n extern struct arm64_ftr_override id_aa64mmfr0_override;\n extern struct arm64_ftr_override id_aa64mmfr1_override;\n extern struct arm64_ftr_override id_aa64mmfr2_override;\n+extern struct arm64_ftr_override id_aa64mmfr4_override;\n extern struct arm64_ftr_override id_aa64pfr0_override;\n extern struct arm64_ftr_override id_aa64pfr1_override;\n extern struct arm64_ftr_override id_aa64zfr0_override;\ndiff --git a/arch/arm64/include/asm/kvm_arm.h b/arch/arm64/include/asm/kvm_arm.h\nindex 3f9233b5a13081..f6cd8510479474 100644\n--- a/arch/arm64/include/asm/kvm_arm.h\n+++ b/arch/arm64/include/asm/kvm_arm.h\n@@ -287,21 +287,6 @@\n \t\t\t\t GENMASK(19, 18) |\t\\\n \t\t\t\t GENMASK(15, 0))\n \n-/*\n- * Polarity masks for HCRX_EL2, limited to the bits that we know about\n- * at this point in time. It doesn't mean that we actually *handle*\n- * them, but that at least those that are not advertised to a guest\n- * will be RES0 for that guest.\n- */\n-#define __HCRX_EL2_MASK\t\t(BIT_ULL(6))\n-#define __HCRX_EL2_nMASK\t(GENMASK_ULL(24, 14) | \\\n-\t\t\t\t GENMASK_ULL(11, 7)  | \\\n-\t\t\t\t GENMASK_ULL(5, 0))\n-#define __HCRX_EL2_RES0\t\t~(__HCRX_EL2_nMASK | __HCRX_EL2_MASK)\n-#define __HCRX_EL2_RES1\t\t~(__HCRX_EL2_nMASK | \\\n-\t\t\t\t  __HCRX_EL2_MASK  | \\\n-\t\t\t\t  __HCRX_EL2_RES0)\n-\n /* Hyp Prefetch Fault Address Register (HPFAR/HDFAR) */\n #define HPFAR_MASK\t(~UL(0xf))\n /*\ndiff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h\nindex 5bf3d7e1d92c77..d6f432b1558f5f 100644\n--- a/arch/arm64/include/asm/kvm_emulate.h\n+++ b/arch/arm64/include/asm/kvm_emulate.h\n@@ -266,6 +266,26 @@ static inline bool vserror_state_is_nested(struct kvm_vcpu *vcpu)\n \t       (__vcpu_sys_reg(vcpu, HCRX_EL2) \u0026 HCRX_EL2_TMEA);\n }\n \n+static inline bool kvm_has_nv2(struct kvm *kvm)\n+{\n+\treturn (cpus_have_final_cap(ARM64_HAS_NESTED_VIRT) \u0026\u0026\n+\t\tkvm_has_feat(kvm, ID_AA64MMFR4_EL1, NV_frac, NV2_ONLY));\n+}\n+\n+static inline bool kvm_has_nv3(struct kvm *kvm)\n+{\n+\treturn (cpus_have_final_cap(ARM64_HAS_NESTED_VIRT) \u0026\u0026\n+\t\tcpus_have_final_cap(ARM64_HAS_NV3) \u0026\u0026\n+\t\tkvm_has_feat(kvm, ID_AA64MMFR4_EL1, NV_frac, NV3));\n+}\n+\n+static inline bool is_nested_nv3_ctxt(struct kvm_vcpu *vcpu)\n+{\n+\treturn (has_vhe() \u0026\u0026 kvm_has_nv3(vcpu-\u003ekvm) \u0026\u0026 is_nested_ctxt(vcpu) \u0026\u0026\n+\t\t(__vcpu_sys_reg(vcpu, HCR_EL2) \u0026 HCR_EL2_NV) \u0026\u0026\n+\t\t(__vcpu_sys_reg(vcpu, HCRX_EL2) \u0026 HCRX_EL2_NVTGE));\n+}\n+\n /*\n  * The layout of SPSR for an AArch32 state is different when observed from an\n  * AArch64 SPSR_ELx or an AArch32 SPSR_*. This function generates the AArch32\n@@ -617,7 +637,7 @@ static __always_inline void kvm_incr_pc(struct kvm_vcpu *vcpu)\n  */\n static inline u64 vcpu_sanitised_cptr_el2(const struct kvm_vcpu *vcpu)\n {\n-\tu64 cptr = __vcpu_sys_reg(vcpu, CPTR_EL2);\n+\tu64 cptr = vcpu_read_sys_reg(vcpu, CPTR_EL2);\n \n \tif (!vcpu_el2_e2h_is_set(vcpu))\n \t\tcptr = translate_cptr_el2_to_cpacr_el1(cptr);\n@@ -686,6 +706,25 @@ static inline void vcpu_set_hcrx(struct kvm_vcpu *vcpu)\n \n \t\tif (kvm_has_feat(kvm, ID_AA64ISAR1_EL1, LS64, LS64_V))\n \t\t\tvcpu-\u003earch.hcrx_el2 |= HCRX_EL2_EnASR;\n+\n+\t\t/*\n+\t\t * NV3 is a host-specific extension, and we always use it\n+\t\t * when present and that the guest uses NV. It may be be\n+\t\t * hidden from the guest though.\n+\t\t */\n+\t\tif (cpus_have_final_cap(ARM64_HAS_NV3) \u0026\u0026\n+\t\t    vcpu_has_nv(vcpu) \u0026\u0026 vcpu_el2_e2h_is_set(vcpu)) {\n+\t\t\tvcpu-\u003earch.hcrx_el2 |= HCRX_EL2_NVTGE;\n+\n+\t\t\t/*\n+\t\t\t * If the guest is NV2-capable, then we need to see\n+\t\t\t * all the TLBIs, as configured in HCR_EL2.\n+\t\t\t * Otherwise, relax the TLBI traps to only TGE=0.\n+\t\t\t */\n+\t\t\tif (!kvm_has_nv2(vcpu-\u003ekvm))\n+\t\t\t\tvcpu-\u003earch.hcrx_el2 |= (HCRX_EL2_NVnTTLB  |\n+\t\t\t\t\t\t\tHCRX_EL2_NVnTTLBIS);\n+\t\t}\n \t}\n }\n #endif /* __ARM64_KVM_EMULATE_H__ */\ndiff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h\nindex bae2c4f92ef5c0..2648c8a717ba05 100644\n--- a/arch/arm64/include/asm/kvm_host.h\n+++ b/arch/arm64/include/asm/kvm_host.h\n@@ -543,6 +543,7 @@ enum vcpu_sysreg {\n \tMDCR_EL2,\t/* Monitor Debug Configuration Register (EL2) */\n \tCNTHCTL_EL2,\t/* Counter-timer Hypervisor Control register */\n \tZCR_EL2,\t/* SVE Control Register (EL2) */\n+\tHCR_EL2,\t/* Hypervisor Control Register */\n \n \t/* Any VNCR-capable reg goes after this point */\n \tMARKER(__VNCR_START__),\n@@ -571,7 +572,7 @@ enum vcpu_sysreg {\n \tVNCR(TFSR_EL1),\t/* Tag Fault Status Register (EL1) */\n \tVNCR(VPIDR_EL2),/* Virtualization Processor ID Register */\n \tVNCR(VMPIDR_EL2),/* Virtualization Multiprocessor ID Register */\n-\tVNCR(HCR_EL2),\t/* Hypervisor Configuration Register */\n+\tVNCR(NVHCR_EL2),/* NV Hypervisor Configuration Register */\n \tVNCR(HSTR_EL2),\t/* Hypervisor System Trap Register */\n \tVNCR(VTTBR_EL2),/* Virtualization Translation Table Base Register */\n \tVNCR(VTCR_EL2),\t/* Virtualization Translation Control Register */\ndiff --git a/arch/arm64/include/asm/vncr_mapping.h b/arch/arm64/include/asm/vncr_mapping.h\nindex 14366d35ce82f0..9e8a49fa8b638f 100644\n--- a/arch/arm64/include/asm/vncr_mapping.h\n+++ b/arch/arm64/include/asm/vncr_mapping.h\n@@ -11,7 +11,7 @@\n #define VNCR_VTCR_EL2           0x040\n #define VNCR_VMPIDR_EL2         0x050\n #define VNCR_CNTVOFF_EL2        0x060\n-#define VNCR_HCR_EL2            0x078\n+#define VNCR_NVHCR_EL2          0x078\n #define VNCR_HSTR_EL2           0x080\n #define VNCR_VPIDR_EL2          0x088\n #define VNCR_TPIDR_EL2          0x090\ndiff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c\nindex 9a22df0c5120f3..14fbfa8e6b7b50 100644\n--- a/arch/arm64/kernel/cpufeature.c\n+++ b/arch/arm64/kernel/cpufeature.c\n@@ -785,6 +785,7 @@ static const struct arm64_ftr_bits ftr_raz[] = {\n struct arm64_ftr_override __read_mostly id_aa64mmfr0_override;\n struct arm64_ftr_override __read_mostly id_aa64mmfr1_override;\n struct arm64_ftr_override __read_mostly id_aa64mmfr2_override;\n+struct arm64_ftr_override __read_mostly id_aa64mmfr4_override;\n struct arm64_ftr_override __read_mostly id_aa64pfr0_override;\n struct arm64_ftr_override __read_mostly id_aa64pfr1_override;\n struct arm64_ftr_override __read_mostly id_aa64zfr0_override;\n@@ -858,7 +859,8 @@ static const struct __ftr_reg_entry {\n \tARM64_FTR_REG_OVERRIDE(SYS_ID_AA64MMFR2_EL1, ftr_id_aa64mmfr2,\n \t\t\t       \u0026id_aa64mmfr2_override),\n \tARM64_FTR_REG(SYS_ID_AA64MMFR3_EL1, ftr_id_aa64mmfr3),\n-\tARM64_FTR_REG(SYS_ID_AA64MMFR4_EL1, ftr_id_aa64mmfr4),\n+\tARM64_FTR_REG_OVERRIDE(SYS_ID_AA64MMFR4_EL1, ftr_id_aa64mmfr4,\n+\t\t\t       \u0026id_aa64mmfr4_override),\n \n \t/* Op1 = 0, CRn = 10, CRm = 4 */\n \tARM64_FTR_REG(SYS_MPAMIDR_EL1, ftr_mpamidr),\n@@ -2620,6 +2622,20 @@ static const struct arm64_cpu_capabilities arm64_features[] = {\n \t\t\t{ /* Sentinel */ }\n \t\t},\n \t},\n+\t{\n+\t\t.desc = \"FEAT_NV2p1\",\n+\t\t.capability = ARM64_HAS_NV2P1,\n+\t\t.type = ARM64_CPUCAP_SYSTEM_FEATURE,\n+\t\t.matches = has_cpuid_feature,\n+\t\tARM64_CPUID_FIELDS(ID_AA64MMFR4_EL1, NV_frac, NV2P1)\n+\t},\n+\t{\n+\t\t.desc = \"FEAT_NV3\",\n+\t\t.capability = ARM64_HAS_NV3,\n+\t\t.type = ARM64_CPUCAP_SYSTEM_FEATURE,\n+\t\t.matches = has_cpuid_feature,\n+\t\tARM64_CPUID_FIELDS(ID_AA64MMFR4_EL1, NV_frac, NV3)\n+\t},\n \t{\n \t\t.capability = ARM64_HAS_32BIT_EL0_DO_NOT_USE,\n \t\t.type = ARM64_CPUCAP_SYSTEM_FEATURE,\ndiff --git a/arch/arm64/kernel/image-vars.h b/arch/arm64/kernel/image-vars.h\nindex d4c7d45ae6bc83..d15c2cb1b0f28a 100644\n--- a/arch/arm64/kernel/image-vars.h\n+++ b/arch/arm64/kernel/image-vars.h\n@@ -51,6 +51,7 @@ PI_EXPORT_SYM(id_aa64isar2_override);\n PI_EXPORT_SYM(id_aa64mmfr0_override);\n PI_EXPORT_SYM(id_aa64mmfr1_override);\n PI_EXPORT_SYM(id_aa64mmfr2_override);\n+PI_EXPORT_SYM(id_aa64mmfr4_override);\n PI_EXPORT_SYM(id_aa64pfr0_override);\n PI_EXPORT_SYM(id_aa64pfr1_override);\n PI_EXPORT_SYM(id_aa64smfr0_override);\ndiff --git a/arch/arm64/kernel/pi/idreg-override.c b/arch/arm64/kernel/pi/idreg-override.c\nindex bc57b290e5e7ba..4e47616bcac23a 100644\n--- a/arch/arm64/kernel/pi/idreg-override.c\n+++ b/arch/arm64/kernel/pi/idreg-override.c\n@@ -106,6 +106,15 @@ static const struct ftr_set_desc mmfr2 __prel64_initconst = {\n \t},\n };\n \n+static const struct ftr_set_desc mmfr4 __prel64_initconst = {\n+\t.name\t\t= \"id_aa64mmfr4\",\n+\t.override\t= \u0026id_aa64mmfr4_override,\n+\t.fields\t\t= {\n+\t\tFIELD(\"nv_frac\", ID_AA64MMFR4_EL1_NV_frac_SHIFT, NULL),\n+\t\t{}\n+\t},\n+};\n+\n static bool __init pfr0_sve_filter(u64 val)\n {\n \t/*\n@@ -220,6 +229,7 @@ PREL64(const struct ftr_set_desc, reg) regs[] __prel64_initconst = {\n \t{ \u0026mmfr0\t},\n \t{ \u0026mmfr1\t},\n \t{ \u0026mmfr2\t},\n+\t{ \u0026mmfr4\t},\n \t{ \u0026pfr0 \t},\n \t{ \u0026pfr1 \t},\n \t{ \u0026isar1\t},\ndiff --git a/arch/arm64/kvm/arch_timer.c b/arch/arm64/kvm/arch_timer.c\nindex 4155fe89b58a17..db60facad9f3c0 100644\n--- a/arch/arm64/kvm/arch_timer.c\n+++ b/arch/arm64/kvm/arch_timer.c\n@@ -876,8 +876,14 @@ static void timer_set_traps(struct kvm_vcpu *vcpu, struct timer_map *map)\n \tassign_clear_set_bit(tvt02, CNTHCTL_EL1NVVCT, clr, set);\n \tassign_clear_set_bit(tpt02, CNTHCTL_EL1NVPCT, clr, set);\n \n-\t/* This only happens on VHE, so use the CNTHCTL_EL2 accessor. */\n-\tsysreg_clear_set(cnthctl_el2, clr, set);\n+\t/*\n+\t * This only happens on VHE, so use the CNTHCTL_EL2 accessor, unless\n+\t * we are sure CNTKCTL_EL1 is completely stateful with FEAT_NV2p1.\n+\t */\n+\tif (!cpus_have_final_cap(ARM64_HAS_NV2P1))\n+\t\tsysreg_clear_set(cnthctl_el2, clr, set);\n+\telse\n+\t\tsysreg_clear_set(cntkctl_el1, clr, set);\n }\n \n void kvm_timer_vcpu_load(struct kvm_vcpu *vcpu)\ndiff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c\nindex 0622162b089e50..7e864791427234 100644\n--- a/arch/arm64/kvm/config.c\n+++ b/arch/arm64/kvm/config.c\n@@ -225,6 +225,7 @@ struct reg_feat_map_desc {\n #define FEAT_HCX\t\tID_AA64MMFR1_EL1, HCX, IMP\n #define FEAT_S2PIE\t\tID_AA64MMFR3_EL1, S2PIE, IMP\n #define FEAT_GCIE\t\tID_AA64PFR2_EL1, GCIE, IMP\n+#define FEAT_NV3\t\tID_AA64MMFR4_EL1, NV_frac, NV3\n \n static bool not_feat_aa64el3(struct kvm *kvm)\n {\n@@ -904,6 +905,12 @@ static const DECLARE_FEAT_MAP_FGT(hdfgwtr2_desc, hdfgwtr2_masks,\n \n \n static const struct reg_bits_to_feat_map hcrx_feat_map[] = {\n+\tNEEDS_FEAT(HCRX_EL2_NVTGE\t\t|\n+\t\t   HCRX_EL2_NVnTTLB\t\t|\n+\t\t   HCRX_EL2_NVnTTLBIS\t\t|\n+\t\t   HCRX_EL2_NVnTTLBOS,\n+\t\t   FEAT_NV3),\n+\tNEEDS_FEAT(HCRX_EL2_SRMASKEn, FEAT_SRMASK),\n \tNEEDS_FEAT(HCRX_EL2_PACMEn, feat_pauth_lr),\n \tNEEDS_FEAT(HCRX_EL2_EnFPM, FEAT_FPMR),\n \tNEEDS_FEAT(HCRX_EL2_GCSEn, FEAT_GCS),\n@@ -933,7 +940,7 @@ static const struct reg_bits_to_feat_map hcrx_feat_map[] = {\n };\n \n \n-static const DECLARE_FEAT_MAP(hcrx_desc, __HCRX_EL2,\n+static const DECLARE_FEAT_MAP(hcrx_desc, HCRX_EL2,\n \t\t\t      hcrx_feat_map, FEAT_HCX);\n \n static const struct reg_bits_to_feat_map hcr_feat_map[] = {\n@@ -1010,6 +1017,9 @@ static const struct reg_bits_to_feat_map hcr_feat_map[] = {\n static const DECLARE_FEAT_MAP(hcr_desc, HCR_EL2,\n \t\t\t      hcr_feat_map, FEAT_AA64EL2);\n \n+static const DECLARE_FEAT_MAP(nvhcr_desc, NVHCR_EL2,\n+\t\t\t      hcr_feat_map, FEAT_NV3);\n+\n static const struct reg_bits_to_feat_map sctlr2_feat_map[] = {\n \tNEEDS_FEAT(SCTLR2_EL1_NMEA\t|\n \t\t   SCTLR2_EL1_EASE,\n@@ -1384,6 +1394,7 @@ void __init check_feature_map(void)\n \tcheck_reg_desc(\u0026hdfgwtr2_desc);\n \tcheck_reg_desc(\u0026hcrx_desc);\n \tcheck_reg_desc(\u0026hcr_desc);\n+\tcheck_reg_desc(\u0026nvhcr_desc);\n \tcheck_reg_desc(\u0026sctlr2_desc);\n \tcheck_reg_desc(\u0026tcr2_el2_desc);\n \tcheck_reg_desc(\u0026sctlr_el1_desc);\n@@ -1579,11 +1590,21 @@ struct resx get_reg_fixed_bits(struct kvm *kvm, enum vcpu_sysreg reg)\n \t\tbreak;\n \tcase HCRX_EL2:\n \t\tresx = compute_reg_resx_bits(kvm, \u0026hcrx_desc, 0, 0);\n-\t\tresx.res1 |= __HCRX_EL2_RES1;\n \t\tbreak;\n \tcase HCR_EL2:\n \t\tresx = compute_reg_resx_bits(kvm, \u0026hcr_desc, 0, 0);\n \t\tbreak;\n+\tcase NVHCR_EL2:\n+\t\t/*\n+\t\t * Only apply sanitisation if we do have FEAT_NV3.\n+\t\t * Otherwise, the register aliases with HCR_EL2 in VNCR,\n+\t\t * and we're better off relying on data transfers between\n+\t\t * NVHCR_EL2 and HCR_EL2 to sanitise things.\n+\t\t */\n+\t\tresx = (kvm_has_nv3(kvm) ?\n+\t\t\tcompute_reg_resx_bits(kvm, \u0026nvhcr_desc, 0, 0) :\n+\t\t\t(typeof(resx)){});\n+\t\tbreak;\n \tcase SCTLR2_EL1:\n \tcase SCTLR2_EL2:\n \t\tresx = compute_reg_resx_bits(kvm, \u0026sctlr2_desc, 0, 0);\ndiff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c\nindex 3c82f392845d14..f5dc578d8c9856 100644\n--- a/arch/arm64/kvm/emulate-nested.c\n+++ b/arch/arm64/kvm/emulate-nested.c\n@@ -90,6 +90,7 @@ enum cgt_group_id {\n \tCGT_HCRX_EnFPM,\n \tCGT_HCRX_TCR2En,\n \tCGT_HCRX_SCTLR2En,\n+\tCGT_HCRX_nNVTGE,\n \n \tCGT_CNTHCTL_EL1TVT,\n \tCGT_CNTHCTL_EL1TVCT,\n@@ -121,6 +122,8 @@ enum cgt_group_id {\n \tCGT_MDCR_TDE_TDRA,\n \tCGT_MDCR_TDCC_TDE_TDA,\n \n+\tCGT_HCR_NV_HCRX_nNVTGE,\n+\n \tCGT_ICH_HCR_TC_TDIR,\n \n \t/*\n@@ -413,6 +416,12 @@ static const struct trap_bits coarse_trap_bits[] = {\n \t\t.mask\t\t= HCRX_EL2_SCTLR2En,\n \t\t.behaviour\t= BEHAVE_FORWARD_RW,\n \t},\n+\t[CGT_HCRX_nNVTGE] = {\n+\t\t.index\t\t= HCRX_EL2,\n+\t\t.value\t\t= 0,\n+\t\t.mask\t\t= HCRX_EL2_NVTGE,\n+\t\t.behaviour\t= BEHAVE_FORWARD_RW,\n+\t},\n \t[CGT_CNTHCTL_EL1TVT] = {\n \t\t.index\t\t= CNTHCTL_EL2,\n \t\t.value\t\t= CNTHCTL_EL1TVT,\n@@ -468,6 +477,7 @@ static const enum cgt_group_id *coarse_control_combo[] = {\n \t\t\t\t\tCGT_HCR_TVM, CGT_HCR_TRVM, CGT_HCRX_SCTLR2En),\n \tMCB(CGT_HCR_TPU_TICAB,\t\tCGT_HCR_TPU, CGT_HCR_TICAB),\n \tMCB(CGT_HCR_TPU_TOCU,\t\tCGT_HCR_TPU, CGT_HCR_TOCU),\n+\tMCB(CGT_HCR_NV_HCRX_nNVTGE,\tCGT_HCR_NV, CGT_HCRX_nNVTGE),\n \tMCB(CGT_HCR_NV1_nNV2_ENSCXT,\tCGT_HCR_NV1_nNV2, CGT_HCR_ENSCXT),\n \tMCB(CGT_MDCR_TPM_TPMCR,\t\tCGT_MDCR_TPM, CGT_MDCR_TPMCR),\n \tMCB(CGT_MDCR_TPM_HPMN,\t\tCGT_MDCR_TPM, CGT_MDCR_HPMN),\n@@ -853,6 +863,7 @@ static const struct encoding_to_trap_config encoding_to_cgt[] __initconst = {\n \tSR_TRAP(SYS_SCTLR2_EL2,\t\tCGT_HCR_NV),\n \tSR_RANGE_TRAP(SYS_HCR_EL2,\n \t\t      SYS_HCRX_EL2,\tCGT_HCR_NV),\n+\tSR_TRAP(SYS_NVHCR_EL2,\t\tCGT_HCR_NV_HCRX_nNVTGE),\n \tSR_TRAP(SYS_SMPRIMAP_EL2,\tCGT_HCR_NV),\n \tSR_TRAP(SYS_SMCR_EL2,\t\tCGT_HCR_NV),\n \tSR_RANGE_TRAP(SYS_TTBR0_EL2,\n@@ -2320,7 +2331,6 @@ int __init populate_nv_trap_config(void)\n \tBUILD_BUG_ON(__NR_CGT_GROUP_IDS__ \u003e BIT(TC_CGT_BITS));\n \tBUILD_BUG_ON(__NR_FGT_GROUP_IDS__ \u003e BIT(TC_FGT_BITS));\n \tBUILD_BUG_ON(__NR_FG_FILTER_IDS__ \u003e BIT(TC_FGF_BITS));\n-\tBUILD_BUG_ON(__HCRX_EL2_MASK \u0026 __HCRX_EL2_nMASK);\n \n \tfor (int i = 0; i \u003c ARRAY_SIZE(encoding_to_cgt); i++) {\n \t\tconst struct encoding_to_trap_config *cgt = \u0026encoding_to_cgt[i];\n@@ -2346,10 +2356,6 @@ int __init populate_nv_trap_config(void)\n \t\t}\n \t}\n \n-\tif (__HCRX_EL2_RES0 != HCRX_EL2_RES0)\n-\t\tkvm_info(\"Sanitised HCR_EL2_RES0 = %016llx, expecting %016llx\\n\",\n-\t\t\t __HCRX_EL2_RES0, HCRX_EL2_RES0);\n-\n \tkvm_info(\"nv: %ld coarse grained trap handlers\\n\",\n \t\t ARRAY_SIZE(encoding_to_cgt));\n \ndiff --git a/arch/arm64/kvm/hyp/include/hyp/switch.h b/arch/arm64/kvm/hyp/include/hyp/switch.h\nindex 4bf624a49591df..7b27296c946070 100644\n--- a/arch/arm64/kvm/hyp/include/hyp/switch.h\n+++ b/arch/arm64/kvm/hyp/include/hyp/switch.h\n@@ -108,9 +108,10 @@ static inline void __activate_cptr_traps_vhe(struct kvm_vcpu *vcpu)\n \t * The architecture is a bit crap (what a surprise): an EL2 guest\n \t * writing to CPTR_EL2 via CPACR_EL1 can't set any of TCPAC or TTA,\n \t * as they are RES0 in the guest's view. To work around it, trap the\n-\t * sucker using the very same bit it can't set...\n+\t * sucker using the very same bit it can't set. FEAT_NV2p1 fixes it.\n \t */\n-\tif (vcpu_el2_e2h_is_set(vcpu) \u0026\u0026 is_hyp_ctxt(vcpu))\n+\tif (!cpus_have_final_cap(ARM64_HAS_NV2P1) \u0026\u0026\n+\t    vcpu_el2_e2h_is_set(vcpu) \u0026\u0026 is_hyp_ctxt(vcpu))\n \t\tval |= CPTR_EL2_TCPAC;\n \n \t/*\n@@ -325,6 +326,24 @@ static inline void __deactivate_traps_mpam(void)\n \t\twrite_sysreg_s(MPAMHCR_HOST_FLAGS, SYS_MPAMHCR_EL2);\n }\n \n+/*\n+ * Just like for HCR_EL2, we can't let the guest mess with some of the\n+ * basics we rely on in HCRX_EL2. However, the major difference is that\n+ * HCRX_EL2 only affects EL1, and never EL2 (sudden outburst of sanity, I\n+ * guess). So it is always the guest inflicting it on its own guestx.\n+ *\n+ * Things we don't want to let the guest control are:\n+ *\n+ * - TMEA: That's for us to decide how an SEA is routed, not the guest.\n+ *\n+ * - PTTWI: Similarly, it is for us to decide whether Reduced Coherency for\n+ *   the PTW is a thing. It really isn't.\n+ *\n+ * - EnIDCP128: We don't allow IMPDEF sysregs -- full stop.\n+ */\n+#define NV_HCRX_GUEST_EXCLUDE\t(HCRX_EL2_TMEA\t    | HCRX_EL2_PTTWI | \\\n+\t\t\t\t HCRX_EL2_EnIDCP128)\n+\n static inline void __activate_traps_common(struct kvm_vcpu *vcpu)\n {\n \tstruct kvm_cpu_context *hctxt = host_data_ptr(host_ctxt);\n@@ -350,8 +369,8 @@ static inline void __activate_traps_common(struct kvm_vcpu *vcpu)\n \t\tu64 hcrx = vcpu-\u003earch.hcrx_el2;\n \t\tif (is_nested_ctxt(vcpu)) {\n \t\t\tu64 val = __vcpu_sys_reg(vcpu, HCRX_EL2);\n-\t\t\thcrx |= val \u0026 __HCRX_EL2_MASK;\n-\t\t\thcrx \u0026= ~(~val \u0026 __HCRX_EL2_nMASK);\n+\t\t\thcrx |= (val \u0026 ~NV_HCRX_GUEST_EXCLUDE);\n+\t\t\thcrx \u0026= ~(~val \u0026 ~NV_HCRX_GUEST_EXCLUDE);\n \t\t}\n \n \t\tctxt_sys_reg(hctxt, HCRX_EL2) = read_sysreg_s(SYS_HCRX_EL2);\ndiff --git a/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h b/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h\nindex a17cbe7582de90..c382848d319479 100644\n--- a/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h\n+++ b/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h\n@@ -172,6 +172,10 @@ static inline void __sysreg_save_el1_state(struct kvm_cpu_context *ctxt)\n \n \tif (ctxt_has_sctlr2(ctxt))\n \t\tctxt_sys_reg(ctxt, SCTLR2_EL1) = read_sysreg_el1(SYS_SCTLR2);\n+\n+\t/* Retrieve L2's HCR_EL2, and save it for future use */\n+\tif (is_nested_nv3_ctxt(ctxt_to_vcpu(ctxt)))\n+\t\tctxt_sys_reg(ctxt, NVHCR_EL2) = read_sysreg_s(SYS_NVHCR_EL2);\n }\n \n static inline void __sysreg_save_el2_return_state(struct kvm_cpu_context *ctxt)\n@@ -285,6 +289,13 @@ static inline void __sysreg_restore_el1_state(struct kvm_cpu_context *ctxt,\n \n \tif (ctxt_has_sctlr2(ctxt))\n \t\twrite_sysreg_el1(ctxt_sys_reg(ctxt, SCTLR2_EL1), SYS_SCTLR2);\n+\n+\t/*\n+\t * Publish the L2 view of HCR_EL2 to the HW if L1 is using NV3.\n+\t * Otherwise, the data is already in place in the L1's own VNCR.\n+\t */\n+\tif (is_nested_nv3_ctxt(ctxt_to_vcpu(ctxt)))\n+\t\twrite_sysreg_s(ctxt_sys_reg(ctxt, NVHCR_EL2), SYS_NVHCR_EL2);\n }\n \n /* Read the VCPU state's PSTATE, but translate (v)EL2 to EL1. */\ndiff --git a/arch/arm64/kvm/hyp/vhe/switch.c b/arch/arm64/kvm/hyp/vhe/switch.c\nindex bbe9cebd3d9d57..f129f22f15618f 100644\n--- a/arch/arm64/kvm/hyp/vhe/switch.c\n+++ b/arch/arm64/kvm/hyp/vhe/switch.c\n@@ -70,6 +70,12 @@ static u64 __compute_hcr(struct kvm_vcpu *vcpu)\n \t\tif (!vcpu_el2_e2h_is_set(vcpu))\n \t\t\thcr |= HCR_NV1;\n \n+\t\t/* Publish the guest's view of HCR_EL2 to the HW */\n+\t\tif (cpus_have_final_cap(ARM64_HAS_NV3) \u0026\u0026 vcpu_el2_e2h_is_set(vcpu))\n+\t\t\twrite_sysreg_s(__vcpu_sys_reg(vcpu, HCR_EL2), SYS_NVHCR_EL2);\n+\t\telse\n+\t\t\t__vcpu_assign_sys_reg(vcpu, NVHCR_EL2, __vcpu_sys_reg(vcpu, HCR_EL2));\n+\n \t\t/*\n \t\t * Nothing in HCR_EL2 should impact running in hypervisor\n \t\t * context, apart from bits we have defined as RESx (E2H,\n@@ -339,6 +345,10 @@ static bool kvm_hyp_handle_eret(struct kvm_vcpu *vcpu, u64 *exit_code)\n \tu64 esr = kvm_vcpu_get_esr(vcpu);\n \tu64 spsr, elr, mode;\n \n+\t/* With NV3, the fast path is handled in HW */\n+\tif (cpus_have_final_cap(ARM64_HAS_NV3) \u0026\u0026 vcpu_el2_e2h_is_set(vcpu))\n+\t\treturn false;\n+\n \t/*\n \t * Going through the whole put/load motions is a waste of time\n \t * if this is a VHE guest hypervisor returning to its own\n@@ -349,8 +359,8 @@ static bool kvm_hyp_handle_eret(struct kvm_vcpu *vcpu, u64 *exit_code)\n \t * Unless the trap has to be forwarded further down the line,\n \t * of course...\n \t */\n-\tif ((__vcpu_sys_reg(vcpu, HCR_EL2) \u0026 HCR_NV) ||\n-\t    (__vcpu_sys_reg(vcpu, HFGITR_EL2) \u0026 HFGITR_EL2_ERET))\n+\tif (is_nested_ctxt(vcpu) ||\n+\t    __vcpu_sys_reg(vcpu, HFGITR_EL2) \u0026 HFGITR_EL2_ERET)\n \t\treturn false;\n \n \tspsr = read_sysreg_el1(SYS_SPSR);\n@@ -441,6 +451,9 @@ static bool kvm_hyp_handle_cpacr_el1(struct kvm_vcpu *vcpu, u64 *exit_code)\n \tu64 esr = kvm_vcpu_get_esr(vcpu);\n \tint rt;\n \n+\tif (cpus_have_final_cap(ARM64_HAS_NV2P1))\n+\t\treturn false;\n+\n \tif (!is_hyp_ctxt(vcpu) || esr_sys64_to_sysreg(esr) != SYS_CPACR_EL1)\n \t\treturn false;\n \n@@ -534,19 +547,17 @@ static const exit_handler_fn hyp_exit_handlers[] = {\n \t[0x3F]\t\t\t\t= kvm_hyp_handle_impdef,\n };\n \n-static inline bool fixup_guest_exit(struct kvm_vcpu *vcpu, u64 *exit_code)\n+static void fixup_nv_guest_exit(struct kvm_vcpu *vcpu)\n {\n-\tsynchronize_vcpu_pstate(vcpu);\n-\n \t/*\n \t * If we were in HYP context on entry, adjust the PSTATE view\n \t * so that the usual helpers work correctly. This enforces our\n \t * invariant that the guest's HYP context status is preserved\n \t * across a run.\n \t */\n-\tif (vcpu_has_nv(vcpu) \u0026\u0026\n-\t    unlikely(host_data_test_flag(VCPU_IN_HYP_CONTEXT))) {\n+\tif (unlikely(host_data_test_flag(VCPU_IN_HYP_CONTEXT))) {\n \t\tu64 mode = *vcpu_cpsr(vcpu) \u0026 (PSR_MODE_MASK | PSR_MODE32_BIT);\n+\t\tu64 hcr;\n \n \t\tswitch (mode) {\n \t\tcase PSR_MODE_EL1t:\n@@ -559,11 +570,26 @@ static inline bool fixup_guest_exit(struct kvm_vcpu *vcpu, u64 *exit_code)\n \n \t\t*vcpu_cpsr(vcpu) \u0026= ~(PSR_MODE_MASK | PSR_MODE32_BIT);\n \t\t*vcpu_cpsr(vcpu) |= mode;\n+\n+\t\t/* Publish the latest HCR_EL2 to the emulation */\n+\t\thcr = (cpus_have_final_cap(ARM64_HAS_NV3) \u0026\u0026\n+\t\t       vcpu_el2_e2h_is_set(vcpu)) ?\n+\t\t\tread_sysreg_s(SYS_NVHCR_EL2) :\n+\t\t\t__vcpu_sys_reg(vcpu, NVHCR_EL2);\n+\n+\t\t__vcpu_assign_sys_reg(vcpu, HCR_EL2, hcr);\n \t}\n \n \t/* Apply extreme paranoia! */\n-\tBUG_ON(vcpu_has_nv(vcpu) \u0026\u0026\n-\t       !!host_data_test_flag(VCPU_IN_HYP_CONTEXT) != is_hyp_ctxt(vcpu));\n+\tBUG_ON(!!host_data_test_flag(VCPU_IN_HYP_CONTEXT) != is_hyp_ctxt(vcpu));\n+}\n+\n+static bool fixup_guest_exit(struct kvm_vcpu *vcpu, u64 *exit_code)\n+{\n+\tsynchronize_vcpu_pstate(vcpu);\n+\n+\tif (vcpu_has_nv(vcpu))\n+\t\tfixup_nv_guest_exit(vcpu);\n \n \treturn __fixup_guest_exit(vcpu, exit_code, hyp_exit_handlers);\n }\ndiff --git a/arch/arm64/kvm/hyp/vhe/sysreg-sr.c b/arch/arm64/kvm/hyp/vhe/sysreg-sr.c\nindex be685b63e8cf26..0c4ef1ce32ae71 100644\n--- a/arch/arm64/kvm/hyp/vhe/sysreg-sr.c\n+++ b/arch/arm64/kvm/hyp/vhe/sysreg-sr.c\n@@ -42,10 +42,12 @@ static void __sysreg_save_vel2_state(struct kvm_vcpu *vcpu)\n \t\tu64 val;\n \n \t\t/*\n-\t\t * We don't save CPTR_EL2, as accesses to CPACR_EL1\n-\t\t * are always trapped, ensuring that the in-memory\n-\t\t * copy is always up-to-date. A small blessing...\n+\t\t * Without FEAT_NV2p1, we don't save CPTR_EL2, as accesses\n+\t\t * to CPACR_EL1 are always trapped, ensuring that the\n+\t\t * in-memory copy is always up-to-date. A small blessing...\n \t\t */\n+\t\tif (cpus_have_final_cap(ARM64_HAS_NV2P1))\n+\t\t\t__vcpu_assign_sys_reg(vcpu, CPTR_EL2, read_sysreg_el1(SYS_CPACR));\n \t\t__vcpu_assign_sys_reg(vcpu, SCTLR_EL2,\t read_sysreg_el1(SYS_SCTLR));\n \t\t__vcpu_assign_sys_reg(vcpu, TTBR0_EL2,\t read_sysreg_el1(SYS_TTBR0));\n \t\t__vcpu_assign_sys_reg(vcpu, TTBR1_EL2,\t read_sysreg_el1(SYS_TTBR1));\n@@ -67,11 +69,18 @@ static void __sysreg_save_vel2_state(struct kvm_vcpu *vcpu)\n \t\t * The EL1 view of CNTKCTL_EL1 has a bunch of RES0 bits where\n \t\t * the interesting CNTHCTL_EL2 bits live. So preserve these\n \t\t * bits when reading back the guest-visible value.\n+\t\t *\n+\t\t * While NV2p1 fixes some of that, it makes CNTHCTL_EL2.ECV\n+\t\t * even more broken than it already was with NV2.\n \t\t */\n \t\tval = read_sysreg_el1(SYS_CNTKCTL);\n-\t\tval \u0026= CNTKCTL_VALID_BITS;\n-\t\t__vcpu_rmw_sys_reg(vcpu, CNTHCTL_EL2, \u0026=, ~CNTKCTL_VALID_BITS);\n-\t\t__vcpu_rmw_sys_reg(vcpu, CNTHCTL_EL2, |=, val);\n+\t\tif (!cpus_have_final_cap(ARM64_HAS_NV2P1)) {\n+\t\t\tval \u0026= CNTKCTL_VALID_BITS;\n+\t\t\t__vcpu_rmw_sys_reg(vcpu, CNTHCTL_EL2, \u0026=, ~CNTKCTL_VALID_BITS);\n+\t\t\t__vcpu_rmw_sys_reg(vcpu, CNTHCTL_EL2, |=, val);\n+\t\t} else {\n+\t\t\t__vcpu_assign_sys_reg(vcpu, CNTHCTL_EL2, val);\n+\t\t}\n \t}\n \n \t__vcpu_assign_sys_reg(vcpu, SP_EL2,\t read_sysreg(sp_el1));\ndiff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c\nindex dfb96edbdc43c6..64c8fb82fadf6d 100644\n--- a/arch/arm64/kvm/nested.c\n+++ b/arch/arm64/kvm/nested.c\n@@ -1728,7 +1728,7 @@ u64 limit_nv_id_reg(struct kvm *kvm, u32 reg, u64 val)\n \t\t * You get EITHER\n \t\t *\n \t\t * - FEAT_VHE without FEAT_E2H0\n-\t\t * - FEAT_NV limited to FEAT_NV2\n+\t\t * - FEAT_NV limited to FEAT_NV2(p1)/NV3\n \t\t * - HCR_EL2.NV1 being RES0\n \t\t *\n \t\t * OR\n@@ -1740,7 +1740,13 @@ u64 limit_nv_id_reg(struct kvm *kvm, u32 reg, u64 val)\n \t\tif (test_bit(KVM_ARM_VCPU_HAS_EL2_E2H0, kvm-\u003earch.vcpu_features)) {\n \t\t\tval = 0;\n \t\t} else {\n-\t\t\tval = SYS_FIELD_PREP_ENUM(ID_AA64MMFR4_EL1, NV_frac, NV2_ONLY);\n+\t\t\tif (cpus_have_final_cap(ARM64_HAS_NV3))\n+\t\t\t\tval = ID_REG_LIMIT_FIELD_ENUM(val, ID_AA64MMFR4_EL1, NV_frac, NV3);\n+\t\t\telse if (cpus_have_final_cap(ARM64_HAS_NV2P1))\n+\t\t\t\tval = ID_REG_LIMIT_FIELD_ENUM(val, ID_AA64MMFR4_EL1, NV_frac, NV2P1);\n+\t\t\telse\n+\t\t\t\tval = SYS_FIELD_PREP_ENUM(ID_AA64MMFR4_EL1, NV_frac, NV2_ONLY);\n+\t\t\tval \u0026= ~ID_AA64MMFR4_EL1_E2H0;\n \t\t\tval |= SYS_FIELD_PREP_ENUM(ID_AA64MMFR4_EL1, E2H0, NI_NV1);\n \t\t}\n \t\tbreak;\n@@ -1826,6 +1832,10 @@ int kvm_init_nv_sysregs(struct kvm_vcpu *vcpu)\n \tresx = get_reg_fixed_bits(kvm, HCR_EL2);\n \tset_sysreg_masks(kvm, HCR_EL2, resx);\n \n+\t/* NVHCR_EL2 */\n+\tresx = get_reg_fixed_bits(kvm, NVHCR_EL2);\n+\tset_sysreg_masks(kvm, NVHCR_EL2, resx);\n+\n \t/* HCRX_EL2 */\n \tresx = get_reg_fixed_bits(kvm, HCRX_EL2);\n \tset_sysreg_masks(kvm, HCRX_EL2, resx);\ndiff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c\nindex 5d5c579d457902..0aeb2e736fde37 100644\n--- a/arch/arm64/kvm/sys_regs.c\n+++ b/arch/arm64/kvm/sys_regs.c\n@@ -183,8 +183,6 @@ static void locate_register(const struct kvm_vcpu *vcpu, enum vcpu_sysreg reg,\n \tswitch (reg) {\n \t\tMAPPED_EL2_SYSREG(SCTLR_EL2,   SCTLR_EL1,\n \t\t\t\t  translate_sctlr_el2_to_sctlr_el1\t     );\n-\t\tMAPPED_EL2_SYSREG(CPTR_EL2,    CPACR_EL1,\n-\t\t\t\t  translate_cptr_el2_to_cpacr_el1\t     );\n \t\tMAPPED_EL2_SYSREG(TTBR0_EL2,   TTBR0_EL1,\n \t\t\t\t  translate_ttbr0_el2_to_ttbr0_el1\t     );\n \t\tMAPPED_EL2_SYSREG(TTBR1_EL2,   TTBR1_EL1,   NULL\t     );\n@@ -210,6 +208,33 @@ static void locate_register(const struct kvm_vcpu *vcpu, enum vcpu_sysreg reg,\n \t\tloc-\u003eloc = ((is_hyp_ctxt(vcpu) \u0026\u0026 vcpu_el2_e2h_is_set(vcpu)) ?\n \t\t\t    SR_LOC_SPECIAL : SR_LOC_MEMORY);\n \t\tbreak;\n+\tcase CPTR_EL2:\n+\t\t/*\n+\t\t * CPTR_EL2 is just as special, and needs a certain amount\n+\t\t * of handholding. It always lives in memory, due to being\n+\t\t * heavily trapped thanks to CPACR_EL1.TCPAC being RES0.\n+\t\t * FEAT_NV2p1 fixes this.\n+\t\t */\n+\t\tlocate_mapped_el2_register(vcpu, CPTR_EL2, CPACR_EL1,\n+\t\t\t\t\t   translate_cptr_el2_to_cpacr_el1,\n+\t\t\t\t\t   loc);\n+\t\tif (is_hyp_ctxt(vcpu) \u0026\u0026 vcpu_el2_e2h_is_set(vcpu))\n+\t\t\tloc-\u003eloc = SR_LOC_SPECIAL;\n+\t\tbreak;\n+\tcase NVHCR_EL2:\n+\t\t/*\n+\t\t * Yes, NVHCR_EL2 maps to itself when loaded in nested\n+\t\t * context. If you feel like the architecture is double\n+\t\t * backing on itself upside down, you're not alone.\n+\t\t */\n+\t\tWARN_ON_ONCE(!kvm_has_nv3(vcpu-\u003ekvm));\n+\t\tif (is_hyp_ctxt(vcpu)) {\n+\t\t\tloc-\u003eloc = SR_LOC_MEMORY;\n+\t\t} else {\n+\t\t\tloc-\u003eloc = SR_LOC_LOADED | SR_LOC_MAPPED;\n+\t\t\tloc-\u003emap_reg = NVHCR_EL2;\n+\t\t}\n+\t\tbreak;\n \tdefault:\n \t\tloc-\u003eloc = locate_direct_register(vcpu, reg);\n \t}\n@@ -249,6 +274,7 @@ static u64 read_sr_from_cpu(enum vcpu_sysreg reg)\n \tcase DACR32_EL2:\tval = read_sysreg_s(SYS_DACR32_EL2);\tbreak;\n \tcase IFSR32_EL2:\tval = read_sysreg_s(SYS_IFSR32_EL2);\tbreak;\n \tcase DBGVCR32_EL2:\tval = read_sysreg_s(SYS_DBGVCR32_EL2);\tbreak;\n+\tcase NVHCR_EL2:\t\tval = read_sysreg_s(SYS_NVHCR_EL2);\tbreak;\n \tdefault:\t\tWARN_ON_ONCE(1);\n \t}\n \n@@ -287,6 +313,7 @@ static void write_sr_to_cpu(enum vcpu_sysreg reg, u64 val)\n \tcase DACR32_EL2:\twrite_sysreg_s(val, SYS_DACR32_EL2);\tbreak;\n \tcase IFSR32_EL2:\twrite_sysreg_s(val, SYS_IFSR32_EL2);\tbreak;\n \tcase DBGVCR32_EL2:\twrite_sysreg_s(val, SYS_DBGVCR32_EL2);\tbreak;\n+\tcase NVHCR_EL2:\t\twrite_sysreg_s(val, SYS_NVHCR_EL2);\tbreak;\n \tdefault:\t\tWARN_ON_ONCE(1);\n \t}\n }\n@@ -311,9 +338,16 @@ u64 vcpu_read_sys_reg(const struct kvm_vcpu *vcpu, enum vcpu_sysreg reg)\n \t\tswitch (reg) {\n \t\tcase CNTHCTL_EL2:\n \t\t\tval = read_sysreg_el1(SYS_CNTKCTL);\n-\t\t\tval \u0026= CNTKCTL_VALID_BITS;\n-\t\t\tval |= __vcpu_sys_reg(vcpu, reg) \u0026 ~CNTKCTL_VALID_BITS;\n+\t\t\tif (!cpus_have_final_cap(ARM64_HAS_NV2P1)) {\n+\t\t\t\tval \u0026= CNTKCTL_VALID_BITS;\n+\t\t\t\tval |= __vcpu_sys_reg(vcpu, reg) \u0026 ~CNTKCTL_VALID_BITS;\n+\t\t\t}\n \t\t\treturn val;\n+\t\tcase CPTR_EL2:\n+\t\t\tif (cpus_have_final_cap(ARM64_HAS_NV2P1))\n+\t\t\t\treturn read_sysreg_el1(SYS_CPACR);\n+\t\t\telse\n+\t\t\t\treturn __vcpu_sys_reg(vcpu, reg);\n \t\tdefault:\n \t\t\tWARN_ON_ONCE(1);\n \t\t}\n@@ -359,6 +393,9 @@ void vcpu_write_sys_reg(struct kvm_vcpu *vcpu, u64 val, enum vcpu_sysreg reg)\n \t\t\t */\n \t\t\twrite_sysreg_el1(val, SYS_CNTKCTL);\n \t\t\tbreak;\n+\t\tcase CPTR_EL2:\n+\t\t\twrite_sysreg_el1(val, SYS_CPACR);\n+\t\t\tbreak;\n \t\tdefault:\n \t\t\tWARN_ON_ONCE(1);\n \t\t}\n@@ -2840,6 +2877,16 @@ static unsigned int vncr_el2_visibility(const struct kvm_vcpu *vcpu,\n \treturn REG_HIDDEN;\n }\n \n+static unsigned int nvhcr_el2_visibility(const struct kvm_vcpu *vcpu,\n+\t\t\t\t\tconst struct sys_reg_desc *rd)\n+{\n+\tif (el2_visibility(vcpu, rd) == 0 \u0026\u0026\n+\t    kvm_has_feat(vcpu-\u003ekvm, ID_AA64MMFR4_EL1, NV_frac, NV3))\n+\t\treturn 0;\n+\n+\treturn REG_HIDDEN;\n+}\n+\n static unsigned int sctlr2_visibility(const struct kvm_vcpu *vcpu,\n \t\t\t\t      const struct sys_reg_desc *rd)\n {\n@@ -3753,6 +3800,8 @@ static const struct sys_reg_desc sys_reg_descs[] = {\n \t\t\t sve_el2_visibility),\n \n \tEL2_REG_VNCR(HCRX_EL2, reset_val, 0),\n+\tEL2_REG_FILTERED(NVHCR_EL2, undef_access, reset_val, 0,\n+\t\t\t nvhcr_el2_visibility),\n \n \tEL2_REG(TTBR0_EL2, access_rw, reset_val, 0),\n \tEL2_REG(TTBR1_EL2, access_rw, reset_val, 0),\ndiff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps\nindex 9b85a84f6fd494..7e0414509e89a8 100644\n--- a/arch/arm64/tools/cpucaps\n+++ b/arch/arm64/tools/cpucaps\n@@ -51,6 +51,8 @@ HAS_LS64_V\n HAS_LSUI\n HAS_MOPS\n HAS_NESTED_VIRT\n+HAS_NV2P1\n+HAS_NV3\n HAS_BBML2_NOABORT\n HAS_PAN\n HAS_PMUV3\ndiff --git a/arch/arm64/tools/gen-sysreg.awk b/arch/arm64/tools/gen-sysreg.awk\nindex 86860ab672dc78..d7f7ceb768fe5c 100755\n--- a/arch/arm64/tools/gen-sysreg.awk\n+++ b/arch/arm64/tools/gen-sysreg.awk\n@@ -228,7 +228,7 @@ $1 == \"EndSysreg\" \u0026\u0026 block_current() == \"Sysreg\" {\n }\n \n # Currently this is effectivey a comment, in future we may want to emit\n-# defines for the fields.\n+# defines for the fields. \"Mapping\" does emit the RESx/UNKN definitions.\n ($1 == \"Fields\" || $1 == \"Mapping\") \u0026\u0026 block_current() == \"Sysreg\" {\n \texpect_fields(2)\n \n@@ -239,9 +239,15 @@ $1 == \"EndSysreg\" \u0026\u0026 block_current() == \"Sysreg\" {\n \tprint \"\"\n \n \tnext_bit = -1\n-\tres0 = null\n-\tres1 = null\n-\tunkn = null\n+\tif ($1 == \"Mapping\") {\n+\t\tres0 = $2 \"_RES0\"\n+\t\tres1 = $2 \"_RES1\"\n+\t\tunkn = $2 \"_UNKN\"\n+\t} else {\n+\t\tres0 = null\n+\t\tres1 = null\n+\t\tunkn = null\n+\t}\n \n \tnext\n }\ndiff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg\nindex bc1788b1662b76..afe9337851a2e3 100644\n--- a/arch/arm64/tools/sysreg\n+++ b/arch/arm64/tools/sysreg\n@@ -2386,17 +2386,40 @@ EndEnum\n EndSysreg\n \n Sysreg\tID_AA64MMFR4_EL1\t3\t0\t0\t7\t4\n-Res0\t63:48\n+UnsignedEnum\t63:60\tMTEFGT\n+\t0b0000\tNI\n+\t0b0001\tIMP\n+EndEnum\n+UnsignedEnum\t59:56\tSCRX\n+\t0b0000\tNI\n+\t0b0001\tIMP\n+EndEnum\n+UnsignedEnum\t55:52\tTEV\n+\t0b0000\tNI\n+\t0b0001\tIMP\n+EndEnum\n+UnsignedEnum\t51:48\tTPS\n+\t0b0000\tVAL_0000\n+\t0b0001\tVAL_0001\n+\t0b0010\tVAL_0010\n+EndEnum\n UnsignedEnum\t47:44\tSRMASK\n \t0b0000\tNI\n \t0b0001\tIMP\n+\t0b0010\tSRMASK2\n+EndEnum\n+UnsignedEnum\t43:40\tTLBID\n+\t0b0000\tNI\n+\t0b0001\tIMP\n EndEnum\n-Res0\t43:40\n UnsignedEnum\t39:36\tE3DSE\n \t0b0000\tNI\n \t0b0001\tIMP\n EndEnum\n-Res0\t35:32\n+UnsignedEnum\t35:32\tEAESR\n+\t0b0000\tNI\n+\t0b0001\tIMP\n+EndEnum\n UnsignedEnum\t31:28\tRMEGDI\n \t0b0000\tNI\n \t0b0001\tIMP\n@@ -2410,6 +2433,7 @@ UnsignedEnum\t23:20\tNV_frac\n \t0b0000\tNV_NV2\n \t0b0001\tNV2_ONLY\n \t0b0010\tNV2P1\n+\t0b0011\tNV3\n EndEnum\n UnsignedEnum\t19:16\tFGWTE3\n \t0b0000\tNI\n@@ -4242,6 +4266,9 @@ Field\t1\tE2TRE\n Field\t0\tE0HTRE\n EndSysreg\n \n+Sysreg\tNVHCR_EL2\t3\t4\t1\t5\t0\n+Mapping\tHCR_EL2\n+EndSysreg\n \n Sysreg HDFGRTR2_EL2\t3\t4\t3\t1\t0\n Res0\t63:25\n@@ -4521,7 +4548,14 @@ Fields\tZCR_ELx\n EndSysreg\n \n Sysreg\tHCRX_EL2\t3\t4\t1\t2\t2\n-Res0\t63:25\n+Res0\t63:35\n+Field\t34\tNVnTTLBOS\n+Field\t33\tNVnTTLBIS\n+Field\t32\tNVnTTLB\n+Res0\t31:28\n+Field\t27\tNVTGE\n+Field\t26\tSRMASKEn\n+Res0\t25\n Field\t24\tPACMEn\n Field\t23\tEnFPM\n Field\t22\tGCSEn\n"
}

2/1 2026/07/02 18:16 agent patch-evaluator
2m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "KVM"
  ],
  "FocusSymbols": [
    "kvm_init_nv_sysregs",
    "limit_nv_id_reg",
    "nvhcr_el2_visibility",
    "get_reg_fixed_bits",
    "fixup_nv_guest_exit"
  ],
  "Reasoning": "The patch implements support for ARM64 Nested Virtualization v3 (FEAT_NV3) and FEAT_NV2p1 in KVM. It modifies core KVM nested virtualization logic, including sysreg handling (e.g., NVHCR_EL2, CPTR_EL2, CNTHCTL_EL2), trap configuration, and guest exit fixups. This is reachable and functional code that can be tested by running KVM guests with nested virtualization enabled.",
  "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 08da62a3cedbf88ff3e68aac97bd1d0ba9a7db03
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Jul 2 18:16:15 2026 +0000

    syz-cluster: applied patch under review

diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h
index a57870fa96db5b..a42683af79fb5d 100644
--- a/arch/arm64/include/asm/cpufeature.h
+++ b/arch/arm64/include/asm/cpufeature.h
@@ -968,6 +968,7 @@ struct arm64_ftr_reg *get_arm64_ftr_reg(u32 sys_id);
 extern struct arm64_ftr_override id_aa64mmfr0_override;
 extern struct arm64_ftr_override id_aa64mmfr1_override;
 extern struct arm64_ftr_override id_aa64mmfr2_override;
+extern struct arm64_ftr_override id_aa64mmfr4_override;
 extern struct arm64_ftr_override id_aa64pfr0_override;
 extern struct arm64_ftr_override id_aa64pfr1_override;
 extern struct arm64_ftr_override id_aa64zfr0_override;
diff --git a/arch/arm64/include/asm/kvm_arm.h b/arch/arm64/include/asm/kvm_arm.h
index 3f9233b5a13081..f6cd8510479474 100644
--- a/arch/arm64/include/asm/kvm_arm.h
+++ b/arch/arm64/include/asm/kvm_arm.h
@@ -287,21 +287,6 @@
 				 GENMASK(19, 18) |	\
 				 GENMASK(15, 0))
 
-/*
- * Polarity masks for HCRX_EL2, limited to the bits that we know about
- * at this point in time. It doesn't mean that we actually *handle*
- * them, but that at least those that are not advertised to a guest
- * will be RES0 for that guest.
- */
-#define __HCRX_EL2_MASK		(BIT_ULL(6))
-#define __HCRX_EL2_nMASK	(GENMASK_ULL(24, 14) | \
-				 GENMASK_ULL(11, 7)  | \
-				 GENMASK_ULL(5, 0))
-#define __HCRX_EL2_RES0		~(__HCRX_EL2_nMASK | __HCRX_EL2_MASK)
-#define __HCRX_EL2_RES1		~(__HCRX_EL2_nMASK | \
-				  __HCRX_EL2_MASK  | \
-				  __HCRX_EL2_RES0)
-
 /* Hyp Prefetch Fault Address Register (HPFAR/HDFAR) */
 #define HPFAR_MASK	(~UL(0xf))
 /*
diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
index 5bf3d7e1d92c77..d6f432b1558f5f 100644
--- a/arch/arm64/include/asm/kvm_emulate.h
+++ b/arch/arm64/include/asm/kvm_emulate.h
@@ -266,6 +266,26 @@ static inline bool vserror_state_is_nested(struct kvm_vcpu *vcpu)
 	       (__vcpu_sys_reg(vcpu, HCRX_EL2) & HCRX_EL2_TMEA);
 }
 
+static inline bool kvm_has_nv2(struct kvm *kvm)
+{
+	return (cpus_have_final_cap(ARM64_HAS_NESTED_VIRT) &&
+		kvm_has_feat(kvm, ID_AA64MMFR4_EL1, NV_frac, NV2_ONLY));
+}
+
+static inline bool kvm_has_nv3(struct kvm *kvm)
+{
+	return (cpus_have_final_cap(ARM64_HAS_NESTED_VIRT) &&
+		cpus_have_final_cap(ARM64_HAS_NV3) &&
+		kvm_has_feat(kvm, ID_AA64MMFR4_EL1, NV_frac, NV3));
+}
+
+static inline bool is_nested_nv3_ctxt(struct kvm_vcpu *vcpu)
+{
+	return (has_vhe() && kvm_has_nv3(vcpu->kvm) && is_nested_ctxt(vcpu) &&
+		(__vcpu_sys_reg(vcpu, HCR_EL2) & HCR_EL2_NV) &&
+		(__vcpu_sys_reg(vcpu, HCRX_EL2) & HCRX_EL2_NVTGE));
+}
+
 /*
  * The layout of SPSR for an AArch32 state is different when observed from an
  * AArch64 SPSR_ELx or an AArch32 SPSR_*. This function generates the AArch32
@@ -617,7 +637,7 @@ static __always_inline void kvm_incr_pc(struct kvm_vcpu *vcpu)
  */
 static inline u64 vcpu_sanitised_cptr_el2(const struct kvm_vcpu *vcpu)
 {
-	u64 cptr = __vcpu_sys_reg(vcpu, CPTR_EL2);
+	u64 cptr = vcpu_read_sys_reg(vcpu, CPTR_EL2);
 
 	if (!vcpu_el2_e2h_is_set(vcpu))
 		cptr = translate_cptr_el2_to_cpacr_el1(cptr);
@@ -686,6 +706,25 @@ static inline void vcpu_set_hcrx(struct kvm_vcpu *vcpu)
 
 		if (kvm_has_feat(kvm, ID_AA64ISAR1_EL1, LS64, LS64_V))
 			vcpu->arch.hcrx_el2 |= HCRX_EL2_EnASR;
+
+		/*
+		 * NV3 is a host-specific extension, and we always use it
+		 * when present and that the guest uses NV. It may be be
+		 * hidden from the guest though.
+		 */
+		if (cpus_have_final_cap(ARM64_HAS_NV3) &&
+		    vcpu_has_nv(vcpu) && vcpu_el2_e2h_is_set(vcpu)) {
+			vcpu->arch.hcrx_el2 |= HCRX_EL2_NVTGE;
+
+			/*
+			 * If the guest is NV2-capable, then we need to see
+			 * all the TLBIs, as configured in HCR_EL2.
+			 * Otherwise, relax the TLBI traps to only TGE=0.
+			 */
+			if (!kvm_has_nv2(vcpu->kvm))
+				vcpu->arch.hcrx_el2 |= (HCRX_EL2_NVnTTLB  |
+							HCRX_EL2_NVnTTLBIS);
+		}
 	}
 }
 #endif /* __ARM64_KVM_EMULATE_H__ */
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index bae2c4f92ef5c0..2648c8a717ba05 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -543,6 +543,7 @@ enum vcpu_sysreg {
 	MDCR_EL2,	/* Monitor Debug Configuration Register (EL2) */
 	CNTHCTL_EL2,	/* Counter-timer Hypervisor Control register */
 	ZCR_EL2,	/* SVE Control Register (EL2) */
+	HCR_EL2,	/* Hypervisor Control Register */
 
 	/* Any VNCR-capable reg goes after this point */
 	MARKER(__VNCR_START__),
@@ -571,7 +572,7 @@ enum vcpu_sysreg {
 	VNCR(TFSR_EL1),	/* Tag Fault Status Register (EL1) */
 	VNCR(VPIDR_EL2),/* Virtualization Processor ID Register */
 	VNCR(VMPIDR_EL2),/* Virtualization Multiprocessor ID Register */
-	VNCR(HCR_EL2),	/* Hypervisor Configuration Register */
+	VNCR(NVHCR_EL2),/* NV Hypervisor Configuration Register */
 	VNCR(HSTR_EL2),	/* Hypervisor System Trap Register */
 	VNCR(VTTBR_EL2),/* Virtualization Translation Table Base Register */
 	VNCR(VTCR_EL2),	/* Virtualization Translation Control Register */
diff --git a/arch/arm64/include/asm/vncr_mapping.h b/arch/arm64/include/asm/vncr_mapping.h
index 14366d35ce82f0..9e8a49fa8b638f 100644
--- a/arch/arm64/include/asm/vncr_mapping.h
+++ b/arch/arm64/include/asm/vncr_mapping.h
@@ -11,7 +11,7 @@
 #define VNCR_VTCR_EL2           0x040
 #define VNCR_VMPIDR_EL2         0x050
 #define VNCR_CNTVOFF_EL2        0x060
-#define VNCR_HCR_EL2            0x078
+#define VNCR_NVHCR_EL2          0x078
 #define VNCR_HSTR_EL2           0x080
 #define VNCR_VPIDR_EL2          0x088
 #define VNCR_TPIDR_EL2          0x090
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index 9a22df0c5120f3..14fbfa8e6b7b50 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -785,6 +785,7 @@ static const struct arm64_ftr_bits ftr_raz[] = {
 struct arm64_ftr_override __read_mostly id_aa64mmfr0_override;
 struct arm64_ftr_override __read_mostly id_aa64mmfr1_override;
 struct arm64_ftr_override __read_mostly id_aa64mmfr2_override;
+struct arm64_ftr_override __read_mostly id_aa64mmfr4_override;
 struct arm64_ftr_override __read_mostly id_aa64pfr0_override;
 struct arm64_ftr_override __read_mostly id_aa64pfr1_override;
 struct arm64_ftr_override __read_mostly id_aa64zfr0_override;
@@ -858,7 +859,8 @@ static const struct __ftr_reg_entry {
 	ARM64_FTR_REG_OVERRIDE(SYS_ID_AA64MMFR2_EL1, ftr_id_aa64mmfr2,
 			       &id_aa64mmfr2_override),
 	ARM64_FTR_REG(SYS_ID_AA64MMFR3_EL1, ftr_id_aa64mmfr3),
-	ARM64_FTR_REG(SYS_ID_AA64MMFR4_EL1, ftr_id_aa64mmfr4),
+	ARM64_FTR_REG_OVERRIDE(SYS_ID_AA64MMFR4_EL1, ftr_id_aa64mmfr4,
+			       &id_aa64mmfr4_override),
 
 	/* Op1 = 0, CRn = 10, CRm = 4 */
 	ARM64_FTR_REG(SYS_MPAMIDR_EL1, ftr_mpamidr),
@@ -2620,6 +2622,20 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
 			{ /* Sentinel */ }
 		},
 	},
+	{
+		.desc = "FEAT_NV2p1",
+		.capability = ARM64_HAS_NV2P1,
+		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
+		.matches = has_cpuid_feature,
+		ARM64_CPUID_FIELDS(ID_AA64MMFR4_EL1, NV_frac, NV2P1)
+	},
+	{
+		.desc = "FEAT_NV3",
+		.capability = ARM64_HAS_NV3,
+		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
+		.matches = has_cpuid_feature,
+		ARM64_CPUID_FIELDS(ID_AA64MMFR4_EL1, NV_frac, NV3)
+	},
 	{
 		.capability = ARM64_HAS_32BIT_EL0_DO_NOT_USE,
 		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
diff --git a/arch/arm64/kernel/image-vars.h b/arch/arm64/kernel/image-vars.h
index d4c7d45ae6bc83..d15c2cb1b0f28a 100644
--- a/arch/arm64/kernel/image-vars.h
+++ b/arch/arm64/kernel/image-vars.h
@@ -51,6 +51,7 @@ PI_EXPORT_SYM(id_aa64isar2_override);
 PI_EXPORT_SYM(id_aa64mmfr0_override);
 PI_EXPORT_SYM(id_aa64mmfr1_override);
 PI_EXPORT_SYM(id_aa64mmfr2_override);
+PI_EXPORT_SYM(id_aa64mmfr4_override);
 PI_EXPORT_SYM(id_aa64pfr0_override);
 PI_EXPORT_SYM(id_aa64pfr1_override);
 PI_EXPORT_SYM(id_aa64smfr0_override);
diff --git a/arch/arm64/kernel/pi/idreg-override.c b/arch/arm64/kernel/pi/idreg-override.c
index bc57b290e5e7ba..4e47616bcac23a 100644
--- a/arch/arm64/kernel/pi/idreg-override.c
+++ b/arch/arm64/kernel/pi/idreg-override.c
@@ -106,6 +106,15 @@ static const struct ftr_set_desc mmfr2 __prel64_initconst = {
 	},
 };
 
+static const struct ftr_set_desc mmfr4 __prel64_initconst = {
+	.name		= "id_aa64mmfr4",
+	.override	= &id_aa64mmfr4_override,
+	.fields		= {
+		FIELD("nv_frac", ID_AA64MMFR4_EL1_NV_frac_SHIFT, NULL),
+		{}
+	},
+};
+
 static bool __init pfr0_sve_filter(u64 val)
 {
 	/*
@@ -220,6 +229,7 @@ PREL64(const struct ftr_set_desc, reg) regs[] __prel64_initconst = {
 	{ &mmfr0	},
 	{ &mmfr1	},
 	{ &mmfr2	},
+	{ &mmfr4	},
 	{ &pfr0 	},
 	{ &pfr1 	},
 	{ &isar1	},
diff --git a/arch/arm64/kvm/arch_timer.c b/arch/arm64/kvm/arch_timer.c
index 4155fe89b58a17..db60facad9f3c0 100644
--- a/arch/arm64/kvm/arch_timer.c
+++ b/arch/arm64/kvm/arch_timer.c
@@ -876,8 +876,14 @@ static void timer_set_traps(struct kvm_vcpu *vcpu, struct timer_map *map)
 	assign_clear_set_bit(tvt02, CNTHCTL_EL1NVVCT, clr, set);
 	assign_clear_set_bit(tpt02, CNTHCTL_EL1NVPCT, clr, set);
 
-	/* This only happens on VHE, so use the CNTHCTL_EL2 accessor. */
-	sysreg_clear_set(cnthctl_el2, clr, set);
+	/*
+	 * This only happens on VHE, so use the CNTHCTL_EL2 accessor, unless
+	 * we are sure CNTKCTL_EL1 is completely stateful with FEAT_NV2p1.
+	 */
+	if (!cpus_have_final_cap(ARM64_HAS_NV2P1))
+		sysreg_clear_set(cnthctl_el2, clr, set);
+	else
+		sysreg_clear_set(cntkctl_el1, clr, set);
 }
 
 void kvm_timer_vcpu_load(struct kvm_vcpu *vcpu)
diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index 0622162b089e50..7e864791427234 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -225,6 +225,7 @@ struct reg_feat_map_desc {
 #define FEAT_HCX		ID_AA64MMFR1_EL1, HCX, IMP
 #define FEAT_S2PIE		ID_AA64MMFR3_EL1, S2PIE, IMP
 #define FEAT_GCIE		ID_AA64PFR2_EL1, GCIE, IMP
+#define FEAT_NV3		ID_AA64MMFR4_EL1, NV_frac, NV3
 
 static bool not_feat_aa64el3(struct kvm *kvm)
 {
@@ -904,6 +905,12 @@ static const DECLARE_FEAT_MAP_FGT(hdfgwtr2_desc, hdfgwtr2_masks,
 
 
 static const struct reg_bits_to_feat_map hcrx_feat_map[] = {
+	NEEDS_FEAT(HCRX_EL2_NVTGE		|
+		   HCRX_EL2_NVnTTLB		|
+		   HCRX_EL2_NVnTTLBIS		|
+		   HCRX_EL2_NVnTTLBOS,
+		   FEAT_NV3),
+	NEEDS_FEAT(HCRX_EL2_SRMASKEn, FEAT_SRMASK),
 	NEEDS_FEAT(HCRX_EL2_PACMEn, feat_pauth_lr),
 	NEEDS_FEAT(HCRX_EL2_EnFPM, FEAT_FPMR),
 	NEEDS_FEAT(HCRX_EL2_GCSEn, FEAT_GCS),
@@ -933,7 +940,7 @@ static const struct reg_bits_to_feat_map hcrx_feat_map[] = {
 };
 
 
-static const DECLARE_FEAT_MAP(hcrx_desc, __HCRX_EL2,
+static const DECLARE_FEAT_MAP(hcrx_desc, HCRX_EL2,
 			      hcrx_feat_map, FEAT_HCX);
 
 static const struct reg_bits_to_feat_map hcr_feat_map[] = {
@@ -1010,6 +1017,9 @@ static const struct reg_bits_to_feat_map hcr_feat_map[] = {
 static const DECLARE_FEAT_MAP(hcr_desc, HCR_EL2,
 			      hcr_feat_map, FEAT_AA64EL2);
 
+static const DECLARE_FEAT_MAP(nvhcr_desc, NVHCR_EL2,
+			      hcr_feat_map, FEAT_NV3);
+
 static const struct reg_bits_to_feat_map sctlr2_feat_map[] = {
 	NEEDS_FEAT(SCTLR2_EL1_NMEA	|
 		   SCTLR2_EL1_EASE,
@@ -1384,6 +1394,7 @@ void __init check_feature_map(void)
 	check_reg_desc(&hdfgwtr2_desc);
 	check_reg_desc(&hcrx_desc);
 	check_reg_desc(&hcr_desc);
+	check_reg_desc(&nvhcr_desc);
 	check_reg_desc(&sctlr2_desc);
 	check_reg_desc(&tcr2_el2_desc);
 	check_reg_desc(&sctlr_el1_desc);
@@ -1579,11 +1590,21 @@ struct resx get_reg_fixed_bits(struct kvm *kvm, enum vcpu_sysreg reg)
 		break;
 	case HCRX_EL2:
 		resx = compute_reg_resx_bits(kvm, &hcrx_desc, 0, 0);
-		resx.res1 |= __HCRX_EL2_RES1;
 		break;
 	case HCR_EL2:
 		resx = compute_reg_resx_bits(kvm, &hcr_desc, 0, 0);
 		break;
+	case NVHCR_EL2:
+		/*
+		 * Only apply sanitisation if we do have FEAT_NV3.
+		 * Otherwise, the register aliases with HCR_EL2 in VNCR,
+		 * and we're better off relying on data transfers between
+		 * NVHCR_EL2 and HCR_EL2 to sanitise things.
+		 */
+		resx = (kvm_has_nv3(kvm) ?
+			compute_reg_resx_bits(kvm, &nvhcr_desc, 0, 0) :
+			(typeof(resx)){});
+		break;
 	case SCTLR2_EL1:
 	case SCTLR2_EL2:
 		resx = compute_reg_resx_bits(kvm, &sctlr2_desc, 0, 0);
diff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c
index 3c82f392845d14..f5dc578d8c9856 100644
--- a/arch/arm64/kvm/emulate-nested.c
+++ b/arch/arm64/kvm/emulate-nested.c
@@ -90,6 +90,7 @@ enum cgt_group_id {
 	CGT_HCRX_EnFPM,
 	CGT_HCRX_TCR2En,
 	CGT_HCRX_SCTLR2En,
+	CGT_HCRX_nNVTGE,
 
 	CGT_CNTHCTL_EL1TVT,
 	CGT_CNTHCTL_EL1TVCT,
@@ -121,6 +122,8 @@ enum cgt_group_id {
 	CGT_MDCR_TDE_TDRA,
 	CGT_MDCR_TDCC_TDE_TDA,
 
+	CGT_HCR_NV_HCRX_nNVTGE,
+
 	CGT_ICH_HCR_TC_TDIR,
 
 	/*
@@ -413,6 +416,12 @@ static const struct trap_bits coarse_trap_bits[] = {
 		.mask		= HCRX_EL2_SCTLR2En,
 		.behaviour	= BEHAVE_FORWARD_RW,
 	},
+	[CGT_HCRX_nNVTGE] = {
+		.index		= HCRX_EL2,
+		.value		= 0,
+		.mask		= HCRX_EL2_NVTGE,
+		.behaviour	= BEHAVE_FORWARD_RW,
+	},
 	[CGT_CNTHCTL_EL1TVT] = {
 		.index		= CNTHCTL_EL2,
 		.value		= CNTHCTL_EL1TVT,
@@ -468,6 +477,7 @@ static const enum cgt_group_id *coarse_control_combo[] = {
 					CGT_HCR_TVM, CGT_HCR_TRVM, CGT_HCRX_SCTLR2En),
 	MCB(CGT_HCR_TPU_TICAB,		CGT_HCR_TPU, CGT_HCR_TICAB),
 	MCB(CGT_HCR_TPU_TOCU,		CGT_HCR_TPU, CGT_HCR_TOCU),
+	MCB(CGT_HCR_NV_HCRX_nNVTGE,	CGT_HCR_NV, CGT_HCRX_nNVTGE),
 	MCB(CGT_HCR_NV1_nNV2_ENSCXT,	CGT_HCR_NV1_nNV2, CGT_HCR_ENSCXT),
 	MCB(CGT_MDCR_TPM_TPMCR,		CGT_MDCR_TPM, CGT_MDCR_TPMCR),
 	MCB(CGT_MDCR_TPM_HPMN,		CGT_MDCR_TPM, CGT_MDCR_HPMN),
@@ -853,6 +863,7 @@ static const struct encoding_to_trap_config encoding_to_cgt[] __initconst = {
 	SR_TRAP(SYS_SCTLR2_EL2,		CGT_HCR_NV),
 	SR_RANGE_TRAP(SYS_HCR_EL2,
 		      SYS_HCRX_EL2,	CGT_HCR_NV),
+	SR_TRAP(SYS_NVHCR_EL2,		CGT_HCR_NV_HCRX_nNVTGE),
 	SR_TRAP(SYS_SMPRIMAP_EL2,	CGT_HCR_NV),
 	SR_TRAP(SYS_SMCR_EL2,		CGT_HCR_NV),
 	SR_RANGE_TRAP(SYS_TTBR0_EL2,
@@ -2320,7 +2331,6 @@ int __init populate_nv_trap_config(void)
 	BUILD_BUG_ON(__NR_CGT_GROUP_IDS__ > BIT(TC_CGT_BITS));
 	BUILD_BUG_ON(__NR_FGT_GROUP_IDS__ > BIT(TC_FGT_BITS));
 	BUILD_BUG_ON(__NR_FG_FILTER_IDS__ > BIT(TC_FGF_BITS));
-	BUILD_BUG_ON(__HCRX_EL2_MASK & __HCRX_EL2_nMASK);
 
 	for (int i = 0; i < ARRAY_SIZE(encoding_to_cgt); i++) {
 		const struct encoding_to_trap_config *cgt = &encoding_to_cgt[i];
@@ -2346,10 +2356,6 @@ int __init populate_nv_trap_config(void)
 		}
 	}
 
-	if (__HCRX_EL2_RES0 != HCRX_EL2_RES0)
-		kvm_info("Sanitised HCR_EL2_RES0 = %016llx, expecting %016llx\n",
-			 __HCRX_EL2_RES0, HCRX_EL2_RES0);
-
 	kvm_info("nv: %ld coarse grained trap handlers\n",
 		 ARRAY_SIZE(encoding_to_cgt));
 
diff --git a/arch/arm64/kvm/hyp/include/hyp/switch.h b/arch/arm64/kvm/hyp/include/hyp/switch.h
index 4bf624a49591df..7b27296c946070 100644
--- a/arch/arm64/kvm/hyp/include/hyp/switch.h
+++ b/arch/arm64/kvm/hyp/include/hyp/switch.h
@@ -108,9 +108,10 @@ static inline void __activate_cptr_traps_vhe(struct kvm_vcpu *vcpu)
 	 * The architecture is a bit crap (what a surprise): an EL2 guest
 	 * writing to CPTR_EL2 via CPACR_EL1 can't set any of TCPAC or TTA,
 	 * as they are RES0 in the guest's view. To work around it, trap the
-	 * sucker using the very same bit it can't set...
+	 * sucker using the very same bit it can't set. FEAT_NV2p1 fixes it.
 	 */
-	if (vcpu_el2_e2h_is_set(vcpu) && is_hyp_ctxt(vcpu))
+	if (!cpus_have_final_cap(ARM64_HAS_NV2P1) &&
+	    vcpu_el2_e2h_is_set(vcpu) && is_hyp_ctxt(vcpu))
 		val |= CPTR_EL2_TCPAC;
 
 	/*
@@ -325,6 +326,24 @@ static inline void __deactivate_traps_mpam(void)
 		write_sysreg_s(MPAMHCR_HOST_FLAGS, SYS_MPAMHCR_EL2);
 }
 
+/*
+ * Just like for HCR_EL2, we can't let the guest mess with some of the
+ * basics we rely on in HCRX_EL2. However, the major difference is that
+ * HCRX_EL2 only affects EL1, and never EL2 (sudden outburst of sanity, I
+ * guess). So it is always the guest inflicting it on its own guestx.
+ *
+ * Things we don't want to let the guest control are:
+ *
+ * - TMEA: That's for us to decide how an SEA is routed, not the guest.
+ *
+ * - PTTWI: Similarly, it is for us to decide whether Reduced Coherency for
+ *   the PTW is a thing. It really isn't.
+ *
+ * - EnIDCP128: We don't allow IMPDEF sysregs -- full stop.
+ */
+#define NV_HCRX_GUEST_EXCLUDE	(HCRX_EL2_TMEA	    | HCRX_EL2_PTTWI | \
+				 HCRX_EL2_EnIDCP128)
+
 static inline void __activate_traps_common(struct kvm_vcpu *vcpu)
 {
 	struct kvm_cpu_context *hctxt = host_data_ptr(host_ctxt);
@@ -350,8 +369,8 @@ static inline void __activate_traps_common(struct kvm_vcpu *vcpu)
 		u64 hcrx = vcpu->arch.hcrx_el2;
 		if (is_nested_ctxt(vcpu)) {
 			u64 val = __vcpu_sys_reg(vcpu, HCRX_EL2);
-			hcrx |= val & __HCRX_EL2_MASK;
-			hcrx &= ~(~val & __HCRX_EL2_nMASK);
+			hcrx |= (val & ~NV_HCRX_GUEST_EXCLUDE);
+			hcrx &= ~(~val & ~NV_HCRX_GUEST_EXCLUDE);
 		}
 
 		ctxt_sys_reg(hctxt, HCRX_EL2) = read_sysreg_s(SYS_HCRX_EL2);
diff --git a/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h b/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h
index a17cbe7582de90..c382848d319479 100644
--- a/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h
+++ b/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h
@@ -172,6 +172,10 @@ static inline void __sysreg_save_el1_state(struct kvm_cpu_context *ctxt)
 
 	if (ctxt_has_sctlr2(ctxt))
 		ctxt_sys_reg(ctxt, SCTLR2_EL1) = read_sysreg_el1(SYS_SCTLR2);
+
+	/* Retrieve L2's HCR_EL2, and save it for future use */
+	if (is_nested_nv3_ctxt(ctxt_to_vcpu(ctxt)))
+		ctxt_sys_reg(ctxt, NVHCR_EL2) = read_sysreg_s(SYS_NVHCR_EL2);
 }
 
 static inline void __sysreg_save_el2_return_state(struct kvm_cpu_context *ctxt)
@@ -285,6 +289,13 @@ static inline void __sysreg_restore_el1_state(struct kvm_cpu_context *ctxt,
 
 	if (ctxt_has_sctlr2(ctxt))
 		write_sysreg_el1(ctxt_sys_reg(ctxt, SCTLR2_EL1), SYS_SCTLR2);
+
+	/*
+	 * Publish the L2 view of HCR_EL2 to the HW if L1 is using NV3.
+	 * Otherwise, the data is already in place in the L1's own VNCR.
+	 */
+	if (is_nested_nv3_ctxt(ctxt_to_vcpu(ctxt)))
+		write_sysreg_s(ctxt_sys_reg(ctxt, NVHCR_EL2), SYS_NVHCR_EL2);
 }
 
 /* Read the VCPU state's PSTATE, but translate (v)EL2 to EL1. */
diff --git a/arch/arm64/kvm/hyp/vhe/switch.c b/arch/arm64/kvm/hyp/vhe/switch.c
index bbe9cebd3d9d57..f129f22f15618f 100644
--- a/arch/arm64/kvm/hyp/vhe/switch.c
+++ b/arch/arm64/kvm/hyp/vhe/switch.c
@@ -70,6 +70,12 @@ static u64 __compute_hcr(struct kvm_vcpu *vcpu)
 		if (!vcpu_el2_e2h_is_set(vcpu))
 			hcr |= HCR_NV1;
 
+		/* Publish the guest's view of HCR_EL2 to the HW */
+		if (cpus_have_final_cap(ARM64_HAS_NV3) && vcpu_el2_e2h_is_set(vcpu))
+			write_sysreg_s(__vcpu_sys_reg(vcpu, HCR_EL2), SYS_NVHCR_EL2);
+		else
+			__vcpu_assign_sys_reg(vcpu, NVHCR_EL2, __vcpu_sys_reg(vcpu, HCR_EL2));
+
 		/*
 		 * Nothing in HCR_EL2 should impact running in hypervisor
 		 * context, apart from bits we have defined as RESx (E2H,
@@ -339,6 +345,10 @@ static bool kvm_hyp_handle_eret(struct kvm_vcpu *vcpu, u64 *exit_code)
 	u64 esr = kvm_vcpu_get_esr(vcpu);
 	u64 spsr, elr, mode;
 
+	/* With NV3, the fast path is handled in HW */
+	if (cpus_have_final_cap(ARM64_HAS_NV3) && vcpu_el2_e2h_is_set(vcpu))
+		return false;
+
 	/*
 	 * Going through the whole put/load motions is a waste of time
 	 * if this is a VHE guest hypervisor returning to its own
@@ -349,8 +359,8 @@ static bool kvm_hyp_handle_eret(struct kvm_vcpu *vcpu, u64 *exit_code)
 	 * Unless the trap has to be forwarded further down the line,
 	 * of course...
 	 */
-	if ((__vcpu_sys_reg(vcpu, HCR_EL2) & HCR_NV) ||
-	    (__vcpu_sys_reg(vcpu, HFGITR_EL2) & HFGITR_EL2_ERET))
+	if (is_nested_ctxt(vcpu) ||
+	    __vcpu_sys_reg(vcpu, HFGITR_EL2) & HFGITR_EL2_ERET)
 		return false;
 
 	spsr = read_sysreg_el1(SYS_SPSR);
@@ -441,6 +451,9 @@ static bool kvm_hyp_handle_cpacr_el1(struct kvm_vcpu *vcpu, u64 *exit_code)
 	u64 esr = kvm_vcpu_get_esr(vcpu);
 	int rt;
 
+	if (cpus_have_final_cap(ARM64_HAS_NV2P1))
+		return false;
+
 	if (!is_hyp_ctxt(vcpu) || esr_sys64_to_sysreg(esr) != SYS_CPACR_EL1)
 		return false;
 
@@ -534,19 +547,17 @@ static const exit_handler_fn hyp_exit_handlers[] = {
 	[0x3F]				= kvm_hyp_handle_impdef,
 };
 
-static inline bool fixup_guest_exit(struct kvm_vcpu *vcpu, u64 *exit_code)
+static void fixup_nv_guest_exit(struct kvm_vcpu *vcpu)
 {
-	synchronize_vcpu_pstate(vcpu);
-
 	/*
 	 * If we were in HYP context on entry, adjust the PSTATE view
 	 * so that the usual helpers work correctly. This enforces our
 	 * invariant that the guest's HYP context status is preserved
 	 * across a run.
 	 */
-	if (vcpu_has_nv(vcpu) &&
-	    unlikely(host_data_test_flag(VCPU_IN_HYP_CONTEXT))) {
+	if (unlikely(host_data_test_flag(VCPU_IN_HYP_CONTEXT))) {
 		u64 mode = *vcpu_cpsr(vcpu) & (PSR_MODE_MASK | PSR_MODE32_BIT);
+		u64 hcr;
 
 		switch (mode) {
 		case PSR_MODE_EL1t:
@@ -559,11 +570,26 @@ static inline bool fixup_guest_exit(struct kvm_vcpu *vcpu, u64 *exit_code)
 
 		*vcpu_cpsr(vcpu) &= ~(PSR_MODE_MASK | PSR_MODE32_BIT);
 		*vcpu_cpsr(vcpu) |= mode;
+
+		/* Publish the latest HCR_EL2 to the emulation */
+		hcr = (cpus_have_final_cap(ARM64_HAS_NV3) &&
+		       vcpu_el2_e2h_is_set(vcpu)) ?
+			read_sysreg_s(SYS_NVHCR_EL2) :
+			__vcpu_sys_reg(vcpu, NVHCR_EL2);
+
+		__vcpu_assign_sys_reg(vcpu, HCR_EL2, hcr);
 	}
 
 	/* Apply extreme paranoia! */
-	BUG_ON(vcpu_has_nv(vcpu) &&
-	       !!host_data_test_flag(VCPU_IN_HYP_CONTEXT) != is_hyp_ctxt(vcpu));
+	BUG_ON(!!host_data_test_flag(VCPU_IN_HYP_CONTEXT) != is_hyp_ctxt(vcpu));
+}
+
+static bool fixup_guest_exit(struct kvm_vcpu *vcpu, u64 *exit_code)
+{
+	synchronize_vcpu_pstate(vcpu);
+
+	if (vcpu_has_nv(vcpu))
+		fixup_nv_guest_exit(vcpu);
 
 	return __fixup_guest_exit(vcpu, exit_code, hyp_exit_handlers);
 }
diff --git a/arch/arm64/kvm/hyp/vhe/sysreg-sr.c b/arch/arm64/kvm/hyp/vhe/sysreg-sr.c
index be685b63e8cf26..0c4ef1ce32ae71 100644
--- a/arch/arm64/kvm/hyp/vhe/sysreg-sr.c
+++ b/arch/arm64/kvm/hyp/vhe/sysreg-sr.c
@@ -42,10 +42,12 @@ static void __sysreg_save_vel2_state(struct kvm_vcpu *vcpu)
 		u64 val;
 
 		/*
-		 * We don't save CPTR_EL2, as accesses to CPACR_EL1
-		 * are always trapped, ensuring that the in-memory
-		 * copy is always up-to-date. A small blessing...
+		 * Without FEAT_NV2p1, we don't save CPTR_EL2, as accesses
+		 * to CPACR_EL1 are always trapped, ensuring that the
+		 * in-memory copy is always up-to-date. A small blessing...
 		 */
+		if (cpus_have_final_cap(ARM64_HAS_NV2P1))
+			__vcpu_assign_sys_reg(vcpu, CPTR_EL2, read_sysreg_el1(SYS_CPACR));
 		__vcpu_assign_sys_reg(vcpu, SCTLR_EL2,	 read_sysreg_el1(SYS_SCTLR));
 		__vcpu_assign_sys_reg(vcpu, TTBR0_EL2,	 read_sysreg_el1(SYS_TTBR0));
 		__vcpu_assign_sys_reg(vcpu, TTBR1_EL2,	 read_sysreg_el1(SYS_TTBR1));
@@ -67,11 +69,18 @@ static void __sysreg_save_vel2_state(struct kvm_vcpu *vcpu)
 		 * The EL1 view of CNTKCTL_EL1 has a bunch of RES0 bits where
 		 * the interesting CNTHCTL_EL2 bits live. So preserve these
 		 * bits when reading back the guest-visible value.
+		 *
+		 * While NV2p1 fixes some of that, it makes CNTHCTL_EL2.ECV
+		 * even more broken than it already was with NV2.
 		 */
 		val = read_sysreg_el1(SYS_CNTKCTL);
-		val &= CNTKCTL_VALID_BITS;
-		__vcpu_rmw_sys_reg(vcpu, CNTHCTL_EL2, &=, ~CNTKCTL_VALID_BITS);
-		__vcpu_rmw_sys_reg(vcpu, CNTHCTL_EL2, |=, val);
+		if (!cpus_have_final_cap(ARM64_HAS_NV2P1)) {
+			val &= CNTKCTL_VALID_BITS;
+			__vcpu_rmw_sys_reg(vcpu, CNTHCTL_EL2, &=, ~CNTKCTL_VALID_BITS);
+			__vcpu_rmw_sys_reg(vcpu, CNTHCTL_EL2, |=, val);
+		} else {
+			__vcpu_assign_sys_reg(vcpu, CNTHCTL_EL2, val);
+		}
 	}
 
 	__vcpu_assign_sys_reg(vcpu, SP_EL2,	 read_sysreg(sp_el1));
diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
index dfb96edbdc43c6..64c8fb82fadf6d 100644
--- a/arch/arm64/kvm/nested.c
+++ b/arch/arm64/kvm/nested.c
@@ -1728,7 +1728,7 @@ u64 limit_nv_id_reg(struct kvm *kvm, u32 reg, u64 val)
 		 * You get EITHER
 		 *
 		 * - FEAT_VHE without FEAT_E2H0
-		 * - FEAT_NV limited to FEAT_NV2
+		 * - FEAT_NV limited to FEAT_NV2(p1)/NV3
 		 * - HCR_EL2.NV1 being RES0
 		 *
 		 * OR
@@ -1740,7 +1740,13 @@ u64 limit_nv_id_reg(struct kvm *kvm, u32 reg, u64 val)
 		if (test_bit(KVM_ARM_VCPU_HAS_EL2_E2H0, kvm->arch.vcpu_features)) {
 			val = 0;
 		} else {
-			val = SYS_FIELD_PREP_ENUM(ID_AA64MMFR4_EL1, NV_frac, NV2_ONLY);
+			if (cpus_have_final_cap(ARM64_HAS_NV3))
+				val = ID_REG_LIMIT_FIELD_ENUM(val, ID_AA64MMFR4_EL1, NV_frac, NV3);
+			else if (cpus_have_final_cap(ARM64_HAS_NV2P1))
+				val = ID_REG_LIMIT_FIELD_ENUM(val, ID_AA64MMFR4_EL1, NV_frac, NV2P1);
+			else
+				val = SYS_FIELD_PREP_ENUM(ID_AA64MMFR4_EL1, NV_frac, NV2_ONLY);
+			val &= ~ID_AA64MMFR4_EL1_E2H0;
 			val |= SYS_FIELD_PREP_ENUM(ID_AA64MMFR4_EL1, E2H0, NI_NV1);
 		}
 		break;
@@ -1826,6 +1832,10 @@ int kvm_init_nv_sysregs(struct kvm_vcpu *vcpu)
 	resx = get_reg_fixed_bits(kvm, HCR_EL2);
 	set_sysreg_masks(kvm, HCR_EL2, resx);
 
+	/* NVHCR_EL2 */
+	resx = get_reg_fixed_bits(kvm, NVHCR_EL2);
+	set_sysreg_masks(kvm, NVHCR_EL2, resx);
+
 	/* HCRX_EL2 */
 	resx = get_reg_fixed_bits(kvm, HCRX_EL2);
 	set_sysreg_masks(kvm, HCRX_EL2, resx);
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 5d5c579d457902..0aeb2e736fde37 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -183,8 +183,6 @@ static void locate_register(const struct kvm_vcpu *vcpu, enum vcpu_sysreg reg,
 	switch (reg) {
 		MAPPED_EL2_SYSREG(SCTLR_EL2,   SCTLR_EL1,
 				  translate_sctlr_el2_to_sctlr_el1	     );
-		MAPPED_EL2_SYSREG(CPTR_EL2,    CPACR_EL1,
-				  translate_cptr_el2_to_cpacr_el1	     );
 		MAPPED_EL2_SYSREG(TTBR0_EL2,   TTBR0_EL1,
 				  translate_ttbr0_el2_to_ttbr0_el1	     );
 		MAPPED_EL2_SYSREG(TTBR1_EL2,   TTBR1_EL1,   NULL	     );
@@ -210,6 +208,33 @@ static void locate_register(const struct kvm_vcpu *vcpu, enum vcpu_sysreg reg,
 		loc->loc = ((is_hyp_ctxt(vcpu) && vcpu_el2_e2h_is_set(vcpu)) ?
 			    SR_LOC_SPECIAL : SR_LOC_MEMORY);
 		break;
+	case CPTR_EL2:
+		/*
+		 * CPTR_EL2 is just as special, and needs a certain amount
+		 * of handholding. It always lives in memory, due to being
+		 * heavily trapped thanks to CPACR_EL1.TCPAC being RES0.
+		 * FEAT_NV2p1 fixes this.
+		 */
+		locate_mapped_el2_register(vcpu, CPTR_EL2, CPACR_EL1,
+					   translate_cptr_el2_to_cpacr_el1,
+					   loc);
+		if (is_hyp_ctxt(vcpu) && vcpu_el2_e2h_is_set(vcpu))
+			loc->loc = SR_LOC_SPECIAL;
+		break;
+	case NVHCR_EL2:
+		/*
+		 * Yes, NVHCR_EL2 maps to itself when loaded in nested
+		 * context. If you feel like the architecture is double
+		 * backing on itself upside down, you're not alone.
+		 */
+		WARN_ON_ONCE(!kvm_has_nv3(vcpu->kvm));
+		if (is_hyp_ctxt(vcpu)) {
+			loc->loc = SR_LOC_MEMORY;
+		} else {
+			loc->loc = SR_LOC_LOADED | SR_LOC_MAPPED;
+			loc->map_reg = NVHCR_EL2;
+		}
+		break;
 	default:
 		loc->loc = locate_direct_register(vcpu, reg);
 	}
@@ -249,6 +274,7 @@ static u64 read_sr_from_cpu(enum vcpu_sysreg reg)
 	case DACR32_EL2:	val = read_sysreg_s(SYS_DACR32_EL2);	break;
 	case IFSR32_EL2:	val = read_sysreg_s(SYS_IFSR32_EL2);	break;
 	case DBGVCR32_EL2:	val = read_sysreg_s(SYS_DBGVCR32_EL2);	break;
+	case NVHCR_EL2:		val = read_sysreg_s(SYS_NVHCR_EL2);	break;
 	default:		WARN_ON_ONCE(1);
 	}
 
@@ -287,6 +313,7 @@ static void write_sr_to_cpu(enum vcpu_sysreg reg, u64 val)
 	case DACR32_EL2:	write_sysreg_s(val, SYS_DACR32_EL2);	break;
 	case IFSR32_EL2:	write_sysreg_s(val, SYS_IFSR32_EL2);	break;
 	case DBGVCR32_EL2:	write_sysreg_s(val, SYS_DBGVCR32_EL2);	break;
+	case NVHCR_EL2:		write_sysreg_s(val, SYS_NVHCR_EL2);	break;
 	default:		WARN_ON_ONCE(1);
 	}
 }
@@ -311,9 +338,16 @@ u64 vcpu_read_sys_reg(const struct kvm_vcpu *vcpu, enum vcpu_sysreg reg)
 		switch (reg) {
 		case CNTHCTL_EL2:
 			val = read_sysreg_el1(SYS_CNTKCTL);
-			val &= CNTKCTL_VALID_BITS;
-			val |= __vcpu_sys_reg(vcpu, reg) & ~CNTKCTL_VALID_BITS;
+			if (!cpus_have_final_cap(ARM64_HAS_NV2P1)) {
+				val &= CNTKCTL_VALID_BITS;
+				val |= __vcpu_sys_reg(vcpu, reg) & ~CNTKCTL_VALID_BITS;
+			}
 			return val;
+		case CPTR_EL2:
+			if (cpus_have_final_cap(ARM64_HAS_NV2P1))
+				return read_sysreg_el1(SYS_CPACR);
+			else
+				return __vcpu_sys_reg(vcpu, reg);
 		default:
 			WARN_ON_ONCE(1);
 		}
@@ -359,6 +393,9 @@ void vcpu_write_sys_reg(struct kvm_vcpu *vcpu, u64 val, enum vcpu_sysreg reg)
 			 */
 			write_sysreg_el1(val, SYS_CNTKCTL);
 			break;
+		case CPTR_EL2:
+			write_sysreg_el1(val, SYS_CPACR);
+			break;
 		default:
 			WARN_ON_ONCE(1);
 		}
@@ -2840,6 +2877,16 @@ static unsigned int vncr_el2_visibility(const struct kvm_vcpu *vcpu,
 	return REG_HIDDEN;
 }
 
+static unsigned int nvhcr_el2_visibility(const struct kvm_vcpu *vcpu,
+					const struct sys_reg_desc *rd)
+{
+	if (el2_visibility(vcpu, rd) == 0 &&
+	    kvm_has_feat(vcpu->kvm, ID_AA64MMFR4_EL1, NV_frac, NV3))
+		return 0;
+
+	return REG_HIDDEN;
+}
+
 static unsigned int sctlr2_visibility(const struct kvm_vcpu *vcpu,
 				      const struct sys_reg_desc *rd)
 {
@@ -3753,6 +3800,8 @@ static const struct sys_reg_desc sys_reg_descs[] = {
 			 sve_el2_visibility),
 
 	EL2_REG_VNCR(HCRX_EL2, reset_val, 0),
+	EL2_REG_FILTERED(NVHCR_EL2, undef_access, reset_val, 0,
+			 nvhcr_el2_visibility),
 
 	EL2_REG(TTBR0_EL2, access_rw, reset_val, 0),
 	EL2_REG(TTBR1_EL2, access_rw, reset_val, 0),
diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps
index 9b85a84f6fd494..7e0414509e89a8 100644
--- a/arch/arm64/tools/cpucaps
+++ b/arch/arm64/tools/cpucaps
@@ -51,6 +51,8 @@ HAS_LS64_V
 HAS_LSUI
 HAS_MOPS
 HAS_NESTED_VIRT
+HAS_NV2P1
+HAS_NV3
 HAS_BBML2_NOABORT
 HAS_PAN
 HAS_PMUV3
diff --git a/arch/arm64/tools/gen-sysreg.awk b/arch/arm64/tools/gen-sysreg.awk
index 86860ab672dc78..d7f7ceb768fe5c 100755
--- a/arch/arm64/tools/gen-sysreg.awk
+++ b/arch/arm64/tools/gen-sysreg.awk
@@ -228,7 +228,7 @@ $1 == "EndSysreg" && block_current() == "Sysreg" {
 }
 
 # Currently this is effectivey a comment, in future we may want to emit
-# defines for the fields.
+# defines for the fields. "Mapping" does emit the RESx/UNKN definitions.
 ($1 == "Fields" || $1 == "Mapping") && block_current() == "Sysreg" {
 	expect_fields(2)
 
@@ -239,9 +239,15 @@ $1 == "EndSysreg" && block_current() == "Sysreg" {
 	print ""
 
 	next_bit = -1
-	res0 = null
-	res1 = null
-	unkn = null
+	if ($1 == "Mapping") {
+		res0 = $2 "_RES0"
+		res1 = $2 "_RES1"
+		unkn = $2 "_UNKN"
+	} else {
+		res0 = null
+		res1 = null
+		unkn = null
+	}
 
 	next
 }
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index bc1788b1662b76..afe9337851a2e3 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -2386,17 +2386,40 @@ EndEnum
 EndSysreg
 
 Sysreg	ID_AA64MMFR4_EL1	3	0	0	7	4
-Res0	63:48
+UnsignedEnum	63:60	MTEFGT
+	0b0000	NI
+	0b0001	IMP
+EndEnum
+UnsignedEnum	59:56	SCRX
+	0b0000	NI
+	0b0001	IMP
+EndEnum
+UnsignedEnum	55:52	TEV
+	0b0000	NI
+	0b0001	IMP
+EndEnum
+UnsignedEnum	51:48	TPS
+	0b0000	VAL_0000
+	0b0001	VAL_0001
+	0b0010	VAL_0010
+EndEnum
 UnsignedEnum	47:44	SRMASK
 	0b0000	NI
 	0b0001	IMP
+	0b0010	SRMASK2
+EndEnum
+UnsignedEnum	43:40	TLBID
+	0b0000	NI
+	0b0001	IMP
 EndEnum
-Res0	43:40
 UnsignedEnum	39:36	E3DSE
 	0b0000	NI
 	0b0001	IMP
 EndEnum
-Res0	35:32
+UnsignedEnum	35:32	EAESR
+	0b0000	NI
+	0b0001	IMP
+EndEnum
 UnsignedEnum	31:28	RMEGDI
 	0b0000	NI
 	0b0001	IMP
@@ -2410,6 +2433,7 @@ UnsignedEnum	23:20	NV_frac
 	0b0000	NV_NV2
 	0b0001	NV2_ONLY
 	0b0010	NV2P1
+	0b0011	NV3
 EndEnum
 UnsignedEnum	19:16	FGWTE3
 	0b0000	NI
@@ -4242,6 +4266,9 @@ Field	1	E2TRE
 Field	0	E0HTRE
 EndSysreg
 
+Sysreg	NVHCR_EL2	3	4	1	5	0
+Mapping	HCR_EL2
+EndSysreg
 
 Sysreg HDFGRTR2_EL2	3	4	3	1	0
 Res0	63:25
@@ -4521,7 +4548,14 @@ Fields	ZCR_ELx
 EndSysreg
 
 Sysreg	HCRX_EL2	3	4	1	2	2
-Res0	63:25
+Res0	63:35
+Field	34	NVnTTLBOS
+Field	33	NVnTTLBIS
+Field	32	NVnTTLB
+Res0	31:28
+Field	27	NVTGE
+Field	26	SRMASKEn
+Res0	25
 Field	24	PACMEn
 Field	23	EnFPM
 Field	22	GCSEn

3/2 2026/07/02 18:16 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17327
							output: 25
							thoughts: 505
						

Thoughts:
**Assessing the Need for Fuzzing: ARM64 KVM Nested Virtualization Changes**

Okay, let's break this down. My initial assessment is that these patches *absolutely* introduce functional changes to the kernel that we should be fuzzing. We're talking about modifications to the ARM64 KVM nested virtualization code, specifically adding support for `FEAT_NV3` and `FEAT_NV2p1`. This is significant; nested virtualization itself is complex, and any changes in this area are prime candidates for fuzzing to uncover potential issues.

The changes span a fairly wide array of files within the `arch/arm64/kvm` directory: header files defining new features and system registers, core configuration, and emulation code. Specifically, I'm noting the additions of `kvm_has_nv2`, `kvm_has_nv3`, `is_nested_nv3_ctxt`, `NVHCR_EL2`, and the associated trap handling and register manipulation. The modifications to `hyp/vhe/switch.c` and `nested.c` are particularly interesting, as those are often hot spots for bugs in virtualization. My initial gut feeling is that these changes, particularly those that involve emulation and register manipulation, are good targets for fuzzing.

My next step is to confirm the code's reachability. Yes, absolutely. KVM ARM64 nested virtualization is a feature that *can* be tested. It's a configured functionality, so we'll need to figure out which `CONFIG_` options are relevant to determine how to enable/configure the feature. Let's do a search for KVM ARM64 nested virtualization configs to determine how we can enable this feature and, therefore, fuzz it effectively.



4/2 2026/07/02 18:16 tool grepper
0m Args:
{
  "Expression": "ARM64_HAS_NESTED_VIRT"
}

Results:
{
  "Output": "arch/arm64/include/asm/kvm_emulate.h=269=static inline bool kvm_has_nv2(struct kvm *kvm)\narch/arm64/include/asm/kvm_emulate.h-270-{\narch/arm64/include/asm/kvm_emulate.h:271:\treturn (cpus_have_final_cap(ARM64_HAS_NESTED_VIRT) \u0026\u0026\narch/arm64/include/asm/kvm_emulate.h-272-\t\tkvm_has_feat(kvm, ID_AA64MMFR4_EL1, NV_frac, NV2_ONLY));\n--\narch/arm64/include/asm/kvm_emulate.h=275=static inline bool kvm_has_nv3(struct kvm *kvm)\narch/arm64/include/asm/kvm_emulate.h-276-{\narch/arm64/include/asm/kvm_emulate.h:277:\treturn (cpus_have_final_cap(ARM64_HAS_NESTED_VIRT) \u0026\u0026\narch/arm64/include/asm/kvm_emulate.h-278-\t\tcpus_have_final_cap(ARM64_HAS_NV3) \u0026\u0026\n--\narch/arm64/include/asm/kvm_host.h=1168=static inline u64 *___ctxt_sys_reg(const struct kvm_cpu_context *ctxt, int r)\n--\narch/arm64/include/asm/kvm_host.h-1170-#if !defined (__KVM_NVHE_HYPERVISOR__)\narch/arm64/include/asm/kvm_host.h:1171:\tif (unlikely(cpus_have_final_cap(ARM64_HAS_NESTED_VIRT) \u0026\u0026\narch/arm64/include/asm/kvm_host.h-1172-\t\t     r \u003e= __VNCR_START__ \u0026\u0026 ctxt-\u003evncr_array))\n--\narch/arm64/include/asm/kvm_nested.h=10=static inline bool vcpu_has_nv(const struct kvm_vcpu *vcpu)\n--\narch/arm64/include/asm/kvm_nested.h-12-\treturn (!__is_defined(__KVM_NVHE_HYPERVISOR__) \u0026\u0026\narch/arm64/include/asm/kvm_nested.h:13:\t\tcpus_have_final_cap(ARM64_HAS_NESTED_VIRT) \u0026\u0026\narch/arm64/include/asm/kvm_nested.h-14-\t\tvcpu_has_feature(vcpu, KVM_ARM_VCPU_HAS_EL2));\n--\narch/arm64/kernel/cpufeature.c=1887=static bool has_nv1(const struct arm64_cpu_capabilities *entry, int scope)\n--\narch/arm64/kernel/cpufeature.c-1903-\narch/arm64/kernel/cpufeature.c:1904:\treturn (__system_matches_cap(ARM64_HAS_NESTED_VIRT) \u0026\u0026\narch/arm64/kernel/cpufeature.c-1905-\t\t!(has_cpuid_feature(entry, scope) ||\n--\narch/arm64/kernel/cpufeature.c=2545=static const struct arm64_cpu_capabilities arm64_features[] = {\n--\narch/arm64/kernel/cpufeature.c-2609-\t\t.desc = \"Nested Virtualization Support\",\narch/arm64/kernel/cpufeature.c:2610:\t\t.capability = ARM64_HAS_NESTED_VIRT,\narch/arm64/kernel/cpufeature.c-2611-\t\t.type = ARM64_CPUCAP_SYSTEM_FEATURE,\n--\narch/arm64/kvm/arm.c=364=int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)\n--\narch/arm64/kvm/arm.c-442-\tcase KVM_CAP_ARM_EL2:\narch/arm64/kvm/arm.c:443:\t\tr = cpus_have_final_cap(ARM64_HAS_NESTED_VIRT);\narch/arm64/kvm/arm.c-444-\t\tbreak;\n--\narch/arm64/kvm/arm.c=1559=static unsigned long system_supported_vcpu_features(void)\n--\narch/arm64/kvm/arm.c-1576-\narch/arm64/kvm/arm.c:1577:\tif (!cpus_have_final_cap(ARM64_HAS_NESTED_VIRT))\narch/arm64/kvm/arm.c-1578-\t\tclear_bit(KVM_ARM_VCPU_HAS_EL2, \u0026features);\n--\narch/arm64/kvm/arm.c=2999=static __init int kvm_arm_init(void)\n--\narch/arm64/kvm/arm.c-3061-\t\t\t\t     \"h\" : \"n\"),\narch/arm64/kvm/arm.c:3062:\t\t cpus_have_final_cap(ARM64_HAS_NESTED_VIRT) ? \"+NV2\": \"\");\narch/arm64/kvm/arm.c-3063-\n--\narch/arm64/kvm/ptdump.c=298=void kvm_s2_ptdump_create_debugfs(struct kvm *kvm)\n--\narch/arm64/kvm/ptdump.c-305-\t\t\t    \u0026kvm-\u003earch.mmu, \u0026kvm_pgtable_levels_fops);\narch/arm64/kvm/ptdump.c:306:\tif (cpus_have_final_cap(ARM64_HAS_NESTED_VIRT))\narch/arm64/kvm/ptdump.c-307-\t\tkvm-\u003earch.debugfs_nv_dentry = debugfs_create_dir(\"nested\", kvm-\u003edebugfs_dentry);\n"
}

5/2 2026/07/02 18:16 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19789
							output: 34
							thoughts: 12
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Hunting for ARM64 KVM Configurations**

Alright, let's get down to brass tacks. I need to dig up some existing KVM configurations tailored for ARM64 architectures. The specifics will depend on what I'm aiming for – performance benchmarking, nested virtualization experimentation, specific hardware implementations, or possibly even debugging a KVM-related issue on ARM. I'm thinking I'll start by querying for common base setups. I'm expecting to find a wealth of configuration options across various distributions. It might be helpful to target particular OS variants or hypervisor versions as a way to narrow the scope, as well as considering kernel versions to avoid compatibility issues. I know that device tree configuration is a critical element with ARM, so I will prioritize configurations that explicitly address the device tree and any specific virtualized hardware dependencies. I'll need to scrutinize how memory is mapped and managed, and especially look for configurations that handle interrupt controllers and the virtualized system's peripherals effectively. Any insights into optimizations for things like virtio drivers or specific hardware extensions (like SVE) would be a bonus, but I will be happy to learn and explore anything I can find.



6/2 2026/07/02 18:17 tool grepper
0m Args:
{
  "Expression": "config KVM",
  "PathPrefix": "arch/arm64/kvm/Kconfig"
}

Results:
{
  "Output": "arch/arm64/kvm/Kconfig=18=if VIRTUALIZATION\narch/arm64/kvm/Kconfig-19-\narch/arm64/kvm/Kconfig:20:menuconfig KVM\narch/arm64/kvm/Kconfig-21-\tbool \"Kernel-based Virtual Machine (KVM) support\"\n"
}

7/2 2026/07/02 18:17 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20194
							output: 32
							thoughts: 18
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Nested Virtualization on ARM64: Configuration Hunt**

Alright, let's dive in. I'm wondering if there are any *specific* configuration nuances or knobs I need to be aware of when dealing with nested virtualization on ARM64 architectures. We all know the basics of virtualization, but ARM64 has its own set of peculiarities, and nested virtualization can be a whole different beast. I'm thinking beyond just the generic CPU features like virtualization extensions. I'm looking for things like: are there specific kernel modules I'll need enabled? Any firmware requirements that might be unique to nested scenarios? Are there any performance caveats that I need to keep in mind, perhaps related to the hypervisor chosen (like KVM or Xen)? Maybe certain optimizations or mitigations that need to be addressed at the host or guest level? I'm trying to mentally map out all the potential gotchas before I even start. I need to know where to find the specific documentation or best practices, or whether I need to get ready to deep dive into the code for a custom configuration to get nested virtualization working optimally.



8/2 2026/07/02 18:17 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "nested",
  "PathPrefix": "arch/arm64/kvm/Kconfig"
}

Results:
{
  "error": "no matches"
}

9/2 2026/07/02 18:17 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20504
							output: 30
							thoughts: 18
						

Thoughts:
Here's a summary of my thought process:

**Diving into the Arm64 Kernel Configuration**

Okay, so I need to figure out where nested virtualization settings are defined for the arm64 architecture within the kernel. My first instinct, given my experience, is to zero in on the relevant Kconfig file. That's almost always the central place for configuring features. So, let's start with `arch/arm64/Kconfig`. It's highly likely that options related to virtualization, especially more advanced features like nested virtualization, will be described and configured within this file or be included from it. I need to quickly scan through this configuration file to locate the necessary settings.



10/2 2026/07/02 18:17 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "NESTED",
  "PathPrefix": "arch/arm64/Kconfig"
}

Results:
{
  "error": "no matches"
}

11/2 2026/07/02 18:17 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20710
							output: 31
							thoughts: 27
						

Thoughts:
Okay, here's my interpretation of that thought process, as if I were having it:

**Digging into a KVM Issue**

Hmm, could this be something specific to KVM configuration? It feels like the most likely starting point. I should probably focus on `CONFIG_KVM` – that seems like the central piece. Okay, let's take a look at the `arch/arm64/kvm/Makefile`. That Makefile should tell me exactly how things are being built, what dependencies are in play, and hopefully, it'll illuminate this specific issue or at least point me in the right direction. It's the logical next step. Let's see what clues this Makefile holds.



12/2 2026/07/02 18:17 tool grepper
0m Args:
{
  "Expression": "nested",
  "PathPrefix": "arch/arm64/kvm/Makefile"
}

Results:
{
  "Output": "arch/arm64/kvm/Makefile=16=kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \\\n--\narch/arm64/kvm/Makefile-19-\t vgic-sys-reg-v3.o fpsimd.o pkvm.o \\\narch/arm64/kvm/Makefile:20:\t arch_timer.o trng.o vmid.o emulate-nested.o nested.o at.o \\\narch/arm64/kvm/Makefile-21-\t vgic/vgic.o vgic/vgic-init.o \\\n--\narch/arm64/kvm/Makefile-25-\t vgic/vgic-mmio-v3.o vgic/vgic-kvm-device.o \\\narch/arm64/kvm/Makefile:26:\t vgic/vgic-its.o vgic/vgic-debug.o vgic/vgic-v3-nested.o \\\narch/arm64/kvm/Makefile-27-\t vgic/vgic-v5.o\n"
}

13/2 2026/07/02 18:17 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21203
							output: 39
							thoughts: 789
						

Thoughts:
**Analyzing KVM Nested Virtualization Changes**

Okay, so I'm looking at this KVM patch, specifically the ARM64 side, and it looks like `emulate-nested.o` and `nested.o` are compiled unconditionally if `CONFIG_KVM` is enabled, which is interesting. The code seems functional and directly modifies nested virtualization behavior. I need to get a handle on the changes, so I'm going to pinpoint the most interesting functions.

From a quick skim, here's what's been touched: `kvm_timer_vcpu_load`, that seems like a hot path to look into. Then there's `check_feature_map`, `get_reg_fixed_bits`, and `populate_nv_trap_config`, which I'll categorize as being more on the initialization side of things. There's a flurry of activity in the `hyp` directory, with `__activate_cptr_traps_vhe`, `__activate_traps_common`, `__sysreg_save_el1_state`, `__sysreg_restore_el1_state`, `__compute_hcr`, `kvm_hyp_handle_eret`, `kvm_hyp_handle_cpacr_el1`, `fixup_nv_guest_exit`, and `__sysreg_save_vel2_state` likely being performance critical paths, all relating to the hypervisor's interaction.

Finally, in the main KVM directory, I see `limit_nv_id_reg`, `kvm_init_nv_sysregs`, and a series of sysreg functions, like `locate_register`, `read_sr_from_cpu`, `write_sr_to_cpu`, `vcpu_read_sys_reg`, `vcpu_write_sys_reg`, and `nvhcr_el2_visibility`. That's a good overview.

My initial focus will be on `limit_nv_id_reg`, `kvm_init_nv_sysregs`, and `nvhcr_el2_visibility` – those seem like they might be the core of what's being modified, or at least the configuration aspects. That should be a good starting point.



14/2 2026/07/02 18:17 tool grepper
0m Args:
{
  "Expression": "limit_nv_id_reg",
  "PathPrefix": "arch/arm64/kvm/nested.c"
}

Results:
{
  "Output": "arch/arm64/kvm/nested.c=1547=static void kvm_map_l1_vncr(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/nested.c-1605- */\narch/arm64/kvm/nested.c:1606:u64 limit_nv_id_reg(struct kvm *kvm, u32 reg, u64 val)\narch/arm64/kvm/nested.c-1607-{\n"
}

15/2 2026/07/02 18:17 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22655
							output: 41
							thoughts: 15
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience and written in the first person:

**Diving into `kvm_init_nv_sysregs`**

Alright, let's take a look at `kvm_init_nv_sysregs`. I suspect this is where KVM initializes the non-volatile system registers, likely related to handling certain aspects of virtualization or guest state persistence. It's a crucial function because if these registers aren't properly configured during guest startup, we're going to see a whole host of unpredictable behaviors, potentially leading to instability, security vulnerabilities, or even complete guest failures. I'm keen to see the specific registers it's touching – things like MSR_IA32_VM_CR, MSR_IA32_VM_HSAVE_PA, and others associated with virtualization extensions. Are they being set to a safe, known state? Is there a check for hardware capabilities before attempting to modify them? The devil's in the details here, so I want to understand the rationale behind each register's initialization and the potential impact of any omissions or unexpected values. I'm also interested to see how it's handled on different CPU architectures. This is an area where subtle hardware variations can have significant implications. I'm hoping to see a well-commented, robust implementation because this kind of initialization is the foundation for a reliable and secure virtual environment. Let's see what we've got.



16/2 2026/07/02 18:17 tool grepper
0m Args:
{
  "Expression": "kvm_init_nv_sysregs",
  "PathPrefix": "arch/arm64/kvm/nested.c"
}

Results:
{
  "Output": "arch/arm64/kvm/nested.c=1790=static __always_inline void set_sysreg_masks(struct kvm *kvm, int sr, struct resx resx)\n--\narch/arm64/kvm/nested.c-1798-\narch/arm64/kvm/nested.c:1799:int kvm_init_nv_sysregs(struct kvm_vcpu *vcpu)\narch/arm64/kvm/nested.c-1800-{\n"
}

17/2 2026/07/02 18:17 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 23162
							output: 42
							thoughts: 14
						

Thoughts:
Okay, here's my interpretation of that thought process, assuming I'm an expert in this field, and rendered in the first person:

**Navigating the Register Landscape**

Alright, time to dive in. My immediate thought is, "Let's check `nvhcr_el2_visibility`." This is where I start. I know this register intimately; it's the gateway, the key to understanding the current state. It's not a suggestion, it's a *check*. I already have a mental model of what it *should* contain, given the system's configuration and the current execution state, but I need a concrete data point. I'm not just idly curious; I'm systematically tracing a potential issue, verifying my assumptions, or preparing to address a specific problem. Knowing the exact value of `nvhcr_el2_visibility` at this point in the process gives me critical insight.



18/2 2026/07/02 18:17 tool grepper
0m Args:
{
  "Expression": "nvhcr_el2_visibility",
  "PathPrefix": "arch/arm64/kvm/sys_regs.c"
}

Results:
{
  "Output": "arch/arm64/kvm/sys_regs.c=2870=static unsigned int vncr_el2_visibility(const struct kvm_vcpu *vcpu,\n--\narch/arm64/kvm/sys_regs.c-2879-\narch/arm64/kvm/sys_regs.c:2880:static unsigned int nvhcr_el2_visibility(const struct kvm_vcpu *vcpu,\narch/arm64/kvm/sys_regs.c-2881-\t\t\t\t\tconst struct sys_reg_desc *rd)\n--\narch/arm64/kvm/sys_regs.c=3242=static const struct sys_reg_desc sys_reg_descs[] = {\n--\narch/arm64/kvm/sys_regs.c-3803-\tEL2_REG_FILTERED(NVHCR_EL2, undef_access, reset_val, 0,\narch/arm64/kvm/sys_regs.c:3804:\t\t\t nvhcr_el2_visibility),\narch/arm64/kvm/sys_regs.c-3805-\n"
}

19/2 2026/07/02 18:17 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 23710
							output: 42
							thoughts: 25
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Diving into System Register Handling**

Okay, so we need to understand how the hypervisor interacts with system registers. My immediate focus is on the core functions responsible for these read and write operations: `vcpu_read_sys_reg` and `vcpu_write_sys_reg`. These are the crucial entry points, the linchpins of managing the virtual CPU's view of the architectural state. I need to understand what these do, I suspect, these low-level functions probably sit right in the middle of all the register-access operations. I want to check how those functions are implemented, what their logic is and where they are called from, to understand how we are correctly handling the virtual machine's system registers. My first step is to check how these functions work, how they are implemented, and where they are called from. I bet the devil is in the details here and that this is where we have problems with handling the correct state of the registers.



20/2 2026/07/02 18:17 tool grepper
0m Args:
{
  "Expression": "vcpu_read_sys_reg",
  "PathPrefix": "arch/arm64/kvm/sys_regs.c"
}

Results:
{
  "Output": "arch/arm64/kvm/sys_regs.c=284=static void write_sr_to_cpu(enum vcpu_sysreg reg, u64 val)\n--\narch/arm64/kvm/sys_regs.c-320-\narch/arm64/kvm/sys_regs.c:321:u64 vcpu_read_sys_reg(const struct kvm_vcpu *vcpu, enum vcpu_sysreg reg)\narch/arm64/kvm/sys_regs.c-322-{\n--\narch/arm64/kvm/sys_regs.c=524=static bool access_rw(struct kvm_vcpu *vcpu,\n--\narch/arm64/kvm/sys_regs.c-530-\telse\narch/arm64/kvm/sys_regs.c:531:\t\tp-\u003eregval = vcpu_read_sys_reg(vcpu, r-\u003ereg);\narch/arm64/kvm/sys_regs.c-532-\n--\narch/arm64/kvm/sys_regs.c=593=static bool access_vm_reg(struct kvm_vcpu *vcpu,\n--\narch/arm64/kvm/sys_regs.c-604-\tif (~mask) {\narch/arm64/kvm/sys_regs.c:605:\t\tval = vcpu_read_sys_reg(vcpu, r-\u003ereg);\narch/arm64/kvm/sys_regs.c-606-\t\tval \u0026= ~mask;\n--\narch/arm64/kvm/sys_regs.c=618=static bool access_actlr(struct kvm_vcpu *vcpu,\n--\narch/arm64/kvm/sys_regs.c-627-\tget_access_mask(r, \u0026mask, \u0026shift);\narch/arm64/kvm/sys_regs.c:628:\tp-\u003eregval = (vcpu_read_sys_reg(vcpu, r-\u003ereg) \u0026 mask) \u003e\u003e shift;\narch/arm64/kvm/sys_regs.c-629-\n--\narch/arm64/kvm/sys_regs.c=2612=static bool access_csselr(struct kvm_vcpu *vcpu, struct sys_reg_params *p,\n--\narch/arm64/kvm/sys_regs.c-2619-\telse\narch/arm64/kvm/sys_regs.c:2620:\t\tp-\u003eregval = vcpu_read_sys_reg(vcpu, reg);\narch/arm64/kvm/sys_regs.c-2621-\treturn true;\n--\narch/arm64/kvm/sys_regs.c=2624=static bool access_ccsidr(struct kvm_vcpu *vcpu, struct sys_reg_params *p,\n--\narch/arm64/kvm/sys_regs.c-2631-\narch/arm64/kvm/sys_regs.c:2632:\tcsselr = vcpu_read_sys_reg(vcpu, CSSELR_EL1);\narch/arm64/kvm/sys_regs.c-2633-\tcsselr \u0026= CSSELR_EL1_Level | CSSELR_EL1_InD;\n--\narch/arm64/kvm/sys_regs.c=2808=static bool access_elr(struct kvm_vcpu *vcpu,\n--\narch/arm64/kvm/sys_regs.c-2814-\telse\narch/arm64/kvm/sys_regs.c:2815:\t\tp-\u003eregval = vcpu_read_sys_reg(vcpu, ELR_EL1);\narch/arm64/kvm/sys_regs.c-2816-\n--\narch/arm64/kvm/sys_regs.c=4079=static bool handle_vmalls12e1is(struct kvm_vcpu *vcpu, struct sys_reg_params *p,\n--\narch/arm64/kvm/sys_regs.c-4087-\narch/arm64/kvm/sys_regs.c:4088:\tvttbr = vcpu_read_sys_reg(vcpu, VTTBR_EL2);\narch/arm64/kvm/sys_regs.c-4089-\tlimit = BIT_ULL(kvm_get_pa_bits(vcpu-\u003ekvm));\n--\narch/arm64/kvm/sys_regs.c=4103=static bool handle_ripas2e1is(struct kvm_vcpu *vcpu, struct sys_reg_params *p,\n--\narch/arm64/kvm/sys_regs.c-4106-\tu32 sys_encoding = sys_insn(p-\u003eOp0, p-\u003eOp1, p-\u003eCRn, p-\u003eCRm, p-\u003eOp2);\narch/arm64/kvm/sys_regs.c:4107:\tu64 vttbr = vcpu_read_sys_reg(vcpu, VTTBR_EL2);\narch/arm64/kvm/sys_regs.c-4108-\tu64 base, range;\n--\narch/arm64/kvm/sys_regs.c=4158=static bool handle_ipas2e1is(struct kvm_vcpu *vcpu, struct sys_reg_params *p,\n--\narch/arm64/kvm/sys_regs.c-4161-\tu32 sys_encoding = sys_insn(p-\u003eOp0, p-\u003eOp1, p-\u003eCRn, p-\u003eCRm, p-\u003eOp2);\narch/arm64/kvm/sys_regs.c:4162:\tu64 vttbr = vcpu_read_sys_reg(vcpu, VTTBR_EL2);\narch/arm64/kvm/sys_regs.c-4163-\n"
}

21/2 2026/07/02 18:17 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25520
							output: 42
							thoughts: 14
						

Thoughts:
Okay, let's dive into this. Here's my thought process on this:

**Analyzing `vcpu_write_sys_reg`**

Alright, the instruction is to look at `vcpu_write_sys_reg`. My first thought is to understand its role and context. Why are we even examining this function? Is it related to a recent bug report? Performance tuning? Security analysis? Knowing the objective will significantly influence how I approach it.

I'll start by making sure I have a solid grasp of what "sys_reg" refers to. It's likely a system-level register within the virtual CPU's context. Understanding which architecture we're dealing with (x86, ARM, etc.) is crucial here, as the specific system registers and their functions are highly architecture-dependent.

Next, I'll need to confirm my understanding of `vcpu_write_sys_reg`. Specifically I'll need to know:
*   **What data structures are involved?** Is there a `vcpu` struct? A register state structure? Are we dealing with shadow registers, or direct hardware access? How does the structure change during the writes?
*   **What are the possible values for the registers being written to?** And what are their meanings? Which registers are considered critical, and which can be more safely modified.
*   **What are the potential failure scenarios?** Are there checks for invalid register values, or access permissions? Are there any race conditions or concurrency concerns if this function is called from multiple vCPUs concurrently?

From there, I'll explore the surrounding code. How does this function interact with other components of the virtualization infrastructure? I need to understand how it fits into the overall lifecycle of a vCPU. What kind of validation is being done before `vcpu_write_sys_reg`? Are there any potential security vulnerabilities here? Are any assumptions made regarding the guest operating system's behavior?

Finally, if I am doing this for a specific purpose, like a bug, I will likely then be looking for specific behavior, such as tracing down the flow of the code, to see how the write impacts later actions.



22/2 2026/07/02 18:17 tool grepper
0m Args:
{
  "Expression": "vcpu_write_sys_reg",
  "PathPrefix": "arch/arm64/kvm/sys_regs.c"
}

Results:
{
  "Output": "arch/arm64/kvm/sys_regs.c=321=u64 vcpu_read_sys_reg(const struct kvm_vcpu *vcpu, enum vcpu_sysreg reg)\n--\narch/arm64/kvm/sys_regs.c-374-\narch/arm64/kvm/sys_regs.c:375:void vcpu_write_sys_reg(struct kvm_vcpu *vcpu, u64 val, enum vcpu_sysreg reg)\narch/arm64/kvm/sys_regs.c-376-{\n--\narch/arm64/kvm/sys_regs.c=524=static bool access_rw(struct kvm_vcpu *vcpu,\n--\narch/arm64/kvm/sys_regs.c-528-\tif (p-\u003eis_write)\narch/arm64/kvm/sys_regs.c:529:\t\tvcpu_write_sys_reg(vcpu, p-\u003eregval, r-\u003ereg);\narch/arm64/kvm/sys_regs.c-530-\telse\n--\narch/arm64/kvm/sys_regs.c=593=static bool access_vm_reg(struct kvm_vcpu *vcpu,\n--\narch/arm64/kvm/sys_regs.c-611-\tval |= (p-\u003eregval \u0026 (mask \u003e\u003e shift)) \u003c\u003c shift;\narch/arm64/kvm/sys_regs.c:612:\tvcpu_write_sys_reg(vcpu, val, r-\u003ereg);\narch/arm64/kvm/sys_regs.c-613-\n--\narch/arm64/kvm/sys_regs.c=1000=static u64 reset_amair_el1(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)\n--\narch/arm64/kvm/sys_regs.c-1002-\tu64 amair = read_sysreg(amair_el1);\narch/arm64/kvm/sys_regs.c:1003:\tvcpu_write_sys_reg(vcpu, amair, AMAIR_EL1);\narch/arm64/kvm/sys_regs.c-1004-\treturn amair;\n--\narch/arm64/kvm/sys_regs.c=1007=static u64 reset_actlr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)\n--\narch/arm64/kvm/sys_regs.c-1009-\tu64 actlr = read_sysreg(actlr_el1);\narch/arm64/kvm/sys_regs.c:1010:\tvcpu_write_sys_reg(vcpu, actlr, ACTLR_EL1);\narch/arm64/kvm/sys_regs.c-1011-\treturn actlr;\n--\narch/arm64/kvm/sys_regs.c=1014=static u64 reset_mpidr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)\n--\narch/arm64/kvm/sys_regs.c-1028-\tmpidr |= (1ULL \u003c\u003c 31);\narch/arm64/kvm/sys_regs.c:1029:\tvcpu_write_sys_reg(vcpu, mpidr, MPIDR_EL1);\narch/arm64/kvm/sys_regs.c-1030-\n--\narch/arm64/kvm/sys_regs.c=2612=static bool access_csselr(struct kvm_vcpu *vcpu, struct sys_reg_params *p,\n--\narch/arm64/kvm/sys_regs.c-2617-\tif (p-\u003eis_write)\narch/arm64/kvm/sys_regs.c:2618:\t\tvcpu_write_sys_reg(vcpu, p-\u003eregval, reg);\narch/arm64/kvm/sys_regs.c-2619-\telse\n--\narch/arm64/kvm/sys_regs.c=2808=static bool access_elr(struct kvm_vcpu *vcpu,\n--\narch/arm64/kvm/sys_regs.c-2812-\tif (p-\u003eis_write)\narch/arm64/kvm/sys_regs.c:2813:\t\tvcpu_write_sys_reg(vcpu, p-\u003eregval, ELR_EL1);\narch/arm64/kvm/sys_regs.c-2814-\telse\n"
}

23/2 2026/07/02 18:17 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 27229
							output: 43
							thoughts: 476
						

Thoughts:
**My Assessment of the KVM Patch**

Okay, so this patch introduces support for a new system register, `NVHCR_EL2`, and tweaks the existing system register read/write functions (`vcpu_read_sys_reg` and `vcpu_write_sys_reg`) to handle `CPTR_EL2` with the new logic, contingent on `ARM64_HAS_NV2P1` being defined. It also modifies `__compute_hcr` to write to either `NVHCR_EL2` or `SYS_NVHCR_EL2`, and importantly, changes `fixup_guest_exit` to call `fixup_nv_guest_exit`, which reads `NVHCR_EL2` and assigns its value to `HCR_EL2`.

This looks like a solid piece of work; definitely worth a thorough fuzzing campaign. My initial focus should be on these key symbols: `kvm_init_nv_sysregs`, `limit_nv_id_reg`, `nvhcr_el2_visibility`, `fixup_nv_guest_exit`, `__sysreg_save_vel2_state`, and `__compute_hcr`.

Aha! Now I'm remembering that `__compute_hcr`, `fixup_nv_guest_exit`, and `__sysreg_save_vel2_state` reside within the `arch/arm64/kvm/hyp/vhe/switch.c` and `arch/arm64/kvm/hyp/vhe/sysreg-sr.c` files. This means these functions are executed in EL2 – the hypervisor's context. The question is can syzkaller be used to target them? The answer is likely yes, by running KVM guests.

Alright, let's make sure we've got all the ducks in a row. I need to confirm if there are any specific kernel configurations required for nested virtualization to work, beyond the obvious. I ran a quick search for `NESTED` in both `arch/arm64/kvm/Kconfig` and the top-level `arch/arm64/Kconfig`, but came up empty. Let me re-examine the `arch/arm64/kvm/Makefile`. Bingo! It looks like `emulate-nested.o` and `nested.o` are compiled unconditionally, as long as `CONFIG_KVM` is enabled. So it seems that `KVM` is the only mandatory configuration.

Just to be absolutely sure, let's verify `ARM64_HAS_NESTED_VIRT` in `arch/arm64/kernel/cpufeature.c` again.



24/2 2026/07/02 18:17 tool grepper
0m Args:
{
  "Expression": "ARM64_HAS_NESTED_VIRT",
  "PathPrefix": "arch/arm64/kernel/cpufeature.c"
}

Results:
{
  "Output": "arch/arm64/kernel/cpufeature.c=1887=static bool has_nv1(const struct arm64_cpu_capabilities *entry, int scope)\n--\narch/arm64/kernel/cpufeature.c-1903-\narch/arm64/kernel/cpufeature.c:1904:\treturn (__system_matches_cap(ARM64_HAS_NESTED_VIRT) \u0026\u0026\narch/arm64/kernel/cpufeature.c-1905-\t\t!(has_cpuid_feature(entry, scope) ||\n--\narch/arm64/kernel/cpufeature.c=2545=static const struct arm64_cpu_capabilities arm64_features[] = {\n--\narch/arm64/kernel/cpufeature.c-2609-\t\t.desc = \"Nested Virtualization Support\",\narch/arm64/kernel/cpufeature.c:2610:\t\t.capability = ARM64_HAS_NESTED_VIRT,\narch/arm64/kernel/cpufeature.c-2611-\t\t.type = ARM64_CPUCAP_SYSTEM_FEATURE,\n"
}

25/2 2026/07/02 18:17 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28615
							output: 35
							thoughts: 74
						

Thoughts:
**My Assessment of ARM64 Nested Virtualization Configuration**

Okay, so I'm thinking about nested virtualization on ARM64 and the configuration options. My initial thought is, there isn't a dedicated `CONFIG_ARM64_NESTED_VIRT` option, which is important to remember. That's a key starting point. Instead, it seems we need to look at the other related configurations to determine support.

The next thought is, what about `CONFIG_ARM64_VHE`? Ah yes, Virtualization Host Extensions. That rings a bell. Nested virtualization *does* depend on VHE, that's almost always the case on ARM64. But then I have to ask myself, isn't VHE usually enabled by default in KVM? Or maybe part of KVM's base configuration? I need to double-check that assumption because it’s important to see if it’s explicitly required or inherently present. Let me make sure I verify that.



26/2 2026/07/02 18:17 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "config ARM64_VHE",
  "PathPrefix": "arch/arm64/Kconfig"
}

Results:
{
  "error": "no matches"
}

27/2 2026/07/02 18:17 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28947
							output: 34
							thoughts: 23
						

Thoughts:
Here's a first-person summary, tailored for an expert audience, of the provided text:

**Diving into KVM Configuration**

Okay, so I need to understand the KVM configuration for the ARM64 architecture in the Linux kernel. Where would I expect to find the settings? Naturally, it's going to be in the architecture-specific Kconfig file. Let's see... I'll check `arch/arm64/kvm/Kconfig`. That's the logical place to start looking for the details on how the KVM functionality is configured for this specific architecture. Time to delve into that file and see what's what. I anticipate seeing options for enabling KVM, defining specific features, and potentially configuring some of the platform-specific aspects. Let's see what gems are hidden in there.



28/2 2026/07/02 18:17 tool grepper
0m Args:
{
  "Expression": "config KVM",
  "PathPrefix": "arch/arm64/kvm/Kconfig"
}

Results:
{
  "Output": "arch/arm64/kvm/Kconfig=18=if VIRTUALIZATION\narch/arm64/kvm/Kconfig-19-\narch/arm64/kvm/Kconfig:20:menuconfig KVM\narch/arm64/kvm/Kconfig-21-\tbool \"Kernel-based Virtual Machine (KVM) support\"\n"
}

29/2 2026/07/02 18:17 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29277
							output: 42
							thoughts: 676
						

Thoughts:
**Analyzing the KVM Configuration for this Patch**

Okay, so I'm looking at this KVM configuration, and it's clear the key lies within these focus symbols. Let's break them down. I have:

*   `kvm_init_nv_sysregs`
*   `limit_nv_id_reg`
*   `nvhcr_el2_visibility`
*   `fixup_nv_guest_exit`
*   `__sysreg_save_vel2_state`
*   `__compute_hcr`

Alright, immediately I recognize that `__compute_hcr` and `__sysreg_save_vel2_state` reside in the hypervisor code, specifically within `arch/arm64/kvm/hyp/vhe/switch.c` and `arch/arm64/kvm/hyp/vhe/sysreg-sr.c`. This makes sense given the functionality. Now, I have to be sure about `fixup_nv_guest_exit`. I believe it's static... yes, confirmed, it's also static within `arch/arm64/kvm/hyp/vhe/switch.c`. Good, and that's the correct name. I remember it was added by the patch. Okay, and `__compute_hcr` is static too, and it computes the HCR, and `__sysreg_save_vel2_state` saves the VEL2 state. `kvm_init_nv_sysregs` is an initialization function, and `limit_nv_id_reg` limits the NV ID register. Finally, `nvhcr_el2_visibility` checks the visibility.

These symbols seem to be well-chosen. They are specific to the code I am interested in. They're not just generic, potentially hot-path functions, other than the ones that are called on the guest exit. But, even those functions are specifically related to the logic introduced by this patch. Wait, `__compute_hcr` is called on every guest entry. It could be a hot path, though. Let's see... and `fixup_nv_guest_exit` is called from `fixup_guest_exit`, which, as I recall, is called on *every* guest exit. That means I need to be careful with those.

Perhaps I should refocus on the init and configuration functions to avoid getting bogged down in hot paths. Let's stick with:

*   `kvm_init_nv_sysregs`
*   `limit_nv_id_reg`
*   `nvhcr_el2_visibility`
*   And, as an extra, `populate_nv_trap_config` from `arch/arm64/kvm/emulate-nested.c`.



30/2 2026/07/02 18:18 tool grepper
0m Args:
{
  "Expression": "populate_nv_trap_config",
  "PathPrefix": "arch/arm64/kvm/emulate-nested.c"
}

Results:
{
  "Output": "arch/arm64/kvm/emulate-nested.c=2297=static __init int check_all_fgt_masks(int ret)\n--\narch/arm64/kvm/emulate-nested.c-2325-\narch/arm64/kvm/emulate-nested.c:2326:int __init populate_nv_trap_config(void)\narch/arm64/kvm/emulate-nested.c-2327-{\n"
}

31/2 2026/07/02 18:18 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 30737
							output: 40
							thoughts: 269
						

Thoughts:
**My Fuzzing Strategy for NV Access**

Okay, so I'm thinking about where to focus my fuzzing efforts related to NV access. `populate_nv_trap_config` looks like a non-starter. It's an `__init` function, so it's only called during boot, meaning no user-controlled input. Not ideal.

However, `limit_nv_id_reg` is called when userspace reads ID registers. That's immediately promising. Then I see `kvm_init_nv_sysregs`, called when a vCPU initializes, and that seems like a solid target too. Next up, `nvhcr_el2_visibility` is called when userspace accesses the system register, which is great.

`vcpu_read_sys_reg` and `vcpu_write_sys_reg` are interesting, as they've been modified to handle `CPTR_EL2` and `CNTHCTL_EL2`. These are called anytime userspace or the guest accesses system registers. They're potentially good, but they're pretty general, which might make fuzzing less effective. Perhaps I should keep an eye on these.

Let me take a quick look at `kvm_has_nv2` and `kvm_has_nv3`. They're inline functions, so probably not the most efficient targets.

Okay, let's switch gears and check out `timer_set_traps` in `arch/arm64/kvm/arch_timer.c`. I have a feeling that could lead somewhere interesting.



32/2 2026/07/02 18:18 tool grepper
0m Args:
{
  "Expression": "timer_set_traps",
  "PathPrefix": "arch/arm64/kvm/arch_timer.c"
}

Results:
{
  "Output": "arch/arm64/kvm/arch_timer.c=747=static void kvm_timer_vcpu_load_nested_switch(struct kvm_vcpu *vcpu,\n--\narch/arm64/kvm/arch_timer.c-780-\narch/arm64/kvm/arch_timer.c:781:static void timer_set_traps(struct kvm_vcpu *vcpu, struct timer_map *map)\narch/arm64/kvm/arch_timer.c-782-{\n--\narch/arm64/kvm/arch_timer.c=889=void kvm_timer_vcpu_load(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/arch_timer.c-920-\narch/arm64/kvm/arch_timer.c:921:\ttimer_set_traps(vcpu, \u0026map);\narch/arm64/kvm/arch_timer.c-922-}\n"
}

33/2 2026/07/02 18:18 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 31651
							output: 37
							thoughts: 69
						

Thoughts:
Here's my summary, as though I'm thinking through this process:

**Diving into KVM Timer Implementation**

Okay, so `timer_set_traps` is the function that's getting called whenever a vCPU gets loaded onto a physical CPU – `kvm_timer_vcpu_load`. That's a classic hot path situation, a performance-critical area, so we need to be extra careful with it. My instinct is to understand exactly *what* is happening within that path, particularly if we're seeing performance regressions or unexpected behavior.

Since we are talking about `arm64`, and the timer behavior depends on the architecture, I'll go straight to `arch/arm64/kvm/config.c` and take a look at `check_feature_map`. It's likely involved with how the timers are configured or what features are supported. Understanding the feature set, and potentially the configuration, will offer clues to the hot path logic and the interplay between vCPU loading and timer setup.



34/2 2026/07/02 18:18 tool grepper
0m Args:
{
  "Expression": "check_feature_map",
  "PathPrefix": "arch/arm64/kvm/config.c"
}

Results:
{
  "Output": "arch/arm64/kvm/config.c=1376=static void __init check_reg_desc(const struct reg_feat_map_desc *r)\n--\narch/arm64/kvm/config.c-1381-\narch/arm64/kvm/config.c:1382:void __init check_feature_map(void)\narch/arm64/kvm/config.c-1383-{\n"
}

35/2 2026/07/02 18:18 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 32099
							output: 39
							thoughts: 44
						

Thoughts:
Here's my thought process, summarized as you requested:

**Initialization and Key Functions**

Okay, so `check_feature_map` is being defined within an `__init` function, so it's probably about initializing something. I need to understand what. My next step is definitely to investigate `get_reg_fixed_bits`, specifically within `arch/arm64/kvm/config.c`. That file is a good place to start, given the file path. It will hopefully provide some context for `check_feature_map` within the broader context of ARM64 KVM configuration. I expect that digging into that function will shed light on the feature map's purpose and how it's initialized, or validated.



36/2 2026/07/02 18:18 tool grepper
0m Args:
{
  "Expression": "get_reg_fixed_bits",
  "PathPrefix": "arch/arm64/kvm/config.c"
}

Results:
{
  "Output": "arch/arm64/kvm/config.c=1509=void compute_fgu(struct kvm *kvm, enum fgt_group_id fgt)\n--\narch/arm64/kvm/config.c-1552-\narch/arm64/kvm/config.c:1553:struct resx get_reg_fixed_bits(struct kvm *kvm, enum vcpu_sysreg reg)\narch/arm64/kvm/config.c-1554-{\n"
}

37/2 2026/07/02 18:18 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 32477
							output: 186
							thoughts: 451
						

Thoughts:
**My Analysis of the KVM Nested Virtualization Patch**

Okay, let's break this down from a fuzzing perspective. The target is vCPU initialization, and `get_reg_fixed_bits` is triggered during that process. That seems like a good starting point. I need to identify the key symbols modified by this patch that are worth focusing on for fuzzing.

My list of candidate focus symbols includes: `kvm_init_nv_sysregs`, `limit_nv_id_reg`, `nvhcr_el2_visibility`, and `get_reg_fixed_bits`. These are all directly related to initialization or key register logic within the nested virtualization implementation. Looking further, I'll add `fixup_nv_guest_exit`, even though it's on the exit path. This is important because it specifically handles the `NVHCR_EL2` logic, which is new.

Now, let's verify the build configuration. The patch affects `arch/arm64/kvm/emulate-nested.c` and `arch/arm64/kvm/nested.c`, and these files are compiled only when `CONFIG_KVM` is enabled. That's straightforward; the `KVM` config is sufficient to build these code paths. No other configuration dependencies are apparent.

So, here's my summarized view for fuzzing:

*   **WorthFuzzing:** True - this is a substantial modification with clear entry points.
*   **FocusSymbols:**
    *   `kvm_init_nv_sysregs`
    *   `limit_nv_id_reg`
    *   `nvhcr_el2_visibility`
    *   `get_reg_fixed_bits`
    *   `fixup_nv_guest_exit`
*   **EnableConfigs:** `KVM`

My reasoning is simple. The patch implements ARM64 Nested Virtualization v3 and v2p1 support in KVM. It fundamentally changes how KVM handles nested virtualization, including the sysregs (NVHCR_EL2, CPTR_EL2, CNTHCTL_EL2), trap configuration, and guest exit flow. This means that by running a nested virtualization KVM guest, we can activate the affected code to be fuzzed.



38/2 2026/07/02 18:18 tool set-results
0m Args:
{
  "EnableConfigs": [
    "KVM"
  ],
  "FocusSymbols": [
    "kvm_init_nv_sysregs",
    "limit_nv_id_reg",
    "nvhcr_el2_visibility",
    "get_reg_fixed_bits",
    "fixup_nv_guest_exit"
  ],
  "Reasoning": "The patch implements support for ARM64 Nested Virtualization v3 (FEAT_NV3) and FEAT_NV2p1 in KVM. It modifies core KVM nested virtualization logic, including sysreg handling (e.g., NVHCR_EL2, CPTR_EL2, CNTHCTL_EL2), trap configuration, and guest exit fixups. This is reachable and functional code that can be tested by running KVM guests with nested virtualization enabled.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "KVM"
  ],
  "FocusSymbols": [
    "kvm_init_nv_sysregs",
    "limit_nv_id_reg",
    "nvhcr_el2_visibility",
    "get_reg_fixed_bits",
    "fixup_nv_guest_exit"
  ],
  "Reasoning": "The patch implements support for ARM64 Nested Virtualization v3 (FEAT_NV3) and FEAT_NV2p1 in KVM. It modifies core KVM nested virtualization logic, including sysreg handling (e.g., NVHCR_EL2, CPTR_EL2, CNTHCTL_EL2), trap configuration, and guest exit fixups. This is reachable and functional code that can be tested by running KVM guests with nested virtualization enabled.",
  "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)