#include "stdafx.h" #include "sys_event_flag.h" #include "Emu/IdManager.h" #include "Emu/IPC.h" #include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/PPUThread.h" #include LOG_CHANNEL(sys_event_flag); lv2_event_flag::lv2_event_flag(utils::serial& ar) : protocol(ar) , key(ar) , type(ar) , name(ar) { ar(pattern); } std::shared_ptr lv2_event_flag::load(utils::serial& ar) { auto eflag = std::make_shared(ar); return lv2_obj::load(eflag->key, eflag); } void lv2_event_flag::save(utils::serial& ar) { ar(protocol, key, type, name, pattern); } error_code sys_event_flag_create(ppu_thread& ppu, vm::ptr id, vm::ptr attr, u64 init) { ppu.state += cpu_flag::wait; 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 auto _attr = *attr; const u32 protocol = _attr.protocol; if (protocol != SYS_SYNC_FIFO && protocol != SYS_SYNC_PRIORITY) { sys_event_flag.error("sys_event_flag_create(): unknown protocol (0x%x)", protocol); 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; } const u64 ipc_key = lv2_obj::get_key(_attr); if (const auto error = lv2_obj::create(_attr.pshared, ipc_key, _attr.flags, [&] { return std::make_shared( _attr.protocol, ipc_key, _attr.type, _attr.name_u64, init); })) { return error; } *id = idm::last_id(); return CELL_OK; } error_code sys_event_flag_destroy(ppu_thread& ppu, u32 id) { ppu.state += cpu_flag::wait; sys_event_flag.warning("sys_event_flag_destroy(id=0x%x)", id); const auto flag = idm::withdraw(id, [&](lv2_event_flag& flag) -> CellError { if (flag.waiters) { return CELL_EBUSY; } lv2_obj::on_id_destroy(flag, flag.key); return {}; }); if (!flag) { return CELL_ESRCH; } if (flag.ret) { return flag.ret; } return CELL_OK; } error_code sys_event_flag_wait(ppu_thread& ppu, u32 id, u64 bitptn, u32 mode, vm::ptr result, u64 timeout) { ppu.state += cpu_flag::wait; 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); // Fix function arguments for external access ppu.gpr[3] = -1; ppu.gpr[4] = bitptn; ppu.gpr[5] = mode; ppu.gpr[6] = 0; // Always set result struct store_result { vm::ptr ptr; u64 val = 0; ~store_result() noexcept { if (ptr) *ptr = val; } } store{result}; if (!lv2_event_flag::check_mode(mode)) { sys_event_flag.error("sys_event_flag_wait(): unknown mode (0x%x)", mode); return CELL_EINVAL; } const auto flag = idm::get(id, [&](lv2_event_flag& flag) -> CellError { if (flag.pattern.fetch_op([&](u64& pat) { return lv2_event_flag::check_pattern(pat, bitptn, mode, &ppu.gpr[6]); }).second) { // TODO: is it possible to return EPERM in this case? return {}; } lv2_obj::notify_all_t notify; std::lock_guard lock(flag.mutex); if (flag.pattern.fetch_op([&](u64& pat) { return lv2_event_flag::check_pattern(pat, bitptn, mode, &ppu.gpr[6]); }).second) { return {}; } if (flag.type == SYS_SYNC_WAITER_SINGLE && !flag.sq.empty()) { return CELL_EPERM; } flag.waiters++; flag.sq.emplace_back(&ppu); flag.sleep(ppu, timeout, true); return CELL_EBUSY; }); if (!flag) { return CELL_ESRCH; } if (flag.ret) { if (flag.ret != CELL_EBUSY) { return flag.ret; } } else { store.val = ppu.gpr[6]; return CELL_OK; } while (auto state = +ppu.state) { if (state & cpu_flag::signal && ppu.state.test_and_reset(cpu_flag::signal)) { break; } if (is_stopped(state)) { std::lock_guard lock(flag->mutex); if (std::find(flag->sq.begin(), flag->sq.end(), &ppu) == flag->sq.end()) { break; } ppu.state += cpu_flag::again; return {}; } if (timeout) { if (lv2_obj::wait_timeout(timeout, &ppu)) { // Wait for rescheduling if (ppu.check_state()) { continue; } std::lock_guard lock(flag->mutex); if (!flag->unqueue(flag->sq, &ppu)) { break; } flag->waiters--; ppu.gpr[3] = CELL_ETIMEDOUT; ppu.gpr[6] = flag->pattern; break; } } else { thread_ctrl::wait_on(ppu.state, state); } } store.val = ppu.gpr[6]; return not_an_error(ppu.gpr[3]); } error_code sys_event_flag_trywait(ppu_thread& ppu, u32 id, u64 bitptn, u32 mode, vm::ptr result) { ppu.state += cpu_flag::wait; sys_event_flag.trace("sys_event_flag_trywait(id=0x%x, bitptn=0x%llx, mode=0x%x, result=*0x%x)", id, bitptn, mode, result); if (result) *result = 0; if (!lv2_event_flag::check_mode(mode)) { sys_event_flag.error("sys_event_flag_trywait(): unknown mode (0x%x)", mode); return CELL_EINVAL; } u64 pattern; const auto flag = idm::check(id, [&](lv2_event_flag& flag) { return flag.pattern.fetch_op([&](u64& pat) { return lv2_event_flag::check_pattern(pat, bitptn, mode, &pattern); }).second; }); if (!flag) { return CELL_ESRCH; } if (!flag.ret) { return not_an_error(CELL_EBUSY); } if (result) *result = pattern; return CELL_OK; } error_code sys_event_flag_set(cpu_thread& cpu, u32 id, u64 bitptn) { cpu.state += cpu_flag::wait; // Warning: may be called from SPU thread. sys_event_flag.trace("sys_event_flag_set(id=0x%x, bitptn=0x%llx)", id, bitptn); const auto flag = idm::get(id); if (!flag) { return CELL_ESRCH; } if ((flag->pattern & bitptn) == bitptn) { return CELL_OK; } if (true) { std::lock_guard lock(flag->mutex); for (auto ppu : flag->sq) { if (ppu->state & cpu_flag::again) { cpu.state += cpu_flag::again; // Fake error for abort return not_an_error(CELL_EAGAIN); } } // Sort sleep queue in required order if (flag->protocol != SYS_SYNC_FIFO) { std::stable_sort(flag->sq.begin(), flag->sq.end(), [](cpu_thread* a, cpu_thread* b) { return static_cast(a)->prio < static_cast(b)->prio; }); } // Process all waiters in single atomic op const u32 count = flag->pattern.atomic_op([&](u64& value) { value |= bitptn; u32 count = 0; for (auto cpu : flag->sq) { auto& ppu = static_cast(*cpu); const u64 pattern = ppu.gpr[4]; const u64 mode = ppu.gpr[5]; if (lv2_event_flag::check_pattern(value, pattern, mode, &ppu.gpr[6])) { ppu.gpr[3] = CELL_OK; count++; } else { ppu.gpr[3] = -1; } } return count; }); if (!count) { return CELL_OK; } // Remove waiters const auto tail = std::remove_if(flag->sq.begin(), flag->sq.end(), [&](cpu_thread* cpu) { auto& ppu = static_cast(*cpu); if (ppu.gpr[3] == CELL_OK) { flag->waiters--; flag->append(cpu); return true; } return false; }); if (tail != flag->sq.end()) { flag->sq.erase(tail, flag->sq.end()); lv2_obj::awake_all(); } } return CELL_OK; } error_code sys_event_flag_clear(ppu_thread& ppu, u32 id, u64 bitptn) { ppu.state += cpu_flag::wait; sys_event_flag.trace("sys_event_flag_clear(id=0x%x, bitptn=0x%llx)", id, bitptn); const auto flag = idm::check(id, [&](lv2_event_flag& flag) { flag.pattern &= bitptn; }); if (!flag) { return CELL_ESRCH; } return CELL_OK; } error_code sys_event_flag_cancel(ppu_thread& ppu, u32 id, vm::ptr num) { ppu.state += cpu_flag::wait; sys_event_flag.trace("sys_event_flag_cancel(id=0x%x, num=*0x%x)", id, num); if (num) *num = 0; const auto flag = idm::get(id); if (!flag) { return CELL_ESRCH; } u32 value = 0; { std::lock_guard lock(flag->mutex); for (auto cpu : flag->sq) { if (cpu->state & cpu_flag::again) { ppu.state += cpu_flag::again; return {}; } } // Get current pattern const u64 pattern = flag->pattern; // Set count value = ::size32(flag->sq); // Signal all threads to return CELL_ECANCELED (protocol does not matter) for (auto thread : ::as_rvalue(std::move(flag->sq))) { auto& ppu = static_cast(*thread); ppu.gpr[3] = CELL_ECANCELED; ppu.gpr[6] = pattern; flag->waiters--; flag->append(thread); } if (value) { lv2_obj::awake_all(); } } static_cast(ppu.test_stopped()); if (num) *num = value; return CELL_OK; } error_code sys_event_flag_get(ppu_thread& ppu, u32 id, vm::ptr flags) { ppu.state += cpu_flag::wait; sys_event_flag.trace("sys_event_flag_get(id=0x%x, flags=*0x%x)", id, flags); const auto flag = idm::check(id, [](lv2_event_flag& flag) { return +flag.pattern; }); if (!flag) { if (flags) *flags = 0; return CELL_ESRCH; } if (!flags) { return CELL_EFAULT; } *flags = flag.ret; return CELL_OK; }