mirror of
https://github.com/open-goal/jak-project
synced 2026-08-06 18:03:30 -04:00
d3cc739e43
This attempts to get into master whatever work was done in this PR / it's earlier PR https://github.com/open-goal/jak-project/pull/3965 I don't want this work to be lost / floating around in massive PRs. However the changes are: - switch to ntsc_v1 instead of PAL as the development target, as we have done for all other games - remove most of the copied-from-jak2/3 changes as they need to be confirmed during the decompilation process not just assumed - avoids committing any changes to `game/kernel/common` as it was not clear to me if these were changes made in jak x's kernel that were not properly broken out into it's own functions. We don't want to accidentally introduce bugs into jak1-3's kernel code. - in other words, if the change in the kernel only happens in jak x...it should likely be specific to jak x's kernel, not common. --------- Co-authored-by: VodBox <dillon@vodbox.io> Co-authored-by: yodah <greenboyyodah@gmail.com>
625 lines
21 KiB
C++
625 lines
21 KiB
C++
#include "dma.h"
|
|
|
|
#include "common/log/log.h"
|
|
#include "common/util/Assert.h"
|
|
|
|
#include "game/overlord/jakx/basefile.h"
|
|
#include "game/overlord/jakx/overlord.h"
|
|
#include "game/overlord/jakx/vag.h"
|
|
#include "game/sce/iop.h"
|
|
#include "game/sound/sdshim.h"
|
|
#include "game/sound/sndshim.h"
|
|
|
|
#define VOICE_BIT(voice) (1 << ((voice) >> 1))
|
|
|
|
namespace jakx {
|
|
OverlordStreamMemory g_overlord_stream_memory;
|
|
using namespace iop;
|
|
namespace {
|
|
|
|
// most recent call to voice_trans_wrapper's arguments
|
|
u32 g_voiceTransMode = 0;
|
|
u32 g_voiceTransSize = 0;
|
|
s16 g_voiceTransChannel = 0;
|
|
const void* g_voiceTransAddr = nullptr;
|
|
u32 g_voiceTransSpuAddr = 0;
|
|
|
|
// if we've started a transfer recently
|
|
bool g_voiceTransRunning = false;
|
|
// when that transfer was started
|
|
u32 g_voiceTransTime = 0;
|
|
|
|
// despite the name, this is really an indicator that the SPU streaming system is waiting
|
|
// for a SPU interrupt on completion.
|
|
bool g_bSpuDmaBusy = false;
|
|
int g_nSpuDmaChannel = 0;
|
|
|
|
ISO_VAGCommand* g_pDmaVagCmd = nullptr;
|
|
ISO_VAGCommand* g_pDmaStereoVagCmd = nullptr;
|
|
|
|
int g_nSpuDmaChunks = 0;
|
|
|
|
std::array<DmaQueueEntry, 16> g_aSpuDmaQueue;
|
|
int g_nSpuDmaQueueHead = 0;
|
|
int g_nSpuDmaQueueTail = 0;
|
|
int g_nSpuDmaQueueCount = 0;
|
|
|
|
struct DmaInterruptHandlerHack {
|
|
s32 chan = 0;
|
|
sceSdTransIntrHandler cb = nullptr;
|
|
void* data;
|
|
int countdown = 0;
|
|
bool pending = false;
|
|
} g_DmaInterruptHack;
|
|
|
|
const char* g_current_stream_name = 0;
|
|
|
|
} // namespace
|
|
|
|
void jakx_overlord_init_globals_dma() {
|
|
g_voiceTransMode = 0;
|
|
g_voiceTransSize = 0;
|
|
g_voiceTransChannel = 0;
|
|
g_voiceTransAddr = nullptr;
|
|
g_voiceTransSpuAddr = 0;
|
|
g_voiceTransRunning = false;
|
|
g_voiceTransTime = 0;
|
|
g_bSpuDmaBusy = false;
|
|
g_nSpuDmaChannel = 0;
|
|
g_pDmaVagCmd = nullptr;
|
|
g_pDmaStereoVagCmd = nullptr;
|
|
g_nSpuDmaChunks = 0;
|
|
g_aSpuDmaQueue = {};
|
|
g_nSpuDmaQueueHead = 0;
|
|
g_nSpuDmaQueueCount = 0;
|
|
g_nSpuDmaQueueTail = 0;
|
|
g_DmaInterruptHack = {};
|
|
}
|
|
|
|
// The DMA callback hack below is used to defer dma completion "interrupts" until the next run
|
|
// of the ISO Thread. This avoids re-entry type problems where the original design would set off
|
|
// a dma transfer in the completion handler of the previous transfer, and expect a few instructions
|
|
// to run after.
|
|
|
|
void uninstall_dma_intr() {
|
|
g_DmaInterruptHack = {};
|
|
}
|
|
|
|
void set_dma_intr_handler_hack(s32 chan, sceSdTransIntrHandler cb, void* data) {
|
|
ASSERT(!g_DmaInterruptHack.cb);
|
|
g_DmaInterruptHack.chan = chan;
|
|
g_DmaInterruptHack.cb = cb;
|
|
g_DmaInterruptHack.data = data;
|
|
g_DmaInterruptHack.countdown = 10;
|
|
g_DmaInterruptHack.pending = true;
|
|
}
|
|
|
|
int SPUDmaIntr(int channel, void* userdata);
|
|
|
|
void complete_dma_now() {
|
|
if (g_DmaInterruptHack.pending) {
|
|
int chan = g_DmaInterruptHack.chan;
|
|
void* data = g_DmaInterruptHack.data;
|
|
g_DmaInterruptHack = {};
|
|
SPUDmaIntr(chan, data);
|
|
}
|
|
}
|
|
|
|
void dma_intr_hack() {
|
|
if (g_DmaInterruptHack.countdown) {
|
|
g_DmaInterruptHack.countdown--;
|
|
if (g_DmaInterruptHack.countdown == 0) {
|
|
int chan = g_DmaInterruptHack.chan;
|
|
void* data = g_DmaInterruptHack.data;
|
|
g_DmaInterruptHack = {};
|
|
SPUDmaIntr(chan, data);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* This function is used to set up a DMA transfer to SPU DMA.
|
|
*
|
|
* This wrapper was added very close to the end of Jak 3's development.
|
|
*
|
|
* I believe it basically checks for dma transfers that are somehow "dropped", and retries them.
|
|
* Since I don't think our IOP framework will ever do this, we have an assert if the dropped logic
|
|
* ever goes off.
|
|
*/
|
|
int voice_trans_wrapper(s16 chan, u32 mode, const void* iop_addr, u32 spu_addr, u32 size) {
|
|
// remember the transfer settings. If there's a transfer in progress, so we can't start here,
|
|
// we'll use these to start the transfer later.
|
|
g_voiceTransMode = mode;
|
|
g_voiceTransSize = size;
|
|
g_voiceTransChannel = chan;
|
|
g_voiceTransAddr = iop_addr;
|
|
g_voiceTransSpuAddr = spu_addr;
|
|
|
|
if (g_voiceTransRunning) {
|
|
// I claim this should never happen, and this is their workaround for a bug.
|
|
ASSERT_NOT_REACHED();
|
|
return -0xd2; // busy
|
|
} else {
|
|
g_voiceTransRunning = true;
|
|
g_voiceTransTime = GetSystemTimeLow();
|
|
|
|
switch (spu_addr) {
|
|
case 0x5040:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 0, 0);
|
|
break;
|
|
case 0x7040:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 0, 1);
|
|
break;
|
|
case 0x9080:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 1, 0);
|
|
break;
|
|
case 0xb080:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 1, 1);
|
|
break;
|
|
case 0xd0c0:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 2, 0);
|
|
break;
|
|
case 0xf0c0:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 2, 1);
|
|
break;
|
|
case 0x11100:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 3, 0);
|
|
break;
|
|
case 0x13100:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 3, 1);
|
|
break;
|
|
case 0x15140:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 4, 0);
|
|
break;
|
|
case 0x17140:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 4, 1);
|
|
break;
|
|
case 0x19180:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 5, 0);
|
|
break;
|
|
case 0x1b180:
|
|
g_overlord_stream_memory.update_name(g_current_stream_name, 5, 1);
|
|
break;
|
|
}
|
|
return sceSdVoiceTrans(chan, mode, iop_addr, spu_addr, size);
|
|
}
|
|
}
|
|
|
|
OverlordStreamMemory::OverlordStreamMemory() {
|
|
for (auto& x : infos) {
|
|
for (auto& y : x) {
|
|
y.idx = 0;
|
|
strcpy(y.name.chars, "Uninitialized");
|
|
}
|
|
}
|
|
}
|
|
|
|
void OverlordStreamMemory::update_name(const char* input, int stream, int side) {
|
|
auto& info = infos[stream][side];
|
|
if (!input) {
|
|
strcpy(info.name.chars, "???");
|
|
info.idx = 0;
|
|
} else {
|
|
if (strcmp(input, info.name.chars) == 0) {
|
|
info.idx++;
|
|
} else {
|
|
info.idx = 0;
|
|
strncpy(info.name.chars, input, 48);
|
|
info.name.chars[47] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
u32 read_rate_calc(u32 pitch) {
|
|
u64 pitch1 = (pitch >> 3);
|
|
u64 mult_result = pitch1 * 0x2492'4925ull;
|
|
return mult_result >> 32;
|
|
}
|
|
|
|
/*!
|
|
* The worst function of all time - the SPU DMA completion interrupt.
|
|
*/
|
|
int SPUDmaIntr(int channel, void* userdata) {
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr enter! {} 0x{:x}", channel, (u64)userdata);
|
|
|
|
if (!g_bSpuDmaBusy) {
|
|
// we got an interrupt, but weren't expecting it, or no longer have the need for the data.
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr exit - not busy");
|
|
return 0;
|
|
}
|
|
|
|
if (channel != g_nSpuDmaChannel) {
|
|
// interrupt was for the wrong channel, somehow.
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr exit - not our channel ??");
|
|
return 0;
|
|
}
|
|
|
|
// since we're in the completion handler, we know that there is no voice trans (SPU DMA) running.
|
|
g_voiceTransRunning = false;
|
|
|
|
// This next block will handle updating the playback command that triggered this dma:
|
|
if (g_pDmaVagCmd) {
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDma for cmd {}", g_pDmaVagCmd->name);
|
|
if (!g_pDmaStereoVagCmd) {
|
|
// non-stereo audio
|
|
|
|
// set a flag to indicate even/odd number of chunks have been dma'd
|
|
if ((g_nSpuDmaChunks & 1) == 0) {
|
|
g_pDmaVagCmd->flags.dma_complete_even_chunk_count = 1;
|
|
} else {
|
|
g_pDmaVagCmd->flags.dma_complete_odd_chunk_count = 1;
|
|
}
|
|
} else {
|
|
// stereo audio. This requires two uploads, one for left/right audio. If we've finished the
|
|
// first, start the second one here:
|
|
if (g_pDmaStereoVagCmd->xfer_size) {
|
|
// parameters for second upload
|
|
int chan = g_pDmaVagCmd->dma_chan;
|
|
const u8* iop_addr = g_pDmaStereoVagCmd->dma_iop_mem_ptr;
|
|
int size = g_pDmaStereoVagCmd->xfer_size;
|
|
|
|
// SPU addr - toggle the buffer based on stereo side:
|
|
// TODO: better explanation of why this picks the correct buffer.
|
|
int spu_addr;
|
|
if ((g_nSpuDmaChunks & 1) == 0) {
|
|
spu_addr = g_pDmaStereoVagCmd->stream_sram;
|
|
} else {
|
|
spu_addr = g_pDmaStereoVagCmd->stream_sram + 0x2000;
|
|
}
|
|
|
|
// these lines reordered to possibly support immediate dma completion callback??
|
|
|
|
// clear flag so we know not to transfer the next part
|
|
g_pDmaStereoVagCmd->xfer_size = 0;
|
|
g_pDmaStereoVagCmd->dma_iop_mem_ptr = nullptr;
|
|
|
|
// start next transfer
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr starting stereo sibling transfer");
|
|
set_dma_intr_handler_hack(g_nSpuDmaChannel, SPUDmaIntr, userdata);
|
|
voice_trans_wrapper(chan, 0, iop_addr, spu_addr, size);
|
|
return 0;
|
|
}
|
|
|
|
// second stereo upload completed - update double-buffering flags
|
|
if ((g_nSpuDmaChunks & 1) == 0) {
|
|
g_pDmaVagCmd->flags.dma_complete_even_chunk_count = 1;
|
|
g_pDmaStereoVagCmd->flags.dma_complete_even_chunk_count = 1;
|
|
} else {
|
|
g_pDmaVagCmd->flags.dma_complete_odd_chunk_count = 1;
|
|
g_pDmaStereoVagCmd->flags.dma_complete_odd_chunk_count = 1;
|
|
}
|
|
}
|
|
|
|
// if this is the first chunk, we'll start the actual audio here:
|
|
// lg::warn("----------> interrupt with chunks {}\n", g_nSpuDmaChunks);
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks count {}", g_nSpuDmaChunks);
|
|
|
|
if (g_nSpuDmaChunks == 0) {
|
|
// compute pitch/playback rate
|
|
int pitch = CalculateVAGPitch(g_pDmaVagCmd->pitch1, g_pDmaVagCmd->pitch_cmd);
|
|
ASSERT(pitch == (pitch & 0xffff));
|
|
|
|
// inform the ISO system how fast we're reading
|
|
if (g_pDmaVagCmd->m_pBaseFile) {
|
|
// unlike actual playback, this is done with the pitch1 value from the file itself - so if
|
|
// we speed up/slow down stuff in debug, it won't change streaming modes
|
|
const int pitch_from_file =
|
|
CalculateVAGPitch(g_pDmaVagCmd->pitch1_file, g_pDmaVagCmd->pitch_cmd);
|
|
int rate = g_pDmaStereoVagCmd ? pitch_from_file * 0x2ee : pitch_from_file * 0x177;
|
|
g_pDmaVagCmd->m_pBaseFile->m_ReadRate = read_rate_calc(rate);
|
|
}
|
|
|
|
// start!
|
|
u32 voice_mask = 0;
|
|
if (!g_pDmaStereoVagCmd) {
|
|
// forget any previous spu address
|
|
g_pDmaVagCmd->current_spu_address = 0;
|
|
|
|
static_assert(SD_VA_SSA == 0x2040);
|
|
static_assert(SD_S_KOFF == 0x1600);
|
|
static_assert(SD_S_KON == 0x1500);
|
|
|
|
static_assert(SD_VP_ADSR1 == 0x300);
|
|
static_assert(SD_VP_ADSR2 == 0x400);
|
|
static_assert(SD_VP_PITCH == 0x200);
|
|
|
|
// before touching SPU2 hardware, wait for voice safety:
|
|
BlockUntilVoiceSafe(g_pDmaVagCmd->voice, 0x900);
|
|
|
|
// set address and ADSR settings
|
|
sceSdSetAddr(g_pDmaVagCmd->voice | SD_VA_SSA, g_pDmaVagCmd->stream_sram + 0x30);
|
|
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_ADSR1, 0xff);
|
|
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_ADSR2, 0x1fc0);
|
|
if (g_pDmaVagCmd->flags.paused) {
|
|
pitch = 0;
|
|
}
|
|
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_PITCH, pitch);
|
|
voice_mask = VOICE_BIT(g_pDmaVagCmd->voice);
|
|
} else {
|
|
// forget any previous spu address
|
|
g_pDmaVagCmd->current_spu_address = 0;
|
|
g_pDmaStereoVagCmd->current_spu_address = 0;
|
|
|
|
// wait for voices to be safe to adjust
|
|
BlockUntilVoiceSafe(g_pDmaVagCmd->voice, 0x900);
|
|
BlockUntilVoiceSafe(g_pDmaStereoVagCmd->voice, 0x900);
|
|
|
|
// set voice params
|
|
sceSdSetAddr(g_pDmaVagCmd->voice | SD_VA_SSA, g_pDmaVagCmd->stream_sram + 0x30);
|
|
sceSdSetAddr(g_pDmaStereoVagCmd->voice | SD_VA_SSA, g_pDmaStereoVagCmd->stream_sram + 0x30);
|
|
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_ADSR1, 0xff);
|
|
sceSdSetParam(g_pDmaStereoVagCmd->voice | SD_VP_ADSR1, 0xff);
|
|
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_ADSR2, 0x1fc0);
|
|
sceSdSetParam(g_pDmaStereoVagCmd->voice | SD_VP_ADSR2, 0x1fc0);
|
|
if (g_pDmaVagCmd->flags.paused) {
|
|
pitch = 0;
|
|
}
|
|
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_PITCH, pitch);
|
|
sceSdSetParam(g_pDmaStereoVagCmd->voice | SD_VP_PITCH, pitch);
|
|
voice_mask = VOICE_BIT(g_pDmaVagCmd->voice) | VOICE_BIT(g_pDmaStereoVagCmd->voice);
|
|
}
|
|
|
|
// do key-on or key-off
|
|
if (g_pDmaVagCmd->flags.paused) {
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks 0, key off");
|
|
BlockUntilAllVoicesSafe();
|
|
sceSdSetSwitch(SD_S_KOFF | (g_pDmaVagCmd->voice & 1), voice_mask);
|
|
} else {
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks 0, key on");
|
|
BlockUntilAllVoicesSafe();
|
|
sceSdSetSwitch(SD_S_KON | (g_pDmaVagCmd->voice & 1), voice_mask);
|
|
}
|
|
|
|
// remember the time of the key-on/off. This is used to avoid sending voice commands
|
|
// quickly, which somehow confuses the sound hardware.
|
|
auto sys_time = GetSystemTimeLow();
|
|
MarkVoiceKeyedOnOff(g_pDmaVagCmd->voice, sys_time);
|
|
if (g_pDmaStereoVagCmd) {
|
|
MarkVoiceKeyedOnOff(g_pDmaStereoVagCmd->voice, sys_time);
|
|
}
|
|
} else if (g_nSpuDmaChunks == 1) {
|
|
g_pDmaVagCmd->flags.saw_chunks1 = 1;
|
|
if (g_pDmaStereoVagCmd) {
|
|
g_pDmaStereoVagCmd->flags.saw_chunks1 = 1;
|
|
}
|
|
|
|
if (g_pDmaVagCmd->flags.paused) {
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks 1, pausing");
|
|
u32 voice_mask = 0;
|
|
if (!g_pDmaStereoVagCmd) {
|
|
// pause by setting pitches to 0
|
|
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_PITCH, 0);
|
|
BlockUntilVoiceSafe(VOICE_BIT(g_pDmaVagCmd->voice), 0x900);
|
|
voice_mask = VOICE_BIT(g_pDmaVagCmd->voice);
|
|
} else {
|
|
sceSdSetParam(g_pDmaStereoVagCmd->voice | SD_VP_PITCH, 0);
|
|
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_PITCH, 0);
|
|
BlockUntilVoiceSafe(VOICE_BIT(g_pDmaVagCmd->voice), 0x900);
|
|
BlockUntilVoiceSafe(VOICE_BIT(g_pDmaStereoVagCmd->voice), 0x900);
|
|
voice_mask = VOICE_BIT(g_pDmaVagCmd->voice) | VOICE_BIT(g_pDmaStereoVagCmd->voice);
|
|
}
|
|
|
|
// switch off
|
|
BlockUntilAllVoicesSafe();
|
|
sceSdSetSwitch(SD_S_KOFF | (g_pDmaVagCmd->voice & 1), voice_mask);
|
|
auto sys_time = GetSystemTimeLow();
|
|
MarkVoiceKeyedOnOff(g_pDmaVagCmd->voice, sys_time);
|
|
if (g_pDmaStereoVagCmd) {
|
|
MarkVoiceKeyedOnOff(g_pDmaStereoVagCmd->voice, sys_time);
|
|
}
|
|
} else {
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks 1, unpausing by call to UnPauseVAG");
|
|
g_pDmaVagCmd->flags.paused = 1;
|
|
UnPauseVAG(g_pDmaVagCmd);
|
|
}
|
|
}
|
|
|
|
// now that we've processed the command from this interrupt, mark it as safe to modify
|
|
g_pDmaVagCmd->safe_to_modify_dma = 1;
|
|
if (g_pDmaStereoVagCmd) {
|
|
g_pDmaStereoVagCmd->safe_to_modify_dma = 1;
|
|
}
|
|
// and forget it!
|
|
g_pDmaVagCmd = nullptr;
|
|
g_pDmaStereoVagCmd = nullptr;
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr dma handling of VAG cmd is complete");
|
|
}
|
|
|
|
// release ref on this page. (interestingly, not a dma ref...)
|
|
if (userdata) {
|
|
CPage* page = (CPage*)userdata;
|
|
int ret = page->ReleaseRef();
|
|
ASSERT(ret >= 0);
|
|
}
|
|
|
|
// now - see if we have another queued dma transfer
|
|
ASSERT(g_nSpuDmaQueueCount >= 0);
|
|
if (g_nSpuDmaQueueCount == 0) {
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr dma queue is empty, disabling interrupt");
|
|
// we're done!
|
|
// set_dma_intr_handler_hack(channel, nullptr, nullptr);
|
|
uninstall_dma_intr();
|
|
// if (-1 < channel) {
|
|
// snd_FreeSPUDMA(channel);
|
|
// }
|
|
g_bSpuDmaBusy = false;
|
|
} else {
|
|
ovrld_log(LogCategory::SPU_DMA_STR,
|
|
"SPUDmaIntr dma queue is not empty, preparing to run {} ({} pending)",
|
|
g_nSpuDmaQueueHead, g_nSpuDmaQueueCount);
|
|
// nope, more dma to run
|
|
auto* next_xfer = &g_aSpuDmaQueue[g_nSpuDmaQueueHead];
|
|
|
|
// set up the next interrupt handler
|
|
set_dma_intr_handler_hack(channel, SPUDmaIntr, next_xfer->user_data);
|
|
|
|
// args for the dma transfer
|
|
int next_chan = channel;
|
|
int next_mode = 0;
|
|
const void* next_iop = next_xfer->iop_mem;
|
|
u32 next_spu = next_xfer->spu_addr;
|
|
u32 next_length = next_xfer->length;
|
|
|
|
// load up the commands to handle
|
|
g_pDmaVagCmd = next_xfer->command;
|
|
g_pDmaStereoVagCmd = nullptr;
|
|
if (g_pDmaVagCmd) {
|
|
g_pDmaStereoVagCmd = g_pDmaVagCmd->stereo_sibling;
|
|
}
|
|
g_nSpuDmaChunks = next_xfer->num_isobuffered_chunks;
|
|
|
|
// advance the queue!
|
|
g_nSpuDmaQueueCount = g_nSpuDmaQueueCount + -1;
|
|
g_nSpuDmaQueueHead = g_nSpuDmaQueueHead + 1;
|
|
if (0xf < g_nSpuDmaQueueHead) {
|
|
g_nSpuDmaQueueHead = 0;
|
|
}
|
|
|
|
// start the next one!
|
|
// set_dma_intr_handler_hack(g_nSpuDmaChannel, SPUDmaIntr, userdata);
|
|
voice_trans_wrapper(next_chan, next_mode, next_iop, next_spu, next_length);
|
|
}
|
|
|
|
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr exit - end of function");
|
|
return 0;
|
|
}
|
|
|
|
/*!
|
|
* Start DMA to EE.
|
|
*/
|
|
void DMA_SendToEE(void* ee_dest,
|
|
const void* iop_src,
|
|
u32 length,
|
|
void callback(void*),
|
|
void* callback_arg) {
|
|
ASSERT(iop_src);
|
|
ASSERT(ee_dest);
|
|
ASSERT(((uintptr_t)iop_src & 3) == 0);
|
|
ASSERT(((uintptr_t)ee_dest & 0xf) == 0);
|
|
ASSERT(length < 0xffff0);
|
|
sceSifDmaData cmd; // DMA settings
|
|
|
|
// setup command
|
|
cmd.mode = 0;
|
|
cmd.data = iop_src;
|
|
cmd.addr = ee_dest;
|
|
cmd.size = length;
|
|
|
|
// instant DMA
|
|
// ovrld_log(LogCategory::EE_DMA, "DMA_SendToEE: 0x{:x}, size {}", (u64)ee_dest, length);
|
|
sceSifSetDma(&cmd, 1);
|
|
|
|
// for now, we'll do the callback here, but I bet it will cause problems
|
|
if (callback) {
|
|
callback(callback_arg);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Start DMA transfer to SPU. Despite the name, this does not actually "sync" - the transfer will
|
|
* be ongoing. If there is an ongoing transfer when this is called, the transfer will be queued.
|
|
*/
|
|
int DMA_SendToSPUAndSync(const u8* iop_mem,
|
|
int length,
|
|
int spu_addr,
|
|
ISO_VAGCommand* cmd,
|
|
void* user_data) {
|
|
// CpuSuspendIntr(local_28);
|
|
int ret = 1;
|
|
bool defer = false;
|
|
|
|
ovrld_log(LogCategory::SPU_DMA_STR,
|
|
"DMA to SPU requested for {}, {} bytes to 0x{:x}, currently busy? {}",
|
|
cmd ? cmd->name : "NO-CMD", length, spu_addr, g_bSpuDmaBusy);
|
|
if (cmd) {
|
|
g_current_stream_name = cmd->name;
|
|
} else {
|
|
const static char* unknown = "unknown";
|
|
g_current_stream_name = unknown;
|
|
}
|
|
|
|
if (g_bSpuDmaBusy == 0) {
|
|
// not busy, we can actually start dma now.
|
|
g_nSpuDmaChannel = snd_GetFreeSPUDMA();
|
|
if (g_nSpuDmaChannel == -1) {
|
|
return 0;
|
|
}
|
|
// set globals for DMA processing
|
|
if (cmd) {
|
|
g_nSpuDmaChunks = cmd->num_isobuffered_chunks;
|
|
g_pDmaStereoVagCmd = cmd->stereo_sibling;
|
|
g_pDmaVagCmd = cmd;
|
|
}
|
|
|
|
} else {
|
|
// busy, need to queue the dma
|
|
ASSERT(g_nSpuDmaQueueCount <= (int)g_aSpuDmaQueue.size());
|
|
|
|
// set values:
|
|
g_aSpuDmaQueue[g_nSpuDmaQueueTail].length = length;
|
|
g_aSpuDmaQueue[g_nSpuDmaQueueTail].spu_addr = spu_addr;
|
|
g_aSpuDmaQueue[g_nSpuDmaQueueTail].user_data = user_data;
|
|
g_aSpuDmaQueue[g_nSpuDmaQueueTail].num_isobuffered_chunks =
|
|
cmd ? cmd->num_isobuffered_chunks : 0;
|
|
g_aSpuDmaQueue[g_nSpuDmaQueueTail].command = cmd;
|
|
g_aSpuDmaQueue[g_nSpuDmaQueueTail].iop_mem = iop_mem;
|
|
g_nSpuDmaQueueCount = g_nSpuDmaQueueCount + 1;
|
|
g_nSpuDmaQueueTail = g_nSpuDmaQueueTail + 1;
|
|
if (0xf < g_nSpuDmaQueueTail) {
|
|
g_nSpuDmaQueueTail = 0;
|
|
}
|
|
defer = true;
|
|
}
|
|
|
|
// set up the stereo command
|
|
if (cmd) {
|
|
cmd->safe_to_modify_dma = 0;
|
|
auto* stereo = cmd->stereo_sibling;
|
|
if (stereo) {
|
|
stereo->num_isobuffered_chunks = cmd->num_isobuffered_chunks;
|
|
stereo->dma_iop_mem_ptr = iop_mem + length;
|
|
cmd->dma_chan = g_nSpuDmaChannel;
|
|
stereo->xfer_size = length;
|
|
}
|
|
}
|
|
|
|
// Note on DMA interrupts.
|
|
// The DMA completion interrupt handler function may start more DMA transfers.
|
|
// If the second transfer's completion interrupt runs before the first transfer's completion
|
|
// interrupt returns, things break. This wasn't an issue on the real PS2 since the DMA takes
|
|
// longer. On PC, this means that we can't just call the completion handler from the DMA start
|
|
// function. Instead, put it at the end of this function.
|
|
|
|
// kick off dma, if we decided not to queue. This copies data immediately to the SPU buffer, but
|
|
// doesn't run the completion interrupt.
|
|
if (!defer) {
|
|
g_bSpuDmaBusy = true;
|
|
set_dma_intr_handler_hack(g_nSpuDmaChannel, SPUDmaIntr, user_data);
|
|
voice_trans_wrapper(g_nSpuDmaChannel, 0, iop_mem, spu_addr, length);
|
|
}
|
|
|
|
// run completion interrupts. the interrupt may start another DMA transfer, which should also
|
|
// finish here.
|
|
while (g_DmaInterruptHack.pending) {
|
|
complete_dma_now();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*!
|
|
* Run a dma transfer that was delayed or dropped.
|
|
*/
|
|
void RunDeferredVoiceTrans() {
|
|
// only if there's a currently happening transfer.
|
|
if (g_voiceTransRunning) {
|
|
if (GetSystemTimeLow() - g_voiceTransTime > 0x384000) {
|
|
ovrld_log(LogCategory::WARN, "DeferredVoiceTrans has detected hung dma... expect problems.");
|
|
// original game also check sceSdVoiceTransStatus here, we'll possibly need to mess with this
|
|
// if we delay dma completion interrupts...
|
|
g_voiceTransRunning = false;
|
|
voice_trans_wrapper(g_voiceTransChannel, g_voiceTransMode, g_voiceTransAddr,
|
|
g_voiceTransSpuAddr, g_voiceTransSize);
|
|
}
|
|
}
|
|
}
|
|
} // namespace jakx
|