Files
archived-pcsx2/pcsx2/SPU2/Dma.cpp

507 lines
12 KiB
C++

// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "SPU2/defs.h"
#include "SPU2/Debug.h"
#include "SPU2/Dma.h"
#include "SPU2/spu2.h"
#include "R3000A.h"
#include "IopHw.h"
#include "Config.h"
#ifdef PCSX2_DEVBUILD
#define safe_fclose(ptr) \
((void)((((ptr) != nullptr) && (std::fclose(ptr), !!0)), (ptr) = nullptr))
static FILE* DMA4LogFile = nullptr;
static FILE* DMA7LogFile = nullptr;
static FILE* ADMA4LogFile = nullptr;
static FILE* ADMA7LogFile = nullptr;
static FILE* ADMAOutLogFile = nullptr;
static FILE* REGWRTLogFile[2] = {0, 0};
void DMALogOpen()
{
if (!SPU2::DMALog())
return;
DMA4LogFile = EmuFolders::OpenLogFile("SPU2dma4.dat", "wb");
DMA7LogFile = EmuFolders::OpenLogFile("SPU2dma7.dat", "wb");
ADMA4LogFile = EmuFolders::OpenLogFile("adma4.raw", "wb");
ADMA7LogFile = EmuFolders::OpenLogFile("adma7.raw", "wb");
ADMAOutLogFile = EmuFolders::OpenLogFile("admaOut.raw", "wb");
}
void DMA4LogWrite(void* lpData, u32 ulSize)
{
if (!SPU2::DMALog())
return;
if (!DMA4LogFile)
return;
fwrite(lpData, ulSize, 1, DMA4LogFile);
}
void DMA7LogWrite(void* lpData, u32 ulSize)
{
if (!SPU2::DMALog())
return;
if (!DMA7LogFile)
return;
fwrite(lpData, ulSize, 1, DMA7LogFile);
}
void ADMAOutLogWrite(void* lpData, u32 ulSize)
{
if (!SPU2::DMALog())
return;
if (!ADMAOutLogFile)
return;
fwrite(lpData, ulSize, 1, ADMAOutLogFile);
}
void RegWriteLog(u32 core, u16 value)
{
if (!SPU2::DMALog())
return;
if (!REGWRTLogFile[core])
return;
fwrite(&value, 2, 1, REGWRTLogFile[core]);
}
void DMALogClose()
{
safe_fclose(DMA4LogFile);
safe_fclose(DMA7LogFile);
safe_fclose(REGWRTLogFile[0]);
safe_fclose(REGWRTLogFile[1]);
safe_fclose(ADMA4LogFile);
safe_fclose(ADMA7LogFile);
safe_fclose(ADMAOutLogFile);
}
#endif
void V_Core::LogAutoDMA(FILE* fp)
{
if (!SPU2::DMALog() || !fp || !DMAPtr)
return;
fwrite(DMAPtr + InputDataProgress, 0x400, 1, fp);
}
void V_Core::AutoDMAReadBuffer(int mode) //mode: 0= split stereo; 1 = do not split stereo
{
u32 spos = InputPosWrite & 0x100; // Starting position passed by TSA
bool leftbuffer = !(InputPosWrite & 0x80);
if (InputPosWrite == 0xFFFF) // Data request not made yet
return;
AutoDMACtrl &= 0x3;
int size = std::min(InputDataLeft, (u32)0x200);
if (!leftbuffer)
size = std::min(size, 0x100);
#ifdef PCSX2_DEVBUILD
LogAutoDMA(Index ? ADMA7LogFile : ADMA4LogFile);
#endif
//ConLog("Refilling ADMA buffer at %x OutPos %x with %x\n", spos, OutPos, size);
// HACKFIX!! DMAPtr can be invalid after a savestate load, so the savestate just forces it
// to nullptr and we ignore it here. (used to work in old VM editions of PCSX2 with fixed
// addressing, but new PCSX2s have dynamic memory addressing).
if (DMAPtr == nullptr)
{
DMAPtr = (u16*)iopPhysMem(MADR);
InputDataProgress = 0;
}
if (mode)
{
if (DMAPtr != nullptr)
memcpy(GetMemPtr(0x2000 + (Index << 10) + spos), DMAPtr + InputDataProgress, size);
MADR += size;
InputDataLeft -= 0x200;
InputDataProgress += 0x200;
}
else
{
while (size)
{
if (!leftbuffer)
spos |= 0x200;
else
spos &= ~0x200;
if (DMAPtr != nullptr)
memcpy(GetMemPtr(0x2000 + (Index << 10) + spos), DMAPtr + InputDataProgress, 0x200);
InputDataTransferred += 0x200;
InputDataLeft -= 0x100;
InputDataProgress += 0x100;
leftbuffer = !leftbuffer;
size -= 0x100;
InputPosWrite += 0x80;
}
}
if (!(InputPosWrite & 0x80))
InputPosWrite = 0xFFFF;
}
void V_Core::StartADMAWrite(u16* pMem, u32 sz)
{
int size = sz;
TimeUpdate(psxRegs.cycle);
if (SPU2::MsgAutoDMA())
{
SPU2::ConLog("* SPU2: DMA%c AutoDMA Transfer of %d bytes to %x (%02x %x %04x).OutPos %x\n",
GetDmaIndexChar(), size << 1, ActiveTSA, DMABits, AutoDMACtrl, (~Regs.ATTR) & 0xffff, OutPos);
}
InputDataProgress = 0;
TADR = MADR + (size << 1);
if ((AutoDMACtrl & (Index + 1)) == 0)
{
ActiveTSA = 0x2000 + (Index << 10);
DMAICounter = size * 4;
LastClock = psxRegs.cycle;
}
else if (size >= 256)
{
InputDataLeft = size;
if (InputPosWrite != 0xFFFF)
{
#ifdef PCM24_S1_INTERLEAVE
if ((Index == 1) && ((PlayMode & 8) == 8))
{
AutoDMAReadBuffer(Index, 1);
}
else
{
AutoDMAReadBuffer(Index, 0);
}
#else
AutoDMAReadBuffer(0);
#endif
}
AdmaInProgress = 1;
}
else
{
if (SPU2::MsgToConsole())
SPU2::ConLog("ADMA%c Error Size of %x too small\n", GetDmaIndexChar(), size);
InputDataLeft = 0;
DMAICounter = size * 4;
LastClock = psxRegs.cycle;
}
}
void V_Core::PlainDMAWrite(u16* pMem, u32 size)
{
if (SPU2::MsgToConsole())
{
// Don't need this anymore. Target may still be good to know though.
/*if((uptr)pMem & 15)
{
ConLog("* SPU2 DMA Write > Misaligned source. Core: %d IOP: %p TSA: 0x%x Size: 0x%x\n", Index, (void*)pMem, TSA, size);
}*/
if (ActiveTSA & 7)
{
SPU2::ConLog("* SPU2 DMA Write > Misaligned target. Core: %d IOP: %p TSA: 0x%x Size: 0x%x\n", Index, (void*)DMAPtr, ActiveTSA, ReadSize);
}
}
TimeUpdate(psxRegs.cycle);
ReadSize = size;
IsDMARead = false;
DMAICounter = 0;
LastClock = psxRegs.cycle;
Regs.STATX &= ~0x80;
Regs.STATX |= 0x400;
TADR = MADR + (size << 1);
if (SPU2::MsgDMA())
{
SPU2::ConLog("* SPU2: DMA%c Write Transfer of %d bytes to %x (%02x %x %04x). IRQE = %d IRQA = %x \n",
GetDmaIndexChar(), size << 1, ActiveTSA, DMABits, AutoDMACtrl, Regs.ATTR & 0xffff,
Cores[Index].IRQEnable, Cores[Index].IRQA);
}
FinishDMAwrite();
}
void V_Core::FinishDMAwrite()
{
if (DMAPtr == nullptr)
{
DMAPtr = (u16*)iopPhysMem(MADR);
}
DMAICounter = ReadSize;
#ifdef PCSX2_DEVBUILD
if (Index == 0)
DMA4LogWrite(DMAPtr, ReadSize << 1);
else
DMA7LogWrite(DMAPtr, ReadSize << 1);
#endif
u32 buff1end = ActiveTSA + std::min(ReadSize, (u32)0x100 + std::abs(DMAICounter / 4));
u32 buff2end = 0;
if (buff1end > 0x100000)
{
buff2end = buff1end - 0x100000;
buff1end = 0x100000;
}
const int cacheIdxStart = ActiveTSA / pcm_WordsPerBlock;
const int cacheIdxEnd = (buff1end + pcm_WordsPerBlock - 1) / pcm_WordsPerBlock;
PcmCacheEntry* cacheLine = &pcm_cache_data[cacheIdxStart];
PcmCacheEntry& cacheEnd = pcm_cache_data[cacheIdxEnd];
do
{
cacheLine->Validated = false;
cacheLine++;
} while (cacheLine != &cacheEnd);
//ConLog( "* SPU2: Cache Clear Range! TSA=0x%x, TDA=0x%x (low8=0x%x, high8=0x%x, len=0x%x)\n",
// ActiveTSA, buff1end, flagTSA, flagTDA, clearLen );
// First Branch needs cleared:
// It starts at TSA and goes to buff1end.
const u32 buff1size = (buff1end - ActiveTSA);
memcpy(GetMemPtr(ActiveTSA), DMAPtr, buff1size * 2);
u32 TDA;
if (buff2end > 0)
{
// second branch needs copied:
// It starts at the beginning of memory and moves forward to buff2end
// endpoint cache should be irrelevant, since it's almost certainly dynamic
// memory below 0x2800 (registers and such)
//const u32 endpt2 = (buff2end + roundUp) / indexer_scalar;
//memset( pcm_cache_flags, 0, endpt2 );
const u32 start = ActiveTSA;
TDA = buff1end;
DMAPtr += TDA - ActiveTSA;
ReadSize -= TDA - ActiveTSA;
ActiveTSA = 0;
// Emulation Grayarea: Should addresses wrap around to zero, or wrap around to
// 0x2800? Hard to know for sure (almost no games depend on this)
memcpy(GetMemPtr(0), DMAPtr, buff2end * 2);
TDA = (buff2end) & 0xfffff;
// Flag interrupt? If IRQA occurs between start and dest, flag it.
// Important: Test both core IRQ settings for either DMA!
// Note: Because this buffer wraps, we use || instead of &&
for (int i = 0; i < 2; i++)
{
// Start is exclusive and end is inclusive... maybe? The end is documented to be inclusive,
// which suggests that memory access doesn't trigger interrupts, incrementing registers does
// (which would mean that if TSA=IRQA an interrupt doesn't fire... I guess?)
// Chaos Legion uses interrupt addresses set to the beginning of the two buffers in a double
// buffer scheme and sets LSA of one of the voices to the start of the opposite buffer.
// However it transfers to the same address right after setting IRQA, which by our previous
// understanding would trigger the interrupt early causing it to switch buffers again immediately
// and an interrupt never fires again, leaving the voices looping the same samples forever.
if (Cores[i].IRQEnable && (Cores[i].IRQA > start || Cores[i].IRQA < TDA))
{
//ConLog("DMAwrite Core %d: IRQ Called (IRQ passed). IRQA = %x Cycles = %d\n", i, Cores[i].IRQA, Cycles );
SetIrqCallDMA(i);
}
}
}
else
{
// Buffer doesn't wrap/overflow!
// Just set the TDA and check for an IRQ...
TDA = buff1end;
// Flag interrupt? If IRQA occurs between start and dest, flag it.
// Important: Test both core IRQ settings for either DMA!
for (int i = 0; i < 2; i++)
{
if (Cores[i].IRQEnable && (Cores[i].IRQA > ActiveTSA && Cores[i].IRQA < TDA))
{
//ConLog("DMAwrite Core %d: IRQ Called (IRQ passed). IRQA = %x Cycles = %d\n", i, Cores[i].IRQA, Cycles );
SetIrqCallDMA(i);
}
}
}
DMAPtr += TDA - ActiveTSA;
ReadSize -= TDA - ActiveTSA;
DMAICounter = (DMAICounter - ReadSize) * 4;
CounterUpdate(DMAICounter);
ActiveTSA = TDA;
ActiveTSA &= 0xfffff;
TSA = ActiveTSA;
}
void V_Core::FinishDMAread()
{
u32 buff1end = ActiveTSA + std::min(ReadSize, (u32)0x100 + std::abs(DMAICounter / 4));
u32 buff2end = 0;
if (buff1end > 0x100000)
{
buff2end = buff1end - 0x100000;
buff1end = 0x100000;
}
if (DMAPtr == nullptr)
{
DMAPtr = (u16*)iopPhysMem(MADR);
}
const u32 buff1size = (buff1end - ActiveTSA);
memcpy(DMARPtr, GetMemPtr(ActiveTSA), buff1size * 2);
// Note on TSA's position after our copy finishes:
// IRQA should be measured by the end of the writepos+0x20. But the TDA
// should be written back at the precise endpoint of the xfer.
u32 TDA;
if (buff2end > 0)
{
const u32 start = ActiveTSA;
TDA = buff1end;
DMARPtr += TDA - ActiveTSA;
ReadSize -= TDA - ActiveTSA;
ActiveTSA = 0;
// second branch needs cleared:
// It starts at the beginning of memory and moves forward to buff2end
memcpy(DMARPtr, GetMemPtr(0), buff2end * 2);
TDA = (buff2end) & 0xfffff;
// Flag interrupt? If IRQA occurs between start and dest, flag it.
// Important: Test both core IRQ settings for either DMA!
// Note: Because this buffer wraps, we use || instead of &&
for (int i = 0; i < 2; i++)
{
if (Cores[i].IRQEnable && (Cores[i].IRQA > start || Cores[i].IRQA < TDA))
{
SetIrqCallDMA(i);
}
}
}
else
{
// Buffer doesn't wrap/overflow!
// Just set the TDA and check for an IRQ...
TDA = buff1end;
// Flag interrupt? If IRQA occurs between start and dest, flag it.
// Important: Test both core IRQ settings for either DMA!
for (int i = 0; i < 2; i++)
{
if (Cores[i].IRQEnable && (Cores[i].IRQA > ActiveTSA && Cores[i].IRQA < TDA))
{
SetIrqCallDMA(i);
}
}
}
DMARPtr += TDA - ActiveTSA;
ReadSize -= TDA - ActiveTSA;
// DMA Reads are done AFTER the delay, so to get the timing right we need to scheule one last DMA to catch IRQ's
if (ReadSize)
DMAICounter = std::min(ReadSize, (u32)0x100) * 4;
else
DMAICounter = 4;
CounterUpdate(DMAICounter);
ActiveTSA = TDA;
ActiveTSA &= 0xfffff;
TSA = ActiveTSA;
}
void V_Core::DoDMAread(u16* pMem, u32 size)
{
TimeUpdate(psxRegs.cycle);
DMARPtr = pMem;
ActiveTSA = TSA & 0xfffff;
ReadSize = size;
IsDMARead = true;
LastClock = psxRegs.cycle;
DMAICounter = std::min(ReadSize, (u32)0x100) * 4;
Regs.STATX &= ~0x80;
Regs.STATX |= 0x400;
//Regs.ATTR |= 0x30;
TADR = MADR + (size << 1);
CounterUpdate(DMAICounter);
if (SPU2::MsgDMA())
{
SPU2::ConLog("* SPU2: DMA%c Read Transfer of %d bytes from %x (%02x %x %04x). IRQE = %d IRQA = %x \n",
GetDmaIndexChar(), size << 1, ActiveTSA, DMABits, AutoDMACtrl, Regs.ATTR & 0xffff,
Cores[Index].IRQEnable, Cores[Index].IRQA);
}
}
void V_Core::DoDMAwrite(u16* pMem, u32 size)
{
DMAPtr = pMem;
if (size < 2)
{
Regs.STATX &= ~0x80;
//Regs.ATTR |= 0x30;
DMAICounter = 1 * 4;
LastClock = psxRegs.cycle;
return;
}
if (IsDevBuild)
{
DebugCores[Index].lastsize = size;
DebugCores[Index].dmaFlag = 2;
}
if (SPU2::MsgToConsole())
{
if (TSA > 0xfffff)
{
SPU2::ConLog("* SPU2: Transfer Start Address out of bounds. TSA is %x\n", TSA);
}
}
ActiveTSA = TSA & 0xfffff;
const bool adma_enable = ((AutoDMACtrl & (Index + 1)) == (Index + 1));
if (adma_enable)
{
StartADMAWrite(pMem, size);
}
else
{
PlainDMAWrite(pMem, size);
Regs.STATX &= ~0x80;
Regs.STATX |= 0x400;
}
}