#include "stdafx.h" #include "sys_interrupt.h" #include "Emu/IdManager.h" #include "Emu/System.h" #include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/PPUThread.h" #include "Emu/Cell/SPUThread.h" #include "Emu/Cell/PPUOpcodes.h" LOG_CHANNEL(sys_interrupt); lv2_int_tag::lv2_int_tag() noexcept : lv2_obj{1} , id(idm::last_id()) { } lv2_int_tag::lv2_int_tag(utils::serial& ar) noexcept : lv2_obj{1} , id(idm::last_id()) , handler([&]() { const u32 id = ar; auto ptr = idm::get_unlocked(id); if (!ptr && id) { Emu.DeferDeserialization([id, &handler = this->handler]() { handler = ensure(idm::get_unlocked(id)); }); } return ptr; }()) { } void lv2_int_tag::save(utils::serial& ar) { ar(lv2_obj::check(handler) ? handler->id : 0); } lv2_int_serv::lv2_int_serv(const std::shared_ptr>& thread, u64 arg1, u64 arg2) noexcept : lv2_obj{1} , id(idm::last_id()) , thread(thread) , arg1(arg1) , arg2(arg2) { } lv2_int_serv::lv2_int_serv(utils::serial& ar) noexcept : lv2_obj{1} , id(idm::last_id()) , thread(idm::get_unlocked>(ar)) , arg1(ar) , arg2(ar) { } void lv2_int_serv::save(utils::serial& ar) { ar(thread && idm::check_unlocked>(thread->id) ? thread->id : 0, arg1, arg2); } 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 ({ { ppu_cmd::reset_stack, 0 }, { ppu_cmd::set_args, 2 }, arg1, arg2, { ppu_cmd::entry_call, 0 }, { ppu_cmd::sleep, 0 }, { ppu_cmd::ptr_call, 0 }, std::bit_cast(&ppu_interrupt_thread_entry) }); } void ppu_thread_exit(ppu_thread&, ppu_opcode_t, be_t*, struct ppu_intrp_func*); void lv2_int_serv::join() const { thread->cmd_list ({ { ppu_cmd::ptr_call, 0 }, std::bit_cast(&ppu_thread_exit) }); thread->cmd_notify++; thread->cmd_notify.notify_one(); (*thread)(); idm::remove_verify>(thread->id, static_cast>>(thread)); } error_code sys_interrupt_tag_destroy(ppu_thread& ppu, u32 intrtag) { ppu.state += cpu_flag::wait; sys_interrupt.warning("sys_interrupt_tag_destroy(intrtag=0x%x)", intrtag); const auto tag = idm::withdraw(intrtag, [](lv2_int_tag& tag) -> CellError { if (lv2_obj::check(tag.handler)) { return CELL_EBUSY; } tag.exists.release(0); return {}; }); if (!tag) { return CELL_ESRCH; } if (tag.ret) { return tag.ret; } return CELL_OK; } error_code _sys_interrupt_thread_establish(ppu_thread& ppu, vm::ptr ih, u32 intrtag, u32 intrthread, u64 arg1, u64 arg2) { ppu.state += cpu_flag::wait; sys_interrupt.warning("_sys_interrupt_thread_establish(ih=*0x%x, intrtag=0x%x, intrthread=0x%x, arg1=0x%llx, arg2=0x%llx)", ih, intrtag, intrthread, arg1, arg2); CellError error = CELL_EAGAIN; const u32 id = idm::import([&]() { std::shared_ptr result; // Get interrupt tag const auto tag = idm::check_unlocked(intrtag); if (!tag) { error = CELL_ESRCH; return result; } // Get interrupt thread const auto it = idm::get_unlocked>(intrthread); if (!it) { error = CELL_ESRCH; return result; } // If interrupt thread is running, it's already established on another interrupt tag if (cpu_flag::stop - it->state) { error = CELL_EAGAIN; return result; } // It's unclear if multiple handlers can be established on single interrupt tag if (lv2_obj::check(tag->handler)) { error = CELL_ESTAT; return result; } result = std::make_shared(it, arg1, arg2); tag->handler = result; it->cmd_list ({ { ppu_cmd::ptr_call, 0 }, std::bit_cast(&ppu_interrupt_thread_entry) }); it->state -= cpu_flag::stop; it->state.notify_one(); return result; }); if (id) { ppu.check_state(); *ih = id; return CELL_OK; } return error; } error_code _sys_interrupt_thread_disestablish(ppu_thread& ppu, u32 ih, vm::ptr r13) { ppu.state += cpu_flag::wait; sys_interrupt.warning("_sys_interrupt_thread_disestablish(ih=0x%x, r13=*0x%x)", ih, r13); const auto handler = idm::withdraw(ih, [](lv2_obj& obj) { obj.exists.release(0); }); if (!handler) { if (const auto thread = idm::withdraw>(ih)) { *r13 = thread->gpr[13]; // It is detached from IDM now so join must be done explicitly now *thread = thread_state::finished; return CELL_OK; } return CELL_ESRCH; } lv2_obj::sleep(ppu); // Wait for sys_interrupt_thread_eoi() and destroy interrupt thread handler->join(); // Save TLS base *r13 = handler->thread->gpr[13]; return CELL_OK; } void sys_interrupt_thread_eoi(ppu_thread& ppu) { ppu.state += cpu_flag::wait; sys_interrupt.trace("sys_interrupt_thread_eoi()"); ppu.state += cpu_flag::ret; lv2_obj::sleep(ppu); ppu.interrupt_thread_executing = false; } 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) || ppu.cmd_notify.exchange(0)) { return; } thread_ctrl::wait_on(ppu.cmd_notify, 0); } }