diff --git a/.gitmodules b/.gitmodules index b7107f241..c19ef41a6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -43,3 +43,7 @@ [submodule "import/nv2a_vsh_cpu"] path = import/nv2a_vsh_cpu url = https://github.com/abaire/nv2a_vsh_cpu.git +[submodule "import/mio"] + path = import/mio + url = https://github.com/mandreyel/mio.git + shadow = true diff --git a/CMakeLists.txt b/CMakeLists.txt index 584f04cf8..b2ef0c9df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,8 @@ add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/import/XbSymbolDatabase") add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/import/SDL2" EXCLUDE_FROM_ALL) +add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/import/mio" EXCLUDE_FROM_ALL) + # Cxbx-Reloaded projects set(CXBXR_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}) @@ -346,6 +348,7 @@ file (GLOB CXBXR_SOURCE_EMU "${CXBXR_ROOT_DIR}/src/core/hle/DSOUND/common/XbInternalDSVoice.cpp" "${CXBXR_ROOT_DIR}/src/core/hle/DSOUND/common/XbInternalStruct.cpp" "${CXBXR_ROOT_DIR}/src/core/hle/Intercept.cpp" + "${CXBXR_ROOT_DIR}/src/core/hle/JVS/JVS.cpp" "${CXBXR_ROOT_DIR}/src/core/hle/Patches.cpp" "${CXBXR_ROOT_DIR}/src/core/hle/XACTENG/XactEng.cpp" "${CXBXR_ROOT_DIR}/src/core/hle/XGRAPHIC/XGraphic.cpp" @@ -376,6 +379,8 @@ file (GLOB CXBXR_SOURCE_EMU "${CXBXR_ROOT_DIR}/src/core/kernel/support/NativeHandle.cpp" "${CXBXR_ROOT_DIR}/src/core/kernel/support/PatchRdtsc.cpp" "${CXBXR_ROOT_DIR}/src/devices/ADM1032Device.cpp" + "${CXBXR_ROOT_DIR}/src/devices/Chihiro/JvsIO.cpp" + "${CXBXR_ROOT_DIR}/src/devices/Chihiro/MediaBoard.cpp" "${CXBXR_ROOT_DIR}/src/devices/EEPROMDevice.cpp" "${CXBXR_ROOT_DIR}/src/devices/network/NVNetDevice.cpp" "${CXBXR_ROOT_DIR}/src/devices/MCPXDevice.cpp" diff --git a/import/SDL2 b/import/SDL2 index 3875ef455..b424665e0 160000 --- a/import/SDL2 +++ b/import/SDL2 @@ -1 +1 @@ -Subproject commit 3875ef45524241213be55540f9c9bebfeadfd4f1 +Subproject commit b424665e0899769b200231ba943353a5fee1b6b6 diff --git a/import/imgui b/import/imgui index da29b776e..8ced41570 160000 --- a/import/imgui +++ b/import/imgui @@ -1 +1 @@ -Subproject commit da29b776eed289db16a8527e5f16a0e1fa540251 +Subproject commit 8ced41570e4692b0d556503683ae45f92416f5f1 diff --git a/import/mio b/import/mio new file mode 160000 index 000000000..3f86a95c0 --- /dev/null +++ b/import/mio @@ -0,0 +1 @@ +Subproject commit 3f86a95c0784d73ce6815237ec33ed25f233b643 diff --git a/import/simpleini b/import/simpleini index f7862c3dd..2af65fcc5 160000 --- a/import/simpleini +++ b/import/simpleini @@ -1 +1 @@ -Subproject commit f7862c3dd7ad35becc2741f268e3402e89a37666 +Subproject commit 2af65fcc504f8242752755e836709762ef7ce062 diff --git a/projects/cxbx/CMakeLists.txt b/projects/cxbx/CMakeLists.txt index b33f1bea9..01dc847fc 100644 --- a/projects/cxbx/CMakeLists.txt +++ b/projects/cxbx/CMakeLists.txt @@ -42,6 +42,9 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") LTC_NO_PRNGS LTC_NO_MISC LTC_NO_PROTOTYPES + + # Enable Chihiro work + CHIHIRO_WORK ) # Reference: https://docs.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-alphabetically @@ -176,6 +179,7 @@ target_link_libraries(cxbx SDL2 imgui libusb + mio::mio_min_winapi ${WINS_LIB} ) diff --git a/projects/cxbxr-emu/CMakeLists.txt b/projects/cxbxr-emu/CMakeLists.txt index adb1888bc..420b9177d 100644 --- a/projects/cxbxr-emu/CMakeLists.txt +++ b/projects/cxbxr-emu/CMakeLists.txt @@ -48,6 +48,9 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # Use inline XXHash version XXH_INLINE_ALL + + # Enable Chihiro work + CHIHIRO_WORK ) add_compile_options( /EHs @@ -172,6 +175,7 @@ target_link_libraries(cxbxr-emu imgui libusb nv2a_vsh_emulator + mio::mio_min_winapi ${WINS_LIB} ) diff --git a/src/common/IPCHybrid.hpp b/src/common/IPCHybrid.hpp index 079a1baaf..a0a1ec215 100644 --- a/src/common/IPCHybrid.hpp +++ b/src/common/IPCHybrid.hpp @@ -49,8 +49,9 @@ void ipc_send_gui_update(IPC_UPDATE_GUI command, const unsigned int value); // ****************************************************************** typedef enum class _IPC_UPDATE_KERNEL { - CONFIG_LOGGING_SYNC = 0 - , CONFIG_INPUT_SYNC + CONFIG_LOGGING_SYNC = 0, + CONFIG_INPUT_SYNC, + CONFIG_CHANGE_TIME } IPC_UPDATE_KERNEL; void ipc_send_kernel_update(IPC_UPDATE_KERNEL command, const int value, const unsigned int hwnd); diff --git a/src/common/Timer.cpp b/src/common/Timer.cpp index c9515856c..422fb32a5 100644 --- a/src/common/Timer.cpp +++ b/src/common/Timer.cpp @@ -25,19 +25,45 @@ // * // ****************************************************************** -#ifdef _WIN32 +#include + #include -#endif #include #include #include +#include #include "Timer.h" #include "common\util\CxbxUtil.h" #include "core\kernel\support\EmuFS.h" -#include "core/kernel/exports/EmuKrnlPs.hpp" -#ifdef __linux__ -#include -#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(&HostQPCFrequency)); + QueryPerformanceCounter(reinterpret_cast(&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 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_guardlock(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 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(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_guardlock(TimerMtx); - TimerObject* pTimer = new TimerObject; - pTimer->Type = CLOCK_REALTIME; - pTimer->Callback = Callback; - pTimer->ExpireTime_MS.store(0); - pTimer->Exit.store(false); - pTimer->Opaque = Arg; - pTimer->Name = Name.empty() ? "Unnamed thread" : std::move(Name); - pTimer->IsXboxTimer = IsXboxTimer; - TimerList.emplace_back(pTimer); - - return pTimer; -} - -// Starts the timer -// Expire_MS must be expressed in NS -void Timer_Start(TimerObject* Timer, uint64_t Expire_MS) -{ - Timer->ExpireTime_MS.store(Expire_MS); - if (Timer->IsXboxTimer) { - xbox::HANDLE hThread; - CxbxrCreateThread(&hThread, xbox::zeroptr, ClockThread, Timer, FALSE); - } - else { - std::thread(ClockThread, Timer).detach(); - } -} - -// Retrives the frequency of the high resolution clock of the host -void Timer_Init() -{ -#ifdef _WIN32 - QueryPerformanceFrequency(reinterpret_cast(&HostQPCFrequency)); - QueryPerformanceCounter(reinterpret_cast(&HostQPCStartTime)); -#elif __linux__ - ClockFrequency = 0; -#else -#error "Unsupported OS" -#endif -} - int64_t Timer_GetScaledPerformanceCounter(int64_t Period) { LARGE_INTEGER currentQPC; diff --git a/src/common/Timer.h b/src/common/Timer.h index 007c75536..5096c3ff3 100644 --- a/src/common/Timer.h +++ b/src/common/Timer.h @@ -40,33 +40,12 @@ #define SCALE_MS_IN_US 1000 #define SCALE_US_IN_US 1 -/* typedef of the timer object and the callback function */ -typedef void(*TimerCB)(void*); -typedef struct _TimerObject -{ - int Type; // timer type - std::atomic_uint64_t ExpireTime_MS; // when the timer expires (ms) - std::atomic_bool Exit; // indicates that the timer should be destroyed - TimerCB Callback; // function to call when the timer expires - void* Opaque; // opaque argument to pass to the callback - std::string Name; // the name of the timer thread (if any) - bool IsXboxTimer; // indicates that the timer should run on the Xbox CPU -} -TimerObject; extern int64_t HostQPCFrequency; -/* Timer exported functions */ -TimerObject* Timer_Create(TimerCB Callback, void* Arg, std::string Name, bool IsXboxTimer); -void Timer_Start(TimerObject* Timer, uint64_t Expire_MS); -void Timer_Exit(TimerObject* Timer); -void Timer_ChangeExpireTime(TimerObject* Timer, uint64_t Expire_ms); -uint64_t GetTime_NS(TimerObject* Timer); -void Timer_Init(); -void Timer_Shutdown(); - +void timer_init(); +uint64_t get_now(); int64_t Timer_GetScaledPerformanceCounter(int64_t Period); - void SleepPrecise(std::chrono::steady_clock::time_point targetTime); #endif diff --git a/src/common/cxbxr.cpp b/src/common/cxbxr.cpp index 50e651069..37042e4cc 100644 --- a/src/common/cxbxr.cpp +++ b/src/common/cxbxr.cpp @@ -121,9 +121,6 @@ bool HandleFirstLaunch() } // NOTE: Require to be after g_renderbase's shutdown process. - // Next thing we need to do is shutdown our timer threads. - Timer_Shutdown(); - // NOTE: Must be last step of shutdown process and before CxbxUnlockFilePath call! // Shutdown the memory manager g_VMManager.Shutdown(); diff --git a/src/common/win32/IPCWindows.cpp b/src/common/win32/IPCWindows.cpp index 82a342d89..cbf04f2c7 100644 --- a/src/common/win32/IPCWindows.cpp +++ b/src/common/win32/IPCWindows.cpp @@ -101,6 +101,10 @@ void ipc_send_kernel_update(IPC_UPDATE_KERNEL command, const int value, const un cmdParam = ID_SYNC_CONFIG_INPUT; break; + case IPC_UPDATE_KERNEL::CONFIG_CHANGE_TIME: + cmdParam = ID_SYNC_TIME_CHANGE; + break; + default: cmdParam = 0; break; diff --git a/src/common/xbe/Xbe.cpp b/src/common/xbe/Xbe.cpp index 36b93ab5c..3f9c07fe3 100644 --- a/src/common/xbe/Xbe.cpp +++ b/src/common/xbe/Xbe.cpp @@ -72,7 +72,7 @@ Xbe::Xbe(const char *x_szFilename) // This is necessary because CxbxInitWindow internally calls g_AffinityPolicy->SetAffinityOther. If we are launched directly from the command line and the dashboard // cannot be opened, we will crash below because g_AffinityPolicy will be empty g_AffinityPolicy = AffinityPolicy::InitPolicy(); - CxbxInitWindow(false); + CxbxInitWindow(); ULONG FatalErrorCode = FATAL_ERROR_XBE_DASH_GENERIC; diff --git a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp index ac58da250..1ddb23013 100644 --- a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp +++ b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp @@ -38,6 +38,7 @@ #include "core\kernel\support\Emu.h" #include "core\kernel\support\EmuFS.h" #include "core\kernel\support\NativeHandle.h" +#include "core\kernel\exports\EmuKrnlKe.h" #include "EmuShared.h" #include "..\FixedFunctionState.h" #include "core\hle\D3D8\ResourceTracker.h" @@ -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(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 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) @@ -3163,6 +3141,12 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(Direct3D_CreateDevice) Direct3D_CreateDevice_Start(pPresentationParameters); + // HACK: Disable Software VertexProcessing (Fixes CreateDevice failure in Chihiro titles) + if (BehaviorFlags & D3DCREATE_SOFTWARE_VERTEXPROCESSING) { + BehaviorFlags &= ~D3DCREATE_SOFTWARE_VERTEXPROCESSING; + BehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING; + } + // Only then call Xbox CreateDevice function hresult_xt hRet = XB_TRMP(Direct3D_CreateDevice)(Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface); @@ -6541,31 +6525,6 @@ xbox::bool_xt WINAPI xbox::EMUPATCH(D3DDevice_GetOverlayUpdateStatus)() return TRUE; } -// ****************************************************************** -// * patch: D3DDevice_BlockUntilVerticalBlank -// ****************************************************************** -xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_BlockUntilVerticalBlank)() -{ - LOG_FUNC(); - - std::unique_lock lk(g_VBConditionMutex); - g_VBConditionVariable.wait(lk); -} - -// ****************************************************************** -// * patch: D3DDevice_SetVerticalBlankCallback -// ****************************************************************** -xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetVerticalBlankCallback) -( - X_D3DVBLANKCALLBACK pCallback -) -{ - LOG_FUNC_ONE_ARG(pCallback); - - g_pXbox_VerticalBlankCallback = pCallback; -} - - // ****************************************************************** // * patch: D3DDevice_SetRenderState_Simple // ****************************************************************** diff --git a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp.unused-patches b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp.unused-patches index 457813b46..0783f0ae2 100644 --- a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp.unused-patches +++ b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp.unused-patches @@ -4256,3 +4256,27 @@ HRESULT WINAPI XTL::EMUPATCH(D3DDevice_GetVertexShaderInput) return 0; } + +// ****************************************************************** +// * patch: D3DDevice_BlockUntilVerticalBlank +// ****************************************************************** +xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_BlockUntilVerticalBlank)() +{ + LOG_FUNC(); + + std::unique_lock lk(g_VBConditionMutex); + g_VBConditionVariable.wait(lk); +} + +// ****************************************************************** +// * patch: D3DDevice_SetVerticalBlankCallback +// ****************************************************************** +xbox::void_xt WINAPI xbox::EMUPATCH(D3DDevice_SetVerticalBlankCallback) +( + X_D3DVBLANKCALLBACK pCallback +) +{ + LOG_FUNC_ONE_ARG(pCallback); + + g_pXbox_VerticalBlankCallback = pCallback; +} diff --git a/src/core/hle/D3D8/Direct3D9/Direct3D9.h b/src/core/hle/D3D8/Direct3D9/Direct3D9.h index aa13b1dd4..e508ff39a 100644 --- a/src/core/hle/D3D8/Direct3D9/Direct3D9.h +++ b/src/core/hle/D3D8/Direct3D9/Direct3D9.h @@ -40,7 +40,7 @@ void LookupTrampolinesD3D(); // initialize render window -extern void CxbxInitWindow(bool bFullInit); +extern void CxbxInitWindow(); void CxbxUpdateNativeD3DResources(); diff --git a/src/core/hle/DSOUND/DirectSound/DirectSound.cpp b/src/core/hle/DSOUND/DirectSound/DirectSound.cpp index f87daaad1..4cd5b087a 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSound.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSound.cpp @@ -53,6 +53,7 @@ // TODO: Move these to LLE APUDevice once we have one! static constexpr uint32_t APU_TIMER_FREQUENCY = 48000; +static uint64_t dsound_last; uint32_t GetAPUTime() { @@ -85,10 +86,6 @@ uint32_t GetAPUTime() there is chance of failure which contain value greater than 0. */ -// Managed memory xbox audio variables -static std::thread dsound_thread; -static void dsound_thread_worker(LPVOID); - #include "DirectSoundInline.hpp" #ifdef __cplusplus @@ -98,6 +95,7 @@ extern "C" { void CxbxInitAudio() { g_EmuShared->GetAudioSettings(&g_XBAudio); + dsound_last = get_now(); } #ifdef __cplusplus @@ -125,10 +123,6 @@ xbox::hresult_xt WINAPI xbox::EMUPATCH(DirectSoundCreate) HRESULT hRet = DS_OK; - if (!initialized) { - dsound_thread = std::thread(dsound_thread_worker, nullptr); - } - // Set this flag when this function is called g_bDSoundCreateCalled = TRUE; @@ -454,51 +448,51 @@ void StreamBufferAudio(xbox::XbHybridDSBuffer* pHybridBuffer, float msToCopy) { } } -static void dsound_thread_worker(LPVOID nullPtr) +void dsound_async_worker() { - g_AffinityPolicy->SetAffinityOther(); + DSoundMutexGuardLock; - const int dsStreamInterval = 300; - int waitCounter = 0; + xbox::LARGE_INTEGER getTime; + xbox::KeQuerySystemTime(&getTime); + DirectSoundDoWork_Stream(getTime); +} - while (true) { - // FIXME time this loop more accurately - // and account for variation in the length of Sleep calls +void dsound_worker() +{ + // Testcase: Gauntlet Dark Legacy, if Sleep(1) then intro videos start to starved often + // unless console is open with logging enabled. This is the cause of stopping intro videos often. - // Testcase: Gauntlet Dark Legacy, if Sleep(1) then intro videos start to starved often - // unless console is open with logging enabled. This is the cause of stopping intro videos often. - Sleep(g_dsBufferStreaming.streamInterval); - waitCounter += g_dsBufferStreaming.streamInterval; + // Enforce mutex guard lock only occur inside below bracket for proper compile build. + DSoundMutexGuardLock; - // Enforce mutex guard lock only occur inside below bracket for proper compile build. - { - DSoundMutexGuardLock; - - if (waitCounter > dsStreamInterval) { - waitCounter = 0; - - // For Async process purpose only - xbox::LARGE_INTEGER getTime; - xbox::KeQuerySystemTime(&getTime); - DirectSoundDoWork_Stream(getTime); + // Stream sound buffer audio + // because the title may change the content of sound buffers at any time + for (auto& pBuffer : g_pDSoundBufferCache) { + // Avoid expensive calls to DirectSound on buffers unless they've been played at least once + // Since some titles create a large amount of buffers, but only use a few + if (pBuffer->emuDSBuffer->EmuStreamingInfo.playRequested) { + DWORD status; + HRESULT hRet = pBuffer->emuDSBuffer->EmuDirectSoundBuffer8->GetStatus(&status); + if (hRet == 0 && status & DSBSTATUS_PLAYING) { + auto streamMs = g_dsBufferStreaming.streamInterval + g_dsBufferStreaming.streamAhead; + StreamBufferAudio(pBuffer, streamMs); } + } + } +} - // Stream sound buffer audio - // because the title may change the content of sound buffers at any time - for (auto& pBuffer : g_pDSoundBufferCache) { - // Avoid expensive calls to DirectSound on buffers unless they've been played at least once - // Since some titles create a large amount of buffers, but only use a few - if (pBuffer->emuDSBuffer->EmuStreamingInfo.playRequested) { - DWORD status; - HRESULT hRet = pBuffer->emuDSBuffer->EmuDirectSoundBuffer8->GetStatus(&status); - if (hRet == 0 && status & DSBSTATUS_PLAYING) { - auto streamMs = g_dsBufferStreaming.streamInterval + g_dsBufferStreaming.streamAhead; - StreamBufferAudio(pBuffer, streamMs); - } - } - } - } +uint64_t dsound_next(uint64_t now) +{ + constexpr uint64_t dsound_period = 300 * 1000; + uint64_t next = dsound_last + dsound_period; + + if (now >= next) { + dsound_async_worker(); + dsound_last = get_now(); + return dsound_period; } + + return dsound_last + dsound_period - now; // time remaining until next dsound async event } // Kismet given name for RadWolfie's experiment major issue in the mutt. diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp index 23e650ed5..289a92da3 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp @@ -81,3 +81,6 @@ extern DsBufferStreaming g_dsBufferStreaming; extern void DirectSoundDoWork_Buffer(xbox::LARGE_INTEGER& time); extern void DirectSoundDoWork_Stream(xbox::LARGE_INTEGER& time); +extern void dsound_async_worker(); +extern void dsound_worker(); +extern uint64_t dsound_next(uint64_t now); diff --git a/src/core/hle/JVS/JVS.cpp b/src/core/hle/JVS/JVS.cpp new file mode 100644 index 000000000..0b1763da7 --- /dev/null +++ b/src/core/hle/JVS/JVS.cpp @@ -0,0 +1,670 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +// ****************************************************************** +// * This file is part of the Cxbx project. +// * +// * Cxbx and Cxbe are free software; you can redistribute them +// * and/or modify them under the terms of the GNU General Public +// * License as published by the Free Software Foundation; either +// * version 2 of the license, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU General Public License for more details. +// * +// * You should have recieved a copy of the GNU General Public License +// * along with this program; see the file COPYING. +// * If not, write to the Free Software Foundation, Inc., +// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA. +// * +// * (c) 2019 Luke Usher +// * +// * All rights reserved +// * +// ****************************************************************** +#define _XBOXKRNL_DEFEXTRN_ + +#define LOG_PREFIX CXBXR_MODULE::JVS + +#undef FIELD_OFFSET // prevent macro redefinition warnings + +#include "EmuShared.h" +#include "common\Logging.h" +#include "common\FilePaths.hpp" +#include "core\kernel\init\CxbxKrnl.h" +#include "core\kernel\support\Emu.h" +#include "core\hle\JVS\JVS.h" +#include "core\hle\Intercept.hpp" +#include "devices\chihiro\JvsIo.h" +#include "devices\Xbox.h" +#include + +#pragma warning(disable:4244) // Silence mio compiler warnings +#include +#pragma warning(default:4244) + +// Global variables used to store JVS related firmware/eeproms +mio::mmap_sink g_BaseBoardQcFirmware; // QC Microcontroller firmware +mio::mmap_sink g_BaseBoardScFirmware; // SC Microcontroller firmware +mio::mmap_sink g_BaseBoardEeprom; // Config EEPROM +mio::mmap_sink g_BaseBoardBackupMemory; // Backup Memory (high-scores, etc) + +typedef struct _baseboard_state_t { + // Switch 1: Horizontal Display, On = Vertical Display + // Switch 2-3: D3D Resolution Configuraton + // Switch 4: 0 = Hardware Vertex Processing, 1 = Software Vertex processing (Causes D3D to fail).. Horizontal frequency? + // Switch 5: Unknown + // Switch 6-8: Connected AV Pack flag + bool DipSwitch[8]; + bool TestButton; + bool ServiceButton; + uint8_t JvsSense; + + void Reset() + { + // TODO: Make this configurable + DipSwitch[0] = false; + DipSwitch[1] = false; + DipSwitch[2] = true; + DipSwitch[3] = true; + DipSwitch[4] = false; + DipSwitch[5] = true; + DipSwitch[6] = true; + DipSwitch[7] = true; + TestButton = false; + ServiceButton = false; + JvsSense = 0; + } + + uint8_t GetAvPack() + { + uint8_t avpack = 0; + + // Dip Switches 6,7,8 combine to form the Av Pack ID + // TODO: Verify the order, these might need to be reversed + avpack &= ~((DipSwitch[5] ? 1 : 0) << 2); + avpack &= ~((DipSwitch[6] ? 1 : 0) << 1); + avpack &= ~ (DipSwitch[7] ? 1 : 0); + + return avpack; + } + + uint8_t GetPINSA() + { + uint8_t PINSA = 0b11101111; // 1 = Off, 0 = On + + // Dip Switches 1-3 are set on PINSA bits 0-2 + PINSA &= ~ (DipSwitch[0] ? 1 : 0); + PINSA &= ~((DipSwitch[1] ? 1 : 0) << 1); + PINSA &= ~((DipSwitch[2] ? 1 : 0) << 2); + + // Bit 3 is currently unknown, so we don't modify that bit + + // Dip Switches 4,5 are set on bits 4,5 + PINSA &= ~((DipSwitch[3] ? 1 : 0) << 4); + PINSA &= ~((DipSwitch[4] ? 1 : 0) << 5); + + // Bit 6 = Test, Bit 7 = Service + PINSA &= ~((TestButton ? 1 : 0) << 6); + PINSA &= ~((ServiceButton ? 1 : 0) << 7); + + return PINSA; + } + + uint8_t GetPINSB() + { + // PINSB bits 0-1 represent the JVS Sense line + return JvsSense; + } + +} baseboard_state_t; + +baseboard_state_t ChihiroBaseBoardState = {}; +DWORD* g_pPINSA = nullptr; // Qc PINSA Register: Contains Filter Board DIP Switches + Test/Service buttons +DWORD* g_pPINSB = nullptr; // Qc PINSB Register: Contains JVS Sense Pin state + +bool JVS_LoadFile(std::string path, mio::mmap_sink& data) +{ + FILE* fp = fopen(path.c_str(), "rb"); + + if (fp == nullptr) { + return false; + } + + std::error_code error; + data = mio::make_mmap_sink(path, error); + + if (error) { + return false; + } + + return true; +} + +void JvsInputThread() +{ + g_AffinityPolicy->SetAffinityOther(GetCurrentThread()); + + while (true) { + // This thread is responsible for reading the emulated Baseboard state + // and setting the correct internal variables + ChihiroBaseBoardState.TestButton = GetAsyncKeyState(VK_F1); + ChihiroBaseBoardState.ServiceButton = GetAsyncKeyState(VK_F2); + + // Call into the Jvs I/O board update function + g_pJvsIo->Update(); + + if (g_pPINSA != nullptr) { + *g_pPINSA = ChihiroBaseBoardState.GetPINSA(); + } + + if (g_pPINSB != nullptr) { + *g_pPINSB = ChihiroBaseBoardState.GetPINSB(); + } + } +} + + +void JVS_Init() +{ + // Init Jvs IO board + g_pJvsIo = new JvsIo(&ChihiroBaseBoardState.JvsSense); + + std::string romPath = g_DataFilePath + std::string("\\EmuDisk\\Chihiro"); + std::string baseBoardQcFirmwarePath = "ic10_g24lc64.bin"; + std::string baseBoardScFirmwarePath = "pc20_g24lc64.bin"; + std::string baseBoardEepromPath = "ic11_24lc024.bin"; + std::string baseBoardBackupRamPath = "backup_ram.bin"; + + if (!JVS_LoadFile((romPath + "\\" + baseBoardQcFirmwarePath).c_str(), g_BaseBoardQcFirmware)) { + ("Failed to load base board firmware: %s", baseBoardQcFirmwarePath.c_str()); + } + + if (!JVS_LoadFile((romPath + "\\" + baseBoardScFirmwarePath).c_str(), g_BaseBoardScFirmware)) { + CxbxrAbort("Failed to load base board qc firmware: %s", baseBoardScFirmwarePath.c_str()); + } + + if (!JVS_LoadFile((romPath + "\\" + baseBoardEepromPath).c_str(), g_BaseBoardEeprom)) { + CxbxrAbort("Failed to load base board EEPROM: %s", baseBoardEepromPath.c_str()); + } + + // backup ram is a special case, we can create it automatically if it doesn't exist + if (!std::filesystem::exists(romPath + "\\" + baseBoardBackupRamPath)) { + FILE *fp = fopen((romPath + "\\" + baseBoardBackupRamPath).c_str(), "w"); + if (fp == nullptr) { + CxbxrAbort("Could not create Backup File: %s", baseBoardBackupRamPath.c_str()); + } + + // Create 128kb empty file for backup ram + fseek(fp, (128 * 1024) - 1, SEEK_SET); + fputc('\0', fp); + fclose(fp); + } + + if (!JVS_LoadFile((romPath + "\\" + baseBoardBackupRamPath).c_str(), g_BaseBoardBackupMemory)) { + CxbxrAbort("Failed to load base board BACKUP RAM: %s", baseBoardBackupRamPath.c_str()); + } + + // Determine which version of JVS_SendCommand this title is using and derive the offset + // TODO: Extract this into a function and also locate PINSB + static int JvsSendCommandVersion = -1; + g_pPINSA = nullptr; + g_pPINSB = nullptr; + + auto JvsSendCommandOffset1 = (uintptr_t)GetXboxSymbolPointer("JVS_SendCommand"); + auto JvsSendCommandOffset2 = (uintptr_t)GetXboxSymbolPointer("JVS_SendCommand2"); + auto JvsSendCommandOffset3 = (uintptr_t)GetXboxSymbolPointer("JVS_SendCommand3"); + + if (JvsSendCommandOffset1) { + JvsSendCommandVersion = 1; + g_pPINSA = *(DWORD**)(JvsSendCommandOffset1 + 0x2A0); + g_pPINSB = (DWORD*)((DWORD)g_pPINSA - 8); + } + + if (JvsSendCommandOffset2) { + JvsSendCommandVersion = 2; + g_pPINSA = *(DWORD**)(JvsSendCommandOffset2 + 0x312); + g_pPINSB = (DWORD*)((DWORD)g_pPINSA - 8); + } + + if (JvsSendCommandOffset3) { + JvsSendCommandVersion = 3; + g_pPINSA = *(DWORD**)(JvsSendCommandOffset3 + 0x307); + g_pPINSB = (DWORD*)((DWORD)g_pPINSA - 8); + + if ((DWORD)g_pPINSA > XBE_MAX_VA) { + // This was invalid, we must have the other varient of SendCommand3 (SEGABOOT) + g_pPINSA = *(DWORD**)(JvsSendCommandOffset3 + 0x302); + g_pPINSB = (DWORD*)((DWORD)g_pPINSA - 8); + } + } + + // Set state to a sane initial default + ChihiroBaseBoardState.Reset(); + + // Auto-Patch Chihiro Region Flag to match the desired game + uint8_t ®ion = (uint8_t &)g_BaseBoardQcFirmware[0x1F00]; + auto regionFlags = g_MediaBoard->GetBootId().regionFlags; + + // The region of the system can be converted to a game region flag by doing 1 << region + // This gives a bitmask that can be ANDed with the BootID region flags to check the games support + if ((regionFlags & (1 << region)) == 0) { + // The region was not compatible, so we need to patch the region flag + // This avoids "Error 05: This game is not acceptable by main board." + // We use USA,EXPORT,JAPAN to make sure mutiple-language games default to English first + if (regionFlags & MB_CHIHIRO_REGION_FLAG_USA) { + region = 2; + } + else if (regionFlags & MB_CHIHIRO_REGION_FLAG_EXPORT) { + region = 3; + } + else if (regionFlags & MB_CHIHIRO_REGION_FLAG_JAPAN) { + region = 1; + } + } + + // Spawn the Chihiro/JVS Input Thread + std::thread(JvsInputThread).detach(); +} + +DWORD WINAPI xbox::EMUPATCH(JVS_SendCommand) +( + DWORD a1, + DWORD Command, + DWORD a3, + DWORD Length, + DWORD a5, + DWORD a6, + DWORD a7, + DWORD a8 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(a1) + LOG_FUNC_ARG(Command) + LOG_FUNC_ARG(a3) + LOG_FUNC_ARG(Length) + LOG_FUNC_ARG(a5) + LOG_FUNC_ARG(a6) + LOG_FUNC_ARG(a7) + LOG_FUNC_ARG(a8) + LOG_FUNC_END; + + LOG_UNIMPLEMENTED(); + + RETURN(0); +} + +DWORD WINAPI xbox::EMUPATCH(JvsBACKUP_Read) +( + DWORD Offset, + DWORD Length, + PUCHAR Buffer, + DWORD a4 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(Offset) + LOG_FUNC_ARG(Length) + LOG_FUNC_ARG(Buffer) + LOG_FUNC_ARG(a4) + LOG_FUNC_END + + memcpy((void*)Buffer, &g_BaseBoardBackupMemory[Offset], Length); + + RETURN(0); +} + +DWORD WINAPI xbox::EMUPATCH(JvsBACKUP_Write) +( + DWORD Offset, + DWORD Length, + PUCHAR Buffer, + DWORD a4 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(Offset) + LOG_FUNC_ARG(Length) + LOG_FUNC_ARG(Buffer) + LOG_FUNC_ARG(a4) + LOG_FUNC_END + + memcpy(&g_BaseBoardBackupMemory[Offset], (void*)Buffer, Length); + + RETURN(0); +} + +DWORD WINAPI xbox::EMUPATCH(JvsEEPROM_Read) +( + DWORD Offset, + DWORD Length, + PUCHAR Buffer, + DWORD a4 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(Offset) + LOG_FUNC_ARG(Length) + LOG_FUNC_ARG_OUT(Buffer) + LOG_FUNC_ARG(a4) + LOG_FUNC_END + + memcpy((void*)Buffer, &g_BaseBoardEeprom[Offset], Length); + + RETURN(0); +} + +DWORD WINAPI xbox::EMUPATCH(JvsEEPROM_Write) +( + DWORD Offset, + DWORD Length, + PUCHAR Buffer, + DWORD a4 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(Offset) + LOG_FUNC_ARG(Length) + LOG_FUNC_ARG_OUT(Buffer) + LOG_FUNC_ARG(a4) + LOG_FUNC_END + + memcpy(&g_BaseBoardEeprom[Offset], (void*)Buffer, Length); + + std::error_code error; + g_BaseBoardEeprom.sync(error); + + if (error) { + EmuLog(LOG_LEVEL::WARNING, "Couldn't sync EEPROM to disk"); + } + + RETURN(0); +} + +DWORD WINAPI xbox::EMUPATCH(JvsFirmwareDownload) +( + DWORD Offset, + DWORD Length, + PUCHAR Buffer, + DWORD a4 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(Offset) + LOG_FUNC_ARG(Length) + LOG_FUNC_ARG_OUT(Buffer) + LOG_FUNC_ARG(a4) + LOG_FUNC_END + + memcpy((void*)Buffer, &g_BaseBoardQcFirmware[Offset], Length); + + RETURN(0); +} + + +DWORD WINAPI xbox::EMUPATCH(JvsFirmwareUpload) +( + DWORD Offset, + DWORD Length, + PUCHAR Buffer, + DWORD a4 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(Offset) + LOG_FUNC_ARG(Length) + LOG_FUNC_ARG(Buffer) + LOG_FUNC_ARG(a4) + LOG_FUNC_END + + memcpy(&g_BaseBoardQcFirmware[Offset], (void*)Buffer, Length); + + RETURN(0); +} + +DWORD WINAPI xbox::EMUPATCH(JvsNodeReceivePacket) +( + PUCHAR Buffer, + PDWORD Length, + DWORD a3 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG_OUT(Buffer) + LOG_FUNC_ARG_OUT(Length) + LOG_FUNC_ARG(a3) + LOG_FUNC_END + + // Receive the packet from the connected IO board + uint8_t DeviceId = g_pJvsIo->GetDeviceId(); + + // TODO : "Number of packets received" below might imply multiple packets might need receiving here... + uint16_t payloadSize = (uint16_t)g_pJvsIo->ReceivePacket(&Buffer[6]); + if (payloadSize > 0) { + Buffer[0] = 0; // Empty header byte, ignored + Buffer[1] = 1; // Number of packets received + Buffer[2] = DeviceId; + Buffer[3] = 0; // Unused + + *Length = payloadSize + 6; + + // Write the payload size header field + *((uint16_t*)&Buffer[4]) = payloadSize; // Packet Length (bytes 4-5) + // TODO : Prevent little/big endian issues here by explicitly setting Buffer[4] and Buffer[5] + } + + RETURN(0); +} + +DWORD WINAPI xbox::EMUPATCH(JvsNodeSendPacket) +( + PUCHAR Buffer, + DWORD Length, + DWORD a3 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(Buffer) + LOG_FUNC_ARG(Length) + LOG_FUNC_ARG(a3) + LOG_FUNC_END + + // Buffer contains two opening bytes, '00' and 'XX', where XX is the number of JVS packets to send + // Each JVS packet is prepended with a '00' byte, the rest of the packet is as-per the JVS I/O standard. + + // Ignore Buffer[0] (should be 0x00) + unsigned packetCount = Buffer[1]; + uint8_t* packetPtr = &Buffer[2]; // First JVS packet starts at offset 2; + + for (unsigned i = 0; i < packetCount; i++) { + // Skip the separator byte (should be 0x00) + packetPtr++; + + // Send the packet to the connected I/O board + size_t bytes = g_pJvsIo->SendPacket(packetPtr); + + // Set packetPtr to the next packet + packetPtr += bytes; + } + + RETURN(0); +} + +// Binary Coded Decimal to Decimal conversion +uint8_t BcdToUint8(uint8_t value) +{ + return value - 6 * (value >> 4); +} + +uint8_t Uint8ToBcd(uint8_t value) +{ + return value + 6 * (value / 10); +} + +DWORD WINAPI xbox::EMUPATCH(JvsRTC_Read) +( + DWORD a1, + DWORD a2, + JvsRTCTime* pTime, + DWORD a4 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(a1) + LOG_FUNC_ARG(a2) + LOG_FUNC_ARG_OUT(time) + LOG_FUNC_ARG(a4) + LOG_FUNC_END + + time_t hostTime; + struct tm* hostTimeInfo; + time(&hostTime); + hostTimeInfo = localtime(&hostTime); + + memset(pTime, 0, sizeof(JvsRTCTime)); + + pTime->day = Uint8ToBcd(hostTimeInfo->tm_mday); + pTime->month = Uint8ToBcd(hostTimeInfo->tm_mon + 1); // Chihiro month counter stats at 1 + pTime->year = Uint8ToBcd(hostTimeInfo->tm_year - 100); // Chihiro starts counting from year 2000 + + pTime->hour = Uint8ToBcd(hostTimeInfo->tm_hour); + pTime->minute = Uint8ToBcd(hostTimeInfo->tm_min); + pTime->second = Uint8ToBcd(hostTimeInfo->tm_sec); + + RETURN(0); +} + +DWORD WINAPI xbox::EMUPATCH(JvsRTC_Write) +( + DWORD a1, + DWORD a2, + JvsRTCTime* pTime, + DWORD a4 + ) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(a1) + LOG_FUNC_ARG(a2) + LOG_FUNC_ARG_OUT(time) + LOG_FUNC_ARG(a4) + LOG_FUNC_END + + LOG_UNIMPLEMENTED(); + + RETURN(0); +} + +DWORD WINAPI xbox::EMUPATCH(JvsScFirmwareDownload) +( + DWORD Offset, + DWORD Length, + PUCHAR Buffer, + DWORD a4 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(Offset) + LOG_FUNC_ARG(Length) + LOG_FUNC_ARG_OUT(Buffer) + LOG_FUNC_ARG(a4) + LOG_FUNC_END + + memcpy((void*)Buffer, &g_BaseBoardScFirmware[Offset], Length); + + RETURN(0); +} + +DWORD WINAPI xbox::EMUPATCH(JvsScFirmwareUpload) +( + DWORD Offset, + DWORD Length, + PUCHAR Buffer, + DWORD a4 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(Offset) + LOG_FUNC_ARG(Length) + LOG_FUNC_ARG(Buffer) + LOG_FUNC_ARG(a4) + LOG_FUNC_END + + memcpy(&g_BaseBoardScFirmware[Offset], (void*)Buffer, Length); + + RETURN(0); +} + +DWORD WINAPI xbox::EMUPATCH(JvsScReceiveMidi) +( + DWORD a1, + DWORD a2, + DWORD a3 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(a1) + LOG_FUNC_ARG(a2) + LOG_FUNC_ARG(a3) + LOG_FUNC_END + + LOG_UNIMPLEMENTED(); + + RETURN(0); +} + +DWORD WINAPI xbox::EMUPATCH(JvsScSendMidi) +( + DWORD a1, + DWORD a2, + DWORD a3 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(a1) + LOG_FUNC_ARG(a2) + LOG_FUNC_ARG(a3) + LOG_FUNC_END + + LOG_UNIMPLEMENTED(); + + RETURN(0); +} + +DWORD WINAPI xbox::EMUPATCH(JvsScReceiveRs323c) +( + PUCHAR Buffer, + DWORD Length, + DWORD a3 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(Buffer) + LOG_FUNC_ARG(Length) + LOG_FUNC_ARG(a3) + LOG_FUNC_END + + LOG_UNIMPLEMENTED(); + + RETURN(0); +} + + +DWORD WINAPI xbox::EMUPATCH(JvsScSendRs323c) +( + PUCHAR Buffer, + DWORD Length, + DWORD a3 +) +{ + LOG_FUNC_BEGIN + LOG_FUNC_ARG(Buffer) + LOG_FUNC_ARG(Length) + LOG_FUNC_ARG(a3) + LOG_FUNC_END + + LOG_UNIMPLEMENTED(); + + RETURN(0); +} diff --git a/src/core/hle/JVS/JVS.h b/src/core/hle/JVS/JVS.h new file mode 100644 index 000000000..d3945abe3 --- /dev/null +++ b/src/core/hle/JVS/JVS.h @@ -0,0 +1,182 @@ +// ****************************************************************** +// * This file is part of the Cxbx project. +// * +// * Cxbx and Cxbe are free software; you can redistribute them +// * and/or modify them under the terms of the GNU General Public +// * License as published by the Free Software Foundation; either +// * version 2 of the license, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU General Public License for more details. +// * +// * You should have recieved a copy of the GNU General Public License +// * along with this program; see the file COPYING. +// * If not, write to the Free Software Foundation, Inc., +// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA. +// * +// * (c) 2019 Luke Usher +// * +// * All rights reserved +// * +// ****************************************************************** +#ifndef JVS_H +#define JVS_H + +// Used by CxbxKrnl to setup JVS roms +void JVS_Init(); + +#include "core\hle\XAPI\Xapi.h" // For EMUPATCH + +namespace xbox { + +DWORD WINAPI EMUPATCH(JVS_SendCommand) + ( + DWORD a1, + DWORD Command, + DWORD a3, + DWORD Length, + DWORD a5, + DWORD a6, + DWORD a7, + DWORD a8 + ); + +DWORD WINAPI EMUPATCH(JvsBACKUP_Read) + ( + DWORD Offset, + DWORD Length, + PUCHAR Buffer, + DWORD a4 + ); + +DWORD WINAPI EMUPATCH(JvsBACKUP_Write) + ( + DWORD Offset, + DWORD Length, + PUCHAR Buffer, + DWORD a4 + ); + +DWORD WINAPI EMUPATCH(JvsEEPROM_Read) + ( + DWORD Offset, + DWORD Length, + PUCHAR Buffer, + DWORD a4 + ); + +DWORD WINAPI EMUPATCH(JvsEEPROM_Write) + ( + DWORD Offset, + DWORD Length, + PUCHAR Buffer, + DWORD a4 + ); + +DWORD WINAPI EMUPATCH(JvsFirmwareDownload) + ( + DWORD Offset, + DWORD Length, + PUCHAR Buffer, + DWORD a4 + ); + +DWORD WINAPI EMUPATCH(JvsFirmwareUpload) + ( + DWORD Offset, + DWORD Length, + PUCHAR Buffer, + DWORD a4 + ); + +DWORD WINAPI EMUPATCH(JvsNodeReceivePacket) + ( + PUCHAR Buffer, + PDWORD Length, + DWORD a3 + ); + +DWORD WINAPI EMUPATCH(JvsNodeSendPacket) + ( + PUCHAR Buffer, + DWORD Length, + DWORD a3 + ); + +typedef struct { + UCHAR second; + UCHAR minute; + UCHAR hour; + UCHAR unused_2; + + UCHAR day; + UCHAR month; + UCHAR year; + UCHAR unused_1; +} JvsRTCTime; + +DWORD WINAPI EMUPATCH(JvsRTC_Read) + ( + DWORD a1, + DWORD a2, + JvsRTCTime *time, + DWORD a4 + ); + +DWORD WINAPI EMUPATCH(JvsRTC_Write) + ( + DWORD a1, + DWORD a2, + JvsRTCTime *time, + DWORD a4 + ); + +DWORD WINAPI EMUPATCH(JvsScFirmwareDownload) + ( + DWORD Offset, + DWORD Length, + PUCHAR Buffer, + DWORD a4 + ); + +DWORD WINAPI EMUPATCH(JvsScFirmwareUpload) + ( + DWORD Offset, + DWORD Length, + PUCHAR Buffer, + DWORD a4 + ); + +DWORD WINAPI EMUPATCH(JvsScReceiveMidi) + ( + DWORD a1, + DWORD a2, + DWORD a3 + ); + +DWORD WINAPI EMUPATCH(JvsScSendMidi) + ( + DWORD a1, + DWORD a2, + DWORD a3 + ); + +DWORD WINAPI EMUPATCH(JvsScReceiveRs323c) + ( + PUCHAR Buffer, + DWORD Length, + DWORD a3 + ); + + +DWORD WINAPI EMUPATCH(JvsScSendRs323c) + ( + PUCHAR Buffer, + DWORD Length, + DWORD a3 + ); +} + +#endif diff --git a/src/core/hle/Patches.cpp b/src/core/hle/Patches.cpp index 236b4c9d2..6ee021ed2 100644 --- a/src/core/hle/Patches.cpp +++ b/src/core/hle/Patches.cpp @@ -29,6 +29,7 @@ #include "core\kernel\init\CxbxKrnl.h" #include "core\kernel\support\Emu.h" #include "core\hle\D3D8\Direct3D9/Direct3D9.h" +#include "core\hle\JVS\JVS.h" #include "core\hle\DSOUND\DirectSound\DirectSound.hpp" #include "Patches.hpp" #include "Intercept.hpp" @@ -66,7 +67,7 @@ std::map g_PatchTable = { PATCH_ENTRY("D3DDevice_BeginPush_8", xbox::EMUPATCH(D3DDevice_BeginPush_8), PATCH_HLE_D3D), PATCH_ENTRY("D3DDevice_BeginVisibilityTest", xbox::EMUPATCH(D3DDevice_BeginVisibilityTest), PATCH_HLE_D3D), PATCH_ENTRY("D3DDevice_BlockOnFence", xbox::EMUPATCH(D3DDevice_BlockOnFence), PATCH_HLE_D3D), - PATCH_ENTRY("D3DDevice_BlockUntilVerticalBlank", xbox::EMUPATCH(D3DDevice_BlockUntilVerticalBlank), PATCH_HLE_D3D), + //PATCH_ENTRY("D3DDevice_BlockUntilVerticalBlank", xbox::EMUPATCH(D3DDevice_BlockUntilVerticalBlank), PATCH_HLE_D3D), PATCH_ENTRY("D3DDevice_Clear", xbox::EMUPATCH(D3DDevice_Clear), PATCH_HLE_D3D), PATCH_ENTRY("D3DDevice_CopyRects", xbox::EMUPATCH(D3DDevice_CopyRects), PATCH_HLE_D3D), // PATCH_ENTRY("D3DDevice_CreateVertexShader", xbox::EMUPATCH(D3DDevice_CreateVertexShader), PATCH_HLE_D3D), @@ -183,7 +184,7 @@ std::map g_PatchTable = { PATCH_ENTRY("D3DDevice_SetVertexShaderConstant_8", xbox::EMUPATCH(D3DDevice_SetVertexShaderConstant_8), PATCH_HLE_D3D), PATCH_ENTRY("D3DDevice_SetVertexShaderInput", xbox::EMUPATCH(D3DDevice_SetVertexShaderInput), PATCH_HLE_D3D), //PATCH_ENTRY("D3DDevice_SetVertexShaderInputDirect", xbox::EMUPATCH(D3DDevice_SetVertexShaderInputDirect), PATCH_HLE_D3D), - PATCH_ENTRY("D3DDevice_SetVerticalBlankCallback", xbox::EMUPATCH(D3DDevice_SetVerticalBlankCallback), PATCH_HLE_D3D), + //PATCH_ENTRY("D3DDevice_SetVerticalBlankCallback", xbox::EMUPATCH(D3DDevice_SetVerticalBlankCallback), PATCH_HLE_D3D), PATCH_ENTRY("D3DDevice_SetViewport", xbox::EMUPATCH(D3DDevice_SetViewport), PATCH_HLE_D3D), PATCH_ENTRY("D3DDevice_Swap", xbox::EMUPATCH(D3DDevice_Swap), PATCH_HLE_D3D), PATCH_ENTRY("D3DDevice_Swap_0", xbox::EMUPATCH(D3DDevice_Swap_0), PATCH_HLE_D3D), @@ -375,6 +376,54 @@ std::map g_PatchTable = { //PATCH_ENTRY("timeSetEvent", xbox::EMUPATCH(timeSetEvent), PATCH_ALWAYS), PATCH_ENTRY("XReadMUMetaData", xbox::EMUPATCH(XReadMUMetaData), PATCH_ALWAYS), PATCH_ENTRY("XUnmountMU", xbox::EMUPATCH(XUnmountMU), PATCH_ALWAYS), + + // JVS Functions + PATCH_ENTRY("JVS_SendCommand", xbox::EMUPATCH(JVS_SendCommand), PATCH_ALWAYS), + PATCH_ENTRY("JVS_SendCommand2", xbox::EMUPATCH(JVS_SendCommand), PATCH_ALWAYS), + PATCH_ENTRY("JVS_SendCommand3", xbox::EMUPATCH(JVS_SendCommand), PATCH_ALWAYS), + PATCH_ENTRY("JvsBACKUP_Read", xbox::EMUPATCH(JvsBACKUP_Read), PATCH_ALWAYS), + PATCH_ENTRY("JvsBACKUP_Read2", xbox::EMUPATCH(JvsBACKUP_Read), PATCH_ALWAYS), + PATCH_ENTRY("JvsBACKUP_Read3", xbox::EMUPATCH(JvsBACKUP_Read), PATCH_ALWAYS), + PATCH_ENTRY("JvsBACKUP_Write", xbox::EMUPATCH(JvsBACKUP_Write), PATCH_ALWAYS), + PATCH_ENTRY("JvsBACKUP_Write2", xbox::EMUPATCH(JvsBACKUP_Write), PATCH_ALWAYS), + PATCH_ENTRY("JvsEEPROM_Read", xbox::EMUPATCH(JvsEEPROM_Read), PATCH_ALWAYS), + PATCH_ENTRY("JvsEEPROM_Read2", xbox::EMUPATCH(JvsEEPROM_Read), PATCH_ALWAYS), + PATCH_ENTRY("JvsEEPROM_Read3", xbox::EMUPATCH(JvsEEPROM_Read), PATCH_ALWAYS), + PATCH_ENTRY("JvsEEPROM_Write", xbox::EMUPATCH(JvsEEPROM_Write), PATCH_ALWAYS), + PATCH_ENTRY("JvsEEPROM_Write2", xbox::EMUPATCH(JvsEEPROM_Write), PATCH_ALWAYS), + PATCH_ENTRY("JvsEEPROM_Write3", xbox::EMUPATCH(JvsEEPROM_Write), PATCH_ALWAYS), + PATCH_ENTRY("JvsFirmwareDownload", xbox::EMUPATCH(JvsFirmwareDownload), PATCH_ALWAYS), + PATCH_ENTRY("JvsFirmwareDownload2", xbox::EMUPATCH(JvsFirmwareDownload), PATCH_ALWAYS), + PATCH_ENTRY("JvsFirmwareDownload3", xbox::EMUPATCH(JvsFirmwareDownload), PATCH_ALWAYS), + PATCH_ENTRY("JvsFirmwareDownload4", xbox::EMUPATCH(JvsFirmwareDownload), PATCH_ALWAYS), + PATCH_ENTRY("JvsFirmwareUpload", xbox::EMUPATCH(JvsFirmwareUpload), PATCH_ALWAYS), + PATCH_ENTRY("JvsFirmwareUpload2", xbox::EMUPATCH(JvsFirmwareUpload), PATCH_ALWAYS), + PATCH_ENTRY("JvsFirmwareUpload3", xbox::EMUPATCH(JvsFirmwareUpload), PATCH_ALWAYS), + PATCH_ENTRY("JvsFirmwareUpload4", xbox::EMUPATCH(JvsFirmwareUpload), PATCH_ALWAYS), + PATCH_ENTRY("JvsNodeReceivePacket", xbox::EMUPATCH(JvsNodeReceivePacket), PATCH_ALWAYS), + PATCH_ENTRY("JvsNodeReceivePacket2", xbox::EMUPATCH(JvsNodeReceivePacket), PATCH_ALWAYS), + PATCH_ENTRY("JvsNodeSendPacket", xbox::EMUPATCH(JvsNodeSendPacket), PATCH_ALWAYS), + PATCH_ENTRY("JvsNodeSendPacket2", xbox::EMUPATCH(JvsNodeSendPacket), PATCH_ALWAYS), + PATCH_ENTRY("JvsRTC_Read", xbox::EMUPATCH(JvsRTC_Read), PATCH_ALWAYS), + PATCH_ENTRY("JvsRTC_Read2", xbox::EMUPATCH(JvsRTC_Read), PATCH_ALWAYS), + PATCH_ENTRY("JvsRTC_Read3", xbox::EMUPATCH(JvsRTC_Read), PATCH_ALWAYS), + PATCH_ENTRY("JvsRTC_Write", xbox::EMUPATCH(JvsRTC_Write), PATCH_ALWAYS), + PATCH_ENTRY("JvsRTC_Write2", xbox::EMUPATCH(JvsRTC_Write), PATCH_ALWAYS), + PATCH_ENTRY("JvsScFirmwareDownload", xbox::EMUPATCH(JvsScFirmwareDownload), PATCH_ALWAYS), + PATCH_ENTRY("JvsScFirmwareDownload2", xbox::EMUPATCH(JvsScFirmwareDownload), PATCH_ALWAYS), + PATCH_ENTRY("JvsScFirmwareDownload3", xbox::EMUPATCH(JvsScFirmwareDownload), PATCH_ALWAYS), + PATCH_ENTRY("JvsScFirmwareDownload4", xbox::EMUPATCH(JvsScFirmwareDownload), PATCH_ALWAYS), + PATCH_ENTRY("JvsScFirmwareUpload", xbox::EMUPATCH(JvsScFirmwareUpload), PATCH_ALWAYS), + PATCH_ENTRY("JvsScFirmwareUpload2", xbox::EMUPATCH(JvsScFirmwareUpload), PATCH_ALWAYS), + PATCH_ENTRY("JvsScFirmwareUpload3", xbox::EMUPATCH(JvsScFirmwareUpload), PATCH_ALWAYS), + PATCH_ENTRY("JvsScReceiveMidi", xbox::EMUPATCH(JvsScReceiveMidi), PATCH_ALWAYS), + PATCH_ENTRY("JvsScReceiveMidi2", xbox::EMUPATCH(JvsScReceiveMidi), PATCH_ALWAYS), + PATCH_ENTRY("JvsScReceiveRs323c", xbox::EMUPATCH(JvsScReceiveRs323c), PATCH_ALWAYS), + PATCH_ENTRY("JvsScReceiveRs323c2", xbox::EMUPATCH(JvsScReceiveRs323c), PATCH_ALWAYS), + PATCH_ENTRY("JvsScSendMidi", xbox::EMUPATCH(JvsScSendMidi), PATCH_ALWAYS), + PATCH_ENTRY("JvsScSendMidi2", xbox::EMUPATCH(JvsScSendMidi), PATCH_ALWAYS), + PATCH_ENTRY("JvsScSendRs323c", xbox::EMUPATCH(JvsScSendRs323c), PATCH_ALWAYS), + PATCH_ENTRY("JvsScSendRs323c2", xbox::EMUPATCH(JvsScSendRs323c), PATCH_ALWAYS), }; std::unordered_map g_FunctionHooks; diff --git a/src/core/hle/XAPI/Xapi.cpp b/src/core/hle/XAPI/Xapi.cpp index a17c1682f..429a7c610 100644 --- a/src/core/hle/XAPI/Xapi.cpp +++ b/src/core/hle/XAPI/Xapi.cpp @@ -960,15 +960,41 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(SignalObjectAndWait) NewTime.QuadPart += (static_cast(dwMilliseconds) * CLOCK_TIME_INCREMENT); } - xbox::dword_xt ret = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable]() -> std::optional { + PKTHREAD kThread = KeGetCurrentThread(); + kThread->WaitStatus = X_STATUS_SUCCESS; + if (!AddWaitObject(kThread, Timeout)) { + RETURN(WAIT_TIMEOUT); + } + + xbox::ntstatus_xt status = WaitApc([hObjectToSignal, hObjectToWaitOn, bAlertable](xbox::PKTHREAD kThread) -> std::optional { DWORD dwRet = SignalObjectAndWait(hObjectToSignal, hObjectToWaitOn, 0, bAlertable); if (dwRet == WAIT_TIMEOUT) { return std::nullopt; } - return std::make_optional(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(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); } // ****************************************************************** diff --git a/src/core/kernel/common/rtl.h b/src/core/kernel/common/rtl.h index 924d11ed8..8af147374 100644 --- a/src/core/kernel/common/rtl.h +++ b/src/core/kernel/common/rtl.h @@ -605,6 +605,9 @@ XBSYSAPI EXPORTNUM(352) void_xt NTAPI RtlRip PCHAR Message ); +void_xt RtlInitSystem(); +extern RTL_CRITICAL_SECTION NtSystemTimeCritSec; + } #endif diff --git a/src/core/kernel/common/types.h b/src/core/kernel/common/types.h index da159c68c..367b29cb4 100644 --- a/src/core/kernel/common/types.h +++ b/src/core/kernel/common/types.h @@ -98,6 +98,8 @@ typedef void* LPSECURITY_ATTRIBUTES; #define X_STATUS_FILE_IS_A_DIRECTORY 0xC00000BAL #define X_STATUS_END_OF_FILE 0xC0000011L #define X_STATUS_INVALID_PAGE_PROTECTION 0xC0000045L +#define X_STATUS_SUSPEND_COUNT_EXCEEDED 0xC000004AL +#define X_STATUS_THREAD_IS_TERMINATING 0xC000004BL #define X_STATUS_CONFLICTING_ADDRESSES 0xC0000018L #define X_STATUS_UNABLE_TO_FREE_VM 0xC000001AL #define X_STATUS_FREE_VM_NOT_AT_BASE 0xC000009FL @@ -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 // ****************************************************************** diff --git a/src/core/kernel/exports/EmuKrnl.cpp b/src/core/kernel/exports/EmuKrnl.cpp index 6934cd1b5..252a0dac7 100644 --- a/src/core/kernel/exports/EmuKrnl.cpp +++ b/src/core/kernel/exports/EmuKrnl.cpp @@ -85,6 +85,8 @@ void InsertTailList(xbox::PLIST_ENTRY pListHead, xbox::PLIST_ENTRY pEntry) //#define RemoveEntryList(e) do { PLIST_ENTRY f = (e)->Flink, b = (e)->Blink; f->Blink = b; b->Flink = f; (e)->Flink = (e)->Blink = NULL; } while (0) // Returns TRUE if the list has become empty after removing the element, FALSE otherwise. +// NOTE: this function is a mess. _EX_Flink and _EX_Flink should never be nullptr, and it should never be called on a detached element either. Try to fix +// the bugs in the caller instead of trying to handle it here with these hacks xbox::boolean_xt RemoveEntryList(xbox::PLIST_ENTRY pEntry) { xbox::PLIST_ENTRY _EX_Flink = pEntry->Flink; @@ -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 diff --git a/src/core/kernel/exports/EmuKrnl.h b/src/core/kernel/exports/EmuKrnl.h index 0b8a691d3..17e458b68 100644 --- a/src/core/kernel/exports/EmuKrnl.h +++ b/src/core/kernel/exports/EmuKrnl.h @@ -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 -std::optional SatisfyWait(T &&Lambda, xbox::PETHREAD eThread, xbox::boolean_xt Alertable, xbox::char_xt WaitMode) +std::optional SatisfyWait(T &&Lambda, xbox::PKTHREAD kThread, xbox::boolean_xt Alertable, xbox::char_xt WaitMode) { - if (const auto ret = Lambda()) { + 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 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 -xbox::ntstatus_xt WaitApc(T &&Lambda, xbox::PLARGE_INTEGER Timeout, xbox::boolean_xt Alertable, xbox::char_xt WaitMode) +template +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(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(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 diff --git a/src/core/kernel/exports/EmuKrnlIo.cpp b/src/core/kernel/exports/EmuKrnlIo.cpp index 173b3d591..4ffecf890 100644 --- a/src/core/kernel/exports/EmuKrnlIo.cpp +++ b/src/core/kernel/exports/EmuKrnlIo.cpp @@ -248,6 +248,14 @@ XBSYSAPI EXPORTNUM(66) xbox::ntstatus_xt NTAPI xbox::IoCreateFile LOG_FUNC_ARG(Options) LOG_FUNC_END; + // If we are emulating the Chihiro, we need to hook mbcom, so return an easily identifable handle + if (g_bIsChihiro) { + if (strncmp(ObjectAttributes->ObjectName->Buffer, DriveMbcom.c_str(), DriveMbcom.length()) == 0) { + *FileHandle = CHIHIRO_MBCOM_HANDLE; + return X_STATUS_SUCCESS; + } + } + NativeObjectAttributes nativeObjectAttributes; // If we are NOT accessing a directory, and we match a partition path, we need to redirect to a partition.bin file diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 8bbfbc8d5..72ed32a37 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -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 { + PKTHREAD kThread = KeGetCurrentThread(); + kThread->WaitStatus = X_STATUS_SUCCESS; + if (!AddWaitObject(kThread, Interval)) { + RETURN(X_STATUS_TIMEOUT); + } + + xbox::ntstatus_xt ret = WaitApc([Alertable](xbox::PKTHREAD kThread) -> std::optional { 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(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(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(reinterpret_cast(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(reinterpret_cast(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(reinterpret_cast(Thread)->UniqueThread); - LONG ret = GetThreadPriority(*nativeHandle); - - // This would work normally, but it will slow down the emulation, - // don't do that if the priority is higher then normal (so our own)! - if (Priority <= THREAD_PRIORITY_NORMAL) { - BOOL result = SetThreadPriority(*nativeHandle, Priority); - if (!result) { - EmuLog(LOG_LEVEL::WARNING, "SetThreadPriority failed: %s", WinError2Str().c_str()); - } - } + 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(reinterpret_cast(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(reinterpret_cast(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([](PKTHREAD Thread) -> std::optional { + if (Thread->State == Ready) { + // We have been readied to resume execution, so exit the wait + return std::make_optional(Thread->WaitStatus); + } + return std::nullopt; + }, Timeout, Alertable, WaitMode, 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([](PKTHREAD Thread) -> std::optional { + if (Thread->State == Ready) { + // We have been readied to resume execution, so exit the wait + return std::make_optional(Thread->WaitStatus); + } + return std::nullopt; + }, Timeout, Alertable, WaitMode, 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) { diff --git a/src/core/kernel/exports/EmuKrnlKe.h b/src/core/kernel/exports/EmuKrnlKe.h index a7e2ebbc7..820c26f78 100644 --- a/src/core/kernel/exports/EmuKrnlKe.h +++ b/src/core/kernel/exports/EmuKrnlKe.h @@ -27,6 +27,8 @@ namespace xbox { + extern std::atomic_flag KeSystemTimeChanged; + void_xt NTAPI KeSetSystemTime ( IN PLARGE_INTEGER NewTime, @@ -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(); } diff --git a/src/core/kernel/exports/EmuKrnlKi.cpp b/src/core/kernel/exports/EmuKrnlKi.cpp index 9e66a18b6..40c7210b9 100644 --- a/src/core/kernel/exports/EmuKrnlKi.cpp +++ b/src/core/kernel/exports/EmuKrnlKi.cpp @@ -59,6 +59,8 @@ * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org) */ +// Also from ReactOS: KiWaitTest, KiWaitSatisfyAll, KiUnwaitThread, KiUnlinkThread + // COPYING file: /* GNU GENERAL PUBLIC LICENSE @@ -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(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 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(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; +} diff --git a/src/core/kernel/exports/EmuKrnlKi.h b/src/core/kernel/exports/EmuKrnlKi.h index e23165d7e..efc929fbc 100644 --- a/src/core/kernel/exports/EmuKrnlKi.h +++ b/src/core/kernel/exports/EmuKrnlKi.h @@ -47,6 +47,12 @@ namespace xbox int Acquired; } KI_TIMER_LOCK; + typedef struct _KI_WAIT_LIST_LOCK + { + std::recursive_mutex Mtx; + int Acquired; + } KI_WAIT_LIST_LOCK; + // NOTE: since the apc list is per-thread, we could also create a different mutex for each kthread extern std::mutex KiApcListMtx; @@ -56,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; diff --git a/src/core/kernel/exports/EmuKrnlNt.cpp b/src/core/kernel/exports/EmuKrnlNt.cpp index 1e7d33fc8..bb445ee95 100644 --- a/src/core/kernel/exports/EmuKrnlNt.cpp +++ b/src/core/kernel/exports/EmuKrnlNt.cpp @@ -47,6 +47,7 @@ namespace NtDll #include "core\kernel\support\EmuFile.h" // For EmuNtSymbolicLinkObject, NtStatusToString(), etc. #include "core\kernel\memory-manager\VMManager.h" // For g_VMManager #include "core\kernel\support\NativeHandle.h" +#include "devices\Xbox.h" #include "CxbxDebugger.h" #pragma warning(disable:4005) // Ignore redefined status values @@ -58,7 +59,7 @@ namespace NtDll #include // Prevent setting the system time from multiple threads at the same time -std::mutex NtSystemTimeMtx; +xbox::RTL_CRITICAL_SECTION xbox::NtSystemTimeCritSec; // ****************************************************************** // * 0x00B8 - NtAllocateVirtualMemory() @@ -1039,7 +1040,7 @@ XBSYSAPI EXPORTNUM(206) xbox::ntstatus_xt NTAPI xbox::NtQueueApcThread PKAPC Apc = static_cast(ExAllocatePoolWithTag(sizeof(KAPC), 'pasP')); if (Apc != zeroptr) { - KeInitializeApc(Apc, &Thread->Tcb, zeroptr, zeroptr, reinterpret_cast(ApcRoutine), UserMode, ApcRoutineContext); + KeInitializeApc(Apc, &Thread->Tcb, KiFreeUserApc, zeroptr, reinterpret_cast(ApcRoutine), UserMode, ApcRoutineContext); if (!KeInsertQueueApc(Apc, ApcStatusBlock, ApcReserved, 0)) { ExFreePool(Apc); result = X_STATUS_UNSUCCESSFUL; @@ -1711,6 +1712,16 @@ XBSYSAPI EXPORTNUM(219) xbox::ntstatus_xt NTAPI xbox::NtReadFile CxbxDebugger::ReportFileRead(FileHandle, Length, Offset); } + // If we are emulating the Chihiro, we need to hook mbcom + if (g_bIsChihiro && FileHandle == CHIHIRO_MBCOM_HANDLE) { + g_MediaBoard->ComRead(ByteOffset->QuadPart, Buffer, Length); + + // Update the Status Block + IoStatusBlock->Status = STATUS_SUCCESS; + IoStatusBlock->Information = Length; + return STATUS_SUCCESS; + } + if (ApcRoutine != nullptr) { // Pack the original parameters to a wrapped context for a custom APC routine CxbxIoDispatcherContext* cxbxContext = new CxbxIoDispatcherContext(IoStatusBlock, ApcRoutine, ApcContext); @@ -1856,15 +1867,20 @@ XBSYSAPI EXPORTNUM(224) xbox::ntstatus_xt NTAPI xbox::NtResumeThread LOG_FUNC_ARG_OUT(PreviousSuspendCount) LOG_FUNC_END; - if (const auto &nativeHandle = GetNativeHandle(ThreadHandle)) { - // Thread handles are created by ob - RETURN(NtDll::NtResumeThread(*nativeHandle, (::PULONG)PreviousSuspendCount)); - } - else { - RETURN(X_STATUS_INVALID_HANDLE); + PETHREAD Thread; + ntstatus_xt result = ObReferenceObjectByHandle(ThreadHandle, &PsThreadObjectType, reinterpret_cast(&Thread)); + if (!X_NT_SUCCESS(result)) { + RETURN(result); } - // TODO : Once we do our own thread-switching, implement NtResumeThread using KetResumeThread + ulong_xt PrevSuspendCount = KeResumeThread(&Thread->Tcb); + ObfDereferenceObject(Thread); + + if (PreviousSuspendCount) { + *PreviousSuspendCount = PrevSuspendCount; + } + + RETURN(X_STATUS_SUCCESS); } // ****************************************************************** @@ -1971,7 +1987,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 +2007,7 @@ XBSYSAPI EXPORTNUM(228) xbox::ntstatus_xt NTAPI xbox::NtSetSystemTime else { ret = STATUS_INVALID_PARAMETER; } - NtSystemTimeMtx.unlock(); + RtlLeaveCriticalSectionAndRegion(&NtSystemTimeCritSec); } RETURN(ret); @@ -2079,15 +2095,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(&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 +2232,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 { + PKTHREAD kThread = KeGetCurrentThread(); + kThread->WaitStatus = X_STATUS_SUCCESS; + if (!AddWaitObject(kThread, Timeout)) { + RETURN(X_STATUS_TIMEOUT); + } + + xbox::ntstatus_xt ret = WaitApc([Count, &nativeHandles, WaitType, Alertable](xbox::PKTHREAD kThread) -> std::optional { NtDll::LARGE_INTEGER ExpireTime; ExpireTime.QuadPart = 0; NTSTATUS Status = NtDll::NtWaitForMultipleObjects( @@ -2221,8 +2260,11 @@ XBSYSAPI EXPORTNUM(235) xbox::ntstatus_xt NTAPI xbox::NtWaitForMultipleObjectsEx if (Status == STATUS_TIMEOUT) { return std::nullopt; } - return std::make_optional(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(kThread->WaitStatus); + }, Timeout, Alertable, WaitMode, kThread); RETURN(ret); } @@ -2269,6 +2311,16 @@ XBSYSAPI EXPORTNUM(236) xbox::ntstatus_xt NTAPI xbox::NtWriteFile CxbxDebugger::ReportFileWrite(FileHandle, Length, Offset); } + // If we are emulating the Chihiro, we need to hook mbcom + if (g_bIsChihiro && FileHandle == CHIHIRO_MBCOM_HANDLE) { + g_MediaBoard->ComWrite(ByteOffset->QuadPart, Buffer, Length); + + // Update the Status Block + IoStatusBlock->Status = STATUS_SUCCESS; + IoStatusBlock->Information = Length; + return STATUS_SUCCESS; + } + if (ApcRoutine != nullptr) { // Pack the original parameters to a wrapped context for a custom APC routine CxbxIoDispatcherContext* cxbxContext = new CxbxIoDispatcherContext(IoStatusBlock, ApcRoutine, ApcContext); diff --git a/src/core/kernel/exports/EmuKrnlPs.cpp b/src/core/kernel/exports/EmuKrnlPs.cpp index 9c0a61ef3..8084ed97d 100644 --- a/src/core/kernel/exports/EmuKrnlPs.cpp +++ b/src/core/kernel/exports/EmuKrnlPs.cpp @@ -121,6 +121,8 @@ static unsigned int WINAPI PCSTProxy params.Ethread, params.TlsDataSize); + xbox::KiExecuteKernelApc(); + auto routine = (xbox::PKSYSTEM_ROUTINE)StartFrame->SystemRoutine; // Debugging notice : When the below line shows up with an Exception dialog and a // message like: "Exception thrown at 0x00026190 in cxbx.exe: 0xC0000005: Access @@ -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)) { diff --git a/src/core/kernel/exports/EmuKrnlRtl.cpp b/src/core/kernel/exports/EmuKrnlRtl.cpp index b309201f6..00e9d7f61 100644 --- a/src/core/kernel/exports/EmuKrnlRtl.cpp +++ b/src/core/kernel/exports/EmuKrnlRtl.cpp @@ -89,6 +89,11 @@ xbox::boolean_xt RtlpCaptureStackLimits( return TRUE; } +xbox::void_xt xbox::RtlInitSystem() +{ + xbox::RtlInitializeCriticalSection(&NtSystemTimeCritSec); +} + // ****************************************************************** // * 0x0104 - RtlAnsiStringToUnicodeString() // ****************************************************************** diff --git a/src/core/kernel/init/CxbxKrnl.cpp b/src/core/kernel/init/CxbxKrnl.cpp index d0447f83b..61c93b043 100644 --- a/src/core/kernel/init/CxbxKrnl.cpp +++ b/src/core/kernel/init/CxbxKrnl.cpp @@ -99,9 +99,6 @@ bool g_bIsChihiro = false; bool g_bIsDevKit = false; bool g_bIsRetail = false; -// Indicates to disable/enable all interrupts when cli and sti instructions are executed -std::atomic_bool g_bEnableAllInterrupts = true; - // Set by the VMManager during initialization. Exported because it's needed in other parts of the emu size_t g_SystemMaxMemory = 0; @@ -113,6 +110,8 @@ ULONG g_CxbxFatalErrorCode = FATAL_ERROR_NONE; // Define function located in EmuXApi so we can call it from here void SetupXboxDeviceTypes(); +extern xbox::void_xt NTAPI system_events(xbox::PVOID arg); + void SetupPerTitleKeys() { // Generate per-title keys from the XBE Certificate @@ -329,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 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(); @@ -686,6 +627,7 @@ static bool CxbxrKrnlXbeSystemSelector(int BootFlags, // Detect XBE type : XbeType xbeType = CxbxKrnl_Xbe->GetXbeType(); EmuLogInit(LOG_LEVEL::INFO, "Auto detect: XbeType = %s", GetXbeTypeToStr(xbeType)); + // Convert XBE type into corresponding system to emulate. switch (xbeType) { case XbeType::xtChihiro: @@ -699,6 +641,10 @@ static bool CxbxrKrnlXbeSystemSelector(int BootFlags, break; DEFAULT_UNREACHABLE; } + + if (std::filesystem::exists(xbeDirectory / "boot.id")) { + emulate_system = SYSTEM_CHIHIRO; + } } EmuLogInit(LOG_LEVEL::INFO, "Host's compatible system types: %2X", reserved_systems); @@ -1220,7 +1166,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 +1308,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 +1421,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 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 @@ -1512,7 +1453,7 @@ void CxbxrKrnlSuspendThreads() // Don't use EmuKeGetPcr because that asserts kpcr xbox::KPCR* Pcr = reinterpret_cast(__readfsdword(TIB_ArbitraryDataSlot)); - + // If there's nothing in list entry, skip this step. if (!ThreadListEntry) { return; diff --git a/src/core/kernel/init/CxbxKrnl.h b/src/core/kernel/init/CxbxKrnl.h index c11af39cc..6269bc9fa 100644 --- a/src/core/kernel/init/CxbxKrnl.h +++ b/src/core/kernel/init/CxbxKrnl.h @@ -157,6 +157,7 @@ void CxbxKrnlNoFunc(); void InitDpcData(); // Implemented in EmuKrnlKe.cpp bool IsDpcActive(); +void ExecuteDpcQueue(); /*! kernel thunk table */ extern uint32_t CxbxKrnl_KernelThunkTable[379]; diff --git a/src/core/kernel/support/Emu.cpp b/src/core/kernel/support/Emu.cpp index da98ab08f..45f697d85 100644 --- a/src/core/kernel/support/Emu.cpp +++ b/src/core/kernel/support/Emu.cpp @@ -49,11 +49,6 @@ bool g_DisablePixelShaders = false; bool g_UseAllCores = false; bool g_SkipRdtscPatching = false; -// Delta added to host SystemTime, used in KiClockIsr and KeSetSystemTime -// This shouldn't need to be atomic, but because raising the IRQL to high lv in KeSetSystemTime doesn't really stop KiClockIsr from running, -// we need it for now to prevent reading a corrupted value while KeSetSystemTime is in the middle of updating it -std::atomic_int64_t HostSystemTimeDelta(0); - // Static Function(s) static int ExitException(LPEXCEPTION_POINTERS e); diff --git a/src/core/kernel/support/Emu.h b/src/core/kernel/support/Emu.h index d12f493ab..2087adc84 100644 --- a/src/core/kernel/support/Emu.h +++ b/src/core/kernel/support/Emu.h @@ -68,9 +68,6 @@ extern HWND g_hEmuWindow; extern HANDLE g_CurrentProcessHandle; // Set in CxbxKrnlMain -// Delta added to host SystemTime, used in KiClockIsr and KeSetSystemTime -extern std::atomic_int64_t HostSystemTimeDelta; - typedef struct DUMMY_KERNEL { IMAGE_DOS_HEADER DosHeader; diff --git a/src/devices/Xbox.cpp b/src/devices/Xbox.cpp index 3d81343db..3ad868612 100644 --- a/src/devices/Xbox.cpp +++ b/src/devices/Xbox.cpp @@ -32,6 +32,7 @@ #include "core\kernel\common\xbox.h" #include "cxbxr.hpp" #include "core\hle\Intercept.hpp" +#include "EmuShared.h" PCIBus* g_PCIBus; SMBus* g_SMBus; @@ -42,6 +43,7 @@ NVNetDevice* g_NVNet; NV2ADevice* g_NV2A; ADM1032Device* g_ADM1032; USBDevice* g_USB0; +MediaBoard* g_MediaBoard; MCPXRevision MCPXRevisionFromHardwareModel(HardwareModel hardwareModel) { @@ -151,15 +153,22 @@ void InitXboxHardware(HardwareModel hardwareModel) // Create devices g_MCPX = new MCPXDevice(mcpx_revision); - g_SMC = new SMCDevice(smc_revision, IS_CHIHIRO(hardwareModel) ? 6 : 1); // 6 = AV_PACK_STANDARD, 1 = AV_PACK_HDTV. Chihiro doesn't support HDTV! +// TODO: For Chihiro, different games modes require different DIP switch settings + // Chihiro FilterBoard dip-switches 6,7,8 change this value! + g_SMC = new SMCDevice(smc_revision, IS_CHIHIRO(hardwareModel) ? 0 : 1); // 0 = AV_PACK_SCART, 1 = AV_PACK_HDTV. Chihiro doesn't support HDTV! // SMC uses different AV_PACK values than the Kernel // See https://xboxdevwiki.net/PIC#The_AV_Pack g_EEPROM = new EEPROMDevice(); g_NVNet = new NVNetDevice(); g_NV2A = new NV2ADevice(); g_ADM1032 = new ADM1032Device(); - if (bLLE_USB) { - g_USB0 = new USBDevice(); + g_USB0 = new USBDevice(); + + if (g_bIsChihiro) { + g_MediaBoard = new MediaBoard(); + char MediaBoardMountPath[xbox::max_path]; + g_EmuShared->GetTitleMountPath(MediaBoardMountPath); + g_MediaBoard->SetMountPath(MediaBoardMountPath); } // Connect devices to SM bus @@ -189,14 +198,12 @@ void InitXboxHardware(HardwareModel hardwareModel) //g_PCIBus->ConnectDevice(PCI_DEVID(0, PCI_DEVFN(5, 0)), g_NVAPU); //g_PCIBus->ConnectDevice(PCI_DEVID(0, PCI_DEVFN(6, 0)), g_AC97); g_PCIBus->ConnectDevice(PCI_DEVID(1, PCI_DEVFN(0, 0)), g_NV2A); - if (bLLE_USB) { - // ergo720: according to some research done by LukeUsher, only Xbox Alpha Kits have a two HCs configuration. This seems to also be confirmed by the xboxdevwiki, - // which states that it has a xircom PGPCI2(OPTI 82C861) 2 USB port PCI card -> 2 ports, not 4. Finally, I disassembled various xbe's and discovered that the number - // of ports per HC is hardcoded as 4 in the driver instead of being detected at runtime by reading the HcRhDescriptorA register and so a game would have to be - // recompiled to support 2 HCs, which further confirms the point. Because we are not going to emulate an Alpha Kit, we can simply ignore the USB1 device. + // ergo720: according to some research done by LukeUsher, only Xbox Alpha Kits have a two HCs configuration. This seems to also be confirmed by the xboxdevwiki, + // which states that it has a xircom PGPCI2(OPTI 82C861) 2 USB port PCI card -> 2 ports, not 4. Finally, I disassembled various xbe's and discovered that the number + // of ports per HC is hardcoded as 4 in the driver instead of being detected at runtime by reading the HcRhDescriptorA register and so a game would have to be + // recompiled to support 2 HCs, which further confirms the point. Because we are not going to emulate an Alpha Kit, we can simply ignore the USB1 device. - g_PCIBus->ConnectDevice(PCI_DEVID(0, PCI_DEVFN(2, 0)), g_USB0); - } + g_PCIBus->ConnectDevice(PCI_DEVID(0, PCI_DEVFN(2, 0)), g_USB0); // TODO : Handle other SMBUS Addresses, like PIC_ADDRESS, XCALIBUR_ADDRESS // Resources : http://pablot.com/misc/fancontroller.cpp diff --git a/src/devices/Xbox.h b/src/devices/Xbox.h index 22a426ca5..d5d681648 100644 --- a/src/devices/Xbox.h +++ b/src/devices/Xbox.h @@ -35,6 +35,7 @@ #include "ADM1032Device.h" // For ADM1032 #include "devices\video\nv2a.h" // For NV2ADevice #include "Usb\USBDevice.h" // For USBDevice +#include "chihiro\MediaBoard.h" #define SMBUS_ADDRESS_MCPX 0x10 // = Write; Read = 0x11 #define SMBUS_ADDRESS_TV_ENCODER 0x88 // = Write; Read = 0x89 @@ -83,5 +84,6 @@ extern EEPROMDevice* g_EEPROM; extern NVNetDevice* g_NVNet; extern NV2ADevice* g_NV2A; extern USBDevice* g_USB0; +extern MediaBoard* g_MediaBoard; extern void InitXboxHardware(HardwareModel hardwareModel); diff --git a/src/devices/chihiro/JvsIo.cpp b/src/devices/chihiro/JvsIo.cpp new file mode 100644 index 000000000..7c60f06c3 --- /dev/null +++ b/src/devices/chihiro/JvsIo.cpp @@ -0,0 +1,443 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +// ****************************************************************** +// * This file is part of the Cxbx project. +// * +// * Cxbx and Cxbe are free software; you can redistribute them +// * and/or modify them under the terms of the GNU General Public +// * License as published by the Free Software Foundation; either +// * version 2 of the license, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU General Public License for more details. +// * +// * You should have recieved a copy of the GNU General Public License +// * along with this program; see the file COPYING. +// * If not, write to the Free Software Foundation, Inc., +// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA. +// * +// * (c) 2019 Luke Usher +// * +// * All rights reserved +// * +// ****************************************************************** + +#include "JvsIo.h" +#include +#include + +JvsIo* g_pJvsIo; + +//#define DEBUG_JVS_PACKETS +#include +#include +// We will emulate SEGA 837-13551 IO Board +JvsIo::JvsIo(uint8_t* sense) +{ + pSense = sense; + + // Version info BCD Format: X.X + CommandFormatRevision = 0x11; + JvsVersion = 0x20; + CommunicationVersion = 0x10; + + BoardID = "SEGA ENTERPRISES,LTD.;I/O BD JVS;837-13551"; +} + +void JvsIo::Update() +{ + // Handle coin input + static bool previousCoinButtonsState = false; + bool currentCoinButtonState = GetAsyncKeyState('5'); + if (currentCoinButtonState && !previousCoinButtonsState) { + Inputs.coins[0].coins += 1; + } + previousCoinButtonsState = currentCoinButtonState; + + // TODO: Update Jvs inputs based on user configuration + // For now, hardcode the inputs for the game we are currently testing (Ollie King) + Inputs.switches.player[0].start = GetAsyncKeyState('1'); // Start + Inputs.analog[1].value = GetAsyncKeyState(VK_LEFT) ? 0x9000 : (GetAsyncKeyState(VK_RIGHT) ? 0x7000 : 0x8000); // Board Swing + Inputs.switches.player[0].up = GetAsyncKeyState(VK_UP); // Board Front + Inputs.switches.player[0].down = GetAsyncKeyState(VK_DOWN); // Board Rear + Inputs.switches.player[0].button[0] = GetAsyncKeyState('A'); // Left Button + Inputs.switches.player[0].button[1] = GetAsyncKeyState('S'); // Right Button +} + +uint8_t JvsIo::GetDeviceId() +{ + return BroadcastPacket ? 0x00 : DeviceId; +} + +int JvsIo::Jvs_Command_F0_Reset(uint8_t* data) +{ + uint8_t ensure_reset = data[1]; + + if (ensure_reset == 0xD9) { + // Set sense to 3 (2.5v) to instruct the baseboard we're ready. + *pSense = 3; + ResponseBuffer.push_back(ReportCode::Handled); // Note : Without this, Chihiro software stops sending packets (but JVS V3 doesn't send this?) + DeviceId = 0; + } +#if 0 // TODO : Is the following required? + else { + ResponseBuffer.push_back(ReportCode::InvalidParameter); + } +#endif + +#if 0 // TODO : Is the following required? + // Detect a consecutive reset + if (data[2] == 0xF0) { + // TODO : Probably ensure the second reset too : if (data[3] == 0xD9) { + // TODO : Handle two consecutive reset's here? + + return 3; + } +#endif + + return 1; +} + +int JvsIo::Jvs_Command_F1_SetDeviceId(uint8_t* data) +{ + // Set Address + DeviceId = data[1]; + + *pSense = 0; // Set sense to 0v + ResponseBuffer.push_back(ReportCode::Handled); + + return 1; +} + +int JvsIo::Jvs_Command_10_GetBoardId() +{ + // Get Board ID + ResponseBuffer.push_back(ReportCode::Handled); + + for (char& c : BoardID) { + ResponseBuffer.push_back(c); + } + + return 0; +} + +int JvsIo::Jvs_Command_11_GetCommandFormat() +{ + ResponseBuffer.push_back(ReportCode::Handled); + ResponseBuffer.push_back(CommandFormatRevision); + + return 0; +} + +int JvsIo::Jvs_Command_12_GetJvsRevision() +{ + ResponseBuffer.push_back(ReportCode::Handled); + ResponseBuffer.push_back(JvsVersion); + + return 0; +} + +int JvsIo::Jvs_Command_13_GetCommunicationVersion() +{ + ResponseBuffer.push_back(ReportCode::Handled); + ResponseBuffer.push_back(CommunicationVersion); + + return 0; +} + +int JvsIo::Jvs_Command_14_GetCapabilities() +{ + ResponseBuffer.push_back(ReportCode::Handled); + + // Capabilities list (4 bytes each) + + // Input capabilities + ResponseBuffer.push_back(CapabilityCode::PlayerSwitchButtonSets); + ResponseBuffer.push_back(JVS_MAX_PLAYERS); // number of players + ResponseBuffer.push_back(13); // 13 button switches per player + ResponseBuffer.push_back(0); + + ResponseBuffer.push_back(CapabilityCode::CoinSlots); + ResponseBuffer.push_back(JVS_MAX_COINS); // number of coin slots + ResponseBuffer.push_back(0); + ResponseBuffer.push_back(0); + + ResponseBuffer.push_back(CapabilityCode::AnalogInputs); + ResponseBuffer.push_back(JVS_MAX_ANALOG); // number of analog input channels + ResponseBuffer.push_back(16); // 16 bits per analog input channel + ResponseBuffer.push_back(0); + + // Output capabilities + ResponseBuffer.push_back(CapabilityCode::GeneralPurposeOutputs); + ResponseBuffer.push_back(6); // number of outputs + ResponseBuffer.push_back(0); + ResponseBuffer.push_back(0); + + ResponseBuffer.push_back(CapabilityCode::EndOfCapabilities); + + return 0; +} + +int JvsIo::Jvs_Command_20_ReadSwitchInputs(uint8_t* data) +{ + static jvs_switch_player_inputs_t default_switch_player_input; + uint8_t nr_switch_players = data[1]; + uint8_t bytesPerSwitchPlayerInput = data[2]; + + ResponseBuffer.push_back(ReportCode::Handled); + + ResponseBuffer.push_back(Inputs.switches.system.GetByte0()); + + for (int i = 0; i < nr_switch_players; i++) { + for (int j = 0; j < bytesPerSwitchPlayerInput; j++) { + // If a title asks for more switch player inputs than we support, pad with dummy data + jvs_switch_player_inputs_t &switch_player_input = (i >= JVS_MAX_PLAYERS) ? default_switch_player_input : Inputs.switches.player[i]; + uint8_t value + = (j == 0) ? switch_player_input.GetByte0() + : (j == 1) ? switch_player_input.GetByte1() + : 0; // Pad any remaining bytes with 0, as we don't have that many inputs available + ResponseBuffer.push_back(value); + } + } + + return 2; +} + +int JvsIo::Jvs_Command_21_ReadCoinInputs(uint8_t* data) +{ + static jvs_coin_slots_t default_coin_slot; + uint8_t nr_coin_slots = data[1]; + + ResponseBuffer.push_back(ReportCode::Handled); + + for (int i = 0; i < nr_coin_slots; i++) { + const uint8_t bytesPerCoinSlot = 2; + for (int j = 0; j < bytesPerCoinSlot; j++) { + // If a title asks for more coin slots than we support, pad with dummy data + jvs_coin_slots_t &coin_slot = (i >= JVS_MAX_COINS) ? default_coin_slot : Inputs.coins[i]; + uint8_t value + = (j == 0) ? coin_slot.GetByte0() + : (j == 1) ? coin_slot.GetByte1() + : 0; // Pad any remaining bytes with 0, as we don't have that many inputs available + ResponseBuffer.push_back(value); + } + } + + return 1; +} + +int JvsIo::Jvs_Command_22_ReadAnalogInputs(uint8_t* data) +{ + static jvs_analog_input_t default_analog; + uint8_t nr_analog_inputs = data[1]; + + ResponseBuffer.push_back(ReportCode::Handled); + + for (int i = 0; i < nr_analog_inputs; i++) { + const uint8_t bytesPerAnalogInput = 2; + for (int j = 0; j < bytesPerAnalogInput; j++) { + // If a title asks for more analog input than we support, pad with dummy data + jvs_analog_input_t &analog_input = (i >= JVS_MAX_ANALOG) ? default_analog : Inputs.analog[i]; + uint8_t value + = (j == 0) ? analog_input.GetByte0() + : (j == 1) ? analog_input.GetByte1() + : 0; // Pad any remaining bytes with 0, as we don't have that many inputs available + ResponseBuffer.push_back(value); + } + } + + return 1; +} + +int JvsIo::Jvs_Command_32_GeneralPurposeOutput(uint8_t* data) +{ + uint8_t banks = data[1]; + + ResponseBuffer.push_back(ReportCode::Handled); + + // TODO: Handle output + + // Input data size is 1 byte indicating the number of banks, followed by one byte per bank + return 1 + banks; +} + +uint8_t JvsIo::GetByte(uint8_t* &buffer) +{ + uint8_t value = *buffer++; +#ifdef DEBUG_JVS_PACKETS + printf(" %02X", value); +#endif + return value; +} + +uint8_t JvsIo::GetEscapedByte(uint8_t* &buffer) +{ + uint8_t value = GetByte(buffer); + + // Special case: 0xD0 is an exception byte that actually returns the next byte + 1 + if (value == ESCAPE_BYTE) { + value = GetByte(buffer) + 1; + } + + return value; +} + +void JvsIo::HandlePacket(jvs_packet_header_t* header, std::vector& packet) +{ + // It's possible for a JVS packet to contain multiple commands, so we must iterate through it + ResponseBuffer.push_back(StatusCode::StatusOkay); // Assume we'll handle the command just fine + for (size_t i = 0; i < packet.size(); i++) { + + BroadcastPacket = packet[i] >= 0xF0; // Set a flag when broadcast packet + + uint8_t* command_data = &packet[i]; + switch (packet[i]) { + // Broadcast Commands + case 0xF0: i += Jvs_Command_F0_Reset(command_data); break; + case 0xF1: i += Jvs_Command_F1_SetDeviceId(command_data); break; + // Init Commands + case 0x10: i += Jvs_Command_10_GetBoardId(); break; + case 0x11: i += Jvs_Command_11_GetCommandFormat(); break; + case 0x12: i += Jvs_Command_12_GetJvsRevision(); break; + case 0x13: i += Jvs_Command_13_GetCommunicationVersion(); break; + case 0x14: i += Jvs_Command_14_GetCapabilities(); break; + case 0x20: i += Jvs_Command_20_ReadSwitchInputs(command_data); break; + case 0x21: i += Jvs_Command_21_ReadCoinInputs(command_data); break; + case 0x22: i += Jvs_Command_22_ReadAnalogInputs(command_data); break; + case 0x32: i += Jvs_Command_32_GeneralPurposeOutput(command_data); break; + default: + // Overwrite the verly-optimistic StatusCode::StatusOkay with Status::Unsupported command + // Don't process any further commands. Existing processed commands must still return their responses. + ResponseBuffer[0] = StatusCode::UnsupportedCommand; + printf("JvsIo::HandlePacket: Unhandled Command %02X\n", packet[i]); + return; + } + } +} + +size_t JvsIo::SendPacket(uint8_t* buffer) +{ + // Remember where the buffer started (so we can calculate the number of bytes we've handled) + uint8_t* buffer_start = buffer; + + // Scan the packet header + jvs_packet_header_t header; + + // First, read the sync byte +#ifdef DEBUG_JVS_PACKETS + printf("JvsIo::SendPacket:"); +#endif + header.sync = GetByte(buffer); // Do not unescape the sync-byte! + if (header.sync != SYNC_BYTE) { +#ifdef DEBUG_JVS_PACKETS + printf(" [Missing SYNC_BYTE!]\n"); +#endif + // If it's wrong, return we've processed (actually, skipped) one byte + return 1; + } + + // Read the target and count bytes + header.target = GetEscapedByte(buffer); + header.count = GetEscapedByte(buffer); + + // Calculate the checksum + uint8_t actual_checksum = header.target + header.count; + + // Decode the payload data + std::vector packet; + for (int i = 0; i < header.count - 1; i++) { // Note : -1 to avoid adding the checksum byte to the packet + uint8_t value = GetEscapedByte(buffer); + packet.push_back(value); + actual_checksum += value; + } + + // Read the checksum from the last byte + uint8_t packet_checksum = GetEscapedByte(buffer); +#ifdef DEBUG_JVS_PACKETS + printf("\n"); +#endif + + // Verify checksum - skip packet if invalid + ResponseBuffer.clear(); + if (packet_checksum != actual_checksum) { + ResponseBuffer.push_back(StatusCode::ChecksumError); + } else { + // If the packet was intended for us, we need to handle it + if (header.target == TARGET_BROADCAST || header.target == DeviceId) { + HandlePacket(&header, packet); + } + } + + // Calculate and return the total packet size including header + size_t total_packet_size = buffer - buffer_start; + + return total_packet_size; +} + +void JvsIo::SendByte(uint8_t* &buffer, uint8_t value) +{ + *buffer++ = value; +} + +void JvsIo::SendEscapedByte(uint8_t* &buffer, uint8_t value) +{ + // Special case: Send an exception byte followed by value - 1 + if (value == SYNC_BYTE || value == ESCAPE_BYTE) { + SendByte(buffer, ESCAPE_BYTE); + value--; + } + + SendByte(buffer, value); +} + +size_t JvsIo::ReceivePacket(uint8_t* buffer) +{ + if (ResponseBuffer.empty()) { + return 0; + } + + // Build a JVS response packet containing the payload + jvs_packet_header_t header; + header.sync = SYNC_BYTE; + header.target = TARGET_MASTER_DEVICE; + header.count = (uint8_t)ResponseBuffer.size() + 1; // Set data size to payload + 1 checksum byte + // TODO : What if count overflows (meaning : responses are bigger than 255 bytes); Should we split it over multiple packets?? + + // Remember where the buffer started (so we can calculate the number of bytes we've send) + uint8_t* buffer_start = buffer; + + // Send the header bytes + SendByte(buffer, header.sync); // Do not escape the sync byte! + SendEscapedByte(buffer, header.target); + SendEscapedByte(buffer, header.count); + + // Calculate the checksum + uint8_t packet_checksum = header.target + header.count; + + // Encode the payload data + for (size_t i = 0; i < ResponseBuffer.size(); i++) { + uint8_t value = ResponseBuffer[i]; + SendEscapedByte(buffer, value); + packet_checksum += value; + } + + // Write the checksum to the last byte + SendEscapedByte(buffer, packet_checksum); + + ResponseBuffer.clear(); + + // Calculate an return the total packet size including header + size_t total_packet_size = buffer - buffer_start; +#ifdef DEBUG_JVS_PACKETS + + printf("JvsIo::ReceivePacket:"); + for (size_t i = 0; i < total_packet_size; i++) { + printf(" %02X", buffer_start[i]); + } + + printf("\n"); +#endif + return total_packet_size; +} diff --git a/src/devices/chihiro/JvsIo.h b/src/devices/chihiro/JvsIo.h new file mode 100644 index 000000000..4f519e4ce --- /dev/null +++ b/src/devices/chihiro/JvsIo.h @@ -0,0 +1,215 @@ +// ****************************************************************** +// * This file is part of the Cxbx project. +// * +// * Cxbx and Cxbe are free software; you can redistribute them +// * and/or modify them under the terms of the GNU General Public +// * License as published by the Free Software Foundation; either +// * version 2 of the license, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU General Public License for more details. +// * +// * You should have recieved a copy of the GNU General Public License +// * along with this program; see the file COPYING. +// * If not, write to the Free Software Foundation, Inc., +// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA. +// * +// * (c) 2019 Luke Usher +// * +// * All rights reserved +// * +// ****************************************************************** + +#ifndef JVSIO_H +#define JVSIO_H + +#include +#include +#include + +typedef struct { + uint8_t sync; + uint8_t target; + uint8_t count; +} jvs_packet_header_t; + +#define JVS_MAX_PLAYERS (2) +#define JVS_MAX_ANALOG (8) +#define JVS_MAX_COINS (JVS_MAX_PLAYERS) + +typedef struct _jvs_switch_player_inputs_t { + bool start = false; + bool service = false; + bool up = false; + bool down = false; + bool left = false; + bool right = false; + bool button[7] = { false }; + + uint8_t GetByte0() { + uint8_t value = 0; + value |= start ? 1 << 7 : 0; + value |= service ? 1 << 6 : 0; + value |= up ? 1 << 5 : 0; + value |= down ? 1 << 4 : 0; + value |= left ? 1 << 3 : 0; + value |= right ? 1 << 2 : 0; + value |= button[0] ? 1 << 1 : 0; + value |= button[1] ? 1 << 0 : 0; + return value; + } + + uint8_t GetByte1() { + uint8_t value = 0; + value |= button[2] ? 1 << 7 : 0; + value |= button[3] ? 1 << 6 : 0; + value |= button[4] ? 1 << 5 : 0; + value |= button[5] ? 1 << 4 : 0; + value |= button[6] ? 1 << 3 : 0; + return value; + } +} jvs_switch_player_inputs_t; + +typedef struct _jvs_switch_system_inputs_t { + bool test = false; + bool tilt1 = false; + bool tilt2 = false; + bool tilt3 = false; + + uint8_t GetByte0() { + uint8_t value = 0; + value |= test ? 1 << 7 : 0; + value |= tilt1 ? 1 << 6 : 0; + value |= tilt2 ? 1 << 5 : 0; + value |= tilt3 ? 1 << 4 : 0; + return value; + } +} jvs_switch_system_inputs_t; + +typedef struct { + jvs_switch_system_inputs_t system; + jvs_switch_player_inputs_t player[JVS_MAX_PLAYERS]; +} jvs_switch_inputs_t; + +typedef struct _jvs_analog_input_t { + uint16_t value = 0x8000; + + uint8_t GetByte0() { + return (value >> 8) & 0xFF; + } + + uint8_t GetByte1() { + return value & 0xFF; + } +} jvs_analog_input_t; + +typedef struct _jvs_coin_slots_t { + uint16_t coins = 0; + uint8_t status = 0; + + uint8_t GetByte0() { + uint8_t value = 0; + value |= (status << 6) & 0xC0; + value |= (coins >> 8) & 0x3F; + return value; + } + + uint8_t GetByte1() { + return coins & 0xFF; + } +} jvs_coin_slots_t; + +typedef struct { + jvs_switch_inputs_t switches; + jvs_analog_input_t analog[JVS_MAX_ANALOG]; + jvs_coin_slots_t coins[JVS_MAX_COINS]; +} jvs_input_states_t; + +class JvsIo +{ +public: + JvsIo(uint8_t *sense); + size_t SendPacket(uint8_t *buffer); + size_t ReceivePacket(uint8_t *buffer); + uint8_t GetDeviceId(); + void Update(); + +private: + const uint8_t SYNC_BYTE = 0xE0; + const uint8_t ESCAPE_BYTE = 0xD0; + + const uint8_t TARGET_MASTER_DEVICE = 0x00; + const uint8_t TARGET_BROADCAST = 0xFF; + + uint8_t GetByte(uint8_t *&buffer); + uint8_t GetEscapedByte(uint8_t *&buffer); + void HandlePacket(jvs_packet_header_t *header, std::vector &packet); + + void SendByte(uint8_t *&buffer, uint8_t value); + void SendEscapedByte(uint8_t *&buffer, uint8_t value); + + enum StatusCode { + StatusOkay = 1, + UnsupportedCommand = 2, + ChecksumError = 3, + AcknowledgeOverflow = 4, + }; + + enum ReportCode { + Handled = 1, + NotEnoughParameters = 2, + InvalidParameter = 3, + Busy = 4, + }; + + enum CapabilityCode { + EndOfCapabilities = 0x00, + // Input capabilities : + PlayerSwitchButtonSets = 0x01, + CoinSlots = 0x02, + AnalogInputs = 0x03, + RotaryInputs = 0x04, // Params : JVS_MAX_ROTARY, 0, 0 + KeycodeInputs = 0x05, + ScreenPointerInputs = 0x06, // Params : Xbits, Ybits, JVS_MAX_POINTERS + SwitchInputs = 0x07, + // Output capabilities : + CardSystem = 0x10, // Params : JVS_MAX_CARDS, 0, 0 + MedalHopper = 0x11, // Params : max?, 0, 0 + GeneralPurposeOutputs = 0x12, // Params : number of outputs, 0, 0 + AnalogOutput = 0x13, // Params : channels, 0, 0 + CharacterOutput = 0x14, // Params : width, height, type + BackupData = 0x15, + }; + + // Commands + // These return the additional param bytes used + int Jvs_Command_F0_Reset(uint8_t *data); + int Jvs_Command_F1_SetDeviceId(uint8_t *data); + int Jvs_Command_10_GetBoardId(); + int Jvs_Command_11_GetCommandFormat(); + int Jvs_Command_12_GetJvsRevision(); + int Jvs_Command_13_GetCommunicationVersion(); + int Jvs_Command_14_GetCapabilities(); + int Jvs_Command_20_ReadSwitchInputs(uint8_t *data); + int Jvs_Command_21_ReadCoinInputs(uint8_t *data); + int Jvs_Command_22_ReadAnalogInputs(uint8_t *data); + int Jvs_Command_32_GeneralPurposeOutput(uint8_t *data); + + bool BroadcastPacket; // Set when the last command was a broadcast + uint8_t *pSense = nullptr; // Pointer to Sense line + uint8_t DeviceId = 0; // Device ID assigned by running title + std::vector ResponseBuffer; // Command Response + + // Device info + uint8_t CommandFormatRevision; + uint8_t JvsVersion; + uint8_t CommunicationVersion; + std::string BoardID; + jvs_input_states_t Inputs; +}; + +extern JvsIo *g_pJvsIo; + +#endif diff --git a/src/devices/chihiro/MediaBoard.cpp b/src/devices/chihiro/MediaBoard.cpp new file mode 100644 index 000000000..122f90512 --- /dev/null +++ b/src/devices/chihiro/MediaBoard.cpp @@ -0,0 +1,160 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +// ****************************************************************** +// * src->devices->chihiro->MediaBoard.cpp +// * +// * This file is part of the Cxbx project. +// * +// * Cxbx and Cxbe are free software; you can redistribute them +// * and/or modify them under the terms of the GNU General Public +// * License as published by the Free Software Foundation; either +// * version 2 of the license, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU General Public License for more details. +// * +// * You should have recieved a copy of the GNU General Public License +// * along with this program; see the file COPYING. +// * If not, write to the Free Software Foundation, Inc., +// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA. +// * +// * (c) 2019 Luke Usher +// * +// * All rights reserved +// * +// ****************************************************************** + +#include "MediaBoard.h" +#include +#include + +#define _XBOXKRNL_DEFEXTRN_ +#define LOG_PREFIX CXBXR_MODULE::JVS // TODO: XBAM + + +#include +#include "core\kernel\init\\CxbxKrnl.h" +#include "core\kernel\exports\EmuKrnl.h" // for HalSystemInterrupts + +chihiro_bootid &MediaBoard::GetBootId() +{ + return BootID; +} + +void MediaBoard::SetMountPath(std::string path) +{ + m_MountPath = path; + + // Load Boot.id from file + FILE* bootidFile = fopen((path+"/boot.id").c_str(), "rb"); + if (bootidFile == nullptr) { + CxbxrAbort("Could not open Chihiro boot.id"); + } + fread(&BootID, 1, sizeof(chihiro_bootid), bootidFile); + fclose(bootidFile); +} + +uint32_t MediaBoard::LpcRead(uint32_t addr, int size) +{ + switch (addr) { + case 0x401E: return 0x0317; // Firmware Version Number + case 0x4020: return 0x00A0; // XBAM String (SEGABOOT reports Media Board is not present if these values change) + case 0x4022: return 0x4258; // Continued + case 0x4024: return 0x4D41; // Continued + case 0x40F0: return 0x0000; // Media Board Type (Type-1 vs Type-3), 0x0000 = Type-1, 0x0100 = Type-3 + case 0x40F4: return 0x03; // 1GB + } + + printf("MediaBoard::LpcRead: Unknown Addr %08X\n", addr); + return 0; +} + +void MediaBoard::LpcWrite(uint32_t addr, uint32_t value, int size) +{ + switch (addr) { + case 0x40E1: HalSystemInterrupts[10].Assert(false); break; + default: + printf("MediaBoard::LpcWrite: Unknown Addr %08X = %08X\n", addr, value); + break; + } +} + +void MediaBoard::ComRead(uint32_t offset, void* buffer, uint32_t length) +{ + // Copy the current read buffer to the output + memcpy(buffer, readBuffer, 0x20); +} + +void MediaBoard::ComWrite(uint32_t offset, void* buffer, uint32_t length) +{ + // Instant replies cause race conditions, software seems to expect at least a little delay + Sleep(100); + + if (offset == 0x900000) { // Some kind of reset? + memcpy(readBuffer, buffer, 0x20); + return; + } else if (offset == 0x900200) { // Command Sector + // Copy the written data to our internal, so we don't trash the original data + memcpy(writeBuffer, buffer, 0x20); + + // Create accessor pointers + auto inputBuffer16 = (uint16_t*)writeBuffer; + auto inputBuffer32 = (uint32_t*)writeBuffer; + auto outputBuffer16 = (uint16_t*)readBuffer; + auto outputBuffer32 = (uint32_t*)readBuffer; + + // If no command word was specified, do nothing + if (inputBuffer16[0] == 0) { + return; + } + + // First word of output gets set to first word of the input, second word gets OR'D with ACK + outputBuffer16[0] = inputBuffer16[0]; + outputBuffer16[1] = inputBuffer16[1] | 0x8000; // ACK? + + // Read the given Command and handle it + uint32_t command = inputBuffer16[1]; + switch (command) { + case MB_CMD_DIMM_SIZE: + outputBuffer32[1] = 1024 * ONE_MB; + break; + case MB_CMD_STATUS: + outputBuffer32[1] = MB_STATUS_READY; + outputBuffer32[2] = 100; // Load/Test Percentage (0-100) + break; + case MB_CMD_FIRMWARE_VERSION: + outputBuffer32[1] = 0x0317; + break; + case MB_CMD_SYSTEM_TYPE: + outputBuffer32[1] = MB_SYSTEM_TYPE_DEVELOPER | MB_SYSTEM_TYPE_GDROM; + break; + case MB_CMD_SERIAL_NUMBER: + memcpy(&outputBuffer32[1], "A89E-25A47354512", 17); + break; + case MB_CMD_HARDWARE_TEST: { + uint32_t testType = inputBuffer32[1]; + xbox::addr_xt resultWritePtr = inputBuffer32[2]; + outputBuffer32[1] = inputBuffer32[1]; + + printf("Perform Test Type %X, place result at %08X\n", testType, resultWritePtr); + + // For now, just pretend we did the test and was successful + // TODO: How to report percentage? Get's stuck on "CHECKING 0% but still shows "TEST OK" + memcpy((void*)resultWritePtr, "TEST OK", 8); + } break; + default: printf("Unhandled MediaBoard Command: %04X\n", command); + } + + // Clear the command bytes + inputBuffer16[0] = 0; + inputBuffer16[1] = 0; + + // Trigger LPC Interrupt + HalSystemInterrupts[10].Assert(true); + return; + } + + printf("Unhandled MediaBoard mbcom: offset %08X\n", offset); +} diff --git a/src/devices/chihiro/MediaBoard.h b/src/devices/chihiro/MediaBoard.h new file mode 100644 index 000000000..84ed4cf20 --- /dev/null +++ b/src/devices/chihiro/MediaBoard.h @@ -0,0 +1,98 @@ +// ****************************************************************** +// * src->devices->chihiro->MediaBoard.h +// * +// * This file is part of the Cxbx project. +// * +// * Cxbx and Cxbe are free software; you can redistribute them +// * and/or modify them under the terms of the GNU General Public +// * License as published by the Free Software Foundation; either +// * version 2 of the license, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU General Public License for more details. +// * +// * You should have recieved a copy of the GNU General Public License +// * along with this program; see the file COPYING. +// * If not, write to the Free Software Foundation, Inc., +// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA. +// * +// * (c) 2019 Luke Usher +// * +// * All rights reserved +// * +// ****************************************************************** + +#ifndef MEDIABOARD_H +#define MEDIABOARD_H + +#include +#include + +#define MB_CMD_DIMM_SIZE 0x0001 +#define MB_CMD_STATUS 0x0100 + #define MB_STATUS_INIT 0 + #define MB_STATUS_CHECKING_NETWORK 1 + #define MB_STATUS_SYSTEM_DISC 2 + #define MB_STATUS_TESTING 3 + #define MB_STATUS_LOADING 4 + #define MB_STATUS_READY 5 + #define MB_STATUS_ERROR 6 +#define MB_CMD_FIRMWARE_VERSION 0x0101 +#define MB_CMD_SYSTEM_TYPE 0x0102 + #define MB_SYSTEM_TYPE_DEVELOPER 0x8000 + #define MB_SYSTEM_TYPE_GDROM 0x0001 +#define MB_CMD_SERIAL_NUMBER 0x0103 +#define MB_CMD_HARDWARE_TEST 0x0301 + +#define MB_CHIHIRO_REGION_FLAG_JAPAN 0x2 +#define MB_CHIHIRO_REGION_FLAG_USA 0x4 +#define MB_CHIHIRO_REGION_FLAG_EXPORT 0x8 + +typedef struct { + char magic[4]; // 0x00 (Always BTID) + uint32_t unknown0[3]; + uint32_t unknown1[4]; + char mediaboardType[4]; // 0x20 (XBAM for Chihiro) + uint32_t unknown2; + uint16_t year; // 0x28 + uint8_t month; // 0x2A + uint8_t day; // 0x2B + uint8_t videoMode; // 0x2C unknown bitmask, resolutions + horizontal/vertical + uint8_t unknown3; + uint8_t type3Compatible; // 0x2E (Type-3 compatibile titles have this set to 1) + uint8_t unknown4; + char gameId[8]; // 0x30 + uint32_t regionFlags; // 0x38 + uint32_t unknown6[9]; + char manufacturer[0x20]; // 0x60 + char gameName[0x20]; // 0x80 + char gameExecutable[0x20]; // 0xA0 + char testExecutable[0x20]; // 0xC0 + char creditTypes[8][0x20]; // 0xE0 +} chihiro_bootid; + +class MediaBoard +{ +public: + void SetMountPath(std::string path); + + // LPC IO handlers + uint32_t LpcRead(uint32_t addr, int size); + void LpcWrite(uint32_t addr, uint32_t value, int size); + + // Mbcom partition handlers + void ComRead(uint32_t offset, void* buffer, uint32_t length); + void ComWrite(uint32_t offset, void* buffer, uint32_t length); + chihiro_bootid &GetBootId(); +private: + uint8_t readBuffer[512]; + uint8_t writeBuffer[512]; + + std::string m_MountPath; + + chihiro_bootid BootID; +}; + +#endif diff --git a/src/devices/network/NVNetDevice.cpp b/src/devices/network/NVNetDevice.cpp index 10bde6a27..8b6b62740 100644 --- a/src/devices/network/NVNetDevice.cpp +++ b/src/devices/network/NVNetDevice.cpp @@ -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 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() diff --git a/src/devices/usb/OHCI.cpp b/src/devices/usb/OHCI.cpp index 875aae5d1..3e0253ba2 100644 --- a/src/devices/usb/OHCI.cpp +++ b/src/devices/usb/OHCI.cpp @@ -279,11 +279,6 @@ OHCI::OHCI(USBDevice* UsbObj) OHCI_StateReset(); } -void OHCI::OHCI_FrameBoundaryWrapper(void* pVoid) -{ - static_cast(pVoid)->OHCI_FrameBoundaryWorker(); -} - void OHCI::OHCI_FrameBoundaryWorker() { OHCI_HCCA hcca; @@ -358,7 +353,7 @@ void OHCI::OHCI_FrameBoundaryWorker() } // Do SOF stuff here - OHCI_SOF(false); + OHCI_SOF(); // Writeback HCCA if (OHCI_WriteHCCA(m_Registers.HcHCCA, &hcca)) { @@ -877,32 +872,23 @@ void OHCI::OHCI_StateReset() void OHCI::OHCI_BusStart() { // Create the EOF timer. - m_pEOFtimer = Timer_Create(OHCI_FrameBoundaryWrapper, this, "", false); + m_pEOFtimer = true; EmuLog(LOG_LEVEL::DEBUG, "Operational event"); // SOF event - OHCI_SOF(true); + OHCI_SOF(); } void OHCI::OHCI_BusStop() { - if (m_pEOFtimer) { - // Delete existing EOF timer - Timer_Exit(m_pEOFtimer); - } - m_pEOFtimer = nullptr; + m_pEOFtimer = false; } -void OHCI::OHCI_SOF(bool bCreate) +void OHCI::OHCI_SOF() { // set current SOF time - m_SOFtime = GetTime_NS(m_pEOFtimer); - - // make timer expire at SOF + 1 ms from now - if (bCreate) { - Timer_Start(m_pEOFtimer, m_UsbFrameTime); - } + m_SOFtime = get_now(); OHCI_SetInterrupt(OHCI_INTR_SF); } @@ -1254,6 +1240,23 @@ void OHCI::OHCI_WriteRegister(xbox::addr_xt Addr, uint32_t Value) } } +uint64_t OHCI::OHCI_next(uint64_t now) +{ + if (m_pEOFtimer) { + constexpr uint64_t ohci_period = 1000; + uint64_t next = m_SOFtime + ohci_period; + + if (now >= next) { + OHCI_FrameBoundaryWorker(); + return ohci_period; + } + + return m_SOFtime + ohci_period - now; // time remaining until EOF + } + + return -1; +} + void OHCI::OHCI_UpdateInterrupt() { if ((m_Registers.HcInterrupt & OHCI_INTR_MIE) && (m_Registers.HcInterruptStatus & m_Registers.HcInterrupt)) { @@ -1278,7 +1281,7 @@ uint32_t OHCI::OHCI_GetFrameRemaining() } // Being in USB operational state guarantees that m_pEOFtimer and m_SOFtime were set already - ticks = GetTime_NS(m_pEOFtimer) - m_SOFtime; + ticks = get_now() - m_SOFtime; // Avoid Muldiv64 if possible if (ticks >= m_UsbFrameTime) { diff --git a/src/devices/usb/OHCI.h b/src/devices/usb/OHCI.h index 92119ba0f..3555667df 100644 --- a/src/devices/usb/OHCI.h +++ b/src/devices/usb/OHCI.h @@ -145,6 +145,10 @@ class OHCI uint32_t OHCI_ReadRegister(xbox::addr_xt Addr); // write a register void OHCI_WriteRegister(xbox::addr_xt Addr, uint32_t Value); + // calculates when the next EOF is due + uint64_t OHCI_next(uint64_t now); + // EOF callback function + void OHCI_FrameBoundaryWorker(); private: @@ -153,7 +157,7 @@ class OHCI // all the registers available in the OHCI standard OHCI_Registers m_Registers; // end-of-frame timer - TimerObject* m_pEOFtimer = nullptr; + bool m_pEOFtimer = false; // time at which a SOF was sent uint64_t m_SOFtime; // the duration of a usb frame @@ -173,10 +177,6 @@ class OHCI // indicates if there is a pending asynchronous packet to process int m_AsyncComplete = 0; - // EOF callback wrapper - static void OHCI_FrameBoundaryWrapper(void* pVoid); - // EOF callback function - void OHCI_FrameBoundaryWorker(); // inform the HCD that we got a problem here... void OHCI_FatalError(); // initialize packet struct @@ -189,8 +189,8 @@ class OHCI void OHCI_BusStart(); // stop sending SOF tokens across the usb bus void OHCI_BusStop(); - // generate a SOF event, and start a timer for EOF - void OHCI_SOF(bool bCreate); + // generate a SOF event + void OHCI_SOF(); // change interrupt status void OHCI_UpdateInterrupt(); // fire an interrupt diff --git a/src/devices/video/EmuNV2A_PRAMDAC.cpp b/src/devices/video/EmuNV2A_PRAMDAC.cpp index 768811e3a..c0e9db80f 100644 --- a/src/devices/video/EmuNV2A_PRAMDAC.cpp +++ b/src/devices/video/EmuNV2A_PRAMDAC.cpp @@ -82,6 +82,7 @@ DEVICE_WRITE32(PRAMDAC) } else { d->pramdac.core_clock_freq = (NV2A_CRYSTAL_FREQ * n) / (1 << p) / m; + d->ptimer_period = ((uint64_t(d->ptimer.alarm_time >> 5) * SCALE_S_IN_US) / d->pramdac.core_clock_freq); } break; diff --git a/src/devices/video/EmuNV2A_PTIMER.cpp b/src/devices/video/EmuNV2A_PTIMER.cpp index d6f6f42f5..5711537b8 100644 --- a/src/devices/video/EmuNV2A_PTIMER.cpp +++ b/src/devices/video/EmuNV2A_PTIMER.cpp @@ -35,17 +35,13 @@ #include "common\util\CxbxUtil.h" -#define NANOSECONDS_PER_SECOND 1000000000 /* PTIMER - time measurement and time-based alarms */ -static uint64_t ptimer_get_clock(NV2AState * d) +static uint64_t ptimer_get_clock(NV2AState *d) { - // Get time in nanoseconds - uint64_t time = std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count(); - - return Muldiv64(Muldiv64(time, + return Muldiv64(Muldiv64(get_now(), (uint32_t)d->pramdac.core_clock_freq, // TODO : Research how this can be updated to accept uint64_t - NANOSECONDS_PER_SECOND), // Was CLOCKS_PER_SEC + SCALE_S_IN_US), // Was CLOCKS_PER_SEC d->ptimer.denominator, d->ptimer.numerator); } @@ -91,6 +87,13 @@ DEVICE_WRITE32(PTIMER) break; case NV_PTIMER_INTR_EN_0: d->ptimer.enabled_interrupts = value; + if (d->ptimer.enabled_interrupts & NV_PTIMER_INTR_EN_0_ALARM) { + d->ptimer_last = get_now(); + d->ptimer_active = true; + } + else if ((d->ptimer.enabled_interrupts & NV_PTIMER_INTR_EN_0_ALARM) == 0) { + d->ptimer_active = false; + } update_irq(d); break; case NV_PTIMER_DENOMINATOR: @@ -101,6 +104,7 @@ DEVICE_WRITE32(PTIMER) break; case NV_PTIMER_ALARM_0: d->ptimer.alarm_time = value; + d->ptimer_period = ((uint64_t(d->ptimer.alarm_time >> 5) * SCALE_S_IN_US) / d->pramdac.core_clock_freq); break; default: //DEVICE_WRITE32_REG(ptimer); // Was : DEBUG_WRITE32_UNHANDLED(PTIMER); diff --git a/src/devices/video/nv2a.cpp b/src/devices/video/nv2a.cpp index 6c0e62aa0..a2a13397e 100644 --- a/src/devices/video/nv2a.cpp +++ b/src/devices/video/nv2a.cpp @@ -51,6 +51,7 @@ #include "core\kernel\init\CxbxKrnl.h" // For XBOX_MEMORY_SIZE, DWORD, etc #include "core\kernel\support\Emu.h" +#include "core\kernel\support\NativeHandle.h" #include "core\kernel\exports\EmuKrnl.h" #include #include @@ -58,6 +59,7 @@ #include "core\hle\Intercept.hpp" #include "common/win32/Threads.h" #include "Logging.h" +#include "Timer.h" #include "vga.h" #include "nv2a.h" // For NV2AState @@ -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 +void nv2a_vblank_interrupt(void *opaque) { - g_AffinityPolicy->SetAffinityOther(); - CxbxSetThreadName("Cxbx NV2A VBLANK"); - auto nextVBlankTime = GetNextVBlankTime(); + NV2AState *d = static_cast(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; + } + else { + d->vblank_cb = nv2a_vblank_interrupt; + } 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; +} diff --git a/src/devices/video/nv2a.h b/src/devices/video/nv2a.h index 43b64c052..03fc3c81a 100644 --- a/src/devices/video/nv2a.h +++ b/src/devices/video/nv2a.h @@ -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; }; diff --git a/src/devices/video/nv2a_int.h b/src/devices/video/nv2a_int.h index 22630b146..ceef33c73 100644 --- a/src/devices/video/nv2a_int.h +++ b/src/devices/video/nv2a_int.h @@ -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; diff --git a/src/devices/x86/EmuX86.cpp b/src/devices/x86/EmuX86.cpp index 85ecc7a4b..3e3a73ac3 100644 --- a/src/devices/x86/EmuX86.cpp +++ b/src/devices/x86/EmuX86.cpp @@ -57,10 +57,15 @@ extern std::atomic_bool g_bEnableAllInterrupts; static int field_pin = 0; -static thread_local bool g_tls_isEmuX86Managed; - uint32_t EmuX86_IORead(xbox::addr_xt addr, int size) { + // If we are running a Chihiro game, emulate the Chihiro LPC device + if (g_bIsChihiro) { + if (addr >= 0x4000 && addr <= 0x40FF) { + return g_MediaBoard->LpcRead(addr, size); + } + } + switch (addr) { case 0x8008: { // TODO : Move 0x8008 TIMER to a device if (size == sizeof(uint32_t)) { @@ -95,6 +100,14 @@ uint32_t EmuX86_IORead(xbox::addr_xt addr, int size) void EmuX86_IOWrite(xbox::addr_xt addr, uint32_t value, int size) { + // If we are running a Chihiro game, emulate the Chihiro LPC device + if (g_bIsChihiro) { + if (addr >= 0x4000 && addr <= 0x40FF) { + g_MediaBoard->LpcWrite(addr, value, size); + return; + } + } + // Pass the IO Write to the PCI Bus, this will handle devices with BARs set to IO addresses if (g_PCIBus->IOWrite(addr, value, size)) { return; @@ -197,11 +210,8 @@ uint32_t EmuX86_Read(xbox::addr_xt addr, int size) return value; } - // EmuX86 is not suppose to do direct read to host memory and should be handle from - // redirect from above statements. If it doesn't meet any requirement, then should be - // handle as possible fatal crash instead of return corrupt value. - g_tls_isEmuX86Managed = false; - + // EmuX86 should not directly access host memory. + EmuLog(LOG_LEVEL::WARNING, "EmuX86_Read(0x%08X, %d) [Unhandled]", addr, size); return 0; } @@ -223,10 +233,8 @@ void EmuX86_Write(xbox::addr_xt addr, uint32_t value, int size) return; } - // EmuX86 is not suppose to do direct write to host memory and should be handle from - // redirect from above statements. If it doesn't meet any requirement, then should be - // handle as possible fatal crash instead of set corrupt value. - g_tls_isEmuX86Managed = false; + // EmuX86 should not directly access host memory. + EmuLog(LOG_LEVEL::WARNING, "EmuX86_Write(0x%08X, 0x%08X, %d) [Unhandled]", addr, value, size); } int ContextRecordOffsetByRegisterType[/*_RegisterType*/R_DR7 + 1] = { 0 }; @@ -2928,7 +2936,6 @@ bool EmuX86_DecodeException(LPEXCEPTION_POINTERS e) // However, if for any reason, an opcode operand cannot be read from or written to, // that case may be logged, but it shouldn't fail the opcode handler. _DInst info; - g_tls_isEmuX86Managed = true; DWORD StartingEip = e->ContextRecord->Eip; EmuLog(LOG_LEVEL::DEBUG, "Starting instruction emulation from 0x%08X", e->ContextRecord->Eip); @@ -3294,15 +3301,11 @@ bool EmuX86_DecodeException(LPEXCEPTION_POINTERS e) return true; } // switch info.opcode - if (g_tls_isEmuX86Managed) { - e->ContextRecord->Eip += info.size; - } - else { - break; - } + + e->ContextRecord->Eip += info.size; } // while true - return g_tls_isEmuX86Managed; + return true; opcode_error: EmuLog(LOG_LEVEL::WARNING, "0x%08X: Error while handling instruction %s (%u)", e->ContextRecord->Eip, Distorm_OpcodeString(info.opcode), info.opcode); diff --git a/src/gui/WndMain.cpp b/src/gui/WndMain.cpp index 28f071c53..6f26b2628 100644 --- a/src/gui/WndMain.cpp +++ b/src/gui/WndMain.cpp @@ -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(m_hwndChild)); + return DefWindowProc(hwnd, uMsg, wParam, lParam); + } + break; + case WM_TIMER: { switch (wParam) diff --git a/src/gui/resource/ResCxbx.h b/src/gui/resource/ResCxbx.h index 927737d14..eff2e721a 100644 --- a/src/gui/resource/ResCxbx.h +++ b/src/gui/resource/ResCxbx.h @@ -353,6 +353,7 @@ #define ID_SETTINGS_EXPERIMENTAL 40113 #define ID_SETTINGS_IGNOREINVALIDXBESIG 40114 #define ID_SETTINGS_IGNOREINVALIDXBESEC 40115 +#define ID_SYNC_TIME_CHANGE 40116 #define IDC_STATIC -1 // Next default values for new objects @@ -360,7 +361,7 @@ #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 139 -#define _APS_NEXT_COMMAND_VALUE 40116 +#define _APS_NEXT_COMMAND_VALUE 40117 #define _APS_NEXT_CONTROL_VALUE 1308 #define _APS_NEXT_SYMED_VALUE 109 #endif