Merge remote-tracking branch 'ergo720/less_busy_loops' into testbed

This commit is contained in:
Luke Usher 2024-05-23 09:22:33 +01:00
commit 4cdb5f5cba
39 changed files with 1145 additions and 836 deletions

View File

@ -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);

View File

@ -25,19 +25,45 @@
// *
// ******************************************************************
#ifdef _WIN32
#include <core\kernel\exports\xboxkrnl.h>
#include <windows.h>
#endif
#include <thread>
#include <vector>
#include <mutex>
#include <array>
#include "Timer.h"
#include "common\util\CxbxUtil.h"
#include "core\kernel\support\EmuFS.h"
#include "core/kernel/exports/EmuKrnlPs.hpp"
#ifdef __linux__
#include <time.h>
#endif
#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"
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
int64_t HostQPCFrequency, HostQPCStartTime;
void timer_init()
{
QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER *>(&HostQPCFrequency));
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER *>(&HostQPCStartTime));
pit_last_qpc = last_qpc = HostQPCStartTime;
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)
@ -69,174 +95,83 @@ 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<TimerObject*> 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 this here
static uint64_t pit_next(uint64_t now)
{
#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;
constexpr uint64_t pit_period = 1000;
uint64_t next = pit_last + pit_period;
if (now >= next) {
xbox::KiClockIsr(now - pit_last);
pit_last = get_now();
return pit_period;
}
return pit_last + pit_period - now; // time remaining until next clock interrupt
}
// Calculates the next expire time of the timer
static inline uint64_t GetNextExpireTime(TimerObject* Timer)
static void update_non_periodic_events()
{
return GetTime_NS(Timer) + Timer->ExpireTime_MS.load();
// update dsound
dsound_worker();
// 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]);
}
}
}
// Deallocates the memory of the timer
void Timer_Destroy(TimerObject* Timer)
uint64_t get_now()
{
unsigned int index, i;
std::lock_guard<std::mutex>lock(TimerMtx);
index = TimerList.size();
for (i = 0; i < index; i++) {
if (Timer == TimerList[i]) {
index = i;
}
}
assert(index != TimerList.size());
delete Timer;
TimerList.erase(TimerList.begin() + index);
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;
}
void Timer_Shutdown()
static uint64_t get_next(uint64_t 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();
std::array<uint64_t, 5> 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());
}
// Thread that runs the timer
void NTAPI ClockThread(void *TimerArg)
xbox::void_xt NTAPI system_events(xbox::PVOID arg)
{
TimerObject *Timer = static_cast<TimerObject *>(TimerArg);
if (!Timer->Name.empty()) {
CxbxSetThreadName(Timer->Name.c_str());
}
if (!Timer->IsXboxTimer) {
g_AffinityPolicy->SetAffinityOther();
}
// 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 scheduled more often
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
uint64_t NewExpireTime = GetNextExpireTime(Timer);
// Always run this thread at dpc level to prevent it from ever executing APCs/DPCs
xbox::KeRaiseIrqlToDpcLevel();
while (true) {
if (GetTime_NS(Timer) > NewExpireTime) {
if (Timer->Exit.load()) {
Timer_Destroy(Timer);
return;
const uint64_t last_time = get_now();
const uint64_t nearest_next = get_next(last_time);
while (true) {
update_non_periodic_events();
uint64_t elapsed_us = get_now() - last_time;
if (elapsed_us >= nearest_next) {
break;
}
Timer->Callback(Timer->Opaque);
NewExpireTime = GetNextExpireTime(Timer);
std::this_thread::yield();
}
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_guard<std::mutex>lock(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()
{
#ifdef _WIN32
QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&HostQPCFrequency));
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&HostQPCStartTime));
#elif __linux__
ClockFrequency = 0;
#else
#error "Unsupported OS"
#endif
}
int64_t Timer_GetScaledPerformanceCounter(int64_t Period)
{
LARGE_INTEGER currentQPC;

View File

@ -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

View File

@ -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();

View File

@ -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;

View File

@ -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;

View File

@ -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"
@ -160,8 +161,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;
@ -169,7 +168,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;
@ -229,7 +227,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);
@ -629,25 +626,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
{
@ -1706,6 +1691,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;
}
@ -1723,6 +1715,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)
@ -1930,47 +1930,25 @@ std::chrono::steady_clock::time_point GetNextVBlankTime()
return steady_clock::now() + duration_cast<steady_clock::duration>(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!
g_Xbox_VBlankData.VBlank++;
EmuLog(LOG_LEVEL::DEBUG, "Timing thread is running.");
// TODO: Fixme. This may not be right...
g_Xbox_SwapData.SwapVBlank = 1;
auto nextVBlankTime = GetNextVBlankTime();
g_Xbox_VBlankData.Swap = 0;
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();
// TODO: This can't be accurate...
g_Xbox_SwapData.TimeUntilSwapVBlank = 0;
// Increment the VBlank Counter and Wake all threads there were waiting for the VBlank to occur
std::unique_lock<std::mutex> 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;
}
// 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)
@ -6541,31 +6519,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<std::mutex> 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
// ******************************************************************

View File

@ -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<std::mutex> 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;
}

View File

@ -40,7 +40,7 @@
void LookupTrampolinesD3D();
// initialize render window
extern void CxbxInitWindow(bool bFullInit);
extern void CxbxInitWindow();
void CxbxUpdateNativeD3DResources();

View File

@ -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.

View File

@ -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);

View File

@ -66,7 +66,7 @@ std::map<const std::string, const xbox_patch_t> 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<const std::string, const xbox_patch_t> 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),

View File

@ -960,15 +960,41 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait)
NewTime.QuadPart += (static_cast<xbox::ulonglong_xt>(dwMilliseconds) * CLOCK_TIME_INCREMENT);
}
xbox::dword_xt ret = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable]() -> std::optional<DWORD> {
PKTHREAD kThread = KeGetCurrentThread();
kThread->WaitStatus = X_STATUS_SUCCESS;
if (!AddWaitObject(kThread, Timeout)) {
RETURN(WAIT_TIMEOUT);
}
xbox::ntstatus_xt status = WaitApc<true>([hObjectToSignal, hObjectToWaitOn, bAlertable](xbox::PKTHREAD kThread) -> std::optional<DWORD> {
DWORD dwRet = SignalObjectAndWait(hObjectToSignal, hObjectToWaitOn, 0, bAlertable);
if (dwRet == WAIT_TIMEOUT) {
return std::nullopt;
}
return std::make_optional<DWORD>(dwRet);
}, Timeout, bAlertable, UserMode);
// 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(kThread, Status, 0);
return std::make_optional<ntstatus_xt>(kThread->WaitStatus);
}, Timeout, bAlertable, UserMode, kThread);
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);
}
// ******************************************************************

View File

@ -605,6 +605,9 @@ XBSYSAPI EXPORTNUM(352) void_xt NTAPI RtlRip
PCHAR Message
);
void_xt RtlInitSystem();
extern RTL_CRITICAL_SECTION NtSystemTimeCritSec;
}
#endif

View File

@ -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
@ -1945,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;
@ -1969,6 +1971,8 @@ typedef struct _KTHREAD
}
KTHREAD, *PKTHREAD, *RESTRICTED_POINTER PRKTHREAD;
#define X_MAXIMUM_SUSPEND_COUNT 0x7F
// ******************************************************************
// * ETHREAD
// ******************************************************************

View File

@ -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;
@ -140,8 +142,6 @@ void RestoreInterruptMode(bool value)
g_bInterruptsEnabled = value;
}
extern void ExecuteDpcQueue();
void KiUnexpectedInterrupt()
{
xbox::KeBugCheck(TRAP_CAUSE_UNKNOWN); // see
@ -178,6 +178,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

View File

@ -52,8 +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];
inline std::condition_variable g_InterruptSignal;
inline std::atomic_bool g_AnyInterruptAsserted = false;
// Indicates to disable/enable all interrupts when cli and sti instructions are executed
inline std::atomic_bool g_bEnableAllInterrupts = true;
class HalSystemInterrupt {
public:
@ -64,8 +64,6 @@ public:
}
m_Asserted = state;
g_AnyInterruptAsserted = true;
g_InterruptSignal.notify_one();
};
void Enable() {
@ -110,17 +108,18 @@ 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<typename T>
std::optional<xbox::ntstatus_xt> SatisfyWait(T &&Lambda, xbox::PETHREAD eThread, xbox::boolean_xt Alertable, xbox::char_xt WaitMode)
std::optional<xbox::ntstatus_xt> 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;
}
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) {
@ -131,56 +130,65 @@ std::optional<xbox::ntstatus_xt> SatisfyWait(T &&Lambda, xbox::PETHREAD eThread,
(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<typename T>
xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolean_xt Alertable, xbox::char_xt WaitMode)
template<bool host_wait, typename T>
xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolean_xt Alertable, xbox::char_xt WaitMode, xbox::PKTHREAD kThread)
{
// 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<xbox::PETHREAD>(EmuKeGetPcr()->Prcb->CurrentThread);
// NOTE1: kThread->Alerted is currently never set. When the alerted mechanism is implemented, the alerts should
// also interrupt the wait.
xbox::ntstatus_xt status;
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
while (true) {
if (const auto ret = SatisfyWait(Lambda, eThread, Alertable, WaitMode)) {
return *ret;
if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) {
status = *ret;
break;
}
std::this_thread::yield();
}
}
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, eThread, Alertable, WaitMode)) {
return *ret;
if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) {
status = *ret;
}
else {
return 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
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<xbox::ulonglong_xt>(AbsoluteExpireTime->QuadPart)) {
if (const auto ret = SatisfyWait(Lambda, eThread, Alertable, WaitMode)) {
return *ret;
while (true) {
if (const auto ret = SatisfyWait(Lambda, kThread, Alertable, WaitMode)) {
status = *ret;
break;
}
if (host_wait && (kThread->State == xbox::Ready)) {
status = kThread->WaitStatus;
break;
}
std::this_thread::yield();
Now = xbox::KeQueryInterruptTime();
}
return X_STATUS_TIMEOUT;
}
if constexpr (host_wait) {
kThread->State = xbox::Running;
}
return status;
}
#endif

View File

@ -97,10 +97,12 @@ 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;
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)
{
@ -130,6 +132,33 @@ xbox::ulonglong_xt LARGE_INTEGER2ULONGLONG(xbox::LARGE_INTEGER value)
break; \
}
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;
KiWaitListLock();
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);
}
xbox::void_xt xbox::KeWaitForDpc()
{
g_DpcData.IsDpcPending.wait(false);
}
// ******************************************************************
// * EmuKeGetPcr()
@ -166,7 +195,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 +213,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;
@ -246,7 +271,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);
}
@ -463,6 +488,7 @@ void ExecuteDpcQueue()
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 :
@ -477,6 +503,8 @@ void ExecuteDpcQueue()
g_DpcData.IsDpcActive.clear();
}
g_DpcData.IsDpcPending.clear();
// Assert(g_DpcData._dwThreadId == GetCurrentThreadId());
// Assert(g_DpcData._dwDpcThreadId == g_DpcData._dwThreadId);
// g_DpcData._dwDpcThreadId = 0;
@ -715,7 +743,13 @@ 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<ntstatus_xt> {
PKTHREAD kThread = KeGetCurrentThread();
kThread->WaitStatus = X_STATUS_SUCCESS;
if (!AddWaitObject(kThread, Interval)) {
RETURN(X_STATUS_TIMEOUT);
}
xbox::ntstatus_xt ret = WaitApc<true>([Alertable](xbox::PKTHREAD kThread) -> std::optional<ntstatus_xt> {
NtDll::LARGE_INTEGER ExpireTime;
ExpireTime.QuadPart = 0;
NTSTATUS Status = NtDll::NtDelayExecution(Alertable, &ExpireTime);
@ -723,8 +757,11 @@ XBSYSAPI EXPORTNUM(99) xbox::ntstatus_xt NTAPI xbox::KeDelayExecutionThread
if (Status >= 0 && Status != STATUS_ALERTED && Status != STATUS_USER_APC) {
return std::nullopt;
}
return std::make_optional<ntstatus_xt>(Status);
}, Interval, Alertable, WaitMode);
// 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<ntstatus_xt>(kThread->WaitStatus);
}, Interval, Alertable, WaitMode, kThread);
if (ret == X_STATUS_TIMEOUT) {
// NOTE: this function considers a timeout a success
@ -1206,35 +1243,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);
}
}
@ -1266,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);
@ -1353,13 +1371,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;
// 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);
std::this_thread::yield();
}
else {
KiWaitListUnlock();
}
Event->Header.SignalState = 0;
@ -1385,9 +1404,7 @@ 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<true>(reinterpret_cast<PETHREAD>(Thread)->UniqueThread);
long_xt ret = GetThreadPriority(*nativeHandle);
long_xt ret = Thread->Priority;
KiUnlockDispatcherDatabase(OldIrql);
@ -1540,12 +1557,14 @@ XBSYSAPI EXPORTNUM(132) xbox::long_xt NTAPI xbox::KeReleaseSemaphore
}
Semaphore->Header.SignalState = adjusted_signalstate;
//TODO: Implement KiWaitTest
#if 0
KiWaitListLock();
if ((initial_state == 0) && (IsListEmpty(&Semaphore->Header.WaitListHead) == FALSE)) {
KiWaitTest(&Semaphore->Header, Increment);
std::this_thread::yield();
}
else {
KiWaitListUnlock();
}
#endif
if (Wait) {
PKTHREAD current_thread = KeGetCurrentThread();
@ -1759,11 +1778,29 @@ 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) {
#if 0
++Thread->SuspendSemaphore.Header.SignalState;
KiWaitListLock();
KiWaitTest(&Thread->SuspendSemaphore, 0);
std::this_thread::yield();
#else
if (const auto &nativeHandle = GetNativeHandle<true>(reinterpret_cast<PETHREAD>(Thread)->UniqueThread)) {
ResumeThread(*nativeHandle);
}
#endif
}
}
RETURN(ret);
KiUnlockDispatcherDatabase(OldIrql);
RETURN(OldCount);
}
XBSYSAPI EXPORTNUM(141) xbox::PLIST_ENTRY NTAPI xbox::KeRundownQueue
@ -1805,25 +1842,15 @@ 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;
KiLockDispatcherDatabase(&oldIRQL);
// It cannot fail because all thread handles are created by ob
const auto &nativeHandle = GetNativeHandle<true>(reinterpret_cast<PETHREAD>(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());
}
}
Thread->Priority = Priority;
long_xt ret = Thread->Priority;
KiUnlockDispatcherDatabase(oldIRQL);
@ -1844,17 +1871,9 @@ 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<true>(reinterpret_cast<PETHREAD>(Thread)->UniqueThread);
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);
@ -1889,7 +1908,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);
@ -1897,16 +1918,14 @@ 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 {
KiWaitListUnlock();
}
} 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);
KiUnwaitThread(WaitBlock->Thread, (NTSTATUS)WaitBlock->WaitKey, Increment);
KiWaitListUnlock();
}
}
@ -1943,6 +1962,7 @@ XBSYSAPI EXPORTNUM(146) xbox::void_xt NTAPI xbox::KeSetEventBoostPriority
return;
}
KiWaitListLock();
if (IsListEmpty(&Event->Header.WaitListHead) != FALSE) {
Event->Header.SignalState = 1;
} else {
@ -1953,11 +1973,9 @@ 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);
KiUnwaitThread(WaitThread, X_STATUS_SUCCESS, 1);
}
KiWaitListUnlock();
KiUnlockDispatcherDatabase(OldIrql);
}
@ -1989,8 +2007,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();
@ -2103,11 +2121,38 @@ 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 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.
if (const auto &nativeHandle = GetNativeHandle<true>(reinterpret_cast<PETHREAD>(Thread)->UniqueThread)) {
SuspendThread(*nativeHandle);
}
#endif
}
}
KiUnlockDispatcherDatabase(OldIrql);
RETURN(OldCount);
}
// ******************************************************************
@ -2203,15 +2248,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);
@ -2270,7 +2313,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;
}
@ -2285,36 +2328,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;
@ -2322,12 +2349,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
@ -2335,9 +2363,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) {
@ -2349,7 +2374,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();
@ -2364,12 +2389,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<false>([](PKTHREAD Thread) -> std::optional<ntstatus_xt> {
if (Thread->State == Ready) {
// We have been readied to resume execution, so exit the wait
return std::make_optional<ntstatus_xt>(Thread->WaitStatus);
}
return std::nullopt;
}, Timeout, Alertable, WaitMode, Thread);
// 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)
@ -2384,10 +2412,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);
@ -2396,14 +2428,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) {
@ -2445,12 +2470,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)) {
@ -2498,36 +2520,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;
@ -2539,9 +2545,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) {
@ -2553,7 +2556,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);
/*
@ -2568,13 +2571,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<false>([](PKTHREAD Thread) -> std::optional<ntstatus_xt> {
if (Thread->State == Ready) {
// We have been readied to resume execution, so exit the wait
return std::make_optional<ntstatus_xt>(Thread->WaitStatus);
}
return std::nullopt;
}, Timeout, Alertable, WaitMode, Thread);
break;
}
// Raise IRQL to DISPATCH_LEVEL and lock the database
@ -2589,10 +2600,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);
@ -2601,14 +2616,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) {

View File

@ -27,6 +27,8 @@
namespace xbox
{
extern std::atomic_flag KeSystemTimeChanged;
void_xt NTAPI KeSetSystemTime
(
IN PLARGE_INTEGER NewTime,
@ -50,5 +52,16 @@ namespace xbox
IN PKPROCESS Process
);
xbox::void_xt KeResumeThreadEx
(
IN PKTHREAD Thread
);
xbox::void_xt KeSuspendThreadEx
(
IN PKTHREAD Thread
);
void_xt KeEmptyQueueApc();
void_xt KeWaitForDpc();
}

View File

@ -59,6 +59,8 @@
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
*/
// Also from ReactOS: KiWaitTest, KiWaitSatisfyAll, KiUnwaitThread, KiUnlinkThread
// COPYING file:
/*
GNU GENERAL PUBLIC LICENSE
@ -84,15 +86,18 @@ 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
#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,55 +134,81 @@ xbox::void_xt xbox::KiTimerUnlock()
KiTimerMtx.Mtx.unlock();
}
xbox::void_xt xbox::KiClockIsr
(
unsigned int ScalingFactor
)
xbox::void_xt xbox::KiWaitListLock()
{
KIRQL OldIrql;
LARGE_INTEGER InterruptTime;
LARGE_INTEGER HostSystemTime;
KiWaitListMtx.Mtx.lock();
KiWaitListMtx.Acquired++;
}
xbox::void_xt xbox::KiWaitListUnlock()
{
KiWaitListMtx.Acquired--;
KiWaitListMtx.Mtx.unlock();
}
xbox::void_xt xbox::KiClockIsr(ulonglong_xt TotalUs)
{
LARGE_INTEGER InterruptTime, SystemTime;
ULONG Hand;
DWORD OldKeTickCount;
OldIrql = KfRaiseIrql(CLOCK_LEVEL);
static uint64_t LostUs;
uint64_t TotalMs = TotalUs / 1000;
LostUs += (TotalUs - TotalMs * 1000);
uint64_t RecoveredMs = LostUs / 1000;
TotalMs += RecoveredMs;
LostUs -= (RecoveredMs * 1000);
// 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 * TotalMs);
KeInterruptTime.High2Time = InterruptTime.u.HighPart;
KeInterruptTime.LowPart = InterruptTime.u.LowPart;
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;
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;
KeSetSystemTime(&HostSystemTime, &OldSystemTime);
}
else {
SystemTime.u.LowPart = KeSystemTime.LowPart;
SystemTime.u.HighPart = KeSystemTime.High1Time;
SystemTime.QuadPart += (CLOCK_TIME_INCREMENT * TotalMs);
KeSystemTime.High2Time = SystemTime.u.HighPart;
KeSystemTime.LowPart = SystemTime.u.LowPart;
KeSystemTime.High1Time = SystemTime.u.HighPart;
}
// Update the tick counter
OldKeTickCount = KeTickCount;
KeTickCount += ScalingFactor;
KeTickCount += static_cast<dword_xt>(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) {
KeInsertQueueDpc(&KiTimerExpireDpc, (PVOID)OldKeTickCount, (PVOID)EndKeTickCount);
break;
}
}
KiTimerMtx.Acquired--;
KiTimerMtx.Mtx.unlock();
}
KfLowerIrql(OldIrql);
}
xbox::void_xt NTAPI xbox::KiCheckTimerTable
@ -328,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;
@ -352,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;
@ -368,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);
@ -484,19 +519,13 @@ xbox::boolean_xt FASTCALL xbox::KiSignalTimer
Timer->Header.SignalState = TRUE;
/* Check if the timer has waiters */
KiWaitListLock();
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);
}
KiWaitTest(Timer, 0);
}
else {
KiWaitListUnlock();
}
/* Check if we have a period */
@ -532,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;
@ -544,34 +573,25 @@ 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;
Timers = 24;
ActiveTimers = 4;
/* Lock the Database and Raise IRQL */
/* Lock the Database */
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;
@ -599,19 +619,13 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration
Period = Timer->Period;
/* Check if there are any waiters */
KiWaitListLock();
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);
}
KiWaitTest(Timer, 0);
}
else {
KiWaitListUnlock();
}
/* Check if we have a period */
@ -709,7 +723,7 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration
break;
}
}
} while (Index != Limit);
}
/* Verify the timer table, on a debug kernel only */
if (g_bIsDebugKernel) {
@ -737,17 +751,17 @@ xbox::void_xt NTAPI xbox::KiTimerExpiration
);
}
KiTimerUnlock();
/* Lower IRQL if we need to */
if (OldIrql != DISPATCH_LEVEL) {
KfLowerIrql(OldIrql);
}
KiTimerUnlock();
}
else
{
/* Unlock the dispatcher */
KiUnlockDispatcherDatabase(OldIrql);
KiTimerUnlock();
KiUnlockDispatcherDatabase(OldIrql);
}
}
@ -791,19 +805,13 @@ xbox::void_xt FASTCALL xbox::KiTimerListExpire
Period = Timer->Period;
/* Check if there's any waiters */
KiWaitListLock();
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);
}
KiWaitTest(Timer, 0);
}
else {
KiWaitListUnlock();
}
/* Check if we have a period */
@ -826,6 +834,8 @@ xbox::void_xt FASTCALL xbox::KiTimerListExpire
}
}
KiTimerUnlock();
/* Check if we still have DPC entries */
if (DpcCalls)
{
@ -849,39 +859,14 @@ xbox::void_xt FASTCALL xbox::KiTimerListExpire
/* Lower IRQL */
KfLowerIrql(OldIrql);
KiTimerUnlock();
}
else
{
/* Unlock the dispatcher */
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;
}
template<xbox::MODE ApcMode>
static xbox::void_xt KiExecuteApc()
{
@ -907,12 +892,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 +952,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 +961,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 +969,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 +1076,210 @@ xbox::void_xt xbox::KiInitializeContextThread(
/* Save back the new value of the kernel stack. */
Thread->KernelStack = reinterpret_cast<PVOID>(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;
}
xbox::void_xt xbox::KiWaitTest
(
IN PVOID Object,
IN KPRIORITY Increment
)
{
PLIST_ENTRY WaitEntry, WaitList;
PKWAIT_BLOCK WaitBlock, NextBlock;
PKTHREAD WaitThread;
PKMUTANT FirstObject = (PKMUTANT)Object;
ASSERT_WAIT_LIST_LOCKED;
/* Loop the Wait Entries */
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 = WaitEntry->Flink;
}
KiWaitListUnlock();
}
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;
if (Thread->State != Waiting) {
// Don't do anything if it was already unwaited
return;
}
/* 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
// 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;
}

View File

@ -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,10 +62,11 @@ namespace xbox
void_xt KiTimerUnlock();
void_xt KiClockIsr
(
IN unsigned int ScalingFactor
);
void_xt KiWaitListLock();
void_xt KiWaitListUnlock();
void_xt KiClockIsr(ulonglong_xt TotalUs);
xbox::void_xt NTAPI KiCheckTimerTable
(
@ -132,7 +139,7 @@ namespace xbox
IN KIRQL OldIrql
);
void_xt FASTCALL KiWaitSatisfyAll
void_xt KiWaitSatisfyAll
(
IN PKWAIT_BLOCK WaitBlock
);
@ -156,7 +163,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 +172,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 +197,48 @@ namespace xbox
IN PKSTART_ROUTINE StartRoutine,
IN PVOID StartContext
);
boolean_xt KiInsertQueueApc
(
IN PRKAPC Apc,
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;

View File

@ -58,7 +58,7 @@ namespace NtDll
#include <mutex>
// Prevent setting the system time from multiple threads at the same time
std::mutex NtSystemTimeMtx;
xbox::RTL_CRITICAL_SECTION xbox::NtSystemTimeCritSec;
// ******************************************************************
// * 0x00B8 - NtAllocateVirtualMemory()
@ -1039,7 +1039,7 @@ XBSYSAPI EXPORTNUM(206) xbox::ntstatus_xt NTAPI xbox::NtQueueApcThread
PKAPC Apc = static_cast<PKAPC>(ExAllocatePoolWithTag(sizeof(KAPC), 'pasP'));
if (Apc != zeroptr) {
KeInitializeApc(Apc, &Thread->Tcb, zeroptr, zeroptr, reinterpret_cast<PKNORMAL_ROUTINE>(ApcRoutine), UserMode, ApcRoutineContext);
KeInitializeApc(Apc, &Thread->Tcb, KiFreeUserApc, zeroptr, reinterpret_cast<PKNORMAL_ROUTINE>(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<PVOID *>(&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);
}
// ******************************************************************
@ -1971,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 */
@ -1991,7 +1996,7 @@ XBSYSAPI EXPORTNUM(228) xbox::ntstatus_xt NTAPI xbox::NtSetSystemTime
else {
ret = STATUS_INVALID_PARAMETER;
}
NtSystemTimeMtx.unlock();
RtlLeaveCriticalSectionAndRegion(&NtSystemTimeCritSec);
}
RETURN(ret);
@ -2079,15 +2084,30 @@ 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<PVOID *>(&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);
ObfDereferenceObject(Thread);
if (PrevSuspendCount == X_STATUS_SUSPEND_COUNT_EXCEEDED) {
RETURN(X_STATUS_SUSPEND_COUNT_EXCEEDED);
}
if (PreviousSuspendCount) {
*PreviousSuspendCount = PrevSuspendCount;
}
RETURN(X_STATUS_SUCCESS);
}
// ******************************************************************
@ -2201,15 +2221,23 @@ 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]);
}
}
// 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<ntstatus_xt> {
PKTHREAD kThread = KeGetCurrentThread();
kThread->WaitStatus = X_STATUS_SUCCESS;
if (!AddWaitObject(kThread, Timeout)) {
RETURN(X_STATUS_TIMEOUT);
}
xbox::ntstatus_xt ret = WaitApc<true>([Count, &nativeHandles, WaitType, Alertable](xbox::PKTHREAD kThread) -> std::optional<ntstatus_xt> {
NtDll::LARGE_INTEGER ExpireTime;
ExpireTime.QuadPart = 0;
NTSTATUS Status = NtDll::NtWaitForMultipleObjects(
@ -2221,8 +2249,11 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx
if (Status == STATUS_TIMEOUT) {
return std::nullopt;
}
return std::make_optional<ntstatus_xt>(Status);
}, Timeout, Alertable, WaitMode);
// 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<ntstatus_xt>(kThread->WaitStatus);
}, Timeout, Alertable, WaitMode, kThread);
RETURN(ret);
}

View File

@ -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
@ -406,13 +408,24 @@ XBSYSAPI EXPORTNUM(255) xbox::ntstatus_xt NTAPI xbox::PsCreateSystemThreadEx
assert(dupHandle);
RegisterXboxHandle(eThread->UniqueThread, dupHandle);
eThread->Tcb.Priority = GetThreadPriority(handle);
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);
// 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);
@ -493,10 +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)) {
// TODO: Implement KiWaitTest's relative objects usage
//KiWaitTest()
assert(0);
KiWaitTest((PVOID)&eThread->Tcb, 0);
std::this_thread::yield();
}
else {
KiWaitListUnlock();
}
if (GetNativeHandle(eThread->UniqueThread)) {

View File

@ -89,6 +89,11 @@ xbox::boolean_xt RtlpCaptureStackLimits(
return TRUE;
}
xbox::void_xt xbox::RtlInitSystem()
{
xbox::RtlInitializeCriticalSection(&NtSystemTimeCritSec);
}
// ******************************************************************
// * 0x0104 - RtlAnsiStringToUnicodeString()
// ******************************************************************

View File

@ -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,64 +328,6 @@ void InitSoftwareInterrupts()
}
#endif
static xbox::void_xt NTAPI CxbxKrnlInterruptThread(xbox::PVOID param)
{
CxbxSetThreadName("CxbxKrnl Interrupts");
#if 0
InitSoftwareInterrupts();
#endif
std::mutex m;
std::unique_lock<std::mutex> 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) {
HalSystemInterrupts[i].Trigger(EmuInterruptList[i]);
}
}
g_InterruptSignal.wait(lock, []() { return g_AnyInterruptAsserted.load() && g_bEnableAllInterrupts.load(); });
g_AnyInterruptAsserted = false;
}
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();
@ -1220,7 +1161,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
@ -1362,10 +1303,11 @@ static void CxbxrKrnlInitHacks()
}
xbox::PsInitSystem();
xbox::KiInitSystem();
xbox::RtlInitSystem();
// 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
@ -1474,23 +1416,17 @@ 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();
__asm add esp, Host2XbStackSizeReserved;
// This will wait forever
std::condition_variable cv;
std::mutex m;
std::unique_lock<std::mutex> 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

View File

@ -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];

View File

@ -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);

View File

@ -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;

View File

@ -158,9 +158,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
@ -189,14 +187,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

View File

@ -476,15 +476,18 @@ void EmuNVNet_Write(xbox::addr_xt addr, uint32_t value, int size)
}
std::thread NVNetRecvThread;
static void NVNetRecvThreadProc(NvNetState_t *s)
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();
uint8_t packet[65536];
static std::unique_ptr<uint8_t[]> packet(new uint8_t[65536]);
while (true) {
int size = g_NVNet->PCAPReceive(packet, 65536);
int size = g_NVNet->PCAPReceive(packet.get(), 65536);
if (size > 0) {
EmuNVNet_DMAPacketToGuest(packet, size);
}
EmuNVNet_DMAPacketToGuest(packet.get(), size);
}
_mm_pause();
}
}
@ -527,7 +530,7 @@ void NVNetDevice::Init()
};
PCAPInit();
NVNetRecvThread = std::thread(NVNetRecvThreadProc, &NvNetState);
NVNetRecvThread = std::thread(NVNetRecvThreadProc);
}
void NVNetDevice::Reset()

View File

@ -279,11 +279,6 @@ OHCI::OHCI(USBDevice* UsbObj)
OHCI_StateReset();
}
void OHCI::OHCI_FrameBoundaryWrapper(void* pVoid)
{
static_cast<OHCI*>(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) {

View File

@ -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

View File

@ -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;

View File

@ -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<uint64_t, std::nano>(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);

View File

@ -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 <backends/imgui_impl_win32.h>
#include <backends/imgui_impl_opengl3.h>
@ -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
@ -128,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;
@ -319,8 +329,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 +1107,27 @@ void NV2ADevice::UpdateHostDisplay(NV2AState *d)
}
// TODO: Fix this properly
static void nv2a_vblank_thread(NV2AState *d)
template<bool should_update_hle>
void nv2a_vblank_interrupt(void *opaque)
{
g_AffinityPolicy->SetAffinityOther();
CxbxSetThreadName("Cxbx NV2A VBLANK");
auto nextVBlankTime = GetNextVBlankTime();
NV2AState *d = static_cast<NV2AState *>(opaque);
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();
if (!d->exiting) [[likely]] {
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);
// 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]);
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// 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();
}
}
}
@ -1189,8 +1201,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...? */
@ -1202,7 +1214,13 @@ void NV2ADevice::Init()
pvideo_init(d);
}
vblank_thread = std::thread(nv2a_vblank_thread, d);
d->vblank_last = get_now();
if (bLLE_GPU) {
d->vblank_cb = nv2a_vblank_interrupt<false>;
}
else {
d->vblank_cb = nv2a_vblank_interrupt<true>;
}
qemu_mutex_init(&d->pfifo.pfifo_lock);
qemu_cond_init(&d->pfifo.puller_cond);
@ -1227,9 +1245,8 @@ 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();
pvideo_destroy(d);
}
@ -1377,3 +1394,45 @@ 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
}
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;
}

View File

@ -108,6 +108,10 @@ public:
static int GetFrameWidth(NV2AState *d);
static int GetFrameHeight(NV2AState *d);
uint64_t vblank_next(uint64_t now);
uint64_t ptimer_next(uint64_t now);
private:
NV2AState *m_nv2a_state;
};

View File

@ -357,10 +357,15 @@ typedef struct OverlayState {
} OverlayState;
typedef struct NV2AState {
void(* vblank_cb)(void *);
uint64_t vblank_last;
// PCIDevice dev;
// 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;

View File

@ -394,6 +394,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<std::uintptr_t>(m_hwndChild));
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
break;
case WM_TIMER:
{
switch (wParam)

View File

@ -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