/* * S390x DIAG instruction helper functions * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. */ #include "qemu/osdep.h" #include "cpu.h" #include "s390x-internal.h" #include "hw/watchdog/wdt_diag288.h" #include "sysemu/cpus.h" #include "hw/s390x/ipl.h" #include "hw/s390x/s390-virtio-ccw.h" #include "sysemu/kvm.h" #include "kvm/kvm_s390x.h" #include "target/s390x/kvm/pv.h" #include "qemu/error-report.h" int handle_diag_288(CPUS390XState *env, uint64_t r1, uint64_t r3) { uint64_t func = env->regs[r1]; uint64_t timeout = env->regs[r1 + 1]; uint64_t action = env->regs[r3]; Object *obj; DIAG288State *diag288; DIAG288Class *diag288_class; if (r1 % 2 || action != 0) { return -1; } /* Timeout must be more than 15 seconds except for timer deletion */ if (func != WDT_DIAG288_CANCEL && timeout < 15) { return -1; } obj = object_resolve_path_type("", TYPE_WDT_DIAG288, NULL); if (!obj) { return -1; } diag288 = DIAG288(obj); diag288_class = DIAG288_GET_CLASS(diag288); return diag288_class->handle_timer(diag288, func, timeout); } static int diag308_parm_check(CPUS390XState *env, uint64_t r1, uint64_t addr, uintptr_t ra, bool write) { /* Handled by the Ultravisor */ if (s390_is_pv()) { return 0; } if ((r1 & 1) || (addr & ~TARGET_PAGE_MASK)) { s390_program_interrupt(env, PGM_SPECIFICATION, ra); return -1; } if (!address_space_access_valid(&address_space_memory, addr, sizeof(IplParameterBlock), write, MEMTXATTRS_UNSPECIFIED)) { s390_program_interrupt(env, PGM_ADDRESSING, ra); return -1; } return 0; } void handle_diag_308(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra) { bool valid; CPUState *cs = env_cpu(env); S390CPU *cpu = env_archcpu(env); uint64_t addr = env->regs[r1]; uint64_t subcode = env->regs[r3]; IplParameterBlock *iplb; if (env->psw.mask & PSW_MASK_PSTATE) { s390_program_interrupt(env, PGM_PRIVILEGED, ra); return; } if (subcode & ~0x0ffffULL) { s390_program_interrupt(env, PGM_SPECIFICATION, ra); return; } if (subcode >= DIAG308_PV_SET && !s390_has_feat(S390_FEAT_UNPACK)) { s390_program_interrupt(env, PGM_SPECIFICATION, ra); return; } switch (subcode) { case DIAG308_RESET_MOD_CLR: s390_ipl_reset_request(cs, S390_RESET_MODIFIED_CLEAR); break; case DIAG308_RESET_LOAD_NORM: s390_ipl_reset_request(cs, S390_RESET_LOAD_NORMAL); break; case DIAG308_LOAD_CLEAR: /* Well we still lack the clearing bit... */ s390_ipl_reset_request(cs, S390_RESET_REIPL); break; case DIAG308_SET: case DIAG308_PV_SET: if (diag308_parm_check(env, r1, addr, ra, false)) { return; } iplb = g_new0(IplParameterBlock, 1); if (!s390_is_pv()) { cpu_physical_memory_read(addr, iplb, sizeof(iplb->len)); } else { s390_cpu_pv_mem_read(cpu, 0, iplb, sizeof(iplb->len)); } if (!iplb_valid_len(iplb)) { env->regs[r1 + 1] = DIAG_308_RC_INVALID; goto out; } if (!s390_is_pv()) { cpu_physical_memory_read(addr, iplb, be32_to_cpu(iplb->len)); } else { s390_cpu_pv_mem_read(cpu, 0, iplb, be32_to_cpu(iplb->len)); } valid = subcode == DIAG308_PV_SET ? iplb_valid_pv(iplb) : iplb_valid(iplb); if (!valid) { if (subcode == DIAG308_SET && iplb->pbt == S390_IPL_TYPE_QEMU_SCSI) { s390_rebuild_iplb(iplb->devno, iplb); s390_ipl_update_diag308(iplb); env->regs[r1 + 1] = DIAG_308_RC_OK; } else { env->regs[r1 + 1] = DIAG_308_RC_INVALID; } goto out; } s390_ipl_update_diag308(iplb); env->regs[r1 + 1] = DIAG_308_RC_OK; out: g_free(iplb); return; case DIAG308_STORE: case DIAG308_PV_STORE: if (diag308_parm_check(env, r1, addr, ra, true)) { return; } if (subcode == DIAG308_PV_STORE) { iplb = s390_ipl_get_iplb_pv(); } else { iplb = s390_ipl_get_iplb(); } if (!iplb) { env->regs[r1 + 1] = DIAG_308_RC_NO_CONF; return; } if (!s390_is_pv()) { cpu_physical_memory_write(addr, iplb, be32_to_cpu(iplb->len)); } else { s390_cpu_pv_mem_write(cpu, 0, iplb, be32_to_cpu(iplb->len)); } env->regs[r1 + 1] = DIAG_308_RC_OK; return; case DIAG308_PV_START: iplb = s390_ipl_get_iplb_pv(); if (!iplb) { env->regs[r1 + 1] = DIAG_308_RC_NO_PV_CONF; return; } if (kvm_enabled() && kvm_s390_get_hpage_1m()) { error_report("Protected VMs can currently not be backed with " "huge pages"); env->regs[r1 + 1] = DIAG_308_RC_INVAL_FOR_PV; return; } s390_ipl_reset_request(cs, S390_RESET_PV); break; default: s390_program_interrupt(env, PGM_SPECIFICATION, ra); break; } }