From d9188f12450529f88714bdd40ab1507b22f46c56 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Thu, 20 Nov 2014 05:11:47 +0100 Subject: [PATCH] Zelda HLE: Rewrite the control flow. Now accurate control flow for DAC. Voice rendering is not implemented yet, but framing seems correct. Not expected to work for anything than DAC UCode games. --- Source/Core/Core/HW/DSPHLE/UCodes/Zelda.cpp | 375 +++++++++++--------- Source/Core/Core/HW/DSPHLE/UCodes/Zelda.h | 101 ++++-- 2 files changed, 292 insertions(+), 184 deletions(-) diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/Zelda.cpp b/Source/Core/Core/HW/DSPHLE/UCodes/Zelda.cpp index 07afb830f7..a3607360eb 100644 --- a/Source/Core/Core/HW/DSPHLE/UCodes/Zelda.cpp +++ b/Source/Core/Core/HW/DSPHLE/UCodes/Zelda.cpp @@ -2,44 +2,30 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -// Games that uses this UCode (exhaustive list): +// Games that use this UCode (exhaustive list): // * Animal Crossing (type ????, CRC ????) // * Donkey Kong Jungle Beat (type ????, CRC ????) // * IPL (type ????, CRC ????) // * Luigi's Mansion (type ????, CRC ????) -// * Mario Kary: Double Dash!! (type ????, CRC ????) +// * Mario Kart: Double Dash!! (type ????, CRC ????) // * Pikmin (type ????, CRC ????) // * Pikmin 2 (type ????, CRC ????) // * Super Mario Galaxy (type ????, CRC ????) // * Super Mario Galaxy 2 (type ????, CRC ????) // * Super Mario Sunshine (type ????, CRC ????) // * The Legend of Zelda: Four Swords Adventures (type ????, CRC ????) -// * The Legend of Zelda: The Wind Waker (type Normal, CRC 86840740) +// * The Legend of Zelda: The Wind Waker (type DAC, CRC 86840740) // * The Legend of Zelda: Twilight Princess (type ????, CRC ????) #include "Core/ConfigManager.h" -#include "Core/HW/DSP.h" #include "Core/HW/DSPHLE/MailHandler.h" #include "Core/HW/DSPHLE/UCodes/UCodes.h" #include "Core/HW/DSPHLE/UCodes/Zelda.h" ZeldaUCode::ZeldaUCode(DSPHLE *dsphle, u32 crc) - : UCodeInterface(dsphle, crc), - m_sync_in_progress(false), - m_max_voice(0), - m_num_sync_mail(0), - m_num_voices(0), - m_sync_cmd_pending(false), - m_current_voice(0), - m_current_buffer(0), - m_num_buffers(0), - m_num_steps(0), - m_list_in_progress(false), - m_step(0), - m_read_offset(0) + : UCodeInterface(dsphle, crc) { - m_mail_handler.PushMail(DSP_INIT); - DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP); + m_mail_handler.PushMail(DSP_INIT, true); m_mail_handler.PushMail(0xF3551111); // handshake } @@ -52,11 +38,39 @@ void ZeldaUCode::Update() { if (NeedsResumeMail()) { - m_mail_handler.PushMail(DSP_RESUME); - DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP); + m_mail_handler.PushMail(DSP_RESUME, true); } } +u32 ZeldaUCode::GetUpdateMs() +{ + return SConfig::GetInstance().bWii ? 3 : 5; +} + +void ZeldaUCode::DoState(PointerWrap &p) +{ + p.Do(m_mail_current_state); + p.Do(m_mail_expected_cmd_mails); + + p.Do(m_sync_max_voice_id); + p.Do(m_sync_flags); + + p.Do(m_cmd_buffer); + p.Do(m_read_offset); + p.Do(m_write_offset); + p.Do(m_pending_commands_count); + p.Do(m_cmd_can_execute); + + p.Do(m_rendering_requested_frames); + p.Do(m_rendering_voices_per_frame); + p.Do(m_rendering_mram_lbuf_addr); + p.Do(m_rendering_mram_rbuf_addr); + p.Do(m_rendering_curr_frame); + p.Do(m_rendering_curr_voice); + + DoStateShared(p); +} + void ZeldaUCode::HandleMail(u32 mail) { if (m_upload_setup_in_progress) // evaluated first! @@ -65,181 +79,218 @@ void ZeldaUCode::HandleMail(u32 mail) return; } - if (m_sync_in_progress) + switch (m_mail_current_state) { - if (m_sync_cmd_pending) + case MailState::WAITING: + if (mail & 0x80000000) { - u32 n = (mail >> 16) & 0xF; - m_max_voice = (n + 1) << 4; - m_sync_flags[n] = mail & 0xFFFF; - m_sync_in_progress = false; - - m_current_voice = m_max_voice; - - if (m_current_voice >= m_num_voices) + if ((mail >> 16) != 0xCDD1) { - // TODO(delroth): Mix audio. + ERROR_LOG(DSPHLE, "Rendering end mail without prefix CDD1: %08x", + mail); + } - m_current_buffer++; + switch (mail & 0xFFFF) + { + case 1: + NOTICE_LOG(DSPHLE, "UCode being replaced."); + m_upload_setup_in_progress = true; + SetMailState(MailState::HALTED); + break; - m_mail_handler.PushMail(DSP_SYNC); - DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP); - m_mail_handler.PushMail(0xF355FF00 | m_current_buffer); + case 2: + NOTICE_LOG(DSPHLE, "UCode being rebooted to ROM."); + SetMailState(MailState::HALTED); + m_dsphle->SetUCode(UCODE_ROM); + break; - m_current_voice = 0; + case 3: + m_cmd_can_execute = true; + RunPendingCommands(); + break; - if (m_current_buffer == m_num_buffers) - { - m_mail_handler.PushMail(DSP_FRAME_END); - m_sync_cmd_pending = false; - } + default: + NOTICE_LOG(DSPHLE, "Unknown end rendering action. Halting."); + case 0: + NOTICE_LOG(DSPHLE, "UCode asked to halt. Stopping any processing."); + SetMailState(MailState::HALTED); + break; + } + } + else if (!(mail & 0xFFFF)) + { + if (RenderingInProgress()) + { + SetMailState(MailState::RENDERING); + } + else + { + NOTICE_LOG(DSPHLE, "Sync mail (%08x) received when rendering was not active. Halting.", + mail); + SetMailState(MailState::HALTED); } } else { - m_sync_in_progress = false; + SetMailState(MailState::WRITING_CMD); + m_mail_expected_cmd_mails = mail & 0xFFFF; } + break; - return; - } + case MailState::RENDERING: + m_sync_max_voice_id = (((mail >> 16) & 0xF) + 1) << 4; + m_sync_flags[(mail >> 16) & 0xFF] = mail & 0xFFFF; - if (m_list_in_progress) - { - if (m_step >= sizeof(m_buffer) / 4) - PanicAlert("m_step out of range"); + RenderAudio(); + SetMailState(MailState::WAITING); + break; - ((u32*)m_buffer)[m_step] = mail; - m_step++; + case MailState::WRITING_CMD: + Write32(mail); - if (m_step >= m_num_steps) + if (--m_mail_expected_cmd_mails == 0) { - ExecuteList(); - m_list_in_progress = false; + m_pending_commands_count += 1; + SetMailState(MailState::WAITING); + RunPendingCommands(); } + break; - return; - } - - // Here holds: m_sync_in_progress == false && m_list_in_progress == false - - // Zelda-only mails: - // - 0000XXXX - Begin list - // - 00000000, 000X0000 - Sync mails - // - CDD1XXXX - comes after DsyncFrame completed, seems to be debugging stuff - - if (mail == 0) - { - m_sync_in_progress = true; - } - else if ((mail >> 16) == 0) - { - m_list_in_progress = true; - m_num_steps = mail; - m_step = 0; - } - else if ((mail >> 16) == 0xCDD1) // A 0xCDD1000X mail should come right after we send a DSP_FRAME_END mail - { - // The low part of the mail tells the operation to perform - // Seeing as every possible operation number halts the uCode, - // except 3, that thing seems to be intended for debugging - switch (mail & 0xFFFF) - { - case 0x0003: // Do nothing - continue normally - return; - - case 0x0001: // accepts params to either DMA to iram and/or DRAM (used for hotbooting a new ucode) - // TODO find a better way to protect from HLEMixer? - m_upload_setup_in_progress = true; - return; - - case 0x0002: // Let IROM play us off - m_dsphle->SetUCode(UCODE_ROM); - return; - - case 0x0000: // Halt - WARN_LOG(DSPHLE, "Zelda uCode: received halting operation %04X", mail & 0xFFFF); - return; - - default: // Invalid (the real ucode would likely crash) - WARN_LOG(DSPHLE, "Zelda uCode: received invalid operation %04X", mail & 0xFFFF); - return; - } - } - else - { - WARN_LOG(DSPHLE, "Zelda uCode: unknown mail %08X", mail); + case MailState::HALTED: + WARN_LOG(DSPHLE, "Received mail %08x while we're halted.", mail); + break; } } -void ZeldaUCode::ExecuteList() +void ZeldaUCode::RunPendingCommands() { - m_read_offset = 0; - - u32 cmd_mail = Read32(); - u32 command = (cmd_mail >> 24) & 0x7f; - u32 sync; - u32 extra_data = cmd_mail & 0xFFFF; - - sync = cmd_mail >> 16; - - switch (command) + if (RenderingInProgress() || !m_cmd_can_execute) { - case 0x00: break; + // No commands can be ran while audio rendering is in progress or + // waiting for an ACK. + return; + } - case 0x01: - Read32(); Read32(); Read32(); Read32(); + while (m_pending_commands_count) + { + m_pending_commands_count--; + + u32 cmd_mail = Read32(); + u32 command = (cmd_mail >> 24) & 0x7f; + u32 sync = cmd_mail >> 16; + u32 extra_data = cmd_mail & 0xFFFF; + + switch (command) + { + case 0x00: + case 0x03: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0E: + case 0x0F: + // NOP commands. Log anyway in case we encounter a new version + // where these are not NOPs anymore. + NOTICE_LOG(DSPHLE, "Received a NOP command: %d", command); + SendCommandAck(CommandAck::STANDARD, sync); break; - case 0x02: - Read32(); Read32(); + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + // Commands that crash the DAC UCode. Log and enter HALTED mode. + NOTICE_LOG(DSPHLE, "Received a crashy command: %d", command); + SetMailState(MailState::HALTED); return; - case 0x03: break; - - case 0x0d: - Read32(); + // Command 01: TODO: find a name and implement. + case 0x01: + WARN_LOG(DSPHLE, "CMD01: %08x %08x %08x %08x", + Read32(), Read32(), Read32(), Read32()); + m_rendering_voices_per_frame = extra_data; + SendCommandAck(CommandAck::STANDARD, sync); break; - case 0x0e: - Read32(); + // Command 02: starts audio processing. NOTE: this handler uses return, + // not break. This is because it hijacks the mail control flow and + // stops processing of further commands until audio processing is done. + case 0x02: + m_rendering_requested_frames = (cmd_mail >> 16) & 0xFF; + m_rendering_mram_lbuf_addr = Read32(); + m_rendering_mram_rbuf_addr = Read32(); + + m_rendering_curr_frame = 0; + m_rendering_curr_voice = 0; + RenderAudio(); + return; + + // Command 0D: TODO: find a name and implement. + case 0x0D: + WARN_LOG(DSPHLE, "CMD0D: %08x", Read32()); + SendCommandAck(CommandAck::STANDARD, sync); break; default: - PanicAlert("Zelda UCode - unknown command: %x (size %i)", command, m_num_steps); - break; + NOTICE_LOG(DSPHLE, "Received a non-existing command (%d), halting.", command); + SetMailState(MailState::HALTED); + return; + } + } +} + +void ZeldaUCode::SendCommandAck(CommandAck ack_type, u16 sync_value) +{ + u32 ack_mail; + switch (ack_type) + { + case CommandAck::STANDARD: ack_mail = DSP_SYNC; break; + case CommandAck::DONE_RENDERING: ack_mail = DSP_FRAME_END; break; + } + m_mail_handler.PushMail(ack_mail, true); + + if (ack_type == CommandAck::STANDARD) + { + m_mail_handler.PushMail(0xF3550000 | sync_value); + } +} + +void ZeldaUCode::RenderAudio() +{ + WARN_LOG(DSPHLE, "RenderAudio() frame %d/%d voice %d/%d (sync to %d)", + m_rendering_curr_frame, m_rendering_requested_frames, + m_rendering_curr_voice, m_rendering_voices_per_frame, + m_sync_max_voice_id); + + if (!RenderingInProgress()) + { + WARN_LOG(DSPHLE, "Trying to render audio while no rendering should be happening."); + return; } - m_mail_handler.PushMail(DSP_SYNC); - DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP); - m_mail_handler.PushMail(0xF3550000 | sync); -} - -u32 ZeldaUCode::GetUpdateMs() -{ - return SConfig::GetInstance().bWii ? 3 : 5; -} - -void ZeldaUCode::DoState(PointerWrap &p) -{ - p.Do(m_sync_in_progress); - p.Do(m_max_voice); - p.Do(m_sync_flags); - - p.Do(m_num_sync_mail); - - p.Do(m_num_voices); - - p.Do(m_sync_cmd_pending); - p.Do(m_current_voice); - p.Do(m_current_buffer); - p.Do(m_num_buffers); - - p.Do(m_num_steps); - p.Do(m_list_in_progress); - p.Do(m_step); - p.Do(m_buffer); - - p.Do(m_read_offset); - - DoStateShared(p); + while (m_rendering_curr_frame < m_rendering_requested_frames) + { + while (m_rendering_curr_voice < m_rendering_voices_per_frame) + { + // If we are not meant to render this voice yet, go back to message + // processing. + if (m_rendering_curr_voice >= m_sync_max_voice_id) + return; + + // TODO(delroth): render. + + m_rendering_curr_voice++; + } + + SendCommandAck(CommandAck::STANDARD, 0xFF00 | m_rendering_curr_frame); + + m_rendering_curr_voice = 0; + m_sync_max_voice_id = 0; + m_rendering_curr_frame++; + } + + SendCommandAck(CommandAck::DONE_RENDERING, 0); + m_cmd_can_execute = false; // Block command execution until ACK is received. } diff --git a/Source/Core/Core/HW/DSPHLE/UCodes/Zelda.h b/Source/Core/Core/HW/DSPHLE/UCodes/Zelda.h index c812f2b324..566b9a4915 100644 --- a/Source/Core/Core/HW/DSPHLE/UCodes/Zelda.h +++ b/Source/Core/Core/HW/DSPHLE/UCodes/Zelda.h @@ -19,35 +19,92 @@ public: void DoState(PointerWrap &p) override; +private: + // UCode state machine. The control flow in the Zelda UCode family is quite + // complex, using interrupt handlers heavily to handle incoming messages + // which, depending on the type, get handled immediately or are queued in a + // command buffer. In this implementation, the synchronous+interrupts flow + // of the original DSP implementation is rewritten in an asynchronous/coro + // + state machine style. It is less readable, but the best we can do given + // our constraints. + enum class MailState : u32 + { + WAITING, + RENDERING, + WRITING_CMD, + HALTED, + }; + MailState m_mail_current_state = MailState::WAITING; + u32 m_mail_expected_cmd_mails = 0; + + // Utility function to set the current state. Useful for debugging and + // logging as a hook point. + void SetMailState(MailState new_state) + { + // WARN_LOG(DSPHLE, "MailState %d -> %d", m_mail_current_state, new_state); + m_mail_current_state = new_state; + } + + // Voice synchronization / audio rendering flow control. When rendering an + // audio frame, only voices up to max_voice_id will be rendered until a + // sync mail arrives, increasing the value of max_voice_id. Additionally, + // these sync mails contain 16 bit values that are used for TODO. + u32 m_sync_max_voice_id = 0; + std::array m_sync_flags; + + // Command buffer (circular queue with r/w indices). Filled by HandleMail + // when the state machine is in WRITING_CMD state. Commands get executed + // when entering WAITING state and we are not rendering audio. + std::array m_cmd_buffer; + u32 m_read_offset = 0; + u32 m_write_offset = 0; + u32 m_pending_commands_count = 0; + bool m_cmd_can_execute = true; + + // Reads a 32 bit value from the command buffer. Advances the read pointer. u32 Read32() { - u32 res = *(u32*)&m_buffer[m_read_offset]; - m_read_offset += 4; + if (m_read_offset == m_write_offset) + { + ERROR_LOG(DSPHLE, "Reading too many command params"); + return 0; + } + + u32 res = m_cmd_buffer[m_read_offset]; + m_read_offset = (m_read_offset + 1) % (sizeof (m_cmd_buffer) / sizeof (u32)); return res; } -private: - bool m_sync_in_progress; - u32 m_max_voice; - u32 m_sync_flags[16]; + // Writes a 32 bit value to the command buffer. Advances the write pointer. + void Write32(u32 val) + { + m_cmd_buffer[m_write_offset] = val; + m_write_offset = (m_write_offset + 1) % (sizeof (m_cmd_buffer) / sizeof (u32)); + } - // Used by SMS version - u32 m_num_sync_mail; + // Tries to run as many commands as possible until either the command + // buffer is empty (pending_commands == 0) or we reached a long lived + // command that needs to hijack the mail control flow. + // + // Might change the current state to indicate crashy commands. + void RunPendingCommands(); - u32 m_num_voices; + // Sends the two mails from DSP to CPU to ack the command execution. + enum class CommandAck : u32 + { + STANDARD, + DONE_RENDERING, + }; + void SendCommandAck(CommandAck ack_type, u16 sync_value); - bool m_sync_cmd_pending; - u32 m_current_voice; - u32 m_current_buffer; - u32 m_num_buffers; + // Audio rendering state. + u32 m_rendering_requested_frames = 0; + u16 m_rendering_voices_per_frame = 0; + u32 m_rendering_mram_lbuf_addr = 0; + u32 m_rendering_mram_rbuf_addr = 0; + u32 m_rendering_curr_frame = 0; + u32 m_rendering_curr_voice = 0; - // List, buffer management ===================== - u32 m_num_steps; - bool m_list_in_progress; - u32 m_step; - u8 m_buffer[1024]; - - u32 m_read_offset; - - void ExecuteList(); + bool RenderingInProgress() const { return m_rendering_curr_frame != m_rendering_requested_frames; } + void RenderAudio(); };