mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-07-02 13:01:27 +12:00
Replaces `std::shared_pointer` with `stx::atomic_ptr` and `stx::shared_ptr`. Notes to programmers: * This pr kills the use of `dynamic_cast`, `std::dynamic_pointer_cast` and `std::weak_ptr` on IDM objects, possible replacement is to save the object ID on the base object, then use idm::check/get_unlocked to the destination type via the saved ID which may be null. Null pointer check is how you can tell type mismatch (as dynamic cast) or object destruction (as weak_ptr locking). * Double-inheritance on IDM objects should be used with care, `stx::shared_ptr` does not support constant-evaluated pointer offsetting to parent/child type. * `idm::check/get_unlocked` can now be used anywhere. Misc fixes: * Fixes some segfaults with RPCN with interaction with IDM. * Fix deadlocks in access violation handler due locking recursion. * Fixes race condition in process exit-spawn on memory containers read. * Fix bug that theoretically can prevent RPCS3 from booting - fix `id_manager::typeinfo` comparison to compare members instead of `memcmp` which can fail spuriously on padding bytes. * Ensure all IDM inherited types of base, either has `id_base` or `id_type` defined locally, this allows to make getters such as `idm::get_unlocked<lv2_socket, lv2_socket_raw>()` which were broken before. (requires save-states invalidation) * Removes broken operator[] overload of `stx::shared_ptr` and `stx::single_ptr` for non-array types.
303 lines
6 KiB
C++
303 lines
6 KiB
C++
#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<lv2_obj, lv2_int_serv>(id);
|
|
|
|
if (!ptr && id)
|
|
{
|
|
Emu.PostponeInitCode([id, &handler = this->handler]()
|
|
{
|
|
handler = ensure(idm::get_unlocked<lv2_obj, lv2_int_serv>(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(shared_ptr<named_thread<ppu_thread>> 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<named_thread<ppu_thread>>(ar))
|
|
, arg1(ar)
|
|
, arg2(ar)
|
|
{
|
|
}
|
|
|
|
void lv2_int_serv::save(utils::serial& ar)
|
|
{
|
|
ar(thread && idm::check_unlocked<named_thread<ppu_thread>>(thread->id) ? thread->id : 0, arg1, arg2);
|
|
}
|
|
|
|
void ppu_interrupt_thread_entry(ppu_thread&, ppu_opcode_t, be_t<u32>*, 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<u64>(&ppu_interrupt_thread_entry)
|
|
});
|
|
}
|
|
|
|
void ppu_thread_exit(ppu_thread&, ppu_opcode_t, be_t<u32>*, struct ppu_intrp_func*);
|
|
|
|
void lv2_int_serv::join() const
|
|
{
|
|
thread->cmd_list
|
|
({
|
|
{ ppu_cmd::ptr_call, 0 },
|
|
std::bit_cast<u64>(&ppu_thread_exit)
|
|
});
|
|
|
|
thread->cmd_notify.store(1);
|
|
thread->cmd_notify.notify_one();
|
|
(*thread)();
|
|
|
|
idm::remove_verify<named_thread<ppu_thread>>(thread->id, 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<lv2_obj, lv2_int_tag>(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<u32> 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<lv2_obj, lv2_int_serv>([&]()
|
|
{
|
|
shared_ptr<lv2_int_serv> result;
|
|
|
|
// Get interrupt tag
|
|
const auto tag = idm::check_unlocked<lv2_obj, lv2_int_tag>(intrtag);
|
|
|
|
if (!tag)
|
|
{
|
|
error = CELL_ESRCH;
|
|
return result;
|
|
}
|
|
|
|
// Get interrupt thread
|
|
const auto it = idm::get_unlocked<named_thread<ppu_thread>>(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 = make_shared<lv2_int_serv>(it, arg1, arg2);
|
|
tag->handler = result;
|
|
|
|
it->cmd_list
|
|
({
|
|
{ ppu_cmd::ptr_call, 0 },
|
|
std::bit_cast<u64>(&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<u64> 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<lv2_obj, lv2_int_serv>(ih, [](lv2_obj& obj)
|
|
{
|
|
obj.exists.release(0);
|
|
});
|
|
|
|
if (!handler)
|
|
{
|
|
if (const auto thread = idm::withdraw<named_thread<ppu_thread>>(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<u32>*, struct ppu_intrp_func*)
|
|
{
|
|
while (true)
|
|
{
|
|
shared_ptr<lv2_int_serv> serv = null_ptr;
|
|
|
|
// Loop endlessly trying to invoke an interrupt if required
|
|
idm::select<named_thread<spu_thread>>([&](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);
|
|
}
|
|
}
|