cf7f61d13f
Convert the s390 CPU to the Resettable interface. This is slightly more involved than the other CPU types were (see commits 9130cade5fc22..d66e64dd006df) because S390 has its own set of different kinds of reset with different behaviours that it needs to trigger. We handle this by adding these reset types to the Resettable ResetType enum. Now instead of having an underlying implementation of reset that is s390-specific and which might be called either directly or via the DeviceClass::reset method, we can implement only the Resettable hold phase method, and have the places that need to trigger an s390-specific reset type do so by calling resettable_reset(). The other option would have been to smuggle in the s390 reset type via, for instance, a field in the CPU state that we set in s390_do_cpu_initial_reset() etc and then examined in the reset method, but doing it this way seems cleaner. The motivation for this change is that this is the last caller of the legacy device_class_set_parent_reset() function, and removing that will let us clean up some glue code that we added for the transition to three-phase reset. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Nina Schoetterl-Glausch <nsg@linux.ibm.com> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Acked-by: Thomas Huth <thuth@redhat.com> Message-id: 20240830145812.1967042-4-peter.maydell@linaro.org
513 lines
14 KiB
C
513 lines
14 KiB
C
/*
|
|
* 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 "hw/boards.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_interrupt() */
|
|
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)
|
|
{
|
|
SigpInfo *si = arg.host_ptr;
|
|
|
|
cpu_synchronize_state(cs);
|
|
resettable_reset(OBJECT(cs), RESET_TYPE_S390_CPU_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)
|
|
{
|
|
SigpInfo *si = arg.host_ptr;
|
|
|
|
cpu_synchronize_state(cs);
|
|
resettable_reset(OBJECT(cs), RESET_TYPE_S390_CPU_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;
|
|
}
|
|
|
|
S390CPU *s390_cpu_addr2state(uint16_t cpu_addr)
|
|
{
|
|
static MachineState *ms;
|
|
|
|
if (!ms) {
|
|
ms = MACHINE(qdev_get_machine());
|
|
g_assert(ms->possible_cpus);
|
|
}
|
|
|
|
/* CPU address corresponds to the core_id and the index */
|
|
if (cpu_addr >= ms->possible_cpus->len) {
|
|
return NULL;
|
|
}
|
|
return S390_CPU(ms->possible_cpus->cpus[cpu_addr].cpu);
|
|
}
|
|
|
|
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);
|
|
|
|
/*
|
|
* Complete the STOP operation before exposing the CPU as
|
|
* STOPPED to the system.
|
|
*/
|
|
if (cpu->env.sigp_order == SIGP_STOP_STORE_STATUS) {
|
|
s390_store_status(cpu, S390_STORE_STATUS_DEF_ADDR, true);
|
|
}
|
|
env->sigp_order = 0;
|
|
if (s390_cpu_set_state(S390_CPU_STATE_STOPPED, cpu) == 0) {
|
|
qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
|
|
}
|
|
env->pending_int &= ~INTERRUPT_STOP;
|
|
}
|
|
|
|
void s390_init_sigp(void)
|
|
{
|
|
qemu_mutex_init(&qemu_sigp_mutex);
|
|
}
|