error_code prx_load_module(std::string path, u64 flags, vm::ptr<sys_prx_load_module_option_t> pOpt) { if (s_prx_ignore.count(path)) { sys_prx.warning("Ignored module: %s", path); const auto prx = idm::make_ptr<lv2_obj, lv2_prx>(); prx->name = path.substr(path.find_last_of('/') + 1); return not_an_error(idm::last_id()); } const auto loadedkeys = fxm::get_always<LoadedNpdrmKeys_t>(); const ppu_prx_object obj = decrypt_self(fs::file(vfs::get(path)), loadedkeys->devKlic.data()); if (obj != elf_error::ok) { return CELL_PRX_ERROR_ILLEGAL_LIBRARY; } const auto prx = ppu_load_prx(obj, path.substr(path.find_last_of('/') + 1)); if (!prx) { return CELL_PRX_ERROR_ILLEGAL_LIBRARY; } ppu_initialize(*prx); sys_prx.success("Loaded module: %s", path); return not_an_error(idm::last_id()); }
error_code cellVideoOutGetNumberOfDevice(u32 videoOut) { cellSysutil.warning("cellVideoOutGetNumberOfDevice(videoOut=%d)", videoOut); switch (videoOut) { case CELL_VIDEO_OUT_PRIMARY: return not_an_error(1); case CELL_VIDEO_OUT_SECONDARY: return not_an_error(0); } return CELL_VIDEO_OUT_ERROR_UNSUPPORTED_VIDEO_OUT; }
error_code cellSubDisplayGetRequiredMemory(vm::ptr<CellSubDisplayParam> pParam) { cellSubDisplay.warning("cellSubDisplayGetRequiredMemory(pParam=*0x%x)", pParam); switch (pParam->version) { case CELL_SUBDISPLAY_VERSION_0001: return not_an_error(CELL_SUBDISPLAY_0001_MEMORY_CONTAINER_SIZE); case CELL_SUBDISPLAY_VERSION_0002: return not_an_error(CELL_SUBDISPLAY_0002_MEMORY_CONTAINER_SIZE); case CELL_SUBDISPLAY_VERSION_0003: return not_an_error(CELL_SUBDISPLAY_0003_MEMORY_CONTAINER_SIZE); } return CELL_SUBDISPLAY_ERROR_INVALID_VALUE; }
error_code cellVideoOutGetResolutionAvailability(u32 videoOut, u32 resolutionId, u32 aspect, u32 option) { cellSysutil.warning("cellVideoOutGetResolutionAvailability(videoOut=%d, resolutionId=0x%x, aspect=%d, option=%d)", videoOut, resolutionId, aspect, option); switch (videoOut) { case CELL_VIDEO_OUT_PRIMARY: return not_an_error( resolutionId == g_video_out_resolution_id.at(g_cfg.video.resolution) && (aspect == CELL_VIDEO_OUT_ASPECT_AUTO || aspect == g_video_out_aspect_id.at(g_cfg.video.aspect_ratio)) ); case CELL_VIDEO_OUT_SECONDARY: return not_an_error(0); } return CELL_VIDEO_OUT_ERROR_UNSUPPORTED_VIDEO_OUT; }
error_code sys_semaphore_trywait(u32 sem_id) { sys_semaphore.trace("sys_semaphore_trywait(sem_id=0x%x)", sem_id); const auto sem = idm::check<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; } } return false; }); if (!sem) { return CELL_ESRCH; } if (!sem.ret) { return not_an_error(CELL_EBUSY); } return CELL_OK; }
error_code sys_mutex_trylock(ppu_thread& ppu, u32 mutex_id) { sys_mutex.trace("sys_mutex_trylock(mutex_id=0x%x)", mutex_id); const auto mutex = idm::check<lv2_obj, lv2_mutex>(mutex_id, [&](lv2_mutex& mutex) { return mutex.try_lock(ppu.id); }); if (!mutex) { return CELL_ESRCH; } if (mutex.ret) { if (mutex.ret == CELL_EBUSY) { return not_an_error(CELL_EBUSY); } return mutex.ret; } return CELL_OK; }
error_code _sys_timer_start(u32 timer_id, u64 base_time, u64 period) { sys_timer.trace("_sys_timer_start(timer_id=0x%x, base_time=0x%llx, period=0x%llx)", timer_id, base_time, period); const u64 start_time = get_system_time(); if (!period && start_time >= base_time) { // Invalid oneshot (TODO: what will happen if both args are 0?) return not_an_error(CELL_ETIMEDOUT); } if (period && period < 100) { // Invalid periodic timer return CELL_EINVAL; } const auto timer = idm::check<lv2_obj, lv2_timer>(timer_id, [&](lv2_timer& timer) -> CellError { semaphore_lock lock(timer.mutex); if (timer.state != SYS_TIMER_STATE_STOP) { return CELL_EBUSY; } if (timer.port.expired()) { return CELL_ENOTCONN; } // sys_timer_start_periodic() will use current time (TODO: is it correct?) timer.expire = base_time ? base_time : start_time + period; timer.period = period; timer.state = SYS_TIMER_STATE_RUN; timer.notify(); return {}; }); if (!timer) { return CELL_ESRCH; } if (timer.ret) { return timer.ret; } return CELL_OK; }
static void assert_eval_result(struct silc_ctx_t* c, const char* input, const char* expected_eval_result) { /* Given: */ write_and_rewind(out, input); /* When: */ silc_obj result = not_an_error(silc_eval(c, silc_read(c, out, silc_err_from_code(SILC_ERR_UNEXPECTED_EOF)))); /* Then: */ silc_print(c, result, in); READ_BUF(in, buf); if (0 != strcmp(buf, expected_eval_result)) { fprintf(stderr, "[eval] actual_result=%s, expected_result=%s\n", buf, expected_eval_result); ASSERT(!"results are not matching"); } }
error_code cellGameDataCheck(u32 type, vm::cptr<char> dirName, vm::ptr<CellGameContentSize> size) { cellGame.warning("cellGameDataCheck(type=%d, dirName=%s, size=*0x%x)", type, dirName, size); if ((type - 1) >= 3 || (type != CELL_GAME_GAMETYPE_DISC && !dirName)) { return {CELL_GAME_ERROR_PARAM, type}; } if (size) { // TODO: Use the free space of the computer's HDD where RPCS3 is being run. size->hddFreeSizeKB = 40 * 1024 * 1024 - 1; // Read explanation in cellHddGameCheck // TODO: Calculate data size for game data, if necessary. size->sizeKB = CELL_GAME_SIZEKB_NOTCALC; size->sysSizeKB = 0; } // TODO: not sure what should be checked there const auto prm = fxm::make<content_permission>(type == CELL_GAME_GAMETYPE_DISC ? "" : dirName.get_ptr(), psf::registry{}); if (!prm) { return CELL_GAME_ERROR_BUSY; } if (type == CELL_GAME_GAMETYPE_GAMEDATA) { prm->can_create = true; } const std::string dir = prm->dir.empty() ? "/dev_bdvd/PS3_GAME"s : "/dev_hdd0/game/" + prm->dir; if (!fs::is_dir(vfs::get(dir))) { cellGame.warning("cellGameDataCheck(): directory '%s' not found", dir); return not_an_error(CELL_GAME_RET_NONE); } prm->sfo = psf::load_object(fs::file(vfs::get(dir + "/PARAM.SFO"))); return CELL_OK; }
error_code _sys_lwmutex_trylock(u32 lwmutex_id) { sys_lwmutex.trace("_sys_lwmutex_trylock(lwmutex_id=0x%x)", lwmutex_id); const auto mutex = idm::check<lv2_obj, lv2_lwmutex>(lwmutex_id, [&](lv2_lwmutex& mutex) { return mutex.signaled.try_dec(); }); if (!mutex) { return CELL_ESRCH; } if (!mutex.ret) { return not_an_error(CELL_EBUSY); } 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_event_port_send(ppu_thread& ppu, u32 eport_id, u64 data1, u64 data2, u64 data3) { sys_event.trace("sys_event_port_send(eport_id=0x%x, data1=0x%llx, data2=0x%llx, data3=0x%llx)", eport_id, data1, data2, data3); const auto port = idm::get<lv2_obj, lv2_event_port>(eport_id, [&](lv2_event_port& port) -> CellError { if (const auto queue = port.queue.lock()) { const u64 source = port.name ? port.name : ((u64)process_getpid() << 32) | (u64)eport_id; if (queue->send(source, data1, data2, data3)) { return {}; } return CELL_EBUSY; } return CELL_ENOTCONN; }); if (!port) { return CELL_ESRCH; } if (port.ret) { if (port.ret == CELL_EBUSY) { return not_an_error(CELL_EBUSY); } return port.ret; } return CELL_OK; }
error_code sys_lwmutex_trylock(ppu_thread& ppu, vm::ptr<sys_lwmutex_t> lwmutex) { sysPrxForUser.trace("sys_lwmutex_trylock(lwmutex=*0x%x)", lwmutex); if (g_cfg.core.hle_lwmutex) { return sys_mutex_trylock(ppu, lwmutex->sleep_queue); } const be_t<u32> tid(ppu.id); // try to lock lightweight mutex const be_t<u32> old_owner = lwmutex->vars.owner.compare_and_swap(lwmutex_free, tid); if (old_owner == lwmutex_free) { // locking succeeded return CELL_OK; } if (old_owner == tid) { // recursive locking if ((lwmutex->attribute & SYS_SYNC_RECURSIVE) == 0) { // if not recursive return CELL_EDEADLK; } if (lwmutex->recursive_count == -1) { // if recursion limit reached return CELL_EKRESOURCE; } // recursive locking succeeded lwmutex->recursive_count++; _mm_mfence(); return CELL_OK; } if (old_owner == lwmutex_dead) { // invalid or deleted mutex return CELL_EINVAL; } if (old_owner == lwmutex_reserved) { // should be locked by the syscall const error_code res = _sys_lwmutex_trylock(lwmutex->sleep_queue); if (res == CELL_OK) { // locking succeeded auto old = lwmutex->vars.owner.exchange(tid); if (old != lwmutex_reserved) { fmt::throw_exception("Locking failed (lwmutex=*0x%x, owner=0x%x)" HERE, lwmutex, old); } } return res; } // locked by another thread return not_an_error(CELL_EBUSY); }
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_signal_all(u32 lwcond_id, u32 lwmutex_id, u32 mode) { sys_lwcond.trace("_sys_lwcond_signal_all(lwcond_id=0x%x, lwmutex_id=0x%x, mode=%d)", lwcond_id, lwmutex_id, mode); // Mode 1: lwmutex was initially owned by the calling thread // Mode 2: lwmutex was not owned by the calling thread and waiter hasn't been increased if (mode < 1 || mode > 2) { fmt::throw_exception("Unknown mode (%d)" HERE, mode); } std::basic_string<cpu_thread*> threads; lv2_lwmutex* mutex = nullptr; const auto cond = idm::check<lv2_obj, lv2_lwcond>(lwcond_id, [&](lv2_lwcond& cond) -> u32 { mutex = idm::check_unlocked<lv2_obj, lv2_lwmutex>(lwmutex_id); if (mutex && cond.waiters) { semaphore_lock lock(mutex->mutex); u32 result = 0; while (const auto cpu = cond.schedule<ppu_thread>(cond.sq, mutex->protocol)) { cond.waiters--; static_cast<ppu_thread*>(cpu)->gpr[3] = mode == 2; if (mode != 2 && !mutex->signaled.fetch_op([](u32& v) { if (v) v--; })) { mutex->sq.emplace_back(cpu); } else { threads.push_back(cpu); } result++; } return result; } return 0; }); if ((lwmutex_id && !mutex) || !cond) { return CELL_ESRCH; } // TODO: signal only one thread for (auto cpu : threads) { cpu->set_signal(); } if (mode == 1) { // Mode 1: return the amount of threads (TODO) return not_an_error(cond.ret); } return CELL_OK; }
error_code _sys_lwcond_signal(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id, u32 ppu_thread_id, u32 mode) { sys_lwcond.trace("_sys_lwcond_signal(lwcond_id=0x%x, lwmutex_id=0x%x, ppu_thread_id=0x%x, mode=%d)", lwcond_id, lwmutex_id, ppu_thread_id, mode); // Mode 1: lwmutex was initially owned by the calling thread // Mode 2: lwmutex was not owned by the calling thread and waiter hasn't been increased // Mode 3: lwmutex was forcefully owned by the calling thread if (mode < 1 || mode > 3) { fmt::throw_exception("Unknown mode (%d)" HERE, mode); } lv2_lwmutex* mutex = nullptr; const auto cond = idm::check<lv2_obj, lv2_lwcond>(lwcond_id, [&](lv2_lwcond& cond) -> cpu_thread* { mutex = idm::check_unlocked<lv2_obj, lv2_lwmutex>(lwmutex_id); if (cond.waiters) { std::lock_guard lock(cond.mutex); cpu_thread* result = nullptr; if (ppu_thread_id != -1) { for (auto cpu : cond.sq) { if (cpu->id == ppu_thread_id) { verify(HERE), cond.unqueue(cond.sq, cpu); result = cpu; break; } } } else { result = cond.schedule<ppu_thread>(cond.sq, cond.control->lwmutex->attribute & SYS_SYNC_ATTR_PROTOCOL_MASK); } if (result) { cond.waiters--; if (mode == 2) { static_cast<ppu_thread*>(result)->gpr[3] = CELL_EBUSY; } if (mode == 1) { verify(HERE), !mutex->signaled; std::lock_guard lock(mutex->mutex); mutex->sq.emplace_back(result); result = nullptr; mode = 2; // Enforce CELL_OK } return result; } } return nullptr; }); if ((lwmutex_id && !mutex) || !cond) { return CELL_ESRCH; } if (cond.ret) { cond->awake(*cond.ret); } else if (mode == 2) { return CELL_OK; } else if (mode == 1 || ppu_thread_id == -1) { return not_an_error(CELL_EPERM); } else { return not_an_error(CELL_ENOENT); } return CELL_OK; }
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]); }
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_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); const u64 start_time = ppu.gpr[10] = get_system_time(); 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; } semaphore_lock lock(mutex->mutex); // Add a waiter cond.waiters++; cond.sq.emplace_back(&ppu); // 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.ret->set_signal(); } // SLEEP 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(mutex->mutex); if (!cond->unqueue(cond->sq, &ppu)) { timeout = 0; continue; } cond->waiters--; if (mutex->signaled.fetch_op([](u32& v) { if (v) v--; })) { return not_an_error(CELL_EDEADLK); } return not_an_error(CELL_ETIMEDOUT); } thread_ctrl::wait_for(timeout - passed); } else { thread_ctrl::wait(); } } // Return cause return not_an_error(ppu.gpr[3] ? CELL_EBUSY : CELL_OK); }
error_code sys_lwmutex_lock(ppu_thread& ppu, vm::ptr<sys_lwmutex_t> lwmutex, u64 timeout) { sysPrxForUser.trace("sys_lwmutex_lock(lwmutex=*0x%x, timeout=0x%llx)", lwmutex, timeout); if (g_cfg.core.hle_lwmutex) { return sys_mutex_lock(ppu, lwmutex->sleep_queue, timeout); } const be_t<u32> tid(ppu.id); // try to lock lightweight mutex const be_t<u32> old_owner = lwmutex->vars.owner.compare_and_swap(lwmutex_free, tid); if (old_owner == lwmutex_free) { // locking succeeded return CELL_OK; } if (old_owner == tid) { // recursive locking if ((lwmutex->attribute & SYS_SYNC_RECURSIVE) == 0) { // if not recursive return CELL_EDEADLK; } if (lwmutex->recursive_count == -1) { // if recursion limit reached return CELL_EKRESOURCE; } // recursive locking succeeded lwmutex->recursive_count++; _mm_mfence(); return CELL_OK; } if (old_owner == lwmutex_dead) { // invalid or deleted mutex return CELL_EINVAL; } for (u32 i = 0; i < 10; i++) { busy_wait(); if (lwmutex->vars.owner.load() == lwmutex_free) { if (lwmutex->vars.owner.compare_and_swap_test(lwmutex_free, tid)) { // locking succeeded return CELL_OK; } } } // atomically increment waiter value using 64 bit op lwmutex->all_info++; if (lwmutex->vars.owner.compare_and_swap_test(lwmutex_free, tid)) { // locking succeeded --lwmutex->all_info; return CELL_OK; } // lock using the syscall const error_code res = _sys_lwmutex_lock(ppu, lwmutex->sleep_queue, timeout); lwmutex->all_info--; if (res == CELL_OK) { // locking succeeded auto old = lwmutex->vars.owner.exchange(tid); if (old != lwmutex_reserved) { fmt::throw_exception("Locking failed (lwmutex=*0x%x, owner=0x%x)" HERE, lwmutex, old); } return CELL_OK; } if (res == CELL_EBUSY && lwmutex->attribute & SYS_SYNC_RETRY) { while (true) { for (u32 i = 0; i < 10; i++) { busy_wait(); if (lwmutex->vars.owner.load() == lwmutex_free) { if (lwmutex->vars.owner.compare_and_swap_test(lwmutex_free, tid)) { return CELL_OK; } } } lwmutex->all_info++; if (lwmutex->vars.owner.compare_and_swap_test(lwmutex_free, tid)) { lwmutex->all_info--; return CELL_OK; } const u64 time0 = timeout ? get_system_time() : 0; const error_code res_ = _sys_lwmutex_lock(ppu, lwmutex->sleep_queue, timeout); if (res_ == CELL_OK) { lwmutex->vars.owner.release(tid); } else if (timeout && res_ != CELL_ETIMEDOUT) { const u64 time_diff = get_system_time() - time0; if (timeout <= time_diff) { lwmutex->all_info--; return not_an_error(CELL_ETIMEDOUT); } timeout -= time_diff; } lwmutex->all_info--; if (res_ != CELL_EBUSY) { return res_; } } } return res; }
error_code _sys_lwcond_signal(u32 lwcond_id, u32 lwmutex_id, u32 ppu_thread_id, u32 mode) { sys_lwcond.trace("_sys_lwcond_signal(lwcond_id=0x%x, lwmutex_id=0x%x, ppu_thread_id=0x%x, mode=%d)", lwcond_id, lwmutex_id, ppu_thread_id, mode); // Mode 1: lwmutex was initially owned by the calling thread // Mode 2: lwmutex was not owned by the calling thread and waiter hasn't been increased // Mode 3: lwmutex was forcefully owned by the calling thread if (mode < 1 || mode > 3) { fmt::throw_exception("Unknown mode (%d)" HERE, mode); } lv2_lwmutex* mutex = nullptr; const auto cond = idm::check<lv2_obj, lv2_lwcond>(lwcond_id, [&](lv2_lwcond& cond) -> cpu_thread* { mutex = idm::check_unlocked<lv2_obj, lv2_lwmutex>(lwmutex_id); if (mutex && cond.waiters) { semaphore_lock lock(mutex->mutex); cpu_thread* result = nullptr; if (ppu_thread_id != -1) { for (auto cpu : cond.sq) { if (cpu->id == ppu_thread_id) { verify(HERE), cond.unqueue(cond.sq, cpu); result = cpu; break; } } } else { result = cond.schedule<ppu_thread>(cond.sq, mutex->protocol); } if (result) { cond.waiters--; static_cast<ppu_thread*>(result)->gpr[3] = mode == 2; if (mode != 2 && !mutex->signaled.fetch_op([](u32& v) { if (v) v--; })) { mutex->sq.emplace_back(result); result = nullptr; mode = 2; // Enforce CELL_OK } return result; } } return nullptr; }); if ((lwmutex_id && !mutex) || !cond) { return CELL_ESRCH; } if (cond.ret) { cond.ret->set_signal(); } else if (mode == 2) { return CELL_OK; } else if (mode == 1 || ppu_thread_id == -1) { return not_an_error(CELL_EPERM); } else { return not_an_error(CELL_ENOENT); } return CELL_OK; }
error_code sys_event_queue_receive(ppu_thread& ppu, u32 equeue_id, vm::ptr<sys_event_t> dummy_event, u64 timeout) { sys_event.trace("sys_event_queue_receive(equeue_id=0x%x, *0x%x, timeout=0x%llx)", equeue_id, dummy_event, timeout); const u64 start_time = get_system_time(); const auto queue = idm::get<lv2_event_queue>(equeue_id, [&](lv2_event_queue& queue) -> CellError { if (queue.type != SYS_PPU_QUEUE) { return CELL_EINVAL; } lv2_lock lock{queue}; if (queue.events.size()) { verify(HERE), queue.waiters.empty(); // Return event data in registers r4-r7 (dummy_event is not used) std::tie(ppu.gpr[4], ppu.gpr[5], ppu.gpr[6], ppu.gpr[7]) = queue.events.front(); queue.events.pop_front(); return {}; } // If cancelled, r3 will contain error code ppu.gpr[3] = CELL_OK; // Add thread to the wait queue queue.waiters.emplace_back(&ppu); return CELL_EBUSY; }); if (!queue) { return CELL_ESRCH; } if (!queue.value) { return CELL_OK; } else if (queue.value != CELL_EBUSY) { return queue.value; } thread_lock lock(ppu); while (!ppu.state.test_and_reset(cpu_flag::signal)) { if (timeout) { const u64 passed = get_system_time() - start_time; if (passed >= timeout) { lv2_lock lock{queue}; for (auto it = queue->waiters.begin(), end = queue->waiters.end(); it != end; it++) { if (*it == &ppu) { queue->waiters.erase(it); return not_an_error(CELL_ETIMEDOUT); } } timeout = 0; continue; } thread_ctrl::wait_for(timeout - passed); } else { thread_ctrl::wait(); } } if (ppu.gpr[3]) { return static_cast<CellError>(ppu.gpr[3]); } return CELL_OK; }
error_code sys_event_queue_receive(ppu_thread& ppu, u32 equeue_id, vm::ptr<sys_event_t> dummy_event, u64 timeout) { sys_event.trace("sys_event_queue_receive(equeue_id=0x%x, *0x%x, timeout=0x%llx)", equeue_id, dummy_event, timeout); const auto queue = idm::get<lv2_obj, lv2_event_queue>(equeue_id, [&](lv2_event_queue& queue) -> CellError { if (queue.type != SYS_PPU_QUEUE) { return CELL_EINVAL; } semaphore_lock lock(queue.mutex); if (queue.events.empty()) { queue.sq.emplace_back(&ppu); queue.sleep(ppu, timeout); return CELL_EBUSY; } std::tie(ppu.gpr[4], ppu.gpr[5], ppu.gpr[6], ppu.gpr[7]) = queue.events.front(); queue.events.pop_front(); return {}; }); if (!queue) { return CELL_ESRCH; } if (queue.ret) { if (queue.ret != CELL_EBUSY) { return queue.ret; } } else { return CELL_OK; } // If cancelled, gpr[3] will be non-zero. Other registers must contain event data. ppu.gpr[3] = 0; while (!ppu.state.test_and_reset(cpu_flag::signal)) { if (timeout) { const u64 passed = get_system_time() - ppu.start_time; if (passed >= timeout) { semaphore_lock lock(queue->mutex); if (!queue->unqueue(queue->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_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; }
" (define a 3)\n" " (define b 4)\n" " (define x (lambda (a) (lambda (b) (+ a (* 2 b) 50000))))\n" " (define y (x 1))\n" " (+ (y 10) (* a 100) (* b 1000))\n" ")", "54321"); silc_free_context(c); END_TEST_METHOD() BEGIN_TEST_METHOD(test_eval_gc) struct silc_ctx_t* c = silc_new_context(); silc_set_default_out(c, in); write_and_rewind(out, "(gc)"); silc_obj result = not_an_error(silc_eval(c, silc_read(c, out, silc_err_from_code(SILC_ERR_UNEXPECTED_EOF)))); ASSERT(SILC_OBJ_NIL == result); silc_free_context(c); END_TEST_METHOD() BEGIN_TEST_METHOD(test_eval_nonfunction) struct silc_ctx_t* c = silc_new_context(); write_and_rewind(out, "(1)"); silc_obj result = silc_eval(c, silc_read(c, out, silc_err_from_code(SILC_ERR_UNEXPECTED_EOF))); ASSERT(SILC_ERR_NOT_A_FUNCTION == silc_try_get_err_code(result)); silc_free_context(c);
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_lwcond_signal_all(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id, u32 mode) { sys_lwcond.trace("_sys_lwcond_signal_all(lwcond_id=0x%x, lwmutex_id=0x%x, mode=%d)", lwcond_id, lwmutex_id, mode); // Mode 1: lwmutex was initially owned by the calling thread // Mode 2: lwmutex was not owned by the calling thread and waiter hasn't been increased if (mode < 1 || mode > 2) { fmt::throw_exception("Unknown mode (%d)" HERE, mode); } std::basic_string<cpu_thread*> threads; lv2_lwmutex* mutex = nullptr; const auto cond = idm::check<lv2_obj, lv2_lwcond>(lwcond_id, [&](lv2_lwcond& cond) -> u32 { mutex = idm::check_unlocked<lv2_obj, lv2_lwmutex>(lwmutex_id); if (cond.waiters) { std::lock_guard lock(cond.mutex); u32 result = 0; while (const auto cpu = cond.schedule<ppu_thread>(cond.sq, cond.control->lwmutex->attribute & SYS_SYNC_ATTR_PROTOCOL_MASK)) { cond.waiters--; if (mode == 2) { static_cast<ppu_thread*>(cpu)->gpr[3] = CELL_EBUSY; } if (mode == 1) { verify(HERE), !mutex->signaled; std::lock_guard lock(mutex->mutex); mutex->sq.emplace_back(cpu); } else { threads.push_back(cpu); } result++; } return result; } return 0; }); if ((lwmutex_id && !mutex) || !cond) { return CELL_ESRCH; } for (auto cpu : threads) { cond->awake(*cpu); } if (mode == 1) { // Mode 1: return the amount of threads (TODO) return not_an_error(cond.ret); } return CELL_OK; }
error_code sceNpTrophyGetTrophyInfo(u32 context, u32 handle, s32 trophyId, vm::ptr<SceNpTrophyDetails> details, vm::ptr<SceNpTrophyData> data) { sceNpTrophy.warning("sceNpTrophyGetTrophyInfo(context=0x%x, handle=0x%x, trophyId=%d, details=*0x%x, data=*0x%x)", context, handle, trophyId, details, data); if (!details && !data) { return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT; } const auto ctxt = idm::get<trophy_context_t>(context); if (!ctxt) { return SCE_NP_TROPHY_ERROR_UNKNOWN_CONTEXT; } const auto hndl = idm::get<trophy_handle_t>(handle); if (!hndl) { return SCE_NP_TROPHY_ERROR_UNKNOWN_HANDLE; } fs::file config(vfs::get("/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + ctxt->trp_name + "/TROPCONF.SFM")); if (!config) { return SCE_NP_TROPHY_ERROR_CONF_DOES_NOT_EXIST; } if (details) memset(details.get_ptr(), 0, sizeof(SceNpTrophyDetails)); if (data) memset(data.get_ptr(), 0, sizeof(SceNpTrophyData)); rXmlDocument doc; doc.Read(config.to_string()); auto trophy_base = doc.GetRoot(); if (trophy_base->GetChildren()->GetName() == "trophyconf") { trophy_base = trophy_base->GetChildren(); } bool found = false; for (std::shared_ptr<rXmlNode> n = trophy_base->GetChildren(); n; n = n->GetNext()) { if (n->GetName() == "trophy" && (trophyId == atoi(n->GetAttribute("id").c_str()))) { found = true; if (n->GetAttribute("hidden")[0] == 'y' && !ctxt->tropusr->GetTrophyUnlockState(trophyId)) // Trophy is hidden { return SCE_NP_TROPHY_ERROR_HIDDEN; } if (details) { details->trophyId = trophyId; switch (n->GetAttribute("ttype")[0]) { case 'B': details->trophyGrade = SCE_NP_TROPHY_GRADE_BRONZE; break; case 'S': details->trophyGrade = SCE_NP_TROPHY_GRADE_SILVER; break; case 'G': details->trophyGrade = SCE_NP_TROPHY_GRADE_GOLD; break; case 'P': details->trophyGrade = SCE_NP_TROPHY_GRADE_PLATINUM; break; } switch (n->GetAttribute("hidden")[0]) { case 'y': details->hidden = true; break; case 'n': details->hidden = false; break; } for (std::shared_ptr<rXmlNode> n2 = n->GetChildren(); n2; n2 = n2->GetNext()) { const std::string n2_name = n2->GetName(); if (n2_name == "name") { strcpy_trunc(details->name, n2->GetNodeContent()); } else if (n2_name == "detail") { strcpy_trunc(details->description, n2->GetNodeContent()); } } } if (data) { data->trophyId = trophyId; data->unlocked = ctxt->tropusr->GetTrophyUnlockState(trophyId) != 0; // ??? data->timestamp = ctxt->tropusr->GetTrophyTimestamp(trophyId); } break; } } if (!found) { return not_an_error(SCE_NP_TROPHY_INVALID_TROPHY_ID); } return CELL_OK; }