KVM: s390: Changes for 7.4

- rework page allocation for secure guest variable storage
 - selftests
 - documentation
 - replace get_zeroed_page with kzalloc
 - fix floating interrupt inject for corner cases
 - initial arm64 on s390 support
 
 This also contains the patch
 "arm64: Use proper include variant"
 as a build fix. It is also part of the arm tree.
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEE+SKTgaM0CPnbq/vKEXu8gLWmHHwFAmrQw/gACgkQEXu8gLWm
 HHwfkhAAgTzwSx9QbBPFMyCsHbKZNgK5x4vieL9E04NV8XbvcEBAXE3xgbVadMNK
 sbqQ6kjuUrJIa9XG4BCjZwfAKqwGh9DGVGAvIbOjtIP17mt2dqVPZeGrimbromaa
 K6h7uKHTxm5SyQ6Ld/OpzTOJAf/S2uKwuoKcHh05NNNcjC1CFnV0PV8PIHIPVGsw
 PLUlZ/MfsPjUH6RA1CjstHRRSAv6DajIriCqS6mGCj03VJIph1uGgdEhbA0iiUyy
 NPHnpikLct9BsyqNL3IVCh0KwYeFzBTzcLOfbZRq4zu3wxbPU/F1Ee+G3AnjNMzW
 iz6V+xZcmq2c9pN45vIAzsU68UJ+3CWSyh41ivj9rZngoHhTCPy6iywIxwioh6ki
 5BCunmZ4wOjXmoirlhaW6bytsFVKeHNxI2uz1FkRAy+AkzFwTHWE401yPZYJI7dd
 F+7d4toXkVC8sI35diRACcHy2SLtwRjPYxC7MF1dU3cCvpwPNkXJdJc4WYHuSROB
 syk+JmG3birkvJgcJE+yxyU4ZEzEaQOPX68rVKkWrtYIr7PGReJWKUADhinZc1dn
 g9zOY3x1E5s6eL3dM/mGbKJF3HYcTNWmaqSTVhniGWD4NKcgpT7s0hEiy39zBjKB
 mNrrYcUT1HJaa034u15BhgNH5z1IKClzdwjSYK6r/junsAuIZhc=
 =e48p
 -----END PGP SIGNATURE-----

Merge tag 'kvm-s390-next-7.4-2' of git://git.kernel.org/pub/scm/linux/kernel/git/kvms390/linux into HEAD

KVM: s390: Changes for 7.4

- rework page allocation for secure guest variable storage
- selftests
- documentation
- replace get_zeroed_page with kzalloc
- fix floating interrupt inject for corner cases
- initial arm64 on s390 support

This also contains the patch
"arm64: Use proper include variant"
as a build fix. It is also part of the arm tree.
This commit is contained in:
Paolo Bonzini 2026-10-19 15:11:58 +02:00
commit 814607cd40
57 changed files with 2883 additions and 92 deletions

View File

@ -280,6 +280,24 @@ Allows user space to disable dea key wrapping, clearing the wrapping key.
:Parameters: none
:Returns: 0
4.5. ATTRIBUTE: KVM_S390_VM_CRYPTO_ENABLE_APIE (w/o)
-------------------------------------------------------
Allows user space to enable AP interpretive execution.
:Parameters: none
:Returns: 0 for success
-EOPNOTSUPP if AP instructions not available for guest
4.6. ATTRIBUTE: KVM_S390_VM_CRYPTO_DISABLE_APIE (w/o)
-------------------------------------------------------
Allows user space to disable AP interpretive execution.
:Parameters: none
:Returns: 0 for success
-EOPNOTSUPP if AP instructions not available for guest
5. GROUP: KVM_S390_VM_MIGRATION
===============================

View File

@ -14377,6 +14377,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*
@ -14389,6 +14390,22 @@ F: tools/testing/selftests/drivers/s390x/uvdevice/
F: tools/testing/selftests/kvm/*/s390/
F: tools/testing/selftests/kvm/s390/
KERNEL VIRTUAL MACHINE for arm-on-s390 (KVM/s390)
R: Steffen Eiden <seiden@linux.ibm.com>
R: Andreas Grapentin <gra@linux.ibm.com>
R: Marc Zyngier <maz@kernel.org>
L: kvm@vger.kernel.org
L: linux-s390@vger.kernel.org
S: Supported
T: git git://git.kernel.org/pub/scm/linux/kernel/git/kvms390/linux.git
F: arch/s390/include/arm64/
F: arch/s390/include/asm/aef.h
F: arch/s390/include/asm/kvm_host_arm64*
F: arch/s390/include/asm/sae*
F: arch/s390/kernel/aef.c
F: arch/s390/kvm/arm64/
F: arch/s390/tools/*arm64*
KERNEL VIRTUAL MACHINE FOR X86 (KVM/x86)
M: Sean Christopherson <seanjc@google.com>
M: Paolo Bonzini <pbonzini@redhat.com>

View File

@ -179,7 +179,7 @@
* come from here. The header relies on the definition of sys_reg()
* earlier in this file.
*/
#include "asm/sysreg-defs.h"
#include <asm/sysreg-defs.h>
/*
* System registers, organised loosely by encoding but grouped together

View File

@ -181,7 +181,7 @@ config S390
select GENERIC_ENTRY
select GENERIC_GETTIMEOFDAY
select GENERIC_SMP_IDLE_THREAD
select GENERIC_IOREMAP if PCI
select GENERIC_IOREMAP
select GLOB
select HAVE_ALIGNED_STRUCT_PAGE
select HAVE_ARCH_AUDITSYSCALL
@ -768,9 +768,6 @@ config PCI_NR_FUNCTIONS
endif # PCI
config HAS_IOMEM
def_bool PCI
config CHSC_SCH
def_tristate m
prompt "Support for CHSC subchannels"

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

@ -6,6 +6,7 @@
#include <asm/bug.h>
#include <asm/fpu.h>
#include <asm/nospec-branch.h>
#include <asm/sae.h>
#include <asm-generic/asm-prototypes.h>
#endif /* _ASM_S390_PROTOTYPES_H */

View File

@ -123,6 +123,7 @@ enum {
HWCAP_NR_NNPA = 20,
HWCAP_NR_PCI_MIO = 21,
HWCAP_NR_SIE = 22,
HWCAP_NR_SAE = 23,
HWCAP_NR_MAX
};
@ -150,6 +151,7 @@ enum {
#define HWCAP_NNPA BIT(HWCAP_NR_NNPA)
#define HWCAP_PCI_MIO BIT(HWCAP_NR_PCI_MIO)
#define HWCAP_SIE BIT(HWCAP_NR_SIE)
#define HWCAP_SAE BIT(HWCAP_NR_SAE)
/*
* These are used to set parameters in the core dumps.

View File

@ -27,14 +27,8 @@ void unxlate_dev_mem_ptr(phys_addr_t phys, void *addr);
/*
* I/O memory mapping functions.
*/
#define ioremap_prot ioremap_prot
#define iounmap iounmap
#define _PAGE_IOREMAP pgprot_val(PAGE_KERNEL)
#define ioremap_wc(addr, size) \
ioremap_prot((addr), (size), pgprot_writecombine(PAGE_KERNEL))
static inline void __iomem *ioport_map(unsigned long port, unsigned int nr)
{
return NULL;
@ -46,6 +40,12 @@ static inline void ioport_unmap(void __iomem *p)
#ifdef CONFIG_PCI
#define ioremap_prot ioremap_prot
#define iounmap iounmap
#define ioremap_wc(addr, size) \
ioremap_prot((addr), (size), pgprot_writecombine(PAGE_KERNEL))
/*
* s390 needs a private implementation of pci_iomap since ioremap with its
* offset parameter isn't sufficient. That's because BAR spaces are not

View File

@ -0,0 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifdef KVM_S390_ARM64
#include <uapi/arm64/kvm.h>
#else
#include <uapi/asm/kvm.h>
#endif

View File

@ -3,7 +3,11 @@
#ifndef ASM_KVM_HOST_H
#define ASM_KVM_HOST_H
#ifdef KVM_S390_ARM64
#include <asm/kvm_host_arm64.h>
#else
#include <asm/kvm_host_s390.h>
#endif
#define PGM_OPERATION 0x01
#define PGM_PRIVILEGED_OP 0x02

View File

@ -0,0 +1,209 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ASM_KVM_HOST_ARM64_H
#define ASM_KVM_HOST_ARM64_H
#include <linux/bug.h>
#include <asm/kvm_host_types.h>
#include <asm/debug.h>
#define vcpu_gp_regs(v) ((v)->arch.sae_block.gpr)
#include <arm64/kvm_host.h>
#include <arm64/ptrace.h>
#include <asm/sae.h>
#define KVM_HAVE_MMU_RWLOCK
#define KVM_MAX_VCPUS 1
#define __KVM_HAVE_VCPU_RUN_PID_CHANGE
#define KVM_S390_ARM64_IMPL_FEATURES 0
#define KVM_HALT_POLL_NS_DEFAULT 50000
#define KVM_S390_MANAGES_S390_GUEST 0
/* Minimal (=no) vgic definitions */
#define KVM_IRQCHIP_NUM_PINS 1
#define irqchip_in_kernel(_k) false
#define __ctxt_sys_reg(ctx, reg) NULL
struct kvm_cpu_context {
/*
* These are just for 32 bit, which we don't have, making them RES0.
* They are exposed to user space.
*/
u64 spsr_abt;
u64 spsr_und;
u64 spsr_irq;
u64 spsr_fiq;
__vector128 __aligned(16) vregs[32];
};
struct kvm_vcpu_arch {
struct kvm_sae_block sae_block;
struct kvm_sae_save_area save_area;
struct kvm_cpu_context ctxt;
u32 host_acrs[NUM_ACRS];
/* Hypervisor Configuration Register */
u64 hcr_el2;
/* Configuration flags, set once and for all before the vcpu can run */
u8 cflags;
/* Input flags to the hypervisor code, potentially cleared after use */
u8 iflags;
/* State flags for kernel bookkeeping, unused by the hypervisor code */
u8 sflags;
/*
* Don't run the guest (internal implementation need).
*
* Contrary to the flags above, this is set/cleared outside of
* a vcpu context, and thus cannot be mixed with the flags
* themselves (or the flag accesses need to be made atomic).
*/
bool pause;
/* vcpu power state */
struct kvm_mp_state mp_state;
/* lock for mp_state & reset_state.reset */
spinlock_t mp_state_lock;
/* vcpu reset state */
struct vcpu_reset_state reset_state;
struct kvm_s390_mmu_cache *mc;
void *debugfs_state_data;
};
struct kvm_vcpu_stat {
struct kvm_vcpu_stat_generic generic;
/* ARM64 stats */
u64 hvc_exit_stat;
u64 wfe_exit_stat;
u64 wfi_exit_stat;
u64 mmio_exit_user;
u64 mmio_exit_kernel;
u64 signal_exits;
u64 exits;
/* GMAP stats */
u64 pfault_sync;
};
#define kvm_has_mte(_kvm) false
#define vcpu_has_sve(_vcpu) false
#define vcpu_has_ptrauth(_vcpu) false
struct kvm_arch_memory_slot {
};
struct kvm_arch {
struct gmap *gmap;
u64 guest_phys_size;
/* VM-wide vCPU feature set */
unsigned long flags;
/* Protects VM-scoped configuration data */
struct mutex config_lock;
debug_info_t *dbf;
DECLARE_BITMAP(vcpu_features, KVM_VCPU_MAX_FEATURES);
unsigned long mem_limit;
};
static inline bool __vcpu_has_feature(const struct kvm_arch *ka, int feature)
{
return test_bit(feature, ka->vcpu_features);
}
struct kvm_vm_stat {
struct kvm_vm_stat_generic generic;
};
static inline bool system_has_full_ptr_auth(void)
{
return true;
}
#define kvm_vm_is_protected(_kvm) false
#define vcpu_is_protected(_vcpu) false
#define vcpu_is_loaded(_vcpu) ((_vcpu)->cpu != -1)
#define KVM_HVA_ERR_BAD (-1UL)
#define KVM_HVA_ERR_RO_BAD (-2UL)
static inline bool kvm_is_error_hva(unsigned long addr)
{
return IS_ERR_VALUE(addr);
}
u32 get_kvm_ipa_limit(void);
/* unused, but required functions */
static inline void kvm_arch_free_memslot(struct kvm *kvm, struct kvm_memory_slot *slot) {}
static inline void kvm_arch_memslots_updated(struct kvm *kvm, u64 gen) {}
static inline void kvm_arch_flush_shadow_all(struct kvm *kvm) {}
static inline void kvm_arch_flush_shadow_memslot(struct kvm *kvm, struct kvm_memory_slot *slot) {}
static inline void kvm_arch_vcpu_blocking(struct kvm_vcpu *vcpu) {}
static inline void kvm_arch_vcpu_unblocking(struct kvm_vcpu *vcpu) {}
static inline void kvm_arch_sync_events(struct kvm *kvm) {}
static inline void kvm_arch_sched_in(struct kvm_vcpu *vcpu, int cpu) {}
static inline void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,
struct kvm_memory_slot *slot,
gfn_t gfn_offset,
unsigned long mask) {}
int kvm_handle_guest_abort(struct kvm_vcpu *vcpu);
void kvm_reset_vcpu(struct kvm_vcpu *vcpu);
/* arm64 guests do not use async-pf. Defined because Kbuild requires it as s390 kvm turns it on. */
#define ASYNC_PF_PER_VCPU 0
struct kvm_arch_async_pf {
unsigned long pfault_token;
};
#define __unsupp_async_call(fn) WARN_ONCE(true, "async not supported on kvm-arm64 %s", fn)
static inline bool kvm_arch_can_dequeue_async_page_present(struct kvm_vcpu *vcpu)
{
__unsupp_async_call(__func__);
return false;
};
static inline void kvm_arch_async_page_ready(struct kvm_vcpu *vcpu,
struct kvm_async_pf *work)
{
__unsupp_async_call(__func__);
};
static inline bool kvm_arch_async_page_not_present(struct kvm_vcpu *vcpu,
struct kvm_async_pf *work)
{
__unsupp_async_call(__func__);
return false;
};
static inline void kvm_arch_async_page_present(struct kvm_vcpu *vcpu,
struct kvm_async_pf *work)
{
__unsupp_async_call(__func__);
};
static inline void kvm_arch_async_page_present_queued(struct kvm_vcpu *vcpu)
{
__unsupp_async_call(__func__);
};
#define kvm_supports_32bit_el0() false
#endif /* ASM_KVM_HOST_ARM64_H */

View File

@ -0,0 +1,124 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ASM_KVM_HOST_ARM64_TYPES_H
#define ASM_KVM_HOST_ARM64_TYPES_H
#include <linux/types.h>
#include <linux/kvm_types.h>
#include <linux/compiler_attributes.h>
#include <asm/page.h>
#include <asm/fault.h>
struct kvm_sae_block {
u64 _0000[16]; /* 0x0000 */
#define SAE_ICPTR_SPURIOUS 0x00
#define SAE_ICPTR_VALIDITY 0x01
#define SAE_ICPTR_HOST_ACCESS_EXCEPTION 0x02
#define SAE_ICPTR_SYNCHRONOUS_EXCEPTION 0x03
#define SAE_ICPTR_TIMER 0x04
#define SAE_ICPTR_PE_INTERCOMM 0x05
#define SAE_ICPTR_GUEST_ADDRESS_SIZE 0x06
#define SAE_ICPTR_STOP 0x07
#define SAE_ICPTR_MIO_ADDRESS 0x08
#define SAE_ICPTR_PMU 0x09
#define SAE_ICPTR_MAINTENANCE 0x0a
u8 icptr; /* 0x0080 */
u8 _0081[7]; /* 0x0081 */
u64 scad; /* 0x0088 */
u64 _0090[16]; /* 0x0090 */
u32 cntp_ctl; /* 0x0110 */
u32 cntv_ctl; /* 0x0114 */
u8 irq_ctl; /* 0x0118 */
u8 _0119[7]; /* 0x0119 */
struct {
u64 ich_hcr_el2; /* 0x0120 */
u64 ich_vmcr_el2; /* 0x0128 */
u64 ich_ap0r0_el2; /* 0x0130 */
u64 ich_ap1r0_el2; /* 0x0138 */
u64 _0140[2]; /* 0x0140 */
u64 ich_lrn_el2[4]; /* 0x0150 */
u64 _0170[4]; /* 0x0170 */
} ic_regs;
u64 _0190[12]; /* 0x0190 */
u64 contextidr_el1; /* 0x01f0 */
u32 wip; /* 0x01f8 */
u32 _01fc; /* 0x01fc */
#define SAE_SD_FORMAT_0 0x00
u8 sdf; /* 0x0200 */
u8 _0201[7]; /* 0x0201 */
u64 mso; /* 0x0208 */
u64 msl; /* 0x0210 */
u64 hbasce; /* 0x0218 */
u64 _0220; /* 0x0220 */
u64 gpto; /* 0x0228 */
u64 ic; /* 0x0230 */
u64 ec; /* 0x0238 */
u64 save_area; /* 0x0240 */
u64 _0248[7]; /* 0x0248 */
u8 _0280[6]; /* 0x0280 */
u16 lrcpua; /* 0x0286 */
u64 pstate; /* 0x0288 */
u64 pc; /* 0x0290 */
u64 sp_el0; /* 0x0298 */
u64 sp_el1; /* 0x02a0 */
u64 _02a8; /* 0x02a8 */
struct { /* 0x02b0 */
u32 _fpcr_res0;
u32 fpcr;
};
struct { /* 0x02b8 */
u32 _fpsr_res0;
u32 fpsr;
};
u16 sve_pregs[16]; /* 0x02c0 */
u16 sve_ffr; /* 0x02e0 */
u8 _02e2[6]; /* 0x02e2 */
u64 _02e8[3]; /* 0x02e8 */
u64 gpr[31]; /* 0x0300 */
u64 _03f8; /* 0x03f8 */
union {
u64 icptd[8]; /* 0x0400 */
/* validity-interception reason; icptr 0x01 */
u16 vir; /* 0x0400 */
/* host access interception details; icptr 0x02 */
struct {
u64 esr_el2; /* 0x0400 */
u8 _0408[6]; /* 0x0408 */
u16 pic; /* 0x040e */
union teid teid; /* 0x0410 */
gva_t far_el2; /* 0x0418 */
gva_t vaddr; /* 0x0420 */
u64 suppl; /* 0x0428 */
u8 gltl; /* 0x0430 */
u8 _0431[7]; /* 0x0431 */
u64 _0438; /* 0x0438 */
} hai;
/* exception-interception details; icptr 0x03 */
struct {
u64 esr_el2; /* 0x0400 */
u64 _0408[2]; /* 0x0408 */
gva_t far_el2; /* 0x0418 */
} trap;
/* timer-interception reason; icptr 0x04 */
#define SAE_IR_TIMER_ID_VIRT BIT(6)
#define SAE_IR_TIMER_ID_PHYS BIT(7)
u8 tir; /* 0x0400 */
};
u64 _0440[376]; /* 0x0440 */
} __packed __aligned(PAGE_SIZE);
static_assert(sizeof(struct kvm_sae_block) == PAGE_SIZE);
struct kvm_sae_save_area {
#define SAE_SAVE_AREA_FORMAT_0 0x00
u8 saf; /* 0x0000 */
u8 _0001[5]; /* 0x0001 */
#define SAE_SAS_VALID BIT(0)
u16 sas; /* 0x0006 */
u64 sdo; /* 0x0008 */
u64 _0010[2]; /* 0x0010 */
u64 regs[508]; /* 0x0020 */
} __packed __aligned(PAGE_SIZE);
static_assert(sizeof(struct kvm_sae_save_area) == PAGE_SIZE);
#endif /* ASM_KVM_HOST_ARM64_TYPES_H */

View File

@ -3,6 +3,10 @@
#ifndef ASM_KVM_HOST_TYPES_H
#define ASM_KVM_HOST_TYPES_H
#ifdef KVM_S390_ARM64
#include <asm/kvm_host_arm64_types.h>
#else
#include <asm/kvm_host_s390_types.h>
#endif /* KVM_S390_ARM64 */
#endif /* ASM_KVM_HOST_TYPES_H */

View File

@ -0,0 +1,49 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __ASM_S390_SAE_H
#define __ASM_S390_SAE_H
#include <linux/linkage.h>
#include <linux/types.h>
#ifndef __ASSEMBLER__
/* defined in arch/s390/kernel/entry.S */
asmlinkage void sae(phys_addr_t sae_block_phys);
#include <linux/io.h>
#include <asm/kvm_host_arm64_types.h>
/**
* stiasrm() - STore and Invalidate Arm System Register Multiple
* @save_area: Pointer to SAE save area
*
* Store the guest system registers to the save area.
* The values in the guest are no longer valid.
*/
static __always_inline void stiasrm(struct kvm_sae_save_area *save_area)
{
asm_inline volatile(
" .insn rre,0xb9a70000,%[r1],0\n"
: "+m" (*save_area)
: [r1] "a" (save_area)
);
}
/**
* lasrm() - Load Arm System Register Multiple
*
* @save_area: Pointer to SAE save area
*
* Load the system registers from save_area into the guest.
*/
static __always_inline void lasrm(struct kvm_sae_save_area *save_area)
{
asm_inline volatile(
" .insn rre,0xb9a60000,%[r1],0\n"
:
: "m" (*save_area), [r1] "a" (save_area)
);
}
#endif /* !__ASSEMBLER__ */
#endif /* __ASM_S390_SAE_H */

View File

@ -56,7 +56,9 @@ struct sclp_core_entry {
u8 siif : 1;
u8 sigpif : 1;
u8 : 3;
u8 reserved2[3];
u8 aef: 1;
u8 : 7;
u8 reserved2[2];
u8 : 2;
u8 ib : 1;
u8 cei : 1;
@ -109,6 +111,7 @@ struct sclp_info {
unsigned char has_aeni : 1;
unsigned char has_aisi : 1;
unsigned char has_astfleie2 : 1;
unsigned char has_aef : 1;
unsigned int ibc;
unsigned int mtid;
unsigned int mtid_cp;

View File

@ -59,6 +59,7 @@ static inline bool on_stack(struct stack_info *info,
struct stack_frame {
union {
unsigned long empty[9];
/* SIE stack frame */
struct {
unsigned long sie_control_block;
unsigned long sie_savearea;
@ -68,6 +69,10 @@ struct stack_frame {
unsigned long sie_guest_asce;
unsigned long sie_irq;
};
/* SAE stack frame */
struct {
unsigned long sae_bear;
};
};
unsigned long gprs[10];
unsigned long back_chain;

View File

@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
# The ARM64 uapi headers under generated/uapi/arm64/ are private copies used
# internally by the s390 KVM ARM64 implementation. They contain #line
# directives and rewritten include paths and must not be shipped to userspace.
no-export-headers += arm64/%

View File

@ -70,6 +70,7 @@ int main(void)
OFFSET(__SF_SIE_CONTROL_PHYS, stack_frame, sie_control_block_phys);
OFFSET(__SF_SIE_GUEST_ASCE, stack_frame, sie_guest_asce);
OFFSET(__SF_SIE_IRQ, stack_frame, sie_irq);
OFFSET(__SF_SAE_BEAR, stack_frame, sae_bear);
DEFINE(STACK_FRAME_OVERHEAD, sizeof(struct stack_frame));
BLANK();
OFFSET(__SFUSER_BACKCHAIN, stack_frame_user, back_chain);

View File

@ -245,6 +245,28 @@ EXPORT_SYMBOL(__sie64a)
EXPORT_SYMBOL(sie_exit)
#endif
#if IS_ENABLED(CONFIG_KVM)
/*
* SAE calling convention:
* %r2 physical address of the SAE control block
*/
SYM_FUNC_START(sae)
stmg %r6,%r14,__SF_GPRS(%r15) # store kernel registers
STBEAR __SF_SAE_BEAR(%r15) # save breaking event address register
.insn rre,0xb9a50000,%r2,0 # Start Arm Execution
LBEAR __SF_SAE_BEAR(%r15) # restore breaking event address register
lmg %r6,%r14,__SF_GPRS(%r15) # restore kernel registers
xgr %r0,%r0 # clear guest registers to
xgr %r1,%r1 # prevent speculative use
xgr %r2,%r2
xgr %r3,%r3
xgr %r4,%r4
xgr %r5,%r5
BR_EX %r14
SYM_FUNC_END(sae)
EXPORT_SYMBOL(sae)
#endif
/*
* SVC interrupt handler routine. System calls are synchronous events and
* are entered with interrupts disabled.

View File

@ -150,6 +150,7 @@ static void show_cpu_summary(struct seq_file *m, void *v)
[HWCAP_NR_NNPA] = "nnpa",
[HWCAP_NR_PCI_MIO] = "pcimio",
[HWCAP_NR_SIE] = "sie",
[HWCAP_NR_SAE] = "sae",
};
int i, cpu;
@ -254,6 +255,8 @@ static int __init setup_hwcaps(void)
/* virtualization support */
if (sclp.has_sief2)
elf_hwcap |= HWCAP_SIE;
if (sclp.has_aef)
elf_hwcap |= HWCAP_SAE;
return 0;
}

View File

@ -16,6 +16,7 @@
#include <linux/swap.h>
#include <linux/pagewalk.h>
#include <linux/backing-dev.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <asm/facility.h>
#include <asm/sections.h>
@ -242,34 +243,38 @@ void uv_free_stor_var(void *stor_var)
}
EXPORT_SYMBOL_FOR_MODULES(uv_free_stor_var, "kvm");
static int uv_alloc_range_cb(pte_t *ptep, unsigned long addr, void *data)
{
struct page *page;
pte_t pte;
page = alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO);
if (!page)
return -ENOMEM;
pte = __pte(page_to_phys(page) | pgprot_val(PAGE_KERNEL));
set_pte(ptep, pte);
return 0;
}
void *uv_alloc_stor_var(unsigned long size)
{
unsigned long i, nr_pages, addr;
struct vm_struct *area;
unsigned long addr;
struct page **pages;
size = PAGE_ALIGN(size);
nr_pages = size >> PAGE_SHIFT;
area = get_vm_area(size, VM_SPARSE);
if (!area)
return NULL;
pages = kvcalloc(nr_pages, sizeof(struct page *), GFP_KERNEL_ACCOUNT);
if (!pages)
goto free_area;
for (i = 0; i < nr_pages; i++) {
pages[i] = alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO);
if (!pages[i])
goto free_pages;
}
addr = (unsigned long)area->addr;
if (apply_to_page_range(&init_mm, addr, size, uv_alloc_range_cb, NULL))
goto out;
if (vm_area_map_pages(area, addr, addr + size, pages))
goto free_pages;
kvfree(pages);
return area->addr;
out:
uv_free_stor_var(area->addr);
free_pages:
for (i = 0; i < nr_pages; i++) {
if (pages[i])
__free_page(pages[i]);
}
kvfree(pages);
free_area:
free_vm_area(area);
return NULL;
}
EXPORT_SYMBOL_FOR_MODULES(uv_alloc_stor_var, "kvm");

View File

@ -30,15 +30,22 @@ config KVM
select KVM_MMU_LOCKLESS_AGING
select KVM_GENERIC_PRE_FAULT_MEMORY
help
Support hosting paravirtualized guest machines using the SIE
virtualization capability on the mainframe. This should work
on any 64bit machine.
Enable support for hosting virtualized, hardware-accelerated
virtual machines via two modules: kvm and kvm_arm64.
This module provides access to the hardware capabilities through
a character device node named /dev/kvm.
The kvm module provides access to host hardware-accelerated s390
guests using the Start Interpretive Execution (SIE) instruction via a
character device node named /dev/kvm. This requires a 64-bit machine
(hardware model).
To compile this as a module, choose M here: the module
will be called kvm.
The kvm_arm64 module provides access to host hardware-accelerated
arm64 guests using the Start ARM Execution (SAE) instruction via a
character device node named /dev/kvm_arm64. This requires hardware
models that support the Arm execution facility (AEF).
To compile this driver as a module, choose M here. The module will be
called kvm for the SIE acceleration and kvm_arm64 for the SAE
acceleration.
If unsure, say N.

View File

@ -4,3 +4,4 @@
# Copyright IBM Corp. 2008
obj-$(CONFIG_KVM) += s390/
obj-$(CONFIG_KVM) += arm64/

2
arch/s390/kvm/arm64/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
generated/*

View File

@ -0,0 +1,110 @@
# SPDX-License-Identifier: GPL-2.0
KVM := ../../../../virt/kvm
KVM_DEV_NAME = kvm_arm64
KVM_DEV_MINOR = MISC_DYNAMIC_MINOR
include $(srctree)/virt/kvm/Makefile.kvm
include $(srctree)/arch/$(SRCARCH)/kvm/gmap/Makefile
include $(src)/Makefile.gen
ccflags-y += -I $(src) -I $(objtree)/$(obj) -I$(srctree)/arch/$(SRCARCH)/kvm/gmap -DKVM_S390_ARM64
kvm-arm64-local := \
arm.o \
guest.o \
handle_exit.o \
inject_fault.o \
mmio.o \
mmu.o \
reset.o
kvm-arm64-obj := $(kvm-arm64-local)
kvm-arm64-obj += $(patsubst %.o,%-arm64.o,$(kvm-y))
kvm-arm64-obj += $(patsubst %.o,%-arm64.o,$(gmap-y))
targets += $(kvm-arm64-obj)
$(obj)/%-arm64.o: $(src)/%.c FORCE
@mkdir -p $(dir $@)
$(call if_changed_rule,cc_o_c)
$(call cmd,force_checksrc)
# %-arm64.o stems don't match any kvm-arm64-y entry so Kbuild derives wrong
# KBUILD_MODNAME/KBUILD_MODFILE. Force include a generated header to override.
KVM_ARM64_MODNAME_H := $(obj)/generated/kvm_arm64_modname.h
ccflags-y += -include $(KVM_ARM64_MODNAME_H)
$(addprefix $(obj)/,$(kvm-arm64-obj)): $(KVM_ARM64_MODNAME_H)
$(foreach sfx, i s ll lst, \
$(eval $(patsubst %.o,%.$(sfx),$(addprefix $(obj)/,$(kvm-arm64-local))): $(KVM_ARM64_MODNAME_H)))
targets += generated/kvm_arm64_modname.h
define filechk_kvm_arm64_modname
echo '#ifndef __KVM_ARM64_MODNAME_H'; \
echo '#define __KVM_ARM64_MODNAME_H'; \
echo '#undef KBUILD_MODNAME'; \
echo '#define KBUILD_MODNAME "$(KVM_DEV_NAME)"'; \
echo '#undef __KBUILD_MODNAME'; \
echo '#define __KBUILD_MODNAME $(KVM_DEV_NAME)'; \
echo '#undef KBUILD_MODFILE'; \
echo '#define KBUILD_MODFILE "$(obj)/kvm-arm64"'; \
echo '#endif /* __KVM_ARM64_MODNAME_H */'
endef
$(KVM_ARM64_MODNAME_H): FORCE
$(call filechk,kvm_arm64_modname)
# Members are not in real-obj-y. Set part-of-builtin to enable instrumentation.
$(addprefix $(obj)/,$(kvm-arm64-obj)): private part-of-builtin := y
ifeq ($(CONFIG_KVM),m)
kvm-arm64-y = $(kvm-arm64-obj)
else ifeq ($(CONFIG_KVM),y)
prereq-o-cmd = $(foreach o, $(filter %.o, $^), $(dir $(o)).$(notdir $(o)).cmd)
cmd_gen_symversions_o = \
grep --no-filename "^\#SYMVER" $(prereq-o-cmd) >> $(dot-target).cmd || true
cmd_ld_o_o = $(cmd_ld)
define rule_ld_o_o
$(call cmd_and_savecmd,ld)
$(call cmd,gen_symversions_o)
endef
LDFLAGS_kvm-unnamespaced.o := -r
$(obj)/kvm-unnamespaced.o: $(addprefix $(obj)/,$(kvm-arm64-obj)) FORCE
$(call if_changed_rule,ld_o_o)
quiet_cmd_nm_filter = NMFLTR $@
cmd_nm_filter = $(NM) --defined-only --format=just-symbols $< | sort -u > $@
$(obj)/kvm_symbol_list: $(obj)/kvm-unnamespaced.o FORCE
$(call if_changed,nm_filter)
# cmd_oc_o_o carries -DKBUILD_MODFILE so generate_builtin_ranges.awk
# can attribute kvm-namespaced.o to kvm_arm64 via its .cmd file.
cmd_oc_o_o = $(cmd_objcopy) -DKBUILD_MODFILE='"$(obj)/kvm-arm64"'
define rule_oc_o_o
$(call cmd,objcopy)
@printf '%s\n' 'savedcmd_$@ := $(call make-cmd,oc_o_o)' > $(dot-target).cmd
$(call cmd,gen_objtooldep)
$(call cmd,gen_symversions_o)
endef
OBJCOPYFLAGS_kvm-namespaced.o := -O $(LD_BFD) --localize-symbols=$(obj)/kvm_symbol_list
$(obj)/kvm-namespaced.o: $(obj)/kvm-unnamespaced.o $(obj)/kvm_symbol_list FORCE
$(call if_changed_rule,oc_o_o)
kvm-arm64-y = kvm-namespaced.o
targets += kvm-unnamespaced.o kvm_symbol_list kvm-namespaced.o
endif
clean-files += kvm-unnamespaced.o kvm_symbol_list kvm-namespaced.o
obj-$(CONFIG_KVM) += kvm-arm64.o

View File

@ -0,0 +1,28 @@
# SPDX-License-Identifier: GPL-2.0
#
# Extracts ARM64 marked sections to .inc files that are included by main source
# files
# List of ARM64 C files to extract
ARM64_CFILES := \
arm.c \
guest.c \
mmio.c
quiet_cmd_extract_inc = GEN $@
cmd_extract_inc = mkdir -p $(dir $@); \
$(AWK) -f $(src)/copy-arm64c.awk $< > $@ || \
{ echo "Error: No or unbalanced ARM64_S390_COMMON markers in $<" >&2; rm -f $@; exit 1; }
ARM64_INC_FILES := $(foreach cfile,$(ARM64_CFILES),generated/$(basename $(cfile)).inc)
$(addprefix $(obj)/,$(ARM64_INC_FILES)): $(obj)/generated/%.inc: $(srctree)/arch/arm64/kvm/%.c $(src)/copy-arm64c.awk FORCE
$(call if_changed,extract_inc)
targets += $(ARM64_INC_FILES)
clean-files += generated/*.inc
$(foreach cfile,$(basename $(ARM64_CFILES)),\
$(eval $(obj)/$(cfile).o $(obj)/$(cfile).i $(obj)/$(cfile).s \
$(obj)/$(cfile).ll $(obj)/$(cfile).lst: \
$(obj)/generated/$(cfile).inc))

754
arch/s390/kvm/arm64/arm.c Normal file
View File

@ -0,0 +1,754 @@
// SPDX-License-Identifier: GPL-2.0
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/miscdevice.h>
#include <linux/kvm.h>
#include <linux/kvm_types.h>
#include <linux/kvm_host.h>
#include <linux/cleanup.h>
#include <linux/fpu.h>
#include <asm/access-regs.h>
#include <arm64/kvm_emulate.h>
#include <arm64/sysreg.h>
#include <gmap.h>
#include <kvm_mmu.h>
#include "arm.h"
#include "handle_exit.h"
#define CREATE_TRACE_POINTS
#include "trace.h"
static unsigned long system_supported_vcpu_features(struct kvm_vcpu *vcpu);
#define __INCL_GEN_ARM_FILE
#include "generated/arm.inc"
#undef __INCL_GEN_ARM_FILE
#define KVM_PHYS_SHIFT (40)
int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
{
int ret;
switch (ext) {
case KVM_CAP_NR_VCPUS:
case KVM_CAP_MAX_VCPUS:
case KVM_CAP_MAX_VCPU_ID:
ret = KVM_MAX_VCPUS;
break;
case KVM_CAP_ARM_VM_IPA_SIZE:
ret = get_kvm_ipa_limit();
break;
case KVM_CAP_IOEVENTFD:
case KVM_CAP_PRE_FAULT_MEMORY:
ret = 1;
break;
default:
ret = 0;
}
return ret;
}
static u64 kvm_max_guest_address(void)
{
u64 max_addr;
max_addr = min_t(u64, TASK_SIZE_MAX, sclp.hamax);
max_addr = max_t(u64, max_addr, SZ_1G - 1);
return ALIGN_DOWN(max_addr + 1, SZ_1G) - 1;
}
static int kvm_gmap_init(struct kvm *kvm)
{
struct crst_table *table;
kvm->arch.gmap = gmap_new(kvm, gpa_to_gfn(kvm->arch.guest_phys_size));
if (!kvm->arch.gmap)
return -ENOMEM;
/* arm64 (on s390) do not have pfault */
clear_bit(GMAP_FLAG_PFAULT_ENABLED, &kvm->arch.gmap->flags);
set_bit(GMAP_FLAG_ALLOW_HPAGE_1M, &kvm->arch.gmap->flags);
table = dereference_asce(kvm->arch.gmap->asce);
crst_table_init((void *)table, _CRSTE_HOLE(table->crstes[0].h.tt).val);
return 0;
}
static int kvm_vm_type_ipa_size_shift(unsigned long type)
{
int phys_shift;
phys_shift = KVM_VM_TYPE_ARM_IPA_SIZE(type);
if (phys_shift) {
if (phys_shift > get_kvm_ipa_limit() ||
phys_shift < ARM64_MIN_PARANGE_BITS)
return -EINVAL;
} else {
phys_shift = KVM_PHYS_SHIFT;
if (phys_shift > get_kvm_ipa_limit()) {
pr_warn_once("%s using unsupported default IPA limit\n",
current->comm);
return -EINVAL;
}
}
return phys_shift;
}
int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
{
char debug_name[32];
int ret;
if (type & ~KVM_VM_TYPE_ARM_IPA_SIZE_MASK)
return -EINVAL;
ret = kvm_vm_type_ipa_size_shift(type);
if (ret < 0)
return ret;
kvm->arch.guest_phys_size = 1UL << ret;
mutex_init(&kvm->arch.config_lock);
bitmap_zero(kvm->arch.vcpu_features, KVM_VCPU_MAX_FEATURES);
snprintf(debug_name, sizeof(debug_name), "kvm-arm64-%u-%u",
current->pid, get_random_u32());
kvm->arch.dbf = debug_register(debug_name, 32, 1, 7 * sizeof(long));
if (!kvm->arch.dbf)
return -ENOMEM;
debug_register_view(kvm->arch.dbf, &debug_sprintf_view);
ret = kvm_gmap_init(kvm);
if (ret)
goto out_err;
kvm->arch.mem_limit = kvm->arch.guest_phys_size;
VM_EVENT(kvm, 3, "vm created with type %lu", type);
return 0;
out_err:
debug_unregister(kvm->arch.dbf);
return ret;
}
vm_fault_t kvm_arch_vcpu_fault(struct kvm_vcpu *vcpu, struct vm_fault *vmf)
{
return VM_FAULT_SIGBUS;
}
long kvm_arch_dev_ioctl(struct file *filp,
unsigned int ioctl, unsigned long arg)
{
return -EINVAL;
}
void kvm_arch_destroy_vm(struct kvm *kvm)
{
kvm_destroy_vcpus(kvm);
debug_unregister(kvm->arch.dbf);
kvm->arch.gmap = gmap_put(kvm->arch.gmap);
}
u32 get_kvm_ipa_limit(void)
{
return fls64(kvm_max_guest_address() + 1) - 1;
}
int kvm_arch_vcpu_precreate(struct kvm *kvm, unsigned int id)
{
return 0;
}
int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
{
struct kvm_sae_block *sae_block = &vcpu->arch.sae_block;
struct kvm_sae_save_area *save_area = &vcpu->arch.save_area;
spin_lock_init(&vcpu->arch.mp_state_lock);
/* Force users to call KVM_ARM_VCPU_INIT */
vcpu_clear_flag(vcpu, VCPU_INITIALIZED);
vcpu->arch.mc = kvm_s390_new_mmu_cache();
if (!vcpu->arch.mc)
return -ENOMEM;
save_area->sdo = virt_to_phys(sae_block);
sae_block->hbasce = vcpu->kvm->arch.gmap->asce.val;
sae_block->mso = 0L;
sae_block->msl = vcpu->kvm->arch.mem_limit - 1;
sae_block->save_area = virt_to_phys(save_area);
VM_EVENT(vcpu->kvm, 3,
"create cpu %d at 0x%p, sae block at 0x%p, satellite at 0x%p",
vcpu->vcpu_id, vcpu, &vcpu->arch.sae_block,
&vcpu->arch.save_area);
return 0;
}
void kvm_arch_vcpu_postcreate(struct kvm_vcpu *vcpu)
{
}
void kvm_arch_vcpu_destroy(struct kvm_vcpu *vcpu)
{
kvm_s390_free_mmu_cache(vcpu->arch.mc);
VCPU_EVENT(vcpu, 3, "%s", "free cpu");
}
void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
{
save_access_regs(&vcpu->arch.host_acrs[0]);
vcpu->cpu = cpu;
lasrm(&vcpu->arch.save_area);
}
void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu)
{
stiasrm(&vcpu->arch.save_area);
vcpu->cpu = -1;
restore_access_regs(&vcpu->arch.host_acrs[0]);
}
int kvm_arch_vcpu_ioctl_get_mpstate(struct kvm_vcpu *vcpu,
struct kvm_mp_state *mp_state)
{
*mp_state = READ_ONCE(vcpu->arch.mp_state);
return 0;
}
int kvm_arch_vcpu_ioctl_set_mpstate(struct kvm_vcpu *vcpu,
struct kvm_mp_state *mp_state)
{
return -EINVAL;
}
int kvm_arch_vcpu_runnable(struct kvm_vcpu *vcpu)
{
return 0;
}
static unsigned long system_supported_vcpu_features(struct kvm_vcpu *vcpu)
{
unsigned long features = KVM_S390_ARM64_IMPL_FEATURES;
return features;
}
bool kvm_arch_vcpu_in_kernel(struct kvm_vcpu *vcpu)
{
return vcpu_mode_priv(vcpu);
}
int kvm_arch_vcpu_run_pid_change(struct kvm_vcpu *vcpu)
{
struct kvm *kvm = vcpu->kvm;
if (!kvm_vcpu_initialized(vcpu))
return -ENOEXEC;
if (!kvm_arm_vcpu_is_finalized(vcpu))
return -EPERM;
if (likely(READ_ONCE(vcpu->pid)))
return 0;
scoped_guard(mutex, &kvm->arch.config_lock)
set_bit(KVM_ARCH_FLAG_HAS_RAN_ONCE, &kvm->arch.flags);
return 0;
}
/**
* check_vcpu_requests - check and handle pending vCPU requests
* @vcpu: the VCPU pointer
*
* Return: 1 if we should enter the guest
* 0 if we should exit to userspace
* < 0 if we should exit to userspace, where the return value indicates
* an error
*/
static int check_vcpu_requests(struct kvm_vcpu *vcpu)
{
if (kvm_request_pending(vcpu)) {
if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu))
return -EIO;
if (kvm_check_request(KVM_REQ_VCPU_RESET, vcpu))
kvm_reset_vcpu(vcpu);
/*
* Clear IRQ_PENDING requests that were made to guarantee
* that a VCPU sees new virtual interrupts.
*/
kvm_check_request(KVM_REQ_IRQ_PENDING, vcpu);
if (kvm_check_request(KVM_REQ_TLB_FLUSH, vcpu))
asce_flush_tlb(vcpu->kvm->arch.gmap->asce);
}
return 1;
}
static int kvm_vcpu_initialize(struct kvm_vcpu *vcpu,
const struct kvm_vcpu_init *init)
{
unsigned long features = init->features[0];
struct kvm *kvm = vcpu->kvm;
guard(mutex)(&kvm->arch.config_lock);
if (test_bit(KVM_ARCH_FLAG_VCPU_FEATURES_CONFIGURED, &kvm->arch.flags) &&
kvm_vcpu_init_changed(vcpu, init))
return -EINVAL;
bitmap_copy(kvm->arch.vcpu_features, &features, KVM_VCPU_MAX_FEATURES);
kvm_reset_vcpu(vcpu);
set_bit(KVM_ARCH_FLAG_VCPU_FEATURES_CONFIGURED, &kvm->arch.flags);
vcpu_set_flag(vcpu, VCPU_INITIALIZED);
return 0;
}
static int kvm_vcpu_set_target(struct kvm_vcpu *vcpu,
const struct kvm_vcpu_init *init)
{
int ret;
if (init->target != KVM_ARM_TARGET_GENERIC_V8)
return -EINVAL;
ret = kvm_vcpu_init_check_features(vcpu, init);
if (ret)
return ret;
if (!kvm_vcpu_initialized(vcpu))
return kvm_vcpu_initialize(vcpu, init);
if (kvm_vcpu_init_changed(vcpu, init))
return -EINVAL;
kvm_reset_vcpu(vcpu);
return 0;
}
static int kvm_arch_vcpu_ioctl_vcpu_init(struct kvm_vcpu *vcpu,
struct kvm_vcpu_init *init)
{
struct kvm_sae_save_area *save_area = &vcpu->arch.save_area;
struct kvm_sae_block *sae_block = &vcpu->arch.sae_block;
int ret;
sae_block->save_area = virt_to_phys(save_area);
save_area->sdo = virt_to_phys(sae_block);
ret = kvm_vcpu_set_target(vcpu, init);
if (ret)
return ret;
scoped_guard(spinlock, &vcpu->arch.mp_state_lock) {
WRITE_ONCE(vcpu->arch.mp_state.mp_state, KVM_MP_STATE_RUNNABLE);
}
return 0;
}
int kvm_vm_ioctl_irq_line(struct kvm *kvm, struct kvm_irq_level *irq_level,
bool line_status)
{
/* stub for now*/
return -EINVAL;
}
static void adjust_pc(struct kvm_vcpu *vcpu)
{
if (vcpu_get_flag(vcpu, INCREMENT_PC)) {
kvm_skip_instr(vcpu);
vcpu_clear_flag(vcpu, INCREMENT_PC);
}
}
static noinstr void arm_vcpu_enter_exit(phys_addr_t sae_block)
{
guest_timing_enter_irqoff();
guest_state_enter_irqoff();
/*
* External interrupts may occur while the SAE instruction is executing
* and must preempt guest execution to maintain responsiveness of the
* host.
*
* As a temporary workaround, run SAE with interrupts enabled so that any
* interrupt is taken via the normal host interrupt path rather than
* being subsumed inside the SAE window.
*/
raw_local_irq_enable();
sae(sae_block);
raw_local_irq_disable();
guest_state_exit_irqoff();
guest_timing_exit_irqoff();
}
/** kvm_arch_vcpu_ioctl_run() - run arm64 vCPU
*
* Execute arm64 guest instructions using SAE.
*
* Returns:
* 1 enter the guest (should not be observed by userspace)
* 0 exit to userspace
* < 0 exit to userspace, where the return value indicates an error
*
*
*/
int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
{
DECLARE_KERNEL_FPU_ONSTACK32(fpu_save);
struct kvm_run *kvm_run = vcpu->run;
int ret;
if (kvm_run->exit_reason == KVM_EXIT_MMIO) {
ret = kvm_handle_mmio_return(vcpu);
if (ret <= 0)
return ret;
}
vcpu_load(vcpu);
kernel_fpu_begin(&fpu_save, KERNEL_FPC | KERNEL_VXR);
load_vx_regs((vcpu->arch.ctxt.vregs));
if (!vcpu->wants_to_run) {
ret = -EINTR;
goto out;
}
kvm_sigset_activate(vcpu);
might_fault();
ret = 1;
kvm_run->exit_reason = KVM_EXIT_UNKNOWN;
while (ret > 0) {
ret = kvm_xfer_to_guest_mode_handle_work(vcpu);
if (!ret)
ret = 1;
if (ret > 0)
ret = check_vcpu_requests(vcpu);
vcpu->arch.sae_block.icptr = 0;
local_irq_disable();
xfer_to_guest_mode_prepare();
/*
* The full memory barrier in smp_store_mb() orders the mode
* store against the kvm_request_pending() check below, so
* any pending kick is visible before we decide to enter the
* guest.
*/
smp_store_mb(vcpu->mode, IN_GUEST_MODE);
if (ret <= 0 || kvm_request_pending(vcpu) ||
xfer_to_guest_mode_work_pending()) {
vcpu->mode = OUTSIDE_GUEST_MODE;
local_irq_enable();
continue;
}
trace_kvm_entry(*vcpu_pc(vcpu));
adjust_pc(vcpu);
arm_vcpu_enter_exit(virt_to_phys(&vcpu->arch.sae_block));
vcpu->mode = OUTSIDE_GUEST_MODE;
vcpu->stat.exits++;
local_irq_enable();
trace_kvm_exit(kvm_vcpu_trap_get_class(vcpu), *vcpu_pc(vcpu));
ret = handle_exit(vcpu);
}
kvm_sigset_deactivate(vcpu);
out:
if (unlikely(vcpu_get_flag(vcpu, INCREMENT_PC)))
adjust_pc(vcpu);
save_vx_regs(vcpu->arch.ctxt.vregs);
kernel_fpu_end(&fpu_save, KERNEL_FPC | KERNEL_VXR);
vcpu_put(vcpu);
return ret;
}
static int kvm_arm_vcpu_set_attr(struct kvm_vcpu *vcpu,
struct kvm_device_attr *attr)
{
int ret;
switch (attr->group) {
default:
ret = -ENXIO;
break;
}
return ret;
}
static int kvm_arm_vcpu_get_attr(struct kvm_vcpu *vcpu,
struct kvm_device_attr *attr)
{
int ret;
switch (attr->group) {
default:
ret = -ENXIO;
break;
}
return ret;
}
static int kvm_arm_vcpu_has_attr(struct kvm_vcpu *vcpu,
struct kvm_device_attr *attr)
{
int ret;
switch (attr->group) {
default:
ret = -ENXIO;
break;
}
return ret;
}
long kvm_arch_vcpu_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
{
struct kvm_vcpu *vcpu = filp->private_data;
void __user *argp = (void __user *)arg;
struct kvm_device_attr attr;
int ret;
switch (ioctl) {
case KVM_ARM_VCPU_INIT: {
struct kvm_vcpu_init init;
ret = -EFAULT;
if (copy_from_user(&init, argp, sizeof(init)))
break;
ret = kvm_arch_vcpu_ioctl_vcpu_init(vcpu, &init);
break;
}
case KVM_SET_ONE_REG:
case KVM_GET_ONE_REG: {
struct kvm_one_reg reg;
ret = -ENOEXEC;
if (unlikely(!kvm_vcpu_initialized(vcpu)))
break;
ret = -EFAULT;
if (copy_from_user(&reg, argp, sizeof(reg)))
break;
if (kvm_check_request(KVM_REQ_VCPU_RESET, vcpu))
kvm_reset_vcpu(vcpu);
if (ioctl == KVM_SET_ONE_REG)
ret = kvm_arm_set_reg(vcpu, &reg);
else
ret = kvm_arm_get_reg(vcpu, &reg);
break;
}
case KVM_GET_REG_LIST: {
struct kvm_reg_list __user *user_list = argp;
struct kvm_reg_list reg_list;
unsigned int n;
ret = -ENOEXEC;
if (unlikely(!kvm_vcpu_initialized(vcpu)))
break;
ret = -EPERM;
if (!kvm_arm_vcpu_is_finalized(vcpu))
break;
ret = -EFAULT;
if (copy_from_user(&reg_list, user_list, sizeof(reg_list)))
break;
n = reg_list.n;
reg_list.n = kvm_arm_num_regs(vcpu);
if (copy_to_user(user_list, &reg_list, sizeof(reg_list)))
break;
ret = -E2BIG;
if (n < reg_list.n)
break;
ret = kvm_arm_copy_reg_indices(vcpu, user_list->reg);
break;
}
case KVM_ARM_VCPU_FINALIZE: {
int what;
if (!kvm_vcpu_initialized(vcpu))
return -ENOEXEC;
if (get_user(what, (const int __user *)argp))
return -EFAULT;
ret = kvm_arm_vcpu_finalize(vcpu, what);
break;
}
case KVM_SET_DEVICE_ATTR: {
ret = -EFAULT;
if (copy_from_user(&attr, argp, sizeof(attr)))
break;
ret = kvm_arm_vcpu_set_attr(vcpu, &attr);
break;
}
case KVM_GET_DEVICE_ATTR: {
ret = -EFAULT;
if (copy_from_user(&attr, argp, sizeof(attr)))
break;
ret = kvm_arm_vcpu_get_attr(vcpu, &attr);
break;
}
case KVM_HAS_DEVICE_ATTR: {
ret = -EFAULT;
if (copy_from_user(&attr, argp, sizeof(attr)))
break;
ret = kvm_arm_vcpu_has_attr(vcpu, &attr);
break;
}
default:
ret = -EINVAL;
}
return ret;
}
int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm,
struct kvm_dirty_log *log)
{
return s390_kvm_mmu_get_dirty_log(kvm, log);
}
bool kvm_age_gfn(struct kvm *kvm, struct kvm_gfn_range *range)
{
scoped_guard(read_lock, &kvm->mmu_lock)
return gmap_age_gfn(kvm->arch.gmap, range->start, range->end);
}
void kvm_arch_sync_dirty_log(struct kvm *kvm, struct kvm_memory_slot *memslot)
{
gfn_t last_gfn = memslot->base_gfn + memslot->npages;
scoped_guard(read_lock, &kvm->mmu_lock)
gmap_sync_dirty_log(kvm->arch.gmap, memslot->base_gfn, last_gfn);
}
int kvm_arch_prepare_memory_region(struct kvm *kvm,
const struct kvm_memory_slot *old,
struct kvm_memory_slot *new,
enum kvm_mr_change change)
{
return s390_kvm_mmu_prepare_memory_region(kvm, old, new, change);
}
void kvm_arch_commit_memory_region(struct kvm *kvm, struct kvm_memory_slot *old,
const struct kvm_memory_slot *new,
enum kvm_mr_change change)
{
}
bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range)
{
return gmap_unmap_gfn_range(kvm->arch.gmap, range->slot, range->start, range->end);
}
bool kvm_test_age_gfn(struct kvm *kvm, struct kvm_gfn_range *range)
{
scoped_guard(read_lock, &kvm->mmu_lock)
return dat_test_age_gfn(kvm->arch.gmap->asce, range->start, range->end);
}
int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
{
void __user *argp = (void __user *)arg;
switch (ioctl) {
case KVM_ARM_PREFERRED_TARGET: {
struct kvm_vcpu_init init = {
.target = KVM_ARM_TARGET_GENERIC_V8,
};
if (copy_to_user(argp, &init, sizeof(init)))
return -EFAULT;
return 0;
}
default:
return -EINVAL;
}
}
bool kvm_arch_irqchip_in_kernel(struct kvm *kvm)
{
return false;
}
int kvm_set_msi(struct kvm_kernel_irq_routing_entry *e, struct kvm *kvm,
int irq_source_id, int level, bool line_status)
{
return -EINVAL;
}
int kvm_set_routing_entry(struct kvm *kvm,
struct kvm_kernel_irq_routing_entry *e,
const struct kvm_irq_routing_entry *ue)
{
return -EINVAL;
}
int kvm_cpu_has_pending_timer(struct kvm_vcpu *vcpu)
{
return 0;
}
long kvm_arch_vcpu_unlocked_ioctl(struct file *filp, unsigned int ioctl,
unsigned long arg)
{
return -ENOIOCTLCMD;
}
static int __init kvm_s390_arm64_init(void)
{
if (!sclp.has_aef)
return -ENODEV;
return kvm_init(sizeof(struct kvm_vcpu), 0, THIS_MODULE);
}
static __exit void kvm_s390_arm64_exit(void)
{
kvm_exit();
}
module_init(kvm_s390_arm64_init);
module_exit(kvm_s390_arm64_exit);

58
arch/s390/kvm/arm64/arm.h Normal file
View File

@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ARCH_S390_KVM_ARM64_H
#define ARCH_S390_KVM_ARM64_H
#include <linux/compiler_attributes.h>
#include <linux/kvm_host.h>
#define VM_EVENT(d_kvm, d_loglevel, d_string, d_args...) \
debug_sprintf_event((d_kvm)->arch.dbf, d_loglevel, \
__stringify(KVM_DEV_NAME) ": " d_string "\n", \
d_args)
#define VCPU_EVENT(d_vcpu, d_loglevel, d_string, d_args...) \
debug_sprintf_event((d_vcpu)->kvm->arch.dbf, d_loglevel, \
__stringify(KVM_DEV_NAME) " %02d[%016llx-%016llx]: " d_string "\n", \
(d_vcpu)->vcpu_id, (d_vcpu)->arch.sae_block.pstate, \
(d_vcpu)->arch.sae_block.pc, d_args)
static __always_inline int kvm_is_ucontrol(struct kvm *kvm)
{
return 0;
}
static __always_inline int __kvm_s390_pv_destroy_page(struct page *page)
{
return 0;
}
static __always_inline void kvm_s390_vsie_gmap_notifier(struct gmap *gmap, gpa_t start, gpa_t end)
{
}
static __always_inline int kvm_s390_pv_get_handle(struct kvm *kvm)
{
return 0;
}
static __always_inline int kvm_s390_is_migration_mode(struct kvm *kvm)
{
return false;
}
static __always_inline bool kvm_arch_setup_async_pf(struct kvm_vcpu *vcpu)
{
return false;
}
static __always_inline void kvm_s390_update_cmma_dirty(struct kvm *kvm, const struct kvm_memory_slot *old)
{
}
/* should never be called */
static __always_inline int kvm_s390_vm_stop_migration(struct kvm *kvm)
{
return -EINVAL;
}
#endif /* ARCH_S390_KVM_ARM64_H */

View File

@ -0,0 +1,82 @@
#!/usr/bin/awk -f
# SPDX-License-Identifier: GPL-2.0
#
# Extract marked sections from ARM64 C files for sharing with s390 KVM
#
# Usage: copy-arm64c.awk <input_file>
#
# Extracts all sections between start/end markers. If no markers found, signals failure.
BEGIN {
# Constants
start_pattern = "^#if ARM64_S390_COMMON == 1$"
end_pattern = "^#endif /\\* ARM64_S390_COMMON == 1 \\*/$"
# State variables
copying = found_marker = 0
file_header_done = 0
depth = 0
}
!file_header_done {
if (/^\/\*/ || /^\/\/ SPDX-License-Identifier:/) {
print
next
}
if (/[[:space:]]\*([[:space:]]|$)/) {
print
next
}
if (/\*\//) {
print " *"
} else {
print "/*"
}
print " * This file was automatically generated from " FILENAME
print " * Do not modify this file directly."
print " */"
print ""
print "#ifndef __INCL_GEN_ARM_FILE"
print "#error do not include this file directly."
print "#endif /* __INCL_GEN_ARM_FILE */"
print "#undef __INCL_GEN_ARM_FILE"
print ""
file_header_done = 1
}
$0 ~ start_pattern {
if (copying) { error = 1; exit }
depth = copying = found_marker = 1
# Map subsequent lines back to the original ARM64 source file so that
# compiler diagnostics point there rather than at the generated copy.
print "#line " (NR + 1) " \"" FILENAME "\""
next
}
copying && /^[ \t]*#[ \t]*if(def|ndef)?([ \t]|$)/ { depth++ }
$0 ~ /^[ \t]*#[ \t]*endif/ {
depth--
if (depth == 0) {
if ($0 ~ end_pattern) {
copying = 0
next
}
error = 1; exit
}
}
/ARM64_S390_COMMON/ { error = 1; exit }
copying {
gsub(/#include <asm\//, "#include <arm64/")
gsub(/#include <uapi\/asm\//, "#include <uapi/arm64/")
print
next
}
END {
exit error || !found_marker || copying
}

135
arch/s390/kvm/arm64/guest.c Normal file
View File

@ -0,0 +1,135 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kvm_host.h>
#include <linux/kvm.h>
#include <arm64/kvm_emulate.h>
#include <arm64/kvm_nested.h>
#define __INCL_GEN_ARM_FILE
#include "generated/guest.inc"
#undef __INCL_GEN_ARM_FILE
const struct kvm_stats_desc kvm_vm_stats_desc[] = {
KVM_GENERIC_VM_STATS()
};
const struct kvm_stats_header kvm_vm_stats_header = {
.name_size = KVM_STATS_NAME_SIZE,
.num_desc = ARRAY_SIZE(kvm_vm_stats_desc),
.id_offset = sizeof(struct kvm_stats_header),
.desc_offset = sizeof(struct kvm_stats_header) + KVM_STATS_NAME_SIZE,
.data_offset = sizeof(struct kvm_stats_header) + KVM_STATS_NAME_SIZE +
sizeof(kvm_vm_stats_desc),
};
const struct kvm_stats_desc kvm_vcpu_stats_desc[] = {
KVM_GENERIC_VCPU_STATS(),
/* ARM64 stats */
STATS_DESC_COUNTER(VCPU, hvc_exit_stat),
STATS_DESC_COUNTER(VCPU, wfe_exit_stat),
STATS_DESC_COUNTER(VCPU, wfi_exit_stat),
STATS_DESC_COUNTER(VCPU, mmio_exit_user),
STATS_DESC_COUNTER(VCPU, mmio_exit_kernel),
STATS_DESC_COUNTER(VCPU, signal_exits),
STATS_DESC_COUNTER(VCPU, exits),
/* GMAP stats */
STATS_DESC_COUNTER(VCPU, pfault_sync),
};
const struct kvm_stats_header kvm_vcpu_stats_header = {
.name_size = KVM_STATS_NAME_SIZE,
.num_desc = ARRAY_SIZE(kvm_vcpu_stats_desc),
.id_offset = sizeof(struct kvm_stats_header),
.desc_offset = sizeof(struct kvm_stats_header) + KVM_STATS_NAME_SIZE,
.data_offset = sizeof(struct kvm_stats_header) + KVM_STATS_NAME_SIZE +
sizeof(kvm_vcpu_stats_desc),
};
int kvm_arm_copy_reg_indices(struct kvm_vcpu *vcpu, u64 __user *uindices)
{
int ret;
ret = copy_core_reg_indices(vcpu, uindices);
if (ret < 0)
return ret;
uindices += ret;
return 0;
}
unsigned long kvm_arm_num_regs(struct kvm_vcpu *vcpu)
{
return num_core_regs(vcpu);
}
int kvm_arm_get_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
{
/* We currently use nothing arch-specific in upper 32 bits */
if ((reg->id & ~KVM_REG_SIZE_MASK) >> 32 != KVM_REG_ARM64 >> 32)
return -EINVAL;
switch (reg->id & KVM_REG_ARM_COPROC_MASK) {
case KVM_REG_ARM_CORE:
return get_core_reg(vcpu, reg);
default:
return -EINVAL;
}
}
int kvm_arm_set_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
{
/* We currently use nothing arch-specific in upper 32 bits */
if ((reg->id & ~KVM_REG_SIZE_MASK) >> 32 != KVM_REG_ARM64 >> 32)
return -EINVAL;
switch (reg->id & KVM_REG_ARM_COPROC_MASK) {
case KVM_REG_ARM_CORE:
return set_core_reg(vcpu, reg);
default:
return -EINVAL;
}
}
int kvm_arch_vcpu_ioctl_get_regs(struct kvm_vcpu *vcpu, struct kvm_regs *regs)
{
return -EINVAL;
}
int kvm_arch_vcpu_ioctl_set_regs(struct kvm_vcpu *vcpu, struct kvm_regs *regs)
{
return -EINVAL;
}
int kvm_arch_vcpu_ioctl_get_sregs(struct kvm_vcpu *vcpu,
struct kvm_sregs *sregs)
{
return -EINVAL;
}
int kvm_arch_vcpu_ioctl_set_sregs(struct kvm_vcpu *vcpu,
struct kvm_sregs *sregs)
{
return -EINVAL;
}
int kvm_arch_vcpu_ioctl_get_fpu(struct kvm_vcpu *vcpu, struct kvm_fpu *fpu)
{
return -EINVAL;
}
int kvm_arch_vcpu_ioctl_set_fpu(struct kvm_vcpu *vcpu, struct kvm_fpu *fpu)
{
return -EINVAL;
}
int kvm_arch_vcpu_ioctl_translate(struct kvm_vcpu *vcpu,
struct kvm_translation *tr)
{
return -EINVAL;
}
int kvm_arch_vcpu_ioctl_set_guest_debug(struct kvm_vcpu *vcpu,
struct kvm_guest_debug *dbg)
{
return -EINVAL;
}

View File

@ -0,0 +1,161 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kvm_host.h>
#include <arm64/esr.h>
#include <arm64/kvm_emulate.h>
#include "handle_exit.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,
[ESR_ELx_EC_IABT_LOW] = kvm_handle_guest_abort,
[ESR_ELx_EC_DABT_LOW] = kvm_handle_guest_abort,
};
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);
}
/*
* Return > 0 to return to guest, < 0 on error, 0 (and set exit_reason) on
* proper exit to userspace.
*/
int handle_exit(struct kvm_vcpu *vcpu)
{
u8 icptr = vcpu->arch.sae_block.icptr;
int ret = 1;
switch (icptr) {
case SAE_ICPTR_SPURIOUS:
break;
case SAE_ICPTR_VALIDITY:
WARN_ONCE(true, "SAE: validity intercept. vir: 0x%04x",
vcpu->arch.sae_block.vir);
vcpu->run->exit_reason = KVM_EXIT_FAIL_ENTRY;
ret = 0;
break;
case SAE_ICPTR_GUEST_ADDRESS_SIZE:
case SAE_ICPTR_HOST_ACCESS_EXCEPTION:
case SAE_ICPTR_SYNCHRONOUS_EXCEPTION:
ret = handle_trap_exceptions(vcpu);
break;
default:
vcpu->run->exit_reason = KVM_EXIT_FAIL_ENTRY;
ret = 0;
}
return ret;
}
/* 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,9 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef KVM_ARM64_HANDLE_EXIT_H
#define KVM_ARM64_HANDLE_EXIT_H
#include <linux/kvm_host.h>
int handle_exit(struct kvm_vcpu *vcpu);
#endif /* KVM_ARM64_HANDLE_EXIT_H */

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;
}

View File

@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kvm_host.h>
#include <linux/bitfield.h>
#include <trace/events/kvm.h>
#include <arm64/kvm_emulate.h>
#include <arm64/sysreg.h>
#include "trace.h"
#define __INCL_GEN_ARM_FILE
#include "generated/mmio.inc"
#undef __INCL_GEN_ARM_FILE

210
arch/s390/kvm/arm64/mmu.c Normal file
View File

@ -0,0 +1,210 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kvm_host.h>
#include <arm64/kvm_emulate.h>
#include <faultin.h>
#include "trace.h"
static inline bool kvm_s390_cur_gmap_fault_is_write(struct kvm_vcpu *vcpu)
{
return (vcpu->arch.sae_block.hai.pic & PGM_INT_CODE_MASK) == PGM_PROTECTION ||
vcpu->arch.sae_block.hai.teid.fsi == TEID_FSI_STORE;
}
/*
* user_mem_abort() - handle a dat fault for the gmap of a vcpu
*
* Return: 0 on success, < 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,
struct kvm_memory_slot *slot, hva_t hva)
{
struct guest_fault f = { };
int ret;
if (kvm_s390_cur_gmap_fault_is_write(vcpu))
f.write_attempt = FOLL_WRITE;
f.gfn = gpa_to_gfn(fault_ipa);
ret = kvm_s390_faultin_gfn(vcpu, NULL, &f);
if (ret <= 0)
return ret;
if (ret == PGM_ADDRESSING)
/*
* Without the relevant sysregs we cannot do anything for now.
* Go back to userspace with an error. TODO sysreg handling
*/
return -ENOEXEC;
KVM_BUG_ON(ret, vcpu->kvm);
return -EINVAL;
}
static int kvm_handle_pic(struct kvm_vcpu *vcpu, bool *translation)
{
switch (kvm_vcpu_fault_pic(vcpu)) {
/* expected cases: */
case PGM_ASCE_TYPE:
case PGM_REGION_FIRST_TRANS:
case PGM_REGION_SECOND_TRANS:
case PGM_REGION_THIRD_TRANS:
case PGM_SEGMENT_TRANSLATION:
case PGM_PAGE_TRANSLATION:
*translation = true;
break;
case PGM_PROTECTION:
break;
/* unexpected cases: */
case 0:
KVM_BUG(1, vcpu->kvm, "On MMU fault path but no fault occurred");
return -EFAULT;
default:
KVM_BUG(1, vcpu->kvm, "Unexpected program interrupt 0x%x, TEID 0x%016lx",
vcpu->arch.sae_block.hai.pic, vcpu->arch.sae_block.hai.teid.val);
send_sig(SIGSEGV, current, 0);
return -EFAULT;
}
return 0;
}
int kvm_handle_guest_abort(struct kvm_vcpu *vcpu)
{
struct kvm_memory_slot *memslot;
bool translation = false;
phys_addr_t fault_ipa;
unsigned long hva;
unsigned long esr;
bool write_fault;
bool guest_size_err;
bool writable;
bool is_iabt;
int ret;
gfn_t gfn;
esr = kvm_vcpu_get_esr(vcpu);
fault_ipa = kvm_vcpu_get_fault_ipa(vcpu);
is_iabt = kvm_vcpu_trap_is_iabt(vcpu);
guest_size_err = vcpu->arch.sae_block.icptr == SAE_ICPTR_GUEST_ADDRESS_SIZE;
if (guest_size_err) {
translation = true;
} else {
ret = kvm_handle_pic(vcpu, &translation);
if (ret)
return ret;
}
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;
if (fault_ipa >= vcpu->kvm->arch.guest_phys_size)
return -ENOEXEC;
}
trace_kvm_guest_fault(*vcpu_pc(vcpu), kvm_vcpu_get_esr(vcpu),
kvm_vcpu_get_hfar(vcpu), fault_ipa);
kvm_vcpu_srcu_read_lock(vcpu);
gfn = fault_ipa >> PAGE_SHIFT;
memslot = gfn_to_memslot(vcpu->kvm, gfn);
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)
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
*/
goto out_unlock;
}
/*
* Check for a cache maintenance operation. Assume the guest is
* cautious and skip instruction
*/
if (kvm_is_error_hva(hva) && esr_dabt_is_cm(esr)) {
kvm_incr_pc(vcpu);
ret = 1;
goto out_unlock;
}
/*
* The IPA is reported as [MAX:12], so we need to
* complement it with the bottom 12 bits from the
* faulting VA. This is always 12 bits, irrespective
* of the page size.
*/
fault_ipa |= kvm_vcpu_get_hfar(vcpu) & ((1 << 12) - 1);
ret = io_mem_abort(vcpu, fault_ipa);
goto out_unlock;
}
/* Userspace should not be able to register out-of-bounds IPAs */
VM_BUG_ON(fault_ipa >= vcpu->kvm->arch.guest_phys_size);
/*
* Proper guest size faults have been injected.
* In theory it's fine to have device memory higher than MSL,
* even if not currently possible, but that would have been handled above.
* So if we get here with a guest size intercept, we have a bug somewhere.
*/
VM_BUG_ON(guest_size_err);
ret = user_mem_abort(vcpu, fault_ipa, memslot, hva);
if (!ret)
ret = 1;
out_unlock:
kvm_vcpu_srcu_read_unlock(vcpu);
return ret;
}
long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu, struct kvm_pre_fault_memory *range)
{
struct guest_fault f = { .gfn = gpa_to_gfn(range->gpa), };
gpa_t end;
int rc;
rc = kvm_s390_faultin_gfn(vcpu, NULL, &f);
if (rc == PGM_ADDRESSING)
return -ENOENT;
if (rc > 0)
return -EIO;
if (rc < 0)
return rc;
if (f.ptep)
return PAGE_SIZE;
end = ALIGN(range->gpa + PAGE_SIZE, f.crste_region3 ? _REGION3_SIZE : HPAGE_SIZE);
return min(range->size, end - range->gpa);
}
int kvm_arch_pre_fault_allowed(struct kvm_vcpu *vcpu)
{
if (!kvm_vcpu_initialized(vcpu))
return -ENOEXEC;
return 0;
}

View File

@ -0,0 +1,71 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kvm_host.h>
#include <linux/fpu.h>
#include <arm64/kvm_emulate.h>
#include <arm64/kvm_nested.h>
bool kvm_arm_vcpu_is_finalized(struct kvm_vcpu *vcpu)
{
return true;
}
static inline void vcpu_reset_hcr(struct kvm_vcpu *vcpu)
{
vcpu->arch.hcr_el2 = HCR_EL2_E2H | HCR_EL2_RW | HCR_EL2_AMO |
HCR_EL2_IMO | HCR_EL2_FMO | HCR_EL2_PTW;
/* traps */
vcpu->arch.hcr_el2 |= HCR_EL2_TSC | HCR_EL2_TID1 | HCR_EL2_TID2 |
HCR_EL2_TID3 | HCR_EL2_TID4 | HCR_EL2_TID5 |
HCR_EL2_TIDCP | HCR_EL2_TLOR;
}
void kvm_reset_vcpu(struct kvm_vcpu *vcpu)
{
struct vcpu_reset_state reset_state;
bool loaded;
scoped_guard(spinlock, &vcpu->arch.mp_state_lock) {
reset_state = vcpu->arch.reset_state;
vcpu->arch.reset_state.reset = false;
}
/*
* Disable preemption around the vcpu reset as we might otherwise race with
* preempt notifiers which call stiasrm/lasrm from put/load
*/
preempt_disable();
/* The reset must run with an unloaded save area */
loaded = vcpu_is_loaded(vcpu);
if (loaded)
vcpu_put(vcpu);
kvm_reset_vcpu_core(vcpu);
/* Reset special registers */
vcpu_reset_hcr(vcpu);
if (reset_state.reset) {
*vcpu_pc(vcpu) = reset_state.pc;
vcpu_clear_flag(vcpu, PENDING_EXCEPTION);
vcpu_clear_flag(vcpu, EXCEPT_MASK);
vcpu_clear_flag(vcpu, INCREMENT_PC);
vcpu_set_reg(vcpu, 0, reset_state.r0);
}
/* Load new vx-regs into HW if they are currently loaded */
if (current->thread.kfpu_flags)
load_vx_regs(vcpu->arch.ctxt.vregs);
if (loaded)
vcpu_load(vcpu);
preempt_enable();
}
int kvm_arm_vcpu_finalize(struct kvm_vcpu *vcpu, int feature)
{
return -EINVAL;
}

101
arch/s390/kvm/arm64/trace.h Normal file
View File

@ -0,0 +1,101 @@
/* SPDX-License-Identifier: GPL-2.0 */
#if !defined(KVM_ARM64_TRACE_KVM_H) || defined(TRACE_HEADER_MULTI_READ)
#define KVM_ARM64_TRACE_KVM_H
#include <linux/tracepoint.h>
#undef TRACE_SYSTEM
#define TRACE_SYSTEM kvm_arm64
#undef TRACE_SYSTEM_VAR
#define TRACE_SYSTEM_VAR kvm_arm64
TRACE_EVENT(kvm_entry,
TP_PROTO(unsigned long vcpu_pc),
TP_ARGS(vcpu_pc),
TP_STRUCT__entry(
__field(unsigned long, vcpu_pc)
),
TP_fast_assign(__entry->vcpu_pc = vcpu_pc;),
TP_printk("PC: 0x%016lx", __entry->vcpu_pc)
);
TRACE_EVENT(kvm_exit,
TP_PROTO(unsigned int esr_ec, unsigned long vcpu_pc),
TP_ARGS(esr_ec, vcpu_pc),
TP_STRUCT__entry(
__field(unsigned int, esr_ec )
__field(unsigned long, vcpu_pc )
),
TP_fast_assign(
__entry->esr_ec = esr_ec;
__entry->vcpu_pc = vcpu_pc;
),
TP_printk("HSR_EC: 0x%04x, PC: 0x%016lx",
__entry->esr_ec,
__entry->vcpu_pc)
);
TRACE_EVENT(kvm_mmio_nisv,
TP_PROTO(unsigned long vcpu_pc, unsigned long esr,
unsigned long far, unsigned long ipa),
TP_ARGS(vcpu_pc, esr, far, ipa),
TP_STRUCT__entry(
__field( unsigned long, vcpu_pc )
__field( unsigned long, esr )
__field( unsigned long, far )
__field( unsigned long, ipa )
),
TP_fast_assign(
__entry->vcpu_pc = vcpu_pc;
__entry->esr = esr;
__entry->far = far;
__entry->ipa = ipa;
),
TP_printk("ipa %#016lx, esr %#016lx, far %#016lx, pc %#016lx",
__entry->ipa, __entry->esr,
__entry->far, __entry->vcpu_pc)
);
TRACE_EVENT(kvm_guest_fault,
TP_PROTO(unsigned long vcpu_pc, unsigned long hsr,
unsigned long hxfar,
unsigned long long ipa),
TP_ARGS(vcpu_pc, hsr, hxfar, ipa),
TP_STRUCT__entry(
__field( unsigned long, vcpu_pc )
__field( unsigned long, hsr )
__field( unsigned long, hxfar )
__field( unsigned long long, ipa )
),
TP_fast_assign(
__entry->vcpu_pc = vcpu_pc;
__entry->hsr = hsr;
__entry->hxfar = hxfar;
__entry->ipa = ipa;
),
TP_printk("ipa %#llx, hsr %#08lx, hxfar %#08lx, pc %#016lx",
__entry->ipa, __entry->hsr,
__entry->hxfar, __entry->vcpu_pc)
);
#endif /* KVM_ARM64_TRACE_KVM_H */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace
/* This part must be outside protection */
#include <trace/define_trace.h>

View File

@ -10,6 +10,11 @@
#include "gmap.h"
#include "faultin.h"
#ifdef KVM_S390_ARM64
#include "arm.h"
#else
#include "s390.h"
#endif
bool kvm_arch_setup_async_pf(struct kvm_vcpu *vcpu);
#define CREATE_TRACE_POINTS

View File

@ -21,7 +21,11 @@
#include "dat.h"
#include "gmap.h"
#ifdef KVM_S390_ARM64
#include "arm.h"
#else
#include "s390.h"
#endif
#include "faultin.h"
static int gmap_limit_to_type(gfn_t limit)

View File

@ -3,7 +3,11 @@
#include <linux/kvm_types.h>
#include <linux/kvm_host.h>
#ifdef KVM_S390_ARM64
#include "arm.h"
#else
#include "s390.h"
#endif
#include "gmap.h"
#include "dat.h"
#include "kvm_mmu.h"
@ -76,7 +80,7 @@ int s390_kvm_mmu_prepare_memory_region(struct kvm *kvm,
return -EINVAL;
}
if (kvm->arch.migration_mode) {
if (kvm_s390_is_migration_mode(kvm)) {
/*
* Turn off migration mode when:
* - userspace creates a new memslot with dirty logging off,

View File

@ -13,6 +13,18 @@
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace_gmap
#ifdef KVM_S390_ARM64
#define __KVM_FIELDS \
__field(unsigned long, pstate) \
__field(unsigned long, pc)
#define __KVM_ASSIGN ({\
__entry->pstate = vcpu->arch.sae_block.pstate; \
__entry->pc = vcpu->arch.sae_block.pc; \
})
#define __KVM_PRINT \
__entry->pstate, \
__entry->pc
#else
#define __KVM_FIELDS \
__field(unsigned long, pswmask) \
__field(unsigned long, pswaddr)
@ -23,6 +35,7 @@
#define __KVM_PRINT \
__entry->pswmask,\
__entry->pswaddr
#endif
TRACE_EVENT(kvm_s390_major_guest_pfault,
TP_PROTO(struct kvm_vcpu *vcpu),

View File

@ -11,6 +11,8 @@
#include <linux/kvm_host.h>
#include <linux/errno.h>
#include <linux/pagemap.h>
#include <linux/cleanup.h>
#include <linux/slab.h>
#include <asm/asm-offsets.h>
#include <asm/irq.h>
@ -411,9 +413,9 @@ static int handle_partial_execution(struct kvm_vcpu *vcpu)
*/
int handle_sthyi(struct kvm_vcpu *vcpu)
{
struct sthyi_sctns *sctns __free(kfree) = NULL;
int reg1, reg2, cc = 0, r = 0;
u64 code, addr, rc = 0;
struct sthyi_sctns *sctns = NULL;
if (!test_kvm_facility(vcpu->kvm, 74))
return kvm_s390_inject_program_int(vcpu, PGM_OPERATION);
@ -430,37 +432,32 @@ int handle_sthyi(struct kvm_vcpu *vcpu)
return kvm_s390_inject_program_int(vcpu, PGM_SPECIFICATION);
if (code & 0xffff) {
cc = 3;
rc = 4;
goto out;
vcpu->run->s.regs.gprs[reg2 + 1] = 4;
kvm_s390_set_psw_cc(vcpu, 3);
return 0;
}
if (!kvm_s390_pv_cpu_is_protected(vcpu) && (addr & ~PAGE_MASK))
return kvm_s390_inject_program_int(vcpu, PGM_SPECIFICATION);
sctns = (void *)get_zeroed_page(GFP_KERNEL_ACCOUNT);
sctns = kzalloc(PAGE_SIZE, GFP_KERNEL_ACCOUNT);
if (!sctns)
return -ENOMEM;
cc = sthyi_fill(sctns, &rc);
if (cc < 0) {
free_page((unsigned long)sctns);
if (cc < 0)
return cc;
}
out:
if (!cc) {
if (kvm_s390_pv_cpu_is_protected(vcpu)) {
memcpy(sida_addr(vcpu->arch.sie_block), sctns, PAGE_SIZE);
} else {
r = write_guest(vcpu, addr, reg2, sctns, PAGE_SIZE);
if (r) {
free_page((unsigned long)sctns);
if (r)
return kvm_s390_inject_prog_cond(vcpu, r);
}
}
}
free_page((unsigned long)sctns);
vcpu->run->s.regs.gprs[reg2 + 1] = rc;
kvm_s390_set_psw_cc(vcpu, cc);
return r;
@ -536,6 +533,15 @@ static int handle_pv_sclp(struct kvm_vcpu *vcpu)
set_bit(IRQ_PEND_EXT_SERVICE, &fi->pending_irqs);
clear_bit(IRQ_PEND_EXT_SERVICE, &fi->masked_irqs);
spin_unlock_irqrestore(&fi->lock, flags);
/*
* We missed the floating IRQ kick since we can only inject
* when we end up here and not when the irq was injected via
* the FLIC.
*
* Now that we have cleared the masking we can kick cpus.
*/
kvm_s390_pv_sclp_kick(vcpu);
return 0;
}

View File

@ -1918,49 +1918,126 @@ static int __inject_io(struct kvm *kvm, struct kvm_s390_interrupt_info *inti)
return 0;
}
static u64 inti_to_irq_pend_mask(u64 type, int isc)
{
switch (type) {
case KVM_S390_MCHK:
/* Only repressible machine checks are floating */
return BIT(IRQ_PEND_MCHK_REP);
case KVM_S390_INT_VIRTIO:
return BIT(IRQ_PEND_VIRTIO);
case KVM_S390_INT_SERVICE:
return BIT(IRQ_PEND_EXT_SERVICE) |
BIT(IRQ_PEND_EXT_SERVICE_EV);
case KVM_S390_INT_PFAULT_DONE:
return BIT(IRQ_PEND_PFAULT_DONE);
case KVM_S390_INT_IO_MIN...KVM_S390_INT_IO_MAX:
return BIT(isc_to_irq_type(isc));
default:
return 0;
}
}
/*
* Find a destination VCPU for a floating irq and kick it.
* Setup intervention masks to catch running vcpus that hopefully open
* their masks soonish and kick sleeping vcpus to motivate them to
* take IRQs.
*/
static void __floating_irq_kick(struct kvm *kvm, u64 type)
static void vcpu_intervention_kick(struct kvm_vcpu *vcpu, u64 type)
{
/* make the VCPU drop out of the SIE, or wake it up if sleeping */
switch (type) {
case KVM_S390_MCHK:
kvm_s390_set_cpuflags(vcpu, CPUSTAT_STOP_INT);
break;
case KVM_S390_INT_IO_MIN...KVM_S390_INT_IO_MAX:
if (!(type & KVM_S390_INT_IO_AI_MASK &&
vcpu->kvm->arch.gisa_int.origin) ||
kvm_s390_pv_cpu_get_handle(vcpu))
kvm_s390_set_cpuflags(vcpu, CPUSTAT_IO_INT);
break;
default:
kvm_s390_set_cpuflags(vcpu, CPUSTAT_EXT_INT);
break;
}
kvm_s390_vcpu_wakeup(vcpu);
}
static void kick_cpu_irq(struct kvm *kvm, u64 type, u64 parm)
{
struct kvm_vcpu *dst_vcpu;
int sigcpu, online_vcpus, nr_tries = 0;
u64 irq_pend_mask;
unsigned long i;
online_vcpus = atomic_read(&kvm->online_vcpus);
if (!online_vcpus)
return;
irq_pend_mask = inti_to_irq_pend_mask(type, parm);
for (sigcpu = kvm->arch.float_int.last_sleep_cpu; ; sigcpu++) {
sigcpu %= online_vcpus;
dst_vcpu = kvm_get_vcpu(kvm, sigcpu);
if (!is_vcpu_stopped(dst_vcpu))
if (!is_vcpu_stopped(dst_vcpu) &&
deliverable_irqs(dst_vcpu) & irq_pend_mask)
break;
/* avoid endless loops if all vcpus are stopped */
if (nr_tries++ >= online_vcpus)
return;
if (nr_tries++ >= online_vcpus) {
dst_vcpu = NULL;
break;
}
}
/* make the VCPU drop out of the SIE, or wake it up if sleeping */
switch (type) {
case KVM_S390_MCHK:
kvm_s390_set_cpuflags(dst_vcpu, CPUSTAT_STOP_INT);
break;
case KVM_S390_INT_IO_MIN...KVM_S390_INT_IO_MAX:
if (!(type & KVM_S390_INT_IO_AI_MASK &&
kvm->arch.gisa_int.origin) ||
kvm_s390_pv_cpu_get_handle(dst_vcpu))
kvm_s390_set_cpuflags(dst_vcpu, CPUSTAT_IO_INT);
break;
default:
kvm_s390_set_cpuflags(dst_vcpu, CPUSTAT_EXT_INT);
break;
/* Nobody was enabled, time to wake all of them */
if (!dst_vcpu) {
kvm_for_each_vcpu(i, dst_vcpu, kvm)
vcpu_intervention_kick(dst_vcpu, type);
return;
}
kvm_s390_vcpu_wakeup(dst_vcpu);
vcpu_intervention_kick(dst_vcpu, type);
}
void kvm_s390_pv_sclp_kick(struct kvm_vcpu *vcpu)
{
/*
* The cpu that called sclp likely will also take the IRQ, no
* need to kick anyone.
*/
if (deliverable_irqs(vcpu) & BIT(IRQ_PEND_EXT_SERVICE))
return;
/*
* For the other cases we might have sleeping cpus with open
* masks. Time to find and kick them.
*/
kick_cpu_irq(vcpu->kvm, KVM_S390_INT_SERVICE, -1);
}
/*
* Find a destination VCPU for a floating irq and kick it.
*/
static void __floating_irq_kick(struct kvm *kvm, u64 type, u64 parm)
{
struct kvm_s390_float_interrupt *fi = &kvm->arch.float_int;
/*
* No need to kick on non-ev service IRQs for PV VMs, we're
* not allowed to inject anyway. We need to wait for the sclp
* instruction notification AFTER re-entry of the vcpu that
* handled the instruction intercept.
*/
if (type == KVM_S390_INT_SERVICE && !(parm & SCCB_EVENT_PENDING) &&
test_bit(IRQ_PEND_EXT_SERVICE, &fi->masked_irqs))
return;
kick_cpu_irq(kvm, type, parm);
}
static int __inject_vm(struct kvm *kvm, struct kvm_s390_interrupt_info *inti)
{
u64 type = READ_ONCE(inti->type);
u64 parm = -1;
int rc;
switch (type) {
@ -1971,12 +2048,15 @@ static int __inject_vm(struct kvm *kvm, struct kvm_s390_interrupt_info *inti)
rc = __inject_virtio(kvm, inti);
break;
case KVM_S390_INT_SERVICE:
parm = inti->ext.ext_params & SCCB_EVENT_PENDING;
rc = __inject_service(kvm, inti);
break;
case KVM_S390_INT_PFAULT_DONE:
rc = __inject_pfault_done(kvm, inti);
break;
case KVM_S390_INT_IO_MIN...KVM_S390_INT_IO_MAX:
/* Grab isc here since __inject_io() might free inti */
parm = int_word_to_isc(inti->io.io_int_word);
rc = __inject_io(kvm, inti);
break;
default:
@ -1985,7 +2065,7 @@ static int __inject_vm(struct kvm *kvm, struct kvm_s390_interrupt_info *inti)
if (rc)
return rc;
__floating_irq_kick(kvm, type);
__floating_irq_kick(kvm, type, parm);
return 0;
}
@ -3690,7 +3770,7 @@ void kvm_s390_gib_destroy(void)
}
chsc_sgib(0);
unregister_adapter_interrupt(&gib_alert_irq);
free_page((unsigned long)gib);
kfree(gib);
gib = NULL;
}
@ -3704,7 +3784,7 @@ int __init kvm_s390_gib_init(u8 nisc)
goto out;
}
gib = (struct kvm_s390_gib *)get_zeroed_page(GFP_KERNEL_ACCOUNT | GFP_DMA);
gib = kzalloc(PAGE_SIZE, GFP_KERNEL_ACCOUNT | GFP_DMA);
if (!gib) {
rc = -ENOMEM;
goto out;
@ -3723,7 +3803,7 @@ int __init kvm_s390_gib_init(u8 nisc)
gib_origin = virt_to_phys(gib);
if (chsc_sgib(gib_origin)) {
pr_err("Associating the GIB with the AIV facility failed\n");
free_page((unsigned long)gib);
kfree(gib);
gib = NULL;
rc = -EIO;
goto out_unreg_gal;
@ -3743,7 +3823,7 @@ int __init kvm_s390_gib_init(u8 nisc)
out_unreg_gal:
unregister_adapter_interrupt(&gib_alert_irq);
out_free_gib:
free_page((unsigned long)gib);
kfree(gib);
gib = NULL;
out:
return rc;

View File

@ -14,6 +14,7 @@
#include <linux/mm_types.h>
#include <linux/pgtable.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <asm/asm-offsets.h>
#include <asm/facility.h>
#include <asm/current.h>
@ -869,7 +870,7 @@ static int handle_stsi(struct kvm_vcpu *vcpu)
int fc = (vcpu->run->s.regs.gprs[0] & 0xf0000000) >> 28;
int sel1 = vcpu->run->s.regs.gprs[0] & 0xff;
int sel2 = vcpu->run->s.regs.gprs[1] & 0xffff;
unsigned long mem = 0;
void *mem = NULL;
u64 operand2;
int rc = 0;
u8 ar;
@ -911,19 +912,19 @@ static int handle_stsi(struct kvm_vcpu *vcpu)
switch (fc) {
case 1: /* same handling for 1 and 2 */
case 2:
mem = get_zeroed_page(GFP_KERNEL_ACCOUNT);
mem = kzalloc(PAGE_SIZE, GFP_KERNEL_ACCOUNT);
if (!mem)
goto out_no_data;
if (stsi((void *) mem, fc, sel1, sel2))
if (stsi(mem, fc, sel1, sel2))
goto out_no_data;
break;
case 3:
if (sel1 != 2 || sel2 != 2)
goto out_no_data;
mem = get_zeroed_page(GFP_KERNEL_ACCOUNT);
mem = kzalloc(PAGE_SIZE, GFP_KERNEL_ACCOUNT);
if (!mem)
goto out_no_data;
handle_stsi_3_2_2(vcpu, (void *) mem);
handle_stsi_3_2_2(vcpu, mem);
break;
case 15: /* fc 15 is fully handled in userspace */
insert_stsi_usr_data(vcpu, operand2, ar, fc, sel1, sel2);
@ -931,10 +932,10 @@ static int handle_stsi(struct kvm_vcpu *vcpu)
return -EREMOTE;
}
if (kvm_s390_pv_cpu_is_protected(vcpu)) {
memcpy(sida_addr(vcpu->arch.sie_block), (void *)mem, PAGE_SIZE);
memcpy(sida_addr(vcpu->arch.sie_block), mem, PAGE_SIZE);
rc = 0;
} else {
rc = write_guest(vcpu, operand2, ar, (void *)mem, PAGE_SIZE);
rc = write_guest(vcpu, operand2, ar, mem, PAGE_SIZE);
}
if (rc) {
rc = kvm_s390_inject_prog_cond(vcpu, rc);
@ -945,14 +946,14 @@ static int handle_stsi(struct kvm_vcpu *vcpu)
rc = -EREMOTE;
}
trace_kvm_s390_handle_stsi(vcpu, fc, sel1, sel2, operand2);
free_page(mem);
kfree(mem);
kvm_s390_set_psw_cc(vcpu, 0);
vcpu->run->s.regs.gprs[0] = 0;
return rc;
out_no_data:
kvm_s390_set_psw_cc(vcpu, 3);
out:
free_page(mem);
kfree(mem);
return rc;
}

View File

@ -3273,8 +3273,8 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
goto out_err;
BUILD_BUG_ON(sizeof(struct sie_page2) != 4096);
kvm->arch.sie_page2 =
(struct sie_page2 *) get_zeroed_page(GFP_KERNEL_ACCOUNT | GFP_DMA);
kvm->arch.sie_page2 = kzalloc_obj(*kvm->arch.sie_page2,
GFP_KERNEL_ACCOUNT | GFP_DMA);
if (!kvm->arch.sie_page2)
goto out_err;
@ -3369,7 +3369,7 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
return 0;
out_err:
free_page((unsigned long)kvm->arch.sie_page2);
kfree(kvm->arch.sie_page2);
debug_unregister(kvm->arch.dbf);
sca_dispose(kvm);
KVM_EVENT(3, "creation of vm failed: %d", rc);
@ -3427,7 +3427,7 @@ void kvm_arch_destroy_vm(struct kvm *kvm)
mmu_notifier_unregister(&kvm->arch.pv.mmu_notifier, kvm->mm);
debug_unregister(kvm->arch.dbf);
free_page((unsigned long)kvm->arch.sie_page2);
kfree(kvm->arch.sie_page2);
kvm_s390_destroy_adapters(kvm);
kvm_s390_clear_float_irqs(kvm);
kvm_s390_vsie_destroy(kvm);
@ -3656,13 +3656,13 @@ static void kvm_s390_vcpu_crypto_setup(struct kvm_vcpu *vcpu)
void kvm_s390_vcpu_unsetup_cmma(struct kvm_vcpu *vcpu)
{
if (vcpu->arch.sie_block->cbrlo)
free_page((unsigned long)phys_to_virt(vcpu->arch.sie_block->cbrlo));
kfree(phys_to_virt(vcpu->arch.sie_block->cbrlo & PAGE_MASK));
vcpu->arch.sie_block->cbrlo = 0;
}
int kvm_s390_vcpu_setup_cmma(struct kvm_vcpu *vcpu)
{
void *cbrlo_page = (void *)get_zeroed_page(GFP_KERNEL_ACCOUNT);
void *cbrlo_page = kzalloc(PAGE_SIZE, GFP_KERNEL_ACCOUNT);
if (!cbrlo_page)
return -ENOMEM;

View File

@ -375,6 +375,7 @@ enum hrtimer_restart kvm_s390_idle_wakeup(struct hrtimer *timer);
int __must_check kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu);
void kvm_s390_clear_local_irqs(struct kvm_vcpu *vcpu);
void kvm_s390_clear_float_irqs(struct kvm *kvm);
void kvm_s390_pv_sclp_kick(struct kvm_vcpu *vcpu);
int __must_check kvm_s390_inject_vm(struct kvm *kvm,
struct kvm_s390_interrupt *s390int,
struct kvm_s390_interrupt_info *inti);

View File

@ -3,12 +3,16 @@
# Makefile for s390 specific build tools
#
include $(srctree)/arch/s390/tools/Makefile.arm64h
kapi := arch/$(ARCH)/include/generated/asm
kapi-hdrs-y := $(kapi)/facility-defs.h $(kapi)/dis-defs.h
kapi-hdrs-$(CONFIG_KVM) += $(ARM64_TARGETS)
PHONY += kapi
kapi: $(kapi-hdrs-y)
kapi: $(kapi-hdrs-y) $(kapi-hdrs-m)
hostprogs += gen_facilities
hostprogs += gen_opcode_table

View File

@ -0,0 +1,55 @@
# SPDX-License-Identifier: GPL-2.0
#
# Share ARM64 headers with s390 KVM
#
arm64api := $(objtree)/arch/$(ARCH)/include/generated/arm64
arm64uapi := $(objtree)/arch/$(ARCH)/include/generated/uapi/arm64
# List of ARM64 headers to share with s390 KVM
# Format: [uapi:]header.h or header-gen.h
#
# * header.h - from arch/arm64/include/asm/ to arm64/
# * uapi:header.h - from arch/arm64/include/uapi/asm/ to uapi/arm64/
# * header-gen.h - generated as header-gen.h from ARM64 header header.h
# If a header contains partial markers (#if ARM64_S390_COMMON == 1) only this part will be copied.
# If the file does not contain any markers the whole file will be copied.
ARM64_SHARED_HEADERS := \
brk-imm.h \
esr.h \
kvm_arm.h \
kvm_emulate-gen.h \
kvm_host.h \
ptrace-gen.h \
sysreg-gen.h \
uapi:hwcap.h \
uapi:kvm.h \
uapi:ptrace.h \
uapi:sve_context.h
filechk_gen_arm_hdr = \
$(AWK) -f $(src)/copy-arm64h.awk $< || \
$(AWK) -f $(src)/copy-arm64h-full.awk $< || \
{ echo "Error: Unbalanced ARM64_S390_COMMON markers in $<" >&2; exit 1; }
arm64-type = $(firstword $(subst :, ,$(1)))
arm64-base = $(or $(word 2,$(subst :, ,$(1))),$(1))
arm64-src-base = $(patsubst %-gen.h,%.h,$(call arm64-base,$(1)))
arm64-src = $(srctree)/arch/arm64/include/$(if $(filter uapi,$(call arm64-type,$(1))),uapi/)asm/$(call arm64-src-base,$(1))
arm64-dst = $(if $(filter uapi,$(call arm64-type,$(1))),$(arm64uapi),$(arm64api))/$(call arm64-base,$(1))
$(foreach hdr,$(ARM64_SHARED_HEADERS),\
$(eval $(call arm64-dst,$(hdr)): $(call arm64-src,$(hdr)) $(src)/copy-arm64h.awk $(src)/copy-arm64h-full.awk FORCE))
ARM64_TARGETS := $(sort $(foreach hdr,$(ARM64_SHARED_HEADERS),$(call arm64-dst,$(hdr))))
$(ARM64_TARGETS):
$(call filechk,gen_arm_hdr)
filechk_gen_sysreg_arm64 = \
$(AWK) -f $(srctree)/arch/arm64/tools/gen-sysreg.awk $(srctree)/arch/arm64/tools/sysreg
$(arm64api)/sysreg-defs.h: $(srctree)/arch/arm64/tools/gen-sysreg.awk $(srctree)/arch/arm64/tools/sysreg FORCE
$(call filechk,gen_sysreg_arm64)
ARM64_TARGETS += $(arm64api)/sysreg-defs.h

View File

@ -0,0 +1,50 @@
#!/usr/bin/awk -f
# SPDX-License-Identifier: GPL-2.0
#
# Process entire ARM64 headers for sharing with s390 KVM
#
# Usage: copy-arm64h-full.awk <input_file>
#
# This processes the entire file (unlike copy-arm64h.awk which only extracts marked sections)
BEGIN {
max_guard_line = 25
guard_found = 0
header_added = 0
}
# Find and process the include guard in the first few lines
NR <= max_guard_line && !guard_found && /^#ifndef [A-Z_0-9]+$/ {
guard_name = $2
guard_found = 1
print "/* This header was copied from " FILENAME " */"
print ""
# Map subsequent lines back to the original ARM64 source file so that
# compiler diagnostics point there rather than at the generated copy.
print "#line " NR " \"" FILENAME "\""
print
header_added = 1
next
}
# Transform include directives
/^#include <uapi\/asm\// {
sub(/<uapi\/asm\//, "<uapi/arm64/")
print
next
}
/^#include <asm\// {
sub(/<asm\//, "<arm64/")
print
next
}
/ARM64_S390_COMMON/ {
exit 1
}
# Pass through all other lines
{
print
}

View File

@ -0,0 +1,83 @@
#!/usr/bin/awk -f
# SPDX-License-Identifier: GPL-2.0
#
# Extract marked sections from ARM64 headers for sharing with s390 KVM
#
# Usage: copy-arm64h.awk <input_file>
#
# Extracts all sections between start/end markers. If no markers found, signals for fallback.
BEGIN {
# Constants
start_pattern = "^#if ARM64_S390_COMMON == 1$"
end_pattern = "^#endif \\/\\* ARM64_S390_COMMON == 1 \\*\\/$"
max_guard_line = 25
# State variables
copying = found_marker = error = 0
guard_name = ""
file_header_done = 0
depth = 0
}
NR <= max_guard_line && !guard_name && /^#ifndef [A-Za-z0-9_]+$/ {
guard_name = $2
next
}
NR > max_guard_line && !guard_name && !file_header_done {
print "error: no include guard found in first " max_guard_line " lines" > "/dev/stderr"
file_header_done = 1
error = 1; exit
}
$0 ~ start_pattern {
if (copying) { error = 1; exit }
if (!found_marker) {
print "/*"
print " * This header was automatically generated from " FILENAME
print " * Do not modify this file directly."
print " */"
print "#ifndef " guard_name
print "#define " guard_name
print ""
}
depth = copying= found_marker = 1
# Map subsequent lines back to the original ARM64 source file so that
# compiler diagnostics point there rather than at the generated copy.
print "#line " (NR + 1) " \"" FILENAME "\""
next
}
guard_name && !found_marker {
next
}
$0 ~ /^[ \t]*#[ \t]*endif/ {
depth--
if (depth == 0) {
if ($0 ~ end_pattern ) {
copying = 0
next
}
error = 1; exit
}
}
/ARM64_S390_COMMON/ { error = 1; exit }
copying {
if ($0 ~ /^[ \t]*#[ \t]*if(def|ndef)?([ \t]|$)/) depth++
gsub(/#include <asm\//, "#include <arm64/")
gsub(/#include <uapi\/asm\//, "#include <uapi/arm64/")
print
next
}
END {
if (error) exit 1
if (found_marker) {
print ""
print "#endif /* " guard_name " */"
}
exit !found_marker || copying
}

View File

@ -594,6 +594,9 @@ b9a0 clp RRF_U0RR
b9a1 tpei RRE_RR
b9a2 ptf RRE_R0
b9a4 uvc RRF_URR
b9a5 sae RRE_R0
b9a6 lasrm RRE_R0
b9a7 stiasrm RRE_R0
b9aa lptea RRF_RURR2
b9ab essa RRF_U0RR
b9ac irbm RRE_RR

View File

@ -96,6 +96,7 @@ static void __init sclp_early_facilities_detect(void)
sclp.has_ib = cpue->ib;
sclp.has_cei = cpue->cei;
sclp.has_skey = cpue->skey;
sclp.has_aef = cpue->aef;
break;
}

View File

@ -231,6 +231,7 @@ TEST_GEN_PROGS_s390 += rseq_test
TEST_GEN_PROGS_s390 += s390/irq_routing
TEST_GEN_PROGS_s390 += mmu_stress_test
TEST_GEN_PROGS_s390 += pre_fault_memory_test
TEST_GEN_PROGS_s390 += s390/irq_injection
TEST_GEN_PROGS_riscv = $(TEST_GEN_PROGS_COMMON)
TEST_GEN_PROGS_riscv += riscv/sbi_pmu_test

View File

@ -0,0 +1,74 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Test for KVM_S390_IRQ - s390x IRQ injection API
*
* Verifies that VM-level interrupt types are correctly rejected when
* injected via the vCPU file descriptor, and that invalid arguments
* to vCPU-level interrupts are also rejected.
*
* Copyright IBM Corp. 2026
* Author: Gabriella Seifert <gseifert@linux.ibm.com>
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include "kvm_util.h"
#include "test_util.h"
#include <linux/kvm.h>
static void test_vm_irq_on_vcpu_fails(struct kvm_vcpu *vcpu, __u64 type)
{
struct kvm_s390_irq irq = { .type = type };
int rc;
rc = __vcpu_ioctl(vcpu, KVM_S390_IRQ, &irq);
ksft_test_result(rc == -1 && errno == EINVAL,
"VM on VCPU IRQ type 0x%llx: expected %d, got %d\n",
type, EINVAL, errno);
}
static void test_vcpu_irq_bad_param_fails(struct kvm_vcpu *vcpu,
struct kvm_s390_irq *irq)
{
int rc;
rc = __vcpu_ioctl(vcpu, KVM_S390_IRQ, irq);
ksft_test_result(rc == -1 && errno == EINVAL,
"VCPU IRQ bad param type 0x%llx: expected %d got %d\n",
irq->type, EINVAL, errno);
}
int main(void)
{
struct kvm_s390_irq irq;
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
ksft_print_header();
ksft_set_plan(6);
vm = vm_create_with_one_vcpu(&vcpu, NULL);
test_vm_irq_on_vcpu_fails(vcpu, KVM_S390_INT_VIRTIO);
test_vm_irq_on_vcpu_fails(vcpu, KVM_S390_INT_SERVICE);
test_vm_irq_on_vcpu_fails(vcpu, KVM_S390_INT_IO_MIN);
memset(&irq, 0, sizeof(irq));
irq.type = KVM_S390_INT_EMERGENCY;
irq.u.emerg.code = 0xffff;
test_vcpu_irq_bad_param_fails(vcpu, &irq);
memset(&irq, 0, sizeof(irq));
irq.type = KVM_S390_INT_EXTERNAL_CALL;
irq.u.extcall.code = 0xffff;
test_vcpu_irq_bad_param_fails(vcpu, &irq);
memset(&irq, 0, sizeof(irq));
irq.type = 0xdeadbeef;
test_vcpu_irq_bad_param_fails(vcpu, &irq);
kvm_vm_free(vm);
ksft_finished();
}