Replace the temporary -ENOEXEC returns of the arm64-on-s390 page fault handling with exception injection into the guest, mirroring arm64's kvm_handle_guest_abort(). Translation faults beyond the physical address range are injected as an address size fault, while faults falling between the VM's IPA size and that range are injected as a synchronous external abort. Instruction aborts and stage-1 walk faults on host addresses that back no guest page are injected as synchronous external aborts as well, and a PGM-addressing from the fault-in path is injected as an address size fault, the closest arm64 equivalent. Injected faults return 1 so the vCPU re-enters the guest with the pending exception instead of exiting to userspace. Co-developed-by: Andreas Grapentin Signed-off-by: Andreas Grapentin Signed-off-by: Steffen Eiden --- arch/s390/include/asm/kvm_host_arm64.h | 1 + arch/s390/kvm/arm64/arm.c | 6 +++ arch/s390/kvm/arm64/mmu.c | 74 +++++++++++++++++++++++----------- arch/s390/kvm/arm64/sys_regs.c | 4 ++ 4 files changed, 62 insertions(+), 23 deletions(-) diff --git a/arch/s390/include/asm/kvm_host_arm64.h b/arch/s390/include/asm/kvm_host_arm64.h index 79549434a1d6..20f51cf9ce37 100644 --- a/arch/s390/include/asm/kvm_host_arm64.h +++ b/arch/s390/include/asm/kvm_host_arm64.h @@ -426,5 +426,6 @@ static inline bool vgic_host_has_gicv5(void) } void kvm_adjust_pc(struct kvm_vcpu *vcpu); +size_t kvm_parange_to_address_sanitized(u32 id_parange); #endif /* ASM_KVM_HOST_ARM64_H */ diff --git a/arch/s390/kvm/arm64/arm.c b/arch/s390/kvm/arm64/arm.c index 91e9f4a75ce7..b5a0f8c1e9a4 100644 --- a/arch/s390/kvm/arm64/arm.c +++ b/arch/s390/kvm/arm64/arm.c @@ -63,9 +63,15 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext) static u64 kvm_max_guest_address(void) { + u64 mmfr0 = read_sanitised_ftr_reg(SYS_ID_AA64MMFR0_EL1); + u64 id_pa_addr; u64 max_addr; + id_pa_addr = kvm_parange_to_address_sanitized( + SYS_FIELD_GET(ID_AA64MMFR0_EL1, PARANGE, mmfr0)); + max_addr = min_t(u64, TASK_SIZE_MAX, sclp.hamax); + max_addr = min_t(u64, max_addr, id_pa_addr); max_addr = max_t(u64, max_addr, SZ_1G - 1); return ALIGN_DOWN(max_addr + 1, SZ_1G) - 1; } diff --git a/arch/s390/kvm/arm64/mmu.c b/arch/s390/kvm/arm64/mmu.c index 31e0d036896f..7a6f6ba326c1 100644 --- a/arch/s390/kvm/arm64/mmu.c +++ b/arch/s390/kvm/arm64/mmu.c @@ -16,7 +16,8 @@ static inline bool kvm_s390_cur_gmap_fault_is_write(struct kvm_vcpu *vcpu) /* * user_mem_abort() - handle a dat fault for the gmap of a vcpu * - * Return: 0 on success, < 0 in case of error. + * Return: 0 if the fault was resolved, 1 if an exception was injected into + * the guest; the caller resumes the vCPU in both cases. < 0 in case of error. * Context: The mm lock must not be held before calling. May sleep. */ static int user_mem_abort(struct kvm_vcpu *vcpu, gpa_t fault_ipa, @@ -32,12 +33,14 @@ static int user_mem_abort(struct kvm_vcpu *vcpu, gpa_t fault_ipa, ret = kvm_s390_faultin_gfn(vcpu, NULL, &f); if (ret <= 0) return ret; - if (ret == PGM_ADDRESSING) + if (ret == PGM_ADDRESSING) { /* - * Without the relevant sysregs we cannot do anything for now. - * Go back to userspace with an error. TODO sysreg handling + * There is no page with the requested address. Inject size fault + * which is the closest arm match to PGM-addressing */ - return -ENOEXEC; + kvm_inject_size_fault(vcpu); + return 1; + } KVM_BUG_ON(ret, vcpu->kvm); return -EINVAL; } @@ -70,6 +73,35 @@ static int kvm_handle_pic(struct kvm_vcpu *vcpu, bool *translation) return 0; } +size_t kvm_parange_to_address_sanitized(u32 id_parange) +{ + static u32 ranges_map[8] = { + [ID_AA64MMFR0_EL1_PARANGE_32] = 32, + [ID_AA64MMFR0_EL1_PARANGE_36] = 36, + [ID_AA64MMFR0_EL1_PARANGE_40] = 40, + [ID_AA64MMFR0_EL1_PARANGE_42] = 42, + [ID_AA64MMFR0_EL1_PARANGE_44] = 44, + [ID_AA64MMFR0_EL1_PARANGE_48] = 48, + [ID_AA64MMFR0_EL1_PARANGE_52] = 52, + [ID_AA64MMFR0_EL1_PARANGE_56] = 56, + }; + u32 parange; + + /* + * Future values must be higher than we know already. + * See ARM DDI 0487C.a. Return a safe limit. + */ + parange = ranges_map[min(id_parange, ID_AA64MMFR0_EL1_PARANGE_56)]; + return BIT_ULL(parange); +} + +static size_t kvm_get_pa_address(struct kvm *kvm) +{ + u32 id_parange = get_idreg_field_enum(kvm, ID_AA64MMFR0_EL1, PARANGE); + + return kvm_parange_to_address_sanitized(id_parange); +} + int kvm_handle_guest_abort(struct kvm_vcpu *vcpu) { struct kvm_memory_slot *memslot; @@ -98,16 +130,17 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu) } if (translation) { - /* - * For both cases: - * Without the relevant sysregs we cannot do anything for now. - * Go back to userspace with an error. TODO sysreg handling - */ - if (fault_ipa >= BIT_ULL(get_kvm_ipa_limit())) - return -ENOEXEC; + /* Beyond the PA range programmed for the guest */ + if (fault_ipa >= kvm_get_pa_address(vcpu->kvm)) { + kvm_inject_size_fault(vcpu); + return 1; + } - if (fault_ipa >= vcpu->kvm->arch.guest_phys_size) - return -ENOEXEC; + /* Falls between the IPA range and the PA range? */ + if (fault_ipa >= vcpu->kvm->arch.guest_phys_size) { + 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), @@ -121,23 +154,18 @@ int kvm_handle_guest_abort(struct kvm_vcpu *vcpu) hva = gfn_to_hva_memslot_prot(memslot, gfn, &writable); write_fault = kvm_s390_cur_gmap_fault_is_write(vcpu); if (kvm_is_error_hva(hva) || (write_fault && !writable)) { - ret = -ENOEXEC; /* * The guest has put either its instructions or its page-tables * somewhere it shouldn't have. Userspace won't be able to do * anything about this (there's no syndrome for a start). - * - * Without the relevant sysregs we cannot do anything for now. - * Go back to userspace with an error. TODO sysreg handling */ - if (is_iabt) + if (is_iabt) { + ret = kvm_inject_sea_iabt(vcpu, kvm_vcpu_get_hfar(vcpu)); goto out_unlock; + } if (kvm_vcpu_abt_iss1tw(vcpu)) { - /* - * Without the relevant sysregs we cannot do anything for now. - * Go back to userspace with an error. TODO sysreg handling - */ + ret = kvm_inject_sea_dabt(vcpu, kvm_vcpu_get_hfar(vcpu)); goto out_unlock; } diff --git a/arch/s390/kvm/arm64/sys_regs.c b/arch/s390/kvm/arm64/sys_regs.c index 417a05ce9015..951a02d89fc5 100644 --- a/arch/s390/kvm/arm64/sys_regs.c +++ b/arch/s390/kvm/arm64/sys_regs.c @@ -317,6 +317,10 @@ static int arm64_check_features(struct kvm_vcpu *vcpu, if (SYS_FIELD_GET(ID_AA64MMFR0_EL1, PARANGE, val) > SYS_FIELD_GET(ID_AA64MMFR0_EL1, PARANGE, host_val)) return -E2BIG; + if (kvm_parange_to_address_sanitized( + SYS_FIELD_GET(ID_AA64MMFR0_EL1, PARANGE, val)) < + vcpu->kvm->arch.guest_phys_size) + return -E2BIG; break; case SYS_CTR_EL0: /* forbid upgrading from VIPT to PIPT */ -- 2.56.0