diff options
Diffstat (limited to 'target-s390x/kvm.c')
-rw-r--r-- | target-s390x/kvm.c | 204 |
1 files changed, 201 insertions, 3 deletions
diff --git a/target-s390x/kvm.c b/target-s390x/kvm.c index c4c867c00f..127a3b8024 100644 --- a/target-s390x/kvm.c +++ b/target-s390x/kvm.c @@ -38,6 +38,7 @@ #include "qapi/qmp/qjson.h" #include "monitor/monitor.h" #include "exec/gdbstub.h" +#include "exec/address-spaces.h" #include "trace.h" #include "qapi-event.h" #include "hw/s390x/s390-pci-inst.h" @@ -1133,6 +1134,114 @@ static void sigp_start(void *arg) si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; } +static void sigp_stop(void *arg) +{ + SigpInfo *si = arg; + struct kvm_s390_irq irq = { + .type = KVM_S390_SIGP_STOP, + }; + + if (s390_cpu_get_state(si->cpu) != CPU_STATE_OPERATING) { + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; + return; + } + + /* disabled wait - sleeping in user space */ + if (CPU(si->cpu)->halted) { + s390_cpu_set_state(CPU_STATE_STOPPED, si->cpu); + } else { + /* execute the stop function */ + si->cpu->env.sigp_order = SIGP_STOP; + kvm_s390_vcpu_interrupt(si->cpu, &irq); + } + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; +} + +#define KVM_S390_STORE_STATUS_DEF_ADDR offsetof(LowCore, floating_pt_save_area) +#define SAVE_AREA_SIZE 512 +static int kvm_s390_store_status(S390CPU *cpu, hwaddr addr, bool store_arch) +{ + static const uint8_t ar_id = 1; + uint64_t ckc = cpu->env.ckc >> 8; + void *mem; + hwaddr len = SAVE_AREA_SIZE; + + mem = cpu_physical_memory_map(addr, &len, 1); + if (!mem) { + return -EFAULT; + } + if (len != SAVE_AREA_SIZE) { + cpu_physical_memory_unmap(mem, len, 1, 0); + return -EFAULT; + } + + if (store_arch) { + cpu_physical_memory_write(offsetof(LowCore, ar_access_id), &ar_id, 1); + } + memcpy(mem, &cpu->env.fregs, 128); + memcpy(mem + 128, &cpu->env.regs, 128); + memcpy(mem + 256, &cpu->env.psw, 16); + memcpy(mem + 280, &cpu->env.psa, 4); + memcpy(mem + 284, &cpu->env.fpc, 4); + memcpy(mem + 292, &cpu->env.todpr, 4); + memcpy(mem + 296, &cpu->env.cputm, 8); + memcpy(mem + 304, &ckc, 8); + memcpy(mem + 320, &cpu->env.aregs, 64); + memcpy(mem + 384, &cpu->env.cregs, 128); + + cpu_physical_memory_unmap(mem, len, 1, len); + + return 0; +} + +static void sigp_stop_and_store_status(void *arg) +{ + SigpInfo *si = arg; + struct kvm_s390_irq irq = { + .type = KVM_S390_SIGP_STOP, + }; + + /* disabled wait - sleeping in user space */ + if (s390_cpu_get_state(si->cpu) == CPU_STATE_OPERATING && + CPU(si->cpu)->halted) { + s390_cpu_set_state(CPU_STATE_STOPPED, si->cpu); + } + + switch (s390_cpu_get_state(si->cpu)) { + case CPU_STATE_OPERATING: + si->cpu->env.sigp_order = SIGP_STOP_STORE_STATUS; + kvm_s390_vcpu_interrupt(si->cpu, &irq); + /* store will be performed when handling the stop intercept */ + break; + case CPU_STATE_STOPPED: + /* already stopped, just store the status */ + cpu_synchronize_state(CPU(si->cpu)); + kvm_s390_store_status(si->cpu, KVM_S390_STORE_STATUS_DEF_ADDR, true); + break; + } + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; +} + +static void sigp_store_status_at_address(void *arg) +{ + SigpInfo *si = arg; + uint32_t address = si->param & 0x7ffffe00u; + + /* cpu has to be stopped */ + if (s390_cpu_get_state(si->cpu) != CPU_STATE_STOPPED) { + set_sigp_status(si, SIGP_STAT_INCORRECT_STATE); + return; + } + + cpu_synchronize_state(CPU(si->cpu)); + + if (kvm_s390_store_status(si->cpu, address, false)) { + set_sigp_status(si, SIGP_STAT_INVALID_PARAMETER); + return; + } + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; +} + static void sigp_restart(void *arg) { SigpInfo *si = arg; @@ -1180,6 +1289,30 @@ static void sigp_cpu_reset(void *arg) si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; } +static void sigp_set_prefix(void *arg) +{ + SigpInfo *si = arg; + uint32_t addr = si->param & 0x7fffe000u; + + cpu_synchronize_state(CPU(si->cpu)); + + if (!address_space_access_valid(&address_space_memory, addr, + sizeof(struct LowCore), false)) { + set_sigp_status(si, SIGP_STAT_INVALID_PARAMETER); + return; + } + + /* cpu has to be stopped */ + if (s390_cpu_get_state(si->cpu) != CPU_STATE_STOPPED) { + set_sigp_status(si, SIGP_STAT_INCORRECT_STATE); + return; + } + + si->cpu->env.psa = addr; + cpu_synchronize_post_init(CPU(si->cpu)); + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; +} + static int handle_sigp_single_dst(S390CPU *dst_cpu, uint8_t order, uint64_t param, uint64_t *status_reg) { @@ -1194,12 +1327,32 @@ static int handle_sigp_single_dst(S390CPU *dst_cpu, uint8_t order, return SIGP_CC_NOT_OPERATIONAL; } + /* only resets can break pending orders */ + if (dst_cpu->env.sigp_order != 0 && + order != SIGP_CPU_RESET && + order != SIGP_INITIAL_CPU_RESET) { + return SIGP_CC_BUSY; + } + switch (order) { case SIGP_START: run_on_cpu(CPU(dst_cpu), sigp_start, &si); break; + case SIGP_STOP: + run_on_cpu(CPU(dst_cpu), sigp_stop, &si); + break; case SIGP_RESTART: run_on_cpu(CPU(dst_cpu), sigp_restart, &si); + break; + case SIGP_STOP_STORE_STATUS: + run_on_cpu(CPU(dst_cpu), sigp_stop_and_store_status, &si); + break; + case SIGP_STORE_STATUS_ADDR: + run_on_cpu(CPU(dst_cpu), sigp_store_status_at_address, &si); + break; + case SIGP_SET_PREFIX: + run_on_cpu(CPU(dst_cpu), sigp_set_prefix, &si); + break; case SIGP_INITIAL_CPU_RESET: run_on_cpu(CPU(dst_cpu), sigp_initial_cpu_reset, &si); break; @@ -1214,6 +1367,48 @@ static int handle_sigp_single_dst(S390CPU *dst_cpu, uint8_t order, return si.cc; } +static int sigp_set_architecture(S390CPU *cpu, uint32_t param, + uint64_t *status_reg) +{ + CPUState *cur_cs; + S390CPU *cur_cpu; + + /* due to the BQL, we are the only active cpu */ + CPU_FOREACH(cur_cs) { + cur_cpu = S390_CPU(cur_cs); + if (cur_cpu->env.sigp_order != 0) { + return SIGP_CC_BUSY; + } + cpu_synchronize_state(cur_cs); + /* all but the current one have to be stopped */ + if (cur_cpu != cpu && + s390_cpu_get_state(cur_cpu) != CPU_STATE_STOPPED) { + *status_reg &= 0xffffffff00000000ULL; + *status_reg |= SIGP_STAT_INCORRECT_STATE; + return SIGP_CC_STATUS_STORED; + } + } + + switch (param & 0xff) { + case SIGP_MODE_ESA_S390: + /* not supported */ + return SIGP_CC_NOT_OPERATIONAL; + case SIGP_MODE_Z_ARCH_TRANS_ALL_PSW: + case SIGP_MODE_Z_ARCH_TRANS_CUR_PSW: + CPU_FOREACH(cur_cs) { + cur_cpu = S390_CPU(cur_cs); + cur_cpu->env.pfault_token = -1UL; + } + break; + default: + *status_reg &= 0xffffffff00000000ULL; + *status_reg |= SIGP_STAT_INVALID_PARAMETER; + return SIGP_CC_STATUS_STORED; + } + + return SIGP_CC_ORDER_CODE_ACCEPTED; +} + #define SIGP_ORDER_MASK 0x000000ff static int handle_sigp(S390CPU *cpu, struct kvm_run *run, uint8_t ipa1) @@ -1236,9 +1431,7 @@ static int handle_sigp(S390CPU *cpu, struct kvm_run *run, uint8_t ipa1) switch (order) { case SIGP_SET_ARCH: - *status_reg &= 0xffffffff00000000ULL; - *status_reg |= SIGP_STAT_INVALID_PARAMETER; - ret = SIGP_CC_STATUS_STORED; + ret = sigp_set_architecture(cpu, param, status_reg); break; default: /* all other sigp orders target a single vcpu */ @@ -1357,6 +1550,11 @@ static int handle_intercept(S390CPU *cpu) if (s390_cpu_set_state(CPU_STATE_STOPPED, cpu) == 0) { qemu_system_shutdown_request(); } + if (cpu->env.sigp_order == SIGP_STOP_STORE_STATUS) { + kvm_s390_store_status(cpu, KVM_S390_STORE_STATUS_DEF_ADDR, + true); + } + cpu->env.sigp_order = 0; r = EXCP_HALTED; break; case ICPT_SOFT_INTERCEPT: |