mirror of
https://github.com/qemu/qemu.git
synced 2024-11-28 06:13:46 +08:00
359507eed1
During Jan's rework of the generic KVM layer, he added some more error checks and actually aborted if something went wrong. Unfortunately, one of the s390 internal error codes slipped through, aborting the VM without needing to. This patch fixes booting of S390x virtual machines in KVM. Signed-off-by: Alexander Graf <agraf@suse.de> CC: Jan Kiszka <jan.kiszka@siemens.com> Signed-off-by: Aurelien Jarno <aurelien@aurel32.net>
522 lines
12 KiB
C
522 lines
12 KiB
C
/*
|
|
* 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
|
|
|
|
const KVMCapabilityInfo kvm_arch_required_capabilities[] = {
|
|
KVM_CAP_LAST_INFO
|
|
};
|
|
|
|
int kvm_arch_init(KVMState *s)
|
|
{
|
|
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, int level)
|
|
{
|
|
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)
|
|
{
|
|
int 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;
|
|
}
|
|
|
|
void kvm_arch_pre_run(CPUState *env, struct kvm_run *run)
|
|
{
|
|
}
|
|
|
|
void kvm_arch_post_run(CPUState *env, struct kvm_run *run)
|
|
{
|
|
}
|
|
|
|
int kvm_arch_process_async_events(CPUState *env)
|
|
{
|
|
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 = -1;
|
|
qemu_cpu_kick(env);
|
|
|
|
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);
|
|
|
|
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 = -1;
|
|
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)
|
|
{
|
|
int i;
|
|
|
|
if (kvm_vcpu_ioctl(env, KVM_S390_INITIAL_RESET, NULL) < 0) {
|
|
perror("cannot init reset vcpu");
|
|
}
|
|
|
|
/* Manually zero out all registers */
|
|
cpu_synchronize_state(env);
|
|
for (i = 0; i < 16; i++) {
|
|
env->regs[i] = 0;
|
|
}
|
|
|
|
dprintf("DONE: SIGP initial reset: %p\n", env);
|
|
return 0;
|
|
}
|
|
|
|
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 = -1;
|
|
|
|
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 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (ret == 0) {
|
|
ret = EXCP_INTERRUPT;
|
|
} else if (ret > 0) {
|
|
ret = 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool kvm_arch_stop_on_emulation_error(CPUState *env)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int kvm_arch_on_sigbus_vcpu(CPUState *env, int code, void *addr)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
int kvm_arch_on_sigbus(int code, void *addr)
|
|
{
|
|
return 1;
|
|
}
|