#ifndef _FINALROM

#include "PRinternal/dbgproto.h"
#include "PR/os_internal.h"
#include "PR/rcp.h"
#include "PR/sptask.h"
#include "PRinternal/rmonint.h"
#include "PR/rdb.h"
#include "PR/os_version.h"

#include "PRinternal/macros.h"

// TODO: these come from headers
#if BUILD_VERSION >= VERSION_K
#ident "$Revision: 1.4 $"
// This revision was bumped down at K for some reason
#ident "$Revision: 3.70 $"
#ident "$Revision: 1.5 $"
#ident "$Revision: 1.2 $"
#ident "$Revision: 1.4 $"
#elif BUILD_VERSION >= VERSION_J
#ident "$Revision: 1.4 $"
#ident "$Revision: 3.71 $"
#ident "$Revision: 1.5 $"
#ident "$Revision: 1.2 $"
#ident "$Revision: 1.4 $"
#else
#ident "$Revision: 3.70 $"
#ident "$Revision: 1.5 $"
#ident "$Revision: 1.2 $"
#ident "$Revision: 1.4 $"
#ident "$Revision: 1.3 $"
#endif

static u32 RCPpc;
static u32 oldIMEMvalue;
static u32 DMEMbuffer[4] ALIGNED(0x8);

typedef union {
    u32 everything;
    struct {
        int opcode : 6;
        int base   : 5;
        int rt     : 5;
        int offset : 16;
    } scalarop;
    struct {
        int opcode  : 6;
        int base    : 5;
        int rt      : 5;
        int size    : 5;
        int element : 4;
        int offset  : 7;
    } vectorop;
} INSTRUCTION;

static void LoadStoreSU(int opcode, int regno) {
    INSTRUCTION inst;

    /* Prepare a scalar load or store instruction at DMEM address 0 */
    inst.everything = 0;
    inst.scalarop.opcode = opcode;
    inst.scalarop.rt = regno;
    __rmonWriteWordTo((u32*)SP_IMEM_START, inst.everything);
    __rmonWriteWordTo((u32*)SP_PC_REG, 0);
}

static void LoadStoreVU(int opcode, int regno) {
    INSTRUCTION inst;

    /* Prepare a vector 128-bit load or store instruction at DMEM address 0 */
    inst.everything = 0;
    inst.vectorop.opcode = opcode;
    inst.vectorop.rt = regno;
    inst.vectorop.size = 4; /* LQV / SQV */
    __rmonWriteWordTo((u32*)SP_IMEM_START, inst.everything);
    __rmonWriteWordTo((u32*)SP_PC_REG, 0);
}

static void SetUpForRCPop(int isVector) {
    /* Save RSP data that would be overwritten when reading or writing registers */
    RCPpc = __rmonReadWordAt((u32*)SP_PC_REG);
    oldIMEMvalue = __rmonReadWordAt((u32*)SP_IMEM_START);
    DMEMbuffer[0] = __rmonReadWordAt((u32*)SP_DMEM_START);
    if (isVector) {
        DMEMbuffer[1] = __rmonReadWordAt((u32*)(SP_DMEM_START + 0x4));
        DMEMbuffer[2] = __rmonReadWordAt((u32*)(SP_DMEM_START + 0x8));
        DMEMbuffer[3] = __rmonReadWordAt((u32*)(SP_DMEM_START + 0xC));
    }
}

static void CleanupFromRCPop(int isVector) {
    /* Restore RSP data that was saved to read or write registers */
    __rmonWriteWordTo((u32*)SP_DMEM_START, DMEMbuffer[0]);
    if (isVector) {
        __rmonWriteWordTo((u32*)(SP_DMEM_START + 0x4), DMEMbuffer[1]);
        __rmonWriteWordTo((u32*)(SP_DMEM_START + 0x8), DMEMbuffer[2]);
        /* BUG: the last word is not restored properly */
        __rmonWriteWordTo((u32*)(SP_DMEM_START + 0xC), DMEMbuffer[2]);
    }
    __rmonWriteWordTo((u32*)SP_IMEM_START, oldIMEMvalue);
    __rmonWriteWordTo((u32*)SP_PC_REG, RCPpc);
}

int __rmonGetGRegisters(KKHeader* req) {
    register KKObjectRequest* request = (KKObjectRequest*)req;
    KKGregEvent reply;

    STUBBED_PRINTF(("GetGRegisters\n"));

    reply.tid = request->object;
    reply.header.code = request->header.code;
    reply.header.error = TV_ERROR_NO_ERROR;

    if (request->header.method == RMON_CPU) {
        OSThread* tptr = __rmonGetTCB(request->object);
        u64* tcbregptr;
        register s32 i;

        if (tptr == NULL) {
            return TV_ERROR_INVALID_ID;
        }

        for (i = GREG_IDX_AT, tcbregptr = &tptr->context.at; i < GREG_IDX_K0; i++, tcbregptr++) {
            reply.registers.gregs[i] = *tcbregptr;
        }
        for (i = GREG_IDX_GP, tcbregptr = &tptr->context.gp; i < GREG_IDX_CAUSE; i++, tcbregptr++) {
            reply.registers.gregs[i] = *tcbregptr;
        }

        reply.registers.gregs[GREG_IDX_CAUSE] = tptr->context.cause;
        reply.registers.gregs[GREG_IDX_PC] = tptr->context.pc;
        reply.registers.gregs[GREG_IDX_SR] = tptr->context.sr;
        reply.registers.gregs[GREG_IDX_ZERO] = 0;
    } else {
        return TV_ERROR_INVALID_ID;
    }

    __rmonSendReply(&reply.header, sizeof(reply), KK_TYPE_REPLY);
    return TV_ERROR_NO_ERROR;
}

int __rmonSetGRegisters(KKHeader* req) {
    register KKGRegsetRequest* request = (KKGRegsetRequest*)req;
    KKObjectEvent reply;

    STUBBED_PRINTF(("SetGRegisters\n"));

    if (request->header.method == RMON_CPU) {
        OSThread* tptr = __rmonGetTCB(request->tid);
        u64* tcbregptr;
        register int i;

        if (tptr == NULL) {
            return TV_ERROR_INVALID_ID;
        }

        for (i = GREG_IDX_AT, tcbregptr = &tptr->context.at; i < GREG_IDX_K0; i++, tcbregptr++) {
            *tcbregptr = (s32)request->registers.gregs[i];
        }

        for (i = GREG_IDX_GP, tcbregptr = &tptr->context.gp; i < GREG_IDX_CAUSE; i++, tcbregptr++) {
            *tcbregptr = (s32)request->registers.gregs[i];
        }

        tptr->context.cause = request->registers.gregs[GREG_IDX_CAUSE];
        tptr->context.pc = request->registers.gregs[GREG_IDX_PC];
        tptr->context.sr = request->registers.gregs[GREG_IDX_SR];
    } else {
        return TV_ERROR_INVALID_ID;
    }

    reply.object = request->tid;
    reply.header.code = request->header.code;
    reply.header.error = TV_ERROR_NO_ERROR;
    __rmonSendReply(&reply.header, sizeof(reply), KK_TYPE_REPLY);
    return TV_ERROR_NO_ERROR;
}

int __rmonGetFRegisters(KKHeader* req) {
    register KKObjectRequest* request = (KKObjectRequest*)req;
    KKFPregEvent reply;
    OSThread* tptr;
    volatile float f UNUSED;

    STUBBED_PRINTF(("GetFRegisters\n"));

    if (req->method != RMON_CPU) {
        return TV_ERROR_INVALID_ID;
    }

    /* touch fpu to ensure registers are saved to the context structure */
    f = 0.0f;

    tptr = __rmonGetTCB(request->object);
    if (tptr == NULL) {
        return TV_ERROR_INVALID_ID;
    }

    __rmonCopyWords((u32*)reply.registers.fpregs.regs, (u32*)&tptr->context.fp0, ARRLEN(reply.registers.fpregs.regs));

    reply.registers.fpcsr = tptr->context.fpcsr;
    reply.header.code = request->header.code;
    reply.header.error = TV_ERROR_NO_ERROR;
    reply.tid = request->object;

    __rmonSendReply(&reply.header, sizeof(reply), KK_TYPE_REPLY);
    return TV_ERROR_NO_ERROR;
}

int __rmonSetFRegisters(KKHeader* req) {
    register KKFPRegsetRequest* request = (KKFPRegsetRequest*)req;
    KKObjectEvent reply;
    OSThread* tptr;
    volatile float f UNUSED;

    STUBBED_PRINTF(("SetFRegisters\n"));

    if (req->method != RMON_CPU) {
        return TV_ERROR_INVALID_ID;
    }

    /* touch fpu to ensure registers are saved to the context structure */
    f = 0.0f;

    tptr = __rmonGetTCB(request->tid);
    if (tptr == NULL) {
        return TV_ERROR_INVALID_ID;
    }

    __rmonCopyWords((u32*)&tptr->context.fp0, (u32*)request->registers.fpregs.regs,
                    ARRLEN(request->registers.fpregs.regs));
    tptr->context.fpcsr = request->registers.fpcsr;

    reply.object = request->tid;
    reply.header.code = request->header.code;
    reply.header.error = TV_ERROR_NO_ERROR;
    __rmonSendReply(&reply.header, sizeof(reply), KK_TYPE_REPLY);
    return TV_ERROR_NO_ERROR;
}

static u32 rmonGetRcpRegister(int regNumber) {
    u32 contents;

    if (__rmonRCPrunning()) {
        return 0;
    }

    SetUpForRCPop(FALSE);
    LoadStoreSU(MIPS_SW_OPCODE, regNumber);
    __rmonStepRCP();
    contents = __rmonReadWordAt((u32*)SP_DMEM_START);
    CleanupFromRCPop(FALSE);

    return contents;
}

int __rmonGetSRegs(KKHeader* req) {
    register KKObjectRequest* request = (KKObjectRequest*)req;
    KKCpSregEvent reply;
    register int i;

    STUBBED_PRINTF(("GetSRegisters\n"));

    if (__rmonRCPrunning()) {
        return TV_ERROR_OPERATIONS_PROTECTED;
    }

    reply.tid = request->object;
    reply.header.code = request->header.code;
    reply.header.error = TV_ERROR_NO_ERROR;

    SetUpForRCPop(FALSE);
    for (i = SREG_IDX_ZERO; i <= SREG_IDX_RA; i++) {
        LoadStoreSU(MIPS_SW_OPCODE, i);
        __rmonStepRCP();
        reply.registers.sregs[i] = __rmonReadWordAt((u32*)SP_DMEM_START);
    }
    CleanupFromRCPop(FALSE);

    reply.registers.sregs[SREG_IDX_DRAM_ADDR] = __rmonReadWordAt((u32*)SP_DRAM_ADDR_REG);
    reply.registers.sregs[SREG_IDX_MEM_ADDR] = __rmonReadWordAt((u32*)SP_MEM_ADDR_REG);
    reply.registers.sregs[SREG_IDX_RD_LEN] = __rmonReadWordAt((u32*)SP_RD_LEN_REG);
    reply.registers.sregs[SREG_IDX_PC] = __rmonReadWordAt((u32*)SP_PC_REG) + SP_IMEM_START;
    reply.registers.sregs[SREG_IDX_WR_LEN] = __rmonReadWordAt((u32*)SP_WR_LEN_REG);
    reply.registers.sregs[SREG_IDX_STATUS] = __rmonReadWordAt((u32*)SP_STATUS_REG);
    reply.registers.sregs[SREG_IDX_DMA_FULL] = __rmonReadWordAt((u32*)SP_DMA_FULL_REG);
    reply.registers.sregs[SREG_IDX_DMA_BUSY] = __rmonReadWordAt((u32*)SP_DMA_BUSY_REG);

    __rmonSendReply(&reply.header, sizeof(reply), KK_TYPE_REPLY);
    return TV_ERROR_NO_ERROR;
}

int __rmonSetSRegs(KKHeader* req) {
    register KKCpScalarRegsetRequest* request = (KKCpScalarRegsetRequest*)req;
    KKObjectEvent reply;
    register int i;

    STUBBED_PRINTF(("SetSRegisters\n"));

    if (__rmonRCPrunning()) {
        return TV_ERROR_OPERATIONS_PROTECTED;
    }

    SetUpForRCPop(FALSE);
    for (i = SREG_IDX_ZERO; i <= SREG_IDX_RA; i++) {
        __rmonWriteWordTo((u32*)SP_DMEM_START, request->registers.sregs[i]);
        LoadStoreSU(MIPS_LW_OPCODE, i);
        __rmonStepRCP();
    }
    CleanupFromRCPop(FALSE);

    __rmonWriteWordTo((u32*)SP_DRAM_ADDR_REG, request->registers.sregs[SREG_IDX_DRAM_ADDR]);
    __rmonWriteWordTo((u32*)SP_MEM_ADDR_REG, request->registers.sregs[SREG_IDX_MEM_ADDR]);
    __rmonWriteWordTo((u32*)SP_PC_REG, request->registers.sregs[SREG_IDX_PC] & 0xFFF);
    __rmonWriteWordTo((u32*)SP_WR_LEN_REG, request->registers.sregs[SREG_IDX_WR_LEN]);
    __rmonWriteWordTo((u32*)SP_STATUS_REG, request->registers.sregs[SREG_IDX_STATUS]);

    reply.object = request->tid;
    reply.header.code = request->header.code;
    reply.header.error = TV_ERROR_NO_ERROR;
    __rmonSendReply(&reply.header, sizeof(reply), KK_TYPE_REPLY);
    return TV_ERROR_NO_ERROR;
}

int __rmonGetVRegs(KKHeader* req) {
    char* cPtr;
    int sent;
    int dataSize;
    register KKObjectRequest* request = (KKObjectRequest*)req;
    KKCpVregEvent reply;
    register int i;

    STUBBED_PRINTF(("GetVRegisters\n"));

    if (__rmonRCPrunning()) {
        return TV_ERROR_OPERATIONS_PROTECTED;
    }

    reply.tid = request->object;
    reply.header.code = request->header.code;
    reply.header.error = TV_ERROR_NO_ERROR;
    reply.header.length = sizeof(reply);

    dataSize = sizeof(reply);
    cPtr = (char*)&dataSize;
    sent = 0;
    while (sent < (signed)sizeof(dataSize)) {
        sent += __osRdbSend(cPtr + sent, sizeof(dataSize) - sent, RDB_TYPE_GtoH_DEBUG);
    }

    __rmonSendHeader(&reply.header, VREG_SIZE, KK_TYPE_REPLY);

    SetUpForRCPop(TRUE);
    for (i = 0; i < VREG_NUM; i++) {
        LoadStoreVU(MIPS_SWC2_OPCODE, i);
        __rmonStepRCP();
        __rmonSendData((void*)SP_DMEM_START, VREG_SIZE);
    }
    CleanupFromRCPop(TRUE);

    return TV_ERROR_NO_ERROR;
}

int __rmonSetVRegs(KKHeader* req) {
    register KKCpVectorRegsetRequest* request = (KKCpVectorRegsetRequest*)req;
    KKObjectEvent reply;
    register int i;

    STUBBED_PRINTF(("SetVRegs\n"));

    if (__rmonRCPrunning()) {
        return TV_ERROR_OPERATIONS_PROTECTED;
    }

    SetUpForRCPop(TRUE);
    for (i = 0; i < VREG_NUM; i++) {
        __rmonCopyWords((u32*)SP_DMEM_START, (u32*)&request->registers.vregs[i], VREG_SIZE / sizeof(u32));
        LoadStoreVU(MIPS_LWC2_OPCODE, i);
        __rmonStepRCP();
    }
    CleanupFromRCPop(TRUE);

    reply.object = request->tid;
    reply.header.code = request->header.code;
    reply.header.error = TV_ERROR_NO_ERROR;
    __rmonSendReply(&reply.header, sizeof(reply), KK_TYPE_REPLY);
    return TV_ERROR_NO_ERROR;
}

u32 __rmonGetRegisterContents(int method, int threadNumber, int regNumber) {
    if (method == RMON_CPU) {
        /* CPU register */
        u32* regPointer;
        OSThread* tptr;

        if (regNumber >= GREG_IDX_AT && regNumber < GREG_IDX_K0) {
            regNumber -= GREG_IDX_AT - GREG_IDX_ZERO;
        } else if (regNumber >= GREG_IDX_GP && regNumber < GREG_IDX_LO) {
            regNumber -= GREG_IDX_GP - GREG_IDX_T9;
        } else {
            return 0;
        }
        tptr = __rmonGetTCB(threadNumber);
        if (tptr == NULL) {
            return 0;
        }
        regPointer = (u32*)&tptr->context;
        regPointer += regNumber;
        return *regPointer;
    } else {
        /* RSP register */
        return rmonGetRcpRegister(regNumber);
    }
}

#endif
