diff options
Diffstat (limited to 'target/s390x/sigp.c')
-rw-r--r-- | target/s390x/sigp.c | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/target/s390x/sigp.c b/target/s390x/sigp.c new file mode 100644 index 000000000..51c727834 --- /dev/null +++ b/target/s390x/sigp.c @@ -0,0 +1,495 @@ +/* + * s390x SIGP instruction handling + * + * Copyright (c) 2009 Alexander Graf <agraf@suse.de> + * Copyright IBM Corp. 2012 + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "cpu.h" +#include "s390x-internal.h" +#include "sysemu/hw_accel.h" +#include "sysemu/runstate.h" +#include "exec/address-spaces.h" +#include "exec/exec-all.h" +#include "sysemu/tcg.h" +#include "trace.h" +#include "qapi/qapi-types-machine.h" + +QemuMutex qemu_sigp_mutex; + +typedef struct SigpInfo { + uint64_t param; + int cc; + uint64_t *status_reg; +} SigpInfo; + +static void set_sigp_status(SigpInfo *si, uint64_t status) +{ + *si->status_reg &= 0xffffffff00000000ULL; + *si->status_reg |= status; + si->cc = SIGP_CC_STATUS_STORED; +} + +static void sigp_sense(S390CPU *dst_cpu, SigpInfo *si) +{ + uint8_t state = s390_cpu_get_state(dst_cpu); + bool ext_call = dst_cpu->env.pending_int & INTERRUPT_EXTERNAL_CALL; + uint64_t status = 0; + + if (!tcg_enabled()) { + /* handled in KVM */ + set_sigp_status(si, SIGP_STAT_INVALID_ORDER); + return; + } + + /* sensing without locks is racy, but it's the same for real hw */ + if (state != S390_CPU_STATE_STOPPED && !ext_call) { + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; + } else { + if (ext_call) { + status |= SIGP_STAT_EXT_CALL_PENDING; + } + if (state == S390_CPU_STATE_STOPPED) { + status |= SIGP_STAT_STOPPED; + } + set_sigp_status(si, status); + } +} + +static void sigp_external_call(S390CPU *src_cpu, S390CPU *dst_cpu, SigpInfo *si) +{ + int ret; + + if (!tcg_enabled()) { + /* handled in KVM */ + set_sigp_status(si, SIGP_STAT_INVALID_ORDER); + return; + } + + ret = cpu_inject_external_call(dst_cpu, src_cpu->env.core_id); + if (!ret) { + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; + } else { + set_sigp_status(si, SIGP_STAT_EXT_CALL_PENDING); + } +} + +static void sigp_emergency(S390CPU *src_cpu, S390CPU *dst_cpu, SigpInfo *si) +{ + if (!tcg_enabled()) { + /* handled in KVM */ + set_sigp_status(si, SIGP_STAT_INVALID_ORDER); + return; + } + + cpu_inject_emergency_signal(dst_cpu, src_cpu->env.core_id); + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; +} + +static void sigp_start(CPUState *cs, run_on_cpu_data arg) +{ + S390CPU *cpu = S390_CPU(cs); + SigpInfo *si = arg.host_ptr; + + if (s390_cpu_get_state(cpu) != S390_CPU_STATE_STOPPED) { + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; + return; + } + + s390_cpu_set_state(S390_CPU_STATE_OPERATING, cpu); + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; +} + +static void sigp_stop(CPUState *cs, run_on_cpu_data arg) +{ + S390CPU *cpu = S390_CPU(cs); + SigpInfo *si = arg.host_ptr; + + if (s390_cpu_get_state(cpu) != S390_CPU_STATE_OPERATING) { + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; + return; + } + + /* disabled wait - sleeping in user space */ + if (cs->halted) { + s390_cpu_set_state(S390_CPU_STATE_STOPPED, cpu); + } else { + /* execute the stop function */ + cpu->env.sigp_order = SIGP_STOP; + cpu_inject_stop(cpu); + } + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; +} + +static void sigp_stop_and_store_status(CPUState *cs, run_on_cpu_data arg) +{ + S390CPU *cpu = S390_CPU(cs); + SigpInfo *si = arg.host_ptr; + + /* disabled wait - sleeping in user space */ + if (s390_cpu_get_state(cpu) == S390_CPU_STATE_OPERATING && cs->halted) { + s390_cpu_set_state(S390_CPU_STATE_STOPPED, cpu); + } + + switch (s390_cpu_get_state(cpu)) { + case S390_CPU_STATE_OPERATING: + cpu->env.sigp_order = SIGP_STOP_STORE_STATUS; + cpu_inject_stop(cpu); + /* store will be performed in do_stop_interrup() */ + break; + case S390_CPU_STATE_STOPPED: + /* already stopped, just store the status */ + cpu_synchronize_state(cs); + s390_store_status(cpu, S390_STORE_STATUS_DEF_ADDR, true); + break; + } + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; +} + +static void sigp_store_status_at_address(CPUState *cs, run_on_cpu_data arg) +{ + S390CPU *cpu = S390_CPU(cs); + SigpInfo *si = arg.host_ptr; + uint32_t address = si->param & 0x7ffffe00u; + + /* cpu has to be stopped */ + if (s390_cpu_get_state(cpu) != S390_CPU_STATE_STOPPED) { + set_sigp_status(si, SIGP_STAT_INCORRECT_STATE); + return; + } + + cpu_synchronize_state(cs); + + if (s390_store_status(cpu, address, false)) { + set_sigp_status(si, SIGP_STAT_INVALID_PARAMETER); + return; + } + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; +} + +#define ADTL_SAVE_LC_MASK 0xfUL +static void sigp_store_adtl_status(CPUState *cs, run_on_cpu_data arg) +{ + S390CPU *cpu = S390_CPU(cs); + SigpInfo *si = arg.host_ptr; + uint8_t lc = si->param & ADTL_SAVE_LC_MASK; + hwaddr addr = si->param & ~ADTL_SAVE_LC_MASK; + hwaddr len = 1UL << (lc ? lc : 10); + + if (!s390_has_feat(S390_FEAT_VECTOR) && + !s390_has_feat(S390_FEAT_GUARDED_STORAGE)) { + set_sigp_status(si, SIGP_STAT_INVALID_ORDER); + return; + } + + /* cpu has to be stopped */ + if (s390_cpu_get_state(cpu) != S390_CPU_STATE_STOPPED) { + set_sigp_status(si, SIGP_STAT_INCORRECT_STATE); + return; + } + + /* address must be aligned to length */ + if (addr & (len - 1)) { + set_sigp_status(si, SIGP_STAT_INVALID_PARAMETER); + return; + } + + /* no GS: only lc == 0 is valid */ + if (!s390_has_feat(S390_FEAT_GUARDED_STORAGE) && + lc != 0) { + set_sigp_status(si, SIGP_STAT_INVALID_PARAMETER); + return; + } + + /* GS: 0, 10, 11, 12 are valid */ + if (s390_has_feat(S390_FEAT_GUARDED_STORAGE) && + lc != 0 && + lc != 10 && + lc != 11 && + lc != 12) { + set_sigp_status(si, SIGP_STAT_INVALID_PARAMETER); + return; + } + + cpu_synchronize_state(cs); + + if (s390_store_adtl_status(cpu, addr, len)) { + set_sigp_status(si, SIGP_STAT_INVALID_PARAMETER); + return; + } + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; +} + +static void sigp_restart(CPUState *cs, run_on_cpu_data arg) +{ + S390CPU *cpu = S390_CPU(cs); + SigpInfo *si = arg.host_ptr; + + switch (s390_cpu_get_state(cpu)) { + case S390_CPU_STATE_STOPPED: + /* the restart irq has to be delivered prior to any other pending irq */ + cpu_synchronize_state(cs); + /* + * Set OPERATING (and unhalting) before loading the restart PSW. + * s390_cpu_set_psw() will then properly halt the CPU again if + * necessary (TCG). + */ + s390_cpu_set_state(S390_CPU_STATE_OPERATING, cpu); + do_restart_interrupt(&cpu->env); + break; + case S390_CPU_STATE_OPERATING: + cpu_inject_restart(cpu); + break; + } + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; +} + +static void sigp_initial_cpu_reset(CPUState *cs, run_on_cpu_data arg) +{ + S390CPU *cpu = S390_CPU(cs); + S390CPUClass *scc = S390_CPU_GET_CLASS(cpu); + SigpInfo *si = arg.host_ptr; + + cpu_synchronize_state(cs); + scc->reset(cs, S390_CPU_RESET_INITIAL); + cpu_synchronize_post_reset(cs); + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; +} + +static void sigp_cpu_reset(CPUState *cs, run_on_cpu_data arg) +{ + S390CPU *cpu = S390_CPU(cs); + S390CPUClass *scc = S390_CPU_GET_CLASS(cpu); + SigpInfo *si = arg.host_ptr; + + cpu_synchronize_state(cs); + scc->reset(cs, S390_CPU_RESET_NORMAL); + cpu_synchronize_post_reset(cs); + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; +} + +static void sigp_set_prefix(CPUState *cs, run_on_cpu_data arg) +{ + S390CPU *cpu = S390_CPU(cs); + SigpInfo *si = arg.host_ptr; + uint32_t addr = si->param & 0x7fffe000u; + + cpu_synchronize_state(cs); + + if (!address_space_access_valid(&address_space_memory, addr, + sizeof(struct LowCore), false, + MEMTXATTRS_UNSPECIFIED)) { + set_sigp_status(si, SIGP_STAT_INVALID_PARAMETER); + return; + } + + /* cpu has to be stopped */ + if (s390_cpu_get_state(cpu) != S390_CPU_STATE_STOPPED) { + set_sigp_status(si, SIGP_STAT_INCORRECT_STATE); + return; + } + + cpu->env.psa = addr; + tlb_flush(cs); + cpu_synchronize_post_init(cs); + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; +} + +static void sigp_cond_emergency(S390CPU *src_cpu, S390CPU *dst_cpu, + SigpInfo *si) +{ + const uint64_t psw_int_mask = PSW_MASK_IO | PSW_MASK_EXT; + uint16_t p_asn, s_asn, asn; + uint64_t psw_addr, psw_mask; + bool idle; + + if (!tcg_enabled()) { + /* handled in KVM */ + set_sigp_status(si, SIGP_STAT_INVALID_ORDER); + return; + } + + /* this looks racy, but these values are only used when STOPPED */ + idle = CPU(dst_cpu)->halted; + psw_addr = dst_cpu->env.psw.addr; + psw_mask = dst_cpu->env.psw.mask; + asn = si->param; + p_asn = dst_cpu->env.cregs[4] & 0xffff; /* Primary ASN */ + s_asn = dst_cpu->env.cregs[3] & 0xffff; /* Secondary ASN */ + + if (s390_cpu_get_state(dst_cpu) != S390_CPU_STATE_STOPPED || + (psw_mask & psw_int_mask) != psw_int_mask || + (idle && psw_addr != 0) || + (!idle && (asn == p_asn || asn == s_asn))) { + cpu_inject_emergency_signal(dst_cpu, src_cpu->env.core_id); + } else { + set_sigp_status(si, SIGP_STAT_INCORRECT_STATE); + } + + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; +} + +static void sigp_sense_running(S390CPU *dst_cpu, SigpInfo *si) +{ + if (!tcg_enabled()) { + /* handled in KVM */ + set_sigp_status(si, SIGP_STAT_INVALID_ORDER); + return; + } + + /* sensing without locks is racy, but it's the same for real hw */ + if (!s390_has_feat(S390_FEAT_SENSE_RUNNING_STATUS)) { + set_sigp_status(si, SIGP_STAT_INVALID_ORDER); + return; + } + + /* If halted (which includes also STOPPED), it is not running */ + if (CPU(dst_cpu)->halted) { + set_sigp_status(si, SIGP_STAT_NOT_RUNNING); + } else { + si->cc = SIGP_CC_ORDER_CODE_ACCEPTED; + } +} + +static int handle_sigp_single_dst(S390CPU *cpu, S390CPU *dst_cpu, uint8_t order, + uint64_t param, uint64_t *status_reg) +{ + SigpInfo si = { + .param = param, + .status_reg = status_reg, + }; + + /* cpu available? */ + if (dst_cpu == NULL) { + 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_SENSE: + sigp_sense(dst_cpu, &si); + break; + case SIGP_EXTERNAL_CALL: + sigp_external_call(cpu, dst_cpu, &si); + break; + case SIGP_EMERGENCY: + sigp_emergency(cpu, dst_cpu, &si); + break; + case SIGP_START: + run_on_cpu(CPU(dst_cpu), sigp_start, RUN_ON_CPU_HOST_PTR(&si)); + break; + case SIGP_STOP: + run_on_cpu(CPU(dst_cpu), sigp_stop, RUN_ON_CPU_HOST_PTR(&si)); + break; + case SIGP_RESTART: + run_on_cpu(CPU(dst_cpu), sigp_restart, RUN_ON_CPU_HOST_PTR(&si)); + break; + case SIGP_STOP_STORE_STATUS: + run_on_cpu(CPU(dst_cpu), sigp_stop_and_store_status, RUN_ON_CPU_HOST_PTR(&si)); + break; + case SIGP_STORE_STATUS_ADDR: + run_on_cpu(CPU(dst_cpu), sigp_store_status_at_address, RUN_ON_CPU_HOST_PTR(&si)); + break; + case SIGP_STORE_ADTL_STATUS: + run_on_cpu(CPU(dst_cpu), sigp_store_adtl_status, RUN_ON_CPU_HOST_PTR(&si)); + break; + case SIGP_SET_PREFIX: + run_on_cpu(CPU(dst_cpu), sigp_set_prefix, RUN_ON_CPU_HOST_PTR(&si)); + break; + case SIGP_INITIAL_CPU_RESET: + run_on_cpu(CPU(dst_cpu), sigp_initial_cpu_reset, RUN_ON_CPU_HOST_PTR(&si)); + break; + case SIGP_CPU_RESET: + run_on_cpu(CPU(dst_cpu), sigp_cpu_reset, RUN_ON_CPU_HOST_PTR(&si)); + break; + case SIGP_COND_EMERGENCY: + sigp_cond_emergency(cpu, dst_cpu, &si); + break; + case SIGP_SENSE_RUNNING: + sigp_sense_running(dst_cpu, &si); + break; + default: + set_sigp_status(&si, SIGP_STAT_INVALID_ORDER); + } + + return si.cc; +} + +static int sigp_set_architecture(S390CPU *cpu, uint32_t param, + uint64_t *status_reg) +{ + *status_reg &= 0xffffffff00000000ULL; + + /* Reject set arch order, with czam we're always in z/Arch mode. */ + *status_reg |= SIGP_STAT_INVALID_PARAMETER; + return SIGP_CC_STATUS_STORED; +} + +int handle_sigp(CPUS390XState *env, uint8_t order, uint64_t r1, uint64_t r3) +{ + uint64_t *status_reg = &env->regs[r1]; + uint64_t param = (r1 % 2) ? env->regs[r1] : env->regs[r1 + 1]; + S390CPU *cpu = env_archcpu(env); + S390CPU *dst_cpu = NULL; + int ret; + + if (qemu_mutex_trylock(&qemu_sigp_mutex)) { + ret = SIGP_CC_BUSY; + goto out; + } + + switch (order) { + case SIGP_SET_ARCH: + ret = sigp_set_architecture(cpu, param, status_reg); + break; + default: + /* all other sigp orders target a single vcpu */ + dst_cpu = s390_cpu_addr2state(env->regs[r3]); + ret = handle_sigp_single_dst(cpu, dst_cpu, order, param, status_reg); + } + qemu_mutex_unlock(&qemu_sigp_mutex); + +out: + trace_sigp_finished(order, CPU(cpu)->cpu_index, + dst_cpu ? CPU(dst_cpu)->cpu_index : -1, ret); + g_assert(ret >= 0); + + return ret; +} + +int s390_cpu_restart(S390CPU *cpu) +{ + SigpInfo si = {}; + + run_on_cpu(CPU(cpu), sigp_restart, RUN_ON_CPU_HOST_PTR(&si)); + return 0; +} + +void do_stop_interrupt(CPUS390XState *env) +{ + S390CPU *cpu = env_archcpu(env); + + if (s390_cpu_set_state(S390_CPU_STATE_STOPPED, cpu) == 0) { + qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN); + } + if (cpu->env.sigp_order == SIGP_STOP_STORE_STATUS) { + s390_store_status(cpu, S390_STORE_STATUS_DEF_ADDR, true); + } + env->sigp_order = 0; + env->pending_int &= ~INTERRUPT_STOP; +} + +void s390_init_sigp(void) +{ + qemu_mutex_init(&qemu_sigp_mutex); +} |