volatile void *__cpReg = (u16 *)0xCC000000;     // 16-bit only
volatile void *__piReg = (u32 *)0xCC003000;     // 32-bit only

typedef struct __GXFifoObj
{
    u32     base;
    u32     top;
    u32     size;
    u32     hiWatermark;
    u32     loWatermark;
    void    *rdPtr;
    void    *wrPtr;
    u32     count;
    u8      bind_cpu,
            bind_gp;
} GXFifoObj;

GXFifoObj   *CPUFifo, *GPFifo;
GXBool      CPGPLinked;

static GXBreakPtCallback BreakPointCB;
static void * __GXCurrentBP;

static BOOL GXOverflowSuspendInProgress;
static u32 __GXOverflowCount;

static BOOL IsWGPipeRedirected;          

// ----------------------------------------------------------------------------

void GXOverflowHandler(OSContext *context)
{
    if(__gxVerif[4] > 1)
    {
        OSReport("[GXOverflowHandler]");
    }

    ASSERT(!GXOverflowSuspendInProgress);

    __GXOverflowCount++;

    __GXWriteFifoIntEnable(FALSE, TRUE);
    __GXWriteFifoIntReset(TRUE, FALSE);

    GXOverflowSuspendInProgress = TRUE;

    if(__gxVerif[4] > 1)
    {
        OSReport("[GXOverflowHandler Sleeping]");
    }

    OSSuspendThread(__GXCurrentThread);
}

void GXUnderflowHandler(OSContext *context)
{
    if(__gxVerif[4] > 1)
    {
        OSReport("[GXUnderflowHandler]");
    }

    ASSERT(GXOverflowSuspendInProgress);

    OSResumeThread(__GXCurrentThread);
    GXOverflowSuspendInProgress = FALSE;

    __GXWriteFifoIntReset(TRUE, TRUE);
    __GXWriteFifoIntEnable(TRUE, FALSE);
}

void GXBreakPointHandler(OSContext *context)
{
    OSContext exceptionContext;

    // disable breakpoint checking
    gx.cpEnable &= ~0x0020;
    __cpReg[0x02] = gx.cpEnable;

    // execute breakpoint callback
    if(BreakPointCB)
    {
        OSClearContext(&exceptionContext);
        OSSetCurrentContext(exceptionContext);
        BreakPointCB();
    }

    OSClearContext(&exceptionContext);
    OSSetCurrentContext(context);
}                   

void GXCPInterruptHandler(__OSInterrupt interrupt, OSContext *context)
{
    gx.cpStatus = __cpReg[0x00];

    // low watermark interrupt
    if((gx.cpEnable >> 3) & 1)          // low watermark enabled ?
    {
        if((gx.cpStatus >> 1) & 1)      // interrupt active ?
        {
            GXUnderflowHandler();
        }
    }

    // high watermark interrupt         // high watermark enabled ?
    if((gx.cpEnable >> 2) & 1)
    {
        if((gx.cpStatus >> 0) & 1)      // interrupt active ?
        {
            GXOverflowHandler();
        }
    }

    // break interrupt
    if((gx.cpEnable >> 5) & 1)          // breakpoint enabled ?
    {
        if((gx.cpStatus >> 4) & 1)      // interrupt active ?
        {
            GXBreakPointHandler();
        }
    }
}

// ----------------------------------------------------------------------------

void GXInitFifoBase(GXFifoObj *fifo, void *base, u32 size)
{
     if(fifo == CPUFifo)
     {
        OSPanic("GXInitFifoBase: fifo is attached to CPU");
     }

     if(fifo == GPFifo)
     {
        OSPanic("GXInitFifoBase: fifo is attached to GP");
     }

     if(base & 0x1f)
     {
        OSPanic("GXInitFifoBase: base must be 32B aligned");
     }

     if(base == NULL)
     {
        OSPanic("GXInitFifoBase: base pointer is NULL");
     }

     if(size & 0x1f)
     {
        OSPanic("GXInitFifoBase: size must be 32B aligned");
     }

     if(size < GX_FIFO_MINSIZE)
     {
        OSPanic("GXInitFifoBase: fifo is not large enough");
     }

     fifo->base  = base;
     fifo->top   = base + size - 4;
     fifo->size  = size;
     fifo->count = 0;

     GXInitFifoLimits(fifo, size - GX_FIFO_HI_WATERMARK_BUFFER, (size / 2) & ~0x1f);
     GXInitFifoPtrs(fifo, base, base);
}

void GXInitFifoLimits(GXFifoObj *fifo, u32 hiWaterMark, u32 loWaterMark)
{
    if(fifo == GPFifo)
    {
        OSPanic("GXInitFifoLimits: fifo is attached to GP");
    }

    if(hiWaterMark & 0x1f)
    {
        OSPanic("GXInitFifoLimits: hiWatermark not 32B aligned");
    }

    if(loWaterMark & 0x1f)
    {
        OSPanic("GXInitFifoLimits: loWatermark not 32B aligned");
    }

    if((base - top) <= hiWaterMark)
    {
        OSPanic("GXInitFifoLimits: hiWatermark too large");
    }

    if(hiWaterMark < loWaterMark)
    {
        OSPanic("GXInitFifoLimits: hiWatermark below lo watermark");
    }
    
    fifo->hiWatermark = hiWaterMark;
    fifo->loWatermark = loWaterMark;
}

void GXInitFifoPtrs(GXFifoObj *fifo, void *readPtr, void *writePtr)
{
    BOOL level;

    if(fifo == CPUFifo)
    {
        OSPanic("GXInitFifoPtrs: fifo is attached to CPU");
    }

    if(fifo == GPFifo)
    {
        OSPanic("GXInitFifoPtrs: fifo is attached to GP");
    }

    if(readPtr & 0x1f)
    {
        OSPanic("GXInitFifoPtrs: readPtr not 32B aligned");
    }

    if(writePtr & 0x1f)
    {
        OSPanic("GXInitFifoPtrs: writePtr not 32B aligned");
    }

    if((readPtr < fifo->base) || (readPtr >= fifo->top))
    {
        OSPanic("GXInitFifoPtrs: readPtr not in fifo range");
    }

    if((writePtr < fifo->base) || (writePtr >= fifo->top))
    {
        OSPanic("GXInitFifoPtrs: writePtr not in fifo range");
    }

    level = OSDisableInterrupts();

    fifo->rdPtr = readPtr;
    fifo->wrPtr = writePtr;
    if((fifo->count = (readPtr - writePtr)) < 0)
    {
        fifo->count += fifo->size;
    }

    OSRestoreInterrupts(level);
}

// ----------------------------------------------------------------------------

void GXSetCPUFifo(GXFifoObj *fifo)
{
    BOOL enabled = OSDisableInterrupts();

    CPUFifo = fifo;

    // immediate mode
    if(CPUFifo == GPFifo)
    {
        __piReg[0x0C] = fifo->base;
        __piReg[0x10] = fifo->top;
        __piReg[0x14] = fifo->wrPtr;

        CPGPLinked = 1;
        __GXWriteFifoIntReset(TRUE, TRUE);
        __GXWriteFifoIntEnable(TRUE, FALSE);
        __GXFifoLink(TRUE);
    }
    // multi-buffer mode
    else
    {
        if(CPGPLinked)
        {
            __GXFifoLink(FALSE);
            CPGPLinked = 0;
        }

        __GXWriteFifoIntEnable(FALSE, FALSE);

        __piReg[0x0C] = fifo->base;
        __piReg[0x10] = fifo->top;
        __piReg[0x14] = fifo->wrPtr;
    }

    PPC_SYNC();
    OSRestoreInterrupts(enabled);
}

void GXSetGPFifo(GXFifoObj *fifo)
{
    BOOL enabled = OSDisableInterrupts();

    __GXFifoReadDisable();
    __GXWriteFifoIntEnable(FALSE, FALSE);

    GPFifo = fifo;
    
    __cpReg[0x20] = (u16)fifo->base;
    __cpReg[0x22] = (u16)(fifo->base >> 16);

    __cpReg[0x24] = (u16)fifo->top;
    __cpReg[0x26] = (u16)(fifo->top >> 16);

    __cpReg[0x30] = (u16)fifo->count;
    __cpReg[0x32] = (u16)(fifo->count >> 16);

    __cpReg[0x34] = (u16)fifo->wrPtr;
    __cpReg[0x36] = (u16)(fifo->wrPtr >> 16);

    __cpReg[0x38] = (u16)fifo->rdPtr;
    __cpReg[0x3A] = (u16)(fifo->rdPtr >> 16);

    __cpReg[0x28] = (u16)fifo->hiWatermark;
    __cpReg[0x2A] = (u16)(fifo->hiWatermark >> 16);

    __cpReg[0x2C] = (u16)fifo->loWatermark;
    __cpReg[0x2E] = (u16)(fifo->loWatermark >> 16);

    PPC_SYNC();

    if(CPUFifo == GPFifo)
    {
        CPGPLinked = 1;
        __GXWriteFifoIntEnable(TRUE, FALSE);
        __GXFifoLink(TRUE);
    }
    else
    {
        CPGPLinked = 0;
        __GXWriteFifoIntEnable(FALSE, FALSE);
        __GXFifoLink(FALSE);
        __GXWriteFifoIntReset(TRUE, TRUE);
    }

    __GXFifoReadEnable();

    OSRestoreInterrupts(enabled);
}

void GXSaveCPUFifo(GXFifoObj *fifo)
{
    if(CPUFifo != fifo)
    {
        OSPanic("GXSaveCPUFifo: fifo is not attached to CPU");
    }

    __GXSaveCPUFifoAux(fifo);
}

static void __GXSaveCPUFifoAux(GXFifoObj *fifo)
{
    BOOL enabled = OSDisableInterrupts();

    GXFlush();

    fifo->base = OSPhysicalToCached(__piReg[0x0C]);
    fifo->top  = OSPhysicalToCached(__piReg[0x10]);
    fifo->wrPtr= OSPhysicalToCached(__piReg[0x14]);

    if(CPGPLinked)
    {
        // save also GP fifo
        fifo->rdPtr = OSPhysicalToCached((__cpReg[0x3A] << 16) | __cpReg[0x38]);
        fifo->count = (__cpReg[0x32]  << 16) | __cpReg[0x30];
    }
    else
    {
        // dont understand :( adjust fifo ?
        fifo->wrPtr = fifo->rdPtr - fifo->count;

        if(fifo->wrPtr < 0)
        {
            fifo->wrPtr += fifo->size;
        }
    }
}

void GXSaveGPFifo(GXFifoObj *fifo)
{
    if(fifo != GPFifo)
    {
        OSPanic("GXSaveGPFifo: fifo is not attached to GP");
    }

    if((__cpReg[0x00] & 0x0002) == 0)
    {
        OSPanic("GXSaveGPFifo: GP is not idle");
    }

    fifo->rdPtr = OSPhysicalToCached((__cpReg[0x3A] << 16) | __cpReg[0x38]);
    fifo->count = (__cpReg[0x32]  << 16) | __cpReg[0x30];
}

void GXGetGPStatus(
    GXBool *overhi,
    GXBool *underlow,
    GXBool *readIdle,
    GXBool *cmdIdle,
    GXBool *brkpt)
{
    gx.cpStatus = __cpReg[0x00];

    *overhi   = (gx.cpStatus >> 0) & 1;
    *underlow = (gx.cpStatus >> 1) & 1;
    *readIdle = (gx.cpStatus >> 2) & 1;
    *cmdIdle  = (gx.cpStatus >> 3) & 1;
    *brkpt    = (gx.cpStatus >> 4) & 1;
}

void GXGetFifoStatus(
    GXFifoObj   *fifo,
    GXBool      *overhi,
    GXBool      *underlow,
    u32         *fifoCount,
    GXBool      *cpu_write,
    GXBool      *gp_read,
    GXBool      *fifowrap)
{
    ...
}

void GXGetFifoPtrs(
    GXFifoObj   *fifo,
    void        **readPtr,
    void        **writePtr)
{
    if(fifo == CPUFifo)
    {
        fifo->wrPtr = OSPhysicalToCached(__piReg[0x14]);
    }

    if(fifo == GPFifo)
    {
        fifo->rdPtr = OSPhysicalToCached((__cpReg[0x3A] << 16) | __cpReg[0x38]);
        fifo->count = (__cpReg[0x32]  << 16) | __cpReg[0x30];
    }
    else
    {
        fifo->wrPtr = fifo->rdPtr - fifo->count;

        if(fifo->wrPtr < 0)
        {
            fifo->wrPtr += fifo->size;
        }
    }

    *readPtr = fifo->rdPtr;
    *writePtr= fifo->wrPtr;
}

void *GXGetFifoBase(GXFifoObj *fifo)
{
    return fifo->rdPtr;
}

u32 GXGetFifoSize(GXFifoObj *fifo)
{
    return fifo->size;
}

void GXGetFifoLimits(GXFifoObj *fifo, u32 *hi, u32 *lo)
{
    *hi = fifo->hiWatermark;
    *lo = fifo->loWatermark;
}

// ----------------------------------------------------------------------------

// breakpoint api

GXBreakPtCallback GXSetBreakPtCallback(GXBreakPtCallback cb)
{
    GXBreakPtCallback oldcb = BreakPointCB;
    BOOL enabled = OSDisableInterrupts();

    BreakPointCB = cb;

    OSRestoreInterrupts(enabled);
    return oldcb;
}

void GXEnableBreakPt(void *breakPtr)
{
    BOOL enabled = OSDisableInterrupts();

    __GXFifoReadDisable();

    __cpReg[0x3C] = (u16)breakPtr;
    __cpReg[0x3E] = (u16)((u32)breakPtr >> 16);

    gx.bpSentNot |= 0x0002;     // clear bp int ?
    gx.bpSentNot |= 0x0020;
    __cpReg[2] = gx.bpSentNot;

    __GXCurrentBP = breakPtr;

    __GXFifoReadEnable();

    OSRestoreInterrupts(enabled);
}

void GXDisableBreakPt(void)
{
    BOOL enabled = OSDisableInterrupts();

    gx.bpSentNot &= ~0x0022;
    __cpReg[2] = gx.bpSentNot;

    __GXCurrentBP = 0;

    OSRestoreInterrupts(enabled);
}

// ----------------------------------------------------------------------------

void __GXFifoInit(void)
{
    __OSSetInterruptHandler(
        __OS_INTERRUPT_PI_CP,
        GXCPInterruptHandler
    );
    __OSUnmaskInterrupts(OS_INTERRUPTMASK_PI_CP);

    __GXCurrentThread = OSGetCurrentThread();
    GXOverflowSuspendInProgress = FALSE;
    CPUFifo = NULL;
    GPFifo  = NULL;
}

// ----------------------------------------------------------------------------

void __GXFifoReadEnable(void)
{
    gx.cpEnable |= 0x0001;
    __cpReg[0x02] = gx.cpEnable;
}

void __GXFifoReadDisable(void)
{
    gx.cpEnable &= ~0x0001;
    __cpReg[0x02] = gx.cpEnable;
}

void __GXFifoLink(u8 en)
{
    u16 old = gx.cpEnable & ~0x0010;
    gx.cpEnable |= (en << 4);
    __cpReg[0x02] = gx.cpEnable;
}

void __GXWriteFifoIntEnable(BOOL hiWatermarkEn, BOOL loWatermarkEn)
{
    u16 old = gx.cpEnable & ~0x000C;
    gx.cpEnable |= (hiWatermarkEn << 2);
    gx.cpEnable |= (loWatermarkEn << 3);
    __cpReg[0x02] = gx.cpEnable;
}

void __GXWriteFifoIntReset(BOOL hiWatermarkClr, BOOL loWatermarkClr)
{
    u16 old = gx.cpClr & ~0x0003;
    gx.cpClr |= (hiWatermarkClr << 0);
    gx.cpClr |= (loWatermarkClr << 1);
    __cpReg[0x04] = gx.cpClr;
}

// for extensive testing of watermark triggers
void __GXInsaneWatermark(void)
{
    GXFifoObj *realFifo = GPFifo;

    // too slow, cough ..
    realFifo->hiWatermark = realFifo->loWatermark + 512; 

    __cpReg[0x28] = (u16)realFifo->hiWatermark;
    __cpReg[0x2A] = (u16)(realFifo->hiWatermark >> 16); 
}

// ----------------------------------------------------------------------------

// strange ..
void __GXCleanGPFifo(void)
{
    GXFifoObj   dummyFifo,
                *gpFifo  = GXGetGPFifo(),
                *cpuFifo = GXGetCPUFifo();
    void        *base;

    base = GXGetFifoBase(gpFifo);

    memset(&dummyFifo, 0, sizeof(GXFifoObj));
    GXInitFifoPtrs(&dummyFifo, base, base);

    GXSetGPFifo(&dummyFifo);
    if(gpFifo == cpuFifo) GXSetCPUFifo(&dummyFifo);

    GXInitFifoPtrs(gpFifo, base, base);

    GXSetGPFifo(gpFifo);
    if(gpFifo == cpuFifo) GXSetCPUFifo(cpuFifo);
}

OSThread *GXSetCurrentGXThread(void)
{
    BOOL enabled = OSDisableInterrupts();
    OSThread old = __GXCurrentThread;

    if(GXOverflowSuspendInProgress)
    {
        OSPanic(
            "GXSetCurrentGXThread: Two threads cannot generate"
            "GX commands at the same time!"
        );
    }

    __GXCurrentThread = OSGetCurrentThread();

    OSRestoreInterrupts(enabled);
    return old;
}

OSThread *GXGetCurrentGXThread(void)
{
    return __GXCurrentThread;
}

// ----------------------------------------------------------------------------

GXFifoObj *GXGetCPUFifo(void)
{
    return CPUFifo;
}

GXFifoObj *GXGetGPFifo(void)
{
    return GPFifo;
}

// ----------------------------------------------------------------------------

u32 GXGetOverflowCount(void)
{
    return __GXOverflowCount;
}

u32 GXResetOverflowCount(void)
{
    __GXOverflowCount = 0;
}

// ----------------------------------------------------------------------------

// write gather pipe redirecting api

volatile void * GXRedirectWriteGatherPipe(void *ptr)
{
    BOOL enabled = OSDisableInterrupts();

    if(__GXinBegin)
    {
        OSPanic(
            "\'GXRedirectWriteGatherPipe\' is not"
            "allowed between GXBegin/GXEnd"
        );
    }

    ASSERT(OFFSET(ptr, 32) == 0);
    ASSERT(!IsWGPipeRedirected);

    IsWGPipeRedirected = TRUE;

    GXFlush();

    // wait until pipe is not empty
    while(PCMfwpar() & WPAR_BNE);

    PPCMtwpar(OSUncachedToPhysical(0xCC008000));

    if(CPGPLinked)
    {
        __GXFifoLink(0);
        __GXWriteFifoIntEnable(0, 0);
    } 

    CPUFifo->wrPtr = OSPhysicalToCached(__piReg[0x14]);

    __piReg[0x0C] = 0;              // base
    __piReg[0x10] = 0x04000000;     // top
    __piReg[0x14] = OSCachedToHardwired(ptr);

    PPC_SYNC();
    OSRestoreInterrupts(enabled);
    return 0xCC008000;
}

void GXRestoreWriteGatherPipe(void)
{
    PPCWGPipe wgpipe : 0xCC008000;
    BOOL enabled;
    int i;
        
    ASSERT(IsWGPipeRedirected);

    IsWGPipeRedirected = FALSE;

    enabled = OSDisableInterrupts();

    // flush fifo
    for(i=0; i<31; i++)
    {
        wgpipe.u8 = 0;
    }

    PPCSync();

    // wait until pipe is not empty
    while(PCMfwpar() & WPAR_BNE);

    PPCMtwpar(OSUncachedToPhysical(0xCC008000));

    __piReg[0x0C] = CPUFifo->base;
    __piReg[0x10] = CPUFifo->top;
    __piReg[0x14] = OSCachedToHardwired(CPUFifo->wrPtr);

    if(CPGPLinked)
    {
        __GXWriteFifoIntReset(TRUE, TRUE);
        __GXWriteFifoIntEnable(TRUE, FALSE);
        __GXFifoLink(TRUE);
    }

    PPC_SYNC();
    OSRestoreInterrupts(enabled);
}

// SUMMARY : GC fifo is most complex hardware, I've seen..
