#include "stdafx.h" #include "Emu/Memory/Memory.h" #include "Emu/System.h" #include "Emu/IdManager.h" #include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/PPUThread.h" #include "sys_event_flag.h" #include logs::channel sys_event_flag("sys_event_flag", logs::level::notice); extern u64 get_system_time(); void lv2_event_flag_t::notify_all(lv2_lock_t) { auto pred = [this](cpu_thread* thread) -> bool { auto& ppu = static_cast(*thread); // load pattern and mode from registers const u64 bitptn = ppu.gpr[4]; const u32 mode = static_cast(ppu.gpr[5]); // check specific pattern if (check_pattern(bitptn, mode)) { // save pattern ppu.gpr[4] = clear_pattern(bitptn, mode); thread->set_signal(); return true; } return false; }; // check all waiters; protocol is ignored in current implementation sq.erase(std::remove_if(sq.begin(), sq.end(), pred), sq.end()); } s32 sys_event_flag_create(vm::ptr id, vm::ptr attr, u64 init) { sys_event_flag.warning("sys_event_flag_create(id=*0x%x, attr=*0x%x, init=0x%llx)", id, attr, init); if (!id || !attr) { return CELL_EFAULT; } const u32 protocol = attr->protocol; if (protocol != SYS_SYNC_FIFO && protocol != SYS_SYNC_RETRY && protocol != SYS_SYNC_PRIORITY && protocol != SYS_SYNC_PRIORITY_INHERIT) { sys_event_flag.error("sys_event_flag_create(): unknown protocol (0x%x)", protocol); return CELL_EINVAL; } if (attr->pshared != SYS_SYNC_NOT_PROCESS_SHARED || attr->ipc_key || attr->flags) { sys_event_flag.error("sys_event_flag_create(): unknown attributes (pshared=0x%x, ipc_key=0x%llx, flags=0x%x)", attr->pshared, attr->ipc_key, attr->flags); return CELL_EINVAL; } const u32 type = attr->type; if (type != SYS_SYNC_WAITER_SINGLE && type != SYS_SYNC_WAITER_MULTIPLE) { sys_event_flag.error("sys_event_flag_create(): unknown type (0x%x)", type); return CELL_EINVAL; } *id = idm::make(init, protocol, type, attr->name_u64); return CELL_OK; } s32 sys_event_flag_destroy(u32 id) { sys_event_flag.warning("sys_event_flag_destroy(id=0x%x)", id); LV2_LOCK; const auto eflag = idm::get(id); if (!eflag) { return CELL_ESRCH; } if (!eflag->sq.empty()) { return CELL_EBUSY; } idm::remove(id); return CELL_OK; } s32 sys_event_flag_wait(ppu_thread& ppu, u32 id, u64 bitptn, u32 mode, vm::ptr result, u64 timeout) { sys_event_flag.trace("sys_event_flag_wait(id=0x%x, bitptn=0x%llx, mode=0x%x, result=*0x%x, timeout=0x%llx)", id, bitptn, mode, result, timeout); const u64 start_time = get_system_time(); // If this syscall is called through the SC instruction, these registers must already contain corresponding values. // But let's fixup them (in the case of explicit function call or something) because these values are used externally. ppu.gpr[4] = bitptn; ppu.gpr[5] = mode; LV2_LOCK; if (result) *result = 0; // This is very annoying. if (!lv2_event_flag_t::check_mode(mode)) { sys_event_flag.error("sys_event_flag_wait(): unknown mode (0x%x)", mode); return CELL_EINVAL; } const auto eflag = idm::get(id); if (!eflag) { return CELL_ESRCH; } if (eflag->type == SYS_SYNC_WAITER_SINGLE && eflag->sq.size() > 0) { return CELL_EPERM; } if (eflag->check_pattern(bitptn, mode)) { const u64 pattern = eflag->clear_pattern(bitptn, mode); if (result) *result = pattern; return CELL_OK; } // add waiter; protocol is ignored in current implementation sleep_entry waiter(eflag->sq, ppu); while (!ppu.state.test_and_reset(cpu_flag::signal)) { CHECK_EMU_STATUS; if (timeout) { const u64 passed = get_system_time() - start_time; if (passed >= timeout) { if (result) *result = eflag->pattern; return CELL_ETIMEDOUT; } get_current_thread_cv().wait_for(lv2_lock, std::chrono::microseconds(timeout - passed)); } else { get_current_thread_cv().wait(lv2_lock); } } // load pattern saved upon signaling if (result) { *result = ppu.gpr[4]; } // check cause if (ppu.gpr[5] == 0) { return CELL_ECANCELED; } return CELL_OK; } s32 sys_event_flag_trywait(u32 id, u64 bitptn, u32 mode, vm::ptr result) { sys_event_flag.trace("sys_event_flag_trywait(id=0x%x, bitptn=0x%llx, mode=0x%x, result=*0x%x)", id, bitptn, mode, result); LV2_LOCK; if (result) *result = 0; // This is very annoying. if (!lv2_event_flag_t::check_mode(mode)) { sys_event_flag.error("sys_event_flag_trywait(): unknown mode (0x%x)", mode); return CELL_EINVAL; } const auto eflag = idm::get(id); if (!eflag) { return CELL_ESRCH; } if (eflag->check_pattern(bitptn, mode)) { const u64 pattern = eflag->clear_pattern(bitptn, mode); if (result) *result = pattern; return CELL_OK; } return CELL_EBUSY; } s32 sys_event_flag_set(u32 id, u64 bitptn) { sys_event_flag.trace("sys_event_flag_set(id=0x%x, bitptn=0x%llx)", id, bitptn); LV2_LOCK; const auto eflag = idm::get(id); if (!eflag) { return CELL_ESRCH; } if (bitptn && ~eflag->pattern.fetch_or(bitptn) & bitptn) { eflag->notify_all(lv2_lock); } return CELL_OK; } s32 sys_event_flag_clear(u32 id, u64 bitptn) { sys_event_flag.trace("sys_event_flag_clear(id=0x%x, bitptn=0x%llx)", id, bitptn); LV2_LOCK; const auto eflag = idm::get(id); if (!eflag) { return CELL_ESRCH; } eflag->pattern &= bitptn; return CELL_OK; } s32 sys_event_flag_cancel(u32 id, vm::ptr num) { sys_event_flag.trace("sys_event_flag_cancel(id=0x%x, num=*0x%x)", id, num); LV2_LOCK; if (num) { *num = 0; } const auto eflag = idm::get(id); if (!eflag) { return CELL_ESRCH; } if (num) { *num = static_cast(eflag->sq.size()); } const u64 pattern = eflag->pattern; // signal all threads to return CELL_ECANCELED for (auto& thread : eflag->sq) { auto& ppu = static_cast(*thread); // save existing pattern ppu.gpr[4] = pattern; // clear "mode" as a sign of cancellation ppu.gpr[5] = 0; thread->set_signal(); } eflag->sq.clear(); return CELL_OK; } s32 sys_event_flag_get(u32 id, vm::ptr flags) { sys_event_flag.trace("sys_event_flag_get(id=0x%x, flags=*0x%x)", id, flags); LV2_LOCK; if (!flags) { return CELL_EFAULT; } const auto eflag = idm::get(id); if (!eflag) { *flags = 0; // This is very annoying. return CELL_ESRCH; } *flags = eflag->pattern; return CELL_OK; }