DMA: Correctly emulate QWC 0 on NORMAL transfers

This commit is contained in:
refractionpcsx2 2020-11-29 01:29:57 +00:00
parent 32f14f48b0
commit 3d43293a5d
8 changed files with 123 additions and 93 deletions

View File

@ -107,7 +107,6 @@
Serial = GUST-00009
Name = Mana Khemia: Alchemists of Al-Revis [Premium Box]
Region = NTSC-J
DMABusyHack = 1
eeRoundMode = 0 // Fixes jump issue.
GIFFIFOHack = 1 // Fixes flickering sprites.
---------------------------------------------
@ -20025,7 +20024,6 @@ Serial = SLES-55443
Name = Mana Khemia - Alchemists of Al-Revis
Region = PAL-E
Compat = 5
DMABusyHack = 1
eeRoundMode = 0 // Fixes jump issue.
GIFFIFOHack = 1 // Fixes flickering sprites.
---------------------------------------------
@ -45471,7 +45469,6 @@ Serial = SLUS-21735
Name = Mana Khemia - Alchemists of Al-Revis
Region = NTSC-U
Compat = 5
DMABusyHack = 1
eeRoundMode = 0 // Fixes jump issue.
GIFFIFOHack = 1 // Fixes flickering sprites.
---------------------------------------------

View File

@ -161,8 +161,7 @@ union tDMA_SADR {
union tDMA_QWC {
struct {
u16 QWC;
u16 _unused;
u32 QWC;
};
u32 _u32;
@ -178,7 +177,7 @@ struct DMACh {
u32 _null0[3];
u32 madr;
u32 _null1[3];
u16 qwc; u16 pad;
u32 qwc;
u32 _null2[3];
u32 tadr;
u32 _null3[3];

View File

@ -392,7 +392,7 @@ void GIFdma()
if (ptag == NULL) return;
//DevCon.Warning("GIF Reading Tag MSK = %x", vif1Regs.mskpath3);
GIF_LOG("gifdmaChain %8.8x_%8.8x size=%d, id=%d, addr=%lx tadr=%lx", ptag[1]._u32, ptag[0]._u32, gifch.qwc, ptag->ID, gifch.madr, gifch.tadr);
if (!CHECK_GIFFIFOHACK)gifRegs.stat.FQC = std::min((u16)0x10, gifch.qwc);// FQC=31, hack ;) (for values of 31 that equal 16) [ used to be 0xE00; // APATH=3]
if (!CHECK_GIFFIFOHACK)gifRegs.stat.FQC = std::min((u32)0x10, gifch.qwc);// FQC=31, hack ;) (for values of 31 that equal 16) [ used to be 0xE00; // APATH=3]
if (dmacRegs.ctrl.STD == STD_GIF)
{
// there are still bugs, need to also check if gifch.madr +16*qwc >= stadr, if not, stall
@ -420,7 +420,7 @@ void GIFdma()
if (!CHECK_GIFFIFOHACK) {
gifRegs.stat.FQC = std::min((u16)0x10, gifch.qwc);// FQC=31, hack ;) (for values of 31 that equal 16) [ used to be 0xE00; // APATH=3]
gifRegs.stat.FQC = std::min((u32)0x10, gifch.qwc);// FQC=31, hack ;) (for values of 31 that equal 16) [ used to be 0xE00; // APATH=3]
clearFIFOstuff(true);
}
@ -470,7 +470,7 @@ void dmaGIF()
gifInterrupt();
}
static u16 QWCinGIFMFIFO(u32 DrainADDR)
static u32 QWCinGIFMFIFO(u32 DrainADDR)
{
u32 ret;
@ -495,7 +495,7 @@ static u16 QWCinGIFMFIFO(u32 DrainADDR)
static __fi bool mfifoGIFrbTransfer()
{
u16 qwc = std::min(QWCinGIFMFIFO(gifch.madr), gifch.qwc);
u32 qwc = std::min(QWCinGIFMFIFO(gifch.madr), gifch.qwc);
if (qwc == 0) {
DevCon.Warning("GIF FIFO EMPTY before transfer (how?)");
}

View File

@ -219,64 +219,57 @@ void IPU0dma()
pMem = dmaGetAddr(ipu0ch.madr, true);
readsize = std::min(ipu0ch.qwc, (u16)ipuRegs.ctrl.OFC);
readsize = std::min(ipu0ch.qwc, (u32)ipuRegs.ctrl.OFC);
ipu_fifo.out.read(pMem, readsize);
ipu0ch.madr += readsize << 4;
ipu0ch.qwc -= readsize; // note: qwc is u16
ipu0ch.qwc -= readsize;
if (dmacRegs.ctrl.STS == STS_fromIPU && ipu0ch.qwc == 0) // STS == fromIPU
if (dmacRegs.ctrl.STS == STS_fromIPU && ipu0ch.qwc == 0) // STS == fromIPU
{
//DevCon.Warning("fromIPU Stall Control");
dmacRegs.stadr.ADDR = ipu0ch.madr;
switch (dmacRegs.ctrl.STD)
{
//DevCon.Warning("fromIPU Stall Control");
dmacRegs.stadr.ADDR = ipu0ch.madr;
switch (dmacRegs.ctrl.STD)
{
case NO_STD:
break;
case STD_GIF: // GIF
//DevCon.Warning("GIFSTALL");
g_nDMATransfer.GIFSTALL = true;
break;
case STD_VIF1: // VIF
//DevCon.Warning("VIFSTALL");
g_nDMATransfer.VIFSTALL = true;
break;
case STD_SIF1:
// DevCon.Warning("SIFSTALL");
g_nDMATransfer.SIFSTALL = true;
break;
}
case NO_STD:
break;
case STD_GIF: // GIF
//DevCon.Warning("GIFSTALL");
g_nDMATransfer.GIFSTALL = true;
break;
case STD_VIF1: // VIF
//DevCon.Warning("VIFSTALL");
g_nDMATransfer.VIFSTALL = true;
break;
case STD_SIF1:
// DevCon.Warning("SIFSTALL");
g_nDMATransfer.SIFSTALL = true;
break;
}
//Fixme ( voodoocycles ):
//This was IPU_INT_FROM(readsize*BIAS );
//This broke vids in Digital Devil Saga
//Note that interrupting based on totalsize is just guessing..
}
//Fixme ( voodoocycles ):
//This was IPU_INT_FROM(readsize*BIAS );
//This broke vids in Digital Devil Saga
//Note that interrupting based on totalsize is just guessing..
IPU_INT_FROM( readsize * BIAS );
if(ipuRegs.ctrl.IFC > 0) IPUProcessInterrupt();
if (ipuRegs.ctrl.IFC > 0) { IPUProcessInterrupt(); }
//return readsize;
}
__fi void dmaIPU0() // fromIPU
{
if (ipu0ch.pad != 0)
{
// Note: pad is the padding right above qwc, so we're testing whether qwc
// has overflowed into pad.
DevCon.Warning(L"IPU0dma's upper 16 bits set to %x", ipu0ch.pad);
ipu0ch.qwc = ipu0ch.pad = 0;
//If we are going to clear down IPU0, we should end it too. Going to test this scenario on the PS2 mind - Refraction
ipu0ch.chcr.STR = false;
hwDmacIrq(DMAC_FROM_IPU);
}
//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;
IPU_INT_FROM( 64 );
// 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 );
@ -286,31 +279,6 @@ __fi void dmaIPU1() // toIPU
{
IPU_LOG("IPU1DMAStart QWC %x, MADR %x, CHCR %x, TADR %x", ipu1ch.qwc, ipu1ch.madr, ipu1ch.chcr._u32, ipu1ch.tadr);
if (ipu1ch.pad != 0)
{
// Note: pad is the padding right above qwc, so we're testing whether qwc
// has overflowed into pad.
DevCon.Warning(L"IPU1dma's upper 16 bits set to %x\n", ipu1ch.pad);
ipu1ch.qwc = ipu1ch.pad = 0;
// If we are going to clear down IPU1, we should end it too.
// Going to test this scenario on the PS2 mind - Refraction
ipu1ch.chcr.STR = false;
hwDmacIrq(DMAC_TO_IPU);
}
if (ipu1ch.chcr.MOD == NORMAL_MODE && ipu1ch.qwc == 0) //avoids freeze when IPU1 Normal error is triggered
{
/*ipu1ch.chcr.STR = false;
// Hack to force stop IPU
ipuRegs.cmd.BUSY = 0;
ipuRegs.ctrl.BUSY = 0;
ipuRegs.topbusy = 0;
//
hwDmacIrq(DMAC_TO_IPU);*/
IPU_LOG("IPU1 Normal error fix");
ipu1ch.qwc = 1;
}
if (ipu1ch.chcr.MOD == CHAIN_MODE) //Chain Mode
{
IPU_LOG("Setting up IPU1 Chain mode");

View File

@ -170,7 +170,7 @@ __fi void vif0Interrupt()
g_vif0Cycles = 0;
vif0Regs.stat.FQC = std::min(vif0ch.qwc, (u16)8);
vif0Regs.stat.FQC = std::min(vif0ch.qwc, (u32)8);
if (!(vif0ch.chcr.STR)) Console.WriteLn("vif0 running when CHCR == %x", vif0ch.chcr._u32);
@ -199,7 +199,7 @@ __fi void vif0Interrupt()
// One game doesn't like vif stalling at end, can't remember what. Spiderman isn't keen on it tho
//vif0ch.chcr.STR = false;
vif0Regs.stat.FQC = std::min((u16)0x8, vif0ch.qwc);
vif0Regs.stat.FQC = std::min((u32)0x8, vif0ch.qwc);
if (vif0ch.qwc > 0 || !vif0.done)
{
vif0Regs.stat.VPS = VPS_DECODING; //If there's more data you need to say it's decoding the next VIF CMD (Onimusha - Blade Warriors)
@ -224,7 +224,7 @@ __fi void vif0Interrupt()
if (vif0.inprogress & 0x1)
{
_VIF0chain();
vif0Regs.stat.FQC = std::min(vif0ch.qwc, (u16)8);
vif0Regs.stat.FQC = std::min(vif0ch.qwc, (u32)8);
CPU_INT(DMAC_VIF0, g_vif0Cycles);
return;
}
@ -239,7 +239,7 @@ __fi void vif0Interrupt()
}
if ((vif0.inprogress & 0x1) == 0) vif0SetupTransfer();
vif0Regs.stat.FQC = std::min(vif0ch.qwc, (u16)8);
vif0Regs.stat.FQC = std::min(vif0ch.qwc, (u32)8);
CPU_INT(DMAC_VIF0, g_vif0Cycles);
return;
}
@ -256,7 +256,7 @@ __fi void vif0Interrupt()
#endif
vif0ch.chcr.STR = false;
vif0Regs.stat.FQC = std::min((u16)0x8, vif0ch.qwc);
vif0Regs.stat.FQC = std::min((u32)0x8, vif0ch.qwc);
vif0.vifstalled.enabled = false;
vif0.irqoffset.enabled = false;
if(vif0.queued_program) vifExecQueue(0);
@ -307,7 +307,7 @@ void dmaVIF0()
vif0.inprogress &= ~0x1;
}
vif0Regs.stat.FQC = std::min((u16)0x8, vif0ch.qwc);
vif0Regs.stat.FQC = std::min((u32)0x8, vif0ch.qwc);
//Using a delay as Beyond Good and Evil does the DMA twice with 2 different TADR's (no checks in the middle, all one block of code),
//the first bit it sends isnt required for it to work.

View File

@ -298,7 +298,7 @@ __fi void vif1Interrupt()
//Console.WriteLn("VIFMFIFO\n");
// Test changed because the Final Fantasy 12 opening somehow has the tag in *Undefined* mode, which is not in the documentation that I saw.
if (vif1ch.chcr.MOD == NORMAL_MODE) Console.WriteLn("MFIFO mode is normal (which isn't normal here)! %x", vif1ch.chcr._u32);
vif1Regs.stat.FQC = std::min((u16)0x10, vif1ch.qwc);
vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
vifMFIFOInterrupt();
return;
}
@ -316,7 +316,7 @@ __fi void vif1Interrupt()
return;
}
vif1Regs.stat.VGW = 0; //Path 3 isn't busy so we don't need to wait for it.
vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u16)16);
vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u32)16);
//Simulated GS transfer time done, clear the flags
}
@ -348,7 +348,7 @@ __fi void vif1Interrupt()
//NFSHPS stalls when the whole packet has gone across (it stalls in the last 32bit cmd)
//In this case VIF will end
vif1Regs.stat.FQC = std::min((u16)0x10, vif1ch.qwc);
vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
if((vif1ch.qwc > 0 || !vif1.done) && !CHECK_VIF1STALLHACK)
{
vif1Regs.stat.VPS = VPS_DECODING; //If there's more data you need to say it's decoding the next VIF CMD (Onimusha - Blade Warriors)
@ -375,7 +375,7 @@ __fi void vif1Interrupt()
_VIF1chain();
// VIF_NORMAL_FROM_MEM_MODE is a very slow operation.
// Timesplitters 2 depends on this beeing a bit higher than 128.
if (vif1ch.chcr.DIR) vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u16)16);
if (vif1ch.chcr.DIR) vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u32)16);
if(!(vif1Regs.stat.VGW && gifUnit.gifPath[GIF_PATH_3].state != GIF_PATH_IDLE)) //If we're waiting on GIF, stop looping, (can be over 1000 loops!)
CPU_INT(DMAC_VIF1, g_vif1Cycles);
@ -392,7 +392,7 @@ __fi void vif1Interrupt()
}
if ((vif1.inprogress & 0x1) == 0) vif1SetupTransfer();
if (vif1ch.chcr.DIR) vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u16)16);
if (vif1ch.chcr.DIR) vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u32)16);
if(!(vif1Regs.stat.VGW && gifUnit.gifPath[GIF_PATH_3].state != GIF_PATH_IDLE)) //If we're waiting on GIF, stop looping, (can be over 1000 loops!)
CPU_INT(DMAC_VIF1, g_vif1Cycles);
@ -416,7 +416,7 @@ __fi void vif1Interrupt()
gifRegs.stat.OPH = false;
}
if (vif1ch.chcr.DIR) vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u16)16);
if (vif1ch.chcr.DIR) vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u32)16);
vif1ch.chcr.STR = false;
vif1.vifstalled.enabled = false;
@ -480,7 +480,7 @@ void dmaVIF1()
}
if (vif1ch.chcr.DIR) vif1Regs.stat.FQC = std::min((u16)0x10, vif1ch.qwc);
if (vif1ch.chcr.DIR) vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
// Chain Mode
CPU_INT(DMAC_VIF1, 4);

View File

@ -24,7 +24,7 @@ static u32 qwctag(u32 mask)
return (dmacRegs.rbor.ADDR + (mask & dmacRegs.rbsr.RMSK));
}
static u16 QWCinVIFMFIFO(u32 DrainADDR, u16 qwc)
static u32 QWCinVIFMFIFO(u32 DrainADDR, u32 qwc)
{
u32 ret;
@ -49,7 +49,7 @@ static u16 QWCinVIFMFIFO(u32 DrainADDR, u16 qwc)
static __fi bool mfifoVIF1rbTransfer()
{
u32 msize = dmacRegs.rbor.ADDR + dmacRegs.rbsr.RMSK + 16;
u16 mfifoqwc = std::min(QWCinVIFMFIFO(vif1ch.madr, vif1ch.qwc), vif1ch.qwc);
u32 mfifoqwc = std::min(QWCinVIFMFIFO(vif1ch.madr, vif1ch.qwc), vif1ch.qwc);
u32 *src;
bool ret;
@ -318,7 +318,7 @@ void vifMFIFOInterrupt()
if (vif1Regs.stat.test(VIF1_STAT_VSS | VIF1_STAT_VIS | VIF1_STAT_VFS)) {
//vif1Regs.stat.FQC = 0; // FQC=0
//vif1ch.chcr.STR = false;
vif1Regs.stat.FQC = std::min((u16)0x10, vif1ch.qwc);
vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
VIF_LOG("VIF1 MFIFO Stalled qwc = %x done = %x inprogress = %x", vif1ch.qwc, vif1.done, vif1.inprogress & 0x10);
//Used to check if the MFIFO was empty, there's really no need if it's finished what it needed.
if((vif1ch.qwc > 0 || !vif1.done)) {
@ -347,7 +347,7 @@ void vifMFIFOInterrupt()
switch(vif1.inprogress & 1) {
case 0: //Set up transfer
mfifoVIF1transfer();
vif1Regs.stat.FQC = std::min((u16)0x10, vif1ch.qwc);
vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
[[fallthrough]];
case 1: //Transfer data
@ -357,7 +357,7 @@ void vifMFIFOInterrupt()
if(!(vif1Regs.stat.VGW && gifUnit.gifPath[GIF_PATH_3].state != GIF_PATH_IDLE)) //If we're waiting on GIF, stop looping, (can be over 1000 loops!)
CPU_INT(DMAC_MFIFO_VIF, (g_vif1Cycles == 0 ? 4 : g_vif1Cycles) );
vif1Regs.stat.FQC = std::min((u16)0x10, vif1ch.qwc);
vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
return;
}
return;
@ -372,7 +372,7 @@ void vifMFIFOInterrupt()
}
g_vif1Cycles = 0;
vif1Regs.stat.FQC = std::min((u16)0x10, vif1ch.qwc);
vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
vif1ch.chcr.STR = false;
hwDmacIrq(DMAC_VIF1);
DMA_LOG("VIF1 MFIFO DMA End");

View File

@ -278,6 +278,12 @@ static __ri void DmaExec( void (*func)(), u32 mem, u32 value )
}
reg.chcr.MOD = 0x1;
}
// As tested on hardware, if NORMAL mode is started with 0 QWC it will actually transfer 1 QWC then underflows and transfer another 0xFFFF QWC's
// The easiest way to handle this is to just say 0x10000 QWC
if (reg.chcr.STR && !reg.chcr.MOD && reg.qwc == 0)
reg.qwc = 0x10000;
if (reg.chcr.STR && dmacRegs.ctrl.DMAE && !psHu8(DMAC_ENABLER+2))
{
func();
@ -318,6 +324,12 @@ __fi bool dmacWrite32( u32 mem, mem32_t& value )
return false;
}
icase(D0_QWC) // dma0 - vif0
{
psHu32(mem) = (u16)value;
return false;
}
icase(D1_CHCR) // dma1 - vif1 - chcr
{
DMA_LOG("VIF1dma EXECUTE, value=0x%x", value);
@ -325,6 +337,12 @@ __fi bool dmacWrite32( u32 mem, mem32_t& value )
return false;
}
icase(D1_QWC) // dma1 - vif1
{
psHu32(mem) = (u16)value;
return false;
}
icase(D2_CHCR) // dma2 - gif
{
DMA_LOG("GIFdma EXECUTE, value=0x%x", value);
@ -332,6 +350,12 @@ __fi bool dmacWrite32( u32 mem, mem32_t& value )
return false;
}
icase(D2_QWC) // dma2 - gif
{
psHu32(mem) = (u16)value;
return false;
}
icase(D3_CHCR) // dma3 - fromIPU
{
DMA_LOG("IPU0dma EXECUTE, value=0x%x\n", value);
@ -339,6 +363,12 @@ __fi bool dmacWrite32( u32 mem, mem32_t& value )
return false;
}
icase(D3_QWC) // dma3 - fromIPU
{
psHu32(mem) = (u16)value;
return false;
}
icase(D4_CHCR) // dma4 - toIPU
{
DMA_LOG("IPU1dma EXECUTE, value=0x%x\n", value);
@ -346,6 +376,12 @@ __fi bool dmacWrite32( u32 mem, mem32_t& value )
return false;
}
icase(D4_QWC) // dma4 - toIPU
{
psHu32(mem) = (u16)value;
return false;
}
icase(D5_CHCR) // dma5 - sif0
{
DMA_LOG("SIF0dma EXECUTE, value=0x%x", value);
@ -353,6 +389,12 @@ __fi bool dmacWrite32( u32 mem, mem32_t& value )
return false;
}
icase(D5_QWC) // dma5 - sif0
{
psHu32(mem) = (u16)value;
return false;
}
icase(D6_CHCR) // dma6 - sif1
{
DMA_LOG("SIF1dma EXECUTE, value=0x%x", value);
@ -360,6 +402,12 @@ __fi bool dmacWrite32( u32 mem, mem32_t& value )
return false;
}
icase(D6_QWC) // dma6 - sif1
{
psHu32(mem) = (u16)value;
return false;
}
icase(D7_CHCR) // dma7 - sif2
{
DMA_LOG("SIF2dma EXECUTE, value=0x%x", value);
@ -367,6 +415,12 @@ __fi bool dmacWrite32( u32 mem, mem32_t& value )
return false;
}
icase(D7_QWC) // dma7 - sif2
{
psHu32(mem) = (u16)value;
return false;
}
icase(D8_CHCR) // dma8 - fromSPR
{
DMA_LOG("SPR0dma EXECUTE (fromSPR), value=0x%x", value);
@ -374,6 +428,12 @@ __fi bool dmacWrite32( u32 mem, mem32_t& value )
return false;
}
icase(D8_QWC) // dma8 - fromSPR
{
psHu32(mem) = (u16)value;
return false;
}
icase(fromSPR_MADR)
{
// SPR bit is fixed at 0 for this channel
@ -409,6 +469,12 @@ __fi bool dmacWrite32( u32 mem, mem32_t& value )
return false;
}
icase(D9_QWC) // dma9 - toSPR
{
psHu32(mem) = (u16)value;
return false;
}
icase(DMAC_CTRL)
{
u32 oldvalue = psHu32(mem);