mirror of https://github.com/PCSX2/pcsx2.git
VIF: Wait for VU on flush + clang format
Improves performance in MTVU + Non-instant a little.
This commit is contained in:
parent
38ee8ccfe3
commit
b058e72fdd
|
@ -248,9 +248,14 @@ __fi void cpuSetNextEventDelta( s32 delta )
|
|||
|
||||
__fi int cpuGetCycles(int interrupt)
|
||||
{
|
||||
int cycles = (cpuRegs.sCycle[interrupt] + cpuRegs.eCycle[interrupt]) - cpuRegs.cycle;
|
||||
|
||||
return std::max(1, cycles);
|
||||
if(interrupt == VU_MTVU_BUSY && (!THREAD_VU1 || INSTANT_VU1))
|
||||
return 1;
|
||||
else
|
||||
{
|
||||
const int cycles = (cpuRegs.sCycle[interrupt] + cpuRegs.eCycle[interrupt]) - cpuRegs.cycle;
|
||||
return std::max(1, cycles);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// tests the cpu cycle against the given start and delta values.
|
||||
|
|
|
@ -40,7 +40,8 @@ void vif1TransferToMemory()
|
|||
u128* pMem = (u128*)dmaGetAddr(vif1ch.madr, false);
|
||||
|
||||
// VIF from gsMemory
|
||||
if (pMem == NULL) { // Is vif0ptag empty?
|
||||
if (pMem == NULL)
|
||||
{ // Is vif0ptag empty?
|
||||
Console.WriteLn("Vif1 Tag BUSERR");
|
||||
dmacRegs.stat.BEIS = true; // Bus Error
|
||||
vif1Regs.stat.FQC = 0;
|
||||
|
@ -55,10 +56,11 @@ void vif1TransferToMemory()
|
|||
// stuff from the GS. The *only* way to handle this case safely is to flush the GS
|
||||
// completely and execute the transfer there-after.
|
||||
//Console.Warning("Real QWC %x", vif1ch.qwc);
|
||||
const u32 size = std::min(vif1.GSLastDownloadSize, (u32)vif1ch.qwc);
|
||||
const u32 size = std::min(vif1.GSLastDownloadSize, (u32)vif1ch.qwc);
|
||||
//const u128* pMemEnd = vif1.GSLastDownloadSize + pMem;
|
||||
|
||||
if (size) {
|
||||
if (size)
|
||||
{
|
||||
// Checking if any crazy game does a partial
|
||||
// gs primitive and then does a gs download...
|
||||
Gif_Path& p1 = gifUnit.gifPath[GIF_PATH_1];
|
||||
|
@ -70,7 +72,7 @@ void vif1TransferToMemory()
|
|||
}
|
||||
|
||||
GetMTGS().InitAndReadFIFO(reinterpret_cast<u8*>(pMem), size);
|
||||
// pMem += size;
|
||||
// pMem += size;
|
||||
|
||||
//Some games such as Alex Ferguson's Player Manager 2001 reads less than GSLastDownloadSize by VIF then reads the remainder by FIFO
|
||||
//Clearing the memory is clearing memory it shouldn't be and kills it.
|
||||
|
@ -89,25 +91,25 @@ void vif1TransferToMemory()
|
|||
|
||||
g_vif1Cycles += size * 2;
|
||||
vif1ch.madr += size * 16; // mgs3 scene changes
|
||||
if (vif1.GSLastDownloadSize >= vif1ch.qwc) {
|
||||
if (vif1.GSLastDownloadSize >= vif1ch.qwc)
|
||||
{
|
||||
vif1.GSLastDownloadSize -= vif1ch.qwc;
|
||||
vif1Regs.stat.FQC = std::min((u32)16, vif1.GSLastDownloadSize);
|
||||
vif1ch.qwc = 0;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
vif1Regs.stat.FQC = 0;
|
||||
vif1ch.qwc -= vif1.GSLastDownloadSize;
|
||||
vif1.GSLastDownloadSize = 0;
|
||||
//This could be potentially bad and cause hangs. I guess we will find out.
|
||||
DevCon.Warning("QWC left on VIF FIFO Reverse");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
bool _VIF1chain()
|
||||
{
|
||||
u32 *pMem;
|
||||
u32* pMem;
|
||||
|
||||
if (vif1ch.qwc == 0)
|
||||
{
|
||||
|
@ -135,7 +137,7 @@ bool _VIF1chain()
|
|||
}
|
||||
|
||||
VIF_LOG("VIF1chain size=%d, madr=%lx, tadr=%lx",
|
||||
vif1ch.qwc, vif1ch.madr, vif1ch.tadr);
|
||||
vif1ch.qwc, vif1ch.madr, vif1ch.tadr);
|
||||
|
||||
if (vif1.irqoffset.enabled)
|
||||
return VIF1transfer(pMem + vif1.irqoffset.value, vif1ch.qwc * 4 - vif1.irqoffset.value, false);
|
||||
|
@ -145,20 +147,21 @@ bool _VIF1chain()
|
|||
|
||||
__fi void vif1SetupTransfer()
|
||||
{
|
||||
tDMA_TAG *ptag;
|
||||
|
||||
tDMA_TAG* ptag;
|
||||
|
||||
ptag = dmaGetAddr(vif1ch.tadr, false); //Set memory pointer to TADR
|
||||
|
||||
if (!(vif1ch.transfer("Vif1 Tag", ptag))) return;
|
||||
if (!(vif1ch.transfer("Vif1 Tag", ptag)))
|
||||
return;
|
||||
|
||||
vif1ch.madr = ptag[1]._u32; //MADR = ADDR field + SPR
|
||||
vif1ch.madr = ptag[1]._u32; //MADR = ADDR field + SPR
|
||||
g_vif1Cycles += 1; // Add 1 g_vifCycles from the QW read for the tag
|
||||
vif1.inprogress &= ~1;
|
||||
|
||||
VIF_LOG("VIF1 Tag %8.8x_%8.8x size=%d, id=%d, madr=%lx, tadr=%lx",
|
||||
ptag[1]._u32, ptag[0]._u32, vif1ch.qwc, ptag->ID, vif1ch.madr, vif1ch.tadr);
|
||||
ptag[1]._u32, ptag[0]._u32, vif1ch.qwc, ptag->ID, vif1ch.madr, vif1ch.tadr);
|
||||
|
||||
if (!vif1.done && ((dmacRegs.ctrl.STD == STD_VIF1) && (ptag->ID == TAG_REFS))) // STD == VIF1
|
||||
if (!vif1.done && ((dmacRegs.ctrl.STD == STD_VIF1) && (ptag->ID == TAG_REFS))) // STD == VIF1
|
||||
{
|
||||
// there are still bugs, need to also check if gif->madr +16*qwc >= stadr, if not, stall
|
||||
if ((vif1ch.madr + vif1ch.qwc * 16) > dmacRegs.stadr.ADDR)
|
||||
|
@ -177,7 +180,7 @@ __fi void vif1SetupTransfer()
|
|||
bool ret;
|
||||
|
||||
alignas(16) static u128 masked_tag;
|
||||
|
||||
|
||||
masked_tag._u64[0] = 0;
|
||||
masked_tag._u64[1] = *((u64*)ptag + 1);
|
||||
|
||||
|
@ -185,7 +188,7 @@ __fi void vif1SetupTransfer()
|
|||
|
||||
if (vif1.irqoffset.enabled)
|
||||
{
|
||||
ret = VIF1transfer((u32*)&masked_tag + vif1.irqoffset.value, 4 - vif1.irqoffset.value, true); //Transfer Tag on stall
|
||||
ret = VIF1transfer((u32*)&masked_tag + vif1.irqoffset.value, 4 - vif1.irqoffset.value, true); //Transfer Tag on stall
|
||||
//ret = VIF1transfer((u32*)ptag + (2 + vif1.irqoffset), 2 - vif1.irqoffset); //Transfer Tag on stall
|
||||
}
|
||||
else
|
||||
|
@ -194,7 +197,7 @@ __fi void vif1SetupTransfer()
|
|||
// to the VU's, which breaks stuff, this is where the 128bit packet will fail, so we ignore the first 2 words
|
||||
vif1.irqoffset.value = 2;
|
||||
vif1.irqoffset.enabled = true;
|
||||
ret = VIF1transfer((u32*)&masked_tag + 2, 2, true); //Transfer Tag
|
||||
ret = VIF1transfer((u32*)&masked_tag + 2, 2, true); //Transfer Tag
|
||||
//ret = VIF1transfer((u32*)ptag + 2, 2); //Transfer Tag
|
||||
}
|
||||
|
||||
|
@ -202,7 +205,7 @@ __fi void vif1SetupTransfer()
|
|||
{
|
||||
vif1.inprogress &= ~1; // Better clear this so it has to do it again (Jak 1)
|
||||
vif1ch.qwc = 0; // Gumball 3000 pauses the DMA when the tag stalls so we need to reset the QWC, it'll be gotten again later
|
||||
return; // IRQ set by VIFTransfer
|
||||
return; // IRQ set by VIFTransfer
|
||||
}
|
||||
}
|
||||
vif1.irqoffset.value = 0;
|
||||
|
@ -210,14 +213,15 @@ __fi void vif1SetupTransfer()
|
|||
|
||||
vif1.done |= hwDmacSrcChainWithStack(vif1ch, ptag->ID);
|
||||
|
||||
if(vif1ch.qwc > 0) vif1.inprogress |= 1;
|
||||
if (vif1ch.qwc > 0)
|
||||
vif1.inprogress |= 1;
|
||||
|
||||
//Check TIE bit of CHCR and IRQ bit of tag
|
||||
if (vif1ch.chcr.TIE && ptag->IRQ)
|
||||
{
|
||||
VIF_LOG("dmaIrq Set");
|
||||
|
||||
//End Transfer
|
||||
//End Transfer
|
||||
vif1.done = true;
|
||||
return;
|
||||
}
|
||||
|
@ -228,14 +232,14 @@ __fi void vif1VUFinish()
|
|||
if (VU0.VI[REG_VPU_STAT].UL & 0x500)
|
||||
{
|
||||
vu1Thread.Get_MTVUChanges();
|
||||
|
||||
|
||||
if (THREAD_VU1 && !INSTANT_VU1 && (VU0.VI[REG_VPU_STAT].UL & 0x100))
|
||||
CPU_INT(VIF_VU1_FINISH, cpuGetCycles(VU_MTVU_BUSY));
|
||||
else
|
||||
CPU_INT(VIF_VU1_FINISH, 128);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (VU0.VI[REG_VPU_STAT].UL & 0x100)
|
||||
{
|
||||
u32 _cycles = VU1.cycle;
|
||||
|
@ -251,7 +255,7 @@ __fi void vif1VUFinish()
|
|||
vif1Regs.stat.VEW = false;
|
||||
VIF_LOG("VU1 finished");
|
||||
|
||||
if(vif1.waitforvu)
|
||||
if (vif1.waitforvu)
|
||||
{
|
||||
vif1.waitforvu = false;
|
||||
//Check if VIF is already scheduled to interrupt, if it's waiting, kick it :P
|
||||
|
@ -263,7 +267,7 @@ __fi void vif1VUFinish()
|
|||
vif1Interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//DevCon.Warning("VU1 state cleared");
|
||||
}
|
||||
|
||||
|
@ -273,19 +277,22 @@ __fi void vif1Interrupt()
|
|||
|
||||
g_vif1Cycles = 0;
|
||||
|
||||
if( gifRegs.stat.APATH == 2 && gifUnit.gifPath[GIF_PATH_2].isDone())
|
||||
if (gifRegs.stat.APATH == 2 && gifUnit.gifPath[GIF_PATH_2].isDone())
|
||||
{
|
||||
gifRegs.stat.APATH = 0;
|
||||
gifRegs.stat.OPH = 0;
|
||||
vif1Regs.stat.VGW = false; //Let vif continue if it's stuck on a flush
|
||||
|
||||
if(gifUnit.checkPaths(1,0,1)) gifUnit.Execute(false, true);
|
||||
if (gifUnit.checkPaths(1, 0, 1))
|
||||
gifUnit.Execute(false, true);
|
||||
}
|
||||
//Some games (Fahrenheit being one) start vif first, let it loop through blankness while it sets MFIFO mode, so we need to check it here.
|
||||
if (dmacRegs.ctrl.MFD == MFD_VIF1) {
|
||||
if (dmacRegs.ctrl.MFD == MFD_VIF1)
|
||||
{
|
||||
//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);
|
||||
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((u32)0x10, vif1ch.qwc);
|
||||
vifMFIFOInterrupt();
|
||||
return;
|
||||
|
@ -293,42 +300,46 @@ __fi void vif1Interrupt()
|
|||
|
||||
// We need to check the direction, if it is downloading
|
||||
// from the GS then we handle that separately (KH2 for testing)
|
||||
if (vif1ch.chcr.DIR) {
|
||||
bool isDirect = (vif1.cmd & 0x7f) == 0x50;
|
||||
if (vif1ch.chcr.DIR)
|
||||
{
|
||||
bool isDirect = (vif1.cmd & 0x7f) == 0x50;
|
||||
bool isDirectHL = (vif1.cmd & 0x7f) == 0x51;
|
||||
if((isDirect && !gifUnit.CanDoPath2())
|
||||
|| (isDirectHL && !gifUnit.CanDoPath2HL())) {
|
||||
if ((isDirect && !gifUnit.CanDoPath2()) || (isDirectHL && !gifUnit.CanDoPath2HL()))
|
||||
{
|
||||
GUNIT_WARN("vif1Interrupt() - Waiting for Path 2 to be ready");
|
||||
CPU_INT(DMAC_VIF1, 128);
|
||||
if(gifRegs.stat.APATH == 3) vif1Regs.stat.VGW = 1; //We're waiting for path 3. Gunslinger II
|
||||
if (gifRegs.stat.APATH == 3)
|
||||
vif1Regs.stat.VGW = 1; //We're waiting for path 3. Gunslinger II
|
||||
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, (u32)16);
|
||||
//Simulated GS transfer time done, clear the flags
|
||||
}
|
||||
|
||||
if(vif1.waitforvu)
|
||||
|
||||
if (vif1.waitforvu)
|
||||
{
|
||||
//DevCon.Warning("Waiting on VU1");
|
||||
//CPU_INT(DMAC_VIF1, 16);
|
||||
CPU_INT(VIF_VU1_FINISH, 16);
|
||||
CPU_INT(VIF_VU1_FINISH, std::max(16, cpuGetCycles(VU_MTVU_BUSY)));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (vif1Regs.stat.VGW)
|
||||
return;
|
||||
|
||||
if (!vif1ch.chcr.STR) Console.WriteLn("Vif1 running when CHCR == %x", vif1ch.chcr._u32);
|
||||
|
||||
if (!vif1ch.chcr.STR)
|
||||
Console.WriteLn("Vif1 running when CHCR == %x", vif1ch.chcr._u32);
|
||||
|
||||
if (vif1.irq && vif1.vifstalled.enabled && vif1.vifstalled.value == VIF_IRQ_STALL)
|
||||
{
|
||||
VIF_LOG("VIF IRQ Firing");
|
||||
if (!vif1Regs.stat.ER1)
|
||||
vif1Regs.stat.INT = true;
|
||||
|
||||
|
||||
//Yakuza watches VIF_STAT so lets do this here.
|
||||
if (((vif1Regs.code >> 24) & 0x7f) != 0x7) {
|
||||
if (((vif1Regs.code >> 24) & 0x7f) != 0x7)
|
||||
{
|
||||
vif1Regs.stat.VIS = true;
|
||||
}
|
||||
|
||||
|
@ -342,7 +353,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((u32)0x10, vif1ch.qwc);
|
||||
if((vif1ch.qwc > 0 || !vif1.done) && !CHECK_VIF1STALLHACK)
|
||||
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)
|
||||
VIF_LOG("VIF1 Stalled");
|
||||
|
@ -356,40 +367,62 @@ __fi void vif1Interrupt()
|
|||
//Mirroring change to VIF0
|
||||
if (vif1.cmd)
|
||||
{
|
||||
if (vif1.done && (vif1ch.qwc == 0)) vif1Regs.stat.VPS = VPS_WAITING;
|
||||
if (vif1.done && (vif1ch.qwc == 0))
|
||||
vif1Regs.stat.VPS = VPS_WAITING;
|
||||
}
|
||||
else
|
||||
{
|
||||
vif1Regs.stat.VPS = VPS_IDLE;
|
||||
}
|
||||
|
||||
|
||||
if (vif1.inprogress & 0x1)
|
||||
{
|
||||
_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, (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!)
|
||||
{
|
||||
_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, (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!)
|
||||
{
|
||||
if (vif1.waitforvu)
|
||||
{
|
||||
//if (cpuGetCycles(VU_MTVU_BUSY) > static_cast<int>(g_vif1Cycles))
|
||||
// DevCon.Warning("Waiting %d instead of %d", cpuGetCycles(VU_MTVU_BUSY), static_cast<int>(g_vif1Cycles));
|
||||
CPU_INT(DMAC_VIF1, std::max(static_cast<int>(g_vif1Cycles), cpuGetCycles(VU_MTVU_BUSY)));
|
||||
}
|
||||
else
|
||||
CPU_INT(DMAC_VIF1, g_vif1Cycles);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vif1.done)
|
||||
{
|
||||
if (!vif1.done)
|
||||
{
|
||||
|
||||
if (!(dmacRegs.ctrl.DMAE) || vif1Regs.stat.VSS) //Stopped or DMA Disabled
|
||||
{
|
||||
//Console.WriteLn("vif1 dma masked");
|
||||
return;
|
||||
}
|
||||
if (!(dmacRegs.ctrl.DMAE) || vif1Regs.stat.VSS) //Stopped or DMA Disabled
|
||||
{
|
||||
//Console.WriteLn("vif1 dma masked");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((vif1.inprogress & 0x1) == 0) vif1SetupTransfer();
|
||||
if (vif1ch.chcr.DIR) vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u32)16);
|
||||
if ((vif1.inprogress & 0x1) == 0)
|
||||
vif1SetupTransfer();
|
||||
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);
|
||||
return;
|
||||
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!)
|
||||
{
|
||||
if (vif1.waitforvu)
|
||||
{
|
||||
//if (cpuGetCycles(VU_MTVU_BUSY) > static_cast<int>(g_vif1Cycles))
|
||||
// DevCon.Warning("Waiting %d instead of %d", cpuGetCycles(VU_MTVU_BUSY), static_cast<int>(g_vif1Cycles));
|
||||
CPU_INT(DMAC_VIF1, std::max(static_cast<int>(g_vif1Cycles), cpuGetCycles(VU_MTVU_BUSY)));
|
||||
}
|
||||
else
|
||||
CPU_INT(DMAC_VIF1, g_vif1Cycles);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (vif1.vifstalled.enabled && vif1.done)
|
||||
|
@ -399,52 +432,55 @@ __fi void vif1Interrupt()
|
|||
return; //Dont want to end if vif is stalled.
|
||||
}
|
||||
#ifdef PCSX2_DEVBUILD
|
||||
if (vif1ch.qwc > 0) DevCon.WriteLn("VIF1 Ending with %x QWC left", vif1ch.qwc);
|
||||
if (vif1.cmd != 0) DevCon.WriteLn("vif1.cmd still set %x tag size %x", vif1.cmd, vif1.tag.size);
|
||||
if (vif1ch.qwc > 0)
|
||||
DevCon.WriteLn("VIF1 Ending with %x QWC left", vif1ch.qwc);
|
||||
if (vif1.cmd != 0)
|
||||
DevCon.WriteLn("vif1.cmd still set %x tag size %x", vif1.cmd, vif1.tag.size);
|
||||
#endif
|
||||
|
||||
if((vif1ch.chcr.DIR == VIF_NORMAL_TO_MEM_MODE) && vif1.GSLastDownloadSize <= 16)
|
||||
if ((vif1ch.chcr.DIR == VIF_NORMAL_TO_MEM_MODE) && vif1.GSLastDownloadSize <= 16)
|
||||
{
|
||||
//Reverse fifo has finished and nothing is left, so lets clear the outputting flag
|
||||
gifRegs.stat.OPH = false;
|
||||
}
|
||||
|
||||
if (vif1ch.chcr.DIR) vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u32)16);
|
||||
if (vif1ch.chcr.DIR)
|
||||
vif1Regs.stat.FQC = std::min(vif1ch.qwc, (u32)16);
|
||||
|
||||
vif1ch.chcr.STR = false;
|
||||
vif1.vifstalled.enabled = false;
|
||||
vif1.irqoffset.enabled = false;
|
||||
if(vif1.queued_program) vifExecQueue(1);
|
||||
if (vif1.queued_program)
|
||||
vifExecQueue(1);
|
||||
g_vif1Cycles = 0;
|
||||
VIF_LOG("VIF1 DMA End");
|
||||
hwDmacIrq(DMAC_VIF1);
|
||||
|
||||
}
|
||||
|
||||
void dmaVIF1()
|
||||
{
|
||||
VIF_LOG("dmaVIF1 chcr = %lx, madr = %lx, qwc = %lx\n"
|
||||
" tadr = %lx, asr0 = %lx, asr1 = %lx",
|
||||
vif1ch.chcr._u32, vif1ch.madr, vif1ch.qwc,
|
||||
vif1ch.tadr, vif1ch.asr0, vif1ch.asr1);
|
||||
" tadr = %lx, asr0 = %lx, asr1 = %lx",
|
||||
vif1ch.chcr._u32, vif1ch.madr, vif1ch.qwc,
|
||||
vif1ch.tadr, vif1ch.asr0, vif1ch.asr1);
|
||||
|
||||
g_vif1Cycles = 0;
|
||||
vif1.inprogress = 0;
|
||||
|
||||
if (vif1ch.qwc > 0) // Normal Mode
|
||||
if (vif1ch.qwc > 0) // Normal Mode
|
||||
{
|
||||
|
||||
|
||||
// ignore tag if it's a GS download (Def Jam Fight for NY)
|
||||
if(vif1ch.chcr.MOD == CHAIN_MODE && vif1ch.chcr.DIR)
|
||||
if (vif1ch.chcr.MOD == CHAIN_MODE && vif1ch.chcr.DIR)
|
||||
{
|
||||
vif1.dmamode = VIF_CHAIN_MODE;
|
||||
//DevCon.Warning(L"VIF1 QWC on Chain CHCR " + vif1ch.chcr.desc());
|
||||
|
||||
|
||||
if ((vif1ch.chcr.tag().ID == TAG_REFE) || (vif1ch.chcr.tag().ID == TAG_END) || (vif1ch.chcr.tag().IRQ && vif1ch.chcr.TIE))
|
||||
{
|
||||
vif1.done = true;
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
vif1.done = false;
|
||||
}
|
||||
|
@ -454,12 +490,13 @@ void dmaVIF1()
|
|||
if (dmacRegs.ctrl.STD == STD_VIF1)
|
||||
Console.WriteLn("DMA Stall Control on VIF1 normal not implemented - Report which game to PCSX2 Team");
|
||||
|
||||
if (vif1ch.chcr.DIR) // from Memory
|
||||
if (vif1ch.chcr.DIR) // from Memory
|
||||
vif1.dmamode = VIF_NORMAL_FROM_MEM_MODE;
|
||||
else
|
||||
vif1.dmamode = VIF_NORMAL_TO_MEM_MODE;
|
||||
|
||||
if(vif1.irqoffset.enabled && !vif1.done) DevCon.Warning("Warning! VIF1 starting a Normal transfer with vif offset set (Possible force stop?)");
|
||||
if (vif1.irqoffset.enabled && !vif1.done)
|
||||
DevCon.Warning("Warning! VIF1 starting a Normal transfer with vif offset set (Possible force stop?)");
|
||||
vif1.done = true;
|
||||
}
|
||||
|
||||
|
@ -470,10 +507,10 @@ void dmaVIF1()
|
|||
vif1.inprogress &= ~0x1;
|
||||
vif1.dmamode = VIF_CHAIN_MODE;
|
||||
vif1.done = false;
|
||||
|
||||
}
|
||||
|
||||
if (vif1ch.chcr.DIR) vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
|
||||
if (vif1ch.chcr.DIR)
|
||||
vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
|
||||
|
||||
// Check VIF isn't stalled before starting the loop.
|
||||
// Batman Vengence does something stupid and instead of cancelling a stall it tries to restart VIF, THEN check the stall
|
||||
|
|
|
@ -27,9 +27,9 @@ static u32 qwctag(u32 mask)
|
|||
static u32 QWCinVIFMFIFO(u32 DrainADDR, u32 qwc)
|
||||
{
|
||||
u32 ret;
|
||||
|
||||
|
||||
//Calculate what we have in the fifo.
|
||||
if(DrainADDR <= spr0ch.madr)
|
||||
if (DrainADDR <= spr0ch.madr)
|
||||
{
|
||||
//Drain is below the tadr, calculate the difference between them
|
||||
ret = (spr0ch.madr - DrainADDR) >> 4;
|
||||
|
@ -37,7 +37,7 @@ static u32 QWCinVIFMFIFO(u32 DrainADDR, u32 qwc)
|
|||
else
|
||||
{
|
||||
u32 limit = dmacRegs.rbor.ADDR + dmacRegs.rbsr.RMSK + 16;
|
||||
//Drain is higher than SPR so it has looped round,
|
||||
//Drain is higher than SPR so it has looped round,
|
||||
//calculate from base to the SPR tag addr and what is left in the top of the ring
|
||||
ret = ((spr0ch.madr - dmacRegs.rbor.ADDR) + (limit - DrainADDR)) >> 4;
|
||||
}
|
||||
|
@ -50,18 +50,19 @@ static __fi bool mfifoVIF1rbTransfer()
|
|||
{
|
||||
u32 msize = dmacRegs.rbor.ADDR + dmacRegs.rbsr.RMSK + 16;
|
||||
u32 mfifoqwc = std::min(QWCinVIFMFIFO(vif1ch.madr, vif1ch.qwc), vif1ch.qwc);
|
||||
u32 *src;
|
||||
u32* src;
|
||||
bool ret;
|
||||
|
||||
if (mfifoqwc == 0) {
|
||||
|
||||
if (mfifoqwc == 0)
|
||||
{
|
||||
DevCon.Warning("VIF MFIFO no QWC before transfer (in transfer function, bit late really)");
|
||||
return true; //Cant do anything, lets forget it
|
||||
return true; //Cant do anything, lets forget it
|
||||
}
|
||||
|
||||
/* Check if the transfer should wrap around the ring buffer */
|
||||
if ((vif1ch.madr + (mfifoqwc << 4)) > (msize))
|
||||
{
|
||||
int s1 = ((msize) - vif1ch.madr) >> 2;
|
||||
int s1 = ((msize)-vif1ch.madr) >> 2;
|
||||
|
||||
VIF_LOG("Split MFIFO");
|
||||
|
||||
|
@ -69,7 +70,8 @@ static __fi bool mfifoVIF1rbTransfer()
|
|||
vif1ch.madr = qwctag(vif1ch.madr);
|
||||
|
||||
src = (u32*)PSM(vif1ch.madr);
|
||||
if (src == NULL) return false;
|
||||
if (src == NULL)
|
||||
return false;
|
||||
|
||||
if (vif1.irqoffset.enabled)
|
||||
ret = VIF1transfer(src + vif1.irqoffset.value, s1 - vif1.irqoffset.value);
|
||||
|
@ -78,15 +80,17 @@ static __fi bool mfifoVIF1rbTransfer()
|
|||
|
||||
if (ret)
|
||||
{
|
||||
if(vif1.irqoffset.value != 0) DevCon.Warning("VIF1 MFIFO Offest != 0! vifoffset=%x", vif1.irqoffset.value);
|
||||
/* and second copy 's2' bytes from 'maddr' to '&data[s1]' */
|
||||
if (vif1.irqoffset.value != 0)
|
||||
DevCon.Warning("VIF1 MFIFO Offest != 0! vifoffset=%x", vif1.irqoffset.value);
|
||||
/* and second copy 's2' bytes from 'maddr' to '&data[s1]' */
|
||||
//DevCon.Warning("Loopyloop");
|
||||
vif1ch.tadr = qwctag(vif1ch.tadr);
|
||||
vif1ch.madr = qwctag(vif1ch.madr);
|
||||
|
||||
src = (u32*)PSM(vif1ch.madr);
|
||||
if (src == NULL) return false;
|
||||
VIF1transfer(src, ((mfifoqwc << 2) - s1));
|
||||
src = (u32*)PSM(vif1ch.madr);
|
||||
if (src == NULL)
|
||||
return false;
|
||||
VIF1transfer(src, ((mfifoqwc << 2) - s1));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -95,7 +99,8 @@ static __fi bool mfifoVIF1rbTransfer()
|
|||
|
||||
/* it doesn't, so just transfer 'qwc*4' words */
|
||||
src = (u32*)PSM(vif1ch.madr);
|
||||
if (src == NULL) return false;
|
||||
if (src == NULL)
|
||||
return false;
|
||||
|
||||
if (vif1.irqoffset.enabled)
|
||||
ret = VIF1transfer(src + vif1.irqoffset.value, mfifoqwc * 4 - vif1.irqoffset.value);
|
||||
|
@ -115,10 +120,11 @@ static __fi void mfifo_VIF1chain()
|
|||
}
|
||||
|
||||
if (vif1ch.madr >= dmacRegs.rbor.ADDR &&
|
||||
vif1ch.madr < (dmacRegs.rbor.ADDR + dmacRegs.rbsr.RMSK + 16u))
|
||||
vif1ch.madr < (dmacRegs.rbor.ADDR + dmacRegs.rbsr.RMSK + 16u))
|
||||
{
|
||||
//if(vif1ch.madr == (dmacRegs.rbor.ADDR + dmacRegs.rbsr.RMSK + 16)) DevCon.Warning("Edge VIF1");
|
||||
if (QWCinVIFMFIFO(vif1ch.madr, vif1ch.qwc) == 0) {
|
||||
if (QWCinVIFMFIFO(vif1ch.madr, vif1ch.qwc) == 0)
|
||||
{
|
||||
VIF_LOG("VIF MFIFO Empty before transfer");
|
||||
vif1.inprogress |= 0x10;
|
||||
g_vif1Cycles += 4;
|
||||
|
@ -132,16 +138,16 @@ static __fi void mfifo_VIF1chain()
|
|||
//It does an END tag (which normally doesn't increment TADR because it breaks Soul Calibur 2)
|
||||
//with a QWC of 1 (rare) so we need to increment the TADR in the case of MFIFO.
|
||||
vif1ch.tadr = vif1ch.madr;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
tDMA_TAG *pMem = dmaGetAddr(vif1ch.madr, !vif1ch.chcr.DIR);
|
||||
tDMA_TAG* pMem = dmaGetAddr(vif1ch.madr, !vif1ch.chcr.DIR);
|
||||
VIF_LOG("Non-MFIFO Location");
|
||||
|
||||
//No need to exit on non-mfifo as it is indirect anyway, so it can be transferring this while spr refills the mfifo
|
||||
|
||||
if (pMem == NULL) return;
|
||||
if (pMem == NULL)
|
||||
return;
|
||||
|
||||
if (vif1.irqoffset.enabled)
|
||||
VIF1transfer((u32*)pMem + vif1.irqoffset.value, vif1ch.qwc * 4 - vif1.irqoffset.value);
|
||||
|
@ -152,19 +158,20 @@ static __fi void mfifo_VIF1chain()
|
|||
|
||||
void mfifoVifMaskMem(int id)
|
||||
{
|
||||
switch (id) {
|
||||
switch (id)
|
||||
{
|
||||
//These five transfer data following the tag, need to check its within the buffer (Front Mission 4)
|
||||
case TAG_CNT:
|
||||
case TAG_NEXT:
|
||||
case TAG_CALL:
|
||||
case TAG_CALL:
|
||||
case TAG_RET:
|
||||
case TAG_END:
|
||||
if(vif1ch.madr < dmacRegs.rbor.ADDR) //probably not needed but we will check anyway.
|
||||
if (vif1ch.madr < dmacRegs.rbor.ADDR) //probably not needed but we will check anyway.
|
||||
{
|
||||
//DevCon.Warning("VIF MFIFO MADR below bottom of ring buffer, wrapping VIF MADR = %x Ring Bottom %x", vif1ch.madr, dmacRegs.rbor.ADDR);
|
||||
vif1ch.madr = qwctag(vif1ch.madr);
|
||||
}
|
||||
if(vif1ch.madr > (dmacRegs.rbor.ADDR + (u32)dmacRegs.rbsr.RMSK)) //Usual scenario is the tag is near the end (Front Mission 4)
|
||||
if (vif1ch.madr > (dmacRegs.rbor.ADDR + (u32)dmacRegs.rbsr.RMSK)) //Usual scenario is the tag is near the end (Front Mission 4)
|
||||
{
|
||||
//DevCon.Warning("VIF MFIFO MADR outside top of ring buffer, wrapping VIF MADR = %x Ring Top %x", vif1ch.madr, (dmacRegs.rbor.ADDR + dmacRegs.rbsr.RMSK)+16);
|
||||
vif1ch.madr = qwctag(vif1ch.madr);
|
||||
|
@ -178,13 +185,14 @@ void mfifoVifMaskMem(int id)
|
|||
|
||||
void mfifoVIF1transfer()
|
||||
{
|
||||
tDMA_TAG *ptag;
|
||||
tDMA_TAG* ptag;
|
||||
|
||||
g_vif1Cycles = 0;
|
||||
|
||||
if (vif1ch.qwc == 0)
|
||||
{
|
||||
if (QWCinVIFMFIFO(vif1ch.tadr, 1) == 0) {
|
||||
if (QWCinVIFMFIFO(vif1ch.tadr, 1) == 0)
|
||||
{
|
||||
VIF_LOG("VIF MFIFO Empty before tag");
|
||||
vif1.inprogress |= 0x10;
|
||||
g_vif1Cycles += 4;
|
||||
|
@ -212,21 +220,20 @@ void mfifoVIF1transfer()
|
|||
|
||||
if (vif1.irqoffset.enabled)
|
||||
{
|
||||
ret = VIF1transfer((u32*)&masked_tag + vif1.irqoffset.value, 4 - vif1.irqoffset.value, true); //Transfer Tag on stall
|
||||
ret = VIF1transfer((u32*)&masked_tag + vif1.irqoffset.value, 4 - vif1.irqoffset.value, true); //Transfer Tag on stall
|
||||
//ret = VIF1transfer((u32*)ptag + (2 + vif1.irqoffset), 2 - vif1.irqoffset); //Transfer Tag on stall
|
||||
}
|
||||
else
|
||||
{
|
||||
vif1.irqoffset.value = 2;
|
||||
vif1.irqoffset.enabled = true;
|
||||
ret = VIF1transfer((u32*)&masked_tag + 2, 2, true); //Transfer Tag
|
||||
ret = VIF1transfer((u32*)&masked_tag + 2, 2, true); //Transfer Tag
|
||||
}
|
||||
|
||||
if (!ret && vif1.irqoffset.enabled)
|
||||
{
|
||||
vif1.inprogress &= ~1;
|
||||
return; //IRQ set by VIFTransfer
|
||||
|
||||
return; //IRQ set by VIFTransfer
|
||||
}
|
||||
g_vif1Cycles += 2;
|
||||
}
|
||||
|
@ -234,12 +241,12 @@ void mfifoVIF1transfer()
|
|||
vif1.irqoffset.value = 0;
|
||||
vif1.irqoffset.enabled = false;
|
||||
|
||||
vif1ch.unsafeTransfer(ptag);
|
||||
vif1ch.unsafeTransfer(ptag);
|
||||
|
||||
vif1ch.madr = ptag[1]._u32;
|
||||
|
||||
VIF_LOG("dmaChain %8.8x_%8.8x size=%d, id=%d, madr=%lx, tadr=%lx spr0 madr = %x",
|
||||
ptag[1]._u32, ptag[0]._u32, vif1ch.qwc, ptag->ID, vif1ch.madr, vif1ch.tadr, spr0ch.madr);
|
||||
ptag[1]._u32, ptag[0]._u32, vif1ch.qwc, ptag->ID, vif1ch.madr, vif1ch.tadr, spr0ch.madr);
|
||||
|
||||
vif1.done |= hwDmacSrcChainWithStack(vif1ch, ptag->ID);
|
||||
|
||||
|
@ -253,13 +260,14 @@ void mfifoVIF1transfer()
|
|||
|
||||
vif1ch.tadr = qwctag(vif1ch.tadr);
|
||||
|
||||
if(vif1ch.qwc > 0) vif1.inprogress |= 1;
|
||||
if (vif1ch.qwc > 0)
|
||||
vif1.inprogress |= 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
DevCon.Warning("Vif MFIFO QWC not 0 on tag");
|
||||
}
|
||||
|
||||
|
||||
|
||||
VIF_LOG("mfifoVIF1transfer end %x madr %x, tadr %x", vif1ch.chcr._u32, vif1ch.madr, vif1ch.tadr);
|
||||
}
|
||||
|
@ -269,33 +277,36 @@ void vifMFIFOInterrupt()
|
|||
g_vif1Cycles = 0;
|
||||
VIF_LOG("vif mfifo interrupt");
|
||||
|
||||
if (dmacRegs.ctrl.MFD != MFD_VIF1) {
|
||||
if (dmacRegs.ctrl.MFD != MFD_VIF1)
|
||||
{
|
||||
vif1Interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
if( gifRegs.stat.APATH == 2 && gifUnit.gifPath[1].isDone())
|
||||
if (gifRegs.stat.APATH == 2 && gifUnit.gifPath[1].isDone())
|
||||
{
|
||||
gifRegs.stat.APATH = 0;
|
||||
gifRegs.stat.OPH = 0;
|
||||
|
||||
if(gifUnit.checkPaths(1,0,1)) gifUnit.Execute(false, true);
|
||||
if (gifUnit.checkPaths(1, 0, 1))
|
||||
gifUnit.Execute(false, true);
|
||||
}
|
||||
|
||||
if (vif1ch.chcr.DIR) {
|
||||
bool isDirect = (vif1.cmd & 0x7f) == 0x50;
|
||||
if (vif1ch.chcr.DIR)
|
||||
{
|
||||
bool isDirect = (vif1.cmd & 0x7f) == 0x50;
|
||||
bool isDirectHL = (vif1.cmd & 0x7f) == 0x51;
|
||||
if((isDirect && !gifUnit.CanDoPath2())
|
||||
|| (isDirectHL && !gifUnit.CanDoPath2HL())) {
|
||||
if ((isDirect && !gifUnit.CanDoPath2()) || (isDirectHL && !gifUnit.CanDoPath2HL()))
|
||||
{
|
||||
GUNIT_WARN("vifMFIFOInterrupt() - Waiting for Path 2 to be ready");
|
||||
CPU_INT(DMAC_MFIFO_VIF, 128);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(vif1.waitforvu)
|
||||
if (vif1.waitforvu)
|
||||
{
|
||||
// DevCon.Warning("Waiting on VU1 MFIFO");
|
||||
CPU_INT(VIF_VU1_FINISH, 16);
|
||||
//DevCon.Warning("Waiting on VU1 MFIFO");
|
||||
CPU_INT(VIF_VU1_FINISH, std::max(16, cpuGetCycles(VU_MTVU_BUSY)));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -303,25 +314,29 @@ void vifMFIFOInterrupt()
|
|||
// we handle that separately (KH2 for testing)
|
||||
|
||||
// Simulated GS transfer time done, clear the flags
|
||||
|
||||
if (vif1.irq && vif1.vifstalled.enabled && vif1.vifstalled.value == VIF_IRQ_STALL) {
|
||||
|
||||
if (vif1.irq && vif1.vifstalled.enabled && vif1.vifstalled.value == VIF_IRQ_STALL)
|
||||
{
|
||||
VIF_LOG("VIF MFIFO Code Interrupt detected");
|
||||
vif1Regs.stat.INT = true;
|
||||
|
||||
if (((vif1Regs.code >> 24) & 0x7f) != 0x7) {
|
||||
if (((vif1Regs.code >> 24) & 0x7f) != 0x7)
|
||||
{
|
||||
vif1Regs.stat.VIS = true;
|
||||
}
|
||||
|
||||
hwIntcIrq(INTC_VIF1);
|
||||
--vif1.irq;
|
||||
|
||||
if (vif1Regs.stat.test(VIF1_STAT_VSS | VIF1_STAT_VIS | VIF1_STAT_VFS)) {
|
||||
|
||||
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((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)) {
|
||||
if ((vif1ch.qwc > 0 || !vif1.done))
|
||||
{
|
||||
vif1Regs.stat.VPS = VPS_DECODING; //If there's more data you need to say it's decoding the next VIF CMD (Onimusha - Blade Warriors)
|
||||
VIF_LOG("VIF1 MFIFO Stalled");
|
||||
return;
|
||||
|
@ -330,33 +345,48 @@ void vifMFIFOInterrupt()
|
|||
}
|
||||
|
||||
//Mirroring change to VIF0
|
||||
if (vif1.cmd) {
|
||||
if (vif1.done && vif1ch.qwc == 0) vif1Regs.stat.VPS = VPS_WAITING;
|
||||
if (vif1.cmd)
|
||||
{
|
||||
if (vif1.done && vif1ch.qwc == 0)
|
||||
vif1Regs.stat.VPS = VPS_WAITING;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
vif1Regs.stat.VPS = VPS_IDLE;
|
||||
}
|
||||
|
||||
if(vif1.inprogress & 0x10) {
|
||||
if (vif1.inprogress & 0x10)
|
||||
{
|
||||
FireMFIFOEmpty();
|
||||
return;
|
||||
}
|
||||
|
||||
vif1.vifstalled.enabled = false;
|
||||
|
||||
if (!vif1.done || vif1ch.qwc) {
|
||||
switch(vif1.inprogress & 1) {
|
||||
if (!vif1.done || vif1ch.qwc)
|
||||
{
|
||||
switch (vif1.inprogress & 1)
|
||||
{
|
||||
case 0: //Set up transfer
|
||||
mfifoVIF1transfer();
|
||||
vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
|
||||
[[fallthrough]];
|
||||
|
||||
case 1: //Transfer data
|
||||
if(vif1.inprogress & 0x1) //Just in case the tag breaks early (or something wierd happens)!
|
||||
if (vif1.inprogress & 0x1) //Just in case the tag breaks early (or something wierd happens)!
|
||||
mfifo_VIF1chain();
|
||||
//Sanity check! making sure we always have non-zero values
|
||||
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) );
|
||||
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!)
|
||||
{
|
||||
if (vif1.waitforvu)
|
||||
{
|
||||
//if (cpuGetCycles(VU_MTVU_BUSY) > static_cast<int>(g_vif1Cycles))
|
||||
// DevCon.Warning("Waiting %d instead of %d", cpuGetCycles(VU_MTVU_BUSY), static_cast<int>(g_vif1Cycles));
|
||||
CPU_INT(DMAC_MFIFO_VIF, std::max(static_cast<int>((g_vif1Cycles == 0 ? 4 : g_vif1Cycles)), cpuGetCycles(VU_MTVU_BUSY)));
|
||||
}
|
||||
else
|
||||
CPU_INT(DMAC_MFIFO_VIF, (g_vif1Cycles == 0 ? 4 : g_vif1Cycles));
|
||||
}
|
||||
|
||||
vif1Regs.stat.FQC = std::min((u32)0x10, vif1ch.qwc);
|
||||
return;
|
||||
|
@ -368,7 +398,8 @@ void vifMFIFOInterrupt()
|
|||
vif1.irqoffset.enabled = false;
|
||||
vif1.done = 1;
|
||||
|
||||
if (spr0ch.madr == vif1ch.tadr) {
|
||||
if (spr0ch.madr == vif1ch.tadr)
|
||||
{
|
||||
FireMFIFOEmpty();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue