diff --git a/target-s390x/cpu.c b/target-s390x/cpu.c index d2f6312e03..e0537fa222 100644 --- a/target-s390x/cpu.c +++ b/target-s390x/cpu.c @@ -96,6 +96,7 @@ static void s390_cpu_reset(CPUState *s) env->pfault_token = -1UL; scc->parent_reset(s); + cpu->env.sigp_order = 0; s390_cpu_set_state(CPU_STATE_STOPPED, cpu); tlb_flush(s, 1); } @@ -131,6 +132,7 @@ static void s390_cpu_full_reset(CPUState *s) CPUS390XState *env = &cpu->env; scc->parent_reset(s); + cpu->env.sigp_order = 0; s390_cpu_set_state(CPU_STATE_STOPPED, cpu); memset(env, 0, offsetof(CPUS390XState, cpu_num)); diff --git a/target-s390x/cpu.h b/target-s390x/cpu.h index 1cdfe5edaf..da0af9453f 100644 --- a/target-s390x/cpu.h +++ b/target-s390x/cpu.h @@ -157,6 +157,9 @@ typedef struct CPUS390XState { #define CPU_STATE_LOAD 0x04 uint8_t cpu_state; + /* currently processed sigp order */ + uint8_t sigp_order; + } CPUS390XState; #include "cpu-qom.h" @@ -411,6 +414,10 @@ S390CPU *s390_cpu_addr2state(uint16_t cpu_addr); unsigned int s390_cpu_halt(S390CPU *cpu); void s390_cpu_unhalt(S390CPU *cpu); unsigned int s390_cpu_set_state(uint8_t cpu_state, S390CPU *cpu); +static inline uint8_t s390_cpu_get_state(S390CPU *cpu) +{ + return cpu->env.cpu_state; +} /* service interrupts are floating therefore we must not pass an cpustate */ void s390_sclp_extint(uint32_t parm); @@ -896,6 +903,11 @@ struct sysib_322 { #define SIGP_STAT_INVALID_ORDER 0x00000002UL #define SIGP_STAT_RECEIVER_CHECK 0x00000001UL +/* SIGP SET ARCHITECTURE modes */ +#define SIGP_MODE_ESA_S390 0 +#define SIGP_MODE_Z_ARCH_TRANS_ALL_PSW 1 +#define SIGP_MODE_Z_ARCH_TRANS_CUR_PSW 2 + void load_psw(CPUS390XState *env, uint64_t mask, uint64_t addr); int mmu_translate(CPUS390XState *env, target_ulong vaddr, int rw, uint64_t asc, target_ulong *raddr, int *flags, bool exc); 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: diff --git a/target-s390x/machine.c b/target-s390x/machine.c index fbcb0d0863..bd4cea726d 100644 --- a/target-s390x/machine.c +++ b/target-s390x/machine.c @@ -36,8 +36,8 @@ static int cpu_post_load(void *opaque, int version_id) const VMStateDescription vmstate_s390_cpu = { .name = "cpu", .post_load = cpu_post_load, - .version_id = 1, - .minimum_version_id = 1, + .version_id = 2, + .minimum_version_id = 2, .fields = (VMStateField[]) { VMSTATE_UINT64(env.fregs[0].ll, S390CPU), VMSTATE_UINT64(env.fregs[1].ll, S390CPU), @@ -71,6 +71,7 @@ const VMStateDescription vmstate_s390_cpu = { VMSTATE_UINT32_ARRAY(env.aregs, S390CPU, 16), VMSTATE_UINT64_ARRAY(env.cregs, S390CPU, 16), VMSTATE_UINT8(env.cpu_state, S390CPU), + VMSTATE_UINT8(env.sigp_order, S390CPU), VMSTATE_END_OF_LIST() }, };