2015-05-06 00:54:15 -04:00
|
|
|
#include <string.h>
|
|
|
|
#include <assert.h>
|
2017-02-11 15:33:42 -05:00
|
|
|
#include "string_format.h"
|
2019-02-06 13:34:51 -05:00
|
|
|
#include "../states/RegisterStateFile.h"
|
2015-05-06 00:54:15 -04:00
|
|
|
#include "../Log.h"
|
|
|
|
#include "Dmac_Channel.h"
|
|
|
|
#include "DMAC.h"
|
|
|
|
|
2018-05-25 12:38:51 -04:00
|
|
|
#define LOG_NAME ("ee_dmac")
|
2018-04-30 21:01:23 +01:00
|
|
|
#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")
|
2015-05-06 00:54:15 -04:00
|
|
|
|
2016-09-18 20:36:50 -04:00
|
|
|
#define DMATAG_IRQ 0x8000
|
2018-04-30 21:01:23 +01:00
|
|
|
#define DMATAG_ID 0x7000
|
2020-09-02 14:53:21 -04:00
|
|
|
#define DMATAG_ADDR_MASK 0xFFFFFFF0
|
2016-09-18 20:36:50 -04:00
|
|
|
|
2015-05-06 00:54:15 -04:00
|
|
|
using namespace Dmac;
|
|
|
|
|
2018-04-30 21:01:23 +01:00
|
|
|
CChannel::CChannel(CDMAC& dmac, unsigned int nNumber, const DmaReceiveHandler& pReceive)
|
|
|
|
: m_dmac(dmac)
|
|
|
|
, m_number(nNumber)
|
|
|
|
, m_receive(pReceive)
|
2015-05-06 00:54:15 -04:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void CChannel::Reset()
|
|
|
|
{
|
|
|
|
memset(&m_CHCR, 0, sizeof(CHCR));
|
2018-04-30 21:01:23 +01:00
|
|
|
m_nMADR = 0;
|
|
|
|
m_nQWC = 0;
|
|
|
|
m_nTADR = 0;
|
|
|
|
m_nSCCTRL = 0;
|
|
|
|
m_nASR[0] = 0;
|
|
|
|
m_nASR[1] = 0;
|
2015-05-06 00:54:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void CChannel::SaveState(Framework::CZipArchiveWriter& archive)
|
|
|
|
{
|
2017-02-11 15:33:42 -05:00
|
|
|
auto path = string_format(STATE_REGS_XML_FORMAT, m_number);
|
2023-07-23 17:22:18 -04:00
|
|
|
auto registerFile = std::make_unique<CRegisterStateFile>(path.c_str());
|
2018-04-30 21:01:23 +01:00
|
|
|
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]);
|
2023-07-23 17:22:18 -04:00
|
|
|
archive.InsertFile(std::move(registerFile));
|
2015-05-06 00:54:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void CChannel::LoadState(Framework::CZipArchiveReader& archive)
|
|
|
|
{
|
2017-02-11 15:33:42 -05:00
|
|
|
auto path = string_format(STATE_REGS_XML_FORMAT, m_number);
|
2015-05-06 00:54:15 -04:00
|
|
|
CRegisterStateFile registerFile(*archive.BeginReadFile(path.c_str()));
|
2018-04-30 21:01:23 +01:00
|
|
|
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);
|
2015-05-06 00:54:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32 CChannel::ReadCHCR()
|
|
|
|
{
|
2018-05-30 13:10:15 -04:00
|
|
|
return m_CHCR;
|
2015-05-06 00:54:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2022-04-16 21:31:53 +02:00
|
|
|
if(m_dmac.m_D_ENABLE & CDMAC::ENABLE_CPND)
|
2015-05-06 00:54:15 -04:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
switch(m_CHCR.nMOD)
|
|
|
|
{
|
|
|
|
case 0x00:
|
|
|
|
ExecuteNormal();
|
|
|
|
break;
|
2015-10-25 21:32:36 -04:00
|
|
|
case 0x02:
|
2015-11-11 22:14:58 -05:00
|
|
|
assert((m_number == CDMAC::CHANNEL_ID_FROM_SPR) || (m_number == CDMAC::CHANNEL_ID_TO_SPR));
|
2015-10-25 21:32:36 -04:00
|
|
|
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;
|
2015-05-06 00:54:15 -04:00
|
|
|
case 0x01:
|
2018-04-30 21:01:23 +01:00
|
|
|
case 0x03: //FFXII uses 3 here, assuming source chain mode
|
2018-05-29 13:42:26 -04:00
|
|
|
if(m_number == CDMAC::CHANNEL_ID_FROM_SPR)
|
|
|
|
{
|
|
|
|
ExecuteDestinationChain();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ExecuteSourceChain();
|
|
|
|
}
|
2015-05-06 00:54:15 -04:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assert(0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CChannel::ExecuteNormal()
|
|
|
|
{
|
2016-07-09 18:33:51 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-11-28 23:32:11 +01:00
|
|
|
// 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.
|
2015-05-06 00:54:15 -04:00
|
|
|
uint32 qwc = m_nQWC;
|
2020-11-28 23:32:11 +01:00
|
|
|
if(qwc == 0)
|
|
|
|
{
|
|
|
|
qwc = 1;
|
|
|
|
}
|
2015-05-06 00:54:15 -04:00
|
|
|
|
2016-07-09 18:33:51 -04:00
|
|
|
if(isMfifo)
|
2015-05-06 00:54:15 -04:00
|
|
|
{
|
2018-06-14 06:50:54 -04:00
|
|
|
m_nMADR &= m_dmac.m_D_RBSR;
|
|
|
|
m_nMADR |= m_dmac.m_D_RBOR;
|
|
|
|
|
2015-05-06 00:54:15 -04:00
|
|
|
//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);
|
|
|
|
|
2020-11-28 23:32:11 +01:00
|
|
|
qwc = std::min<int32>(qwc, (ringBufferSize - ringBufferAddr) / 0x10);
|
2015-05-06 00:54:15 -04:00
|
|
|
}
|
|
|
|
|
2015-11-16 19:44:17 -05:00
|
|
|
uint32 nRecv = m_receive(m_nMADR, qwc, m_CHCR.nDIR, false);
|
2015-05-06 00:54:15 -04:00
|
|
|
|
2018-04-30 21:01:23 +01:00
|
|
|
m_nMADR += nRecv * 0x10;
|
|
|
|
m_nQWC -= nRecv;
|
2015-05-06 00:54:15 -04:00
|
|
|
|
2021-10-05 10:16:22 -04:00
|
|
|
m_nQWC &= CDMAC::QWC_WRITE_MASK;
|
|
|
|
|
2015-05-06 00:54:15 -04:00
|
|
|
if(m_nQWC == 0)
|
|
|
|
{
|
|
|
|
ClearSTR();
|
|
|
|
}
|
|
|
|
|
2016-07-09 18:33:51 -04:00
|
|
|
if(isMfifo)
|
2015-05-06 00:54:15 -04:00
|
|
|
{
|
|
|
|
//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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-25 21:32:36 -04:00
|
|
|
void CChannel::ExecuteInterleave()
|
|
|
|
{
|
|
|
|
assert((m_nQWC % m_dmac.m_D_SQWC.tqwc) == 0);
|
|
|
|
while(1)
|
|
|
|
{
|
|
|
|
//Transfer
|
|
|
|
{
|
2018-04-30 21:01:23 +01:00
|
|
|
uint32 qwc = m_dmac.m_D_SQWC.tqwc;
|
2015-11-16 19:44:17 -05:00
|
|
|
uint32 recv = m_receive(m_nMADR, qwc, CHCR_DIR_FROM, false);
|
2015-10-25 21:32:36 -04:00
|
|
|
assert(recv == qwc);
|
|
|
|
|
|
|
|
m_nMADR += recv * 0x10;
|
2018-04-30 21:01:23 +01:00
|
|
|
m_nQWC -= recv;
|
2015-10-25 21:32:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
//Skip
|
|
|
|
m_nMADR += m_dmac.m_D_SQWC.sqwc * 0x10;
|
|
|
|
|
|
|
|
if(m_nQWC == 0)
|
|
|
|
{
|
|
|
|
ClearSTR();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-06 00:54:15 -04:00
|
|
|
void CChannel::ExecuteSourceChain()
|
|
|
|
{
|
2016-07-09 18:33:51 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-06-30 21:25:25 -04:00
|
|
|
bool isStallDrainChannel = false;
|
|
|
|
switch(m_dmac.m_D_CTRL.std)
|
|
|
|
{
|
2021-12-17 17:07:16 -05:00
|
|
|
case CDMAC::D_CTRL_STD_NONE:
|
2017-06-30 21:25:25 -04:00
|
|
|
isStallDrainChannel = false;
|
|
|
|
break;
|
2021-12-17 17:07:16 -05:00
|
|
|
case CDMAC::D_CTRL_STD_VIF1:
|
2017-06-30 21:25:25 -04:00
|
|
|
isStallDrainChannel = (m_number == CDMAC::CHANNEL_ID_VIF1);
|
|
|
|
break;
|
2021-12-17 17:07:16 -05:00
|
|
|
case CDMAC::D_CTRL_STD_GIF:
|
2017-08-26 18:46:10 -04:00
|
|
|
isStallDrainChannel = (m_number == CDMAC::CHANNEL_ID_GIF);
|
|
|
|
break;
|
2017-06-30 21:25:25 -04:00
|
|
|
default:
|
|
|
|
assert(false);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Pause transfer if channel is stalled
|
2018-05-28 13:27:25 -04:00
|
|
|
if(isStallDrainChannel && ((m_CHCR.nTAG & DMATAG_ID) == (DMATAG_SRC_REFS << 12)) && (m_nMADR >= m_dmac.m_D_STADR))
|
2017-06-30 21:25:25 -04:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-05-06 00:54:15 -04:00
|
|
|
//Execute current
|
|
|
|
if(m_nQWC != 0)
|
|
|
|
{
|
2021-06-24 21:28:21 -04:00
|
|
|
ExecuteSourceChainTransfer(isMfifo);
|
2015-05-06 00:54:15 -04:00
|
|
|
|
|
|
|
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
|
2016-07-09 18:33:51 -04:00
|
|
|
if(isMfifo)
|
2015-05-06 00:54:15 -04:00
|
|
|
{
|
|
|
|
//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;
|
2015-11-16 19:44:17 -05:00
|
|
|
if(m_receive(m_nTADR, 1, CHCR_DIR_FROM, true) != 1)
|
2015-05-06 00:54:15 -04:00
|
|
|
{
|
|
|
|
//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
|
|
|
|
{
|
2022-04-04 14:46:56 -04:00
|
|
|
if(CDMAC::IsEndSrcTagId(m_CHCR.nTAG))
|
2015-05-06 00:54:15 -04:00
|
|
|
{
|
|
|
|
ClearSTR();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-09-18 20:36:50 -04:00
|
|
|
if((m_CHCR.nTIE != 0) && ((m_CHCR.nTAG & DMATAG_IRQ) != 0))
|
|
|
|
{
|
|
|
|
ClearSTR();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-05-06 00:54:15 -04:00
|
|
|
//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;
|
2015-11-16 19:44:17 -05:00
|
|
|
if(m_receive(m_nTADR, 1, CHCR_DIR_FROM, true) != 1)
|
2015-05-06 00:54:15 -04:00
|
|
|
{
|
|
|
|
//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
|
2016-09-17 23:14:51 -04:00
|
|
|
m_CHCR.nTAG = static_cast<uint16>(nTag >> 16);
|
2015-05-06 00:54:15 -04:00
|
|
|
|
|
|
|
uint8 nID = static_cast<uint8>((nTag >> 28) & 0x07);
|
|
|
|
|
|
|
|
switch(nID)
|
|
|
|
{
|
2018-05-28 13:27:25 -04:00
|
|
|
case DMATAG_SRC_REFE:
|
2015-05-06 00:54:15 -04:00
|
|
|
//REFE - Data to transfer is pointer in memory address, transfer is done
|
2020-09-02 14:53:21 -04:00
|
|
|
m_nMADR = (uint32)((nTag >> 32) & DMATAG_ADDR_MASK);
|
2018-04-30 21:01:23 +01:00
|
|
|
m_nQWC = (uint32)((nTag >> 0) & 0x0000FFFF);
|
|
|
|
m_nTADR = m_nTADR + 0x10;
|
2015-05-06 00:54:15 -04:00
|
|
|
break;
|
2018-05-28 13:27:25 -04:00
|
|
|
case DMATAG_SRC_CNT:
|
2015-05-06 00:54:15 -04:00
|
|
|
//CNT - Data to transfer is after the tag, next tag is after the data
|
2018-04-30 21:01:23 +01:00
|
|
|
m_nMADR = m_nTADR + 0x10;
|
|
|
|
m_nQWC = (uint32)(nTag & 0xFFFF);
|
|
|
|
m_nTADR = (m_nQWC * 0x10) + m_nMADR;
|
2015-05-06 00:54:15 -04:00
|
|
|
break;
|
2018-05-28 13:27:25 -04:00
|
|
|
case DMATAG_SRC_NEXT:
|
2015-05-06 00:54:15 -04:00
|
|
|
//NEXT - Transfers data after tag, next tag is at position in ADDR field
|
2018-04-30 21:01:23 +01:00
|
|
|
m_nMADR = m_nTADR + 0x10;
|
|
|
|
m_nQWC = (uint32)((nTag >> 0) & 0x0000FFFF);
|
2020-09-02 14:53:21 -04:00
|
|
|
m_nTADR = (uint32)((nTag >> 32) & DMATAG_ADDR_MASK);
|
2015-05-06 00:54:15 -04:00
|
|
|
break;
|
2018-05-28 13:27:25 -04:00
|
|
|
case DMATAG_SRC_REF:
|
|
|
|
case DMATAG_SRC_REFS:
|
2015-05-06 00:54:15 -04:00
|
|
|
//REF/REFS - Data to transfer is pointed in memory address, next tag is after this tag
|
2020-09-02 14:53:21 -04:00
|
|
|
m_nMADR = (uint32)((nTag >> 32) & DMATAG_ADDR_MASK);
|
2018-04-30 21:01:23 +01:00
|
|
|
m_nQWC = (uint32)((nTag >> 0) & 0x0000FFFF);
|
|
|
|
m_nTADR = m_nTADR + 0x10;
|
2015-05-06 00:54:15 -04:00
|
|
|
break;
|
2018-05-28 13:27:25 -04:00
|
|
|
case DMATAG_SRC_CALL:
|
2015-05-06 00:54:15 -04:00
|
|
|
//CALL - Transfers QWC after the tag, saves next address in ASR, TADR = ADDR
|
|
|
|
assert(m_CHCR.nASP < 2);
|
2018-04-30 21:01:23 +01:00
|
|
|
m_nMADR = m_nTADR + 0x10;
|
|
|
|
m_nQWC = (uint32)(nTag & 0xFFFF);
|
|
|
|
m_nASR[m_CHCR.nASP] = m_nMADR + (m_nQWC * 0x10);
|
2020-09-02 14:53:21 -04:00
|
|
|
m_nTADR = (uint32)((nTag >> 32) & DMATAG_ADDR_MASK);
|
2015-05-06 00:54:15 -04:00
|
|
|
m_CHCR.nASP++;
|
|
|
|
break;
|
2018-05-28 13:27:25 -04:00
|
|
|
case DMATAG_SRC_RET:
|
2015-05-06 00:54:15 -04:00
|
|
|
//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];
|
|
|
|
}
|
2018-04-30 21:01:23 +01:00
|
|
|
else
|
2015-05-06 00:54:15 -04:00
|
|
|
{
|
|
|
|
m_nSCCTRL |= SCCTRL_RETTOP;
|
|
|
|
}
|
|
|
|
break;
|
2018-05-28 13:27:25 -04:00
|
|
|
case DMATAG_SRC_END:
|
2015-05-06 00:54:15 -04:00
|
|
|
//END - Data to transfer is after the tag, transfer is finished
|
2018-04-30 21:01:23 +01:00
|
|
|
m_nMADR = m_nTADR + 0x10;
|
|
|
|
m_nQWC = (uint32)(nTag & 0xFFFF);
|
2015-05-06 00:54:15 -04:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
m_nQWC = 0;
|
|
|
|
assert(0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-09-02 14:53:21 -04:00
|
|
|
assert((m_nMADR & 0xF) == 0);
|
|
|
|
assert((m_nTADR & 0xF) == 0);
|
|
|
|
|
2017-06-30 21:25:25 -04:00
|
|
|
//Pause transfer if channel is stalled
|
2018-05-28 13:27:25 -04:00
|
|
|
if(isStallDrainChannel && (nID == DMATAG_SRC_REFS) && (m_nMADR >= m_dmac.m_D_STADR))
|
2017-06-30 21:25:25 -04:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-06-24 21:28:21 -04:00
|
|
|
ExecuteSourceChainTransfer(isMfifo);
|
2015-05-06 00:54:15 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-29 13:42:26 -04:00
|
|
|
void CChannel::ExecuteDestinationChain()
|
|
|
|
{
|
|
|
|
assert(m_number == CDMAC::CHANNEL_ID_FROM_SPR);
|
|
|
|
|
|
|
|
while(m_CHCR.nSTR == 1)
|
|
|
|
{
|
2022-04-04 14:47:55 -04:00
|
|
|
//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<DMAtag>(m_dmac.FetchDMATag(m_dmac.m_D8_SADR | 0x80000000));
|
|
|
|
m_dmac.m_D8_SADR += 0x10;
|
2018-05-29 13:42:26 -04:00
|
|
|
|
2022-04-04 14:47:55 -04:00
|
|
|
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;
|
|
|
|
}
|
2018-05-29 13:42:26 -04:00
|
|
|
|
2022-04-04 14:47:55 -04:00
|
|
|
m_CHCR.nTAG = static_cast<uint16>(tag >> 16);
|
2018-05-29 13:42:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32 recv = m_receive(m_nMADR, m_nQWC, m_CHCR.nDIR, false);
|
|
|
|
assert(recv == m_nQWC);
|
|
|
|
|
|
|
|
m_nMADR += recv * 0x10;
|
|
|
|
m_nQWC -= recv;
|
|
|
|
|
2022-04-04 14:47:55 -04:00
|
|
|
if(CDMAC::IsEndDstTagId(m_CHCR.nTAG))
|
2018-05-29 13:42:26 -04:00
|
|
|
{
|
|
|
|
ClearSTR();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-06 00:54:15 -04:00
|
|
|
void CChannel::SetReceiveHandler(const DmaReceiveHandler& handler)
|
|
|
|
{
|
2015-11-11 22:14:58 -05:00
|
|
|
m_receive = handler;
|
2015-05-06 00:54:15 -04:00
|
|
|
}
|
|
|
|
|
2021-06-24 21:28:21 -04:00
|
|
|
void CChannel::ExecuteSourceChainTransfer(bool isMfifo)
|
|
|
|
{
|
|
|
|
uint32 nID = m_CHCR.nTAG >> 12;
|
|
|
|
|
|
|
|
uint32 qwc = m_nQWC;
|
|
|
|
if((nID == DMATAG_SRC_CNT) && isMfifo)
|
|
|
|
{
|
|
|
|
//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<int32>(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)
|
|
|
|
{
|
|
|
|
//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;
|
2022-02-23 09:12:33 -05:00
|
|
|
|
|
|
|
//Check MFIFO empty
|
|
|
|
if(m_nTADR == m_dmac.m_D8.m_nMADR)
|
|
|
|
{
|
|
|
|
m_dmac.m_D_STAT |= CDMAC::D_STAT_MEIS;
|
|
|
|
}
|
2021-06-24 21:28:21 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-06 00:54:15 -04:00
|
|
|
void CChannel::ClearSTR()
|
|
|
|
{
|
|
|
|
m_CHCR.nSTR = ~m_CHCR.nSTR;
|
|
|
|
|
|
|
|
//Set interrupt
|
2015-11-11 22:14:58 -05:00
|
|
|
m_dmac.m_D_STAT |= (1 << m_number);
|
2015-05-06 00:54:15 -04:00
|
|
|
|
|
|
|
m_dmac.UpdateCpCond();
|
|
|
|
}
|