KVM: s390: arm64: Implement required functions

Implement the mostly trivial functions that the shared arm64 (kvm)
code & headers oblige s390 to implement.

MAINTAINERS: Add arch/s390/include/arm64/* to KVM/s390

Implement a very basic smccc handler that (non-compliantly) is just able
to stop a vcpu.

Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Christian Borntraeger <borntraeger@linux.ibm.com>
This commit is contained in:
Steffen Eiden 2026-04-02 06:00:00 +02:00 committed by Christian Borntraeger
parent 761404d7ac
commit 3fb328f76b
7 changed files with 314 additions and 0 deletions

View File

@ -14372,6 +14372,7 @@ L: kvm@vger.kernel.org
S: Supported
T: git git://git.kernel.org/pub/scm/linux/kernel/git/kvms390/linux.git
F: Documentation/virt/kvm/s390*
F: arch/s390/include/arm64/*
F: arch/s390/include/asm/gmap_helpers.h
F: arch/s390/include/asm/kvm*
F: arch/s390/include/uapi/asm/kvm*

View File

@ -0,0 +1,130 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __S390_ARM64_KVM_EMULATE_H__
#define __S390_ARM64_KVM_EMULATE_H__
#include <asm/fault.h>
#include <linux/kvm_host.h>
#include <arm64/kvm_nested.h>
#include <arm64/ptrace.h>
#include <arm64/kvm_arm.h>
#include <arm64/sysreg.h>
static __always_inline unsigned long *vcpu_pc(const struct kvm_vcpu *vcpu)
{
return (unsigned long *)&vcpu->arch.sae_block.pc;
}
static __always_inline unsigned long *vcpu_cpsr(const struct kvm_vcpu *vcpu)
{
return (unsigned long *)&vcpu->arch.sae_block.pstate;
}
static __always_inline unsigned long *vcpu_sp_el0(const struct kvm_vcpu *vcpu)
{
return (unsigned long *)&vcpu->arch.sae_block.sp_el0;
}
static __always_inline u64 *vcpu_sp_el1(struct kvm_vcpu *vcpu)
{
return &vcpu->arch.sae_block.sp_el1;
}
static __always_inline __vector128 *vcpu_vreg(struct kvm_vcpu *vcpu, int off)
{
return &vcpu->arch.ctxt.vregs[off];
}
static __always_inline u32 *vcpu_fpsr(struct kvm_vcpu *vcpu)
{
return &vcpu->arch.sae_block.fpsr;
}
static __always_inline u32 *vcpu_fpcr(struct kvm_vcpu *vcpu)
{
return &vcpu->arch.sae_block.fpcr;
}
static __always_inline bool vcpu_mode_is_32bit(const struct kvm_vcpu *vcpu)
{
return false;
}
static __always_inline u64 kvm_vcpu_get_esr(const struct kvm_vcpu *vcpu)
{
return vcpu->arch.sae_block.hai.esr_el2;
}
static inline unsigned long *vcpu_hcr(struct kvm_vcpu *vcpu)
{
return (unsigned long *)&vcpu->arch.hcr_el2;
}
static __always_inline unsigned long kvm_vcpu_get_hfar(const struct kvm_vcpu *vcpu)
{
return vcpu->arch.sae_block.hai.far_el2;
}
static __always_inline phys_addr_t kvm_vcpu_get_fault_ipa(const struct kvm_vcpu *vcpu)
{
return gfn_to_gpa(vcpu->arch.sae_block.hai.teid.addr);
}
static inline u16 kvm_vcpu_fault_pic(const struct kvm_vcpu *vcpu)
{
return vcpu->arch.sae_block.hai.pic & PGM_INT_CODE_MASK;
}
static __always_inline
bool kvm_vcpu_trap_is_permission_fault(const struct kvm_vcpu *vcpu)
{
return kvm_vcpu_fault_pic(vcpu) == PGM_PROTECTION;
}
static __always_inline bool kvm_condition_valid(const struct kvm_vcpu *vcpu)
{
return true;
}
static __always_inline bool vcpu_el1_is_32bit(struct kvm_vcpu *vcpu)
{
return false;
}
static inline bool kvm_vcpu_is_be(struct kvm_vcpu *vcpu)
{
return false;
}
static inline int kvm_vcpu_abt_gltl(struct kvm_vcpu *vcpu)
{
return vcpu->arch.sae_block.hai.gltl;
}
static inline bool vcpu_mode_priv(const struct kvm_vcpu *vcpu)
{
u32 mode = *vcpu_cpsr(vcpu) & PSR_MODE_MASK;
return mode != PSR_MODE_EL0t;
}
static inline void kvm_skip_instr(struct kvm_vcpu *vcpu)
{
*vcpu_pc(vcpu) += 4;
*vcpu_cpsr(vcpu) &= ~PSR_BTYPE_MASK;
/* advance the singlestep state machine */
*vcpu_cpsr(vcpu) &= ~SPSR_ELx_SS;
}
static inline void kvm_reset_fpsimd(struct kvm_vcpu *vcpu)
{
memset(vcpu->arch.ctxt.vregs, 0, sizeof(vcpu->arch.ctxt.vregs));
vcpu->arch.sae_block.fpsr = 0;
vcpu->arch.sae_block.fpcr = 0;
}
#include <arm64/kvm_emulate-gen.h>
#endif /* __S390_ARM64_KVM_EMULATE_H__ */

View File

@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ASM_KVM_NESTED_H
#define ASM_KVM_NESTED_H
static inline bool vcpu_has_nv(const struct kvm_vcpu *vcpu)
{
return false;
}
#endif /* ASM_KVM_NESTED_H */

View File

@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __S390_ARM64_PTRACE_H__
#define __S390_ARM64_PTRACE_H__
#include <arm64/sysreg.h>
#include <uapi/arm64/ptrace.h>
#include <arm64/ptrace-gen.h>
#endif /* __S390_ARM64_PTRACE_H__ */

View File

@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __S390_ARM64_SYSREG_H__
#define __S390_ARM64_SYSREG_H__
#include <linux/bits.h>
#include <linux/stringify.h>
#include <linux/kasan-tags.h>
#include <linux/kconfig.h>
#include <arm64/sysreg-gen.h>
#endif /* __S390_ARM64_SYSREG_H__ */

View File

@ -0,0 +1,127 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kvm_host.h>
#include <arm64/esr.h>
#include <arm64/kvm_emulate.h>
typedef int (*exit_handle_fn)(struct kvm_vcpu *);
#define PSCI_0_2_FN_SYSTEM_OFF 0x84000008
#define PSCI_RET_NOT_SUPPORTED -1
#define PSCI_RET_INTERNAL_FAILURE -6
/*
* Temporary smc/hvc handler. Non-compliant implementation (features missing).
* Implements only system off so that test programs are able to end their execution.
* Multi-vCPU power state management and KVM_REQ_SLEEP handling are deferred
* to future SMP series.
*/
static int kvm_smccc_call_handler(struct kvm_vcpu *vcpu)
{
u32 func_id = vcpu_get_reg(vcpu, 0);
u64 val = PSCI_RET_NOT_SUPPORTED;
int ret = 1;
if (func_id == PSCI_0_2_FN_SYSTEM_OFF) {
scoped_guard(spinlock, &vcpu->arch.mp_state_lock) {
WRITE_ONCE(vcpu->arch.mp_state.mp_state, KVM_MP_STATE_STOPPED);
}
memset(&vcpu->run->system_event, 0,
sizeof(vcpu->run->system_event));
vcpu->run->system_event.type = KVM_SYSTEM_EVENT_SHUTDOWN;
vcpu->run->system_event.ndata = 1;
vcpu->run->system_event.data[0] = 0;
vcpu->run->exit_reason = KVM_EXIT_SYSTEM_EVENT;
val = PSCI_RET_INTERNAL_FAILURE;
ret = 0;
}
vcpu_set_reg(vcpu, 0, val);
return ret;
}
static int handle_hvc(struct kvm_vcpu *vcpu)
{
vcpu->stat.hvc_exit_stat++;
return kvm_smccc_call_handler(vcpu);
}
static int kvm_handle_unknown_ec(struct kvm_vcpu *vcpu)
{
u64 esr = kvm_vcpu_get_esr(vcpu);
kvm_pr_unimpl("Unknown exception class: esr: %#016llx -- %s\n",
esr, esr_get_class_string(esr));
kvm_inject_undefined(vcpu);
return 1;
}
static exit_handle_fn arm_exit_handlers[] = {
[0 ... ESR_ELx_EC_MAX] = kvm_handle_unknown_ec,
[ESR_ELx_EC_HVC64] = handle_hvc,
};
static exit_handle_fn kvm_get_exit_handler(struct kvm_vcpu *vcpu)
{
u64 esr = kvm_vcpu_get_esr(vcpu);
u8 esr_ec = ESR_ELx_EC(esr);
return arm_exit_handlers[esr_ec];
}
/*
* We may be single-stepping an emulated instruction. If the emulation
* has been completed in the kernel, we can return to userspace with a
* KVM_EXIT_DEBUG, otherwise userspace needs to complete its
* emulation first.
*/
static int handle_trap_exceptions(struct kvm_vcpu *vcpu)
{
exit_handle_fn exit_handler;
exit_handler = kvm_get_exit_handler(vcpu);
return exit_handler(vcpu);
}
/* manually copied from arch/arm64/kernel/traps.c */
static const char * const esr_class_str[] = {
[0 ... ESR_ELx_EC_MAX] = "UNRECOGNIZED EC",
[ESR_ELx_EC_UNKNOWN] = "Unknown/Uncategorized",
[ESR_ELx_EC_WFx] = "WFI/WFE",
[ESR_ELx_EC_FP_ASIMD] = "ASIMD",
[ESR_ELx_EC_PAC] = "PAC",
[ESR_ELx_EC_BTI] = "BTI",
[ESR_ELx_EC_ILL] = "PSTATE.IL",
[ESR_ELx_EC_SVC64] = "SVC (AArch64)",
[ESR_ELx_EC_HVC64] = "HVC (AArch64)",
[ESR_ELx_EC_SMC64] = "SMC (AArch64)",
[ESR_ELx_EC_SYS64] = "MSR/MRS (AArch64)",
[ESR_ELx_EC_SVE] = "SVE",
[ESR_ELx_EC_ERET] = "ERET/ERETAA/ERETAB",
[ESR_ELx_EC_FPAC] = "FPAC",
[ESR_ELx_EC_SME] = "SME",
[ESR_ELx_EC_IMP_DEF] = "EL3 IMP DEF",
[ESR_ELx_EC_IABT_LOW] = "IABT (lower EL)",
[ESR_ELx_EC_IABT_CUR] = "IABT (current EL)",
[ESR_ELx_EC_PC_ALIGN] = "PC Alignment",
[ESR_ELx_EC_DABT_LOW] = "DABT (lower EL)",
[ESR_ELx_EC_DABT_CUR] = "DABT (current EL)",
[ESR_ELx_EC_SP_ALIGN] = "SP Alignment",
[ESR_ELx_EC_MOPS] = "MOPS",
[ESR_ELx_EC_FP_EXC64] = "FP (AArch64)",
[ESR_ELx_EC_GCS] = "Guarded Control Stack",
[ESR_ELx_EC_SERROR] = "SError",
[ESR_ELx_EC_BREAKPT_LOW] = "Breakpoint (lower EL)",
[ESR_ELx_EC_BREAKPT_CUR] = "Breakpoint (current EL)",
[ESR_ELx_EC_SOFTSTP_LOW] = "Software Step (lower EL)",
[ESR_ELx_EC_SOFTSTP_CUR] = "Software Step (current EL)",
[ESR_ELx_EC_WATCHPT_LOW] = "Watchpoint (lower EL)",
[ESR_ELx_EC_WATCHPT_CUR] = "Watchpoint (current EL)",
[ESR_ELx_EC_BRK64] = "BRK (AArch64)",
};
const char *esr_get_class_string(unsigned long esr)
{
return esr_class_str[ESR_ELx_EC(esr)];
}

View File

@ -0,0 +1,21 @@
// SPDX-License-Identifier: GPL-2.0
#include <arm64/kvm_emulate.h>
/**
* kvm_inject_undefined - inject an undefined instruction into the guest
* @vcpu: The vCPU in which to inject the exception
*
* It is assumed that this code is called from the VCPU thread and that the
* VCPU therefore is not currently executing guest code.
*/
void kvm_inject_undefined(struct kvm_vcpu *vcpu)
{
/* Stub until s390 supports arm64 sysregs TODO sysregs*/
}
int kvm_inject_sea(struct kvm_vcpu *vcpu, bool iabt, u64 addr)
{
/* Stub until s390 supports arm64 sysregs TODO sysregs*/
return 1;
}