From patchwork Sat Dec 5 11:44:24 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Graf X-Patchwork-Id: 40387 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 40ED8B7BDF for ; Sat, 5 Dec 2009 22:50:09 +1100 (EST) Received: from localhost ([127.0.0.1]:54911 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1NGt9J-0006JQ-Rd for incoming@patchwork.ozlabs.org; Sat, 05 Dec 2009 06:50:05 -0500 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1NGt45-00048O-OG for qemu-devel@nongnu.org; Sat, 05 Dec 2009 06:44:41 -0500 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1NGt3z-00044O-33 for qemu-devel@nongnu.org; Sat, 05 Dec 2009 06:44:40 -0500 Received: from [199.232.76.173] (port=49815 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1NGt3y-00043u-JT for qemu-devel@nongnu.org; Sat, 05 Dec 2009 06:44:34 -0500 Received: from cantor2.suse.de ([195.135.220.15]:36011 helo=mx2.suse.de) by monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1NGt3x-0008G4-AZ for qemu-devel@nongnu.org; Sat, 05 Dec 2009 06:44:33 -0500 Received: from relay1.suse.de (charybdis-ext.suse.de [195.135.221.2]) by mx2.suse.de (Postfix) with ESMTP id 1F2ED8765C; Sat, 5 Dec 2009 12:44:32 +0100 (CET) From: Alexander Graf To: qemu-devel@nongnu.org Date: Sat, 5 Dec 2009 12:44:24 +0100 Message-Id: <1260013471-18691-5-git-send-email-agraf@suse.de> X-Mailer: git-send-email 1.6.0.2 In-Reply-To: <1260013471-18691-1-git-send-email-agraf@suse.de> References: <1260013471-18691-1-git-send-email-agraf@suse.de> X-detected-operating-system: by monty-python.gnu.org: GNU/Linux 2.4-2.6 Cc: Carsten Otte , Aurelien Jarno Subject: [Qemu-devel] [PATCH 04/11] Add KVM support for S390x X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org S390x was one of the first platforms that received support for KVM back in the day. Unfortunately until now there hasn't been a qemu implementation that would enable users to actually run guests. So let's include support for KVM S390x in qemu! Signed-off-by: Alexander Graf --- v5 -> v6: - use constants for sclp commands (kvm.c) - change _kvm... to kvm_..._internal - coding style --- configure | 4 +- target-s390x/kvm.c | 482 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 485 insertions(+), 1 deletions(-) create mode 100644 target-s390x/kvm.c diff --git a/configure b/configure index 77c3ec1..b944e49 100755 --- a/configure +++ b/configure @@ -1377,6 +1377,8 @@ EOF kvm_cflags="$kvm_cflags -I$kerneldir/arch/x86/include" elif test "$cpu" = "ppc" -a -d "$kerneldir/arch/powerpc/include" ; then kvm_cflags="$kvm_cflags -I$kerneldir/arch/powerpc/include" + elif test "$cpu" = "s390x" -a -d "$kerneldir/arch/s390/include" ; then + kvm_cflags="$kvm_cflags -I$kerneldir/arch/s390/include" elif test -d "$kerneldir/arch/$cpu/include" ; then kvm_cflags="$kvm_cflags -I$kerneldir/arch/$cpu/include" fi @@ -2393,7 +2395,7 @@ case "$target_arch2" in fi esac case "$target_arch2" in - i386|x86_64|ppcemb|ppc|ppc64) + i386|x86_64|ppcemb|ppc|ppc64|s390x) # Make sure the target and host cpus are compatible if test "$kvm" = "yes" -a "$target_softmmu" = "yes" -a \ \( "$target_arch2" = "$cpu" -o \ diff --git a/target-s390x/kvm.c b/target-s390x/kvm.c new file mode 100644 index 0000000..da109fd --- /dev/null +++ b/target-s390x/kvm.c @@ -0,0 +1,482 @@ +/* + * QEMU S390x KVM implementation + * + * Copyright (c) 2009 Alexander Graf + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include +#include + +#include +#include + +#include "qemu-common.h" +#include "qemu-timer.h" +#include "sysemu.h" +#include "kvm.h" +#include "cpu.h" +#include "device_tree.h" + +/* #define DEBUG_KVM */ + +#ifdef DEBUG_KVM +#define dprintf(fmt, ...) \ + do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) +#else +#define dprintf(fmt, ...) \ + do { } while (0) +#endif + +#define IPA0_DIAG 0x8300 +#define IPA0_SIGP 0xae00 +#define IPA0_PRIV 0xb200 + +#define PRIV_SCLP_CALL 0x20 +#define DIAG_KVM_HYPERCALL 0x500 +#define DIAG_KVM_BREAKPOINT 0x501 + +#define SCP_LENGTH 0x00 +#define SCP_FUNCTION_CODE 0x02 +#define SCP_CONTROL_MASK 0x03 +#define SCP_RESPONSE_CODE 0x06 +#define SCP_MEM_CODE 0x08 +#define SCP_INCREMENT 0x0a + +#define ICPT_INSTRUCTION 0x04 +#define ICPT_WAITPSW 0x1c +#define ICPT_SOFT_INTERCEPT 0x24 +#define ICPT_CPU_STOP 0x28 +#define ICPT_IO 0x40 + +#define SIGP_RESTART 0x06 +#define SIGP_INITIAL_CPU_RESET 0x0b +#define SIGP_STORE_STATUS_ADDR 0x0e +#define SIGP_SET_ARCH 0x12 + +#define SCLP_CMDW_READ_SCP_INFO 0x00020001 +#define SCLP_CMDW_READ_SCP_INFO_FORCED 0x00120001 + +int kvm_arch_init(KVMState *s, int smp_cpus) +{ + return 0; +} + +int kvm_arch_init_vcpu(CPUState *env) +{ + int ret = 0; + + if (kvm_vcpu_ioctl(env, KVM_S390_INITIAL_RESET, NULL) < 0) { + perror("cannot init reset vcpu"); + } + + return ret; +} + +void kvm_arch_reset_vcpu(CPUState *env) +{ +} + +int kvm_arch_put_registers(CPUState *env) +{ + struct kvm_regs regs; + int ret; + int i; + + ret = kvm_vcpu_ioctl(env, KVM_GET_REGS, ®s); + if (ret < 0) { + return ret; + } + + for (i = 0; i < 16; i++) { + regs.gprs[i] = env->regs[i]; + } + + ret = kvm_vcpu_ioctl(env, KVM_SET_REGS, ®s); + if (ret < 0) { + return ret; + } + + env->kvm_run->psw_addr = env->psw.addr; + env->kvm_run->psw_mask = env->psw.mask; + + return ret; +} + +int kvm_arch_get_registers(CPUState *env) +{ + uint32_t ret; + struct kvm_regs regs; + int i; + + ret = kvm_vcpu_ioctl(env, KVM_GET_REGS, ®s); + if (ret < 0) { + return ret; + } + + for (i = 0; i < 16; i++) { + env->regs[i] = regs.gprs[i]; + } + + env->psw.addr = env->kvm_run->psw_addr; + env->psw.mask = env->kvm_run->psw_mask; + + return 0; +} + +int kvm_arch_insert_sw_breakpoint(CPUState *env, struct kvm_sw_breakpoint *bp) +{ + static const uint8_t diag_501[] = {0x83, 0x24, 0x05, 0x01}; + + if (cpu_memory_rw_debug(env, bp->pc, (uint8_t *)&bp->saved_insn, 4, 0) || + cpu_memory_rw_debug(env, bp->pc, (uint8_t *)diag_501, 4, 1)) { + return -EINVAL; + } + return 0; +} + +int kvm_arch_remove_sw_breakpoint(CPUState *env, struct kvm_sw_breakpoint *bp) +{ + uint8_t t[4]; + static const uint8_t diag_501[] = {0x83, 0x24, 0x05, 0x01}; + + if (cpu_memory_rw_debug(env, bp->pc, t, 4, 0)) { + return -EINVAL; + } else if (memcmp(t, diag_501, 4)) { + return -EINVAL; + } else if (cpu_memory_rw_debug(env, bp->pc, (uint8_t *)&bp->saved_insn, 1, 1)) { + return -EINVAL; + } + + return 0; +} + +int kvm_arch_pre_run(CPUState *env, struct kvm_run *run) +{ + return 0; +} + +int kvm_arch_post_run(CPUState *env, struct kvm_run *run) +{ + return 0; +} + +static void kvm_s390_interrupt_internal(CPUState *env, int type, uint32_t parm, + uint64_t parm64, int vm) +{ + struct kvm_s390_interrupt kvmint; + int r; + + if (!env->kvm_state) { + return; + } + + env->halted = 0; + env->exception_index = 0; + + kvmint.type = type; + kvmint.parm = parm; + kvmint.parm64 = parm64; + + if (vm) { + r = kvm_vm_ioctl(env->kvm_state, KVM_S390_INTERRUPT, &kvmint); + } else { + r = kvm_vcpu_ioctl(env, KVM_S390_INTERRUPT, &kvmint); + } + + if (r < 0) { + fprintf(stderr, "KVM failed to inject interrupt\n"); + exit(1); + } +} + +void kvm_s390_virtio_irq(CPUState *env, int config_change, uint64_t token) +{ + kvm_s390_interrupt_internal(env, KVM_S390_INT_VIRTIO, config_change, + token, 1); +} + +static void kvm_s390_interrupt(CPUState *env, int type, uint32_t code) +{ + kvm_s390_interrupt_internal(env, type, code, 0, 0); +} + +static void enter_pgmcheck(CPUState *env, uint16_t code) +{ + kvm_s390_interrupt(env, KVM_S390_PROGRAM_INT, code); +} + +static void setcc(CPUState *env, uint64_t cc) +{ + env->kvm_run->psw_mask &= ~(3ul << 44); + env->kvm_run->psw_mask |= (cc & 3) << 44; + + env->psw.mask &= ~(3ul << 44); + env->psw.mask |= (cc & 3) << 44; +} + +static int sclp_service_call(CPUState *env, struct kvm_run *run, uint16_t ipbh0) +{ + uint32_t sccb; + uint64_t code; + int r = 0; + + cpu_synchronize_state(env); + sccb = env->regs[ipbh0 & 0xf]; + code = env->regs[(ipbh0 & 0xf0) >> 4]; + + dprintf("sclp(0x%x, 0x%lx)\n", sccb, code); + + if (sccb & ~0x7ffffff8ul) { + fprintf(stderr, "KVM: invalid sccb address 0x%x\n", sccb); + r = -1; + goto out; + } + + switch(code) { + case SCLP_CMDW_READ_SCP_INFO: + case SCLP_CMDW_READ_SCP_INFO_FORCED: + stw_phys(sccb + SCP_MEM_CODE, ram_size >> 20); + stb_phys(sccb + SCP_INCREMENT, 1); + stw_phys(sccb + SCP_RESPONSE_CODE, 0x10); + setcc(env, 0); + + kvm_s390_interrupt_internal(env, KVM_S390_INT_SERVICE, + sccb & ~3, 0, 1); + break; + default: + dprintf("KVM: invalid sclp call 0x%x / 0x%lx\n", sccb, code); + r = -1; + break; + } + +out: + if (r < 0) { + setcc(env, 3); + } + return 0; +} + +static int handle_priv(CPUState *env, struct kvm_run *run, uint8_t ipa1) +{ + int r = 0; + uint16_t ipbh0 = (run->s390_sieic.ipb & 0xffff0000) >> 16; + + dprintf("KVM: PRIV: %d\n", ipa1); + switch (ipa1) { + case PRIV_SCLP_CALL: + r = sclp_service_call(env, run, ipbh0); + break; + default: + dprintf("KVM: unknown PRIV: 0x%x\n", ipa1); + r = -1; + break; + } + + return r; +} + +static int handle_hypercall(CPUState *env, struct kvm_run *run) +{ + int r; + + cpu_synchronize_state(env); + r = s390_virtio_hypercall(env); + kvm_arch_put_registers(env); + + return r; +} + +static int handle_diag(CPUState *env, struct kvm_run *run, int ipb_code) +{ + int r = 0; + + switch (ipb_code) { + case DIAG_KVM_HYPERCALL: + r = handle_hypercall(env, run); + break; + case DIAG_KVM_BREAKPOINT: + sleep(10); + break; + default: + dprintf("KVM: unknown DIAG: 0x%x\n", ipb_code); + r = -1; + break; + } + + return r; +} + +static int s390_cpu_restart(CPUState *env) +{ + kvm_s390_interrupt(env, KVM_S390_RESTART, 0); + env->halted = 0; + env->exception_index = 0; + qemu_cpu_kick(env); + dprintf("DONE: SIGP cpu restart: %p\n", env); + return 0; +} + +static int s390_store_status(CPUState *env, uint32_t parameter) +{ + /* XXX */ + fprintf(stderr, "XXX SIGP store status\n"); + return -1; +} + +static int s390_cpu_initial_reset(CPUState *env) +{ + /* XXX */ + fprintf(stderr, "XXX SIGP init\n"); + return -1; +} + +static int handle_sigp(CPUState *env, struct kvm_run *run, uint8_t ipa1) +{ + uint8_t order_code; + uint32_t parameter; + uint16_t cpu_addr; + uint8_t t; + int r = -1; + CPUState *target_env; + + cpu_synchronize_state(env); + + /* get order code */ + order_code = run->s390_sieic.ipb >> 28; + if (order_code > 0) { + order_code = env->regs[order_code]; + } + order_code += (run->s390_sieic.ipb & 0x0fff0000) >> 16; + + /* get parameters */ + t = (ipa1 & 0xf0) >> 4; + if (!(t % 2)) { + t++; + } + + parameter = env->regs[t] & 0x7ffffe00; + cpu_addr = env->regs[ipa1 & 0x0f]; + + target_env = s390_cpu_addr2state(cpu_addr); + if (!target_env) { + goto out; + } + + switch (order_code) { + case SIGP_RESTART: + r = s390_cpu_restart(target_env); + break; + case SIGP_STORE_STATUS_ADDR: + r = s390_store_status(target_env, parameter); + break; + case SIGP_SET_ARCH: + /* make the caller panic */ + return -1; + case SIGP_INITIAL_CPU_RESET: + r = s390_cpu_initial_reset(target_env); + break; + default: + fprintf(stderr, "KVM: unknown SIGP: 0x%x\n", ipa1); + break; + } + +out: + setcc(env, r ? 3 : 0); + return 0; +} + +static int handle_instruction(CPUState *env, struct kvm_run *run) +{ + unsigned int ipa0 = (run->s390_sieic.ipa & 0xff00); + uint8_t ipa1 = run->s390_sieic.ipa & 0x00ff; + int ipb_code = (run->s390_sieic.ipb & 0x0fff0000) >> 16; + int r = 0; + + dprintf("handle_instruction 0x%x 0x%x\n", run->s390_sieic.ipa, run->s390_sieic.ipb); + switch (ipa0) { + case IPA0_PRIV: + r = handle_priv(env, run, ipa1); + break; + case IPA0_DIAG: + r = handle_diag(env, run, ipb_code); + break; + case IPA0_SIGP: + r = handle_sigp(env, run, ipa1); + break; + } + + if (r < 0) { + enter_pgmcheck(env, 0x0001); + } + return r; +} + +static int handle_intercept(CPUState *env) +{ + struct kvm_run *run = env->kvm_run; + int icpt_code = run->s390_sieic.icptcode; + int r = 0; + + dprintf("intercept: 0x%x (at 0x%lx)\n", icpt_code, env->kvm_run->psw_addr); + switch (icpt_code) { + case ICPT_INSTRUCTION: + r = handle_instruction(env, run); + break; + case ICPT_WAITPSW: + /* XXX What to do on system shutdown? */ + env->halted = 1; + env->exception_index = EXCP_HLT; + break; + case ICPT_SOFT_INTERCEPT: + fprintf(stderr, "KVM unimplemented icpt SOFT\n"); + exit(1); + break; + case ICPT_CPU_STOP: + qemu_system_shutdown_request(); + break; + case ICPT_IO: + fprintf(stderr, "KVM unimplemented icpt IO\n"); + exit(1); + break; + default: + fprintf(stderr, "Unknown intercept code: %d\n", icpt_code); + exit(1); + break; + } + + return r; +} + +int kvm_arch_handle_exit(CPUState *env, struct kvm_run *run) +{ + int ret = 0; + + switch (run->exit_reason) { + case KVM_EXIT_S390_SIEIC: + ret = handle_intercept(env); + break; + case KVM_EXIT_S390_RESET: + fprintf(stderr, "RESET not implemented\n"); + exit(1); + break; + default: + fprintf(stderr, "Unknown KVM exit: %d\n", run->exit_reason); + break; + } + + return ret; +}