diff options
Diffstat (limited to 'target-s390x')
-rw-r--r-- | target-s390x/kvm.c | 483 |
1 files changed, 483 insertions, 0 deletions
diff --git a/target-s390x/kvm.c b/target-s390x/kvm.c new file mode 100644 index 0000000000..b6aac424db --- /dev/null +++ b/target-s390x/kvm.c @@ -0,0 +1,483 @@ +/* + * QEMU S390x KVM implementation + * + * Copyright (c) 2009 Alexander Graf <agraf@suse.de> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/mman.h> + +#include <linux/kvm.h> +#include <asm/ptrace.h> + +#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) +{ + /* FIXME: add code to reset vcpu. */ +} + +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; +} |