From c0c52c33b963d0fb7c92cb9e9f6955eabbbda5ca Mon Sep 17 00:00:00 2001 From: Eladash Date: Thu, 23 Sep 2021 18:17:16 +0300 Subject: [PATCH] SPU: Implement interrupts handling for remaining events --- rpcs3/Emu/Cell/SPUInterpreter.cpp | 20 +++++++- rpcs3/Emu/Cell/SPUThread.cpp | 77 ++++++++++++++++++++++++++----- rpcs3/Emu/Cell/SPUThread.h | 7 +-- 3 files changed, 87 insertions(+), 17 deletions(-) diff --git a/rpcs3/Emu/Cell/SPUInterpreter.cpp b/rpcs3/Emu/Cell/SPUInterpreter.cpp index 045105d441..56cf5b4dbf 100644 --- a/rpcs3/Emu/Cell/SPUInterpreter.cpp +++ b/rpcs3/Emu/Cell/SPUInterpreter.cpp @@ -125,7 +125,13 @@ void spu_interpreter::set_interrupt_status(spu_thread& spu, spu_opcode_t op) bool spu_interpreter::STOP(spu_thread& spu, spu_opcode_t op) { - if (!spu.stop_and_signal(op.opcode & 0x3fff)) + const bool allow = std::exchange(spu.allow_interrupts_in_cpu_work, false); + + const bool advance_pc = spu.stop_and_signal(op.opcode & 0x3fff); + + spu.allow_interrupts_in_cpu_work = allow; + + if (!advance_pc) { return false; } @@ -166,8 +172,12 @@ bool spu_interpreter::MFSPR(spu_thread& spu, spu_opcode_t op) bool spu_interpreter::RDCH(spu_thread& spu, spu_opcode_t op) { + const bool allow = std::exchange(spu.allow_interrupts_in_cpu_work, false); + const s64 result = spu.get_ch_value(op.ra); + spu.allow_interrupts_in_cpu_work = allow; + if (result < 0) { return false; @@ -428,7 +438,13 @@ bool spu_interpreter::MTSPR(spu_thread&, spu_opcode_t) bool spu_interpreter::WRCH(spu_thread& spu, spu_opcode_t op) { - if (!spu.set_ch_value(op.ra, spu.gpr[op.rt]._u32[3])) + const bool allow = std::exchange(spu.allow_interrupts_in_cpu_work, false); + + const bool advance_pc = spu.set_ch_value(op.ra, spu.gpr[op.rt]._u32[3]); + + spu.allow_interrupts_in_cpu_work = allow; + + if (!advance_pc) { return false; } diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index 6eb538281f..e67464493a 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -1643,6 +1643,8 @@ void spu_thread::cpu_task() { ensure(spu_runtime::g_interpreter); + allow_interrupts_in_cpu_work = true; + while (true) { if (state) [[unlikely]] @@ -1653,6 +1655,8 @@ void spu_thread::cpu_task() spu_runtime::g_interpreter(*this, _ptr(0), nullptr); } + + allow_interrupts_in_cpu_work = false; } } @@ -1663,15 +1667,57 @@ void spu_thread::cpu_work() return; } + const u32 old_iter_count = cpu_work_iteration_count++; + const auto timeout = +g_cfg.core.mfc_transfers_timeout; - // If either MFC size exceeds limit or timeout has been reached execute pending MFC commands - if (mfc_size > g_cfg.core.mfc_transfers_shuffling || (timeout && get_system_time() - mfc_last_timestamp >= timeout)) + bool work_left = false; + + if (u32 shuffle_count = g_cfg.core.mfc_transfers_shuffling) { - do_mfc(false, false); + // If either MFC size exceeds limit or timeout has been reached execute pending MFC commands + if (mfc_size > shuffle_count || (timeout && get_system_time() - mfc_last_timestamp >= timeout)) + { + work_left = do_mfc(false, false); + } + else + { + work_left = mfc_size != 0; // TODO: Optimize + } } + bool gen_interrupt = false; + + // Check interrupts every 16 iterations + if (!(old_iter_count % 16) && allow_interrupts_in_cpu_work) + { + if (u32 mask = ch_events.load().mask & SPU_EVENT_INTR_BUSY_CHECK) + { + // LR check is expensive, do it once in a while + if (old_iter_count /*% 256*/) + { + mask &= ~SPU_EVENT_LR; + } + + get_events(mask); + } + + gen_interrupt = check_mfc_interrupts(pc); + work_left |= interrupts_enabled; + } + in_cpu_work = false; + + if (!work_left) + { + state -= cpu_flag::pending; + } + + if (gen_interrupt) + { + // Interrupt! escape everything and restart execution + spu_runtime::g_escape(this); + } } struct raw_spu_cleanup @@ -2967,7 +3013,7 @@ void spu_thread::do_putlluc(const spu_mfc_cmd& args) vm::reservation_notifier(addr).notify_all(-128); } -void spu_thread::do_mfc(bool can_escape, bool must_finish) +bool spu_thread::do_mfc(bool can_escape, bool must_finish) { u32 removed = 0; u32 barrier = 0; @@ -3119,15 +3165,11 @@ void spu_thread::do_mfc(bool can_escape, bool must_finish) // Exit early, not all pending commands have to be executed at a single iteration // Update last timestamp so the next MFC timeout check will use the current time mfc_last_timestamp = get_system_time(); - return; + return true; } } - if (state & cpu_flag::pending) - { - // No more pending work - state -= cpu_flag::pending; - } + return false; } bool spu_thread::check_mfc_interrupts(u32 next_pc) @@ -3752,9 +3794,20 @@ void spu_thread::set_interrupt_status(bool enable) if (enable) { // Detect enabling interrupts with events masked - if (auto mask = ch_events.load().mask; mask & ~SPU_EVENT_INTR_IMPLEMENTED) + if (auto mask = ch_events.load().mask; mask & SPU_EVENT_INTR_BUSY_CHECK) { - fmt::throw_exception("SPU Interrupts not implemented (mask=0x%x)", mask); + if (g_cfg.core.spu_decoder != spu_decoder_type::precise && g_cfg.core.spu_decoder != spu_decoder_type::fast) + { + fmt::throw_exception("SPU Interrupts not implemented (mask=0x%x): Use interpreterts", mask); + } + + spu_log.trace("SPU Interrupts (mask=0x%x) are using CPU busy checking mode", mask); + + // Process interrupts in cpu_work() + if (state.none_of(cpu_flag::pending)) + { + state += cpu_flag::pending; + } } } diff --git a/rpcs3/Emu/Cell/SPUThread.h b/rpcs3/Emu/Cell/SPUThread.h index 9ee096fe6b..0e1f47e2be 100644 --- a/rpcs3/Emu/Cell/SPUThread.h +++ b/rpcs3/Emu/Cell/SPUThread.h @@ -82,8 +82,7 @@ enum : u32 SPU_EVENT_IMPLEMENTED = SPU_EVENT_LR | SPU_EVENT_TM | SPU_EVENT_SN | SPU_EVENT_S1 | SPU_EVENT_S2, // Mask of implemented events SPU_EVENT_INTR_IMPLEMENTED = SPU_EVENT_SN, - - SPU_EVENT_INTR_TEST = SPU_EVENT_INTR_IMPLEMENTED, + SPU_EVENT_INTR_BUSY_CHECK = SPU_EVENT_IMPLEMENTED & ~SPU_EVENT_INTR_IMPLEMENTED, }; // SPU Class 0 Interrupts @@ -779,6 +778,8 @@ public: static constexpr u32 max_mfc_dump_idx = 2048; bool in_cpu_work = false; + bool allow_interrupts_in_cpu_work = false; + u8 cpu_work_iteration_count = 0; std::array stack_mirror; // Return address information @@ -793,7 +794,7 @@ public: bool do_list_transfer(spu_mfc_cmd& args); void do_putlluc(const spu_mfc_cmd& args); bool do_putllc(const spu_mfc_cmd& args); - void do_mfc(bool can_escape = true, bool must_finish = true); + bool do_mfc(bool can_escape = true, bool must_finish = true); u32 get_mfc_completed() const; bool process_mfc_cmd();