diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index bca2bc9afb..a82356f8e0 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -1355,6 +1355,8 @@ ppu_thread::~ppu_thread() perf_log.notice("Perf stats for instructions: total %u", exec_bytes / 4); } +void ppu_interrupt_thread_entry(ppu_thread&, ppu_opcode_t, be_t*, struct ppu_intrp_func*); + ppu_thread::ppu_thread(const ppu_thread_params& param, std::string_view name, u32 prio, int detached) : cpu_thread(idm::last_id()) , prio(prio) @@ -1375,6 +1377,14 @@ ppu_thread::ppu_thread(const ppu_thread_params& param, std::string_view name, u3 gpr[3] = param.arg0; gpr[4] = param.arg1; } + else + { + cmd_list + ({ + { ppu_cmd::ptr_call, 0 }, + std::bit_cast(&ppu_interrupt_thread_entry) + }); + } // Trigger the scheduler state += cpu_flag::suspend; diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index 4fd15a190c..e996419a5c 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -985,7 +985,7 @@ void spu_int_ctrl_t::set(u64 ints) if (auto handler = tag->handler; lv2_obj::check(handler)) { rlock.unlock(); - handler->exec(); + thread_ctrl::notify(*handler->thread); } } } diff --git a/rpcs3/Emu/Cell/SPUThread.h b/rpcs3/Emu/Cell/SPUThread.h index 28869973fe..1cc9b6a9d6 100644 --- a/rpcs3/Emu/Cell/SPUThread.h +++ b/rpcs3/Emu/Cell/SPUThread.h @@ -171,6 +171,9 @@ struct spu_channel // Low 32 bits contain value atomic_t data; + // Pending value to be inserted when it is possible at pop() + atomic_t jostling_value; + public: static constexpr u32 off_wait = 32; static constexpr u32 off_count = 63; @@ -258,12 +261,25 @@ public: } // Pop unconditionally (loading last value), may require notification + // If the SPU tries to insert a value, do it instead the SPU u32 pop() { // Value is not cleared and may be read again - const u64 old = data.fetch_and(~(bit_count | bit_wait)); + constexpr u64 mask = bit_count | bit_wait; - if (old & bit_wait) + const u64 old = data.fetch_op([&](u64& data) + { + if ((data & mask) == mask) + { + // Insert the pending value, leave no time in which the channel has no data + data = bit_count | jostling_value; + return; + } + + data &= ~mask; + }); + + if ((old & mask) == mask) { data.notify_one(); } @@ -300,6 +316,11 @@ public: if (spu.is_stopped()) { + if (u64 old2 = data.exchange(0); old2 & bit_count) + { + return static_cast(old2); + } + return -1; } @@ -310,40 +331,48 @@ public: // Waiting for channel push state availability, actually pushing if specified bool push_wait(cpu_thread& spu, u32 value, bool push = true) { + u64 state; + data.fetch_op([&](u64& data) + { + if (data & bit_count) [[unlikely]] + { + jostling_value.release(push ? value : static_cast(data)); + data |= bit_wait; + } + else if (push) + { + data = bit_count | value; + } + else + { + state = data; + return false; + } + + state = data; + return true; + }); + while (true) { - u64 state; - data.fetch_op([&](u64& data) - { - if (data & bit_count) [[unlikely]] - { - data |= bit_wait; - } - else if (push) - { - data = bit_count | value; - } - else - { - state = data; - return false; - } - - state = data; - return true; - }); - if (!(state & bit_wait)) { + if (!push) + { + data &= ~bit_count; + } + return true; } if (spu.is_stopped()) { + data &= ~bit_wait; return false; } thread_ctrl::wait_on(data, state); + state = data; } } diff --git a/rpcs3/Emu/Cell/lv2/sys_interrupt.cpp b/rpcs3/Emu/Cell/lv2/sys_interrupt.cpp index 36ed8e517d..d9c0801423 100644 --- a/rpcs3/Emu/Cell/lv2/sys_interrupt.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_interrupt.cpp @@ -5,6 +5,7 @@ #include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/PPUThread.h" +#include "Emu/Cell/SPUThread.h" #include "Emu/Cell/PPUOpcodes.h" LOG_CHANNEL(sys_interrupt); @@ -24,6 +25,8 @@ lv2_int_serv::lv2_int_serv(const std::shared_ptr>& thre exists.release(1); } +void ppu_interrupt_thread_entry(ppu_thread&, ppu_opcode_t, be_t*, struct ppu_intrp_func*); + void lv2_int_serv::exec() const { thread->cmd_list @@ -31,14 +34,16 @@ void lv2_int_serv::exec() const { ppu_cmd::reset_stack, 0 }, { ppu_cmd::set_args, 2 }, arg1, arg2, { ppu_cmd::opd_call, 0 }, thread->entry_func, - { ppu_cmd::sleep, 0 } + { ppu_cmd::sleep, 0 }, + { ppu_cmd::ptr_call, 0 }, + std::bit_cast(&ppu_interrupt_thread_entry) }); thread->cmd_notify++; thread->cmd_notify.notify_one(); } -bool ppu_thread_exit(ppu_thread& ppu); +void ppu_thread_exit(ppu_thread&, ppu_opcode_t, be_t*, struct ppu_intrp_func*); void lv2_int_serv::join() const { @@ -185,4 +190,61 @@ void sys_interrupt_thread_eoi(ppu_thread& ppu) sys_interrupt.trace("sys_interrupt_thread_eoi()"); ppu.state += cpu_flag::ret; + + lv2_obj::sleep(ppu); +} + +void ppu_interrupt_thread_entry(ppu_thread& ppu, ppu_opcode_t, be_t*, struct ppu_intrp_func*) +{ + while (true) + { + std::shared_ptr serv = nullptr; + + // Loop endlessly trying to invoke an interrupt if required + idm::select>([&](u32, spu_thread& spu) + { + if (spu.get_type() != spu_type::threaded) + { + auto& ctrl = spu.int_ctrl[2]; + + if (lv2_obj::check(ctrl.tag)) + { + auto& handler = ctrl.tag->handler; + + if (lv2_obj::check(handler)) + { + if (handler->thread.get() == &ppu) + { + if (spu.ch_out_intr_mbox.get_count() && ctrl.mask & SPU_INT2_STAT_MAILBOX_INT) + { + ctrl.stat |= SPU_INT2_STAT_MAILBOX_INT; + } + + if (ctrl.mask & ctrl.stat) + { + ensure(!serv); + serv = handler; + } + } + } + } + } + }); + + if (serv) + { + // Queue interrupt, after the interrupt has finished the PPU returns to this loop + serv->exec(); + return; + } + + const auto state = +ppu.state; + + if (::is_stopped(state)) + { + return; + } + + thread_ctrl::wait_on(ppu.state, state); + } } diff --git a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp index 938ea8fb58..0473fb484a 100644 --- a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp @@ -8,6 +8,7 @@ #include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/PPUThread.h" #include "Emu/Cell/PPUCallback.h" +#include "Emu/Cell/PPUOpcodes.h" #include "Emu/Memory/vm_locking.h" #include "sys_event.h" #include "sys_process.h" @@ -35,7 +36,7 @@ struct ppu_thread_cleaner ppu_thread_cleaner& operator=(const ppu_thread_cleaner&) = delete; }; -bool ppu_thread_exit(ppu_thread& ppu) +void ppu_thread_exit(ppu_thread& ppu, ppu_opcode_t, be_t*, struct ppu_intrp_func*) { ppu.state += cpu_flag::exit + cpu_flag::wait; @@ -53,8 +54,6 @@ bool ppu_thread_exit(ppu_thread& ppu) ppu.call_history.index = 0; ppu_log.notice("Calling history: %s", str); } - - return false; } void _sys_ppu_thread_exit(ppu_thread& ppu, u64 errorcode) @@ -117,7 +116,7 @@ void _sys_ppu_thread_exit(ppu_thread& ppu, u64 errorcode) thread_ctrl::wait_on(ppu.joiner, ppu_join_status::zombie); } - ppu_thread_exit(ppu); + ppu_thread_exit(ppu, {}, nullptr, nullptr); } s32 sys_ppu_thread_yield(ppu_thread& ppu)