mirror of https://github.com/PCSX2/pcsx2.git
741 lines
20 KiB
C++
741 lines
20 KiB
C++
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#include "ps2/Iop/IopHw_Internal.h"
|
|
#include "ps2/HwInternal.h"
|
|
#include "ps2/pgif.h"
|
|
#include "IopHw.h"
|
|
#include "IopDma.h"
|
|
#include "Common.h"
|
|
|
|
//NOTES (TODO):
|
|
/*
|
|
- 8 and 16 bit access to the PGPU regs is not emulated... is it ever used? Emulating it would be tricky.
|
|
|
|
|
|
------
|
|
|
|
Much of the code is ("very") unoptimized, because it is a bit cleaner and more complete this way.
|
|
|
|
All the PS1 GPU info comes from psx-spx: http://problemkaputt.de/psx-spx.htm
|
|
|
|
|
|
*/
|
|
|
|
//Debug printf:
|
|
// Set to 1 to log PGIF HW IO
|
|
#define LOG_REG 0
|
|
|
|
#if LOG_REG
|
|
#define REG_LOG pgifConLog
|
|
#else
|
|
#define REG_LOG(...) do {} while(0)
|
|
#endif
|
|
|
|
// Set to 1 to log PGPU DMA
|
|
#define LOG_PGPU_DMA 1
|
|
|
|
#if LOG_PGPU_DMA
|
|
#define PGPU_DMA_LOG pgifConLog
|
|
#else
|
|
#define PGPU_DMA_LOG(...) do {} while(0)
|
|
#endif
|
|
|
|
u32 old_gp0_value = 0;
|
|
void fillFifoOnDrain(void);
|
|
void drainPgpuDmaLl(void);
|
|
void drainPgpuDmaNrToGpu(void);
|
|
void drainPgpuDmaNrToIop(void);
|
|
|
|
void ringBufPut(struct ringBuf_t* rb, u32* data)
|
|
{
|
|
if (rb->count < rb->size)
|
|
{
|
|
//there is available space
|
|
*(rb->buf + rb->head) = *data;
|
|
if ((++(rb->head)) >= rb->size)
|
|
rb->head = 0; //wrap back when the end is reached
|
|
rb->count++;
|
|
}
|
|
else
|
|
{
|
|
// This should never happen. If it does, the code is bad somewhere.
|
|
Console.Error("PGIF FIFO overflow! sz= %X", rb->size);
|
|
}
|
|
}
|
|
|
|
void ringBufGet(struct ringBuf_t* rb, u32* data)
|
|
{
|
|
if (rb->count > 0)
|
|
{
|
|
//there is available data
|
|
*data = *(rb->buf + rb->tail);
|
|
if ((++(rb->tail)) >= rb->size)
|
|
rb->tail = 0; //wrap back when the end is reached
|
|
rb->count--;
|
|
}
|
|
else
|
|
{
|
|
// This should never happen. If it does, the code is bad somewhere.
|
|
Console.Error("PGIF FIFO underflow! sz= %X", rb->size);
|
|
}
|
|
}
|
|
|
|
void ringBufferClear(struct ringBuf_t* rb)
|
|
{
|
|
rb->head = 0;
|
|
rb->tail = 0;
|
|
rb->count = 0;
|
|
// wisi comment:
|
|
// Yes, the memset should be commented-out. The reason is that it shouldn't really be necessary (but I am not sure).
|
|
// It is better not to be enabled, because clearing the new huge PGIF FIFO, can waste significant time.
|
|
//memset(rb->buf, 0, rb->size * sizeof(u32));
|
|
return;
|
|
}
|
|
|
|
|
|
//Ring buffers definition and initialization:
|
|
//Command (GP1) FIFO, size= 0x8 words:
|
|
#define PGIF_CMD_RB_SIZE 0x8
|
|
struct ringBuf_t rb_gp1; //Ring buffer control variables.
|
|
u32 pgif_gp1_buffer[PGIF_CMD_RB_SIZE] = {0}; //Ring buffer data.
|
|
|
|
//Data (GP0) FIFO - the so-called (in PS1DRV) "GFIFO", (real) size= 0x20 words:
|
|
//Using small (the real) FIFO size, disturbs MDEC video (and other stuff),
|
|
//because the MDEC does DMA instantly, while this emulation drains the FIFO,
|
|
//only when the PS1DRV gets data from it, which depends on IOP-EE sync, among other things.
|
|
//The reson it works in the real hardware, is that the MDEC DMA would run in the pauses of GPU DMA,
|
|
//thus the GPU DMA would never get data, MDEC hasn't yet written to RAM yet (too early).
|
|
#define PGIF_DAT_RB_SIZE 0x20000
|
|
struct ringBuf_t rb_gp0; //Ring buffer control variables.
|
|
u32 pgif_gp0_buffer[PGIF_DAT_RB_SIZE] = {0}; //Ring buffer data.
|
|
dma_t dma;
|
|
|
|
|
|
//TODO: Make this be called by IopHw.cpp / psxHwReset()... but maybe it should be called by the EE reset func,
|
|
//given that the PGIF is in the EE ASIC, on the other side of the SBUS.
|
|
void pgifInit()
|
|
{
|
|
rb_gp1.buf = pgif_gp1_buffer;
|
|
rb_gp1.size = PGIF_CMD_RB_SIZE;
|
|
ringBufferClear(&rb_gp1);
|
|
|
|
rb_gp0.buf = pgif_gp0_buffer;
|
|
rb_gp0.size = PGIF_DAT_RB_SIZE;
|
|
ringBufferClear(&rb_gp0);
|
|
|
|
pgpu.stat.write(0);
|
|
pgif.ctrl.write(0);
|
|
old_gp0_value = 0;
|
|
|
|
|
|
dmaRegs.madr.address = 0;
|
|
dmaRegs.bcr.write(0);
|
|
dmaRegs.chcr.write(0);
|
|
//pgpuDmaTadr = 0;
|
|
|
|
dma.state.ll_active = 0;
|
|
dma.state.to_gpu_active = 0;
|
|
dma.state.to_iop_active = 0;
|
|
|
|
dma.ll_dma.data_read_address = 0;
|
|
dma.ll_dma.current_word = 0;
|
|
dma.ll_dma.total_words = 0;
|
|
dma.ll_dma.next_address = 0;
|
|
|
|
dma.normal.total_words = 0;
|
|
dma.normal.current_word = 0;
|
|
dma.normal.address = 0;
|
|
}
|
|
|
|
//Interrupt-related (IOP, EE and DMA):
|
|
|
|
void triggerPgifInt(int subCause)
|
|
{
|
|
// Probably we should try to mess with delta here.
|
|
hwIntcIrq(15);
|
|
cpuSetEvent();
|
|
}
|
|
|
|
void getIrqCmd(u32 data)
|
|
{
|
|
//For the IOP - GPU. This is triggered by the GP0(1Fh) - Interrupt Request (IRQ1) command.
|
|
//This may break stuff, because it doesn't detect whether this is really a GP0() command or data...
|
|
//Since PS1 HW also didn't recognize that is data or not, we should left it enabled.
|
|
{
|
|
if ((data & 0xFF000000) == 0x1F000000)
|
|
{
|
|
pgpu.stat.bits.IRQ1 = 1;
|
|
iopIntcIrq(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ackGpuIrq1()
|
|
{
|
|
//Acknowledge for the IOP GPU interrupt.
|
|
pgpu.stat.bits.IRQ1 = 0;
|
|
}
|
|
|
|
void pgpuDmaIntr(int trigDma)
|
|
{
|
|
//For the IOP GPU DMA channel.
|
|
//trigDma: 1=normal,ToGPU; 2=normal,FromGPU, 3=LinkedList
|
|
|
|
// psxmode: 25.09.2016 at this point, the emulator works even when removing this interrupt call. how? why?
|
|
#if PREVENT_IRQ_ON_NORM_DMA_TO_GPU == 1
|
|
if (trigDma != 1) //Interrupt on ToGPU DMA breaks some games. TODO: Why?
|
|
#endif
|
|
psxDmaInterrupt(2);
|
|
}
|
|
|
|
|
|
//Pass-through & intercepting functions:
|
|
|
|
u32 immRespHndl(u32 cmd, u32 data)
|
|
{
|
|
//Handles the GP1(10h) command, that requires immediate response.
|
|
//The data argument is the old data of the register (shouldn't be critical what it contains).
|
|
switch ((cmd & 0x7))
|
|
{
|
|
case 0:
|
|
case 1:
|
|
case 6:
|
|
case 7:
|
|
break; //Returns Nothing (old value in GPUREAD remains unchanged)
|
|
case 2:
|
|
data = pgif.imm_response.reg.e2 & 0x000FFFFF;
|
|
break; //Read Texture Window setting ;GP0(E2h) ;20bit/MSBs=Nothing
|
|
case 3:
|
|
data = pgif.imm_response.reg.e3 & 0x0007FFFF;
|
|
break; //Read Draw area top left ;GP0(E3h) ;19bit/MSBs=Nothing
|
|
case 4:
|
|
data = pgif.imm_response.reg.e4 & 0x0007FFFF;
|
|
break; //Read Draw area bottom right ;GP0(E4h) ;19bit/MSBs=Nothing
|
|
case 5:
|
|
data = pgif.imm_response.reg.e5 & 0x003FFFFF;
|
|
break; //Read Draw offset ;GP0(E5h) ;22bit
|
|
}
|
|
return data;
|
|
}
|
|
|
|
void handleGp1Command(u32 cmd)
|
|
{
|
|
//Check GP1() command and configure PGIF accordingly.
|
|
//Commands 0x00 - 0x01, 0x03, 0x05 - 0x08 are fully handled in ps1drv.
|
|
const u32 cmdNr = ((cmd >> 24) & 0xFF) & 0x3F;
|
|
switch (cmdNr)
|
|
{
|
|
case 2: //Acknowledge GPU IRQ
|
|
ackGpuIrq1();
|
|
break;
|
|
case 4: //DMA Direction / Data Request. The PS1DRV doesn't care of this... Should we do something on pgif ctrl?
|
|
pgpu.stat.bits.DDIR = cmd & 0x3;
|
|
//Since DREQ bit is dependent on DDIR bits, we should set it as soon as command is processed.
|
|
switch (pgpu.stat.bits.DDIR) //29-30 bit (0=Off, 1=FIFO, 2=CPUtoGP0, 3=GPUREADtoCPU) ;GP1(04h).0-1
|
|
{
|
|
case 0x00: // When GP1(04h)=0 ---> Always zero (0)
|
|
pgpu.stat.bits.DREQ = 0;
|
|
break;
|
|
case 0x01: // When GP1(04h)=1 ---> FIFO State (0=Full, 1=Not Full)
|
|
if (rb_gp0.count < (rb_gp0.size - PGIF_DAT_RB_LEAVE_FREE))
|
|
{
|
|
pgpu.stat.bits.DREQ = 1;
|
|
}
|
|
else
|
|
{
|
|
pgpu.stat.bits.DREQ = 0;
|
|
}
|
|
break;
|
|
case 0x02: // When GP1(04h)=2 ---> Same as GPUSTAT.28
|
|
pgpu.stat.bits.DREQ = pgpu.stat.bits.RDMA;
|
|
drainPgpuDmaLl(); //See comment in this function.
|
|
break;
|
|
case 0x03: //When GP1(04h)=3 ---> Same as GPUSTAT.27
|
|
pgpu.stat.bits.DREQ = pgpu.stat.bits.RSEND;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
u32 getUpdPgpuStatReg()
|
|
{
|
|
//PS1DRV does set bit RSEND on (probably - took from command print) GP0(C0h), should we do something?
|
|
//The PS1 program pools this bit to determine if there is data in the FIFO, it can get. Then starts DMA to get it.
|
|
|
|
//The PS1 program will not send the DMA direction command (GP1(04h)) and will not start DMA until this bit (27) becomes set.
|
|
pgpu.stat.bits.RSEND = pgif.ctrl.bits.data_from_gpu_ready;
|
|
return pgpu.stat.get();
|
|
}
|
|
|
|
u8 getGP0RbC_Count()
|
|
{
|
|
//Returns "correct" element-in-FIFO count, even if extremely large buffer is used.
|
|
return std::min(rb_gp0.count, 0x1F);
|
|
}
|
|
|
|
u32 getUpdPgifCtrlReg()
|
|
{
|
|
//Update fifo counts before returning register value
|
|
pgif.ctrl.bits.GP0_fifo_count = getGP0RbC_Count();
|
|
pgif.ctrl.bits.GP1_fifo_count = rb_gp1.count;
|
|
return pgif.ctrl.get();
|
|
}
|
|
|
|
|
|
void rb_gp1_Get(u32* data)
|
|
{
|
|
ringBufGet(&rb_gp1, data);
|
|
handleGp1Command(*data); //Setup GP1 right after reading command from FIFO.
|
|
}
|
|
|
|
void rb_gp0_Get(u32* data)
|
|
{
|
|
if (rb_gp0.count > 0)
|
|
{
|
|
ringBufGet(&rb_gp0, data);
|
|
getIrqCmd(*data); //Checks if an IRQ CMD passes through here and triggers IRQ when it does.
|
|
}
|
|
else
|
|
{
|
|
*data = old_gp0_value;
|
|
}
|
|
}
|
|
|
|
//PS1 GPU registers I/O handlers:
|
|
|
|
void psxGPUw(int addr, u32 data)
|
|
{
|
|
REG_LOG("PGPU write 0x%08X = 0x%08X", addr, data);
|
|
if (addr == HW_PS1_GPU_DATA)
|
|
{
|
|
ringBufPut(&rb_gp0, &data);
|
|
}
|
|
else if (addr == HW_PS1_GPU_STATUS)
|
|
{
|
|
// Check for Cmd 0x10-0x1F
|
|
u8 imm_check = (data >> 28);
|
|
imm_check &= 0x3;
|
|
if (imm_check == 1)
|
|
{
|
|
//Handle immediate-response command. Commands are NOT sent to the Fifo apparently (PS1DRV).
|
|
old_gp0_value = immRespHndl(data, old_gp0_value);
|
|
}
|
|
else
|
|
{
|
|
triggerPgifInt(0);
|
|
ringBufPut(&rb_gp1, &data);
|
|
}
|
|
}
|
|
}
|
|
|
|
u32 psxGPUr(int addr)
|
|
{
|
|
u32 data = 0;
|
|
if (addr == HW_PS1_GPU_DATA)
|
|
{
|
|
rb_gp0_Get(&data);
|
|
}
|
|
else if (addr == HW_PS1_GPU_STATUS)
|
|
{
|
|
data = getUpdPgpuStatReg();
|
|
}
|
|
if (addr != HW_PS1_GPU_STATUS)
|
|
REG_LOG("PGPU read 0x%08X = 0x%08X", addr, data);
|
|
|
|
return data;
|
|
}
|
|
|
|
// PGIF registers I/O handlers:
|
|
|
|
void PGIFw(int addr, u32 data)
|
|
{
|
|
//if (((addr != PGIF_CTRL) || (addr != PGPU_STAT) || ((addr == PGIF_CTRL) && (getUpdPgifCtrlReg() != data))) && (addr != PGPU_STAT))
|
|
REG_LOG("PGIF write 0x%08X = 0x%08X 0x%08X EEpc= %08X IOPpc= %08X ", addr, data, getUpdPgifCtrlReg(), cpuRegs.pc, psxRegs.pc);
|
|
|
|
switch (addr)
|
|
{
|
|
case PGPU_STAT:
|
|
pgpu.stat.write(data); //Should all bits be writable?
|
|
break;
|
|
case PGIF_CTRL:
|
|
pgif.ctrl.write(data);
|
|
fillFifoOnDrain(); //Now this checks the 0x8 bit of the PGIF_CTRL reg, so it here too,
|
|
break; //so that it gets updated immediately once it is set.
|
|
case IMM_E2:
|
|
pgif.imm_response.reg.e2 = data;
|
|
break;
|
|
case IMM_E3:
|
|
pgif.imm_response.reg.e3 = data;
|
|
break;
|
|
case IMM_E4:
|
|
pgif.imm_response.reg.e4 = data;
|
|
break;
|
|
case IMM_E5:
|
|
pgif.imm_response.reg.e5 = data;
|
|
break;
|
|
case PGPU_CMD_FIFO:
|
|
Console.Error("PGIF CMD FIFO write by EE (SHOULDN'T HAPPEN) 0x%08X = 0x%08X", addr, data);
|
|
break;
|
|
case PGPU_DAT_FIFO:
|
|
ringBufPut(&rb_gp0, &data);
|
|
// Console.WriteLn( "\n\r PGIF REVERSE !!! DATA write 0x%08X = 0x%08X IF_CTRL= %08X PGPU_STAT= %08X CmdCnt 0x%X \n\r", addr, data, getUpdPgifCtrlReg(), getUpdPgpuStatReg(), rb_gp1.count);
|
|
drainPgpuDmaNrToIop();
|
|
break;
|
|
default:
|
|
DevCon.Error("PGIF write to unknown location 0xx% , data: %x", addr, data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Read PGIF Hardware Registers.
|
|
u32 PGIFr(int addr)
|
|
{
|
|
u32 data = 0;
|
|
switch (addr)
|
|
{
|
|
case PGPU_STAT:
|
|
data = pgpu.stat.get();
|
|
break;
|
|
case PGIF_CTRL:
|
|
data = getUpdPgifCtrlReg();
|
|
break;
|
|
case IMM_E2:
|
|
data = pgif.imm_response.reg.e2;
|
|
break;
|
|
case IMM_E3:
|
|
data = pgif.imm_response.reg.e3;
|
|
break;
|
|
case IMM_E4:
|
|
data = pgif.imm_response.reg.e4;
|
|
break;
|
|
case IMM_E5:
|
|
data = pgif.imm_response.reg.e5;
|
|
break;
|
|
case PGPU_CMD_FIFO:
|
|
rb_gp1_Get(&data);
|
|
break;
|
|
case PGPU_DAT_FIFO:
|
|
fillFifoOnDrain();
|
|
rb_gp0_Get(&data);
|
|
break;
|
|
default:
|
|
DevCon.Error("PGIF read from unknown location 0xx%", addr);
|
|
break;
|
|
}
|
|
//if (addr != PGPU_DAT_FIFO)
|
|
//if ((addr != PGIF_CTRL) || (getUpdPgifCtrlReg() != data))
|
|
// if (addr != PGPU_STAT)
|
|
// REG_LOG("PGIF read %08X = %08X GPU_ST %08X IF_STAT %08X IOPpc %08X EEpc= %08X ", addr, data, getUpdPgpuStatReg(), getUpdPgifCtrlReg(), psxRegs.pc, cpuRegs.pc);
|
|
|
|
return data;
|
|
}
|
|
|
|
void PGIFrQword(u32 addr, void* dat)
|
|
{
|
|
u32* data = (u32*)dat;
|
|
|
|
if (addr == PGPU_CMD_FIFO)
|
|
{
|
|
//shouldn't happen
|
|
Console.Error("PGIF QW CMD read =ERR!");
|
|
}
|
|
else if (addr == PGPU_DAT_FIFO)
|
|
{
|
|
fillFifoOnDrain();
|
|
rb_gp0_Get(data + 0);
|
|
rb_gp0_Get(data + 1);
|
|
rb_gp0_Get(data + 2);
|
|
rb_gp0_Get(data + 3);
|
|
|
|
fillFifoOnDrain();
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLn("PGIF QWord Read from address %08X ERR - shouldnt happen!", addr);
|
|
Console.WriteLn("Data = %08X %08X %08X %08X ", *(u32*)(data + 0), *(u32*)(data + 1), *(u32*)(data + 2), *(u32*)(data + 3));
|
|
}
|
|
}
|
|
|
|
void PGIFwQword(u32 addr, void* dat)
|
|
{
|
|
u32* data = (u32*)dat;
|
|
DevCon.Warning("WARNING PGIF WRITE BY PS1DRV ! - NOT KNOWN TO EVER BE DONE!");
|
|
Console.WriteLn("PGIF QW write 0x%08X = 0x%08X %08X %08X %08X ", addr, *(u32*)(data + 0), *(u32*)(data + 1), *(u32*)(data + 2), *(u32*)(data + 3));
|
|
|
|
if (addr == PGPU_CMD_FIFO)
|
|
{
|
|
Console.Error("PGIF QW CMD write!");
|
|
}
|
|
else if (addr == PGPU_DAT_FIFO)
|
|
{
|
|
ringBufPut(&rb_gp0, (u32*)(data + 0));
|
|
ringBufPut(&rb_gp0, (u32*)(data + 1));
|
|
ringBufPut(&rb_gp0, (u32*)(data + 2));
|
|
ringBufPut(&rb_gp0, (u32*)(data + 3));
|
|
drainPgpuDmaNrToIop();
|
|
}
|
|
}
|
|
|
|
//DMA-emulating functions:
|
|
|
|
//This function is used as a global FIFO-DMA-fill function and both Linked-list normal DMA call it,
|
|
void fillFifoOnDrain()
|
|
{
|
|
//Skip filing FIFO with elements, if PS1DRV hasn't set this bit.
|
|
//Maybe it could be cleared once FIFO has data?
|
|
if (!pgif.ctrl.bits.fifo_GP0_ready_for_data)
|
|
return;
|
|
|
|
|
|
//This is done here in a loop, rather than recursively in each function, because a very large buffer causes stack oveflow.
|
|
while ((rb_gp0.count < ((rb_gp0.size) - PGIF_DAT_RB_LEAVE_FREE)) && ((dma.state.to_gpu_active) || (dma.state.ll_active)))
|
|
{
|
|
drainPgpuDmaLl();
|
|
drainPgpuDmaNrToGpu();
|
|
}
|
|
//Clear bit as DMA will be run - normally it should be cleared only once the current request finishes, but the IOP won't notice anything anyway.
|
|
//WARNING: Current implementation assume that GPU->IOP DMA uses this flag, so we only clear it here if the mode is not GPU->IOP.
|
|
if (((dma.state.ll_active) || (dma.state.to_gpu_active)) && (!dma.state.to_iop_active))
|
|
{
|
|
pgif.ctrl.bits.fifo_GP0_ready_for_data = 0;
|
|
}
|
|
}
|
|
|
|
void drainPgpuDmaLl()
|
|
{
|
|
if (!dma.state.ll_active)
|
|
return;
|
|
|
|
//Some games (Breath of Fire 3 US) set-up linked-list DMA, but don't immediatelly have the list correctly set-up,
|
|
//so the result is that this function loops indefinitely, because of some links pointing back to themselves, forming a loop.
|
|
//The solution is to only start DMA once the GP1(04h) - DMA Direction / Data Request command has been set to the value 0x2 (CPU->GPU DMA)
|
|
|
|
//Buffer full - needs to be drained first.
|
|
if (rb_gp0.count >= ((rb_gp0.size) - PGIF_DAT_RB_LEAVE_FREE))
|
|
return;
|
|
|
|
if (dmaRegs.chcr.bits.MAS)
|
|
DevCon.Error("Unimplemented backward memory step on PGPU DMA Linked List");
|
|
|
|
if (dma.ll_dma.current_word >= dma.ll_dma.total_words)
|
|
{
|
|
if (dma.ll_dma.next_address == DMA_LL_END_CODE)
|
|
{
|
|
//Reached end of linked list
|
|
dma.state.ll_active = 0;
|
|
dmaRegs.madr.address = 0x00FFFFFF;
|
|
dmaRegs.chcr.bits.BUSY = 0; //Transfer completed => clear busy flag
|
|
pgpuDmaIntr(3);
|
|
PGPU_DMA_LOG("PGPU DMA Linked List Finished");
|
|
}
|
|
else
|
|
{
|
|
//Or the beginning of a new one
|
|
u32 data = iopMemRead32(dma.ll_dma.next_address);
|
|
PGPU_DMA_LOG( "Next PGPU LL DMA header= %08X ", data);
|
|
dmaRegs.madr.address = data & 0x00FFFFFF; //Copy the address in MADR.
|
|
dma.ll_dma.data_read_address = dma.ll_dma.next_address + 4; //start of data section of packet
|
|
dma.ll_dma.current_word = 0;
|
|
dma.ll_dma.total_words = (data >> 24) & 0xFF; // Current length of packet and future address of header word.
|
|
dma.ll_dma.next_address = dmaRegs.madr.address;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//We are in the middle of linked list transfer
|
|
u32 data = iopMemRead32(dma.ll_dma.data_read_address);
|
|
PGPU_DMA_LOG( "PGPU LL DMA data= %08X addr %08X ", data, dma.ll_dma.data_read_address);
|
|
ringBufPut(&rb_gp0, &data);
|
|
dma.ll_dma.data_read_address += 4;
|
|
dma.ll_dma.current_word++;
|
|
}
|
|
}
|
|
|
|
void drainPgpuDmaNrToGpu()
|
|
{
|
|
if (!dma.state.to_gpu_active)
|
|
return;
|
|
|
|
//Buffer full - needs to be drained first.
|
|
if (rb_gp0.count >= ((rb_gp0.size) - PGIF_DAT_RB_LEAVE_FREE))
|
|
return;
|
|
|
|
if (dma.normal.current_word < dma.normal.total_words)
|
|
{
|
|
u32 data = iopMemRead32(dma.normal.address);
|
|
PGPU_DMA_LOG( "To GPU Normal DMA data= %08X addr %08X ", data, dma.ll_dma.data_read_address);
|
|
|
|
ringBufPut(&rb_gp0, &data);
|
|
if (dmaRegs.chcr.bits.MAS)
|
|
{
|
|
DevCon.Error("Unimplemented backward memory step on TO GPU DMA");
|
|
}
|
|
dmaRegs.madr.address += 4;
|
|
dma.normal.address += 4;
|
|
dma.normal.current_word++;
|
|
|
|
// decrease block amount only if full block size were drained.
|
|
if ((dma.normal.current_word % dmaRegs.bcr.bit.block_size) == 0)
|
|
dmaRegs.bcr.bit.block_amount -= 1;
|
|
}
|
|
if (dma.normal.current_word >= dma.normal.total_words)
|
|
{
|
|
//Reached end of sequence = complete
|
|
dma.state.to_gpu_active = 0;
|
|
dmaRegs.chcr.bits.BUSY = 0;
|
|
pgpuDmaIntr(1);
|
|
PGPU_DMA_LOG("To GPU DMA Normal FINISHED");
|
|
}
|
|
}
|
|
|
|
void drainPgpuDmaNrToIop()
|
|
{
|
|
if (!dma.state.to_iop_active || rb_gp0.count <= 0)
|
|
return;
|
|
|
|
if (dma.normal.current_word < dma.normal.total_words)
|
|
{
|
|
u32 data = 0;
|
|
//This is not the best way, but... is there another?
|
|
ringBufGet(&rb_gp0, &data);
|
|
iopMemWrite32(dma.normal.address, data);
|
|
if (dmaRegs.chcr.bits.MAS)
|
|
{
|
|
DevCon.Error("Unimplemented backward memory step on FROM GPU DMA");
|
|
}
|
|
dmaRegs.madr.address += 4;
|
|
dma.normal.address += 4;
|
|
dma.normal.current_word++;
|
|
//dmaRegs.madr.address += 4; //It is unclear if this should be done exactly so... // kozarovv: WHY EVEN DO THAT AGAIN?
|
|
if ((dma.normal.current_word % dmaRegs.bcr.bit.block_size) == 0)
|
|
{
|
|
// decrease block amount only if full block size were drained.
|
|
dmaRegs.bcr.bit.block_amount -= 1;
|
|
}
|
|
PGPU_DMA_LOG("GPU->IOP ba: %x , cw: %x , tw: %x" ,dmaRegs.bcr.bit.block_amount, dma.normal.current_word, dma.normal.total_words);
|
|
}
|
|
if (dma.normal.current_word >= dma.normal.total_words)
|
|
{
|
|
dma.state.to_iop_active = 0;
|
|
dmaRegs.chcr.bits.BUSY = 0;
|
|
pgpuDmaIntr(2);
|
|
}
|
|
|
|
if (rb_gp0.count > 0)
|
|
drainPgpuDmaNrToIop();
|
|
}
|
|
|
|
|
|
void processPgpuDma()
|
|
{
|
|
if (!dmaRegs.chcr.bits.TSM)
|
|
{
|
|
Console.Error("SyncMode 0 on GPU DMA!");
|
|
}
|
|
if (dmaRegs.chcr.bits.TSM == 3)
|
|
{
|
|
Console.Warning("SyncMode 3! Assuming SyncMode 1");
|
|
dmaRegs.chcr.bits.TSM = 1;
|
|
}
|
|
PGPU_DMA_LOG("Starting GPU DMA! CHCR %08X BCR %08X MADR %08X ", dmaRegs.chcr.get(), dmaRegs.bcr.get(), dmaRegs.madr.address);
|
|
|
|
//Linked List Mode
|
|
if (dmaRegs.chcr.bits.TSM == 2)
|
|
{
|
|
//To GPU
|
|
if (dmaRegs.chcr.bits.DIR)
|
|
{
|
|
dma.state.ll_active = 1;
|
|
dma.ll_dma.next_address = (dmaRegs.madr.address & 0x00FFFFFF); //The address in IOP RAM where to load the first header word from
|
|
dma.ll_dma.current_word = 0;
|
|
dma.ll_dma.total_words = 0;
|
|
PGPU_DMA_LOG("LL DMA FILL");
|
|
|
|
//fill a single word in fifo now, because otherwise PS1DRV won't know that a transfer is pending.
|
|
fillFifoOnDrain();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Console.Error("Error: Linked list from GPU DMA!");
|
|
return;
|
|
}
|
|
}
|
|
dma.normal.current_word = 0;
|
|
dma.normal.address = dmaRegs.madr.address & 0x1FFFFFFF; // Sould we allow whole range? Maybe for psx SPR?
|
|
dma.normal.total_words = (dmaRegs.bcr.bit.block_size * dmaRegs.bcr.get_block_amount());
|
|
|
|
if (dmaRegs.chcr.bits.DIR) // to gpu
|
|
{
|
|
PGPU_DMA_LOG("NORMAL DMA TO GPU");
|
|
dma.state.to_gpu_active = 1;
|
|
fillFifoOnDrain();
|
|
}
|
|
else
|
|
{
|
|
PGPU_DMA_LOG("NORMAL DMA FROM GPU");
|
|
dma.state.to_iop_active = 1;
|
|
drainPgpuDmaNrToIop();
|
|
}
|
|
}
|
|
|
|
u32 psxDma2GpuR(u32 addr)
|
|
{
|
|
u32 data = 0;
|
|
addr &= 0x1FFFFFFF;
|
|
switch (addr)
|
|
{
|
|
case PGPU_DMA_MADR:
|
|
data = dmaRegs.madr.address;
|
|
break;
|
|
case PGPU_DMA_BCR:
|
|
data = dmaRegs.bcr.get();
|
|
break;
|
|
case PGPU_DMA_CHCR:
|
|
data = dmaRegs.chcr.get();
|
|
break;
|
|
case PGPU_DMA_TADR:
|
|
data = pgpuDmaTadr;
|
|
Console.Error("PGPU DMA read TADR!");
|
|
break;
|
|
default:
|
|
Console.Error("Unknown PGPU DMA read 0x%08X", addr);
|
|
break;
|
|
}
|
|
if (addr != PGPU_DMA_CHCR)
|
|
PGPU_DMA_LOG("PGPU DMA read 0x%08X = 0x%08X", addr, data);
|
|
return data;
|
|
}
|
|
|
|
void psxDma2GpuW(u32 addr, u32 data)
|
|
{
|
|
PGPU_DMA_LOG("PGPU DMA write 0x%08X = 0x%08X", addr, data);
|
|
addr &= 0x1FFFFFFF;
|
|
switch (addr)
|
|
{
|
|
case PGPU_DMA_MADR:
|
|
dmaRegs.madr.address = (data & 0x00FFFFFF);
|
|
break;
|
|
case PGPU_DMA_BCR:
|
|
dmaRegs.bcr.write(data);
|
|
break;
|
|
case PGPU_DMA_CHCR:
|
|
dmaRegs.chcr.write(data);
|
|
if (dmaRegs.chcr.bits.BUSY)
|
|
{
|
|
processPgpuDma();
|
|
}
|
|
break;
|
|
case PGPU_DMA_TADR:
|
|
pgpuDmaTadr = data;
|
|
Console.Error("PGPU DMA write TADR! ");
|
|
break;
|
|
default:
|
|
Console.Error("Unknown PGPU DMA write 0x%08X = 0x%08X", addr, data);
|
|
break;
|
|
}
|
|
} |