#include #include #include "string_format.h" #include "../states/RegisterStateFile.h" #include "Log.h" #include "Dmac_Channel.h" #include "DMAC.h" #define LOG_NAME ("ee_dmac") #define STATE_REGS_XML_FORMAT ("dmac/channel_%d.xml") #define STATE_REGS_CHCR ("CHCR") #define STATE_REGS_MADR ("MADR") #define STATE_REGS_QWC ("QWC") #define STATE_REGS_TADR ("TADR") #define STATE_REGS_SCCTRL ("SCCTRL") #define STATE_REGS_ASR0 ("ASR0") #define STATE_REGS_ASR1 ("ASR1") #define DMATAG_IRQ 0x8000 #define DMATAG_ID 0x7000 #define DMATAG_ADDR_MASK 0xFFFFFFF0 using namespace Dmac; CChannel::CChannel(CDMAC& dmac, unsigned int nNumber, const DmaReceiveHandler& pReceive) : m_dmac(dmac) , m_number(nNumber) , m_receive(pReceive) { } void CChannel::Reset() { memset(&m_CHCR, 0, sizeof(CHCR)); m_nMADR = 0; m_nQWC = 0; m_nTADR = 0; m_nSCCTRL = 0; m_nASR[0] = 0; m_nASR[1] = 0; } void CChannel::SaveState(Framework::CZipArchiveWriter& archive) { auto path = string_format(STATE_REGS_XML_FORMAT, m_number); auto registerFile = std::make_unique(path.c_str()); registerFile->SetRegister32(STATE_REGS_CHCR, m_CHCR); registerFile->SetRegister32(STATE_REGS_MADR, m_nMADR); registerFile->SetRegister32(STATE_REGS_QWC, m_nQWC); registerFile->SetRegister32(STATE_REGS_TADR, m_nTADR); registerFile->SetRegister32(STATE_REGS_SCCTRL, m_nSCCTRL); registerFile->SetRegister32(STATE_REGS_ASR0, m_nASR[0]); registerFile->SetRegister32(STATE_REGS_ASR1, m_nASR[1]); archive.InsertFile(std::move(registerFile)); } void CChannel::LoadState(Framework::CZipArchiveReader& archive) { auto path = string_format(STATE_REGS_XML_FORMAT, m_number); CRegisterStateFile registerFile(*archive.BeginReadFile(path.c_str())); m_CHCR <<= registerFile.GetRegister32(STATE_REGS_CHCR); m_nMADR = registerFile.GetRegister32(STATE_REGS_MADR); m_nQWC = registerFile.GetRegister32(STATE_REGS_QWC); m_nTADR = registerFile.GetRegister32(STATE_REGS_TADR); m_nSCCTRL = registerFile.GetRegister32(STATE_REGS_SCCTRL); m_nASR[0] = registerFile.GetRegister32(STATE_REGS_ASR0); m_nASR[1] = registerFile.GetRegister32(STATE_REGS_ASR1); } uint32 CChannel::ReadCHCR() { return m_CHCR; } void CChannel::WriteCHCR(uint32 nValue) { bool nSuspend = false; //We need to check if the purpose of this write is to suspend the current transfer if(m_dmac.m_D_ENABLE & CDMAC::ENABLE_CPND) { nSuspend = (m_CHCR.nSTR != 0) && ((nValue & CDMAC::CHCR_STR) == 0); } if(m_CHCR.nSTR == 1) { m_CHCR.nSTR = ((nValue & CDMAC::CHCR_STR) != 0) ? 1 : 0; } else { m_CHCR = *(CHCR*)&nValue; } if(m_CHCR.nSTR != 0) { if(m_nQWC == 0) { m_nSCCTRL |= SCCTRL_INITXFER; } m_nSCCTRL &= ~SCCTRL_RETTOP; Execute(); } } void CChannel::Execute() { if(m_CHCR.nSTR != 0) { if(m_dmac.m_D_ENABLE & CDMAC::ENABLE_CPND) { return; } switch(m_CHCR.nMOD) { case 0x00: ExecuteNormal(); break; case 0x02: assert((m_number == CDMAC::CHANNEL_ID_FROM_SPR) || (m_number == CDMAC::CHANNEL_ID_TO_SPR)); if((m_dmac.m_D_SQWC.sqwc == 0) || (m_dmac.m_D_SQWC.tqwc == 0)) { //If SQWC or TQWC is 0, execute normally ExecuteNormal(); } else { ExecuteInterleave(); } break; case 0x01: case 0x03: //FFXII uses 3 here, assuming source chain mode if(m_number == CDMAC::CHANNEL_ID_FROM_SPR) { ExecuteDestinationChain(); } else { ExecuteSourceChain(); } break; default: assert(0); break; } } } void CChannel::ExecuteNormal() { bool isMfifo = false; switch(m_dmac.m_D_CTRL.mfd) { case 0: isMfifo = false; break; case 1: //Invalid assert(false); break; case 2: case 3: isMfifo = (m_number == CDMAC::CHANNEL_ID_FROM_SPR); break; } // When starting a normal transfer, always at least one // QW is send, regardless of the value of the QWC register. // In case of a QWC of 0, this causes the QWC register to // underflow down the line, which is intentional (actually // happens on real hardware). // Games that do exhibit a normal transfer with 0 QWC // (Enter the Matrix, Star Wars Racer Revenge) mitigate // this by setting the QWC register each frame. uint32 qwc = m_nQWC; if(qwc == 0) { qwc = 1; } if(isMfifo) { m_nMADR &= m_dmac.m_D_RBSR; m_nMADR |= m_dmac.m_D_RBOR; //Adjust QWC if we're in MFIFO mode uint32 ringBufferAddr = m_nMADR - m_dmac.m_D_RBOR; uint32 ringBufferSize = m_dmac.m_D_RBSR + 0x10; assert(ringBufferAddr < ringBufferSize); qwc = std::min(qwc, (ringBufferSize - ringBufferAddr) / 0x10); } uint32 nRecv = m_receive(m_nMADR, qwc, m_CHCR.nDIR, false); m_nMADR += nRecv * 0x10; m_nQWC -= nRecv; m_nQWC &= CDMAC::QWC_WRITE_MASK; if(m_nQWC == 0) { ClearSTR(); } if(isMfifo) { //Loop MADR if needed uint32 ringBufferSize = m_dmac.m_D_RBSR + 0x10; if(m_nMADR == (m_dmac.m_D_RBOR + ringBufferSize)) { m_nMADR = m_dmac.m_D_RBOR; } } } void CChannel::ExecuteInterleave() { assert((m_nQWC % m_dmac.m_D_SQWC.tqwc) == 0); while(1) { //Transfer { uint32 qwc = m_dmac.m_D_SQWC.tqwc; uint32 recv = m_receive(m_nMADR, qwc, CHCR_DIR_FROM, false); assert(recv == qwc); m_nMADR += recv * 0x10; m_nQWC -= recv; } //Skip m_nMADR += m_dmac.m_D_SQWC.sqwc * 0x10; if(m_nQWC == 0) { ClearSTR(); break; } } } void CChannel::ExecuteSourceChain() { bool isMfifo = false; switch(m_dmac.m_D_CTRL.mfd) { case 0: isMfifo = false; break; case 1: //Invalid assert(false); break; case 2: isMfifo = (m_number == CDMAC::CHANNEL_ID_VIF1); break; case 3: isMfifo = (m_number == CDMAC::CHANNEL_ID_GIF); break; } bool isStallDrainChannel = false; switch(m_dmac.m_D_CTRL.std) { case CDMAC::D_CTRL_STD_NONE: isStallDrainChannel = false; break; case CDMAC::D_CTRL_STD_VIF1: isStallDrainChannel = (m_number == CDMAC::CHANNEL_ID_VIF1); break; case CDMAC::D_CTRL_STD_GIF: isStallDrainChannel = (m_number == CDMAC::CHANNEL_ID_GIF); break; default: assert(false); break; } //Pause transfer if channel is stalled if(isStallDrainChannel && ((m_CHCR.nTAG & DMATAG_ID) == (DMATAG_SRC_REFS << 12)) && (m_nMADR >= m_dmac.m_D_STADR)) { return; } //Execute current if(m_nQWC != 0) { ExecuteSourceChainTransfer(isMfifo); if(m_nQWC != 0) { //Transfer isn't finished, suspend for now return; } } while(m_CHCR.nSTR == 1) { //Check if MFIFO is enabled with this channel if(isMfifo) { //Hold transfer if not ready if(m_nTADR == m_dmac.m_D8.m_nMADR) { break; } } //Check if device received DMAtag if(m_CHCR.nReserved0) { assert(m_CHCR.nTTE); m_CHCR.nReserved0 = 0; if(m_receive(m_nTADR, 1, CHCR_DIR_FROM, true) != 1) { //Device didn't receive DmaTag, break for now m_CHCR.nReserved0 = 1; break; } } else { //Check if we've finished our DMA transfer if(m_nQWC == 0) { if(m_nSCCTRL & SCCTRL_INITXFER) { //Clear this bit m_nSCCTRL &= ~SCCTRL_INITXFER; } else { if(CDMAC::IsEndSrcTagId(m_CHCR.nTAG)) { ClearSTR(); continue; } if((m_CHCR.nTIE != 0) && ((m_CHCR.nTAG & DMATAG_IRQ) != 0)) { ClearSTR(); continue; } //Transfer must also end when we encounter a RET tag with ASR == 0 if(m_nSCCTRL & SCCTRL_RETTOP) { ClearSTR(); m_nSCCTRL &= ~SCCTRL_RETTOP; continue; } } } else { //Suspend transfer break; } if(m_CHCR.nTTE == 1) { m_CHCR.nReserved0 = 0; if(m_receive(m_nTADR, 1, CHCR_DIR_FROM, true) != 1) { //Device didn't receive DmaTag, break for now m_CHCR.nReserved0 = 1; break; } } } //Half-Life does this... if(m_nTADR == 0) { ClearSTR(); continue; } uint64 nTag = m_dmac.FetchDMATag(m_nTADR); //Save higher 16 bits of tag into CHCR m_CHCR.nTAG = static_cast(nTag >> 16); uint8 nID = static_cast((nTag >> 28) & 0x07); switch(nID) { case DMATAG_SRC_REFE: //REFE - Data to transfer is pointer in memory address, transfer is done m_nMADR = (uint32)((nTag >> 32) & DMATAG_ADDR_MASK); m_nQWC = (uint32)((nTag >> 0) & 0x0000FFFF); m_nTADR = m_nTADR + 0x10; break; case DMATAG_SRC_CNT: //CNT - Data to transfer is after the tag, next tag is after the data m_nMADR = m_nTADR + 0x10; m_nQWC = (uint32)(nTag & 0xFFFF); m_nTADR = (m_nQWC * 0x10) + m_nMADR; break; case DMATAG_SRC_NEXT: //NEXT - Transfers data after tag, next tag is at position in ADDR field m_nMADR = m_nTADR + 0x10; m_nQWC = (uint32)((nTag >> 0) & 0x0000FFFF); m_nTADR = (uint32)((nTag >> 32) & DMATAG_ADDR_MASK); break; case DMATAG_SRC_REF: case DMATAG_SRC_REFS: //REF/REFS - Data to transfer is pointed in memory address, next tag is after this tag m_nMADR = (uint32)((nTag >> 32) & DMATAG_ADDR_MASK); m_nQWC = (uint32)((nTag >> 0) & 0x0000FFFF); m_nTADR = m_nTADR + 0x10; break; case DMATAG_SRC_CALL: //CALL - Transfers QWC after the tag, saves next address in ASR, TADR = ADDR assert(m_CHCR.nASP < 2); m_nMADR = m_nTADR + 0x10; m_nQWC = (uint32)(nTag & 0xFFFF); m_nASR[m_CHCR.nASP] = m_nMADR + (m_nQWC * 0x10); m_nTADR = (uint32)((nTag >> 32) & DMATAG_ADDR_MASK); m_CHCR.nASP++; break; case DMATAG_SRC_RET: //RET - Transfers QWC after the tag, pops TADR from ASR m_nMADR = m_nTADR + 0x10; m_nQWC = (uint32)(nTag & 0xFFFF); if(m_CHCR.nASP > 0) { m_CHCR.nASP--; m_nTADR = m_nASR[m_CHCR.nASP]; } else { m_nSCCTRL |= SCCTRL_RETTOP; } break; case DMATAG_SRC_END: //END - Data to transfer is after the tag, transfer is finished m_nMADR = m_nTADR + 0x10; m_nQWC = (uint32)(nTag & 0xFFFF); break; default: m_nQWC = 0; assert(0); break; } assert((m_nMADR & 0xF) == 0); assert((m_nTADR & 0xF) == 0); //Pause transfer if channel is stalled if(isStallDrainChannel && (nID == DMATAG_SRC_REFS) && (m_nMADR >= m_dmac.m_D_STADR)) { continue; } ExecuteSourceChainTransfer(isMfifo); } } void CChannel::ExecuteDestinationChain() { assert(m_number == CDMAC::CHANNEL_ID_FROM_SPR); while(m_CHCR.nSTR == 1) { //QWC is 0, fetch a new tag //Some games don't reset the TAG value in CHCR and start a transfer that looks like a normal transfer //I guess this is because TAG is still the end tag and the DMAC only fetches a new tag if it's started with QWC = 0 //- Hitman 2: Silent Assassin //- Ridge Racer 5 if(m_nQWC == 0) { auto tag = make_convertible(m_dmac.FetchDMATag(m_dmac.m_D8_SADR | 0x80000000)); m_dmac.m_D8_SADR += 0x10; assert(tag.irq == 0); assert(tag.pce == 0); assert((tag.addr & 0x0F) == 0); switch(tag.id) { case DMATAG_DST_CNTS: //Stall register update not supported yet assert(m_dmac.m_D_CTRL.sts != CDMAC::D_CTRL_STS_FROM_SPR); [[fallthrough]]; case DMATAG_DST_CNT: case DMATAG_DST_END: m_nMADR = tag.addr; m_nQWC = tag.qwc; break; default: assert(false); break; } m_CHCR.nTAG = static_cast(tag >> 16); } uint32 recv = m_receive(m_nMADR, m_nQWC, m_CHCR.nDIR, false); assert(recv == m_nQWC); m_nMADR += recv * 0x10; m_nQWC -= recv; if(CDMAC::IsEndDstTagId(m_CHCR.nTAG)) { ClearSTR(); } } } void CChannel::SetReceiveHandler(const DmaReceiveHandler& handler) { m_receive = handler; } void CChannel::ExecuteSourceChainTransfer(bool isMfifo) { uint32 nID = m_CHCR.nTAG >> 12; uint32 qwc = m_nQWC; if(isMfifo && ((nID == DMATAG_SRC_CNT) || (nID == DMATAG_SRC_END))) { //Adjust QWC in MFIFO mode uint32 ringBufferAddr = m_nMADR - m_dmac.m_D_RBOR; uint32 ringBufferSize = m_dmac.m_D_RBSR + 0x10; assert(ringBufferAddr <= ringBufferSize); qwc = std::min(m_nQWC, (ringBufferSize - ringBufferAddr) / 0x10); } if(qwc != 0) { uint32 nRecv = m_receive(m_nMADR, qwc, CHCR_DIR_FROM, false); m_nMADR += nRecv * 0x10; m_nQWC -= nRecv; } if(isMfifo) { if(((nID == DMATAG_SRC_CNT) || (nID == DMATAG_SRC_END))) { //Loop MADR if needed uint32 ringBufferSize = m_dmac.m_D_RBSR + 0x10; if(m_nMADR == (m_dmac.m_D_RBOR + ringBufferSize)) { m_nMADR = m_dmac.m_D_RBOR; } } //Mask TADR because it's in the ring buffer zone m_nTADR -= m_dmac.m_D_RBOR; m_nTADR &= m_dmac.m_D_RBSR; m_nTADR += m_dmac.m_D_RBOR; //Check MFIFO empty if(m_nTADR == m_dmac.m_D8.m_nMADR) { m_dmac.m_D_STAT |= CDMAC::D_STAT_MEIS; } } } void CChannel::ClearSTR() { m_CHCR.nSTR = ~m_CHCR.nSTR; //Set interrupt m_dmac.m_D_STAT |= (1 << m_number); m_dmac.UpdateCpCond(); }