#include "stdafx.h" #include "sys_timer.h" #include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/PPUThread.h" #include "Emu/Cell/timers.hpp" #include "Emu/IdManager.h" #include "Emu/System.h" #include "Emu/system_config.h" #include "sys_event.h" #include "sys_process.h" #include "util/asm.hpp" #include #include LOG_CHANNEL(sys_timer); struct lv2_timer_thread { shared_mutex mutex; std::deque> timers; lv2_timer_thread(); void operator()(); // SAVESTATE_INIT_POS(46); // FREE SAVESTATE_INIT_POS number static constexpr auto thread_name = "Timer Thread"sv; }; lv2_timer::lv2_timer(utils::serial &ar) : lv2_obj(1), state(ar), port(lv2_event_queue::load_ptr(ar, port, "timer")), source(ar), data1(ar), data2(ar), expire(ar), period(ar) {} void lv2_timer::save(utils::serial &ar) { USING_SERIALIZATION_VERSION(lv2_sync); ar(state), lv2_event_queue::save_ptr(ar, port.get()), ar(source, data1, data2, expire, period); } u64 lv2_timer::check(u64 _now) noexcept { while (true) { const u32 _state = +state; if (_state == SYS_TIMER_STATE_RUN) { u64 next = expire; // If aborting, perform the last accurate check for event if (_now >= next) { lv2_obj::notify_all_t notify; std::lock_guard lock(mutex); return check_unlocked(_now); } return (next - _now); } break; } return umax; } u64 lv2_timer::check_unlocked(u64 _now) noexcept { const u64 next = expire; if (_now < next || state != SYS_TIMER_STATE_RUN) { return umax; } if (port) { port->send(source, data1, data2, next); } if (period) { // Set next expiration time and check again const u64 expire0 = utils::add_saturate(next, period); expire.release(expire0); return utils::sub_saturate(expire0, _now); } // Stop after oneshot state.release(SYS_TIMER_STATE_STOP); return umax; } lv2_timer_thread::lv2_timer_thread() { Emu.PostponeInitCode([this]() { idm::select([&](u32 id, lv2_timer &) { timers.emplace_back(idm::get_unlocked(id)); }); }); } void lv2_timer_thread::operator()() { u64 sleep_time = 0; while (true) { if (sleep_time != umax) { // Scale time sleep_time = std::min(sleep_time, u64{umax} / 100) * 100 / g_cfg.core.clocks_scale; } thread_ctrl::wait_for(sleep_time); if (thread_ctrl::state() == thread_state::aborting) { break; } sleep_time = umax; if (Emu.IsPausedOrReady()) { sleep_time = 10000; continue; } const u64 _now = get_guest_system_time(); reader_lock lock(mutex); for (const auto &timer : timers) { while (lv2_obj::check(timer)) { if (thread_ctrl::state() == thread_state::aborting) { break; } if (const u64 advised_sleep_time = timer->check(_now)) { if (sleep_time > advised_sleep_time) { sleep_time = advised_sleep_time; } break; } } } } } error_code sys_timer_create(ppu_thread &ppu, vm::ptr timer_id) { ppu.state += cpu_flag::wait; sys_timer.warning("sys_timer_create(timer_id=*0x%x)", timer_id); if (auto ptr = idm::make_ptr()) { auto &thread = g_fxo->get>(); { std::lock_guard lock(thread.mutex); // Theoretically could have been destroyed by sys_timer_destroy by now if (auto it = std::find(thread.timers.begin(), thread.timers.end(), ptr); it == thread.timers.end()) { thread.timers.emplace_back(std::move(ptr)); } } ppu.check_state(); *timer_id = idm::last_id(); return CELL_OK; } return CELL_EAGAIN; } error_code sys_timer_destroy(ppu_thread &ppu, u32 timer_id) { ppu.state += cpu_flag::wait; sys_timer.warning("sys_timer_destroy(timer_id=0x%x)", timer_id); auto timer = idm::withdraw( timer_id, [&](lv2_timer &timer) -> CellError { if (reader_lock lock(timer.mutex); lv2_obj::check(timer.port)) { return CELL_EISCONN; } timer.exists--; return {}; }); if (!timer) { return CELL_ESRCH; } if (timer.ret) { return timer.ret; } auto &thread = g_fxo->get>(); std::lock_guard lock(thread.mutex); if (auto it = std::find(thread.timers.begin(), thread.timers.end(), timer.ptr); it != thread.timers.end()) { thread.timers.erase(it); } return CELL_OK; } error_code sys_timer_get_information(ppu_thread &ppu, u32 timer_id, vm::ptr info) { ppu.state += cpu_flag::wait; sys_timer.trace("sys_timer_get_information(timer_id=0x%x, info=*0x%x)", timer_id, info); sys_timer_information_t _info{}; const u64 now = get_guest_system_time(); const auto timer = idm::check(timer_id, [&](lv2_timer &timer) { std::lock_guard lock(timer.mutex); timer.check_unlocked(now); timer.get_information(_info); }); if (!timer) { return CELL_ESRCH; } ppu.check_state(); std::memcpy(info.get_ptr(), &_info, info.size()); return CELL_OK; } error_code _sys_timer_start(ppu_thread &ppu, u32 timer_id, u64 base_time, u64 period) { ppu.state += cpu_flag::wait; (period ? sys_timer.warning : 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_guest_system_time(); if (period && period < 100) { // Invalid periodic timer return CELL_EINVAL; } const auto timer = idm::check( timer_id, [&](lv2_timer &timer) -> CellError { std::lock_guard lock(timer.mutex); // LV2 Disassembly: Simple nullptr check (assignment test, do not use // lv2_obj::check here) if (!timer.port) { return CELL_ENOTCONN; } timer.check_unlocked(start_time); if (timer.state != SYS_TIMER_STATE_STOP) { return CELL_EBUSY; } if (!period && start_time >= base_time) { // Invalid oneshot return CELL_ETIMEDOUT; } const u64 expire = period == 0 ? base_time : // oneshot base_time == 0 ? utils::add_saturate(start_time, period) : // periodic timer with no base (using start time as base) start_time < utils::add_saturate(base_time, period) ? utils::add_saturate(base_time, period) : // periodic with base time over start time [&]() -> u64 // periodic timer base before start time (align to // be at least a period over start time) { // Optimized from a loop in LV2: // do // { // base_time += period; // } // while (base_time < start_time); const u64 start_time_with_base_time_reminder = utils::add_saturate( start_time - start_time % period, base_time % period); return utils::add_saturate( start_time_with_base_time_reminder, start_time_with_base_time_reminder < start_time ? period : 0); }(); timer.expire = expire; timer.period = period; timer.state = SYS_TIMER_STATE_RUN; return {}; }); if (!timer) { return CELL_ESRCH; } if (timer.ret) { if (timer.ret == CELL_ETIMEDOUT) { return not_an_error(timer.ret); } return timer.ret; } g_fxo->get>()([] {}); return CELL_OK; } error_code sys_timer_stop(ppu_thread &ppu, u32 timer_id) { ppu.state += cpu_flag::wait; sys_timer.trace("sys_timer_stop()"); const auto timer = idm::check( timer_id, [now = get_guest_system_time(), notify = lv2_obj::notify_all_t()](lv2_timer &timer) { std::lock_guard lock(timer.mutex); timer.check_unlocked(now); timer.state = SYS_TIMER_STATE_STOP; }); if (!timer) { return CELL_ESRCH; } return CELL_OK; } error_code sys_timer_connect_event_queue(ppu_thread &ppu, u32 timer_id, u32 queue_id, u64 name, u64 data1, u64 data2) { ppu.state += cpu_flag::wait; sys_timer.warning("sys_timer_connect_event_queue(timer_id=0x%x, " "queue_id=0x%x, name=0x%llx, data1=0x%llx, data2=0x%llx)", timer_id, queue_id, name, data1, data2); const auto timer = idm::check( timer_id, [&](lv2_timer &timer) -> CellError { auto found = idm::get_unlocked(queue_id); if (!found) { return CELL_ESRCH; } std::lock_guard lock(timer.mutex); if (lv2_obj::check(timer.port)) { return CELL_EISCONN; } // Connect event queue timer.port = found; timer.source = name ? name : (u64{process_getpid() + 0u} << 32) | u64{timer_id}; timer.data1 = data1; timer.data2 = data2; return {}; }); if (!timer) { return CELL_ESRCH; } if (timer.ret) { return timer.ret; } return CELL_OK; } error_code sys_timer_disconnect_event_queue(ppu_thread &ppu, u32 timer_id) { ppu.state += cpu_flag::wait; sys_timer.warning("sys_timer_disconnect_event_queue(timer_id=0x%x)", timer_id); const auto timer = idm::check( timer_id, [now = get_guest_system_time(), notify = lv2_obj::notify_all_t()](lv2_timer &timer) -> CellError { std::lock_guard lock(timer.mutex); timer.check_unlocked(now); timer.state = SYS_TIMER_STATE_STOP; if (!lv2_obj::check(timer.port)) { return CELL_ENOTCONN; } timer.port.reset(); return {}; }); if (!timer) { return CELL_ESRCH; } if (timer.ret) { return timer.ret; } return CELL_OK; } error_code sys_timer_sleep(ppu_thread &ppu, u32 sleep_time) { ppu.state += cpu_flag::wait; sys_timer.trace("sys_timer_sleep(sleep_time=%d)", sleep_time); return sys_timer_usleep(ppu, sleep_time * u64{1000000}); } error_code sys_timer_usleep(ppu_thread &ppu, u64 sleep_time) { ppu.state += cpu_flag::wait; sys_timer.trace("sys_timer_usleep(sleep_time=0x%llx)", sleep_time); if (sleep_time) { const s64 add_time = g_cfg.core.usleep_addend; // Over/underflow checks if (add_time >= 0) { sleep_time = utils::add_saturate(sleep_time, add_time); } else { sleep_time = std::max(1, utils::sub_saturate(sleep_time, -add_time)); } lv2_obj::sleep(ppu, g_cfg.core.sleep_timers_accuracy < sleep_timers_accuracy_level::_usleep ? sleep_time : 0); if (!lv2_obj::wait_timeout(sleep_time, &ppu, true, true)) { ppu.state += cpu_flag::again; } } else { std::this_thread::yield(); } return CELL_OK; }