| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/24 22:42 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"KMSANReasoning": "",
"NeedsKMSAN": false,
"Reasoning": "The patch series adds support for KVM_PRE_FAULT_MEMORY on arm64. All substantive implementation changes (stage-2 page table walking, prefault abort handling, ESR parsing) are located in arch/arm64/ and are exclusive to arm64, making them structurally unreachable on the target architecture (amd64). The changes to Documentation/ and tools/testing/selftests/ are non-kernel or user-space changes. The only change to common code in virt/kvm/kvm_main.c introduces a weak fallback hook kvm_arch_vcpu_allow_pre_fault_memory() that unconditionally returns true on amd64, leaving control flow and state management on x86 completely unaltered. Therefore, there are no reachable modified code paths worth fuzzing on amd64.",
"WorthFuzzing": false
} |
| 1/1 | 2026/09/24 22:42 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 60fb16a4fe6b0c035e7b16d9586a6fca6865d428\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Thu Sep 24 22:42:24 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst\nindex 1a90598901c57..71b642148b7f6 100644\n--- a/Documentation/virt/kvm/api.rst\n+++ b/Documentation/virt/kvm/api.rst\n@@ -6489,7 +6489,7 @@ See KVM_SET_USER_MEMORY_REGION2 for additional details.\n ---------------------------\n \n :Capability: KVM_CAP_PRE_FAULT_MEMORY\n-:Architectures: none\n+:Architectures: x86, s390, arm64\n :Type: vcpu ioctl\n :Parameters: struct kvm_pre_fault_memory (in/out)\n :Returns: 0 if at least one page is processed, \u003c 0 on error\n@@ -6497,12 +6497,15 @@ See KVM_SET_USER_MEMORY_REGION2 for additional details.\n Errors:\n \n ========== ===============================================================\n+ EAGAIN A race occurred before progress was made, but a retry may succeed.\n EINVAL The specified `gpa` and `size` were invalid (e.g. not\n page aligned, causes an overflow, or size is zero), or the VM\n is UCONTROL (s390).\n ENOENT The specified `gpa` is outside defined memslots.\n+ ENOEXEC The vCPU has not been initialised (arm64).\n EINTR An unmasked signal is pending and no page was processed.\n EFAULT The parameter address was invalid.\n+ EHWPOISON A poisoned host page was encountered.\n EOPNOTSUPP Mapping memory for a GPA is unsupported by the\n hypervisor, and/or for the current vCPU state/mode.\n EIO unexpected error conditions (also causes a WARN)\n@@ -6522,7 +6525,17 @@ Errors:\n KVM_PRE_FAULT_MEMORY populates KVM's stage-2 page tables used to map memory\n for the current vCPU state. KVM maps memory as if the vCPU generated a\n stage-2 read page fault, e.g. faults in memory as needed, but doesn't break\n-CoW. On x86, KVM does not mark any newly created stage-2 PTE as Accessed.\n+CoW. On arm64, KVM marks newly created stage-2 PTEs as Accessed, as it\n+does for any stage-2 fault, but leaves the Accessed state of existing PTEs\n+unchanged. On x86, KVM does not mark any newly created stage-2 PTE as\n+Accessed, and for s390 it is not applicable.\n+\n+On arm64, a GPA is interpreted as an IPA, and never interpreted as the IPA\n+of a nested guest. Pre-faulting only populates canonical stage-2 page\n+tables.\n+\n+The feature is not supported on arm64 if the protected KVM (pKVM) feature\n+is enabled.\n \n In the case of confidential VM types where there is an initial set up of\n private guest memory before the guest is 'finalized'/measured, this ioctl\n@@ -6537,7 +6550,7 @@ When the ioctl returns, the input values are updated to point to the\n remaining range. If `size` \u003e 0 on return, the caller can just issue\n the ioctl again with the same `struct kvm_map_memory` argument.\n \n-Shadow page tables cannot support this ioctl because they\n+On x86, shadow page tables cannot support this ioctl because they\n are indexed by virtual address or nested guest physical address.\n Calling this ioctl when the guest is using shadow page tables (for\n example because it is running a nested guest with nested page tables)\ndiff --git a/arch/arm64/include/asm/esr.h b/arch/arm64/include/asm/esr.h\nindex f816f5d77f1a5..9c0205983d9a8 100644\n--- a/arch/arm64/include/asm/esr.h\n+++ b/arch/arm64/include/asm/esr.h\n@@ -437,6 +437,50 @@\n #ifndef __ASSEMBLER__\n #include \u003casm/types.h\u003e\n \n+static __always_inline u8 esr_get_ec(unsigned long esr)\n+{\n+\treturn ESR_ELx_EC(esr);\n+}\n+\n+static __always_inline bool esr_trap_is_iabt(unsigned long esr)\n+{\n+\treturn esr_get_ec(esr) == ESR_ELx_EC_IABT_LOW;\n+}\n+\n+static __always_inline bool esr_abt_is_s1ptw(unsigned long esr)\n+{\n+\treturn esr \u0026 ESR_ELx_S1PTW;\n+}\n+\n+/* Always check for S1PTW *before* using this. */\n+static __always_inline bool esr_dabt_is_write(unsigned long esr)\n+{\n+\treturn esr \u0026 ESR_ELx_WNR;\n+}\n+\n+static __always_inline bool esr_dabt_is_cm(unsigned long esr)\n+{\n+\treturn esr \u0026 ESR_ELx_CM;\n+}\n+\n+static __always_inline bool esr_abt_is_exec_fault(unsigned long esr)\n+{\n+\treturn esr_trap_is_iabt(esr) \u0026\u0026 !esr_abt_is_s1ptw(esr);\n+}\n+\n+static __always_inline bool esr_abt_is_sea(unsigned long esr)\n+{\n+\tswitch (esr \u0026 ESR_ELx_FSC) {\n+\tcase ESR_ELx_FSC_EXTABT:\n+\tcase ESR_ELx_FSC_SEA_TTW(-1) ... ESR_ELx_FSC_SEA_TTW(3):\n+\tcase ESR_ELx_FSC_SECC:\n+\tcase ESR_ELx_FSC_SECC_TTW(-1) ... ESR_ELx_FSC_SECC_TTW(3):\n+\t\treturn true;\n+\tdefault:\n+\t\treturn false;\n+\t}\n+}\n+\n static inline unsigned long esr_brk_comment(unsigned long esr)\n {\n \treturn esr \u0026 ESR_ELx_BRK64_ISS_COMMENT_MASK;\ndiff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h\nindex a3c1928bdf743..a3b2722655543 100644\n--- a/arch/arm64/include/asm/kvm_emulate.h\n+++ b/arch/arm64/include/asm/kvm_emulate.h\n@@ -411,18 +411,13 @@ static __always_inline int kvm_vcpu_dabt_get_rd(const struct kvm_vcpu *vcpu)\n \n static __always_inline bool kvm_vcpu_abt_iss1tw(const struct kvm_vcpu *vcpu)\n {\n-\treturn !!(kvm_vcpu_get_esr(vcpu) \u0026 ESR_ELx_S1PTW);\n+\treturn esr_abt_is_s1ptw(kvm_vcpu_get_esr(vcpu));\n }\n \n /* Always check for S1PTW *before* using this. */\n static __always_inline bool kvm_vcpu_dabt_iswrite(const struct kvm_vcpu *vcpu)\n {\n-\treturn kvm_vcpu_get_esr(vcpu) \u0026 ESR_ELx_WNR;\n-}\n-\n-static inline bool kvm_vcpu_dabt_is_cm(const struct kvm_vcpu *vcpu)\n-{\n-\treturn !!(kvm_vcpu_get_esr(vcpu) \u0026 ESR_ELx_CM);\n+\treturn esr_dabt_is_write(kvm_vcpu_get_esr(vcpu));\n }\n \n static __always_inline unsigned int kvm_vcpu_dabt_get_as(const struct kvm_vcpu *vcpu)\n@@ -438,17 +433,12 @@ static __always_inline bool kvm_vcpu_trap_il_is32bit(const struct kvm_vcpu *vcpu\n \n static __always_inline u8 kvm_vcpu_trap_get_class(const struct kvm_vcpu *vcpu)\n {\n-\treturn ESR_ELx_EC(kvm_vcpu_get_esr(vcpu));\n+\treturn esr_get_ec(kvm_vcpu_get_esr(vcpu));\n }\n \n static inline bool kvm_vcpu_trap_is_iabt(const struct kvm_vcpu *vcpu)\n {\n-\treturn kvm_vcpu_trap_get_class(vcpu) == ESR_ELx_EC_IABT_LOW;\n-}\n-\n-static inline bool kvm_vcpu_trap_is_exec_fault(const struct kvm_vcpu *vcpu)\n-{\n-\treturn kvm_vcpu_trap_is_iabt(vcpu) \u0026\u0026 !kvm_vcpu_abt_iss1tw(vcpu);\n+\treturn esr_trap_is_iabt(kvm_vcpu_get_esr(vcpu));\n }\n \n static __always_inline u8 kvm_vcpu_trap_get_fault(const struct kvm_vcpu *vcpu)\n@@ -468,26 +458,9 @@ bool kvm_vcpu_trap_is_translation_fault(const struct kvm_vcpu *vcpu)\n \treturn esr_fsc_is_translation_fault(kvm_vcpu_get_esr(vcpu));\n }\n \n-static inline\n-u64 kvm_vcpu_trap_get_perm_fault_granule(const struct kvm_vcpu *vcpu)\n-{\n-\tunsigned long esr = kvm_vcpu_get_esr(vcpu);\n-\n-\tBUG_ON(!esr_fsc_is_permission_fault(esr));\n-\treturn BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(esr \u0026 ESR_ELx_FSC_LEVEL));\n-}\n-\n static __always_inline bool kvm_vcpu_abt_issea(const struct kvm_vcpu *vcpu)\n {\n-\tswitch (kvm_vcpu_trap_get_fault(vcpu)) {\n-\tcase ESR_ELx_FSC_EXTABT:\n-\tcase ESR_ELx_FSC_SEA_TTW(-1) ... ESR_ELx_FSC_SEA_TTW(3):\n-\tcase ESR_ELx_FSC_SECC:\n-\tcase ESR_ELx_FSC_SECC_TTW(-1) ... ESR_ELx_FSC_SECC_TTW(3):\n-\t\treturn true;\n-\tdefault:\n-\t\treturn false;\n-\t}\n+\treturn esr_abt_is_sea(kvm_vcpu_get_esr(vcpu));\n }\n \n static __always_inline int kvm_vcpu_sys_get_rt(struct kvm_vcpu *vcpu)\n@@ -496,9 +469,9 @@ static __always_inline int kvm_vcpu_sys_get_rt(struct kvm_vcpu *vcpu)\n \treturn ESR_ELx_SYS64_ISS_RT(esr);\n }\n \n-static inline bool kvm_is_write_fault(struct kvm_vcpu *vcpu)\n+static inline bool esr_abt_is_write_fault(unsigned long esr)\n {\n-\tif (kvm_vcpu_abt_iss1tw(vcpu)) {\n+\tif (esr_abt_is_s1ptw(esr)) {\n \t\t/*\n \t\t * Only a permission fault on a S1PTW should be\n \t\t * considered as a write. Otherwise, page tables baked\n@@ -511,13 +484,18 @@ static inline bool kvm_is_write_fault(struct kvm_vcpu *vcpu)\n \t\t * first), then a permission fault to allow the flags\n \t\t * to be set.\n \t\t */\n-\t\treturn kvm_vcpu_trap_is_permission_fault(vcpu);\n+\t\treturn esr_fsc_is_permission_fault(esr);\n \t}\n \n-\tif (kvm_vcpu_trap_is_iabt(vcpu))\n+\tif (esr_trap_is_iabt(esr))\n \t\treturn false;\n \n-\treturn kvm_vcpu_dabt_iswrite(vcpu);\n+\treturn esr_dabt_is_write(esr);\n+}\n+\n+static inline bool kvm_is_write_fault(struct kvm_vcpu *vcpu)\n+{\n+\treturn esr_abt_is_write_fault(kvm_vcpu_get_esr(vcpu));\n }\n \n static inline unsigned long kvm_vcpu_get_mpidr_aff(struct kvm_vcpu *vcpu)\ndiff --git a/arch/arm64/include/asm/kvm_pgtable.h b/arch/arm64/include/asm/kvm_pgtable.h\nindex 20d12da3d28ee..589db1d6405fa 100644\n--- a/arch/arm64/include/asm/kvm_pgtable.h\n+++ b/arch/arm64/include/asm/kvm_pgtable.h\n@@ -859,6 +859,8 @@ int kvm_pgtable_walk(struct kvm_pgtable *pgt, u64 addr, u64 size,\n * @addr:\tInput address for the start of the walk.\n * @ptep:\tPointer to storage for the retrieved PTE.\n * @level:\tPointer to storage for the level of the retrieved PTE.\n+ * @flags:\tFlags to control the page-table walk\n+ *\t\t(see struct kvm_pgtable_visit_ctx).\n *\n * The offset of @addr within a page is ignored.\n *\n@@ -869,7 +871,8 @@ int kvm_pgtable_walk(struct kvm_pgtable *pgt, u64 addr, u64 size,\n * Return: 0 on success, negative error code on failure.\n */\n int kvm_pgtable_get_leaf(struct kvm_pgtable *pgt, u64 addr,\n-\t\t\t kvm_pte_t *ptep, s8 *level);\n+\t\t\t kvm_pte_t *ptep, s8 *level,\n+\t\t\t enum kvm_pgtable_walk_flags flags);\n \n /**\n * kvm_pgtable_stage2_pte_prot() - Retrieve the protection attributes of a\ndiff --git a/arch/arm64/include/asm/kvm_pkvm.h b/arch/arm64/include/asm/kvm_pkvm.h\nindex cad60569f0619..c1831c8e421d4 100644\n--- a/arch/arm64/include/asm/kvm_pkvm.h\n+++ b/arch/arm64/include/asm/kvm_pkvm.h\n@@ -44,9 +44,9 @@ static inline bool kvm_pkvm_ext_allowed(struct kvm *kvm, long ext)\n \tcase KVM_CAP_ARM_PTRAUTH_GENERIC:\n \t\treturn true;\n \tcase KVM_CAP_ARM_MTE:\n-\t\treturn false;\n \tcase KVM_CAP_ARM_EAGER_SPLIT_CHUNK_SIZE:\n \tcase KVM_CAP_ARM_SUPPORTED_BLOCK_SIZES:\n+\tcase KVM_CAP_PRE_FAULT_MEMORY:\n \t\treturn false;\n \tdefault:\n \t\treturn !kvm || !kvm_vm_is_protected(kvm);\ndiff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig\nindex 449154f9a4852..71233068b7cb6 100644\n--- a/arch/arm64/kvm/Kconfig\n+++ b/arch/arm64/kvm/Kconfig\n@@ -37,6 +37,7 @@ menuconfig KVM\n \tselect SCHED_INFO\n \tselect GUEST_PERF_EVENTS if PERF_EVENTS\n \tselect KVM_GUEST_MEMFD\n+\tselect KVM_GENERIC_PRE_FAULT_MEMORY\n \thelp\n \t Support hosting virtualized guest machines.\n \ndiff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c\nindex 31f803010c57c..367638e1e0204 100644\n--- a/arch/arm64/kvm/arm.c\n+++ b/arch/arm64/kvm/arm.c\n@@ -410,6 +410,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)\n \tcase KVM_CAP_COUNTER_OFFSET:\n \tcase KVM_CAP_ARM_WRITABLE_IMP_ID_REGS:\n \tcase KVM_CAP_ARM_SEA_TO_USER:\n+\tcase KVM_CAP_PRE_FAULT_MEMORY:\n \t\tr = 1;\n \t\tbreak;\n \tcase KVM_CAP_SET_GUEST_DEBUG2:\ndiff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c\nindex a6a47c1e058b3..443ee433119ef 100644\n--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c\n+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c\n@@ -540,7 +540,7 @@ static int host_stage2_adjust_range(u64 addr, struct kvm_mem_range *range)\n \tint ret;\n \n \thyp_assert_lock_held(\u0026host_mmu.lock);\n-\tret = kvm_pgtable_get_leaf(\u0026host_mmu.pgt, addr, \u0026pte, \u0026level);\n+\tret = kvm_pgtable_get_leaf(\u0026host_mmu.pgt, addr, \u0026pte, \u0026level, 0);\n \tif (ret)\n \t\treturn ret;\n \n@@ -930,7 +930,7 @@ static int get_valid_guest_pte(struct pkvm_hyp_vm *vm, u64 ipa, kvm_pte_t *ptep,\n \ts8 level;\n \tint ret;\n \n-\tret = kvm_pgtable_get_leaf(\u0026vm-\u003epgt, ipa, \u0026pte, \u0026level);\n+\tret = kvm_pgtable_get_leaf(\u0026vm-\u003epgt, ipa, \u0026pte, \u0026level, 0);\n \tif (ret)\n \t\treturn ret;\n \tif (guest_pte_is_poisoned(pte))\n@@ -979,7 +979,7 @@ int __pkvm_vcpu_in_poison_fault(struct pkvm_hyp_vcpu *hyp_vcpu)\n \tipa |= FAR_TO_FIPA_OFFSET(kvm_vcpu_get_hfar(\u0026hyp_vcpu-\u003evcpu));\n \n \tguest_lock_component(vm);\n-\tret = kvm_pgtable_get_leaf(\u0026vm-\u003epgt, ipa, \u0026pte, \u0026level);\n+\tret = kvm_pgtable_get_leaf(\u0026vm-\u003epgt, ipa, \u0026pte, \u0026level, 0);\n \tif (ret)\n \t\tgoto unlock;\n \n@@ -1335,7 +1335,7 @@ static int host_stage2_get_guest_info(phys_addr_t phys, struct pkvm_hyp_vm **vm,\n \t\treturn -EPERM;\n \t}\n \n-\tret = kvm_pgtable_get_leaf(\u0026host_mmu.pgt, phys, \u0026pte, \u0026level);\n+\tret = kvm_pgtable_get_leaf(\u0026host_mmu.pgt, phys, \u0026pte, \u0026level, 0);\n \tif (ret)\n \t\treturn ret;\n \n@@ -1564,7 +1564,7 @@ static int __check_host_shared_guest(struct pkvm_hyp_vm *vm, u64 *__phys, u64 ip\n \ts8 level;\n \tint ret;\n \n-\tret = kvm_pgtable_get_leaf(\u0026vm-\u003epgt, ipa, \u0026pte, \u0026level);\n+\tret = kvm_pgtable_get_leaf(\u0026vm-\u003epgt, ipa, \u0026pte, \u0026level, 0);\n \tif (ret)\n \t\treturn ret;\n \tif (!kvm_pte_valid(pte))\ndiff --git a/arch/arm64/kvm/hyp/nvhe/mm.c b/arch/arm64/kvm/hyp/nvhe/mm.c\nindex 29ab5ee9d57fc..bcf2fd1ddd75d 100644\n--- a/arch/arm64/kvm/hyp/nvhe/mm.c\n+++ b/arch/arm64/kvm/hyp/nvhe/mm.c\n@@ -486,7 +486,7 @@ static int check_page_ownership(phys_addr_t phys)\n \t\t\treturn -EPERM;\n \t}\n \n-\tret = kvm_pgtable_get_leaf(\u0026host_mmu.pgt, phys, \u0026pte, NULL);\n+\tret = kvm_pgtable_get_leaf(\u0026host_mmu.pgt, phys, \u0026pte, NULL, 0);\n \tif (ret)\n \t\treturn ret;\n \ndiff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c\nindex b74dd5ce1efd3..347eec3957d6a 100644\n--- a/arch/arm64/kvm/hyp/pgtable.c\n+++ b/arch/arm64/kvm/hyp/pgtable.c\n@@ -298,12 +298,13 @@ static int leaf_walker(const struct kvm_pgtable_visit_ctx *ctx,\n }\n \n int kvm_pgtable_get_leaf(struct kvm_pgtable *pgt, u64 addr,\n-\t\t\t kvm_pte_t *ptep, s8 *level)\n+\t\t\t kvm_pte_t *ptep, s8 *level,\n+\t\t\t enum kvm_pgtable_walk_flags flags)\n {\n \tstruct leaf_walk_data data;\n \tstruct kvm_pgtable_walker walker = {\n \t\t.cb\t= leaf_walker,\n-\t\t.flags\t= KVM_PGTABLE_WALK_LEAF,\n+\t\t.flags\t= flags | KVM_PGTABLE_WALK_LEAF,\n \t\t.arg\t= \u0026data,\n \t};\n \tint ret;\ndiff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c\nindex d6187295c3736..98c8ca0faf792 100644\n--- a/arch/arm64/kvm/mmu.c\n+++ b/arch/arm64/kvm/mmu.c\n@@ -5,6 +5,7 @@\n */\n \n #include \u003clinux/acpi.h\u003e\n+#include \u003clinux/cleanup.h\u003e\n #include \u003clinux/mman.h\u003e\n #include \u003clinux/kvm_host.h\u003e\n #include \u003clinux/interval_tree.h\u003e\n@@ -895,7 +896,7 @@ static int get_user_mapping_size(struct kvm *kvm, u64 addr)\n \t * IPI-ing threads).\n \t */\n \tlocal_irq_save(flags);\n-\tret = kvm_pgtable_get_leaf(\u0026pgt, addr, \u0026pte, \u0026level);\n+\tret = kvm_pgtable_get_leaf(\u0026pgt, addr, \u0026pte, \u0026level, 0);\n \tlocal_irq_restore(flags);\n \n \tif (ret)\n@@ -1606,9 +1607,9 @@ static void *get_mmu_memcache(struct kvm_vcpu *vcpu)\n \t\treturn \u0026vcpu-\u003earch.pkvm_memcache;\n }\n \n-static int topup_mmu_memcache(struct kvm_vcpu *vcpu, void *memcache)\n+static int topup_mmu_memcache(struct kvm_s2_mmu *mmu, void *memcache)\n {\n-\tint min_pages = kvm_mmu_cache_min_pages(vcpu-\u003earch.hw_mmu);\n+\tint min_pages = kvm_mmu_cache_min_pages(mmu);\n \n \tif (!is_protected_kvm_enabled())\n \t\treturn kvm_mmu_topup_memory_cache(memcache, min_pages);\n@@ -1655,15 +1656,47 @@ struct kvm_s2_fault_desc {\n \tstruct kvm_s2_trans\t*nested;\n \tstruct kvm_memory_slot\t*memslot;\n \tunsigned long\t\thva;\n+\tunsigned long\t\tesr;\n+\tstruct kvm_s2_mmu\t*mmu;\n };\n \n-static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)\n+struct kvm_s2_fault_result {\n+\tunsigned long mapping_size;\n+};\n+\n+static bool kvm_s2_fault_is_perm(const struct kvm_s2_fault_desc *s2fd)\n+{\n+\treturn esr_fsc_is_permission_fault(s2fd-\u003eesr);\n+}\n+\n+static bool kvm_s2_fault_is_exec(const struct kvm_s2_fault_desc *s2fd)\n+{\n+\treturn esr_abt_is_exec_fault(s2fd-\u003eesr);\n+}\n+\n+static bool kvm_s2_fault_is_write(const struct kvm_s2_fault_desc *s2fd)\n+{\n+\treturn esr_abt_is_write_fault(s2fd-\u003eesr);\n+}\n+\n+static u64 kvm_s2_perm_fault_granule(const struct kvm_s2_fault_desc *s2fd)\n+{\n+\tu64 level;\n+\n+\tif (!kvm_s2_fault_is_perm(s2fd))\n+\t\treturn 0;\n+\tlevel = s2fd-\u003eesr \u0026 ESR_ELx_FSC_LEVEL;\n+\treturn BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(level));\n+}\n+\n+static int gmem_abort(const struct kvm_s2_fault_desc *s2fd,\n+\t\t struct kvm_s2_fault_result *result)\n {\n \tbool write_fault, exec_fault;\n-\tbool perm_fault = kvm_vcpu_trap_is_permission_fault(s2fd-\u003evcpu);\n+\tbool perm_fault = kvm_s2_fault_is_perm(s2fd);\n \tenum kvm_pgtable_walk_flags flags = KVM_PGTABLE_WALK_SHARED;\n \tenum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_R;\n-\tstruct kvm_pgtable *pgt = s2fd-\u003evcpu-\u003earch.hw_mmu-\u003epgt;\n+\tstruct kvm_pgtable *pgt = s2fd-\u003emmu-\u003epgt;\n \tstruct kvm_guest_s2_mapping *mapping = NULL;\n \tunsigned long mmu_seq;\n \tstruct page *page;\n@@ -1675,7 +1708,7 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)\n \n \tif (!perm_fault) {\n \t\tmemcache = get_mmu_memcache(s2fd-\u003evcpu);\n-\t\tret = topup_mmu_memcache(s2fd-\u003evcpu, memcache);\n+\t\tret = topup_mmu_memcache(s2fd-\u003emmu, memcache);\n \t\tif (ret)\n \t\t\treturn ret;\n \t\tif (kvm_is_nested_s2_mmu(kvm, pgt-\u003emmu)) {\n@@ -1690,8 +1723,8 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)\n \telse\n \t\tgfn = s2fd-\u003efault_ipa \u003e\u003e PAGE_SHIFT;\n \n-\twrite_fault = kvm_is_write_fault(s2fd-\u003evcpu);\n-\texec_fault = kvm_vcpu_trap_is_exec_fault(s2fd-\u003evcpu);\n+\twrite_fault = kvm_s2_fault_is_write(s2fd);\n+\texec_fault = kvm_s2_fault_is_exec(s2fd);\n \n \tVM_WARN_ON_ONCE(write_fault \u0026\u0026 exec_fault);\n \n@@ -1757,7 +1790,13 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)\n \tif ((prot \u0026 KVM_PGTABLE_PROT_W) \u0026\u0026 !ret)\n \t\tmark_page_dirty_in_slot(kvm, s2fd-\u003ememslot, gfn);\n \n-\treturn ret != -EAGAIN ? ret : 0;\n+\tif (ret == -EAGAIN)\n+\t\treturn result ? ret : 0;\n+\n+\tif (result \u0026\u0026 !ret)\n+\t\tresult-\u003emapping_size = PAGE_SIZE;\n+\n+\treturn ret;\n }\n \n struct kvm_s2_fault_vma_info {\n@@ -1779,7 +1818,7 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)\n {\n \tunsigned int flags = FOLL_HWPOISON | FOLL_LONGTERM | FOLL_WRITE;\n \tstruct kvm_vcpu *vcpu = s2fd-\u003evcpu;\n-\tstruct kvm_pgtable *pgt = vcpu-\u003earch.hw_mmu-\u003epgt;\n+\tstruct kvm_pgtable *pgt = s2fd-\u003emmu-\u003epgt;\n \tstruct mm_struct *mm = current-\u003emm;\n \tstruct kvm *kvm = vcpu-\u003ekvm;\n \tvoid *hyp_memcache;\n@@ -1787,7 +1826,7 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)\n \tint ret;\n \n \thyp_memcache = get_mmu_memcache(vcpu);\n-\tret = topup_mmu_memcache(vcpu, hyp_memcache);\n+\tret = topup_mmu_memcache(s2fd-\u003emmu, hyp_memcache);\n \tif (ret)\n \t\treturn -ENOMEM;\n \n@@ -1910,11 +1949,6 @@ static short kvm_s2_resolve_vma_size(const struct kvm_s2_fault_desc *s2fd,\n \treturn vma_shift;\n }\n \n-static bool kvm_s2_fault_is_perm(const struct kvm_s2_fault_desc *s2fd)\n-{\n-\treturn kvm_vcpu_trap_is_permission_fault(s2fd-\u003evcpu);\n-}\n-\n static int kvm_s2_fault_get_vma_info(const struct kvm_s2_fault_desc *s2fd,\n \t\t\t\t struct kvm_s2_fault_vma_info *s2vi)\n {\n@@ -1980,13 +2014,11 @@ static int kvm_s2_fault_pin_pfn(const struct kvm_s2_fault_desc *s2fd,\n \t\treturn ret;\n \n \ts2vi-\u003epfn = __kvm_faultin_pfn(s2fd-\u003ememslot, get_canonical_gfn(s2fd, s2vi),\n-\t\t\t\t kvm_is_write_fault(s2fd-\u003evcpu) ? FOLL_WRITE : 0,\n+\t\t\t\t kvm_s2_fault_is_write(s2fd) ? FOLL_WRITE : 0,\n \t\t\t\t \u0026s2vi-\u003emap_writable, \u0026s2vi-\u003epage);\n \tif (unlikely(is_error_noslot_pfn(s2vi-\u003epfn))) {\n-\t\tif (s2vi-\u003epfn == KVM_PFN_ERR_HWPOISON) {\n-\t\t\tkvm_send_hwpoison_signal(s2fd-\u003ehva, __ffs(s2vi-\u003evma_pagesize));\n-\t\t\treturn 0;\n-\t\t}\n+\t\tif (s2vi-\u003epfn == KVM_PFN_ERR_HWPOISON)\n+\t\t\treturn -EHWPOISON;\n \t\treturn -EFAULT;\n \t}\n \n@@ -2038,7 +2070,7 @@ static int kvm_s2_fault_compute_prot(const struct kvm_s2_fault_desc *s2fd,\n {\n \tstruct kvm *kvm = s2fd-\u003evcpu-\u003ekvm;\n \n-\tif (kvm_vcpu_trap_is_exec_fault(s2fd-\u003evcpu) \u0026\u0026 s2vi-\u003emap_non_cacheable)\n+\tif (kvm_s2_fault_is_exec(s2fd) \u0026\u0026 s2vi-\u003emap_non_cacheable)\n \t\treturn -ENOEXEC;\n \n \t/*\n@@ -2047,7 +2079,7 @@ static int kvm_s2_fault_compute_prot(const struct kvm_s2_fault_desc *s2fd,\n \t * and trigger the exception here. Since the memslot is valid, inject\n \t * the fault back to the guest.\n \t */\n-\tif (esr_fsc_is_excl_atomic_fault(kvm_vcpu_get_esr(s2fd-\u003evcpu))) {\n+\tif (esr_fsc_is_excl_atomic_fault(s2fd-\u003eesr)) {\n \t\tkvm_inject_dabt_excl_atomic(s2fd-\u003evcpu, kvm_vcpu_get_hfar(s2fd-\u003evcpu));\n \t\treturn 1;\n \t}\n@@ -2056,13 +2088,13 @@ static int kvm_s2_fault_compute_prot(const struct kvm_s2_fault_desc *s2fd,\n \n \tif (s2vi-\u003emap_writable \u0026\u0026 (s2vi-\u003edevice ||\n \t\t\t\t !memslot_is_logging(s2fd-\u003ememslot) ||\n-\t\t\t\t kvm_is_write_fault(s2fd-\u003evcpu)))\n+\t\t\t\t kvm_s2_fault_is_write(s2fd)))\n \t\t*prot |= KVM_PGTABLE_PROT_W;\n \n \tif (s2fd-\u003enested)\n \t\t*prot = adjust_nested_fault_perms(s2fd-\u003enested, *prot);\n \n-\tif (kvm_vcpu_trap_is_exec_fault(s2fd-\u003evcpu))\n+\tif (kvm_s2_fault_is_exec(s2fd))\n \t\t*prot |= KVM_PGTABLE_PROT_X;\n \n \tif (s2vi-\u003emap_non_cacheable)\n@@ -2086,7 +2118,8 @@ static int kvm_s2_fault_compute_prot(const struct kvm_s2_fault_desc *s2fd,\n static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,\n \t\t\t const struct kvm_s2_fault_vma_info *s2vi,\n \t\t\t enum kvm_pgtable_prot prot,\n-\t\t\t void *memcache)\n+\t\t\t void *memcache,\n+\t\t\t struct kvm_s2_fault_result *result)\n {\n \tenum kvm_pgtable_walk_flags flags = KVM_PGTABLE_WALK_SHARED;\n \tstruct kvm_guest_s2_mapping *mapping = NULL;\n@@ -2110,13 +2143,12 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,\n \t}\n \n \tkvm_fault_lock(kvm);\n-\tpgt = s2fd-\u003evcpu-\u003earch.hw_mmu-\u003epgt;\n+\tpgt = s2fd-\u003emmu-\u003epgt;\n \tret = -EAGAIN;\n \tif (mmu_invalidate_retry(kvm, s2vi-\u003emmu_seq))\n \t\tgoto out_unlock;\n \n-\tperm_fault_granule = (kvm_s2_fault_is_perm(s2fd) ?\n-\t\t\t kvm_vcpu_trap_get_perm_fault_granule(s2fd-\u003evcpu) : 0);\n+\tperm_fault_granule = kvm_s2_perm_fault_granule(s2fd);\n \tmapping_size = s2vi-\u003evma_pagesize;\n \tpfn = s2vi-\u003epfn;\n \tgfn = s2vi-\u003egfn;\n@@ -2188,14 +2220,19 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,\n \t\tmark_page_dirty_in_slot(kvm, s2fd-\u003ememslot,\n \t\t\t\t\tgpa_to_gfn(canonical_ipa));\n \n-\tif (ret != -EAGAIN)\n-\t\treturn ret;\n-\treturn 0;\n+\tif (ret == -EAGAIN)\n+\t\treturn result ? ret : 0;\n+\n+\tif (result \u0026\u0026 !ret)\n+\t\tresult-\u003emapping_size = mapping_size;\n+\n+\treturn ret;\n }\n \n-static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd)\n+static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd,\n+\t\t\t struct kvm_s2_fault_result *result)\n {\n-\tbool perm_fault = kvm_vcpu_trap_is_permission_fault(s2fd-\u003evcpu);\n+\tbool perm_fault = kvm_s2_fault_is_perm(s2fd);\n \tstruct kvm_s2_fault_vma_info s2vi = {};\n \tenum kvm_pgtable_prot prot;\n \tvoid *memcache;\n@@ -2213,7 +2250,7 @@ static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd)\n \tmemcache = get_mmu_memcache(s2fd-\u003evcpu);\n \tif (!perm_fault || memslot_is_logging(s2fd-\u003ememslot) ||\n \t is_protected_kvm_enabled()) {\n-\t\tret = topup_mmu_memcache(s2fd-\u003evcpu, memcache);\n+\t\tret = topup_mmu_memcache(s2fd-\u003emmu, memcache);\n \t\tif (ret)\n \t\t\treturn ret;\n \t}\n@@ -2223,6 +2260,13 @@ static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd)\n \t * get block mapping for device MMIO region.\n \t */\n \tret = kvm_s2_fault_pin_pfn(s2fd, \u0026s2vi);\n+\tif (ret == -EHWPOISON) {\n+\t\t/* If result is specified, let the caller handle this. */\n+\t\tif (result)\n+\t\t\treturn -EHWPOISON;\n+\t\tkvm_send_hwpoison_signal(s2fd-\u003ehva, __ffs(s2vi.vma_pagesize));\n+\t\treturn 0;\n+\t}\n \tif (ret != 1)\n \t\treturn ret;\n \n@@ -2232,7 +2276,7 @@ static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd)\n \t\treturn ret;\n \t}\n \n-\treturn kvm_s2_fault_map(s2fd, \u0026s2vi, prot, memcache);\n+\treturn kvm_s2_fault_map(s2fd, \u0026s2vi, prot, memcache, result);\n }\n \n /* Resolve the access fault by making the page young again. */\n@@ -2342,7 +2386,8 @@ int kvm_handle_guest_sea(struct kvm_vcpu *vcpu)\n int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)\n {\n \tstruct kvm_s2_trans nested_trans, *nested = NULL;\n-\tunsigned long esr;\n+\tunsigned long esr = kvm_vcpu_get_esr(vcpu);\n+\tstruct kvm_s2_mmu *mmu = vcpu-\u003earch.hw_mmu;\n \tphys_addr_t fault_ipa; /* The address we faulted on */\n \tphys_addr_t ipa; /* Always the IPA in the L1 guest phys space */\n \tstruct kvm_memory_slot *memslot;\n@@ -2351,11 +2396,9 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)\n \tgfn_t gfn;\n \tint ret, idx;\n \n-\tif (kvm_vcpu_abt_issea(vcpu))\n+\tif (esr_abt_is_sea(esr))\n \t\treturn kvm_handle_guest_sea(vcpu);\n \n-\tesr = kvm_vcpu_get_esr(vcpu);\n-\n \t/*\n \t * The fault IPA should be reliable at this point as we're not dealing\n \t * with an SEA.\n@@ -2364,7 +2407,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)\n \tif (KVM_BUG_ON(ipa == INVALID_GPA, vcpu-\u003ekvm))\n \t\treturn -EFAULT;\n \n-\tis_iabt = kvm_vcpu_trap_is_iabt(vcpu);\n+\tis_iabt = esr_trap_is_iabt(esr);\n \n \tif (esr_fsc_is_translation_fault(esr)) {\n \t\t/* Beyond sanitised PARange (which is the IPA limit) */\n@@ -2374,14 +2417,14 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)\n \t\t}\n \n \t\t/* Falls between the IPA range and the PARange? */\n-\t\tif (fault_ipa \u003e= BIT_ULL(VTCR_EL2_IPA(vcpu-\u003earch.hw_mmu-\u003evtcr))) {\n+\t\tif (fault_ipa \u003e= BIT_ULL(VTCR_EL2_IPA(mmu-\u003evtcr))) {\n \t\t\tfault_ipa |= FAR_TO_FIPA_OFFSET(kvm_vcpu_get_hfar(vcpu));\n \n \t\t\treturn kvm_inject_sea(vcpu, is_iabt, fault_ipa);\n \t\t}\n \t}\n \n-\ttrace_kvm_guest_fault(*vcpu_pc(vcpu), kvm_vcpu_get_esr(vcpu),\n+\ttrace_kvm_guest_fault(*vcpu_pc(vcpu), esr,\n \t\t\t kvm_vcpu_get_hfar(vcpu), fault_ipa);\n \n \t/* Check the stage-2 fault is trans. fault or write fault */\n@@ -2390,9 +2433,9 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)\n \t !esr_fsc_is_access_flag_fault(esr) \u0026\u0026\n \t !esr_fsc_is_excl_atomic_fault(esr)) {\n \t\tkvm_err(\"Unsupported FSC: EC=%#x xFSC=%#lx ESR_EL2=%#lx\\n\",\n-\t\t\tkvm_vcpu_trap_get_class(vcpu),\n-\t\t\t(unsigned long)kvm_vcpu_trap_get_fault(vcpu),\n-\t\t\t(unsigned long)kvm_vcpu_get_esr(vcpu));\n+\t\t\tesr_get_ec(esr),\n+\t\t\t(unsigned long)(esr \u0026 ESR_ELx_FSC),\n+\t\t\t(unsigned long)esr);\n \t\treturn -EFAULT;\n \t}\n \n@@ -2411,8 +2454,8 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)\n \t * nothing to walk and we treat it as a 1:1 before going through the\n \t * canonical translation.\n \t */\n-\tif (kvm_is_nested_s2_mmu(vcpu-\u003ekvm,vcpu-\u003earch.hw_mmu) \u0026\u0026\n-\t vcpu-\u003earch.hw_mmu-\u003enested_stage2_enabled) {\n+\tif (kvm_is_nested_s2_mmu(vcpu-\u003ekvm, mmu) \u0026\u0026\n+\t mmu-\u003enested_stage2_enabled) {\n \t\tu32 esr;\n \n \t\tret = kvm_walk_nested_s2(vcpu, fault_ipa, \u0026nested_trans);\n@@ -2441,7 +2484,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)\n \tgfn = ipa \u003e\u003e PAGE_SHIFT;\n \tmemslot = gfn_to_memslot(vcpu-\u003ekvm, gfn);\n \thva = gfn_to_hva_memslot_prot(memslot, gfn, \u0026writable);\n-\twrite_fault = kvm_is_write_fault(vcpu);\n+\twrite_fault = esr_abt_is_write_fault(esr);\n \tif (kvm_is_error_hva(hva) || (write_fault \u0026\u0026 !writable)) {\n \t\t/*\n \t\t * The guest has put either its instructions or its page-tables\n@@ -2454,7 +2497,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (kvm_vcpu_abt_iss1tw(vcpu)) {\n+\t\tif (esr_abt_is_s1ptw(esr)) {\n \t\t\tret = kvm_inject_sea_dabt(vcpu, kvm_vcpu_get_hfar(vcpu));\n \t\t\tgoto out_unlock;\n \t\t}\n@@ -2469,7 +2512,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)\n \t\t * So let's assume that the guest is just being\n \t\t * cautious, and skip the instruction.\n \t\t */\n-\t\tif (kvm_is_error_hva(hva) \u0026\u0026 kvm_vcpu_dabt_is_cm(vcpu)) {\n+\t\tif (kvm_is_error_hva(hva) \u0026\u0026 esr_dabt_is_cm(esr)) {\n \t\t\tkvm_incr_pc(vcpu);\n \t\t\tret = 1;\n \t\t\tgoto out_unlock;\n@@ -2487,7 +2530,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)\n \t}\n \n \t/* Userspace should not be able to register out-of-bounds IPAs */\n-\tVM_BUG_ON(ipa \u003e= kvm_phys_size(vcpu-\u003earch.hw_mmu));\n+\tVM_BUG_ON(ipa \u003e= kvm_phys_size(mmu));\n \n \tif (esr_fsc_is_access_flag_fault(esr)) {\n \t\thandle_access_fault(vcpu, fault_ipa);\n@@ -2501,19 +2544,20 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)\n \t\t.nested\t\t= nested,\n \t\t.memslot\t= memslot,\n \t\t.hva\t\t= hva,\n+\t\t.esr\t\t= esr,\n+\t\t.mmu\t\t= mmu,\n \t};\n \n \tif (kvm_vm_is_protected(vcpu-\u003ekvm)) {\n \t\tret = pkvm_mem_abort(\u0026s2fd);\n \t} else {\n-\t\tVM_WARN_ON_ONCE(kvm_vcpu_trap_is_permission_fault(vcpu) \u0026\u0026\n-\t\t\t\t!write_fault \u0026\u0026\n-\t\t\t\t!kvm_vcpu_trap_is_exec_fault(vcpu));\n+\t\tVM_WARN_ON_ONCE(kvm_s2_fault_is_perm(\u0026s2fd) \u0026\u0026 !write_fault \u0026\u0026\n+\t\t\t\t!kvm_s2_fault_is_exec(\u0026s2fd));\n \n \t\tif (kvm_slot_has_gmem(memslot))\n-\t\t\tret = gmem_abort(\u0026s2fd);\n+\t\t\tret = gmem_abort(\u0026s2fd, NULL);\n \t\telse\n-\t\t\tret = user_mem_abort(\u0026s2fd);\n+\t\t\tret = user_mem_abort(\u0026s2fd, NULL);\n \t}\n \n \tif (ret == 0)\n@@ -2890,3 +2934,149 @@ void kvm_toggle_cache(struct kvm_vcpu *vcpu, bool was_enabled)\n \n \ttrace_kvm_toggle_cache(*vcpu_pc(vcpu), was_enabled, now_enabled);\n }\n+\n+/*\n+ * Try to walk to the specified GPA in canonical mmu - if unmapped returns 0, if\n+ * mapped returns the granule size, otherwise returns an error.\n+ */\n+static long kvm_walk_s2(struct kvm_pgtable *pgt,\n+\t\t\tgpa_t gpa, s8 *level)\n+{\n+\tstruct kvm *kvm = kvm_s2_mmu_to_kvm(pgt-\u003emmu);\n+\tkvm_pte_t pte;\n+\tlong ret;\n+\n+\tguard(read_lock)(\u0026kvm-\u003emmu_lock);\n+\n+\tret = kvm_pgtable_get_leaf(pgt, gpa, \u0026pte, level,\n+\t\t\t\t KVM_PGTABLE_WALK_SHARED);\n+\tif (ret)\n+\t\treturn ret;\n+\t/* Unpopulated, must fault. */\n+\tif (!kvm_pte_valid(pte))\n+\t\treturn 0;\n+\treturn kvm_granule_size(*level);\n+}\n+\n+/* Synthesised data abort at specified page table level. */\n+#define PRE_FAULT_ESR(level)\t\t\t\t\\\n+\t ((ESR_ELx_EC_DABT_LOW \u003c\u003c ESR_ELx_EC_SHIFT) |\t\\\n+\t ESR_ELx_IL | ESR_ELx_FSC_FAULT_L(level))\n+\n+/* Retrieve either a read-only or a read/write hva. */\n+static hva_t gfn_to_hva_memslot_read(struct kvm_memory_slot *slot, gfn_t gfn)\n+{\n+\treturn gfn_to_hva_memslot_prot(slot, gfn, /*writable=*/NULL);\n+}\n+\n+static long __pre_fault_s2(struct kvm_s2_mmu *mmu, struct kvm_vcpu *vcpu,\n+\t\t\t gpa_t gpa, struct kvm_memory_slot *memslot, s8 level)\n+{\n+\tconst bool is_gmem = kvm_slot_has_gmem(memslot);\n+\tconst gfn_t gfn = gpa_to_gfn(gpa);\n+\tconst hva_t hva = is_gmem ? 0 : gfn_to_hva_memslot_read(memslot, gfn);\n+\tconst struct kvm_s2_fault_desc s2fd = {\n+\t\t.vcpu\t\t= vcpu,\n+\t\t.fault_ipa\t= gpa,\n+\t\t.nested\t\t= NULL,\n+\t\t.memslot\t= memslot,\n+\t\t.hva\t\t= hva,\n+\t\t.esr\t\t= PRE_FAULT_ESR(level),\n+\t\t.mmu\t\t= mmu,\n+\t};\n+\tstruct kvm_s2_fault_result result = {};\n+\tlong ret;\n+\n+\tif (kvm_is_error_hva(hva))\n+\t\treturn -EFAULT;\n+\n+\tif (is_gmem)\n+\t\tret = gmem_abort(\u0026s2fd, \u0026result);\n+\telse\n+\t\tret = user_mem_abort(\u0026s2fd, \u0026result);\n+\tif (IS_ERR_VALUE(ret))\n+\t\treturn ret;\n+\treturn result.mapping_size;\n+}\n+\n+static long pre_fault_s2(struct kvm_s2_mmu *mmu, struct kvm_vcpu *vcpu,\n+\t\t\t gpa_t gpa, struct kvm_memory_slot *memslot)\n+{\n+\ts8 level;\n+\tlong ret;\n+\n+\t/* Try a walk first. */\n+\tret = kvm_walk_s2(mmu-\u003epgt, gpa, \u0026level);\n+\tif (ret)\n+\t\treturn ret;\n+\t/* OK, have to fault page in. */\n+\treturn __pre_fault_s2(mmu, vcpu, gpa, memslot, level);\n+}\n+\n+static unsigned long\n+pre_fault_bytes_consumed(gpa_t gpa, unsigned long granule_size,\n+\t\t\t unsigned long bytes_remaining)\n+{\n+\t/* Granules are always a power-of-2. */\n+\tconst unsigned long granule_bytes_remaining =\n+\t\tgranule_size - (gpa % granule_size);\n+\n+\treturn min(granule_bytes_remaining, bytes_remaining);\n+}\n+\n+/* If you lose the race this many times, time to give up. */\n+#define MAX_PRE_FAULT_RETRIES 3\n+\n+bool kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu)\n+{\n+\t/* Only initialised vCPUs can pre-fault. */\n+\treturn kvm_vcpu_initialized(vcpu);\n+}\n+\n+/**\n+ * kvm_arch_vcpu_pre_fault_memory - pre-fault stage-2 page tables for the\n+ * specified GPA.\n+ * @vcpu:\tThe VCPU pointer\n+ * @range:\t{gpa, size, flags} tuple\n+ *\n+ * The mapping performed is always best-effort - faulting in is necessarily\n+ * racey. The ranges faulted in are canonical, nested page tables are ignored.\n+ *\n+ * @range-\u003egpa specifies the GPA to pre-fault, @range-\u003esize specifies how many\n+ * bytes remain to be pre-faulted and @range-\u003eflags is reserved and must be 0.\n+ *\n+ * Returns: the number of bytes the pre-fault consumed, or an error.\n+ */\n+long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,\n+\t\t\t\t struct kvm_pre_fault_memory *range)\n+{\n+\tstruct kvm *kvm = vcpu-\u003ekvm;\n+\tconst u64 bytes_remaining = range-\u003esize;\n+\tstruct kvm_s2_mmu *mmu = \u0026kvm-\u003earch.mmu; /* Canonical. */\n+\tstruct kvm_memory_slot *memslot;\n+\tconst gpa_t gpa = range-\u003egpa;\n+\tint num_retries = 0;\n+\tlong ret;\n+\n+\t/*\n+\t * pKVM is unsupported as their vCPUs are instantiated on first run and\n+\t * pre-faulting only running vCPUs would be inconsistent and confusing.\n+\t */\n+\tif (is_protected_kvm_enabled())\n+\t\treturn -EOPNOTSUPP;\n+\n+\tmemslot = gfn_to_memslot(kvm, gpa_to_gfn(gpa));\n+\tif (!memslot)\n+\t\treturn -ENOENT;\n+\t/* SRCU must be released for progress and only userland can do that. */\n+\tif (memslot-\u003eflags \u0026 KVM_MEMSLOT_INVALID)\n+\t\treturn -EAGAIN;\n+\n+\tdo {\n+\t\tret = pre_fault_s2(mmu, vcpu, gpa, memslot);\n+\t} while (ret == -EAGAIN \u0026\u0026 num_retries++ \u003c MAX_PRE_FAULT_RETRIES);\n+\n+\tif (IS_ERR_VALUE(ret))\n+\t\treturn ret;\n+\treturn pre_fault_bytes_consumed(gpa, ret, bytes_remaining);\n+}\ndiff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c\nindex ec754a865a006..cd868af63b1b8 100644\n--- a/arch/arm64/kvm/nested.c\n+++ b/arch/arm64/kvm/nested.c\n@@ -662,7 +662,7 @@ static u8 get_guest_mapping_ttl(struct kvm_s2_mmu *mmu, u64 addr)\n \t\treturn 0;\n \n \ttmp \u0026= ~(sz - 1);\n-\tif (kvm_pgtable_get_leaf(mmu-\u003epgt, tmp, \u0026pte, NULL))\n+\tif (kvm_pgtable_get_leaf(mmu-\u003epgt, tmp, \u0026pte, NULL, 0))\n \t\tgoto again;\n \tif (!(pte \u0026 PTE_VALID))\n \t\tgoto again;\ndiff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h\nindex 03bfc92864b6e..c11fda8704a3e 100644\n--- a/include/linux/kvm_host.h\n+++ b/include/linux/kvm_host.h\n@@ -1693,6 +1693,7 @@ int kvm_arch_vcpu_should_kick(struct kvm_vcpu *vcpu);\n bool kvm_arch_dy_runnable(struct kvm_vcpu *vcpu);\n bool kvm_arch_dy_has_pending_interrupt(struct kvm_vcpu *vcpu);\n bool kvm_arch_vcpu_preempted_in_kernel(struct kvm_vcpu *vcpu);\n+bool kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu);\n void kvm_arch_pre_destroy_vm(struct kvm *kvm);\n void kvm_arch_create_vm_debugfs(struct kvm *kvm);\n \ndiff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm\nindex 6a1482e3a286b..908bdc7cf4f58 100644\n--- a/tools/testing/selftests/kvm/Makefile.kvm\n+++ b/tools/testing/selftests/kvm/Makefile.kvm\n@@ -178,6 +178,7 @@ TEST_GEN_PROGS_arm64 += arm64/debug-exceptions\n TEST_GEN_PROGS_arm64 += arm64/hello_el2\n TEST_GEN_PROGS_arm64 += arm64/host_sve\n TEST_GEN_PROGS_arm64 += arm64/hypercalls\n+TEST_GEN_PROGS_arm64 += arm64/nv_pre_fault_memory_test\n TEST_GEN_PROGS_arm64 += arm64/external_aborts\n TEST_GEN_PROGS_arm64 += arm64/mmio_sign_ext\n TEST_GEN_PROGS_arm64 += arm64/page_fault_test\n@@ -205,6 +206,7 @@ TEST_GEN_PROGS_arm64 += guest_memfd_test\n TEST_GEN_PROGS_arm64 += mmu_stress_test\n TEST_GEN_PROGS_arm64 += rseq_test\n TEST_GEN_PROGS_arm64 += steal_time\n+TEST_GEN_PROGS_arm64 += pre_fault_memory_test\n \n TEST_GEN_PROGS_s390 = $(TEST_GEN_PROGS_COMMON)\n TEST_GEN_PROGS_s390 += s390/memop\ndiff --git a/tools/testing/selftests/kvm/arm64/nv_pre_fault_memory_test.c b/tools/testing/selftests/kvm/arm64/nv_pre_fault_memory_test.c\nnew file mode 100644\nindex 0000000000000..09c1db3038566\n--- /dev/null\n+++ b/tools/testing/selftests/kvm/arm64/nv_pre_fault_memory_test.c\n@@ -0,0 +1,158 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * nv_pre_fault_memory_test - Test KVM_PRE_FAULT_MEMORY on a vCPU whose\n+ * last-run context is nested.\n+ *\n+ * The guest enters vEL2, sets up its EL2 translation configuration into the\n+ * real EL1 registers then ERETs to vEL1 and exits to userspace so its vCPU\n+ * last-run context is nested backed by a shadow stage 2 MMU.\n+ *\n+ * Assert that pre-faulting ignores that and targets the canonical stage-2\n+ * page tables only.\n+ */\n+#include \"kvm_util.h\"\n+#include \"processor.h\"\n+#include \"test_util.h\"\n+#include \"ucall.h\"\n+\n+#include \u003casm/sysreg.h\u003e\n+#include \u003clinux/sizes.h\u003e\n+\n+#define TEST_MEM_SLOT\t\t10\n+#define TEST_MEM_SIZE\t\tSZ_2M\n+#define TEST_MEM_GPA\t\tSZ_1G\n+\n+static void guest_el1_code(void)\n+{\n+\tu64 offset;\n+\n+\tGUEST_ASSERT_EQ(get_current_el(), 1);\n+\n+\t/* Exit to userspace with the vEL1 (nested) context live. */\n+\tGUEST_SYNC(1);\n+\n+\t/*\n+\t * Touch the prefaulted range. vstage-2 is disabled, so the shadow\n+\t * stage-2 is a 1:1 view of the canonical IPA space.\n+\t */\n+\tfor (offset = 0; offset \u003c TEST_MEM_SIZE; offset += SZ_4K)\n+\t\tREAD_ONCE(*(u64 *)(TEST_MEM_GPA + offset));\n+\n+\tGUEST_DONE();\n+}\n+\n+static void guest_code(void)\n+{\n+\tu64 sp;\n+\n+\tGUEST_ASSERT_EQ(get_current_el(), 2);\n+\n+\t/*\n+\t * Mirror the EL2 translation regime into the real EL1 registers so\n+\t * that vEL1 runs on the test's stage-1 page tables. With E2H=1, the\n+\t * _EL1 accessors read the EL2 registers, and the _EL12 accessors\n+\t * write the real EL1 registers.\n+\t */\n+\twrite_sysreg_s(read_sysreg(sctlr_el1), SYS_SCTLR_EL12);\n+\twrite_sysreg_s(read_sysreg(tcr_el1), SYS_TCR_EL12);\n+\twrite_sysreg_s(read_sysreg(ttbr0_el1), SYS_TTBR0_EL12);\n+\twrite_sysreg_s(read_sysreg(mair_el1), SYS_MAIR_EL12);\n+\twrite_sysreg_s(read_sysreg(cpacr_el1), SYS_CPACR_EL12);\n+\n+\t/* Run vEL1 on the same stack. */\n+\tasm volatile(\"mov %0, sp\" : \"=r\"(sp));\n+\twrite_sysreg(sp, sp_el1);\n+\n+\t/*\n+\t * Drop TGE so that vEL1 is a nested context rather than host EL0.\n+\t * KVM backs it with a shadow stage-2 MMU even though vstage-2 is\n+\t * disabled (HCR_EL2.VM=0).\n+\t */\n+\twrite_sysreg(read_sysreg(hcr_el2) \u0026 ~HCR_EL2_TGE, hcr_el2);\n+\tisb();\n+\n+\twrite_sysreg(PSR_MODE_EL1h | PSR_F_BIT | PSR_I_BIT | PSR_A_BIT |\n+\t\t PSR_D_BIT, spsr_el2);\n+\twrite_sysreg((u64)guest_el1_code, elr_el2);\n+\tasm volatile(\"eret\");\n+\n+\tGUEST_ASSERT(false);\n+}\n+\n+static void pre_fault(struct kvm_vcpu *vcpu, u64 gpa, u64 size)\n+{\n+\tstruct kvm_pre_fault_memory range = {\n+\t\t.gpa = gpa,\n+\t\t.size = size,\n+\t};\n+\tint ret;\n+\n+\tdo {\n+\t\tret = __vcpu_ioctl(vcpu, KVM_PRE_FAULT_MEMORY, \u0026range);\n+\t} while ((!ret \u0026\u0026 range.size) ||\n+\t\t (ret \u003c 0 \u0026\u0026 (errno == EINTR || errno == EAGAIN)));\n+\n+\tTEST_ASSERT(!ret, \"KVM_PRE_FAULT_MEMORY failed, ret: %d errno: %d\",\n+\t\t ret, errno);\n+\tTEST_ASSERT_EQ(range.size, 0);\n+}\n+\n+int main(void)\n+{\n+\tstruct kvm_vcpu_init init;\n+\tstruct kvm_vcpu *vcpu;\n+\tstruct kvm_vm *vm;\n+\tstruct ucall uc;\n+\tu64 npages;\n+\n+\tTEST_REQUIRE(test_supports_el2());\n+\tTEST_REQUIRE(kvm_check_cap(KVM_CAP_PRE_FAULT_MEMORY));\n+\n+\tvm = vm_create(1);\n+\n+\tkvm_get_default_vcpu_target(vm, \u0026init);\n+\tinit.features[0] |= BIT(KVM_ARM_VCPU_HAS_EL2);\n+\tvcpu = aarch64_vcpu_add(vm, 0, \u0026init, guest_code);\n+\tkvm_arch_vm_finalize_vcpus(vm);\n+\n+\tnpages = TEST_MEM_SIZE / vm-\u003epage_size;\n+\tvm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, TEST_MEM_GPA,\n+\t\t\t\t TEST_MEM_SLOT, npages, 0);\n+\tvirt_map(vm, TEST_MEM_GPA, TEST_MEM_GPA, npages);\n+\n+\t/* Run the guest until it has ERET'd from vEL2 to vEL1. */\n+\tvcpu_run(vcpu);\n+\tswitch (get_ucall(vcpu, \u0026uc)) {\n+\tcase UCALL_SYNC:\n+\t\tTEST_ASSERT_EQ(uc.args[1], 1);\n+\t\tbreak;\n+\tcase UCALL_ABORT:\n+\t\tREPORT_GUEST_ASSERT(uc);\n+\t\tbreak;\n+\tdefault:\n+\t\tTEST_FAIL(\"Unhandled ucall: %ld\", uc.cmd);\n+\t}\n+\n+\t/*\n+\t * The vCPU's last-run context is vEL1, so its hw_mmu is a shadow\n+\t * stage-2 MMU.\n+\t *\n+\t * Pre-faulting must ignore that and populate the canonical stage-2.\n+\t */\n+\tpre_fault(vcpu, TEST_MEM_GPA, TEST_MEM_SIZE);\n+\n+\t/* Resume at vEL1 and touch the prefaulted range. */\n+\tvcpu_run(vcpu);\n+\tswitch (get_ucall(vcpu, \u0026uc)) {\n+\tcase UCALL_DONE:\n+\t\tbreak;\n+\tcase UCALL_ABORT:\n+\t\tREPORT_GUEST_ASSERT(uc);\n+\t\tbreak;\n+\tdefault:\n+\t\tTEST_FAIL(\"Unhandled ucall: %ld\", uc.cmd);\n+\t}\n+\n+\tkvm_vm_free(vm);\n+\treturn 0;\n+}\ndiff --git a/tools/testing/selftests/kvm/pre_fault_memory_test.c b/tools/testing/selftests/kvm/pre_fault_memory_test.c\nindex c57631aab3d38..3082fe09b95fa 100644\n--- a/tools/testing/selftests/kvm/pre_fault_memory_test.c\n+++ b/tools/testing/selftests/kvm/pre_fault_memory_test.c\n@@ -12,19 +12,29 @@\n #include \u003cprocessor.h\u003e\n #include \u003cpthread.h\u003e\n #include \u003cucall_common.h\u003e\n+#include \u003cguest_modes.h\u003e\n \n /* Arbitrarily chosen values */\n-#define TEST_SIZE\t\t(SZ_2M + PAGE_SIZE)\n-#define TEST_NPAGES\t\t(TEST_SIZE / PAGE_SIZE)\n+#define TEST_BASE_SIZE\t\tSZ_2M\n #define TEST_SLOT\t\t10\n \n+/* Storage of test info to share with guest code */\n+struct test_config {\n+\tu64 page_size;\n+\tu64 test_size;\n+\tu64 test_num_pages;\n+};\n+\n+static struct test_config test_config;\n+\n static void guest_code(u64 base_gva)\n {\n \tvolatile u64 val __used;\n+\tstruct test_config *config = \u0026test_config;\n \tint i;\n \n-\tfor (i = 0; i \u003c TEST_NPAGES; i++) {\n-\t\tu64 *src = (u64 *)(base_gva + i * PAGE_SIZE);\n+\tfor (i = 0; i \u003c config-\u003etest_num_pages; i++) {\n+\t\tu64 *src = (u64 *)(base_gva + i * config-\u003epage_size);\n \n \t\tval = *src;\n \t}\n@@ -36,6 +46,7 @@ struct slot_worker_data {\n \tstruct kvm_vm *vm;\n \tgpa_t gpa;\n \tu32 flags;\n+\tenum vm_mem_backing_src_type mem_backing_src;\n \tbool worker_ready;\n \tbool prefault_ready;\n \tbool recreate_slot;\n@@ -56,14 +67,16 @@ static void *delete_slot_worker(void *__data)\n \twhile (!READ_ONCE(data-\u003erecreate_slot))\n \t\tcpu_relax();\n \n-\tvm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, data-\u003egpa,\n-\t\t\t\t TEST_SLOT, TEST_NPAGES, data-\u003eflags);\n+\tvm_userspace_mem_region_add(vm, data-\u003emem_backing_src, data-\u003egpa,\n+\t\t\t\t TEST_SLOT, test_config.test_num_pages, data-\u003eflags);\n \n \treturn NULL;\n }\n \n static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,\n-\t\t\t u64 size, u64 expected_left, bool private)\n+\t\t\t u64 size, u64 expected_left,\n+\t\t\t enum vm_mem_backing_src_type mem_backing_src,\n+\t\t\t bool private)\n {\n \tstruct kvm_pre_fault_memory range = {\n \t\t.gpa = base_gpa + offset,\n@@ -74,6 +87,7 @@ static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,\n \t\t.vm = vcpu-\u003evm,\n \t\t.gpa = base_gpa,\n \t\t.flags = private ? KVM_MEM_GUEST_MEMFD : 0,\n+\t\t.mem_backing_src = mem_backing_src,\n \t};\n \tbool slot_recreated = false;\n \tpthread_t slot_worker;\n@@ -150,8 +164,8 @@ static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,\n \t/*\n \t * Assert success if prefaulting the entire range should succeed, i.e.\n \t * complete with no bytes remaining. Otherwise prefaulting should have\n-\t * failed due to ENOENT (due to RET_PF_EMULATE for emulated MMIO when\n-\t * no memslot exists).\n+\t * failed due to ENOENT (no memslot exists for the GPA; on x86 this\n+\t * surfaces via RET_PF_EMULATE).\n \t */\n \tif (!expected_left)\n \t\tTEST_ASSERT_VM_VCPU_IOCTL(!ret, KVM_PRE_FAULT_MEMORY, ret, vcpu-\u003evm);\n@@ -160,39 +174,82 @@ static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,\n \t\t\t\t\t KVM_PRE_FAULT_MEMORY, ret, vcpu-\u003evm);\n }\n \n-static void __test_pre_fault_memory(unsigned long vm_type, bool private)\n+struct test_params {\n+\tunsigned long vm_type;\n+\tbool private;\n+\tenum vm_mem_backing_src_type mem_backing_src;\n+};\n+\n+static void __test_pre_fault_memory(enum vm_guest_mode guest_mode, void *arg)\n {\n-\tgpa_t gpa, gva, alignment, guest_page_size;\n+\tgpa_t gpa, gva, alignment, guest_page_size, host_page_size;\n+\tgpa_t backing_src_pagesz, mem_page_size;\n+\tstruct test_params *p = arg;\n \tconst struct vm_shape shape = {\n-\t\t.mode = VM_MODE_DEFAULT,\n-\t\t.type = vm_type,\n+\t\t.mode = guest_mode,\n+\t\t.type = p-\u003evm_type,\n \t};\n \tstruct kvm_vcpu *vcpu;\n+\tstruct kvm_run *run;\n \tstruct kvm_vm *vm;\n \tstruct ucall uc;\n \n+\tpr_info(\"Testing guest mode: %s\\n\", vm_guest_mode_string(guest_mode));\n+\tpr_info(\"Testing memory backing src type: %s\\n\",\n+\t\tvm_mem_backing_src_alias(p-\u003emem_backing_src)-\u003ename);\n+\n \tvm = vm_create_shape_with_one_vcpu(shape, \u0026vcpu, guest_code);\n \n-\talignment = guest_page_size = vm_guest_mode_params[VM_MODE_DEFAULT].page_size;\n-\tgpa = (vm-\u003emax_gfn - TEST_NPAGES) * guest_page_size;\n+\tguest_page_size = vm_guest_mode_params[guest_mode].page_size;\n+\thost_page_size = getpagesize();\n+\tbacking_src_pagesz = get_backing_src_pagesz(p-\u003emem_backing_src);\n+\tmem_page_size = max(host_page_size, backing_src_pagesz);\n+\n+\ttest_config.page_size = guest_page_size;\n+\ttest_config.test_size = align_up(TEST_BASE_SIZE + test_config.page_size,\n+\t\t\t\t\t mem_page_size);\n+\ttest_config.test_num_pages = vm_calc_num_guest_pages(vm-\u003emode, test_config.test_size);\n+\n+\tgpa = (vm-\u003emax_gfn - test_config.test_num_pages) * test_config.page_size;\n \talignment = SZ_2M;\n+\talignment = max(alignment, mem_page_size);\n \tgpa = align_down(gpa, alignment);\n \tgva = gpa \u0026 ((1ULL \u003c\u003c (vm-\u003eva_bits - 1)) - 1);\n \n-\tvm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, gpa, TEST_SLOT,\n-\t\t\t\t TEST_NPAGES, private ? KVM_MEM_GUEST_MEMFD : 0);\n-\tvirt_map(vm, gva, gpa, TEST_NPAGES);\n+\tvm_userspace_mem_region_add(vm, p-\u003emem_backing_src,\n+\t\t\t\t gpa, TEST_SLOT, test_config.test_num_pages,\n+\t\t\t\t p-\u003eprivate ? KVM_MEM_GUEST_MEMFD : 0);\n+\tvirt_map(vm, gva, gpa, test_config.test_num_pages);\n+\n+\tif (p-\u003eprivate)\n+\t\tvm_mem_set_private(vm, gpa, test_config.test_size);\n+\n+\tpre_fault_memory(vcpu, gpa, 0, test_config.test_size, 0,\n+\t\t\t p-\u003emem_backing_src, p-\u003eprivate);\n+\t/* Retry the same range after the first prefault attempt. */\n+\tpre_fault_memory(vcpu, gpa, 0, test_config.test_size, 0,\n+\t\t\t p-\u003emem_backing_src, p-\u003eprivate);\n+\tpre_fault_memory(vcpu, gpa,\n+\t\t\t test_config.test_size - host_page_size,\n+\t\t\t host_page_size * 2, host_page_size,\n+\t\t\t p-\u003emem_backing_src, p-\u003eprivate);\n+\tpre_fault_memory(vcpu, gpa, test_config.test_size,\n+\t\t\t host_page_size, host_page_size,\n+\t\t\t p-\u003emem_backing_src, p-\u003eprivate);\n \n-\tif (private)\n-\t\tvm_mem_set_private(vm, gpa, TEST_SIZE);\n+\tvcpu_args_set(vcpu, 1, gva);\n \n-\tpre_fault_memory(vcpu, gpa, 0, SZ_2M, 0, private);\n-\tpre_fault_memory(vcpu, gpa, SZ_2M, PAGE_SIZE * 2, PAGE_SIZE, private);\n-\tpre_fault_memory(vcpu, gpa, TEST_SIZE, PAGE_SIZE, PAGE_SIZE, private);\n+\t/* Export the shared variables to the guest. */\n+\tsync_global_to_guest(vm, test_config);\n \n-\tvcpu_args_set(vcpu, 1, gva);\n \tvcpu_run(vcpu);\n \n+\trun = vcpu-\u003erun;\n+\tTEST_ASSERT(run-\u003eexit_reason == UCALL_EXIT_REASON,\n+\t\t \"Wanted %s, got exit reason: %u (%s)\",\n+\t\t exit_reason_str(UCALL_EXIT_REASON),\n+\t\t run-\u003eexit_reason, exit_reason_str(run-\u003eexit_reason));\n+\n \tswitch (get_ucall(vcpu, \u0026uc)) {\n \tcase UCALL_ABORT:\n \t\tREPORT_GUEST_ASSERT(uc);\n@@ -207,24 +264,61 @@ static void __test_pre_fault_memory(unsigned long vm_type, bool private)\n \tkvm_vm_free(vm);\n }\n \n-static void test_pre_fault_memory(unsigned long vm_type, bool private)\n+static void test_pre_fault_memory(unsigned long vm_type, enum vm_mem_backing_src_type backing_src,\n+\t\t\t\t bool private)\n {\n+\tstruct test_params p = {\n+\t\t.vm_type = vm_type,\n+\t\t.private = private,\n+\t\t.mem_backing_src = backing_src,\n+\t};\n+\n \tif (vm_type \u0026\u0026 !(kvm_check_cap(KVM_CAP_VM_TYPES) \u0026 BIT(vm_type))) {\n \t\tpr_info(\"Skipping tests for vm_type 0x%lx\\n\", vm_type);\n \t\treturn;\n \t}\n \n-\t__test_pre_fault_memory(vm_type, private);\n+\tfor_each_guest_mode(__test_pre_fault_memory, \u0026p);\n+}\n+\n+static void help(char *name)\n+{\n+\tputs(\"\");\n+\tprintf(\"usage: %s [-h] [-m mode] [-s mem-type]\\n\", name);\n+\tputs(\"\");\n+\tguest_modes_help();\n+\tbacking_src_help(\"-s\");\n+\tputs(\"\");\n }\n \n int main(int argc, char *argv[])\n {\n+\tenum vm_mem_backing_src_type backing = DEFAULT_VM_MEM_SRC;\n+\tint opt;\n+\n+\tguest_modes_append_default();\n+\n+\twhile ((opt = getopt(argc, argv, \"hm:s:\")) != -1) {\n+\t\tswitch (opt) {\n+\t\tcase 'm':\n+\t\t\tguest_modes_cmdline(optarg);\n+\t\t\tbreak;\n+\t\tcase 's':\n+\t\t\tbacking = parse_backing_src_type(optarg);\n+\t\t\tbreak;\n+\t\tcase 'h':\n+\t\tdefault:\n+\t\t\thelp(argv[0]);\n+\t\t\texit(0);\n+\t\t}\n+\t}\n+\n \tTEST_REQUIRE(kvm_check_cap(KVM_CAP_PRE_FAULT_MEMORY));\n \n-\ttest_pre_fault_memory(0, false);\n+\ttest_pre_fault_memory(0, backing, false);\n #ifdef __x86_64__\n-\ttest_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, false);\n-\ttest_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, true);\n+\ttest_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, backing, false);\n+\ttest_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, backing, true);\n #endif\n \treturn 0;\n }\ndiff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c\nindex 65eb26a0520d8..9662baf2bfcab 100644\n--- a/virt/kvm/kvm_main.c\n+++ b/virt/kvm/kvm_main.c\n@@ -3961,6 +3961,11 @@ bool __weak kvm_arch_dy_has_pending_interrupt(struct kvm_vcpu *vcpu)\n \treturn false;\n }\n \n+bool __weak kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu)\n+{\n+\treturn true;\n+}\n+\n void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode)\n {\n \tint nr_vcpus, start, i, idx, yielded;\n@@ -4365,6 +4370,9 @@ static int kvm_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,\n \t range-\u003egpa + range-\u003esize \u003c= range-\u003egpa)\n \t\treturn -EINVAL;\n \n+\tif (!kvm_arch_vcpu_allow_pre_fault_memory(vcpu))\n+\t\treturn -ENOEXEC;\n+\n \tvcpu_load(vcpu);\n \tidx = srcu_read_lock(\u0026vcpu-\u003ekvm-\u003esrcu);\n \n"
} |
| 2/1 | 2026/09/24 22:42 | agent | patch-evaluator |
1mModel:core Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch series adds support for KVM_PRE_FAULT_MEMORY on arm64. All substantive implementation changes (stage-2 page table walking, prefault abort handling, ESR parsing) are located in arch/arm64/ and are exclusive to arm64, making them structurally unreachable on the target architecture (amd64). The changes to Documentation/ and tools/testing/selftests/ are non-kernel or user-space changes. The only change to common code in virt/kvm/kvm_main.c introduces a weak fallback hook kvm_arch_vcpu_allow_pre_fault_memory() that unconditionally returns true on amd64, leaving control flow and state management on x86 completely unaltered. Therefore, there are no reachable modified code paths worth fuzzing on amd64.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 60fb16a4fe6b0c035e7b16d9586a6fca6865d428
Author: syz-cluster <triage@syzkaller.com>
Date: Thu Sep 24 22:42:24 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 1a90598901c57..71b642148b7f6 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -6489,7 +6489,7 @@ See KVM_SET_USER_MEMORY_REGION2 for additional details.
---------------------------
:Capability: KVM_CAP_PRE_FAULT_MEMORY
-:Architectures: none
+:Architectures: x86, s390, arm64
:Type: vcpu ioctl
:Parameters: struct kvm_pre_fault_memory (in/out)
:Returns: 0 if at least one page is processed, < 0 on error
@@ -6497,12 +6497,15 @@ See KVM_SET_USER_MEMORY_REGION2 for additional details.
Errors:
========== ===============================================================
+ EAGAIN A race occurred before progress was made, but a retry may succeed.
EINVAL The specified `gpa` and `size` were invalid (e.g. not
page aligned, causes an overflow, or size is zero), or the VM
is UCONTROL (s390).
ENOENT The specified `gpa` is outside defined memslots.
+ ENOEXEC The vCPU has not been initialised (arm64).
EINTR An unmasked signal is pending and no page was processed.
EFAULT The parameter address was invalid.
+ EHWPOISON A poisoned host page was encountered.
EOPNOTSUPP Mapping memory for a GPA is unsupported by the
hypervisor, and/or for the current vCPU state/mode.
EIO unexpected error conditions (also causes a WARN)
@@ -6522,7 +6525,17 @@ Errors:
KVM_PRE_FAULT_MEMORY populates KVM's stage-2 page tables used to map memory
for the current vCPU state. KVM maps memory as if the vCPU generated a
stage-2 read page fault, e.g. faults in memory as needed, but doesn't break
-CoW. On x86, KVM does not mark any newly created stage-2 PTE as Accessed.
+CoW. On arm64, KVM marks newly created stage-2 PTEs as Accessed, as it
+does for any stage-2 fault, but leaves the Accessed state of existing PTEs
+unchanged. On x86, KVM does not mark any newly created stage-2 PTE as
+Accessed, and for s390 it is not applicable.
+
+On arm64, a GPA is interpreted as an IPA, and never interpreted as the IPA
+of a nested guest. Pre-faulting only populates canonical stage-2 page
+tables.
+
+The feature is not supported on arm64 if the protected KVM (pKVM) feature
+is enabled.
In the case of confidential VM types where there is an initial set up of
private guest memory before the guest is 'finalized'/measured, this ioctl
@@ -6537,7 +6550,7 @@ When the ioctl returns, the input values are updated to point to the
remaining range. If `size` > 0 on return, the caller can just issue
the ioctl again with the same `struct kvm_map_memory` argument.
-Shadow page tables cannot support this ioctl because they
+On x86, shadow page tables cannot support this ioctl because they
are indexed by virtual address or nested guest physical address.
Calling this ioctl when the guest is using shadow page tables (for
example because it is running a nested guest with nested page tables)
diff --git a/arch/arm64/include/asm/esr.h b/arch/arm64/include/asm/esr.h
index f816f5d77f1a5..9c0205983d9a8 100644
--- a/arch/arm64/include/asm/esr.h
+++ b/arch/arm64/include/asm/esr.h
@@ -437,6 +437,50 @@
#ifndef __ASSEMBLER__
#include <asm/types.h>
+static __always_inline u8 esr_get_ec(unsigned long esr)
+{
+ return ESR_ELx_EC(esr);
+}
+
+static __always_inline bool esr_trap_is_iabt(unsigned long esr)
+{
+ return esr_get_ec(esr) == ESR_ELx_EC_IABT_LOW;
+}
+
+static __always_inline bool esr_abt_is_s1ptw(unsigned long esr)
+{
+ return esr & ESR_ELx_S1PTW;
+}
+
+/* Always check for S1PTW *before* using this. */
+static __always_inline bool esr_dabt_is_write(unsigned long esr)
+{
+ return esr & ESR_ELx_WNR;
+}
+
+static __always_inline bool esr_dabt_is_cm(unsigned long esr)
+{
+ return esr & ESR_ELx_CM;
+}
+
+static __always_inline bool esr_abt_is_exec_fault(unsigned long esr)
+{
+ return esr_trap_is_iabt(esr) && !esr_abt_is_s1ptw(esr);
+}
+
+static __always_inline bool esr_abt_is_sea(unsigned long esr)
+{
+ switch (esr & ESR_ELx_FSC) {
+ case ESR_ELx_FSC_EXTABT:
+ case ESR_ELx_FSC_SEA_TTW(-1) ... ESR_ELx_FSC_SEA_TTW(3):
+ case ESR_ELx_FSC_SECC:
+ case ESR_ELx_FSC_SECC_TTW(-1) ... ESR_ELx_FSC_SECC_TTW(3):
+ return true;
+ default:
+ return false;
+ }
+}
+
static inline unsigned long esr_brk_comment(unsigned long esr)
{
return esr & ESR_ELx_BRK64_ISS_COMMENT_MASK;
diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
index a3c1928bdf743..a3b2722655543 100644
--- a/arch/arm64/include/asm/kvm_emulate.h
+++ b/arch/arm64/include/asm/kvm_emulate.h
@@ -411,18 +411,13 @@ static __always_inline int kvm_vcpu_dabt_get_rd(const struct kvm_vcpu *vcpu)
static __always_inline bool kvm_vcpu_abt_iss1tw(const struct kvm_vcpu *vcpu)
{
- return !!(kvm_vcpu_get_esr(vcpu) & ESR_ELx_S1PTW);
+ return esr_abt_is_s1ptw(kvm_vcpu_get_esr(vcpu));
}
/* Always check for S1PTW *before* using this. */
static __always_inline bool kvm_vcpu_dabt_iswrite(const struct kvm_vcpu *vcpu)
{
- return kvm_vcpu_get_esr(vcpu) & ESR_ELx_WNR;
-}
-
-static inline bool kvm_vcpu_dabt_is_cm(const struct kvm_vcpu *vcpu)
-{
- return !!(kvm_vcpu_get_esr(vcpu) & ESR_ELx_CM);
+ return esr_dabt_is_write(kvm_vcpu_get_esr(vcpu));
}
static __always_inline unsigned int kvm_vcpu_dabt_get_as(const struct kvm_vcpu *vcpu)
@@ -438,17 +433,12 @@ static __always_inline bool kvm_vcpu_trap_il_is32bit(const struct kvm_vcpu *vcpu
static __always_inline u8 kvm_vcpu_trap_get_class(const struct kvm_vcpu *vcpu)
{
- return ESR_ELx_EC(kvm_vcpu_get_esr(vcpu));
+ return esr_get_ec(kvm_vcpu_get_esr(vcpu));
}
static inline bool kvm_vcpu_trap_is_iabt(const struct kvm_vcpu *vcpu)
{
- return kvm_vcpu_trap_get_class(vcpu) == ESR_ELx_EC_IABT_LOW;
-}
-
-static inline bool kvm_vcpu_trap_is_exec_fault(const struct kvm_vcpu *vcpu)
-{
- return kvm_vcpu_trap_is_iabt(vcpu) && !kvm_vcpu_abt_iss1tw(vcpu);
+ return esr_trap_is_iabt(kvm_vcpu_get_esr(vcpu));
}
static __always_inline u8 kvm_vcpu_trap_get_fault(const struct kvm_vcpu *vcpu)
@@ -468,26 +458,9 @@ bool kvm_vcpu_trap_is_translation_fault(const struct kvm_vcpu *vcpu)
return esr_fsc_is_translation_fault(kvm_vcpu_get_esr(vcpu));
}
-static inline
-u64 kvm_vcpu_trap_get_perm_fault_granule(const struct kvm_vcpu *vcpu)
-{
- unsigned long esr = kvm_vcpu_get_esr(vcpu);
-
- BUG_ON(!esr_fsc_is_permission_fault(esr));
- return BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(esr & ESR_ELx_FSC_LEVEL));
-}
-
static __always_inline bool kvm_vcpu_abt_issea(const struct kvm_vcpu *vcpu)
{
- switch (kvm_vcpu_trap_get_fault(vcpu)) {
- case ESR_ELx_FSC_EXTABT:
- case ESR_ELx_FSC_SEA_TTW(-1) ... ESR_ELx_FSC_SEA_TTW(3):
- case ESR_ELx_FSC_SECC:
- case ESR_ELx_FSC_SECC_TTW(-1) ... ESR_ELx_FSC_SECC_TTW(3):
- return true;
- default:
- return false;
- }
+ return esr_abt_is_sea(kvm_vcpu_get_esr(vcpu));
}
static __always_inline int kvm_vcpu_sys_get_rt(struct kvm_vcpu *vcpu)
@@ -496,9 +469,9 @@ static __always_inline int kvm_vcpu_sys_get_rt(struct kvm_vcpu *vcpu)
return ESR_ELx_SYS64_ISS_RT(esr);
}
-static inline bool kvm_is_write_fault(struct kvm_vcpu *vcpu)
+static inline bool esr_abt_is_write_fault(unsigned long esr)
{
- if (kvm_vcpu_abt_iss1tw(vcpu)) {
+ if (esr_abt_is_s1ptw(esr)) {
/*
* Only a permission fault on a S1PTW should be
* considered as a write. Otherwise, page tables baked
@@ -511,13 +484,18 @@ static inline bool kvm_is_write_fault(struct kvm_vcpu *vcpu)
* first), then a permission fault to allow the flags
* to be set.
*/
- return kvm_vcpu_trap_is_permission_fault(vcpu);
+ return esr_fsc_is_permission_fault(esr);
}
- if (kvm_vcpu_trap_is_iabt(vcpu))
+ if (esr_trap_is_iabt(esr))
return false;
- return kvm_vcpu_dabt_iswrite(vcpu);
+ return esr_dabt_is_write(esr);
+}
+
+static inline bool kvm_is_write_fault(struct kvm_vcpu *vcpu)
+{
+ return esr_abt_is_write_fault(kvm_vcpu_get_esr(vcpu));
}
static inline unsigned long kvm_vcpu_get_mpidr_aff(struct kvm_vcpu *vcpu)
diff --git a/arch/arm64/include/asm/kvm_pgtable.h b/arch/arm64/include/asm/kvm_pgtable.h
index 20d12da3d28ee..589db1d6405fa 100644
--- a/arch/arm64/include/asm/kvm_pgtable.h
+++ b/arch/arm64/include/asm/kvm_pgtable.h
@@ -859,6 +859,8 @@ int kvm_pgtable_walk(struct kvm_pgtable *pgt, u64 addr, u64 size,
* @addr: Input address for the start of the walk.
* @ptep: Pointer to storage for the retrieved PTE.
* @level: Pointer to storage for the level of the retrieved PTE.
+ * @flags: Flags to control the page-table walk
+ * (see struct kvm_pgtable_visit_ctx).
*
* The offset of @addr within a page is ignored.
*
@@ -869,7 +871,8 @@ int kvm_pgtable_walk(struct kvm_pgtable *pgt, u64 addr, u64 size,
* Return: 0 on success, negative error code on failure.
*/
int kvm_pgtable_get_leaf(struct kvm_pgtable *pgt, u64 addr,
- kvm_pte_t *ptep, s8 *level);
+ kvm_pte_t *ptep, s8 *level,
+ enum kvm_pgtable_walk_flags flags);
/**
* kvm_pgtable_stage2_pte_prot() - Retrieve the protection attributes of a
diff --git a/arch/arm64/include/asm/kvm_pkvm.h b/arch/arm64/include/asm/kvm_pkvm.h
index cad60569f0619..c1831c8e421d4 100644
--- a/arch/arm64/include/asm/kvm_pkvm.h
+++ b/arch/arm64/include/asm/kvm_pkvm.h
@@ -44,9 +44,9 @@ static inline bool kvm_pkvm_ext_allowed(struct kvm *kvm, long ext)
case KVM_CAP_ARM_PTRAUTH_GENERIC:
return true;
case KVM_CAP_ARM_MTE:
- return false;
case KVM_CAP_ARM_EAGER_SPLIT_CHUNK_SIZE:
case KVM_CAP_ARM_SUPPORTED_BLOCK_SIZES:
+ case KVM_CAP_PRE_FAULT_MEMORY:
return false;
default:
return !kvm || !kvm_vm_is_protected(kvm);
diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig
index 449154f9a4852..71233068b7cb6 100644
--- a/arch/arm64/kvm/Kconfig
+++ b/arch/arm64/kvm/Kconfig
@@ -37,6 +37,7 @@ menuconfig KVM
select SCHED_INFO
select GUEST_PERF_EVENTS if PERF_EVENTS
select KVM_GUEST_MEMFD
+ select KVM_GENERIC_PRE_FAULT_MEMORY
help
Support hosting virtualized guest machines.
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 31f803010c57c..367638e1e0204 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -410,6 +410,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
case KVM_CAP_COUNTER_OFFSET:
case KVM_CAP_ARM_WRITABLE_IMP_ID_REGS:
case KVM_CAP_ARM_SEA_TO_USER:
+ case KVM_CAP_PRE_FAULT_MEMORY:
r = 1;
break;
case KVM_CAP_SET_GUEST_DEBUG2:
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index a6a47c1e058b3..443ee433119ef 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -540,7 +540,7 @@ static int host_stage2_adjust_range(u64 addr, struct kvm_mem_range *range)
int ret;
hyp_assert_lock_held(&host_mmu.lock);
- ret = kvm_pgtable_get_leaf(&host_mmu.pgt, addr, &pte, &level);
+ ret = kvm_pgtable_get_leaf(&host_mmu.pgt, addr, &pte, &level, 0);
if (ret)
return ret;
@@ -930,7 +930,7 @@ static int get_valid_guest_pte(struct pkvm_hyp_vm *vm, u64 ipa, kvm_pte_t *ptep,
s8 level;
int ret;
- ret = kvm_pgtable_get_leaf(&vm->pgt, ipa, &pte, &level);
+ ret = kvm_pgtable_get_leaf(&vm->pgt, ipa, &pte, &level, 0);
if (ret)
return ret;
if (guest_pte_is_poisoned(pte))
@@ -979,7 +979,7 @@ int __pkvm_vcpu_in_poison_fault(struct pkvm_hyp_vcpu *hyp_vcpu)
ipa |= FAR_TO_FIPA_OFFSET(kvm_vcpu_get_hfar(&hyp_vcpu->vcpu));
guest_lock_component(vm);
- ret = kvm_pgtable_get_leaf(&vm->pgt, ipa, &pte, &level);
+ ret = kvm_pgtable_get_leaf(&vm->pgt, ipa, &pte, &level, 0);
if (ret)
goto unlock;
@@ -1335,7 +1335,7 @@ static int host_stage2_get_guest_info(phys_addr_t phys, struct pkvm_hyp_vm **vm,
return -EPERM;
}
- ret = kvm_pgtable_get_leaf(&host_mmu.pgt, phys, &pte, &level);
+ ret = kvm_pgtable_get_leaf(&host_mmu.pgt, phys, &pte, &level, 0);
if (ret)
return ret;
@@ -1564,7 +1564,7 @@ static int __check_host_shared_guest(struct pkvm_hyp_vm *vm, u64 *__phys, u64 ip
s8 level;
int ret;
- ret = kvm_pgtable_get_leaf(&vm->pgt, ipa, &pte, &level);
+ ret = kvm_pgtable_get_leaf(&vm->pgt, ipa, &pte, &level, 0);
if (ret)
return ret;
if (!kvm_pte_valid(pte))
diff --git a/arch/arm64/kvm/hyp/nvhe/mm.c b/arch/arm64/kvm/hyp/nvhe/mm.c
index 29ab5ee9d57fc..bcf2fd1ddd75d 100644
--- a/arch/arm64/kvm/hyp/nvhe/mm.c
+++ b/arch/arm64/kvm/hyp/nvhe/mm.c
@@ -486,7 +486,7 @@ static int check_page_ownership(phys_addr_t phys)
return -EPERM;
}
- ret = kvm_pgtable_get_leaf(&host_mmu.pgt, phys, &pte, NULL);
+ ret = kvm_pgtable_get_leaf(&host_mmu.pgt, phys, &pte, NULL, 0);
if (ret)
return ret;
diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c
index b74dd5ce1efd3..347eec3957d6a 100644
--- a/arch/arm64/kvm/hyp/pgtable.c
+++ b/arch/arm64/kvm/hyp/pgtable.c
@@ -298,12 +298,13 @@ static int leaf_walker(const struct kvm_pgtable_visit_ctx *ctx,
}
int kvm_pgtable_get_leaf(struct kvm_pgtable *pgt, u64 addr,
- kvm_pte_t *ptep, s8 *level)
+ kvm_pte_t *ptep, s8 *level,
+ enum kvm_pgtable_walk_flags flags)
{
struct leaf_walk_data data;
struct kvm_pgtable_walker walker = {
.cb = leaf_walker,
- .flags = KVM_PGTABLE_WALK_LEAF,
+ .flags = flags | KVM_PGTABLE_WALK_LEAF,
.arg = &data,
};
int ret;
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index d6187295c3736..98c8ca0faf792 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -5,6 +5,7 @@
*/
#include <linux/acpi.h>
+#include <linux/cleanup.h>
#include <linux/mman.h>
#include <linux/kvm_host.h>
#include <linux/interval_tree.h>
@@ -895,7 +896,7 @@ static int get_user_mapping_size(struct kvm *kvm, u64 addr)
* IPI-ing threads).
*/
local_irq_save(flags);
- ret = kvm_pgtable_get_leaf(&pgt, addr, &pte, &level);
+ ret = kvm_pgtable_get_leaf(&pgt, addr, &pte, &level, 0);
local_irq_restore(flags);
if (ret)
@@ -1606,9 +1607,9 @@ static void *get_mmu_memcache(struct kvm_vcpu *vcpu)
return &vcpu->arch.pkvm_memcache;
}
-static int topup_mmu_memcache(struct kvm_vcpu *vcpu, void *memcache)
+static int topup_mmu_memcache(struct kvm_s2_mmu *mmu, void *memcache)
{
- int min_pages = kvm_mmu_cache_min_pages(vcpu->arch.hw_mmu);
+ int min_pages = kvm_mmu_cache_min_pages(mmu);
if (!is_protected_kvm_enabled())
return kvm_mmu_topup_memory_cache(memcache, min_pages);
@@ -1655,15 +1656,47 @@ struct kvm_s2_fault_desc {
struct kvm_s2_trans *nested;
struct kvm_memory_slot *memslot;
unsigned long hva;
+ unsigned long esr;
+ struct kvm_s2_mmu *mmu;
};
-static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
+struct kvm_s2_fault_result {
+ unsigned long mapping_size;
+};
+
+static bool kvm_s2_fault_is_perm(const struct kvm_s2_fault_desc *s2fd)
+{
+ return esr_fsc_is_permission_fault(s2fd->esr);
+}
+
+static bool kvm_s2_fault_is_exec(const struct kvm_s2_fault_desc *s2fd)
+{
+ return esr_abt_is_exec_fault(s2fd->esr);
+}
+
+static bool kvm_s2_fault_is_write(const struct kvm_s2_fault_desc *s2fd)
+{
+ return esr_abt_is_write_fault(s2fd->esr);
+}
+
+static u64 kvm_s2_perm_fault_granule(const struct kvm_s2_fault_desc *s2fd)
+{
+ u64 level;
+
+ if (!kvm_s2_fault_is_perm(s2fd))
+ return 0;
+ level = s2fd->esr & ESR_ELx_FSC_LEVEL;
+ return BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(level));
+}
+
+static int gmem_abort(const struct kvm_s2_fault_desc *s2fd,
+ struct kvm_s2_fault_result *result)
{
bool write_fault, exec_fault;
- bool perm_fault = kvm_vcpu_trap_is_permission_fault(s2fd->vcpu);
+ bool perm_fault = kvm_s2_fault_is_perm(s2fd);
enum kvm_pgtable_walk_flags flags = KVM_PGTABLE_WALK_SHARED;
enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_R;
- struct kvm_pgtable *pgt = s2fd->vcpu->arch.hw_mmu->pgt;
+ struct kvm_pgtable *pgt = s2fd->mmu->pgt;
struct kvm_guest_s2_mapping *mapping = NULL;
unsigned long mmu_seq;
struct page *page;
@@ -1675,7 +1708,7 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
if (!perm_fault) {
memcache = get_mmu_memcache(s2fd->vcpu);
- ret = topup_mmu_memcache(s2fd->vcpu, memcache);
+ ret = topup_mmu_memcache(s2fd->mmu, memcache);
if (ret)
return ret;
if (kvm_is_nested_s2_mmu(kvm, pgt->mmu)) {
@@ -1690,8 +1723,8 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
else
gfn = s2fd->fault_ipa >> PAGE_SHIFT;
- write_fault = kvm_is_write_fault(s2fd->vcpu);
- exec_fault = kvm_vcpu_trap_is_exec_fault(s2fd->vcpu);
+ write_fault = kvm_s2_fault_is_write(s2fd);
+ exec_fault = kvm_s2_fault_is_exec(s2fd);
VM_WARN_ON_ONCE(write_fault && exec_fault);
@@ -1757,7 +1790,13 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
if ((prot & KVM_PGTABLE_PROT_W) && !ret)
mark_page_dirty_in_slot(kvm, s2fd->memslot, gfn);
- return ret != -EAGAIN ? ret : 0;
+ if (ret == -EAGAIN)
+ return result ? ret : 0;
+
+ if (result && !ret)
+ result->mapping_size = PAGE_SIZE;
+
+ return ret;
}
struct kvm_s2_fault_vma_info {
@@ -1779,7 +1818,7 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
{
unsigned int flags = FOLL_HWPOISON | FOLL_LONGTERM | FOLL_WRITE;
struct kvm_vcpu *vcpu = s2fd->vcpu;
- struct kvm_pgtable *pgt = vcpu->arch.hw_mmu->pgt;
+ struct kvm_pgtable *pgt = s2fd->mmu->pgt;
struct mm_struct *mm = current->mm;
struct kvm *kvm = vcpu->kvm;
void *hyp_memcache;
@@ -1787,7 +1826,7 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
int ret;
hyp_memcache = get_mmu_memcache(vcpu);
- ret = topup_mmu_memcache(vcpu, hyp_memcache);
+ ret = topup_mmu_memcache(s2fd->mmu, hyp_memcache);
if (ret)
return -ENOMEM;
@@ -1910,11 +1949,6 @@ static short kvm_s2_resolve_vma_size(const struct kvm_s2_fault_desc *s2fd,
return vma_shift;
}
-static bool kvm_s2_fault_is_perm(const struct kvm_s2_fault_desc *s2fd)
-{
- return kvm_vcpu_trap_is_permission_fault(s2fd->vcpu);
-}
-
static int kvm_s2_fault_get_vma_info(const struct kvm_s2_fault_desc *s2fd,
struct kvm_s2_fault_vma_info *s2vi)
{
@@ -1980,13 +2014,11 @@ static int kvm_s2_fault_pin_pfn(const struct kvm_s2_fault_desc *s2fd,
return ret;
s2vi->pfn = __kvm_faultin_pfn(s2fd->memslot, get_canonical_gfn(s2fd, s2vi),
- kvm_is_write_fault(s2fd->vcpu) ? FOLL_WRITE : 0,
+ kvm_s2_fault_is_write(s2fd) ? FOLL_WRITE : 0,
&s2vi->map_writable, &s2vi->page);
if (unlikely(is_error_noslot_pfn(s2vi->pfn))) {
- if (s2vi->pfn == KVM_PFN_ERR_HWPOISON) {
- kvm_send_hwpoison_signal(s2fd->hva, __ffs(s2vi->vma_pagesize));
- return 0;
- }
+ if (s2vi->pfn == KVM_PFN_ERR_HWPOISON)
+ return -EHWPOISON;
return -EFAULT;
}
@@ -2038,7 +2070,7 @@ static int kvm_s2_fault_compute_prot(const struct kvm_s2_fault_desc *s2fd,
{
struct kvm *kvm = s2fd->vcpu->kvm;
- if (kvm_vcpu_trap_is_exec_fault(s2fd->vcpu) && s2vi->map_non_cacheable)
+ if (kvm_s2_fault_is_exec(s2fd) && s2vi->map_non_cacheable)
return -ENOEXEC;
/*
@@ -2047,7 +2079,7 @@ static int kvm_s2_fault_compute_prot(const struct kvm_s2_fault_desc *s2fd,
* and trigger the exception here. Since the memslot is valid, inject
* the fault back to the guest.
*/
- if (esr_fsc_is_excl_atomic_fault(kvm_vcpu_get_esr(s2fd->vcpu))) {
+ if (esr_fsc_is_excl_atomic_fault(s2fd->esr)) {
kvm_inject_dabt_excl_atomic(s2fd->vcpu, kvm_vcpu_get_hfar(s2fd->vcpu));
return 1;
}
@@ -2056,13 +2088,13 @@ static int kvm_s2_fault_compute_prot(const struct kvm_s2_fault_desc *s2fd,
if (s2vi->map_writable && (s2vi->device ||
!memslot_is_logging(s2fd->memslot) ||
- kvm_is_write_fault(s2fd->vcpu)))
+ kvm_s2_fault_is_write(s2fd)))
*prot |= KVM_PGTABLE_PROT_W;
if (s2fd->nested)
*prot = adjust_nested_fault_perms(s2fd->nested, *prot);
- if (kvm_vcpu_trap_is_exec_fault(s2fd->vcpu))
+ if (kvm_s2_fault_is_exec(s2fd))
*prot |= KVM_PGTABLE_PROT_X;
if (s2vi->map_non_cacheable)
@@ -2086,7 +2118,8 @@ static int kvm_s2_fault_compute_prot(const struct kvm_s2_fault_desc *s2fd,
static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
const struct kvm_s2_fault_vma_info *s2vi,
enum kvm_pgtable_prot prot,
- void *memcache)
+ void *memcache,
+ struct kvm_s2_fault_result *result)
{
enum kvm_pgtable_walk_flags flags = KVM_PGTABLE_WALK_SHARED;
struct kvm_guest_s2_mapping *mapping = NULL;
@@ -2110,13 +2143,12 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
}
kvm_fault_lock(kvm);
- pgt = s2fd->vcpu->arch.hw_mmu->pgt;
+ pgt = s2fd->mmu->pgt;
ret = -EAGAIN;
if (mmu_invalidate_retry(kvm, s2vi->mmu_seq))
goto out_unlock;
- perm_fault_granule = (kvm_s2_fault_is_perm(s2fd) ?
- kvm_vcpu_trap_get_perm_fault_granule(s2fd->vcpu) : 0);
+ perm_fault_granule = kvm_s2_perm_fault_granule(s2fd);
mapping_size = s2vi->vma_pagesize;
pfn = s2vi->pfn;
gfn = s2vi->gfn;
@@ -2188,14 +2220,19 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
mark_page_dirty_in_slot(kvm, s2fd->memslot,
gpa_to_gfn(canonical_ipa));
- if (ret != -EAGAIN)
- return ret;
- return 0;
+ if (ret == -EAGAIN)
+ return result ? ret : 0;
+
+ if (result && !ret)
+ result->mapping_size = mapping_size;
+
+ return ret;
}
-static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd)
+static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd,
+ struct kvm_s2_fault_result *result)
{
- bool perm_fault = kvm_vcpu_trap_is_permission_fault(s2fd->vcpu);
+ bool perm_fault = kvm_s2_fault_is_perm(s2fd);
struct kvm_s2_fault_vma_info s2vi = {};
enum kvm_pgtable_prot prot;
void *memcache;
@@ -2213,7 +2250,7 @@ static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd)
memcache = get_mmu_memcache(s2fd->vcpu);
if (!perm_fault || memslot_is_logging(s2fd->memslot) ||
is_protected_kvm_enabled()) {
- ret = topup_mmu_memcache(s2fd->vcpu, memcache);
+ ret = topup_mmu_memcache(s2fd->mmu, memcache);
if (ret)
return ret;
}
@@ -2223,6 +2260,13 @@ static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd)
* get block mapping for device MMIO region.
*/
ret = kvm_s2_fault_pin_pfn(s2fd, &s2vi);
+ if (ret == -EHWPOISON) {
+ /* If result is specified, let the caller handle this. */
+ if (result)
+ return -EHWPOISON;
+ kvm_send_hwpoison_signal(s2fd->hva, __ffs(s2vi.vma_pagesize));
+ return 0;
+ }
if (ret != 1)
return ret;
@@ -2232,7 +2276,7 @@ static int user_mem_abort(const struct kvm_s2_fault_desc *s2fd)
return ret;
}
- return kvm_s2_fault_map(s2fd, &s2vi, prot, memcache);
+ return kvm_s2_fault_map(s2fd, &s2vi, prot, memcache, result);
}
/* Resolve the access fault by making the page young again. */
@@ -2342,7 +2386,8 @@ int kvm_handle_guest_sea(struct kvm_vcpu *vcpu)
int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
{
struct kvm_s2_trans nested_trans, *nested = NULL;
- unsigned long esr;
+ unsigned long esr = kvm_vcpu_get_esr(vcpu);
+ struct kvm_s2_mmu *mmu = vcpu->arch.hw_mmu;
phys_addr_t fault_ipa; /* The address we faulted on */
phys_addr_t ipa; /* Always the IPA in the L1 guest phys space */
struct kvm_memory_slot *memslot;
@@ -2351,11 +2396,9 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
gfn_t gfn;
int ret, idx;
- if (kvm_vcpu_abt_issea(vcpu))
+ if (esr_abt_is_sea(esr))
return kvm_handle_guest_sea(vcpu);
- esr = kvm_vcpu_get_esr(vcpu);
-
/*
* The fault IPA should be reliable at this point as we're not dealing
* with an SEA.
@@ -2364,7 +2407,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
if (KVM_BUG_ON(ipa == INVALID_GPA, vcpu->kvm))
return -EFAULT;
- is_iabt = kvm_vcpu_trap_is_iabt(vcpu);
+ is_iabt = esr_trap_is_iabt(esr);
if (esr_fsc_is_translation_fault(esr)) {
/* Beyond sanitised PARange (which is the IPA limit) */
@@ -2374,14 +2417,14 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
}
/* Falls between the IPA range and the PARange? */
- if (fault_ipa >= BIT_ULL(VTCR_EL2_IPA(vcpu->arch.hw_mmu->vtcr))) {
+ if (fault_ipa >= BIT_ULL(VTCR_EL2_IPA(mmu->vtcr))) {
fault_ipa |= FAR_TO_FIPA_OFFSET(kvm_vcpu_get_hfar(vcpu));
return kvm_inject_sea(vcpu, is_iabt, fault_ipa);
}
}
- trace_kvm_guest_fault(*vcpu_pc(vcpu), kvm_vcpu_get_esr(vcpu),
+ trace_kvm_guest_fault(*vcpu_pc(vcpu), esr,
kvm_vcpu_get_hfar(vcpu), fault_ipa);
/* Check the stage-2 fault is trans. fault or write fault */
@@ -2390,9 +2433,9 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
!esr_fsc_is_access_flag_fault(esr) &&
!esr_fsc_is_excl_atomic_fault(esr)) {
kvm_err("Unsupported FSC: EC=%#x xFSC=%#lx ESR_EL2=%#lx\n",
- kvm_vcpu_trap_get_class(vcpu),
- (unsigned long)kvm_vcpu_trap_get_fault(vcpu),
- (unsigned long)kvm_vcpu_get_esr(vcpu));
+ esr_get_ec(esr),
+ (unsigned long)(esr & ESR_ELx_FSC),
+ (unsigned long)esr);
return -EFAULT;
}
@@ -2411,8 +2454,8 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
* nothing to walk and we treat it as a 1:1 before going through the
* canonical translation.
*/
- if (kvm_is_nested_s2_mmu(vcpu->kvm,vcpu->arch.hw_mmu) &&
- vcpu->arch.hw_mmu->nested_stage2_enabled) {
+ if (kvm_is_nested_s2_mmu(vcpu->kvm, mmu) &&
+ mmu->nested_stage2_enabled) {
u32 esr;
ret = kvm_walk_nested_s2(vcpu, fault_ipa, &nested_trans);
@@ -2441,7 +2484,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
gfn = ipa >> PAGE_SHIFT;
memslot = gfn_to_memslot(vcpu->kvm, gfn);
hva = gfn_to_hva_memslot_prot(memslot, gfn, &writable);
- write_fault = kvm_is_write_fault(vcpu);
+ write_fault = esr_abt_is_write_fault(esr);
if (kvm_is_error_hva(hva) || (write_fault && !writable)) {
/*
* The guest has put either its instructions or its page-tables
@@ -2454,7 +2497,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
goto out;
}
- if (kvm_vcpu_abt_iss1tw(vcpu)) {
+ if (esr_abt_is_s1ptw(esr)) {
ret = kvm_inject_sea_dabt(vcpu, kvm_vcpu_get_hfar(vcpu));
goto out_unlock;
}
@@ -2469,7 +2512,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
* So let's assume that the guest is just being
* cautious, and skip the instruction.
*/
- if (kvm_is_error_hva(hva) && kvm_vcpu_dabt_is_cm(vcpu)) {
+ if (kvm_is_error_hva(hva) && esr_dabt_is_cm(esr)) {
kvm_incr_pc(vcpu);
ret = 1;
goto out_unlock;
@@ -2487,7 +2530,7 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
}
/* Userspace should not be able to register out-of-bounds IPAs */
- VM_BUG_ON(ipa >= kvm_phys_size(vcpu->arch.hw_mmu));
+ VM_BUG_ON(ipa >= kvm_phys_size(mmu));
if (esr_fsc_is_access_flag_fault(esr)) {
handle_access_fault(vcpu, fault_ipa);
@@ -2501,19 +2544,20 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
.nested = nested,
.memslot = memslot,
.hva = hva,
+ .esr = esr,
+ .mmu = mmu,
};
if (kvm_vm_is_protected(vcpu->kvm)) {
ret = pkvm_mem_abort(&s2fd);
} else {
- VM_WARN_ON_ONCE(kvm_vcpu_trap_is_permission_fault(vcpu) &&
- !write_fault &&
- !kvm_vcpu_trap_is_exec_fault(vcpu));
+ VM_WARN_ON_ONCE(kvm_s2_fault_is_perm(&s2fd) && !write_fault &&
+ !kvm_s2_fault_is_exec(&s2fd));
if (kvm_slot_has_gmem(memslot))
- ret = gmem_abort(&s2fd);
+ ret = gmem_abort(&s2fd, NULL);
else
- ret = user_mem_abort(&s2fd);
+ ret = user_mem_abort(&s2fd, NULL);
}
if (ret == 0)
@@ -2890,3 +2934,149 @@ void kvm_toggle_cache(struct kvm_vcpu *vcpu, bool was_enabled)
trace_kvm_toggle_cache(*vcpu_pc(vcpu), was_enabled, now_enabled);
}
+
+/*
+ * Try to walk to the specified GPA in canonical mmu - if unmapped returns 0, if
+ * mapped returns the granule size, otherwise returns an error.
+ */
+static long kvm_walk_s2(struct kvm_pgtable *pgt,
+ gpa_t gpa, s8 *level)
+{
+ struct kvm *kvm = kvm_s2_mmu_to_kvm(pgt->mmu);
+ kvm_pte_t pte;
+ long ret;
+
+ guard(read_lock)(&kvm->mmu_lock);
+
+ ret = kvm_pgtable_get_leaf(pgt, gpa, &pte, level,
+ KVM_PGTABLE_WALK_SHARED);
+ if (ret)
+ return ret;
+ /* Unpopulated, must fault. */
+ if (!kvm_pte_valid(pte))
+ return 0;
+ return kvm_granule_size(*level);
+}
+
+/* Synthesised data abort at specified page table level. */
+#define PRE_FAULT_ESR(level) \
+ ((ESR_ELx_EC_DABT_LOW << ESR_ELx_EC_SHIFT) | \
+ ESR_ELx_IL | ESR_ELx_FSC_FAULT_L(level))
+
+/* Retrieve either a read-only or a read/write hva. */
+static hva_t gfn_to_hva_memslot_read(struct kvm_memory_slot *slot, gfn_t gfn)
+{
+ return gfn_to_hva_memslot_prot(slot, gfn, /*writable=*/NULL);
+}
+
+static long __pre_fault_s2(struct kvm_s2_mmu *mmu, struct kvm_vcpu *vcpu,
+ gpa_t gpa, struct kvm_memory_slot *memslot, s8 level)
+{
+ const bool is_gmem = kvm_slot_has_gmem(memslot);
+ const gfn_t gfn = gpa_to_gfn(gpa);
+ const hva_t hva = is_gmem ? 0 : gfn_to_hva_memslot_read(memslot, gfn);
+ const struct kvm_s2_fault_desc s2fd = {
+ .vcpu = vcpu,
+ .fault_ipa = gpa,
+ .nested = NULL,
+ .memslot = memslot,
+ .hva = hva,
+ .esr = PRE_FAULT_ESR(level),
+ .mmu = mmu,
+ };
+ struct kvm_s2_fault_result result = {};
+ long ret;
+
+ if (kvm_is_error_hva(hva))
+ return -EFAULT;
+
+ if (is_gmem)
+ ret = gmem_abort(&s2fd, &result);
+ else
+ ret = user_mem_abort(&s2fd, &result);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+ return result.mapping_size;
+}
+
+static long pre_fault_s2(struct kvm_s2_mmu *mmu, struct kvm_vcpu *vcpu,
+ gpa_t gpa, struct kvm_memory_slot *memslot)
+{
+ s8 level;
+ long ret;
+
+ /* Try a walk first. */
+ ret = kvm_walk_s2(mmu->pgt, gpa, &level);
+ if (ret)
+ return ret;
+ /* OK, have to fault page in. */
+ return __pre_fault_s2(mmu, vcpu, gpa, memslot, level);
+}
+
+static unsigned long
+pre_fault_bytes_consumed(gpa_t gpa, unsigned long granule_size,
+ unsigned long bytes_remaining)
+{
+ /* Granules are always a power-of-2. */
+ const unsigned long granule_bytes_remaining =
+ granule_size - (gpa % granule_size);
+
+ return min(granule_bytes_remaining, bytes_remaining);
+}
+
+/* If you lose the race this many times, time to give up. */
+#define MAX_PRE_FAULT_RETRIES 3
+
+bool kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu)
+{
+ /* Only initialised vCPUs can pre-fault. */
+ return kvm_vcpu_initialized(vcpu);
+}
+
+/**
+ * kvm_arch_vcpu_pre_fault_memory - pre-fault stage-2 page tables for the
+ * specified GPA.
+ * @vcpu: The VCPU pointer
+ * @range: {gpa, size, flags} tuple
+ *
+ * The mapping performed is always best-effort - faulting in is necessarily
+ * racey. The ranges faulted in are canonical, nested page tables are ignored.
+ *
+ * @range->gpa specifies the GPA to pre-fault, @range->size specifies how many
+ * bytes remain to be pre-faulted and @range->flags is reserved and must be 0.
+ *
+ * Returns: the number of bytes the pre-fault consumed, or an error.
+ */
+long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
+ struct kvm_pre_fault_memory *range)
+{
+ struct kvm *kvm = vcpu->kvm;
+ const u64 bytes_remaining = range->size;
+ struct kvm_s2_mmu *mmu = &kvm->arch.mmu; /* Canonical. */
+ struct kvm_memory_slot *memslot;
+ const gpa_t gpa = range->gpa;
+ int num_retries = 0;
+ long ret;
+
+ /*
+ * pKVM is unsupported as their vCPUs are instantiated on first run and
+ * pre-faulting only running vCPUs would be inconsistent and confusing.
+ */
+ if (is_protected_kvm_enabled())
+ return -EOPNOTSUPP;
+
+ memslot = gfn_to_memslot(kvm, gpa_to_gfn(gpa));
+ if (!memslot)
+ return -ENOENT;
+ /* SRCU must be released for progress and only userland can do that. */
+ if (memslot->flags & KVM_MEMSLOT_INVALID)
+ return -EAGAIN;
+
+ do {
+ ret = pre_fault_s2(mmu, vcpu, gpa, memslot);
+ } while (ret == -EAGAIN && num_retries++ < MAX_PRE_FAULT_RETRIES);
+
+ if (IS_ERR_VALUE(ret))
+ return ret;
+ return pre_fault_bytes_consumed(gpa, ret, bytes_remaining);
+}
diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
index ec754a865a006..cd868af63b1b8 100644
--- a/arch/arm64/kvm/nested.c
+++ b/arch/arm64/kvm/nested.c
@@ -662,7 +662,7 @@ static u8 get_guest_mapping_ttl(struct kvm_s2_mmu *mmu, u64 addr)
return 0;
tmp &= ~(sz - 1);
- if (kvm_pgtable_get_leaf(mmu->pgt, tmp, &pte, NULL))
+ if (kvm_pgtable_get_leaf(mmu->pgt, tmp, &pte, NULL, 0))
goto again;
if (!(pte & PTE_VALID))
goto again;
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 03bfc92864b6e..c11fda8704a3e 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -1693,6 +1693,7 @@ int kvm_arch_vcpu_should_kick(struct kvm_vcpu *vcpu);
bool kvm_arch_dy_runnable(struct kvm_vcpu *vcpu);
bool kvm_arch_dy_has_pending_interrupt(struct kvm_vcpu *vcpu);
bool kvm_arch_vcpu_preempted_in_kernel(struct kvm_vcpu *vcpu);
+bool kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu);
void kvm_arch_pre_destroy_vm(struct kvm *kvm);
void kvm_arch_create_vm_debugfs(struct kvm *kvm);
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 6a1482e3a286b..908bdc7cf4f58 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -178,6 +178,7 @@ TEST_GEN_PROGS_arm64 += arm64/debug-exceptions
TEST_GEN_PROGS_arm64 += arm64/hello_el2
TEST_GEN_PROGS_arm64 += arm64/host_sve
TEST_GEN_PROGS_arm64 += arm64/hypercalls
+TEST_GEN_PROGS_arm64 += arm64/nv_pre_fault_memory_test
TEST_GEN_PROGS_arm64 += arm64/external_aborts
TEST_GEN_PROGS_arm64 += arm64/mmio_sign_ext
TEST_GEN_PROGS_arm64 += arm64/page_fault_test
@@ -205,6 +206,7 @@ TEST_GEN_PROGS_arm64 += guest_memfd_test
TEST_GEN_PROGS_arm64 += mmu_stress_test
TEST_GEN_PROGS_arm64 += rseq_test
TEST_GEN_PROGS_arm64 += steal_time
+TEST_GEN_PROGS_arm64 += pre_fault_memory_test
TEST_GEN_PROGS_s390 = $(TEST_GEN_PROGS_COMMON)
TEST_GEN_PROGS_s390 += s390/memop
diff --git a/tools/testing/selftests/kvm/arm64/nv_pre_fault_memory_test.c b/tools/testing/selftests/kvm/arm64/nv_pre_fault_memory_test.c
new file mode 100644
index 0000000000000..09c1db3038566
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/nv_pre_fault_memory_test.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * nv_pre_fault_memory_test - Test KVM_PRE_FAULT_MEMORY on a vCPU whose
+ * last-run context is nested.
+ *
+ * The guest enters vEL2, sets up its EL2 translation configuration into the
+ * real EL1 registers then ERETs to vEL1 and exits to userspace so its vCPU
+ * last-run context is nested backed by a shadow stage 2 MMU.
+ *
+ * Assert that pre-faulting ignores that and targets the canonical stage-2
+ * page tables only.
+ */
+#include "kvm_util.h"
+#include "processor.h"
+#include "test_util.h"
+#include "ucall.h"
+
+#include <asm/sysreg.h>
+#include <linux/sizes.h>
+
+#define TEST_MEM_SLOT 10
+#define TEST_MEM_SIZE SZ_2M
+#define TEST_MEM_GPA SZ_1G
+
+static void guest_el1_code(void)
+{
+ u64 offset;
+
+ GUEST_ASSERT_EQ(get_current_el(), 1);
+
+ /* Exit to userspace with the vEL1 (nested) context live. */
+ GUEST_SYNC(1);
+
+ /*
+ * Touch the prefaulted range. vstage-2 is disabled, so the shadow
+ * stage-2 is a 1:1 view of the canonical IPA space.
+ */
+ for (offset = 0; offset < TEST_MEM_SIZE; offset += SZ_4K)
+ READ_ONCE(*(u64 *)(TEST_MEM_GPA + offset));
+
+ GUEST_DONE();
+}
+
+static void guest_code(void)
+{
+ u64 sp;
+
+ GUEST_ASSERT_EQ(get_current_el(), 2);
+
+ /*
+ * Mirror the EL2 translation regime into the real EL1 registers so
+ * that vEL1 runs on the test's stage-1 page tables. With E2H=1, the
+ * _EL1 accessors read the EL2 registers, and the _EL12 accessors
+ * write the real EL1 registers.
+ */
+ write_sysreg_s(read_sysreg(sctlr_el1), SYS_SCTLR_EL12);
+ write_sysreg_s(read_sysreg(tcr_el1), SYS_TCR_EL12);
+ write_sysreg_s(read_sysreg(ttbr0_el1), SYS_TTBR0_EL12);
+ write_sysreg_s(read_sysreg(mair_el1), SYS_MAIR_EL12);
+ write_sysreg_s(read_sysreg(cpacr_el1), SYS_CPACR_EL12);
+
+ /* Run vEL1 on the same stack. */
+ asm volatile("mov %0, sp" : "=r"(sp));
+ write_sysreg(sp, sp_el1);
+
+ /*
+ * Drop TGE so that vEL1 is a nested context rather than host EL0.
+ * KVM backs it with a shadow stage-2 MMU even though vstage-2 is
+ * disabled (HCR_EL2.VM=0).
+ */
+ write_sysreg(read_sysreg(hcr_el2) & ~HCR_EL2_TGE, hcr_el2);
+ isb();
+
+ write_sysreg(PSR_MODE_EL1h | PSR_F_BIT | PSR_I_BIT | PSR_A_BIT |
+ PSR_D_BIT, spsr_el2);
+ write_sysreg((u64)guest_el1_code, elr_el2);
+ asm volatile("eret");
+
+ GUEST_ASSERT(false);
+}
+
+static void pre_fault(struct kvm_vcpu *vcpu, u64 gpa, u64 size)
+{
+ struct kvm_pre_fault_memory range = {
+ .gpa = gpa,
+ .size = size,
+ };
+ int ret;
+
+ do {
+ ret = __vcpu_ioctl(vcpu, KVM_PRE_FAULT_MEMORY, &range);
+ } while ((!ret && range.size) ||
+ (ret < 0 && (errno == EINTR || errno == EAGAIN)));
+
+ TEST_ASSERT(!ret, "KVM_PRE_FAULT_MEMORY failed, ret: %d errno: %d",
+ ret, errno);
+ TEST_ASSERT_EQ(range.size, 0);
+}
+
+int main(void)
+{
+ struct kvm_vcpu_init init;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ struct ucall uc;
+ u64 npages;
+
+ TEST_REQUIRE(test_supports_el2());
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_PRE_FAULT_MEMORY));
+
+ vm = vm_create(1);
+
+ kvm_get_default_vcpu_target(vm, &init);
+ init.features[0] |= BIT(KVM_ARM_VCPU_HAS_EL2);
+ vcpu = aarch64_vcpu_add(vm, 0, &init, guest_code);
+ kvm_arch_vm_finalize_vcpus(vm);
+
+ npages = TEST_MEM_SIZE / vm->page_size;
+ vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, TEST_MEM_GPA,
+ TEST_MEM_SLOT, npages, 0);
+ virt_map(vm, TEST_MEM_GPA, TEST_MEM_GPA, npages);
+
+ /* Run the guest until it has ERET'd from vEL2 to vEL1. */
+ vcpu_run(vcpu);
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_SYNC:
+ TEST_ASSERT_EQ(uc.args[1], 1);
+ break;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ default:
+ TEST_FAIL("Unhandled ucall: %ld", uc.cmd);
+ }
+
+ /*
+ * The vCPU's last-run context is vEL1, so its hw_mmu is a shadow
+ * stage-2 MMU.
+ *
+ * Pre-faulting must ignore that and populate the canonical stage-2.
+ */
+ pre_fault(vcpu, TEST_MEM_GPA, TEST_MEM_SIZE);
+
+ /* Resume at vEL1 and touch the prefaulted range. */
+ vcpu_run(vcpu);
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_DONE:
+ break;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ default:
+ TEST_FAIL("Unhandled ucall: %ld", uc.cmd);
+ }
+
+ kvm_vm_free(vm);
+ return 0;
+}
diff --git a/tools/testing/selftests/kvm/pre_fault_memory_test.c b/tools/testing/selftests/kvm/pre_fault_memory_test.c
index c57631aab3d38..3082fe09b95fa 100644
--- a/tools/testing/selftests/kvm/pre_fault_memory_test.c
+++ b/tools/testing/selftests/kvm/pre_fault_memory_test.c
@@ -12,19 +12,29 @@
#include <processor.h>
#include <pthread.h>
#include <ucall_common.h>
+#include <guest_modes.h>
/* Arbitrarily chosen values */
-#define TEST_SIZE (SZ_2M + PAGE_SIZE)
-#define TEST_NPAGES (TEST_SIZE / PAGE_SIZE)
+#define TEST_BASE_SIZE SZ_2M
#define TEST_SLOT 10
+/* Storage of test info to share with guest code */
+struct test_config {
+ u64 page_size;
+ u64 test_size;
+ u64 test_num_pages;
+};
+
+static struct test_config test_config;
+
static void guest_code(u64 base_gva)
{
volatile u64 val __used;
+ struct test_config *config = &test_config;
int i;
- for (i = 0; i < TEST_NPAGES; i++) {
- u64 *src = (u64 *)(base_gva + i * PAGE_SIZE);
+ for (i = 0; i < config->test_num_pages; i++) {
+ u64 *src = (u64 *)(base_gva + i * config->page_size);
val = *src;
}
@@ -36,6 +46,7 @@ struct slot_worker_data {
struct kvm_vm *vm;
gpa_t gpa;
u32 flags;
+ enum vm_mem_backing_src_type mem_backing_src;
bool worker_ready;
bool prefault_ready;
bool recreate_slot;
@@ -56,14 +67,16 @@ static void *delete_slot_worker(void *__data)
while (!READ_ONCE(data->recreate_slot))
cpu_relax();
- vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, data->gpa,
- TEST_SLOT, TEST_NPAGES, data->flags);
+ vm_userspace_mem_region_add(vm, data->mem_backing_src, data->gpa,
+ TEST_SLOT, test_config.test_num_pages, data->flags);
return NULL;
}
static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,
- u64 size, u64 expected_left, bool private)
+ u64 size, u64 expected_left,
+ enum vm_mem_backing_src_type mem_backing_src,
+ bool private)
{
struct kvm_pre_fault_memory range = {
.gpa = base_gpa + offset,
@@ -74,6 +87,7 @@ static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,
.vm = vcpu->vm,
.gpa = base_gpa,
.flags = private ? KVM_MEM_GUEST_MEMFD : 0,
+ .mem_backing_src = mem_backing_src,
};
bool slot_recreated = false;
pthread_t slot_worker;
@@ -150,8 +164,8 @@ static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,
/*
* Assert success if prefaulting the entire range should succeed, i.e.
* complete with no bytes remaining. Otherwise prefaulting should have
- * failed due to ENOENT (due to RET_PF_EMULATE for emulated MMIO when
- * no memslot exists).
+ * failed due to ENOENT (no memslot exists for the GPA; on x86 this
+ * surfaces via RET_PF_EMULATE).
*/
if (!expected_left)
TEST_ASSERT_VM_VCPU_IOCTL(!ret, KVM_PRE_FAULT_MEMORY, ret, vcpu->vm);
@@ -160,39 +174,82 @@ static void pre_fault_memory(struct kvm_vcpu *vcpu, u64 base_gpa, u64 offset,
KVM_PRE_FAULT_MEMORY, ret, vcpu->vm);
}
-static void __test_pre_fault_memory(unsigned long vm_type, bool private)
+struct test_params {
+ unsigned long vm_type;
+ bool private;
+ enum vm_mem_backing_src_type mem_backing_src;
+};
+
+static void __test_pre_fault_memory(enum vm_guest_mode guest_mode, void *arg)
{
- gpa_t gpa, gva, alignment, guest_page_size;
+ gpa_t gpa, gva, alignment, guest_page_size, host_page_size;
+ gpa_t backing_src_pagesz, mem_page_size;
+ struct test_params *p = arg;
const struct vm_shape shape = {
- .mode = VM_MODE_DEFAULT,
- .type = vm_type,
+ .mode = guest_mode,
+ .type = p->vm_type,
};
struct kvm_vcpu *vcpu;
+ struct kvm_run *run;
struct kvm_vm *vm;
struct ucall uc;
+ pr_info("Testing guest mode: %s\n", vm_guest_mode_string(guest_mode));
+ pr_info("Testing memory backing src type: %s\n",
+ vm_mem_backing_src_alias(p->mem_backing_src)->name);
+
vm = vm_create_shape_with_one_vcpu(shape, &vcpu, guest_code);
- alignment = guest_page_size = vm_guest_mode_params[VM_MODE_DEFAULT].page_size;
- gpa = (vm->max_gfn - TEST_NPAGES) * guest_page_size;
+ guest_page_size = vm_guest_mode_params[guest_mode].page_size;
+ host_page_size = getpagesize();
+ backing_src_pagesz = get_backing_src_pagesz(p->mem_backing_src);
+ mem_page_size = max(host_page_size, backing_src_pagesz);
+
+ test_config.page_size = guest_page_size;
+ test_config.test_size = align_up(TEST_BASE_SIZE + test_config.page_size,
+ mem_page_size);
+ test_config.test_num_pages = vm_calc_num_guest_pages(vm->mode, test_config.test_size);
+
+ gpa = (vm->max_gfn - test_config.test_num_pages) * test_config.page_size;
alignment = SZ_2M;
+ alignment = max(alignment, mem_page_size);
gpa = align_down(gpa, alignment);
gva = gpa & ((1ULL << (vm->va_bits - 1)) - 1);
- vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, gpa, TEST_SLOT,
- TEST_NPAGES, private ? KVM_MEM_GUEST_MEMFD : 0);
- virt_map(vm, gva, gpa, TEST_NPAGES);
+ vm_userspace_mem_region_add(vm, p->mem_backing_src,
+ gpa, TEST_SLOT, test_config.test_num_pages,
+ p->private ? KVM_MEM_GUEST_MEMFD : 0);
+ virt_map(vm, gva, gpa, test_config.test_num_pages);
+
+ if (p->private)
+ vm_mem_set_private(vm, gpa, test_config.test_size);
+
+ pre_fault_memory(vcpu, gpa, 0, test_config.test_size, 0,
+ p->mem_backing_src, p->private);
+ /* Retry the same range after the first prefault attempt. */
+ pre_fault_memory(vcpu, gpa, 0, test_config.test_size, 0,
+ p->mem_backing_src, p->private);
+ pre_fault_memory(vcpu, gpa,
+ test_config.test_size - host_page_size,
+ host_page_size * 2, host_page_size,
+ p->mem_backing_src, p->private);
+ pre_fault_memory(vcpu, gpa, test_config.test_size,
+ host_page_size, host_page_size,
+ p->mem_backing_src, p->private);
- if (private)
- vm_mem_set_private(vm, gpa, TEST_SIZE);
+ vcpu_args_set(vcpu, 1, gva);
- pre_fault_memory(vcpu, gpa, 0, SZ_2M, 0, private);
- pre_fault_memory(vcpu, gpa, SZ_2M, PAGE_SIZE * 2, PAGE_SIZE, private);
- pre_fault_memory(vcpu, gpa, TEST_SIZE, PAGE_SIZE, PAGE_SIZE, private);
+ /* Export the shared variables to the guest. */
+ sync_global_to_guest(vm, test_config);
- vcpu_args_set(vcpu, 1, gva);
vcpu_run(vcpu);
+ run = vcpu->run;
+ TEST_ASSERT(run->exit_reason == UCALL_EXIT_REASON,
+ "Wanted %s, got exit reason: %u (%s)",
+ exit_reason_str(UCALL_EXIT_REASON),
+ run->exit_reason, exit_reason_str(run->exit_reason));
+
switch (get_ucall(vcpu, &uc)) {
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
@@ -207,24 +264,61 @@ static void __test_pre_fault_memory(unsigned long vm_type, bool private)
kvm_vm_free(vm);
}
-static void test_pre_fault_memory(unsigned long vm_type, bool private)
+static void test_pre_fault_memory(unsigned long vm_type, enum vm_mem_backing_src_type backing_src,
+ bool private)
{
+ struct test_params p = {
+ .vm_type = vm_type,
+ .private = private,
+ .mem_backing_src = backing_src,
+ };
+
if (vm_type && !(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(vm_type))) {
pr_info("Skipping tests for vm_type 0x%lx\n", vm_type);
return;
}
- __test_pre_fault_memory(vm_type, private);
+ for_each_guest_mode(__test_pre_fault_memory, &p);
+}
+
+static void help(char *name)
+{
+ puts("");
+ printf("usage: %s [-h] [-m mode] [-s mem-type]\n", name);
+ puts("");
+ guest_modes_help();
+ backing_src_help("-s");
+ puts("");
}
int main(int argc, char *argv[])
{
+ enum vm_mem_backing_src_type backing = DEFAULT_VM_MEM_SRC;
+ int opt;
+
+ guest_modes_append_default();
+
+ while ((opt = getopt(argc, argv, "hm:s:")) != -1) {
+ switch (opt) {
+ case 'm':
+ guest_modes_cmdline(optarg);
+ break;
+ case 's':
+ backing = parse_backing_src_type(optarg);
+ break;
+ case 'h':
+ default:
+ help(argv[0]);
+ exit(0);
+ }
+ }
+
TEST_REQUIRE(kvm_check_cap(KVM_CAP_PRE_FAULT_MEMORY));
- test_pre_fault_memory(0, false);
+ test_pre_fault_memory(0, backing, false);
#ifdef __x86_64__
- test_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, false);
- test_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, true);
+ test_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, backing, false);
+ test_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, backing, true);
#endif
return 0;
}
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 65eb26a0520d8..9662baf2bfcab 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -3961,6 +3961,11 @@ bool __weak kvm_arch_dy_has_pending_interrupt(struct kvm_vcpu *vcpu)
return false;
}
+bool __weak kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu)
+{
+ return true;
+}
+
void kvm_vcpu_on_spin(struct kvm_vcpu *me, bool yield_to_kernel_mode)
{
int nr_vcpus, start, i, idx, yielded;
@@ -4365,6 +4370,9 @@ static int kvm_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
range->gpa + range->size <= range->gpa)
return -EINVAL;
+ if (!kvm_arch_vcpu_allow_pre_fault_memory(vcpu))
+ return -ENOEXEC;
+
vcpu_load(vcpu);
idx = srcu_read_lock(&vcpu->kvm->srcu);
|
| 3/2 | 2026/09/24 22:42 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 24000 output: 29 thoughts: 692 Thoughts: **Analyzing This Patch: Targeting ARM64, But With Broader Implications?** Okay, so the target architecture is definitely amd64... wait, no, my mistake. It *seems* to be primarily focused on ARM64, given the bulk of the changes. Let's dig in. First, the diff... I see documentation updates in `Documentation/virt/kvm/api.rst` - that's always good for clarifying the intent. The majority of the changes, however, are deeply embedded in `arch/arm64` directories – include files, Kconfig, and core KVM components like emulation, pagetables, memory protection, and nesting. The selftests in `tools/testing/selftests/kvm` also look to be involved. I can see the ARM64-specific changes are substantial. But, a moment... what's this? I see changes, a new weak function `kvm_arch_vcpu_allow_pre_fault_memory` added in `include/linux/kvm_host.h`, and implemented in `virt/kvm/kvm_main.c`. And a conditional check using that function in `kvm_vcpu_pre_fault_memory`. `virt/kvm/kvm_main.c` – that's a *generic* KVM file. Hmm. Okay, let's step back... is this only for ARM64, or does this touch the amd64/x86 code path too? My brain is telling me that the KVM core is compiled for x86 (given `CONFIG_KVM`), so it has to be relevant, doesn't it? Let me check: Does x86 define `kvm_arch_vcpu_allow_pre_fault_memory`? Need to check with grepper. |
| 4/2 | 2026/09/24 22:42 | tool | grepper |
0mArgs:{
"Expression": "kvm_arch_vcpu_allow_pre_fault_memory"
}Results: {
"Output": "arch/arm64/kvm/mmu.c=3017=pre_fault_bytes_consumed(gpa_t gpa, unsigned long granule_size,\n--\narch/arm64/kvm/mmu.c-3029-\narch/arm64/kvm/mmu.c:3030:bool kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu)\narch/arm64/kvm/mmu.c-3031-{\n--\ninclude/linux/kvm_host.h=1695=bool kvm_arch_vcpu_preempted_in_kernel(struct kvm_vcpu *vcpu);\ninclude/linux/kvm_host.h:1696:bool kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu);\ninclude/linux/kvm_host.h-1697-void kvm_arch_pre_destroy_vm(struct kvm *kvm);\n--\nvirt/kvm/kvm_main.c=3959=bool __weak kvm_arch_dy_has_pending_interrupt(struct kvm_vcpu *vcpu)\n--\nvirt/kvm/kvm_main.c-3963-\nvirt/kvm/kvm_main.c:3964:bool __weak kvm_arch_vcpu_allow_pre_fault_memory(struct kvm_vcpu *vcpu)\nvirt/kvm/kvm_main.c-3965-{\n--\nvirt/kvm/kvm_main.c=4358=static int kvm_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,\n--\nvirt/kvm/kvm_main.c-4372-\nvirt/kvm/kvm_main.c:4373:\tif (!kvm_arch_vcpu_allow_pre_fault_memory(vcpu))\nvirt/kvm/kvm_main.c-4374-\t\treturn -ENOEXEC;\n"
} |
| 5/2 | 2026/09/24 22:42 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 25643 output: 40 thoughts: 157 Thoughts: Here's my thought process, as requested: **Investigating KVM Pre-Fault Memory Handling** Okay, so I'm looking at this KVM code and noticing a key difference. It seems that only the `arch/arm64` architecture *actually* implements `kvm_arch_vcpu_allow_pre_fault_memory`. On x86 (amd64), it falls back to a `__weak` implementation, which is defined in `virt/kvm/kvm_main.c`. That weak implementation simply returns `true` which, on the face of it, suggests that pre-faulting is always allowed on x86. But hang on a second... this feels like a potential blind spot. If pre-faulting is always *allowed*, what is `kvm_vcpu_pre_fault_memory` actually *doing* on x86? Why is the function even there if its controlling conditional is always satisfied? This warrants a deeper dive. I need to understand what's happening within `kvm_vcpu_pre_fault_memory` on x86 to get the full picture. Let's go read the source in `virt/kvm/kvm_main.c`. |
| 6/2 | 2026/09/24 22:42 | tool | read-file |
0mArgs:{
"File": "virt/kvm/kvm_main.c",
"FirstLine": 4355,
"LineCount": 40
}Results: {
"Contents": "4355:\t}\n4356:\t\n4357:\t#ifdef CONFIG_KVM_GENERIC_PRE_FAULT_MEMORY\n4358:\tstatic int kvm_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,\n4359:\t\t\t\t\t struct kvm_pre_fault_memory *range)\n4360:\t{\n4361:\t\tint idx;\n4362:\t\tlong r;\n4363:\t\tu64 full_size;\n4364:\t\n4365:\t\tif (range-\u003eflags)\n4366:\t\t\treturn -EINVAL;\n4367:\t\n4368:\t\tif (!PAGE_ALIGNED(range-\u003egpa) ||\n4369:\t\t !PAGE_ALIGNED(range-\u003esize) ||\n4370:\t\t range-\u003egpa + range-\u003esize \u003c= range-\u003egpa)\n4371:\t\t\treturn -EINVAL;\n4372:\t\n4373:\t\tif (!kvm_arch_vcpu_allow_pre_fault_memory(vcpu))\n4374:\t\t\treturn -ENOEXEC;\n4375:\t\n4376:\t\tvcpu_load(vcpu);\n4377:\t\tidx = srcu_read_lock(\u0026vcpu-\u003ekvm-\u003esrcu);\n4378:\t\n4379:\t\tfull_size = range-\u003esize;\n4380:\t\tdo {\n4381:\t\t\tif (signal_pending(current)) {\n4382:\t\t\t\tr = -EINTR;\n4383:\t\t\t\tbreak;\n4384:\t\t\t}\n4385:\t\n4386:\t\t\tr = kvm_arch_vcpu_pre_fault_memory(vcpu, range);\n4387:\t\t\tif (WARN_ON_ONCE(r == 0 || r == -EIO))\n4388:\t\t\t\tbreak;\n4389:\t\n4390:\t\t\tif (r \u003c 0)\n4391:\t\t\t\tbreak;\n4392:\t\n4393:\t\t\trange-\u003esize -= r;\n4394:\t\t\trange-\u003egpa += r;\n"
} |
| 7/2 | 2026/09/24 22:42 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 26666 output: 25 thoughts: 27 Thoughts: Okay, let's dive into this. Here's my thought process on this: **Analyzing CONFIG_KVM_GENERIC_PRE_FAULT_MEMORY** Hmm, `CONFIG_KVM_GENERIC_PRE_FAULT_MEMORY`... Right, before I get into anything more complex, I need to see exactly what this is and how it's being used. I need to understand its definition and any dependencies. That means digging into the code and tracing its usage. I'll need to look at the config files, of course, to see when this option is enabled or disabled. And then the source code, to see where it gets referenced within the KVM framework. I need to understand the control flow, look for if-then-else blocks, anything that affects its influence on memory management. Knowing the precise context of where it's employed is vital. I'll be looking for relevant data structures and functions. Gotta get the full picture to comprehend its significance properly. |
| 8/2 | 2026/09/24 22:42 | tool | grepper |
0mArgs:{
"Expression": "KVM_GENERIC_PRE_FAULT_MEMORY"
}Results: {
"Output": "arch/arm64/kvm/Kconfig=20=menuconfig KVM\n--\narch/arm64/kvm/Kconfig-39-\tselect KVM_GUEST_MEMFD\narch/arm64/kvm/Kconfig:40:\tselect KVM_GENERIC_PRE_FAULT_MEMORY\narch/arm64/kvm/Kconfig-41-\thelp\n--\narch/s390/kvm/Kconfig=19=config KVM\n--\narch/s390/kvm/Kconfig-32-\tselect KVM_MMU_LOCKLESS_AGING\narch/s390/kvm/Kconfig:33:\tselect KVM_GENERIC_PRE_FAULT_MEMORY\narch/s390/kvm/Kconfig-34-\thelp\n--\narch/x86/kvm/Kconfig=20=config KVM_X86\n--\narch/x86/kvm/Kconfig-47-\tselect KVM_GENERIC_HARDWARE_ENABLING\narch/x86/kvm/Kconfig:48:\tselect KVM_GENERIC_PRE_FAULT_MEMORY\narch/x86/kvm/Kconfig-49-\tselect KVM_WERROR if WERROR\n--\ninclude/linux/kvm_host.h=2643=void kvm_arch_gmem_invalidate_range(struct kvm *kvm, struct kvm_gfn_range *range);\n--\ninclude/linux/kvm_host.h-2645-\ninclude/linux/kvm_host.h:2646:#ifdef CONFIG_KVM_GENERIC_PRE_FAULT_MEMORY\ninclude/linux/kvm_host.h-2647-long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,\n--\nvirt/kvm/Kconfig=68=config KVM_GENERIC_DIRTYLOG_READ_PROTECT\n--\nvirt/kvm/Kconfig-70-\nvirt/kvm/Kconfig:71:config KVM_GENERIC_PRE_FAULT_MEMORY\nvirt/kvm/Kconfig-72- bool\n--\nvirt/kvm/kvm_main.c=4332=static int kvm_vcpu_ioctl_get_stats_fd(struct kvm_vcpu *vcpu)\n--\nvirt/kvm/kvm_main.c-4356-\nvirt/kvm/kvm_main.c:4357:#ifdef CONFIG_KVM_GENERIC_PRE_FAULT_MEMORY\nvirt/kvm/kvm_main.c-4358-static int kvm_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,\n--\nvirt/kvm/kvm_main.c=4433=static long kvm_vcpu_ioctl(struct file *filp,\n--\nvirt/kvm/kvm_main.c-4658-\t}\nvirt/kvm/kvm_main.c:4659:#ifdef CONFIG_KVM_GENERIC_PRE_FAULT_MEMORY\nvirt/kvm/kvm_main.c-4660-\tcase KVM_PRE_FAULT_MEMORY: {\n"
} |
| 9/2 | 2026/09/24 22:42 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 27728 output: 38 thoughts: 2490 Thoughts: **Analysis of KVM Pre-Fault Memory Patch for Fuzzing Assessment** Okay, so I'm looking at this patch and it seems to introduce support for `KVM_PRE_FAULT_MEMORY`, but primarily for arm64. The x86 side, in `virt/kvm/kvm_main.c`, just has this `__weak` function `kvm_arch_vcpu_allow_pre_fault_memory` that always returns `true`, and a check using it. I need to figure out if this is worth fuzzing for amd64, which is the target architecture. The diff touches a lot of arm64-specific files and some shared KVM headers and utilities. Initially, I see that x86 already has the `KVM_GENERIC_PRE_FAULT_MEMORY` selected. The patch adds a check using `kvm_arch_vcpu_allow_pre_fault_memory()` in `kvm_vcpu_pre_fault_memory()`. Crucially, on x86, this weak function just returns `true`. Since x86 already has `KVM_PRE_FAULT_MEMORY`, it seems the main point of this patch is the arm64 implementation. But let's not jump to conclusions about NFCI (no functional change in implementation). I have to be very careful. The instructions say that I should only return `WorthFuzzing=false` if *all* modified code falls into certain categories. The *key* part is "Code paths exclusive to architectures other than the target architecture". So, are the changes exclusive to arm64? Yes, most of the *substantive* code changes are. All the `arch/arm64/*` files are arm64-specific and not reachable on x86/amd64. `Documentation/*`, and `tools/testing/selftests/*` are non-executable or user-space. However, `virt/kvm/kvm_main.c` is definitely touched. And that file *is* compiled for x86. When syzkaller fuzzer calls `KVM_PRE_FAULT_MEMORY` on a vcpu, the call goes through `kvm_vcpu_ioctl` -> `kvm_vcpu_pre_fault_memory`. So `virt/kvm/kvm_main.c` is on a reachable execution path and fuzzable. The instruction said that any reachable executable code will return true. Even though the weak function in x86/amd64 will always returns true, because the modified code in `virt/kvm/kvm_main.c` *is* reachable on the target architecture. I have to return `WorthFuzzing=true`. Even though the added check seems trivial, it is *reachable* code that might be part of more complex, and thus fuzzy, control flow in the future. |
| 10/2 | 2026/09/24 22:42 | tool | grepper |
0mArgs:{
"Expression": "kvm_arch_vcpu_pre_fault_memory",
"PathPrefix": "arch/x86"
}Results: {
"Output": "arch/x86/kvm/mmu/mmu.c=5045=static int kvm_tdp_page_prefault(struct kvm_vcpu *vcpu, gpa_t gpa,\n--\narch/x86/kvm/mmu/mmu.c-5088-\narch/x86/kvm/mmu/mmu.c:5089:long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,\narch/x86/kvm/mmu/mmu.c-5090-\t\t\t\t struct kvm_pre_fault_memory *range)\n--\narch/x86/kvm/vmx/tdx.c=1698=static int tdx_sept_map_leaf_spte(struct kvm *kvm, gfn_t gfn, enum pg_level level,\n--\narch/x86/kvm/vmx/tdx.c-1710-\t/*\narch/x86/kvm/vmx/tdx.c:1711:\t * Ensure pre_fault_allowed is read by kvm_arch_vcpu_pre_fault_memory()\narch/x86/kvm/vmx/tdx.c-1712-\t * before kvm_tdx-\u003estate. Userspace must not be allowed to pre-fault\n"
} |
| 11/2 | 2026/09/24 22:42 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 31238 output: 202 thoughts: 16380 Thoughts: **Summary of Thought Process** Alright, let's break this down. We're asked to decide if a patch is "WorthFuzzing" on amd64, given its focus on arm64 and some small common code changes. My initial reaction is to carefully assess the "negative criteria"—when we *shouldn't* fuzz. The prompt's wording is crucial: "ONLY IF ALL modified code falls strictly into one or more of these categories". That's the key. First, I identified the modified code: `virt/kvm/kvm_main.c`, `arch/arm64/*`, `Documentation/`, and `tools/testing/selftests/*`. I know `Documentation/` and `tools/testing/selftests/` are likely out because they are non-kernel or user-space related. The `arch/arm64/*` stuff is *definitely* exclusive to arm64 and thus untouchable when evaluating for amd64, that's straightforward. Then came `virt/kvm/kvm_main.c`. Here's where it got interesting. The patch added a weak function `kvm_arch_vcpu_allow_pre_fault_memory()` which returns `true` as the default. And importantly, it calls this function within `kvm_vcpu_pre_fault_memory()`. My first inclination was that ANY modification of reachable code warranted fuzzing, but then I had to go back to the wording: "all modified code falls strictly into *one or more* of these categories". The crucial question becomes: does the new code path within `virt/kvm/kvm_main.c` (calling the weak function) make the patch worth fuzzing? On amd64, the weak function *always* returns true, so the new check effectively does nothing. It's essentially a no-op on the target architecture. The *significant* code changes, those implementing the pre-fault memory feature, are all *within* `arch/arm64`. The phrasing of the "unreachable execution paths" is key here: it specifies the goal is not to try and interpret the patch authors *intent*. The evaluation is strictly about whether the target can benefit from fuzzing with this patch. While the code *is* reachable (in the sense that it compiles and is executed) calling a function returning true and no different from the original behavior does not add to what can be discovered by fuzzing. And, the critical "Code paths exclusive to architectures other than the target architecture." is key because the *only* functional new code path on pre-faulting is that of arm64 which requires the pre-check. So, while the change in `virt/kvm/kvm_main.c` *looks* like a modification to a core subsystem, the only execution path that causes a change on amd64 is the call to a weak function that does nothing. The real logic and bug surface (if any) are in `arch/arm64/`. Since all functional code is exclusive to the arm64 architecture I must say `WorthFuzzing=false`. FocusSymbols, of course, are empty because nothing would be fuzzable to discover. |
| 12/2 | 2026/09/24 22:43 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch series adds support for KVM_PRE_FAULT_MEMORY on arm64. All substantive implementation changes (stage-2 page table walking, prefault abort handling, ESR parsing) are located in arch/arm64/ and are exclusive to arm64, making them structurally unreachable on the target architecture (amd64). The changes to Documentation/ and tools/testing/selftests/ are non-kernel or user-space changes. The only change to common code in virt/kvm/kvm_main.c introduces a weak fallback hook kvm_arch_vcpu_allow_pre_fault_memory() that unconditionally returns true on amd64, leaving control flow and state management on x86 completely unaltered. Therefore, there are no reachable modified code paths worth fuzzing on amd64.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch series adds support for KVM_PRE_FAULT_MEMORY on arm64. All substantive implementation changes (stage-2 page table walking, prefault abort handling, ESR parsing) are located in arch/arm64/ and are exclusive to arm64, making them structurally unreachable on the target architecture (amd64). The changes to Documentation/ and tools/testing/selftests/ are non-kernel or user-space changes. The only change to common code in virt/kvm/kvm_main.c introduces a weak fallback hook kvm_arch_vcpu_allow_pre_fault_memory() that unconditionally returns true on amd64, leaving control flow and state management on x86 completely unaltered. Therefore, there are no reachable modified code paths worth fuzzing on amd64.",
"WorthFuzzing": false
} |
| 13/1 | 2026/09/24 22:43 | action | If |
0mArgs:{
"WorthFuzzing": false
}Results: null |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|