// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team // SPDX-License-Identifier: GPL-3.0+ #include "Common.h" #include "Hardware.h" #include "MTVU.h" #include "IPU/IPUdma.h" #include "ps2/HwInternal.h" bool DMACh::transfer(const char *s, tDMA_TAG* ptag) { if (ptag == NULL) // Is ptag empty? { throwBusError(s); return false; } chcrTransfer(ptag); qwcTransfer(ptag); return true; } void DMACh::unsafeTransfer(tDMA_TAG* ptag) { chcrTransfer(ptag); qwcTransfer(ptag); } tDMA_TAG *DMACh::getAddr(u32 addr, u32 num, bool write) { tDMA_TAG *ptr = dmaGetAddr(addr, write); if (ptr == NULL) { throwBusError("dmaGetAddr"); setDmacStat(num); chcr.STR = false; } return ptr; } tDMA_TAG *DMACh::DMAtransfer(u32 addr, u32 num) { tDMA_TAG *tag = getAddr(addr, num, false); if (tag == NULL) return NULL; chcrTransfer(tag); qwcTransfer(tag); return tag; } tDMA_TAG DMACh::dma_tag() { return chcr.tag(); } std::string DMACh::cmq_to_str() const { return StringUtil::StdStringFromFormat("chcr = %x, madr = %x, qwc = %x", chcr._u32, madr, qwc); } std::string DMACh::cmqt_to_str() const { return StringUtil::StdStringFromFormat("chcr = %x, madr = %x, qwc = %x, tadr = %1x", chcr._u32, madr, qwc, tadr); } __fi void throwBusError(const char *s) { Console.Error("%s BUSERR", s); dmacRegs.stat.BEIS = true; } __fi void setDmacStat(u32 num) { dmacRegs.stat.set_flags(1 << num); } // Note: Dma addresses are guaranteed to be aligned to 16 bytes (128 bits) __fi tDMA_TAG* SPRdmaGetAddr(u32 addr, bool write) { // if (addr & 0xf) { DMA_LOG("*PCSX2*: DMA address not 128bit aligned: %8.8x", addr); } //For some reason Getaway references SPR Memory from itself using SPR0, oh well, let it i guess... if((addr & 0x70000000) == 0x70000000) { return (tDMA_TAG*)&eeMem->Scratch[addr & 0x3ff0]; } // FIXME: Why??? DMA uses physical addresses addr &= 0x1ffffff0; if (addr < Ps2MemSize::ExposedRam) { return (tDMA_TAG*)&eeMem->Main[addr]; } else if (addr < 0x10000000) { return (tDMA_TAG*)(write ? eeMem->ZeroWrite : eeMem->ZeroRead); } else if ((addr >= 0x11000000) && (addr < 0x11010000)) { if (addr >= 0x11008000 && THREAD_VU1) { DevCon.Warning("MTVU: SPR Accessing VU1 Memory"); vu1Thread.WaitVU(); } //Access for VU Memory if((addr >= 0x1100c000) && (addr < 0x11010000)) { //DevCon.Warning("VU1 Mem %x", addr); return (tDMA_TAG*)(VU1.Mem + (addr & 0x3ff0)); } if((addr >= 0x11004000) && (addr < 0x11008000)) { //DevCon.Warning("VU0 Mem %x", addr); return (tDMA_TAG*)(VU0.Mem + (addr & 0xff0)); } //Possibly not needed but the manual doesn't say SPR cannot access it. if((addr >= 0x11000000) && (addr < 0x11004000)) { //DevCon.Warning("VU0 Micro %x", addr); return (tDMA_TAG*)(VU0.Micro + (addr & 0xff0)); } if((addr >= 0x11008000) && (addr < 0x1100c000)) { //DevCon.Warning("VU1 Micro %x", addr); return (tDMA_TAG*)(VU1.Micro + (addr & 0x3ff0)); } // Unreachable return NULL; } else { Console.Error( "*PCSX2*: DMA error: %8.8x", addr); return NULL; } } // Note: Dma addresses are guaranteed to be aligned to 16 bytes (128 bits) __ri tDMA_TAG *dmaGetAddr(u32 addr, bool write) { // if (addr & 0xf) { DMA_LOG("*PCSX2*: DMA address not 128bit aligned: %8.8x", addr); } if (DMA_TAG(addr).SPR) return (tDMA_TAG*)&eeMem->Scratch[addr & 0x3ff0]; // FIXME: Why??? DMA uses physical addresses addr &= 0x1ffffff0; if (addr < Ps2MemSize::ExposedRam) { return (tDMA_TAG*)&eeMem->Main[addr]; } else if (addr < 0x10000000) { return (tDMA_TAG*)(write ? eeMem->ZeroWrite : eeMem->ZeroRead); } else if (addr < 0x10004000) { // Secret scratchpad address for DMA = end of maximum main memory? //Console.Warning("Writing to the scratchpad without the SPR flag set!"); return (tDMA_TAG*)&eeMem->Scratch[addr & 0x3ff0]; } else { Console.Error( "*PCSX2*: DMA error: %8.8x", addr); return NULL; } } // Returns true if the DMA is enabled and executed successfully. Returns false if execution // was blocked (DMAE or master DMA enabler). static bool QuickDmaExec( void (*func)(), u32 mem) { bool ret = false; DMACh& reg = (DMACh&)psHu32(mem); if (reg.chcr.STR && dmacRegs.ctrl.DMAE && !psHu8(DMAC_ENABLER+2)) { func(); ret = true; } return ret; } static tDMAC_QUEUE QueuedDMA(0); static u32 oldvalue = 0; static void StartQueuedDMA() { if (QueuedDMA.VIF0) { DMA_LOG("Resuming DMA for VIF0"); QueuedDMA.VIF0 = !QuickDmaExec(dmaVIF0, D0_CHCR); } if (QueuedDMA.VIF1) { DMA_LOG("Resuming DMA for VIF1"); QueuedDMA.VIF1 = !QuickDmaExec(dmaVIF1, D1_CHCR); } if (QueuedDMA.GIF ) { DMA_LOG("Resuming DMA for GIF" ); QueuedDMA.GIF = !QuickDmaExec(dmaGIF , D2_CHCR); } if (QueuedDMA.IPU0) { DMA_LOG("Resuming DMA for IPU0"); QueuedDMA.IPU0 = !QuickDmaExec(dmaIPU0, D3_CHCR); } if (QueuedDMA.IPU1) { DMA_LOG("Resuming DMA for IPU1"); QueuedDMA.IPU1 = !QuickDmaExec(dmaIPU1, D4_CHCR); } if (QueuedDMA.SIF0) { DMA_LOG("Resuming DMA for SIF0"); QueuedDMA.SIF0 = !QuickDmaExec(dmaSIF0, D5_CHCR); } if (QueuedDMA.SIF1) { DMA_LOG("Resuming DMA for SIF1"); QueuedDMA.SIF1 = !QuickDmaExec(dmaSIF1, D6_CHCR); } if (QueuedDMA.SIF2) { DMA_LOG("Resuming DMA for SIF2"); QueuedDMA.SIF2 = !QuickDmaExec(dmaSIF2, D7_CHCR); } if (QueuedDMA.SPR0) { DMA_LOG("Resuming DMA for SPR0"); QueuedDMA.SPR0 = !QuickDmaExec(dmaSPR0, D8_CHCR); } if (QueuedDMA.SPR1) { DMA_LOG("Resuming DMA for SPR1"); QueuedDMA.SPR1 = !QuickDmaExec(dmaSPR1, D9_CHCR); } } static __ri void DmaExec( void (*func)(), u32 mem, u32 value ) { DMACh& reg = (DMACh&)psHu32(mem); tDMA_CHCR chcr(value); //It's invalid for the hardware to write a DMA while it is active, not without Suspending the DMAC if (reg.chcr.STR) { const uint channel = ChannelNumber(mem); //As the manual states "Fields other than STR can only be written to when the DMA is stopped" //Also "The DMA may not stop properly just by writing 0 to STR" //So the presumption is that STR can be written to (ala force stop the DMA) but nothing else //If the developer wishes to alter any of the other fields, it must be done AFTER the STR has been written, //it will not work before or during this event. if(chcr.STR == 0) { //DevCon.Warning(L"32bit Force Stopping %s (Current CHCR %x) while DMA active", ChcrName(mem), reg.chcr._u32, chcr._u32); reg.chcr.STR = 0; //We need to clear any existing DMA loops that are in progress else they will continue! if(channel == 1) { cpuClearInt( 10 ); QueuedDMA._u16 &= ~(1 << 10); //Clear any queued DMA requests for this channel } else if (channel == 2) { cpuClearInt( 11 ); QueuedDMA._u16 &= ~(1 << 11); //Clear any queued DMA requests for this channel } cpuClearInt( channel ); QueuedDMA._u16 &= ~(1 << channel); //Clear any queued DMA requests for this channel } //else DevCon.Warning(L"32bit Attempted to change %s CHCR (Currently %x) with %x while DMA active, ignoring QWC = %x", ChcrName(mem), reg.chcr._u32, chcr._u32, reg.qwc); return; } //if(reg.chcr.TAG != chcr.TAG && chcr.MOD == CHAIN_MODE) DevCon.Warning(L"32bit CHCR Tag on %s changed to %x from %x QWC = %x Channel Not Active", ChcrName(mem), chcr.TAG, reg.chcr.TAG, reg.qwc); reg.chcr.set(value); //Final Fantasy XII sets the DMA Mode to 3 which doesn't exist. On some channels (like SPR) this will break logic completely. so lets assume they mean chain. if (reg.chcr.MOD == 0x3) { static bool warned; //Check if the warning has already been output to console, to prevent constant spam. if (!warned) { DevCon.Warning("%s CHCR.MOD set to 3, assuming 1 (chain)", ChcrName(mem)); warned = true; } 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(); } else if (reg.chcr.STR) { //DevCon.Warning(L"32bit %s DMA Start while DMAC Disabled\n", ChcrName(mem)); QueuedDMA._u16 |= (1 << ChannelNumber(mem)); //Queue the DMA up to be started then the DMA's are Enabled and or the Suspend is lifted } //else QueuedDMA._u16 &~= (1 << ChannelNumber(mem)); // } template< uint page > __fi u32 dmacRead32( u32 mem ) { // Fixme: OPH hack. Toggle the flag on GIF_STAT access. (rama) if ((CHECK_OPHFLAGHACK) && (page << 12) == (mem & (0xf << 12)) && (mem == GIF_STAT)) { static unsigned counter = 1; if (++counter == 8) counter = 2; // Set OPH and APATH from counter, cycling paths and alternating OPH return (gifRegs.stat._u32 & ~(7 << 9)) | ((counter & 1) ? (counter << 9) : 0); } return psHu32(mem); } // Returns TRUE if the caller should do writeback of the register to eeHw; false if the // register has no writeback, or if the writeback is handled internally. template< uint page > __fi bool dmacWrite32( u32 mem, mem32_t& value ) { // DMA Writes are invalid to everything except the STR on CHCR when it is busy // However this isn't completely confirmed and this might vary depending on if // using chain or normal modes, DMA's may be handled internally. // Metal Saga requires the QWC during IPU_FROM to be written but not MADR // similar happens with Mana Khemia. // In other cases such as Pilot Down Behind Enemy Lines, it seems to expect the DMA // to have finished before it writes the new information, otherwise the game breaks. if (CHECK_DMABUSYHACK && (mem & 0xf0) && mem >= 0x10008000 && mem <= 0x1000E000) { if ((psHu32(mem & ~0xff) & 0x100) && dmacRegs.ctrl.DMAE && !psHu8(DMAC_ENABLER + 2)) { //DevCon.Warning("Gamefix: Write to DMA addr %x while STR is busy!", mem); while (psHu32(mem & ~0xff) & 0x100) { switch ((mem >> 8) & 0xFF) { case 0x80: // VIF0 vif0Interrupt(); cpuRegs.interrupt &= ~(1 << DMAC_VIF0); break; case 0x90: // VIF1 if (vif1Regs.stat.VEW) { vu1Finish(false); vif1VUFinish(); } else vif1Interrupt(); cpuRegs.interrupt &= ~(1 << DMAC_VIF1); break; case 0xA0: // GIF gifInterrupt(); cpuRegs.interrupt &= ~(1 << DMAC_GIF); break; case 0xB0: // IPUFROM [[fallthrough]]; case 0xB4: // IPUTO if ((mem & 0xff) == 0x20) goto allow_write; // I'm so sorry else return false; break; case 0xD0: // SPRFROM SPRFROMinterrupt(); cpuRegs.interrupt &= ~(1 << DMAC_FROM_SPR); break; case 0xD4: // SPRTO SPRTOinterrupt(); cpuRegs.interrupt &= ~(1 << DMAC_TO_SPR); break; default: return false; } } } allow_write:; } switch(mem) { case (D0_QWC): // dma0 - vif0 case (D1_QWC): // dma1 - vif1 case (D2_QWC): // dma2 - gif case (D3_QWC): // dma3 - fromIPU case (D4_QWC): // dma4 - toIPU case (D5_QWC): // dma5 - sif0 case (D6_QWC): // dma6 - sif1 case (D7_QWC): // dma7 - sif2 case (D8_QWC): // dma8 - fromSPR case (D9_QWC): // dma9 - toSPR { psHu32(mem) = (u16)value; return false; } case (D0_CHCR): // dma0 - vif0 { DMA_LOG("VIF0dma EXECUTE, value=0x%x", value); DmaExec(dmaVIF0, mem, value); return false; } case (D1_CHCR): // dma1 - vif1 - chcr { DMA_LOG("VIF1dma EXECUTE, value=0x%x", value); DmaExec(dmaVIF1, mem, value); return false; } case (D2_CHCR): // dma2 - gif { DMA_LOG("GIFdma EXECUTE, value=0x%x", value); DmaExec(dmaGIF, mem, value); return false; } case (D3_CHCR): // dma3 - fromIPU { DMA_LOG("IPU0dma EXECUTE, value=0x%x\n", value); DmaExec(dmaIPU0, mem, value); return false; } case (D4_CHCR): // dma4 - toIPU { DMA_LOG("IPU1dma EXECUTE, value=0x%x\n", value); DmaExec(dmaIPU1, mem, value); return false; } case (D5_CHCR): // dma5 - sif0 { DMA_LOG("SIF0dma EXECUTE, value=0x%x", value); DmaExec(dmaSIF0, mem, value); return false; } case (D6_CHCR): // dma6 - sif1 { DMA_LOG("SIF1dma EXECUTE, value=0x%x", value); DmaExec(dmaSIF1, mem, value); return false; } case (D7_CHCR): // dma7 - sif2 { DMA_LOG("SIF2dma EXECUTE, value=0x%x", value); DmaExec(dmaSIF2, mem, value); return false; } case (D8_CHCR): // dma8 - fromSPR { DMA_LOG("SPR0dma EXECUTE (fromSPR), value=0x%x", value); DmaExec(dmaSPR0, mem, value); return false; } case (D9_CHCR): // dma9 - toSPR { DMA_LOG("SPR1dma EXECUTE (toSPR), value=0x%x", value); DmaExec(dmaSPR1, mem, value); return false; } case (fromSPR_MADR): case (toSPR_MADR): { // SPR bit is fixed at 0 for this channel psHu32(mem) = value & 0x7FFFFFFF; return false; } case (fromSPR_SADR): case (toSPR_SADR): { // Address must be QW aligned and fit in the 16K range of SPR psHu32(mem) = value & 0x3FF0; return false; } case (DMAC_CTRL): { u32 oldvalue = psHu32(mem); HW_LOG("DMAC_CTRL Write 32bit %x", value); psHu32(mem) = value; //Check for DMAS that were started while the DMAC was disabled if (((oldvalue & 0x1) == 0) && ((value & 0x1) == 1)) { if (!QueuedDMA.empty()) StartQueuedDMA(); } #ifdef PCSX2_DEVBUILD if ((oldvalue & 0x30) != (value & 0x30)) { std::string new_source; switch ((value & 0x30) >> 4) { case 1: new_source = "SIF0"; break; case 2: new_source = "fromSPR"; break; case 3: new_source = "fromIPU"; break; default: new_source = "None"; break; } //DevCon.Warning("32bit Stall Source Changed to %s", new_source.c_str()); } if ((oldvalue & 0xC0) != (value & 0xC0)) { std::string new_dest; switch ((value & 0xC0) >> 6) { case 1: new_dest = "VIF1"; break; case 2: new_dest = "GIF"; break; case 3: new_dest = "SIF1"; break; default: new_dest = "None"; break; } //DevCon.Warning("32bit Stall Destination Changed to %s", new_dest.c_str()); } #endif return false; } //Midway are a bunch of idiots, writing to E100 (reserved) instead of E010 //Which causes a CPCOND0 to fail. case (DMAC_FAKESTAT): case (DMAC_STAT): { if (mem == DMAC_FAKESTAT) { HW_LOG("Midways own DMAC_STAT Write 32bit %x", value); } else HW_LOG("DMAC_STAT Write 32bit %x", value); // lower 16 bits: clear on 1 // upper 16 bits: reverse on 1 psHu16(0xe010) &= ~(value & 0xffff); psHu16(0xe012) ^= (u16)(value >> 16); cpuTestDMACInts(); return false; } case (DMAC_ENABLEW): { HW_LOG("DMAC_ENABLEW Write 32bit %lx", value); oldvalue = psHu8(DMAC_ENABLEW + 2); psHu32(DMAC_ENABLEW) = value; psHu32(DMAC_ENABLER) = value; if (((oldvalue & 0x1) == 1) && (((value >> 16) & 0x1) == 0)) { if (!QueuedDMA.empty()) StartQueuedDMA(); } return false; } default: return true; } // fall-through: use the default writeback provided by caller. return true; } template u32 dmacRead32<0x03>( u32 mem ); template bool dmacWrite32<0x00>( u32 mem, mem32_t& value ); template bool dmacWrite32<0x01>( u32 mem, mem32_t& value ); template bool dmacWrite32<0x02>( u32 mem, mem32_t& value ); template bool dmacWrite32<0x03>( u32 mem, mem32_t& value ); template bool dmacWrite32<0x04>( u32 mem, mem32_t& value ); template bool dmacWrite32<0x05>( u32 mem, mem32_t& value ); template bool dmacWrite32<0x06>( u32 mem, mem32_t& value ); template bool dmacWrite32<0x07>( u32 mem, mem32_t& value ); template bool dmacWrite32<0x08>( u32 mem, mem32_t& value ); template bool dmacWrite32<0x09>( u32 mem, mem32_t& value ); template bool dmacWrite32<0x0a>( u32 mem, mem32_t& value ); template bool dmacWrite32<0x0b>( u32 mem, mem32_t& value ); template bool dmacWrite32<0x0c>( u32 mem, mem32_t& value ); template bool dmacWrite32<0x0d>( u32 mem, mem32_t& value ); template bool dmacWrite32<0x0e>( u32 mem, mem32_t& value ); template bool dmacWrite32<0x0f>( u32 mem, mem32_t& value );