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(