diff --git a/Documentation/virt/kvm/devices/vm.rst b/Documentation/virt/kvm/devices/vm.rst index a4d39fa1b083..78e63eb9994e 100644 --- a/Documentation/virt/kvm/devices/vm.rst +++ b/Documentation/virt/kvm/devices/vm.rst @@ -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 =============================== diff --git a/MAINTAINERS b/MAINTAINERS index 70c3c02011eb..dd9680d9de0b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -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 +R: Andreas Grapentin +R: Marc Zyngier +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 M: Paolo Bonzini diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h index b4f5dedff5ae..aa65603455a2 100644 --- a/arch/arm64/include/asm/sysreg.h +++ b/arch/arm64/include/asm/sysreg.h @@ -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 /* * System registers, organised loosely by encoding but grouped together diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index 4b51bc6e8948..5a01fbd8e32b 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -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" diff --git a/arch/s390/include/arm64/kvm_emulate.h b/arch/s390/include/arm64/kvm_emulate.h new file mode 100644 index 000000000000..416c4f96db05 --- /dev/null +++ b/arch/s390/include/arm64/kvm_emulate.h @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __S390_ARM64_KVM_EMULATE_H__ +#define __S390_ARM64_KVM_EMULATE_H__ + +#include +#include + +#include +#include +#include +#include + +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 + +#endif /* __S390_ARM64_KVM_EMULATE_H__ */ diff --git a/arch/s390/include/arm64/kvm_nested.h b/arch/s390/include/arm64/kvm_nested.h new file mode 100644 index 000000000000..e950b1a10c41 --- /dev/null +++ b/arch/s390/include/arm64/kvm_nested.h @@ -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 */ diff --git a/arch/s390/include/arm64/ptrace.h b/arch/s390/include/arm64/ptrace.h new file mode 100644 index 000000000000..449b85582c97 --- /dev/null +++ b/arch/s390/include/arm64/ptrace.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __S390_ARM64_PTRACE_H__ +#define __S390_ARM64_PTRACE_H__ + +#include +#include + +#include + +#endif /* __S390_ARM64_PTRACE_H__ */ diff --git a/arch/s390/include/arm64/sysreg.h b/arch/s390/include/arm64/sysreg.h new file mode 100644 index 000000000000..ac6b638fef56 --- /dev/null +++ b/arch/s390/include/arm64/sysreg.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __S390_ARM64_SYSREG_H__ +#define __S390_ARM64_SYSREG_H__ + +#include +#include +#include +#include + +#include + +#endif /* __S390_ARM64_SYSREG_H__ */ diff --git a/arch/s390/include/asm/asm-prototypes.h b/arch/s390/include/asm/asm-prototypes.h index d4da4436d02b..faa2afb259a1 100644 --- a/arch/s390/include/asm/asm-prototypes.h +++ b/arch/s390/include/asm/asm-prototypes.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #endif /* _ASM_S390_PROTOTYPES_H */ diff --git a/arch/s390/include/asm/elf.h b/arch/s390/include/asm/elf.h index bb63fa4d20bb..ad3108ecfb07 100644 --- a/arch/s390/include/asm/elf.h +++ b/arch/s390/include/asm/elf.h @@ -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. diff --git a/arch/s390/include/asm/io.h b/arch/s390/include/asm/io.h index faddb9aef3b8..ef0490edeccd 100644 --- a/arch/s390/include/asm/io.h +++ b/arch/s390/include/asm/io.h @@ -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 diff --git a/arch/s390/include/asm/kvm.h b/arch/s390/include/asm/kvm.h new file mode 100644 index 000000000000..aeb11dc631d5 --- /dev/null +++ b/arch/s390/include/asm/kvm.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifdef KVM_S390_ARM64 +#include +#else +#include +#endif diff --git a/arch/s390/include/asm/kvm_host.h b/arch/s390/include/asm/kvm_host.h index 147325978225..ac3440a617c1 100644 --- a/arch/s390/include/asm/kvm_host.h +++ b/arch/s390/include/asm/kvm_host.h @@ -3,7 +3,11 @@ #ifndef ASM_KVM_HOST_H #define ASM_KVM_HOST_H +#ifdef KVM_S390_ARM64 +#include +#else #include +#endif #define PGM_OPERATION 0x01 #define PGM_PRIVILEGED_OP 0x02 diff --git a/arch/s390/include/asm/kvm_host_arm64.h b/arch/s390/include/asm/kvm_host_arm64.h new file mode 100644 index 000000000000..82343e723ebf --- /dev/null +++ b/arch/s390/include/asm/kvm_host_arm64.h @@ -0,0 +1,209 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef ASM_KVM_HOST_ARM64_H +#define ASM_KVM_HOST_ARM64_H + +#include + +#include +#include + +#define vcpu_gp_regs(v) ((v)->arch.sae_block.gpr) + +#include +#include + +#include + +#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 */ diff --git a/arch/s390/include/asm/kvm_host_arm64_types.h b/arch/s390/include/asm/kvm_host_arm64_types.h new file mode 100644 index 000000000000..8783a66d3029 --- /dev/null +++ b/arch/s390/include/asm/kvm_host_arm64_types.h @@ -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 +#include +#include +#include +#include + +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 */ diff --git a/arch/s390/include/asm/kvm_host_types.h b/arch/s390/include/asm/kvm_host_types.h index e5bdba07cab0..d308739ebcf5 100644 --- a/arch/s390/include/asm/kvm_host_types.h +++ b/arch/s390/include/asm/kvm_host_types.h @@ -3,6 +3,10 @@ #ifndef ASM_KVM_HOST_TYPES_H #define ASM_KVM_HOST_TYPES_H +#ifdef KVM_S390_ARM64 +#include +#else #include +#endif /* KVM_S390_ARM64 */ #endif /* ASM_KVM_HOST_TYPES_H */ diff --git a/arch/s390/include/asm/sae.h b/arch/s390/include/asm/sae.h new file mode 100644 index 000000000000..26d89fae0836 --- /dev/null +++ b/arch/s390/include/asm/sae.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ASM_S390_SAE_H +#define __ASM_S390_SAE_H + +#include +#include + +#ifndef __ASSEMBLER__ + +/* defined in arch/s390/kernel/entry.S */ +asmlinkage void sae(phys_addr_t sae_block_phys); + +#include +#include + +/** + * 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 */ diff --git a/arch/s390/include/asm/sclp.h b/arch/s390/include/asm/sclp.h index 44066550b9b1..68f61b71a2d5 100644 --- a/arch/s390/include/asm/sclp.h +++ b/arch/s390/include/asm/sclp.h @@ -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; diff --git a/arch/s390/include/asm/stacktrace.h b/arch/s390/include/asm/stacktrace.h index ac3606c3babe..2d332d7c8145 100644 --- a/arch/s390/include/asm/stacktrace.h +++ b/arch/s390/include/asm/stacktrace.h @@ -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; diff --git a/arch/s390/include/uapi/Kbuild b/arch/s390/include/uapi/Kbuild new file mode 100644 index 000000000000..81dcaaf42c40 --- /dev/null +++ b/arch/s390/include/uapi/Kbuild @@ -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/% diff --git a/arch/s390/kernel/asm-offsets.c b/arch/s390/kernel/asm-offsets.c index f6dd2b67dcee..f9f9c2246074 100644 --- a/arch/s390/kernel/asm-offsets.c +++ b/arch/s390/kernel/asm-offsets.c @@ -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); diff --git a/arch/s390/kernel/entry.S b/arch/s390/kernel/entry.S index 10dd9bbdf985..2004fd7642d7 100644 --- a/arch/s390/kernel/entry.S +++ b/arch/s390/kernel/entry.S @@ -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. diff --git a/arch/s390/kernel/processor.c b/arch/s390/kernel/processor.c index e33a3eccda56..6da55a158027 100644 --- a/arch/s390/kernel/processor.c +++ b/arch/s390/kernel/processor.c @@ -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; } diff --git a/arch/s390/kernel/uv.c b/arch/s390/kernel/uv.c index 8ea9dd7704ff..abb94720c79b 100644 --- a/arch/s390/kernel/uv.c +++ b/arch/s390/kernel/uv.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -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"); diff --git a/arch/s390/kvm/Kconfig b/arch/s390/kvm/Kconfig index 9045ae20fd89..e7b525d04c6d 100644 --- a/arch/s390/kvm/Kconfig +++ b/arch/s390/kvm/Kconfig @@ -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. diff --git a/arch/s390/kvm/Makefile b/arch/s390/kvm/Makefile index a4e3875b5bdd..380db443a0e9 100644 --- a/arch/s390/kvm/Makefile +++ b/arch/s390/kvm/Makefile @@ -4,3 +4,4 @@ # Copyright IBM Corp. 2008 obj-$(CONFIG_KVM) += s390/ +obj-$(CONFIG_KVM) += arm64/ diff --git a/arch/s390/kvm/arm64/.gitignore b/arch/s390/kvm/arm64/.gitignore new file mode 100644 index 000000000000..bd9d90694676 --- /dev/null +++ b/arch/s390/kvm/arm64/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +generated/* diff --git a/arch/s390/kvm/arm64/Makefile b/arch/s390/kvm/arm64/Makefile new file mode 100644 index 000000000000..3f0fe5c9f0a1 --- /dev/null +++ b/arch/s390/kvm/arm64/Makefile @@ -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 + diff --git a/arch/s390/kvm/arm64/Makefile.gen b/arch/s390/kvm/arm64/Makefile.gen new file mode 100644 index 000000000000..9c33b068fab5 --- /dev/null +++ b/arch/s390/kvm/arm64/Makefile.gen @@ -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)) diff --git a/arch/s390/kvm/arm64/arm.c b/arch/s390/kvm/arm64/arm.c new file mode 100644 index 000000000000..bd9d85235f43 --- /dev/null +++ b/arch/s390/kvm/arm64/arm.c @@ -0,0 +1,754 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#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(®, 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, ®); + else + ret = kvm_arm_get_reg(vcpu, ®); + 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(®_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, ®_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); diff --git a/arch/s390/kvm/arm64/arm.h b/arch/s390/kvm/arm64/arm.h new file mode 100644 index 000000000000..760230685ca1 --- /dev/null +++ b/arch/s390/kvm/arm64/arm.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef ARCH_S390_KVM_ARM64_H +#define ARCH_S390_KVM_ARM64_H + +#include +#include + +#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 */ diff --git a/arch/s390/kvm/arm64/copy-arm64c.awk b/arch/s390/kvm/arm64/copy-arm64c.awk new file mode 100644 index 000000000000..640a7bcb7e23 --- /dev/null +++ b/arch/s390/kvm/arm64/copy-arm64c.awk @@ -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 +# +# 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 +#include + +#include +#include + +#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; +} diff --git a/arch/s390/kvm/arm64/handle_exit.c b/arch/s390/kvm/arm64/handle_exit.c new file mode 100644 index 000000000000..e0c7773c2165 --- /dev/null +++ b/arch/s390/kvm/arm64/handle_exit.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +#include +#include + +#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)]; +} diff --git a/arch/s390/kvm/arm64/handle_exit.h b/arch/s390/kvm/arm64/handle_exit.h new file mode 100644 index 000000000000..65ae58721537 --- /dev/null +++ b/arch/s390/kvm/arm64/handle_exit.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef KVM_ARM64_HANDLE_EXIT_H +#define KVM_ARM64_HANDLE_EXIT_H + +#include + +int handle_exit(struct kvm_vcpu *vcpu); + +#endif /* KVM_ARM64_HANDLE_EXIT_H */ diff --git a/arch/s390/kvm/arm64/inject_fault.c b/arch/s390/kvm/arm64/inject_fault.c new file mode 100644 index 000000000000..425dbeaa421c --- /dev/null +++ b/arch/s390/kvm/arm64/inject_fault.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +/** + * 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; +} diff --git a/arch/s390/kvm/arm64/mmio.c b/arch/s390/kvm/arm64/mmio.c new file mode 100644 index 000000000000..908a6b43ddf7 --- /dev/null +++ b/arch/s390/kvm/arm64/mmio.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +#include + +#include +#include + +#include "trace.h" + +#define __INCL_GEN_ARM_FILE +#include "generated/mmio.inc" +#undef __INCL_GEN_ARM_FILE diff --git a/arch/s390/kvm/arm64/mmu.c b/arch/s390/kvm/arm64/mmu.c new file mode 100644 index 000000000000..31e0d036896f --- /dev/null +++ b/arch/s390/kvm/arm64/mmu.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 +#include + +#include + +#include + +#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; +} diff --git a/arch/s390/kvm/arm64/reset.c b/arch/s390/kvm/arm64/reset.c new file mode 100644 index 000000000000..e4cdce1dbf9d --- /dev/null +++ b/arch/s390/kvm/arm64/reset.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +#include +#include + +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; +} diff --git a/arch/s390/kvm/arm64/trace.h b/arch/s390/kvm/arm64/trace.h new file mode 100644 index 000000000000..ee232c4f5a6d --- /dev/null +++ b/arch/s390/kvm/arm64/trace.h @@ -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 + +#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 diff --git a/arch/s390/kvm/gmap/faultin.c b/arch/s390/kvm/gmap/faultin.c index 1dc79807012c..2e93bd563d5c 100644 --- a/arch/s390/kvm/gmap/faultin.c +++ b/arch/s390/kvm/gmap/faultin.c @@ -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 diff --git a/arch/s390/kvm/gmap/gmap.c b/arch/s390/kvm/gmap/gmap.c index 3f3fa864cc36..bf91e3d5013b 100644 --- a/arch/s390/kvm/gmap/gmap.c +++ b/arch/s390/kvm/gmap/gmap.c @@ -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) diff --git a/arch/s390/kvm/gmap/kvm_mmu.c b/arch/s390/kvm/gmap/kvm_mmu.c index c2ffb5e59ec6..4e2da60b9859 100644 --- a/arch/s390/kvm/gmap/kvm_mmu.c +++ b/arch/s390/kvm/gmap/kvm_mmu.c @@ -3,7 +3,11 @@ #include #include +#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, diff --git a/arch/s390/kvm/gmap/trace_gmap.h b/arch/s390/kvm/gmap/trace_gmap.h index 78d26dae59f9..1dce3ef3b0d9 100644 --- a/arch/s390/kvm/gmap/trace_gmap.h +++ b/arch/s390/kvm/gmap/trace_gmap.h @@ -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), diff --git a/arch/s390/kvm/s390/intercept.c b/arch/s390/kvm/s390/intercept.c index ca1205dfac8b..adfde2a656e7 100644 --- a/arch/s390/kvm/s390/intercept.c +++ b/arch/s390/kvm/s390/intercept.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include @@ -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; } diff --git a/arch/s390/kvm/s390/interrupt.c b/arch/s390/kvm/s390/interrupt.c index f892f4307883..31a6074feffc 100644 --- a/arch/s390/kvm/s390/interrupt.c +++ b/arch/s390/kvm/s390/interrupt.c @@ -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; diff --git a/arch/s390/kvm/s390/priv.c b/arch/s390/kvm/s390/priv.c index b3cb2c2c3aa7..856939acf012 100644 --- a/arch/s390/kvm/s390/priv.c +++ b/arch/s390/kvm/s390/priv.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -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; } diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c index 4140dbd6c7ac..18fb200f854a 100644 --- a/arch/s390/kvm/s390/s390.c +++ b/arch/s390/kvm/s390/s390.c @@ -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; diff --git a/arch/s390/kvm/s390/s390.h b/arch/s390/kvm/s390/s390.h index aa0d1d062f8d..9ed1af5583e3 100644 --- a/arch/s390/kvm/s390/s390.h +++ b/arch/s390/kvm/s390/s390.h @@ -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); diff --git a/arch/s390/tools/Makefile b/arch/s390/tools/Makefile index f2862364fb42..d0aa7e076bad 100644 --- a/arch/s390/tools/Makefile +++ b/arch/s390/tools/Makefile @@ -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 diff --git a/arch/s390/tools/Makefile.arm64h b/arch/s390/tools/Makefile.arm64h new file mode 100644 index 000000000000..f3f8e9d9bdd8 --- /dev/null +++ b/arch/s390/tools/Makefile.arm64h @@ -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 diff --git a/arch/s390/tools/copy-arm64h-full.awk b/arch/s390/tools/copy-arm64h-full.awk new file mode 100644 index 000000000000..ad7bb98dc4a6 --- /dev/null +++ b/arch/s390/tools/copy-arm64h-full.awk @@ -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 +# +# 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 +# +# 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 ib; sclp.has_cei = cpue->cei; sclp.has_skey = cpue->skey; + sclp.has_aef = cpue->aef; break; } diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 150f8487041b..3cb35a6963d5 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -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 diff --git a/tools/testing/selftests/kvm/s390/irq_injection.c b/tools/testing/selftests/kvm/s390/irq_injection.c new file mode 100644 index 000000000000..75e0528f9b01 --- /dev/null +++ b/tools/testing/selftests/kvm/s390/irq_injection.c @@ -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 + */ +#include +#include +#include +#include +#include +#include +#include "kvm_util.h" +#include "test_util.h" +#include + +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(); +}