error_code sys_ppu_thread_start(ppu_thread& ppu, u32 thread_id) { sys_ppu_thread.trace("sys_ppu_thread_start(thread_id=0x%x)", thread_id); const auto thread = idm::get<named_thread<ppu_thread>>(thread_id, [&](ppu_thread& thread) { lv2_obj::awake(thread, -2); }); if (!thread) { return CELL_ESRCH; } if (!thread->state.test_and_reset(cpu_flag::stop)) { // TODO: what happens there? return CELL_EPERM; } else { thread_ctrl::notify(*thread); // Dirty hack for sound: confirm the creation of _mxr000 event queue if (thread->ppu_name.get() == "_cellsurMixerMain"sv) { lv2_obj::sleep(ppu); while (!idm::select<lv2_obj, lv2_event_queue>([](u32, lv2_event_queue& eq) { //some games do not set event queue name, though key seems constant for them return (eq.name == "_mxr000\0"_u64) || (eq.key == 0x8000cafe02460300); })) { if (ppu.is_stopped()) { return 0; } thread_ctrl::wait_for(50000); } if (ppu.test_stopped()) { return 0; } } } return CELL_OK; }
void sys_interrupt_thread_eoi(ppu_thread& ppu) // Low-level PPU function example { // Low-level function body must guard all C++-ish calls and all objects with non-trivial destructors thread_guard{ppu}, sys_interrupt.trace("sys_interrupt_thread_eoi()"); ppu.state += cpu_flag::ret; // Throw if this syscall was not called directly by the SC instruction (hack) if (ppu.lr == 0 || ppu.gpr[11] != 88) { // Low-level function must disable interrupts before throwing (not related to sys_interrupt_*, it's rather coincidence) ppu->interrupt_disable(); throw cpu_flag::ret; } }
error_code cellAudioQuit(ppu_thread& ppu) { cellAudio.warning("cellAudioQuit()"); // Stop audio thread auto g_audio = g_idm->lock<named_thread<audio_thread>>(0); if (!g_audio) { return CELL_AUDIO_ERROR_NOT_INIT; } // Signal to abort, release lock *g_audio.get() = thread_state::aborting; g_audio.unlock(); while (true) { if (ppu.is_stopped()) { return 0; } thread_ctrl::wait_for(1000); auto g_audio = g_idm->lock<named_thread<audio_thread>>(0); if (*g_audio.get() == thread_state::finished) { const auto buf = g_audio->ports[0].addr; const auto ind = g_audio->ports[0].index; g_audio.destroy(); g_audio.unlock(); vm::dealloc(buf.addr()); vm::dealloc(ind.addr()); break; } } return CELL_OK; }
// Note: the way we do things here is inaccurate(but maybe sufficient) // The real function goes over a table of 0x20 entries[ event_code:u32 callback_addr:u32 ] // Those callbacks are registered through cellSysutilRegisterCallbackDispatcher(u32 event_code, vm::ptr<void> func_addr) // The function goes through all the callback looking for one callback associated with event 0x100, if any is found it is called with parameters r3=0x101 r4=0 // This particular CB seems to be associated with sysutil itself // Then it checks for events on an event_queue associated with sysutil, checks if any cb is associated with that event and calls them with parameters that come from the event error_code cellSysutilCheckCallback(ppu_thread& ppu) { cellSysutil.trace("cellSysutilCheckCallback()"); const auto cbm = fxm::get_always<sysutil_cb_manager>(); for (auto&& func : cbm->registered.pop_all()) { if (s32 res = func(ppu)) { // Currently impossible return not_an_error(res); } if (ppu.is_stopped()) { return 0; } } return CELL_OK; }
error_code sys_ppu_thread_join(ppu_thread& ppu, u32 thread_id, vm::ptr<u64> vptr) { vm::temporary_unlock(ppu); sys_ppu_thread.trace("sys_ppu_thread_join(thread_id=0x%x, vptr=*0x%x)", thread_id, vptr); const auto thread = idm::get<named_thread<ppu_thread>>(thread_id, [&](ppu_thread& thread) -> CellError { CellError result = thread.joiner.atomic_op([&](u32& value) -> CellError { if (value == -3) { value = -2; return CELL_EBUSY; } if (value == -2) { return CELL_ESRCH; } if (value) { return CELL_EINVAL; } // TODO: check precedence? if (&ppu == &thread) { return CELL_EDEADLK; } value = ppu.id; return {}; }); if (!result) { lv2_obj::sleep(ppu); } return result; }); if (!thread) { return CELL_ESRCH; } if (thread.ret && thread.ret != CELL_EBUSY) { return thread.ret; } // Wait for cleanup (*thread.ptr)(); if (ppu.test_stopped()) { return 0; } // Cleanup idm::remove<named_thread<ppu_thread>>(thread->id); if (!vptr) { return CELL_EFAULT; } // Get the exit status from the register *vptr = thread->gpr[3]; return CELL_OK; }
error_code _sys_lwmutex_lock(ppu_thread& ppu, u32 lwmutex_id, u64 timeout) { sys_lwmutex.trace("_sys_lwmutex_lock(lwmutex_id=0x%x, timeout=0x%llx)", lwmutex_id, timeout); const auto mutex = idm::get<lv2_obj, lv2_lwmutex>(lwmutex_id, [&](lv2_lwmutex& mutex) { if (mutex.signaled.try_dec()) { return true; } std::lock_guard lock(mutex.mutex); if (mutex.signaled.try_dec()) { return true; } mutex.sq.emplace_back(&ppu); mutex.sleep(ppu, timeout); return false; }); if (!mutex) { return CELL_ESRCH; } if (mutex.ret) { return CELL_OK; } ppu.gpr[3] = CELL_OK; while (!ppu.state.test_and_reset(cpu_flag::signal)) { if (ppu.is_stopped()) { return 0; } if (timeout) { const u64 passed = get_system_time() - ppu.start_time; if (passed >= timeout) { std::lock_guard lock(mutex->mutex); if (!mutex->unqueue(mutex->sq, &ppu)) { timeout = 0; continue; } ppu.gpr[3] = CELL_ETIMEDOUT; break; } thread_ctrl::wait_for(timeout - passed); } else { thread_ctrl::wait(); } } return not_an_error(ppu.gpr[3]); }
error_code _sys_lwcond_queue_wait(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id, u64 timeout) { sys_lwcond.trace("_sys_lwcond_queue_wait(lwcond_id=0x%x, lwmutex_id=0x%x, timeout=0x%llx)", lwcond_id, lwmutex_id, timeout); std::shared_ptr<lv2_lwmutex> mutex; const auto cond = idm::get<lv2_obj, lv2_lwcond>(lwcond_id, [&](lv2_lwcond& cond) -> cpu_thread* { mutex = idm::get_unlocked<lv2_obj, lv2_lwmutex>(lwmutex_id); if (!mutex) { return nullptr; } std::lock_guard lock(cond.mutex); // Add a waiter cond.waiters++; cond.sq.emplace_back(&ppu); cond.sleep(ppu, timeout); std::lock_guard lock2(mutex->mutex); // Process lwmutex sleep queue if (const auto cpu = mutex->schedule<ppu_thread>(mutex->sq, mutex->protocol)) { return cpu; } mutex->signaled++; return nullptr; }); if (!cond || !mutex) { return CELL_ESRCH; } if (cond.ret) { cond->awake(*cond.ret); } ppu.gpr[3] = CELL_OK; while (!ppu.state.test_and_reset(cpu_flag::signal)) { if (ppu.is_stopped()) { return 0; } if (timeout) { const u64 passed = get_system_time() - ppu.start_time; if (passed >= timeout) { std::lock_guard lock(cond->mutex); if (!cond->unqueue(cond->sq, &ppu)) { timeout = 0; continue; } cond->waiters--; if (mutex->signaled.try_dec()) { ppu.gpr[3] = CELL_EDEADLK; break; } ppu.gpr[3] = CELL_ETIMEDOUT; break; } thread_ctrl::wait_for(timeout - passed); } else { thread_ctrl::wait(); } } // Return cause return not_an_error(ppu.gpr[3]); }
T get(std::size_t index) const { const u32 i = (u32)index + g_count; return ppu_gpr_cast<T>(i < 8 ? ctx->gpr[3 + i] : +*ctx->get_stack_arg(i)); }
// TODO static std::string ps3_fmt(ppu_thread& context, vm::cptr<char> fmt, u32 g_count) { std::string result; for (char c = *fmt++; c; c = *fmt++) { switch (c) { case '%': { const auto start = fmt - 1; // read flags const bool plus_sign = *fmt == '+' ? fmt++, true : false; const bool minus_sign = *fmt == '-' ? fmt++, true : false; const bool space_sign = *fmt == ' ' ? fmt++, true : false; const bool number_sign = *fmt == '#' ? fmt++, true : false; const bool zero_padding = *fmt == '0' ? fmt++, true : false; // read width const u32 width = [&]() -> u32 { u32 width = 0; if (*fmt == '*') { fmt++; return context.get_next_arg(g_count); } while (*fmt - '0' < 10) { width = width * 10 + (*fmt++ - '0'); } return width; }(); // read precision const u32 prec = [&]() -> u32 { u32 prec = 0; if (*fmt != '.') { return 0; } if (*++fmt == '*') { fmt++; return context.get_next_arg(g_count); } while (*fmt - '0' < 10) { prec = prec * 10 + (*fmt++ - '0'); } return prec; }(); switch (char cf = *fmt++) { case '%': { if (plus_sign || minus_sign || space_sign || number_sign || zero_padding || width || prec) break; result += '%'; continue; } case 'd': case 'i': { // signed decimal const s64 value = context.get_next_arg(g_count); if (plus_sign || minus_sign || space_sign || number_sign || zero_padding || width || prec) break; result += fmt::format("%lld", value); continue; } case 'x': case 'X': { // hexadecimal const u64 value = context.get_next_arg(g_count); if (plus_sign || minus_sign || space_sign || prec) break; if (number_sign && value) { result += cf == 'x' ? "0x" : "0X"; } const std::string& hex = fmt::format(cf == 'x' ? "%llx" : "%llX", value); if (hex.length() >= width) { result += hex; } else if (zero_padding) { result += std::string(width - hex.length(), '0') + hex; } else { result += hex + std::string(width - hex.length(), ' '); } continue; } case 's': { // string auto string = vm::cptr<char, u64>::make(context.get_next_arg(g_count)); if (plus_sign || minus_sign || space_sign || number_sign || zero_padding || width || prec) break; result += string.get_ptr(); continue; } case 'u': { // unsigned decimal const u64 value = context.get_next_arg(g_count); if (plus_sign || minus_sign || space_sign || number_sign || zero_padding || width || prec) break; result += fmt::format("%llu", value); continue; } } fmt::throw_exception("Unknown formatting: '%s'" HERE, start.get_ptr()); } } result += c; } return result; }
error_code sys_mutex_lock(ppu_thread& ppu, u32 mutex_id, u64 timeout) { sys_mutex.trace("sys_mutex_lock(mutex_id=0x%x, timeout=0x%llx)", mutex_id, timeout); const auto mutex = idm::get<lv2_obj, lv2_mutex>(mutex_id, [&](lv2_mutex& mutex) { CellError result = mutex.try_lock(ppu.id); if (result == CELL_EBUSY) { std::lock_guard lock(mutex.mutex); if (mutex.try_own(ppu, ppu.id)) { result = {}; } else { mutex.sleep(ppu, timeout); } } return result; }); if (!mutex) { return CELL_ESRCH; } if (mutex.ret) { if (mutex.ret != CELL_EBUSY) { return mutex.ret; } } else { return CELL_OK; } ppu.gpr[3] = CELL_OK; while (!ppu.state.test_and_reset(cpu_flag::signal)) { if (ppu.is_stopped()) { return 0; } if (timeout) { const u64 passed = get_system_time() - ppu.start_time; if (passed >= timeout) { std::lock_guard lock(mutex->mutex); if (!mutex->unqueue(mutex->sq, &ppu)) { timeout = 0; continue; } ppu.gpr[3] = CELL_ETIMEDOUT; break; } thread_ctrl::wait_for(timeout - passed); } else { thread_ctrl::wait(); } } return not_an_error(ppu.gpr[3]); }
error_code sys_semaphore_wait(ppu_thread& ppu, u32 sem_id, u64 timeout) { sys_semaphore.trace("sys_semaphore_wait(sem_id=0x%x, timeout=0x%llx)", sem_id, timeout); const u64 start_time = ppu.gpr[10] = get_system_time(); const auto sem = idm::get<lv2_obj, lv2_sema>(sem_id, [&](lv2_sema& sema) { const s32 val = sema.val; if (val > 0) { if (sema.val.compare_and_swap_test(val, val - 1)) { return true; } } semaphore_lock lock(sema.mutex); if (sema.val-- <= 0) { sema.sq.emplace_back(&ppu); sema.sleep(ppu, start_time, timeout); return false; } return true; }); if (!sem) { return CELL_ESRCH; } if (sem.ret) { return CELL_OK; } ppu.gpr[3] = CELL_OK; while (!ppu.state.test_and_reset(cpu_flag::signal)) { if (timeout) { const u64 passed = get_system_time() - start_time; if (passed >= timeout) { semaphore_lock lock(sem->mutex); const s32 val = sem->val.fetch_op([](s32& val) { if (val < 0) { val++; } }); if (val >= 0) { timeout = 0; continue; } verify(HERE), sem->unqueue(sem->sq, &ppu); ppu.gpr[3] = CELL_ETIMEDOUT; break; } thread_ctrl::wait_for(timeout - passed); } else { thread_ctrl::wait(); } } ppu.check_state(); return not_an_error(ppu.gpr[3]); }
error_code sys_semaphore_post(ppu_thread& ppu, u32 sem_id, s32 count) { sys_semaphore.trace("sys_semaphore_post(sem_id=0x%x, count=%d)", sem_id, count); if (count < 0) { return CELL_EINVAL; } const auto sem = idm::get<lv2_obj, lv2_sema>(sem_id, [&](lv2_sema& sema) { const s32 val = sema.val; if (val >= 0 && count <= sema.max - val) { if (sema.val.compare_and_swap_test(val, val + count)) { return true; } } return false; }); if (!sem) { return CELL_ESRCH; } if (sem.ret) { return CELL_OK; } else { semaphore_lock lock(sem->mutex); const s32 val = sem->val.fetch_op([=](s32& val) { if (val + s64{count} <= sem->max) { val += count; } }); if (val + s64{count} > sem->max) { return not_an_error(CELL_EBUSY); } // Wake threads for (s32 i = std::min<s32>(-std::min<s32>(val, 0), count); i > 0; i--) { const auto cpu = verify(HERE, sem->schedule<ppu_thread>(sem->sq, sem->protocol)); sem->awake(*cpu); } } ppu.check_state(); return CELL_OK; }