/*
 * 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 "internal.h"
//#include "exec/address-spaces.h"
#include "exec/exec-all.h"
#include "sysemu/tcg.h"

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;

//     /* 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;

//     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)
// {
//     cpu_inject_emergency_signal(dst_cpu, src_cpu->env.core_id);
//     si->cc = SIGP_CC_ORDER_CODE_ACCEPTED;
// }

#if 0
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.
         * load_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);
    CPUS390XState *env = &cpu->env;
    SigpInfo *si = arg.host_ptr;
    uint32_t addr = si->param & 0x7fffe000u;

    // cpu_synchronize_state(cs);

    if (!address_space_access_valid(env_cpu(env)->as, 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;
}
#endif

// 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;

//     /* 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)
// {
//     /* 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)
// {
//     bool all_stopped = true;

// #if 0
//     CPU_FOREACH(cur_cs) {
//         cur_cpu = S390_CPU(cur_cs);

//         if (cur_cpu == cpu) {
//             continue;
//         }
//         if (s390_cpu_get_state(cur_cpu) != S390_CPU_STATE_STOPPED) {
//             all_stopped = false;
//         }
//     }
// #endif

//     all_stopped = false;
//     *status_reg &= 0xffffffff00000000ULL;

//     /* Reject set arch order, with czam we're always in z/Arch mode. */
//     *status_reg |= (all_stopped ? SIGP_STAT_INVALID_PARAMETER :
//                     SIGP_STAT_INCORRECT_STATE);
//     return SIGP_CC_STATUS_STORED;
// }

#if 0
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;

    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);
    }

    return ret;
}
#endif

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)
{
}
