From 2476ad43b0054a8222ecb3193b242ba0aa633f48 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Fri, 3 Mar 2023 00:34:50 +0100 Subject: [PATCH 01/35] Removed unnecessary lock in the interrupt thread --- src/core/kernel/exports/EmuKrnl.h | 4 ---- src/core/kernel/init/CxbxKrnl.cpp | 7 ++----- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/core/kernel/exports/EmuKrnl.h b/src/core/kernel/exports/EmuKrnl.h index 0b8a691d3..a86a8633f 100644 --- a/src/core/kernel/exports/EmuKrnl.h +++ b/src/core/kernel/exports/EmuKrnl.h @@ -52,8 +52,6 @@ xbox::PLIST_ENTRY RemoveTailList(xbox::PLIST_ENTRY pListHead); extern xbox::LAUNCH_DATA_PAGE DefaultLaunchDataPage; extern xbox::PKINTERRUPT EmuInterruptList[MAX_BUS_INTERRUPT_LEVEL + 1]; -inline std::condition_variable g_InterruptSignal; -inline std::atomic_bool g_AnyInterruptAsserted = false; class HalSystemInterrupt { public: @@ -64,8 +62,6 @@ public: } m_Asserted = state; - g_AnyInterruptAsserted = true; - g_InterruptSignal.notify_one(); }; void Enable() { diff --git a/src/core/kernel/init/CxbxKrnl.cpp b/src/core/kernel/init/CxbxKrnl.cpp index 3643381af..8ba656e01 100644 --- a/src/core/kernel/init/CxbxKrnl.cpp +++ b/src/core/kernel/init/CxbxKrnl.cpp @@ -337,17 +337,14 @@ static xbox::void_xt NTAPI CxbxKrnlInterruptThread(xbox::PVOID param) InitSoftwareInterrupts(); #endif - std::mutex m; - std::unique_lock lock(m); while (true) { for (int i = 0; i < MAX_BUS_INTERRUPT_LEVEL; i++) { // If the interrupt is pending and connected, process it - if (HalSystemInterrupts[i].IsPending() && EmuInterruptList[i] && EmuInterruptList[i]->Connected) { + if (g_bEnableAllInterrupts && HalSystemInterrupts[i].IsPending() && EmuInterruptList[i] && EmuInterruptList[i]->Connected) { HalSystemInterrupts[i].Trigger(EmuInterruptList[i]); } } - g_InterruptSignal.wait(lock, []() { return g_AnyInterruptAsserted.load() && g_bEnableAllInterrupts.load(); }); - g_AnyInterruptAsserted = false; + _mm_pause(); } assert(0); From a3fda7b2759eb97f195ef70f00d00871a1249c99 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Thu, 2 Mar 2023 22:34:05 +0100 Subject: [PATCH 02/35] Merge lle and hle vblank routines in a single thread --- src/common/xbe/Xbe.cpp | 2 +- src/core/hle/D3D8/Direct3D9/Direct3D9.cpp | 75 ++++++++--------------- src/core/hle/D3D8/Direct3D9/Direct3D9.h | 2 +- src/core/kernel/init/CxbxKrnl.cpp | 2 +- src/devices/video/nv2a.cpp | 63 +++++++++++++------ src/devices/video/nv2a_int.h | 1 + 6 files changed, 73 insertions(+), 72 deletions(-) diff --git a/src/common/xbe/Xbe.cpp b/src/common/xbe/Xbe.cpp index 36b93ab5c..3f9c07fe3 100644 --- a/src/common/xbe/Xbe.cpp +++ b/src/common/xbe/Xbe.cpp @@ -72,7 +72,7 @@ Xbe::Xbe(const char *x_szFilename) // This is necessary because CxbxInitWindow internally calls g_AffinityPolicy->SetAffinityOther. If we are launched directly from the command line and the dashboard // cannot be opened, we will crash below because g_AffinityPolicy will be empty g_AffinityPolicy = AffinityPolicy::InitPolicy(); - CxbxInitWindow(false); + CxbxInitWindow(); ULONG FatalErrorCode = FATAL_ERROR_XBE_DASH_GENERIC; diff --git a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp index 9237c8197..04cb005c5 100644 --- a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp +++ b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp @@ -228,7 +228,6 @@ static xbox::dword_xt *g_Xbox_D3DDevice; // TODO: This should b // Static Function(s) static DWORD WINAPI EmuRenderWindow(LPVOID); static LRESULT WINAPI EmuMsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); -static xbox::void_xt NTAPI EmuUpdateTickCount(xbox::PVOID Arg); static inline void EmuVerifyResourceIsRegistered(xbox::X_D3DResource *pResource, DWORD D3DUsage, int iTextureStage, DWORD dwSize); static void UpdateCurrentMSpFAndFPS(); // Used for benchmarking/fps count static void CxbxImpl_SetRenderTarget(xbox::X_D3DSurface *pRenderTarget, xbox::X_D3DSurface *pNewZStencil); @@ -628,25 +627,13 @@ const char *D3DErrorString(HRESULT hResult) return buffer; } -void CxbxInitWindow(bool bFullInit) +void CxbxInitWindow() { g_EmuShared->GetVideoSettings(&g_XBVideo); if(g_XBVideo.bFullScreen) CxbxKrnl_hEmuParent = NULL; - // create timing thread - if (bFullInit && !bLLE_GPU) - { - xbox::HANDLE hThread; - xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, EmuUpdateTickCount, xbox::zeroptr, FALSE); - // We set the priority of this thread a bit higher, to assure reliable timing : - auto nativeHandle = GetNativeHandle(hThread); - assert(nativeHandle); - SetThreadPriority(*nativeHandle, THREAD_PRIORITY_ABOVE_NORMAL); - g_AffinityPolicy->SetAffinityOther(*nativeHandle); - } - /* TODO : Port this Dxbx code : // create vblank handling thread { @@ -1925,47 +1912,33 @@ std::chrono::steady_clock::time_point GetNextVBlankTime() return steady_clock::now() + duration_cast(ms); } -// timing thread procedure -static xbox::void_xt NTAPI EmuUpdateTickCount(xbox::PVOID Arg) +void hle_vblank() { - CxbxSetThreadName("Cxbx Timing Thread"); + // Note: This whole code block can be removed once NV2A interrupts are implemented + // And Both Swap and Present can be ran unpatched + // Once that is in place, MiniPort + Direct3D will handle this on it's own! + // Increment the VBlank Counter and Wake all threads there were waiting for the VBlank to occur + std::unique_lock lk(g_VBConditionMutex); + g_Xbox_VBlankData.VBlank++; + g_VBConditionVariable.notify_all(); - EmuLog(LOG_LEVEL::DEBUG, "Timing thread is running."); + // TODO: Fixme. This may not be right... + g_Xbox_SwapData.SwapVBlank = 1; - auto nextVBlankTime = GetNextVBlankTime(); - - while(true) - { - // Wait for VBlank - // Note: This whole code block can be removed once NV2A interrupts are implemented - // And Both Swap and Present can be ran unpatched - // Once that is in place, MiniPort + Direct3D will handle this on it's own! - SleepPrecise(nextVBlankTime); - nextVBlankTime = GetNextVBlankTime(); - - // Increment the VBlank Counter and Wake all threads there were waiting for the VBlank to occur - std::unique_lock lk(g_VBConditionMutex); - g_Xbox_VBlankData.VBlank++; - g_VBConditionVariable.notify_all(); - - // TODO: Fixme. This may not be right... - g_Xbox_SwapData.SwapVBlank = 1; - - if(g_pXbox_VerticalBlankCallback != xbox::zeroptr) - { - g_pXbox_VerticalBlankCallback(&g_Xbox_VBlankData); - } - - g_Xbox_VBlankData.Swap = 0; - - // TODO: This can't be accurate... - g_Xbox_SwapData.TimeUntilSwapVBlank = 0; - - // TODO: Recalculate this for PAL version if necessary. - // Also, we should check the D3DPRESENT_INTERVAL value for accurracy. - // g_Xbox_SwapData.TimeBetweenSwapVBlanks = 1/60; - g_Xbox_SwapData.TimeBetweenSwapVBlanks = 0; + if (g_pXbox_VerticalBlankCallback != xbox::zeroptr) + { + g_pXbox_VerticalBlankCallback(&g_Xbox_VBlankData); } + + g_Xbox_VBlankData.Swap = 0; + + // TODO: This can't be accurate... + g_Xbox_SwapData.TimeUntilSwapVBlank = 0; + + // TODO: Recalculate this for PAL version if necessary. + // Also, we should check the D3DPRESENT_INTERVAL value for accurracy. + // g_Xbox_SwapData.TimeBetweenSwapVBlanks = 1/60; + g_Xbox_SwapData.TimeBetweenSwapVBlanks = 0; } void UpdateDepthStencilFlags(IDirect3DSurface *pDepthStencilSurface) diff --git a/src/core/hle/D3D8/Direct3D9/Direct3D9.h b/src/core/hle/D3D8/Direct3D9/Direct3D9.h index aa13b1dd4..e508ff39a 100644 --- a/src/core/hle/D3D8/Direct3D9/Direct3D9.h +++ b/src/core/hle/D3D8/Direct3D9/Direct3D9.h @@ -40,7 +40,7 @@ void LookupTrampolinesD3D(); // initialize render window -extern void CxbxInitWindow(bool bFullInit); +extern void CxbxInitWindow(); void CxbxUpdateNativeD3DResources(); diff --git a/src/core/kernel/init/CxbxKrnl.cpp b/src/core/kernel/init/CxbxKrnl.cpp index 8ba656e01..bee2631cc 100644 --- a/src/core/kernel/init/CxbxKrnl.cpp +++ b/src/core/kernel/init/CxbxKrnl.cpp @@ -1344,7 +1344,7 @@ static void CxbxrKrnlInitHacks() // initialize graphics EmuLogInit(LOG_LEVEL::DEBUG, "Initializing render window."); - CxbxInitWindow(true); + CxbxInitWindow(); // Now process the boot flags to see if there are any special conditions to handle if (BootFlags & BOOT_EJECT_PENDING) {} // TODO diff --git a/src/devices/video/nv2a.cpp b/src/devices/video/nv2a.cpp index 6c0e62aa0..0f1b1d645 100644 --- a/src/devices/video/nv2a.cpp +++ b/src/devices/video/nv2a.cpp @@ -51,6 +51,7 @@ #include "core\kernel\init\CxbxKrnl.h" // For XBOX_MEMORY_SIZE, DWORD, etc #include "core\kernel\support\Emu.h" +#include "core\kernel\support\NativeHandle.h" #include "core\kernel\exports\EmuKrnl.h" #include #include @@ -58,6 +59,7 @@ #include "core\hle\Intercept.hpp" #include "common/win32/Threads.h" #include "Logging.h" +#include "Timer.h" #include "vga.h" #include "nv2a.h" // For NV2AState @@ -319,8 +321,8 @@ const NV2ABlockInfo* EmuNV2A_Block(xbox::addr_xt addr) // HACK: Until we implement VGA/proper interrupt generation // we simulate VBLANK by calling the interrupt at 60Hz -std::thread vblank_thread; extern std::chrono::steady_clock::time_point GetNextVBlankTime(); +extern void hle_vblank(); void _check_gl_reset() { @@ -1097,25 +1099,37 @@ void NV2ADevice::UpdateHostDisplay(NV2AState *d) } // TODO: Fix this properly -static void nv2a_vblank_thread(NV2AState *d) +template +static xbox::void_xt NTAPI nv2a_vblank_thread(xbox::PVOID arg) { - g_AffinityPolicy->SetAffinityOther(); - CxbxSetThreadName("Cxbx NV2A VBLANK"); + NV2AState *d = static_cast(arg); + + if constexpr (should_update_hle) { + CxbxSetThreadName("Cxbxr NV2A and HLE VBLANK"); + } + else { + g_AffinityPolicy->SetAffinityOther(); + CxbxSetThreadName("Cxbxr NV2A VBLANK"); + } + auto nextVBlankTime = GetNextVBlankTime(); - while (!d->exiting) { - // Handle VBlank - if (std::chrono::steady_clock::now() > nextVBlankTime) { - d->pcrtc.pending_interrupts |= NV_PCRTC_INTR_0_VBLANK; - update_irq(d); - nextVBlankTime = GetNextVBlankTime(); + while (!d->exiting) [[unlikely]] { - // TODO: We should swap here for the purposes of supporting overlays + direct framebuffer access - // But it causes crashes on AMD hardware for reasons currently unknown... - //NV2ADevice::UpdateHostDisplay(d); + // Wait for VBlank + SleepPrecise(nextVBlankTime); + nextVBlankTime = GetNextVBlankTime(); + + d->pcrtc.pending_interrupts |= NV_PCRTC_INTR_0_VBLANK; + update_irq(d); + + // TODO: We should swap here for the purposes of supporting overlays + direct framebuffer access + // But it causes crashes on AMD hardware for reasons currently unknown... + //NV2ADevice::UpdateHostDisplay(d); + + if constexpr (should_update_hle) { + hle_vblank(); } - - std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } @@ -1202,7 +1216,20 @@ void NV2ADevice::Init() pvideo_init(d); } - vblank_thread = std::thread(nv2a_vblank_thread, d); + + if (bLLE_GPU) { + xbox::HANDLE hThread; + xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, nv2a_vblank_thread, d, FALSE); + d->vblank_thread = *GetNativeHandle(hThread); + } + else { + xbox::HANDLE hThread; + xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, nv2a_vblank_thread, d, FALSE); + // We set the priority of this thread a bit higher, to assure reliable timing + d->vblank_thread = *GetNativeHandle(hThread); + SetThreadPriority(d->vblank_thread, THREAD_PRIORITY_ABOVE_NORMAL); + g_AffinityPolicy->SetAffinityOther(d->vblank_thread); + } qemu_mutex_init(&d->pfifo.pfifo_lock); qemu_cond_init(&d->pfifo.puller_cond); @@ -1227,9 +1254,9 @@ void NV2ADevice::Reset() qemu_cond_broadcast(&d->pfifo.pusher_cond); d->pfifo.puller_thread.join(); d->pfifo.pusher_thread.join(); - qemu_mutex_destroy(&d->pfifo.pfifo_lock); // Cbxbx addition + qemu_mutex_destroy(&d->pfifo.pfifo_lock); // Cxbxr addition if (d->pgraph.opengl_enabled) { - vblank_thread.join(); + WaitForSingleObject(d->vblank_thread, INFINITE); pvideo_destroy(d); } diff --git a/src/devices/video/nv2a_int.h b/src/devices/video/nv2a_int.h index 22630b146..72c8276c3 100644 --- a/src/devices/video/nv2a_int.h +++ b/src/devices/video/nv2a_int.h @@ -357,6 +357,7 @@ typedef struct OverlayState { } OverlayState; typedef struct NV2AState { + HANDLE vblank_thread; // PCIDevice dev; // qemu_irq irq; bool exiting; From 6d8bd340496d2a8a07b81ec8c6874e234991d4a9 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Sat, 4 Mar 2023 13:06:29 +0100 Subject: [PATCH 03/35] Merge many different periodic events in a single thread, instead of each having its own busy loop This merges vblank, ohci's eof, pit interrupt, dsound sync and async workers, nvnet packet processing and system interrupt --- src/common/Timer.cpp | 238 ++++++++---------- src/common/Timer.h | 25 +- src/common/cxbxr.cpp | 3 - .../hle/DSOUND/DirectSound/DirectSound.cpp | 84 +++---- .../DSOUND/DirectSound/DirectSoundGlobal.hpp | 3 + src/core/kernel/exports/EmuKrnl.h | 2 + src/core/kernel/init/CxbxKrnl.cpp | 71 +----- src/devices/Xbox.cpp | 16 +- src/devices/network/NVNetDevice.cpp | 15 +- src/devices/network/NVNetDevice.h | 2 + src/devices/usb/OHCI.cpp | 45 ++-- src/devices/usb/OHCI.h | 14 +- src/devices/video/nv2a.cpp | 55 ++-- src/devices/video/nv2a.h | 3 + src/devices/video/nv2a_int.h | 3 +- 15 files changed, 229 insertions(+), 350 deletions(-) diff --git a/src/common/Timer.cpp b/src/common/Timer.cpp index c9515856c..cdc8474d4 100644 --- a/src/common/Timer.cpp +++ b/src/common/Timer.cpp @@ -25,20 +25,37 @@ // * // ****************************************************************** +#include + #ifdef _WIN32 #include #endif #include #include #include +#include #include "Timer.h" #include "common\util\CxbxUtil.h" #include "core\kernel\support\EmuFS.h" -#include "core/kernel/exports/EmuKrnlPs.hpp" +#include "core\kernel\exports\EmuKrnlPs.hpp" +#include "core\kernel\exports\EmuKrnl.h" +#include "devices\Xbox.h" +#include "devices\usb\OHCI.h" +#include "core\hle\DSOUND\DirectSound\DirectSoundGlobal.hpp" #ifdef __linux__ #include #endif + +static uint64_t last_qpc; // last time when QPC was called +static uint64_t last_time; // starting time point until the next periodic event is due +static uint64_t exec_time; // total execution time in us since the emulation started +static uint64_t pit_last; // last time when the pit time was updated +static uint64_t pit_last_qpc; // last QPC time of the pit +// The frequency of the high resolution clock of the host, and the start time +int64_t HostQPCFrequency, HostQPCStartTime; + + // More precise sleep, but with increased CPU usage void SleepPrecise(std::chrono::steady_clock::time_point targetTime) { @@ -69,167 +86,116 @@ void SleepPrecise(std::chrono::steady_clock::time_point targetTime) } } -// Virtual clocks will probably become useful once LLE CPU is implemented, but for now we don't need them. -// See the QEMUClockType QEMU_CLOCK_VIRTUAL of XQEMU for more info. -#define CLOCK_REALTIME 0 -//#define CLOCK_VIRTUALTIME 1 - - -// Vector storing all the timers created -static std::vector TimerList; -// The frequency of the high resolution clock of the host, and the start time -int64_t HostQPCFrequency, HostQPCStartTime; -// Lock to acquire when accessing TimerList -std::mutex TimerMtx; - - -// Returns the current time of the timer -uint64_t GetTime_NS(TimerObject* Timer) +// NOTE: the pit device is not implemented right now, so we put these here +static void pit_interrupt() { -#ifdef _WIN32 - uint64_t Ret = Timer_GetScaledPerformanceCounter(SCALE_S_IN_NS); -#elif __linux__ - static struct timespec ts; - clock_gettime(CLOCK_MONOTONIC_RAW, &ts); - uint64_t Ret = Muldiv64(ts.tv_sec, SCALE_S_IN_NS, 1) + ts.tv_nsec; -#else -#error "Unsupported OS" -#endif - return Ret; + LARGE_INTEGER CurrentTicks; + uint64_t Delta; + uint64_t Microseconds; + unsigned int IncrementScaling; + static uint64_t Error = 0; + static uint64_t UnaccountedMicroseconds = 0; + + // This keeps track of how many us have elapsed between two cycles, so that the xbox clocks are updated + // with the proper increment (instead of blindly adding a single increment at every step) + + QueryPerformanceCounter(&CurrentTicks); + Delta = CurrentTicks.QuadPart - pit_last_qpc; + pit_last_qpc = CurrentTicks.QuadPart; + + Error += (Delta * SCALE_S_IN_US); + Microseconds = Error / HostQPCFrequency; + Error -= (Microseconds * HostQPCFrequency); + + UnaccountedMicroseconds += Microseconds; + IncrementScaling = (unsigned int)(UnaccountedMicroseconds / 1000); // -> 1 ms = 1000us -> time between two xbox clock interrupts + UnaccountedMicroseconds -= (IncrementScaling * 1000); + + xbox::KiClockIsr(IncrementScaling); } -// Calculates the next expire time of the timer -static inline uint64_t GetNextExpireTime(TimerObject* Timer) +static uint64_t pit_next(uint64_t now) { - return GetTime_NS(Timer) + Timer->ExpireTime_MS.load(); + constexpr uint64_t pit_period = 1000; + uint64_t next = pit_last + pit_period; + + if (now >= next) { + pit_interrupt(); + pit_last = get_now(); + return pit_period; + } + + return pit_last + pit_period - now; // time remaining until next clock interrupt } -// Deallocates the memory of the timer -void Timer_Destroy(TimerObject* Timer) +static void update_non_periodic_events() { - unsigned int index, i; - std::lock_guardlock(TimerMtx); - - index = TimerList.size(); - for (i = 0; i < index; i++) { - if (Timer == TimerList[i]) { - index = i; + // update dsound + dsound_worker(); + + // check nvnet + NVNetRecv(); + + // check for hw interrupts + for (int i = 0; i < MAX_BUS_INTERRUPT_LEVEL; i++) { + // If the interrupt is pending and connected, process it + if (g_bEnableAllInterrupts && HalSystemInterrupts[i].IsPending() && EmuInterruptList[i] && EmuInterruptList[i]->Connected) { + HalSystemInterrupts[i].Trigger(EmuInterruptList[i]); } } - - assert(index != TimerList.size()); - delete Timer; - TimerList.erase(TimerList.begin() + index); } -void Timer_Shutdown() +uint64_t get_now() { - unsigned i, iXboxThreads = 0; - TimerMtx.lock(); - - for (i = 0; i < TimerList.size(); i++) { - TimerObject* Timer = TimerList[i]; - // We only need to terminate host threads. - if (!Timer->IsXboxTimer) { - Timer_Exit(Timer); - } - // If the thread is xbox, we need to increment for while statement check - else { - iXboxThreads++; - } - } - - // Only perform wait for host threads, otherwise xbox threads are - // already handled within xbox kernel for shutdown process. See CxbxrKrnlSuspendThreads function. - int counter = 0; - while (iXboxThreads != TimerList.size()) { - if (counter >= 8) { - break; - } - TimerMtx.unlock(); - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - TimerMtx.lock(); - counter++; - } - TimerList.clear(); - TimerMtx.unlock(); + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + uint64_t elapsed_us = now.QuadPart - last_qpc; + last_qpc = now.QuadPart; + elapsed_us *= 1000000; + elapsed_us /= HostQPCFrequency; + exec_time += elapsed_us; + return exec_time; } -// Thread that runs the timer -void NTAPI ClockThread(void *TimerArg) +static uint64_t get_next(uint64_t now) { - TimerObject *Timer = static_cast(TimerArg); - if (!Timer->Name.empty()) { - CxbxSetThreadName(Timer->Name.c_str()); - } - if (!Timer->IsXboxTimer) { - g_AffinityPolicy->SetAffinityOther(); - } - - uint64_t NewExpireTime = GetNextExpireTime(Timer); + std::array next; + next[0] = g_NV2A->vblank_next(now); + next[1] = pit_next(now); + next[2] = g_USB0->m_HostController->OHCI_next(now); + next[3] = dsound_next(now); + return *std::min_element(next.begin(), next.end()); +} +xbox::void_xt NTAPI system_events(xbox::PVOID arg) +{ while (true) { - if (GetTime_NS(Timer) > NewExpireTime) { - if (Timer->Exit.load()) { - Timer_Destroy(Timer); - return; + const uint64_t nearest_next = get_next(get_now()); + QueryPerformanceCounter(reinterpret_cast(&last_time)); + + while (true) { + update_non_periodic_events(); + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + uint64_t elapsed_us = static_cast(now.QuadPart) - last_time; + elapsed_us *= 1000000; + elapsed_us /= HostQPCFrequency; + if (elapsed_us >= nearest_next) { + break; } - Timer->Callback(Timer->Opaque); - NewExpireTime = GetNextExpireTime(Timer); + _mm_pause(); } - Sleep(1); // prevent burning the cpu - } -} - -// Changes the expire time of a timer -void Timer_ChangeExpireTime(TimerObject* Timer, uint64_t Expire_ms) -{ - Timer->ExpireTime_MS.store(Expire_ms); -} - -// Destroys the timer -void Timer_Exit(TimerObject* Timer) -{ - Timer->Exit.store(true); -} - -// Allocates the memory for the timer object -TimerObject* Timer_Create(TimerCB Callback, void* Arg, std::string Name, bool IsXboxTimer) -{ - std::lock_guardlock(TimerMtx); - TimerObject* pTimer = new TimerObject; - pTimer->Type = CLOCK_REALTIME; - pTimer->Callback = Callback; - pTimer->ExpireTime_MS.store(0); - pTimer->Exit.store(false); - pTimer->Opaque = Arg; - pTimer->Name = Name.empty() ? "Unnamed thread" : std::move(Name); - pTimer->IsXboxTimer = IsXboxTimer; - TimerList.emplace_back(pTimer); - - return pTimer; -} - -// Starts the timer -// Expire_MS must be expressed in NS -void Timer_Start(TimerObject* Timer, uint64_t Expire_MS) -{ - Timer->ExpireTime_MS.store(Expire_MS); - if (Timer->IsXboxTimer) { - xbox::HANDLE hThread; - CxbxrCreateThread(&hThread, xbox::zeroptr, ClockThread, Timer, FALSE); - } - else { - std::thread(ClockThread, Timer).detach(); } } // Retrives the frequency of the high resolution clock of the host -void Timer_Init() +void timer_init() { #ifdef _WIN32 QueryPerformanceFrequency(reinterpret_cast(&HostQPCFrequency)); - QueryPerformanceCounter(reinterpret_cast(&HostQPCStartTime)); + QueryPerformanceCounter(reinterpret_cast(&last_qpc)); + pit_last_qpc = last_qpc; + pit_last = get_now(); #elif __linux__ ClockFrequency = 0; #else diff --git a/src/common/Timer.h b/src/common/Timer.h index 007c75536..5096c3ff3 100644 --- a/src/common/Timer.h +++ b/src/common/Timer.h @@ -40,33 +40,12 @@ #define SCALE_MS_IN_US 1000 #define SCALE_US_IN_US 1 -/* typedef of the timer object and the callback function */ -typedef void(*TimerCB)(void*); -typedef struct _TimerObject -{ - int Type; // timer type - std::atomic_uint64_t ExpireTime_MS; // when the timer expires (ms) - std::atomic_bool Exit; // indicates that the timer should be destroyed - TimerCB Callback; // function to call when the timer expires - void* Opaque; // opaque argument to pass to the callback - std::string Name; // the name of the timer thread (if any) - bool IsXboxTimer; // indicates that the timer should run on the Xbox CPU -} -TimerObject; extern int64_t HostQPCFrequency; -/* Timer exported functions */ -TimerObject* Timer_Create(TimerCB Callback, void* Arg, std::string Name, bool IsXboxTimer); -void Timer_Start(TimerObject* Timer, uint64_t Expire_MS); -void Timer_Exit(TimerObject* Timer); -void Timer_ChangeExpireTime(TimerObject* Timer, uint64_t Expire_ms); -uint64_t GetTime_NS(TimerObject* Timer); -void Timer_Init(); -void Timer_Shutdown(); - +void timer_init(); +uint64_t get_now(); int64_t Timer_GetScaledPerformanceCounter(int64_t Period); - void SleepPrecise(std::chrono::steady_clock::time_point targetTime); #endif diff --git a/src/common/cxbxr.cpp b/src/common/cxbxr.cpp index 50e651069..37042e4cc 100644 --- a/src/common/cxbxr.cpp +++ b/src/common/cxbxr.cpp @@ -121,9 +121,6 @@ bool HandleFirstLaunch() } // NOTE: Require to be after g_renderbase's shutdown process. - // Next thing we need to do is shutdown our timer threads. - Timer_Shutdown(); - // NOTE: Must be last step of shutdown process and before CxbxUnlockFilePath call! // Shutdown the memory manager g_VMManager.Shutdown(); diff --git a/src/core/hle/DSOUND/DirectSound/DirectSound.cpp b/src/core/hle/DSOUND/DirectSound/DirectSound.cpp index f87daaad1..4cd5b087a 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSound.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSound.cpp @@ -53,6 +53,7 @@ // TODO: Move these to LLE APUDevice once we have one! static constexpr uint32_t APU_TIMER_FREQUENCY = 48000; +static uint64_t dsound_last; uint32_t GetAPUTime() { @@ -85,10 +86,6 @@ uint32_t GetAPUTime() there is chance of failure which contain value greater than 0. */ -// Managed memory xbox audio variables -static std::thread dsound_thread; -static void dsound_thread_worker(LPVOID); - #include "DirectSoundInline.hpp" #ifdef __cplusplus @@ -98,6 +95,7 @@ extern "C" { void CxbxInitAudio() { g_EmuShared->GetAudioSettings(&g_XBAudio); + dsound_last = get_now(); } #ifdef __cplusplus @@ -125,10 +123,6 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(DirectSoundCreate) HRESULT hRet = DS_OK; - if (!initialized) { - dsound_thread = std::thread(dsound_thread_worker, nullptr); - } - // Set this flag when this function is called g_bDSoundCreateCalled = TRUE; @@ -454,51 +448,51 @@ void StreamBufferAudio(xbox::XbHybridDSBuffer* pHybridBuffer, float msToCopy) { } } -static void dsound_thread_worker(LPVOID nullPtr) +void dsound_async_worker() { - g_AffinityPolicy->SetAffinityOther(); + DSoundMutexGuardLock; - const int dsStreamInterval = 300; - int waitCounter = 0; + xbox::LARGE_INTEGER getTime; + xbox::KeQuerySystemTime(&getTime); + DirectSoundDoWork_Stream(getTime); +} - while (true) { - // FIXME time this loop more accurately - // and account for variation in the length of Sleep calls +void dsound_worker() +{ + // Testcase: Gauntlet Dark Legacy, if Sleep(1) then intro videos start to starved often + // unless console is open with logging enabled. This is the cause of stopping intro videos often. - // Testcase: Gauntlet Dark Legacy, if Sleep(1) then intro videos start to starved often - // unless console is open with logging enabled. This is the cause of stopping intro videos often. - Sleep(g_dsBufferStreaming.streamInterval); - waitCounter += g_dsBufferStreaming.streamInterval; + // Enforce mutex guard lock only occur inside below bracket for proper compile build. + DSoundMutexGuardLock; - // Enforce mutex guard lock only occur inside below bracket for proper compile build. - { - DSoundMutexGuardLock; - - if (waitCounter > dsStreamInterval) { - waitCounter = 0; - - // For Async process purpose only - xbox::LARGE_INTEGER getTime; - xbox::KeQuerySystemTime(&getTime); - DirectSoundDoWork_Stream(getTime); + // Stream sound buffer audio + // because the title may change the content of sound buffers at any time + for (auto& pBuffer : g_pDSoundBufferCache) { + // Avoid expensive calls to DirectSound on buffers unless they've been played at least once + // Since some titles create a large amount of buffers, but only use a few + if (pBuffer->emuDSBuffer->EmuStreamingInfo.playRequested) { + DWORD status; + HRESULT hRet = pBuffer->emuDSBuffer->EmuDirectSoundBuffer8->GetStatus(&status); + if (hRet == 0 && status & DSBSTATUS_PLAYING) { + auto streamMs = g_dsBufferStreaming.streamInterval + g_dsBufferStreaming.streamAhead; + StreamBufferAudio(pBuffer, streamMs); } + } + } +} - // Stream sound buffer audio - // because the title may change the content of sound buffers at any time - for (auto& pBuffer : g_pDSoundBufferCache) { - // Avoid expensive calls to DirectSound on buffers unless they've been played at least once - // Since some titles create a large amount of buffers, but only use a few - if (pBuffer->emuDSBuffer->EmuStreamingInfo.playRequested) { - DWORD status; - HRESULT hRet = pBuffer->emuDSBuffer->EmuDirectSoundBuffer8->GetStatus(&status); - if (hRet == 0 && status & DSBSTATUS_PLAYING) { - auto streamMs = g_dsBufferStreaming.streamInterval + g_dsBufferStreaming.streamAhead; - StreamBufferAudio(pBuffer, streamMs); - } - } - } - } +uint64_t dsound_next(uint64_t now) +{ + constexpr uint64_t dsound_period = 300 * 1000; + uint64_t next = dsound_last + dsound_period; + + if (now >= next) { + dsound_async_worker(); + dsound_last = get_now(); + return dsound_period; } + + return dsound_last + dsound_period - now; // time remaining until next dsound async event } // Kismet given name for RadWolfie's experiment major issue in the mutt. diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp index 23e650ed5..289a92da3 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp @@ -81,3 +81,6 @@ extern DsBufferStreaming g_dsBufferStreaming; extern void DirectSoundDoWork_Buffer(xbox::LARGE_INTEGER& time); extern void DirectSoundDoWork_Stream(xbox::LARGE_INTEGER& time); +extern void dsound_async_worker(); +extern void dsound_worker(); +extern uint64_t dsound_next(uint64_t now); diff --git a/src/core/kernel/exports/EmuKrnl.h b/src/core/kernel/exports/EmuKrnl.h index a86a8633f..d68e18a9c 100644 --- a/src/core/kernel/exports/EmuKrnl.h +++ b/src/core/kernel/exports/EmuKrnl.h @@ -52,6 +52,8 @@ xbox::PLIST_ENTRY RemoveTailList(xbox::PLIST_ENTRY pListHead); extern xbox::LAUNCH_DATA_PAGE DefaultLaunchDataPage; extern xbox::PKINTERRUPT EmuInterruptList[MAX_BUS_INTERRUPT_LEVEL + 1]; +// Indicates to disable/enable all interrupts when cli and sti instructions are executed +inline std::atomic_bool g_bEnableAllInterrupts = true; class HalSystemInterrupt { public: diff --git a/src/core/kernel/init/CxbxKrnl.cpp b/src/core/kernel/init/CxbxKrnl.cpp index bee2631cc..3ed6d83ae 100644 --- a/src/core/kernel/init/CxbxKrnl.cpp +++ b/src/core/kernel/init/CxbxKrnl.cpp @@ -99,9 +99,6 @@ bool g_bIsChihiro = false; bool g_bIsDevKit = false; bool g_bIsRetail = false; -// Indicates to disable/enable all interrupts when cli and sti instructions are executed -std::atomic_bool g_bEnableAllInterrupts = true; - // Set by the VMManager during initialization. Exported because it's needed in other parts of the emu size_t g_SystemMaxMemory = 0; @@ -113,6 +110,8 @@ ULONG g_CxbxFatalErrorCode = FATAL_ERROR_NONE; // Define function located in EmuXApi so we can call it from here void SetupXboxDeviceTypes(); +extern xbox::void_xt NTAPI system_events(xbox::PVOID arg); + void SetupPerTitleKeys() { // Generate per-title keys from the XBE Certificate @@ -329,61 +328,6 @@ void InitSoftwareInterrupts() } #endif -static xbox::void_xt NTAPI CxbxKrnlInterruptThread(xbox::PVOID param) -{ - CxbxSetThreadName("CxbxKrnl Interrupts"); - -#if 0 - InitSoftwareInterrupts(); -#endif - - while (true) { - for (int i = 0; i < MAX_BUS_INTERRUPT_LEVEL; i++) { - // If the interrupt is pending and connected, process it - if (g_bEnableAllInterrupts && HalSystemInterrupts[i].IsPending() && EmuInterruptList[i] && EmuInterruptList[i]->Connected) { - HalSystemInterrupts[i].Trigger(EmuInterruptList[i]); - } - } - _mm_pause(); - } - - assert(0); -} - -static void CxbxKrnlClockThread(void* pVoid) -{ - LARGE_INTEGER CurrentTicks; - uint64_t Delta; - uint64_t Microseconds; - unsigned int IncrementScaling; - static uint64_t LastTicks = 0; - static uint64_t Error = 0; - static uint64_t UnaccountedMicroseconds = 0; - - // This keeps track of how many us have elapsed between two cycles, so that the xbox clocks are updated - // with the proper increment (instead of blindly adding a single increment at every step) - - if (LastTicks == 0) { - QueryPerformanceCounter(&CurrentTicks); - LastTicks = CurrentTicks.QuadPart; - CurrentTicks.QuadPart = 0; - } - - QueryPerformanceCounter(&CurrentTicks); - Delta = CurrentTicks.QuadPart - LastTicks; - LastTicks = CurrentTicks.QuadPart; - - Error += (Delta * SCALE_S_IN_US); - Microseconds = Error / HostQPCFrequency; - Error -= (Microseconds * HostQPCFrequency); - - UnaccountedMicroseconds += Microseconds; - IncrementScaling = (unsigned int)(UnaccountedMicroseconds / 1000); // -> 1 ms = 1000us -> time between two xbox clock interrupts - UnaccountedMicroseconds -= (IncrementScaling * 1000); - - xbox::KiClockIsr(IncrementScaling); -} - void MapThunkTable(uint32_t* kt, uint32_t* pThunkTable) { const bool SendDebugReports = (pThunkTable == CxbxKrnl_KernelThunkTable) && CxbxDebugger::CanReport(); @@ -1199,7 +1143,7 @@ static void CxbxrKrnlInitHacks() g_pCertificate = &CxbxKrnl_Xbe->m_Certificate; // Initialize timer subsystem - Timer_Init(); + timer_init(); // for unicode conversions setlocale(LC_ALL, "English"); // Initialize DPC global @@ -1453,13 +1397,10 @@ static void CxbxrKrnlInitHacks() #endif EmuX86_Init(); - // Create the interrupt processing thread + // Start the event thread xbox::HANDLE hThread; - CxbxrCreateThread(&hThread, xbox::zeroptr, CxbxKrnlInterruptThread, xbox::zeroptr, FALSE); - // Start the kernel clock thread - TimerObject* KernelClockThr = Timer_Create(CxbxKrnlClockThread, nullptr, "Kernel clock thread", true); - Timer_Start(KernelClockThr, SCALE_MS_IN_NS); - + xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, system_events, xbox::zeroptr, FALSE); + // Launch the xbe xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, CxbxLaunchXbe, Entry, FALSE); EmuKeFreePcr(); diff --git a/src/devices/Xbox.cpp b/src/devices/Xbox.cpp index 68b21371e..b40380fb7 100644 --- a/src/devices/Xbox.cpp +++ b/src/devices/Xbox.cpp @@ -125,9 +125,7 @@ void InitXboxHardware(HardwareModel hardwareModel) g_NVNet = new NVNetDevice(); g_NV2A = new NV2ADevice(); g_ADM1032 = new ADM1032Device(); - if (bLLE_USB) { - g_USB0 = new USBDevice(); - } + g_USB0 = new USBDevice(); // Connect devices to SM bus g_SMBus->ConnectDevice(SMBUS_ADDRESS_SYSTEM_MICRO_CONTROLLER, g_SMC); // W 0x20 R 0x21 @@ -156,14 +154,12 @@ void InitXboxHardware(HardwareModel hardwareModel) //g_PCIBus->ConnectDevice(PCI_DEVID(0, PCI_DEVFN(5, 0)), g_NVAPU); //g_PCIBus->ConnectDevice(PCI_DEVID(0, PCI_DEVFN(6, 0)), g_AC97); g_PCIBus->ConnectDevice(PCI_DEVID(1, PCI_DEVFN(0, 0)), g_NV2A); - if (bLLE_USB) { - // ergo720: according to some research done by LukeUsher, only Xbox Alpha Kits have a two HCs configuration. This seems to also be confirmed by the xboxdevwiki, - // which states that it has a xircom PGPCI2(OPTI 82C861) 2 USB port PCI card -> 2 ports, not 4. Finally, I disassembled various xbe's and discovered that the number - // of ports per HC is hardcoded as 4 in the driver instead of being detected at runtime by reading the HcRhDescriptorA register and so a game would have to be - // recompiled to support 2 HCs, which further confirms the point. Because we are not going to emulate an Alpha Kit, we can simply ignore the USB1 device. + // ergo720: according to some research done by LukeUsher, only Xbox Alpha Kits have a two HCs configuration. This seems to also be confirmed by the xboxdevwiki, + // which states that it has a xircom PGPCI2(OPTI 82C861) 2 USB port PCI card -> 2 ports, not 4. Finally, I disassembled various xbe's and discovered that the number + // of ports per HC is hardcoded as 4 in the driver instead of being detected at runtime by reading the HcRhDescriptorA register and so a game would have to be + // recompiled to support 2 HCs, which further confirms the point. Because we are not going to emulate an Alpha Kit, we can simply ignore the USB1 device. - g_PCIBus->ConnectDevice(PCI_DEVID(0, PCI_DEVFN(2, 0)), g_USB0); - } + g_PCIBus->ConnectDevice(PCI_DEVID(0, PCI_DEVFN(2, 0)), g_USB0); // TODO : Handle other SMBUS Addresses, like PIC_ADDRESS, XCALIBUR_ADDRESS // Resources : http://pablot.com/misc/fancontroller.cpp diff --git a/src/devices/network/NVNetDevice.cpp b/src/devices/network/NVNetDevice.cpp index 10bde6a27..72e405c22 100644 --- a/src/devices/network/NVNetDevice.cpp +++ b/src/devices/network/NVNetDevice.cpp @@ -475,16 +475,12 @@ void EmuNVNet_Write(xbox::addr_xt addr, uint32_t value, int size) EmuLog(LOG_LEVEL::DEBUG, "Write%d: %s (0x%.8X) = 0x%.8X", size * 8, EmuNVNet_GetRegisterName(addr), addr, value); } -std::thread NVNetRecvThread; -static void NVNetRecvThreadProc(NvNetState_t *s) +void NVNetRecv() { - g_AffinityPolicy->SetAffinityOther(); - uint8_t packet[65536]; - while (true) { - int size = g_NVNet->PCAPReceive(packet, 65536); - if (size > 0) { - EmuNVNet_DMAPacketToGuest(packet, size); - } + static std::unique_ptr packet(new uint8_t[65536]); + int size = g_NVNet->PCAPReceive(packet.get(), 65536); + if (size > 0) { + EmuNVNet_DMAPacketToGuest(packet.get(), size); } } @@ -527,7 +523,6 @@ void NVNetDevice::Init() }; PCAPInit(); - NVNetRecvThread = std::thread(NVNetRecvThreadProc, &NvNetState); } void NVNetDevice::Reset() diff --git a/src/devices/network/NVNetDevice.h b/src/devices/network/NVNetDevice.h index d2a069396..f7a1c3e97 100644 --- a/src/devices/network/NVNetDevice.h +++ b/src/devices/network/NVNetDevice.h @@ -229,3 +229,5 @@ private: mac_address m_GuestMacAddress = { 0x00, 0x50, 0xF2, 0x00, 0x00, 0x34 }; mac_address m_BroadcastMacAddress = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; }; + +void NVNetRecv(); diff --git a/src/devices/usb/OHCI.cpp b/src/devices/usb/OHCI.cpp index 875aae5d1..3e0253ba2 100644 --- a/src/devices/usb/OHCI.cpp +++ b/src/devices/usb/OHCI.cpp @@ -279,11 +279,6 @@ OHCI::OHCI(USBDevice* UsbObj) OHCI_StateReset(); } -void OHCI::OHCI_FrameBoundaryWrapper(void* pVoid) -{ - static_cast(pVoid)->OHCI_FrameBoundaryWorker(); -} - void OHCI::OHCI_FrameBoundaryWorker() { OHCI_HCCA hcca; @@ -358,7 +353,7 @@ void OHCI::OHCI_FrameBoundaryWorker() } // Do SOF stuff here - OHCI_SOF(false); + OHCI_SOF(); // Writeback HCCA if (OHCI_WriteHCCA(m_Registers.HcHCCA, &hcca)) { @@ -877,32 +872,23 @@ void OHCI::OHCI_StateReset() void OHCI::OHCI_BusStart() { // Create the EOF timer. - m_pEOFtimer = Timer_Create(OHCI_FrameBoundaryWrapper, this, "", false); + m_pEOFtimer = true; EmuLog(LOG_LEVEL::DEBUG, "Operational event"); // SOF event - OHCI_SOF(true); + OHCI_SOF(); } void OHCI::OHCI_BusStop() { - if (m_pEOFtimer) { - // Delete existing EOF timer - Timer_Exit(m_pEOFtimer); - } - m_pEOFtimer = nullptr; + m_pEOFtimer = false; } -void OHCI::OHCI_SOF(bool bCreate) +void OHCI::OHCI_SOF() { // set current SOF time - m_SOFtime = GetTime_NS(m_pEOFtimer); - - // make timer expire at SOF + 1 ms from now - if (bCreate) { - Timer_Start(m_pEOFtimer, m_UsbFrameTime); - } + m_SOFtime = get_now(); OHCI_SetInterrupt(OHCI_INTR_SF); } @@ -1254,6 +1240,23 @@ void OHCI::OHCI_WriteRegister(xbox::addr_xt Addr, uint32_t Value) } } +uint64_t OHCI::OHCI_next(uint64_t now) +{ + if (m_pEOFtimer) { + constexpr uint64_t ohci_period = 1000; + uint64_t next = m_SOFtime + ohci_period; + + if (now >= next) { + OHCI_FrameBoundaryWorker(); + return ohci_period; + } + + return m_SOFtime + ohci_period - now; // time remaining until EOF + } + + return -1; +} + void OHCI::OHCI_UpdateInterrupt() { if ((m_Registers.HcInterrupt & OHCI_INTR_MIE) && (m_Registers.HcInterruptStatus & m_Registers.HcInterrupt)) { @@ -1278,7 +1281,7 @@ uint32_t OHCI::OHCI_GetFrameRemaining() } // Being in USB operational state guarantees that m_pEOFtimer and m_SOFtime were set already - ticks = GetTime_NS(m_pEOFtimer) - m_SOFtime; + ticks = get_now() - m_SOFtime; // Avoid Muldiv64 if possible if (ticks >= m_UsbFrameTime) { diff --git a/src/devices/usb/OHCI.h b/src/devices/usb/OHCI.h index 92119ba0f..3555667df 100644 --- a/src/devices/usb/OHCI.h +++ b/src/devices/usb/OHCI.h @@ -145,6 +145,10 @@ class OHCI uint32_t OHCI_ReadRegister(xbox::addr_xt Addr); // write a register void OHCI_WriteRegister(xbox::addr_xt Addr, uint32_t Value); + // calculates when the next EOF is due + uint64_t OHCI_next(uint64_t now); + // EOF callback function + void OHCI_FrameBoundaryWorker(); private: @@ -153,7 +157,7 @@ class OHCI // all the registers available in the OHCI standard OHCI_Registers m_Registers; // end-of-frame timer - TimerObject* m_pEOFtimer = nullptr; + bool m_pEOFtimer = false; // time at which a SOF was sent uint64_t m_SOFtime; // the duration of a usb frame @@ -173,10 +177,6 @@ class OHCI // indicates if there is a pending asynchronous packet to process int m_AsyncComplete = 0; - // EOF callback wrapper - static void OHCI_FrameBoundaryWrapper(void* pVoid); - // EOF callback function - void OHCI_FrameBoundaryWorker(); // inform the HCD that we got a problem here... void OHCI_FatalError(); // initialize packet struct @@ -189,8 +189,8 @@ class OHCI void OHCI_BusStart(); // stop sending SOF tokens across the usb bus void OHCI_BusStop(); - // generate a SOF event, and start a timer for EOF - void OHCI_SOF(bool bCreate); + // generate a SOF event + void OHCI_SOF(); // change interrupt status void OHCI_UpdateInterrupt(); // fire an interrupt diff --git a/src/devices/video/nv2a.cpp b/src/devices/video/nv2a.cpp index 0f1b1d645..2200a4acc 100644 --- a/src/devices/video/nv2a.cpp +++ b/src/devices/video/nv2a.cpp @@ -1100,29 +1100,19 @@ void NV2ADevice::UpdateHostDisplay(NV2AState *d) // TODO: Fix this properly template -static xbox::void_xt NTAPI nv2a_vblank_thread(xbox::PVOID arg) +void nv2a_vblank_interrupt(void *opaque) { - NV2AState *d = static_cast(arg); - - if constexpr (should_update_hle) { - CxbxSetThreadName("Cxbxr NV2A and HLE VBLANK"); - } - else { - g_AffinityPolicy->SetAffinityOther(); - CxbxSetThreadName("Cxbxr NV2A VBLANK"); - } - - auto nextVBlankTime = GetNextVBlankTime(); - - while (!d->exiting) [[unlikely]] { - - // Wait for VBlank - SleepPrecise(nextVBlankTime); - nextVBlankTime = GetNextVBlankTime(); + NV2AState *d = static_cast(opaque); + if (!d->exiting) [[likely]] { d->pcrtc.pending_interrupts |= NV_PCRTC_INTR_0_VBLANK; update_irq(d); + // trigger the gpu interrupt if it was asserted in update_irq + if (g_bEnableAllInterrupts && HalSystemInterrupts[3].IsPending() && EmuInterruptList[3] && EmuInterruptList[3]->Connected) { + HalSystemInterrupts[3].Trigger(EmuInterruptList[3]); + } + // TODO: We should swap here for the purposes of supporting overlays + direct framebuffer access // But it causes crashes on AMD hardware for reasons currently unknown... //NV2ADevice::UpdateHostDisplay(d); @@ -1216,19 +1206,12 @@ void NV2ADevice::Init() pvideo_init(d); } - + d->vblank_last = get_now(); if (bLLE_GPU) { - xbox::HANDLE hThread; - xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, nv2a_vblank_thread, d, FALSE); - d->vblank_thread = *GetNativeHandle(hThread); + d->vblank_cb = nv2a_vblank_interrupt; } else { - xbox::HANDLE hThread; - xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, nv2a_vblank_thread, d, FALSE); - // We set the priority of this thread a bit higher, to assure reliable timing - d->vblank_thread = *GetNativeHandle(hThread); - SetThreadPriority(d->vblank_thread, THREAD_PRIORITY_ABOVE_NORMAL); - g_AffinityPolicy->SetAffinityOther(d->vblank_thread); + d->vblank_cb = nv2a_vblank_interrupt; } qemu_mutex_init(&d->pfifo.pfifo_lock); @@ -1256,7 +1239,6 @@ void NV2ADevice::Reset() d->pfifo.pusher_thread.join(); qemu_mutex_destroy(&d->pfifo.pfifo_lock); // Cxbxr addition if (d->pgraph.opengl_enabled) { - WaitForSingleObject(d->vblank_thread, INFINITE); pvideo_destroy(d); } @@ -1404,3 +1386,18 @@ int NV2ADevice::GetFrameWidth(NV2AState* d) return width; } + +uint64_t NV2ADevice::vblank_next(uint64_t now) +{ + // TODO: this should use a vblank period of 20ms when we are in 50Hz PAL mode + constexpr uint64_t vblank_period = 16.6666666667 * 1000; + uint64_t next = m_nv2a_state->vblank_last + vblank_period; + + if (now >= next) { + m_nv2a_state->vblank_cb(m_nv2a_state); + m_nv2a_state->vblank_last = get_now(); + return vblank_period; + } + + return m_nv2a_state->vblank_last + vblank_period - now; // time remaining until next vblank +} diff --git a/src/devices/video/nv2a.h b/src/devices/video/nv2a.h index 43b64c052..881a16acd 100644 --- a/src/devices/video/nv2a.h +++ b/src/devices/video/nv2a.h @@ -108,6 +108,9 @@ public: static int GetFrameWidth(NV2AState *d); static int GetFrameHeight(NV2AState *d); + + uint64_t vblank_next(uint64_t now); + private: NV2AState *m_nv2a_state; }; diff --git a/src/devices/video/nv2a_int.h b/src/devices/video/nv2a_int.h index 72c8276c3..ffeb539bb 100644 --- a/src/devices/video/nv2a_int.h +++ b/src/devices/video/nv2a_int.h @@ -357,7 +357,8 @@ typedef struct OverlayState { } OverlayState; typedef struct NV2AState { - HANDLE vblank_thread; + void(* vblank_cb)(void *); + uint64_t vblank_last; // PCIDevice dev; // qemu_irq irq; bool exiting; From d11a5e8773eba679e1c35f4a677351b6fc7b0f26 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Sat, 4 Mar 2023 16:02:36 +0100 Subject: [PATCH 04/35] Fixed a bug in KeSetBasePriorityThread --- src/core/kernel/exports/EmuKrnlKe.cpp | 28 ++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 8bbfbc8d5..05f021b89 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -1805,8 +1805,8 @@ XBSYSAPI EXPORTNUM(143) xbox::long_xt NTAPI xbox::KeSetBasePriorityThread ) { LOG_FUNC_BEGIN - LOG_FUNC_ARG_OUT(Thread) - LOG_FUNC_ARG_OUT(Priority) + LOG_FUNC_ARG(Thread) + LOG_FUNC_ARG(Priority) LOG_FUNC_END; KIRQL oldIRQL; @@ -1814,20 +1814,22 @@ XBSYSAPI EXPORTNUM(143) xbox::long_xt NTAPI xbox::KeSetBasePriorityThread // It cannot fail because all thread handles are created by ob const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread); - LONG ret = GetThreadPriority(*nativeHandle); - // This would work normally, but it will slow down the emulation, - // don't do that if the priority is higher then normal (so our own)! - if (Priority <= THREAD_PRIORITY_NORMAL) { - BOOL result = SetThreadPriority(*nativeHandle, Priority); - if (!result) { - EmuLog(LOG_LEVEL::WARNING, "SetThreadPriority failed: %s", WinError2Str().c_str()); - } + if (Priority == 16) { + Priority = THREAD_PRIORITY_TIME_CRITICAL; + } + else if (Priority == -16) { + Priority = THREAD_PRIORITY_IDLE; + } + + BOOL result = SetThreadPriority(*nativeHandle, Priority); + if (!result) { + EmuLog(LOG_LEVEL::WARNING, "SetThreadPriority failed: %s", WinError2Str().c_str()); } KiUnlockDispatcherDatabase(oldIRQL); - RETURN(ret); + RETURN(result); } XBSYSAPI EXPORTNUM(144) xbox::boolean_xt NTAPI xbox::KeSetDisableBoostThread @@ -1989,8 +1991,8 @@ XBSYSAPI EXPORTNUM(148) xbox::boolean_xt NTAPI xbox::KeSetPriorityThread ) { LOG_FUNC_BEGIN - LOG_FUNC_ARG_OUT(Thread) - LOG_FUNC_ARG_OUT(Priority) + LOG_FUNC_ARG(Thread) + LOG_FUNC_ARG(Priority) LOG_FUNC_END; LOG_UNIMPLEMENTED(); From c7c107720ed7e3be652b3c3cacfbd473ecd9d0ea Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Sun, 5 Mar 2023 00:03:57 +0100 Subject: [PATCH 05/35] Implemented suspend/resume kernel Nt routines with the corresponding Ke routines --- src/core/kernel/common/types.h | 4 ++ src/core/kernel/exports/EmuKrnlKe.cpp | 69 +++++++++++++-------------- src/core/kernel/exports/EmuKrnlKi.cpp | 62 ++++++++++++++++++++++-- src/core/kernel/exports/EmuKrnlKi.h | 18 ++++++- src/core/kernel/exports/EmuKrnlNt.cpp | 52 ++++++++++++++------ src/core/kernel/exports/EmuKrnlPs.cpp | 8 +++- 6 files changed, 155 insertions(+), 58 deletions(-) diff --git a/src/core/kernel/common/types.h b/src/core/kernel/common/types.h index ab29c23dd..c3e192c1f 100644 --- a/src/core/kernel/common/types.h +++ b/src/core/kernel/common/types.h @@ -98,6 +98,8 @@ typedef void* LPSECURITY_ATTRIBUTES; #define X_STATUS_FILE_IS_A_DIRECTORY 0xC00000BAL #define X_STATUS_END_OF_FILE 0xC0000011L #define X_STATUS_INVALID_PAGE_PROTECTION 0xC0000045L +#define X_STATUS_SUSPEND_COUNT_EXCEEDED 0xC000004AL +#define X_STATUS_THREAD_IS_TERMINATING 0xC000004BL #define X_STATUS_CONFLICTING_ADDRESSES 0xC0000018L #define X_STATUS_UNABLE_TO_FREE_VM 0xC000001AL #define X_STATUS_FREE_VM_NOT_AT_BASE 0xC000009FL @@ -1969,6 +1971,8 @@ typedef struct _KTHREAD } KTHREAD, *PKTHREAD, *RESTRICTED_POINTER PRKTHREAD; +#define X_MAXIMUM_SUSPEND_COUNT 0x7F + // ****************************************************************** // * ETHREAD // ****************************************************************** diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 05f021b89..d5b8f48ba 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -1206,35 +1206,9 @@ XBSYSAPI EXPORTNUM(118) xbox::boolean_xt NTAPI xbox::KeInsertQueueApc Apc->SystemArgument1 = SystemArgument1; Apc->SystemArgument2 = SystemArgument2; - if (Apc->Inserted) { - KfLowerIrql(OldIrql); - RETURN(FALSE); - } - else { - KiApcListMtx.lock(); - InsertTailList(&kThread->ApcState.ApcListHead[Apc->ApcMode], &Apc->ApcListEntry); - Apc->Inserted = TRUE; - KiApcListMtx.unlock(); - - // We can only attempt to execute the queued apc right away if it is been inserted in the current thread, because otherwise the KTHREAD - // in the fs selector will not be correct - if (kThread == KeGetCurrentThread()) { - if (Apc->ApcMode == KernelMode) { // kernel apc - // NOTE: this is wrong, we should check the thread state instead of just signaling the kernel apc, but we currently - // don't set the appropriate state in kthread - kThread->ApcState.KernelApcPending = TRUE; - KiExecuteKernelApc(); - } - else if ((kThread->WaitMode == UserMode) && (kThread->Alertable)) { // user apc - // NOTE: this should also check the thread state - kThread->ApcState.UserApcPending = TRUE; - KiExecuteUserApc(); - } - } - - KfLowerIrql(OldIrql); - RETURN(TRUE); - } + boolean_xt result = KiInsertQueueApc(Apc, Increment); + KfLowerIrql(OldIrql); + RETURN(result); } } @@ -1759,11 +1733,20 @@ XBSYSAPI EXPORTNUM(140) xbox::ulong_xt NTAPI xbox::KeResumeThread { LOG_FUNC_ONE_ARG(Thread); - NTSTATUS ret = X_STATUS_SUCCESS; + KIRQL OldIrql; + KiLockDispatcherDatabase(&OldIrql); - LOG_UNIMPLEMENTED(); + char_xt OldCount = Thread->SuspendCount; + if (OldCount != 0) { + --Thread->SuspendCount; + if (Thread->SuspendCount == 0) { + ++Thread->SuspendSemaphore.Header.SignalState; + } + } - RETURN(ret); + KiUnlockDispatcherDatabase(OldIrql); + + RETURN(OldCount); } XBSYSAPI EXPORTNUM(141) xbox::PLIST_ENTRY NTAPI xbox::KeRundownQueue @@ -2105,11 +2088,27 @@ XBSYSAPI EXPORTNUM(152) xbox::ulong_xt NTAPI xbox::KeSuspendThread { LOG_FUNC_ONE_ARG(Thread); - NTSTATUS ret = X_STATUS_SUCCESS; + KIRQL OldIrql; + KiLockDispatcherDatabase(&OldIrql); - LOG_UNIMPLEMENTED(); + char_xt OldCount = Thread->SuspendCount; + if (OldCount == X_MAXIMUM_SUSPEND_COUNT) { + KiUnlockDispatcherDatabase(OldIrql); + RETURN(X_STATUS_SUSPEND_COUNT_EXCEEDED); + } - RETURN(ret); + if (Thread->ApcState.ApcQueueable == TRUE) { + ++Thread->SuspendCount; + if (OldCount == 0) { + if (KiInsertQueueApc(&Thread->SuspendApc, 0) == FALSE) { + --Thread->SuspendSemaphore.Header.SignalState; + } + } + } + + KiUnlockDispatcherDatabase(OldIrql); + + RETURN(OldCount); } // ****************************************************************** diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index 9e66a18b6..3387746b1 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -907,12 +907,13 @@ static xbox::void_xt KiExecuteApc() Apc->Inserted = FALSE; xbox::KiApcListMtx.unlock(); - // NOTE: we never use KernelRoutine because that is only used for kernel APCs, which we currently don't use + // This is either KiFreeUserApc, which frees the memory of the apc, or KiSuspendNop, which does nothing + (Apc->KernelRoutine)(Apc, &Apc->NormalRoutine, &Apc->NormalContext, &Apc->SystemArgument1, &Apc->SystemArgument2); + if (Apc->NormalRoutine != xbox::zeroptr) { (Apc->NormalRoutine)(Apc->NormalContext, Apc->SystemArgument1, Apc->SystemArgument2); } - xbox::ExFreePool(Apc); xbox::KiApcListMtx.lock(); } @@ -966,7 +967,8 @@ xbox::PLARGE_INTEGER FASTCALL xbox::KiComputeWaitInterval } // Source: ReactOS -xbox::void_xt NTAPI xbox::KiSuspendNop( +xbox::void_xt NTAPI xbox::KiSuspendNop +( IN PKAPC Apc, IN PKNORMAL_ROUTINE* NormalRoutine, IN PVOID* NormalContext, @@ -974,7 +976,7 @@ xbox::void_xt NTAPI xbox::KiSuspendNop( IN PVOID* SystemArgument2 ) { - /* Does nothing */ + /* Does nothing because the memory of the suspend apc is part of kthread */ UNREFERENCED_PARAMETER(Apc); UNREFERENCED_PARAMETER(NormalRoutine); UNREFERENCED_PARAMETER(NormalContext); @@ -982,8 +984,21 @@ xbox::void_xt NTAPI xbox::KiSuspendNop( UNREFERENCED_PARAMETER(SystemArgument2); } +xbox::void_xt NTAPI xbox::KiFreeUserApc +( + IN PKAPC Apc, + IN PKNORMAL_ROUTINE *NormalRoutine, + IN PVOID *NormalContext, + IN PVOID *SystemArgument1, + IN PVOID *SystemArgument2 +) +{ + ExFreePool(Apc); +} + // Source: ReactOS -xbox::void_xt NTAPI xbox::KiSuspendThread( +xbox::void_xt NTAPI xbox::KiSuspendThread +( IN PVOID NormalContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 @@ -1076,3 +1091,40 @@ xbox::void_xt xbox::KiInitializeContextThread( /* Save back the new value of the kernel stack. */ Thread->KernelStack = reinterpret_cast(CtxSwitchFrame); } + +xbox::boolean_xt xbox::KiInsertQueueApc +( + IN PRKAPC Apc, + IN KPRIORITY Increment +) +{ + PKTHREAD kThread = Apc->Thread; + KiApcListMtx.lock(); + if (Apc->Inserted) { + KiApcListMtx.unlock(); + return FALSE; + } + InsertTailList(&kThread->ApcState.ApcListHead[Apc->ApcMode], &Apc->ApcListEntry); + Apc->Inserted = TRUE; + KiApcListMtx.unlock(); + + // We can only attempt to execute the queued apc right away if it is been inserted in the current thread, because otherwise the KTHREAD + // in the fs selector will not be correct + if (Apc->ApcMode == KernelMode) { // kernel apc + kThread->ApcState.KernelApcPending = TRUE; + // NOTE: this is wrong, we should check the thread state instead of just signaling the kernel apc, but we currently + // don't set the appropriate state in kthread + if (kThread == KeGetCurrentThread()) { + KiExecuteKernelApc(); + } + } + else if ((kThread->WaitMode == UserMode) && (kThread->Alertable)) { // user apc + kThread->ApcState.UserApcPending = TRUE; + // NOTE: this should also check the thread state + if (kThread == KeGetCurrentThread()) { + KiExecuteUserApc(); + } + } + + return TRUE; +} diff --git a/src/core/kernel/exports/EmuKrnlKi.h b/src/core/kernel/exports/EmuKrnlKi.h index e23165d7e..8f08a5e76 100644 --- a/src/core/kernel/exports/EmuKrnlKi.h +++ b/src/core/kernel/exports/EmuKrnlKi.h @@ -156,7 +156,8 @@ namespace xbox ); // Source: ReactOS - void_xt NTAPI KiSuspendNop( + void_xt NTAPI KiSuspendNop + ( IN PKAPC Apc, IN PKNORMAL_ROUTINE* NormalRoutine, IN PVOID* NormalContext, @@ -164,6 +165,15 @@ namespace xbox IN PVOID* SystemArgument2 ); + void_xt NTAPI KiFreeUserApc + ( + IN PKAPC Apc, + IN PKNORMAL_ROUTINE *NormalRoutine, + IN PVOID *NormalContext, + IN PVOID *SystemArgument1, + IN PVOID *SystemArgument2 + ); + // Source: ReactOS void_xt NTAPI KiSuspendThread( IN PVOID NormalContext, @@ -180,6 +190,12 @@ namespace xbox IN PKSTART_ROUTINE StartRoutine, IN PVOID StartContext ); + + boolean_xt KiInsertQueueApc + ( + IN PRKAPC Apc, + IN KPRIORITY Increment + ); }; extern xbox::KPROCESS KiUniqueProcess; diff --git a/src/core/kernel/exports/EmuKrnlNt.cpp b/src/core/kernel/exports/EmuKrnlNt.cpp index 1e7d33fc8..fd461dff7 100644 --- a/src/core/kernel/exports/EmuKrnlNt.cpp +++ b/src/core/kernel/exports/EmuKrnlNt.cpp @@ -1039,7 +1039,7 @@ XBSYSAPI EXPORTNUM(206) xbox::ntstatus_xt NTAPI xbox::NtQueueApcThread PKAPC Apc = static_cast(ExAllocatePoolWithTag(sizeof(KAPC), 'pasP')); if (Apc != zeroptr) { - KeInitializeApc(Apc, &Thread->Tcb, zeroptr, zeroptr, reinterpret_cast(ApcRoutine), UserMode, ApcRoutineContext); + KeInitializeApc(Apc, &Thread->Tcb, KiFreeUserApc, zeroptr, reinterpret_cast(ApcRoutine), UserMode, ApcRoutineContext); if (!KeInsertQueueApc(Apc, ApcStatusBlock, ApcReserved, 0)) { ExFreePool(Apc); result = X_STATUS_UNSUCCESSFUL; @@ -1856,15 +1856,20 @@ XBSYSAPI EXPORTNUM(224) xbox::ntstatus_xt NTAPI xbox::NtResumeThread LOG_FUNC_ARG_OUT(PreviousSuspendCount) LOG_FUNC_END; - if (const auto &nativeHandle = GetNativeHandle(ThreadHandle)) { - // Thread handles are created by ob - RETURN(NtDll::NtResumeThread(*nativeHandle, (::PULONG)PreviousSuspendCount)); - } - else { - RETURN(X_STATUS_INVALID_HANDLE); + PETHREAD Thread; + ntstatus_xt result = ObReferenceObjectByHandle(ThreadHandle, &PsThreadObjectType, reinterpret_cast(&Thread)); + if (!X_NT_SUCCESS(result)) { + RETURN(result); } - // TODO : Once we do our own thread-switching, implement NtResumeThread using KetResumeThread + ulong_xt PrevSuspendCount = KeResumeThread(&Thread->Tcb); + ObfDereferenceObject(Thread); + + if (PreviousSuspendCount) { + *PreviousSuspendCount = PrevSuspendCount; + } + + RETURN(X_STATUS_SUCCESS); } // ****************************************************************** @@ -2079,15 +2084,32 @@ XBSYSAPI EXPORTNUM(231) xbox::ntstatus_xt NTAPI xbox::NtSuspendThread LOG_FUNC_ARG_OUT(PreviousSuspendCount) LOG_FUNC_END; - if (const auto &nativeHandle = GetNativeHandle(ThreadHandle)) { - // Thread handles are created by ob - RETURN(NtDll::NtSuspendThread(*nativeHandle, (::PULONG)PreviousSuspendCount)); - } - else { - RETURN(X_STATUS_INVALID_HANDLE); + PETHREAD Thread; + ntstatus_xt result = ObReferenceObjectByHandle(ThreadHandle, &PsThreadObjectType, reinterpret_cast(&Thread)); + if (!X_NT_SUCCESS(result)) { + RETURN(result); } - // TODO : Once we do our own thread-switching, implement NtSuspendThread using KeSuspendThread + if (Thread != PspGetCurrentThread()) { + if (Thread->Tcb.HasTerminated) { + ObfDereferenceObject(Thread); + RETURN(X_STATUS_THREAD_IS_TERMINATING); + } + } + + ulong_xt PrevSuspendCount = KeSuspendThread(&Thread->Tcb); + if (PrevSuspendCount == X_STATUS_SUSPEND_COUNT_EXCEEDED) { + ObfDereferenceObject(Thread); + RETURN(X_STATUS_SUSPEND_COUNT_EXCEEDED); + } + + ObfDereferenceObject(Thread); + + if (PreviousSuspendCount) { + *PreviousSuspendCount = PrevSuspendCount; + } + + RETURN(X_STATUS_SUCCESS); } // ****************************************************************** diff --git a/src/core/kernel/exports/EmuKrnlPs.cpp b/src/core/kernel/exports/EmuKrnlPs.cpp index 9c0a61ef3..dd7f6ade1 100644 --- a/src/core/kernel/exports/EmuKrnlPs.cpp +++ b/src/core/kernel/exports/EmuKrnlPs.cpp @@ -121,6 +121,8 @@ static unsigned int WINAPI PCSTProxy params.Ethread, params.TlsDataSize); + xbox::KiExecuteKernelApc(); + auto routine = (xbox::PKSYSTEM_ROUTINE)StartFrame->SystemRoutine; // Debugging notice : When the below line shows up with an Exception dialog and a // message like: "Exception thrown at 0x00026190 in cxbx.exe: 0xC0000005: Access @@ -409,10 +411,12 @@ XBSYSAPI EXPORTNUM(255) xbox::ntstatus_xt NTAPI xbox::PsCreateSystemThreadEx g_AffinityPolicy->SetAffinityXbox(handle); // Now that ThreadId is populated and affinity is changed, resume the thread (unless the guest passed CREATE_SUSPENDED) - if (!CreateSuspended) { - ResumeThread(handle); + if (CreateSuspended) { + KeSuspendThread(&eThread->Tcb); } + ResumeThread(handle); + // Log ThreadID identical to how GetCurrentThreadID() is rendered : EmuLog(LOG_LEVEL::DEBUG, "Created Xbox proxy thread. Handle : 0x%X, ThreadId : [0x%.4X], Native Handle : 0x%X, Native ThreadId : [0x%.4X]", *ThreadHandle, eThread->UniqueThread, handle, ThreadId); From d8ae1892b4f36cb426437a2f2e4c1152084ba1e6 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Tue, 7 Mar 2023 15:28:37 +0100 Subject: [PATCH 06/35] Removed scaling hack in KeInterruptTime and KeTickCount + added yield in system_events routine This fixes the stuttering in Halo 2, Metal slug 3, JSRF and restores PDO, PSO to the same state as in master --- src/common/Timer.cpp | 32 +++------------------------ src/core/kernel/exports/EmuKrnlKi.cpp | 9 +++----- src/core/kernel/exports/EmuKrnlKi.h | 5 +---- 3 files changed, 7 insertions(+), 39 deletions(-) diff --git a/src/common/Timer.cpp b/src/common/Timer.cpp index cdc8474d4..0a0bb6a52 100644 --- a/src/common/Timer.cpp +++ b/src/common/Timer.cpp @@ -86,41 +86,14 @@ void SleepPrecise(std::chrono::steady_clock::time_point targetTime) } } -// NOTE: the pit device is not implemented right now, so we put these here -static void pit_interrupt() -{ - LARGE_INTEGER CurrentTicks; - uint64_t Delta; - uint64_t Microseconds; - unsigned int IncrementScaling; - static uint64_t Error = 0; - static uint64_t UnaccountedMicroseconds = 0; - - // This keeps track of how many us have elapsed between two cycles, so that the xbox clocks are updated - // with the proper increment (instead of blindly adding a single increment at every step) - - QueryPerformanceCounter(&CurrentTicks); - Delta = CurrentTicks.QuadPart - pit_last_qpc; - pit_last_qpc = CurrentTicks.QuadPart; - - Error += (Delta * SCALE_S_IN_US); - Microseconds = Error / HostQPCFrequency; - Error -= (Microseconds * HostQPCFrequency); - - UnaccountedMicroseconds += Microseconds; - IncrementScaling = (unsigned int)(UnaccountedMicroseconds / 1000); // -> 1 ms = 1000us -> time between two xbox clock interrupts - UnaccountedMicroseconds -= (IncrementScaling * 1000); - - xbox::KiClockIsr(IncrementScaling); -} - +// NOTE: the pit device is not implemented right now, so we put this here static uint64_t pit_next(uint64_t now) { constexpr uint64_t pit_period = 1000; uint64_t next = pit_last + pit_period; if (now >= next) { - pit_interrupt(); + xbox::KiClockIsr(); pit_last = get_now(); return pit_period; } @@ -183,6 +156,7 @@ xbox::void_xt NTAPI system_events(xbox::PVOID arg) if (elapsed_us >= nearest_next) { break; } + std::this_thread::yield(); _mm_pause(); } } diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index 3387746b1..8335551d7 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -129,10 +129,7 @@ xbox::void_xt xbox::KiTimerUnlock() KiTimerMtx.Mtx.unlock(); } -xbox::void_xt xbox::KiClockIsr -( - unsigned int ScalingFactor -) +xbox::void_xt xbox::KiClockIsr() { KIRQL OldIrql; LARGE_INTEGER InterruptTime; @@ -145,7 +142,7 @@ xbox::void_xt xbox::KiClockIsr // Update the interrupt time InterruptTime.u.LowPart = KeInterruptTime.LowPart; InterruptTime.u.HighPart = KeInterruptTime.High1Time; - InterruptTime.QuadPart += (CLOCK_TIME_INCREMENT * ScalingFactor); + InterruptTime.QuadPart += CLOCK_TIME_INCREMENT; KeInterruptTime.High2Time = InterruptTime.u.HighPart; KeInterruptTime.LowPart = InterruptTime.u.LowPart; KeInterruptTime.High1Time = InterruptTime.u.HighPart; @@ -161,7 +158,7 @@ xbox::void_xt xbox::KiClockIsr // Update the tick counter OldKeTickCount = KeTickCount; - KeTickCount += ScalingFactor; + ++KeTickCount; // Because this function must be fast to continuously update the kernel clocks, if somebody else is currently // holding the lock, we won't wait and instead skip the check of the timers for this cycle diff --git a/src/core/kernel/exports/EmuKrnlKi.h b/src/core/kernel/exports/EmuKrnlKi.h index 8f08a5e76..3db4e17ce 100644 --- a/src/core/kernel/exports/EmuKrnlKi.h +++ b/src/core/kernel/exports/EmuKrnlKi.h @@ -56,10 +56,7 @@ namespace xbox void_xt KiTimerUnlock(); - void_xt KiClockIsr - ( - IN unsigned int ScalingFactor - ); + void_xt KiClockIsr(); xbox::void_xt NTAPI KiCheckTimerTable ( From c349fbbc0014e7722f9c6afb92db3712c6cc48a6 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Tue, 7 Mar 2023 15:40:42 +0100 Subject: [PATCH 07/35] Moved position of ObfDereferenceObject in NtSuspendThread --- src/core/kernel/exports/EmuKrnlNt.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/core/kernel/exports/EmuKrnlNt.cpp b/src/core/kernel/exports/EmuKrnlNt.cpp index fd461dff7..19be4b8e0 100644 --- a/src/core/kernel/exports/EmuKrnlNt.cpp +++ b/src/core/kernel/exports/EmuKrnlNt.cpp @@ -2098,13 +2098,11 @@ XBSYSAPI EXPORTNUM(231) xbox::ntstatus_xt NTAPI xbox::NtSuspendThread } ulong_xt PrevSuspendCount = KeSuspendThread(&Thread->Tcb); + ObfDereferenceObject(Thread); if (PrevSuspendCount == X_STATUS_SUSPEND_COUNT_EXCEEDED) { - ObfDereferenceObject(Thread); RETURN(X_STATUS_SUSPEND_COUNT_EXCEEDED); } - ObfDereferenceObject(Thread); - if (PreviousSuspendCount) { *PreviousSuspendCount = PrevSuspendCount; } From 6e63ecd7ccb313f476ae3b879d45a407d2b29ecb Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Wed, 8 Mar 2023 22:57:31 +0100 Subject: [PATCH 08/35] Avoid triggering multiple gpu interrupts outside the vblank --- src/common/Timer.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/common/Timer.cpp b/src/common/Timer.cpp index 0a0bb6a52..2a48976f1 100644 --- a/src/common/Timer.cpp +++ b/src/common/Timer.cpp @@ -109,8 +109,14 @@ static void update_non_periodic_events() // check nvnet NVNetRecv(); - // check for hw interrupts - for (int i = 0; i < MAX_BUS_INTERRUPT_LEVEL; i++) { + // check for hw interrupts, but skip the gpu interrupt since that is serviced in vblank_next + for (int i = 0; i < 3; i++) { + // If the interrupt is pending and connected, process it + if (g_bEnableAllInterrupts && HalSystemInterrupts[i].IsPending() && EmuInterruptList[i] && EmuInterruptList[i]->Connected) { + HalSystemInterrupts[i].Trigger(EmuInterruptList[i]); + } + } + for (int i = 4; i < MAX_BUS_INTERRUPT_LEVEL; i++) { // If the interrupt is pending and connected, process it if (g_bEnableAllInterrupts && HalSystemInterrupts[i].IsPending() && EmuInterruptList[i] && EmuInterruptList[i]->Connected) { HalSystemInterrupts[i].Trigger(EmuInterruptList[i]); From b1ee59fab2445effce0712657d606ffdbf346944 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Thu, 9 Mar 2023 14:18:49 +0100 Subject: [PATCH 09/35] Unpatch D3DDevice_BlockUntilVerticalBlank and D3DDevice_SetVerticalBlankCallback --- src/core/hle/D3D8/Direct3D9/Direct3D9.cpp | 36 ------------------- .../Direct3D9/Direct3D9.cpp.unused-patches | 24 +++++++++++++ src/core/hle/Patches.cpp | 4 +-- 3 files changed, 26 insertions(+), 38 deletions(-) diff --git a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp index 04cb005c5..674d55c06 100644 --- a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp +++ b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp @@ -159,8 +159,6 @@ static IDirect3DQuery *g_pHostQueryWaitForIdle = nullptr; static IDirect3DQuery *g_pHostQueryCallbackEvent = nullptr; static int g_RenderUpscaleFactor = 1; -static std::condition_variable g_VBConditionVariable; // Used in BlockUntilVerticalBlank -static std::mutex g_VBConditionMutex; // Used in BlockUntilVerticalBlank static DWORD g_VBLastSwap = 0; static xbox::dword_xt g_Xbox_PresentationInterval_Default = D3DPRESENT_INTERVAL_IMMEDIATE; @@ -168,7 +166,6 @@ static xbox::dword_xt g_Xbox_PresentationInterval_Default = D3 static xbox::X_D3DSWAPDATA g_Xbox_SwapData = {0}; // current swap information static xbox::X_D3DSWAPCALLBACK g_pXbox_SwapCallback = xbox::zeroptr; // Swap/Present callback routine static xbox::X_D3DVBLANKDATA g_Xbox_VBlankData = {0}; // current vertical blank information -static xbox::X_D3DVBLANKCALLBACK g_pXbox_VerticalBlankCallback = xbox::zeroptr; // Vertical-Blank callback routine xbox::X_D3DSurface *g_pXbox_BackBufferSurface = xbox::zeroptr; static xbox::X_D3DSurface *g_pXbox_DefaultDepthStencilSurface = xbox::zeroptr; @@ -1917,19 +1914,11 @@ void hle_vblank() // Note: This whole code block can be removed once NV2A interrupts are implemented // And Both Swap and Present can be ran unpatched // Once that is in place, MiniPort + Direct3D will handle this on it's own! - // Increment the VBlank Counter and Wake all threads there were waiting for the VBlank to occur - std::unique_lock lk(g_VBConditionMutex); g_Xbox_VBlankData.VBlank++; - g_VBConditionVariable.notify_all(); // TODO: Fixme. This may not be right... g_Xbox_SwapData.SwapVBlank = 1; - if (g_pXbox_VerticalBlankCallback != xbox::zeroptr) - { - g_pXbox_VerticalBlankCallback(&g_Xbox_VBlankData); - } - g_Xbox_VBlankData.Swap = 0; // TODO: This can't be accurate... @@ -6509,31 +6498,6 @@ xbox::bool_xt WINAPI xbox::EMUPATCH(D3DDevice_GetOverlayUpdateStatus)() return TRUE; } -// ****************************************************************** -// * patch: D3DDevice_BlockUntilVerticalBlank -// ****************************************************************** -xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_BlockUntilVerticalBlank)() -{ - LOG_FUNC(); - - std::unique_lock lk(g_VBConditionMutex); - g_VBConditionVariable.wait(lk); -} - -// ****************************************************************** -// * patch: D3DDevice_SetVerticalBlankCallback -// ****************************************************************** -xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetVerticalBlankCallback) -( - X_D3DVBLANKCALLBACK pCallback -) -{ - LOG_FUNC_ONE_ARG(pCallback); - - g_pXbox_VerticalBlankCallback = pCallback; -} - - // ****************************************************************** // * patch: D3DDevice_SetRenderState_Simple // ****************************************************************** diff --git a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp.unused-patches b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp.unused-patches index 457813b46..0783f0ae2 100644 --- a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp.unused-patches +++ b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp.unused-patches @@ -4256,3 +4256,27 @@ HRESULT WINAPI XTL::EMUPATCH(D3DDevice_GetVertexShaderInput) return 0; } + +// ****************************************************************** +// * patch: D3DDevice_BlockUntilVerticalBlank +// ****************************************************************** +xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_BlockUntilVerticalBlank)() +{ + LOG_FUNC(); + + std::unique_lock lk(g_VBConditionMutex); + g_VBConditionVariable.wait(lk); +} + +// ****************************************************************** +// * patch: D3DDevice_SetVerticalBlankCallback +// ****************************************************************** +xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetVerticalBlankCallback) +( + X_D3DVBLANKCALLBACK pCallback +) +{ + LOG_FUNC_ONE_ARG(pCallback); + + g_pXbox_VerticalBlankCallback = pCallback; +} diff --git a/src/core/hle/Patches.cpp b/src/core/hle/Patches.cpp index 236b4c9d2..d4ccf44cc 100644 --- a/src/core/hle/Patches.cpp +++ b/src/core/hle/Patches.cpp @@ -66,7 +66,7 @@ std::map g_PatchTable = { PATCH_ENTRY("D3DDevice_BeginPush_8", xbox::EMUPATCH(D3DDevice_BeginPush_8), PATCH_HLE_D3D), PATCH_ENTRY("D3DDevice_BeginVisibilityTest", xbox::EMUPATCH(D3DDevice_BeginVisibilityTest), PATCH_HLE_D3D), PATCH_ENTRY("D3DDevice_BlockOnFence", xbox::EMUPATCH(D3DDevice_BlockOnFence), PATCH_HLE_D3D), - PATCH_ENTRY("D3DDevice_BlockUntilVerticalBlank", xbox::EMUPATCH(D3DDevice_BlockUntilVerticalBlank), PATCH_HLE_D3D), + //PATCH_ENTRY("D3DDevice_BlockUntilVerticalBlank", xbox::EMUPATCH(D3DDevice_BlockUntilVerticalBlank), PATCH_HLE_D3D), PATCH_ENTRY("D3DDevice_Clear", xbox::EMUPATCH(D3DDevice_Clear), PATCH_HLE_D3D), PATCH_ENTRY("D3DDevice_CopyRects", xbox::EMUPATCH(D3DDevice_CopyRects), PATCH_HLE_D3D), // PATCH_ENTRY("D3DDevice_CreateVertexShader", xbox::EMUPATCH(D3DDevice_CreateVertexShader), PATCH_HLE_D3D), @@ -183,7 +183,7 @@ std::map g_PatchTable = { PATCH_ENTRY("D3DDevice_SetVertexShaderConstant_8", xbox::EMUPATCH(D3DDevice_SetVertexShaderConstant_8), PATCH_HLE_D3D), PATCH_ENTRY("D3DDevice_SetVertexShaderInput", xbox::EMUPATCH(D3DDevice_SetVertexShaderInput), PATCH_HLE_D3D), //PATCH_ENTRY("D3DDevice_SetVertexShaderInputDirect", xbox::EMUPATCH(D3DDevice_SetVertexShaderInputDirect), PATCH_HLE_D3D), - PATCH_ENTRY("D3DDevice_SetVerticalBlankCallback", xbox::EMUPATCH(D3DDevice_SetVerticalBlankCallback), PATCH_HLE_D3D), + //PATCH_ENTRY("D3DDevice_SetVerticalBlankCallback", xbox::EMUPATCH(D3DDevice_SetVerticalBlankCallback), PATCH_HLE_D3D), PATCH_ENTRY("D3DDevice_SetViewport", xbox::EMUPATCH(D3DDevice_SetViewport), PATCH_HLE_D3D), PATCH_ENTRY("D3DDevice_Swap", xbox::EMUPATCH(D3DDevice_Swap), PATCH_HLE_D3D), PATCH_ENTRY("D3DDevice_Swap_0", xbox::EMUPATCH(D3DDevice_Swap_0), PATCH_HLE_D3D), From e323ad50b56fd299c4ba45d0354a0d0a11a60fe0 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Thu, 9 Mar 2023 14:32:52 +0100 Subject: [PATCH 10/35] Only change the priority of a thread if it is being set above normal --- src/core/kernel/exports/EmuKrnlKe.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index d5b8f48ba..9419ce3ac 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -1797,6 +1797,7 @@ XBSYSAPI EXPORTNUM(143) xbox::long_xt NTAPI xbox::KeSetBasePriorityThread // It cannot fail because all thread handles are created by ob const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread); + LONG ret = GetThreadPriority(*nativeHandle); if (Priority == 16) { Priority = THREAD_PRIORITY_TIME_CRITICAL; @@ -1805,14 +1806,21 @@ XBSYSAPI EXPORTNUM(143) xbox::long_xt NTAPI xbox::KeSetBasePriorityThread Priority = THREAD_PRIORITY_IDLE; } - BOOL result = SetThreadPriority(*nativeHandle, Priority); - if (!result) { - EmuLog(LOG_LEVEL::WARNING, "SetThreadPriority failed: %s", WinError2Str().c_str()); + // HACK: only change the priority if set above normal. Test case: Black. It calls this to set the priority of a thread to THREAD_PRIORITY_LOWEST, and that same + // thread calls D3DDevice_BlockUntilVerticalBlank. The problem arises because there is also another thread that runs at higher priority and calls D3DDevice_BlockUntilVerticalBlank + // too to wait on the vblank kevent. When the vblank does occur, this other thread will satisfy the wait first, and set the kevent back to non-signalled. Thus, the other + // thread will miss the signal because typically, Windows won't re-schedule it after many more vblank have already occured. The proper solution is to boost the priority of + // the thread when the kevent is signalled, with the increment argument specified in KeSetEvent. Such boosts should also be appiled whenever a thread satisfies a wait. + if (Priority >= THREAD_PRIORITY_NORMAL) { + BOOL result = SetThreadPriority(*nativeHandle, Priority); + if (!result) { + EmuLog(LOG_LEVEL::WARNING, "SetThreadPriority failed: %s", WinError2Str().c_str()); + } } KiUnlockDispatcherDatabase(oldIRQL); - RETURN(result); + RETURN(ret); } XBSYSAPI EXPORTNUM(144) xbox::boolean_xt NTAPI xbox::KeSetDisableBoostThread From f52e261c4aae80aa1d6ed92b2e64a4334be8efab Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Fri, 10 Mar 2023 17:18:21 +0100 Subject: [PATCH 11/35] Implemented kernel unwait routines + updated/fixed KeWaitForMultipleObjects and KeWaitForSingleObject --- src/core/kernel/common/types.h | 2 +- src/core/kernel/exports/EmuKrnlKe.cpp | 192 ++++++++------------ src/core/kernel/exports/EmuKrnlKi.cpp | 248 ++++++++++++++++++++------ src/core/kernel/exports/EmuKrnlKi.h | 54 +++++- src/core/kernel/exports/EmuKrnlPs.cpp | 5 +- 5 files changed, 319 insertions(+), 182 deletions(-) diff --git a/src/core/kernel/common/types.h b/src/core/kernel/common/types.h index c3e192c1f..ab68b5e2e 100644 --- a/src/core/kernel/common/types.h +++ b/src/core/kernel/common/types.h @@ -1947,7 +1947,7 @@ typedef struct _KTHREAD /* 0x56/86 */ char_xt WaitNext; /* 0x57/87 */ char_xt WaitReason; /* 0x58/88 */ PKWAIT_BLOCK WaitBlockList; - /* 0x5C/92 */ LIST_ENTRY WaitListEntry; + /* 0x5C/92 */ LIST_ENTRY WaitListEntry; // Used to place the thread in the ready list of the scheduler /* 0x64/100 */ ulong_xt WaitTime; /* 0x68/104 */ ulong_xt KernelApcDisable; /* 0x6C/108 */ ulong_xt Quantum; diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 9419ce3ac..6d4355e14 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -246,7 +246,7 @@ xbox::void_xt NTAPI xbox::KeSetSystemTime } } - /* Process expired timers. This releases the dispatcher and timer locks */ + /* Process expired timers. This releases the dispatcher and timer locks, then it yields */ KiTimerListExpire(&TempList2, OldIrql); } @@ -1329,11 +1329,7 @@ XBSYSAPI EXPORTNUM(123) xbox::long_xt NTAPI xbox::KePulseEvent LONG OldState = Event->Header.SignalState; if ((OldState == 0) && (IsListEmpty(&Event->Header.WaitListHead) == FALSE)) { Event->Header.SignalState = 1; - // TODO: KiWaitTest(Event, Increment); - // For now, we just sleep to give other threads time to wake - // KiWaitTest and related functions require correct thread scheduling to implement first - // This will have to wait until CPU emulation at v1.0 - Sleep(1); + KiWaitTest(Event, Increment); } Event->Header.SignalState = 0; @@ -1514,12 +1510,9 @@ XBSYSAPI EXPORTNUM(132) xbox::long_xt NTAPI xbox::KeReleaseSemaphore } Semaphore->Header.SignalState = adjusted_signalstate; - //TODO: Implement KiWaitTest -#if 0 if ((initial_state == 0) && (IsListEmpty(&Semaphore->Header.WaitListHead) == FALSE)) { KiWaitTest(&Semaphore->Header, Increment); } -#endif if (Wait) { PKTHREAD current_thread = KeGetCurrentThread(); @@ -1741,6 +1734,7 @@ XBSYSAPI EXPORTNUM(140) xbox::ulong_xt NTAPI xbox::KeResumeThread --Thread->SuspendCount; if (Thread->SuspendCount == 0) { ++Thread->SuspendSemaphore.Header.SignalState; + KiWaitTest(&Thread->SuspendSemaphore, 0); } } @@ -1890,16 +1884,10 @@ XBSYSAPI EXPORTNUM(145) xbox::long_xt NTAPI xbox::KeSetEvent (WaitBlock->WaitType != WaitAny)) { if (OldState == 0) { Event->Header.SignalState = 1; - // TODO: KiWaitTest(Event, Increment); - // For now, we just sleep to give other threads time to wake - // See KePulseEvent - Sleep(1); + KiWaitTest(Event, Increment); } } else { - // TODO: KiUnwaitThread(WaitBlock->Thread, (NTSTATUS)WaitBlock->WaitKey, Increment); - // For now, we just sleep to give other threads time to wake - // See KePulseEvent - Sleep(1); + KiUnwaitThreadAndLock(WaitBlock->Thread, (NTSTATUS)WaitBlock->WaitKey, Increment); } } @@ -1946,10 +1934,7 @@ XBSYSAPI EXPORTNUM(146) xbox::void_xt NTAPI xbox::KeSetEventBoostPriority } WaitThread->Quantum = WaitThread->ApcState.Process->ThreadQuantum; - // TODO: KiUnwaitThread(WaitThread, X_STATUS_SUCCESS, 1); - // For now, we just sleep to give other threads time to wake - // See KePulseEvent - Sleep(1); + KiUnwaitThreadAndLock(WaitThread, X_STATUS_SUCCESS, 1); } KiUnlockDispatcherDatabase(OldIrql); @@ -2212,15 +2197,13 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects // Wait Loop // This loop ends PLARGE_INTEGER OriginalTime = Timeout; - LARGE_INTEGER DueTime, NewTime; - KWAIT_BLOCK StackWaitBlock; - PKWAIT_BLOCK WaitBlock = &StackWaitBlock; + PKWAIT_BLOCK WaitBlock; BOOLEAN WaitSatisfied; NTSTATUS WaitStatus; PKMUTANT ObjectMutant; - // Hack variable (remove this when the thread scheduler is here) - bool timeout_set = false; do { + Thread->WaitBlockList = WaitBlockArray; + // Check if we need to let an APC run. This should immediately trigger APC interrupt via a call to UnlockDispatcherDatabase if (Thread->ApcState.KernelApcPending && (Thread->WaitIrql < APC_LEVEL)) { KiUnlockDispatcherDatabase(Thread->WaitIrql); @@ -2279,7 +2262,7 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects // Check if the wait can be satisfied immediately if ((WaitType == WaitAll) && (WaitSatisfied)) { WaitBlock->NextWaitBlock = &WaitBlockArray[0]; - KiWaitSatisfyAll(WaitBlock); + KiWaitSatisfyAllAndLock(WaitBlock); WaitStatus = (NTSTATUS)Thread->WaitStatus; goto NoWait; } @@ -2294,36 +2277,20 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects goto NoWait; } - // Setup a timer for the thread but only once (for now) - if (!timeout_set) { - KiTimerLock(); - PKTIMER Timer = &Thread->Timer; - PKWAIT_BLOCK WaitTimer = &Thread->TimerWaitBlock; - WaitBlock->NextWaitBlock = WaitTimer; - Timer->Header.WaitListHead.Flink = &WaitTimer->WaitListEntry; - Timer->Header.WaitListHead.Blink = &WaitTimer->WaitListEntry; - WaitTimer->NextWaitBlock = WaitBlock; - if (KiInsertTreeTimer(Timer, *Timeout) == FALSE) { - WaitStatus = (NTSTATUS)STATUS_TIMEOUT; - KiTimerUnlock(); - goto NoWait; - } - - // Boring, ensure that we only set the thread timer once. Otherwise, this will cause to insert the same - // thread timer over and over in the timer list, which will prevent KiTimerExpiration from removing these - // duplicated timers and thus it will attempt to endlessly remove the same unremoved timers, causing a deadlock. - // This can be removed once KiSwapThread and the kernel/user APCs are implemented - timeout_set = true; - DueTime.QuadPart = Timer->DueTime.QuadPart; + // Setup a timer for the thread + KiTimerLock(); + PKTIMER Timer = &Thread->Timer; + PKWAIT_BLOCK WaitTimer = &Thread->TimerWaitBlock; + WaitBlock->NextWaitBlock = WaitTimer; + Timer->Header.WaitListHead.Flink = &WaitTimer->WaitListEntry; + Timer->Header.WaitListHead.Blink = &WaitTimer->WaitListEntry; + WaitTimer->NextWaitBlock = WaitBlock; + if (KiInsertTreeTimer(Timer, *Timeout) == FALSE) { + WaitStatus = (NTSTATUS)STATUS_TIMEOUT; KiTimerUnlock(); - } - - // KiTimerExpiration has removed the timer but the objects were not signaled, so we have a timeout - // (remove this when the thread scheduler is here) - if (Thread->Timer.Header.Inserted == FALSE) { - WaitStatus = (NTSTATUS)(STATUS_TIMEOUT); goto NoWait; } + KiTimerUnlock(); } else { WaitBlock->NextWaitBlock = WaitBlock; @@ -2331,12 +2298,13 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects WaitBlock->NextWaitBlock = &WaitBlockArray[0]; WaitBlock = &WaitBlockArray[0]; + KiWaitListLock(); do { ObjectMutant = (PKMUTANT)WaitBlock->Object; - //InsertTailList(&ObjectMutant->Header.WaitListHead, &WaitBlock->WaitListEntry); + InsertTailList(&ObjectMutant->Header.WaitListHead, &WaitBlock->WaitListEntry); WaitBlock = WaitBlock->NextWaitBlock; } while (WaitBlock != &WaitBlockArray[0]); - + KiWaitListUnlock(); /* TODO: We can't implement this and the return values until we have our own thread scheduler @@ -2344,9 +2312,6 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects This code can all be enabled once we have CPU emulation and our own scheduler in v1.0 */ - // Insert the WaitBlock - //InsertTailList(&ObjectMutant->Header.WaitListHead, &WaitBlock->WaitListEntry); - // If the current thread is processing a queue object, wake other treads using the same queue PRKQUEUE Queue = (PRKQUEUE)Thread->Queue; if (Queue != NULL) { @@ -2358,7 +2323,7 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects Thread->WaitMode = WaitMode; Thread->WaitReason = (UCHAR)WaitReason; Thread->WaitTime = KeTickCount; - //Thread->State = Waiting; + Thread->State = Waiting; //KiInsertWaitList(WaitMode, Thread); //WaitStatus = (NTSTATUS)KiSwapThread(); @@ -2373,12 +2338,15 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects //} // TODO: Remove this after we have our own scheduler and the above is implemented - Sleep(0); + WaitStatus = WaitApc([Thread]() -> std::optional { + if (Thread->State == Ready) { + // We have been readied to resume execution, so exit the wait + return std::make_optional(Thread->WaitStatus); + } + return std::nullopt; + }, Timeout, Alertable, WaitMode); - // Reduce the timout if necessary - if (Timeout != nullptr) { - Timeout = KiComputeWaitInterval(OriginalTime, &DueTime, &NewTime); - } + break; } // Raise IRQL to DISPATCH_LEVEL and lock the database (only if it's not already at this level) @@ -2393,10 +2361,14 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects // The waiting thead has been alerted, or an APC needs to be delivered // So unlock the dispatcher database, lower the IRQ and return the status + Thread->State = Running; KiUnlockDispatcherDatabase(Thread->WaitIrql); +#if 0 + // No need for this at the moment, since WaitApc already executes user APCs if (WaitStatus == X_STATUS_USER_APC) { KiExecuteUserApc(); } +#endif RETURN(WaitStatus); @@ -2405,14 +2377,7 @@ NoWait: // Unlock the database and return the status //TODO: KiAdjustQuantumThread(Thread); - // Don't forget to remove the thread timer if the objects were signaled before the timer expired - // (remove this when the thread scheduler is here) - if (timeout_set && Thread->Timer.Header.Inserted == TRUE) { - KiTimerLock(); - KxRemoveTreeTimer(&Thread->Timer); - KiTimerUnlock(); - } - + Thread->State = Running; KiUnlockDispatcherDatabase(Thread->WaitIrql); if (WaitStatus == X_STATUS_USER_APC) { @@ -2454,12 +2419,9 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject // Wait Loop // This loop ends PLARGE_INTEGER OriginalTime = Timeout; - LARGE_INTEGER DueTime, NewTime; KWAIT_BLOCK StackWaitBlock; PKWAIT_BLOCK WaitBlock = &StackWaitBlock; - NTSTATUS WaitStatus; - // Hack variable (remove this when the thread scheduler is here) - bool timeout_set = false; + ntstatus_xt WaitStatus; do { // Check if we need to let an APC run. This should immediately trigger APC interrupt via a call to UnlockDispatcherDatabase if (Thread->ApcState.KernelApcPending && (Thread->WaitIrql < APC_LEVEL)) { @@ -2507,36 +2469,20 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject goto NoWait; } - // Setup a timer for the thread but only once (for now) - if (!timeout_set) { - KiTimerLock(); - PKTIMER Timer = &Thread->Timer; - PKWAIT_BLOCK WaitTimer = &Thread->TimerWaitBlock; - WaitBlock->NextWaitBlock = WaitTimer; - Timer->Header.WaitListHead.Flink = &WaitTimer->WaitListEntry; - Timer->Header.WaitListHead.Blink = &WaitTimer->WaitListEntry; - WaitTimer->NextWaitBlock = WaitBlock; - if (KiInsertTreeTimer(Timer, *Timeout) == FALSE) { - WaitStatus = (NTSTATUS)STATUS_TIMEOUT; - KiTimerUnlock(); - goto NoWait; - } - - // Boring, ensure that we only set the thread timer once. Otherwise, this will cause to insert the same - // thread timer over and over in the timer list, which will prevent KiTimerExpiration from removing these - // duplicated timers and thus it will attempt to endlessly remove the same unremoved timers, causing a deadlock. - // This can be removed once KiSwapThread and the kernel/user APCs are implemented - timeout_set = true; - DueTime.QuadPart = Timer->DueTime.QuadPart; + // Setup a timer for the thread + KiTimerLock(); + PKTIMER Timer = &Thread->Timer; + PKWAIT_BLOCK WaitTimer = &Thread->TimerWaitBlock; + WaitBlock->NextWaitBlock = WaitTimer; + Timer->Header.WaitListHead.Flink = &WaitTimer->WaitListEntry; + Timer->Header.WaitListHead.Blink = &WaitTimer->WaitListEntry; + WaitTimer->NextWaitBlock = WaitBlock; + if (KiInsertTreeTimer(Timer, *Timeout) == FALSE) { + WaitStatus = (NTSTATUS)STATUS_TIMEOUT; KiTimerUnlock(); - } - - // KiTimerExpiration has removed the timer but the object was not signaled, so we have a timeout - // (remove this when the thread scheduler is here) - if (Thread->Timer.Header.Inserted == FALSE) { - WaitStatus = (NTSTATUS)(STATUS_TIMEOUT); goto NoWait; } + KiTimerUnlock(); } else { WaitBlock->NextWaitBlock = WaitBlock; @@ -2548,9 +2494,6 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject This code can all be enabled once we have CPU emulation and our own scheduler in v1.0 */ - // Insert the WaitBlock - //InsertTailList(&ObjectMutant->Header.WaitListHead, &WaitBlock->WaitListEntry); - // If the current thread is processing a queue object, wake other treads using the same queue PRKQUEUE Queue = (PRKQUEUE)Thread->Queue; if (Queue != NULL) { @@ -2562,7 +2505,7 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject Thread->WaitMode = WaitMode; Thread->WaitReason = (UCHAR)WaitReason; Thread->WaitTime = KeTickCount; - // TODO: Thread->State = Waiting; + Thread->State = Waiting; //KiInsertWaitList(WaitMode, Thread); /* @@ -2577,13 +2520,21 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject return WaitStatus; } */ - // TODO: Remove this after we have our own scheduler and the above is implemented - Sleep(0); + // Insert the WaitBlock + KiWaitListLock(); + InsertTailList(&ObjectMutant->Header.WaitListHead, &WaitBlock->WaitListEntry); + KiWaitListUnlock(); - // Reduce the timout if necessary - if (Timeout != nullptr) { - Timeout = KiComputeWaitInterval(OriginalTime, &DueTime, &NewTime); - } + // TODO: Remove this after we have our own scheduler and the above is implemented + WaitStatus = WaitApc([Thread]() -> std::optional { + if (Thread->State == Ready) { + // We have been readied to resume execution, so exit the wait + return std::make_optional(Thread->WaitStatus); + } + return std::nullopt; + }, Timeout, Alertable, WaitMode); + + break; } // Raise IRQL to DISPATCH_LEVEL and lock the database @@ -2598,10 +2549,14 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject // The waiting thead has been alerted, or an APC needs to be delivered // So unlock the dispatcher database, lower the IRQ and return the status + Thread->State = Running; KiUnlockDispatcherDatabase(Thread->WaitIrql); +#if 0 + // No need for this at the moment, since WaitApc already executes user APCs if (WaitStatus == X_STATUS_USER_APC) { KiExecuteUserApc(); } +#endif RETURN(WaitStatus); @@ -2610,14 +2565,7 @@ NoWait: // Unlock the database and return the status //TODO: KiAdjustQuantumThread(Thread); - // Don't forget to remove the thread timer if the object was signaled before the timer expired - // (remove this when the thread scheduler is here) - if (timeout_set && Thread->Timer.Header.Inserted == TRUE) { - KiTimerLock(); - KxRemoveTreeTimer(&Thread->Timer); - KiTimerUnlock(); - } - + Thread->State = Running; KiUnlockDispatcherDatabase(Thread->WaitIrql); if (WaitStatus == X_STATUS_USER_APC) { diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index 8335551d7..fc416dbda 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -59,6 +59,8 @@ * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org) */ +// Also from ReactOS: KiWaitTest, KiWaitSatisfyAll, KiUnwaitThread, KiUnlinkThread + // COPYING file: /* GNU GENERAL PUBLIC LICENSE @@ -88,11 +90,13 @@ the said software). #define MAX_TIMER_DPCS 16 #define ASSERT_TIMER_LOCKED assert(KiTimerMtx.Acquired > 0) +#define ASSERT_WAIT_LIST_LOCKED assert(KiWaitListMtx.Acquired > 0) xbox::KPROCESS KiUniqueProcess; const xbox::ulong_xt CLOCK_TIME_INCREMENT = 0x2710; xbox::KDPC KiTimerExpireDpc; xbox::KI_TIMER_LOCK KiTimerMtx; +xbox::KI_WAIT_LIST_LOCK KiWaitListMtx; xbox::KTIMER_TABLE_ENTRY KiTimerTableListHead[TIMER_TABLE_SIZE]; xbox::LIST_ENTRY KiWaitInListHead; std::mutex xbox::KiApcListMtx; @@ -129,6 +133,18 @@ xbox::void_xt xbox::KiTimerUnlock() KiTimerMtx.Mtx.unlock(); } +xbox::void_xt xbox::KiWaitListLock() +{ + KiWaitListMtx.Mtx.lock(); + KiWaitListMtx.Acquired++; +} + +xbox::void_xt xbox::KiWaitListUnlock() +{ + KiWaitListMtx.Acquired--; + KiWaitListMtx.Mtx.unlock(); +} + xbox::void_xt xbox::KiClockIsr() { KIRQL OldIrql; @@ -483,17 +499,7 @@ xbox::boolean_xt FASTCALL xbox::KiSignalTimer /* Check if the timer has waiters */ if (!IsListEmpty(&Timer->Header.WaitListHead)) { - /* Check the type of event */ - if (Timer->Header.Type == TimerNotificationObject) - { - /* Unwait the thread */ - // KxUnwaitThread(&Timer->Header, IO_NO_INCREMENT); - } - else - { - /* Otherwise unwait the thread and signal the timer */ - // KxUnwaitThreadForEvent((PKEVENT)Timer, IO_NO_INCREMENT); - } + KiWaitTestNoYield(Timer, 0); } /* Check if we have a period */ @@ -598,17 +604,7 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration /* Check if there are any waiters */ if (!IsListEmpty(&Timer->Header.WaitListHead)) { - /* Check the type of event */ - if (Timer->Header.Type == TimerNotificationObject) - { - /* Unwait the thread */ - // KxUnwaitThread(&Timer->Header, IO_NO_INCREMENT); - } - else - { - /* Otherwise unwait the thread and signal the timer */ - // KxUnwaitThreadForEvent((PKEVENT)Timer, IO_NO_INCREMENT); - } + KiWaitTestNoYield(Timer, 0); } /* Check if we have a period */ @@ -790,17 +786,7 @@ xbox::void_xt FASTCALL xbox::KiTimerListExpire /* Check if there's any waiters */ if (!IsListEmpty(&Timer->Header.WaitListHead)) { - /* Check the type of event */ - if (Timer->Header.Type == TimerNotificationObject) - { - /* Unwait the thread */ - // KxUnwaitThread(&Timer->Header, IO_NO_INCREMENT); - } - else - { - /* Otherwise unwait the thread and signal the timer */ - // KxUnwaitThreadForEvent((PKEVENT)Timer, IO_NO_INCREMENT); - } + KiWaitTestNoYield(Timer, 0); } /* Check if we have a period */ @@ -854,29 +840,9 @@ xbox::void_xt FASTCALL xbox::KiTimerListExpire KiUnlockDispatcherDatabase(OldIrql); KiTimerUnlock(); } -} -xbox::void_xt FASTCALL xbox::KiWaitSatisfyAll -( - IN xbox::PKWAIT_BLOCK WaitBlock -) -{ - PKMUTANT Object; - PRKTHREAD Thread; - PKWAIT_BLOCK WaitBlock1; - - WaitBlock1 = WaitBlock; - Thread = WaitBlock1->Thread; - do { - if (WaitBlock1->WaitKey != (cshort_xt)STATUS_TIMEOUT) { - Object = (PKMUTANT)WaitBlock1->Object; - KiWaitSatisfyAny(Object, Thread); - } - - WaitBlock1 = WaitBlock1->NextWaitBlock; - } while (WaitBlock1 != WaitBlock); - - return; + // We have unwaited the threads with the expired timers and called their DPCs, so we can yield now + std::this_thread::yield(); } template @@ -1125,3 +1091,175 @@ xbox::boolean_xt xbox::KiInsertQueueApc return TRUE; } + +xbox::void_xt xbox::KiWaitTestNoYield +( + IN PVOID Object, + IN KPRIORITY Increment +) +{ + PLIST_ENTRY WaitEntry, WaitList; + PKWAIT_BLOCK WaitBlock, NextBlock; + PKTHREAD WaitThread; + PKMUTANT FirstObject = (PKMUTANT)Object; + + /* Loop the Wait Entries */ + KiWaitListLock(); + WaitList = &FirstObject->Header.WaitListHead; + WaitEntry = WaitList->Flink; + while ((FirstObject->Header.SignalState > 0) && (WaitEntry != WaitList)) { + /* Get the current wait block */ + WaitBlock = CONTAINING_RECORD(WaitEntry, KWAIT_BLOCK, WaitListEntry); + WaitThread = WaitBlock->Thread; + + /* Check the current Wait Mode */ + if (WaitBlock->WaitType == WaitAny) { + /* Easy case, satisfy only this wait */ + KiWaitSatisfyAny(FirstObject, WaitThread); + } + else { + /* WaitAll, check that all the objects are signalled */ + NextBlock = WaitBlock->NextWaitBlock; + while (NextBlock != WaitBlock) { + if (NextBlock->WaitKey != X_STATUS_TIMEOUT) { + PKMUTANT Mutant = (PKMUTANT)NextBlock->Object; + // NOTE: we ignore mutants because we forward them to ntdll + if (Mutant->Header.SignalState <= 0) { + // We found at least one object not in the signalled state, so we cannot satisfy the wait + goto NextWaitEntry; + } + } + + NextBlock = NextBlock->NextWaitBlock; + } + + KiWaitSatisfyAll(WaitBlock); + } + + /* Now do the rest of the unwait */ + KiUnwaitThread(WaitThread, WaitBlock->WaitKey, Increment); +NextWaitEntry: + WaitEntry = WaitList->Flink; + } + KiWaitListUnlock(); +} + +xbox::void_xt xbox::KiWaitTest +( + IN PVOID Object, + IN KPRIORITY Increment +) +{ + KiWaitTestNoYield(Object, Increment); + + // Now that we have unwaited all the threads we could, yield + std::this_thread::yield(); +} + +xbox::void_xt xbox::KiWaitSatisfyAll +( + IN PKWAIT_BLOCK FirstBlock +) +{ + PKWAIT_BLOCK WaitBlock = FirstBlock; + PKTHREAD WaitThread = WaitBlock->Thread; + + ASSERT_WAIT_LIST_LOCKED; + + /* Loop through all the Wait Blocks, and wake each Object */ + do { + /* Make sure it hasn't timed out */ + if (WaitBlock->WaitKey != X_STATUS_TIMEOUT) { + /* Wake the Object */ + KiWaitSatisfyAny((PKMUTANT)WaitBlock->Object, WaitThread); + } + + /* Move to the next block */ + WaitBlock = WaitBlock->NextWaitBlock; + } while (WaitBlock != FirstBlock); +} + +xbox::void_xt xbox::KiWaitSatisfyAllAndLock +( + IN PKWAIT_BLOCK FirstBlock +) +{ + KiWaitListLock(); + KiWaitSatisfyAll(FirstBlock); + KiWaitListUnlock(); +} + +xbox::void_xt xbox::KiUnwaitThread +( + IN PKTHREAD Thread, + IN long_ptr_xt WaitStatus, + IN KPRIORITY Increment +) +{ + ASSERT_WAIT_LIST_LOCKED; + + /* Unlink the thread */ + KiUnlinkThread(Thread, WaitStatus); + + // We cannot schedule the thread, so we'll just set its state to Ready + Thread->State = Ready; +} + +xbox::void_xt xbox::KiUnwaitThreadAndLock +( + IN PKTHREAD Thread, + IN long_ptr_xt WaitStatus, + IN KPRIORITY Increment +) +{ + KiWaitListLock(); + KiUnwaitThread(Thread, WaitStatus, Increment); + KiWaitListUnlock(); +} + +xbox::void_xt xbox::KiUnlinkThread +( + IN PKTHREAD Thread, + IN long_ptr_xt WaitStatus +) +{ + PKWAIT_BLOCK WaitBlock; + PKTIMER Timer; + + ASSERT_WAIT_LIST_LOCKED; + + /* Update wait status */ + Thread->WaitStatus |= WaitStatus; + + /* Remove the Wait Blocks from the list */ + WaitBlock = Thread->WaitBlockList; + do { + /* Remove it */ + RemoveEntryList(&WaitBlock->WaitListEntry); + + /* Go to the next one */ + WaitBlock = WaitBlock->NextWaitBlock; + } while (WaitBlock != Thread->WaitBlockList); + +#if 0 + // Disabled, as we currently don't put threads in the ready list + /* Remove the thread from the wait list! */ + if (Thread->WaitListEntry.Flink) { + RemoveEntryList(&Thread->WaitListEntry); + } +#endif + + /* Check if there's a Thread Timer */ + Timer = &Thread->Timer; + if (Timer->Header.Inserted) { + KiTimerLock(); + KxRemoveTreeTimer(Timer); + KiTimerUnlock(); + } + +#if 0 + // Disabled, because we don't support queues + /* Increment the Queue's active threads */ + if (Thread->Queue) Thread->Queue->CurrentCount++; +#endif +} diff --git a/src/core/kernel/exports/EmuKrnlKi.h b/src/core/kernel/exports/EmuKrnlKi.h index 3db4e17ce..0d8ec0245 100644 --- a/src/core/kernel/exports/EmuKrnlKi.h +++ b/src/core/kernel/exports/EmuKrnlKi.h @@ -47,6 +47,12 @@ namespace xbox int Acquired; } KI_TIMER_LOCK; + typedef struct _KI_WAIT_LIST_LOCK + { + std::recursive_mutex Mtx; + int Acquired; + } KI_WAIT_LIST_LOCK; + // NOTE: since the apc list is per-thread, we could also create a different mutex for each kthread extern std::mutex KiApcListMtx; @@ -56,6 +62,10 @@ namespace xbox void_xt KiTimerUnlock(); + void_xt KiWaitListLock(); + + void_xt KiWaitListUnlock(); + void_xt KiClockIsr(); xbox::void_xt NTAPI KiCheckTimerTable @@ -129,7 +139,7 @@ namespace xbox IN KIRQL OldIrql ); - void_xt FASTCALL KiWaitSatisfyAll + void_xt KiWaitSatisfyAll ( IN PKWAIT_BLOCK WaitBlock ); @@ -193,6 +203,48 @@ namespace xbox IN PRKAPC Apc, IN KPRIORITY Increment ); + + void_xt KiWaitTestNoYield + ( + IN PVOID Object, + IN KPRIORITY Increment + ); + + void_xt KiWaitTest + ( + IN PVOID Object, + IN KPRIORITY Increment + ); + + void_xt KiWaitSatisfyAll + ( + IN PKWAIT_BLOCK FirstBlock + ); + + void_xt KiWaitSatisfyAllAndLock + ( + IN PKWAIT_BLOCK FirstBlock + ); + + void_xt KiUnwaitThread + ( + IN PKTHREAD Thread, + IN long_ptr_xt WaitStatus, + IN KPRIORITY Increment + ); + + void_xt KiUnwaitThreadAndLock + ( + IN PKTHREAD Thread, + IN long_ptr_xt WaitStatus, + IN KPRIORITY Increment + ); + + void_xt KiUnlinkThread + ( + IN PKTHREAD Thread, + IN long_ptr_xt WaitStatus + ); }; extern xbox::KPROCESS KiUniqueProcess; diff --git a/src/core/kernel/exports/EmuKrnlPs.cpp b/src/core/kernel/exports/EmuKrnlPs.cpp index dd7f6ade1..e324251b4 100644 --- a/src/core/kernel/exports/EmuKrnlPs.cpp +++ b/src/core/kernel/exports/EmuKrnlPs.cpp @@ -121,6 +121,7 @@ static unsigned int WINAPI PCSTProxy params.Ethread, params.TlsDataSize); + eThread->Tcb.State = xbox::Running; xbox::KiExecuteKernelApc(); auto routine = (xbox::PKSYSTEM_ROUTINE)StartFrame->SystemRoutine; @@ -498,9 +499,7 @@ XBSYSAPI EXPORTNUM(258) xbox::void_xt NTAPI xbox::PsTerminateSystemThread eThread->ExitStatus = ExitStatus; eThread->Tcb.Header.SignalState = 1; if (!IsListEmpty(&eThread->Tcb.Header.WaitListHead)) { - // TODO: Implement KiWaitTest's relative objects usage - //KiWaitTest() - assert(0); + KiWaitTest((PVOID)&eThread->Tcb, 0); } if (GetNativeHandle(eThread->UniqueThread)) { From d1c98836044252a95206633c4241f532fa81e388 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Fri, 10 Mar 2023 17:20:07 +0100 Subject: [PATCH 12/35] Make sure to hold the DPC lock until the DPC list has been emptied This fixes a crash in Lord of the rings: The fellowship of the ring --- src/core/kernel/exports/EmuKrnlKe.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 6d4355e14..41a36ca14 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -462,7 +462,7 @@ void ExecuteDpcQueue() // Set DpcRoutineActive to support KeIsExecutingDpc: g_DpcData.IsDpcActive.test_and_set(); KeGetCurrentPrcb()->DpcRoutineActive = TRUE; // Experimental - LeaveCriticalSection(&(g_DpcData.Lock)); + EmuLog(LOG_LEVEL::DEBUG, "Global DpcQueue, calling DPC object 0x%.8X at 0x%.8X", pkdpc, pkdpc->DeferredRoutine); // Call the Deferred Procedure : @@ -472,7 +472,6 @@ void ExecuteDpcQueue() pkdpc->SystemArgument1, pkdpc->SystemArgument2); - EnterCriticalSection(&(g_DpcData.Lock)); KeGetCurrentPrcb()->DpcRoutineActive = FALSE; // Experimental g_DpcData.IsDpcActive.clear(); } From c828d586dbc09f6b4d327e0892bcc2c81762403c Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Sat, 11 Mar 2023 16:40:59 +0100 Subject: [PATCH 13/35] Fixed thread order initialization when a thread starts suspended --- src/core/kernel/exports/EmuKrnlPs.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/core/kernel/exports/EmuKrnlPs.cpp b/src/core/kernel/exports/EmuKrnlPs.cpp index e324251b4..230c94995 100644 --- a/src/core/kernel/exports/EmuKrnlPs.cpp +++ b/src/core/kernel/exports/EmuKrnlPs.cpp @@ -121,8 +121,8 @@ static unsigned int WINAPI PCSTProxy params.Ethread, params.TlsDataSize); - eThread->Tcb.State = xbox::Running; xbox::KiExecuteKernelApc(); + eThread->Tcb.State = xbox::Running; auto routine = (xbox::PKSYSTEM_ROUTINE)StartFrame->SystemRoutine; // Debugging notice : When the below line shows up with an Exception dialog and a @@ -411,16 +411,20 @@ XBSYSAPI EXPORTNUM(255) xbox::ntstatus_xt NTAPI xbox::PsCreateSystemThreadEx g_AffinityPolicy->SetAffinityXbox(handle); - // Now that ThreadId is populated and affinity is changed, resume the thread (unless the guest passed CREATE_SUSPENDED) + // Now that ThreadId is populated and affinity is changed, resume the thread (unless the guest passed CREATE_SUSPENDED), then wait until the new thread has + // finished initialization if (CreateSuspended) { KeSuspendThread(&eThread->Tcb); } - ResumeThread(handle); - // Log ThreadID identical to how GetCurrentThreadID() is rendered : EmuLog(LOG_LEVEL::DEBUG, "Created Xbox proxy thread. Handle : 0x%X, ThreadId : [0x%.4X], Native Handle : 0x%X, Native ThreadId : [0x%.4X]", *ThreadHandle, eThread->UniqueThread, handle, ThreadId); + + ResumeThread(handle); + while (eThread->Tcb.State == Initialized) { + std::this_thread::yield(); + } } RETURN(X_STATUS_SUCCESS); From 4ea6ecd2479e68fe2fe79866e6d11fa47dda614b Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Sun, 12 Mar 2023 10:35:54 +0100 Subject: [PATCH 14/35] Removed delta amount added to KeSystemTime --- src/common/Timer.cpp | 35 +++++++++++--------------- src/core/kernel/common/rtl.h | 3 +++ src/core/kernel/exports/EmuKrnlKe.cpp | 6 +---- src/core/kernel/exports/EmuKrnlKi.cpp | 16 ++++++------ src/core/kernel/exports/EmuKrnlNt.cpp | 6 ++--- src/core/kernel/exports/EmuKrnlRtl.cpp | 5 ++++ src/core/kernel/init/CxbxKrnl.cpp | 1 + src/core/kernel/support/Emu.cpp | 5 ---- src/core/kernel/support/Emu.h | 3 --- 9 files changed, 35 insertions(+), 45 deletions(-) diff --git a/src/common/Timer.cpp b/src/common/Timer.cpp index 2a48976f1..2945fc32f 100644 --- a/src/common/Timer.cpp +++ b/src/common/Timer.cpp @@ -27,9 +27,7 @@ #include -#ifdef _WIN32 #include -#endif #include #include #include @@ -42,9 +40,6 @@ #include "devices\Xbox.h" #include "devices\usb\OHCI.h" #include "core\hle\DSOUND\DirectSound\DirectSoundGlobal.hpp" -#ifdef __linux__ -#include -#endif static uint64_t last_qpc; // last time when QPC was called @@ -56,6 +51,21 @@ static uint64_t pit_last_qpc; // last QPC time of the pit int64_t HostQPCFrequency, HostQPCStartTime; +void timer_init() +{ + QueryPerformanceFrequency(reinterpret_cast(&HostQPCFrequency)); + QueryPerformanceCounter(reinterpret_cast(&last_qpc)); + pit_last_qpc = last_qpc; + pit_last = get_now(); + + // Synchronize xbox system time with host time + LARGE_INTEGER HostSystemTime; + GetSystemTimeAsFileTime((LPFILETIME)&HostSystemTime); + xbox::KeSystemTime.High2Time = HostSystemTime.u.HighPart; + xbox::KeSystemTime.LowPart = HostSystemTime.u.LowPart; + xbox::KeSystemTime.High1Time = HostSystemTime.u.HighPart; +} + // More precise sleep, but with increased CPU usage void SleepPrecise(std::chrono::steady_clock::time_point targetTime) { @@ -168,21 +178,6 @@ xbox::void_xt NTAPI system_events(xbox::PVOID arg) } } -// Retrives the frequency of the high resolution clock of the host -void timer_init() -{ -#ifdef _WIN32 - QueryPerformanceFrequency(reinterpret_cast(&HostQPCFrequency)); - QueryPerformanceCounter(reinterpret_cast(&last_qpc)); - pit_last_qpc = last_qpc; - pit_last = get_now(); -#elif __linux__ - ClockFrequency = 0; -#else -#error "Unsupported OS" -#endif -} - int64_t Timer_GetScaledPerformanceCounter(int64_t Period) { LARGE_INTEGER currentQPC; diff --git a/src/core/kernel/common/rtl.h b/src/core/kernel/common/rtl.h index 924d11ed8..8af147374 100644 --- a/src/core/kernel/common/rtl.h +++ b/src/core/kernel/common/rtl.h @@ -605,6 +605,9 @@ XBSYSAPI EXPORTNUM(352) void_xt NTAPI RtlRip PCHAR Message ); +void_xt RtlInitSystem(); +extern RTL_CRITICAL_SECTION NtSystemTimeCritSec; + } #endif diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 41a36ca14..25de991f7 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -166,7 +166,7 @@ xbox::void_xt NTAPI xbox::KeSetSystemTime ) { KIRQL OldIrql, OldIrql2; - LARGE_INTEGER DeltaTime, HostTime; + LARGE_INTEGER DeltaTime; PLIST_ENTRY ListHead, NextEntry; PKTIMER Timer; LIST_ENTRY TempList, TempList2; @@ -184,10 +184,6 @@ xbox::void_xt NTAPI xbox::KeSetSystemTime /* Query the system time now */ KeQuerySystemTime(OldTime); - /* Surely, we won't set the system time here, but we CAN remember a delta to the host system time */ - HostTime.QuadPart = OldTime->QuadPart - HostSystemTimeDelta.load(); - HostSystemTimeDelta = NewTime->QuadPart - HostTime.QuadPart; - /* Calculate the difference between the new and the old time */ DeltaTime.QuadPart = NewTime->QuadPart - OldTime->QuadPart; diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index fc416dbda..5dd308383 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -148,8 +148,7 @@ xbox::void_xt xbox::KiWaitListUnlock() xbox::void_xt xbox::KiClockIsr() { KIRQL OldIrql; - LARGE_INTEGER InterruptTime; - LARGE_INTEGER HostSystemTime; + LARGE_INTEGER InterruptTime, SystemTime; ULONG Hand; DWORD OldKeTickCount; @@ -164,13 +163,12 @@ xbox::void_xt xbox::KiClockIsr() KeInterruptTime.High1Time = InterruptTime.u.HighPart; // Update the system time - // NOTE: I'm not sure if we should round down the host system time to the nearest multiple - // of the Xbox clock increment... - GetSystemTimeAsFileTime((LPFILETIME)&HostSystemTime); - HostSystemTime.QuadPart += HostSystemTimeDelta.load(); - KeSystemTime.High2Time = HostSystemTime.u.HighPart; - KeSystemTime.LowPart = HostSystemTime.u.LowPart; - KeSystemTime.High1Time = HostSystemTime.u.HighPart; + SystemTime.u.LowPart = KeSystemTime.LowPart; + SystemTime.u.HighPart = KeSystemTime.High1Time; + SystemTime.QuadPart += CLOCK_TIME_INCREMENT; + KeSystemTime.High2Time = SystemTime.u.HighPart; + KeSystemTime.LowPart = SystemTime.u.LowPart; + KeSystemTime.High1Time = SystemTime.u.HighPart; // Update the tick counter OldKeTickCount = KeTickCount; diff --git a/src/core/kernel/exports/EmuKrnlNt.cpp b/src/core/kernel/exports/EmuKrnlNt.cpp index 19be4b8e0..35ee825e8 100644 --- a/src/core/kernel/exports/EmuKrnlNt.cpp +++ b/src/core/kernel/exports/EmuKrnlNt.cpp @@ -58,7 +58,7 @@ namespace NtDll #include // Prevent setting the system time from multiple threads at the same time -std::mutex NtSystemTimeMtx; +xbox::RTL_CRITICAL_SECTION xbox::NtSystemTimeCritSec; // ****************************************************************** // * 0x00B8 - NtAllocateVirtualMemory() @@ -1976,7 +1976,7 @@ XBSYSAPI EXPORTNUM(228) xbox::ntstatus_xt NTAPI xbox::NtSetSystemTime ret = STATUS_ACCESS_VIOLATION; } else { - NtSystemTimeMtx.lock(); + RtlEnterCriticalSectionAndRegion(&NtSystemTimeCritSec); NewSystemTime = *SystemTime; if (NewSystemTime.u.HighPart > 0 && NewSystemTime.u.HighPart <= 0x20000000) { /* Convert the time and set it in HAL */ @@ -1996,7 +1996,7 @@ XBSYSAPI EXPORTNUM(228) xbox::ntstatus_xt NTAPI xbox::NtSetSystemTime else { ret = STATUS_INVALID_PARAMETER; } - NtSystemTimeMtx.unlock(); + RtlLeaveCriticalSectionAndRegion(&NtSystemTimeCritSec); } RETURN(ret); diff --git a/src/core/kernel/exports/EmuKrnlRtl.cpp b/src/core/kernel/exports/EmuKrnlRtl.cpp index 671cca6d5..dad495104 100644 --- a/src/core/kernel/exports/EmuKrnlRtl.cpp +++ b/src/core/kernel/exports/EmuKrnlRtl.cpp @@ -89,6 +89,11 @@ xbox::boolean_xt RtlpCaptureStackLimits( return TRUE; } +xbox::void_xt xbox::RtlInitSystem() +{ + xbox::RtlInitializeCriticalSection(&NtSystemTimeCritSec); +} + // ****************************************************************** // * 0x0104 - RtlAnsiStringToUnicodeString() // ****************************************************************** diff --git a/src/core/kernel/init/CxbxKrnl.cpp b/src/core/kernel/init/CxbxKrnl.cpp index 3ed6d83ae..a679eb905 100644 --- a/src/core/kernel/init/CxbxKrnl.cpp +++ b/src/core/kernel/init/CxbxKrnl.cpp @@ -1285,6 +1285,7 @@ static void CxbxrKrnlInitHacks() } xbox::PsInitSystem(); xbox::KiInitSystem(); + xbox::RtlInitSystem(); // initialize graphics EmuLogInit(LOG_LEVEL::DEBUG, "Initializing render window."); diff --git a/src/core/kernel/support/Emu.cpp b/src/core/kernel/support/Emu.cpp index da98ab08f..45f697d85 100644 --- a/src/core/kernel/support/Emu.cpp +++ b/src/core/kernel/support/Emu.cpp @@ -49,11 +49,6 @@ bool g_DisablePixelShaders = false; bool g_UseAllCores = false; bool g_SkipRdtscPatching = false; -// Delta added to host SystemTime, used in KiClockIsr and KeSetSystemTime -// This shouldn't need to be atomic, but because raising the IRQL to high lv in KeSetSystemTime doesn't really stop KiClockIsr from running, -// we need it for now to prevent reading a corrupted value while KeSetSystemTime is in the middle of updating it -std::atomic_int64_t HostSystemTimeDelta(0); - // Static Function(s) static int ExitException(LPEXCEPTION_POINTERS e); diff --git a/src/core/kernel/support/Emu.h b/src/core/kernel/support/Emu.h index d12f493ab..2087adc84 100644 --- a/src/core/kernel/support/Emu.h +++ b/src/core/kernel/support/Emu.h @@ -68,9 +68,6 @@ extern HWND g_hEmuWindow; extern HANDLE g_CurrentProcessHandle; // Set in CxbxKrnlMain -// Delta added to host SystemTime, used in KiClockIsr and KeSetSystemTime -extern std::atomic_int64_t HostSystemTimeDelta; - typedef struct DUMMY_KERNEL { IMAGE_DOS_HEADER DosHeader; From 32b4393085080e4dc65b80f2dafe5f9482b78df7 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Sun, 12 Mar 2023 10:52:35 +0100 Subject: [PATCH 15/35] Raise priority of system events thread --- src/common/Timer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/common/Timer.cpp b/src/common/Timer.cpp index 2945fc32f..983b85d7d 100644 --- a/src/common/Timer.cpp +++ b/src/common/Timer.cpp @@ -158,6 +158,10 @@ static uint64_t get_next(uint64_t now) xbox::void_xt NTAPI system_events(xbox::PVOID arg) { + // Testing shows that, if this thread has the same priority of the other xbox threads, it can take tens, even hundreds of ms to complete a single loop. + // So we increase its priority to above normal, so that it completes a loop roughly every 3.1ms + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); + while (true) { const uint64_t nearest_next = get_next(get_now()); QueryPerformanceCounter(reinterpret_cast(&last_time)); From b72cfaa909a77129226adbde585f6b7a6db58c37 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Sun, 12 Mar 2023 11:20:39 +0100 Subject: [PATCH 16/35] Account for delays between calls to KiClockIsr This fixes the slowness in the dashboard --- src/common/Timer.cpp | 2 +- src/core/kernel/exports/EmuKrnlKi.cpp | 8 ++++---- src/core/kernel/exports/EmuKrnlKi.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/Timer.cpp b/src/common/Timer.cpp index 983b85d7d..830459de9 100644 --- a/src/common/Timer.cpp +++ b/src/common/Timer.cpp @@ -103,7 +103,7 @@ static uint64_t pit_next(uint64_t now) uint64_t next = pit_last + pit_period; if (now >= next) { - xbox::KiClockIsr(); + xbox::KiClockIsr((now - pit_last - pit_period) / 1000); pit_last = get_now(); return pit_period; } diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index 5dd308383..8680815bf 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -145,7 +145,7 @@ xbox::void_xt xbox::KiWaitListUnlock() KiWaitListMtx.Mtx.unlock(); } -xbox::void_xt xbox::KiClockIsr() +xbox::void_xt xbox::KiClockIsr(ulonglong_xt ExtraMs) { KIRQL OldIrql; LARGE_INTEGER InterruptTime, SystemTime; @@ -157,7 +157,7 @@ xbox::void_xt xbox::KiClockIsr() // Update the interrupt time InterruptTime.u.LowPart = KeInterruptTime.LowPart; InterruptTime.u.HighPart = KeInterruptTime.High1Time; - InterruptTime.QuadPart += CLOCK_TIME_INCREMENT; + InterruptTime.QuadPart += (CLOCK_TIME_INCREMENT * (1 + ExtraMs)); KeInterruptTime.High2Time = InterruptTime.u.HighPart; KeInterruptTime.LowPart = InterruptTime.u.LowPart; KeInterruptTime.High1Time = InterruptTime.u.HighPart; @@ -165,14 +165,14 @@ xbox::void_xt xbox::KiClockIsr() // Update the system time SystemTime.u.LowPart = KeSystemTime.LowPart; SystemTime.u.HighPart = KeSystemTime.High1Time; - SystemTime.QuadPart += CLOCK_TIME_INCREMENT; + SystemTime.QuadPart += (CLOCK_TIME_INCREMENT * (1 + ExtraMs)); KeSystemTime.High2Time = SystemTime.u.HighPart; KeSystemTime.LowPart = SystemTime.u.LowPart; KeSystemTime.High1Time = SystemTime.u.HighPart; // Update the tick counter OldKeTickCount = KeTickCount; - ++KeTickCount; + KeTickCount += (1 + static_cast(ExtraMs)); // Because this function must be fast to continuously update the kernel clocks, if somebody else is currently // holding the lock, we won't wait and instead skip the check of the timers for this cycle diff --git a/src/core/kernel/exports/EmuKrnlKi.h b/src/core/kernel/exports/EmuKrnlKi.h index 0d8ec0245..868b64c1c 100644 --- a/src/core/kernel/exports/EmuKrnlKi.h +++ b/src/core/kernel/exports/EmuKrnlKi.h @@ -66,7 +66,7 @@ namespace xbox void_xt KiWaitListUnlock(); - void_xt KiClockIsr(); + void_xt KiClockIsr(ulonglong_xt ExtraMs); xbox::void_xt NTAPI KiCheckTimerTable ( From 5d510752e62fd1d175ad0146a54e8f26b5e1e329 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Sun, 12 Mar 2023 16:20:28 +0100 Subject: [PATCH 17/35] Adjust KeSystemTime when the host system time is changed by the user --- src/common/IPCHybrid.hpp | 5 +++-- src/common/win32/IPCWindows.cpp | 4 ++++ src/core/hle/D3D8/Direct3D9/Direct3D9.cpp | 16 ++++++++++++++ src/core/kernel/exports/EmuKrnlKe.cpp | 1 + src/core/kernel/exports/EmuKrnlKe.h | 2 ++ src/core/kernel/exports/EmuKrnlKi.cpp | 26 +++++++++++++++++------ src/gui/WndMain.cpp | 7 ++++++ src/gui/resource/ResCxbx.h | 3 ++- 8 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/common/IPCHybrid.hpp b/src/common/IPCHybrid.hpp index 079a1baaf..a0a1ec215 100644 --- a/src/common/IPCHybrid.hpp +++ b/src/common/IPCHybrid.hpp @@ -49,8 +49,9 @@ void ipc_send_gui_update(IPC_UPDATE_GUI command, const unsigned int value); // ****************************************************************** typedef enum class _IPC_UPDATE_KERNEL { - CONFIG_LOGGING_SYNC = 0 - , CONFIG_INPUT_SYNC + CONFIG_LOGGING_SYNC = 0, + CONFIG_INPUT_SYNC, + CONFIG_CHANGE_TIME } IPC_UPDATE_KERNEL; void ipc_send_kernel_update(IPC_UPDATE_KERNEL command, const int value, const unsigned int hwnd); diff --git a/src/common/win32/IPCWindows.cpp b/src/common/win32/IPCWindows.cpp index 82a342d89..cbf04f2c7 100644 --- a/src/common/win32/IPCWindows.cpp +++ b/src/common/win32/IPCWindows.cpp @@ -101,6 +101,10 @@ void ipc_send_kernel_update(IPC_UPDATE_KERNEL command, const int value, const un cmdParam = ID_SYNC_CONFIG_INPUT; break; + case IPC_UPDATE_KERNEL::CONFIG_CHANGE_TIME: + cmdParam = ID_SYNC_TIME_CHANGE; + break; + default: cmdParam = 0; break; diff --git a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp index 674d55c06..188096a70 100644 --- a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp +++ b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp @@ -38,6 +38,7 @@ #include "core\kernel\support\Emu.h" #include "core\kernel\support\EmuFS.h" #include "core\kernel\support\NativeHandle.h" +#include "core\kernel\exports\EmuKrnlKe.h" #include "EmuShared.h" #include "..\FixedFunctionState.h" #include "core\hle\D3D8\ResourceTracker.h" @@ -1685,6 +1686,13 @@ static LRESULT WINAPI EmuMsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPar } break; + case ID_SYNC_TIME_CHANGE: + { + // Sent by the GUI when it detects WM_TIMECHANGE + xbox::KeSystemTimeChanged.test_and_set(); + } + break; + default: break; } @@ -1702,6 +1710,14 @@ static LRESULT WINAPI EmuMsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPar } break; + case WM_TIMECHANGE: + { + // NOTE: this is only received if the loader was launched from the command line without the GUI + xbox::KeSystemTimeChanged.test_and_set(); + return DefWindowProc(hWnd, msg, wParam, lParam); + } + break; + case WM_SYSKEYDOWN: { if(wParam == VK_RETURN) diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 25de991f7..540d5a274 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -101,6 +101,7 @@ typedef struct _DpcData { } DpcData; DpcData g_DpcData = { 0 }; // Note : g_DpcData is initialized in InitDpcData() +std::atomic_flag xbox::KeSystemTimeChanged; xbox::ulonglong_xt LARGE_INTEGER2ULONGLONG(xbox::LARGE_INTEGER value) { diff --git a/src/core/kernel/exports/EmuKrnlKe.h b/src/core/kernel/exports/EmuKrnlKe.h index a7e2ebbc7..03c514eca 100644 --- a/src/core/kernel/exports/EmuKrnlKe.h +++ b/src/core/kernel/exports/EmuKrnlKe.h @@ -27,6 +27,8 @@ namespace xbox { + extern std::atomic_flag KeSystemTimeChanged; + void_xt NTAPI KeSetSystemTime ( IN PLARGE_INTEGER NewTime, diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index 8680815bf..b48db1b94 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -86,6 +86,7 @@ the said software). #include "Logging.h" // For LOG_FUNC() #include "EmuKrnl.h" // for the list support functions #include "EmuKrnlKi.h" +#include "EmuKrnlKe.h" #define MAX_TIMER_DPCS 16 @@ -163,12 +164,25 @@ xbox::void_xt xbox::KiClockIsr(ulonglong_xt ExtraMs) KeInterruptTime.High1Time = InterruptTime.u.HighPart; // Update the system time - SystemTime.u.LowPart = KeSystemTime.LowPart; - SystemTime.u.HighPart = KeSystemTime.High1Time; - SystemTime.QuadPart += (CLOCK_TIME_INCREMENT * (1 + ExtraMs)); - KeSystemTime.High2Time = SystemTime.u.HighPart; - KeSystemTime.LowPart = SystemTime.u.LowPart; - KeSystemTime.High1Time = SystemTime.u.HighPart; + if (KeSystemTimeChanged.test()) [[unlikely]] { + KeSystemTimeChanged.clear(); + LARGE_INTEGER HostSystemTime, OldSystemTime; + GetSystemTimeAsFileTime((LPFILETIME)&HostSystemTime); + xbox::KeSystemTime.High2Time = HostSystemTime.u.HighPart; + xbox::KeSystemTime.LowPart = HostSystemTime.u.LowPart; + xbox::KeSystemTime.High1Time = HostSystemTime.u.HighPart; + KfLowerIrql(OldIrql); + KeSetSystemTime(&HostSystemTime, &OldSystemTime); + OldIrql = KfRaiseIrql(CLOCK_LEVEL); + } + else { + SystemTime.u.LowPart = KeSystemTime.LowPart; + SystemTime.u.HighPart = KeSystemTime.High1Time; + SystemTime.QuadPart += (CLOCK_TIME_INCREMENT * (1 + ExtraMs)); + KeSystemTime.High2Time = SystemTime.u.HighPart; + KeSystemTime.LowPart = SystemTime.u.LowPart; + KeSystemTime.High1Time = SystemTime.u.HighPart; + } // Update the tick counter OldKeTickCount = KeTickCount; diff --git a/src/gui/WndMain.cpp b/src/gui/WndMain.cpp index d34447ee7..557173090 100644 --- a/src/gui/WndMain.cpp +++ b/src/gui/WndMain.cpp @@ -393,6 +393,13 @@ LRESULT CALLBACK WndMain::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lP }; break; // added per PVS suggestion. + case WM_TIMECHANGE: + { + ipc_send_kernel_update(IPC_UPDATE_KERNEL::CONFIG_CHANGE_TIME, 0, reinterpret_cast(m_hwndChild)); + return DefWindowProc(hwnd, uMsg, wParam, lParam); + } + break; + case WM_TIMER: { switch (wParam) diff --git a/src/gui/resource/ResCxbx.h b/src/gui/resource/ResCxbx.h index 927737d14..eff2e721a 100644 --- a/src/gui/resource/ResCxbx.h +++ b/src/gui/resource/ResCxbx.h @@ -353,6 +353,7 @@ #define ID_SETTINGS_EXPERIMENTAL 40113 #define ID_SETTINGS_IGNOREINVALIDXBESIG 40114 #define ID_SETTINGS_IGNOREINVALIDXBESEC 40115 +#define ID_SYNC_TIME_CHANGE 40116 #define IDC_STATIC -1 // Next default values for new objects @@ -360,7 +361,7 @@ #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 139 -#define _APS_NEXT_COMMAND_VALUE 40116 +#define _APS_NEXT_COMMAND_VALUE 40117 #define _APS_NEXT_CONTROL_VALUE 1308 #define _APS_NEXT_SYMED_VALUE 109 #endif From 7dc2ac080f05fd09e523fa025d9dfbdf74a820d3 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Sun, 12 Mar 2023 16:50:56 +0100 Subject: [PATCH 18/35] Use get_now directly in system_events instead of qpc --- src/common/Timer.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/common/Timer.cpp b/src/common/Timer.cpp index 830459de9..b605654a9 100644 --- a/src/common/Timer.cpp +++ b/src/common/Timer.cpp @@ -43,7 +43,6 @@ static uint64_t last_qpc; // last time when QPC was called -static uint64_t last_time; // starting time point until the next periodic event is due static uint64_t exec_time; // total execution time in us since the emulation started static uint64_t pit_last; // last time when the pit time was updated static uint64_t pit_last_qpc; // last QPC time of the pit @@ -163,21 +162,16 @@ xbox::void_xt NTAPI system_events(xbox::PVOID arg) SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); while (true) { - const uint64_t nearest_next = get_next(get_now()); - QueryPerformanceCounter(reinterpret_cast(&last_time)); + const uint64_t last_time = get_now(); + const uint64_t nearest_next = get_next(last_time); while (true) { update_non_periodic_events(); - LARGE_INTEGER now; - QueryPerformanceCounter(&now); - uint64_t elapsed_us = static_cast(now.QuadPart) - last_time; - elapsed_us *= 1000000; - elapsed_us /= HostQPCFrequency; + uint64_t elapsed_us = get_now() - last_time; if (elapsed_us >= nearest_next) { break; } std::this_thread::yield(); - _mm_pause(); } } } From 9e3873d1df10f780b0bf1d2c1f4e35ce27a27980 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Sun, 12 Mar 2023 16:51:38 +0100 Subject: [PATCH 19/35] Place nvnet in its own thread --- src/common/Timer.cpp | 3 --- src/devices/network/NVNetDevice.cpp | 16 ++++++++++++---- src/devices/network/NVNetDevice.h | 2 -- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/common/Timer.cpp b/src/common/Timer.cpp index b605654a9..969308279 100644 --- a/src/common/Timer.cpp +++ b/src/common/Timer.cpp @@ -115,9 +115,6 @@ static void update_non_periodic_events() // update dsound dsound_worker(); - // check nvnet - NVNetRecv(); - // check for hw interrupts, but skip the gpu interrupt since that is serviced in vblank_next for (int i = 0; i < 3; i++) { // If the interrupt is pending and connected, process it diff --git a/src/devices/network/NVNetDevice.cpp b/src/devices/network/NVNetDevice.cpp index 72e405c22..8b6b62740 100644 --- a/src/devices/network/NVNetDevice.cpp +++ b/src/devices/network/NVNetDevice.cpp @@ -475,12 +475,19 @@ void EmuNVNet_Write(xbox::addr_xt addr, uint32_t value, int size) EmuLog(LOG_LEVEL::DEBUG, "Write%d: %s (0x%.8X) = 0x%.8X", size * 8, EmuNVNet_GetRegisterName(addr), addr, value); } -void NVNetRecv() +std::thread NVNetRecvThread; +void NVNetRecvThreadProc() { + // NOTE: profiling shows that the winpcap function can take up to 1/6th of the total cpu time of the loader process, so avoid placing + // this function in system_events + g_AffinityPolicy->SetAffinityOther(); static std::unique_ptr packet(new uint8_t[65536]); - int size = g_NVNet->PCAPReceive(packet.get(), 65536); - if (size > 0) { - EmuNVNet_DMAPacketToGuest(packet.get(), size); + while (true) { + int size = g_NVNet->PCAPReceive(packet.get(), 65536); + if (size > 0) { + EmuNVNet_DMAPacketToGuest(packet.get(), size); + } + _mm_pause(); } } @@ -523,6 +530,7 @@ void NVNetDevice::Init() }; PCAPInit(); + NVNetRecvThread = std::thread(NVNetRecvThreadProc); } void NVNetDevice::Reset() diff --git a/src/devices/network/NVNetDevice.h b/src/devices/network/NVNetDevice.h index f7a1c3e97..d2a069396 100644 --- a/src/devices/network/NVNetDevice.h +++ b/src/devices/network/NVNetDevice.h @@ -229,5 +229,3 @@ private: mac_address m_GuestMacAddress = { 0x00, 0x50, 0xF2, 0x00, 0x00, 0x34 }; mac_address m_BroadcastMacAddress = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; }; - -void NVNetRecv(); From fad4120841c6927752f695d28b938492270248ee Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Sun, 12 Mar 2023 17:28:44 +0100 Subject: [PATCH 20/35] Restore single interrupt loop in update_non_periodic_events --- src/common/Timer.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/common/Timer.cpp b/src/common/Timer.cpp index 969308279..1551ca751 100644 --- a/src/common/Timer.cpp +++ b/src/common/Timer.cpp @@ -116,16 +116,12 @@ static void update_non_periodic_events() dsound_worker(); // check for hw interrupts, but skip the gpu interrupt since that is serviced in vblank_next - for (int i = 0; i < 3; i++) { - // If the interrupt is pending and connected, process it - if (g_bEnableAllInterrupts && HalSystemInterrupts[i].IsPending() && EmuInterruptList[i] && EmuInterruptList[i]->Connected) { - HalSystemInterrupts[i].Trigger(EmuInterruptList[i]); - } - } - for (int i = 4; i < MAX_BUS_INTERRUPT_LEVEL; i++) { - // If the interrupt is pending and connected, process it - if (g_bEnableAllInterrupts && HalSystemInterrupts[i].IsPending() && EmuInterruptList[i] && EmuInterruptList[i]->Connected) { - HalSystemInterrupts[i].Trigger(EmuInterruptList[i]); + for (int i = 0; i < MAX_BUS_INTERRUPT_LEVEL; i++) { + if (i != 3) { + // If the interrupt is pending and connected, process it + if (g_bEnableAllInterrupts && HalSystemInterrupts[i].IsPending() && EmuInterruptList[i] && EmuInterruptList[i]->Connected) { + HalSystemInterrupts[i].Trigger(EmuInterruptList[i]); + } } } } From 745f450a6c6ef35df3e12cdd6b5e444237606048 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Mon, 13 Mar 2023 16:19:30 +0100 Subject: [PATCH 21/35] Setup a KTIMER for the other functions using WaitApc too --- src/core/hle/XAPI/Xapi.cpp | 2 +- src/core/kernel/exports/EmuKrnl.h | 53 ++++++++++++++++++--------- src/core/kernel/exports/EmuKrnlKe.cpp | 6 +-- src/core/kernel/exports/EmuKrnlNt.cpp | 2 +- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/core/hle/XAPI/Xapi.cpp b/src/core/hle/XAPI/Xapi.cpp index a17c1682f..a66ffd73d 100644 --- a/src/core/hle/XAPI/Xapi.cpp +++ b/src/core/hle/XAPI/Xapi.cpp @@ -960,7 +960,7 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) NewTime.QuadPart += (static_cast(dwMilliseconds) * CLOCK_TIME_INCREMENT); } - xbox::dword_xt ret = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable]() -> std::optional { + xbox::dword_xt ret = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable]() -> std::optional { DWORD dwRet = SignalObjectAndWait(hObjectToSignal, hObjectToWaitOn, 0, bAlertable); if (dwRet == WAIT_TIMEOUT) { return std::nullopt; diff --git a/src/core/kernel/exports/EmuKrnl.h b/src/core/kernel/exports/EmuKrnl.h index d68e18a9c..3aa2f8d8b 100644 --- a/src/core/kernel/exports/EmuKrnl.h +++ b/src/core/kernel/exports/EmuKrnl.h @@ -110,15 +110,15 @@ void RestoreInterruptMode(bool value); void CallSoftwareInterrupt(const xbox::KIRQL SoftwareIrql); template -std::optional SatisfyWait(T &&Lambda, xbox::PETHREAD eThread, xbox::boolean_xt Alertable, xbox::char_xt WaitMode) +std::optional SatisfyWait(T &&Lambda, xbox::PKTHREAD kThread, xbox::boolean_xt Alertable, xbox::char_xt WaitMode) { if (const auto ret = Lambda()) { return ret; } xbox::KiApcListMtx.lock(); - bool EmptyKernel = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[xbox::KernelMode]); - bool EmptyUser = IsListEmpty(&eThread->Tcb.ApcState.ApcListHead[xbox::UserMode]); + bool EmptyKernel = IsListEmpty(&kThread->ApcState.ApcListHead[xbox::KernelMode]); + bool EmptyUser = IsListEmpty(&kThread->ApcState.ApcListHead[xbox::UserMode]); xbox::KiApcListMtx.unlock(); if (EmptyKernel == false) { @@ -135,18 +135,20 @@ std::optional SatisfyWait(T &&Lambda, xbox::PETHREAD eThread, return std::nullopt; } -template +template xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolean_xt Alertable, xbox::char_xt WaitMode) { // NOTE: kThread->Alerted is currently never set. When the alerted mechanism is implemented, the alerts should // also interrupt the wait - xbox::PETHREAD eThread = reinterpret_cast(EmuKeGetPcr()->Prcb->CurrentThread); + xbox::PKTHREAD kThread = EmuKeGetPcr()->Prcb->CurrentThread; if (Timeout == nullptr) { // No timout specified, so this is an infinite wait until an alert, a user apc or the object(s) become(s) signalled + kThread->State = xbox::Waiting; while (true) { - if (const auto ret = SatisfyWait(Lambda, eThread, Alertable, WaitMode)) { + if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) { + kThread->State = xbox::Running; return *ret; } @@ -155,7 +157,7 @@ xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolea } else if (Timeout->QuadPart == 0) { // A zero timeout means that we only have to check the conditions once and then return immediately if they are not satisfied - if (const auto ret = SatisfyWait(Lambda, eThread, Alertable, WaitMode)) { + if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) { return *ret; } else { @@ -164,20 +166,37 @@ xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolea } else { // A non-zero timeout means we have to check the conditions until we reach the requested time - xbox::LARGE_INTEGER ExpireTime, DueTime, NewTime; - xbox::ulonglong_xt Now; - ExpireTime.QuadPart = DueTime.QuadPart = Timeout->QuadPart; // either positive, negative, but not NULL - xbox::PLARGE_INTEGER AbsoluteExpireTime = xbox::KiComputeWaitInterval(&ExpireTime, &DueTime, &NewTime, &Now); - while (Now <= static_cast(AbsoluteExpireTime->QuadPart)) { - if (const auto ret = SatisfyWait(Lambda, eThread, Alertable, WaitMode)) { + if constexpr (setup_ktimer) { + // Setup a timer if it wasn't already by our caller. This is necessary because in this way, KiTimerExpiration can discover the timeout + // and yield to us. Otherwise, we will only be able to discover the timeout when Windows decides to schedule us again, and testing shows that + // tends to happen much later than the due time + xbox::KiTimerLock(); + xbox::PKWAIT_BLOCK WaitBlock = &kThread->TimerWaitBlock; + kThread->WaitBlockList = WaitBlock; + xbox::PKTIMER Timer = &kThread->Timer; + WaitBlock->NextWaitBlock = WaitBlock; + Timer->Header.WaitListHead.Flink = &WaitBlock->WaitListEntry; + Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; + if (xbox::KiInsertTreeTimer(Timer, *Timeout) == FALSE) { + xbox::KiTimerUnlock(); + return X_STATUS_TIMEOUT; + } + xbox::KiTimerUnlock(); + } + kThread->State = xbox::Waiting; + while (true) { + if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) { + kThread->State = xbox::Running; return *ret; } - std::this_thread::yield(); - Now = xbox::KeQueryInterruptTime(); - } + if (setup_ktimer && (kThread->State == xbox::Ready)) { + kThread->State = xbox::Running; + return kThread->WaitStatus; + } - return X_STATUS_TIMEOUT; + std::this_thread::yield(); + } } } diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 540d5a274..79fa6c8e2 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -711,7 +711,7 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread // We can't remove NtDll::NtDelayExecution until all APCs queued by Io are implemented by our kernel as well // Test case: Metal Slug 3 - xbox::ntstatus_xt ret = WaitApc([Alertable]() -> std::optional { + xbox::ntstatus_xt ret = WaitApc([Alertable]() -> std::optional { NtDll::LARGE_INTEGER ExpireTime; ExpireTime.QuadPart = 0; NTSTATUS Status = NtDll::NtDelayExecution(Alertable, &ExpireTime); @@ -2334,7 +2334,7 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects //} // TODO: Remove this after we have our own scheduler and the above is implemented - WaitStatus = WaitApc([Thread]() -> std::optional { + WaitStatus = WaitApc([Thread]() -> std::optional { if (Thread->State == Ready) { // We have been readied to resume execution, so exit the wait return std::make_optional(Thread->WaitStatus); @@ -2522,7 +2522,7 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject KiWaitListUnlock(); // TODO: Remove this after we have our own scheduler and the above is implemented - WaitStatus = WaitApc([Thread]() -> std::optional { + WaitStatus = WaitApc([Thread]() -> std::optional { if (Thread->State == Ready) { // We have been readied to resume execution, so exit the wait return std::make_optional(Thread->WaitStatus); diff --git a/src/core/kernel/exports/EmuKrnlNt.cpp b/src/core/kernel/exports/EmuKrnlNt.cpp index 35ee825e8..cb2c8f324 100644 --- a/src/core/kernel/exports/EmuKrnlNt.cpp +++ b/src/core/kernel/exports/EmuKrnlNt.cpp @@ -2229,7 +2229,7 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx // Because user APCs from NtQueueApcThread are now handled by the kernel, we need to wait for them ourselves - xbox::ntstatus_xt ret = WaitApc([Count, &nativeHandles, WaitType, Alertable]() -> std::optional { + xbox::ntstatus_xt ret = WaitApc([Count, &nativeHandles, WaitType, Alertable]() -> std::optional { NtDll::LARGE_INTEGER ExpireTime; ExpireTime.QuadPart = 0; NTSTATUS Status = NtDll::NtWaitForMultipleObjects( From c3b3a1b107a29d932a6a4de6d2edd26341960290 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Wed, 15 Mar 2023 00:09:50 +0100 Subject: [PATCH 22/35] Hack: <= thread priority instead of >= --- src/core/kernel/exports/EmuKrnlKe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 79fa6c8e2..49a83e273 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -1801,7 +1801,7 @@ XBSYSAPI EXPORTNUM(143) xbox::long_xt NTAPI xbox::KeSetBasePriorityThread // too to wait on the vblank kevent. When the vblank does occur, this other thread will satisfy the wait first, and set the kevent back to non-signalled. Thus, the other // thread will miss the signal because typically, Windows won't re-schedule it after many more vblank have already occured. The proper solution is to boost the priority of // the thread when the kevent is signalled, with the increment argument specified in KeSetEvent. Such boosts should also be appiled whenever a thread satisfies a wait. - if (Priority >= THREAD_PRIORITY_NORMAL) { + if (Priority <= THREAD_PRIORITY_NORMAL) { BOOL result = SetThreadPriority(*nativeHandle, Priority); if (!result) { EmuLog(LOG_LEVEL::WARNING, "SetThreadPriority failed: %s", WinError2Str().c_str()); From 8cefa8ba8f2f15a134bddee7b7b6163113e7e8f9 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Tue, 14 Mar 2023 23:57:40 +0100 Subject: [PATCH 23/35] Revert to using the host to do thread suspension --- src/core/kernel/exports/EmuKrnlKe.cpp | 37 +++++++++++++++++++++++++++ src/core/kernel/exports/EmuKrnlKe.h | 10 ++++++++ src/core/kernel/exports/EmuKrnlPs.cpp | 15 ++++++----- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 49a83e273..b39bc60ff 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -132,6 +132,28 @@ xbox::ulonglong_xt LARGE_INTEGER2ULONGLONG(xbox::LARGE_INTEGER value) } +xbox::void_xt xbox::KeResumeThreadEx +( + IN PKTHREAD Thread +) +{ + // This is only to be used to synchronize new thread creation with the thread that spawned it + + Thread->SuspendSemaphore.Header.SignalState = 1; + KiWaitTest(&Thread->SuspendSemaphore, 0); +} + +xbox::void_xt xbox::KeSuspendThreadEx +( + IN PKTHREAD Thread +) +{ + // This is only to be used to synchronize new thread creation with the thread that spawned it + + Thread->SuspendSemaphore.Header.SignalState = 0; + KiInsertQueueApc(&Thread->SuspendApc, 0); +} + // ****************************************************************** // * EmuKeGetPcr() // * NOTE: This is a macro on the Xbox, however we implement it @@ -1729,8 +1751,13 @@ XBSYSAPI EXPORTNUM(140) xbox::ulong_xt NTAPI xbox::KeResumeThread if (OldCount != 0) { --Thread->SuspendCount; if (Thread->SuspendCount == 0) { +#if 0 ++Thread->SuspendSemaphore.Header.SignalState; KiWaitTest(&Thread->SuspendSemaphore, 0); +#else + const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread); + ResumeThread(*nativeHandle); +#endif } } @@ -2089,10 +2116,20 @@ XBSYSAPI EXPORTNUM(152) xbox::ulong_xt NTAPI xbox::KeSuspendThread if (Thread->ApcState.ApcQueueable == TRUE) { ++Thread->SuspendCount; if (OldCount == 0) { +#if 0 if (KiInsertQueueApc(&Thread->SuspendApc, 0) == FALSE) { --Thread->SuspendSemaphore.Header.SignalState; } +#else + // JSRF creates a thread at 0x0013BC30 and then it attempts to continuously suspend/resume it. Unfortunately, this thread performs a never ending loop (and + // terminates if it ever exit the loop), and never calls any kernel functions in the middle. This means that our suspend APC will never be executed and so + // we cannot suspend such thread. Thus, we will always have to rely on the host to do the suspension, as long as we do direct execution. Note that this is + // a general issue for all kernel APCs too. + const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread); + SuspendThread(*nativeHandle); +#endif } + } KiUnlockDispatcherDatabase(OldIrql); diff --git a/src/core/kernel/exports/EmuKrnlKe.h b/src/core/kernel/exports/EmuKrnlKe.h index 03c514eca..02ca32c71 100644 --- a/src/core/kernel/exports/EmuKrnlKe.h +++ b/src/core/kernel/exports/EmuKrnlKe.h @@ -52,5 +52,15 @@ namespace xbox IN PKPROCESS Process ); + xbox::void_xt KeResumeThreadEx + ( + IN PKTHREAD Thread + ); + + xbox::void_xt KeSuspendThreadEx + ( + IN PKTHREAD Thread + ); + void_xt KeEmptyQueueApc(); } diff --git a/src/core/kernel/exports/EmuKrnlPs.cpp b/src/core/kernel/exports/EmuKrnlPs.cpp index 230c94995..033493269 100644 --- a/src/core/kernel/exports/EmuKrnlPs.cpp +++ b/src/core/kernel/exports/EmuKrnlPs.cpp @@ -122,7 +122,6 @@ static unsigned int WINAPI PCSTProxy params.TlsDataSize); xbox::KiExecuteKernelApc(); - eThread->Tcb.State = xbox::Running; auto routine = (xbox::PKSYSTEM_ROUTINE)StartFrame->SystemRoutine; // Debugging notice : When the below line shows up with an Exception dialog and a @@ -411,20 +410,24 @@ XBSYSAPI EXPORTNUM(255) xbox::ntstatus_xt NTAPI xbox::PsCreateSystemThreadEx g_AffinityPolicy->SetAffinityXbox(handle); + // Wait for the initialization of the remaining thread state + KeSuspendThreadEx(&eThread->Tcb); + ResumeThread(handle); + while (eThread->Tcb.State == Initialized) { + std::this_thread::yield(); + } + // Now that ThreadId is populated and affinity is changed, resume the thread (unless the guest passed CREATE_SUSPENDED), then wait until the new thread has // finished initialization if (CreateSuspended) { KeSuspendThread(&eThread->Tcb); } + KeResumeThreadEx(&eThread->Tcb); + // Log ThreadID identical to how GetCurrentThreadID() is rendered : EmuLog(LOG_LEVEL::DEBUG, "Created Xbox proxy thread. Handle : 0x%X, ThreadId : [0x%.4X], Native Handle : 0x%X, Native ThreadId : [0x%.4X]", *ThreadHandle, eThread->UniqueThread, handle, ThreadId); - - ResumeThread(handle); - while (eThread->Tcb.State == Initialized) { - std::this_thread::yield(); - } } RETURN(X_STATUS_SUCCESS); From 253c198421aef8cf00fd009e2fe46683a20ee7ca Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Sat, 18 Mar 2023 11:38:39 +0100 Subject: [PATCH 24/35] Always create a wait object even when we satisfy the wait on the host side + fixed a bug in KiWaitTestNoYield This fixes an occasionl freeze in Steel Battalion + the slowness in JSRF --- src/core/hle/XAPI/Xapi.cpp | 38 ++++++++++++++++++++++++--- src/core/kernel/exports/EmuKrnl.cpp | 2 ++ src/core/kernel/exports/EmuKrnl.h | 22 +++++++++------- src/core/kernel/exports/EmuKrnlKe.cpp | 15 +++++++++++ src/core/kernel/exports/EmuKrnlKi.cpp | 2 +- src/core/kernel/exports/EmuKrnlNt.cpp | 15 +++++++++++ 6 files changed, 81 insertions(+), 13 deletions(-) diff --git a/src/core/hle/XAPI/Xapi.cpp b/src/core/hle/XAPI/Xapi.cpp index a66ffd73d..a0c1368c8 100644 --- a/src/core/hle/XAPI/Xapi.cpp +++ b/src/core/hle/XAPI/Xapi.cpp @@ -960,15 +960,47 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) NewTime.QuadPart += (static_cast(dwMilliseconds) * CLOCK_TIME_INCREMENT); } - xbox::dword_xt ret = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable]() -> std::optional { + if (!Timeout || (Timeout->QuadPart == 0)) { + // Use the built-in ktimer as a dummy wait object, so that KiUnwaitThreadAndLock can still work + PKTHREAD kThread = KeGetCurrentThread(); + xbox::PKWAIT_BLOCK WaitBlock = &kThread->TimerWaitBlock; + kThread->WaitBlockList = WaitBlock; + xbox::PKTIMER Timer = &kThread->Timer; + WaitBlock->NextWaitBlock = WaitBlock; + Timer->Header.WaitListHead.Flink = &WaitBlock->WaitListEntry; + Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; + InsertTailList(&Timer->Header.WaitListHead, &WaitBlock->WaitListEntry); + } + + xbox::ntstatus_xt status = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable]() -> std::optional { DWORD dwRet = SignalObjectAndWait(hObjectToSignal, hObjectToWaitOn, 0, bAlertable); if (dwRet == WAIT_TIMEOUT) { return std::nullopt; } - return std::make_optional(dwRet); + // If the wait was satisfied with the host, then also unwait the thread on the guest side, to be sure to remove WaitBlocks that might have been added + // to the thread + xbox::ntstatus_xt Status; + switch (dwRet) + { + case WAIT_ABANDONED: Status = X_STATUS_ABANDONED; break; + case WAIT_IO_COMPLETION: Status = X_STATUS_USER_APC; break; + case WAIT_OBJECT_0: Status = X_STATUS_SUCCESS; break; + default: Status = X_STATUS_INVALID_HANDLE; + } + xbox::KiUnwaitThreadAndLock(xbox::KeGetCurrentThread(), Status, 0); + return std::make_optional(Status); }, Timeout, bAlertable, UserMode); - RETURN((ret == X_STATUS_USER_APC) ? WAIT_IO_COMPLETION : (ret == X_STATUS_TIMEOUT) ? WAIT_TIMEOUT : ret); + xbox::dword_xt ret; + switch (status) + { + case X_STATUS_ABANDONED: ret = WAIT_ABANDONED; break; + case X_STATUS_USER_APC: ret = WAIT_IO_COMPLETION; break; + case X_STATUS_SUCCESS: ret = WAIT_OBJECT_0; break; + case X_STATUS_TIMEOUT: ret = WAIT_TIMEOUT; break; + default: ret = WAIT_FAILED; + } + RETURN(ret); } // ****************************************************************** diff --git a/src/core/kernel/exports/EmuKrnl.cpp b/src/core/kernel/exports/EmuKrnl.cpp index 6934cd1b5..b8424ae5d 100644 --- a/src/core/kernel/exports/EmuKrnl.cpp +++ b/src/core/kernel/exports/EmuKrnl.cpp @@ -85,6 +85,8 @@ void InsertTailList(xbox::PLIST_ENTRY pListHead, xbox::PLIST_ENTRY pEntry) //#define RemoveEntryList(e) do { PLIST_ENTRY f = (e)->Flink, b = (e)->Blink; f->Blink = b; b->Flink = f; (e)->Flink = (e)->Blink = NULL; } while (0) // Returns TRUE if the list has become empty after removing the element, FALSE otherwise. +// NOTE: this function is a mess. _EX_Flink and _EX_Flink should never be nullptr, and it should never be called on a detached element either. Try to fix +// the bugs in the caller instead of trying to handle it here with these hacks xbox::boolean_xt RemoveEntryList(xbox::PLIST_ENTRY pEntry) { xbox::PLIST_ENTRY _EX_Flink = pEntry->Flink; diff --git a/src/core/kernel/exports/EmuKrnl.h b/src/core/kernel/exports/EmuKrnl.h index 3aa2f8d8b..c735b834a 100644 --- a/src/core/kernel/exports/EmuKrnl.h +++ b/src/core/kernel/exports/EmuKrnl.h @@ -141,15 +141,16 @@ xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolea // NOTE: kThread->Alerted is currently never set. When the alerted mechanism is implemented, the alerts should // also interrupt the wait - xbox::PKTHREAD kThread = EmuKeGetPcr()->Prcb->CurrentThread; + xbox::ntstatus_xt status; + xbox::PKTHREAD kThread = xbox::KeGetCurrentThread(); if (Timeout == nullptr) { // No timout specified, so this is an infinite wait until an alert, a user apc or the object(s) become(s) signalled kThread->State = xbox::Waiting; while (true) { if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) { - kThread->State = xbox::Running; - return *ret; + status = *ret; + break; } std::this_thread::yield(); @@ -158,10 +159,10 @@ xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolea else if (Timeout->QuadPart == 0) { // A zero timeout means that we only have to check the conditions once and then return immediately if they are not satisfied if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) { - return *ret; + status = *ret; } else { - return X_STATUS_TIMEOUT; + status = X_STATUS_TIMEOUT; } } else { @@ -186,18 +187,21 @@ xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolea kThread->State = xbox::Waiting; while (true) { if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) { - kThread->State = xbox::Running; - return *ret; + status = *ret; + break; } if (setup_ktimer && (kThread->State == xbox::Ready)) { - kThread->State = xbox::Running; - return kThread->WaitStatus; + status = kThread->WaitStatus; + break; } std::this_thread::yield(); } } + + kThread->State = xbox::Running; + return status; } #endif diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index b39bc60ff..4e7bf7a0b 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -733,6 +733,18 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread // We can't remove NtDll::NtDelayExecution until all APCs queued by Io are implemented by our kernel as well // Test case: Metal Slug 3 + if (!Interval || (Interval->QuadPart == 0)) { + // Use the built-in ktimer as a dummy wait object, so that KiUnwaitThreadAndLock can still work + PKTHREAD kThread = KeGetCurrentThread(); + xbox::PKWAIT_BLOCK WaitBlock = &kThread->TimerWaitBlock; + kThread->WaitBlockList = WaitBlock; + xbox::PKTIMER Timer = &kThread->Timer; + WaitBlock->NextWaitBlock = WaitBlock; + Timer->Header.WaitListHead.Flink = &WaitBlock->WaitListEntry; + Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; + InsertTailList(&Timer->Header.WaitListHead, &WaitBlock->WaitListEntry); + } + xbox::ntstatus_xt ret = WaitApc([Alertable]() -> std::optional { NtDll::LARGE_INTEGER ExpireTime; ExpireTime.QuadPart = 0; @@ -741,6 +753,9 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread if (Status >= 0 && Status != STATUS_ALERTED && Status != STATUS_USER_APC) { return std::nullopt; } + // If the wait was satisfied with the host, then also unwait the thread on the guest side, to be sure to remove WaitBlocks that might have been added + // to the thread. Test case: Steel Battalion + xbox::KiUnwaitThreadAndLock(xbox::KeGetCurrentThread(), Status, 0); return std::make_optional(Status); }, Interval, Alertable, WaitMode); diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index b48db1b94..958540554 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -1151,7 +1151,7 @@ xbox::void_xt xbox::KiWaitTestNoYield /* Now do the rest of the unwait */ KiUnwaitThread(WaitThread, WaitBlock->WaitKey, Increment); NextWaitEntry: - WaitEntry = WaitList->Flink; + WaitEntry = WaitEntry->Flink; } KiWaitListUnlock(); } diff --git a/src/core/kernel/exports/EmuKrnlNt.cpp b/src/core/kernel/exports/EmuKrnlNt.cpp index cb2c8f324..6767e8d9f 100644 --- a/src/core/kernel/exports/EmuKrnlNt.cpp +++ b/src/core/kernel/exports/EmuKrnlNt.cpp @@ -2229,6 +2229,18 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx // Because user APCs from NtQueueApcThread are now handled by the kernel, we need to wait for them ourselves + if (!Timeout || (Timeout->QuadPart == 0)) { + // Use the built-in ktimer as a dummy wait object, so that KiUnwaitThreadAndLock can still work + PKTHREAD kThread = KeGetCurrentThread(); + xbox::PKWAIT_BLOCK WaitBlock = &kThread->TimerWaitBlock; + kThread->WaitBlockList = WaitBlock; + xbox::PKTIMER Timer = &kThread->Timer; + WaitBlock->NextWaitBlock = WaitBlock; + Timer->Header.WaitListHead.Flink = &WaitBlock->WaitListEntry; + Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; + InsertTailList(&Timer->Header.WaitListHead, &WaitBlock->WaitListEntry); + } + xbox::ntstatus_xt ret = WaitApc([Count, &nativeHandles, WaitType, Alertable]() -> std::optional { NtDll::LARGE_INTEGER ExpireTime; ExpireTime.QuadPart = 0; @@ -2241,6 +2253,9 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx if (Status == STATUS_TIMEOUT) { return std::nullopt; } + // If the wait was satisfied with the host, then also unwait the thread on the guest side, to be sure to remove WaitBlocks that might have been added + // to the thread. Test case: Steel Battalion + xbox::KiUnwaitThreadAndLock(xbox::KeGetCurrentThread(), Status, 0); return std::make_optional(Status); }, Timeout, Alertable, WaitMode); From e454e901ccbf09aecdaaac0794cdf9b9e98606e8 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Sun, 19 Mar 2023 00:09:16 +0100 Subject: [PATCH 25/35] Fixed a race condition in WaitApc + removed wrong InsertTailList for ktimers used during a timeout This fixes almost all broken games in this branch. Still broken: PDO: 1 fps vs 10 fps, DOA3: freezes after title screen, Lord of the rings The third era: Unable to determine default Xbox backbuffer error??? --- src/core/hle/XAPI/Xapi.cpp | 1 - src/core/kernel/exports/EmuKrnl.h | 24 ++++++++++++++++-------- src/core/kernel/exports/EmuKrnlKe.cpp | 1 - src/core/kernel/exports/EmuKrnlNt.cpp | 1 - 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/core/hle/XAPI/Xapi.cpp b/src/core/hle/XAPI/Xapi.cpp index a0c1368c8..42af70701 100644 --- a/src/core/hle/XAPI/Xapi.cpp +++ b/src/core/hle/XAPI/Xapi.cpp @@ -969,7 +969,6 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) WaitBlock->NextWaitBlock = WaitBlock; Timer->Header.WaitListHead.Flink = &WaitBlock->WaitListEntry; Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; - InsertTailList(&Timer->Header.WaitListHead, &WaitBlock->WaitListEntry); } xbox::ntstatus_xt status = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable]() -> std::optional { diff --git a/src/core/kernel/exports/EmuKrnl.h b/src/core/kernel/exports/EmuKrnl.h index c735b834a..c8fbae836 100644 --- a/src/core/kernel/exports/EmuKrnl.h +++ b/src/core/kernel/exports/EmuKrnl.h @@ -135,18 +135,24 @@ std::optional SatisfyWait(T &&Lambda, xbox::PKTHREAD kThread, return std::nullopt; } -template +template xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolean_xt Alertable, xbox::char_xt WaitMode) { - // NOTE: kThread->Alerted is currently never set. When the alerted mechanism is implemented, the alerts should - // also interrupt the wait + // NOTE1: kThread->Alerted is currently never set. When the alerted mechanism is implemented, the alerts should + // also interrupt the wait. + // NOTE2: kThread->State should only be set when this function performs a host wait. Otherwise, a race condition exists where the guest satisfies the wait + // and calls KiUnwaitThread (which sets the state to Ready), before we set the Waiting state here. This will then cause a deadlock, since the wait here expects + // a Ready state instead, which was previosuly overwritten by Waiting. Test case: PsCreateSystemThreadEx when it suspends/resumes the child thread with + // KeSuspend/ResumeThreadEx. xbox::ntstatus_xt status; xbox::PKTHREAD kThread = xbox::KeGetCurrentThread(); if (Timeout == nullptr) { // No timout specified, so this is an infinite wait until an alert, a user apc or the object(s) become(s) signalled - kThread->State = xbox::Waiting; + if constexpr (host_wait) { + kThread->State = xbox::Waiting; + } while (true) { if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) { status = *ret; @@ -167,7 +173,7 @@ xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolea } else { // A non-zero timeout means we have to check the conditions until we reach the requested time - if constexpr (setup_ktimer) { + if constexpr (host_wait) { // Setup a timer if it wasn't already by our caller. This is necessary because in this way, KiTimerExpiration can discover the timeout // and yield to us. Otherwise, we will only be able to discover the timeout when Windows decides to schedule us again, and testing shows that // tends to happen much later than the due time @@ -182,16 +188,16 @@ xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolea xbox::KiTimerUnlock(); return X_STATUS_TIMEOUT; } + kThread->State = xbox::Waiting; xbox::KiTimerUnlock(); } - kThread->State = xbox::Waiting; while (true) { if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) { status = *ret; break; } - if (setup_ktimer && (kThread->State == xbox::Ready)) { + if (host_wait && (kThread->State == xbox::Ready)) { status = kThread->WaitStatus; break; } @@ -200,7 +206,9 @@ xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolea } } - kThread->State = xbox::Running; + if constexpr (host_wait) { + kThread->State = xbox::Running; + } return status; } diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 4e7bf7a0b..0cb8203ec 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -742,7 +742,6 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread WaitBlock->NextWaitBlock = WaitBlock; Timer->Header.WaitListHead.Flink = &WaitBlock->WaitListEntry; Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; - InsertTailList(&Timer->Header.WaitListHead, &WaitBlock->WaitListEntry); } xbox::ntstatus_xt ret = WaitApc([Alertable]() -> std::optional { diff --git a/src/core/kernel/exports/EmuKrnlNt.cpp b/src/core/kernel/exports/EmuKrnlNt.cpp index 6767e8d9f..886c701b3 100644 --- a/src/core/kernel/exports/EmuKrnlNt.cpp +++ b/src/core/kernel/exports/EmuKrnlNt.cpp @@ -2238,7 +2238,6 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx WaitBlock->NextWaitBlock = WaitBlock; Timer->Header.WaitListHead.Flink = &WaitBlock->WaitListEntry; Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; - InsertTailList(&Timer->Header.WaitListHead, &WaitBlock->WaitListEntry); } xbox::ntstatus_xt ret = WaitApc([Count, &nativeHandles, WaitType, Alertable]() -> std::optional { From 9238ae3f81739d7f019cb8517a3c89689e28b5f3 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Mon, 20 Mar 2023 13:27:26 +0100 Subject: [PATCH 26/35] Account for partial milliseconds in KiClockIsr This fixes the slowness in The lord of the rings: the third era --- src/common/Timer.cpp | 4 ++-- src/core/kernel/exports/EmuKrnlKi.cpp | 14 ++++++++++---- src/core/kernel/exports/EmuKrnlKi.h | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/common/Timer.cpp b/src/common/Timer.cpp index 1551ca751..a4fe0f76f 100644 --- a/src/common/Timer.cpp +++ b/src/common/Timer.cpp @@ -102,7 +102,7 @@ static uint64_t pit_next(uint64_t now) uint64_t next = pit_last + pit_period; if (now >= next) { - xbox::KiClockIsr((now - pit_last - pit_period) / 1000); + xbox::KiClockIsr(now - pit_last); pit_last = get_now(); return pit_period; } @@ -151,7 +151,7 @@ static uint64_t get_next(uint64_t now) xbox::void_xt NTAPI system_events(xbox::PVOID arg) { // Testing shows that, if this thread has the same priority of the other xbox threads, it can take tens, even hundreds of ms to complete a single loop. - // So we increase its priority to above normal, so that it completes a loop roughly every 3.1ms + // So we increase its priority to above normal, so that it scheduled more often SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); while (true) { diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index 958540554..682fbcb46 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -146,19 +146,25 @@ xbox::void_xt xbox::KiWaitListUnlock() KiWaitListMtx.Mtx.unlock(); } -xbox::void_xt xbox::KiClockIsr(ulonglong_xt ExtraMs) +xbox::void_xt xbox::KiClockIsr(ulonglong_xt TotalUs) { KIRQL OldIrql; LARGE_INTEGER InterruptTime, SystemTime; ULONG Hand; DWORD OldKeTickCount; + static uint64_t LostUs; + uint64_t TotalMs = TotalUs / 1000; + LostUs += (TotalUs - TotalMs * 1000); + uint64_t RecoveredMs = LostUs / 1000; + TotalMs += RecoveredMs; + LostUs -= (RecoveredMs * 1000); OldIrql = KfRaiseIrql(CLOCK_LEVEL); // Update the interrupt time InterruptTime.u.LowPart = KeInterruptTime.LowPart; InterruptTime.u.HighPart = KeInterruptTime.High1Time; - InterruptTime.QuadPart += (CLOCK_TIME_INCREMENT * (1 + ExtraMs)); + InterruptTime.QuadPart += (CLOCK_TIME_INCREMENT * TotalMs); KeInterruptTime.High2Time = InterruptTime.u.HighPart; KeInterruptTime.LowPart = InterruptTime.u.LowPart; KeInterruptTime.High1Time = InterruptTime.u.HighPart; @@ -178,7 +184,7 @@ xbox::void_xt xbox::KiClockIsr(ulonglong_xt ExtraMs) else { SystemTime.u.LowPart = KeSystemTime.LowPart; SystemTime.u.HighPart = KeSystemTime.High1Time; - SystemTime.QuadPart += (CLOCK_TIME_INCREMENT * (1 + ExtraMs)); + SystemTime.QuadPart += (CLOCK_TIME_INCREMENT * TotalMs); KeSystemTime.High2Time = SystemTime.u.HighPart; KeSystemTime.LowPart = SystemTime.u.LowPart; KeSystemTime.High1Time = SystemTime.u.HighPart; @@ -186,7 +192,7 @@ xbox::void_xt xbox::KiClockIsr(ulonglong_xt ExtraMs) // Update the tick counter OldKeTickCount = KeTickCount; - KeTickCount += (1 + static_cast(ExtraMs)); + KeTickCount += (1 + static_cast(TotalMs)); // Because this function must be fast to continuously update the kernel clocks, if somebody else is currently // holding the lock, we won't wait and instead skip the check of the timers for this cycle diff --git a/src/core/kernel/exports/EmuKrnlKi.h b/src/core/kernel/exports/EmuKrnlKi.h index 868b64c1c..d436093a1 100644 --- a/src/core/kernel/exports/EmuKrnlKi.h +++ b/src/core/kernel/exports/EmuKrnlKi.h @@ -66,7 +66,7 @@ namespace xbox void_xt KiWaitListUnlock(); - void_xt KiClockIsr(ulonglong_xt ExtraMs); + void_xt KiClockIsr(ulonglong_xt TotalUs); xbox::void_xt NTAPI KiCheckTimerTable ( From c2928744878fa6dd3d9b518c55fe5ff87f7b7e28 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Mon, 20 Mar 2023 17:25:43 +0100 Subject: [PATCH 27/35] Fixed a bug in KiInsertTimerTable + log all objects being waited on in NtWaitForMultipleObjectsEx This fixes a crash in Metal Slug 3 --- src/core/kernel/exports/EmuKrnlKi.cpp | 10 +++++++--- src/core/kernel/exports/EmuKrnlNt.cpp | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index 682fbcb46..c927c0cb5 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -359,8 +359,8 @@ xbox::boolean_xt FASTCALL xbox::KiInsertTimerTable IN xbox::ulong_xt Hand ) { - LARGE_INTEGER InterruptTime; - LONGLONG DueTime = Timer->DueTime.QuadPart; + ULARGE_INTEGER InterruptTime; + ULONGLONG DueTime = Timer->DueTime.QuadPart; BOOLEAN Expired = FALSE; PLIST_ENTRY ListHead, NextEntry; PKTIMER CurrentTimer; @@ -383,7 +383,7 @@ xbox::boolean_xt FASTCALL xbox::KiInsertTimerTable CurrentTimer = CONTAINING_RECORD(NextEntry, KTIMER, TimerListEntry); /* Now check if we can fit it before */ - if ((ULONGLONG)DueTime >= CurrentTimer->DueTime.QuadPart) break; + if (DueTime >= CurrentTimer->DueTime.QuadPart) break; /* Keep looping */ NextEntry = NextEntry->Blink; @@ -399,6 +399,10 @@ xbox::boolean_xt FASTCALL xbox::KiInsertTimerTable KiTimerTableListHead[Hand].Time.QuadPart = DueTime; /* Make sure it hasn't expired already */ + // NOTE: DueTime must be unsigned so that we can perform un unsigned comparison with the interrupt time. Otherwise, if DueTime is very large, it will be + // interpreted as a very small negative number, which will cause the function to think the timer has already expired, when it didn't. Test case: Metal Slug 3. + // It uses KeDelayExecutionThread with a relative timeout of 0x8000000000000000, which is then interpreted here as a negative number that immediately satisfies + // the wait. The title crashes shortly after, since the wait was supposed to end with a user APC queued by NtQueueApcThread instead InterruptTime.QuadPart = KeQueryInterruptTime(); if (DueTime <= InterruptTime.QuadPart) { EmuLog(LOG_LEVEL::DEBUG, "Timer %p already expired", Timer); diff --git a/src/core/kernel/exports/EmuKrnlNt.cpp b/src/core/kernel/exports/EmuKrnlNt.cpp index 886c701b3..e01484dad 100644 --- a/src/core/kernel/exports/EmuKrnlNt.cpp +++ b/src/core/kernel/exports/EmuKrnlNt.cpp @@ -2221,9 +2221,11 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx if (const auto &nativeHandle = GetNativeHandle(Handles[i])) { // This is a ob handle, so replace it with its native counterpart nativeHandles[i] = *nativeHandle; + EmuLog(LOG_LEVEL::DEBUG, "xbox handle: %p", nativeHandles[i]); } else { nativeHandles[i] = Handles[i]; + EmuLog(LOG_LEVEL::DEBUG, "native handle: %p", nativeHandles[i]); } } From 680340f53dc50e19f04b2cf6b2a8b256750174fb Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Wed, 22 Mar 2023 20:51:21 +0100 Subject: [PATCH 28/35] Make sure that GetNativeHandle succeeds before attempting to get the native handle This fixes a sporadic crash in Panzer Dragoon Orta, where the title calls KeSetBasePriorityThread on a thread that has already terminated --- src/core/kernel/exports/EmuKrnlKe.cpp | 75 +++++++++++++++------------ src/core/kernel/exports/EmuKrnlPs.cpp | 1 + 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 0cb8203ec..7d8c52c7d 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -1387,9 +1387,13 @@ XBSYSAPI EXPORTNUM(124) xbox::long_xt NTAPI xbox::KeQueryBasePriorityThread KIRQL OldIrql; KiLockDispatcherDatabase(&OldIrql); - // It cannot fail because all thread handles are created by ob - const auto& nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread); - long_xt ret = GetThreadPriority(*nativeHandle); + long_xt ret; + if (const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread)) { + ret = GetThreadPriority(*nativeHandle); + } + else { + ret = Thread->Priority; + } KiUnlockDispatcherDatabase(OldIrql); @@ -1769,8 +1773,9 @@ XBSYSAPI EXPORTNUM(140) xbox::ulong_xt NTAPI xbox::KeResumeThread ++Thread->SuspendSemaphore.Header.SignalState; KiWaitTest(&Thread->SuspendSemaphore, 0); #else - const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread); - ResumeThread(*nativeHandle); + if (const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread)) { + ResumeThread(*nativeHandle); + } #endif } } @@ -1826,27 +1831,31 @@ XBSYSAPI EXPORTNUM(143) xbox::long_xt NTAPI xbox::KeSetBasePriorityThread KIRQL oldIRQL; KiLockDispatcherDatabase(&oldIRQL); - // It cannot fail because all thread handles are created by ob - const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread); - LONG ret = GetThreadPriority(*nativeHandle); + long_xt ret; + if (const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread)) { + ret = GetThreadPriority(*nativeHandle); - if (Priority == 16) { - Priority = THREAD_PRIORITY_TIME_CRITICAL; - } - else if (Priority == -16) { - Priority = THREAD_PRIORITY_IDLE; - } - - // HACK: only change the priority if set above normal. Test case: Black. It calls this to set the priority of a thread to THREAD_PRIORITY_LOWEST, and that same - // thread calls D3DDevice_BlockUntilVerticalBlank. The problem arises because there is also another thread that runs at higher priority and calls D3DDevice_BlockUntilVerticalBlank - // too to wait on the vblank kevent. When the vblank does occur, this other thread will satisfy the wait first, and set the kevent back to non-signalled. Thus, the other - // thread will miss the signal because typically, Windows won't re-schedule it after many more vblank have already occured. The proper solution is to boost the priority of - // the thread when the kevent is signalled, with the increment argument specified in KeSetEvent. Such boosts should also be appiled whenever a thread satisfies a wait. - if (Priority <= THREAD_PRIORITY_NORMAL) { - BOOL result = SetThreadPriority(*nativeHandle, Priority); - if (!result) { - EmuLog(LOG_LEVEL::WARNING, "SetThreadPriority failed: %s", WinError2Str().c_str()); + if (Priority == 16) { + Priority = THREAD_PRIORITY_TIME_CRITICAL; } + else if (Priority == -16) { + Priority = THREAD_PRIORITY_IDLE; + } + + // HACK: only change the priority if set above normal. Test case: Black. It calls this to set the priority of a thread to THREAD_PRIORITY_LOWEST, and that same + // thread calls D3DDevice_BlockUntilVerticalBlank. The problem arises because there is also another thread that runs at higher priority and calls D3DDevice_BlockUntilVerticalBlank + // too to wait on the vblank kevent. When the vblank does occur, this other thread will satisfy the wait first, and set the kevent back to non-signalled. Thus, the other + // thread will miss the signal because typically, Windows won't re-schedule it after many more vblank have already occured. The proper solution is to boost the priority of + // the thread when the kevent is signalled, with the increment argument specified in KeSetEvent. Such boosts should also be appiled whenever a thread satisfies a wait. + if (Priority <= THREAD_PRIORITY_NORMAL) { + BOOL result = SetThreadPriority(*nativeHandle, Priority); + if (!result) { + EmuLog(LOG_LEVEL::WARNING, "SetThreadPriority failed: %s", WinError2Str().c_str()); + } + } + } + else { + ret = Thread->Priority; } KiUnlockDispatcherDatabase(oldIRQL); @@ -1868,17 +1877,16 @@ XBSYSAPI EXPORTNUM(144) xbox::boolean_xt NTAPI xbox::KeSetDisableBoostThread KIRQL oldIRQL; KiLockDispatcherDatabase(&oldIRQL); - // It cannot fail because all thread handles are created by ob - const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread); + if (const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread)) { + BOOL bRet = SetThreadPriorityBoost(*nativeHandle, Disable); + if (!bRet) { + EmuLog(LOG_LEVEL::WARNING, "SetThreadPriorityBoost failed: %s", WinError2Str().c_str()); + } + } boolean_xt prevDisableBoost = Thread->DisableBoost; Thread->DisableBoost = (CHAR)Disable; - BOOL bRet = SetThreadPriorityBoost(*nativeHandle, Disable); - if (!bRet) { - EmuLog(LOG_LEVEL::WARNING, "SetThreadPriorityBoost failed: %s", WinError2Str().c_str()); - } - KiUnlockDispatcherDatabase(oldIRQL); RETURN(prevDisableBoost); @@ -2139,8 +2147,9 @@ XBSYSAPI EXPORTNUM(152) xbox::ulong_xt NTAPI xbox::KeSuspendThread // terminates if it ever exit the loop), and never calls any kernel functions in the middle. This means that our suspend APC will never be executed and so // we cannot suspend such thread. Thus, we will always have to rely on the host to do the suspension, as long as we do direct execution. Note that this is // a general issue for all kernel APCs too. - const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread); - SuspendThread(*nativeHandle); + if (const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread)) { + SuspendThread(*nativeHandle); + } #endif } diff --git a/src/core/kernel/exports/EmuKrnlPs.cpp b/src/core/kernel/exports/EmuKrnlPs.cpp index 033493269..aebbb3af4 100644 --- a/src/core/kernel/exports/EmuKrnlPs.cpp +++ b/src/core/kernel/exports/EmuKrnlPs.cpp @@ -408,6 +408,7 @@ XBSYSAPI EXPORTNUM(255) xbox::ntstatus_xt NTAPI xbox::PsCreateSystemThreadEx assert(dupHandle); RegisterXboxHandle(eThread->UniqueThread, dupHandle); + eThread->Tcb.Priority = GetThreadPriority(handle); g_AffinityPolicy->SetAffinityXbox(handle); // Wait for the initialization of the remaining thread state From ac31523b097776ac372d767522f001e77a2dba5d Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Wed, 22 Mar 2023 21:42:34 +0100 Subject: [PATCH 29/35] Make sure to reset WaitStatus when a new wait starts This fixes an issue in Panzer Dragoon Orta, where KeDelayExecutionThread would return X_STATUS_TIMEOUT | X_STATUS_USER_APC --- src/core/hle/XAPI/Xapi.cpp | 7 ++++--- src/core/kernel/exports/EmuKrnlKe.cpp | 8 +++++--- src/core/kernel/exports/EmuKrnlNt.cpp | 7 ++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/core/hle/XAPI/Xapi.cpp b/src/core/hle/XAPI/Xapi.cpp index 42af70701..1be38ec11 100644 --- a/src/core/hle/XAPI/Xapi.cpp +++ b/src/core/hle/XAPI/Xapi.cpp @@ -960,9 +960,10 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) NewTime.QuadPart += (static_cast(dwMilliseconds) * CLOCK_TIME_INCREMENT); } + PKTHREAD kThread = KeGetCurrentThread(); + kThread->WaitStatus = X_STATUS_SUCCESS; if (!Timeout || (Timeout->QuadPart == 0)) { // Use the built-in ktimer as a dummy wait object, so that KiUnwaitThreadAndLock can still work - PKTHREAD kThread = KeGetCurrentThread(); xbox::PKWAIT_BLOCK WaitBlock = &kThread->TimerWaitBlock; kThread->WaitBlockList = WaitBlock; xbox::PKTIMER Timer = &kThread->Timer; @@ -971,7 +972,7 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; } - xbox::ntstatus_xt status = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable]() -> std::optional { + xbox::ntstatus_xt status = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable, kThread]() -> std::optional { DWORD dwRet = SignalObjectAndWait(hObjectToSignal, hObjectToWaitOn, 0, bAlertable); if (dwRet == WAIT_TIMEOUT) { return std::nullopt; @@ -986,7 +987,7 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) case WAIT_OBJECT_0: Status = X_STATUS_SUCCESS; break; default: Status = X_STATUS_INVALID_HANDLE; } - xbox::KiUnwaitThreadAndLock(xbox::KeGetCurrentThread(), Status, 0); + xbox::KiUnwaitThreadAndLock(kThread, Status, 0); return std::make_optional(Status); }, Timeout, bAlertable, UserMode); diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 7d8c52c7d..8f2bd8da1 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -733,9 +733,10 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread // We can't remove NtDll::NtDelayExecution until all APCs queued by Io are implemented by our kernel as well // Test case: Metal Slug 3 + PKTHREAD kThread = KeGetCurrentThread(); + kThread->WaitStatus = X_STATUS_SUCCESS; if (!Interval || (Interval->QuadPart == 0)) { // Use the built-in ktimer as a dummy wait object, so that KiUnwaitThreadAndLock can still work - PKTHREAD kThread = KeGetCurrentThread(); xbox::PKWAIT_BLOCK WaitBlock = &kThread->TimerWaitBlock; kThread->WaitBlockList = WaitBlock; xbox::PKTIMER Timer = &kThread->Timer; @@ -744,7 +745,7 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; } - xbox::ntstatus_xt ret = WaitApc([Alertable]() -> std::optional { + xbox::ntstatus_xt ret = WaitApc([Alertable, kThread]() -> std::optional { NtDll::LARGE_INTEGER ExpireTime; ExpireTime.QuadPart = 0; NTSTATUS Status = NtDll::NtDelayExecution(Alertable, &ExpireTime); @@ -752,9 +753,10 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread if (Status >= 0 && Status != STATUS_ALERTED && Status != STATUS_USER_APC) { return std::nullopt; } + EmuLog(LOG_LEVEL::DEBUG, "KeDelayExecutionThread -> Staus: %X", Status); // If the wait was satisfied with the host, then also unwait the thread on the guest side, to be sure to remove WaitBlocks that might have been added // to the thread. Test case: Steel Battalion - xbox::KiUnwaitThreadAndLock(xbox::KeGetCurrentThread(), Status, 0); + xbox::KiUnwaitThreadAndLock(kThread, Status, 0); return std::make_optional(Status); }, Interval, Alertable, WaitMode); diff --git a/src/core/kernel/exports/EmuKrnlNt.cpp b/src/core/kernel/exports/EmuKrnlNt.cpp index e01484dad..8b9249a06 100644 --- a/src/core/kernel/exports/EmuKrnlNt.cpp +++ b/src/core/kernel/exports/EmuKrnlNt.cpp @@ -2231,9 +2231,10 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx // Because user APCs from NtQueueApcThread are now handled by the kernel, we need to wait for them ourselves + PKTHREAD kThread = KeGetCurrentThread(); + kThread->WaitStatus = X_STATUS_SUCCESS; if (!Timeout || (Timeout->QuadPart == 0)) { // Use the built-in ktimer as a dummy wait object, so that KiUnwaitThreadAndLock can still work - PKTHREAD kThread = KeGetCurrentThread(); xbox::PKWAIT_BLOCK WaitBlock = &kThread->TimerWaitBlock; kThread->WaitBlockList = WaitBlock; xbox::PKTIMER Timer = &kThread->Timer; @@ -2242,7 +2243,7 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; } - xbox::ntstatus_xt ret = WaitApc([Count, &nativeHandles, WaitType, Alertable]() -> std::optional { + xbox::ntstatus_xt ret = WaitApc([Count, &nativeHandles, WaitType, Alertable, kThread]() -> std::optional { NtDll::LARGE_INTEGER ExpireTime; ExpireTime.QuadPart = 0; NTSTATUS Status = NtDll::NtWaitForMultipleObjects( @@ -2256,7 +2257,7 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx } // If the wait was satisfied with the host, then also unwait the thread on the guest side, to be sure to remove WaitBlocks that might have been added // to the thread. Test case: Steel Battalion - xbox::KiUnwaitThreadAndLock(xbox::KeGetCurrentThread(), Status, 0); + xbox::KiUnwaitThreadAndLock(kThread, Status, 0); return std::make_optional(Status); }, Timeout, Alertable, WaitMode); From ea1657018fe5d232badcac0656e3b1818a24e9f5 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Thu, 23 Mar 2023 01:45:39 +0100 Subject: [PATCH 30/35] Fixed a bug in KeTickCount + check all timer indices when we are late in KiClockIsr This almost completely fixes the slowness in Panzer Dragoon Orta --- src/core/kernel/exports/EmuKrnlKi.cpp | 42 ++++++++++++--------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index c927c0cb5..465506c3f 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -95,7 +95,6 @@ the said software). xbox::KPROCESS KiUniqueProcess; const xbox::ulong_xt CLOCK_TIME_INCREMENT = 0x2710; -xbox::KDPC KiTimerExpireDpc; xbox::KI_TIMER_LOCK KiTimerMtx; xbox::KI_WAIT_LIST_LOCK KiWaitListMtx; xbox::KTIMER_TABLE_ENTRY KiTimerTableListHead[TIMER_TABLE_SIZE]; @@ -112,7 +111,6 @@ xbox::void_xt xbox::KiInitSystem() InitializeListHead(&KiWaitInListHead); KiTimerMtx.Acquired = 0; - KeInitializeDpc(&KiTimerExpireDpc, KiTimerExpiration, NULL); for (unsigned i = 0; i < TIMER_TABLE_SIZE; i++) { InitializeListHead(&KiTimerTableListHead[i].Entry); KiTimerTableListHead[i].Time.u.HighPart = 0xFFFFFFFF; @@ -192,17 +190,24 @@ xbox::void_xt xbox::KiClockIsr(ulonglong_xt TotalUs) // Update the tick counter OldKeTickCount = KeTickCount; - KeTickCount += (1 + static_cast(TotalMs)); + KeTickCount += static_cast(TotalMs); // Because this function must be fast to continuously update the kernel clocks, if somebody else is currently // holding the lock, we won't wait and instead skip the check of the timers for this cycle if (KiTimerMtx.Mtx.try_lock()) { KiTimerMtx.Acquired++; // Check if a timer has expired - Hand = OldKeTickCount & (TIMER_TABLE_SIZE - 1); - if (KiTimerTableListHead[Hand].Entry.Flink != &KiTimerTableListHead[Hand].Entry && - (ULONGLONG)InterruptTime.QuadPart >= KiTimerTableListHead[Hand].Time.QuadPart) { - KeInsertQueueDpc(&KiTimerExpireDpc, (PVOID)Hand, 0); + // On real hw, this is called every ms, so it only needs to check a single timer index. However, testing on the emulator shows that this can have a delay + // larger than a ms. If we only check the index corresponding to OldKeTickCount, then we will miss timers that might have expired already, causing an unpredictable + // delay on threads that are waiting with those timeouts + dword_xt EndKeTickCount = (KeTickCount - OldKeTickCount) >= TIMER_TABLE_SIZE ? OldKeTickCount + TIMER_TABLE_SIZE : KeTickCount; + for (dword_xt i = OldKeTickCount; i < EndKeTickCount; ++i) { + Hand = i & (TIMER_TABLE_SIZE - 1); + if (KiTimerTableListHead[Hand].Entry.Flink != &KiTimerTableListHead[Hand].Entry && + (ULONGLONG)InterruptTime.QuadPart >= KiTimerTableListHead[Hand].Time.QuadPart) { + // NOTE: instead of using a DPC, we call KiTimerExpiration directly to speed things up, and avoid the DPC lock + KiTimerExpiration(zeroptr, zeroptr, (PVOID)Hand, 0); + } } KiTimerMtx.Acquired--; KiTimerMtx.Mtx.unlock(); @@ -588,9 +593,10 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration Timers = 24; ActiveTimers = 4; - /* Lock the Database and Raise IRQL */ - KiTimerLock(); - KiLockDispatcherDatabase(&OldIrql); + /* Lock the Database */ + // NOTE: this function is only called from KiClockIsr, with the irql at CLOCK_LEVEL + ASSERT_TIMER_LOCKED; + OldIrql = KeGetCurrentIrql(); /* Start expiration loop */ do @@ -674,7 +680,7 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration ActiveTimers = 4; /* Lock the dispatcher database */ - KiLockDispatcherDatabaseAtDpcLevel(); + KfRaiseIrql(OldIrql); } } else @@ -717,7 +723,7 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration ActiveTimers = 4; /* Lock the dispatcher database */ - KiLockDispatcherDatabaseAtDpcLevel(); + KfRaiseIrql(OldIrql); } /* Done looping */ @@ -752,17 +758,7 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration ); } - /* Lower IRQL if we need to */ - if (OldIrql != DISPATCH_LEVEL) { - KfLowerIrql(OldIrql); - } - KiTimerUnlock(); - } - else - { - /* Unlock the dispatcher */ - KiUnlockDispatcherDatabase(OldIrql); - KiTimerUnlock(); + KfRaiseIrql(OldIrql); } } From ccd77fcf4d608db4ad0ad1df0c98b716abff9763 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Sun, 26 Mar 2023 21:26:30 +0200 Subject: [PATCH 31/35] Fixed wrong nv2a clock frequency This is accessed by DOAU via PTIMER only in PAL50 mode --- src/devices/video/nv2a.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/devices/video/nv2a.cpp b/src/devices/video/nv2a.cpp index 2200a4acc..92522ad16 100644 --- a/src/devices/video/nv2a.cpp +++ b/src/devices/video/nv2a.cpp @@ -1193,8 +1193,8 @@ void NV2ADevice::Init() d->vram_ptr = (uint8_t*)PHYSICAL_MAP_BASE; d->vram_size = g_SystemMaxMemory; - d->pramdac.core_clock_coeff = 0x00011c01; /* 189MHz...? */ - d->pramdac.core_clock_freq = 189000000; + d->pramdac.core_clock_coeff = 0x00011C01; /* 233MHz...? */ + d->pramdac.core_clock_freq = 233333324; d->pramdac.memory_clock_coeff = 0; d->pramdac.video_clock_coeff = 0x0003C20D; /* 25182Khz...? */ From 709108b045543e989e5dbd28c78dc38e5c367401 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Tue, 28 Mar 2023 00:02:34 +0200 Subject: [PATCH 32/35] Implemented PTIMER alarm interrupt of NV2A + fixed a bug in timer_init This fixes DOAU showing the dirty disk error in PAL50 mode --- src/common/Timer.cpp | 17 +++++++------ src/devices/video/EmuNV2A_PRAMDAC.cpp | 1 + src/devices/video/EmuNV2A_PTIMER.cpp | 18 ++++++++------ src/devices/video/nv2a.cpp | 35 +++++++++++++++++++++++++++ src/devices/video/nv2a.h | 1 + src/devices/video/nv2a_int.h | 3 +++ 6 files changed, 60 insertions(+), 15 deletions(-) diff --git a/src/common/Timer.cpp b/src/common/Timer.cpp index a4fe0f76f..c361e2750 100644 --- a/src/common/Timer.cpp +++ b/src/common/Timer.cpp @@ -42,8 +42,8 @@ #include "core\hle\DSOUND\DirectSound\DirectSoundGlobal.hpp" -static uint64_t last_qpc; // last time when QPC was called -static uint64_t exec_time; // total execution time in us since the emulation started +static std::atomic_uint64_t last_qpc; // last time when QPC was called +static std::atomic_uint64_t exec_time; // total execution time in us since the emulation started static uint64_t pit_last; // last time when the pit time was updated static uint64_t pit_last_qpc; // last QPC time of the pit // The frequency of the high resolution clock of the host, and the start time @@ -53,8 +53,8 @@ int64_t HostQPCFrequency, HostQPCStartTime; void timer_init() { QueryPerformanceFrequency(reinterpret_cast(&HostQPCFrequency)); - QueryPerformanceCounter(reinterpret_cast(&last_qpc)); - pit_last_qpc = last_qpc; + QueryPerformanceCounter(reinterpret_cast(&HostQPCStartTime)); + pit_last_qpc = last_qpc = HostQPCStartTime; pit_last = get_now(); // Synchronize xbox system time with host time @@ -140,11 +140,12 @@ uint64_t get_now() static uint64_t get_next(uint64_t now) { - std::array next; + std::array next; next[0] = g_NV2A->vblank_next(now); - next[1] = pit_next(now); - next[2] = g_USB0->m_HostController->OHCI_next(now); - next[3] = dsound_next(now); + next[1] = g_NV2A->ptimer_next(now); + next[2] = pit_next(now); + next[3] = g_USB0->m_HostController->OHCI_next(now); + next[4] = dsound_next(now); return *std::min_element(next.begin(), next.end()); } diff --git a/src/devices/video/EmuNV2A_PRAMDAC.cpp b/src/devices/video/EmuNV2A_PRAMDAC.cpp index 768811e3a..c0e9db80f 100644 --- a/src/devices/video/EmuNV2A_PRAMDAC.cpp +++ b/src/devices/video/EmuNV2A_PRAMDAC.cpp @@ -82,6 +82,7 @@ DEVICE_WRITE32(PRAMDAC) } else { d->pramdac.core_clock_freq = (NV2A_CRYSTAL_FREQ * n) / (1 << p) / m; + d->ptimer_period = ((uint64_t(d->ptimer.alarm_time >> 5) * SCALE_S_IN_US) / d->pramdac.core_clock_freq); } break; diff --git a/src/devices/video/EmuNV2A_PTIMER.cpp b/src/devices/video/EmuNV2A_PTIMER.cpp index d6f6f42f5..5711537b8 100644 --- a/src/devices/video/EmuNV2A_PTIMER.cpp +++ b/src/devices/video/EmuNV2A_PTIMER.cpp @@ -35,17 +35,13 @@ #include "common\util\CxbxUtil.h" -#define NANOSECONDS_PER_SECOND 1000000000 /* PTIMER - time measurement and time-based alarms */ -static uint64_t ptimer_get_clock(NV2AState * d) +static uint64_t ptimer_get_clock(NV2AState *d) { - // Get time in nanoseconds - uint64_t time = std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count(); - - return Muldiv64(Muldiv64(time, + return Muldiv64(Muldiv64(get_now(), (uint32_t)d->pramdac.core_clock_freq, // TODO : Research how this can be updated to accept uint64_t - NANOSECONDS_PER_SECOND), // Was CLOCKS_PER_SEC + SCALE_S_IN_US), // Was CLOCKS_PER_SEC d->ptimer.denominator, d->ptimer.numerator); } @@ -91,6 +87,13 @@ DEVICE_WRITE32(PTIMER) break; case NV_PTIMER_INTR_EN_0: d->ptimer.enabled_interrupts = value; + if (d->ptimer.enabled_interrupts & NV_PTIMER_INTR_EN_0_ALARM) { + d->ptimer_last = get_now(); + d->ptimer_active = true; + } + else if ((d->ptimer.enabled_interrupts & NV_PTIMER_INTR_EN_0_ALARM) == 0) { + d->ptimer_active = false; + } update_irq(d); break; case NV_PTIMER_DENOMINATOR: @@ -101,6 +104,7 @@ DEVICE_WRITE32(PTIMER) break; case NV_PTIMER_ALARM_0: d->ptimer.alarm_time = value; + d->ptimer_period = ((uint64_t(d->ptimer.alarm_time >> 5) * SCALE_S_IN_US) / d->pramdac.core_clock_freq); break; default: //DEVICE_WRITE32_REG(ptimer); // Was : DEBUG_WRITE32_UNHANDLED(PTIMER); diff --git a/src/devices/video/nv2a.cpp b/src/devices/video/nv2a.cpp index 92522ad16..a2a13397e 100644 --- a/src/devices/video/nv2a.cpp +++ b/src/devices/video/nv2a.cpp @@ -130,6 +130,14 @@ static void update_irq(NV2AState *d) d->pmc.pending_interrupts &= ~NV_PMC_INTR_0_PVIDEO; } + /* PTIMER */ + if (d->ptimer.pending_interrupts & d->ptimer.enabled_interrupts) { + d->pmc.pending_interrupts |= NV_PMC_INTR_0_PTIMER; + } + else { + d->pmc.pending_interrupts &= ~NV_PMC_INTR_0_PTIMER; + } + /* TODO : PBUS * / if (d->pbus.pending_interrupts & d->pbus.enabled_interrupts) { d->pmc.pending_interrupts |= NV_PMC_INTR_0_PBUS; @@ -1401,3 +1409,30 @@ uint64_t NV2ADevice::vblank_next(uint64_t now) return m_nv2a_state->vblank_last + vblank_period - now; // time remaining until next vblank } + +uint64_t NV2ADevice::ptimer_next(uint64_t now) +{ + // Test case: Dead or Alive Ultimate uses this when in PAL50 mode only + if (m_nv2a_state->ptimer_active) { + const uint64_t ptimer_period = m_nv2a_state->ptimer_period; + uint64_t next = m_nv2a_state->ptimer_last + ptimer_period; + + if (now >= next) { + if (!m_nv2a_state->exiting) [[likely]] { + m_nv2a_state->ptimer.pending_interrupts |= NV_PTIMER_INTR_0_ALARM; + update_irq(m_nv2a_state); + + // trigger the gpu interrupt if it was asserted in update_irq + if (g_bEnableAllInterrupts && HalSystemInterrupts[3].IsPending() && EmuInterruptList[3] && EmuInterruptList[3]->Connected) { + HalSystemInterrupts[3].Trigger(EmuInterruptList[3]); + } + } + m_nv2a_state->ptimer_last = get_now(); + return ptimer_period; + } + + return m_nv2a_state->ptimer_last + ptimer_period - now; // time remaining until next ptimer interrupt + } + + return -1; +} diff --git a/src/devices/video/nv2a.h b/src/devices/video/nv2a.h index 881a16acd..03fc3c81a 100644 --- a/src/devices/video/nv2a.h +++ b/src/devices/video/nv2a.h @@ -110,6 +110,7 @@ public: static int GetFrameHeight(NV2AState *d); uint64_t vblank_next(uint64_t now); + uint64_t ptimer_next(uint64_t now); private: NV2AState *m_nv2a_state; diff --git a/src/devices/video/nv2a_int.h b/src/devices/video/nv2a_int.h index ffeb539bb..ceef33c73 100644 --- a/src/devices/video/nv2a_int.h +++ b/src/devices/video/nv2a_int.h @@ -363,6 +363,9 @@ typedef struct NV2AState { // qemu_irq irq; bool exiting; bool enable_overlay = false; + bool ptimer_active = false; + uint64_t ptimer_last; + uint64_t ptimer_period; // VGACommonState vga; // GraphicHwOps hw_ops; From e89b5b2130a24d2d2518882069dac81107ed57c1 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Fri, 31 Mar 2023 15:49:40 +0200 Subject: [PATCH 33/35] Fixed an issue in WaitApc where the wait block was not removed when using a zero timeout or when satisfied by a user APC + properly lock the wait block operations to avoid a race between SatisfyWait and KiTimerExpiration --- src/core/hle/XAPI/Xapi.cpp | 16 +++------ src/core/kernel/exports/EmuKrnl.cpp | 27 +++++++++++++++ src/core/kernel/exports/EmuKrnl.h | 41 ++++++---------------- src/core/kernel/exports/EmuKrnlKe.cpp | 50 +++++++++++++++++---------- src/core/kernel/exports/EmuKrnlKi.cpp | 44 ++++++++++++++--------- src/core/kernel/exports/EmuKrnlKi.h | 6 ---- src/core/kernel/exports/EmuKrnlNt.cpp | 16 +++------ src/core/kernel/exports/EmuKrnlPs.cpp | 5 +++ 8 files changed, 111 insertions(+), 94 deletions(-) diff --git a/src/core/hle/XAPI/Xapi.cpp b/src/core/hle/XAPI/Xapi.cpp index 1be38ec11..429a7c610 100644 --- a/src/core/hle/XAPI/Xapi.cpp +++ b/src/core/hle/XAPI/Xapi.cpp @@ -962,17 +962,11 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) PKTHREAD kThread = KeGetCurrentThread(); kThread->WaitStatus = X_STATUS_SUCCESS; - if (!Timeout || (Timeout->QuadPart == 0)) { - // Use the built-in ktimer as a dummy wait object, so that KiUnwaitThreadAndLock can still work - xbox::PKWAIT_BLOCK WaitBlock = &kThread->TimerWaitBlock; - kThread->WaitBlockList = WaitBlock; - xbox::PKTIMER Timer = &kThread->Timer; - WaitBlock->NextWaitBlock = WaitBlock; - Timer->Header.WaitListHead.Flink = &WaitBlock->WaitListEntry; - Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; + if (!AddWaitObject(kThread, Timeout)) { + RETURN(WAIT_TIMEOUT); } - xbox::ntstatus_xt status = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable, kThread]() -> std::optional { + xbox::ntstatus_xt status = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable](xbox::PKTHREAD kThread) -> std::optional { DWORD dwRet = SignalObjectAndWait(hObjectToSignal, hObjectToWaitOn, 0, bAlertable); if (dwRet == WAIT_TIMEOUT) { return std::nullopt; @@ -988,8 +982,8 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) default: Status = X_STATUS_INVALID_HANDLE; } xbox::KiUnwaitThreadAndLock(kThread, Status, 0); - return std::make_optional(Status); - }, Timeout, bAlertable, UserMode); + return std::make_optional(kThread->WaitStatus); + }, Timeout, bAlertable, UserMode, kThread); xbox::dword_xt ret; switch (status) diff --git a/src/core/kernel/exports/EmuKrnl.cpp b/src/core/kernel/exports/EmuKrnl.cpp index b8424ae5d..8d396e6a9 100644 --- a/src/core/kernel/exports/EmuKrnl.cpp +++ b/src/core/kernel/exports/EmuKrnl.cpp @@ -180,6 +180,33 @@ void CallSoftwareInterrupt(const xbox::KIRQL SoftwareIrql) HalInterruptRequestRegister ^= (1 << SoftwareIrql); } +bool AddWaitObject(xbox::PKTHREAD kThread, xbox::PLARGE_INTEGER Timeout) +{ + // Use the built-in ktimer as a dummy wait object, so that KiUnwaitThreadAndLock can still work + xbox::KiTimerLock(); + xbox::PKWAIT_BLOCK WaitBlock = &kThread->TimerWaitBlock; + kThread->WaitBlockList = WaitBlock; + xbox::PKTIMER Timer = &kThread->Timer; + WaitBlock->NextWaitBlock = WaitBlock; + Timer->Header.WaitListHead.Flink = &WaitBlock->WaitListEntry; + Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; + if (Timeout && Timeout->QuadPart) { + // Setup a timer so that KiTimerExpiration can discover the timeout and yield to us. + // Otherwise, we will only be able to discover the timeout when Windows decides to schedule us again, and testing shows that + // tends to happen much later than the due time + if (xbox::KiInsertTreeTimer(Timer, *Timeout) == FALSE) { + // Sanity check: set WaitBlockList to nullptr so that we can catch the case where a waiter starts a new wait but forgets to setup a new wait block. This + // way, we will crash instead of silently using the pointer to the old block + kThread->WaitBlockList = xbox::zeroptr; + xbox::KiTimerUnlock(); + return false; + } + } + kThread->State = xbox::Waiting; + xbox::KiTimerUnlock(); + return true; +} + // This masks have been verified to be correct against a kernel dump const DWORD IrqlMasks[] = { 0xFFFFFFFE, // IRQL 0 diff --git a/src/core/kernel/exports/EmuKrnl.h b/src/core/kernel/exports/EmuKrnl.h index c8fbae836..17e458b68 100644 --- a/src/core/kernel/exports/EmuKrnl.h +++ b/src/core/kernel/exports/EmuKrnl.h @@ -108,11 +108,12 @@ extern HalSystemInterrupt HalSystemInterrupts[MAX_BUS_INTERRUPT_LEVEL + 1]; bool DisableInterrupts(); void RestoreInterruptMode(bool value); void CallSoftwareInterrupt(const xbox::KIRQL SoftwareIrql); +bool AddWaitObject(xbox::PKTHREAD kThread, xbox::PLARGE_INTEGER Timeout); template std::optional SatisfyWait(T &&Lambda, xbox::PKTHREAD kThread, xbox::boolean_xt Alertable, xbox::char_xt WaitMode) { - if (const auto ret = Lambda()) { + if (const auto ret = Lambda(kThread)) { return ret; } @@ -129,30 +130,22 @@ std::optional SatisfyWait(T &&Lambda, xbox::PKTHREAD kThread, (Alertable == TRUE) && (WaitMode == xbox::UserMode)) { xbox::KiExecuteUserApc(); - return X_STATUS_USER_APC; + xbox::KiUnwaitThreadAndLock(kThread, X_STATUS_USER_APC, 0); + return kThread->WaitStatus; } return std::nullopt; } template -xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolean_xt Alertable, xbox::char_xt WaitMode) +xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolean_xt Alertable, xbox::char_xt WaitMode, xbox::PKTHREAD kThread) { // NOTE1: kThread->Alerted is currently never set. When the alerted mechanism is implemented, the alerts should // also interrupt the wait. - // NOTE2: kThread->State should only be set when this function performs a host wait. Otherwise, a race condition exists where the guest satisfies the wait - // and calls KiUnwaitThread (which sets the state to Ready), before we set the Waiting state here. This will then cause a deadlock, since the wait here expects - // a Ready state instead, which was previosuly overwritten by Waiting. Test case: PsCreateSystemThreadEx when it suspends/resumes the child thread with - // KeSuspend/ResumeThreadEx. xbox::ntstatus_xt status; - xbox::PKTHREAD kThread = xbox::KeGetCurrentThread(); - if (Timeout == nullptr) { // No timout specified, so this is an infinite wait until an alert, a user apc or the object(s) become(s) signalled - if constexpr (host_wait) { - kThread->State = xbox::Waiting; - } while (true) { if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) { status = *ret; @@ -163,34 +156,20 @@ xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolea } } else if (Timeout->QuadPart == 0) { + assert(host_wait); // A zero timeout means that we only have to check the conditions once and then return immediately if they are not satisfied if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) { status = *ret; } else { - status = X_STATUS_TIMEOUT; + // If the wait failed, then always remove the wait block. Note that this can only happen with host waits, since guest waits never call us at all + // when Timeout->QuadPart == 0. Test case: Halo 2 (sporadically when playing the intro video) + xbox::KiUnwaitThreadAndLock(kThread, X_STATUS_TIMEOUT, 0); + status = kThread->WaitStatus; } } else { // A non-zero timeout means we have to check the conditions until we reach the requested time - if constexpr (host_wait) { - // Setup a timer if it wasn't already by our caller. This is necessary because in this way, KiTimerExpiration can discover the timeout - // and yield to us. Otherwise, we will only be able to discover the timeout when Windows decides to schedule us again, and testing shows that - // tends to happen much later than the due time - xbox::KiTimerLock(); - xbox::PKWAIT_BLOCK WaitBlock = &kThread->TimerWaitBlock; - kThread->WaitBlockList = WaitBlock; - xbox::PKTIMER Timer = &kThread->Timer; - WaitBlock->NextWaitBlock = WaitBlock; - Timer->Header.WaitListHead.Flink = &WaitBlock->WaitListEntry; - Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; - if (xbox::KiInsertTreeTimer(Timer, *Timeout) == FALSE) { - xbox::KiTimerUnlock(); - return X_STATUS_TIMEOUT; - } - kThread->State = xbox::Waiting; - xbox::KiTimerUnlock(); - } while (true) { if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) { status = *ret; diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 8f2bd8da1..4038c6d0f 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -140,6 +140,7 @@ xbox::void_xt xbox::KeResumeThreadEx // This is only to be used to synchronize new thread creation with the thread that spawned it Thread->SuspendSemaphore.Header.SignalState = 1; + KiWaitListLock(); KiWaitTest(&Thread->SuspendSemaphore, 0); } @@ -735,17 +736,11 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread PKTHREAD kThread = KeGetCurrentThread(); kThread->WaitStatus = X_STATUS_SUCCESS; - if (!Interval || (Interval->QuadPart == 0)) { - // Use the built-in ktimer as a dummy wait object, so that KiUnwaitThreadAndLock can still work - xbox::PKWAIT_BLOCK WaitBlock = &kThread->TimerWaitBlock; - kThread->WaitBlockList = WaitBlock; - xbox::PKTIMER Timer = &kThread->Timer; - WaitBlock->NextWaitBlock = WaitBlock; - Timer->Header.WaitListHead.Flink = &WaitBlock->WaitListEntry; - Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; + if (!AddWaitObject(kThread, Interval)) { + RETURN(X_STATUS_TIMEOUT); } - xbox::ntstatus_xt ret = WaitApc([Alertable, kThread]() -> std::optional { + xbox::ntstatus_xt ret = WaitApc([Alertable](xbox::PKTHREAD kThread) -> std::optional { NtDll::LARGE_INTEGER ExpireTime; ExpireTime.QuadPart = 0; NTSTATUS Status = NtDll::NtDelayExecution(Alertable, &ExpireTime); @@ -753,12 +748,11 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread if (Status >= 0 && Status != STATUS_ALERTED && Status != STATUS_USER_APC) { return std::nullopt; } - EmuLog(LOG_LEVEL::DEBUG, "KeDelayExecutionThread -> Staus: %X", Status); // If the wait was satisfied with the host, then also unwait the thread on the guest side, to be sure to remove WaitBlocks that might have been added // to the thread. Test case: Steel Battalion xbox::KiUnwaitThreadAndLock(kThread, Status, 0); - return std::make_optional(Status); - }, Interval, Alertable, WaitMode); + return std::make_optional(kThread->WaitStatus); + }, Interval, Alertable, WaitMode, kThread); if (ret == X_STATUS_TIMEOUT) { // NOTE: this function considers a timeout a success @@ -1361,9 +1355,14 @@ XBSYSAPI EXPORTNUM(123) xbox::long_xt NTAPI xbox::KePulseEvent } LONG OldState = Event->Header.SignalState; + KiWaitListLock(); if ((OldState == 0) && (IsListEmpty(&Event->Header.WaitListHead) == FALSE)) { Event->Header.SignalState = 1; KiWaitTest(Event, Increment); + std::this_thread::yield(); + } + else { + KiWaitListUnlock(); } Event->Header.SignalState = 0; @@ -1548,8 +1547,13 @@ XBSYSAPI EXPORTNUM(132) xbox::long_xt NTAPI xbox::KeReleaseSemaphore } Semaphore->Header.SignalState = adjusted_signalstate; + KiWaitListLock(); if ((initial_state == 0) && (IsListEmpty(&Semaphore->Header.WaitListHead) == FALSE)) { KiWaitTest(&Semaphore->Header, Increment); + std::this_thread::yield(); + } + else { + KiWaitListUnlock(); } if (Wait) { @@ -1773,7 +1777,9 @@ XBSYSAPI EXPORTNUM(140) xbox::ulong_xt NTAPI xbox::KeResumeThread if (Thread->SuspendCount == 0) { #if 0 ++Thread->SuspendSemaphore.Header.SignalState; + KiWaitListLock(); KiWaitTest(&Thread->SuspendSemaphore, 0); + std::this_thread::yield(); #else if (const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread)) { ResumeThread(*nativeHandle); @@ -1923,7 +1929,9 @@ XBSYSAPI EXPORTNUM(145) xbox::long_xt NTAPI xbox::KeSetEvent } LONG OldState = Event->Header.SignalState; + KiWaitListLock(); if (IsListEmpty(&Event->Header.WaitListHead) != FALSE) { + KiWaitListUnlock(); Event->Header.SignalState = 1; } else { PKWAIT_BLOCK WaitBlock = CONTAINING_RECORD(Event->Header.WaitListHead.Flink, KWAIT_BLOCK, WaitListEntry); @@ -1933,8 +1941,12 @@ XBSYSAPI EXPORTNUM(145) xbox::long_xt NTAPI xbox::KeSetEvent Event->Header.SignalState = 1; KiWaitTest(Event, Increment); } + else { + KiWaitListUnlock(); + } } else { - KiUnwaitThreadAndLock(WaitBlock->Thread, (NTSTATUS)WaitBlock->WaitKey, Increment); + KiUnwaitThread(WaitBlock->Thread, (NTSTATUS)WaitBlock->WaitKey, Increment); + KiWaitListUnlock(); } } @@ -1971,6 +1983,7 @@ XBSYSAPI EXPORTNUM(146) xbox::void_xt NTAPI xbox::KeSetEventBoostPriority return; } + KiWaitListLock(); if (IsListEmpty(&Event->Header.WaitListHead) != FALSE) { Event->Header.SignalState = 1; } else { @@ -1981,8 +1994,9 @@ XBSYSAPI EXPORTNUM(146) xbox::void_xt NTAPI xbox::KeSetEventBoostPriority } WaitThread->Quantum = WaitThread->ApcState.Process->ThreadQuantum; - KiUnwaitThreadAndLock(WaitThread, X_STATUS_SUCCESS, 1); + KiUnwaitThread(WaitThread, X_STATUS_SUCCESS, 1); } + KiWaitListUnlock(); KiUnlockDispatcherDatabase(OldIrql); } @@ -2396,13 +2410,13 @@ XBSYSAPI EXPORTNUM(158) xbox::ntstatus_xt NTAPI xbox::KeWaitForMultipleObjects //} // TODO: Remove this after we have our own scheduler and the above is implemented - WaitStatus = WaitApc([Thread]() -> std::optional { + WaitStatus = WaitApc([](PKTHREAD Thread) -> std::optional { if (Thread->State == Ready) { // We have been readied to resume execution, so exit the wait return std::make_optional(Thread->WaitStatus); } return std::nullopt; - }, Timeout, Alertable, WaitMode); + }, Timeout, Alertable, WaitMode, Thread); break; } @@ -2584,13 +2598,13 @@ XBSYSAPI EXPORTNUM(159) xbox::ntstatus_xt NTAPI xbox::KeWaitForSingleObject KiWaitListUnlock(); // TODO: Remove this after we have our own scheduler and the above is implemented - WaitStatus = WaitApc([Thread]() -> std::optional { + WaitStatus = WaitApc([](PKTHREAD Thread) -> std::optional { if (Thread->State == Ready) { // We have been readied to resume execution, so exit the wait return std::make_optional(Thread->WaitStatus); } return std::nullopt; - }, Timeout, Alertable, WaitMode); + }, Timeout, Alertable, WaitMode, Thread); break; } diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index 465506c3f..1f0aca78a 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -524,9 +524,13 @@ xbox::boolean_xt FASTCALL xbox::KiSignalTimer Timer->Header.SignalState = TRUE; /* Check if the timer has waiters */ + KiWaitListLock(); if (!IsListEmpty(&Timer->Header.WaitListHead)) { - KiWaitTestNoYield(Timer, 0); + KiWaitTest(Timer, 0); + } + else { + KiWaitListUnlock(); } /* Check if we have a period */ @@ -630,9 +634,13 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration Period = Timer->Period; /* Check if there are any waiters */ + KiWaitListLock(); if (!IsListEmpty(&Timer->Header.WaitListHead)) { - KiWaitTestNoYield(Timer, 0); + KiWaitTest(Timer, 0); + } + else { + KiWaitListUnlock(); } /* Check if we have a period */ @@ -802,9 +810,13 @@ xbox::void_xt FASTCALL xbox::KiTimerListExpire Period = Timer->Period; /* Check if there's any waiters */ + KiWaitListLock(); if (!IsListEmpty(&Timer->Header.WaitListHead)) { - KiWaitTestNoYield(Timer, 0); + KiWaitTest(Timer, 0); + } + else { + KiWaitListUnlock(); } /* Check if we have a period */ @@ -1110,7 +1122,7 @@ xbox::boolean_xt xbox::KiInsertQueueApc return TRUE; } -xbox::void_xt xbox::KiWaitTestNoYield +xbox::void_xt xbox::KiWaitTest ( IN PVOID Object, IN KPRIORITY Increment @@ -1121,8 +1133,9 @@ xbox::void_xt xbox::KiWaitTestNoYield PKTHREAD WaitThread; PKMUTANT FirstObject = (PKMUTANT)Object; + ASSERT_WAIT_LIST_LOCKED; + /* Loop the Wait Entries */ - KiWaitListLock(); WaitList = &FirstObject->Header.WaitListHead; WaitEntry = WaitList->Flink; while ((FirstObject->Header.SignalState > 0) && (WaitEntry != WaitList)) { @@ -1162,18 +1175,6 @@ NextWaitEntry: KiWaitListUnlock(); } -xbox::void_xt xbox::KiWaitTest -( - IN PVOID Object, - IN KPRIORITY Increment -) -{ - KiWaitTestNoYield(Object, Increment); - - // Now that we have unwaited all the threads we could, yield - std::this_thread::yield(); -} - xbox::void_xt xbox::KiWaitSatisfyAll ( IN PKWAIT_BLOCK FirstBlock @@ -1216,6 +1217,11 @@ xbox::void_xt xbox::KiUnwaitThread { ASSERT_WAIT_LIST_LOCKED; + if (Thread->State != Waiting) { + // Don't do anything if it was already unwaited + return; + } + /* Unlink the thread */ KiUnlinkThread(Thread, WaitStatus); @@ -1280,4 +1286,8 @@ xbox::void_xt xbox::KiUnlinkThread /* Increment the Queue's active threads */ if (Thread->Queue) Thread->Queue->CurrentCount++; #endif + + // Sanity check: set WaitBlockList to nullptr so that we can catch the case where a waiter starts a new wait but forgets to setup a new wait block. This + // way, we will crash instead of silently using the pointer to the old block + Thread->WaitBlockList = zeroptr; } diff --git a/src/core/kernel/exports/EmuKrnlKi.h b/src/core/kernel/exports/EmuKrnlKi.h index d436093a1..efc929fbc 100644 --- a/src/core/kernel/exports/EmuKrnlKi.h +++ b/src/core/kernel/exports/EmuKrnlKi.h @@ -204,12 +204,6 @@ namespace xbox IN KPRIORITY Increment ); - void_xt KiWaitTestNoYield - ( - IN PVOID Object, - IN KPRIORITY Increment - ); - void_xt KiWaitTest ( IN PVOID Object, diff --git a/src/core/kernel/exports/EmuKrnlNt.cpp b/src/core/kernel/exports/EmuKrnlNt.cpp index 8b9249a06..8595019cb 100644 --- a/src/core/kernel/exports/EmuKrnlNt.cpp +++ b/src/core/kernel/exports/EmuKrnlNt.cpp @@ -2233,17 +2233,11 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx PKTHREAD kThread = KeGetCurrentThread(); kThread->WaitStatus = X_STATUS_SUCCESS; - if (!Timeout || (Timeout->QuadPart == 0)) { - // Use the built-in ktimer as a dummy wait object, so that KiUnwaitThreadAndLock can still work - xbox::PKWAIT_BLOCK WaitBlock = &kThread->TimerWaitBlock; - kThread->WaitBlockList = WaitBlock; - xbox::PKTIMER Timer = &kThread->Timer; - WaitBlock->NextWaitBlock = WaitBlock; - Timer->Header.WaitListHead.Flink = &WaitBlock->WaitListEntry; - Timer->Header.WaitListHead.Blink = &WaitBlock->WaitListEntry; + if (!AddWaitObject(kThread, Timeout)) { + RETURN(X_STATUS_TIMEOUT); } - xbox::ntstatus_xt ret = WaitApc([Count, &nativeHandles, WaitType, Alertable, kThread]() -> std::optional { + xbox::ntstatus_xt ret = WaitApc([Count, &nativeHandles, WaitType, Alertable](xbox::PKTHREAD kThread) -> std::optional { NtDll::LARGE_INTEGER ExpireTime; ExpireTime.QuadPart = 0; NTSTATUS Status = NtDll::NtWaitForMultipleObjects( @@ -2258,8 +2252,8 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx // If the wait was satisfied with the host, then also unwait the thread on the guest side, to be sure to remove WaitBlocks that might have been added // to the thread. Test case: Steel Battalion xbox::KiUnwaitThreadAndLock(kThread, Status, 0); - return std::make_optional(Status); - }, Timeout, Alertable, WaitMode); + return std::make_optional(kThread->WaitStatus); + }, Timeout, Alertable, WaitMode, kThread); RETURN(ret); } diff --git a/src/core/kernel/exports/EmuKrnlPs.cpp b/src/core/kernel/exports/EmuKrnlPs.cpp index aebbb3af4..8084ed97d 100644 --- a/src/core/kernel/exports/EmuKrnlPs.cpp +++ b/src/core/kernel/exports/EmuKrnlPs.cpp @@ -506,8 +506,13 @@ XBSYSAPI EXPORTNUM(258) xbox::void_xt NTAPI xbox::PsTerminateSystemThread KeQuerySystemTime(&eThread->ExitTime); eThread->ExitStatus = ExitStatus; eThread->Tcb.Header.SignalState = 1; + KiWaitListLock(); if (!IsListEmpty(&eThread->Tcb.Header.WaitListHead)) { KiWaitTest((PVOID)&eThread->Tcb, 0); + std::this_thread::yield(); + } + else { + KiWaitListUnlock(); } if (GetNativeHandle(eThread->UniqueThread)) { From 32ec4ee6f9fcf67172962c0c1b8b31d03e7faa2b Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Sat, 1 Apr 2023 18:55:56 +0200 Subject: [PATCH 34/35] Use a DPC for expired timers + don't execute NV2A DPCs from the timer thread to avoid the exception overhead --- src/common/Timer.cpp | 26 +++++------ src/core/kernel/exports/EmuKrnl.cpp | 2 - src/core/kernel/exports/EmuKrnlKe.cpp | 20 ++++++++- src/core/kernel/exports/EmuKrnlKe.h | 1 + src/core/kernel/exports/EmuKrnlKi.cpp | 62 ++++++++++++--------------- src/core/kernel/init/CxbxKrnl.cpp | 13 +++--- src/core/kernel/init/CxbxKrnl.h | 1 + 7 files changed, 66 insertions(+), 59 deletions(-) diff --git a/src/common/Timer.cpp b/src/common/Timer.cpp index c361e2750..422fb32a5 100644 --- a/src/common/Timer.cpp +++ b/src/common/Timer.cpp @@ -115,13 +115,11 @@ static void update_non_periodic_events() // update dsound dsound_worker(); - // check for hw interrupts, but skip the gpu interrupt since that is serviced in vblank_next + // check for hw interrupts for (int i = 0; i < MAX_BUS_INTERRUPT_LEVEL; i++) { - if (i != 3) { - // If the interrupt is pending and connected, process it - if (g_bEnableAllInterrupts && HalSystemInterrupts[i].IsPending() && EmuInterruptList[i] && EmuInterruptList[i]->Connected) { - HalSystemInterrupts[i].Trigger(EmuInterruptList[i]); - } + // If the interrupt is pending and connected, process it + if (g_bEnableAllInterrupts && HalSystemInterrupts[i].IsPending() && EmuInterruptList[i] && EmuInterruptList[i]->Connected) { + HalSystemInterrupts[i].Trigger(EmuInterruptList[i]); } } } @@ -140,12 +138,13 @@ uint64_t get_now() static uint64_t get_next(uint64_t now) { - std::array next; - next[0] = g_NV2A->vblank_next(now); - next[1] = g_NV2A->ptimer_next(now); - next[2] = pit_next(now); - next[3] = g_USB0->m_HostController->OHCI_next(now); - next[4] = dsound_next(now); + std::array next = { + pit_next(now), + g_NV2A->vblank_next(now), + g_NV2A->ptimer_next(now), + g_USB0->m_HostController->OHCI_next(now), + dsound_next(now) + }; return *std::min_element(next.begin(), next.end()); } @@ -155,6 +154,9 @@ xbox::void_xt NTAPI system_events(xbox::PVOID arg) // So we increase its priority to above normal, so that it scheduled more often SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); + // Always run this thread at dpc level to prevent it from ever executing APCs/DPCs + xbox::KeRaiseIrqlToDpcLevel(); + while (true) { const uint64_t last_time = get_now(); const uint64_t nearest_next = get_next(last_time); diff --git a/src/core/kernel/exports/EmuKrnl.cpp b/src/core/kernel/exports/EmuKrnl.cpp index 8d396e6a9..252a0dac7 100644 --- a/src/core/kernel/exports/EmuKrnl.cpp +++ b/src/core/kernel/exports/EmuKrnl.cpp @@ -142,8 +142,6 @@ void RestoreInterruptMode(bool value) g_bInterruptsEnabled = value; } -extern void ExecuteDpcQueue(); - void KiUnexpectedInterrupt() { xbox::KeBugCheck(TRAP_CAUSE_UNKNOWN); // see diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 4038c6d0f..ffdd8b984 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -97,6 +97,7 @@ namespace NtDll typedef struct _DpcData { CRITICAL_SECTION Lock; std::atomic_flag IsDpcActive; + std::atomic_flag IsDpcPending; xbox::LIST_ENTRY DpcQueue; // TODO : Use KeGetCurrentPrcb()->DpcListHead instead } DpcData; @@ -131,7 +132,6 @@ xbox::ulonglong_xt LARGE_INTEGER2ULONGLONG(xbox::LARGE_INTEGER value) break; \ } - xbox::void_xt xbox::KeResumeThreadEx ( IN PKTHREAD Thread @@ -155,6 +155,11 @@ xbox::void_xt xbox::KeSuspendThreadEx KiInsertQueueApc(&Thread->SuspendApc, 0); } +xbox::void_xt xbox::KeWaitForDpc() +{ + g_DpcData.IsDpcPending.wait(false); +} + // ****************************************************************** // * EmuKeGetPcr() // * NOTE: This is a macro on the Xbox, however we implement it @@ -482,6 +487,7 @@ void ExecuteDpcQueue() // Set DpcRoutineActive to support KeIsExecutingDpc: g_DpcData.IsDpcActive.test_and_set(); KeGetCurrentPrcb()->DpcRoutineActive = TRUE; // Experimental + LeaveCriticalSection(&(g_DpcData.Lock)); EmuLog(LOG_LEVEL::DEBUG, "Global DpcQueue, calling DPC object 0x%.8X at 0x%.8X", pkdpc, pkdpc->DeferredRoutine); @@ -492,10 +498,13 @@ void ExecuteDpcQueue() pkdpc->SystemArgument1, pkdpc->SystemArgument2); + EnterCriticalSection(&(g_DpcData.Lock)); KeGetCurrentPrcb()->DpcRoutineActive = FALSE; // Experimental g_DpcData.IsDpcActive.clear(); } + g_DpcData.IsDpcPending.clear(); + // Assert(g_DpcData._dwThreadId == GetCurrentThreadId()); // Assert(g_DpcData._dwDpcThreadId == g_DpcData._dwThreadId); // g_DpcData._dwDpcThreadId = 0; @@ -1268,18 +1277,25 @@ XBSYSAPI EXPORTNUM(119) xbox::boolean_xt NTAPI xbox::KeInsertQueueDpc Dpc->SystemArgument1 = SystemArgument1; Dpc->SystemArgument2 = SystemArgument2; InsertTailList(&(g_DpcData.DpcQueue), &(Dpc->DpcListEntry)); + LeaveCriticalSection(&(g_DpcData.Lock)); + g_DpcData.IsDpcPending.test_and_set(); + g_DpcData.IsDpcPending.notify_one(); + // TODO : Instead of DpcQueue, add the DPC to KeGetCurrentPrcb()->DpcListHead // Signal the Dpc handling code there's work to do if (!IsDpcActive()) { HalRequestSoftwareInterrupt(DISPATCH_LEVEL); } + // OpenXbox has this instead: // if (!pKPRCB->DpcRoutineActive && !pKPRCB->DpcInterruptRequested) { // pKPRCB->DpcInterruptRequested = TRUE; } + else { + LeaveCriticalSection(&(g_DpcData.Lock)); + } // Thread-safety is no longer required anymore - LeaveCriticalSection(&(g_DpcData.Lock)); // TODO : Instead, enable interrupts - use KeLowerIrql(OldIrql) ? RETURN(NeedsInsertion); diff --git a/src/core/kernel/exports/EmuKrnlKe.h b/src/core/kernel/exports/EmuKrnlKe.h index 02ca32c71..820c26f78 100644 --- a/src/core/kernel/exports/EmuKrnlKe.h +++ b/src/core/kernel/exports/EmuKrnlKe.h @@ -63,4 +63,5 @@ namespace xbox ); void_xt KeEmptyQueueApc(); + void_xt KeWaitForDpc(); } diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index 1f0aca78a..40c7210b9 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -95,6 +95,7 @@ the said software). xbox::KPROCESS KiUniqueProcess; const xbox::ulong_xt CLOCK_TIME_INCREMENT = 0x2710; +xbox::KDPC KiTimerExpireDpc; xbox::KI_TIMER_LOCK KiTimerMtx; xbox::KI_WAIT_LIST_LOCK KiWaitListMtx; xbox::KTIMER_TABLE_ENTRY KiTimerTableListHead[TIMER_TABLE_SIZE]; @@ -111,6 +112,7 @@ xbox::void_xt xbox::KiInitSystem() InitializeListHead(&KiWaitInListHead); KiTimerMtx.Acquired = 0; + KeInitializeDpc(&KiTimerExpireDpc, KiTimerExpiration, NULL); for (unsigned i = 0; i < TIMER_TABLE_SIZE; i++) { InitializeListHead(&KiTimerTableListHead[i].Entry); KiTimerTableListHead[i].Time.u.HighPart = 0xFFFFFFFF; @@ -146,7 +148,6 @@ xbox::void_xt xbox::KiWaitListUnlock() xbox::void_xt xbox::KiClockIsr(ulonglong_xt TotalUs) { - KIRQL OldIrql; LARGE_INTEGER InterruptTime, SystemTime; ULONG Hand; DWORD OldKeTickCount; @@ -157,8 +158,6 @@ xbox::void_xt xbox::KiClockIsr(ulonglong_xt TotalUs) TotalMs += RecoveredMs; LostUs -= (RecoveredMs * 1000); - OldIrql = KfRaiseIrql(CLOCK_LEVEL); - // Update the interrupt time InterruptTime.u.LowPart = KeInterruptTime.LowPart; InterruptTime.u.HighPart = KeInterruptTime.High1Time; @@ -175,9 +174,7 @@ xbox::void_xt xbox::KiClockIsr(ulonglong_xt TotalUs) xbox::KeSystemTime.High2Time = HostSystemTime.u.HighPart; xbox::KeSystemTime.LowPart = HostSystemTime.u.LowPart; xbox::KeSystemTime.High1Time = HostSystemTime.u.HighPart; - KfLowerIrql(OldIrql); KeSetSystemTime(&HostSystemTime, &OldSystemTime); - OldIrql = KfRaiseIrql(CLOCK_LEVEL); } else { SystemTime.u.LowPart = KeSystemTime.LowPart; @@ -205,15 +202,13 @@ xbox::void_xt xbox::KiClockIsr(ulonglong_xt TotalUs) Hand = i & (TIMER_TABLE_SIZE - 1); if (KiTimerTableListHead[Hand].Entry.Flink != &KiTimerTableListHead[Hand].Entry && (ULONGLONG)InterruptTime.QuadPart >= KiTimerTableListHead[Hand].Time.QuadPart) { - // NOTE: instead of using a DPC, we call KiTimerExpiration directly to speed things up, and avoid the DPC lock - KiTimerExpiration(zeroptr, zeroptr, (PVOID)Hand, 0); + KeInsertQueueDpc(&KiTimerExpireDpc, (PVOID)OldKeTickCount, (PVOID)EndKeTickCount); + break; } } KiTimerMtx.Acquired--; KiTimerMtx.Mtx.unlock(); } - - KfLowerIrql(OldIrql); } xbox::void_xt NTAPI xbox::KiCheckTimerTable @@ -566,7 +561,7 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration { ULARGE_INTEGER SystemTime, InterruptTime; LARGE_INTEGER Interval; - LONG Limit, Index, i; + LONG i; ULONG Timers, ActiveTimers, DpcCalls; PLIST_ENTRY ListHead, NextEntry; KIRQL OldIrql; @@ -578,19 +573,10 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration /* Query system and interrupt time */ KeQuerySystemTime((PLARGE_INTEGER)&SystemTime); InterruptTime.QuadPart = KeQueryInterruptTime(); - Limit = KeTickCount; /* Get the index of the timer and normalize it */ - Index = PtrToLong(SystemArgument1); - if ((Limit - Index) >= TIMER_TABLE_SIZE) - { - /* Normalize it */ - Limit = Index + TIMER_TABLE_SIZE - 1; - } - - /* Setup index and actual limit */ - Index--; - Limit &= (TIMER_TABLE_SIZE - 1); + dword_xt OldKeTickCount = PtrToLong(SystemArgument1); + dword_xt EndKeTickCount = PtrToLong(SystemArgument2); /* Setup accounting data */ DpcCalls = 0; @@ -598,15 +584,14 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration ActiveTimers = 4; /* Lock the Database */ - // NOTE: this function is only called from KiClockIsr, with the irql at CLOCK_LEVEL - ASSERT_TIMER_LOCKED; - OldIrql = KeGetCurrentIrql(); + KiTimerLock(); + KiLockDispatcherDatabase(&OldIrql); /* Start expiration loop */ - do + for (dword_xt i = OldKeTickCount; i < EndKeTickCount; ++i) { /* Get the current index */ - Index = (Index + 1) & (TIMER_TABLE_SIZE - 1); + dword_xt Index = i & (TIMER_TABLE_SIZE - 1); /* Get list pointers and loop the list */ ListHead = &KiTimerTableListHead[Index].Entry; @@ -688,7 +673,7 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration ActiveTimers = 4; /* Lock the dispatcher database */ - KfRaiseIrql(OldIrql); + KiLockDispatcherDatabaseAtDpcLevel(); } } else @@ -731,14 +716,14 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration ActiveTimers = 4; /* Lock the dispatcher database */ - KfRaiseIrql(OldIrql); + KiLockDispatcherDatabaseAtDpcLevel(); } /* Done looping */ break; } } - } while (Index != Limit); + } /* Verify the timer table, on a debug kernel only */ if (g_bIsDebugKernel) { @@ -766,7 +751,17 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration ); } - KfRaiseIrql(OldIrql); + KiTimerUnlock(); + /* Lower IRQL if we need to */ + if (OldIrql != DISPATCH_LEVEL) { + KfLowerIrql(OldIrql); + } + } + else + { + /* Unlock the dispatcher */ + KiTimerUnlock(); + KiUnlockDispatcherDatabase(OldIrql); } } @@ -839,6 +834,8 @@ xbox::void_xt FASTCALL xbox::KiTimerListExpire } } + KiTimerUnlock(); + /* Check if we still have DPC entries */ if (DpcCalls) { @@ -862,17 +859,12 @@ xbox::void_xt FASTCALL xbox::KiTimerListExpire /* Lower IRQL */ KfLowerIrql(OldIrql); - KiTimerUnlock(); } else { /* Unlock the dispatcher */ KiUnlockDispatcherDatabase(OldIrql); - KiTimerUnlock(); } - - // We have unwaited the threads with the expired timers and called their DPCs, so we can yield now - std::this_thread::yield(); } template diff --git a/src/core/kernel/init/CxbxKrnl.cpp b/src/core/kernel/init/CxbxKrnl.cpp index a679eb905..75ed08139 100644 --- a/src/core/kernel/init/CxbxKrnl.cpp +++ b/src/core/kernel/init/CxbxKrnl.cpp @@ -1404,14 +1404,11 @@ static void CxbxrKrnlInitHacks() // Launch the xbe xbox::PsCreateSystemThread(&hThread, xbox::zeroptr, CxbxLaunchXbe, Entry, FALSE); - EmuKeFreePcr(); - __asm add esp, Host2XbStackSizeReserved; - - // This will wait forever - std::condition_variable cv; - std::mutex m; - std::unique_lock lock(m); - cv.wait(lock, [] { return false; }); + xbox::KeRaiseIrqlToDpcLevel(); + while (true) { + xbox::KeWaitForDpc(); + ExecuteDpcQueue(); + } } // REMARK: the following is useless, but PatrickvL has asked to keep it for documentation purposes diff --git a/src/core/kernel/init/CxbxKrnl.h b/src/core/kernel/init/CxbxKrnl.h index c11af39cc..6269bc9fa 100644 --- a/src/core/kernel/init/CxbxKrnl.h +++ b/src/core/kernel/init/CxbxKrnl.h @@ -157,6 +157,7 @@ void CxbxKrnlNoFunc(); void InitDpcData(); // Implemented in EmuKrnlKe.cpp bool IsDpcActive(); +void ExecuteDpcQueue(); /*! kernel thunk table */ extern uint32_t CxbxKrnl_KernelThunkTable[379]; From 7563dd3ecf16458f1510e4a2f62fd567bba2dfe1 Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Wed, 5 Apr 2023 17:48:31 +0200 Subject: [PATCH 35/35] Never change the thread priority on the host and the disable boost flag too This fixes almost all the games that were broken in this branch --- src/core/kernel/exports/EmuKrnlKe.cpp | 43 ++------------------------- 1 file changed, 3 insertions(+), 40 deletions(-) diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index ffdd8b984..72ed32a37 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -1404,13 +1404,7 @@ XBSYSAPI EXPORTNUM(124) xbox::long_xt NTAPI xbox::KeQueryBasePriorityThread KIRQL OldIrql; KiLockDispatcherDatabase(&OldIrql); - long_xt ret; - if (const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread)) { - ret = GetThreadPriority(*nativeHandle); - } - else { - ret = Thread->Priority; - } + long_xt ret = Thread->Priority; KiUnlockDispatcherDatabase(OldIrql); @@ -1855,32 +1849,8 @@ XBSYSAPI EXPORTNUM(143) xbox::long_xt NTAPI xbox::KeSetBasePriorityThread KIRQL oldIRQL; KiLockDispatcherDatabase(&oldIRQL); - long_xt ret; - if (const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread)) { - ret = GetThreadPriority(*nativeHandle); - - if (Priority == 16) { - Priority = THREAD_PRIORITY_TIME_CRITICAL; - } - else if (Priority == -16) { - Priority = THREAD_PRIORITY_IDLE; - } - - // HACK: only change the priority if set above normal. Test case: Black. It calls this to set the priority of a thread to THREAD_PRIORITY_LOWEST, and that same - // thread calls D3DDevice_BlockUntilVerticalBlank. The problem arises because there is also another thread that runs at higher priority and calls D3DDevice_BlockUntilVerticalBlank - // too to wait on the vblank kevent. When the vblank does occur, this other thread will satisfy the wait first, and set the kevent back to non-signalled. Thus, the other - // thread will miss the signal because typically, Windows won't re-schedule it after many more vblank have already occured. The proper solution is to boost the priority of - // the thread when the kevent is signalled, with the increment argument specified in KeSetEvent. Such boosts should also be appiled whenever a thread satisfies a wait. - if (Priority <= THREAD_PRIORITY_NORMAL) { - BOOL result = SetThreadPriority(*nativeHandle, Priority); - if (!result) { - EmuLog(LOG_LEVEL::WARNING, "SetThreadPriority failed: %s", WinError2Str().c_str()); - } - } - } - else { - ret = Thread->Priority; - } + Thread->Priority = Priority; + long_xt ret = Thread->Priority; KiUnlockDispatcherDatabase(oldIRQL); @@ -1901,13 +1871,6 @@ XBSYSAPI EXPORTNUM(144) xbox::boolean_xt NTAPI xbox::KeSetDisableBoostThread KIRQL oldIRQL; KiLockDispatcherDatabase(&oldIRQL); - if (const auto &nativeHandle = GetNativeHandle(reinterpret_cast(Thread)->UniqueThread)) { - BOOL bRet = SetThreadPriorityBoost(*nativeHandle, Disable); - if (!bRet) { - EmuLog(LOG_LEVEL::WARNING, "SetThreadPriorityBoost failed: %s", WinError2Str().c_str()); - } - } - boolean_xt prevDisableBoost = Thread->DisableBoost; Thread->DisableBoost = (CHAR)Disable;