Files
archived-pcsx2/pcsx2/IPU/IPUdma.cpp

305 lines
7.4 KiB
C++

// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "Common.h"
#include "IPU/IPU.h"
#include "IPU/IPUdma.h"
#include "IPU/IPU_MultiISA.h"
IPUDMAStatus IPU1Status;
void ipuDmaReset()
{
IPU1Status.InProgress = false;
IPU1Status.DMAFinished = true;
}
bool SaveStateBase::ipuDmaFreeze()
{
if (!FreezeTag("IPUdma"))
return false;
Freeze(IPU1Status);
return IsOkay();
}
static __fi int IPU1chain() {
int totalqwc = 0;
int qwc = ipu1ch.qwc;
u32 *pMem;
pMem = (u32*)dmaGetAddr(ipu1ch.madr, false);
if (pMem == NULL)
{
Console.Error("ipu1dma NULL!");
return totalqwc;
}
//Write our data to the fifo
qwc = ipu_fifo.in.write(pMem, qwc);
ipu1ch.madr += qwc << 4;
ipu1ch.qwc -= qwc;
totalqwc += qwc;
//Update TADR etc
hwDmacSrcTadrInc(ipu1ch);
if (!ipu1ch.qwc)
IPU1Status.InProgress = false;
return totalqwc;
}
void IPU1dma()
{
if(!ipu1ch.chcr.STR || ipu1ch.chcr.MOD == 2)
{
//We MUST stop the IPU from trying to fill the FIFO with more data if the DMA has been suspended
//if we don't, we risk causing the data to go out of sync with the fifo and we end up losing some!
//This is true for Dragons Quest 8 and probably others which suspend the DMA.
DevCon.Warning("IPU1 running when IPU1 DMA disabled! CHCR %x QWC %x", ipu1ch.chcr._u32, ipu1ch.qwc);
CPU_SET_DMASTALL(DMAC_TO_IPU, true);
return;
}
if (IPUCoreStatus.DataRequested == false)
{
// IPU isn't expecting any data, so put it in to wait mode.
cpuRegs.eCycle[4] = 0x9999;
CPU_SET_DMASTALL(DMAC_TO_IPU, true);
// Shouldn't Happen.
if (IPUCoreStatus.WaitingOnIPUTo)
{
IPUCoreStatus.WaitingOnIPUTo = false;
IPU_INT_PROCESS(4 * BIAS);
}
return;
}
int tagcycles = 0;
int totalqwc = 0;
IPU_LOG("IPU1 DMA Called QWC %x Finished %d In Progress %d tadr %x", ipu1ch.qwc, IPU1Status.DMAFinished, IPU1Status.InProgress, ipu1ch.tadr);
if (!IPU1Status.InProgress)
{
if (IPU1Status.DMAFinished)
DevCon.Warning("IPU1 DMA Somehow reading tag when finished??");
tDMA_TAG* ptag = dmaGetAddr(ipu1ch.tadr, false); //Set memory pointer to TADR
if (!ipu1ch.transfer("IPU1", ptag))
{
return;
}
ipu1ch.madr = ptag[1]._u32;
tagcycles += 1; // Add 1 cycles from the QW read for the tag
if (ipu1ch.chcr.TTE) DevCon.Warning("TTE?");
IPU1Status.DMAFinished = hwDmacSrcChain(ipu1ch, ptag->ID);
IPU_LOG("dmaIPU1 dmaChain %8.8x_%8.8x size=%d, addr=%lx, fifosize=%x",
ptag[1]._u32, ptag[0]._u32, ipu1ch.qwc, ipu1ch.madr, 8 - g_BP.IFC);
if (ipu1ch.chcr.TIE && ptag->IRQ) //Tag Interrupt is set, so schedule the end/interrupt
IPU1Status.DMAFinished = true;
if (ipu1ch.qwc)
IPU1Status.InProgress = true;
}
if (IPU1Status.InProgress)
totalqwc += IPU1chain();
// Nothing has been processed except maybe a tag, or the DMA is ending
if(totalqwc == 0 || (IPU1Status.DMAFinished && !IPU1Status.InProgress))
{
totalqwc = std::max(4, totalqwc) + tagcycles;
IPU_INT_TO(totalqwc * BIAS);
}
else
{
cpuRegs.eCycle[4] = 0x9999;
CPU_SET_DMASTALL(DMAC_TO_IPU, true);
}
if (IPUCoreStatus.WaitingOnIPUTo && g_BP.IFC >= 1)
{
IPUCoreStatus.WaitingOnIPUTo = false;
IPU_INT_PROCESS(totalqwc * BIAS);
}
IPU_LOG("Completed Call IPU1 DMA QWC Remaining %x Finished %d In Progress %d tadr %x", ipu1ch.qwc, IPU1Status.DMAFinished, IPU1Status.InProgress, ipu1ch.tadr);
}
void IPU0dma()
{
if(!ipuRegs.ctrl.OFC)
{
// This shouldn't happen.
if (IPUCoreStatus.WaitingOnIPUFrom)
{
IPUCoreStatus.WaitingOnIPUFrom = false;
IPUProcessInterrupt();
}
CPU_SET_DMASTALL(DMAC_FROM_IPU, true);
return;
}
int readsize;
tDMA_TAG* pMem;
if ((!(ipu0ch.chcr.STR) || (cpuRegs.interrupt & (1 << DMAC_FROM_IPU))) || (ipu0ch.qwc == 0))
{
DevCon.Warning("How??");
// This shouldn't happen.
if (IPUCoreStatus.WaitingOnIPUFrom)
{
IPUCoreStatus.WaitingOnIPUFrom = false;
IPU_INT_PROCESS(ipuRegs.ctrl.OFC * BIAS);
}
return;
}
pxAssert(!(ipu0ch.chcr.TTE));
IPU_LOG("dmaIPU0 chcr = %lx, madr = %lx, qwc = %lx",
ipu0ch.chcr._u32, ipu0ch.madr, ipu0ch.qwc);
pxAssert(ipu0ch.chcr.MOD == NORMAL_MODE);
pMem = dmaGetAddr(ipu0ch.madr, true);
readsize = std::min(ipu0ch.qwc, (u32)ipuRegs.ctrl.OFC);
ipu_fifo.out.read(pMem, readsize);
ipu0ch.madr += readsize << 4;
ipu0ch.qwc -= readsize;
if (dmacRegs.ctrl.STS == STS_fromIPU) // STS == fromIPU
{
//DevCon.Warning("fromIPU Stall Control");
dmacRegs.stadr.ADDR = ipu0ch.madr;
}
if (!ipu0ch.qwc)
IPU_INT_FROM(readsize * BIAS);
CPU_SET_DMASTALL(DMAC_FROM_IPU, true);
if (ipuRegs.ctrl.BUSY && IPUCoreStatus.WaitingOnIPUFrom)
{
IPUCoreStatus.WaitingOnIPUFrom = false;
IPU_INT_PROCESS(readsize * BIAS);
}
}
__fi void dmaIPU0() // fromIPU
{
//if (dmacRegs.ctrl.STS == STS_fromIPU) DevCon.Warning("DMA Stall enabled on IPU0");
if (dmacRegs.ctrl.STS == STS_fromIPU) // STS == fromIPU - Initial settings
dmacRegs.stadr.ADDR = ipu0ch.madr;
CPU_SET_DMASTALL(DMAC_FROM_IPU, false);
// Note: This should probably be a very small value, however anything lower than this will break Mana Khemia
// This is because the game sends bad DMA information, starts an IDEC, then sets it to the correct values
// but because our IPU is too quick, it messes up the sync between the DMA and IPU.
// So this will do until (if) we sort the timing out of IPU, shouldn't cause any problems for games for now.
//IPU_INT_FROM( 160 );
// Update 22/12/2021 - Doesn't seem to need this now after fixing some FIFO/DMA behaviour
IPU0dma();
// Explanation of this:
// The DMA logic on a NORMAL transfer is generally a "transfer first, ask questions later" so when it's sent
// QWC == 0 (which we change to 0x10000) it transfers, causing an underflow, then asks if it's reached 0
// since IPU_FROM is beholden to the OUT FIFO, if there's nothing to transfer, it will stay at 0 and won't underflow
// so the DMA will end.
if (ipu0ch.qwc == 0x10000)
{
ipu0ch.qwc = 0;
ipu0ch.chcr.STR = false;
hwDmacIrq(DMAC_FROM_IPU);
DMA_LOG("IPU0 DMA End");
}
}
__fi void dmaIPU1() // toIPU
{
IPU_LOG("IPU1DMAStart QWC %x, MADR %x, CHCR %x, TADR %x", ipu1ch.qwc, ipu1ch.madr, ipu1ch.chcr._u32, ipu1ch.tadr);
CPU_SET_DMASTALL(DMAC_TO_IPU, false);
if (ipu1ch.chcr.MOD == CHAIN_MODE) //Chain Mode
{
IPU_LOG("Setting up IPU1 Chain mode");
if(ipu1ch.qwc == 0)
{
IPU1Status.InProgress = false;
IPU1Status.DMAFinished = false;
}
else // Attempting to continue a previous chain
{
IPU_LOG("Resuming DMA TAG %x", (ipu1ch.chcr.TAG >> 12));
IPU1Status.InProgress = true;
if ((ipu1ch.chcr.tag().ID == TAG_REFE) || (ipu1ch.chcr.tag().ID == TAG_END) || (ipu1ch.chcr.tag().IRQ && ipu1ch.chcr.TIE))
{
IPU1Status.DMAFinished = true;
}
else
{
IPU1Status.DMAFinished = false;
}
}
}
else // Normal Mode
{
IPU_LOG("Setting up IPU1 Normal mode");
IPU1Status.InProgress = true;
IPU1Status.DMAFinished = true;
}
IPU1dma();
}
void ipuCMDProcess()
{
IPUProcessInterrupt();
}
void ipu0Interrupt()
{
IPU_LOG("ipu0Interrupt: %x", cpuRegs.cycle);
if(ipu0ch.qwc > 0)
{
IPU0dma();
return;
}
ipu0ch.chcr.STR = false;
hwDmacIrq(DMAC_FROM_IPU);
CPU_SET_DMASTALL(DMAC_FROM_IPU, false);
DMA_LOG("IPU0 DMA End");
}
__fi void ipu1Interrupt()
{
IPU_LOG("ipu1Interrupt %x:", cpuRegs.cycle);
if(!IPU1Status.DMAFinished || IPU1Status.InProgress) //Sanity Check
{
IPU1dma();
return;
}
DMA_LOG("IPU1 DMA End");
ipu1ch.chcr.STR = false;
hwDmacIrq(DMAC_TO_IPU);
CPU_SET_DMASTALL(DMAC_TO_IPU, false);
}