From 0039bb639493b2d1e2764cae380311ba8e87704b Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 18 Dec 2018 03:33:36 -0200 Subject: Refactor SVC handler (#540) * Refactor SVC handler * Get rid of KernelErr * Split kernel code files into multiple folders --- Ryujinx.HLE/HOS/GlobalStateTable.cs | 2 +- Ryujinx.HLE/HOS/Horizon.cs | 5 +- Ryujinx.HLE/HOS/Ipc/IpcHandler.cs | 8 +- Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs | 10 - Ryujinx.HLE/HOS/Kernel/ArbitrationType.cs | 9 - .../HOS/Kernel/Common/IKFutureSchedulerObject.cs | 7 + Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs | 42 + Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs | 147 ++ .../HOS/Kernel/Common/KSynchronizationObject.cs | 35 + Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs | 143 ++ Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs | 137 ++ Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs | 32 + Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs | 72 + Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs | 13 + Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs | 128 + Ryujinx.HLE/HOS/Kernel/DramMemoryMap.cs | 15 - Ryujinx.HLE/HOS/Kernel/HleCoreManager.cs | 66 - Ryujinx.HLE/HOS/Kernel/HleProcessDebugger.cs | 310 --- Ryujinx.HLE/HOS/Kernel/HleScheduler.cs | 149 -- Ryujinx.HLE/HOS/Kernel/IKFutureSchedulerObject.cs | 7 - Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs | 33 + Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs | 28 + Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs | 16 + Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs | 31 + Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs | 650 ------ Ryujinx.HLE/HOS/Kernel/KAutoObject.cs | 42 - Ryujinx.HLE/HOS/Kernel/KClientPort.cs | 31 - Ryujinx.HLE/HOS/Kernel/KConditionVariable.cs | 71 - Ryujinx.HLE/HOS/Kernel/KContextIdManager.cs | 83 - Ryujinx.HLE/HOS/Kernel/KCoreContext.cs | 81 - Ryujinx.HLE/HOS/Kernel/KCriticalSection.cs | 93 - Ryujinx.HLE/HOS/Kernel/KEvent.cs | 14 - Ryujinx.HLE/HOS/Kernel/KHandleEntry.cs | 17 - Ryujinx.HLE/HOS/Kernel/KHandleTable.cs | 207 -- Ryujinx.HLE/HOS/Kernel/KMemoryArrange.cs | 22 - Ryujinx.HLE/HOS/Kernel/KMemoryArrangeRegion.cs | 16 - Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs | 43 - Ryujinx.HLE/HOS/Kernel/KMemoryBlockAllocator.cs | 19 - Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs | 33 - Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs | 2458 ------------------- Ryujinx.HLE/HOS/Kernel/KMemoryRegionBlock.cs | 43 - Ryujinx.HLE/HOS/Kernel/KMemoryRegionManager.cs | 428 ---- Ryujinx.HLE/HOS/Kernel/KPageList.cs | 80 - Ryujinx.HLE/HOS/Kernel/KPageNode.cs | 14 - Ryujinx.HLE/HOS/Kernel/KPort.cs | 26 - Ryujinx.HLE/HOS/Kernel/KProcess.cs | 1013 -------- Ryujinx.HLE/HOS/Kernel/KProcessCapabilities.cs | 311 --- Ryujinx.HLE/HOS/Kernel/KReadableEvent.cs | 62 - Ryujinx.HLE/HOS/Kernel/KResourceLimit.cs | 146 -- Ryujinx.HLE/HOS/Kernel/KScheduler.cs | 233 -- Ryujinx.HLE/HOS/Kernel/KSchedulingData.cs | 207 -- Ryujinx.HLE/HOS/Kernel/KServerPort.cs | 14 - Ryujinx.HLE/HOS/Kernel/KSession.cs | 31 - Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs | 68 - Ryujinx.HLE/HOS/Kernel/KSlabHeap.cs | 50 - Ryujinx.HLE/HOS/Kernel/KSynchronization.cs | 135 -- Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs | 34 - Ryujinx.HLE/HOS/Kernel/KThread.cs | 1030 -------- Ryujinx.HLE/HOS/Kernel/KTimeManager.cs | 143 -- Ryujinx.HLE/HOS/Kernel/KTlsPageInfo.cs | 73 - Ryujinx.HLE/HOS/Kernel/KTlsPageManager.cs | 60 - Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs | 14 - Ryujinx.HLE/HOS/Kernel/KWritableEvent.cs | 22 - Ryujinx.HLE/HOS/Kernel/KernelErr.cs | 24 - Ryujinx.HLE/HOS/Kernel/KernelInit.cs | 136 -- Ryujinx.HLE/HOS/Kernel/KernelResult.cs | 31 - Ryujinx.HLE/HOS/Kernel/KernelTransfer.cs | 71 - Ryujinx.HLE/HOS/Kernel/LimitableResource.cs | 13 - Ryujinx.HLE/HOS/Kernel/Memory/AddressSpaceType.cs | 10 + Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs | 15 + Ryujinx.HLE/HOS/Kernel/Memory/KMemoryArrange.cs | 22 + .../HOS/Kernel/Memory/KMemoryArrangeRegion.cs | 16 + Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs | 43 + .../HOS/Kernel/Memory/KMemoryBlockAllocator.cs | 19 + Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs | 33 + Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs | 2460 ++++++++++++++++++++ .../HOS/Kernel/Memory/KMemoryRegionBlock.cs | 43 + .../HOS/Kernel/Memory/KMemoryRegionManager.cs | 429 ++++ Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs | 81 + Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs | 14 + Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs | 70 + Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs | 50 + Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs | 14 + Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs | 22 + Ryujinx.HLE/HOS/Kernel/Memory/MemoryOperation.cs | 12 + Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs | 18 + Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs | 10 + Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs | 49 + Ryujinx.HLE/HOS/Kernel/MemoryAttribute.cs | 22 - Ryujinx.HLE/HOS/Kernel/MemoryOperation.cs | 12 - Ryujinx.HLE/HOS/Kernel/MemoryPermission.cs | 18 - Ryujinx.HLE/HOS/Kernel/MemoryRegion.cs | 10 - Ryujinx.HLE/HOS/Kernel/MemoryState.cs | 49 - Ryujinx.HLE/HOS/Kernel/MersenneTwister.cs | 128 - .../HOS/Kernel/Process/HleProcessDebugger.cs | 311 +++ .../HOS/Kernel/Process/KContextIdManager.cs | 83 + Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs | 17 + Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs | 209 ++ Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 1017 ++++++++ .../HOS/Kernel/Process/KProcessCapabilities.cs | 314 +++ Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs | 75 + Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs | 61 + .../HOS/Kernel/Process/ProcessCreationInfo.cs | 37 + Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs | 14 + Ryujinx.HLE/HOS/Kernel/ProcessCreationInfo.cs | 37 - Ryujinx.HLE/HOS/Kernel/ProcessState.cs | 14 - Ryujinx.HLE/HOS/Kernel/SignalType.cs | 9 - .../Kernel/SupervisorCall/InvalidSvcException.cs | 9 + .../HOS/Kernel/SupervisorCall/SvcHandler.cs | 61 + Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs | 394 ++++ Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs | 762 ++++++ Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs | 340 +++ Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThread.cs | 420 ++++ .../HOS/Kernel/SupervisorCall/SvcThreadSync.cs | 250 ++ Ryujinx.HLE/HOS/Kernel/SvcHandler.cs | 122 - Ryujinx.HLE/HOS/Kernel/SvcMemory.cs | 581 ----- Ryujinx.HLE/HOS/Kernel/SvcSystem.cs | 827 ------- Ryujinx.HLE/HOS/Kernel/SvcThread.cs | 464 ---- Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs | 373 --- Ryujinx.HLE/HOS/Kernel/ThreadSchedState.cs | 19 - Ryujinx.HLE/HOS/Kernel/ThreadType.cs | 10 - .../HOS/Kernel/Threading/ArbitrationType.cs | 9 + Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs | 66 + Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs | 149 ++ .../HOS/Kernel/Threading/KAddressArbiter.cs | 654 ++++++ .../HOS/Kernel/Threading/KConditionVariable.cs | 71 + Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs | 81 + .../HOS/Kernel/Threading/KCriticalSection.cs | 93 + Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs | 14 + Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs | 64 + Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs | 234 ++ .../HOS/Kernel/Threading/KSchedulingData.cs | 207 ++ .../HOS/Kernel/Threading/KSynchronization.cs | 136 ++ Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs | 1030 ++++++++ Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs | 24 + Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs | 9 + .../HOS/Kernel/Threading/ThreadSchedState.cs | 19 + Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs | 10 + Ryujinx.HLE/HOS/ProgramLoader.cs | 4 +- Ryujinx.HLE/HOS/ServiceCtx.cs | 3 +- Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs | 3 +- Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs | 3 +- .../HOS/Services/Am/ILibraryAppletAccessor.cs | 3 +- Ryujinx.HLE/HOS/Services/Am/ISelfController.cs | 3 +- Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs | 3 +- .../Services/Aud/AudioRenderer/IAudioRenderer.cs | 3 +- Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs | 3 +- Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs | 2 +- Ryujinx.HLE/HOS/Services/Hid/IAppletResource.cs | 3 +- Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs | 5 +- Ryujinx.HLE/HOS/Services/IpcService.cs | 3 +- Ryujinx.HLE/HOS/Services/Ldr/IRoInterface.cs | 4 +- Ryujinx.HLE/HOS/Services/Nfp/IUser.cs | 3 +- Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs | 3 +- Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs | 4 +- .../HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs | 2 +- .../Nv/NvHostChannel/NvHostChannelIoctl.cs | 2 +- .../HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs | 2 +- Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs | 2 +- Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs | 2 +- Ryujinx.HLE/HOS/Services/Psm/IPsmSession.cs | 3 +- Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs | 3 +- .../HOS/Services/Vi/IApplicationDisplayService.cs | 2 +- Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs | 3 +- Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs | 2 +- Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs | 2 +- 166 files changed, 11599 insertions(+), 11687 deletions(-) delete mode 100644 Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/ArbitrationType.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/DramMemoryMap.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/HleCoreManager.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/HleProcessDebugger.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/HleScheduler.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/IKFutureSchedulerObject.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KAutoObject.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KClientPort.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KConditionVariable.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KContextIdManager.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KCoreContext.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KCriticalSection.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KEvent.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KHandleEntry.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KHandleTable.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KMemoryArrange.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KMemoryArrangeRegion.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KMemoryBlockAllocator.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KMemoryRegionBlock.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KMemoryRegionManager.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KPageList.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KPageNode.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KPort.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KProcess.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KProcessCapabilities.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KReadableEvent.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KResourceLimit.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KScheduler.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KSchedulingData.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KServerPort.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KSession.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KSlabHeap.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KSynchronization.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KThread.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KTimeManager.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KTlsPageInfo.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KTlsPageManager.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KWritableEvent.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KernelErr.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KernelInit.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KernelResult.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/KernelTransfer.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/LimitableResource.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/AddressSpaceType.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/KMemoryArrange.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/KMemoryArrangeRegion.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockAllocator.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionBlock.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/MemoryOperation.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/MemoryAttribute.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/MemoryOperation.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/MemoryPermission.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/MemoryRegion.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/MemoryState.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/MersenneTwister.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/ProcessCreationInfo.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/ProcessState.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/SignalType.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/SupervisorCall/InvalidSvcException.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThread.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThreadSync.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/SvcHandler.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/SvcMemory.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/SvcSystem.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/SvcThread.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/ThreadSchedState.cs delete mode 100644 Ryujinx.HLE/HOS/Kernel/ThreadType.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs create mode 100644 Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs diff --git a/Ryujinx.HLE/HOS/GlobalStateTable.cs b/Ryujinx.HLE/HOS/GlobalStateTable.cs index a350dee2..5e5e5ecf 100644 --- a/Ryujinx.HLE/HOS/GlobalStateTable.cs +++ b/Ryujinx.HLE/HOS/GlobalStateTable.cs @@ -1,4 +1,4 @@ -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Process; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index a5ea5354..5c2ca9b3 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -2,7 +2,10 @@ using LibHac; using Ryujinx.Common.Logging; using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.HOS.Font; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Npdm; diff --git a/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs b/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs index e5d19236..ecfa25ed 100644 --- a/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs +++ b/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs @@ -1,5 +1,7 @@ using ChocolArm64.Memory; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using Ryujinx.HLE.HOS.Kernel.Process; using System; using System.IO; @@ -7,7 +9,7 @@ namespace Ryujinx.HLE.HOS.Ipc { static class IpcHandler { - public static long IpcCall( + public static KernelResult IpcCall( Switch device, KProcess process, MemoryManager memory, @@ -100,7 +102,7 @@ namespace Ryujinx.HLE.HOS.Ipc memory.WriteBytes(cmdPtr, response.GetBytes(cmdPtr)); } - return 0; + return KernelResult.Success; } private static IpcMessage FillResponse(IpcMessage response, long result, params int[] values) diff --git a/Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs b/Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs deleted file mode 100644 index 6f7b230e..00000000 --- a/Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - enum AddressSpaceType - { - Addr32Bits = 0, - Addr36Bits = 1, - Addr32BitsNoMap = 2, - Addr39Bits = 3 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/ArbitrationType.cs b/Ryujinx.HLE/HOS/Kernel/ArbitrationType.cs deleted file mode 100644 index b584d719..00000000 --- a/Ryujinx.HLE/HOS/Kernel/ArbitrationType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - enum ArbitrationType - { - WaitIfLessThan = 0, - DecrementAndWaitIfLessThan = 1, - WaitIfEqual = 2 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs b/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs new file mode 100644 index 00000000..473683ff --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + interface IKFutureSchedulerObject + { + void TimeUp(); + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs b/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs new file mode 100644 index 00000000..ddb0c71f --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs @@ -0,0 +1,42 @@ +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + class KAutoObject + { + protected Horizon System; + + public KAutoObject(Horizon system) + { + System = system; + } + + public virtual KernelResult SetName(string name) + { + if (!System.AutoObjectNames.TryAdd(name, this)) + { + return KernelResult.InvalidState; + } + + return KernelResult.Success; + } + + public static KernelResult RemoveName(Horizon system, string name) + { + if (!system.AutoObjectNames.TryRemove(name, out _)) + { + return KernelResult.NotFound; + } + + return KernelResult.Success; + } + + public static KAutoObject FindNamedObject(Horizon system, string name) + { + if (system.AutoObjectNames.TryGetValue(name, out KAutoObject obj)) + { + return obj; + } + + return null; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs b/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs new file mode 100644 index 00000000..01bba65f --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs @@ -0,0 +1,147 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + class KResourceLimit + { + private const int Time10SecondsMs = 10000; + + private long[] _current; + private long[] _limit; + private long[] _available; + + private object _lockObj; + + private LinkedList _waitingThreads; + + private int _waitingThreadsCount; + + private Horizon _system; + + public KResourceLimit(Horizon system) + { + _current = new long[(int)LimitableResource.Count]; + _limit = new long[(int)LimitableResource.Count]; + _available = new long[(int)LimitableResource.Count]; + + _lockObj = new object(); + + _waitingThreads = new LinkedList(); + + _system = system; + } + + public bool Reserve(LimitableResource resource, ulong amount) + { + return Reserve(resource, (long)amount); + } + + public bool Reserve(LimitableResource resource, long amount) + { + return Reserve(resource, amount, KTimeManager.ConvertMillisecondsToNanoseconds(Time10SecondsMs)); + } + + public bool Reserve(LimitableResource resource, long amount, long timeout) + { + long endTimePoint = KTimeManager.ConvertNanosecondsToMilliseconds(timeout); + + endTimePoint += PerformanceCounter.ElapsedMilliseconds; + + bool success = false; + + int index = GetIndex(resource); + + lock (_lockObj) + { + long newCurrent = _current[index] + amount; + + while (newCurrent > _limit[index] && _available[index] + amount <= _limit[index]) + { + _waitingThreadsCount++; + + KConditionVariable.Wait(_system, _waitingThreads, _lockObj, timeout); + + _waitingThreadsCount--; + + newCurrent = _current[index] + amount; + + if (timeout >= 0 && PerformanceCounter.ElapsedMilliseconds > endTimePoint) + { + break; + } + } + + if (newCurrent <= _limit[index]) + { + _current[index] = newCurrent; + + success = true; + } + } + + return success; + } + + public void Release(LimitableResource resource, ulong amount) + { + Release(resource, (long)amount); + } + + public void Release(LimitableResource resource, long amount) + { + Release(resource, amount, amount); + } + + private void Release(LimitableResource resource, long usedAmount, long availableAmount) + { + int index = GetIndex(resource); + + lock (_lockObj) + { + _current [index] -= usedAmount; + _available[index] -= availableAmount; + + if (_waitingThreadsCount > 0) + { + KConditionVariable.NotifyAll(_system, _waitingThreads); + } + } + } + + public long GetRemainingValue(LimitableResource resource) + { + int index = GetIndex(resource); + + lock (_lockObj) + { + return _limit[index] - _current[index]; + } + } + + public KernelResult SetLimitValue(LimitableResource resource, long limit) + { + int index = GetIndex(resource); + + lock (_lockObj) + { + if (_current[index] <= limit) + { + _limit[index] = limit; + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidState; + } + } + } + + private static int GetIndex(LimitableResource resource) + { + return (int)resource; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs b/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs new file mode 100644 index 00000000..87e55312 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs @@ -0,0 +1,35 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + class KSynchronizationObject : KAutoObject + { + public LinkedList WaitingThreads; + + public KSynchronizationObject(Horizon system) : base(system) + { + WaitingThreads = new LinkedList(); + } + + public LinkedListNode AddWaitingThread(KThread thread) + { + return WaitingThreads.AddLast(thread); + } + + public void RemoveWaitingThread(LinkedListNode node) + { + WaitingThreads.Remove(node); + } + + public virtual void Signal() + { + System.Synchronization.SignalObject(this); + } + + public virtual bool IsSignaled() + { + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs new file mode 100644 index 00000000..f6a9e6f9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs @@ -0,0 +1,143 @@ +using Ryujinx.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + class KTimeManager : IDisposable + { + private class WaitingObject + { + public IKFutureSchedulerObject Object { get; private set; } + + public long TimePoint { get; private set; } + + public WaitingObject(IKFutureSchedulerObject schedulerObj, long timePoint) + { + Object = schedulerObj; + TimePoint = timePoint; + } + } + + private List _waitingObjects; + + private AutoResetEvent _waitEvent; + + private bool _keepRunning; + + public KTimeManager() + { + _waitingObjects = new List(); + + _keepRunning = true; + + Thread work = new Thread(WaitAndCheckScheduledObjects); + + work.Start(); + } + + public void ScheduleFutureInvocation(IKFutureSchedulerObject schedulerObj, long timeout) + { + long timePoint = PerformanceCounter.ElapsedMilliseconds + ConvertNanosecondsToMilliseconds(timeout); + + lock (_waitingObjects) + { + _waitingObjects.Add(new WaitingObject(schedulerObj, timePoint)); + } + + _waitEvent.Set(); + } + + public static long ConvertNanosecondsToMilliseconds(long time) + { + time /= 1000000; + + if ((ulong)time > int.MaxValue) + { + return int.MaxValue; + } + + return time; + } + + public static long ConvertMillisecondsToNanoseconds(long time) + { + return time * 1000000; + } + + public static long ConvertMillisecondsToTicks(long time) + { + return time * 19200; + } + + public void UnscheduleFutureInvocation(IKFutureSchedulerObject Object) + { + lock (_waitingObjects) + { + _waitingObjects.RemoveAll(x => x.Object == Object); + } + } + + private void WaitAndCheckScheduledObjects() + { + using (_waitEvent = new AutoResetEvent(false)) + { + while (_keepRunning) + { + WaitingObject next; + + lock (_waitingObjects) + { + next = _waitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault(); + } + + if (next != null) + { + long timePoint = PerformanceCounter.ElapsedMilliseconds; + + if (next.TimePoint > timePoint) + { + _waitEvent.WaitOne((int)(next.TimePoint - timePoint)); + } + + bool timeUp = PerformanceCounter.ElapsedMilliseconds >= next.TimePoint; + + if (timeUp) + { + lock (_waitingObjects) + { + timeUp = _waitingObjects.Remove(next); + } + } + + if (timeUp) + { + next.Object.TimeUp(); + } + } + else + { + _waitEvent.WaitOne(); + } + } + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _keepRunning = false; + + _waitEvent?.Set(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs b/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs new file mode 100644 index 00000000..2a2aa743 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs @@ -0,0 +1,137 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + static class KernelInit + { + public static void InitializeResourceLimit(KResourceLimit resourceLimit) + { + void EnsureSuccess(KernelResult result) + { + if (result != KernelResult.Success) + { + throw new InvalidOperationException($"Unexpected result \"{result}\"."); + } + } + + int kernelMemoryCfg = 0; + + long ramSize = GetRamSize(kernelMemoryCfg); + + EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Memory, ramSize)); + EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Thread, 800)); + EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Event, 700)); + EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.TransferMemory, 200)); + EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Session, 900)); + + if (!resourceLimit.Reserve(LimitableResource.Memory, 0) || + !resourceLimit.Reserve(LimitableResource.Memory, 0x60000)) + { + throw new InvalidOperationException("Unexpected failure reserving memory on resource limit."); + } + } + + public static KMemoryRegionManager[] GetMemoryRegions() + { + KMemoryArrange arrange = GetMemoryArrange(); + + return new KMemoryRegionManager[] + { + GetMemoryRegion(arrange.Application), + GetMemoryRegion(arrange.Applet), + GetMemoryRegion(arrange.Service), + GetMemoryRegion(arrange.NvServices) + }; + } + + private static KMemoryRegionManager GetMemoryRegion(KMemoryArrangeRegion region) + { + return new KMemoryRegionManager(region.Address, region.Size, region.EndAddr); + } + + private static KMemoryArrange GetMemoryArrange() + { + int mcEmemCfg = 0x1000; + + ulong ememApertureSize = (ulong)(mcEmemCfg & 0x3fff) << 20; + + int kernelMemoryCfg = 0; + + ulong ramSize = (ulong)GetRamSize(kernelMemoryCfg); + + ulong ramPart0; + ulong ramPart1; + + if (ramSize * 2 > ememApertureSize) + { + ramPart0 = ememApertureSize / 2; + ramPart1 = ememApertureSize / 2; + } + else + { + ramPart0 = ememApertureSize; + ramPart1 = 0; + } + + int memoryArrange = 1; + + ulong applicationRgSize; + + switch (memoryArrange) + { + case 2: applicationRgSize = 0x80000000; break; + case 0x11: + case 0x21: applicationRgSize = 0x133400000; break; + default: applicationRgSize = 0xcd500000; break; + } + + ulong appletRgSize; + + switch (memoryArrange) + { + case 2: appletRgSize = 0x61200000; break; + case 3: appletRgSize = 0x1c000000; break; + case 0x11: appletRgSize = 0x23200000; break; + case 0x12: + case 0x21: appletRgSize = 0x89100000; break; + default: appletRgSize = 0x1fb00000; break; + } + + KMemoryArrangeRegion serviceRg; + KMemoryArrangeRegion nvServicesRg; + KMemoryArrangeRegion appletRg; + KMemoryArrangeRegion applicationRg; + + const ulong nvServicesRgSize = 0x29ba000; + + ulong applicationRgEnd = DramMemoryMap.DramEnd; //- RamPart0; + + applicationRg = new KMemoryArrangeRegion(applicationRgEnd - applicationRgSize, applicationRgSize); + + ulong nvServicesRgEnd = applicationRg.Address - appletRgSize; + + nvServicesRg = new KMemoryArrangeRegion(nvServicesRgEnd - nvServicesRgSize, nvServicesRgSize); + appletRg = new KMemoryArrangeRegion(nvServicesRgEnd, appletRgSize); + + //Note: There is an extra region used by the kernel, however + //since we are doing HLE we are not going to use that memory, so give all + //the remaining memory space to services. + ulong serviceRgSize = nvServicesRg.Address - DramMemoryMap.SlabHeapEnd; + + serviceRg = new KMemoryArrangeRegion(DramMemoryMap.SlabHeapEnd, serviceRgSize); + + return new KMemoryArrange(serviceRg, nvServicesRg, appletRg, applicationRg); + } + + private static long GetRamSize(int kernelMemoryCfg) + { + switch ((kernelMemoryCfg >> 16) & 3) + { + case 1: return 0x180000000; + case 2: return 0x200000000; + default: return 0x100000000; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs b/Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs new file mode 100644 index 00000000..cea24693 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs @@ -0,0 +1,32 @@ +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + enum KernelResult + { + Success = 0, + InvalidCapability = 0x1c01, + ThreadNotStarted = 0x7201, + ThreadTerminating = 0x7601, + InvalidSize = 0xca01, + InvalidAddress = 0xcc01, + OutOfResource = 0xce01, + OutOfMemory = 0xd001, + HandleTableFull = 0xd201, + InvalidMemState = 0xd401, + InvalidPermission = 0xd801, + InvalidMemRange = 0xdc01, + InvalidPriority = 0xe001, + InvalidCpuCore = 0xe201, + InvalidHandle = 0xe401, + UserCopyFailed = 0xe601, + InvalidCombination = 0xe801, + TimedOut = 0xea01, + Cancelled = 0xec01, + MaximumExceeded = 0xee01, + InvalidEnumValue = 0xf001, + NotFound = 0xf201, + InvalidThread = 0xf401, + InvalidState = 0xfa01, + ReservedValue = 0xfc01, + ResLimitExceeded = 0x10801 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs b/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs new file mode 100644 index 00000000..a29b1722 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs @@ -0,0 +1,72 @@ +using Ryujinx.HLE.HOS.Kernel.Process; +using ChocolArm64.Memory; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + static class KernelTransfer + { + public static bool UserToKernelInt32(Horizon system, ulong address, out int value) + { + KProcess currentProcess = system.Scheduler.GetCurrentProcess(); + + if (currentProcess.CpuMemory.IsMapped((long)address) && + currentProcess.CpuMemory.IsMapped((long)address + 3)) + { + value = currentProcess.CpuMemory.ReadInt32((long)address); + + return true; + } + + value = 0; + + return false; + } + + public static bool UserToKernelString(Horizon system, ulong address, int size, out string value) + { + KProcess currentProcess = system.Scheduler.GetCurrentProcess(); + + if (currentProcess.CpuMemory.IsMapped((long)address) && + currentProcess.CpuMemory.IsMapped((long)address + size - 1)) + { + value = MemoryHelper.ReadAsciiString(currentProcess.CpuMemory, (long)address, size); + + return true; + } + + value = null; + + return false; + } + + public static bool KernelToUserInt32(Horizon system, ulong address, int value) + { + KProcess currentProcess = system.Scheduler.GetCurrentProcess(); + + if (currentProcess.CpuMemory.IsMapped((long)address) && + currentProcess.CpuMemory.IsMapped((long)address + 3)) + { + currentProcess.CpuMemory.WriteInt32ToSharedAddr((long)address, value); + + return true; + } + + return false; + } + + public static bool KernelToUserInt64(Horizon system, ulong address, long value) + { + KProcess currentProcess = system.Scheduler.GetCurrentProcess(); + + if (currentProcess.CpuMemory.IsMapped((long)address) && + currentProcess.CpuMemory.IsMapped((long)address + 7)) + { + currentProcess.CpuMemory.WriteInt64((long)address, value); + + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs b/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs new file mode 100644 index 00000000..2e6a3e45 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + enum LimitableResource : byte + { + Memory = 0, + Thread = 1, + Event = 2, + TransferMemory = 3, + Session = 4, + + Count = 5 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs b/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs new file mode 100644 index 00000000..6b686901 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs @@ -0,0 +1,128 @@ +using Ryujinx.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + class MersenneTwister + { + private int _index; + private uint[] _mt; + + public MersenneTwister(uint seed) + { + _mt = new uint[624]; + + _mt[0] = seed; + + for (int mtIdx = 1; mtIdx < _mt.Length; mtIdx++) + { + uint prev = _mt[mtIdx - 1]; + + _mt[mtIdx] = (uint)(0x6c078965 * (prev ^ (prev >> 30)) + mtIdx); + } + + _index = _mt.Length; + } + + public long GenRandomNumber(long min, long max) + { + long range = max - min; + + if (min == max) + { + return min; + } + + if (range == -1) + { + //Increment would cause a overflow, special case. + return GenRandomNumber(2, 2, 32, 0xffffffffu, 0xffffffffu); + } + + range++; + + //This is log2(Range) plus one. + int nextRangeLog2 = 64 - BitUtils.CountLeadingZeros64(range); + + //If Range is already power of 2, subtract one to use log2(Range) directly. + int rangeLog2 = nextRangeLog2 - (BitUtils.IsPowerOfTwo64(range) ? 1 : 0); + + int parts = rangeLog2 > 32 ? 2 : 1; + int bitsPerPart = rangeLog2 / parts; + + int fullParts = parts - (rangeLog2 - parts * bitsPerPart); + + uint mask = 0xffffffffu >> (32 - bitsPerPart); + uint maskPlus1 = 0xffffffffu >> (31 - bitsPerPart); + + long randomNumber; + + do + { + randomNumber = GenRandomNumber(parts, fullParts, bitsPerPart, mask, maskPlus1); + } + while ((ulong)randomNumber >= (ulong)range); + + return min + randomNumber; + } + + private long GenRandomNumber( + int parts, + int fullParts, + int bitsPerPart, + uint mask, + uint maskPlus1) + { + long randomNumber = 0; + + int part = 0; + + for (; part < fullParts; part++) + { + randomNumber <<= bitsPerPart; + randomNumber |= GenRandomNumber() & mask; + } + + for (; part < parts; part++) + { + randomNumber <<= bitsPerPart + 1; + randomNumber |= GenRandomNumber() & maskPlus1; + } + + return randomNumber; + } + + private uint GenRandomNumber() + { + if (_index >= _mt.Length) + { + Twist(); + } + + uint value = _mt[_index++]; + + value ^= value >> 11; + value ^= (value << 7) & 0x9d2c5680; + value ^= (value << 15) & 0xefc60000; + value ^= value >> 18; + + return value; + } + + private void Twist() + { + for (int mtIdx = 0; mtIdx < _mt.Length; mtIdx++) + { + uint value = (_mt[mtIdx] & 0x80000000) + (_mt[(mtIdx + 1) % _mt.Length] & 0x7fffffff); + + _mt[mtIdx] = _mt[(mtIdx + 397) % _mt.Length] ^ (value >> 1); + + if ((value & 1) != 0) + { + _mt[mtIdx] ^= 0x9908b0df; + } + } + + _index = 0; + } + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/DramMemoryMap.cs b/Ryujinx.HLE/HOS/Kernel/DramMemoryMap.cs deleted file mode 100644 index b20a83e2..00000000 --- a/Ryujinx.HLE/HOS/Kernel/DramMemoryMap.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - static class DramMemoryMap - { - public const ulong DramBase = 0x80000000; - public const ulong DramSize = 0x100000000; - public const ulong DramEnd = DramBase + DramSize; - - public const ulong KernelReserveBase = DramBase + 0x60000; - - public const ulong SlabHeapBase = KernelReserveBase + 0x85000; - public const ulong SlapHeapSize = 0xa21000; - public const ulong SlabHeapEnd = SlabHeapBase + SlapHeapSize; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/HleCoreManager.cs b/Ryujinx.HLE/HOS/Kernel/HleCoreManager.cs deleted file mode 100644 index 04017d6d..00000000 --- a/Ryujinx.HLE/HOS/Kernel/HleCoreManager.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Collections.Concurrent; -using System.Threading; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class HleCoreManager - { - private class PausableThread - { - public ManualResetEvent Event { get; private set; } - - public bool IsExiting { get; set; } - - public PausableThread() - { - Event = new ManualResetEvent(false); - } - } - - private ConcurrentDictionary _threads; - - public HleCoreManager() - { - _threads = new ConcurrentDictionary(); - } - - public void Set(Thread thread) - { - GetThread(thread).Event.Set(); - } - - public void Reset(Thread thread) - { - GetThread(thread).Event.Reset(); - } - - public void Wait(Thread thread) - { - PausableThread pausableThread = GetThread(thread); - - if (!pausableThread.IsExiting) - { - pausableThread.Event.WaitOne(); - } - } - - public void Exit(Thread thread) - { - GetThread(thread).IsExiting = true; - } - - private PausableThread GetThread(Thread thread) - { - return _threads.GetOrAdd(thread, (key) => new PausableThread()); - } - - public void RemoveThread(Thread thread) - { - if (_threads.TryRemove(thread, out PausableThread pausableThread)) - { - pausableThread.Event.Set(); - pausableThread.Event.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/HleProcessDebugger.cs b/Ryujinx.HLE/HOS/Kernel/HleProcessDebugger.cs deleted file mode 100644 index fd3ac1f5..00000000 --- a/Ryujinx.HLE/HOS/Kernel/HleProcessDebugger.cs +++ /dev/null @@ -1,310 +0,0 @@ -using ChocolArm64.Memory; -using ChocolArm64.State; -using Ryujinx.Common.Logging; -using Ryujinx.HLE.HOS.Diagnostics.Demangler; -using Ryujinx.HLE.Loaders.Elf; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class HleProcessDebugger - { - private const int Mod0 = 'M' << 0 | 'O' << 8 | 'D' << 16 | '0' << 24; - - private KProcess _owner; - - private class Image - { - public long BaseAddress { get; private set; } - - public ElfSymbol[] Symbols { get; private set; } - - public Image(long baseAddress, ElfSymbol[] symbols) - { - BaseAddress = baseAddress; - Symbols = symbols; - } - } - - private List _images; - - private int _loaded; - - public HleProcessDebugger(KProcess owner) - { - _owner = owner; - - _images = new List(); - } - - public void PrintGuestStackTrace(CpuThreadState threadState) - { - EnsureLoaded(); - - StringBuilder trace = new StringBuilder(); - - trace.AppendLine("Guest stack trace:"); - - void AppendTrace(long address) - { - Image image = GetImage(address, out int imageIndex); - - if (image == null || !TryGetSubName(image, address, out string subName)) - { - subName = $"Sub{address:x16}"; - } - else if (subName.StartsWith("_Z")) - { - subName = Demangler.Parse(subName); - } - - if (image != null) - { - long offset = address - image.BaseAddress; - - string imageName = GetGuessedNsoNameFromIndex(imageIndex); - - string imageNameAndOffset = $"[{_owner.Name}] {imageName}:0x{offset:x8}"; - - trace.AppendLine($" {imageNameAndOffset} {subName}"); - } - else - { - trace.AppendLine($" [{_owner.Name}] ??? {subName}"); - } - } - - long framePointer = (long)threadState.X29; - - while (framePointer != 0) - { - if ((framePointer & 7) != 0 || - !_owner.CpuMemory.IsMapped(framePointer) || - !_owner.CpuMemory.IsMapped(framePointer + 8)) - { - break; - } - - //Note: This is the return address, we need to subtract one instruction - //worth of bytes to get the branch instruction address. - AppendTrace(_owner.CpuMemory.ReadInt64(framePointer + 8) - 4); - - framePointer = _owner.CpuMemory.ReadInt64(framePointer); - } - - Logger.PrintInfo(LogClass.Cpu, trace.ToString()); - } - - private bool TryGetSubName(Image image, long address, out string name) - { - address -= image.BaseAddress; - - int left = 0; - int right = image.Symbols.Length - 1; - - while (left <= right) - { - int size = right - left; - - int middle = left + (size >> 1); - - ElfSymbol symbol = image.Symbols[middle]; - - long endAddr = symbol.Value + symbol.Size; - - if ((ulong)address >= (ulong)symbol.Value && (ulong)address < (ulong)endAddr) - { - name = symbol.Name; - - return true; - } - - if ((ulong)address < (ulong)symbol.Value) - { - right = middle - 1; - } - else - { - left = middle + 1; - } - } - - name = null; - - return false; - } - - private Image GetImage(long address, out int index) - { - lock (_images) - { - for (index = _images.Count - 1; index >= 0; index--) - { - if ((ulong)address >= (ulong)_images[index].BaseAddress) - { - return _images[index]; - } - } - } - - return null; - } - - private string GetGuessedNsoNameFromIndex(int index) - { - if ((uint)index > 11) - { - return "???"; - } - - if (index == 0) - { - return "rtld"; - } - else if (index == 1) - { - return "main"; - } - else if (index == GetImagesCount() - 1) - { - return "sdk"; - } - else - { - return "subsdk" + (index - 2); - } - } - - private int GetImagesCount() - { - lock (_images) - { - return _images.Count; - } - } - - private void EnsureLoaded() - { - if (Interlocked.CompareExchange(ref _loaded, 1, 0) == 0) - { - ScanMemoryForTextSegments(); - } - } - - private void ScanMemoryForTextSegments() - { - ulong oldAddress = 0; - ulong address = 0; - - while (address >= oldAddress) - { - KMemoryInfo info = _owner.MemoryManager.QueryMemory(address); - - if (info.State == MemoryState.Reserved) - { - break; - } - - if (info.State == MemoryState.CodeStatic && info.Permission == MemoryPermission.ReadAndExecute) - { - LoadMod0Symbols(_owner.CpuMemory, (long)info.Address); - } - - oldAddress = address; - - address = info.Address + info.Size; - } - } - - private void LoadMod0Symbols(MemoryManager memory, long textOffset) - { - long mod0Offset = textOffset + memory.ReadUInt32(textOffset + 4); - - if (mod0Offset < textOffset || !memory.IsMapped(mod0Offset) || (mod0Offset & 3) != 0) - { - return; - } - - Dictionary dynamic = new Dictionary(); - - int mod0Magic = memory.ReadInt32(mod0Offset + 0x0); - - if (mod0Magic != Mod0) - { - return; - } - - long dynamicOffset = memory.ReadInt32(mod0Offset + 0x4) + mod0Offset; - long bssStartOffset = memory.ReadInt32(mod0Offset + 0x8) + mod0Offset; - long bssEndOffset = memory.ReadInt32(mod0Offset + 0xc) + mod0Offset; - long ehHdrStartOffset = memory.ReadInt32(mod0Offset + 0x10) + mod0Offset; - long ehHdrEndOffset = memory.ReadInt32(mod0Offset + 0x14) + mod0Offset; - long modObjOffset = memory.ReadInt32(mod0Offset + 0x18) + mod0Offset; - - while (true) - { - long tagVal = memory.ReadInt64(dynamicOffset + 0); - long value = memory.ReadInt64(dynamicOffset + 8); - - dynamicOffset += 0x10; - - ElfDynamicTag tag = (ElfDynamicTag)tagVal; - - if (tag == ElfDynamicTag.DT_NULL) - { - break; - } - - dynamic[tag] = value; - } - - if (!dynamic.TryGetValue(ElfDynamicTag.DT_STRTAB, out long strTab) || - !dynamic.TryGetValue(ElfDynamicTag.DT_SYMTAB, out long symTab) || - !dynamic.TryGetValue(ElfDynamicTag.DT_SYMENT, out long symEntSize)) - { - return; - } - - long strTblAddr = textOffset + strTab; - long symTblAddr = textOffset + symTab; - - List symbols = new List(); - - while ((ulong)symTblAddr < (ulong)strTblAddr) - { - ElfSymbol sym = GetSymbol(memory, symTblAddr, strTblAddr); - - symbols.Add(sym); - - symTblAddr += symEntSize; - } - - lock (_images) - { - _images.Add(new Image(textOffset, symbols.OrderBy(x => x.Value).ToArray())); - } - } - - private ElfSymbol GetSymbol(MemoryManager memory, long address, long strTblAddr) - { - int nameIndex = memory.ReadInt32(address + 0); - int info = memory.ReadByte (address + 4); - int other = memory.ReadByte (address + 5); - int shIdx = memory.ReadInt16(address + 6); - long value = memory.ReadInt64(address + 8); - long size = memory.ReadInt64(address + 16); - - string name = string.Empty; - - for (int chr; (chr = memory.ReadByte(strTblAddr + nameIndex++)) != 0;) - { - name += (char)chr; - } - - return new ElfSymbol(name, info, other, shIdx, value, size); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/HleScheduler.cs b/Ryujinx.HLE/HOS/Kernel/HleScheduler.cs deleted file mode 100644 index 1a4c9dfe..00000000 --- a/Ryujinx.HLE/HOS/Kernel/HleScheduler.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.Threading; - -namespace Ryujinx.HLE.HOS.Kernel -{ - partial class KScheduler - { - private const int RoundRobinTimeQuantumMs = 10; - - private int _currentCore; - - public bool MultiCoreScheduling { get; set; } - - public HleCoreManager CoreManager { get; private set; } - - private bool _keepPreempting; - - public void StartAutoPreemptionThread() - { - Thread preemptionThread = new Thread(PreemptCurrentThread); - - _keepPreempting = true; - - preemptionThread.Start(); - } - - public void ContextSwitch() - { - lock (CoreContexts) - { - if (MultiCoreScheduling) - { - int selectedCount = 0; - - for (int core = 0; core < CpuCoresCount; core++) - { - KCoreContext coreContext = CoreContexts[core]; - - if (coreContext.ContextSwitchNeeded && (coreContext.CurrentThread?.Context.IsCurrentThread() ?? false)) - { - coreContext.ContextSwitch(); - } - - if (coreContext.CurrentThread?.Context.IsCurrentThread() ?? false) - { - selectedCount++; - } - } - - if (selectedCount == 0) - { - CoreManager.Reset(Thread.CurrentThread); - } - else if (selectedCount == 1) - { - CoreManager.Set(Thread.CurrentThread); - } - else - { - throw new InvalidOperationException("Thread scheduled in more than one core!"); - } - } - else - { - KThread currentThread = CoreContexts[_currentCore].CurrentThread; - - bool hasThreadExecuting = currentThread != null; - - if (hasThreadExecuting) - { - //If this is not the thread that is currently executing, we need - //to request an interrupt to allow safely starting another thread. - if (!currentThread.Context.IsCurrentThread()) - { - currentThread.Context.RequestInterrupt(); - - return; - } - - CoreManager.Reset(currentThread.Context.Work); - } - - //Advance current core and try picking a thread, - //keep advancing if it is null. - for (int core = 0; core < 4; core++) - { - _currentCore = (_currentCore + 1) % CpuCoresCount; - - KCoreContext coreContext = CoreContexts[_currentCore]; - - coreContext.UpdateCurrentThread(); - - if (coreContext.CurrentThread != null) - { - coreContext.CurrentThread.ClearExclusive(); - - CoreManager.Set(coreContext.CurrentThread.Context.Work); - - coreContext.CurrentThread.Context.Execute(); - - break; - } - } - - //If nothing was running before, then we are on a "external" - //HLE thread, we don't need to wait. - if (!hasThreadExecuting) - { - return; - } - } - } - - CoreManager.Wait(Thread.CurrentThread); - } - - private void PreemptCurrentThread() - { - //Preempts current thread every 10 milliseconds on a round-robin fashion, - //when multi core scheduling is disabled, to try ensuring that all threads - //gets a chance to run. - while (_keepPreempting) - { - lock (CoreContexts) - { - KThread currentThread = CoreContexts[_currentCore].CurrentThread; - - currentThread?.Context.RequestInterrupt(); - } - - PreemptThreads(); - - Thread.Sleep(RoundRobinTimeQuantumMs); - } - } - - public void ExitThread(KThread thread) - { - thread.Context.StopExecution(); - - CoreManager.Exit(thread.Context.Work); - } - - public void RemoveThread(KThread thread) - { - CoreManager.RemoveThread(thread.Context.Work); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/IKFutureSchedulerObject.cs b/Ryujinx.HLE/HOS/Kernel/IKFutureSchedulerObject.cs deleted file mode 100644 index 6a255e65..00000000 --- a/Ryujinx.HLE/HOS/Kernel/IKFutureSchedulerObject.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - interface IKFutureSchedulerObject - { - void TimeUp(); - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs new file mode 100644 index 00000000..ddfe2096 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs @@ -0,0 +1,33 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KClientPort : KSynchronizationObject + { + private int _sessionsCount; + private int _currentCapacity; + private int _maxSessions; + + private KPort _parent; + + public KClientPort(Horizon system) : base(system) { } + + public void Initialize(KPort parent, int maxSessions) + { + _maxSessions = maxSessions; + _parent = parent; + } + + public new static KernelResult RemoveName(Horizon system, string name) + { + KAutoObject foundObj = FindNamedObject(system, name); + + if (!(foundObj is KClientPort)) + { + return KernelResult.NotFound; + } + + return KAutoObject.RemoveName(system, name); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs new file mode 100644 index 00000000..16e9a111 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs @@ -0,0 +1,28 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KPort : KAutoObject + { + public KServerPort ServerPort { get; private set; } + public KClientPort ClientPort { get; private set; } + + private long _nameAddress; + private bool _isLight; + + public KPort(Horizon system) : base(system) + { + ServerPort = new KServerPort(system); + ClientPort = new KClientPort(system); + } + + public void Initialize(int maxSessions, bool isLight, long nameAddress) + { + ServerPort.Initialize(this); + ClientPort.Initialize(this, maxSessions); + + _isLight = isLight; + _nameAddress = nameAddress; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs new file mode 100644 index 00000000..d4d3bcd2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs @@ -0,0 +1,16 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KServerPort : KSynchronizationObject + { + private KPort _parent; + + public KServerPort(Horizon system) : base(system) { } + + public void Initialize(KPort parent) + { + _parent = parent; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs b/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs new file mode 100644 index 00000000..f2b30493 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs @@ -0,0 +1,31 @@ +using Ryujinx.HLE.HOS.Services; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KSession : IDisposable + { + public IpcService Service { get; private set; } + + public string ServiceName { get; private set; } + + public KSession(IpcService service, string serviceName) + { + Service = service; + ServiceName = serviceName; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && Service is IDisposable disposableService) + { + disposableService.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs b/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs deleted file mode 100644 index b4352485..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs +++ /dev/null @@ -1,650 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -using static Ryujinx.HLE.HOS.ErrorCode; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KAddressArbiter - { - private const int HasListenersMask = 0x40000000; - - private Horizon _system; - - public List CondVarThreads; - public List ArbiterThreads; - - public KAddressArbiter(Horizon system) - { - _system = system; - - CondVarThreads = new List(); - ArbiterThreads = new List(); - } - - public long ArbitrateLock(int ownerHandle, long mutexAddress, int requesterHandle) - { - KThread currentThread = _system.Scheduler.GetCurrentThread(); - - _system.CriticalSection.Enter(); - - currentThread.SignaledObj = null; - currentThread.ObjSyncResult = 0; - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - if (!KernelTransfer.UserToKernelInt32(_system, mutexAddress, out int mutexValue)) - { - _system.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - } - - if (mutexValue != (ownerHandle | HasListenersMask)) - { - _system.CriticalSection.Leave(); - - return 0; - } - - KThread mutexOwner = currentProcess.HandleTable.GetObject(ownerHandle); - - if (mutexOwner == null) - { - _system.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - } - - currentThread.MutexAddress = mutexAddress; - currentThread.ThreadHandleForUserMutex = requesterHandle; - - mutexOwner.AddMutexWaiter(currentThread); - - currentThread.Reschedule(ThreadSchedState.Paused); - - _system.CriticalSection.Leave(); - _system.CriticalSection.Enter(); - - if (currentThread.MutexOwner != null) - { - currentThread.MutexOwner.RemoveMutexWaiter(currentThread); - } - - _system.CriticalSection.Leave(); - - return (uint)currentThread.ObjSyncResult; - } - - public long ArbitrateUnlock(long mutexAddress) - { - _system.CriticalSection.Enter(); - - KThread currentThread = _system.Scheduler.GetCurrentThread(); - - (long result, KThread newOwnerThread) = MutexUnlock(currentThread, mutexAddress); - - if (result != 0 && newOwnerThread != null) - { - newOwnerThread.SignaledObj = null; - newOwnerThread.ObjSyncResult = (int)result; - } - - _system.CriticalSection.Leave(); - - return result; - } - - public long WaitProcessWideKeyAtomic( - long mutexAddress, - long condVarAddress, - int threadHandle, - long timeout) - { - _system.CriticalSection.Enter(); - - KThread currentThread = _system.Scheduler.GetCurrentThread(); - - currentThread.SignaledObj = null; - currentThread.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.Timeout); - - if (currentThread.ShallBeTerminated || - currentThread.SchedFlags == ThreadSchedState.TerminationPending) - { - _system.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); - } - - (long result, _) = MutexUnlock(currentThread, mutexAddress); - - if (result != 0) - { - _system.CriticalSection.Leave(); - - return result; - } - - currentThread.MutexAddress = mutexAddress; - currentThread.ThreadHandleForUserMutex = threadHandle; - currentThread.CondVarAddress = condVarAddress; - - CondVarThreads.Add(currentThread); - - if (timeout != 0) - { - currentThread.Reschedule(ThreadSchedState.Paused); - - if (timeout > 0) - { - _system.TimeManager.ScheduleFutureInvocation(currentThread, timeout); - } - } - - _system.CriticalSection.Leave(); - - if (timeout > 0) - { - _system.TimeManager.UnscheduleFutureInvocation(currentThread); - } - - _system.CriticalSection.Enter(); - - if (currentThread.MutexOwner != null) - { - currentThread.MutexOwner.RemoveMutexWaiter(currentThread); - } - - CondVarThreads.Remove(currentThread); - - _system.CriticalSection.Leave(); - - return (uint)currentThread.ObjSyncResult; - } - - private (long, KThread) MutexUnlock(KThread currentThread, long mutexAddress) - { - KThread newOwnerThread = currentThread.RelinquishMutex(mutexAddress, out int count); - - int mutexValue = 0; - - if (newOwnerThread != null) - { - mutexValue = newOwnerThread.ThreadHandleForUserMutex; - - if (count >= 2) - { - mutexValue |= HasListenersMask; - } - - newOwnerThread.SignaledObj = null; - newOwnerThread.ObjSyncResult = 0; - - newOwnerThread.ReleaseAndResume(); - } - - long result = 0; - - if (!KernelTransfer.KernelToUserInt32(_system, mutexAddress, mutexValue)) - { - result = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - } - - return (result, newOwnerThread); - } - - public void SignalProcessWideKey(long address, int count) - { - Queue signaledThreads = new Queue(); - - _system.CriticalSection.Enter(); - - IOrderedEnumerable sortedThreads = CondVarThreads.OrderBy(x => x.DynamicPriority); - - foreach (KThread thread in sortedThreads.Where(x => x.CondVarAddress == address)) - { - TryAcquireMutex(thread); - - signaledThreads.Enqueue(thread); - - //If the count is <= 0, we should signal all threads waiting. - if (count >= 1 && --count == 0) - { - break; - } - } - - while (signaledThreads.TryDequeue(out KThread thread)) - { - CondVarThreads.Remove(thread); - } - - _system.CriticalSection.Leave(); - } - - private KThread TryAcquireMutex(KThread requester) - { - long address = requester.MutexAddress; - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - currentProcess.CpuMemory.SetExclusive(0, address); - - if (!KernelTransfer.UserToKernelInt32(_system, address, out int mutexValue)) - { - //Invalid address. - currentProcess.CpuMemory.ClearExclusive(0); - - requester.SignaledObj = null; - requester.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return null; - } - - while (true) - { - if (currentProcess.CpuMemory.TestExclusive(0, address)) - { - if (mutexValue != 0) - { - //Update value to indicate there is a mutex waiter now. - currentProcess.CpuMemory.WriteInt32(address, mutexValue | HasListenersMask); - } - else - { - //No thread owning the mutex, assign to requesting thread. - currentProcess.CpuMemory.WriteInt32(address, requester.ThreadHandleForUserMutex); - } - - currentProcess.CpuMemory.ClearExclusiveForStore(0); - - break; - } - - currentProcess.CpuMemory.SetExclusive(0, address); - - mutexValue = currentProcess.CpuMemory.ReadInt32(address); - } - - if (mutexValue == 0) - { - //We now own the mutex. - requester.SignaledObj = null; - requester.ObjSyncResult = 0; - - requester.ReleaseAndResume(); - - return null; - } - - mutexValue &= ~HasListenersMask; - - KThread mutexOwner = currentProcess.HandleTable.GetObject(mutexValue); - - if (mutexOwner != null) - { - //Mutex already belongs to another thread, wait for it. - mutexOwner.AddMutexWaiter(requester); - } - else - { - //Invalid mutex owner. - requester.SignaledObj = null; - requester.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - requester.ReleaseAndResume(); - } - - return mutexOwner; - } - - public long WaitForAddressIfEqual(long address, int value, long timeout) - { - KThread currentThread = _system.Scheduler.GetCurrentThread(); - - _system.CriticalSection.Enter(); - - if (currentThread.ShallBeTerminated || - currentThread.SchedFlags == ThreadSchedState.TerminationPending) - { - _system.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); - } - - currentThread.SignaledObj = null; - currentThread.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.Timeout); - - if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue)) - { - _system.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - } - - if (currentValue == value) - { - if (timeout == 0) - { - _system.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.Timeout); - } - - currentThread.MutexAddress = address; - currentThread.WaitingInArbitration = true; - - InsertSortedByPriority(ArbiterThreads, currentThread); - - currentThread.Reschedule(ThreadSchedState.Paused); - - if (timeout > 0) - { - _system.TimeManager.ScheduleFutureInvocation(currentThread, timeout); - } - - _system.CriticalSection.Leave(); - - if (timeout > 0) - { - _system.TimeManager.UnscheduleFutureInvocation(currentThread); - } - - _system.CriticalSection.Enter(); - - if (currentThread.WaitingInArbitration) - { - ArbiterThreads.Remove(currentThread); - - currentThread.WaitingInArbitration = false; - } - - _system.CriticalSection.Leave(); - - return currentThread.ObjSyncResult; - } - - _system.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); - } - - public long WaitForAddressIfLessThan(long address, int value, bool shouldDecrement, long timeout) - { - KThread currentThread = _system.Scheduler.GetCurrentThread(); - - _system.CriticalSection.Enter(); - - if (currentThread.ShallBeTerminated || - currentThread.SchedFlags == ThreadSchedState.TerminationPending) - { - _system.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); - } - - currentThread.SignaledObj = null; - currentThread.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.Timeout); - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - //If ShouldDecrement is true, do atomic decrement of the value at Address. - currentProcess.CpuMemory.SetExclusive(0, address); - - if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue)) - { - _system.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - } - - if (shouldDecrement) - { - while (currentValue < value) - { - if (currentProcess.CpuMemory.TestExclusive(0, address)) - { - currentProcess.CpuMemory.WriteInt32(address, currentValue - 1); - - currentProcess.CpuMemory.ClearExclusiveForStore(0); - - break; - } - - currentProcess.CpuMemory.SetExclusive(0, address); - - currentValue = currentProcess.CpuMemory.ReadInt32(address); - } - } - - currentProcess.CpuMemory.ClearExclusive(0); - - if (currentValue < value) - { - if (timeout == 0) - { - _system.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.Timeout); - } - - currentThread.MutexAddress = address; - currentThread.WaitingInArbitration = true; - - InsertSortedByPriority(ArbiterThreads, currentThread); - - currentThread.Reschedule(ThreadSchedState.Paused); - - if (timeout > 0) - { - _system.TimeManager.ScheduleFutureInvocation(currentThread, timeout); - } - - _system.CriticalSection.Leave(); - - if (timeout > 0) - { - _system.TimeManager.UnscheduleFutureInvocation(currentThread); - } - - _system.CriticalSection.Enter(); - - if (currentThread.WaitingInArbitration) - { - ArbiterThreads.Remove(currentThread); - - currentThread.WaitingInArbitration = false; - } - - _system.CriticalSection.Leave(); - - return currentThread.ObjSyncResult; - } - - _system.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); - } - - private void InsertSortedByPriority(List threads, KThread thread) - { - int nextIndex = -1; - - for (int index = 0; index < threads.Count; index++) - { - if (threads[index].DynamicPriority > thread.DynamicPriority) - { - nextIndex = index; - - break; - } - } - - if (nextIndex != -1) - { - threads.Insert(nextIndex, thread); - } - else - { - threads.Add(thread); - } - } - - public long Signal(long address, int count) - { - _system.CriticalSection.Enter(); - - WakeArbiterThreads(address, count); - - _system.CriticalSection.Leave(); - - return 0; - } - - public long SignalAndIncrementIfEqual(long address, int value, int count) - { - _system.CriticalSection.Enter(); - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - currentProcess.CpuMemory.SetExclusive(0, address); - - if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue)) - { - _system.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - } - - while (currentValue == value) - { - if (currentProcess.CpuMemory.TestExclusive(0, address)) - { - currentProcess.CpuMemory.WriteInt32(address, currentValue + 1); - - currentProcess.CpuMemory.ClearExclusiveForStore(0); - - break; - } - - currentProcess.CpuMemory.SetExclusive(0, address); - - currentValue = currentProcess.CpuMemory.ReadInt32(address); - } - - currentProcess.CpuMemory.ClearExclusive(0); - - if (currentValue != value) - { - _system.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); - } - - WakeArbiterThreads(address, count); - - _system.CriticalSection.Leave(); - - return 0; - } - - public long SignalAndModifyIfEqual(long address, int value, int count) - { - _system.CriticalSection.Enter(); - - int offset; - - //The value is decremented if the number of threads waiting is less - //or equal to the Count of threads to be signaled, or Count is zero - //or negative. It is incremented if there are no threads waiting. - int waitingCount = 0; - - foreach (KThread thread in ArbiterThreads.Where(x => x.MutexAddress == address)) - { - if (++waitingCount > count) - { - break; - } - } - - if (waitingCount > 0) - { - offset = waitingCount <= count || count <= 0 ? -1 : 0; - } - else - { - offset = 1; - } - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - currentProcess.CpuMemory.SetExclusive(0, address); - - if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue)) - { - _system.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - } - - while (currentValue == value) - { - if (currentProcess.CpuMemory.TestExclusive(0, address)) - { - currentProcess.CpuMemory.WriteInt32(address, currentValue + offset); - - currentProcess.CpuMemory.ClearExclusiveForStore(0); - - break; - } - - currentProcess.CpuMemory.SetExclusive(0, address); - - currentValue = currentProcess.CpuMemory.ReadInt32(address); - } - - currentProcess.CpuMemory.ClearExclusive(0); - - if (currentValue != value) - { - _system.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); - } - - WakeArbiterThreads(address, count); - - _system.CriticalSection.Leave(); - - return 0; - } - - private void WakeArbiterThreads(long address, int count) - { - Queue signaledThreads = new Queue(); - - foreach (KThread thread in ArbiterThreads.Where(x => x.MutexAddress == address)) - { - signaledThreads.Enqueue(thread); - - //If the count is <= 0, we should signal all threads waiting. - if (count >= 1 && --count == 0) - { - break; - } - } - - while (signaledThreads.TryDequeue(out KThread thread)) - { - thread.SignaledObj = null; - thread.ObjSyncResult = 0; - - thread.ReleaseAndResume(); - - thread.WaitingInArbitration = false; - - ArbiterThreads.Remove(thread); - } - } - } -} diff --git a/Ryujinx.HLE/HOS/Kernel/KAutoObject.cs b/Ryujinx.HLE/HOS/Kernel/KAutoObject.cs deleted file mode 100644 index f49beaac..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KAutoObject.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - class KAutoObject - { - protected Horizon System; - - public KAutoObject(Horizon system) - { - System = system; - } - - public virtual KernelResult SetName(string name) - { - if (!System.AutoObjectNames.TryAdd(name, this)) - { - return KernelResult.InvalidState; - } - - return KernelResult.Success; - } - - public static KernelResult RemoveName(Horizon system, string name) - { - if (!system.AutoObjectNames.TryRemove(name, out _)) - { - return KernelResult.NotFound; - } - - return KernelResult.Success; - } - - public static KAutoObject FindNamedObject(Horizon system, string name) - { - if (system.AutoObjectNames.TryGetValue(name, out KAutoObject obj)) - { - return obj; - } - - return null; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KClientPort.cs b/Ryujinx.HLE/HOS/Kernel/KClientPort.cs deleted file mode 100644 index 57547627..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KClientPort.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - class KClientPort : KSynchronizationObject - { - private int _sessionsCount; - private int _currentCapacity; - private int _maxSessions; - - private KPort _parent; - - public KClientPort(Horizon system) : base(system) { } - - public void Initialize(KPort parent, int maxSessions) - { - _maxSessions = maxSessions; - _parent = parent; - } - - public new static KernelResult RemoveName(Horizon system, string name) - { - KAutoObject foundObj = FindNamedObject(system, name); - - if (!(foundObj is KClientPort)) - { - return KernelResult.NotFound; - } - - return KAutoObject.RemoveName(system, name); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KConditionVariable.cs b/Ryujinx.HLE/HOS/Kernel/KConditionVariable.cs deleted file mode 100644 index 15c96c24..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KConditionVariable.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.Collections.Generic; -using System.Threading; - -namespace Ryujinx.HLE.HOS.Kernel -{ - static class KConditionVariable - { - public static void Wait(Horizon system, LinkedList threadList, object mutex, long timeout) - { - KThread currentThread = system.Scheduler.GetCurrentThread(); - - system.CriticalSection.Enter(); - - Monitor.Exit(mutex); - - currentThread.Withholder = threadList; - - currentThread.Reschedule(ThreadSchedState.Paused); - - currentThread.WithholderNode = threadList.AddLast(currentThread); - - if (currentThread.ShallBeTerminated || - currentThread.SchedFlags == ThreadSchedState.TerminationPending) - { - threadList.Remove(currentThread.WithholderNode); - - currentThread.Reschedule(ThreadSchedState.Running); - - currentThread.Withholder = null; - - system.CriticalSection.Leave(); - } - else - { - if (timeout > 0) - { - system.TimeManager.ScheduleFutureInvocation(currentThread, timeout); - } - - system.CriticalSection.Leave(); - - if (timeout > 0) - { - system.TimeManager.UnscheduleFutureInvocation(currentThread); - } - } - - Monitor.Enter(mutex); - } - - public static void NotifyAll(Horizon system, LinkedList threadList) - { - system.CriticalSection.Enter(); - - LinkedListNode node = threadList.First; - - for (; node != null; node = threadList.First) - { - KThread thread = node.Value; - - threadList.Remove(thread.WithholderNode); - - thread.Withholder = null; - - thread.Reschedule(ThreadSchedState.Running); - } - - system.CriticalSection.Leave(); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KContextIdManager.cs b/Ryujinx.HLE/HOS/Kernel/KContextIdManager.cs deleted file mode 100644 index 80a1c1c7..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KContextIdManager.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Ryujinx.Common; -using System; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KContextIdManager - { - private const int IdMasksCount = 8; - - private int[] _idMasks; - - private int _nextFreeBitHint; - - public KContextIdManager() - { - _idMasks = new int[IdMasksCount]; - } - - public int GetId() - { - lock (_idMasks) - { - int id = 0; - - if (!TestBit(_nextFreeBitHint)) - { - id = _nextFreeBitHint; - } - else - { - for (int index = 0; index < IdMasksCount; index++) - { - int mask = _idMasks[index]; - - int firstFreeBit = BitUtils.CountLeadingZeros32((mask + 1) & ~mask); - - if (firstFreeBit < 32) - { - int baseBit = index * 32 + 31; - - id = baseBit - firstFreeBit; - - break; - } - else if (index == IdMasksCount - 1) - { - throw new InvalidOperationException("Maximum number of Ids reached!"); - } - } - } - - _nextFreeBitHint = id + 1; - - SetBit(id); - - return id; - } - } - - public void PutId(int id) - { - lock (_idMasks) - { - ClearBit(id); - } - } - - private bool TestBit(int bit) - { - return (_idMasks[_nextFreeBitHint / 32] & (1 << (_nextFreeBitHint & 31))) != 0; - } - - private void SetBit(int bit) - { - _idMasks[_nextFreeBitHint / 32] |= (1 << (_nextFreeBitHint & 31)); - } - - private void ClearBit(int bit) - { - _idMasks[_nextFreeBitHint / 32] &= ~(1 << (_nextFreeBitHint & 31)); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KCoreContext.cs b/Ryujinx.HLE/HOS/Kernel/KCoreContext.cs deleted file mode 100644 index 4ca3c25a..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KCoreContext.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Ryujinx.Common; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KCoreContext - { - private KScheduler _scheduler; - - private HleCoreManager _coreManager; - - public bool ContextSwitchNeeded { get; private set; } - - public long LastContextSwitchTime { get; private set; } - - public long TotalIdleTimeTicks { get; private set; } //TODO - - public KThread CurrentThread { get; private set; } - public KThread SelectedThread { get; private set; } - - public KCoreContext(KScheduler scheduler, HleCoreManager coreManager) - { - _scheduler = scheduler; - _coreManager = coreManager; - } - - public void SelectThread(KThread thread) - { - SelectedThread = thread; - - if (SelectedThread != CurrentThread) - { - ContextSwitchNeeded = true; - } - } - - public void UpdateCurrentThread() - { - ContextSwitchNeeded = false; - - LastContextSwitchTime = PerformanceCounter.ElapsedMilliseconds; - - CurrentThread = SelectedThread; - - if (CurrentThread != null) - { - long currentTime = PerformanceCounter.ElapsedMilliseconds; - - CurrentThread.TotalTimeRunning += currentTime - CurrentThread.LastScheduledTime; - CurrentThread.LastScheduledTime = currentTime; - } - } - - public void ContextSwitch() - { - ContextSwitchNeeded = false; - - LastContextSwitchTime = PerformanceCounter.ElapsedMilliseconds; - - if (CurrentThread != null) - { - _coreManager.Reset(CurrentThread.Context.Work); - } - - CurrentThread = SelectedThread; - - if (CurrentThread != null) - { - long currentTime = PerformanceCounter.ElapsedMilliseconds; - - CurrentThread.TotalTimeRunning += currentTime - CurrentThread.LastScheduledTime; - CurrentThread.LastScheduledTime = currentTime; - - CurrentThread.ClearExclusive(); - - _coreManager.Set(CurrentThread.Context.Work); - - CurrentThread.Context.Execute(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KCriticalSection.cs b/Ryujinx.HLE/HOS/Kernel/KCriticalSection.cs deleted file mode 100644 index a54dc6cb..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KCriticalSection.cs +++ /dev/null @@ -1,93 +0,0 @@ -using ChocolArm64; -using System.Threading; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KCriticalSection - { - private Horizon _system; - - public object LockObj { get; private set; } - - private int _recursionCount; - - public KCriticalSection(Horizon system) - { - _system = system; - - LockObj = new object(); - } - - public void Enter() - { - Monitor.Enter(LockObj); - - _recursionCount++; - } - - public void Leave() - { - if (_recursionCount == 0) - { - return; - } - - bool doContextSwitch = false; - - if (--_recursionCount == 0) - { - if (_system.Scheduler.ThreadReselectionRequested) - { - _system.Scheduler.SelectThreads(); - } - - Monitor.Exit(LockObj); - - if (_system.Scheduler.MultiCoreScheduling) - { - lock (_system.Scheduler.CoreContexts) - { - for (int core = 0; core < KScheduler.CpuCoresCount; core++) - { - KCoreContext coreContext = _system.Scheduler.CoreContexts[core]; - - if (coreContext.ContextSwitchNeeded) - { - CpuThread currentHleThread = coreContext.CurrentThread?.Context; - - if (currentHleThread == null) - { - //Nothing is running, we can perform the context switch immediately. - coreContext.ContextSwitch(); - } - else if (currentHleThread.IsCurrentThread()) - { - //Thread running on the current core, context switch will block. - doContextSwitch = true; - } - else - { - //Thread running on another core, request a interrupt. - currentHleThread.RequestInterrupt(); - } - } - } - } - } - else - { - doContextSwitch = true; - } - } - else - { - Monitor.Exit(LockObj); - } - - if (doContextSwitch) - { - _system.Scheduler.ContextSwitch(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KEvent.cs b/Ryujinx.HLE/HOS/Kernel/KEvent.cs deleted file mode 100644 index aa2124ec..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KEvent.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - class KEvent - { - public KReadableEvent ReadableEvent { get; private set; } - public KWritableEvent WritableEvent { get; private set; } - - public KEvent(Horizon system) - { - ReadableEvent = new KReadableEvent(system, this); - WritableEvent = new KWritableEvent(this); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KHandleEntry.cs b/Ryujinx.HLE/HOS/Kernel/KHandleEntry.cs deleted file mode 100644 index 42e59329..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KHandleEntry.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - class KHandleEntry - { - public KHandleEntry Next { get; set; } - - public int Index { get; private set; } - - public ushort HandleId { get; set; } - public object Obj { get; set; } - - public KHandleEntry(int index) - { - Index = index; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KHandleTable.cs b/Ryujinx.HLE/HOS/Kernel/KHandleTable.cs deleted file mode 100644 index 88d0c513..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KHandleTable.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KHandleTable - { - private const int SelfThreadHandle = (0x1ffff << 15) | 0; - private const int SelfProcessHandle = (0x1ffff << 15) | 1; - - private Horizon _system; - - private KHandleEntry[] _table; - - private KHandleEntry _tableHead; - private KHandleEntry _nextFreeEntry; - - private int _activeSlotsCount; - - private int _size; - - private ushort _idCounter; - - public KHandleTable(Horizon system) - { - _system = system; - } - - public KernelResult Initialize(int size) - { - if ((uint)size > 1024) - { - return KernelResult.OutOfMemory; - } - - if (size < 1) - { - size = 1024; - } - - _size = size; - - _idCounter = 1; - - _table = new KHandleEntry[size]; - - _tableHead = new KHandleEntry(0); - - KHandleEntry entry = _tableHead; - - for (int index = 0; index < size; index++) - { - _table[index] = entry; - - entry.Next = new KHandleEntry(index + 1); - - entry = entry.Next; - } - - _table[size - 1].Next = null; - - _nextFreeEntry = _tableHead; - - return KernelResult.Success; - } - - public KernelResult GenerateHandle(object obj, out int handle) - { - handle = 0; - - lock (_table) - { - if (_activeSlotsCount >= _size) - { - return KernelResult.HandleTableFull; - } - - KHandleEntry entry = _nextFreeEntry; - - _nextFreeEntry = entry.Next; - - entry.Obj = obj; - entry.HandleId = _idCounter; - - _activeSlotsCount++; - - handle = (int)((_idCounter << 15) & 0xffff8000) | entry.Index; - - if ((short)(_idCounter + 1) >= 0) - { - _idCounter++; - } - else - { - _idCounter = 1; - } - } - - return KernelResult.Success; - } - - public bool CloseHandle(int handle) - { - if ((handle >> 30) != 0 || - handle == SelfThreadHandle || - handle == SelfProcessHandle) - { - return false; - } - - int index = (handle >> 0) & 0x7fff; - int handleId = (handle >> 15); - - bool result = false; - - lock (_table) - { - if (handleId != 0 && index < _size) - { - KHandleEntry entry = _table[index]; - - if (entry.Obj != null && entry.HandleId == handleId) - { - entry.Obj = null; - entry.Next = _nextFreeEntry; - - _nextFreeEntry = entry; - - _activeSlotsCount--; - - result = true; - } - } - } - - return result; - } - - public T GetObject(int handle) - { - int index = (handle >> 0) & 0x7fff; - int handleId = (handle >> 15); - - lock (_table) - { - if ((handle >> 30) == 0 && handleId != 0) - { - KHandleEntry entry = _table[index]; - - if (entry.HandleId == handleId && entry.Obj is T obj) - { - return obj; - } - } - } - - return default(T); - } - - public KThread GetKThread(int handle) - { - if (handle == SelfThreadHandle) - { - return _system.Scheduler.GetCurrentThread(); - } - else - { - return GetObject(handle); - } - } - - public KProcess GetKProcess(int handle) - { - if (handle == SelfProcessHandle) - { - return _system.Scheduler.GetCurrentProcess(); - } - else - { - return GetObject(handle); - } - } - - public void Destroy() - { - lock (_table) - { - for (int index = 0; index < _size; index++) - { - KHandleEntry entry = _table[index]; - - if (entry.Obj != null) - { - if (entry.Obj is IDisposable disposableObj) - { - disposableObj.Dispose(); - } - - entry.Obj = null; - entry.Next = _nextFreeEntry; - - _nextFreeEntry = entry; - } - } - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryArrange.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryArrange.cs deleted file mode 100644 index de8bf3b1..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KMemoryArrange.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - class KMemoryArrange - { - public KMemoryArrangeRegion Service { get; private set; } - public KMemoryArrangeRegion NvServices { get; private set; } - public KMemoryArrangeRegion Applet { get; private set; } - public KMemoryArrangeRegion Application { get; private set; } - - public KMemoryArrange( - KMemoryArrangeRegion service, - KMemoryArrangeRegion nvServices, - KMemoryArrangeRegion applet, - KMemoryArrangeRegion application) - { - Service = service; - NvServices = nvServices; - Applet = applet; - Application = application; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryArrangeRegion.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryArrangeRegion.cs deleted file mode 100644 index 4fe58d73..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KMemoryArrangeRegion.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - struct KMemoryArrangeRegion - { - public ulong Address { get; private set; } - public ulong Size { get; private set; } - - public ulong EndAddr => Address + Size; - - public KMemoryArrangeRegion(ulong address, ulong size) - { - Address = address; - Size = size; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs deleted file mode 100644 index 44b7a683..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - class KMemoryBlock - { - public ulong BaseAddress { get; set; } - public ulong PagesCount { get; set; } - - public MemoryState State { get; set; } - public MemoryPermission Permission { get; set; } - public MemoryAttribute Attribute { get; set; } - - public int IpcRefCount { get; set; } - public int DeviceRefCount { get; set; } - - public KMemoryBlock( - ulong baseAddress, - ulong pagesCount, - MemoryState state, - MemoryPermission permission, - MemoryAttribute attribute) - { - BaseAddress = baseAddress; - PagesCount = pagesCount; - State = state; - Attribute = attribute; - Permission = permission; - } - - public KMemoryInfo GetInfo() - { - ulong size = PagesCount * KMemoryManager.PageSize; - - return new KMemoryInfo( - BaseAddress, - size, - State, - Permission, - Attribute, - IpcRefCount, - DeviceRefCount); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryBlockAllocator.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryBlockAllocator.cs deleted file mode 100644 index 375685e6..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KMemoryBlockAllocator.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - class KMemoryBlockAllocator - { - private ulong _capacityElements; - - public int Count { get; set; } - - public KMemoryBlockAllocator(ulong capacityElements) - { - _capacityElements = capacityElements; - } - - public bool CanAllocate(int count) - { - return (ulong)(Count + count) <= _capacityElements; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs deleted file mode 100644 index 0372e0d3..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - class KMemoryInfo - { - public ulong Address { get; private set; } - public ulong Size { get; private set; } - - public MemoryState State { get; private set; } - public MemoryPermission Permission { get; private set; } - public MemoryAttribute Attribute { get; private set; } - - public int IpcRefCount { get; private set; } - public int DeviceRefCount { get; private set; } - - public KMemoryInfo( - ulong address, - ulong size, - MemoryState state, - MemoryPermission permission, - MemoryAttribute attribute, - int ipcRefCount, - int deviceRefCount) - { - Address = address; - Size = size; - State = state; - Attribute = attribute; - Permission = permission; - IpcRefCount = ipcRefCount; - DeviceRefCount = deviceRefCount; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs deleted file mode 100644 index 831844c7..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs +++ /dev/null @@ -1,2458 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.Common; -using System; -using System.Collections.Generic; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KMemoryManager - { - public const int PageSize = 0x1000; - - private const int KMemoryBlockSize = 0x40; - - //We need 2 blocks for the case where a big block - //needs to be split in 2, plus one block that will be the new one inserted. - private const int MaxBlocksNeededForInsertion = 2; - - private LinkedList _blocks; - - private MemoryManager _cpuMemory; - - private Horizon _system; - - public ulong AddrSpaceStart { get; private set; } - public ulong AddrSpaceEnd { get; private set; } - - public ulong CodeRegionStart { get; private set; } - public ulong CodeRegionEnd { get; private set; } - - public ulong HeapRegionStart { get; private set; } - public ulong HeapRegionEnd { get; private set; } - - private ulong _currentHeapAddr; - - public ulong AliasRegionStart { get; private set; } - public ulong AliasRegionEnd { get; private set; } - - public ulong StackRegionStart { get; private set; } - public ulong StackRegionEnd { get; private set; } - - public ulong TlsIoRegionStart { get; private set; } - public ulong TlsIoRegionEnd { get; private set; } - - private ulong _heapCapacity; - - public ulong PhysicalMemoryUsage { get; private set; } - - private MemoryRegion _memRegion; - - private bool _aslrDisabled; - - public int AddrSpaceWidth { get; private set; } - - private bool _isKernel; - private bool _aslrEnabled; - - private KMemoryBlockAllocator _blockAllocator; - - private int _contextId; - - private MersenneTwister _randomNumberGenerator; - - public KMemoryManager(Horizon system, MemoryManager cpuMemory) - { - _system = system; - _cpuMemory = cpuMemory; - - _blocks = new LinkedList(); - } - - private static readonly int[] AddrSpaceSizes = new int[] { 32, 36, 32, 39 }; - - public KernelResult InitializeForProcess( - AddressSpaceType addrSpaceType, - bool aslrEnabled, - bool aslrDisabled, - MemoryRegion memRegion, - ulong address, - ulong size, - KMemoryBlockAllocator blockAllocator) - { - if ((uint)addrSpaceType > (uint)AddressSpaceType.Addr39Bits) - { - throw new ArgumentException(nameof(addrSpaceType)); - } - - _contextId = _system.ContextIdManager.GetId(); - - ulong addrSpaceBase = 0; - ulong addrSpaceSize = 1UL << AddrSpaceSizes[(int)addrSpaceType]; - - KernelResult result = CreateUserAddressSpace( - addrSpaceType, - aslrEnabled, - aslrDisabled, - addrSpaceBase, - addrSpaceSize, - memRegion, - address, - size, - blockAllocator); - - if (result != KernelResult.Success) - { - _system.ContextIdManager.PutId(_contextId); - } - - return result; - } - - private class Region - { - public ulong Start; - public ulong End; - public ulong Size; - public ulong AslrOffset; - } - - private KernelResult CreateUserAddressSpace( - AddressSpaceType addrSpaceType, - bool aslrEnabled, - bool aslrDisabled, - ulong addrSpaceStart, - ulong addrSpaceEnd, - MemoryRegion memRegion, - ulong address, - ulong size, - KMemoryBlockAllocator blockAllocator) - { - ulong endAddr = address + size; - - Region aliasRegion = new Region(); - Region heapRegion = new Region(); - Region stackRegion = new Region(); - Region tlsIoRegion = new Region(); - - ulong codeRegionSize; - ulong stackAndTlsIoStart; - ulong stackAndTlsIoEnd; - ulong baseAddress; - - switch (addrSpaceType) - { - case AddressSpaceType.Addr32Bits: - aliasRegion.Size = 0x40000000; - heapRegion.Size = 0x40000000; - stackRegion.Size = 0; - tlsIoRegion.Size = 0; - CodeRegionStart = 0x200000; - codeRegionSize = 0x3fe00000; - stackAndTlsIoStart = 0x200000; - stackAndTlsIoEnd = 0x40000000; - baseAddress = 0x200000; - AddrSpaceWidth = 32; - break; - - case AddressSpaceType.Addr36Bits: - aliasRegion.Size = 0x180000000; - heapRegion.Size = 0x180000000; - stackRegion.Size = 0; - tlsIoRegion.Size = 0; - CodeRegionStart = 0x8000000; - codeRegionSize = 0x78000000; - stackAndTlsIoStart = 0x8000000; - stackAndTlsIoEnd = 0x80000000; - baseAddress = 0x8000000; - AddrSpaceWidth = 36; - break; - - case AddressSpaceType.Addr32BitsNoMap: - aliasRegion.Size = 0; - heapRegion.Size = 0x80000000; - stackRegion.Size = 0; - tlsIoRegion.Size = 0; - CodeRegionStart = 0x200000; - codeRegionSize = 0x3fe00000; - stackAndTlsIoStart = 0x200000; - stackAndTlsIoEnd = 0x40000000; - baseAddress = 0x200000; - AddrSpaceWidth = 32; - break; - - case AddressSpaceType.Addr39Bits: - aliasRegion.Size = 0x1000000000; - heapRegion.Size = 0x180000000; - stackRegion.Size = 0x80000000; - tlsIoRegion.Size = 0x1000000000; - CodeRegionStart = BitUtils.AlignDown(address, 0x200000); - codeRegionSize = BitUtils.AlignUp (endAddr, 0x200000) - CodeRegionStart; - stackAndTlsIoStart = 0; - stackAndTlsIoEnd = 0; - baseAddress = 0x8000000; - AddrSpaceWidth = 39; - break; - - default: throw new ArgumentException(nameof(addrSpaceType)); - } - - CodeRegionEnd = CodeRegionStart + codeRegionSize; - - ulong mapBaseAddress; - ulong mapAvailableSize; - - if (CodeRegionStart - baseAddress >= addrSpaceEnd - CodeRegionEnd) - { - //Has more space before the start of the code region. - mapBaseAddress = baseAddress; - mapAvailableSize = CodeRegionStart - baseAddress; - } - else - { - //Has more space after the end of the code region. - mapBaseAddress = CodeRegionEnd; - mapAvailableSize = addrSpaceEnd - CodeRegionEnd; - } - - ulong mapTotalSize = aliasRegion.Size + heapRegion.Size + stackRegion.Size + tlsIoRegion.Size; - - ulong aslrMaxOffset = mapAvailableSize - mapTotalSize; - - _aslrEnabled = aslrEnabled; - - AddrSpaceStart = addrSpaceStart; - AddrSpaceEnd = addrSpaceEnd; - - _blockAllocator = blockAllocator; - - if (mapAvailableSize < mapTotalSize) - { - return KernelResult.OutOfMemory; - } - - if (aslrEnabled) - { - aliasRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset >> 21) << 21; - heapRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset >> 21) << 21; - stackRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset >> 21) << 21; - tlsIoRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset >> 21) << 21; - } - - //Regions are sorted based on ASLR offset. - //When ASLR is disabled, the order is Map, Heap, NewMap and TlsIo. - aliasRegion.Start = mapBaseAddress + aliasRegion.AslrOffset; - aliasRegion.End = aliasRegion.Start + aliasRegion.Size; - heapRegion.Start = mapBaseAddress + heapRegion.AslrOffset; - heapRegion.End = heapRegion.Start + heapRegion.Size; - stackRegion.Start = mapBaseAddress + stackRegion.AslrOffset; - stackRegion.End = stackRegion.Start + stackRegion.Size; - tlsIoRegion.Start = mapBaseAddress + tlsIoRegion.AslrOffset; - tlsIoRegion.End = tlsIoRegion.Start + tlsIoRegion.Size; - - SortRegion(heapRegion, aliasRegion); - - if (stackRegion.Size != 0) - { - SortRegion(stackRegion, aliasRegion); - SortRegion(stackRegion, heapRegion); - } - else - { - stackRegion.Start = stackAndTlsIoStart; - stackRegion.End = stackAndTlsIoEnd; - } - - if (tlsIoRegion.Size != 0) - { - SortRegion(tlsIoRegion, aliasRegion); - SortRegion(tlsIoRegion, heapRegion); - SortRegion(tlsIoRegion, stackRegion); - } - else - { - tlsIoRegion.Start = stackAndTlsIoStart; - tlsIoRegion.End = stackAndTlsIoEnd; - } - - AliasRegionStart = aliasRegion.Start; - AliasRegionEnd = aliasRegion.End; - HeapRegionStart = heapRegion.Start; - HeapRegionEnd = heapRegion.End; - StackRegionStart = stackRegion.Start; - StackRegionEnd = stackRegion.End; - TlsIoRegionStart = tlsIoRegion.Start; - TlsIoRegionEnd = tlsIoRegion.End; - - _currentHeapAddr = HeapRegionStart; - _heapCapacity = 0; - PhysicalMemoryUsage = 0; - - _memRegion = memRegion; - _aslrDisabled = aslrDisabled; - - return InitializeBlocks(addrSpaceStart, addrSpaceEnd); - } - - private ulong GetRandomValue(ulong min, ulong max) - { - return (ulong)GetRandomValue((long)min, (long)max); - } - - private long GetRandomValue(long min, long max) - { - if (_randomNumberGenerator == null) - { - _randomNumberGenerator = new MersenneTwister(0); - } - - return _randomNumberGenerator.GenRandomNumber(min, max); - } - - private static void SortRegion(Region lhs, Region rhs) - { - if (lhs.AslrOffset < rhs.AslrOffset) - { - rhs.Start += lhs.Size; - rhs.End += lhs.Size; - } - else - { - lhs.Start += rhs.Size; - lhs.End += rhs.Size; - } - } - - private KernelResult InitializeBlocks(ulong addrSpaceStart, ulong addrSpaceEnd) - { - //First insertion will always need only a single block, - //because there's nothing else to split. - if (!_blockAllocator.CanAllocate(1)) - { - return KernelResult.OutOfResource; - } - - ulong addrSpacePagesCount = (addrSpaceEnd - addrSpaceStart) / PageSize; - - InsertBlock(addrSpaceStart, addrSpacePagesCount, MemoryState.Unmapped); - - return KernelResult.Success; - } - - public KernelResult MapPages( - ulong address, - KPageList pageList, - MemoryState state, - MemoryPermission permission) - { - ulong pagesCount = pageList.GetPagesCount(); - - ulong size = pagesCount * PageSize; - - if (!ValidateRegionForState(address, size, state)) - { - return KernelResult.InvalidMemState; - } - - lock (_blocks) - { - if (!IsUnmapped(address, pagesCount * PageSize)) - { - return KernelResult.InvalidMemState; - } - - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) - { - return KernelResult.OutOfResource; - } - - KernelResult result = MapPages(address, pageList, permission); - - if (result == KernelResult.Success) - { - InsertBlock(address, pagesCount, state, permission); - } - - return result; - } - } - - public KernelResult UnmapPages(ulong address, KPageList pageList, MemoryState stateExpected) - { - ulong pagesCount = pageList.GetPagesCount(); - - ulong size = pagesCount * PageSize; - - ulong endAddr = address + size; - - ulong addrSpacePagesCount = (AddrSpaceEnd - AddrSpaceStart) / PageSize; - - if (AddrSpaceStart > address) - { - return KernelResult.InvalidMemState; - } - - if (addrSpacePagesCount < pagesCount) - { - return KernelResult.InvalidMemState; - } - - if (endAddr - 1 > AddrSpaceEnd - 1) - { - return KernelResult.InvalidMemState; - } - - lock (_blocks) - { - KPageList currentPageList = new KPageList(); - - AddVaRangeToPageList(currentPageList, address, pagesCount); - - if (!currentPageList.IsEqual(pageList)) - { - return KernelResult.InvalidMemRange; - } - - if (CheckRange( - address, - size, - MemoryState.Mask, - stateExpected, - MemoryPermission.None, - MemoryPermission.None, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out MemoryState state, - out _, - out _)) - { - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) - { - return KernelResult.OutOfResource; - } - - KernelResult result = MmuUnmap(address, pagesCount); - - if (result == KernelResult.Success) - { - InsertBlock(address, pagesCount, MemoryState.Unmapped); - } - - return result; - } - else - { - return KernelResult.InvalidMemState; - } - } - } - - public KernelResult MapNormalMemory(long address, long size, MemoryPermission permission) - { - //TODO. - return KernelResult.Success; - } - - public KernelResult MapIoMemory(long address, long size, MemoryPermission permission) - { - //TODO. - return KernelResult.Success; - } - - public KernelResult AllocateOrMapPa( - ulong neededPagesCount, - int alignment, - ulong srcPa, - bool map, - ulong regionStart, - ulong regionPagesCount, - MemoryState state, - MemoryPermission permission, - out ulong address) - { - address = 0; - - ulong regionSize = regionPagesCount * PageSize; - - ulong regionEndAddr = regionStart + regionSize; - - if (!ValidateRegionForState(regionStart, regionSize, state)) - { - return KernelResult.InvalidMemState; - } - - if (regionPagesCount <= neededPagesCount) - { - return KernelResult.OutOfMemory; - } - - ulong reservedPagesCount = _isKernel ? 1UL : 4UL; - - lock (_blocks) - { - if (_aslrEnabled) - { - ulong totalNeededSize = (reservedPagesCount + neededPagesCount) * PageSize; - - ulong remainingPages = regionPagesCount - neededPagesCount; - - ulong aslrMaxOffset = ((remainingPages + reservedPagesCount) * PageSize) / (ulong)alignment; - - for (int attempt = 0; attempt < 8; attempt++) - { - address = BitUtils.AlignDown(regionStart + GetRandomValue(0, aslrMaxOffset) * (ulong)alignment, alignment); - - ulong endAddr = address + totalNeededSize; - - KMemoryInfo info = FindBlock(address).GetInfo(); - - if (info.State != MemoryState.Unmapped) - { - continue; - } - - ulong currBaseAddr = info.Address + reservedPagesCount * PageSize; - ulong currEndAddr = info.Address + info.Size; - - if (address >= regionStart && - address >= currBaseAddr && - endAddr - 1 <= regionEndAddr - 1 && - endAddr - 1 <= currEndAddr - 1) - { - break; - } - } - - if (address == 0) - { - ulong aslrPage = GetRandomValue(0, aslrMaxOffset); - - address = FindFirstFit( - regionStart + aslrPage * PageSize, - regionPagesCount - aslrPage, - neededPagesCount, - alignment, - 0, - reservedPagesCount); - } - } - - if (address == 0) - { - address = FindFirstFit( - regionStart, - regionPagesCount, - neededPagesCount, - alignment, - 0, - reservedPagesCount); - } - - if (address == 0) - { - return KernelResult.OutOfMemory; - } - - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) - { - return KernelResult.OutOfResource; - } - - MemoryOperation operation = map - ? MemoryOperation.MapPa - : MemoryOperation.Allocate; - - KernelResult result = DoMmuOperation( - address, - neededPagesCount, - srcPa, - map, - permission, - operation); - - if (result != KernelResult.Success) - { - return result; - } - - InsertBlock(address, neededPagesCount, state, permission); - } - - return KernelResult.Success; - } - - public KernelResult MapNewProcessCode( - ulong address, - ulong pagesCount, - MemoryState state, - MemoryPermission permission) - { - ulong size = pagesCount * PageSize; - - if (!ValidateRegionForState(address, size, state)) - { - return KernelResult.InvalidMemState; - } - - lock (_blocks) - { - if (!IsUnmapped(address, size)) - { - return KernelResult.InvalidMemState; - } - - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) - { - return KernelResult.OutOfResource; - } - - KernelResult result = DoMmuOperation( - address, - pagesCount, - 0, - false, - permission, - MemoryOperation.Allocate); - - if (result == KernelResult.Success) - { - InsertBlock(address, pagesCount, state, permission); - } - - return result; - } - } - - public KernelResult MapProcessCodeMemory(ulong dst, ulong src, ulong size) - { - ulong pagesCount = size / PageSize; - - lock (_blocks) - { - bool success = CheckRange( - src, - size, - MemoryState.Mask, - MemoryState.Heap, - MemoryPermission.Mask, - MemoryPermission.ReadAndWrite, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out MemoryState state, - out MemoryPermission permission, - out _); - - success &= IsUnmapped(dst, size); - - if (success) - { - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) - { - return KernelResult.OutOfResource; - } - - KPageList pageList = new KPageList(); - - AddVaRangeToPageList(pageList, src, pagesCount); - - KernelResult result = MmuChangePermission(src, pagesCount, MemoryPermission.None); - - if (result != KernelResult.Success) - { - return result; - } - - result = MapPages(dst, pageList, MemoryPermission.None); - - if (result != KernelResult.Success) - { - MmuChangePermission(src, pagesCount, permission); - - return result; - } - - InsertBlock(src, pagesCount, state, MemoryPermission.None, MemoryAttribute.Borrowed); - InsertBlock(dst, pagesCount, MemoryState.ModCodeStatic); - - return KernelResult.Success; - } - else - { - return KernelResult.InvalidMemState; - } - } - } - - public KernelResult UnmapProcessCodeMemory(ulong dst, ulong src, ulong size) - { - ulong pagesCount = size / PageSize; - - lock (_blocks) - { - bool success = CheckRange( - src, - size, - MemoryState.Mask, - MemoryState.Heap, - MemoryPermission.None, - MemoryPermission.None, - MemoryAttribute.Mask, - MemoryAttribute.Borrowed, - MemoryAttribute.IpcAndDeviceMapped, - out _, - out _, - out _); - - success &= CheckRange( - dst, - PageSize, - MemoryState.UnmapProcessCodeMemoryAllowed, - MemoryState.UnmapProcessCodeMemoryAllowed, - MemoryPermission.None, - MemoryPermission.None, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out MemoryState state, - out _, - out _); - - success &= CheckRange( - dst, - size, - MemoryState.Mask, - state, - MemoryPermission.None, - MemoryPermission.None, - MemoryAttribute.Mask, - MemoryAttribute.None); - - if (success) - { - KernelResult result = MmuUnmap(dst, pagesCount); - - if (result != KernelResult.Success) - { - return result; - } - - //TODO: Missing some checks here. - - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) - { - return KernelResult.OutOfResource; - } - - InsertBlock(dst, pagesCount, MemoryState.Unmapped); - InsertBlock(src, pagesCount, MemoryState.Heap, MemoryPermission.ReadAndWrite); - - return KernelResult.Success; - } - else - { - return KernelResult.InvalidMemState; - } - } - } - - public KernelResult SetHeapSize(ulong size, out ulong address) - { - address = 0; - - if (size > HeapRegionEnd - HeapRegionStart) - { - return KernelResult.OutOfMemory; - } - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - ulong currentHeapSize = GetHeapSize(); - - if (currentHeapSize <= size) - { - //Expand. - ulong diffSize = size - currentHeapSize; - - lock (_blocks) - { - if (currentProcess.ResourceLimit != null && diffSize != 0 && - !currentProcess.ResourceLimit.Reserve(LimitableResource.Memory, diffSize)) - { - return KernelResult.ResLimitExceeded; - } - - ulong pagesCount = diffSize / PageSize; - - KMemoryRegionManager region = GetMemoryRegionManager(); - - KernelResult result = region.AllocatePages(pagesCount, _aslrDisabled, out KPageList pageList); - - void CleanUpForError() - { - if (pageList != null) - { - region.FreePages(pageList); - } - - if (currentProcess.ResourceLimit != null && diffSize != 0) - { - currentProcess.ResourceLimit.Release(LimitableResource.Memory, diffSize); - } - } - - if (result != KernelResult.Success) - { - CleanUpForError(); - - return result; - } - - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) - { - CleanUpForError(); - - return KernelResult.OutOfResource; - } - - if (!IsUnmapped(_currentHeapAddr, diffSize)) - { - CleanUpForError(); - - return KernelResult.InvalidMemState; - } - - result = DoMmuOperation( - _currentHeapAddr, - pagesCount, - pageList, - MemoryPermission.ReadAndWrite, - MemoryOperation.MapVa); - - if (result != KernelResult.Success) - { - CleanUpForError(); - - return result; - } - - InsertBlock(_currentHeapAddr, pagesCount, MemoryState.Heap, MemoryPermission.ReadAndWrite); - } - } - else - { - //Shrink. - ulong freeAddr = HeapRegionStart + size; - ulong diffSize = currentHeapSize - size; - - lock (_blocks) - { - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) - { - return KernelResult.OutOfResource; - } - - if (!CheckRange( - freeAddr, - diffSize, - MemoryState.Mask, - MemoryState.Heap, - MemoryPermission.Mask, - MemoryPermission.ReadAndWrite, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out _, - out _, - out _)) - { - return KernelResult.InvalidMemState; - } - - ulong pagesCount = diffSize / PageSize; - - KernelResult result = MmuUnmap(freeAddr, pagesCount); - - if (result != KernelResult.Success) - { - return result; - } - - currentProcess.ResourceLimit?.Release(LimitableResource.Memory, BitUtils.AlignDown(diffSize, PageSize)); - - InsertBlock(freeAddr, pagesCount, MemoryState.Unmapped); - } - } - - _currentHeapAddr = HeapRegionStart + size; - - address = HeapRegionStart; - - return KernelResult.Success; - } - - public ulong GetTotalHeapSize() - { - lock (_blocks) - { - return GetHeapSize() + PhysicalMemoryUsage; - } - } - - private ulong GetHeapSize() - { - return _currentHeapAddr - HeapRegionStart; - } - - public KernelResult SetHeapCapacity(ulong capacity) - { - lock (_blocks) - { - _heapCapacity = capacity; - } - - return KernelResult.Success; - } - - public KernelResult SetMemoryAttribute( - ulong address, - ulong size, - MemoryAttribute attributeMask, - MemoryAttribute attributeValue) - { - lock (_blocks) - { - if (CheckRange( - address, - size, - MemoryState.AttributeChangeAllowed, - MemoryState.AttributeChangeAllowed, - MemoryPermission.None, - MemoryPermission.None, - MemoryAttribute.BorrowedAndIpcMapped, - MemoryAttribute.None, - MemoryAttribute.DeviceMappedAndUncached, - out MemoryState state, - out MemoryPermission permission, - out MemoryAttribute attribute)) - { - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) - { - return KernelResult.OutOfResource; - } - - ulong pagesCount = size / PageSize; - - attribute &= ~attributeMask; - attribute |= attributeMask & attributeValue; - - InsertBlock(address, pagesCount, state, permission, attribute); - - return KernelResult.Success; - } - else - { - return KernelResult.InvalidMemState; - } - } - } - - public KMemoryInfo QueryMemory(ulong address) - { - if (address >= AddrSpaceStart && - address < AddrSpaceEnd) - { - lock (_blocks) - { - return FindBlock(address).GetInfo(); - } - } - else - { - return new KMemoryInfo( - AddrSpaceEnd, - ~AddrSpaceEnd + 1, - MemoryState.Reserved, - MemoryPermission.None, - MemoryAttribute.None, - 0, - 0); - } - } - - public KernelResult Map(ulong dst, ulong src, ulong size) - { - bool success; - - lock (_blocks) - { - success = CheckRange( - src, - size, - MemoryState.MapAllowed, - MemoryState.MapAllowed, - MemoryPermission.Mask, - MemoryPermission.ReadAndWrite, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out MemoryState srcState, - out _, - out _); - - success &= IsUnmapped(dst, size); - - if (success) - { - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) - { - return KernelResult.OutOfResource; - } - - ulong pagesCount = size / PageSize; - - KPageList pageList = new KPageList(); - - AddVaRangeToPageList(pageList, src, pagesCount); - - KernelResult result = MmuChangePermission(src, pagesCount, MemoryPermission.None); - - if (result != KernelResult.Success) - { - return result; - } - - result = MapPages(dst, pageList, MemoryPermission.ReadAndWrite); - - if (result != KernelResult.Success) - { - if (MmuChangePermission(src, pagesCount, MemoryPermission.ReadAndWrite) != KernelResult.Success) - { - throw new InvalidOperationException("Unexpected failure reverting memory permission."); - } - - return result; - } - - InsertBlock(src, pagesCount, srcState, MemoryPermission.None, MemoryAttribute.Borrowed); - InsertBlock(dst, pagesCount, MemoryState.Stack, MemoryPermission.ReadAndWrite); - - return KernelResult.Success; - } - else - { - return KernelResult.InvalidMemState; - } - } - } - - public KernelResult UnmapForKernel(ulong address, ulong pagesCount, MemoryState stateExpected) - { - ulong size = pagesCount * PageSize; - - lock (_blocks) - { - if (CheckRange( - address, - size, - MemoryState.Mask, - stateExpected, - MemoryPermission.None, - MemoryPermission.None, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out _, - out _, - out _)) - { - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) - { - return KernelResult.OutOfResource; - } - - KernelResult result = MmuUnmap(address, pagesCount); - - if (result == KernelResult.Success) - { - InsertBlock(address, pagesCount, MemoryState.Unmapped); - } - - return KernelResult.Success; - } - else - { - return KernelResult.InvalidMemState; - } - } - } - - public KernelResult Unmap(ulong dst, ulong src, ulong size) - { - bool success; - - lock (_blocks) - { - success = CheckRange( - src, - size, - MemoryState.MapAllowed, - MemoryState.MapAllowed, - MemoryPermission.Mask, - MemoryPermission.None, - MemoryAttribute.Mask, - MemoryAttribute.Borrowed, - MemoryAttribute.IpcAndDeviceMapped, - out MemoryState srcState, - out _, - out _); - - success &= CheckRange( - dst, - size, - MemoryState.Mask, - MemoryState.Stack, - MemoryPermission.None, - MemoryPermission.None, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out _, - out MemoryPermission dstPermission, - out _); - - if (success) - { - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) - { - return KernelResult.OutOfResource; - } - - ulong pagesCount = size / PageSize; - - KPageList srcPageList = new KPageList(); - KPageList dstPageList = new KPageList(); - - AddVaRangeToPageList(srcPageList, src, pagesCount); - AddVaRangeToPageList(dstPageList, dst, pagesCount); - - if (!dstPageList.IsEqual(srcPageList)) - { - return KernelResult.InvalidMemRange; - } - - KernelResult result = MmuUnmap(dst, pagesCount); - - if (result != KernelResult.Success) - { - return result; - } - - result = MmuChangePermission(src, pagesCount, MemoryPermission.ReadAndWrite); - - if (result != KernelResult.Success) - { - MapPages(dst, dstPageList, dstPermission); - - return result; - } - - InsertBlock(src, pagesCount, srcState, MemoryPermission.ReadAndWrite); - InsertBlock(dst, pagesCount, MemoryState.Unmapped); - - return KernelResult.Success; - } - else - { - return KernelResult.InvalidMemState; - } - } - } - - public KernelResult ReserveTransferMemory(ulong address, ulong size, MemoryPermission permission) - { - lock (_blocks) - { - if (CheckRange( - address, - size, - MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated, - MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated, - MemoryPermission.Mask, - MemoryPermission.ReadAndWrite, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out MemoryState state, - out _, - out MemoryAttribute attribute)) - { - //TODO: Missing checks. - - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) - { - return KernelResult.OutOfResource; - } - - ulong pagesCount = size / PageSize; - - attribute |= MemoryAttribute.Borrowed; - - InsertBlock(address, pagesCount, state, permission, attribute); - - return KernelResult.Success; - } - else - { - return KernelResult.InvalidMemState; - } - } - } - - public KernelResult ResetTransferMemory(ulong address, ulong size) - { - lock (_blocks) - { - if (CheckRange( - address, - size, - MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated, - MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated, - MemoryPermission.None, - MemoryPermission.None, - MemoryAttribute.Mask, - MemoryAttribute.Borrowed, - MemoryAttribute.IpcAndDeviceMapped, - out MemoryState state, - out _, - out _)) - { - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) - { - return KernelResult.OutOfResource; - } - - ulong pagesCount = size / PageSize; - - InsertBlock(address, pagesCount, state, MemoryPermission.ReadAndWrite); - - return KernelResult.Success; - } - else - { - return KernelResult.InvalidMemState; - } - } - } - - public KernelResult SetProcessMemoryPermission(ulong address, ulong size, MemoryPermission permission) - { - lock (_blocks) - { - if (CheckRange( - address, - size, - MemoryState.ProcessPermissionChangeAllowed, - MemoryState.ProcessPermissionChangeAllowed, - MemoryPermission.None, - MemoryPermission.None, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out MemoryState oldState, - out MemoryPermission oldPermission, - out _)) - { - MemoryState newState = oldState; - - //If writing into the code region is allowed, then we need - //to change it to mutable. - if ((permission & MemoryPermission.Write) != 0) - { - if (oldState == MemoryState.CodeStatic) - { - newState = MemoryState.CodeMutable; - } - else if (oldState == MemoryState.ModCodeStatic) - { - newState = MemoryState.ModCodeMutable; - } - else - { - throw new InvalidOperationException($"Memory state \"{oldState}\" not valid for this operation."); - } - } - - if (newState != oldState || permission != oldPermission) - { - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) - { - return KernelResult.OutOfResource; - } - - ulong pagesCount = size / PageSize; - - MemoryOperation operation = (permission & MemoryPermission.Execute) != 0 - ? MemoryOperation.ChangePermsAndAttributes - : MemoryOperation.ChangePermRw; - - KernelResult result = DoMmuOperation(address, pagesCount, 0, false, permission, operation); - - if (result != KernelResult.Success) - { - return result; - } - - InsertBlock(address, pagesCount, newState, permission); - } - - return KernelResult.Success; - } - else - { - return KernelResult.InvalidMemState; - } - } - } - - public KernelResult MapPhysicalMemory(ulong address, ulong size) - { - ulong endAddr = address + size; - - lock (_blocks) - { - ulong mappedSize = 0; - - KMemoryInfo info; - - LinkedListNode node = FindBlockNode(address); - - do - { - info = node.Value.GetInfo(); - - if (info.State != MemoryState.Unmapped) - { - mappedSize += GetSizeInRange(info, address, endAddr); - } - - node = node.Next; - } - while (info.Address + info.Size < endAddr && node != null); - - if (mappedSize == size) - { - return KernelResult.Success; - } - - ulong remainingSize = size - mappedSize; - - ulong remainingPages = remainingSize / PageSize; - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - if (currentProcess.ResourceLimit != null && - !currentProcess.ResourceLimit.Reserve(LimitableResource.Memory, remainingSize)) - { - return KernelResult.ResLimitExceeded; - } - - KMemoryRegionManager region = GetMemoryRegionManager(); - - KernelResult result = region.AllocatePages(remainingPages, _aslrDisabled, out KPageList pageList); - - void CleanUpForError() - { - if (pageList != null) - { - region.FreePages(pageList); - } - - currentProcess.ResourceLimit?.Release(LimitableResource.Memory, remainingSize); - } - - if (result != KernelResult.Success) - { - CleanUpForError(); - - return result; - } - - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) - { - CleanUpForError(); - - return KernelResult.OutOfResource; - } - - MapPhysicalMemory(pageList, address, endAddr); - - PhysicalMemoryUsage += remainingSize; - - ulong pagesCount = size / PageSize; - - InsertBlock( - address, - pagesCount, - MemoryState.Unmapped, - MemoryPermission.None, - MemoryAttribute.None, - MemoryState.Heap, - MemoryPermission.ReadAndWrite, - MemoryAttribute.None); - } - - return KernelResult.Success; - } - - public KernelResult UnmapPhysicalMemory(ulong address, ulong size) - { - ulong endAddr = address + size; - - lock (_blocks) - { - //Scan, ensure that the region can be unmapped (all blocks are heap or - //already unmapped), fill pages list for freeing memory. - ulong heapMappedSize = 0; - - KPageList pageList = new KPageList(); - - KMemoryInfo info; - - LinkedListNode baseNode = FindBlockNode(address); - - LinkedListNode node = baseNode; - - do - { - info = node.Value.GetInfo(); - - if (info.State == MemoryState.Heap) - { - if (info.Attribute != MemoryAttribute.None) - { - return KernelResult.InvalidMemState; - } - - ulong blockSize = GetSizeInRange(info, address, endAddr); - ulong blockAddress = GetAddrInRange(info, address); - - AddVaRangeToPageList(pageList, blockAddress, blockSize / PageSize); - - heapMappedSize += blockSize; - } - else if (info.State != MemoryState.Unmapped) - { - return KernelResult.InvalidMemState; - } - - node = node.Next; - } - while (info.Address + info.Size < endAddr && node != null); - - if (heapMappedSize == 0) - { - return KernelResult.Success; - } - - if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) - { - return KernelResult.OutOfResource; - } - - //Try to unmap all the heap mapped memory inside range. - KernelResult result = KernelResult.Success; - - node = baseNode; - - do - { - info = node.Value.GetInfo(); - - if (info.State == MemoryState.Heap) - { - ulong blockSize = GetSizeInRange(info, address, endAddr); - ulong blockAddress = GetAddrInRange(info, address); - - ulong blockPagesCount = blockSize / PageSize; - - result = MmuUnmap(blockAddress, blockPagesCount); - - if (result != KernelResult.Success) - { - //If we failed to unmap, we need to remap everything back again. - MapPhysicalMemory(pageList, address, blockAddress + blockSize); - - break; - } - } - - node = node.Next; - } - while (info.Address + info.Size < endAddr && node != null); - - if (result == KernelResult.Success) - { - GetMemoryRegionManager().FreePages(pageList); - - PhysicalMemoryUsage -= heapMappedSize; - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - currentProcess.ResourceLimit?.Release(LimitableResource.Memory, heapMappedSize); - - ulong pagesCount = size / PageSize; - - InsertBlock(address, pagesCount, MemoryState.Unmapped); - } - - return result; - } - } - - private void MapPhysicalMemory(KPageList pageList, ulong address, ulong endAddr) - { - KMemoryInfo info; - - LinkedListNode node = FindBlockNode(address); - - LinkedListNode pageListNode = pageList.Nodes.First; - - KPageNode pageNode = pageListNode.Value; - - ulong srcPa = pageNode.Address; - ulong srcPaPages = pageNode.PagesCount; - - do - { - info = node.Value.GetInfo(); - - if (info.State == MemoryState.Unmapped) - { - ulong blockSize = GetSizeInRange(info, address, endAddr); - - ulong dstVaPages = blockSize / PageSize; - - ulong dstVa = GetAddrInRange(info, address); - - while (dstVaPages > 0) - { - if (srcPaPages == 0) - { - pageListNode = pageListNode.Next; - - pageNode = pageListNode.Value; - - srcPa = pageNode.Address; - srcPaPages = pageNode.PagesCount; - } - - ulong pagesCount = srcPaPages; - - if (pagesCount > dstVaPages) - { - pagesCount = dstVaPages; - } - - DoMmuOperation( - dstVa, - pagesCount, - srcPa, - true, - MemoryPermission.ReadAndWrite, - MemoryOperation.MapPa); - - dstVa += pagesCount * PageSize; - srcPa += pagesCount * PageSize; - srcPaPages -= pagesCount; - dstVaPages -= pagesCount; - } - } - - node = node.Next; - } - while (info.Address + info.Size < endAddr && node != null); - } - - private static ulong GetSizeInRange(KMemoryInfo info, ulong start, ulong end) - { - ulong endAddr = info.Size + info.Address; - ulong size = info.Size; - - if (info.Address < start) - { - size -= start - info.Address; - } - - if (endAddr > end) - { - size -= endAddr - end; - } - - return size; - } - - private static ulong GetAddrInRange(KMemoryInfo info, ulong start) - { - if (info.Address < start) - { - return start; - } - - return info.Address; - } - - private void AddVaRangeToPageList(KPageList pageList, ulong start, ulong pagesCount) - { - ulong address = start; - - while (address < start + pagesCount * PageSize) - { - KernelResult result = ConvertVaToPa(address, out ulong pa); - - if (result != KernelResult.Success) - { - throw new InvalidOperationException("Unexpected failure translating virtual address."); - } - - pageList.AddRange(pa, 1); - - address += PageSize; - } - } - - private bool IsUnmapped(ulong address, ulong size) - { - return CheckRange( - address, - size, - MemoryState.Mask, - MemoryState.Unmapped, - MemoryPermission.Mask, - MemoryPermission.None, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out _, - out _, - out _); - } - - private bool CheckRange( - ulong address, - ulong size, - MemoryState stateMask, - MemoryState stateExpected, - MemoryPermission permissionMask, - MemoryPermission permissionExpected, - MemoryAttribute attributeMask, - MemoryAttribute attributeExpected, - MemoryAttribute attributeIgnoreMask, - out MemoryState outState, - out MemoryPermission outPermission, - out MemoryAttribute outAttribute) - { - ulong endAddr = address + size - 1; - - LinkedListNode node = FindBlockNode(address); - - KMemoryInfo info = node.Value.GetInfo(); - - MemoryState firstState = info.State; - MemoryPermission firstPermission = info.Permission; - MemoryAttribute firstAttribute = info.Attribute; - - do - { - info = node.Value.GetInfo(); - - //Check if the block state matches what we expect. - if ( firstState != info.State || - firstPermission != info.Permission || - (info.Attribute & attributeMask) != attributeExpected || - (firstAttribute | attributeIgnoreMask) != (info.Attribute | attributeIgnoreMask) || - (firstState & stateMask) != stateExpected || - (firstPermission & permissionMask) != permissionExpected) - { - break; - } - - //Check if this is the last block on the range, if so return success. - if (endAddr <= info.Address + info.Size - 1) - { - outState = firstState; - outPermission = firstPermission; - outAttribute = firstAttribute & ~attributeIgnoreMask; - - return true; - } - - node = node.Next; - } - while (node != null); - - outState = MemoryState.Unmapped; - outPermission = MemoryPermission.None; - outAttribute = MemoryAttribute.None; - - return false; - } - - private bool CheckRange( - ulong address, - ulong size, - MemoryState stateMask, - MemoryState stateExpected, - MemoryPermission permissionMask, - MemoryPermission permissionExpected, - MemoryAttribute attributeMask, - MemoryAttribute attributeExpected) - { - ulong endAddr = address + size - 1; - - LinkedListNode node = FindBlockNode(address); - - do - { - KMemoryInfo info = node.Value.GetInfo(); - - //Check if the block state matches what we expect. - if ((info.State & stateMask) != stateExpected || - (info.Permission & permissionMask) != permissionExpected || - (info.Attribute & attributeMask) != attributeExpected) - { - break; - } - - //Check if this is the last block on the range, if so return success. - if (endAddr <= info.Address + info.Size - 1) - { - return true; - } - - node = node.Next; - } - while (node != null); - - return false; - } - - private void InsertBlock( - ulong baseAddress, - ulong pagesCount, - MemoryState oldState, - MemoryPermission oldPermission, - MemoryAttribute oldAttribute, - MemoryState newState, - MemoryPermission newPermission, - MemoryAttribute newAttribute) - { - //Insert new block on the list only on areas where the state - //of the block matches the state specified on the Old* state - //arguments, otherwise leave it as is. - int oldCount = _blocks.Count; - - oldAttribute |= MemoryAttribute.IpcAndDeviceMapped; - - ulong endAddr = pagesCount * PageSize + baseAddress; - - LinkedListNode node = _blocks.First; - - while (node != null) - { - LinkedListNode newNode = node; - LinkedListNode nextNode = node.Next; - - KMemoryBlock currBlock = node.Value; - - ulong currBaseAddr = currBlock.BaseAddress; - ulong currEndAddr = currBlock.PagesCount * PageSize + currBaseAddr; - - if (baseAddress < currEndAddr && currBaseAddr < endAddr) - { - MemoryAttribute currBlockAttr = currBlock.Attribute | MemoryAttribute.IpcAndDeviceMapped; - - if (currBlock.State != oldState || - currBlock.Permission != oldPermission || - currBlockAttr != oldAttribute) - { - node = nextNode; - - continue; - } - - if (currBaseAddr >= baseAddress && currEndAddr <= endAddr) - { - currBlock.State = newState; - currBlock.Permission = newPermission; - currBlock.Attribute &= ~MemoryAttribute.IpcAndDeviceMapped; - currBlock.Attribute |= newAttribute; - } - else if (currBaseAddr >= baseAddress) - { - currBlock.BaseAddress = endAddr; - - currBlock.PagesCount = (currEndAddr - endAddr) / PageSize; - - ulong newPagesCount = (endAddr - currBaseAddr) / PageSize; - - newNode = _blocks.AddBefore(node, new KMemoryBlock( - currBaseAddr, - newPagesCount, - newState, - newPermission, - newAttribute)); - } - else if (currEndAddr <= endAddr) - { - currBlock.PagesCount = (baseAddress - currBaseAddr) / PageSize; - - ulong newPagesCount = (currEndAddr - baseAddress) / PageSize; - - newNode = _blocks.AddAfter(node, new KMemoryBlock( - baseAddress, - newPagesCount, - newState, - newPermission, - newAttribute)); - } - else - { - currBlock.PagesCount = (baseAddress - currBaseAddr) / PageSize; - - ulong nextPagesCount = (currEndAddr - endAddr) / PageSize; - - newNode = _blocks.AddAfter(node, new KMemoryBlock( - baseAddress, - pagesCount, - newState, - newPermission, - newAttribute)); - - _blocks.AddAfter(newNode, new KMemoryBlock( - endAddr, - nextPagesCount, - currBlock.State, - currBlock.Permission, - currBlock.Attribute)); - - nextNode = null; - } - - MergeEqualStateNeighbours(newNode); - } - - node = nextNode; - } - - _blockAllocator.Count += _blocks.Count - oldCount; - } - - private void InsertBlock( - ulong baseAddress, - ulong pagesCount, - MemoryState state, - MemoryPermission permission = MemoryPermission.None, - MemoryAttribute attribute = MemoryAttribute.None) - { - //Inserts new block at the list, replacing and spliting - //existing blocks as needed. - KMemoryBlock block = new KMemoryBlock(baseAddress, pagesCount, state, permission, attribute); - - int oldCount = _blocks.Count; - - ulong endAddr = pagesCount * PageSize + baseAddress; - - LinkedListNode newNode = null; - - LinkedListNode node = _blocks.First; - - while (node != null) - { - KMemoryBlock currBlock = node.Value; - - LinkedListNode nextNode = node.Next; - - ulong currBaseAddr = currBlock.BaseAddress; - ulong currEndAddr = currBlock.PagesCount * PageSize + currBaseAddr; - - if (baseAddress < currEndAddr && currBaseAddr < endAddr) - { - if (baseAddress >= currBaseAddr && endAddr <= currEndAddr) - { - block.Attribute |= currBlock.Attribute & MemoryAttribute.IpcAndDeviceMapped; - } - - if (baseAddress > currBaseAddr && endAddr < currEndAddr) - { - currBlock.PagesCount = (baseAddress - currBaseAddr) / PageSize; - - ulong nextPagesCount = (currEndAddr - endAddr) / PageSize; - - newNode = _blocks.AddAfter(node, block); - - _blocks.AddAfter(newNode, new KMemoryBlock( - endAddr, - nextPagesCount, - currBlock.State, - currBlock.Permission, - currBlock.Attribute)); - - break; - } - else if (baseAddress <= currBaseAddr && endAddr < currEndAddr) - { - currBlock.BaseAddress = endAddr; - - currBlock.PagesCount = (currEndAddr - endAddr) / PageSize; - - if (newNode == null) - { - newNode = _blocks.AddBefore(node, block); - } - } - else if (baseAddress > currBaseAddr && endAddr >= currEndAddr) - { - currBlock.PagesCount = (baseAddress - currBaseAddr) / PageSize; - - if (newNode == null) - { - newNode = _blocks.AddAfter(node, block); - } - } - else - { - if (newNode == null) - { - newNode = _blocks.AddBefore(node, block); - } - - _blocks.Remove(node); - } - } - - node = nextNode; - } - - if (newNode == null) - { - newNode = _blocks.AddFirst(block); - } - - MergeEqualStateNeighbours(newNode); - - _blockAllocator.Count += _blocks.Count - oldCount; - } - - private void MergeEqualStateNeighbours(LinkedListNode node) - { - KMemoryBlock block = node.Value; - - ulong endAddr = block.PagesCount * PageSize + block.BaseAddress; - - if (node.Previous != null) - { - KMemoryBlock previous = node.Previous.Value; - - if (BlockStateEquals(block, previous)) - { - _blocks.Remove(node.Previous); - - block.BaseAddress = previous.BaseAddress; - } - } - - if (node.Next != null) - { - KMemoryBlock next = node.Next.Value; - - if (BlockStateEquals(block, next)) - { - _blocks.Remove(node.Next); - - endAddr = next.BaseAddress + next.PagesCount * PageSize; - } - } - - block.PagesCount = (endAddr - block.BaseAddress) / PageSize; - } - - private static bool BlockStateEquals(KMemoryBlock lhs, KMemoryBlock rhs) - { - return lhs.State == rhs.State && - lhs.Permission == rhs.Permission && - lhs.Attribute == rhs.Attribute && - lhs.DeviceRefCount == rhs.DeviceRefCount && - lhs.IpcRefCount == rhs.IpcRefCount; - } - - private ulong FindFirstFit( - ulong regionStart, - ulong regionPagesCount, - ulong neededPagesCount, - int alignment, - ulong reservedStart, - ulong reservedPagesCount) - { - ulong reservedSize = reservedPagesCount * PageSize; - - ulong totalNeededSize = reservedSize + neededPagesCount * PageSize; - - ulong regionEndAddr = regionStart + regionPagesCount * PageSize; - - LinkedListNode node = FindBlockNode(regionStart); - - KMemoryInfo info = node.Value.GetInfo(); - - while (regionEndAddr >= info.Address) - { - if (info.State == MemoryState.Unmapped) - { - ulong currBaseAddr = info.Address + reservedSize; - ulong currEndAddr = info.Address + info.Size - 1; - - ulong address = BitUtils.AlignDown(currBaseAddr, alignment) + reservedStart; - - if (currBaseAddr > address) - { - address += (ulong)alignment; - } - - ulong allocationEndAddr = address + totalNeededSize - 1; - - if (allocationEndAddr <= regionEndAddr && - allocationEndAddr <= currEndAddr && - address < allocationEndAddr) - { - return address; - } - } - - node = node.Next; - - if (node == null) - { - break; - } - - info = node.Value.GetInfo(); - } - - return 0; - } - - private KMemoryBlock FindBlock(ulong address) - { - return FindBlockNode(address)?.Value; - } - - private LinkedListNode FindBlockNode(ulong address) - { - lock (_blocks) - { - LinkedListNode node = _blocks.First; - - while (node != null) - { - KMemoryBlock block = node.Value; - - ulong currEndAddr = block.PagesCount * PageSize + block.BaseAddress; - - if (block.BaseAddress <= address && currEndAddr - 1 >= address) - { - return node; - } - - node = node.Next; - } - } - - return null; - } - - private bool ValidateRegionForState(ulong address, ulong size, MemoryState state) - { - ulong endAddr = address + size; - - ulong regionBaseAddr = GetBaseAddrForState(state); - - ulong regionEndAddr = regionBaseAddr + GetSizeForState(state); - - bool InsideRegion() - { - return regionBaseAddr <= address && - endAddr > address && - endAddr - 1 <= regionEndAddr - 1; - } - - bool OutsideHeapRegion() - { - return endAddr <= HeapRegionStart || - address >= HeapRegionEnd; - } - - bool OutsideMapRegion() - { - return endAddr <= AliasRegionStart || - address >= AliasRegionEnd; - } - - switch (state) - { - case MemoryState.Io: - case MemoryState.Normal: - case MemoryState.CodeStatic: - case MemoryState.CodeMutable: - case MemoryState.SharedMemory: - case MemoryState.ModCodeStatic: - case MemoryState.ModCodeMutable: - case MemoryState.Stack: - case MemoryState.ThreadLocal: - case MemoryState.TransferMemoryIsolated: - case MemoryState.TransferMemory: - case MemoryState.ProcessMemory: - case MemoryState.CodeReadOnly: - case MemoryState.CodeWritable: - return InsideRegion() && OutsideHeapRegion() && OutsideMapRegion(); - - case MemoryState.Heap: - return InsideRegion() && OutsideMapRegion(); - - case MemoryState.IpcBuffer0: - case MemoryState.IpcBuffer1: - case MemoryState.IpcBuffer3: - return InsideRegion() && OutsideHeapRegion(); - - case MemoryState.KernelStack: - return InsideRegion(); - } - - throw new ArgumentException($"Invalid state value \"{state}\"."); - } - - private ulong GetBaseAddrForState(MemoryState state) - { - switch (state) - { - case MemoryState.Io: - case MemoryState.Normal: - case MemoryState.ThreadLocal: - return TlsIoRegionStart; - - case MemoryState.CodeStatic: - case MemoryState.CodeMutable: - case MemoryState.SharedMemory: - case MemoryState.ModCodeStatic: - case MemoryState.ModCodeMutable: - case MemoryState.TransferMemoryIsolated: - case MemoryState.TransferMemory: - case MemoryState.ProcessMemory: - case MemoryState.CodeReadOnly: - case MemoryState.CodeWritable: - return GetAddrSpaceBaseAddr(); - - case MemoryState.Heap: - return HeapRegionStart; - - case MemoryState.IpcBuffer0: - case MemoryState.IpcBuffer1: - case MemoryState.IpcBuffer3: - return AliasRegionStart; - - case MemoryState.Stack: - return StackRegionStart; - - case MemoryState.KernelStack: - return AddrSpaceStart; - } - - throw new ArgumentException($"Invalid state value \"{state}\"."); - } - - private ulong GetSizeForState(MemoryState state) - { - switch (state) - { - case MemoryState.Io: - case MemoryState.Normal: - case MemoryState.ThreadLocal: - return TlsIoRegionEnd - TlsIoRegionStart; - - case MemoryState.CodeStatic: - case MemoryState.CodeMutable: - case MemoryState.SharedMemory: - case MemoryState.ModCodeStatic: - case MemoryState.ModCodeMutable: - case MemoryState.TransferMemoryIsolated: - case MemoryState.TransferMemory: - case MemoryState.ProcessMemory: - case MemoryState.CodeReadOnly: - case MemoryState.CodeWritable: - return GetAddrSpaceSize(); - - case MemoryState.Heap: - return HeapRegionEnd - HeapRegionStart; - - case MemoryState.IpcBuffer0: - case MemoryState.IpcBuffer1: - case MemoryState.IpcBuffer3: - return AliasRegionEnd - AliasRegionStart; - - case MemoryState.Stack: - return StackRegionEnd - StackRegionStart; - - case MemoryState.KernelStack: - return AddrSpaceEnd - AddrSpaceStart; - } - - throw new ArgumentException($"Invalid state value \"{state}\"."); - } - - public ulong GetAddrSpaceBaseAddr() - { - if (AddrSpaceWidth == 36 || AddrSpaceWidth == 39) - { - return 0x8000000; - } - else if (AddrSpaceWidth == 32) - { - return 0x200000; - } - else - { - throw new InvalidOperationException("Invalid address space width!"); - } - } - - public ulong GetAddrSpaceSize() - { - if (AddrSpaceWidth == 36) - { - return 0xff8000000; - } - else if (AddrSpaceWidth == 39) - { - return 0x7ff8000000; - } - else if (AddrSpaceWidth == 32) - { - return 0xffe00000; - } - else - { - throw new InvalidOperationException("Invalid address space width!"); - } - } - - private KernelResult MapPages(ulong address, KPageList pageList, MemoryPermission permission) - { - ulong currAddr = address; - - KernelResult result = KernelResult.Success; - - foreach (KPageNode pageNode in pageList) - { - result = DoMmuOperation( - currAddr, - pageNode.PagesCount, - pageNode.Address, - true, - permission, - MemoryOperation.MapPa); - - if (result != KernelResult.Success) - { - KMemoryInfo info = FindBlock(currAddr).GetInfo(); - - ulong pagesCount = (address - currAddr) / PageSize; - - result = MmuUnmap(address, pagesCount); - - break; - } - - currAddr += pageNode.PagesCount * PageSize; - } - - return result; - } - - private KernelResult MmuUnmap(ulong address, ulong pagesCount) - { - return DoMmuOperation( - address, - pagesCount, - 0, - false, - MemoryPermission.None, - MemoryOperation.Unmap); - } - - private KernelResult MmuChangePermission(ulong address, ulong pagesCount, MemoryPermission permission) - { - return DoMmuOperation( - address, - pagesCount, - 0, - false, - permission, - MemoryOperation.ChangePermRw); - } - - private KernelResult DoMmuOperation( - ulong dstVa, - ulong pagesCount, - ulong srcPa, - bool map, - MemoryPermission permission, - MemoryOperation operation) - { - if (map != (operation == MemoryOperation.MapPa)) - { - throw new ArgumentException(nameof(map) + " value is invalid for this operation."); - } - - KernelResult result; - - switch (operation) - { - case MemoryOperation.MapPa: - { - ulong size = pagesCount * PageSize; - - _cpuMemory.Map((long)dstVa, (long)(srcPa - DramMemoryMap.DramBase), (long)size); - - result = KernelResult.Success; - - break; - } - - case MemoryOperation.Allocate: - { - KMemoryRegionManager region = GetMemoryRegionManager(); - - result = region.AllocatePages(pagesCount, _aslrDisabled, out KPageList pageList); - - if (result == KernelResult.Success) - { - result = MmuMapPages(dstVa, pageList); - } - - break; - } - - case MemoryOperation.Unmap: - { - ulong size = pagesCount * PageSize; - - _cpuMemory.Unmap((long)dstVa, (long)size); - - result = KernelResult.Success; - - break; - } - - case MemoryOperation.ChangePermRw: result = KernelResult.Success; break; - case MemoryOperation.ChangePermsAndAttributes: result = KernelResult.Success; break; - - default: throw new ArgumentException($"Invalid operation \"{operation}\"."); - } - - return result; - } - - private KernelResult DoMmuOperation( - ulong address, - ulong pagesCount, - KPageList pageList, - MemoryPermission permission, - MemoryOperation operation) - { - if (operation != MemoryOperation.MapVa) - { - throw new ArgumentException($"Invalid memory operation \"{operation}\" specified."); - } - - return MmuMapPages(address, pageList); - } - - private KMemoryRegionManager GetMemoryRegionManager() - { - return _system.MemoryRegions[(int)_memRegion]; - } - - private KernelResult MmuMapPages(ulong address, KPageList pageList) - { - foreach (KPageNode pageNode in pageList) - { - ulong size = pageNode.PagesCount * PageSize; - - _cpuMemory.Map((long)address, (long)(pageNode.Address - DramMemoryMap.DramBase), (long)size); - - address += size; - } - - return KernelResult.Success; - } - - public KernelResult ConvertVaToPa(ulong va, out ulong pa) - { - pa = DramMemoryMap.DramBase + (ulong)_cpuMemory.GetPhysicalAddress((long)va); - - return KernelResult.Success; - } - - public long GetMmUsedPages() - { - lock (_blocks) - { - return BitUtils.DivRoundUp(GetMmUsedSize(), PageSize); - } - } - - private long GetMmUsedSize() - { - return _blocks.Count * KMemoryBlockSize; - } - - public bool IsInvalidRegion(ulong address, ulong size) - { - return address + size - 1 > GetAddrSpaceBaseAddr() + GetAddrSpaceSize() - 1; - } - - public bool InsideAddrSpace(ulong address, ulong size) - { - return AddrSpaceStart <= address && address + size - 1 <= AddrSpaceEnd - 1; - } - - public bool InsideAliasRegion(ulong address, ulong size) - { - return address + size > AliasRegionStart && AliasRegionEnd > address; - } - - public bool InsideHeapRegion(ulong address, ulong size) - { - return address + size > HeapRegionStart && HeapRegionEnd > address; - } - - public bool InsideStackRegion(ulong address, ulong size) - { - return address + size > StackRegionStart && StackRegionEnd > address; - } - - public bool OutsideAliasRegion(ulong address, ulong size) - { - return AliasRegionStart > address || address + size - 1 > AliasRegionEnd - 1; - } - - public bool OutsideAddrSpace(ulong address, ulong size) - { - return AddrSpaceStart > address || address + size - 1 > AddrSpaceEnd - 1; - } - - public bool OutsideStackRegion(ulong address, ulong size) - { - return StackRegionStart > address || address + size - 1 > StackRegionEnd - 1; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryRegionBlock.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryRegionBlock.cs deleted file mode 100644 index f7e85e9a..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KMemoryRegionBlock.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - class KMemoryRegionBlock - { - public long[][] Masks; - - public ulong FreeCount; - public int MaxLevel; - public ulong StartAligned; - public ulong SizeInBlocksTruncated; - public ulong SizeInBlocksRounded; - public int Order; - public int NextOrder; - - public bool TryCoalesce(int index, int size) - { - long mask = ((1L << size) - 1) << (index & 63); - - index /= 64; - - if ((mask & ~Masks[MaxLevel - 1][index]) != 0) - { - return false; - } - - Masks[MaxLevel - 1][index] &= ~mask; - - for (int level = MaxLevel - 2; level >= 0; level--, index /= 64) - { - Masks[level][index / 64] &= ~(1L << (index & 63)); - - if (Masks[level][index / 64] != 0) - { - break; - } - } - - FreeCount -= (ulong)size; - - return true; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryRegionManager.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryRegionManager.cs deleted file mode 100644 index b9265b13..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KMemoryRegionManager.cs +++ /dev/null @@ -1,428 +0,0 @@ -using Ryujinx.Common; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KMemoryRegionManager - { - private static readonly int[] BlockOrders = new int[] { 12, 16, 21, 22, 25, 29, 30 }; - - public ulong Address { get; private set; } - public ulong EndAddr { get; private set; } - public ulong Size { get; private set; } - - private int _blockOrdersCount; - - private KMemoryRegionBlock[] _blocks; - - public KMemoryRegionManager(ulong address, ulong size, ulong endAddr) - { - _blocks = new KMemoryRegionBlock[BlockOrders.Length]; - - Address = address; - Size = size; - EndAddr = endAddr; - - _blockOrdersCount = BlockOrders.Length; - - for (int blockIndex = 0; blockIndex < _blockOrdersCount; blockIndex++) - { - _blocks[blockIndex] = new KMemoryRegionBlock(); - - _blocks[blockIndex].Order = BlockOrders[blockIndex]; - - int nextOrder = blockIndex == _blockOrdersCount - 1 ? 0 : BlockOrders[blockIndex + 1]; - - _blocks[blockIndex].NextOrder = nextOrder; - - int currBlockSize = 1 << BlockOrders[blockIndex]; - int nextBlockSize = currBlockSize; - - if (nextOrder != 0) - { - nextBlockSize = 1 << nextOrder; - } - - ulong startAligned = BitUtils.AlignDown(address, nextBlockSize); - ulong endAddrAligned = BitUtils.AlignDown(endAddr, currBlockSize); - - ulong sizeInBlocksTruncated = (endAddrAligned - startAligned) >> BlockOrders[blockIndex]; - - ulong endAddrRounded = BitUtils.AlignUp(address + size, nextBlockSize); - - ulong sizeInBlocksRounded = (endAddrRounded - startAligned) >> BlockOrders[blockIndex]; - - _blocks[blockIndex].StartAligned = startAligned; - _blocks[blockIndex].SizeInBlocksTruncated = sizeInBlocksTruncated; - _blocks[blockIndex].SizeInBlocksRounded = sizeInBlocksRounded; - - ulong currSizeInBlocks = sizeInBlocksRounded; - - int maxLevel = 0; - - do - { - maxLevel++; - } - while ((currSizeInBlocks /= 64) != 0); - - _blocks[blockIndex].MaxLevel = maxLevel; - - _blocks[blockIndex].Masks = new long[maxLevel][]; - - currSizeInBlocks = sizeInBlocksRounded; - - for (int level = maxLevel - 1; level >= 0; level--) - { - currSizeInBlocks = (currSizeInBlocks + 63) / 64; - - _blocks[blockIndex].Masks[level] = new long[currSizeInBlocks]; - } - } - - if (size != 0) - { - FreePages(address, size / KMemoryManager.PageSize); - } - } - - public KernelResult AllocatePages(ulong pagesCount, bool backwards, out KPageList pageList) - { - lock (_blocks) - { - return AllocatePagesImpl(pagesCount, backwards, out pageList); - } - } - - private KernelResult AllocatePagesImpl(ulong pagesCount, bool backwards, out KPageList pageList) - { - pageList = new KPageList(); - - if (_blockOrdersCount > 0) - { - if (GetFreePagesImpl() < pagesCount) - { - return KernelResult.OutOfMemory; - } - } - else if (pagesCount != 0) - { - return KernelResult.OutOfMemory; - } - - for (int blockIndex = _blockOrdersCount - 1; blockIndex >= 0; blockIndex--) - { - KMemoryRegionBlock block = _blocks[blockIndex]; - - ulong bestFitBlockSize = 1UL << block.Order; - - ulong blockPagesCount = bestFitBlockSize / KMemoryManager.PageSize; - - //Check if this is the best fit for this page size. - //If so, try allocating as much requested pages as possible. - while (blockPagesCount <= pagesCount) - { - ulong address = 0; - - for (int currBlockIndex = blockIndex; - currBlockIndex < _blockOrdersCount && address == 0; - currBlockIndex++) - { - block = _blocks[currBlockIndex]; - - int index = 0; - - bool zeroMask = false; - - for (int level = 0; level < block.MaxLevel; level++) - { - long mask = block.Masks[level][index]; - - if (mask == 0) - { - zeroMask = true; - - break; - } - - if (backwards) - { - index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask); - } - else - { - index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask)); - } - } - - if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask) - { - continue; - } - - block.FreeCount--; - - int tempIdx = index; - - for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64) - { - block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63)); - - if (block.Masks[level][tempIdx / 64] != 0) - { - break; - } - } - - address = block.StartAligned + ((ulong)index << block.Order); - } - - for (int currBlockIndex = blockIndex; - currBlockIndex < _blockOrdersCount && address == 0; - currBlockIndex++) - { - block = _blocks[currBlockIndex]; - - int index = 0; - - bool zeroMask = false; - - for (int level = 0; level < block.MaxLevel; level++) - { - long mask = block.Masks[level][index]; - - if (mask == 0) - { - zeroMask = true; - - break; - } - - if (backwards) - { - index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask)); - } - else - { - index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask); - } - } - - if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask) - { - continue; - } - - block.FreeCount--; - - int tempIdx = index; - - for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64) - { - block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63)); - - if (block.Masks[level][tempIdx / 64] != 0) - { - break; - } - } - - address = block.StartAligned + ((ulong)index << block.Order); - } - - //The address being zero means that no free space was found on that order, - //just give up and try with the next one. - if (address == 0) - { - break; - } - - //If we are using a larger order than best fit, then we should - //split it into smaller blocks. - ulong firstFreeBlockSize = 1UL << block.Order; - - if (firstFreeBlockSize > bestFitBlockSize) - { - FreePages(address + bestFitBlockSize, (firstFreeBlockSize - bestFitBlockSize) / KMemoryManager.PageSize); - } - - //Add new allocated page(s) to the pages list. - //If an error occurs, then free all allocated pages and fail. - KernelResult result = pageList.AddRange(address, blockPagesCount); - - if (result != KernelResult.Success) - { - FreePages(address, blockPagesCount); - - foreach (KPageNode pageNode in pageList) - { - FreePages(pageNode.Address, pageNode.PagesCount); - } - - return result; - } - - pagesCount -= blockPagesCount; - } - } - - //Success case, all requested pages were allocated successfully. - if (pagesCount == 0) - { - return KernelResult.Success; - } - - //Error case, free allocated pages and return out of memory. - foreach (KPageNode pageNode in pageList) - { - FreePages(pageNode.Address, pageNode.PagesCount); - } - - pageList = null; - - return KernelResult.OutOfMemory; - } - - public void FreePages(KPageList pageList) - { - lock (_blocks) - { - foreach (KPageNode pageNode in pageList) - { - FreePages(pageNode.Address, pageNode.PagesCount); - } - } - } - - private void FreePages(ulong address, ulong pagesCount) - { - ulong endAddr = address + pagesCount * KMemoryManager.PageSize; - - int blockIndex = _blockOrdersCount - 1; - - ulong addressRounded = 0; - ulong endAddrTruncated = 0; - - for (; blockIndex >= 0; blockIndex--) - { - KMemoryRegionBlock allocInfo = _blocks[blockIndex]; - - int blockSize = 1 << allocInfo.Order; - - addressRounded = BitUtils.AlignUp (address, blockSize); - endAddrTruncated = BitUtils.AlignDown(endAddr, blockSize); - - if (addressRounded < endAddrTruncated) - { - break; - } - } - - void FreeRegion(ulong currAddress) - { - for (int currBlockIndex = blockIndex; - currBlockIndex < _blockOrdersCount && currAddress != 0; - currBlockIndex++) - { - KMemoryRegionBlock block = _blocks[currBlockIndex]; - - block.FreeCount++; - - ulong freedBlocks = (currAddress - block.StartAligned) >> block.Order; - - int index = (int)freedBlocks; - - for (int level = block.MaxLevel - 1; level >= 0; level--, index /= 64) - { - long mask = block.Masks[level][index / 64]; - - block.Masks[level][index / 64] = mask | (1L << (index & 63)); - - if (mask != 0) - { - break; - } - } - - int blockSizeDelta = 1 << (block.NextOrder - block.Order); - - int freedBlocksTruncated = BitUtils.AlignDown((int)freedBlocks, blockSizeDelta); - - if (!block.TryCoalesce(freedBlocksTruncated, blockSizeDelta)) - { - break; - } - - currAddress = block.StartAligned + ((ulong)freedBlocksTruncated << block.Order); - } - } - - //Free inside aligned region. - ulong baseAddress = addressRounded; - - while (baseAddress < endAddrTruncated) - { - ulong blockSize = 1UL << _blocks[blockIndex].Order; - - FreeRegion(baseAddress); - - baseAddress += blockSize; - } - - int nextBlockIndex = blockIndex - 1; - - //Free region between Address and aligned region start. - baseAddress = addressRounded; - - for (blockIndex = nextBlockIndex; blockIndex >= 0; blockIndex--) - { - ulong blockSize = 1UL << _blocks[blockIndex].Order; - - while (baseAddress - blockSize >= address) - { - baseAddress -= blockSize; - - FreeRegion(baseAddress); - } - } - - //Free region between aligned region end and End Address. - baseAddress = endAddrTruncated; - - for (blockIndex = nextBlockIndex; blockIndex >= 0; blockIndex--) - { - ulong blockSize = 1UL << _blocks[blockIndex].Order; - - while (baseAddress + blockSize <= endAddr) - { - FreeRegion(baseAddress); - - baseAddress += blockSize; - } - } - } - - public ulong GetFreePages() - { - lock (_blocks) - { - return GetFreePagesImpl(); - } - } - - private ulong GetFreePagesImpl() - { - ulong availablePages = 0; - - for (int blockIndex = 0; blockIndex < _blockOrdersCount; blockIndex++) - { - KMemoryRegionBlock block = _blocks[blockIndex]; - - ulong blockPagesCount = (1UL << block.Order) / KMemoryManager.PageSize; - - availablePages += blockPagesCount * block.FreeCount; - } - - return availablePages; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KPageList.cs b/Ryujinx.HLE/HOS/Kernel/KPageList.cs deleted file mode 100644 index b24d126f..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KPageList.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.Collections; -using System.Collections.Generic; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KPageList : IEnumerable - { - public LinkedList Nodes { get; private set; } - - public KPageList() - { - Nodes = new LinkedList(); - } - - public KernelResult AddRange(ulong address, ulong pagesCount) - { - if (pagesCount != 0) - { - if (Nodes.Last != null) - { - KPageNode lastNode = Nodes.Last.Value; - - if (lastNode.Address + lastNode.PagesCount * KMemoryManager.PageSize == address) - { - address = lastNode.Address; - pagesCount += lastNode.PagesCount; - - Nodes.RemoveLast(); - } - } - - Nodes.AddLast(new KPageNode(address, pagesCount)); - } - - return KernelResult.Success; - } - - public ulong GetPagesCount() - { - ulong sum = 0; - - foreach (KPageNode node in Nodes) - { - sum += node.PagesCount; - } - - return sum; - } - - public bool IsEqual(KPageList other) - { - LinkedListNode thisNode = Nodes.First; - LinkedListNode otherNode = other.Nodes.First; - - while (thisNode != null && otherNode != null) - { - if (thisNode.Value.Address != otherNode.Value.Address || - thisNode.Value.PagesCount != otherNode.Value.PagesCount) - { - return false; - } - - thisNode = thisNode.Next; - otherNode = otherNode.Next; - } - - return thisNode == null && otherNode == null; - } - - public IEnumerator GetEnumerator() - { - return Nodes.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KPageNode.cs b/Ryujinx.HLE/HOS/Kernel/KPageNode.cs deleted file mode 100644 index 5cdb1c49..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KPageNode.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - struct KPageNode - { - public ulong Address; - public ulong PagesCount; - - public KPageNode(ulong address, ulong pagesCount) - { - Address = address; - PagesCount = pagesCount; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KPort.cs b/Ryujinx.HLE/HOS/Kernel/KPort.cs deleted file mode 100644 index a6c5b375..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KPort.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - class KPort : KAutoObject - { - public KServerPort ServerPort { get; private set; } - public KClientPort ClientPort { get; private set; } - - private long _nameAddress; - private bool _isLight; - - public KPort(Horizon system) : base(system) - { - ServerPort = new KServerPort(system); - ClientPort = new KClientPort(system); - } - - public void Initialize(int maxSessions, bool isLight, long nameAddress) - { - ServerPort.Initialize(this); - ClientPort.Initialize(this, maxSessions); - - _isLight = isLight; - _nameAddress = nameAddress; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/KProcess.cs deleted file mode 100644 index 6d91f41c..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KProcess.cs +++ /dev/null @@ -1,1013 +0,0 @@ -using ChocolArm64; -using ChocolArm64.Events; -using ChocolArm64.Memory; -using Ryujinx.Common; -using Ryujinx.Common.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KProcess : KSynchronizationObject - { - public const int KernelVersionMajor = 10; - public const int KernelVersionMinor = 4; - public const int KernelVersionRevision = 0; - - public const int KernelVersionPacked = - (KernelVersionMajor << 19) | - (KernelVersionMinor << 15) | - (KernelVersionRevision << 0); - - public KMemoryManager MemoryManager { get; private set; } - - private SortedDictionary _fullTlsPages; - private SortedDictionary _freeTlsPages; - - public int DefaultCpuCore { get; private set; } - - public bool Debug { get; private set; } - - public KResourceLimit ResourceLimit { get; private set; } - - public ulong PersonalMmHeapPagesCount { get; private set; } - - private ProcessState _state; - - private object _processLock; - private object _threadingLock; - - public KAddressArbiter AddressArbiter { get; private set; } - - public long[] RandomEntropy { get; private set; } - - private bool _signaled; - private bool _useSystemMemBlocks; - - public string Name { get; private set; } - - private int _threadCount; - - public int MmuFlags { get; private set; } - - private MemoryRegion _memRegion; - - public KProcessCapabilities Capabilities { get; private set; } - - public long TitleId { get; private set; } - public long Pid { get; private set; } - - private long _creationTimestamp; - private ulong _entrypoint; - private ulong _imageSize; - private ulong _mainThreadStackSize; - private ulong _memoryUsageCapacity; - private int _category; - - public KHandleTable HandleTable { get; private set; } - - public ulong UserExceptionContextAddress { get; private set; } - - private LinkedList _threads; - - public bool IsPaused { get; private set; } - - public Translator Translator { get; private set; } - - public MemoryManager CpuMemory { get; private set; } - - private SvcHandler _svcHandler; - - public HleProcessDebugger Debugger { get; private set; } - - public KProcess(Horizon system) : base(system) - { - _processLock = new object(); - _threadingLock = new object(); - - CpuMemory = new MemoryManager(system.Device.Memory.RamPointer); - - CpuMemory.InvalidAccess += InvalidAccessHandler; - - AddressArbiter = new KAddressArbiter(system); - - MemoryManager = new KMemoryManager(system, CpuMemory); - - _fullTlsPages = new SortedDictionary(); - _freeTlsPages = new SortedDictionary(); - - Capabilities = new KProcessCapabilities(); - - RandomEntropy = new long[KScheduler.CpuCoresCount]; - - _threads = new LinkedList(); - - Translator = new Translator(); - - Translator.CpuTrace += CpuTraceHandler; - - _svcHandler = new SvcHandler(system.Device, this); - - Debugger = new HleProcessDebugger(this); - } - - public KernelResult InitializeKip( - ProcessCreationInfo creationInfo, - int[] caps, - KPageList pageList, - KResourceLimit resourceLimit, - MemoryRegion memRegion) - { - ResourceLimit = resourceLimit; - _memRegion = memRegion; - - AddressSpaceType addrSpaceType = (AddressSpaceType)((creationInfo.MmuFlags >> 1) & 7); - - bool aslrEnabled = ((creationInfo.MmuFlags >> 5) & 1) != 0; - - ulong codeAddress = creationInfo.CodeAddress; - - ulong codeSize = (ulong)creationInfo.CodePagesCount * KMemoryManager.PageSize; - - KMemoryBlockAllocator memoryBlockAllocator = (MmuFlags & 0x40) != 0 - ? System.LargeMemoryBlockAllocator - : System.SmallMemoryBlockAllocator; - - KernelResult result = MemoryManager.InitializeForProcess( - addrSpaceType, - aslrEnabled, - !aslrEnabled, - memRegion, - codeAddress, - codeSize, - memoryBlockAllocator); - - if (result != KernelResult.Success) - { - return result; - } - - if (!ValidateCodeAddressAndSize(codeAddress, codeSize)) - { - return KernelResult.InvalidMemRange; - } - - result = MemoryManager.MapPages( - codeAddress, - pageList, - MemoryState.CodeStatic, - MemoryPermission.None); - - if (result != KernelResult.Success) - { - return result; - } - - result = Capabilities.InitializeForKernel(caps, MemoryManager); - - if (result != KernelResult.Success) - { - return result; - } - - Pid = System.GetKipId(); - - if (Pid == 0 || (ulong)Pid >= Horizon.InitialProcessId) - { - throw new InvalidOperationException($"Invalid KIP Id {Pid}."); - } - - result = ParseProcessInfo(creationInfo); - - return result; - } - - public KernelResult Initialize( - ProcessCreationInfo creationInfo, - int[] caps, - KResourceLimit resourceLimit, - MemoryRegion memRegion) - { - ResourceLimit = resourceLimit; - _memRegion = memRegion; - - ulong personalMmHeapSize = GetPersonalMmHeapSize((ulong)creationInfo.PersonalMmHeapPagesCount, memRegion); - - ulong codePagesCount = (ulong)creationInfo.CodePagesCount; - - ulong neededSizeForProcess = personalMmHeapSize + codePagesCount * KMemoryManager.PageSize; - - if (neededSizeForProcess != 0 && resourceLimit != null) - { - if (!resourceLimit.Reserve(LimitableResource.Memory, neededSizeForProcess)) - { - return KernelResult.ResLimitExceeded; - } - } - - void CleanUpForError() - { - if (neededSizeForProcess != 0 && resourceLimit != null) - { - resourceLimit.Release(LimitableResource.Memory, neededSizeForProcess); - } - } - - PersonalMmHeapPagesCount = (ulong)creationInfo.PersonalMmHeapPagesCount; - - KMemoryBlockAllocator memoryBlockAllocator; - - if (PersonalMmHeapPagesCount != 0) - { - memoryBlockAllocator = new KMemoryBlockAllocator(PersonalMmHeapPagesCount * KMemoryManager.PageSize); - } - else - { - memoryBlockAllocator = (MmuFlags & 0x40) != 0 - ? System.LargeMemoryBlockAllocator - : System.SmallMemoryBlockAllocator; - } - - AddressSpaceType addrSpaceType = (AddressSpaceType)((creationInfo.MmuFlags >> 1) & 7); - - bool aslrEnabled = ((creationInfo.MmuFlags >> 5) & 1) != 0; - - ulong codeAddress = creationInfo.CodeAddress; - - ulong codeSize = codePagesCount * KMemoryManager.PageSize; - - KernelResult result = MemoryManager.InitializeForProcess( - addrSpaceType, - aslrEnabled, - !aslrEnabled, - memRegion, - codeAddress, - codeSize, - memoryBlockAllocator); - - if (result != KernelResult.Success) - { - CleanUpForError(); - - return result; - } - - if (!ValidateCodeAddressAndSize(codeAddress, codeSize)) - { - CleanUpForError(); - - return KernelResult.InvalidMemRange; - } - - result = MemoryManager.MapNewProcessCode( - codeAddress, - codePagesCount, - MemoryState.CodeStatic, - MemoryPermission.None); - - if (result != KernelResult.Success) - { - CleanUpForError(); - - return result; - } - - result = Capabilities.InitializeForUser(caps, MemoryManager); - - if (result != KernelResult.Success) - { - CleanUpForError(); - - return result; - } - - Pid = System.GetProcessId(); - - if (Pid == -1 || (ulong)Pid < Horizon.InitialProcessId) - { - throw new InvalidOperationException($"Invalid Process Id {Pid}."); - } - - result = ParseProcessInfo(creationInfo); - - if (result != KernelResult.Success) - { - CleanUpForError(); - } - - return result; - } - - private bool ValidateCodeAddressAndSize(ulong address, ulong size) - { - ulong codeRegionStart; - ulong codeRegionSize; - - switch (MemoryManager.AddrSpaceWidth) - { - case 32: - codeRegionStart = 0x200000; - codeRegionSize = 0x3fe00000; - break; - - case 36: - codeRegionStart = 0x8000000; - codeRegionSize = 0x78000000; - break; - - case 39: - codeRegionStart = 0x8000000; - codeRegionSize = 0x7ff8000000; - break; - - default: throw new InvalidOperationException("Invalid address space width on memory manager."); - } - - ulong endAddr = address + size; - - ulong codeRegionEnd = codeRegionStart + codeRegionSize; - - if (endAddr <= address || - endAddr - 1 > codeRegionEnd - 1) - { - return false; - } - - if (MemoryManager.InsideHeapRegion (address, size) || - MemoryManager.InsideAliasRegion(address, size)) - { - return false; - } - - return true; - } - - private KernelResult ParseProcessInfo(ProcessCreationInfo creationInfo) - { - //Ensure that the current kernel version is equal or above to the minimum required. - uint requiredKernelVersionMajor = (uint)Capabilities.KernelReleaseVersion >> 19; - uint requiredKernelVersionMinor = ((uint)Capabilities.KernelReleaseVersion >> 15) & 0xf; - - if (System.EnableVersionChecks) - { - if (requiredKernelVersionMajor > KernelVersionMajor) - { - return KernelResult.InvalidCombination; - } - - if (requiredKernelVersionMajor != KernelVersionMajor && requiredKernelVersionMajor < 3) - { - return KernelResult.InvalidCombination; - } - - if (requiredKernelVersionMinor > KernelVersionMinor) - { - return KernelResult.InvalidCombination; - } - } - - KernelResult result = AllocateThreadLocalStorage(out ulong userExceptionContextAddress); - - if (result != KernelResult.Success) - { - return result; - } - - UserExceptionContextAddress = userExceptionContextAddress; - - MemoryHelper.FillWithZeros(CpuMemory, (long)userExceptionContextAddress, KTlsPageInfo.TlsEntrySize); - - Name = creationInfo.Name; - - _state = ProcessState.Created; - - _creationTimestamp = PerformanceCounter.ElapsedMilliseconds; - - MmuFlags = creationInfo.MmuFlags; - _category = creationInfo.Category; - TitleId = creationInfo.TitleId; - _entrypoint = creationInfo.CodeAddress; - _imageSize = (ulong)creationInfo.CodePagesCount * KMemoryManager.PageSize; - - _useSystemMemBlocks = ((MmuFlags >> 6) & 1) != 0; - - switch ((AddressSpaceType)((MmuFlags >> 1) & 7)) - { - case AddressSpaceType.Addr32Bits: - case AddressSpaceType.Addr36Bits: - case AddressSpaceType.Addr39Bits: - _memoryUsageCapacity = MemoryManager.HeapRegionEnd - - MemoryManager.HeapRegionStart; - break; - - case AddressSpaceType.Addr32BitsNoMap: - _memoryUsageCapacity = MemoryManager.HeapRegionEnd - - MemoryManager.HeapRegionStart + - MemoryManager.AliasRegionEnd - - MemoryManager.AliasRegionStart; - break; - - default: throw new InvalidOperationException($"Invalid MMU flags value 0x{MmuFlags:x2}."); - } - - GenerateRandomEntropy(); - - return KernelResult.Success; - } - - public KernelResult AllocateThreadLocalStorage(out ulong address) - { - System.CriticalSection.Enter(); - - KernelResult result; - - if (_freeTlsPages.Count > 0) - { - //If we have free TLS pages available, just use the first one. - KTlsPageInfo pageInfo = _freeTlsPages.Values.First(); - - if (!pageInfo.TryGetFreePage(out address)) - { - throw new InvalidOperationException("Unexpected failure getting free TLS page!"); - } - - if (pageInfo.IsFull()) - { - _freeTlsPages.Remove(pageInfo.PageAddr); - - _fullTlsPages.Add(pageInfo.PageAddr, pageInfo); - } - - result = KernelResult.Success; - } - else - { - //Otherwise, we need to create a new one. - result = AllocateTlsPage(out KTlsPageInfo pageInfo); - - if (result == KernelResult.Success) - { - if (!pageInfo.TryGetFreePage(out address)) - { - throw new InvalidOperationException("Unexpected failure getting free TLS page!"); - } - - _freeTlsPages.Add(pageInfo.PageAddr, pageInfo); - } - else - { - address = 0; - } - } - - System.CriticalSection.Leave(); - - return result; - } - - private KernelResult AllocateTlsPage(out KTlsPageInfo pageInfo) - { - pageInfo = default(KTlsPageInfo); - - if (!System.UserSlabHeapPages.TryGetItem(out ulong tlsPagePa)) - { - return KernelResult.OutOfMemory; - } - - ulong regionStart = MemoryManager.TlsIoRegionStart; - ulong regionSize = MemoryManager.TlsIoRegionEnd - regionStart; - - ulong regionPagesCount = regionSize / KMemoryManager.PageSize; - - KernelResult result = MemoryManager.AllocateOrMapPa( - 1, - KMemoryManager.PageSize, - tlsPagePa, - true, - regionStart, - regionPagesCount, - MemoryState.ThreadLocal, - MemoryPermission.ReadAndWrite, - out ulong tlsPageVa); - - if (result != KernelResult.Success) - { - System.UserSlabHeapPages.Free(tlsPagePa); - } - else - { - pageInfo = new KTlsPageInfo(tlsPageVa); - - MemoryHelper.FillWithZeros(CpuMemory, (long)tlsPageVa, KMemoryManager.PageSize); - } - - return result; - } - - public KernelResult FreeThreadLocalStorage(ulong tlsSlotAddr) - { - ulong tlsPageAddr = BitUtils.AlignDown(tlsSlotAddr, KMemoryManager.PageSize); - - System.CriticalSection.Enter(); - - KernelResult result = KernelResult.Success; - - KTlsPageInfo pageInfo = null; - - if (_fullTlsPages.TryGetValue(tlsPageAddr, out pageInfo)) - { - //TLS page was full, free slot and move to free pages tree. - _fullTlsPages.Remove(tlsPageAddr); - - _freeTlsPages.Add(tlsPageAddr, pageInfo); - } - else if (!_freeTlsPages.TryGetValue(tlsPageAddr, out pageInfo)) - { - result = KernelResult.InvalidAddress; - } - - if (pageInfo != null) - { - pageInfo.FreeTlsSlot(tlsSlotAddr); - - if (pageInfo.IsEmpty()) - { - //TLS page is now empty, we should ensure it is removed - //from all trees, and free the memory it was using. - _freeTlsPages.Remove(tlsPageAddr); - - System.CriticalSection.Leave(); - - FreeTlsPage(pageInfo); - - return KernelResult.Success; - } - } - - System.CriticalSection.Leave(); - - return result; - } - - private KernelResult FreeTlsPage(KTlsPageInfo pageInfo) - { - KernelResult result = MemoryManager.ConvertVaToPa(pageInfo.PageAddr, out ulong tlsPagePa); - - if (result != KernelResult.Success) - { - throw new InvalidOperationException("Unexpected failure translating virtual address to physical."); - } - - result = MemoryManager.UnmapForKernel(pageInfo.PageAddr, 1, MemoryState.ThreadLocal); - - if (result == KernelResult.Success) - { - System.UserSlabHeapPages.Free(tlsPagePa); - } - - return result; - } - - private void GenerateRandomEntropy() - { - //TODO. - } - - public KernelResult Start(int mainThreadPriority, ulong stackSize) - { - lock (_processLock) - { - if (_state > ProcessState.CreatedAttached) - { - return KernelResult.InvalidState; - } - - if (ResourceLimit != null && !ResourceLimit.Reserve(LimitableResource.Thread, 1)) - { - return KernelResult.ResLimitExceeded; - } - - KResourceLimit threadResourceLimit = ResourceLimit; - KResourceLimit memoryResourceLimit = null; - - if (_mainThreadStackSize != 0) - { - throw new InvalidOperationException("Trying to start a process with a invalid state!"); - } - - ulong stackSizeRounded = BitUtils.AlignUp(stackSize, KMemoryManager.PageSize); - - ulong neededSize = stackSizeRounded + _imageSize; - - //Check if the needed size for the code and the stack will fit on the - //memory usage capacity of this Process. Also check for possible overflow - //on the above addition. - if (neededSize > _memoryUsageCapacity || - neededSize < stackSizeRounded) - { - threadResourceLimit?.Release(LimitableResource.Thread, 1); - - return KernelResult.OutOfMemory; - } - - if (stackSizeRounded != 0 && ResourceLimit != null) - { - memoryResourceLimit = ResourceLimit; - - if (!memoryResourceLimit.Reserve(LimitableResource.Memory, stackSizeRounded)) - { - threadResourceLimit?.Release(LimitableResource.Thread, 1); - - return KernelResult.ResLimitExceeded; - } - } - - KernelResult result; - - KThread mainThread = null; - - ulong stackTop = 0; - - void CleanUpForError() - { - mainThread?.Terminate(); - HandleTable.Destroy(); - - if (_mainThreadStackSize != 0) - { - ulong stackBottom = stackTop - _mainThreadStackSize; - - ulong stackPagesCount = _mainThreadStackSize / KMemoryManager.PageSize; - - MemoryManager.UnmapForKernel(stackBottom, stackPagesCount, MemoryState.Stack); - } - - memoryResourceLimit?.Release(LimitableResource.Memory, stackSizeRounded); - threadResourceLimit?.Release(LimitableResource.Thread, 1); - } - - if (stackSizeRounded != 0) - { - ulong stackPagesCount = stackSizeRounded / KMemoryManager.PageSize; - - ulong regionStart = MemoryManager.StackRegionStart; - ulong regionSize = MemoryManager.StackRegionEnd - regionStart; - - ulong regionPagesCount = regionSize / KMemoryManager.PageSize; - - result = MemoryManager.AllocateOrMapPa( - stackPagesCount, - KMemoryManager.PageSize, - 0, - false, - regionStart, - regionPagesCount, - MemoryState.Stack, - MemoryPermission.ReadAndWrite, - out ulong stackBottom); - - if (result != KernelResult.Success) - { - CleanUpForError(); - - return result; - } - - _mainThreadStackSize += stackSizeRounded; - - stackTop = stackBottom + stackSizeRounded; - } - - ulong heapCapacity = _memoryUsageCapacity - _mainThreadStackSize - _imageSize; - - result = MemoryManager.SetHeapCapacity(heapCapacity); - - if (result != KernelResult.Success) - { - CleanUpForError(); - - return result; - } - - HandleTable = new KHandleTable(System); - - result = HandleTable.Initialize(Capabilities.HandleTableSize); - - if (result != KernelResult.Success) - { - CleanUpForError(); - - return result; - } - - mainThread = new KThread(System); - - result = mainThread.Initialize( - _entrypoint, - 0, - stackTop, - mainThreadPriority, - DefaultCpuCore, - this); - - if (result != KernelResult.Success) - { - CleanUpForError(); - - return result; - } - - result = HandleTable.GenerateHandle(mainThread, out int mainThreadHandle); - - if (result != KernelResult.Success) - { - CleanUpForError(); - - return result; - } - - mainThread.SetEntryArguments(0, mainThreadHandle); - - ProcessState oldState = _state; - ProcessState newState = _state != ProcessState.Created - ? ProcessState.Attached - : ProcessState.Started; - - SetState(newState); - - //TODO: We can't call KThread.Start from a non-guest thread. - //We will need to make some changes to allow the creation of - //dummy threads that will be used to initialize the current - //thread on KCoreContext so that GetCurrentThread doesn't fail. - /* Result = MainThread.Start(); - - if (Result != KernelResult.Success) - { - SetState(OldState); - - CleanUpForError(); - } */ - - mainThread.Reschedule(ThreadSchedState.Running); - - return result; - } - } - - private void SetState(ProcessState newState) - { - if (_state != newState) - { - _state = newState; - _signaled = true; - - Signal(); - } - } - - public KernelResult InitializeThread( - KThread thread, - ulong entrypoint, - ulong argsPtr, - ulong stackTop, - int priority, - int cpuCore) - { - lock (_processLock) - { - return thread.Initialize(entrypoint, argsPtr, stackTop, priority, cpuCore, this); - } - } - - public void SubscribeThreadEventHandlers(CpuThread context) - { - context.ThreadState.Interrupt += InterruptHandler; - context.ThreadState.SvcCall += _svcHandler.SvcCall; - } - - private void InterruptHandler(object sender, EventArgs e) - { - System.Scheduler.ContextSwitch(); - } - - public void IncrementThreadCount() - { - Interlocked.Increment(ref _threadCount); - - System.ThreadCounter.AddCount(); - } - - public void DecrementThreadCountAndTerminateIfZero() - { - System.ThreadCounter.Signal(); - - if (Interlocked.Decrement(ref _threadCount) == 0) - { - Terminate(); - } - } - - public ulong GetMemoryCapacity() - { - ulong totalCapacity = (ulong)ResourceLimit.GetRemainingValue(LimitableResource.Memory); - - totalCapacity += MemoryManager.GetTotalHeapSize(); - - totalCapacity += GetPersonalMmHeapSize(); - - totalCapacity += _imageSize + _mainThreadStackSize; - - if (totalCapacity <= _memoryUsageCapacity) - { - return totalCapacity; - } - - return _memoryUsageCapacity; - } - - public ulong GetMemoryUsage() - { - return _imageSize + _mainThreadStackSize + MemoryManager.GetTotalHeapSize() + GetPersonalMmHeapSize(); - } - - public ulong GetMemoryCapacityWithoutPersonalMmHeap() - { - return GetMemoryCapacity() - GetPersonalMmHeapSize(); - } - - public ulong GetMemoryUsageWithoutPersonalMmHeap() - { - return GetMemoryUsage() - GetPersonalMmHeapSize(); - } - - private ulong GetPersonalMmHeapSize() - { - return GetPersonalMmHeapSize(PersonalMmHeapPagesCount, _memRegion); - } - - private static ulong GetPersonalMmHeapSize(ulong personalMmHeapPagesCount, MemoryRegion memRegion) - { - if (memRegion == MemoryRegion.Applet) - { - return 0; - } - - return personalMmHeapPagesCount * KMemoryManager.PageSize; - } - - public void AddThread(KThread thread) - { - lock (_threadingLock) - { - thread.ProcessListNode = _threads.AddLast(thread); - } - } - - public void RemoveThread(KThread thread) - { - lock (_threadingLock) - { - _threads.Remove(thread.ProcessListNode); - } - } - - public bool IsCpuCoreAllowed(int core) - { - return (Capabilities.AllowedCpuCoresMask & (1L << core)) != 0; - } - - public bool IsPriorityAllowed(int priority) - { - return (Capabilities.AllowedThreadPriosMask & (1L << priority)) != 0; - } - - public override bool IsSignaled() - { - return _signaled; - } - - public KernelResult Terminate() - { - KernelResult result; - - bool shallTerminate = false; - - System.CriticalSection.Enter(); - - lock (_processLock) - { - if (_state >= ProcessState.Started) - { - if (_state == ProcessState.Started || - _state == ProcessState.Crashed || - _state == ProcessState.Attached || - _state == ProcessState.DebugSuspended) - { - SetState(ProcessState.Exiting); - - shallTerminate = true; - } - - result = KernelResult.Success; - } - else - { - result = KernelResult.InvalidState; - } - } - - System.CriticalSection.Leave(); - - if (shallTerminate) - { - //UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread()); - - HandleTable.Destroy(); - - SignalExitForDebugEvent(); - SignalExit(); - } - - return result; - } - - private void UnpauseAndTerminateAllThreadsExcept(KThread thread) - { - //TODO. - } - - private void SignalExitForDebugEvent() - { - //TODO: Debug events. - } - - private void SignalExit() - { - if (ResourceLimit != null) - { - ResourceLimit.Release(LimitableResource.Memory, GetMemoryUsage()); - } - - System.CriticalSection.Enter(); - - SetState(ProcessState.Exited); - - System.CriticalSection.Leave(); - } - - public KernelResult ClearIfNotExited() - { - KernelResult result; - - System.CriticalSection.Enter(); - - lock (_processLock) - { - if (_state != ProcessState.Exited && _signaled) - { - _signaled = false; - - result = KernelResult.Success; - } - else - { - result = KernelResult.InvalidState; - } - } - - System.CriticalSection.Leave(); - - return result; - } - - public void StopAllThreads() - { - lock (_threadingLock) - { - foreach (KThread thread in _threads) - { - thread.Context.StopExecution(); - - System.Scheduler.CoreManager.Set(thread.Context.Work); - } - } - } - - private void InvalidAccessHandler(object sender, MemoryAccessEventArgs e) - { - PrintCurrentThreadStackTrace(); - } - - public void PrintCurrentThreadStackTrace() - { - System.Scheduler.GetCurrentThread().PrintGuestStackTrace(); - } - - private void CpuTraceHandler(object sender, CpuTraceEventArgs e) - { - Logger.PrintInfo(LogClass.Cpu, $"Executing at 0x{e.Position:X16}."); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KProcessCapabilities.cs b/Ryujinx.HLE/HOS/Kernel/KProcessCapabilities.cs deleted file mode 100644 index 4dc8500c..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KProcessCapabilities.cs +++ /dev/null @@ -1,311 +0,0 @@ -using Ryujinx.Common; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KProcessCapabilities - { - public byte[] SvcAccessMask { get; private set; } - public byte[] IrqAccessMask { get; private set; } - - public long AllowedCpuCoresMask { get; private set; } - public long AllowedThreadPriosMask { get; private set; } - - public int DebuggingFlags { get; private set; } - public int HandleTableSize { get; private set; } - public int KernelReleaseVersion { get; private set; } - public int ApplicationType { get; private set; } - - public KProcessCapabilities() - { - SvcAccessMask = new byte[0x10]; - IrqAccessMask = new byte[0x80]; - } - - public KernelResult InitializeForKernel(int[] caps, KMemoryManager memoryManager) - { - AllowedCpuCoresMask = 0xf; - AllowedThreadPriosMask = -1; - DebuggingFlags &= ~3; - KernelReleaseVersion = KProcess.KernelVersionPacked; - - return Parse(caps, memoryManager); - } - - public KernelResult InitializeForUser(int[] caps, KMemoryManager memoryManager) - { - return Parse(caps, memoryManager); - } - - private KernelResult Parse(int[] caps, KMemoryManager memoryManager) - { - int mask0 = 0; - int mask1 = 0; - - for (int index = 0; index < caps.Length; index++) - { - int cap = caps[index]; - - if (((cap + 1) & ~cap) != 0x40) - { - KernelResult result = ParseCapability(cap, ref mask0, ref mask1, memoryManager); - - if (result != KernelResult.Success) - { - return result; - } - } - else - { - if ((uint)index + 1 >= caps.Length) - { - return KernelResult.InvalidCombination; - } - - int prevCap = cap; - - cap = caps[++index]; - - if (((cap + 1) & ~cap) != 0x40) - { - return KernelResult.InvalidCombination; - } - - if ((cap & 0x78000000) != 0) - { - return KernelResult.MaximumExceeded; - } - - if ((cap & 0x7ffff80) == 0) - { - return KernelResult.InvalidSize; - } - - long address = ((long)(uint)prevCap << 5) & 0xffffff000; - long size = ((long)(uint)cap << 5) & 0xfffff000; - - if (((ulong)(address + size - 1) >> 36) != 0) - { - return KernelResult.InvalidAddress; - } - - MemoryPermission perm = (prevCap >> 31) != 0 - ? MemoryPermission.Read - : MemoryPermission.ReadAndWrite; - - KernelResult result; - - if ((cap >> 31) != 0) - { - result = memoryManager.MapNormalMemory(address, size, perm); - } - else - { - result = memoryManager.MapIoMemory(address, size, perm); - } - - if (result != KernelResult.Success) - { - return result; - } - } - } - - return KernelResult.Success; - } - - private KernelResult ParseCapability(int cap, ref int mask0, ref int mask1, KMemoryManager memoryManager) - { - int code = (cap + 1) & ~cap; - - if (code == 1) - { - return KernelResult.InvalidCapability; - } - else if (code == 0) - { - return KernelResult.Success; - } - - int codeMask = 1 << (32 - BitUtils.CountLeadingZeros32(code + 1)); - - //Check if the property was already set. - if (((mask0 & codeMask) & 0x1e008) != 0) - { - return KernelResult.InvalidCombination; - } - - mask0 |= codeMask; - - switch (code) - { - case 8: - { - if (AllowedCpuCoresMask != 0 || AllowedThreadPriosMask != 0) - { - return KernelResult.InvalidCapability; - } - - int lowestCpuCore = (cap >> 16) & 0xff; - int highestCpuCore = (cap >> 24) & 0xff; - - if (lowestCpuCore > highestCpuCore) - { - return KernelResult.InvalidCombination; - } - - int highestThreadPrio = (cap >> 4) & 0x3f; - int lowestThreadPrio = (cap >> 10) & 0x3f; - - if (lowestThreadPrio > highestThreadPrio) - { - return KernelResult.InvalidCombination; - } - - if (highestCpuCore >= KScheduler.CpuCoresCount) - { - return KernelResult.InvalidCpuCore; - } - - AllowedCpuCoresMask = GetMaskFromMinMax(lowestCpuCore, highestCpuCore); - AllowedThreadPriosMask = GetMaskFromMinMax(lowestThreadPrio, highestThreadPrio); - - break; - } - - case 0x10: - { - int slot = (cap >> 29) & 7; - - int svcSlotMask = 1 << slot; - - if ((mask1 & svcSlotMask) != 0) - { - return KernelResult.InvalidCombination; - } - - mask1 |= svcSlotMask; - - int svcMask = (cap >> 5) & 0xffffff; - - int baseSvc = slot * 24; - - for (int index = 0; index < 24; index++) - { - if (((svcMask >> index) & 1) == 0) - { - continue; - } - - int svcId = baseSvc + index; - - if (svcId > 0x7f) - { - return KernelResult.MaximumExceeded; - } - - SvcAccessMask[svcId / 8] |= (byte)(1 << (svcId & 7)); - } - - break; - } - - case 0x80: - { - long address = ((long)(uint)cap << 4) & 0xffffff000; - - memoryManager.MapIoMemory(address, KMemoryManager.PageSize, MemoryPermission.ReadAndWrite); - - break; - } - - case 0x800: - { - //TODO: GIC distributor check. - int irq0 = (cap >> 12) & 0x3ff; - int irq1 = (cap >> 22) & 0x3ff; - - if (irq0 != 0x3ff) - { - IrqAccessMask[irq0 / 8] |= (byte)(1 << (irq0 & 7)); - } - - if (irq1 != 0x3ff) - { - IrqAccessMask[irq1 / 8] |= (byte)(1 << (irq1 & 7)); - } - - break; - } - - case 0x2000: - { - int applicationType = cap >> 14; - - if ((uint)applicationType > 7) - { - return KernelResult.ReservedValue; - } - - ApplicationType = applicationType; - - break; - } - - case 0x4000: - { - //Note: This check is bugged on kernel too, we are just replicating the bug here. - if ((KernelReleaseVersion >> 17) != 0 || cap < 0x80000) - { - return KernelResult.ReservedValue; - } - - KernelReleaseVersion = cap; - - break; - } - - case 0x8000: - { - int handleTableSize = cap >> 26; - - if ((uint)handleTableSize > 0x3ff) - { - return KernelResult.ReservedValue; - } - - HandleTableSize = handleTableSize; - - break; - } - - case 0x10000: - { - int debuggingFlags = cap >> 19; - - if ((uint)debuggingFlags > 3) - { - return KernelResult.ReservedValue; - } - - DebuggingFlags &= ~3; - DebuggingFlags |= debuggingFlags; - - break; - } - - default: return KernelResult.InvalidCapability; - } - - return KernelResult.Success; - } - - private static long GetMaskFromMinMax(int min, int max) - { - int range = max - min + 1; - - long mask = (1L << range) - 1; - - return mask << min; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KReadableEvent.cs b/Ryujinx.HLE/HOS/Kernel/KReadableEvent.cs deleted file mode 100644 index e1a3e63f..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KReadableEvent.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - class KReadableEvent : KSynchronizationObject - { - private KEvent _parent; - - private bool _signaled; - - public KReadableEvent(Horizon system, KEvent parent) : base(system) - { - _parent = parent; - } - - public override void Signal() - { - System.CriticalSection.Enter(); - - if (!_signaled) - { - _signaled = true; - - base.Signal(); - } - - System.CriticalSection.Leave(); - } - - public KernelResult Clear() - { - _signaled = false; - - return KernelResult.Success; - } - - public KernelResult ClearIfSignaled() - { - KernelResult result; - - System.CriticalSection.Enter(); - - if (_signaled) - { - _signaled = false; - - result = KernelResult.Success; - } - else - { - result = KernelResult.InvalidState; - } - - System.CriticalSection.Leave(); - - return result; - } - - public override bool IsSignaled() - { - return _signaled; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KResourceLimit.cs b/Ryujinx.HLE/HOS/Kernel/KResourceLimit.cs deleted file mode 100644 index 09c53e5b..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KResourceLimit.cs +++ /dev/null @@ -1,146 +0,0 @@ -using Ryujinx.Common; -using System.Collections.Generic; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KResourceLimit - { - private const int Time10SecondsMs = 10000; - - private long[] _current; - private long[] _limit; - private long[] _available; - - private object _lockObj; - - private LinkedList _waitingThreads; - - private int _waitingThreadsCount; - - private Horizon _system; - - public KResourceLimit(Horizon system) - { - _current = new long[(int)LimitableResource.Count]; - _limit = new long[(int)LimitableResource.Count]; - _available = new long[(int)LimitableResource.Count]; - - _lockObj = new object(); - - _waitingThreads = new LinkedList(); - - _system = system; - } - - public bool Reserve(LimitableResource resource, ulong amount) - { - return Reserve(resource, (long)amount); - } - - public bool Reserve(LimitableResource resource, long amount) - { - return Reserve(resource, amount, KTimeManager.ConvertMillisecondsToNanoseconds(Time10SecondsMs)); - } - - public bool Reserve(LimitableResource resource, long amount, long timeout) - { - long endTimePoint = KTimeManager.ConvertNanosecondsToMilliseconds(timeout); - - endTimePoint += PerformanceCounter.ElapsedMilliseconds; - - bool success = false; - - int index = GetIndex(resource); - - lock (_lockObj) - { - long newCurrent = _current[index] + amount; - - while (newCurrent > _limit[index] && _available[index] + amount <= _limit[index]) - { - _waitingThreadsCount++; - - KConditionVariable.Wait(_system, _waitingThreads, _lockObj, timeout); - - _waitingThreadsCount--; - - newCurrent = _current[index] + amount; - - if (timeout >= 0 && PerformanceCounter.ElapsedMilliseconds > endTimePoint) - { - break; - } - } - - if (newCurrent <= _limit[index]) - { - _current[index] = newCurrent; - - success = true; - } - } - - return success; - } - - public void Release(LimitableResource resource, ulong amount) - { - Release(resource, (long)amount); - } - - public void Release(LimitableResource resource, long amount) - { - Release(resource, amount, amount); - } - - private void Release(LimitableResource resource, long usedAmount, long availableAmount) - { - int index = GetIndex(resource); - - lock (_lockObj) - { - _current [index] -= usedAmount; - _available[index] -= availableAmount; - - if (_waitingThreadsCount > 0) - { - KConditionVariable.NotifyAll(_system, _waitingThreads); - } - } - } - - public long GetRemainingValue(LimitableResource resource) - { - int index = GetIndex(resource); - - lock (_lockObj) - { - return _limit[index] - _current[index]; - } - } - - public KernelResult SetLimitValue(LimitableResource resource, long limit) - { - int index = GetIndex(resource); - - lock (_lockObj) - { - if (_current[index] <= limit) - { - _limit[index] = limit; - - return KernelResult.Success; - } - else - { - return KernelResult.InvalidState; - } - } - } - - private static int GetIndex(LimitableResource resource) - { - return (int)resource; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KScheduler.cs b/Ryujinx.HLE/HOS/Kernel/KScheduler.cs deleted file mode 100644 index f85b9e8c..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KScheduler.cs +++ /dev/null @@ -1,233 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Ryujinx.HLE.HOS.Kernel -{ - partial class KScheduler : IDisposable - { - public const int PrioritiesCount = 64; - public const int CpuCoresCount = 4; - - private const int PreemptionPriorityCores012 = 59; - private const int PreemptionPriorityCore3 = 63; - - private Horizon _system; - - public KSchedulingData SchedulingData { get; private set; } - - public KCoreContext[] CoreContexts { get; private set; } - - public bool ThreadReselectionRequested { get; set; } - - public KScheduler(Horizon system) - { - _system = system; - - SchedulingData = new KSchedulingData(); - - CoreManager = new HleCoreManager(); - - CoreContexts = new KCoreContext[CpuCoresCount]; - - for (int core = 0; core < CpuCoresCount; core++) - { - CoreContexts[core] = new KCoreContext(this, CoreManager); - } - } - - private void PreemptThreads() - { - _system.CriticalSection.Enter(); - - PreemptThread(PreemptionPriorityCores012, 0); - PreemptThread(PreemptionPriorityCores012, 1); - PreemptThread(PreemptionPriorityCores012, 2); - PreemptThread(PreemptionPriorityCore3, 3); - - _system.CriticalSection.Leave(); - } - - private void PreemptThread(int prio, int core) - { - IEnumerable scheduledThreads = SchedulingData.ScheduledThreads(core); - - KThread selectedThread = scheduledThreads.FirstOrDefault(x => x.DynamicPriority == prio); - - //Yield priority queue. - if (selectedThread != null) - { - SchedulingData.Reschedule(prio, core, selectedThread); - } - - IEnumerable SuitableCandidates() - { - foreach (KThread thread in SchedulingData.SuggestedThreads(core)) - { - int srcCore = thread.CurrentCore; - - if (srcCore >= 0) - { - KThread highestPrioSrcCore = SchedulingData.ScheduledThreads(srcCore).FirstOrDefault(); - - if (highestPrioSrcCore != null && highestPrioSrcCore.DynamicPriority < 2) - { - break; - } - - if (highestPrioSrcCore == thread) - { - continue; - } - } - - //If the candidate was scheduled after the current thread, then it's not worth it. - if (selectedThread == null || selectedThread.LastScheduledTime >= thread.LastScheduledTime) - { - yield return thread; - } - } - } - - //Select candidate threads that could run on this core. - //Only take into account threads that are not yet selected. - KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority == prio); - - if (dst != null) - { - SchedulingData.TransferToCore(prio, core, dst); - - selectedThread = dst; - } - - //If the priority of the currently selected thread is lower than preemption priority, - //then allow threads with lower priorities to be selected aswell. - if (selectedThread != null && selectedThread.DynamicPriority > prio) - { - Func predicate = x => x.DynamicPriority >= selectedThread.DynamicPriority; - - dst = SuitableCandidates().FirstOrDefault(predicate); - - if (dst != null) - { - SchedulingData.TransferToCore(dst.DynamicPriority, core, dst); - } - } - - ThreadReselectionRequested = true; - } - - public void SelectThreads() - { - ThreadReselectionRequested = false; - - for (int core = 0; core < CpuCoresCount; core++) - { - KThread thread = SchedulingData.ScheduledThreads(core).FirstOrDefault(); - - CoreContexts[core].SelectThread(thread); - } - - for (int core = 0; core < CpuCoresCount; core++) - { - //If the core is not idle (there's already a thread running on it), - //then we don't need to attempt load balancing. - if (SchedulingData.ScheduledThreads(core).Any()) - { - continue; - } - - int[] srcCoresHighestPrioThreads = new int[CpuCoresCount]; - - int srcCoresHighestPrioThreadsCount = 0; - - KThread dst = null; - - //Select candidate threads that could run on this core. - //Give preference to threads that are not yet selected. - foreach (KThread thread in SchedulingData.SuggestedThreads(core)) - { - if (thread.CurrentCore < 0 || thread != CoreContexts[thread.CurrentCore].SelectedThread) - { - dst = thread; - - break; - } - - srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = thread.CurrentCore; - } - - //Not yet selected candidate found. - if (dst != null) - { - //Priorities < 2 are used for the kernel message dispatching - //threads, we should skip load balancing entirely. - if (dst.DynamicPriority >= 2) - { - SchedulingData.TransferToCore(dst.DynamicPriority, core, dst); - - CoreContexts[core].SelectThread(dst); - } - - continue; - } - - //All candiates are already selected, choose the best one - //(the first one that doesn't make the source core idle if moved). - for (int index = 0; index < srcCoresHighestPrioThreadsCount; index++) - { - int srcCore = srcCoresHighestPrioThreads[index]; - - KThread src = SchedulingData.ScheduledThreads(srcCore).ElementAtOrDefault(1); - - if (src != null) - { - //Run the second thread on the queue on the source core, - //move the first one to the current core. - KThread origSelectedCoreSrc = CoreContexts[srcCore].SelectedThread; - - CoreContexts[srcCore].SelectThread(src); - - SchedulingData.TransferToCore(origSelectedCoreSrc.DynamicPriority, core, origSelectedCoreSrc); - - CoreContexts[core].SelectThread(origSelectedCoreSrc); - } - } - } - } - - public KThread GetCurrentThread() - { - lock (CoreContexts) - { - for (int core = 0; core < CpuCoresCount; core++) - { - if (CoreContexts[core].CurrentThread?.Context.IsCurrentThread() ?? false) - { - return CoreContexts[core].CurrentThread; - } - } - } - - throw new InvalidOperationException("Current thread is not scheduled!"); - } - - public KProcess GetCurrentProcess() - { - return GetCurrentThread().Owner; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _keepPreempting = false; - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KSchedulingData.cs b/Ryujinx.HLE/HOS/Kernel/KSchedulingData.cs deleted file mode 100644 index 65116036..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KSchedulingData.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KSchedulingData - { - private LinkedList[][] _scheduledThreadsPerPrioPerCore; - private LinkedList[][] _suggestedThreadsPerPrioPerCore; - - private long[] _scheduledPrioritiesPerCore; - private long[] _suggestedPrioritiesPerCore; - - public KSchedulingData() - { - _suggestedThreadsPerPrioPerCore = new LinkedList[KScheduler.PrioritiesCount][]; - _scheduledThreadsPerPrioPerCore = new LinkedList[KScheduler.PrioritiesCount][]; - - for (int prio = 0; prio < KScheduler.PrioritiesCount; prio++) - { - _suggestedThreadsPerPrioPerCore[prio] = new LinkedList[KScheduler.CpuCoresCount]; - _scheduledThreadsPerPrioPerCore[prio] = new LinkedList[KScheduler.CpuCoresCount]; - - for (int core = 0; core < KScheduler.CpuCoresCount; core++) - { - _suggestedThreadsPerPrioPerCore[prio][core] = new LinkedList(); - _scheduledThreadsPerPrioPerCore[prio][core] = new LinkedList(); - } - } - - _scheduledPrioritiesPerCore = new long[KScheduler.CpuCoresCount]; - _suggestedPrioritiesPerCore = new long[KScheduler.CpuCoresCount]; - } - - public IEnumerable SuggestedThreads(int core) - { - return Iterate(_suggestedThreadsPerPrioPerCore, _suggestedPrioritiesPerCore, core); - } - - public IEnumerable ScheduledThreads(int core) - { - return Iterate(_scheduledThreadsPerPrioPerCore, _scheduledPrioritiesPerCore, core); - } - - private IEnumerable Iterate(LinkedList[][] listPerPrioPerCore, long[] prios, int core) - { - long prioMask = prios[core]; - - int prio = CountTrailingZeros(prioMask); - - prioMask &= ~(1L << prio); - - while (prio < KScheduler.PrioritiesCount) - { - LinkedList list = listPerPrioPerCore[prio][core]; - - LinkedListNode node = list.First; - - while (node != null) - { - yield return node.Value; - - node = node.Next; - } - - prio = CountTrailingZeros(prioMask); - - prioMask &= ~(1L << prio); - } - } - - private int CountTrailingZeros(long value) - { - int count = 0; - - while (((value >> count) & 0xf) == 0 && count < 64) - { - count += 4; - } - - while (((value >> count) & 1) == 0 && count < 64) - { - count++; - } - - return count; - } - - public void TransferToCore(int prio, int dstCore, KThread thread) - { - bool schedulable = thread.DynamicPriority < KScheduler.PrioritiesCount; - - int srcCore = thread.CurrentCore; - - thread.CurrentCore = dstCore; - - if (srcCore == dstCore || !schedulable) - { - return; - } - - if (srcCore >= 0) - { - Unschedule(prio, srcCore, thread); - } - - if (dstCore >= 0) - { - Unsuggest(prio, dstCore, thread); - Schedule(prio, dstCore, thread); - } - - if (srcCore >= 0) - { - Suggest(prio, srcCore, thread); - } - } - - public void Suggest(int prio, int core, KThread thread) - { - if (prio >= KScheduler.PrioritiesCount) - { - return; - } - - thread.SiblingsPerCore[core] = SuggestedQueue(prio, core).AddFirst(thread); - - _suggestedPrioritiesPerCore[core] |= 1L << prio; - } - - public void Unsuggest(int prio, int core, KThread thread) - { - if (prio >= KScheduler.PrioritiesCount) - { - return; - } - - LinkedList queue = SuggestedQueue(prio, core); - - queue.Remove(thread.SiblingsPerCore[core]); - - if (queue.First == null) - { - _suggestedPrioritiesPerCore[core] &= ~(1L << prio); - } - } - - public void Schedule(int prio, int core, KThread thread) - { - if (prio >= KScheduler.PrioritiesCount) - { - return; - } - - thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddLast(thread); - - _scheduledPrioritiesPerCore[core] |= 1L << prio; - } - - public void SchedulePrepend(int prio, int core, KThread thread) - { - if (prio >= KScheduler.PrioritiesCount) - { - return; - } - - thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddFirst(thread); - - _scheduledPrioritiesPerCore[core] |= 1L << prio; - } - - public void Reschedule(int prio, int core, KThread thread) - { - LinkedList queue = ScheduledQueue(prio, core); - - queue.Remove(thread.SiblingsPerCore[core]); - - thread.SiblingsPerCore[core] = queue.AddLast(thread); - } - - public void Unschedule(int prio, int core, KThread thread) - { - if (prio >= KScheduler.PrioritiesCount) - { - return; - } - - LinkedList queue = ScheduledQueue(prio, core); - - queue.Remove(thread.SiblingsPerCore[core]); - - if (queue.First == null) - { - _scheduledPrioritiesPerCore[core] &= ~(1L << prio); - } - } - - private LinkedList SuggestedQueue(int prio, int core) - { - return _suggestedThreadsPerPrioPerCore[prio][core]; - } - - private LinkedList ScheduledQueue(int prio, int core) - { - return _scheduledThreadsPerPrioPerCore[prio][core]; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KServerPort.cs b/Ryujinx.HLE/HOS/Kernel/KServerPort.cs deleted file mode 100644 index 0aa74e48..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KServerPort.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - class KServerPort : KSynchronizationObject - { - private KPort _parent; - - public KServerPort(Horizon system) : base(system) { } - - public void Initialize(KPort parent) - { - _parent = parent; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KSession.cs b/Ryujinx.HLE/HOS/Kernel/KSession.cs deleted file mode 100644 index 361a7479..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KSession.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Ryujinx.HLE.HOS.Services; -using System; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KSession : IDisposable - { - public IpcService Service { get; private set; } - - public string ServiceName { get; private set; } - - public KSession(IpcService service, string serviceName) - { - Service = service; - ServiceName = serviceName; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing && Service is IDisposable disposableService) - { - disposableService.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs b/Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs deleted file mode 100644 index 0e9f8840..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Ryujinx.Common; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KSharedMemory - { - private KPageList _pageList; - - private long _ownerPid; - - private MemoryPermission _ownerPermission; - private MemoryPermission _userPermission; - - public KSharedMemory( - KPageList pageList, - long ownerPid, - MemoryPermission ownerPermission, - MemoryPermission userPermission) - { - _pageList = pageList; - _ownerPid = ownerPid; - _ownerPermission = ownerPermission; - _userPermission = userPermission; - } - - public KernelResult MapIntoProcess( - KMemoryManager memoryManager, - ulong address, - ulong size, - KProcess process, - MemoryPermission permission) - { - ulong pagesCountRounded = BitUtils.DivRoundUp(size, KMemoryManager.PageSize); - - if (_pageList.GetPagesCount() != pagesCountRounded) - { - return KernelResult.InvalidSize; - } - - MemoryPermission expectedPermission = process.Pid == _ownerPid - ? _ownerPermission - : _userPermission; - - if (permission != expectedPermission) - { - return KernelResult.InvalidPermission; - } - - return memoryManager.MapPages(address, _pageList, MemoryState.SharedMemory, permission); - } - - public KernelResult UnmapFromProcess( - KMemoryManager memoryManager, - ulong address, - ulong size, - KProcess process) - { - ulong pagesCountRounded = BitUtils.DivRoundUp(size, KMemoryManager.PageSize); - - if (_pageList.GetPagesCount() != pagesCountRounded) - { - return KernelResult.InvalidSize; - } - - return memoryManager.UnmapPages(address, _pageList, MemoryState.SharedMemory); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KSlabHeap.cs b/Ryujinx.HLE/HOS/Kernel/KSlabHeap.cs deleted file mode 100644 index 84c4dc01..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KSlabHeap.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KSlabHeap - { - private LinkedList _items; - - public KSlabHeap(ulong pa, ulong itemSize, ulong size) - { - _items = new LinkedList(); - - int itemsCount = (int)(size / itemSize); - - for (int index = 0; index < itemsCount; index++) - { - _items.AddLast(pa); - - pa += itemSize; - } - } - - public bool TryGetItem(out ulong pa) - { - lock (_items) - { - if (_items.First != null) - { - pa = _items.First.Value; - - _items.RemoveFirst(); - - return true; - } - } - - pa = 0; - - return false; - } - - public void Free(ulong pa) - { - lock (_items) - { - _items.AddFirst(pa); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KSynchronization.cs b/Ryujinx.HLE/HOS/Kernel/KSynchronization.cs deleted file mode 100644 index 51b74a03..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KSynchronization.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System.Collections.Generic; - -using static Ryujinx.HLE.HOS.ErrorCode; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KSynchronization - { - private Horizon _system; - - public KSynchronization(Horizon system) - { - _system = system; - } - - public long WaitFor(KSynchronizationObject[] syncObjs, long timeout, ref int hndIndex) - { - long result = MakeError(ErrorModule.Kernel, KernelErr.Timeout); - - _system.CriticalSection.Enter(); - - //Check if objects are already signaled before waiting. - for (int index = 0; index < syncObjs.Length; index++) - { - if (!syncObjs[index].IsSignaled()) - { - continue; - } - - hndIndex = index; - - _system.CriticalSection.Leave(); - - return 0; - } - - if (timeout == 0) - { - _system.CriticalSection.Leave(); - - return result; - } - - KThread currentThread = _system.Scheduler.GetCurrentThread(); - - if (currentThread.ShallBeTerminated || - currentThread.SchedFlags == ThreadSchedState.TerminationPending) - { - result = MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); - } - else if (currentThread.SyncCancelled) - { - currentThread.SyncCancelled = false; - - result = MakeError(ErrorModule.Kernel, KernelErr.Cancelled); - } - else - { - LinkedListNode[] syncNodes = new LinkedListNode[syncObjs.Length]; - - for (int index = 0; index < syncObjs.Length; index++) - { - syncNodes[index] = syncObjs[index].AddWaitingThread(currentThread); - } - - currentThread.WaitingSync = true; - currentThread.SignaledObj = null; - currentThread.ObjSyncResult = (int)result; - - currentThread.Reschedule(ThreadSchedState.Paused); - - if (timeout > 0) - { - _system.TimeManager.ScheduleFutureInvocation(currentThread, timeout); - } - - _system.CriticalSection.Leave(); - - currentThread.WaitingSync = false; - - if (timeout > 0) - { - _system.TimeManager.UnscheduleFutureInvocation(currentThread); - } - - _system.CriticalSection.Enter(); - - result = (uint)currentThread.ObjSyncResult; - - hndIndex = -1; - - for (int index = 0; index < syncObjs.Length; index++) - { - syncObjs[index].RemoveWaitingThread(syncNodes[index]); - - if (syncObjs[index] == currentThread.SignaledObj) - { - hndIndex = index; - } - } - } - - _system.CriticalSection.Leave(); - - return result; - } - - public void SignalObject(KSynchronizationObject syncObj) - { - _system.CriticalSection.Enter(); - - if (syncObj.IsSignaled()) - { - LinkedListNode node = syncObj.WaitingThreads.First; - - while (node != null) - { - KThread thread = node.Value; - - if ((thread.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused) - { - thread.SignaledObj = syncObj; - thread.ObjSyncResult = 0; - - thread.Reschedule(ThreadSchedState.Running); - } - - node = node.Next; - } - } - - _system.CriticalSection.Leave(); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs b/Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs deleted file mode 100644 index 79f0673f..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KSynchronizationObject : KAutoObject - { - public LinkedList WaitingThreads; - - public KSynchronizationObject(Horizon system) : base(system) - { - WaitingThreads = new LinkedList(); - } - - public LinkedListNode AddWaitingThread(KThread thread) - { - return WaitingThreads.AddLast(thread); - } - - public void RemoveWaitingThread(LinkedListNode node) - { - WaitingThreads.Remove(node); - } - - public virtual void Signal() - { - System.Synchronization.SignalObject(this); - } - - public virtual bool IsSignaled() - { - return false; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KThread.cs b/Ryujinx.HLE/HOS/Kernel/KThread.cs deleted file mode 100644 index 846b41aa..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KThread.cs +++ /dev/null @@ -1,1030 +0,0 @@ -using ChocolArm64; -using ChocolArm64.Memory; -using System; -using System.Collections.Generic; -using System.Linq; - -using static Ryujinx.HLE.HOS.ErrorCode; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KThread : KSynchronizationObject, IKFutureSchedulerObject - { - public CpuThread Context { get; private set; } - - public long AffinityMask { get; set; } - - public long ThreadUid { get; private set; } - - public long TotalTimeRunning { get; set; } - - public KSynchronizationObject SignaledObj { get; set; } - - public long CondVarAddress { get; set; } - - private ulong _entrypoint; - - public long MutexAddress { get; set; } - - public KProcess Owner { get; private set; } - - private ulong _tlsAddress; - - public long LastScheduledTime { get; set; } - - public LinkedListNode[] SiblingsPerCore { get; private set; } - - public LinkedList Withholder { get; set; } - public LinkedListNode WithholderNode { get; set; } - - public LinkedListNode ProcessListNode { get; set; } - - private LinkedList _mutexWaiters; - private LinkedListNode _mutexWaiterNode; - - public KThread MutexOwner { get; private set; } - - public int ThreadHandleForUserMutex { get; set; } - - private ThreadSchedState _forcePauseFlags; - - public int ObjSyncResult { get; set; } - - public int DynamicPriority { get; set; } - public int CurrentCore { get; set; } - public int BasePriority { get; set; } - public int PreferredCore { get; set; } - - private long _affinityMaskOverride; - private int _preferredCoreOverride; - private int _affinityOverrideCount; - - public ThreadSchedState SchedFlags { get; private set; } - - public bool ShallBeTerminated { get; private set; } - - public bool SyncCancelled { get; set; } - public bool WaitingSync { get; set; } - - private bool _hasExited; - - public bool WaitingInArbitration { get; set; } - - private KScheduler _scheduler; - - private KSchedulingData _schedulingData; - - public long LastPc { get; set; } - - public KThread(Horizon system) : base(system) - { - _scheduler = system.Scheduler; - _schedulingData = system.Scheduler.SchedulingData; - - SiblingsPerCore = new LinkedListNode[KScheduler.CpuCoresCount]; - - _mutexWaiters = new LinkedList(); - } - - public KernelResult Initialize( - ulong entrypoint, - ulong argsPtr, - ulong stackTop, - int priority, - int defaultCpuCore, - KProcess owner, - ThreadType type = ThreadType.User) - { - if ((uint)type > 3) - { - throw new ArgumentException($"Invalid thread type \"{type}\"."); - } - - PreferredCore = defaultCpuCore; - - AffinityMask |= 1L << defaultCpuCore; - - SchedFlags = type == ThreadType.Dummy - ? ThreadSchedState.Running - : ThreadSchedState.None; - - CurrentCore = PreferredCore; - - DynamicPriority = priority; - BasePriority = priority; - - ObjSyncResult = 0x7201; - - _entrypoint = entrypoint; - - if (type == ThreadType.User) - { - if (owner.AllocateThreadLocalStorage(out _tlsAddress) != KernelResult.Success) - { - return KernelResult.OutOfMemory; - } - - MemoryHelper.FillWithZeros(owner.CpuMemory, (long)_tlsAddress, KTlsPageInfo.TlsEntrySize); - } - - bool is64Bits; - - if (owner != null) - { - Owner = owner; - - owner.IncrementThreadCount(); - - is64Bits = (owner.MmuFlags & 1) != 0; - } - else - { - is64Bits = true; - } - - Context = new CpuThread(owner.Translator, owner.CpuMemory, (long)entrypoint); - - Context.ThreadState.X0 = argsPtr; - Context.ThreadState.X31 = stackTop; - - Context.ThreadState.CntfrqEl0 = 19200000; - Context.ThreadState.Tpidr = (long)_tlsAddress; - - owner.SubscribeThreadEventHandlers(Context); - - Context.WorkFinished += ThreadFinishedHandler; - - ThreadUid = System.GetThreadUid(); - - if (owner != null) - { - owner.AddThread(this); - - if (owner.IsPaused) - { - System.CriticalSection.Enter(); - - if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) - { - System.CriticalSection.Leave(); - - return KernelResult.Success; - } - - _forcePauseFlags |= ThreadSchedState.ProcessPauseFlag; - - CombineForcePauseFlags(); - - System.CriticalSection.Leave(); - } - } - - return KernelResult.Success; - } - - public KernelResult Start() - { - if (!System.KernelInitialized) - { - System.CriticalSection.Enter(); - - if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending) - { - _forcePauseFlags |= ThreadSchedState.KernelInitPauseFlag; - - CombineForcePauseFlags(); - } - - System.CriticalSection.Leave(); - } - - KernelResult result = KernelResult.ThreadTerminating; - - System.CriticalSection.Enter(); - - if (!ShallBeTerminated) - { - KThread currentThread = System.Scheduler.GetCurrentThread(); - - while (SchedFlags != ThreadSchedState.TerminationPending && - currentThread.SchedFlags != ThreadSchedState.TerminationPending && - !currentThread.ShallBeTerminated) - { - if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.None) - { - result = KernelResult.InvalidState; - - break; - } - - if (currentThread._forcePauseFlags == ThreadSchedState.None) - { - if (Owner != null && _forcePauseFlags != ThreadSchedState.None) - { - CombineForcePauseFlags(); - } - - SetNewSchedFlags(ThreadSchedState.Running); - - result = KernelResult.Success; - - break; - } - else - { - currentThread.CombineForcePauseFlags(); - - System.CriticalSection.Leave(); - System.CriticalSection.Enter(); - - if (currentThread.ShallBeTerminated) - { - break; - } - } - } - } - - System.CriticalSection.Leave(); - - return result; - } - - public void Exit() - { - System.CriticalSection.Enter(); - - _forcePauseFlags &= ~ThreadSchedState.ForcePauseMask; - - ExitImpl(); - - System.CriticalSection.Leave(); - } - - private void ExitImpl() - { - System.CriticalSection.Enter(); - - SetNewSchedFlags(ThreadSchedState.TerminationPending); - - _hasExited = true; - - Signal(); - - System.CriticalSection.Leave(); - } - - public long Sleep(long timeout) - { - System.CriticalSection.Enter(); - - if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) - { - System.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); - } - - SetNewSchedFlags(ThreadSchedState.Paused); - - if (timeout > 0) - { - System.TimeManager.ScheduleFutureInvocation(this, timeout); - } - - System.CriticalSection.Leave(); - - if (timeout > 0) - { - System.TimeManager.UnscheduleFutureInvocation(this); - } - - return 0; - } - - public void Yield() - { - System.CriticalSection.Enter(); - - if (SchedFlags != ThreadSchedState.Running) - { - System.CriticalSection.Leave(); - - System.Scheduler.ContextSwitch(); - - return; - } - - if (DynamicPriority < KScheduler.PrioritiesCount) - { - //Move current thread to the end of the queue. - _schedulingData.Reschedule(DynamicPriority, CurrentCore, this); - } - - _scheduler.ThreadReselectionRequested = true; - - System.CriticalSection.Leave(); - - System.Scheduler.ContextSwitch(); - } - - public void YieldWithLoadBalancing() - { - System.CriticalSection.Enter(); - - if (SchedFlags != ThreadSchedState.Running) - { - System.CriticalSection.Leave(); - - System.Scheduler.ContextSwitch(); - - return; - } - - int prio = DynamicPriority; - int core = CurrentCore; - - KThread nextThreadOnCurrentQueue = null; - - if (DynamicPriority < KScheduler.PrioritiesCount) - { - //Move current thread to the end of the queue. - _schedulingData.Reschedule(prio, core, this); - - Func predicate = x => x.DynamicPriority == prio; - - nextThreadOnCurrentQueue = _schedulingData.ScheduledThreads(core).FirstOrDefault(predicate); - } - - IEnumerable SuitableCandidates() - { - foreach (KThread thread in _schedulingData.SuggestedThreads(core)) - { - int srcCore = thread.CurrentCore; - - if (srcCore >= 0) - { - KThread selectedSrcCore = _scheduler.CoreContexts[srcCore].SelectedThread; - - if (selectedSrcCore == thread || ((selectedSrcCore?.DynamicPriority ?? 2) < 2)) - { - continue; - } - } - - //If the candidate was scheduled after the current thread, then it's not worth it, - //unless the priority is higher than the current one. - if (nextThreadOnCurrentQueue.LastScheduledTime >= thread.LastScheduledTime || - nextThreadOnCurrentQueue.DynamicPriority < thread.DynamicPriority) - { - yield return thread; - } - } - } - - KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority <= prio); - - if (dst != null) - { - _schedulingData.TransferToCore(dst.DynamicPriority, core, dst); - - _scheduler.ThreadReselectionRequested = true; - } - - if (this != nextThreadOnCurrentQueue) - { - _scheduler.ThreadReselectionRequested = true; - } - - System.CriticalSection.Leave(); - - System.Scheduler.ContextSwitch(); - } - - public void YieldAndWaitForLoadBalancing() - { - System.CriticalSection.Enter(); - - if (SchedFlags != ThreadSchedState.Running) - { - System.CriticalSection.Leave(); - - System.Scheduler.ContextSwitch(); - - return; - } - - int core = CurrentCore; - - _schedulingData.TransferToCore(DynamicPriority, -1, this); - - KThread selectedThread = null; - - if (!_schedulingData.ScheduledThreads(core).Any()) - { - foreach (KThread thread in _schedulingData.SuggestedThreads(core)) - { - if (thread.CurrentCore < 0) - { - continue; - } - - KThread firstCandidate = _schedulingData.ScheduledThreads(thread.CurrentCore).FirstOrDefault(); - - if (firstCandidate == thread) - { - continue; - } - - if (firstCandidate == null || firstCandidate.DynamicPriority >= 2) - { - _schedulingData.TransferToCore(thread.DynamicPriority, core, thread); - - selectedThread = thread; - } - - break; - } - } - - if (selectedThread != this) - { - _scheduler.ThreadReselectionRequested = true; - } - - System.CriticalSection.Leave(); - - System.Scheduler.ContextSwitch(); - } - - public void SetPriority(int priority) - { - System.CriticalSection.Enter(); - - BasePriority = priority; - - UpdatePriorityInheritance(); - - System.CriticalSection.Leave(); - } - - public long SetActivity(bool pause) - { - long result = 0; - - System.CriticalSection.Enter(); - - ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask; - - if (lowNibble != ThreadSchedState.Paused && lowNibble != ThreadSchedState.Running) - { - System.CriticalSection.Leave(); - - return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); - } - - System.CriticalSection.Enter(); - - if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending) - { - if (pause) - { - //Pause, the force pause flag should be clear (thread is NOT paused). - if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0) - { - _forcePauseFlags |= ThreadSchedState.ThreadPauseFlag; - - CombineForcePauseFlags(); - } - else - { - result = MakeError(ErrorModule.Kernel, KernelErr.InvalidState); - } - } - else - { - //Unpause, the force pause flag should be set (thread is paused). - if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0) - { - ThreadSchedState oldForcePauseFlags = _forcePauseFlags; - - _forcePauseFlags &= ~ThreadSchedState.ThreadPauseFlag; - - if ((oldForcePauseFlags & ~ThreadSchedState.ThreadPauseFlag) == ThreadSchedState.None) - { - ThreadSchedState oldSchedFlags = SchedFlags; - - SchedFlags &= ThreadSchedState.LowMask; - - AdjustScheduling(oldSchedFlags); - } - } - else - { - result = MakeError(ErrorModule.Kernel, KernelErr.InvalidState); - } - } - } - - System.CriticalSection.Leave(); - System.CriticalSection.Leave(); - - return result; - } - - public void CancelSynchronization() - { - System.CriticalSection.Enter(); - - if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.Paused || !WaitingSync) - { - SyncCancelled = true; - } - else if (Withholder != null) - { - Withholder.Remove(WithholderNode); - - SetNewSchedFlags(ThreadSchedState.Running); - - Withholder = null; - - SyncCancelled = true; - } - else - { - SignaledObj = null; - ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.Cancelled); - - SetNewSchedFlags(ThreadSchedState.Running); - - SyncCancelled = false; - } - - System.CriticalSection.Leave(); - } - - public KernelResult SetCoreAndAffinityMask(int newCore, long newAffinityMask) - { - System.CriticalSection.Enter(); - - bool useOverride = _affinityOverrideCount != 0; - - //The value -3 is "do not change the preferred core". - if (newCore == -3) - { - newCore = useOverride ? _preferredCoreOverride : PreferredCore; - - if ((newAffinityMask & (1 << newCore)) == 0) - { - System.CriticalSection.Leave(); - - return KernelResult.InvalidCombination; - } - } - - if (useOverride) - { - _preferredCoreOverride = newCore; - _affinityMaskOverride = newAffinityMask; - } - else - { - long oldAffinityMask = AffinityMask; - - PreferredCore = newCore; - AffinityMask = newAffinityMask; - - if (oldAffinityMask != newAffinityMask) - { - int oldCore = CurrentCore; - - if (CurrentCore >= 0 && ((AffinityMask >> CurrentCore) & 1) == 0) - { - if (PreferredCore < 0) - { - CurrentCore = HighestSetCore(AffinityMask); - } - else - { - CurrentCore = PreferredCore; - } - } - - AdjustSchedulingForNewAffinity(oldAffinityMask, oldCore); - } - } - - System.CriticalSection.Leave(); - - return KernelResult.Success; - } - - private static int HighestSetCore(long mask) - { - for (int core = KScheduler.CpuCoresCount - 1; core >= 0; core--) - { - if (((mask >> core) & 1) != 0) - { - return core; - } - } - - return -1; - } - - private void CombineForcePauseFlags() - { - ThreadSchedState oldFlags = SchedFlags; - ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask; - - SchedFlags = lowNibble | _forcePauseFlags; - - AdjustScheduling(oldFlags); - } - - private void SetNewSchedFlags(ThreadSchedState newFlags) - { - System.CriticalSection.Enter(); - - ThreadSchedState oldFlags = SchedFlags; - - SchedFlags = (oldFlags & ThreadSchedState.HighMask) | newFlags; - - if ((oldFlags & ThreadSchedState.LowMask) != newFlags) - { - AdjustScheduling(oldFlags); - } - - System.CriticalSection.Leave(); - } - - public void ReleaseAndResume() - { - System.CriticalSection.Enter(); - - if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused) - { - if (Withholder != null) - { - Withholder.Remove(WithholderNode); - - SetNewSchedFlags(ThreadSchedState.Running); - - Withholder = null; - } - else - { - SetNewSchedFlags(ThreadSchedState.Running); - } - } - - System.CriticalSection.Leave(); - } - - public void Reschedule(ThreadSchedState newFlags) - { - System.CriticalSection.Enter(); - - ThreadSchedState oldFlags = SchedFlags; - - SchedFlags = (oldFlags & ThreadSchedState.HighMask) | - (newFlags & ThreadSchedState.LowMask); - - AdjustScheduling(oldFlags); - - System.CriticalSection.Leave(); - } - - public void AddMutexWaiter(KThread requester) - { - AddToMutexWaitersList(requester); - - requester.MutexOwner = this; - - UpdatePriorityInheritance(); - } - - public void RemoveMutexWaiter(KThread thread) - { - if (thread._mutexWaiterNode?.List != null) - { - _mutexWaiters.Remove(thread._mutexWaiterNode); - } - - thread.MutexOwner = null; - - UpdatePriorityInheritance(); - } - - public KThread RelinquishMutex(long mutexAddress, out int count) - { - count = 0; - - if (_mutexWaiters.First == null) - { - return null; - } - - KThread newMutexOwner = null; - - LinkedListNode currentNode = _mutexWaiters.First; - - do - { - //Skip all threads that are not waiting for this mutex. - while (currentNode != null && currentNode.Value.MutexAddress != mutexAddress) - { - currentNode = currentNode.Next; - } - - if (currentNode == null) - { - break; - } - - LinkedListNode nextNode = currentNode.Next; - - _mutexWaiters.Remove(currentNode); - - currentNode.Value.MutexOwner = newMutexOwner; - - if (newMutexOwner != null) - { - //New owner was already selected, re-insert on new owner list. - newMutexOwner.AddToMutexWaitersList(currentNode.Value); - } - else - { - //New owner not selected yet, use current thread. - newMutexOwner = currentNode.Value; - } - - count++; - - currentNode = nextNode; - } - while (currentNode != null); - - if (newMutexOwner != null) - { - UpdatePriorityInheritance(); - - newMutexOwner.UpdatePriorityInheritance(); - } - - return newMutexOwner; - } - - private void UpdatePriorityInheritance() - { - //If any of the threads waiting for the mutex has - //higher priority than the current thread, then - //the current thread inherits that priority. - int highestPriority = BasePriority; - - if (_mutexWaiters.First != null) - { - int waitingDynamicPriority = _mutexWaiters.First.Value.DynamicPriority; - - if (waitingDynamicPriority < highestPriority) - { - highestPriority = waitingDynamicPriority; - } - } - - if (highestPriority != DynamicPriority) - { - int oldPriority = DynamicPriority; - - DynamicPriority = highestPriority; - - AdjustSchedulingForNewPriority(oldPriority); - - if (MutexOwner != null) - { - //Remove and re-insert to ensure proper sorting based on new priority. - MutexOwner._mutexWaiters.Remove(_mutexWaiterNode); - - MutexOwner.AddToMutexWaitersList(this); - - MutexOwner.UpdatePriorityInheritance(); - } - } - } - - private void AddToMutexWaitersList(KThread thread) - { - LinkedListNode nextPrio = _mutexWaiters.First; - - int currentPriority = thread.DynamicPriority; - - while (nextPrio != null && nextPrio.Value.DynamicPriority <= currentPriority) - { - nextPrio = nextPrio.Next; - } - - if (nextPrio != null) - { - thread._mutexWaiterNode = _mutexWaiters.AddBefore(nextPrio, thread); - } - else - { - thread._mutexWaiterNode = _mutexWaiters.AddLast(thread); - } - } - - private void AdjustScheduling(ThreadSchedState oldFlags) - { - if (oldFlags == SchedFlags) - { - return; - } - - if (oldFlags == ThreadSchedState.Running) - { - //Was running, now it's stopped. - if (CurrentCore >= 0) - { - _schedulingData.Unschedule(DynamicPriority, CurrentCore, this); - } - - for (int core = 0; core < KScheduler.CpuCoresCount; core++) - { - if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) - { - _schedulingData.Unsuggest(DynamicPriority, core, this); - } - } - } - else if (SchedFlags == ThreadSchedState.Running) - { - //Was stopped, now it's running. - if (CurrentCore >= 0) - { - _schedulingData.Schedule(DynamicPriority, CurrentCore, this); - } - - for (int core = 0; core < KScheduler.CpuCoresCount; core++) - { - if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) - { - _schedulingData.Suggest(DynamicPriority, core, this); - } - } - } - - _scheduler.ThreadReselectionRequested = true; - } - - private void AdjustSchedulingForNewPriority(int oldPriority) - { - if (SchedFlags != ThreadSchedState.Running) - { - return; - } - - //Remove thread from the old priority queues. - if (CurrentCore >= 0) - { - _schedulingData.Unschedule(oldPriority, CurrentCore, this); - } - - for (int core = 0; core < KScheduler.CpuCoresCount; core++) - { - if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) - { - _schedulingData.Unsuggest(oldPriority, core, this); - } - } - - //Add thread to the new priority queues. - KThread currentThread = _scheduler.GetCurrentThread(); - - if (CurrentCore >= 0) - { - if (currentThread == this) - { - _schedulingData.SchedulePrepend(DynamicPriority, CurrentCore, this); - } - else - { - _schedulingData.Schedule(DynamicPriority, CurrentCore, this); - } - } - - for (int core = 0; core < KScheduler.CpuCoresCount; core++) - { - if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) - { - _schedulingData.Suggest(DynamicPriority, core, this); - } - } - - _scheduler.ThreadReselectionRequested = true; - } - - private void AdjustSchedulingForNewAffinity(long oldAffinityMask, int oldCore) - { - if (SchedFlags != ThreadSchedState.Running || DynamicPriority >= KScheduler.PrioritiesCount) - { - return; - } - - //Remove from old queues. - for (int core = 0; core < KScheduler.CpuCoresCount; core++) - { - if (((oldAffinityMask >> core) & 1) != 0) - { - if (core == oldCore) - { - _schedulingData.Unschedule(DynamicPriority, core, this); - } - else - { - _schedulingData.Unsuggest(DynamicPriority, core, this); - } - } - } - - //Insert on new queues. - for (int core = 0; core < KScheduler.CpuCoresCount; core++) - { - if (((AffinityMask >> core) & 1) != 0) - { - if (core == CurrentCore) - { - _schedulingData.Schedule(DynamicPriority, core, this); - } - else - { - _schedulingData.Suggest(DynamicPriority, core, this); - } - } - } - - _scheduler.ThreadReselectionRequested = true; - } - - public override bool IsSignaled() - { - return _hasExited; - } - - public void SetEntryArguments(long argsPtr, int threadHandle) - { - Context.ThreadState.X0 = (ulong)argsPtr; - Context.ThreadState.X1 = (ulong)threadHandle; - } - - public void ClearExclusive() - { - Owner.CpuMemory.ClearExclusive(CurrentCore); - } - - public void TimeUp() - { - ReleaseAndResume(); - } - - public void PrintGuestStackTrace() - { - Owner.Debugger.PrintGuestStackTrace(Context.ThreadState); - } - - private void ThreadFinishedHandler(object sender, EventArgs e) - { - System.Scheduler.ExitThread(this); - - Terminate(); - - System.Scheduler.RemoveThread(this); - } - - public void Terminate() - { - Owner?.RemoveThread(this); - - if (_tlsAddress != 0 && Owner.FreeThreadLocalStorage(_tlsAddress) != KernelResult.Success) - { - throw new InvalidOperationException("Unexpected failure freeing thread local storage."); - } - - System.CriticalSection.Enter(); - - //Wake up all threads that may be waiting for a mutex being held - //by this thread. - foreach (KThread thread in _mutexWaiters) - { - thread.MutexOwner = null; - thread._preferredCoreOverride = 0; - thread.ObjSyncResult = 0xfa01; - - thread.ReleaseAndResume(); - } - - System.CriticalSection.Leave(); - - Owner?.DecrementThreadCountAndTerminateIfZero(); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KTimeManager.cs b/Ryujinx.HLE/HOS/Kernel/KTimeManager.cs deleted file mode 100644 index 0c2551a3..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KTimeManager.cs +++ /dev/null @@ -1,143 +0,0 @@ -using Ryujinx.Common; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KTimeManager : IDisposable - { - private class WaitingObject - { - public IKFutureSchedulerObject Object { get; private set; } - - public long TimePoint { get; private set; } - - public WaitingObject(IKFutureSchedulerObject schedulerObj, long timePoint) - { - Object = schedulerObj; - TimePoint = timePoint; - } - } - - private List _waitingObjects; - - private AutoResetEvent _waitEvent; - - private bool _keepRunning; - - public KTimeManager() - { - _waitingObjects = new List(); - - _keepRunning = true; - - Thread work = new Thread(WaitAndCheckScheduledObjects); - - work.Start(); - } - - public void ScheduleFutureInvocation(IKFutureSchedulerObject schedulerObj, long timeout) - { - long timePoint = PerformanceCounter.ElapsedMilliseconds + ConvertNanosecondsToMilliseconds(timeout); - - lock (_waitingObjects) - { - _waitingObjects.Add(new WaitingObject(schedulerObj, timePoint)); - } - - _waitEvent.Set(); - } - - public static long ConvertNanosecondsToMilliseconds(long time) - { - time /= 1000000; - - if ((ulong)time > int.MaxValue) - { - return int.MaxValue; - } - - return time; - } - - public static long ConvertMillisecondsToNanoseconds(long time) - { - return time * 1000000; - } - - public static long ConvertMillisecondsToTicks(long time) - { - return time * 19200; - } - - public void UnscheduleFutureInvocation(IKFutureSchedulerObject Object) - { - lock (_waitingObjects) - { - _waitingObjects.RemoveAll(x => x.Object == Object); - } - } - - private void WaitAndCheckScheduledObjects() - { - using (_waitEvent = new AutoResetEvent(false)) - { - while (_keepRunning) - { - WaitingObject next; - - lock (_waitingObjects) - { - next = _waitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault(); - } - - if (next != null) - { - long timePoint = PerformanceCounter.ElapsedMilliseconds; - - if (next.TimePoint > timePoint) - { - _waitEvent.WaitOne((int)(next.TimePoint - timePoint)); - } - - bool timeUp = PerformanceCounter.ElapsedMilliseconds >= next.TimePoint; - - if (timeUp) - { - lock (_waitingObjects) - { - timeUp = _waitingObjects.Remove(next); - } - } - - if (timeUp) - { - next.Object.TimeUp(); - } - } - else - { - _waitEvent.WaitOne(); - } - } - } - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _keepRunning = false; - - _waitEvent?.Set(); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KTlsPageInfo.cs b/Ryujinx.HLE/HOS/Kernel/KTlsPageInfo.cs deleted file mode 100644 index ff5ecf13..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KTlsPageInfo.cs +++ /dev/null @@ -1,73 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - class KTlsPageInfo - { - public const int TlsEntrySize = 0x200; - - public ulong PageAddr { get; private set; } - - private bool[] _isSlotFree; - - public KTlsPageInfo(ulong pageAddress) - { - PageAddr = pageAddress; - - _isSlotFree = new bool[KMemoryManager.PageSize / TlsEntrySize]; - - for (int index = 0; index < _isSlotFree.Length; index++) - { - _isSlotFree[index] = true; - } - } - - public bool TryGetFreePage(out ulong address) - { - address = PageAddr; - - for (int index = 0; index < _isSlotFree.Length; index++) - { - if (_isSlotFree[index]) - { - _isSlotFree[index] = false; - - return true; - } - - address += TlsEntrySize; - } - - address = 0; - - return false; - } - - public bool IsFull() - { - bool hasFree = false; - - for (int index = 0; index < _isSlotFree.Length; index++) - { - hasFree |= _isSlotFree[index]; - } - - return !hasFree; - } - - public bool IsEmpty() - { - bool allFree = true; - - for (int index = 0; index < _isSlotFree.Length; index++) - { - allFree &= _isSlotFree[index]; - } - - return allFree; - } - - public void FreeTlsSlot(ulong address) - { - _isSlotFree[(address - PageAddr) / TlsEntrySize] = true; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KTlsPageManager.cs b/Ryujinx.HLE/HOS/Kernel/KTlsPageManager.cs deleted file mode 100644 index 75f595eb..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KTlsPageManager.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class KTlsPageManager - { - private const int TlsEntrySize = 0x200; - - private long _pagePosition; - - private int _usedSlots; - - private bool[] _slots; - - public bool IsEmpty => _usedSlots == 0; - public bool IsFull => _usedSlots == _slots.Length; - - public KTlsPageManager(long pagePosition) - { - _pagePosition = pagePosition; - - _slots = new bool[KMemoryManager.PageSize / TlsEntrySize]; - } - - public bool TryGetFreeTlsAddr(out long position) - { - position = _pagePosition; - - for (int index = 0; index < _slots.Length; index++) - { - if (!_slots[index]) - { - _slots[index] = true; - - _usedSlots++; - - return true; - } - - position += TlsEntrySize; - } - - position = 0; - - return false; - } - - public void FreeTlsSlot(int slot) - { - if ((uint)slot > _slots.Length) - { - throw new ArgumentOutOfRangeException(nameof(slot)); - } - - _slots[slot] = false; - - _usedSlots--; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs b/Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs deleted file mode 100644 index d8837851..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - class KTransferMemory - { - public ulong Address { get; private set; } - public ulong Size { get; private set; } - - public KTransferMemory(ulong address, ulong size) - { - Address = address; - Size = size; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KWritableEvent.cs b/Ryujinx.HLE/HOS/Kernel/KWritableEvent.cs deleted file mode 100644 index 4d56a92d..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KWritableEvent.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - class KWritableEvent - { - private KEvent _parent; - - public KWritableEvent(KEvent parent) - { - _parent = parent; - } - - public void Signal() - { - _parent.ReadableEvent.Signal(); - } - - public KernelResult Clear() - { - return _parent.ReadableEvent.Clear(); - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KernelErr.cs b/Ryujinx.HLE/HOS/Kernel/KernelErr.cs deleted file mode 100644 index e0b196f4..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KernelErr.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - static class KernelErr - { - public const int ThreadTerminating = 59; - public const int InvalidSize = 101; - public const int InvalidAddress = 102; - public const int OutOfMemory = 104; - public const int HandleTableFull = 105; - public const int NoAccessPerm = 106; - public const int InvalidPermission = 108; - public const int InvalidMemRange = 110; - public const int InvalidPriority = 112; - public const int InvalidCoreId = 113; - public const int InvalidHandle = 114; - public const int InvalidMaskValue = 116; - public const int Timeout = 117; - public const int Cancelled = 118; - public const int CountOutOfRange = 119; - public const int InvalidEnumValue = 120; - public const int InvalidThread = 122; - public const int InvalidState = 125; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KernelInit.cs b/Ryujinx.HLE/HOS/Kernel/KernelInit.cs deleted file mode 100644 index a797951b..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KernelInit.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System; - -namespace Ryujinx.HLE.HOS.Kernel -{ - static class KernelInit - { - public static void InitializeResourceLimit(KResourceLimit resourceLimit) - { - void EnsureSuccess(KernelResult result) - { - if (result != KernelResult.Success) - { - throw new InvalidOperationException($"Unexpected result \"{result}\"."); - } - } - - int kernelMemoryCfg = 0; - - long ramSize = GetRamSize(kernelMemoryCfg); - - EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Memory, ramSize)); - EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Thread, 800)); - EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Event, 700)); - EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.TransferMemory, 200)); - EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Session, 900)); - - if (!resourceLimit.Reserve(LimitableResource.Memory, 0) || - !resourceLimit.Reserve(LimitableResource.Memory, 0x60000)) - { - throw new InvalidOperationException("Unexpected failure reserving memory on resource limit."); - } - } - - public static KMemoryRegionManager[] GetMemoryRegions() - { - KMemoryArrange arrange = GetMemoryArrange(); - - return new KMemoryRegionManager[] - { - GetMemoryRegion(arrange.Application), - GetMemoryRegion(arrange.Applet), - GetMemoryRegion(arrange.Service), - GetMemoryRegion(arrange.NvServices) - }; - } - - private static KMemoryRegionManager GetMemoryRegion(KMemoryArrangeRegion region) - { - return new KMemoryRegionManager(region.Address, region.Size, region.EndAddr); - } - - private static KMemoryArrange GetMemoryArrange() - { - int mcEmemCfg = 0x1000; - - ulong ememApertureSize = (ulong)(mcEmemCfg & 0x3fff) << 20; - - int kernelMemoryCfg = 0; - - ulong ramSize = (ulong)GetRamSize(kernelMemoryCfg); - - ulong ramPart0; - ulong ramPart1; - - if (ramSize * 2 > ememApertureSize) - { - ramPart0 = ememApertureSize / 2; - ramPart1 = ememApertureSize / 2; - } - else - { - ramPart0 = ememApertureSize; - ramPart1 = 0; - } - - int memoryArrange = 1; - - ulong applicationRgSize; - - switch (memoryArrange) - { - case 2: applicationRgSize = 0x80000000; break; - case 0x11: - case 0x21: applicationRgSize = 0x133400000; break; - default: applicationRgSize = 0xcd500000; break; - } - - ulong appletRgSize; - - switch (memoryArrange) - { - case 2: appletRgSize = 0x61200000; break; - case 3: appletRgSize = 0x1c000000; break; - case 0x11: appletRgSize = 0x23200000; break; - case 0x12: - case 0x21: appletRgSize = 0x89100000; break; - default: appletRgSize = 0x1fb00000; break; - } - - KMemoryArrangeRegion serviceRg; - KMemoryArrangeRegion nvServicesRg; - KMemoryArrangeRegion appletRg; - KMemoryArrangeRegion applicationRg; - - const ulong nvServicesRgSize = 0x29ba000; - - ulong applicationRgEnd = DramMemoryMap.DramEnd; //- RamPart0; - - applicationRg = new KMemoryArrangeRegion(applicationRgEnd - applicationRgSize, applicationRgSize); - - ulong nvServicesRgEnd = applicationRg.Address - appletRgSize; - - nvServicesRg = new KMemoryArrangeRegion(nvServicesRgEnd - nvServicesRgSize, nvServicesRgSize); - appletRg = new KMemoryArrangeRegion(nvServicesRgEnd, appletRgSize); - - //Note: There is an extra region used by the kernel, however - //since we are doing HLE we are not going to use that memory, so give all - //the remaining memory space to services. - ulong serviceRgSize = nvServicesRg.Address - DramMemoryMap.SlabHeapEnd; - - serviceRg = new KMemoryArrangeRegion(DramMemoryMap.SlabHeapEnd, serviceRgSize); - - return new KMemoryArrange(serviceRg, nvServicesRg, appletRg, applicationRg); - } - - private static long GetRamSize(int kernelMemoryCfg) - { - switch ((kernelMemoryCfg >> 16) & 3) - { - case 1: return 0x180000000; - case 2: return 0x200000000; - default: return 0x100000000; - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KernelResult.cs b/Ryujinx.HLE/HOS/Kernel/KernelResult.cs deleted file mode 100644 index 9870d175..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KernelResult.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - enum KernelResult - { - Success = 0, - InvalidCapability = 0x1c01, - ThreadTerminating = 0x7601, - InvalidSize = 0xca01, - InvalidAddress = 0xcc01, - OutOfResource = 0xce01, - OutOfMemory = 0xd001, - HandleTableFull = 0xd201, - InvalidMemState = 0xd401, - InvalidPermission = 0xd801, - InvalidMemRange = 0xdc01, - InvalidPriority = 0xe001, - InvalidCpuCore = 0xe201, - InvalidHandle = 0xe401, - UserCopyFailed = 0xe601, - InvalidCombination = 0xe801, - TimedOut = 0xea01, - Cancelled = 0xec01, - MaximumExceeded = 0xee01, - InvalidEnumValue = 0xf001, - NotFound = 0xf201, - InvalidThread = 0xf401, - InvalidState = 0xfa01, - ReservedValue = 0xfc01, - ResLimitExceeded = 0x10801 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KernelTransfer.cs b/Ryujinx.HLE/HOS/Kernel/KernelTransfer.cs deleted file mode 100644 index c0ce72c0..00000000 --- a/Ryujinx.HLE/HOS/Kernel/KernelTransfer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using ChocolArm64.Memory; - -namespace Ryujinx.HLE.HOS.Kernel -{ - static class KernelTransfer - { - public static bool UserToKernelInt32(Horizon system, long address, out int value) - { - KProcess currentProcess = system.Scheduler.GetCurrentProcess(); - - if (currentProcess.CpuMemory.IsMapped(address) && - currentProcess.CpuMemory.IsMapped(address + 3)) - { - value = currentProcess.CpuMemory.ReadInt32(address); - - return true; - } - - value = 0; - - return false; - } - - public static bool UserToKernelString(Horizon system, long address, int size, out string value) - { - KProcess currentProcess = system.Scheduler.GetCurrentProcess(); - - if (currentProcess.CpuMemory.IsMapped(address) && - currentProcess.CpuMemory.IsMapped(address + size - 1)) - { - value = MemoryHelper.ReadAsciiString(currentProcess.CpuMemory, address, size); - - return true; - } - - value = null; - - return false; - } - - public static bool KernelToUserInt32(Horizon system, long address, int value) - { - KProcess currentProcess = system.Scheduler.GetCurrentProcess(); - - if (currentProcess.CpuMemory.IsMapped(address) && - currentProcess.CpuMemory.IsMapped(address + 3)) - { - currentProcess.CpuMemory.WriteInt32ToSharedAddr(address, value); - - return true; - } - - return false; - } - - public static bool KernelToUserInt64(Horizon system, long address, long value) - { - KProcess currentProcess = system.Scheduler.GetCurrentProcess(); - - if (currentProcess.CpuMemory.IsMapped(address) && - currentProcess.CpuMemory.IsMapped(address + 7)) - { - currentProcess.CpuMemory.WriteInt64(address, value); - - return true; - } - - return false; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/LimitableResource.cs b/Ryujinx.HLE/HOS/Kernel/LimitableResource.cs deleted file mode 100644 index baab4222..00000000 --- a/Ryujinx.HLE/HOS/Kernel/LimitableResource.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - enum LimitableResource : byte - { - Memory = 0, - Thread = 1, - Event = 2, - TransferMemory = 3, - Session = 4, - - Count = 5 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/AddressSpaceType.cs b/Ryujinx.HLE/HOS/Kernel/Memory/AddressSpaceType.cs new file mode 100644 index 00000000..8395c577 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/AddressSpaceType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + enum AddressSpaceType + { + Addr32Bits = 0, + Addr36Bits = 1, + Addr32BitsNoMap = 2, + Addr39Bits = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs b/Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs new file mode 100644 index 00000000..261c2972 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + static class DramMemoryMap + { + public const ulong DramBase = 0x80000000; + public const ulong DramSize = 0x100000000; + public const ulong DramEnd = DramBase + DramSize; + + public const ulong KernelReserveBase = DramBase + 0x60000; + + public const ulong SlabHeapBase = KernelReserveBase + 0x85000; + public const ulong SlapHeapSize = 0xa21000; + public const ulong SlabHeapEnd = SlabHeapBase + SlapHeapSize; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryArrange.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryArrange.cs new file mode 100644 index 00000000..7dfc2b77 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryArrange.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryArrange + { + public KMemoryArrangeRegion Service { get; private set; } + public KMemoryArrangeRegion NvServices { get; private set; } + public KMemoryArrangeRegion Applet { get; private set; } + public KMemoryArrangeRegion Application { get; private set; } + + public KMemoryArrange( + KMemoryArrangeRegion service, + KMemoryArrangeRegion nvServices, + KMemoryArrangeRegion applet, + KMemoryArrangeRegion application) + { + Service = service; + NvServices = nvServices; + Applet = applet; + Application = application; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryArrangeRegion.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryArrangeRegion.cs new file mode 100644 index 00000000..eaf0fe5f --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryArrangeRegion.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + struct KMemoryArrangeRegion + { + public ulong Address { get; private set; } + public ulong Size { get; private set; } + + public ulong EndAddr => Address + Size; + + public KMemoryArrangeRegion(ulong address, ulong size) + { + Address = address; + Size = size; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs new file mode 100644 index 00000000..89a19498 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryBlock + { + public ulong BaseAddress { get; set; } + public ulong PagesCount { get; set; } + + public MemoryState State { get; set; } + public MemoryPermission Permission { get; set; } + public MemoryAttribute Attribute { get; set; } + + public int IpcRefCount { get; set; } + public int DeviceRefCount { get; set; } + + public KMemoryBlock( + ulong baseAddress, + ulong pagesCount, + MemoryState state, + MemoryPermission permission, + MemoryAttribute attribute) + { + BaseAddress = baseAddress; + PagesCount = pagesCount; + State = state; + Attribute = attribute; + Permission = permission; + } + + public KMemoryInfo GetInfo() + { + ulong size = PagesCount * KMemoryManager.PageSize; + + return new KMemoryInfo( + BaseAddress, + size, + State, + Permission, + Attribute, + IpcRefCount, + DeviceRefCount); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockAllocator.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockAllocator.cs new file mode 100644 index 00000000..ae68bf39 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockAllocator.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryBlockAllocator + { + private ulong _capacityElements; + + public int Count { get; set; } + + public KMemoryBlockAllocator(ulong capacityElements) + { + _capacityElements = capacityElements; + } + + public bool CanAllocate(int count) + { + return (ulong)(Count + count) <= _capacityElements; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs new file mode 100644 index 00000000..226ce77c --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryInfo + { + public ulong Address { get; private set; } + public ulong Size { get; private set; } + + public MemoryState State { get; private set; } + public MemoryPermission Permission { get; private set; } + public MemoryAttribute Attribute { get; private set; } + + public int IpcRefCount { get; private set; } + public int DeviceRefCount { get; private set; } + + public KMemoryInfo( + ulong address, + ulong size, + MemoryState state, + MemoryPermission permission, + MemoryAttribute attribute, + int ipcRefCount, + int deviceRefCount) + { + Address = address; + Size = size; + State = state; + Attribute = attribute; + Permission = permission; + IpcRefCount = ipcRefCount; + DeviceRefCount = deviceRefCount; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs new file mode 100644 index 00000000..fb5dec04 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs @@ -0,0 +1,2460 @@ +using ChocolArm64.Memory; +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryManager + { + public const int PageSize = 0x1000; + + private const int KMemoryBlockSize = 0x40; + + //We need 2 blocks for the case where a big block + //needs to be split in 2, plus one block that will be the new one inserted. + private const int MaxBlocksNeededForInsertion = 2; + + private LinkedList _blocks; + + private MemoryManager _cpuMemory; + + private Horizon _system; + + public ulong AddrSpaceStart { get; private set; } + public ulong AddrSpaceEnd { get; private set; } + + public ulong CodeRegionStart { get; private set; } + public ulong CodeRegionEnd { get; private set; } + + public ulong HeapRegionStart { get; private set; } + public ulong HeapRegionEnd { get; private set; } + + private ulong _currentHeapAddr; + + public ulong AliasRegionStart { get; private set; } + public ulong AliasRegionEnd { get; private set; } + + public ulong StackRegionStart { get; private set; } + public ulong StackRegionEnd { get; private set; } + + public ulong TlsIoRegionStart { get; private set; } + public ulong TlsIoRegionEnd { get; private set; } + + private ulong _heapCapacity; + + public ulong PhysicalMemoryUsage { get; private set; } + + private MemoryRegion _memRegion; + + private bool _aslrDisabled; + + public int AddrSpaceWidth { get; private set; } + + private bool _isKernel; + private bool _aslrEnabled; + + private KMemoryBlockAllocator _blockAllocator; + + private int _contextId; + + private MersenneTwister _randomNumberGenerator; + + public KMemoryManager(Horizon system, MemoryManager cpuMemory) + { + _system = system; + _cpuMemory = cpuMemory; + + _blocks = new LinkedList(); + } + + private static readonly int[] AddrSpaceSizes = new int[] { 32, 36, 32, 39 }; + + public KernelResult InitializeForProcess( + AddressSpaceType addrSpaceType, + bool aslrEnabled, + bool aslrDisabled, + MemoryRegion memRegion, + ulong address, + ulong size, + KMemoryBlockAllocator blockAllocator) + { + if ((uint)addrSpaceType > (uint)AddressSpaceType.Addr39Bits) + { + throw new ArgumentException(nameof(addrSpaceType)); + } + + _contextId = _system.ContextIdManager.GetId(); + + ulong addrSpaceBase = 0; + ulong addrSpaceSize = 1UL << AddrSpaceSizes[(int)addrSpaceType]; + + KernelResult result = CreateUserAddressSpace( + addrSpaceType, + aslrEnabled, + aslrDisabled, + addrSpaceBase, + addrSpaceSize, + memRegion, + address, + size, + blockAllocator); + + if (result != KernelResult.Success) + { + _system.ContextIdManager.PutId(_contextId); + } + + return result; + } + + private class Region + { + public ulong Start; + public ulong End; + public ulong Size; + public ulong AslrOffset; + } + + private KernelResult CreateUserAddressSpace( + AddressSpaceType addrSpaceType, + bool aslrEnabled, + bool aslrDisabled, + ulong addrSpaceStart, + ulong addrSpaceEnd, + MemoryRegion memRegion, + ulong address, + ulong size, + KMemoryBlockAllocator blockAllocator) + { + ulong endAddr = address + size; + + Region aliasRegion = new Region(); + Region heapRegion = new Region(); + Region stackRegion = new Region(); + Region tlsIoRegion = new Region(); + + ulong codeRegionSize; + ulong stackAndTlsIoStart; + ulong stackAndTlsIoEnd; + ulong baseAddress; + + switch (addrSpaceType) + { + case AddressSpaceType.Addr32Bits: + aliasRegion.Size = 0x40000000; + heapRegion.Size = 0x40000000; + stackRegion.Size = 0; + tlsIoRegion.Size = 0; + CodeRegionStart = 0x200000; + codeRegionSize = 0x3fe00000; + stackAndTlsIoStart = 0x200000; + stackAndTlsIoEnd = 0x40000000; + baseAddress = 0x200000; + AddrSpaceWidth = 32; + break; + + case AddressSpaceType.Addr36Bits: + aliasRegion.Size = 0x180000000; + heapRegion.Size = 0x180000000; + stackRegion.Size = 0; + tlsIoRegion.Size = 0; + CodeRegionStart = 0x8000000; + codeRegionSize = 0x78000000; + stackAndTlsIoStart = 0x8000000; + stackAndTlsIoEnd = 0x80000000; + baseAddress = 0x8000000; + AddrSpaceWidth = 36; + break; + + case AddressSpaceType.Addr32BitsNoMap: + aliasRegion.Size = 0; + heapRegion.Size = 0x80000000; + stackRegion.Size = 0; + tlsIoRegion.Size = 0; + CodeRegionStart = 0x200000; + codeRegionSize = 0x3fe00000; + stackAndTlsIoStart = 0x200000; + stackAndTlsIoEnd = 0x40000000; + baseAddress = 0x200000; + AddrSpaceWidth = 32; + break; + + case AddressSpaceType.Addr39Bits: + aliasRegion.Size = 0x1000000000; + heapRegion.Size = 0x180000000; + stackRegion.Size = 0x80000000; + tlsIoRegion.Size = 0x1000000000; + CodeRegionStart = BitUtils.AlignDown(address, 0x200000); + codeRegionSize = BitUtils.AlignUp (endAddr, 0x200000) - CodeRegionStart; + stackAndTlsIoStart = 0; + stackAndTlsIoEnd = 0; + baseAddress = 0x8000000; + AddrSpaceWidth = 39; + break; + + default: throw new ArgumentException(nameof(addrSpaceType)); + } + + CodeRegionEnd = CodeRegionStart + codeRegionSize; + + ulong mapBaseAddress; + ulong mapAvailableSize; + + if (CodeRegionStart - baseAddress >= addrSpaceEnd - CodeRegionEnd) + { + //Has more space before the start of the code region. + mapBaseAddress = baseAddress; + mapAvailableSize = CodeRegionStart - baseAddress; + } + else + { + //Has more space after the end of the code region. + mapBaseAddress = CodeRegionEnd; + mapAvailableSize = addrSpaceEnd - CodeRegionEnd; + } + + ulong mapTotalSize = aliasRegion.Size + heapRegion.Size + stackRegion.Size + tlsIoRegion.Size; + + ulong aslrMaxOffset = mapAvailableSize - mapTotalSize; + + _aslrEnabled = aslrEnabled; + + AddrSpaceStart = addrSpaceStart; + AddrSpaceEnd = addrSpaceEnd; + + _blockAllocator = blockAllocator; + + if (mapAvailableSize < mapTotalSize) + { + return KernelResult.OutOfMemory; + } + + if (aslrEnabled) + { + aliasRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset >> 21) << 21; + heapRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset >> 21) << 21; + stackRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset >> 21) << 21; + tlsIoRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset >> 21) << 21; + } + + //Regions are sorted based on ASLR offset. + //When ASLR is disabled, the order is Map, Heap, NewMap and TlsIo. + aliasRegion.Start = mapBaseAddress + aliasRegion.AslrOffset; + aliasRegion.End = aliasRegion.Start + aliasRegion.Size; + heapRegion.Start = mapBaseAddress + heapRegion.AslrOffset; + heapRegion.End = heapRegion.Start + heapRegion.Size; + stackRegion.Start = mapBaseAddress + stackRegion.AslrOffset; + stackRegion.End = stackRegion.Start + stackRegion.Size; + tlsIoRegion.Start = mapBaseAddress + tlsIoRegion.AslrOffset; + tlsIoRegion.End = tlsIoRegion.Start + tlsIoRegion.Size; + + SortRegion(heapRegion, aliasRegion); + + if (stackRegion.Size != 0) + { + SortRegion(stackRegion, aliasRegion); + SortRegion(stackRegion, heapRegion); + } + else + { + stackRegion.Start = stackAndTlsIoStart; + stackRegion.End = stackAndTlsIoEnd; + } + + if (tlsIoRegion.Size != 0) + { + SortRegion(tlsIoRegion, aliasRegion); + SortRegion(tlsIoRegion, heapRegion); + SortRegion(tlsIoRegion, stackRegion); + } + else + { + tlsIoRegion.Start = stackAndTlsIoStart; + tlsIoRegion.End = stackAndTlsIoEnd; + } + + AliasRegionStart = aliasRegion.Start; + AliasRegionEnd = aliasRegion.End; + HeapRegionStart = heapRegion.Start; + HeapRegionEnd = heapRegion.End; + StackRegionStart = stackRegion.Start; + StackRegionEnd = stackRegion.End; + TlsIoRegionStart = tlsIoRegion.Start; + TlsIoRegionEnd = tlsIoRegion.End; + + _currentHeapAddr = HeapRegionStart; + _heapCapacity = 0; + PhysicalMemoryUsage = 0; + + _memRegion = memRegion; + _aslrDisabled = aslrDisabled; + + return InitializeBlocks(addrSpaceStart, addrSpaceEnd); + } + + private ulong GetRandomValue(ulong min, ulong max) + { + return (ulong)GetRandomValue((long)min, (long)max); + } + + private long GetRandomValue(long min, long max) + { + if (_randomNumberGenerator == null) + { + _randomNumberGenerator = new MersenneTwister(0); + } + + return _randomNumberGenerator.GenRandomNumber(min, max); + } + + private static void SortRegion(Region lhs, Region rhs) + { + if (lhs.AslrOffset < rhs.AslrOffset) + { + rhs.Start += lhs.Size; + rhs.End += lhs.Size; + } + else + { + lhs.Start += rhs.Size; + lhs.End += rhs.Size; + } + } + + private KernelResult InitializeBlocks(ulong addrSpaceStart, ulong addrSpaceEnd) + { + //First insertion will always need only a single block, + //because there's nothing else to split. + if (!_blockAllocator.CanAllocate(1)) + { + return KernelResult.OutOfResource; + } + + ulong addrSpacePagesCount = (addrSpaceEnd - addrSpaceStart) / PageSize; + + InsertBlock(addrSpaceStart, addrSpacePagesCount, MemoryState.Unmapped); + + return KernelResult.Success; + } + + public KernelResult MapPages( + ulong address, + KPageList pageList, + MemoryState state, + MemoryPermission permission) + { + ulong pagesCount = pageList.GetPagesCount(); + + ulong size = pagesCount * PageSize; + + if (!ValidateRegionForState(address, size, state)) + { + return KernelResult.InvalidMemState; + } + + lock (_blocks) + { + if (!IsUnmapped(address, pagesCount * PageSize)) + { + return KernelResult.InvalidMemState; + } + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + KernelResult result = MapPages(address, pageList, permission); + + if (result == KernelResult.Success) + { + InsertBlock(address, pagesCount, state, permission); + } + + return result; + } + } + + public KernelResult UnmapPages(ulong address, KPageList pageList, MemoryState stateExpected) + { + ulong pagesCount = pageList.GetPagesCount(); + + ulong size = pagesCount * PageSize; + + ulong endAddr = address + size; + + ulong addrSpacePagesCount = (AddrSpaceEnd - AddrSpaceStart) / PageSize; + + if (AddrSpaceStart > address) + { + return KernelResult.InvalidMemState; + } + + if (addrSpacePagesCount < pagesCount) + { + return KernelResult.InvalidMemState; + } + + if (endAddr - 1 > AddrSpaceEnd - 1) + { + return KernelResult.InvalidMemState; + } + + lock (_blocks) + { + KPageList currentPageList = new KPageList(); + + AddVaRangeToPageList(currentPageList, address, pagesCount); + + if (!currentPageList.IsEqual(pageList)) + { + return KernelResult.InvalidMemRange; + } + + if (CheckRange( + address, + size, + MemoryState.Mask, + stateExpected, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState state, + out _, + out _)) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + KernelResult result = MmuUnmap(address, pagesCount); + + if (result == KernelResult.Success) + { + InsertBlock(address, pagesCount, MemoryState.Unmapped); + } + + return result; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult MapNormalMemory(long address, long size, MemoryPermission permission) + { + //TODO. + return KernelResult.Success; + } + + public KernelResult MapIoMemory(long address, long size, MemoryPermission permission) + { + //TODO. + return KernelResult.Success; + } + + public KernelResult AllocateOrMapPa( + ulong neededPagesCount, + int alignment, + ulong srcPa, + bool map, + ulong regionStart, + ulong regionPagesCount, + MemoryState state, + MemoryPermission permission, + out ulong address) + { + address = 0; + + ulong regionSize = regionPagesCount * PageSize; + + ulong regionEndAddr = regionStart + regionSize; + + if (!ValidateRegionForState(regionStart, regionSize, state)) + { + return KernelResult.InvalidMemState; + } + + if (regionPagesCount <= neededPagesCount) + { + return KernelResult.OutOfMemory; + } + + ulong reservedPagesCount = _isKernel ? 1UL : 4UL; + + lock (_blocks) + { + if (_aslrEnabled) + { + ulong totalNeededSize = (reservedPagesCount + neededPagesCount) * PageSize; + + ulong remainingPages = regionPagesCount - neededPagesCount; + + ulong aslrMaxOffset = ((remainingPages + reservedPagesCount) * PageSize) / (ulong)alignment; + + for (int attempt = 0; attempt < 8; attempt++) + { + address = BitUtils.AlignDown(regionStart + GetRandomValue(0, aslrMaxOffset) * (ulong)alignment, alignment); + + ulong endAddr = address + totalNeededSize; + + KMemoryInfo info = FindBlock(address).GetInfo(); + + if (info.State != MemoryState.Unmapped) + { + continue; + } + + ulong currBaseAddr = info.Address + reservedPagesCount * PageSize; + ulong currEndAddr = info.Address + info.Size; + + if (address >= regionStart && + address >= currBaseAddr && + endAddr - 1 <= regionEndAddr - 1 && + endAddr - 1 <= currEndAddr - 1) + { + break; + } + } + + if (address == 0) + { + ulong aslrPage = GetRandomValue(0, aslrMaxOffset); + + address = FindFirstFit( + regionStart + aslrPage * PageSize, + regionPagesCount - aslrPage, + neededPagesCount, + alignment, + 0, + reservedPagesCount); + } + } + + if (address == 0) + { + address = FindFirstFit( + regionStart, + regionPagesCount, + neededPagesCount, + alignment, + 0, + reservedPagesCount); + } + + if (address == 0) + { + return KernelResult.OutOfMemory; + } + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + MemoryOperation operation = map + ? MemoryOperation.MapPa + : MemoryOperation.Allocate; + + KernelResult result = DoMmuOperation( + address, + neededPagesCount, + srcPa, + map, + permission, + operation); + + if (result != KernelResult.Success) + { + return result; + } + + InsertBlock(address, neededPagesCount, state, permission); + } + + return KernelResult.Success; + } + + public KernelResult MapNewProcessCode( + ulong address, + ulong pagesCount, + MemoryState state, + MemoryPermission permission) + { + ulong size = pagesCount * PageSize; + + if (!ValidateRegionForState(address, size, state)) + { + return KernelResult.InvalidMemState; + } + + lock (_blocks) + { + if (!IsUnmapped(address, size)) + { + return KernelResult.InvalidMemState; + } + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + KernelResult result = DoMmuOperation( + address, + pagesCount, + 0, + false, + permission, + MemoryOperation.Allocate); + + if (result == KernelResult.Success) + { + InsertBlock(address, pagesCount, state, permission); + } + + return result; + } + } + + public KernelResult MapProcessCodeMemory(ulong dst, ulong src, ulong size) + { + ulong pagesCount = size / PageSize; + + lock (_blocks) + { + bool success = CheckRange( + src, + size, + MemoryState.Mask, + MemoryState.Heap, + MemoryPermission.Mask, + MemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState state, + out MemoryPermission permission, + out _); + + success &= IsUnmapped(dst, size); + + if (success) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } + + KPageList pageList = new KPageList(); + + AddVaRangeToPageList(pageList, src, pagesCount); + + KernelResult result = MmuChangePermission(src, pagesCount, MemoryPermission.None); + + if (result != KernelResult.Success) + { + return result; + } + + result = MapPages(dst, pageList, MemoryPermission.None); + + if (result != KernelResult.Success) + { + MmuChangePermission(src, pagesCount, permission); + + return result; + } + + InsertBlock(src, pagesCount, state, MemoryPermission.None, MemoryAttribute.Borrowed); + InsertBlock(dst, pagesCount, MemoryState.ModCodeStatic); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult UnmapProcessCodeMemory(ulong dst, ulong src, ulong size) + { + ulong pagesCount = size / PageSize; + + lock (_blocks) + { + bool success = CheckRange( + src, + size, + MemoryState.Mask, + MemoryState.Heap, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.Borrowed, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _); + + success &= CheckRange( + dst, + PageSize, + MemoryState.UnmapProcessCodeMemoryAllowed, + MemoryState.UnmapProcessCodeMemoryAllowed, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState state, + out _, + out _); + + success &= CheckRange( + dst, + size, + MemoryState.Mask, + state, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None); + + if (success) + { + KernelResult result = MmuUnmap(dst, pagesCount); + + if (result != KernelResult.Success) + { + return result; + } + + //TODO: Missing some checks here. + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } + + InsertBlock(dst, pagesCount, MemoryState.Unmapped); + InsertBlock(src, pagesCount, MemoryState.Heap, MemoryPermission.ReadAndWrite); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult SetHeapSize(ulong size, out ulong address) + { + address = 0; + + if (size > HeapRegionEnd - HeapRegionStart) + { + return KernelResult.OutOfMemory; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + ulong currentHeapSize = GetHeapSize(); + + if (currentHeapSize <= size) + { + //Expand. + ulong diffSize = size - currentHeapSize; + + lock (_blocks) + { + if (currentProcess.ResourceLimit != null && diffSize != 0 && + !currentProcess.ResourceLimit.Reserve(LimitableResource.Memory, diffSize)) + { + return KernelResult.ResLimitExceeded; + } + + ulong pagesCount = diffSize / PageSize; + + KMemoryRegionManager region = GetMemoryRegionManager(); + + KernelResult result = region.AllocatePages(pagesCount, _aslrDisabled, out KPageList pageList); + + void CleanUpForError() + { + if (pageList != null) + { + region.FreePages(pageList); + } + + if (currentProcess.ResourceLimit != null && diffSize != 0) + { + currentProcess.ResourceLimit.Release(LimitableResource.Memory, diffSize); + } + } + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + CleanUpForError(); + + return KernelResult.OutOfResource; + } + + if (!IsUnmapped(_currentHeapAddr, diffSize)) + { + CleanUpForError(); + + return KernelResult.InvalidMemState; + } + + result = DoMmuOperation( + _currentHeapAddr, + pagesCount, + pageList, + MemoryPermission.ReadAndWrite, + MemoryOperation.MapVa); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + InsertBlock(_currentHeapAddr, pagesCount, MemoryState.Heap, MemoryPermission.ReadAndWrite); + } + } + else + { + //Shrink. + ulong freeAddr = HeapRegionStart + size; + ulong diffSize = currentHeapSize - size; + + lock (_blocks) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + if (!CheckRange( + freeAddr, + diffSize, + MemoryState.Mask, + MemoryState.Heap, + MemoryPermission.Mask, + MemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _)) + { + return KernelResult.InvalidMemState; + } + + ulong pagesCount = diffSize / PageSize; + + KernelResult result = MmuUnmap(freeAddr, pagesCount); + + if (result != KernelResult.Success) + { + return result; + } + + currentProcess.ResourceLimit?.Release(LimitableResource.Memory, BitUtils.AlignDown(diffSize, PageSize)); + + InsertBlock(freeAddr, pagesCount, MemoryState.Unmapped); + } + } + + _currentHeapAddr = HeapRegionStart + size; + + address = HeapRegionStart; + + return KernelResult.Success; + } + + public ulong GetTotalHeapSize() + { + lock (_blocks) + { + return GetHeapSize() + PhysicalMemoryUsage; + } + } + + private ulong GetHeapSize() + { + return _currentHeapAddr - HeapRegionStart; + } + + public KernelResult SetHeapCapacity(ulong capacity) + { + lock (_blocks) + { + _heapCapacity = capacity; + } + + return KernelResult.Success; + } + + public KernelResult SetMemoryAttribute( + ulong address, + ulong size, + MemoryAttribute attributeMask, + MemoryAttribute attributeValue) + { + lock (_blocks) + { + if (CheckRange( + address, + size, + MemoryState.AttributeChangeAllowed, + MemoryState.AttributeChangeAllowed, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.BorrowedAndIpcMapped, + MemoryAttribute.None, + MemoryAttribute.DeviceMappedAndUncached, + out MemoryState state, + out MemoryPermission permission, + out MemoryAttribute attribute)) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + attribute &= ~attributeMask; + attribute |= attributeMask & attributeValue; + + InsertBlock(address, pagesCount, state, permission, attribute); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KMemoryInfo QueryMemory(ulong address) + { + if (address >= AddrSpaceStart && + address < AddrSpaceEnd) + { + lock (_blocks) + { + return FindBlock(address).GetInfo(); + } + } + else + { + return new KMemoryInfo( + AddrSpaceEnd, + ~AddrSpaceEnd + 1, + MemoryState.Reserved, + MemoryPermission.None, + MemoryAttribute.None, + 0, + 0); + } + } + + public KernelResult Map(ulong dst, ulong src, ulong size) + { + bool success; + + lock (_blocks) + { + success = CheckRange( + src, + size, + MemoryState.MapAllowed, + MemoryState.MapAllowed, + MemoryPermission.Mask, + MemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState srcState, + out _, + out _); + + success &= IsUnmapped(dst, size); + + if (success) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + KPageList pageList = new KPageList(); + + AddVaRangeToPageList(pageList, src, pagesCount); + + KernelResult result = MmuChangePermission(src, pagesCount, MemoryPermission.None); + + if (result != KernelResult.Success) + { + return result; + } + + result = MapPages(dst, pageList, MemoryPermission.ReadAndWrite); + + if (result != KernelResult.Success) + { + if (MmuChangePermission(src, pagesCount, MemoryPermission.ReadAndWrite) != KernelResult.Success) + { + throw new InvalidOperationException("Unexpected failure reverting memory permission."); + } + + return result; + } + + InsertBlock(src, pagesCount, srcState, MemoryPermission.None, MemoryAttribute.Borrowed); + InsertBlock(dst, pagesCount, MemoryState.Stack, MemoryPermission.ReadAndWrite); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult UnmapForKernel(ulong address, ulong pagesCount, MemoryState stateExpected) + { + ulong size = pagesCount * PageSize; + + lock (_blocks) + { + if (CheckRange( + address, + size, + MemoryState.Mask, + stateExpected, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _)) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + KernelResult result = MmuUnmap(address, pagesCount); + + if (result == KernelResult.Success) + { + InsertBlock(address, pagesCount, MemoryState.Unmapped); + } + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult Unmap(ulong dst, ulong src, ulong size) + { + bool success; + + lock (_blocks) + { + success = CheckRange( + src, + size, + MemoryState.MapAllowed, + MemoryState.MapAllowed, + MemoryPermission.Mask, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.Borrowed, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState srcState, + out _, + out _); + + success &= CheckRange( + dst, + size, + MemoryState.Mask, + MemoryState.Stack, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out MemoryPermission dstPermission, + out _); + + if (success) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + KPageList srcPageList = new KPageList(); + KPageList dstPageList = new KPageList(); + + AddVaRangeToPageList(srcPageList, src, pagesCount); + AddVaRangeToPageList(dstPageList, dst, pagesCount); + + if (!dstPageList.IsEqual(srcPageList)) + { + return KernelResult.InvalidMemRange; + } + + KernelResult result = MmuUnmap(dst, pagesCount); + + if (result != KernelResult.Success) + { + return result; + } + + result = MmuChangePermission(src, pagesCount, MemoryPermission.ReadAndWrite); + + if (result != KernelResult.Success) + { + MapPages(dst, dstPageList, dstPermission); + + return result; + } + + InsertBlock(src, pagesCount, srcState, MemoryPermission.ReadAndWrite); + InsertBlock(dst, pagesCount, MemoryState.Unmapped); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult ReserveTransferMemory(ulong address, ulong size, MemoryPermission permission) + { + lock (_blocks) + { + if (CheckRange( + address, + size, + MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated, + MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated, + MemoryPermission.Mask, + MemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState state, + out _, + out MemoryAttribute attribute)) + { + //TODO: Missing checks. + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + attribute |= MemoryAttribute.Borrowed; + + InsertBlock(address, pagesCount, state, permission, attribute); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult ResetTransferMemory(ulong address, ulong size) + { + lock (_blocks) + { + if (CheckRange( + address, + size, + MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated, + MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.Borrowed, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState state, + out _, + out _)) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + InsertBlock(address, pagesCount, state, MemoryPermission.ReadAndWrite); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult SetProcessMemoryPermission(ulong address, ulong size, MemoryPermission permission) + { + lock (_blocks) + { + if (CheckRange( + address, + size, + MemoryState.ProcessPermissionChangeAllowed, + MemoryState.ProcessPermissionChangeAllowed, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState oldState, + out MemoryPermission oldPermission, + out _)) + { + MemoryState newState = oldState; + + //If writing into the code region is allowed, then we need + //to change it to mutable. + if ((permission & MemoryPermission.Write) != 0) + { + if (oldState == MemoryState.CodeStatic) + { + newState = MemoryState.CodeMutable; + } + else if (oldState == MemoryState.ModCodeStatic) + { + newState = MemoryState.ModCodeMutable; + } + else + { + throw new InvalidOperationException($"Memory state \"{oldState}\" not valid for this operation."); + } + } + + if (newState != oldState || permission != oldPermission) + { + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + MemoryOperation operation = (permission & MemoryPermission.Execute) != 0 + ? MemoryOperation.ChangePermsAndAttributes + : MemoryOperation.ChangePermRw; + + KernelResult result = DoMmuOperation(address, pagesCount, 0, false, permission, operation); + + if (result != KernelResult.Success) + { + return result; + } + + InsertBlock(address, pagesCount, newState, permission); + } + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult MapPhysicalMemory(ulong address, ulong size) + { + ulong endAddr = address + size; + + lock (_blocks) + { + ulong mappedSize = 0; + + KMemoryInfo info; + + LinkedListNode node = FindBlockNode(address); + + do + { + info = node.Value.GetInfo(); + + if (info.State != MemoryState.Unmapped) + { + mappedSize += GetSizeInRange(info, address, endAddr); + } + + node = node.Next; + } + while (info.Address + info.Size < endAddr && node != null); + + if (mappedSize == size) + { + return KernelResult.Success; + } + + ulong remainingSize = size - mappedSize; + + ulong remainingPages = remainingSize / PageSize; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if (currentProcess.ResourceLimit != null && + !currentProcess.ResourceLimit.Reserve(LimitableResource.Memory, remainingSize)) + { + return KernelResult.ResLimitExceeded; + } + + KMemoryRegionManager region = GetMemoryRegionManager(); + + KernelResult result = region.AllocatePages(remainingPages, _aslrDisabled, out KPageList pageList); + + void CleanUpForError() + { + if (pageList != null) + { + region.FreePages(pageList); + } + + currentProcess.ResourceLimit?.Release(LimitableResource.Memory, remainingSize); + } + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + CleanUpForError(); + + return KernelResult.OutOfResource; + } + + MapPhysicalMemory(pageList, address, endAddr); + + PhysicalMemoryUsage += remainingSize; + + ulong pagesCount = size / PageSize; + + InsertBlock( + address, + pagesCount, + MemoryState.Unmapped, + MemoryPermission.None, + MemoryAttribute.None, + MemoryState.Heap, + MemoryPermission.ReadAndWrite, + MemoryAttribute.None); + } + + return KernelResult.Success; + } + + public KernelResult UnmapPhysicalMemory(ulong address, ulong size) + { + ulong endAddr = address + size; + + lock (_blocks) + { + //Scan, ensure that the region can be unmapped (all blocks are heap or + //already unmapped), fill pages list for freeing memory. + ulong heapMappedSize = 0; + + KPageList pageList = new KPageList(); + + KMemoryInfo info; + + LinkedListNode baseNode = FindBlockNode(address); + + LinkedListNode node = baseNode; + + do + { + info = node.Value.GetInfo(); + + if (info.State == MemoryState.Heap) + { + if (info.Attribute != MemoryAttribute.None) + { + return KernelResult.InvalidMemState; + } + + ulong blockSize = GetSizeInRange(info, address, endAddr); + ulong blockAddress = GetAddrInRange(info, address); + + AddVaRangeToPageList(pageList, blockAddress, blockSize / PageSize); + + heapMappedSize += blockSize; + } + else if (info.State != MemoryState.Unmapped) + { + return KernelResult.InvalidMemState; + } + + node = node.Next; + } + while (info.Address + info.Size < endAddr && node != null); + + if (heapMappedSize == 0) + { + return KernelResult.Success; + } + + if (!_blockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + //Try to unmap all the heap mapped memory inside range. + KernelResult result = KernelResult.Success; + + node = baseNode; + + do + { + info = node.Value.GetInfo(); + + if (info.State == MemoryState.Heap) + { + ulong blockSize = GetSizeInRange(info, address, endAddr); + ulong blockAddress = GetAddrInRange(info, address); + + ulong blockPagesCount = blockSize / PageSize; + + result = MmuUnmap(blockAddress, blockPagesCount); + + if (result != KernelResult.Success) + { + //If we failed to unmap, we need to remap everything back again. + MapPhysicalMemory(pageList, address, blockAddress + blockSize); + + break; + } + } + + node = node.Next; + } + while (info.Address + info.Size < endAddr && node != null); + + if (result == KernelResult.Success) + { + GetMemoryRegionManager().FreePages(pageList); + + PhysicalMemoryUsage -= heapMappedSize; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + currentProcess.ResourceLimit?.Release(LimitableResource.Memory, heapMappedSize); + + ulong pagesCount = size / PageSize; + + InsertBlock(address, pagesCount, MemoryState.Unmapped); + } + + return result; + } + } + + private void MapPhysicalMemory(KPageList pageList, ulong address, ulong endAddr) + { + KMemoryInfo info; + + LinkedListNode node = FindBlockNode(address); + + LinkedListNode pageListNode = pageList.Nodes.First; + + KPageNode pageNode = pageListNode.Value; + + ulong srcPa = pageNode.Address; + ulong srcPaPages = pageNode.PagesCount; + + do + { + info = node.Value.GetInfo(); + + if (info.State == MemoryState.Unmapped) + { + ulong blockSize = GetSizeInRange(info, address, endAddr); + + ulong dstVaPages = blockSize / PageSize; + + ulong dstVa = GetAddrInRange(info, address); + + while (dstVaPages > 0) + { + if (srcPaPages == 0) + { + pageListNode = pageListNode.Next; + + pageNode = pageListNode.Value; + + srcPa = pageNode.Address; + srcPaPages = pageNode.PagesCount; + } + + ulong pagesCount = srcPaPages; + + if (pagesCount > dstVaPages) + { + pagesCount = dstVaPages; + } + + DoMmuOperation( + dstVa, + pagesCount, + srcPa, + true, + MemoryPermission.ReadAndWrite, + MemoryOperation.MapPa); + + dstVa += pagesCount * PageSize; + srcPa += pagesCount * PageSize; + srcPaPages -= pagesCount; + dstVaPages -= pagesCount; + } + } + + node = node.Next; + } + while (info.Address + info.Size < endAddr && node != null); + } + + private static ulong GetSizeInRange(KMemoryInfo info, ulong start, ulong end) + { + ulong endAddr = info.Size + info.Address; + ulong size = info.Size; + + if (info.Address < start) + { + size -= start - info.Address; + } + + if (endAddr > end) + { + size -= endAddr - end; + } + + return size; + } + + private static ulong GetAddrInRange(KMemoryInfo info, ulong start) + { + if (info.Address < start) + { + return start; + } + + return info.Address; + } + + private void AddVaRangeToPageList(KPageList pageList, ulong start, ulong pagesCount) + { + ulong address = start; + + while (address < start + pagesCount * PageSize) + { + KernelResult result = ConvertVaToPa(address, out ulong pa); + + if (result != KernelResult.Success) + { + throw new InvalidOperationException("Unexpected failure translating virtual address."); + } + + pageList.AddRange(pa, 1); + + address += PageSize; + } + } + + private bool IsUnmapped(ulong address, ulong size) + { + return CheckRange( + address, + size, + MemoryState.Mask, + MemoryState.Unmapped, + MemoryPermission.Mask, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _); + } + + private bool CheckRange( + ulong address, + ulong size, + MemoryState stateMask, + MemoryState stateExpected, + MemoryPermission permissionMask, + MemoryPermission permissionExpected, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected, + MemoryAttribute attributeIgnoreMask, + out MemoryState outState, + out MemoryPermission outPermission, + out MemoryAttribute outAttribute) + { + ulong endAddr = address + size - 1; + + LinkedListNode node = FindBlockNode(address); + + KMemoryInfo info = node.Value.GetInfo(); + + MemoryState firstState = info.State; + MemoryPermission firstPermission = info.Permission; + MemoryAttribute firstAttribute = info.Attribute; + + do + { + info = node.Value.GetInfo(); + + //Check if the block state matches what we expect. + if ( firstState != info.State || + firstPermission != info.Permission || + (info.Attribute & attributeMask) != attributeExpected || + (firstAttribute | attributeIgnoreMask) != (info.Attribute | attributeIgnoreMask) || + (firstState & stateMask) != stateExpected || + (firstPermission & permissionMask) != permissionExpected) + { + break; + } + + //Check if this is the last block on the range, if so return success. + if (endAddr <= info.Address + info.Size - 1) + { + outState = firstState; + outPermission = firstPermission; + outAttribute = firstAttribute & ~attributeIgnoreMask; + + return true; + } + + node = node.Next; + } + while (node != null); + + outState = MemoryState.Unmapped; + outPermission = MemoryPermission.None; + outAttribute = MemoryAttribute.None; + + return false; + } + + private bool CheckRange( + ulong address, + ulong size, + MemoryState stateMask, + MemoryState stateExpected, + MemoryPermission permissionMask, + MemoryPermission permissionExpected, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected) + { + ulong endAddr = address + size - 1; + + LinkedListNode node = FindBlockNode(address); + + do + { + KMemoryInfo info = node.Value.GetInfo(); + + //Check if the block state matches what we expect. + if ((info.State & stateMask) != stateExpected || + (info.Permission & permissionMask) != permissionExpected || + (info.Attribute & attributeMask) != attributeExpected) + { + break; + } + + //Check if this is the last block on the range, if so return success. + if (endAddr <= info.Address + info.Size - 1) + { + return true; + } + + node = node.Next; + } + while (node != null); + + return false; + } + + private void InsertBlock( + ulong baseAddress, + ulong pagesCount, + MemoryState oldState, + MemoryPermission oldPermission, + MemoryAttribute oldAttribute, + MemoryState newState, + MemoryPermission newPermission, + MemoryAttribute newAttribute) + { + //Insert new block on the list only on areas where the state + //of the block matches the state specified on the Old* state + //arguments, otherwise leave it as is. + int oldCount = _blocks.Count; + + oldAttribute |= MemoryAttribute.IpcAndDeviceMapped; + + ulong endAddr = pagesCount * PageSize + baseAddress; + + LinkedListNode node = _blocks.First; + + while (node != null) + { + LinkedListNode newNode = node; + LinkedListNode nextNode = node.Next; + + KMemoryBlock currBlock = node.Value; + + ulong currBaseAddr = currBlock.BaseAddress; + ulong currEndAddr = currBlock.PagesCount * PageSize + currBaseAddr; + + if (baseAddress < currEndAddr && currBaseAddr < endAddr) + { + MemoryAttribute currBlockAttr = currBlock.Attribute | MemoryAttribute.IpcAndDeviceMapped; + + if (currBlock.State != oldState || + currBlock.Permission != oldPermission || + currBlockAttr != oldAttribute) + { + node = nextNode; + + continue; + } + + if (currBaseAddr >= baseAddress && currEndAddr <= endAddr) + { + currBlock.State = newState; + currBlock.Permission = newPermission; + currBlock.Attribute &= ~MemoryAttribute.IpcAndDeviceMapped; + currBlock.Attribute |= newAttribute; + } + else if (currBaseAddr >= baseAddress) + { + currBlock.BaseAddress = endAddr; + + currBlock.PagesCount = (currEndAddr - endAddr) / PageSize; + + ulong newPagesCount = (endAddr - currBaseAddr) / PageSize; + + newNode = _blocks.AddBefore(node, new KMemoryBlock( + currBaseAddr, + newPagesCount, + newState, + newPermission, + newAttribute)); + } + else if (currEndAddr <= endAddr) + { + currBlock.PagesCount = (baseAddress - currBaseAddr) / PageSize; + + ulong newPagesCount = (currEndAddr - baseAddress) / PageSize; + + newNode = _blocks.AddAfter(node, new KMemoryBlock( + baseAddress, + newPagesCount, + newState, + newPermission, + newAttribute)); + } + else + { + currBlock.PagesCount = (baseAddress - currBaseAddr) / PageSize; + + ulong nextPagesCount = (currEndAddr - endAddr) / PageSize; + + newNode = _blocks.AddAfter(node, new KMemoryBlock( + baseAddress, + pagesCount, + newState, + newPermission, + newAttribute)); + + _blocks.AddAfter(newNode, new KMemoryBlock( + endAddr, + nextPagesCount, + currBlock.State, + currBlock.Permission, + currBlock.Attribute)); + + nextNode = null; + } + + MergeEqualStateNeighbours(newNode); + } + + node = nextNode; + } + + _blockAllocator.Count += _blocks.Count - oldCount; + } + + private void InsertBlock( + ulong baseAddress, + ulong pagesCount, + MemoryState state, + MemoryPermission permission = MemoryPermission.None, + MemoryAttribute attribute = MemoryAttribute.None) + { + //Inserts new block at the list, replacing and spliting + //existing blocks as needed. + KMemoryBlock block = new KMemoryBlock(baseAddress, pagesCount, state, permission, attribute); + + int oldCount = _blocks.Count; + + ulong endAddr = pagesCount * PageSize + baseAddress; + + LinkedListNode newNode = null; + + LinkedListNode node = _blocks.First; + + while (node != null) + { + KMemoryBlock currBlock = node.Value; + + LinkedListNode nextNode = node.Next; + + ulong currBaseAddr = currBlock.BaseAddress; + ulong currEndAddr = currBlock.PagesCount * PageSize + currBaseAddr; + + if (baseAddress < currEndAddr && currBaseAddr < endAddr) + { + if (baseAddress >= currBaseAddr && endAddr <= currEndAddr) + { + block.Attribute |= currBlock.Attribute & MemoryAttribute.IpcAndDeviceMapped; + } + + if (baseAddress > currBaseAddr && endAddr < currEndAddr) + { + currBlock.PagesCount = (baseAddress - currBaseAddr) / PageSize; + + ulong nextPagesCount = (currEndAddr - endAddr) / PageSize; + + newNode = _blocks.AddAfter(node, block); + + _blocks.AddAfter(newNode, new KMemoryBlock( + endAddr, + nextPagesCount, + currBlock.State, + currBlock.Permission, + currBlock.Attribute)); + + break; + } + else if (baseAddress <= currBaseAddr && endAddr < currEndAddr) + { + currBlock.BaseAddress = endAddr; + + currBlock.PagesCount = (currEndAddr - endAddr) / PageSize; + + if (newNode == null) + { + newNode = _blocks.AddBefore(node, block); + } + } + else if (baseAddress > currBaseAddr && endAddr >= currEndAddr) + { + currBlock.PagesCount = (baseAddress - currBaseAddr) / PageSize; + + if (newNode == null) + { + newNode = _blocks.AddAfter(node, block); + } + } + else + { + if (newNode == null) + { + newNode = _blocks.AddBefore(node, block); + } + + _blocks.Remove(node); + } + } + + node = nextNode; + } + + if (newNode == null) + { + newNode = _blocks.AddFirst(block); + } + + MergeEqualStateNeighbours(newNode); + + _blockAllocator.Count += _blocks.Count - oldCount; + } + + private void MergeEqualStateNeighbours(LinkedListNode node) + { + KMemoryBlock block = node.Value; + + ulong endAddr = block.PagesCount * PageSize + block.BaseAddress; + + if (node.Previous != null) + { + KMemoryBlock previous = node.Previous.Value; + + if (BlockStateEquals(block, previous)) + { + _blocks.Remove(node.Previous); + + block.BaseAddress = previous.BaseAddress; + } + } + + if (node.Next != null) + { + KMemoryBlock next = node.Next.Value; + + if (BlockStateEquals(block, next)) + { + _blocks.Remove(node.Next); + + endAddr = next.BaseAddress + next.PagesCount * PageSize; + } + } + + block.PagesCount = (endAddr - block.BaseAddress) / PageSize; + } + + private static bool BlockStateEquals(KMemoryBlock lhs, KMemoryBlock rhs) + { + return lhs.State == rhs.State && + lhs.Permission == rhs.Permission && + lhs.Attribute == rhs.Attribute && + lhs.DeviceRefCount == rhs.DeviceRefCount && + lhs.IpcRefCount == rhs.IpcRefCount; + } + + private ulong FindFirstFit( + ulong regionStart, + ulong regionPagesCount, + ulong neededPagesCount, + int alignment, + ulong reservedStart, + ulong reservedPagesCount) + { + ulong reservedSize = reservedPagesCount * PageSize; + + ulong totalNeededSize = reservedSize + neededPagesCount * PageSize; + + ulong regionEndAddr = regionStart + regionPagesCount * PageSize; + + LinkedListNode node = FindBlockNode(regionStart); + + KMemoryInfo info = node.Value.GetInfo(); + + while (regionEndAddr >= info.Address) + { + if (info.State == MemoryState.Unmapped) + { + ulong currBaseAddr = info.Address + reservedSize; + ulong currEndAddr = info.Address + info.Size - 1; + + ulong address = BitUtils.AlignDown(currBaseAddr, alignment) + reservedStart; + + if (currBaseAddr > address) + { + address += (ulong)alignment; + } + + ulong allocationEndAddr = address + totalNeededSize - 1; + + if (allocationEndAddr <= regionEndAddr && + allocationEndAddr <= currEndAddr && + address < allocationEndAddr) + { + return address; + } + } + + node = node.Next; + + if (node == null) + { + break; + } + + info = node.Value.GetInfo(); + } + + return 0; + } + + private KMemoryBlock FindBlock(ulong address) + { + return FindBlockNode(address)?.Value; + } + + private LinkedListNode FindBlockNode(ulong address) + { + lock (_blocks) + { + LinkedListNode node = _blocks.First; + + while (node != null) + { + KMemoryBlock block = node.Value; + + ulong currEndAddr = block.PagesCount * PageSize + block.BaseAddress; + + if (block.BaseAddress <= address && currEndAddr - 1 >= address) + { + return node; + } + + node = node.Next; + } + } + + return null; + } + + private bool ValidateRegionForState(ulong address, ulong size, MemoryState state) + { + ulong endAddr = address + size; + + ulong regionBaseAddr = GetBaseAddrForState(state); + + ulong regionEndAddr = regionBaseAddr + GetSizeForState(state); + + bool InsideRegion() + { + return regionBaseAddr <= address && + endAddr > address && + endAddr - 1 <= regionEndAddr - 1; + } + + bool OutsideHeapRegion() + { + return endAddr <= HeapRegionStart || + address >= HeapRegionEnd; + } + + bool OutsideMapRegion() + { + return endAddr <= AliasRegionStart || + address >= AliasRegionEnd; + } + + switch (state) + { + case MemoryState.Io: + case MemoryState.Normal: + case MemoryState.CodeStatic: + case MemoryState.CodeMutable: + case MemoryState.SharedMemory: + case MemoryState.ModCodeStatic: + case MemoryState.ModCodeMutable: + case MemoryState.Stack: + case MemoryState.ThreadLocal: + case MemoryState.TransferMemoryIsolated: + case MemoryState.TransferMemory: + case MemoryState.ProcessMemory: + case MemoryState.CodeReadOnly: + case MemoryState.CodeWritable: + return InsideRegion() && OutsideHeapRegion() && OutsideMapRegion(); + + case MemoryState.Heap: + return InsideRegion() && OutsideMapRegion(); + + case MemoryState.IpcBuffer0: + case MemoryState.IpcBuffer1: + case MemoryState.IpcBuffer3: + return InsideRegion() && OutsideHeapRegion(); + + case MemoryState.KernelStack: + return InsideRegion(); + } + + throw new ArgumentException($"Invalid state value \"{state}\"."); + } + + private ulong GetBaseAddrForState(MemoryState state) + { + switch (state) + { + case MemoryState.Io: + case MemoryState.Normal: + case MemoryState.ThreadLocal: + return TlsIoRegionStart; + + case MemoryState.CodeStatic: + case MemoryState.CodeMutable: + case MemoryState.SharedMemory: + case MemoryState.ModCodeStatic: + case MemoryState.ModCodeMutable: + case MemoryState.TransferMemoryIsolated: + case MemoryState.TransferMemory: + case MemoryState.ProcessMemory: + case MemoryState.CodeReadOnly: + case MemoryState.CodeWritable: + return GetAddrSpaceBaseAddr(); + + case MemoryState.Heap: + return HeapRegionStart; + + case MemoryState.IpcBuffer0: + case MemoryState.IpcBuffer1: + case MemoryState.IpcBuffer3: + return AliasRegionStart; + + case MemoryState.Stack: + return StackRegionStart; + + case MemoryState.KernelStack: + return AddrSpaceStart; + } + + throw new ArgumentException($"Invalid state value \"{state}\"."); + } + + private ulong GetSizeForState(MemoryState state) + { + switch (state) + { + case MemoryState.Io: + case MemoryState.Normal: + case MemoryState.ThreadLocal: + return TlsIoRegionEnd - TlsIoRegionStart; + + case MemoryState.CodeStatic: + case MemoryState.CodeMutable: + case MemoryState.SharedMemory: + case MemoryState.ModCodeStatic: + case MemoryState.ModCodeMutable: + case MemoryState.TransferMemoryIsolated: + case MemoryState.TransferMemory: + case MemoryState.ProcessMemory: + case MemoryState.CodeReadOnly: + case MemoryState.CodeWritable: + return GetAddrSpaceSize(); + + case MemoryState.Heap: + return HeapRegionEnd - HeapRegionStart; + + case MemoryState.IpcBuffer0: + case MemoryState.IpcBuffer1: + case MemoryState.IpcBuffer3: + return AliasRegionEnd - AliasRegionStart; + + case MemoryState.Stack: + return StackRegionEnd - StackRegionStart; + + case MemoryState.KernelStack: + return AddrSpaceEnd - AddrSpaceStart; + } + + throw new ArgumentException($"Invalid state value \"{state}\"."); + } + + public ulong GetAddrSpaceBaseAddr() + { + if (AddrSpaceWidth == 36 || AddrSpaceWidth == 39) + { + return 0x8000000; + } + else if (AddrSpaceWidth == 32) + { + return 0x200000; + } + else + { + throw new InvalidOperationException("Invalid address space width!"); + } + } + + public ulong GetAddrSpaceSize() + { + if (AddrSpaceWidth == 36) + { + return 0xff8000000; + } + else if (AddrSpaceWidth == 39) + { + return 0x7ff8000000; + } + else if (AddrSpaceWidth == 32) + { + return 0xffe00000; + } + else + { + throw new InvalidOperationException("Invalid address space width!"); + } + } + + private KernelResult MapPages(ulong address, KPageList pageList, MemoryPermission permission) + { + ulong currAddr = address; + + KernelResult result = KernelResult.Success; + + foreach (KPageNode pageNode in pageList) + { + result = DoMmuOperation( + currAddr, + pageNode.PagesCount, + pageNode.Address, + true, + permission, + MemoryOperation.MapPa); + + if (result != KernelResult.Success) + { + KMemoryInfo info = FindBlock(currAddr).GetInfo(); + + ulong pagesCount = (address - currAddr) / PageSize; + + result = MmuUnmap(address, pagesCount); + + break; + } + + currAddr += pageNode.PagesCount * PageSize; + } + + return result; + } + + private KernelResult MmuUnmap(ulong address, ulong pagesCount) + { + return DoMmuOperation( + address, + pagesCount, + 0, + false, + MemoryPermission.None, + MemoryOperation.Unmap); + } + + private KernelResult MmuChangePermission(ulong address, ulong pagesCount, MemoryPermission permission) + { + return DoMmuOperation( + address, + pagesCount, + 0, + false, + permission, + MemoryOperation.ChangePermRw); + } + + private KernelResult DoMmuOperation( + ulong dstVa, + ulong pagesCount, + ulong srcPa, + bool map, + MemoryPermission permission, + MemoryOperation operation) + { + if (map != (operation == MemoryOperation.MapPa)) + { + throw new ArgumentException(nameof(map) + " value is invalid for this operation."); + } + + KernelResult result; + + switch (operation) + { + case MemoryOperation.MapPa: + { + ulong size = pagesCount * PageSize; + + _cpuMemory.Map((long)dstVa, (long)(srcPa - DramMemoryMap.DramBase), (long)size); + + result = KernelResult.Success; + + break; + } + + case MemoryOperation.Allocate: + { + KMemoryRegionManager region = GetMemoryRegionManager(); + + result = region.AllocatePages(pagesCount, _aslrDisabled, out KPageList pageList); + + if (result == KernelResult.Success) + { + result = MmuMapPages(dstVa, pageList); + } + + break; + } + + case MemoryOperation.Unmap: + { + ulong size = pagesCount * PageSize; + + _cpuMemory.Unmap((long)dstVa, (long)size); + + result = KernelResult.Success; + + break; + } + + case MemoryOperation.ChangePermRw: result = KernelResult.Success; break; + case MemoryOperation.ChangePermsAndAttributes: result = KernelResult.Success; break; + + default: throw new ArgumentException($"Invalid operation \"{operation}\"."); + } + + return result; + } + + private KernelResult DoMmuOperation( + ulong address, + ulong pagesCount, + KPageList pageList, + MemoryPermission permission, + MemoryOperation operation) + { + if (operation != MemoryOperation.MapVa) + { + throw new ArgumentException($"Invalid memory operation \"{operation}\" specified."); + } + + return MmuMapPages(address, pageList); + } + + private KMemoryRegionManager GetMemoryRegionManager() + { + return _system.MemoryRegions[(int)_memRegion]; + } + + private KernelResult MmuMapPages(ulong address, KPageList pageList) + { + foreach (KPageNode pageNode in pageList) + { + ulong size = pageNode.PagesCount * PageSize; + + _cpuMemory.Map((long)address, (long)(pageNode.Address - DramMemoryMap.DramBase), (long)size); + + address += size; + } + + return KernelResult.Success; + } + + public KernelResult ConvertVaToPa(ulong va, out ulong pa) + { + pa = DramMemoryMap.DramBase + (ulong)_cpuMemory.GetPhysicalAddress((long)va); + + return KernelResult.Success; + } + + public long GetMmUsedPages() + { + lock (_blocks) + { + return BitUtils.DivRoundUp(GetMmUsedSize(), PageSize); + } + } + + private long GetMmUsedSize() + { + return _blocks.Count * KMemoryBlockSize; + } + + public bool IsInvalidRegion(ulong address, ulong size) + { + return address + size - 1 > GetAddrSpaceBaseAddr() + GetAddrSpaceSize() - 1; + } + + public bool InsideAddrSpace(ulong address, ulong size) + { + return AddrSpaceStart <= address && address + size - 1 <= AddrSpaceEnd - 1; + } + + public bool InsideAliasRegion(ulong address, ulong size) + { + return address + size > AliasRegionStart && AliasRegionEnd > address; + } + + public bool InsideHeapRegion(ulong address, ulong size) + { + return address + size > HeapRegionStart && HeapRegionEnd > address; + } + + public bool InsideStackRegion(ulong address, ulong size) + { + return address + size > StackRegionStart && StackRegionEnd > address; + } + + public bool OutsideAliasRegion(ulong address, ulong size) + { + return AliasRegionStart > address || address + size - 1 > AliasRegionEnd - 1; + } + + public bool OutsideAddrSpace(ulong address, ulong size) + { + return AddrSpaceStart > address || address + size - 1 > AddrSpaceEnd - 1; + } + + public bool OutsideStackRegion(ulong address, ulong size) + { + return StackRegionStart > address || address + size - 1 > StackRegionEnd - 1; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionBlock.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionBlock.cs new file mode 100644 index 00000000..3334ff43 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionBlock.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryRegionBlock + { + public long[][] Masks; + + public ulong FreeCount; + public int MaxLevel; + public ulong StartAligned; + public ulong SizeInBlocksTruncated; + public ulong SizeInBlocksRounded; + public int Order; + public int NextOrder; + + public bool TryCoalesce(int index, int size) + { + long mask = ((1L << size) - 1) << (index & 63); + + index /= 64; + + if ((mask & ~Masks[MaxLevel - 1][index]) != 0) + { + return false; + } + + Masks[MaxLevel - 1][index] &= ~mask; + + for (int level = MaxLevel - 2; level >= 0; level--, index /= 64) + { + Masks[level][index / 64] &= ~(1L << (index & 63)); + + if (Masks[level][index / 64] != 0) + { + break; + } + } + + FreeCount -= (ulong)size; + + return true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs new file mode 100644 index 00000000..777e9aa9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs @@ -0,0 +1,429 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryRegionManager + { + private static readonly int[] BlockOrders = new int[] { 12, 16, 21, 22, 25, 29, 30 }; + + public ulong Address { get; private set; } + public ulong EndAddr { get; private set; } + public ulong Size { get; private set; } + + private int _blockOrdersCount; + + private KMemoryRegionBlock[] _blocks; + + public KMemoryRegionManager(ulong address, ulong size, ulong endAddr) + { + _blocks = new KMemoryRegionBlock[BlockOrders.Length]; + + Address = address; + Size = size; + EndAddr = endAddr; + + _blockOrdersCount = BlockOrders.Length; + + for (int blockIndex = 0; blockIndex < _blockOrdersCount; blockIndex++) + { + _blocks[blockIndex] = new KMemoryRegionBlock(); + + _blocks[blockIndex].Order = BlockOrders[blockIndex]; + + int nextOrder = blockIndex == _blockOrdersCount - 1 ? 0 : BlockOrders[blockIndex + 1]; + + _blocks[blockIndex].NextOrder = nextOrder; + + int currBlockSize = 1 << BlockOrders[blockIndex]; + int nextBlockSize = currBlockSize; + + if (nextOrder != 0) + { + nextBlockSize = 1 << nextOrder; + } + + ulong startAligned = BitUtils.AlignDown(address, nextBlockSize); + ulong endAddrAligned = BitUtils.AlignDown(endAddr, currBlockSize); + + ulong sizeInBlocksTruncated = (endAddrAligned - startAligned) >> BlockOrders[blockIndex]; + + ulong endAddrRounded = BitUtils.AlignUp(address + size, nextBlockSize); + + ulong sizeInBlocksRounded = (endAddrRounded - startAligned) >> BlockOrders[blockIndex]; + + _blocks[blockIndex].StartAligned = startAligned; + _blocks[blockIndex].SizeInBlocksTruncated = sizeInBlocksTruncated; + _blocks[blockIndex].SizeInBlocksRounded = sizeInBlocksRounded; + + ulong currSizeInBlocks = sizeInBlocksRounded; + + int maxLevel = 0; + + do + { + maxLevel++; + } + while ((currSizeInBlocks /= 64) != 0); + + _blocks[blockIndex].MaxLevel = maxLevel; + + _blocks[blockIndex].Masks = new long[maxLevel][]; + + currSizeInBlocks = sizeInBlocksRounded; + + for (int level = maxLevel - 1; level >= 0; level--) + { + currSizeInBlocks = (currSizeInBlocks + 63) / 64; + + _blocks[blockIndex].Masks[level] = new long[currSizeInBlocks]; + } + } + + if (size != 0) + { + FreePages(address, size / KMemoryManager.PageSize); + } + } + + public KernelResult AllocatePages(ulong pagesCount, bool backwards, out KPageList pageList) + { + lock (_blocks) + { + return AllocatePagesImpl(pagesCount, backwards, out pageList); + } + } + + private KernelResult AllocatePagesImpl(ulong pagesCount, bool backwards, out KPageList pageList) + { + pageList = new KPageList(); + + if (_blockOrdersCount > 0) + { + if (GetFreePagesImpl() < pagesCount) + { + return KernelResult.OutOfMemory; + } + } + else if (pagesCount != 0) + { + return KernelResult.OutOfMemory; + } + + for (int blockIndex = _blockOrdersCount - 1; blockIndex >= 0; blockIndex--) + { + KMemoryRegionBlock block = _blocks[blockIndex]; + + ulong bestFitBlockSize = 1UL << block.Order; + + ulong blockPagesCount = bestFitBlockSize / KMemoryManager.PageSize; + + //Check if this is the best fit for this page size. + //If so, try allocating as much requested pages as possible. + while (blockPagesCount <= pagesCount) + { + ulong address = 0; + + for (int currBlockIndex = blockIndex; + currBlockIndex < _blockOrdersCount && address == 0; + currBlockIndex++) + { + block = _blocks[currBlockIndex]; + + int index = 0; + + bool zeroMask = false; + + for (int level = 0; level < block.MaxLevel; level++) + { + long mask = block.Masks[level][index]; + + if (mask == 0) + { + zeroMask = true; + + break; + } + + if (backwards) + { + index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask); + } + else + { + index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask)); + } + } + + if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask) + { + continue; + } + + block.FreeCount--; + + int tempIdx = index; + + for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64) + { + block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63)); + + if (block.Masks[level][tempIdx / 64] != 0) + { + break; + } + } + + address = block.StartAligned + ((ulong)index << block.Order); + } + + for (int currBlockIndex = blockIndex; + currBlockIndex < _blockOrdersCount && address == 0; + currBlockIndex++) + { + block = _blocks[currBlockIndex]; + + int index = 0; + + bool zeroMask = false; + + for (int level = 0; level < block.MaxLevel; level++) + { + long mask = block.Masks[level][index]; + + if (mask == 0) + { + zeroMask = true; + + break; + } + + if (backwards) + { + index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask)); + } + else + { + index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask); + } + } + + if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask) + { + continue; + } + + block.FreeCount--; + + int tempIdx = index; + + for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64) + { + block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63)); + + if (block.Masks[level][tempIdx / 64] != 0) + { + break; + } + } + + address = block.StartAligned + ((ulong)index << block.Order); + } + + //The address being zero means that no free space was found on that order, + //just give up and try with the next one. + if (address == 0) + { + break; + } + + //If we are using a larger order than best fit, then we should + //split it into smaller blocks. + ulong firstFreeBlockSize = 1UL << block.Order; + + if (firstFreeBlockSize > bestFitBlockSize) + { + FreePages(address + bestFitBlockSize, (firstFreeBlockSize - bestFitBlockSize) / KMemoryManager.PageSize); + } + + //Add new allocated page(s) to the pages list. + //If an error occurs, then free all allocated pages and fail. + KernelResult result = pageList.AddRange(address, blockPagesCount); + + if (result != KernelResult.Success) + { + FreePages(address, blockPagesCount); + + foreach (KPageNode pageNode in pageList) + { + FreePages(pageNode.Address, pageNode.PagesCount); + } + + return result; + } + + pagesCount -= blockPagesCount; + } + } + + //Success case, all requested pages were allocated successfully. + if (pagesCount == 0) + { + return KernelResult.Success; + } + + //Error case, free allocated pages and return out of memory. + foreach (KPageNode pageNode in pageList) + { + FreePages(pageNode.Address, pageNode.PagesCount); + } + + pageList = null; + + return KernelResult.OutOfMemory; + } + + public void FreePages(KPageList pageList) + { + lock (_blocks) + { + foreach (KPageNode pageNode in pageList) + { + FreePages(pageNode.Address, pageNode.PagesCount); + } + } + } + + private void FreePages(ulong address, ulong pagesCount) + { + ulong endAddr = address + pagesCount * KMemoryManager.PageSize; + + int blockIndex = _blockOrdersCount - 1; + + ulong addressRounded = 0; + ulong endAddrTruncated = 0; + + for (; blockIndex >= 0; blockIndex--) + { + KMemoryRegionBlock allocInfo = _blocks[blockIndex]; + + int blockSize = 1 << allocInfo.Order; + + addressRounded = BitUtils.AlignUp (address, blockSize); + endAddrTruncated = BitUtils.AlignDown(endAddr, blockSize); + + if (addressRounded < endAddrTruncated) + { + break; + } + } + + void FreeRegion(ulong currAddress) + { + for (int currBlockIndex = blockIndex; + currBlockIndex < _blockOrdersCount && currAddress != 0; + currBlockIndex++) + { + KMemoryRegionBlock block = _blocks[currBlockIndex]; + + block.FreeCount++; + + ulong freedBlocks = (currAddress - block.StartAligned) >> block.Order; + + int index = (int)freedBlocks; + + for (int level = block.MaxLevel - 1; level >= 0; level--, index /= 64) + { + long mask = block.Masks[level][index / 64]; + + block.Masks[level][index / 64] = mask | (1L << (index & 63)); + + if (mask != 0) + { + break; + } + } + + int blockSizeDelta = 1 << (block.NextOrder - block.Order); + + int freedBlocksTruncated = BitUtils.AlignDown((int)freedBlocks, blockSizeDelta); + + if (!block.TryCoalesce(freedBlocksTruncated, blockSizeDelta)) + { + break; + } + + currAddress = block.StartAligned + ((ulong)freedBlocksTruncated << block.Order); + } + } + + //Free inside aligned region. + ulong baseAddress = addressRounded; + + while (baseAddress < endAddrTruncated) + { + ulong blockSize = 1UL << _blocks[blockIndex].Order; + + FreeRegion(baseAddress); + + baseAddress += blockSize; + } + + int nextBlockIndex = blockIndex - 1; + + //Free region between Address and aligned region start. + baseAddress = addressRounded; + + for (blockIndex = nextBlockIndex; blockIndex >= 0; blockIndex--) + { + ulong blockSize = 1UL << _blocks[blockIndex].Order; + + while (baseAddress - blockSize >= address) + { + baseAddress -= blockSize; + + FreeRegion(baseAddress); + } + } + + //Free region between aligned region end and End Address. + baseAddress = endAddrTruncated; + + for (blockIndex = nextBlockIndex; blockIndex >= 0; blockIndex--) + { + ulong blockSize = 1UL << _blocks[blockIndex].Order; + + while (baseAddress + blockSize <= endAddr) + { + FreeRegion(baseAddress); + + baseAddress += blockSize; + } + } + } + + public ulong GetFreePages() + { + lock (_blocks) + { + return GetFreePagesImpl(); + } + } + + private ulong GetFreePagesImpl() + { + ulong availablePages = 0; + + for (int blockIndex = 0; blockIndex < _blockOrdersCount; blockIndex++) + { + KMemoryRegionBlock block = _blocks[blockIndex]; + + ulong blockPagesCount = (1UL << block.Order) / KMemoryManager.PageSize; + + availablePages += blockPagesCount * block.FreeCount; + } + + return availablePages; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs new file mode 100644 index 00000000..f0935dcc --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs @@ -0,0 +1,81 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using System.Collections; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KPageList : IEnumerable + { + public LinkedList Nodes { get; private set; } + + public KPageList() + { + Nodes = new LinkedList(); + } + + public KernelResult AddRange(ulong address, ulong pagesCount) + { + if (pagesCount != 0) + { + if (Nodes.Last != null) + { + KPageNode lastNode = Nodes.Last.Value; + + if (lastNode.Address + lastNode.PagesCount * KMemoryManager.PageSize == address) + { + address = lastNode.Address; + pagesCount += lastNode.PagesCount; + + Nodes.RemoveLast(); + } + } + + Nodes.AddLast(new KPageNode(address, pagesCount)); + } + + return KernelResult.Success; + } + + public ulong GetPagesCount() + { + ulong sum = 0; + + foreach (KPageNode node in Nodes) + { + sum += node.PagesCount; + } + + return sum; + } + + public bool IsEqual(KPageList other) + { + LinkedListNode thisNode = Nodes.First; + LinkedListNode otherNode = other.Nodes.First; + + while (thisNode != null && otherNode != null) + { + if (thisNode.Value.Address != otherNode.Value.Address || + thisNode.Value.PagesCount != otherNode.Value.PagesCount) + { + return false; + } + + thisNode = thisNode.Next; + otherNode = otherNode.Next; + } + + return thisNode == null && otherNode == null; + } + + public IEnumerator GetEnumerator() + { + return Nodes.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs new file mode 100644 index 00000000..ada41687 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + struct KPageNode + { + public ulong Address; + public ulong PagesCount; + + public KPageNode(ulong address, ulong pagesCount) + { + Address = address; + PagesCount = pagesCount; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs new file mode 100644 index 00000000..f2a05bda --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs @@ -0,0 +1,70 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KSharedMemory + { + private KPageList _pageList; + + private long _ownerPid; + + private MemoryPermission _ownerPermission; + private MemoryPermission _userPermission; + + public KSharedMemory( + KPageList pageList, + long ownerPid, + MemoryPermission ownerPermission, + MemoryPermission userPermission) + { + _pageList = pageList; + _ownerPid = ownerPid; + _ownerPermission = ownerPermission; + _userPermission = userPermission; + } + + public KernelResult MapIntoProcess( + KMemoryManager memoryManager, + ulong address, + ulong size, + KProcess process, + MemoryPermission permission) + { + ulong pagesCountRounded = BitUtils.DivRoundUp(size, KMemoryManager.PageSize); + + if (_pageList.GetPagesCount() != pagesCountRounded) + { + return KernelResult.InvalidSize; + } + + MemoryPermission expectedPermission = process.Pid == _ownerPid + ? _ownerPermission + : _userPermission; + + if (permission != expectedPermission) + { + return KernelResult.InvalidPermission; + } + + return memoryManager.MapPages(address, _pageList, MemoryState.SharedMemory, permission); + } + + public KernelResult UnmapFromProcess( + KMemoryManager memoryManager, + ulong address, + ulong size, + KProcess process) + { + ulong pagesCountRounded = BitUtils.DivRoundUp(size, KMemoryManager.PageSize); + + if (_pageList.GetPagesCount() != pagesCountRounded) + { + return KernelResult.InvalidSize; + } + + return memoryManager.UnmapPages(address, _pageList, MemoryState.SharedMemory); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs new file mode 100644 index 00000000..9051e84c --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KSlabHeap + { + private LinkedList _items; + + public KSlabHeap(ulong pa, ulong itemSize, ulong size) + { + _items = new LinkedList(); + + int itemsCount = (int)(size / itemSize); + + for (int index = 0; index < itemsCount; index++) + { + _items.AddLast(pa); + + pa += itemSize; + } + } + + public bool TryGetItem(out ulong pa) + { + lock (_items) + { + if (_items.First != null) + { + pa = _items.First.Value; + + _items.RemoveFirst(); + + return true; + } + } + + pa = 0; + + return false; + } + + public void Free(ulong pa) + { + lock (_items) + { + _items.AddFirst(pa); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs b/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs new file mode 100644 index 00000000..02367e89 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KTransferMemory + { + public ulong Address { get; private set; } + public ulong Size { get; private set; } + + public KTransferMemory(ulong address, ulong size) + { + Address = address; + Size = size; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs new file mode 100644 index 00000000..42407ffe --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs @@ -0,0 +1,22 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + [Flags] + enum MemoryAttribute : byte + { + None = 0, + Mask = 0xff, + + Borrowed = 1 << 0, + IpcMapped = 1 << 1, + DeviceMapped = 1 << 2, + Uncached = 1 << 3, + + IpcAndDeviceMapped = IpcMapped | DeviceMapped, + + BorrowedAndIpcMapped = Borrowed | IpcMapped, + + DeviceMappedAndUncached = DeviceMapped | Uncached + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/MemoryOperation.cs b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryOperation.cs new file mode 100644 index 00000000..7f7f29de --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryOperation.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + enum MemoryOperation + { + MapPa, + MapVa, + Allocate, + Unmap, + ChangePermRw, + ChangePermsAndAttributes + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs new file mode 100644 index 00000000..0ad90abd --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs @@ -0,0 +1,18 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + [Flags] + enum MemoryPermission : byte + { + None = 0, + Mask = 0xff, + + Read = 1 << 0, + Write = 1 << 1, + Execute = 1 << 2, + + ReadAndWrite = Read | Write, + ReadAndExecute = Read | Execute + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs new file mode 100644 index 00000000..ad719bde --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + enum MemoryRegion + { + Application = 0, + Applet = 1, + Service = 2, + NvServices = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs new file mode 100644 index 00000000..f7161a88 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs @@ -0,0 +1,49 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + [Flags] + enum MemoryState : uint + { + Unmapped = 0x00000000, + Io = 0x00002001, + Normal = 0x00042002, + CodeStatic = 0x00DC7E03, + CodeMutable = 0x03FEBD04, + Heap = 0x037EBD05, + SharedMemory = 0x00402006, + ModCodeStatic = 0x00DD7E08, + ModCodeMutable = 0x03FFBD09, + IpcBuffer0 = 0x005C3C0A, + Stack = 0x005C3C0B, + ThreadLocal = 0x0040200C, + TransferMemoryIsolated = 0x015C3C0D, + TransferMemory = 0x005C380E, + ProcessMemory = 0x0040380F, + Reserved = 0x00000010, + IpcBuffer1 = 0x005C3811, + IpcBuffer3 = 0x004C2812, + KernelStack = 0x00002013, + CodeReadOnly = 0x00402214, + CodeWritable = 0x00402015, + Mask = 0xffffffff, + + PermissionChangeAllowed = 1 << 8, + ForceReadWritableByDebugSyscalls = 1 << 9, + IpcSendAllowedType0 = 1 << 10, + IpcSendAllowedType3 = 1 << 11, + IpcSendAllowedType1 = 1 << 12, + ProcessPermissionChangeAllowed = 1 << 14, + MapAllowed = 1 << 15, + UnmapProcessCodeMemoryAllowed = 1 << 16, + TransferMemoryAllowed = 1 << 17, + QueryPhysicalAddressAllowed = 1 << 18, + MapDeviceAllowed = 1 << 19, + MapDeviceAlignedAllowed = 1 << 20, + IpcBufferAllowed = 1 << 21, + IsPoolAllocated = 1 << 22, + MapProcessAllowed = 1 << 23, + AttributeChangeAllowed = 1 << 24, + CodeMemoryAllowed = 1 << 25 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/MemoryAttribute.cs b/Ryujinx.HLE/HOS/Kernel/MemoryAttribute.cs deleted file mode 100644 index 8f3197cb..00000000 --- a/Ryujinx.HLE/HOS/Kernel/MemoryAttribute.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace Ryujinx.HLE.HOS.Kernel -{ - [Flags] - enum MemoryAttribute : byte - { - None = 0, - Mask = 0xff, - - Borrowed = 1 << 0, - IpcMapped = 1 << 1, - DeviceMapped = 1 << 2, - Uncached = 1 << 3, - - IpcAndDeviceMapped = IpcMapped | DeviceMapped, - - BorrowedAndIpcMapped = Borrowed | IpcMapped, - - DeviceMappedAndUncached = DeviceMapped | Uncached - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/MemoryOperation.cs b/Ryujinx.HLE/HOS/Kernel/MemoryOperation.cs deleted file mode 100644 index b9350121..00000000 --- a/Ryujinx.HLE/HOS/Kernel/MemoryOperation.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - enum MemoryOperation - { - MapPa, - MapVa, - Allocate, - Unmap, - ChangePermRw, - ChangePermsAndAttributes - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/MemoryPermission.cs b/Ryujinx.HLE/HOS/Kernel/MemoryPermission.cs deleted file mode 100644 index 63539c2e..00000000 --- a/Ryujinx.HLE/HOS/Kernel/MemoryPermission.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace Ryujinx.HLE.HOS.Kernel -{ - [Flags] - enum MemoryPermission : byte - { - None = 0, - Mask = 0xff, - - Read = 1 << 0, - Write = 1 << 1, - Execute = 1 << 2, - - ReadAndWrite = Read | Write, - ReadAndExecute = Read | Execute - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/MemoryRegion.cs b/Ryujinx.HLE/HOS/Kernel/MemoryRegion.cs deleted file mode 100644 index ea4f33c9..00000000 --- a/Ryujinx.HLE/HOS/Kernel/MemoryRegion.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - enum MemoryRegion - { - Application = 0, - Applet = 1, - Service = 2, - NvServices = 3 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/MemoryState.cs b/Ryujinx.HLE/HOS/Kernel/MemoryState.cs deleted file mode 100644 index e2ce27ef..00000000 --- a/Ryujinx.HLE/HOS/Kernel/MemoryState.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; - -namespace Ryujinx.HLE.HOS.Kernel -{ - [Flags] - enum MemoryState : uint - { - Unmapped = 0x00000000, - Io = 0x00002001, - Normal = 0x00042002, - CodeStatic = 0x00DC7E03, - CodeMutable = 0x03FEBD04, - Heap = 0x037EBD05, - SharedMemory = 0x00402006, - ModCodeStatic = 0x00DD7E08, - ModCodeMutable = 0x03FFBD09, - IpcBuffer0 = 0x005C3C0A, - Stack = 0x005C3C0B, - ThreadLocal = 0x0040200C, - TransferMemoryIsolated = 0x015C3C0D, - TransferMemory = 0x005C380E, - ProcessMemory = 0x0040380F, - Reserved = 0x00000010, - IpcBuffer1 = 0x005C3811, - IpcBuffer3 = 0x004C2812, - KernelStack = 0x00002013, - CodeReadOnly = 0x00402214, - CodeWritable = 0x00402015, - Mask = 0xffffffff, - - PermissionChangeAllowed = 1 << 8, - ForceReadWritableByDebugSyscalls = 1 << 9, - IpcSendAllowedType0 = 1 << 10, - IpcSendAllowedType3 = 1 << 11, - IpcSendAllowedType1 = 1 << 12, - ProcessPermissionChangeAllowed = 1 << 14, - MapAllowed = 1 << 15, - UnmapProcessCodeMemoryAllowed = 1 << 16, - TransferMemoryAllowed = 1 << 17, - QueryPhysicalAddressAllowed = 1 << 18, - MapDeviceAllowed = 1 << 19, - MapDeviceAlignedAllowed = 1 << 20, - IpcBufferAllowed = 1 << 21, - IsPoolAllocated = 1 << 22, - MapProcessAllowed = 1 << 23, - AttributeChangeAllowed = 1 << 24, - CodeMemoryAllowed = 1 << 25 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/MersenneTwister.cs b/Ryujinx.HLE/HOS/Kernel/MersenneTwister.cs deleted file mode 100644 index 5307bdc9..00000000 --- a/Ryujinx.HLE/HOS/Kernel/MersenneTwister.cs +++ /dev/null @@ -1,128 +0,0 @@ -using Ryujinx.Common; - -namespace Ryujinx.HLE.HOS.Kernel -{ - class MersenneTwister - { - private int _index; - private uint[] _mt; - - public MersenneTwister(uint seed) - { - _mt = new uint[624]; - - _mt[0] = seed; - - for (int mtIdx = 1; mtIdx < _mt.Length; mtIdx++) - { - uint prev = _mt[mtIdx - 1]; - - _mt[mtIdx] = (uint)(0x6c078965 * (prev ^ (prev >> 30)) + mtIdx); - } - - _index = _mt.Length; - } - - public long GenRandomNumber(long min, long max) - { - long range = max - min; - - if (min == max) - { - return min; - } - - if (range == -1) - { - //Increment would cause a overflow, special case. - return GenRandomNumber(2, 2, 32, 0xffffffffu, 0xffffffffu); - } - - range++; - - //This is log2(Range) plus one. - int nextRangeLog2 = 64 - BitUtils.CountLeadingZeros64(range); - - //If Range is already power of 2, subtract one to use log2(Range) directly. - int rangeLog2 = nextRangeLog2 - (BitUtils.IsPowerOfTwo64(range) ? 1 : 0); - - int parts = rangeLog2 > 32 ? 2 : 1; - int bitsPerPart = rangeLog2 / parts; - - int fullParts = parts - (rangeLog2 - parts * bitsPerPart); - - uint mask = 0xffffffffu >> (32 - bitsPerPart); - uint maskPlus1 = 0xffffffffu >> (31 - bitsPerPart); - - long randomNumber; - - do - { - randomNumber = GenRandomNumber(parts, fullParts, bitsPerPart, mask, maskPlus1); - } - while ((ulong)randomNumber >= (ulong)range); - - return min + randomNumber; - } - - private long GenRandomNumber( - int parts, - int fullParts, - int bitsPerPart, - uint mask, - uint maskPlus1) - { - long randomNumber = 0; - - int part = 0; - - for (; part < fullParts; part++) - { - randomNumber <<= bitsPerPart; - randomNumber |= GenRandomNumber() & mask; - } - - for (; part < parts; part++) - { - randomNumber <<= bitsPerPart + 1; - randomNumber |= GenRandomNumber() & maskPlus1; - } - - return randomNumber; - } - - private uint GenRandomNumber() - { - if (_index >= _mt.Length) - { - Twist(); - } - - uint value = _mt[_index++]; - - value ^= value >> 11; - value ^= (value << 7) & 0x9d2c5680; - value ^= (value << 15) & 0xefc60000; - value ^= value >> 18; - - return value; - } - - private void Twist() - { - for (int mtIdx = 0; mtIdx < _mt.Length; mtIdx++) - { - uint value = (_mt[mtIdx] & 0x80000000) + (_mt[(mtIdx + 1) % _mt.Length] & 0x7fffffff); - - _mt[mtIdx] = _mt[(mtIdx + 397) % _mt.Length] ^ (value >> 1); - - if ((value & 1) != 0) - { - _mt[mtIdx] ^= 0x9908b0df; - } - } - - _index = 0; - } - } -} diff --git a/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs b/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs new file mode 100644 index 00000000..30fa4a5f --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs @@ -0,0 +1,311 @@ +using ChocolArm64.Memory; +using ChocolArm64.State; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Diagnostics.Demangler; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.Loaders.Elf; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class HleProcessDebugger + { + private const int Mod0 = 'M' << 0 | 'O' << 8 | 'D' << 16 | '0' << 24; + + private KProcess _owner; + + private class Image + { + public long BaseAddress { get; private set; } + + public ElfSymbol[] Symbols { get; private set; } + + public Image(long baseAddress, ElfSymbol[] symbols) + { + BaseAddress = baseAddress; + Symbols = symbols; + } + } + + private List _images; + + private int _loaded; + + public HleProcessDebugger(KProcess owner) + { + _owner = owner; + + _images = new List(); + } + + public void PrintGuestStackTrace(CpuThreadState threadState) + { + EnsureLoaded(); + + StringBuilder trace = new StringBuilder(); + + trace.AppendLine("Guest stack trace:"); + + void AppendTrace(long address) + { + Image image = GetImage(address, out int imageIndex); + + if (image == null || !TryGetSubName(image, address, out string subName)) + { + subName = $"Sub{address:x16}"; + } + else if (subName.StartsWith("_Z")) + { + subName = Demangler.Parse(subName); + } + + if (image != null) + { + long offset = address - image.BaseAddress; + + string imageName = GetGuessedNsoNameFromIndex(imageIndex); + + string imageNameAndOffset = $"[{_owner.Name}] {imageName}:0x{offset:x8}"; + + trace.AppendLine($" {imageNameAndOffset} {subName}"); + } + else + { + trace.AppendLine($" [{_owner.Name}] ??? {subName}"); + } + } + + long framePointer = (long)threadState.X29; + + while (framePointer != 0) + { + if ((framePointer & 7) != 0 || + !_owner.CpuMemory.IsMapped(framePointer) || + !_owner.CpuMemory.IsMapped(framePointer + 8)) + { + break; + } + + //Note: This is the return address, we need to subtract one instruction + //worth of bytes to get the branch instruction address. + AppendTrace(_owner.CpuMemory.ReadInt64(framePointer + 8) - 4); + + framePointer = _owner.CpuMemory.ReadInt64(framePointer); + } + + Logger.PrintInfo(LogClass.Cpu, trace.ToString()); + } + + private bool TryGetSubName(Image image, long address, out string name) + { + address -= image.BaseAddress; + + int left = 0; + int right = image.Symbols.Length - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + ElfSymbol symbol = image.Symbols[middle]; + + long endAddr = symbol.Value + symbol.Size; + + if ((ulong)address >= (ulong)symbol.Value && (ulong)address < (ulong)endAddr) + { + name = symbol.Name; + + return true; + } + + if ((ulong)address < (ulong)symbol.Value) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + name = null; + + return false; + } + + private Image GetImage(long address, out int index) + { + lock (_images) + { + for (index = _images.Count - 1; index >= 0; index--) + { + if ((ulong)address >= (ulong)_images[index].BaseAddress) + { + return _images[index]; + } + } + } + + return null; + } + + private string GetGuessedNsoNameFromIndex(int index) + { + if ((uint)index > 11) + { + return "???"; + } + + if (index == 0) + { + return "rtld"; + } + else if (index == 1) + { + return "main"; + } + else if (index == GetImagesCount() - 1) + { + return "sdk"; + } + else + { + return "subsdk" + (index - 2); + } + } + + private int GetImagesCount() + { + lock (_images) + { + return _images.Count; + } + } + + private void EnsureLoaded() + { + if (Interlocked.CompareExchange(ref _loaded, 1, 0) == 0) + { + ScanMemoryForTextSegments(); + } + } + + private void ScanMemoryForTextSegments() + { + ulong oldAddress = 0; + ulong address = 0; + + while (address >= oldAddress) + { + KMemoryInfo info = _owner.MemoryManager.QueryMemory(address); + + if (info.State == MemoryState.Reserved) + { + break; + } + + if (info.State == MemoryState.CodeStatic && info.Permission == MemoryPermission.ReadAndExecute) + { + LoadMod0Symbols(_owner.CpuMemory, (long)info.Address); + } + + oldAddress = address; + + address = info.Address + info.Size; + } + } + + private void LoadMod0Symbols(MemoryManager memory, long textOffset) + { + long mod0Offset = textOffset + memory.ReadUInt32(textOffset + 4); + + if (mod0Offset < textOffset || !memory.IsMapped(mod0Offset) || (mod0Offset & 3) != 0) + { + return; + } + + Dictionary dynamic = new Dictionary(); + + int mod0Magic = memory.ReadInt32(mod0Offset + 0x0); + + if (mod0Magic != Mod0) + { + return; + } + + long dynamicOffset = memory.ReadInt32(mod0Offset + 0x4) + mod0Offset; + long bssStartOffset = memory.ReadInt32(mod0Offset + 0x8) + mod0Offset; + long bssEndOffset = memory.ReadInt32(mod0Offset + 0xc) + mod0Offset; + long ehHdrStartOffset = memory.ReadInt32(mod0Offset + 0x10) + mod0Offset; + long ehHdrEndOffset = memory.ReadInt32(mod0Offset + 0x14) + mod0Offset; + long modObjOffset = memory.ReadInt32(mod0Offset + 0x18) + mod0Offset; + + while (true) + { + long tagVal = memory.ReadInt64(dynamicOffset + 0); + long value = memory.ReadInt64(dynamicOffset + 8); + + dynamicOffset += 0x10; + + ElfDynamicTag tag = (ElfDynamicTag)tagVal; + + if (tag == ElfDynamicTag.DT_NULL) + { + break; + } + + dynamic[tag] = value; + } + + if (!dynamic.TryGetValue(ElfDynamicTag.DT_STRTAB, out long strTab) || + !dynamic.TryGetValue(ElfDynamicTag.DT_SYMTAB, out long symTab) || + !dynamic.TryGetValue(ElfDynamicTag.DT_SYMENT, out long symEntSize)) + { + return; + } + + long strTblAddr = textOffset + strTab; + long symTblAddr = textOffset + symTab; + + List symbols = new List(); + + while ((ulong)symTblAddr < (ulong)strTblAddr) + { + ElfSymbol sym = GetSymbol(memory, symTblAddr, strTblAddr); + + symbols.Add(sym); + + symTblAddr += symEntSize; + } + + lock (_images) + { + _images.Add(new Image(textOffset, symbols.OrderBy(x => x.Value).ToArray())); + } + } + + private ElfSymbol GetSymbol(MemoryManager memory, long address, long strTblAddr) + { + int nameIndex = memory.ReadInt32(address + 0); + int info = memory.ReadByte (address + 4); + int other = memory.ReadByte (address + 5); + int shIdx = memory.ReadInt16(address + 6); + long value = memory.ReadInt64(address + 8); + long size = memory.ReadInt64(address + 16); + + string name = string.Empty; + + for (int chr; (chr = memory.ReadByte(strTblAddr + nameIndex++)) != 0;) + { + name += (char)chr; + } + + return new ElfSymbol(name, info, other, shIdx, value, size); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs b/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs new file mode 100644 index 00000000..0392b930 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs @@ -0,0 +1,83 @@ +using Ryujinx.Common; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KContextIdManager + { + private const int IdMasksCount = 8; + + private int[] _idMasks; + + private int _nextFreeBitHint; + + public KContextIdManager() + { + _idMasks = new int[IdMasksCount]; + } + + public int GetId() + { + lock (_idMasks) + { + int id = 0; + + if (!TestBit(_nextFreeBitHint)) + { + id = _nextFreeBitHint; + } + else + { + for (int index = 0; index < IdMasksCount; index++) + { + int mask = _idMasks[index]; + + int firstFreeBit = BitUtils.CountLeadingZeros32((mask + 1) & ~mask); + + if (firstFreeBit < 32) + { + int baseBit = index * 32 + 31; + + id = baseBit - firstFreeBit; + + break; + } + else if (index == IdMasksCount - 1) + { + throw new InvalidOperationException("Maximum number of Ids reached!"); + } + } + } + + _nextFreeBitHint = id + 1; + + SetBit(id); + + return id; + } + } + + public void PutId(int id) + { + lock (_idMasks) + { + ClearBit(id); + } + } + + private bool TestBit(int bit) + { + return (_idMasks[_nextFreeBitHint / 32] & (1 << (_nextFreeBitHint & 31))) != 0; + } + + private void SetBit(int bit) + { + _idMasks[_nextFreeBitHint / 32] |= (1 << (_nextFreeBitHint & 31)); + } + + private void ClearBit(int bit) + { + _idMasks[_nextFreeBitHint / 32] &= ~(1 << (_nextFreeBitHint & 31)); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs b/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs new file mode 100644 index 00000000..87137d0f --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KHandleEntry + { + public KHandleEntry Next { get; set; } + + public int Index { get; private set; } + + public ushort HandleId { get; set; } + public object Obj { get; set; } + + public KHandleEntry(int index) + { + Index = index; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs b/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs new file mode 100644 index 00000000..413edf94 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs @@ -0,0 +1,209 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KHandleTable + { + private const int SelfThreadHandle = (0x1ffff << 15) | 0; + private const int SelfProcessHandle = (0x1ffff << 15) | 1; + + private Horizon _system; + + private KHandleEntry[] _table; + + private KHandleEntry _tableHead; + private KHandleEntry _nextFreeEntry; + + private int _activeSlotsCount; + + private int _size; + + private ushort _idCounter; + + public KHandleTable(Horizon system) + { + _system = system; + } + + public KernelResult Initialize(int size) + { + if ((uint)size > 1024) + { + return KernelResult.OutOfMemory; + } + + if (size < 1) + { + size = 1024; + } + + _size = size; + + _idCounter = 1; + + _table = new KHandleEntry[size]; + + _tableHead = new KHandleEntry(0); + + KHandleEntry entry = _tableHead; + + for (int index = 0; index < size; index++) + { + _table[index] = entry; + + entry.Next = new KHandleEntry(index + 1); + + entry = entry.Next; + } + + _table[size - 1].Next = null; + + _nextFreeEntry = _tableHead; + + return KernelResult.Success; + } + + public KernelResult GenerateHandle(object obj, out int handle) + { + handle = 0; + + lock (_table) + { + if (_activeSlotsCount >= _size) + { + return KernelResult.HandleTableFull; + } + + KHandleEntry entry = _nextFreeEntry; + + _nextFreeEntry = entry.Next; + + entry.Obj = obj; + entry.HandleId = _idCounter; + + _activeSlotsCount++; + + handle = (int)((_idCounter << 15) & 0xffff8000) | entry.Index; + + if ((short)(_idCounter + 1) >= 0) + { + _idCounter++; + } + else + { + _idCounter = 1; + } + } + + return KernelResult.Success; + } + + public bool CloseHandle(int handle) + { + if ((handle >> 30) != 0 || + handle == SelfThreadHandle || + handle == SelfProcessHandle) + { + return false; + } + + int index = (handle >> 0) & 0x7fff; + int handleId = (handle >> 15); + + bool result = false; + + lock (_table) + { + if (handleId != 0 && index < _size) + { + KHandleEntry entry = _table[index]; + + if (entry.Obj != null && entry.HandleId == handleId) + { + entry.Obj = null; + entry.Next = _nextFreeEntry; + + _nextFreeEntry = entry; + + _activeSlotsCount--; + + result = true; + } + } + } + + return result; + } + + public T GetObject(int handle) + { + int index = (handle >> 0) & 0x7fff; + int handleId = (handle >> 15); + + lock (_table) + { + if ((handle >> 30) == 0 && handleId != 0) + { + KHandleEntry entry = _table[index]; + + if (entry.HandleId == handleId && entry.Obj is T obj) + { + return obj; + } + } + } + + return default(T); + } + + public KThread GetKThread(int handle) + { + if (handle == SelfThreadHandle) + { + return _system.Scheduler.GetCurrentThread(); + } + else + { + return GetObject(handle); + } + } + + public KProcess GetKProcess(int handle) + { + if (handle == SelfProcessHandle) + { + return _system.Scheduler.GetCurrentProcess(); + } + else + { + return GetObject(handle); + } + } + + public void Destroy() + { + lock (_table) + { + for (int index = 0; index < _size; index++) + { + KHandleEntry entry = _table[index]; + + if (entry.Obj != null) + { + if (entry.Obj is IDisposable disposableObj) + { + disposableObj.Dispose(); + } + + entry.Obj = null; + entry.Next = _nextFreeEntry; + + _nextFreeEntry = entry; + } + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs new file mode 100644 index 00000000..0d77a495 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -0,0 +1,1017 @@ +using ChocolArm64; +using ChocolArm64.Events; +using ChocolArm64.Memory; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.SupervisorCall; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KProcess : KSynchronizationObject + { + public const int KernelVersionMajor = 10; + public const int KernelVersionMinor = 4; + public const int KernelVersionRevision = 0; + + public const int KernelVersionPacked = + (KernelVersionMajor << 19) | + (KernelVersionMinor << 15) | + (KernelVersionRevision << 0); + + public KMemoryManager MemoryManager { get; private set; } + + private SortedDictionary _fullTlsPages; + private SortedDictionary _freeTlsPages; + + public int DefaultCpuCore { get; private set; } + + public bool Debug { get; private set; } + + public KResourceLimit ResourceLimit { get; private set; } + + public ulong PersonalMmHeapPagesCount { get; private set; } + + private ProcessState _state; + + private object _processLock; + private object _threadingLock; + + public KAddressArbiter AddressArbiter { get; private set; } + + public long[] RandomEntropy { get; private set; } + + private bool _signaled; + private bool _useSystemMemBlocks; + + public string Name { get; private set; } + + private int _threadCount; + + public int MmuFlags { get; private set; } + + private MemoryRegion _memRegion; + + public KProcessCapabilities Capabilities { get; private set; } + + public long TitleId { get; private set; } + public long Pid { get; private set; } + + private long _creationTimestamp; + private ulong _entrypoint; + private ulong _imageSize; + private ulong _mainThreadStackSize; + private ulong _memoryUsageCapacity; + private int _category; + + public KHandleTable HandleTable { get; private set; } + + public ulong UserExceptionContextAddress { get; private set; } + + private LinkedList _threads; + + public bool IsPaused { get; private set; } + + public Translator Translator { get; private set; } + + public MemoryManager CpuMemory { get; private set; } + + private SvcHandler _svcHandler; + + public HleProcessDebugger Debugger { get; private set; } + + public KProcess(Horizon system) : base(system) + { + _processLock = new object(); + _threadingLock = new object(); + + CpuMemory = new MemoryManager(system.Device.Memory.RamPointer); + + CpuMemory.InvalidAccess += InvalidAccessHandler; + + AddressArbiter = new KAddressArbiter(system); + + MemoryManager = new KMemoryManager(system, CpuMemory); + + _fullTlsPages = new SortedDictionary(); + _freeTlsPages = new SortedDictionary(); + + Capabilities = new KProcessCapabilities(); + + RandomEntropy = new long[KScheduler.CpuCoresCount]; + + _threads = new LinkedList(); + + Translator = new Translator(); + + Translator.CpuTrace += CpuTraceHandler; + + _svcHandler = new SvcHandler(system.Device, this); + + Debugger = new HleProcessDebugger(this); + } + + public KernelResult InitializeKip( + ProcessCreationInfo creationInfo, + int[] caps, + KPageList pageList, + KResourceLimit resourceLimit, + MemoryRegion memRegion) + { + ResourceLimit = resourceLimit; + _memRegion = memRegion; + + AddressSpaceType addrSpaceType = (AddressSpaceType)((creationInfo.MmuFlags >> 1) & 7); + + bool aslrEnabled = ((creationInfo.MmuFlags >> 5) & 1) != 0; + + ulong codeAddress = creationInfo.CodeAddress; + + ulong codeSize = (ulong)creationInfo.CodePagesCount * KMemoryManager.PageSize; + + KMemoryBlockAllocator memoryBlockAllocator = (MmuFlags & 0x40) != 0 + ? System.LargeMemoryBlockAllocator + : System.SmallMemoryBlockAllocator; + + KernelResult result = MemoryManager.InitializeForProcess( + addrSpaceType, + aslrEnabled, + !aslrEnabled, + memRegion, + codeAddress, + codeSize, + memoryBlockAllocator); + + if (result != KernelResult.Success) + { + return result; + } + + if (!ValidateCodeAddressAndSize(codeAddress, codeSize)) + { + return KernelResult.InvalidMemRange; + } + + result = MemoryManager.MapPages( + codeAddress, + pageList, + MemoryState.CodeStatic, + MemoryPermission.None); + + if (result != KernelResult.Success) + { + return result; + } + + result = Capabilities.InitializeForKernel(caps, MemoryManager); + + if (result != KernelResult.Success) + { + return result; + } + + Pid = System.GetKipId(); + + if (Pid == 0 || (ulong)Pid >= Horizon.InitialProcessId) + { + throw new InvalidOperationException($"Invalid KIP Id {Pid}."); + } + + result = ParseProcessInfo(creationInfo); + + return result; + } + + public KernelResult Initialize( + ProcessCreationInfo creationInfo, + int[] caps, + KResourceLimit resourceLimit, + MemoryRegion memRegion) + { + ResourceLimit = resourceLimit; + _memRegion = memRegion; + + ulong personalMmHeapSize = GetPersonalMmHeapSize((ulong)creationInfo.PersonalMmHeapPagesCount, memRegion); + + ulong codePagesCount = (ulong)creationInfo.CodePagesCount; + + ulong neededSizeForProcess = personalMmHeapSize + codePagesCount * KMemoryManager.PageSize; + + if (neededSizeForProcess != 0 && resourceLimit != null) + { + if (!resourceLimit.Reserve(LimitableResource.Memory, neededSizeForProcess)) + { + return KernelResult.ResLimitExceeded; + } + } + + void CleanUpForError() + { + if (neededSizeForProcess != 0 && resourceLimit != null) + { + resourceLimit.Release(LimitableResource.Memory, neededSizeForProcess); + } + } + + PersonalMmHeapPagesCount = (ulong)creationInfo.PersonalMmHeapPagesCount; + + KMemoryBlockAllocator memoryBlockAllocator; + + if (PersonalMmHeapPagesCount != 0) + { + memoryBlockAllocator = new KMemoryBlockAllocator(PersonalMmHeapPagesCount * KMemoryManager.PageSize); + } + else + { + memoryBlockAllocator = (MmuFlags & 0x40) != 0 + ? System.LargeMemoryBlockAllocator + : System.SmallMemoryBlockAllocator; + } + + AddressSpaceType addrSpaceType = (AddressSpaceType)((creationInfo.MmuFlags >> 1) & 7); + + bool aslrEnabled = ((creationInfo.MmuFlags >> 5) & 1) != 0; + + ulong codeAddress = creationInfo.CodeAddress; + + ulong codeSize = codePagesCount * KMemoryManager.PageSize; + + KernelResult result = MemoryManager.InitializeForProcess( + addrSpaceType, + aslrEnabled, + !aslrEnabled, + memRegion, + codeAddress, + codeSize, + memoryBlockAllocator); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + if (!ValidateCodeAddressAndSize(codeAddress, codeSize)) + { + CleanUpForError(); + + return KernelResult.InvalidMemRange; + } + + result = MemoryManager.MapNewProcessCode( + codeAddress, + codePagesCount, + MemoryState.CodeStatic, + MemoryPermission.None); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + result = Capabilities.InitializeForUser(caps, MemoryManager); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + Pid = System.GetProcessId(); + + if (Pid == -1 || (ulong)Pid < Horizon.InitialProcessId) + { + throw new InvalidOperationException($"Invalid Process Id {Pid}."); + } + + result = ParseProcessInfo(creationInfo); + + if (result != KernelResult.Success) + { + CleanUpForError(); + } + + return result; + } + + private bool ValidateCodeAddressAndSize(ulong address, ulong size) + { + ulong codeRegionStart; + ulong codeRegionSize; + + switch (MemoryManager.AddrSpaceWidth) + { + case 32: + codeRegionStart = 0x200000; + codeRegionSize = 0x3fe00000; + break; + + case 36: + codeRegionStart = 0x8000000; + codeRegionSize = 0x78000000; + break; + + case 39: + codeRegionStart = 0x8000000; + codeRegionSize = 0x7ff8000000; + break; + + default: throw new InvalidOperationException("Invalid address space width on memory manager."); + } + + ulong endAddr = address + size; + + ulong codeRegionEnd = codeRegionStart + codeRegionSize; + + if (endAddr <= address || + endAddr - 1 > codeRegionEnd - 1) + { + return false; + } + + if (MemoryManager.InsideHeapRegion (address, size) || + MemoryManager.InsideAliasRegion(address, size)) + { + return false; + } + + return true; + } + + private KernelResult ParseProcessInfo(ProcessCreationInfo creationInfo) + { + //Ensure that the current kernel version is equal or above to the minimum required. + uint requiredKernelVersionMajor = (uint)Capabilities.KernelReleaseVersion >> 19; + uint requiredKernelVersionMinor = ((uint)Capabilities.KernelReleaseVersion >> 15) & 0xf; + + if (System.EnableVersionChecks) + { + if (requiredKernelVersionMajor > KernelVersionMajor) + { + return KernelResult.InvalidCombination; + } + + if (requiredKernelVersionMajor != KernelVersionMajor && requiredKernelVersionMajor < 3) + { + return KernelResult.InvalidCombination; + } + + if (requiredKernelVersionMinor > KernelVersionMinor) + { + return KernelResult.InvalidCombination; + } + } + + KernelResult result = AllocateThreadLocalStorage(out ulong userExceptionContextAddress); + + if (result != KernelResult.Success) + { + return result; + } + + UserExceptionContextAddress = userExceptionContextAddress; + + MemoryHelper.FillWithZeros(CpuMemory, (long)userExceptionContextAddress, KTlsPageInfo.TlsEntrySize); + + Name = creationInfo.Name; + + _state = ProcessState.Created; + + _creationTimestamp = PerformanceCounter.ElapsedMilliseconds; + + MmuFlags = creationInfo.MmuFlags; + _category = creationInfo.Category; + TitleId = creationInfo.TitleId; + _entrypoint = creationInfo.CodeAddress; + _imageSize = (ulong)creationInfo.CodePagesCount * KMemoryManager.PageSize; + + _useSystemMemBlocks = ((MmuFlags >> 6) & 1) != 0; + + switch ((AddressSpaceType)((MmuFlags >> 1) & 7)) + { + case AddressSpaceType.Addr32Bits: + case AddressSpaceType.Addr36Bits: + case AddressSpaceType.Addr39Bits: + _memoryUsageCapacity = MemoryManager.HeapRegionEnd - + MemoryManager.HeapRegionStart; + break; + + case AddressSpaceType.Addr32BitsNoMap: + _memoryUsageCapacity = MemoryManager.HeapRegionEnd - + MemoryManager.HeapRegionStart + + MemoryManager.AliasRegionEnd - + MemoryManager.AliasRegionStart; + break; + + default: throw new InvalidOperationException($"Invalid MMU flags value 0x{MmuFlags:x2}."); + } + + GenerateRandomEntropy(); + + return KernelResult.Success; + } + + public KernelResult AllocateThreadLocalStorage(out ulong address) + { + System.CriticalSection.Enter(); + + KernelResult result; + + if (_freeTlsPages.Count > 0) + { + //If we have free TLS pages available, just use the first one. + KTlsPageInfo pageInfo = _freeTlsPages.Values.First(); + + if (!pageInfo.TryGetFreePage(out address)) + { + throw new InvalidOperationException("Unexpected failure getting free TLS page!"); + } + + if (pageInfo.IsFull()) + { + _freeTlsPages.Remove(pageInfo.PageAddr); + + _fullTlsPages.Add(pageInfo.PageAddr, pageInfo); + } + + result = KernelResult.Success; + } + else + { + //Otherwise, we need to create a new one. + result = AllocateTlsPage(out KTlsPageInfo pageInfo); + + if (result == KernelResult.Success) + { + if (!pageInfo.TryGetFreePage(out address)) + { + throw new InvalidOperationException("Unexpected failure getting free TLS page!"); + } + + _freeTlsPages.Add(pageInfo.PageAddr, pageInfo); + } + else + { + address = 0; + } + } + + System.CriticalSection.Leave(); + + return result; + } + + private KernelResult AllocateTlsPage(out KTlsPageInfo pageInfo) + { + pageInfo = default(KTlsPageInfo); + + if (!System.UserSlabHeapPages.TryGetItem(out ulong tlsPagePa)) + { + return KernelResult.OutOfMemory; + } + + ulong regionStart = MemoryManager.TlsIoRegionStart; + ulong regionSize = MemoryManager.TlsIoRegionEnd - regionStart; + + ulong regionPagesCount = regionSize / KMemoryManager.PageSize; + + KernelResult result = MemoryManager.AllocateOrMapPa( + 1, + KMemoryManager.PageSize, + tlsPagePa, + true, + regionStart, + regionPagesCount, + MemoryState.ThreadLocal, + MemoryPermission.ReadAndWrite, + out ulong tlsPageVa); + + if (result != KernelResult.Success) + { + System.UserSlabHeapPages.Free(tlsPagePa); + } + else + { + pageInfo = new KTlsPageInfo(tlsPageVa); + + MemoryHelper.FillWithZeros(CpuMemory, (long)tlsPageVa, KMemoryManager.PageSize); + } + + return result; + } + + public KernelResult FreeThreadLocalStorage(ulong tlsSlotAddr) + { + ulong tlsPageAddr = BitUtils.AlignDown(tlsSlotAddr, KMemoryManager.PageSize); + + System.CriticalSection.Enter(); + + KernelResult result = KernelResult.Success; + + KTlsPageInfo pageInfo = null; + + if (_fullTlsPages.TryGetValue(tlsPageAddr, out pageInfo)) + { + //TLS page was full, free slot and move to free pages tree. + _fullTlsPages.Remove(tlsPageAddr); + + _freeTlsPages.Add(tlsPageAddr, pageInfo); + } + else if (!_freeTlsPages.TryGetValue(tlsPageAddr, out pageInfo)) + { + result = KernelResult.InvalidAddress; + } + + if (pageInfo != null) + { + pageInfo.FreeTlsSlot(tlsSlotAddr); + + if (pageInfo.IsEmpty()) + { + //TLS page is now empty, we should ensure it is removed + //from all trees, and free the memory it was using. + _freeTlsPages.Remove(tlsPageAddr); + + System.CriticalSection.Leave(); + + FreeTlsPage(pageInfo); + + return KernelResult.Success; + } + } + + System.CriticalSection.Leave(); + + return result; + } + + private KernelResult FreeTlsPage(KTlsPageInfo pageInfo) + { + KernelResult result = MemoryManager.ConvertVaToPa(pageInfo.PageAddr, out ulong tlsPagePa); + + if (result != KernelResult.Success) + { + throw new InvalidOperationException("Unexpected failure translating virtual address to physical."); + } + + result = MemoryManager.UnmapForKernel(pageInfo.PageAddr, 1, MemoryState.ThreadLocal); + + if (result == KernelResult.Success) + { + System.UserSlabHeapPages.Free(tlsPagePa); + } + + return result; + } + + private void GenerateRandomEntropy() + { + //TODO. + } + + public KernelResult Start(int mainThreadPriority, ulong stackSize) + { + lock (_processLock) + { + if (_state > ProcessState.CreatedAttached) + { + return KernelResult.InvalidState; + } + + if (ResourceLimit != null && !ResourceLimit.Reserve(LimitableResource.Thread, 1)) + { + return KernelResult.ResLimitExceeded; + } + + KResourceLimit threadResourceLimit = ResourceLimit; + KResourceLimit memoryResourceLimit = null; + + if (_mainThreadStackSize != 0) + { + throw new InvalidOperationException("Trying to start a process with a invalid state!"); + } + + ulong stackSizeRounded = BitUtils.AlignUp(stackSize, KMemoryManager.PageSize); + + ulong neededSize = stackSizeRounded + _imageSize; + + //Check if the needed size for the code and the stack will fit on the + //memory usage capacity of this Process. Also check for possible overflow + //on the above addition. + if (neededSize > _memoryUsageCapacity || + neededSize < stackSizeRounded) + { + threadResourceLimit?.Release(LimitableResource.Thread, 1); + + return KernelResult.OutOfMemory; + } + + if (stackSizeRounded != 0 && ResourceLimit != null) + { + memoryResourceLimit = ResourceLimit; + + if (!memoryResourceLimit.Reserve(LimitableResource.Memory, stackSizeRounded)) + { + threadResourceLimit?.Release(LimitableResource.Thread, 1); + + return KernelResult.ResLimitExceeded; + } + } + + KernelResult result; + + KThread mainThread = null; + + ulong stackTop = 0; + + void CleanUpForError() + { + mainThread?.Terminate(); + HandleTable.Destroy(); + + if (_mainThreadStackSize != 0) + { + ulong stackBottom = stackTop - _mainThreadStackSize; + + ulong stackPagesCount = _mainThreadStackSize / KMemoryManager.PageSize; + + MemoryManager.UnmapForKernel(stackBottom, stackPagesCount, MemoryState.Stack); + } + + memoryResourceLimit?.Release(LimitableResource.Memory, stackSizeRounded); + threadResourceLimit?.Release(LimitableResource.Thread, 1); + } + + if (stackSizeRounded != 0) + { + ulong stackPagesCount = stackSizeRounded / KMemoryManager.PageSize; + + ulong regionStart = MemoryManager.StackRegionStart; + ulong regionSize = MemoryManager.StackRegionEnd - regionStart; + + ulong regionPagesCount = regionSize / KMemoryManager.PageSize; + + result = MemoryManager.AllocateOrMapPa( + stackPagesCount, + KMemoryManager.PageSize, + 0, + false, + regionStart, + regionPagesCount, + MemoryState.Stack, + MemoryPermission.ReadAndWrite, + out ulong stackBottom); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + _mainThreadStackSize += stackSizeRounded; + + stackTop = stackBottom + stackSizeRounded; + } + + ulong heapCapacity = _memoryUsageCapacity - _mainThreadStackSize - _imageSize; + + result = MemoryManager.SetHeapCapacity(heapCapacity); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + HandleTable = new KHandleTable(System); + + result = HandleTable.Initialize(Capabilities.HandleTableSize); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + mainThread = new KThread(System); + + result = mainThread.Initialize( + _entrypoint, + 0, + stackTop, + mainThreadPriority, + DefaultCpuCore, + this); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + result = HandleTable.GenerateHandle(mainThread, out int mainThreadHandle); + + if (result != KernelResult.Success) + { + CleanUpForError(); + + return result; + } + + mainThread.SetEntryArguments(0, mainThreadHandle); + + ProcessState oldState = _state; + ProcessState newState = _state != ProcessState.Created + ? ProcessState.Attached + : ProcessState.Started; + + SetState(newState); + + //TODO: We can't call KThread.Start from a non-guest thread. + //We will need to make some changes to allow the creation of + //dummy threads that will be used to initialize the current + //thread on KCoreContext so that GetCurrentThread doesn't fail. + /* Result = MainThread.Start(); + + if (Result != KernelResult.Success) + { + SetState(OldState); + + CleanUpForError(); + } */ + + mainThread.Reschedule(ThreadSchedState.Running); + + return result; + } + } + + private void SetState(ProcessState newState) + { + if (_state != newState) + { + _state = newState; + _signaled = true; + + Signal(); + } + } + + public KernelResult InitializeThread( + KThread thread, + ulong entrypoint, + ulong argsPtr, + ulong stackTop, + int priority, + int cpuCore) + { + lock (_processLock) + { + return thread.Initialize(entrypoint, argsPtr, stackTop, priority, cpuCore, this); + } + } + + public void SubscribeThreadEventHandlers(CpuThread context) + { + context.ThreadState.Interrupt += InterruptHandler; + context.ThreadState.SvcCall += _svcHandler.SvcCall; + } + + private void InterruptHandler(object sender, EventArgs e) + { + System.Scheduler.ContextSwitch(); + } + + public void IncrementThreadCount() + { + Interlocked.Increment(ref _threadCount); + + System.ThreadCounter.AddCount(); + } + + public void DecrementThreadCountAndTerminateIfZero() + { + System.ThreadCounter.Signal(); + + if (Interlocked.Decrement(ref _threadCount) == 0) + { + Terminate(); + } + } + + public ulong GetMemoryCapacity() + { + ulong totalCapacity = (ulong)ResourceLimit.GetRemainingValue(LimitableResource.Memory); + + totalCapacity += MemoryManager.GetTotalHeapSize(); + + totalCapacity += GetPersonalMmHeapSize(); + + totalCapacity += _imageSize + _mainThreadStackSize; + + if (totalCapacity <= _memoryUsageCapacity) + { + return totalCapacity; + } + + return _memoryUsageCapacity; + } + + public ulong GetMemoryUsage() + { + return _imageSize + _mainThreadStackSize + MemoryManager.GetTotalHeapSize() + GetPersonalMmHeapSize(); + } + + public ulong GetMemoryCapacityWithoutPersonalMmHeap() + { + return GetMemoryCapacity() - GetPersonalMmHeapSize(); + } + + public ulong GetMemoryUsageWithoutPersonalMmHeap() + { + return GetMemoryUsage() - GetPersonalMmHeapSize(); + } + + private ulong GetPersonalMmHeapSize() + { + return GetPersonalMmHeapSize(PersonalMmHeapPagesCount, _memRegion); + } + + private static ulong GetPersonalMmHeapSize(ulong personalMmHeapPagesCount, MemoryRegion memRegion) + { + if (memRegion == MemoryRegion.Applet) + { + return 0; + } + + return personalMmHeapPagesCount * KMemoryManager.PageSize; + } + + public void AddThread(KThread thread) + { + lock (_threadingLock) + { + thread.ProcessListNode = _threads.AddLast(thread); + } + } + + public void RemoveThread(KThread thread) + { + lock (_threadingLock) + { + _threads.Remove(thread.ProcessListNode); + } + } + + public bool IsCpuCoreAllowed(int core) + { + return (Capabilities.AllowedCpuCoresMask & (1L << core)) != 0; + } + + public bool IsPriorityAllowed(int priority) + { + return (Capabilities.AllowedThreadPriosMask & (1L << priority)) != 0; + } + + public override bool IsSignaled() + { + return _signaled; + } + + public KernelResult Terminate() + { + KernelResult result; + + bool shallTerminate = false; + + System.CriticalSection.Enter(); + + lock (_processLock) + { + if (_state >= ProcessState.Started) + { + if (_state == ProcessState.Started || + _state == ProcessState.Crashed || + _state == ProcessState.Attached || + _state == ProcessState.DebugSuspended) + { + SetState(ProcessState.Exiting); + + shallTerminate = true; + } + + result = KernelResult.Success; + } + else + { + result = KernelResult.InvalidState; + } + } + + System.CriticalSection.Leave(); + + if (shallTerminate) + { + //UnpauseAndTerminateAllThreadsExcept(System.Scheduler.GetCurrentThread()); + + HandleTable.Destroy(); + + SignalExitForDebugEvent(); + SignalExit(); + } + + return result; + } + + private void UnpauseAndTerminateAllThreadsExcept(KThread thread) + { + //TODO. + } + + private void SignalExitForDebugEvent() + { + //TODO: Debug events. + } + + private void SignalExit() + { + if (ResourceLimit != null) + { + ResourceLimit.Release(LimitableResource.Memory, GetMemoryUsage()); + } + + System.CriticalSection.Enter(); + + SetState(ProcessState.Exited); + + System.CriticalSection.Leave(); + } + + public KernelResult ClearIfNotExited() + { + KernelResult result; + + System.CriticalSection.Enter(); + + lock (_processLock) + { + if (_state != ProcessState.Exited && _signaled) + { + _signaled = false; + + result = KernelResult.Success; + } + else + { + result = KernelResult.InvalidState; + } + } + + System.CriticalSection.Leave(); + + return result; + } + + public void StopAllThreads() + { + lock (_threadingLock) + { + foreach (KThread thread in _threads) + { + thread.Context.StopExecution(); + + System.Scheduler.CoreManager.Set(thread.Context.Work); + } + } + } + + private void InvalidAccessHandler(object sender, MemoryAccessEventArgs e) + { + PrintCurrentThreadStackTrace(); + } + + public void PrintCurrentThreadStackTrace() + { + System.Scheduler.GetCurrentThread().PrintGuestStackTrace(); + } + + private void CpuTraceHandler(object sender, CpuTraceEventArgs e) + { + Logger.PrintInfo(LogClass.Cpu, $"Executing at 0x{e.Position:X16}."); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs new file mode 100644 index 00000000..033f0a2c --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs @@ -0,0 +1,314 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KProcessCapabilities + { + public byte[] SvcAccessMask { get; private set; } + public byte[] IrqAccessMask { get; private set; } + + public long AllowedCpuCoresMask { get; private set; } + public long AllowedThreadPriosMask { get; private set; } + + public int DebuggingFlags { get; private set; } + public int HandleTableSize { get; private set; } + public int KernelReleaseVersion { get; private set; } + public int ApplicationType { get; private set; } + + public KProcessCapabilities() + { + SvcAccessMask = new byte[0x10]; + IrqAccessMask = new byte[0x80]; + } + + public KernelResult InitializeForKernel(int[] caps, KMemoryManager memoryManager) + { + AllowedCpuCoresMask = 0xf; + AllowedThreadPriosMask = -1; + DebuggingFlags &= ~3; + KernelReleaseVersion = KProcess.KernelVersionPacked; + + return Parse(caps, memoryManager); + } + + public KernelResult InitializeForUser(int[] caps, KMemoryManager memoryManager) + { + return Parse(caps, memoryManager); + } + + private KernelResult Parse(int[] caps, KMemoryManager memoryManager) + { + int mask0 = 0; + int mask1 = 0; + + for (int index = 0; index < caps.Length; index++) + { + int cap = caps[index]; + + if (((cap + 1) & ~cap) != 0x40) + { + KernelResult result = ParseCapability(cap, ref mask0, ref mask1, memoryManager); + + if (result != KernelResult.Success) + { + return result; + } + } + else + { + if ((uint)index + 1 >= caps.Length) + { + return KernelResult.InvalidCombination; + } + + int prevCap = cap; + + cap = caps[++index]; + + if (((cap + 1) & ~cap) != 0x40) + { + return KernelResult.InvalidCombination; + } + + if ((cap & 0x78000000) != 0) + { + return KernelResult.MaximumExceeded; + } + + if ((cap & 0x7ffff80) == 0) + { + return KernelResult.InvalidSize; + } + + long address = ((long)(uint)prevCap << 5) & 0xffffff000; + long size = ((long)(uint)cap << 5) & 0xfffff000; + + if (((ulong)(address + size - 1) >> 36) != 0) + { + return KernelResult.InvalidAddress; + } + + MemoryPermission perm = (prevCap >> 31) != 0 + ? MemoryPermission.Read + : MemoryPermission.ReadAndWrite; + + KernelResult result; + + if ((cap >> 31) != 0) + { + result = memoryManager.MapNormalMemory(address, size, perm); + } + else + { + result = memoryManager.MapIoMemory(address, size, perm); + } + + if (result != KernelResult.Success) + { + return result; + } + } + } + + return KernelResult.Success; + } + + private KernelResult ParseCapability(int cap, ref int mask0, ref int mask1, KMemoryManager memoryManager) + { + int code = (cap + 1) & ~cap; + + if (code == 1) + { + return KernelResult.InvalidCapability; + } + else if (code == 0) + { + return KernelResult.Success; + } + + int codeMask = 1 << (32 - BitUtils.CountLeadingZeros32(code + 1)); + + //Check if the property was already set. + if (((mask0 & codeMask) & 0x1e008) != 0) + { + return KernelResult.InvalidCombination; + } + + mask0 |= codeMask; + + switch (code) + { + case 8: + { + if (AllowedCpuCoresMask != 0 || AllowedThreadPriosMask != 0) + { + return KernelResult.InvalidCapability; + } + + int lowestCpuCore = (cap >> 16) & 0xff; + int highestCpuCore = (cap >> 24) & 0xff; + + if (lowestCpuCore > highestCpuCore) + { + return KernelResult.InvalidCombination; + } + + int highestThreadPrio = (cap >> 4) & 0x3f; + int lowestThreadPrio = (cap >> 10) & 0x3f; + + if (lowestThreadPrio > highestThreadPrio) + { + return KernelResult.InvalidCombination; + } + + if (highestCpuCore >= KScheduler.CpuCoresCount) + { + return KernelResult.InvalidCpuCore; + } + + AllowedCpuCoresMask = GetMaskFromMinMax(lowestCpuCore, highestCpuCore); + AllowedThreadPriosMask = GetMaskFromMinMax(lowestThreadPrio, highestThreadPrio); + + break; + } + + case 0x10: + { + int slot = (cap >> 29) & 7; + + int svcSlotMask = 1 << slot; + + if ((mask1 & svcSlotMask) != 0) + { + return KernelResult.InvalidCombination; + } + + mask1 |= svcSlotMask; + + int svcMask = (cap >> 5) & 0xffffff; + + int baseSvc = slot * 24; + + for (int index = 0; index < 24; index++) + { + if (((svcMask >> index) & 1) == 0) + { + continue; + } + + int svcId = baseSvc + index; + + if (svcId > 0x7f) + { + return KernelResult.MaximumExceeded; + } + + SvcAccessMask[svcId / 8] |= (byte)(1 << (svcId & 7)); + } + + break; + } + + case 0x80: + { + long address = ((long)(uint)cap << 4) & 0xffffff000; + + memoryManager.MapIoMemory(address, KMemoryManager.PageSize, MemoryPermission.ReadAndWrite); + + break; + } + + case 0x800: + { + //TODO: GIC distributor check. + int irq0 = (cap >> 12) & 0x3ff; + int irq1 = (cap >> 22) & 0x3ff; + + if (irq0 != 0x3ff) + { + IrqAccessMask[irq0 / 8] |= (byte)(1 << (irq0 & 7)); + } + + if (irq1 != 0x3ff) + { + IrqAccessMask[irq1 / 8] |= (byte)(1 << (irq1 & 7)); + } + + break; + } + + case 0x2000: + { + int applicationType = cap >> 14; + + if ((uint)applicationType > 7) + { + return KernelResult.ReservedValue; + } + + ApplicationType = applicationType; + + break; + } + + case 0x4000: + { + //Note: This check is bugged on kernel too, we are just replicating the bug here. + if ((KernelReleaseVersion >> 17) != 0 || cap < 0x80000) + { + return KernelResult.ReservedValue; + } + + KernelReleaseVersion = cap; + + break; + } + + case 0x8000: + { + int handleTableSize = cap >> 26; + + if ((uint)handleTableSize > 0x3ff) + { + return KernelResult.ReservedValue; + } + + HandleTableSize = handleTableSize; + + break; + } + + case 0x10000: + { + int debuggingFlags = cap >> 19; + + if ((uint)debuggingFlags > 3) + { + return KernelResult.ReservedValue; + } + + DebuggingFlags &= ~3; + DebuggingFlags |= debuggingFlags; + + break; + } + + default: return KernelResult.InvalidCapability; + } + + return KernelResult.Success; + } + + private static long GetMaskFromMinMax(int min, int max) + { + int range = max - min + 1; + + long mask = (1L << range) - 1; + + return mask << min; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs b/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs new file mode 100644 index 00000000..5ce5a299 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs @@ -0,0 +1,75 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KTlsPageInfo + { + public const int TlsEntrySize = 0x200; + + public ulong PageAddr { get; private set; } + + private bool[] _isSlotFree; + + public KTlsPageInfo(ulong pageAddress) + { + PageAddr = pageAddress; + + _isSlotFree = new bool[KMemoryManager.PageSize / TlsEntrySize]; + + for (int index = 0; index < _isSlotFree.Length; index++) + { + _isSlotFree[index] = true; + } + } + + public bool TryGetFreePage(out ulong address) + { + address = PageAddr; + + for (int index = 0; index < _isSlotFree.Length; index++) + { + if (_isSlotFree[index]) + { + _isSlotFree[index] = false; + + return true; + } + + address += TlsEntrySize; + } + + address = 0; + + return false; + } + + public bool IsFull() + { + bool hasFree = false; + + for (int index = 0; index < _isSlotFree.Length; index++) + { + hasFree |= _isSlotFree[index]; + } + + return !hasFree; + } + + public bool IsEmpty() + { + bool allFree = true; + + for (int index = 0; index < _isSlotFree.Length; index++) + { + allFree &= _isSlotFree[index]; + } + + return allFree; + } + + public void FreeTlsSlot(ulong address) + { + _isSlotFree[(address - PageAddr) / TlsEntrySize] = true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs b/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs new file mode 100644 index 00000000..03174e5b --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs @@ -0,0 +1,61 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KTlsPageManager + { + private const int TlsEntrySize = 0x200; + + private long _pagePosition; + + private int _usedSlots; + + private bool[] _slots; + + public bool IsEmpty => _usedSlots == 0; + public bool IsFull => _usedSlots == _slots.Length; + + public KTlsPageManager(long pagePosition) + { + _pagePosition = pagePosition; + + _slots = new bool[KMemoryManager.PageSize / TlsEntrySize]; + } + + public bool TryGetFreeTlsAddr(out long position) + { + position = _pagePosition; + + for (int index = 0; index < _slots.Length; index++) + { + if (!_slots[index]) + { + _slots[index] = true; + + _usedSlots++; + + return true; + } + + position += TlsEntrySize; + } + + position = 0; + + return false; + } + + public void FreeTlsSlot(int slot) + { + if ((uint)slot > _slots.Length) + { + throw new ArgumentOutOfRangeException(nameof(slot)); + } + + _slots[slot] = false; + + _usedSlots--; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs b/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs new file mode 100644 index 00000000..ba9f54bf --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + struct ProcessCreationInfo + { + public string Name { get; private set; } + + public int Category { get; private set; } + public long TitleId { get; private set; } + + public ulong CodeAddress { get; private set; } + public int CodePagesCount { get; private set; } + + public int MmuFlags { get; private set; } + public int ResourceLimitHandle { get; private set; } + public int PersonalMmHeapPagesCount { get; private set; } + + public ProcessCreationInfo( + string name, + int category, + long titleId, + ulong codeAddress, + int codePagesCount, + int mmuFlags, + int resourceLimitHandle, + int personalMmHeapPagesCount) + { + Name = name; + Category = category; + TitleId = titleId; + CodeAddress = codeAddress; + CodePagesCount = codePagesCount; + MmuFlags = mmuFlags; + ResourceLimitHandle = resourceLimitHandle; + PersonalMmHeapPagesCount = personalMmHeapPagesCount; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs b/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs new file mode 100644 index 00000000..5ef3077e --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + enum ProcessState : byte + { + Created = 0, + CreatedAttached = 1, + Started = 2, + Crashed = 3, + Attached = 4, + Exiting = 5, + Exited = 6, + DebugSuspended = 7 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/ProcessCreationInfo.cs b/Ryujinx.HLE/HOS/Kernel/ProcessCreationInfo.cs deleted file mode 100644 index 7b2e8b72..00000000 --- a/Ryujinx.HLE/HOS/Kernel/ProcessCreationInfo.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - struct ProcessCreationInfo - { - public string Name { get; private set; } - - public int Category { get; private set; } - public long TitleId { get; private set; } - - public ulong CodeAddress { get; private set; } - public int CodePagesCount { get; private set; } - - public int MmuFlags { get; private set; } - public int ResourceLimitHandle { get; private set; } - public int PersonalMmHeapPagesCount { get; private set; } - - public ProcessCreationInfo( - string name, - int category, - long titleId, - ulong codeAddress, - int codePagesCount, - int mmuFlags, - int resourceLimitHandle, - int personalMmHeapPagesCount) - { - Name = name; - Category = category; - TitleId = titleId; - CodeAddress = codeAddress; - CodePagesCount = codePagesCount; - MmuFlags = mmuFlags; - ResourceLimitHandle = resourceLimitHandle; - PersonalMmHeapPagesCount = personalMmHeapPagesCount; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/ProcessState.cs b/Ryujinx.HLE/HOS/Kernel/ProcessState.cs deleted file mode 100644 index 98ff4207..00000000 --- a/Ryujinx.HLE/HOS/Kernel/ProcessState.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - enum ProcessState : byte - { - Created = 0, - CreatedAttached = 1, - Started = 2, - Crashed = 3, - Attached = 4, - Exiting = 5, - Exited = 6, - DebugSuspended = 7 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SignalType.cs b/Ryujinx.HLE/HOS/Kernel/SignalType.cs deleted file mode 100644 index 05803151..00000000 --- a/Ryujinx.HLE/HOS/Kernel/SignalType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - enum SignalType - { - Signal = 0, - SignalAndIncrementIfEqual = 1, - SignalAndModifyIfEqual = 2 - } -} diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InvalidSvcException.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InvalidSvcException.cs new file mode 100644 index 00000000..b78a2284 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InvalidSvcException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + public class InvalidSvcException : Exception + { + public InvalidSvcException(string message) : base(message) { } + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs new file mode 100644 index 00000000..08340b06 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcHandler.cs @@ -0,0 +1,61 @@ +using ChocolArm64.Events; +using ChocolArm64.Memory; +using ChocolArm64.State; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + partial class SvcHandler + { + private Switch _device; + private KProcess _process; + private Horizon _system; + private MemoryManager _memory; + + private struct HleIpcMessage + { + public KThread Thread { get; private set; } + public KSession Session { get; private set; } + public IpcMessage Message { get; private set; } + public long MessagePtr { get; private set; } + + public HleIpcMessage( + KThread thread, + KSession session, + IpcMessage message, + long messagePtr) + { + Thread = thread; + Session = session; + Message = message; + MessagePtr = messagePtr; + } + } + + public SvcHandler(Switch device, KProcess process) + { + _device = device; + _process = process; + _system = device.System; + _memory = process.CpuMemory; + } + + public void SvcCall(object sender, InstExceptionEventArgs e) + { + Action svcFunc = SvcTable.GetSvcFunc(e.Id); + + if (svcFunc == null) + { + throw new NotImplementedException($"SVC 0x{e.Id:X4} is not implemented."); + } + + CpuThreadState threadState = (CpuThreadState)sender; + + svcFunc(this, threadState); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs new file mode 100644 index 00000000..e6590522 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs @@ -0,0 +1,394 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + partial class SvcHandler + { + public KernelResult SetHeapSize64(ulong size, out ulong position) + { + return SetHeapSize(size, out position); + } + + private KernelResult SetHeapSize(ulong size, out ulong position) + { + if ((size & 0xfffffffe001fffff) != 0) + { + position = 0; + + return KernelResult.InvalidSize; + } + + return _process.MemoryManager.SetHeapSize(size, out position); + } + + public KernelResult SetMemoryAttribute64( + ulong position, + ulong size, + MemoryAttribute attributeMask, + MemoryAttribute attributeValue) + { + return SetMemoryAttribute(position, size, attributeMask, attributeValue); + } + + private KernelResult SetMemoryAttribute( + ulong position, + ulong size, + MemoryAttribute attributeMask, + MemoryAttribute attributeValue) + { + if (!PageAligned(position)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + MemoryAttribute attributes = attributeMask | attributeValue; + + if (attributes != attributeMask || + (attributes | MemoryAttribute.Uncached) != MemoryAttribute.Uncached) + { + return KernelResult.InvalidCombination; + } + + KernelResult result = _process.MemoryManager.SetMemoryAttribute( + position, + size, + attributeMask, + attributeValue); + + if (result == KernelResult.Success) + { + _memory.StopObservingRegion((long)position, (long)size); + } + + return result; + } + + public KernelResult MapMemory64(ulong dst, ulong src, ulong size) + { + return MapMemory(dst, src, size); + } + + private KernelResult MapMemory(ulong dst, ulong src, ulong size) + { + if (!PageAligned(src | dst)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (src + size <= src || dst + size <= dst) + { + return KernelResult.InvalidMemState; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if (!currentProcess.MemoryManager.InsideAddrSpace(src, size)) + { + return KernelResult.InvalidMemState; + } + + if (currentProcess.MemoryManager.OutsideStackRegion(dst, size) || + currentProcess.MemoryManager.InsideHeapRegion (dst, size) || + currentProcess.MemoryManager.InsideAliasRegion (dst, size)) + { + return KernelResult.InvalidMemRange; + } + + return _process.MemoryManager.Map(dst, src, size); + } + + public KernelResult UnmapMemory64(ulong dst, ulong src, ulong size) + { + return UnmapMemory(dst, src, size); + } + + private KernelResult UnmapMemory(ulong dst, ulong src, ulong size) + { + if (!PageAligned(src | dst)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (src + size <= src || dst + size <= dst) + { + return KernelResult.InvalidMemState; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if (!currentProcess.MemoryManager.InsideAddrSpace(src, size)) + { + return KernelResult.InvalidMemState; + } + + if (currentProcess.MemoryManager.OutsideStackRegion(dst, size) || + currentProcess.MemoryManager.InsideHeapRegion (dst, size) || + currentProcess.MemoryManager.InsideAliasRegion (dst, size)) + { + return KernelResult.InvalidMemRange; + } + + return _process.MemoryManager.Unmap(dst, src, size); + } + + public KernelResult QueryMemory64(ulong infoPtr, ulong x1, ulong position) + { + return QueryMemory(infoPtr, position); + } + + private KernelResult QueryMemory(ulong infoPtr, ulong position) + { + KMemoryInfo blkInfo = _process.MemoryManager.QueryMemory(position); + + _memory.WriteUInt64((long)infoPtr + 0x00, blkInfo.Address); + _memory.WriteUInt64((long)infoPtr + 0x08, blkInfo.Size); + _memory.WriteInt32 ((long)infoPtr + 0x10, (int)blkInfo.State & 0xff); + _memory.WriteInt32 ((long)infoPtr + 0x14, (int)blkInfo.Attribute); + _memory.WriteInt32 ((long)infoPtr + 0x18, (int)blkInfo.Permission); + _memory.WriteInt32 ((long)infoPtr + 0x1c, blkInfo.IpcRefCount); + _memory.WriteInt32 ((long)infoPtr + 0x20, blkInfo.DeviceRefCount); + _memory.WriteInt32 ((long)infoPtr + 0x24, 0); + + return KernelResult.Success; + } + + public KernelResult MapSharedMemory64(int handle, ulong address, ulong size, MemoryPermission permission) + { + return MapSharedMemory(handle, address, size, permission); + } + + private KernelResult MapSharedMemory(int handle, ulong address, ulong size, MemoryPermission permission) + { + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemState; + } + + if ((permission | MemoryPermission.Write) != MemoryPermission.ReadAndWrite) + { + return KernelResult.InvalidPermission; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KSharedMemory sharedMemory = currentProcess.HandleTable.GetObject(handle); + + if (sharedMemory == null) + { + return KernelResult.InvalidHandle; + } + + if (currentProcess.MemoryManager.IsInvalidRegion (address, size) || + currentProcess.MemoryManager.InsideHeapRegion (address, size) || + currentProcess.MemoryManager.InsideAliasRegion(address, size)) + { + return KernelResult.InvalidMemRange; + } + + return sharedMemory.MapIntoProcess( + currentProcess.MemoryManager, + address, + size, + currentProcess, + permission); + } + + public KernelResult UnmapSharedMemory64(int handle, ulong address, ulong size) + { + return UnmapSharedMemory(handle, address, size); + } + + private KernelResult UnmapSharedMemory(int handle, ulong address, ulong size) + { + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemState; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KSharedMemory sharedMemory = currentProcess.HandleTable.GetObject(handle); + + if (sharedMemory == null) + { + return KernelResult.InvalidHandle; + } + + if (currentProcess.MemoryManager.IsInvalidRegion (address, size) || + currentProcess.MemoryManager.InsideHeapRegion (address, size) || + currentProcess.MemoryManager.InsideAliasRegion(address, size)) + { + return KernelResult.InvalidMemRange; + } + + return sharedMemory.UnmapFromProcess( + currentProcess.MemoryManager, + address, + size, + currentProcess); + } + + public KernelResult CreateTransferMemory64( + ulong address, + ulong size, + MemoryPermission permission, + out int handle) + { + return CreateTransferMemory(address, size, permission, out handle); + } + + private KernelResult CreateTransferMemory(ulong address, ulong size, MemoryPermission permission, out int handle) + { + handle = 0; + + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemState; + } + + if (permission > MemoryPermission.ReadAndWrite || permission == MemoryPermission.Write) + { + return KernelResult.InvalidPermission; + } + + KernelResult result = _process.MemoryManager.ReserveTransferMemory(address, size, permission); + + if (result != KernelResult.Success) + { + return result; + } + + KTransferMemory transferMemory = new KTransferMemory(address, size); + + return _process.HandleTable.GenerateHandle(transferMemory, out handle); + } + + public KernelResult MapPhysicalMemory64(ulong address, ulong size) + { + return MapPhysicalMemory(address, size); + } + + private KernelResult MapPhysicalMemory(ulong address, ulong size) + { + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemRange; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if ((currentProcess.PersonalMmHeapPagesCount & 0xfffffffffffff) == 0) + { + return KernelResult.InvalidState; + } + + if (!currentProcess.MemoryManager.InsideAddrSpace (address, size) || + currentProcess.MemoryManager.OutsideAliasRegion(address, size)) + { + return KernelResult.InvalidMemRange; + } + + return _process.MemoryManager.MapPhysicalMemory(address, size); + } + + public KernelResult UnmapPhysicalMemory64(ulong address, ulong size) + { + return MapPhysicalMemory(address, size); + } + + private KernelResult UnmapPhysicalMemory(ulong address, ulong size) + { + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemRange; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if ((currentProcess.PersonalMmHeapPagesCount & 0xfffffffffffff) == 0) + { + return KernelResult.InvalidState; + } + + if (!currentProcess.MemoryManager.InsideAddrSpace (address, size) || + currentProcess.MemoryManager.OutsideAliasRegion(address, size)) + { + return KernelResult.InvalidMemRange; + } + + return _process.MemoryManager.UnmapPhysicalMemory(address, size); + } + + private static bool PageAligned(ulong position) + { + return (position & (KMemoryManager.PageSize - 1)) == 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs new file mode 100644 index 00000000..b0563356 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcSystem.cs @@ -0,0 +1,762 @@ +using ChocolArm64.Memory; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + partial class SvcHandler + { + public void ExitProcess64() + { + ExitProcess(); + } + + private void ExitProcess() + { + _system.Scheduler.GetCurrentProcess().Terminate(); + } + + public KernelResult SignalEvent64(int handle) + { + return SignalEvent(handle); + } + + private KernelResult SignalEvent(int handle) + { + KWritableEvent writableEvent = _process.HandleTable.GetObject(handle); + + KernelResult result; + + if (writableEvent != null) + { + writableEvent.Signal(); + + result = KernelResult.Success; + } + else + { + result = KernelResult.InvalidHandle; + } + + return result; + } + + public KernelResult ClearEvent64(int handle) + { + return ClearEvent(handle); + } + + private KernelResult ClearEvent(int handle) + { + KernelResult result; + + KWritableEvent writableEvent = _process.HandleTable.GetObject(handle); + + if (writableEvent == null) + { + KReadableEvent readableEvent = _process.HandleTable.GetObject(handle); + + result = readableEvent?.Clear() ?? KernelResult.InvalidHandle; + } + else + { + result = writableEvent.Clear(); + } + + return result; + } + + public KernelResult CloseHandle64(int handle) + { + return CloseHandle(handle); + } + + private KernelResult CloseHandle(int handle) + { + object obj = _process.HandleTable.GetObject(handle); + + _process.HandleTable.CloseHandle(handle); + + if (obj == null) + { + return KernelResult.InvalidHandle; + } + + if (obj is KSession session) + { + session.Dispose(); + } + else if (obj is KTransferMemory transferMemory) + { + _process.MemoryManager.ResetTransferMemory( + transferMemory.Address, + transferMemory.Size); + } + + return KernelResult.Success; + } + + public KernelResult ResetSignal64(int handle) + { + return ResetSignal(handle); + } + + private KernelResult ResetSignal(int handle) + { + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KReadableEvent readableEvent = currentProcess.HandleTable.GetObject(handle); + + KernelResult result; + + if (readableEvent != null) + { + result = readableEvent.ClearIfSignaled(); + } + else + { + KProcess process = currentProcess.HandleTable.GetKProcess(handle); + + if (process != null) + { + result = process.ClearIfNotExited(); + } + else + { + result = KernelResult.InvalidHandle; + } + } + + return result; + } + + public ulong GetSystemTick64() + { + return _system.Scheduler.GetCurrentThread().Context.ThreadState.CntpctEl0; + } + + public KernelResult ConnectToNamedPort64(ulong namePtr, out int handle) + { + return ConnectToNamedPort(namePtr, out handle); + } + + private KernelResult ConnectToNamedPort(ulong namePtr, out int handle) + { + string name = MemoryHelper.ReadAsciiString(_memory, (long)namePtr, 8); + + //TODO: Validate that app has perms to access the service, and that the service + //actually exists, return error codes otherwise. + KSession session = new KSession(ServiceFactory.MakeService(_system, name), name); + + return _process.HandleTable.GenerateHandle(session, out handle); + } + + public KernelResult SendSyncRequest64(int handle) + { + return SendSyncRequest((ulong)_system.Scheduler.GetCurrentThread().Context.ThreadState.Tpidr, 0x100, handle); + } + + public KernelResult SendSyncRequestWithUserBuffer64(ulong messagePtr, ulong size, int handle) + { + return SendSyncRequest(messagePtr, size, handle); + } + + private KernelResult SendSyncRequest(ulong messagePtr, ulong size, int handle) + { + byte[] messageData = _memory.ReadBytes((long)messagePtr, (long)size); + + KSession session = _process.HandleTable.GetObject(handle); + + if (session != null) + { + _system.CriticalSection.Enter(); + + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.Success; + + currentThread.Reschedule(ThreadSchedState.Paused); + + IpcMessage message = new IpcMessage(messageData, (long)messagePtr); + + ThreadPool.QueueUserWorkItem(ProcessIpcRequest, new HleIpcMessage( + currentThread, + session, + message, + (long)messagePtr)); + + _system.ThreadCounter.AddCount(); + + _system.CriticalSection.Leave(); + + return currentThread.ObjSyncResult; + } + else + { + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid session handle 0x{handle:x8}!"); + + return KernelResult.InvalidHandle; + } + } + + private void ProcessIpcRequest(object state) + { + HleIpcMessage ipcMessage = (HleIpcMessage)state; + + ipcMessage.Thread.ObjSyncResult = IpcHandler.IpcCall( + _device, + _process, + _memory, + ipcMessage.Session, + ipcMessage.Message, + ipcMessage.MessagePtr); + + _system.ThreadCounter.Signal(); + + ipcMessage.Thread.Reschedule(ThreadSchedState.Running); + } + + public KernelResult GetProcessId64(int handle, out long pid) + { + return GetProcessId(handle, out pid); + } + + private KernelResult GetProcessId(int handle, out long pid) + { + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KProcess process = currentProcess.HandleTable.GetKProcess(handle); + + if (process == null) + { + KThread thread = currentProcess.HandleTable.GetKThread(handle); + + if (thread != null) + { + process = thread.Owner; + } + + //TODO: KDebugEvent. + } + + pid = process?.Pid ?? 0; + + return process != null + ? KernelResult.Success + : KernelResult.InvalidHandle; + } + + public void Break64(ulong reason, ulong x1, ulong info) + { + Break(reason); + } + + private void Break(ulong reason) + { + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + if ((reason & (1UL << 31)) == 0) + { + currentThread.PrintGuestStackTrace(); + + throw new GuestBrokeExecutionException(); + } + else + { + Logger.PrintInfo(LogClass.KernelSvc, "Debugger triggered."); + + currentThread.PrintGuestStackTrace(); + } + } + + public void OutputDebugString64(ulong strPtr, ulong size) + { + OutputDebugString(strPtr, size); + } + + private void OutputDebugString(ulong strPtr, ulong size) + { + string str = MemoryHelper.ReadAsciiString(_memory, (long)strPtr, (long)size); + + Logger.PrintWarning(LogClass.KernelSvc, str); + } + + public KernelResult GetInfo64(uint id, int handle, long subId, out long value) + { + return GetInfo(id, handle, subId, out value); + } + + private KernelResult GetInfo(uint id, int handle, long subId, out long value) + { + value = 0; + + switch (id) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 20: + case 21: + case 22: + { + if (subId != 0) + { + return KernelResult.InvalidCombination; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KProcess process = currentProcess.HandleTable.GetKProcess(handle); + + if (process == null) + { + return KernelResult.InvalidHandle; + } + + switch (id) + { + case 0: value = process.Capabilities.AllowedCpuCoresMask; break; + case 1: value = process.Capabilities.AllowedThreadPriosMask; break; + + case 2: value = (long)process.MemoryManager.AliasRegionStart; break; + case 3: value = (long)(process.MemoryManager.AliasRegionEnd - + process.MemoryManager.AliasRegionStart); break; + + case 4: value = (long)process.MemoryManager.HeapRegionStart; break; + case 5: value = (long)(process.MemoryManager.HeapRegionEnd - + process.MemoryManager.HeapRegionStart); break; + + case 6: value = (long)process.GetMemoryCapacity(); break; + + case 7: value = (long)process.GetMemoryUsage(); break; + + case 12: value = (long)process.MemoryManager.GetAddrSpaceBaseAddr(); break; + + case 13: value = (long)process.MemoryManager.GetAddrSpaceSize(); break; + + case 14: value = (long)process.MemoryManager.StackRegionStart; break; + case 15: value = (long)(process.MemoryManager.StackRegionEnd - + process.MemoryManager.StackRegionStart); break; + + case 16: value = (long)process.PersonalMmHeapPagesCount * KMemoryManager.PageSize; break; + + case 17: + if (process.PersonalMmHeapPagesCount != 0) + { + value = process.MemoryManager.GetMmUsedPages() * KMemoryManager.PageSize; + } + + break; + + case 18: value = process.TitleId; break; + + case 20: value = (long)process.UserExceptionContextAddress; break; + + case 21: value = (long)process.GetMemoryCapacityWithoutPersonalMmHeap(); break; + + case 22: value = (long)process.GetMemoryUsageWithoutPersonalMmHeap(); break; + } + + break; + } + + case 8: + { + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + if (subId != 0) + { + return KernelResult.InvalidCombination; + } + + value = _system.Scheduler.GetCurrentProcess().Debug ? 1 : 0; + + break; + } + + case 9: + { + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + if (subId != 0) + { + return KernelResult.InvalidCombination; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if (currentProcess.ResourceLimit != null) + { + KHandleTable handleTable = currentProcess.HandleTable; + KResourceLimit resourceLimit = currentProcess.ResourceLimit; + + KernelResult result = handleTable.GenerateHandle(resourceLimit, out int resLimHandle); + + if (result != KernelResult.Success) + { + return result; + } + + value = (uint)resLimHandle; + } + + break; + } + + case 10: + { + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + int currentCore = _system.Scheduler.GetCurrentThread().CurrentCore; + + if (subId != -1 && subId != currentCore) + { + return KernelResult.InvalidCombination; + } + + value = _system.Scheduler.CoreContexts[currentCore].TotalIdleTimeTicks; + + break; + } + + case 11: + { + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + if ((ulong)subId > 3) + { + return KernelResult.InvalidCombination; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + + value = currentProcess.RandomEntropy[subId]; + + break; + } + + case 0xf0000002u: + { + if (subId < -1 || subId > 3) + { + return KernelResult.InvalidCombination; + } + + KThread thread = _system.Scheduler.GetCurrentProcess().HandleTable.GetKThread(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + int currentCore = currentThread.CurrentCore; + + if (subId != -1 && subId != currentCore) + { + return KernelResult.Success; + } + + KCoreContext coreContext = _system.Scheduler.CoreContexts[currentCore]; + + long timeDelta = PerformanceCounter.ElapsedMilliseconds - coreContext.LastContextSwitchTime; + + if (subId != -1) + { + value = KTimeManager.ConvertMillisecondsToTicks(timeDelta); + } + else + { + long totalTimeRunning = thread.TotalTimeRunning; + + if (thread == currentThread) + { + totalTimeRunning += timeDelta; + } + + value = KTimeManager.ConvertMillisecondsToTicks(totalTimeRunning); + } + + break; + } + + default: return KernelResult.InvalidEnumValue; + } + + return KernelResult.Success; + } + + public KernelResult CreateEvent64(out int wEventHandle, out int rEventHandle) + { + return CreateEvent(out wEventHandle, out rEventHandle); + } + + private KernelResult CreateEvent(out int wEventHandle, out int rEventHandle) + { + KEvent Event = new KEvent(_system); + + KernelResult result = _process.HandleTable.GenerateHandle(Event.WritableEvent, out wEventHandle); + + if (result == KernelResult.Success) + { + result = _process.HandleTable.GenerateHandle(Event.ReadableEvent, out rEventHandle); + + if (result != KernelResult.Success) + { + _process.HandleTable.CloseHandle(wEventHandle); + } + } + else + { + rEventHandle = 0; + } + + return result; + } + + public KernelResult GetProcessList64(ulong address, int maxCount, out int count) + { + return GetProcessList(address, maxCount, out count); + } + + private KernelResult GetProcessList(ulong address, int maxCount, out int count) + { + count = 0; + + if ((maxCount >> 28) != 0) + { + return KernelResult.MaximumExceeded; + } + + if (maxCount != 0) + { + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + ulong copySize = (ulong)maxCount * 8; + + if (address + copySize <= address) + { + return KernelResult.InvalidMemState; + } + + if (currentProcess.MemoryManager.OutsideAddrSpace(address, copySize)) + { + return KernelResult.InvalidMemState; + } + } + + int copyCount = 0; + + lock (_system.Processes) + { + foreach (KProcess process in _system.Processes.Values) + { + if (copyCount < maxCount) + { + if (!KernelTransfer.KernelToUserInt64(_system, address + (ulong)copyCount * 8, process.Pid)) + { + return KernelResult.UserCopyFailed; + } + } + + copyCount++; + } + } + + count = copyCount; + + return KernelResult.Success; + } + + public KernelResult GetSystemInfo64(uint id, int handle, long subId, out long value) + { + return GetSystemInfo(id, handle, subId, out value); + } + + private KernelResult GetSystemInfo(uint id, int handle, long subId, out long value) + { + value = 0; + + if (id > 2) + { + return KernelResult.InvalidEnumValue; + } + + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + if (id < 2) + { + if ((ulong)subId > 3) + { + return KernelResult.InvalidCombination; + } + + KMemoryRegionManager region = _system.MemoryRegions[subId]; + + switch (id) + { + //Memory region capacity. + case 0: value = (long)region.Size; break; + + //Memory region free space. + case 1: + { + ulong freePagesCount = region.GetFreePages(); + + value = (long)(freePagesCount * KMemoryManager.PageSize); + + break; + } + } + } + else /* if (Id == 2) */ + { + if ((ulong)subId > 1) + { + return KernelResult.InvalidCombination; + } + + switch (subId) + { + case 0: value = _system.PrivilegedProcessLowestId; break; + case 1: value = _system.PrivilegedProcessHighestId; break; + } + } + + return KernelResult.Success; + } + + public KernelResult CreatePort64( + int maxSessions, + bool isLight, + ulong namePtr, + out int serverPortHandle, + out int clientPortHandle) + { + return CreatePort(maxSessions, isLight, namePtr, out serverPortHandle, out clientPortHandle); + } + + private KernelResult CreatePort( + int maxSessions, + bool isLight, + ulong namePtr, + out int serverPortHandle, + out int clientPortHandle) + { + serverPortHandle = clientPortHandle = 0; + + if (maxSessions < 1) + { + return KernelResult.MaximumExceeded; + } + + KPort port = new KPort(_system); + + port.Initialize(maxSessions, isLight, (long)namePtr); + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ClientPort, out clientPortHandle); + + if (result != KernelResult.Success) + { + return result; + } + + result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out serverPortHandle); + + if (result != KernelResult.Success) + { + currentProcess.HandleTable.CloseHandle(clientPortHandle); + } + + return result; + } + + public KernelResult ManageNamedPort64(ulong namePtr, int maxSessions, out int handle) + { + return ManageNamedPort(namePtr, maxSessions, out handle); + } + + private KernelResult ManageNamedPort(ulong namePtr, int maxSessions, out int handle) + { + handle = 0; + + if (!KernelTransfer.UserToKernelString(_system, namePtr, 12, out string name)) + { + return KernelResult.UserCopyFailed; + } + + if (maxSessions < 0 || name.Length > 11) + { + return KernelResult.MaximumExceeded; + } + + if (maxSessions == 0) + { + return KClientPort.RemoveName(_system, name); + } + + KPort port = new KPort(_system); + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out handle); + + if (result != KernelResult.Success) + { + return result; + } + + port.Initialize(maxSessions, false, 0); + + result = port.SetName(name); + + if (result != KernelResult.Success) + { + currentProcess.HandleTable.CloseHandle(handle); + } + + return result; + } + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs new file mode 100644 index 00000000..a6111777 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs @@ -0,0 +1,340 @@ +using ChocolArm64.State; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Common; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + static class SvcTable + { + private const int SvcFuncMaxArguments = 8; + + private static Dictionary _svcFuncs64; + + private static Action[] _svcTable64; + + static SvcTable() + { + _svcFuncs64 = new Dictionary + { + { 0x01, nameof(SvcHandler.SetHeapSize64) }, + { 0x03, nameof(SvcHandler.SetMemoryAttribute64) }, + { 0x04, nameof(SvcHandler.MapMemory64) }, + { 0x05, nameof(SvcHandler.UnmapMemory64) }, + { 0x06, nameof(SvcHandler.QueryMemory64) }, + { 0x07, nameof(SvcHandler.ExitProcess64) }, + { 0x08, nameof(SvcHandler.CreateThread64) }, + { 0x09, nameof(SvcHandler.StartThread64) }, + { 0x0a, nameof(SvcHandler.ExitThread64) }, + { 0x0b, nameof(SvcHandler.SleepThread64) }, + { 0x0c, nameof(SvcHandler.GetThreadPriority64) }, + { 0x0d, nameof(SvcHandler.SetThreadPriority64) }, + { 0x0e, nameof(SvcHandler.GetThreadCoreMask64) }, + { 0x0f, nameof(SvcHandler.SetThreadCoreMask64) }, + { 0x10, nameof(SvcHandler.GetCurrentProcessorNumber64) }, + { 0x11, nameof(SvcHandler.SignalEvent64) }, + { 0x12, nameof(SvcHandler.ClearEvent64) }, + { 0x13, nameof(SvcHandler.MapSharedMemory64) }, + { 0x14, nameof(SvcHandler.UnmapSharedMemory64) }, + { 0x15, nameof(SvcHandler.CreateTransferMemory64) }, + { 0x16, nameof(SvcHandler.CloseHandle64) }, + { 0x17, nameof(SvcHandler.ResetSignal64) }, + { 0x18, nameof(SvcHandler.WaitSynchronization64) }, + { 0x19, nameof(SvcHandler.CancelSynchronization64) }, + { 0x1a, nameof(SvcHandler.ArbitrateLock64) }, + { 0x1b, nameof(SvcHandler.ArbitrateUnlock64) }, + { 0x1c, nameof(SvcHandler.WaitProcessWideKeyAtomic64) }, + { 0x1d, nameof(SvcHandler.SignalProcessWideKey64) }, + { 0x1e, nameof(SvcHandler.GetSystemTick64) }, + { 0x1f, nameof(SvcHandler.ConnectToNamedPort64) }, + { 0x21, nameof(SvcHandler.SendSyncRequest64) }, + { 0x22, nameof(SvcHandler.SendSyncRequestWithUserBuffer64) }, + { 0x24, nameof(SvcHandler.GetProcessId64) }, + { 0x25, nameof(SvcHandler.GetThreadId64) }, + { 0x26, nameof(SvcHandler.Break64) }, + { 0x27, nameof(SvcHandler.OutputDebugString64) }, + { 0x29, nameof(SvcHandler.GetInfo64) }, + { 0x2c, nameof(SvcHandler.MapPhysicalMemory64) }, + { 0x2d, nameof(SvcHandler.UnmapPhysicalMemory64) }, + { 0x32, nameof(SvcHandler.SetThreadActivity64) }, + { 0x33, nameof(SvcHandler.GetThreadContext364) }, + { 0x34, nameof(SvcHandler.WaitForAddress64) }, + { 0x35, nameof(SvcHandler.SignalToAddress64) }, + { 0x45, nameof(SvcHandler.CreateEvent64) }, + { 0x65, nameof(SvcHandler.GetProcessList64) }, + { 0x6f, nameof(SvcHandler.GetSystemInfo64) }, + { 0x70, nameof(SvcHandler.CreatePort64) }, + { 0x71, nameof(SvcHandler.ManageNamedPort64) } + }; + + _svcTable64 = new Action[0x80]; + } + + public static Action GetSvcFunc(int svcId) + { + if (_svcTable64[svcId] != null) + { + return _svcTable64[svcId]; + } + + if (_svcFuncs64.TryGetValue(svcId, out string svcName)) + { + return _svcTable64[svcId] = GenerateMethod(svcName); + } + + return null; + } + + private static Action GenerateMethod(string svcName) + { + Type[] argTypes = new Type[] { typeof(SvcHandler), typeof(CpuThreadState) }; + + DynamicMethod method = new DynamicMethod(svcName, null, argTypes); + + MethodInfo methodInfo = typeof(SvcHandler).GetMethod(svcName); + + ParameterInfo[] methodArgs = methodInfo.GetParameters(); + + if (methodArgs.Length > SvcFuncMaxArguments) + { + throw new InvalidOperationException($"Method \"{svcName}\" has too many arguments, max is 8."); + } + + ILGenerator generator = method.GetILGenerator(); + + void ConvertToArgType(Type sourceType) + { + CheckIfTypeIsSupported(sourceType, svcName); + + switch (Type.GetTypeCode(sourceType)) + { + case TypeCode.UInt32: generator.Emit(OpCodes.Conv_U4); break; + case TypeCode.Int32: generator.Emit(OpCodes.Conv_I4); break; + case TypeCode.UInt16: generator.Emit(OpCodes.Conv_U2); break; + case TypeCode.Int16: generator.Emit(OpCodes.Conv_I2); break; + case TypeCode.Byte: generator.Emit(OpCodes.Conv_U1); break; + case TypeCode.SByte: generator.Emit(OpCodes.Conv_I1); break; + + case TypeCode.Boolean: + generator.Emit(OpCodes.Conv_I4); + generator.Emit(OpCodes.Ldc_I4_1); + generator.Emit(OpCodes.And); + break; + } + } + + void ConvertToFieldType(Type sourceType) + { + CheckIfTypeIsSupported(sourceType, svcName); + + switch (Type.GetTypeCode(sourceType)) + { + case TypeCode.UInt32: + case TypeCode.Int32: + case TypeCode.UInt16: + case TypeCode.Int16: + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.Boolean: + generator.Emit(OpCodes.Conv_U8); + break; + } + } + + //For functions returning output values, the first registers + //are used to hold pointers where the value will be stored, + //so they can't be used to pass argument and we must + //skip them. + int byRefArgsCount = 0; + + for (int index = 0; index < methodArgs.Length; index++) + { + if (methodArgs[index].ParameterType.IsByRef) + { + byRefArgsCount++; + } + } + + //Print all the arguments for debugging purposes. + int inputArgsCount = methodArgs.Length - byRefArgsCount; + + generator.Emit(OpCodes.Ldc_I4_S, inputArgsCount); + + generator.Emit(OpCodes.Newarr, typeof(object)); + + string argsFormat = svcName; + + for (int index = 0; index < inputArgsCount; index++) + { + argsFormat += $" {methodArgs[index].Name}: 0x{{{index}:X8}},"; + + generator.Emit(OpCodes.Dup); + generator.Emit(OpCodes.Ldc_I4_S, index); + generator.Emit(OpCodes.Conv_I); + + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldfld, GetStateFieldX(byRefArgsCount + index)); + + generator.Emit(OpCodes.Box, typeof(ulong)); + + generator.Emit(OpCodes.Stelem_Ref); + } + + argsFormat = argsFormat.Substring(0, argsFormat.Length - 1); + + generator.Emit(OpCodes.Ldstr, argsFormat); + + BindingFlags staticNonPublic = BindingFlags.NonPublic | BindingFlags.Static; + + MethodInfo printArgsMethod = typeof(SvcTable).GetMethod(nameof(PrintArguments), staticNonPublic); + + generator.Emit(OpCodes.Call, printArgsMethod); + + //Call the SVC function handler. + generator.Emit(OpCodes.Ldarg_0); + + List locals = new List(); + + for (int index = 0; index < methodArgs.Length; index++) + { + Type argType = methodArgs[index].ParameterType; + + if (argType.IsByRef) + { + argType = argType.GetElementType(); + + LocalBuilder local = generator.DeclareLocal(argType); + + locals.Add(local); + + if (!methodArgs[index].IsOut) + { + throw new InvalidOperationException($"Method \"{svcName}\" has a invalid ref type \"{argType.Name}\"."); + } + + generator.Emit(OpCodes.Ldloca_S, (byte)local.LocalIndex); + } + else + { + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldfld, GetStateFieldX(byRefArgsCount + index)); + + ConvertToArgType(argType); + } + } + + generator.Emit(OpCodes.Call, methodInfo); + + int outRegIndex = 0; + + Type retType = methodInfo.ReturnType; + + //Print result code. + if (retType == typeof(KernelResult)) + { + MethodInfo printResultMethod = typeof(SvcTable).GetMethod(nameof(PrintResult), staticNonPublic); + + generator.Emit(OpCodes.Dup); + generator.Emit(OpCodes.Ldstr, svcName); + generator.Emit(OpCodes.Call, printResultMethod); + } + + //Save return value into register X0 (when the method has a return value). + if (retType != typeof(void)) + { + CheckIfTypeIsSupported(retType, svcName); + + LocalBuilder tempLocal = generator.DeclareLocal(retType); + + generator.Emit(OpCodes.Stloc, tempLocal); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldloc, tempLocal); + + ConvertToFieldType(retType); + + generator.Emit(OpCodes.Stfld, GetStateFieldX(outRegIndex++)); + } + + for (int index = 0; index < locals.Count; index++) + { + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldloc, locals[index]); + + ConvertToFieldType(locals[index].LocalType); + + generator.Emit(OpCodes.Stfld, GetStateFieldX(outRegIndex++)); + } + + //Zero out the remaining unused registers. + while (outRegIndex < SvcFuncMaxArguments) + { + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldc_I8, 0L); + generator.Emit(OpCodes.Stfld, GetStateFieldX(outRegIndex++)); + } + + generator.Emit(OpCodes.Ret); + + return (Action)method.CreateDelegate(typeof(Action)); + } + + private static FieldInfo GetStateFieldX(int index) + { + switch (index) + { + case 0: return typeof(CpuThreadState).GetField(nameof(CpuThreadState.X0)); + case 1: return typeof(CpuThreadState).GetField(nameof(CpuThreadState.X1)); + case 2: return typeof(CpuThreadState).GetField(nameof(CpuThreadState.X2)); + case 3: return typeof(CpuThreadState).GetField(nameof(CpuThreadState.X3)); + case 4: return typeof(CpuThreadState).GetField(nameof(CpuThreadState.X4)); + case 5: return typeof(CpuThreadState).GetField(nameof(CpuThreadState.X5)); + case 6: return typeof(CpuThreadState).GetField(nameof(CpuThreadState.X6)); + case 7: return typeof(CpuThreadState).GetField(nameof(CpuThreadState.X7)); + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + private static void CheckIfTypeIsSupported(Type type, string svcName) + { + switch (Type.GetTypeCode(type)) + { + case TypeCode.UInt64: + case TypeCode.Int64: + case TypeCode.UInt32: + case TypeCode.Int32: + case TypeCode.UInt16: + case TypeCode.Int16: + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.Boolean: + return; + } + + throw new InvalidSvcException($"Method \"{svcName}\" has a invalid ref type \"{type.Name}\"."); + } + + private static void PrintResult(KernelResult result, string svcName) + { + if (result != KernelResult.Success && + result != KernelResult.TimedOut && + result != KernelResult.Cancelled && + result != KernelResult.InvalidState) + { + Logger.PrintWarning(LogClass.KernelSvc, $"{svcName} returned error {result}."); + } + else + { + Logger.PrintDebug(LogClass.KernelSvc, $"{svcName} returned result {result}."); + } + } + + private static void PrintArguments(object[] argValues, string format) + { + Logger.PrintDebug(LogClass.KernelSvc, string.Format(format, argValues)); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThread.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThread.cs new file mode 100644 index 00000000..1e1927fe --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThread.cs @@ -0,0 +1,420 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + partial class SvcHandler + { + public KernelResult CreateThread64( + ulong entrypoint, + ulong argsPtr, + ulong stackTop, + int priority, + int cpuCore, + out int handle) + { + return CreateThread(entrypoint, argsPtr, stackTop, priority, cpuCore, out handle); + } + + private KernelResult CreateThread( + ulong entrypoint, + ulong argsPtr, + ulong stackTop, + int priority, + int cpuCore, + out int handle) + { + handle = 0; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if (cpuCore == -2) + { + cpuCore = currentProcess.DefaultCpuCore; + } + + if ((uint)cpuCore >= KScheduler.CpuCoresCount || !currentProcess.IsCpuCoreAllowed(cpuCore)) + { + return KernelResult.InvalidCpuCore; + } + + if ((uint)priority >= KScheduler.PrioritiesCount || !currentProcess.IsPriorityAllowed(priority)) + { + return KernelResult.InvalidPriority; + } + + long timeout = KTimeManager.ConvertMillisecondsToNanoseconds(100); + + if (currentProcess.ResourceLimit != null && + !currentProcess.ResourceLimit.Reserve(LimitableResource.Thread, 1, timeout)) + { + return KernelResult.ResLimitExceeded; + } + + KThread thread = new KThread(_system); + + KernelResult result = currentProcess.InitializeThread( + thread, + entrypoint, + argsPtr, + stackTop, + priority, + cpuCore); + + if (result != KernelResult.Success) + { + currentProcess.ResourceLimit?.Release(LimitableResource.Thread, 1); + + return result; + } + + result = _process.HandleTable.GenerateHandle(thread, out handle); + + if (result != KernelResult.Success) + { + thread.Terminate(); + + currentProcess.ResourceLimit?.Release(LimitableResource.Thread, 1); + } + + return result; + } + + public KernelResult StartThread64(int handle) + { + return StartThread(handle); + } + + private KernelResult StartThread(int handle) + { + KThread thread = _process.HandleTable.GetObject(handle); + + if (thread != null) + { + return thread.Start(); + } + else + { + return KernelResult.InvalidHandle; + } + } + + public void ExitThread64() + { + ExitThread(); + } + + private void ExitThread() + { + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + _system.Scheduler.ExitThread(currentThread); + + currentThread.Exit(); + } + + public void SleepThread64(long timeout) + { + SleepThread(timeout); + } + + private void SleepThread(long timeout) + { + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + if (timeout < 1) + { + switch (timeout) + { + case 0: currentThread.Yield(); break; + case -1: currentThread.YieldWithLoadBalancing(); break; + case -2: currentThread.YieldAndWaitForLoadBalancing(); break; + } + } + else + { + currentThread.Sleep(timeout); + } + } + + public KernelResult GetThreadPriority64(int handle, out int priority) + { + return GetThreadPriority(handle, out priority); + } + + private KernelResult GetThreadPriority(int handle, out int priority) + { + KThread thread = _process.HandleTable.GetKThread(handle); + + if (thread != null) + { + priority = thread.DynamicPriority; + + return KernelResult.Success; + } + else + { + priority = 0; + + return KernelResult.InvalidHandle; + } + } + + public KernelResult SetThreadPriority64(int handle, int priority) + { + return SetThreadPriority(handle, priority); + } + + public KernelResult SetThreadPriority(int handle, int priority) + { + //TODO: NPDM check. + + KThread thread = _process.HandleTable.GetKThread(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + thread.SetPriority(priority); + + return KernelResult.Success; + } + + public KernelResult GetThreadCoreMask64(int handle, out int preferredCore, out long affinityMask) + { + return GetThreadCoreMask(handle, out preferredCore, out affinityMask); + } + + private KernelResult GetThreadCoreMask(int handle, out int preferredCore, out long affinityMask) + { + KThread thread = _process.HandleTable.GetKThread(handle); + + if (thread != null) + { + preferredCore = thread.PreferredCore; + affinityMask = thread.AffinityMask; + + return KernelResult.Success; + } + else + { + preferredCore = 0; + affinityMask = 0; + + return KernelResult.InvalidHandle; + } + } + + public KernelResult SetThreadCoreMask64(int handle, int preferredCore, long affinityMask) + { + return SetThreadCoreMask(handle, preferredCore, affinityMask); + } + + private KernelResult SetThreadCoreMask(int handle, int preferredCore, long affinityMask) + { + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if (preferredCore == -2) + { + preferredCore = currentProcess.DefaultCpuCore; + + affinityMask = 1 << preferredCore; + } + else + { + if ((currentProcess.Capabilities.AllowedCpuCoresMask | affinityMask) != + currentProcess.Capabilities.AllowedCpuCoresMask) + { + return KernelResult.InvalidCpuCore; + } + + if (affinityMask == 0) + { + return KernelResult.InvalidCombination; + } + + if ((uint)preferredCore > 3) + { + if ((preferredCore | 2) != -1) + { + return KernelResult.InvalidCpuCore; + } + } + else if ((affinityMask & (1 << preferredCore)) == 0) + { + return KernelResult.InvalidCombination; + } + } + + KThread thread = _process.HandleTable.GetKThread(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + return thread.SetCoreAndAffinityMask(preferredCore, affinityMask); + } + + public int GetCurrentProcessorNumber64() + { + return _system.Scheduler.GetCurrentThread().CurrentCore; + } + + public KernelResult GetThreadId64(int handle, out long threadUid) + { + return GetThreadId(handle, out threadUid); + } + + private KernelResult GetThreadId(int handle, out long threadUid) + { + KThread thread = _process.HandleTable.GetKThread(handle); + + if (thread != null) + { + threadUid = thread.ThreadUid; + + return KernelResult.Success; + } + else + { + threadUid = 0; + + return KernelResult.InvalidHandle; + } + } + + public KernelResult SetThreadActivity64(int handle, bool pause) + { + return SetThreadActivity(handle, pause); + } + + private KernelResult SetThreadActivity(int handle, bool pause) + { + KThread thread = _process.HandleTable.GetObject(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + if (thread.Owner != _system.Scheduler.GetCurrentProcess()) + { + return KernelResult.InvalidHandle; + } + + if (thread == _system.Scheduler.GetCurrentThread()) + { + return KernelResult.InvalidThread; + } + + return thread.SetActivity(pause); + } + + public KernelResult GetThreadContext364(ulong address, int handle) + { + return GetThreadContext3(address, handle); + } + + private KernelResult GetThreadContext3(ulong address, int handle) + { + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + KThread thread = _process.HandleTable.GetObject(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + if (thread.Owner != currentProcess) + { + return KernelResult.InvalidHandle; + } + + if (currentThread == thread) + { + return KernelResult.InvalidThread; + } + + _memory.WriteUInt64((long)address + 0x0, thread.Context.ThreadState.X0); + _memory.WriteUInt64((long)address + 0x8, thread.Context.ThreadState.X1); + _memory.WriteUInt64((long)address + 0x10, thread.Context.ThreadState.X2); + _memory.WriteUInt64((long)address + 0x18, thread.Context.ThreadState.X3); + _memory.WriteUInt64((long)address + 0x20, thread.Context.ThreadState.X4); + _memory.WriteUInt64((long)address + 0x28, thread.Context.ThreadState.X5); + _memory.WriteUInt64((long)address + 0x30, thread.Context.ThreadState.X6); + _memory.WriteUInt64((long)address + 0x38, thread.Context.ThreadState.X7); + _memory.WriteUInt64((long)address + 0x40, thread.Context.ThreadState.X8); + _memory.WriteUInt64((long)address + 0x48, thread.Context.ThreadState.X9); + _memory.WriteUInt64((long)address + 0x50, thread.Context.ThreadState.X10); + _memory.WriteUInt64((long)address + 0x58, thread.Context.ThreadState.X11); + _memory.WriteUInt64((long)address + 0x60, thread.Context.ThreadState.X12); + _memory.WriteUInt64((long)address + 0x68, thread.Context.ThreadState.X13); + _memory.WriteUInt64((long)address + 0x70, thread.Context.ThreadState.X14); + _memory.WriteUInt64((long)address + 0x78, thread.Context.ThreadState.X15); + _memory.WriteUInt64((long)address + 0x80, thread.Context.ThreadState.X16); + _memory.WriteUInt64((long)address + 0x88, thread.Context.ThreadState.X17); + _memory.WriteUInt64((long)address + 0x90, thread.Context.ThreadState.X18); + _memory.WriteUInt64((long)address + 0x98, thread.Context.ThreadState.X19); + _memory.WriteUInt64((long)address + 0xa0, thread.Context.ThreadState.X20); + _memory.WriteUInt64((long)address + 0xa8, thread.Context.ThreadState.X21); + _memory.WriteUInt64((long)address + 0xb0, thread.Context.ThreadState.X22); + _memory.WriteUInt64((long)address + 0xb8, thread.Context.ThreadState.X23); + _memory.WriteUInt64((long)address + 0xc0, thread.Context.ThreadState.X24); + _memory.WriteUInt64((long)address + 0xc8, thread.Context.ThreadState.X25); + _memory.WriteUInt64((long)address + 0xd0, thread.Context.ThreadState.X26); + _memory.WriteUInt64((long)address + 0xd8, thread.Context.ThreadState.X27); + _memory.WriteUInt64((long)address + 0xe0, thread.Context.ThreadState.X28); + _memory.WriteUInt64((long)address + 0xe8, thread.Context.ThreadState.X29); + _memory.WriteUInt64((long)address + 0xf0, thread.Context.ThreadState.X30); + _memory.WriteUInt64((long)address + 0xf8, thread.Context.ThreadState.X31); + + _memory.WriteInt64((long)address + 0x100, thread.LastPc); + + _memory.WriteUInt64((long)address + 0x108, (ulong)thread.Context.ThreadState.Psr); + + _memory.WriteVector128((long)address + 0x110, thread.Context.ThreadState.V0); + _memory.WriteVector128((long)address + 0x120, thread.Context.ThreadState.V1); + _memory.WriteVector128((long)address + 0x130, thread.Context.ThreadState.V2); + _memory.WriteVector128((long)address + 0x140, thread.Context.ThreadState.V3); + _memory.WriteVector128((long)address + 0x150, thread.Context.ThreadState.V4); + _memory.WriteVector128((long)address + 0x160, thread.Context.ThreadState.V5); + _memory.WriteVector128((long)address + 0x170, thread.Context.ThreadState.V6); + _memory.WriteVector128((long)address + 0x180, thread.Context.ThreadState.V7); + _memory.WriteVector128((long)address + 0x190, thread.Context.ThreadState.V8); + _memory.WriteVector128((long)address + 0x1a0, thread.Context.ThreadState.V9); + _memory.WriteVector128((long)address + 0x1b0, thread.Context.ThreadState.V10); + _memory.WriteVector128((long)address + 0x1c0, thread.Context.ThreadState.V11); + _memory.WriteVector128((long)address + 0x1d0, thread.Context.ThreadState.V12); + _memory.WriteVector128((long)address + 0x1e0, thread.Context.ThreadState.V13); + _memory.WriteVector128((long)address + 0x1f0, thread.Context.ThreadState.V14); + _memory.WriteVector128((long)address + 0x200, thread.Context.ThreadState.V15); + _memory.WriteVector128((long)address + 0x210, thread.Context.ThreadState.V16); + _memory.WriteVector128((long)address + 0x220, thread.Context.ThreadState.V17); + _memory.WriteVector128((long)address + 0x230, thread.Context.ThreadState.V18); + _memory.WriteVector128((long)address + 0x240, thread.Context.ThreadState.V19); + _memory.WriteVector128((long)address + 0x250, thread.Context.ThreadState.V20); + _memory.WriteVector128((long)address + 0x260, thread.Context.ThreadState.V21); + _memory.WriteVector128((long)address + 0x270, thread.Context.ThreadState.V22); + _memory.WriteVector128((long)address + 0x280, thread.Context.ThreadState.V23); + _memory.WriteVector128((long)address + 0x290, thread.Context.ThreadState.V24); + _memory.WriteVector128((long)address + 0x2a0, thread.Context.ThreadState.V25); + _memory.WriteVector128((long)address + 0x2b0, thread.Context.ThreadState.V26); + _memory.WriteVector128((long)address + 0x2c0, thread.Context.ThreadState.V27); + _memory.WriteVector128((long)address + 0x2d0, thread.Context.ThreadState.V28); + _memory.WriteVector128((long)address + 0x2e0, thread.Context.ThreadState.V29); + _memory.WriteVector128((long)address + 0x2f0, thread.Context.ThreadState.V30); + _memory.WriteVector128((long)address + 0x300, thread.Context.ThreadState.V31); + + _memory.WriteInt32((long)address + 0x310, thread.Context.ThreadState.Fpcr); + _memory.WriteInt32((long)address + 0x314, thread.Context.ThreadState.Fpsr); + _memory.WriteInt64((long)address + 0x318, thread.Context.ThreadState.Tpidr); + + return KernelResult.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThreadSync.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThreadSync.cs new file mode 100644 index 00000000..ecda9e2d --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcThreadSync.cs @@ -0,0 +1,250 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + partial class SvcHandler + { + public KernelResult WaitSynchronization64(ulong handlesPtr, int handlesCount, long timeout, out int handleIndex) + { + return WaitSynchronization(handlesPtr, handlesCount, timeout, out handleIndex); + } + + private KernelResult WaitSynchronization(ulong handlesPtr, int handlesCount, long timeout, out int handleIndex) + { + handleIndex = 0; + + if ((uint)handlesCount > 0x40) + { + return KernelResult.MaximumExceeded; + } + + List syncObjs = new List(); + + for (int index = 0; index < handlesCount; index++) + { + int handle = _memory.ReadInt32((long)handlesPtr + index * 4); + + KSynchronizationObject syncObj = _process.HandleTable.GetObject(handle); + + if (syncObj == null) + { + break; + } + + syncObjs.Add(syncObj); + } + + return _system.Synchronization.WaitFor(syncObjs.ToArray(), timeout, out handleIndex); + } + + public KernelResult CancelSynchronization64(int handle) + { + return CancelSynchronization(handle); + } + + private KernelResult CancelSynchronization(int handle) + { + KThread thread = _process.HandleTable.GetKThread(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + thread.CancelSynchronization(); + + return KernelResult.Success; + } + + public KernelResult ArbitrateLock64(int ownerHandle, ulong mutexAddress, int requesterHandle) + { + return ArbitrateLock(ownerHandle, mutexAddress, requesterHandle); + } + + private KernelResult ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle) + { + if (IsPointingInsideKernel(mutexAddress)) + { + return KernelResult.InvalidMemState; + } + + if (IsAddressNotWordAligned(mutexAddress)) + { + return KernelResult.InvalidAddress; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + return currentProcess.AddressArbiter.ArbitrateLock(ownerHandle, mutexAddress, requesterHandle); + } + + public KernelResult ArbitrateUnlock64(ulong mutexAddress) + { + return ArbitrateUnlock(mutexAddress); + } + + private KernelResult ArbitrateUnlock(ulong mutexAddress) + { + if (IsPointingInsideKernel(mutexAddress)) + { + return KernelResult.InvalidMemState; + } + + if (IsAddressNotWordAligned(mutexAddress)) + { + return KernelResult.InvalidAddress; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + return currentProcess.AddressArbiter.ArbitrateUnlock(mutexAddress); + } + + public KernelResult WaitProcessWideKeyAtomic64( + ulong mutexAddress, + ulong condVarAddress, + int handle, + long timeout) + { + return WaitProcessWideKeyAtomic(mutexAddress, condVarAddress, handle, timeout); + } + + private KernelResult WaitProcessWideKeyAtomic( + ulong mutexAddress, + ulong condVarAddress, + int handle, + long timeout) + { + if (IsPointingInsideKernel(mutexAddress)) + { + return KernelResult.InvalidMemState; + } + + if (IsAddressNotWordAligned(mutexAddress)) + { + return KernelResult.InvalidAddress; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + return currentProcess.AddressArbiter.WaitProcessWideKeyAtomic( + mutexAddress, + condVarAddress, + handle, + timeout); + } + + public KernelResult SignalProcessWideKey64(ulong address, int count) + { + return SignalProcessWideKey(address, count); + } + + private KernelResult SignalProcessWideKey(ulong address, int count) + { + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + currentProcess.AddressArbiter.SignalProcessWideKey(address, count); + + return KernelResult.Success; + } + + public KernelResult WaitForAddress64(ulong address, ArbitrationType type, int value, long timeout) + { + return WaitForAddress(address, type, value, timeout); + } + + private KernelResult WaitForAddress(ulong address, ArbitrationType type, int value, long timeout) + { + if (IsPointingInsideKernel(address)) + { + return KernelResult.InvalidMemState; + } + + if (IsAddressNotWordAligned(address)) + { + return KernelResult.InvalidAddress; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KernelResult result; + + switch (type) + { + case ArbitrationType.WaitIfLessThan: + result = currentProcess.AddressArbiter.WaitForAddressIfLessThan(address, value, false, timeout); + break; + + case ArbitrationType.DecrementAndWaitIfLessThan: + result = currentProcess.AddressArbiter.WaitForAddressIfLessThan(address, value, true, timeout); + break; + + case ArbitrationType.WaitIfEqual: + result = currentProcess.AddressArbiter.WaitForAddressIfEqual(address, value, timeout); + break; + + default: + result = KernelResult.InvalidEnumValue; + break; + } + + return result; + } + + public KernelResult SignalToAddress64(ulong address, SignalType type, int value, int count) + { + return SignalToAddress(address, type, value, count); + } + + private KernelResult SignalToAddress(ulong address, SignalType type, int value, int count) + { + if (IsPointingInsideKernel(address)) + { + return KernelResult.InvalidMemState; + } + + if (IsAddressNotWordAligned(address)) + { + return KernelResult.InvalidAddress; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + KernelResult result; + + switch (type) + { + case SignalType.Signal: + result = currentProcess.AddressArbiter.Signal(address, count); + break; + + case SignalType.SignalAndIncrementIfEqual: + result = currentProcess.AddressArbiter.SignalAndIncrementIfEqual(address, value, count); + break; + + case SignalType.SignalAndModifyIfEqual: + result = currentProcess.AddressArbiter.SignalAndModifyIfEqual(address, value, count); + break; + + default: + result = KernelResult.InvalidEnumValue; + break; + } + + return result; + } + + private bool IsPointingInsideKernel(ulong address) + { + return (address + 0x1000000000) < 0xffffff000; + } + + private bool IsAddressNotWordAligned(ulong address) + { + return (address & 3) != 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs b/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs deleted file mode 100644 index 78a0cc85..00000000 --- a/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs +++ /dev/null @@ -1,122 +0,0 @@ -using ChocolArm64.Events; -using ChocolArm64.Memory; -using ChocolArm64.State; -using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.Common.Logging; -using System; -using System.Collections.Generic; - -namespace Ryujinx.HLE.HOS.Kernel -{ - partial class SvcHandler - { - private delegate void SvcFunc(CpuThreadState threadState); - - private Dictionary _svcFuncs; - - private Switch _device; - private KProcess _process; - private Horizon _system; - private MemoryManager _memory; - - private struct HleIpcMessage - { - public KThread Thread { get; private set; } - public KSession Session { get; private set; } - public IpcMessage Message { get; private set; } - public long MessagePtr { get; private set; } - - public HleIpcMessage( - KThread thread, - KSession session, - IpcMessage message, - long messagePtr) - { - Thread = thread; - Session = session; - Message = message; - MessagePtr = messagePtr; - } - } - - public SvcHandler(Switch device, KProcess process) - { - _svcFuncs = new Dictionary - { - { 0x01, SvcSetHeapSize }, - { 0x03, SvcSetMemoryAttribute }, - { 0x04, SvcMapMemory }, - { 0x05, SvcUnmapMemory }, - { 0x06, SvcQueryMemory }, - { 0x07, SvcExitProcess }, - { 0x08, CreateThread64 }, - { 0x09, SvcStartThread }, - { 0x0a, SvcExitThread }, - { 0x0b, SvcSleepThread }, - { 0x0c, SvcGetThreadPriority }, - { 0x0d, SvcSetThreadPriority }, - { 0x0e, SvcGetThreadCoreMask }, - { 0x0f, SetThreadCoreMask64 }, - { 0x10, SvcGetCurrentProcessorNumber }, - { 0x11, SignalEvent64 }, - { 0x12, ClearEvent64 }, - { 0x13, SvcMapSharedMemory }, - { 0x14, SvcUnmapSharedMemory }, - { 0x15, SvcCreateTransferMemory }, - { 0x16, SvcCloseHandle }, - { 0x17, ResetSignal64 }, - { 0x18, SvcWaitSynchronization }, - { 0x19, SvcCancelSynchronization }, - { 0x1a, SvcArbitrateLock }, - { 0x1b, SvcArbitrateUnlock }, - { 0x1c, SvcWaitProcessWideKeyAtomic }, - { 0x1d, SvcSignalProcessWideKey }, - { 0x1e, SvcGetSystemTick }, - { 0x1f, SvcConnectToNamedPort }, - { 0x21, SvcSendSyncRequest }, - { 0x22, SvcSendSyncRequestWithUserBuffer }, - { 0x24, GetProcessId64 }, - { 0x25, SvcGetThreadId }, - { 0x26, SvcBreak }, - { 0x27, SvcOutputDebugString }, - { 0x29, GetInfo64 }, - { 0x2c, SvcMapPhysicalMemory }, - { 0x2d, SvcUnmapPhysicalMemory }, - { 0x32, SvcSetThreadActivity }, - { 0x33, SvcGetThreadContext3 }, - { 0x34, SvcWaitForAddress }, - { 0x35, SvcSignalToAddress }, - { 0x45, CreateEvent64 }, - { 0x65, GetProcessList64 }, - { 0x6f, GetSystemInfo64 }, - { 0x70, CreatePort64 }, - { 0x71, ManageNamedPort64 } - }; - - _device = device; - _process = process; - _system = device.System; - _memory = process.CpuMemory; - } - - public void SvcCall(object sender, InstExceptionEventArgs e) - { - CpuThreadState threadState = (CpuThreadState)sender; - - if (_svcFuncs.TryGetValue(e.Id, out SvcFunc func)) - { - Logger.PrintDebug(LogClass.KernelSvc, $"{func.Method.Name} called."); - - func(threadState); - - Logger.PrintDebug(LogClass.KernelSvc, $"{func.Method.Name} ended."); - } - else - { - //Process.PrintStackTrace(ThreadState); - - throw new NotImplementedException($"0x{e.Id:x4}"); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs b/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs deleted file mode 100644 index c99c1e98..00000000 --- a/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs +++ /dev/null @@ -1,581 +0,0 @@ -using ChocolArm64.State; -using Ryujinx.Common.Logging; - -using static Ryujinx.HLE.HOS.ErrorCode; - -namespace Ryujinx.HLE.HOS.Kernel -{ - partial class SvcHandler - { - private void SvcSetHeapSize(CpuThreadState threadState) - { - ulong size = threadState.X1; - - if ((size & 0xfffffffe001fffff) != 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Heap size 0x{size:x16} is not aligned!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); - - return; - } - - KernelResult result = _process.MemoryManager.SetHeapSize(size, out ulong position); - - threadState.X0 = (ulong)result; - - if (result == KernelResult.Success) - { - threadState.X1 = position; - } - else - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error \"{result}\"."); - } - } - - private void SvcSetMemoryAttribute(CpuThreadState threadState) - { - ulong position = threadState.X0; - ulong size = threadState.X1; - - if (!PageAligned(position)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{position:x16} is not page aligned!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - if (!PageAligned(size) || size == 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{size:x16} is not page aligned or is zero!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); - - return; - } - - MemoryAttribute attributeMask = (MemoryAttribute)threadState.X2; - MemoryAttribute attributeValue = (MemoryAttribute)threadState.X3; - - MemoryAttribute attributes = attributeMask | attributeValue; - - if (attributes != attributeMask || - (attributes | MemoryAttribute.Uncached) != MemoryAttribute.Uncached) - { - Logger.PrintWarning(LogClass.KernelSvc, "Invalid memory attributes!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue); - - return; - } - - KernelResult result = _process.MemoryManager.SetMemoryAttribute( - position, - size, - attributeMask, - attributeValue); - - if (result != KernelResult.Success) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error \"{result}\"."); - } - else - { - _memory.StopObservingRegion((long)position, (long)size); - } - - threadState.X0 = (ulong)result; - } - - private void SvcMapMemory(CpuThreadState threadState) - { - ulong dst = threadState.X0; - ulong src = threadState.X1; - ulong size = threadState.X2; - - if (!PageAligned(src | dst)) - { - Logger.PrintWarning(LogClass.KernelSvc, "Addresses are not page aligned!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - if (!PageAligned(size) || size == 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{size:x16} is not page aligned or is zero!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); - - return; - } - - if (src + size <= src || dst + size <= dst) - { - Logger.PrintWarning(LogClass.KernelSvc, "Addresses outside of range!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - if (!currentProcess.MemoryManager.InsideAddrSpace(src, size)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Src address 0x{src:x16} out of range!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - if (currentProcess.MemoryManager.OutsideStackRegion(dst, size) || - currentProcess.MemoryManager.InsideHeapRegion (dst, size) || - currentProcess.MemoryManager.InsideAliasRegion (dst, size)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Dst address 0x{dst:x16} out of range!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); - - return; - } - - KernelResult result = _process.MemoryManager.Map(dst, src, size); - - if (result != KernelResult.Success) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{result:x}!"); - } - - threadState.X0 = (ulong)result; - } - - private void SvcUnmapMemory(CpuThreadState threadState) - { - ulong dst = threadState.X0; - ulong src = threadState.X1; - ulong size = threadState.X2; - - if (!PageAligned(src | dst)) - { - Logger.PrintWarning(LogClass.KernelSvc, "Addresses are not page aligned!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - if (!PageAligned(size) || size == 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{size:x16} is not page aligned or is zero!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); - - return; - } - - if (src + size <= src || dst + size <= dst) - { - Logger.PrintWarning(LogClass.KernelSvc, "Addresses outside of range!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - if (!currentProcess.MemoryManager.InsideAddrSpace(src, size)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Src address 0x{src:x16} out of range!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - if (currentProcess.MemoryManager.OutsideStackRegion(dst, size) || - currentProcess.MemoryManager.InsideHeapRegion (dst, size) || - currentProcess.MemoryManager.InsideAliasRegion (dst, size)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Dst address 0x{dst:x16} out of range!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange); - - return; - } - - KernelResult result = _process.MemoryManager.Unmap(dst, src, size); - - if (result != KernelResult.Success) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{result:x}!"); - } - - threadState.X0 = (ulong)result; - } - - private void SvcQueryMemory(CpuThreadState threadState) - { - long infoPtr = (long)threadState.X0; - ulong position = threadState.X2; - - KMemoryInfo blkInfo = _process.MemoryManager.QueryMemory(position); - - _memory.WriteUInt64(infoPtr + 0x00, blkInfo.Address); - _memory.WriteUInt64(infoPtr + 0x08, blkInfo.Size); - _memory.WriteInt32 (infoPtr + 0x10, (int)blkInfo.State & 0xff); - _memory.WriteInt32 (infoPtr + 0x14, (int)blkInfo.Attribute); - _memory.WriteInt32 (infoPtr + 0x18, (int)blkInfo.Permission); - _memory.WriteInt32 (infoPtr + 0x1c, blkInfo.IpcRefCount); - _memory.WriteInt32 (infoPtr + 0x20, blkInfo.DeviceRefCount); - _memory.WriteInt32 (infoPtr + 0x24, 0); - - threadState.X0 = 0; - threadState.X1 = 0; - } - - private void SvcMapSharedMemory(CpuThreadState threadState) - { - int handle = (int)threadState.X0; - ulong address = threadState.X1; - ulong size = threadState.X2; - - if (!PageAligned(address)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{address:x16} is not page aligned!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - if (!PageAligned(size) || size == 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{size:x16} is not page aligned or is zero!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); - - return; - } - - if (address + size <= address) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{address:x16} / size 0x{size:x16}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - MemoryPermission permission = (MemoryPermission)threadState.X3; - - if ((permission | MemoryPermission.Write) != MemoryPermission.ReadAndWrite) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid permission {permission}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPermission); - - return; - } - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - KSharedMemory sharedMemory = currentProcess.HandleTable.GetObject(handle); - - if (sharedMemory == null) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid shared memory handle 0x{handle:x8}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - if (currentProcess.MemoryManager.IsInvalidRegion (address, size) || - currentProcess.MemoryManager.InsideHeapRegion (address, size) || - currentProcess.MemoryManager.InsideAliasRegion(address, size)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{address:x16} out of range!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - KernelResult result = sharedMemory.MapIntoProcess( - currentProcess.MemoryManager, - address, - size, - currentProcess, - permission); - - if (result != KernelResult.Success) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error \"{result}\"."); - } - - threadState.X0 = (ulong)result; - } - - private void SvcUnmapSharedMemory(CpuThreadState threadState) - { - int handle = (int)threadState.X0; - ulong address = threadState.X1; - ulong size = threadState.X2; - - if (!PageAligned(address)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{address:x16} is not page aligned!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - if (!PageAligned(size) || size == 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{size:x16} is not page aligned or is zero!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); - - return; - } - - if (address + size <= address) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{address:x16} / size 0x{size:x16}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - KSharedMemory sharedMemory = currentProcess.HandleTable.GetObject(handle); - - if (sharedMemory == null) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid shared memory handle 0x{handle:x8}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - if (currentProcess.MemoryManager.IsInvalidRegion (address, size) || - currentProcess.MemoryManager.InsideHeapRegion (address, size) || - currentProcess.MemoryManager.InsideAliasRegion(address, size)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{address:x16} out of range!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - KernelResult result = sharedMemory.UnmapFromProcess( - currentProcess.MemoryManager, - address, - size, - currentProcess); - - if (result != KernelResult.Success) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error \"{result}\"."); - } - - threadState.X0 = (ulong)result; - } - - private void SvcCreateTransferMemory(CpuThreadState threadState) - { - ulong address = threadState.X1; - ulong size = threadState.X2; - - if (!PageAligned(address)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{address:x16} is not page aligned!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - if (!PageAligned(size) || size == 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{size:x16} is not page aligned or is zero!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - if (address + size <= address) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{address:x16} / size 0x{size:x16}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - MemoryPermission permission = (MemoryPermission)threadState.X3; - - if (permission > MemoryPermission.ReadAndWrite || permission == MemoryPermission.Write) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid permission {permission}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPermission); - - return; - } - - _process.MemoryManager.ReserveTransferMemory(address, size, permission); - - KTransferMemory transferMemory = new KTransferMemory(address, size); - - KernelResult result = _process.HandleTable.GenerateHandle(transferMemory, out int handle); - - threadState.X0 = (uint)result; - threadState.X1 = (ulong)handle; - } - - private void SvcMapPhysicalMemory(CpuThreadState threadState) - { - ulong address = threadState.X0; - ulong size = threadState.X1; - - if (!PageAligned(address)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{address:x16} is not page aligned!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - if (!PageAligned(size) || size == 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{size:x16} is not page aligned or is zero!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); - - return; - } - - if (address + size <= address) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{address:x16} / size 0x{size:x16}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - if ((currentProcess.PersonalMmHeapPagesCount & 0xfffffffffffff) == 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"System resource size is zero."); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidState); - - return; - } - - if (!currentProcess.MemoryManager.InsideAddrSpace (address, size) || - currentProcess.MemoryManager.OutsideAliasRegion(address, size)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid address {address:x16}."); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - KernelResult result = _process.MemoryManager.MapPhysicalMemory(address, size); - - if (result != KernelResult.Success) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{result:x}!"); - } - - threadState.X0 = (ulong)result; - } - - private void SvcUnmapPhysicalMemory(CpuThreadState threadState) - { - ulong address = threadState.X0; - ulong size = threadState.X1; - - if (!PageAligned(address)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{address:x16} is not page aligned!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - if (!PageAligned(size) || size == 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{size:x16} is not page aligned or is zero!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); - - return; - } - - if (address + size <= address) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{address:x16} / size 0x{size:x16}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - if ((currentProcess.PersonalMmHeapPagesCount & 0xfffffffffffff) == 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"System resource size is zero."); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidState); - - return; - } - - if (!currentProcess.MemoryManager.InsideAddrSpace (address, size) || - currentProcess.MemoryManager.OutsideAliasRegion(address, size)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid address {address:x16}."); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - KernelResult result = _process.MemoryManager.UnmapPhysicalMemory(address, size); - - if (result != KernelResult.Success) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{result:x}!"); - } - - threadState.X0 = (ulong)result; - } - - private static bool PageAligned(ulong position) - { - return (position & (KMemoryManager.PageSize - 1)) == 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs b/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs deleted file mode 100644 index e42c2b4e..00000000 --- a/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs +++ /dev/null @@ -1,827 +0,0 @@ -using ChocolArm64.Memory; -using ChocolArm64.State; -using Ryujinx.Common; -using Ryujinx.Common.Logging; -using Ryujinx.HLE.Exceptions; -using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Services; -using System; -using System.Threading; - -using static Ryujinx.HLE.HOS.ErrorCode; - -namespace Ryujinx.HLE.HOS.Kernel -{ - partial class SvcHandler - { - private void SvcExitProcess(CpuThreadState threadState) - { - _system.Scheduler.GetCurrentProcess().Terminate(); - } - - private void SignalEvent64(CpuThreadState threadState) - { - threadState.X0 = (ulong)SignalEvent((int)threadState.X0); - } - - private KernelResult SignalEvent(int handle) - { - KWritableEvent writableEvent = _process.HandleTable.GetObject(handle); - - KernelResult result; - - if (writableEvent != null) - { - writableEvent.Signal(); - - result = KernelResult.Success; - } - else - { - result = KernelResult.InvalidHandle; - } - - if (result != KernelResult.Success) - { - Logger.PrintWarning(LogClass.KernelSvc, "Operation failed with error: " + result + "!"); - } - - return result; - } - - private void ClearEvent64(CpuThreadState threadState) - { - threadState.X0 = (ulong)ClearEvent((int)threadState.X0); - } - - private KernelResult ClearEvent(int handle) - { - KernelResult result; - - KWritableEvent writableEvent = _process.HandleTable.GetObject(handle); - - if (writableEvent == null) - { - KReadableEvent readableEvent = _process.HandleTable.GetObject(handle); - - result = readableEvent?.Clear() ?? KernelResult.InvalidHandle; - } - else - { - result = writableEvent.Clear(); - } - - if (result != KernelResult.Success) - { - Logger.PrintWarning(LogClass.KernelSvc, "Operation failed with error: " + result + "!"); - } - - return result; - } - - private void SvcCloseHandle(CpuThreadState threadState) - { - int handle = (int)threadState.X0; - - object obj = _process.HandleTable.GetObject(handle); - - _process.HandleTable.CloseHandle(handle); - - if (obj == null) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{handle:x8}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - if (obj is KSession session) - { - session.Dispose(); - } - else if (obj is KTransferMemory transferMemory) - { - _process.MemoryManager.ResetTransferMemory( - transferMemory.Address, - transferMemory.Size); - } - - threadState.X0 = 0; - } - - private void ResetSignal64(CpuThreadState threadState) - { - threadState.X0 = (ulong)ResetSignal((int)threadState.X0); - } - - private KernelResult ResetSignal(int handle) - { - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - KReadableEvent readableEvent = currentProcess.HandleTable.GetObject(handle); - - KernelResult result; - - if (readableEvent != null) - { - result = readableEvent.ClearIfSignaled(); - } - else - { - KProcess process = currentProcess.HandleTable.GetKProcess(handle); - - if (process != null) - { - result = process.ClearIfNotExited(); - } - else - { - result = KernelResult.InvalidHandle; - } - } - - if (result == KernelResult.InvalidState) - { - Logger.PrintDebug(LogClass.KernelSvc, "Operation failed with error: " + result + "!"); - } - else if (result != KernelResult.Success) - { - Logger.PrintWarning(LogClass.KernelSvc, "Operation failed with error: " + result + "!"); - } - - return result; - } - - private void SvcGetSystemTick(CpuThreadState threadState) - { - threadState.X0 = threadState.CntpctEl0; - } - - private void SvcConnectToNamedPort(CpuThreadState threadState) - { - long stackPtr = (long)threadState.X0; - long namePtr = (long)threadState.X1; - - string name = MemoryHelper.ReadAsciiString(_memory, namePtr, 8); - - //TODO: Validate that app has perms to access the service, and that the service - //actually exists, return error codes otherwise. - KSession session = new KSession(ServiceFactory.MakeService(_system, name), name); - - if (_process.HandleTable.GenerateHandle(session, out int handle) != KernelResult.Success) - { - throw new InvalidOperationException("Out of handles!"); - } - - threadState.X0 = 0; - threadState.X1 = (uint)handle; - } - - private void SvcSendSyncRequest(CpuThreadState threadState) - { - SendSyncRequest(threadState, threadState.Tpidr, 0x100, (int)threadState.X0); - } - - private void SvcSendSyncRequestWithUserBuffer(CpuThreadState threadState) - { - SendSyncRequest( - threadState, - (long)threadState.X0, - (long)threadState.X1, - (int)threadState.X2); - } - - private void SendSyncRequest(CpuThreadState threadState, long messagePtr, long size, int handle) - { - byte[] messageData = _memory.ReadBytes(messagePtr, size); - - KSession session = _process.HandleTable.GetObject(handle); - - if (session != null) - { - _system.CriticalSection.Enter(); - - KThread currentThread = _system.Scheduler.GetCurrentThread(); - - currentThread.SignaledObj = null; - currentThread.ObjSyncResult = 0; - - currentThread.Reschedule(ThreadSchedState.Paused); - - IpcMessage message = new IpcMessage(messageData, messagePtr); - - ThreadPool.QueueUserWorkItem(ProcessIpcRequest, new HleIpcMessage( - currentThread, - session, - message, - messagePtr)); - - _system.ThreadCounter.AddCount(); - - _system.CriticalSection.Leave(); - - threadState.X0 = (ulong)currentThread.ObjSyncResult; - } - else - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid session handle 0x{handle:x8}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - } - } - - private void ProcessIpcRequest(object state) - { - HleIpcMessage ipcMessage = (HleIpcMessage)state; - - ipcMessage.Thread.ObjSyncResult = (int)IpcHandler.IpcCall( - _device, - _process, - _memory, - ipcMessage.Session, - ipcMessage.Message, - ipcMessage.MessagePtr); - - _system.ThreadCounter.Signal(); - - ipcMessage.Thread.Reschedule(ThreadSchedState.Running); - } - - private void GetProcessId64(CpuThreadState threadState) - { - int handle = (int)threadState.X1; - - KernelResult result = GetProcessId(handle, out long pid); - - threadState.X0 = (ulong)result; - threadState.X1 = (ulong)pid; - } - - private KernelResult GetProcessId(int handle, out long pid) - { - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - KProcess process = currentProcess.HandleTable.GetKProcess(handle); - - if (process == null) - { - KThread thread = currentProcess.HandleTable.GetKThread(handle); - - if (thread != null) - { - process = thread.Owner; - } - - //TODO: KDebugEvent. - } - - pid = process?.Pid ?? 0; - - return process != null - ? KernelResult.Success - : KernelResult.InvalidHandle; - } - - private void SvcBreak(CpuThreadState threadState) - { - long reason = (long)threadState.X0; - long unknown = (long)threadState.X1; - long info = (long)threadState.X2; - - KThread currentThread = _system.Scheduler.GetCurrentThread(); - - if ((reason & (1 << 31)) == 0) - { - currentThread.PrintGuestStackTrace(); - - throw new GuestBrokeExecutionException(); - } - else - { - Logger.PrintInfo(LogClass.KernelSvc, "Debugger triggered."); - - currentThread.PrintGuestStackTrace(); - } - } - - private void SvcOutputDebugString(CpuThreadState threadState) - { - long position = (long)threadState.X0; - long size = (long)threadState.X1; - - string str = MemoryHelper.ReadAsciiString(_memory, position, size); - - Logger.PrintWarning(LogClass.KernelSvc, str); - - threadState.X0 = 0; - } - - private void GetInfo64(CpuThreadState threadState) - { - long stackPtr = (long)threadState.X0; - uint id = (uint)threadState.X1; - int handle = (int)threadState.X2; - long subId = (long)threadState.X3; - - KernelResult result = GetInfo(id, handle, subId, out long value); - - threadState.X0 = (ulong)result; - threadState.X1 = (ulong)value; - } - - private KernelResult GetInfo(uint id, int handle, long subId, out long value) - { - value = 0; - - switch (id) - { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - case 12: - case 13: - case 14: - case 15: - case 16: - case 17: - case 18: - case 20: - case 21: - case 22: - { - if (subId != 0) - { - return KernelResult.InvalidCombination; - } - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - KProcess process = currentProcess.HandleTable.GetKProcess(handle); - - if (process == null) - { - return KernelResult.InvalidHandle; - } - - switch (id) - { - case 0: value = process.Capabilities.AllowedCpuCoresMask; break; - case 1: value = process.Capabilities.AllowedThreadPriosMask; break; - - case 2: value = (long)process.MemoryManager.AliasRegionStart; break; - case 3: value = (long)(process.MemoryManager.AliasRegionEnd - - process.MemoryManager.AliasRegionStart); break; - - case 4: value = (long)process.MemoryManager.HeapRegionStart; break; - case 5: value = (long)(process.MemoryManager.HeapRegionEnd - - process.MemoryManager.HeapRegionStart); break; - - case 6: value = (long)process.GetMemoryCapacity(); break; - - case 7: value = (long)process.GetMemoryUsage(); break; - - case 12: value = (long)process.MemoryManager.GetAddrSpaceBaseAddr(); break; - - case 13: value = (long)process.MemoryManager.GetAddrSpaceSize(); break; - - case 14: value = (long)process.MemoryManager.StackRegionStart; break; - case 15: value = (long)(process.MemoryManager.StackRegionEnd - - process.MemoryManager.StackRegionStart); break; - - case 16: value = (long)process.PersonalMmHeapPagesCount * KMemoryManager.PageSize; break; - - case 17: - if (process.PersonalMmHeapPagesCount != 0) - { - value = process.MemoryManager.GetMmUsedPages() * KMemoryManager.PageSize; - } - - break; - - case 18: value = process.TitleId; break; - - case 20: value = (long)process.UserExceptionContextAddress; break; - - case 21: value = (long)process.GetMemoryCapacityWithoutPersonalMmHeap(); break; - - case 22: value = (long)process.GetMemoryUsageWithoutPersonalMmHeap(); break; - } - - break; - } - - case 8: - { - if (handle != 0) - { - return KernelResult.InvalidHandle; - } - - if (subId != 0) - { - return KernelResult.InvalidCombination; - } - - value = _system.Scheduler.GetCurrentProcess().Debug ? 1 : 0; - - break; - } - - case 9: - { - if (handle != 0) - { - return KernelResult.InvalidHandle; - } - - if (subId != 0) - { - return KernelResult.InvalidCombination; - } - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - if (currentProcess.ResourceLimit != null) - { - KHandleTable handleTable = currentProcess.HandleTable; - KResourceLimit resourceLimit = currentProcess.ResourceLimit; - - KernelResult result = handleTable.GenerateHandle(resourceLimit, out int resLimHandle); - - if (result != KernelResult.Success) - { - return result; - } - - value = (uint)resLimHandle; - } - - break; - } - - case 10: - { - if (handle != 0) - { - return KernelResult.InvalidHandle; - } - - int currentCore = _system.Scheduler.GetCurrentThread().CurrentCore; - - if (subId != -1 && subId != currentCore) - { - return KernelResult.InvalidCombination; - } - - value = _system.Scheduler.CoreContexts[currentCore].TotalIdleTimeTicks; - - break; - } - - case 11: - { - if (handle != 0) - { - return KernelResult.InvalidHandle; - } - - if ((ulong)subId > 3) - { - return KernelResult.InvalidCombination; - } - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - - value = currentProcess.RandomEntropy[subId]; - - break; - } - - case 0xf0000002u: - { - if (subId < -1 || subId > 3) - { - return KernelResult.InvalidCombination; - } - - KThread thread = _system.Scheduler.GetCurrentProcess().HandleTable.GetKThread(handle); - - if (thread == null) - { - return KernelResult.InvalidHandle; - } - - KThread currentThread = _system.Scheduler.GetCurrentThread(); - - int currentCore = currentThread.CurrentCore; - - if (subId != -1 && subId != currentCore) - { - return KernelResult.Success; - } - - KCoreContext coreContext = _system.Scheduler.CoreContexts[currentCore]; - - long timeDelta = PerformanceCounter.ElapsedMilliseconds - coreContext.LastContextSwitchTime; - - if (subId != -1) - { - value = KTimeManager.ConvertMillisecondsToTicks(timeDelta); - } - else - { - long totalTimeRunning = thread.TotalTimeRunning; - - if (thread == currentThread) - { - totalTimeRunning += timeDelta; - } - - value = KTimeManager.ConvertMillisecondsToTicks(totalTimeRunning); - } - - break; - } - - default: return KernelResult.InvalidEnumValue; - } - - return KernelResult.Success; - } - - private void CreateEvent64(CpuThreadState state) - { - KernelResult result = CreateEvent(out int wEventHandle, out int rEventHandle); - - state.X0 = (ulong)result; - state.X1 = (ulong)wEventHandle; - state.X2 = (ulong)rEventHandle; - } - - private KernelResult CreateEvent(out int wEventHandle, out int rEventHandle) - { - KEvent Event = new KEvent(_system); - - KernelResult result = _process.HandleTable.GenerateHandle(Event.WritableEvent, out wEventHandle); - - if (result == KernelResult.Success) - { - result = _process.HandleTable.GenerateHandle(Event.ReadableEvent, out rEventHandle); - - if (result != KernelResult.Success) - { - _process.HandleTable.CloseHandle(wEventHandle); - } - } - else - { - rEventHandle = 0; - } - - return result; - } - - private void GetProcessList64(CpuThreadState state) - { - ulong address = state.X1; - int maxOut = (int)state.X2; - - KernelResult result = GetProcessList(address, maxOut, out int count); - - state.X0 = (ulong)result; - state.X1 = (ulong)count; - } - - private KernelResult GetProcessList(ulong address, int maxCount, out int count) - { - count = 0; - - if ((maxCount >> 28) != 0) - { - return KernelResult.MaximumExceeded; - } - - if (maxCount != 0) - { - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - ulong copySize = (ulong)maxCount * 8; - - if (address + copySize <= address) - { - return KernelResult.InvalidMemState; - } - - if (currentProcess.MemoryManager.OutsideAddrSpace(address, copySize)) - { - return KernelResult.InvalidMemState; - } - } - - int copyCount = 0; - - lock (_system.Processes) - { - foreach (KProcess process in _system.Processes.Values) - { - if (copyCount < maxCount) - { - if (!KernelTransfer.KernelToUserInt64(_system, (long)address + copyCount * 8, process.Pid)) - { - return KernelResult.UserCopyFailed; - } - } - - copyCount++; - } - } - - count = copyCount; - - return KernelResult.Success; - } - - private void GetSystemInfo64(CpuThreadState state) - { - uint id = (uint)state.X1; - int handle = (int)state.X2; - long subId = (long)state.X3; - - KernelResult result = GetSystemInfo(id, handle, subId, out long value); - - state.X0 = (ulong)result; - state.X1 = (ulong)value; - } - - private KernelResult GetSystemInfo(uint id, int handle, long subId, out long value) - { - value = 0; - - if (id > 2) - { - return KernelResult.InvalidEnumValue; - } - - if (handle != 0) - { - return KernelResult.InvalidHandle; - } - - if (id < 2) - { - if ((ulong)subId > 3) - { - return KernelResult.InvalidCombination; - } - - KMemoryRegionManager region = _system.MemoryRegions[subId]; - - switch (id) - { - //Memory region capacity. - case 0: value = (long)region.Size; break; - - //Memory region free space. - case 1: - { - ulong freePagesCount = region.GetFreePages(); - - value = (long)(freePagesCount * KMemoryManager.PageSize); - - break; - } - } - } - else /* if (Id == 2) */ - { - if ((ulong)subId > 1) - { - return KernelResult.InvalidCombination; - } - - switch (subId) - { - case 0: value = _system.PrivilegedProcessLowestId; break; - case 1: value = _system.PrivilegedProcessHighestId; break; - } - } - - return KernelResult.Success; - } - - private void CreatePort64(CpuThreadState state) - { - int maxSessions = (int)state.X2; - bool isLight = (state.X3 & 1) != 0; - long nameAddress = (long)state.X4; - - KernelResult result = CreatePort( - maxSessions, - isLight, - nameAddress, - out int serverPortHandle, - out int clientPortHandle); - - state.X0 = (ulong)result; - state.X1 = (ulong)serverPortHandle; - state.X2 = (ulong)clientPortHandle; - } - - private KernelResult CreatePort( - int maxSessions, - bool isLight, - long nameAddress, - out int serverPortHandle, - out int clientPortHandle) - { - serverPortHandle = clientPortHandle = 0; - - if (maxSessions < 1) - { - return KernelResult.MaximumExceeded; - } - - KPort port = new KPort(_system); - - port.Initialize(maxSessions, isLight, nameAddress); - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ClientPort, out clientPortHandle); - - if (result != KernelResult.Success) - { - return result; - } - - result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out serverPortHandle); - - if (result != KernelResult.Success) - { - currentProcess.HandleTable.CloseHandle(clientPortHandle); - } - - return result; - } - - private void ManageNamedPort64(CpuThreadState state) - { - long nameAddress = (long)state.X1; - int maxSessions = (int)state.X2; - - KernelResult result = ManageNamedPort(nameAddress, maxSessions, out int handle); - - state.X0 = (ulong)result; - state.X1 = (ulong)handle; - } - - private KernelResult ManageNamedPort(long nameAddress, int maxSessions, out int handle) - { - handle = 0; - - if (!KernelTransfer.UserToKernelString(_system, nameAddress, 12, out string name)) - { - return KernelResult.UserCopyFailed; - } - - if (maxSessions < 0 || name.Length > 11) - { - return KernelResult.MaximumExceeded; - } - - if (maxSessions == 0) - { - return KClientPort.RemoveName(_system, name); - } - - KPort port = new KPort(_system); - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out handle); - - if (result != KernelResult.Success) - { - return result; - } - - port.Initialize(maxSessions, false, 0); - - result = port.SetName(name); - - if (result != KernelResult.Success) - { - currentProcess.HandleTable.CloseHandle(handle); - } - - return result; - } - } -} diff --git a/Ryujinx.HLE/HOS/Kernel/SvcThread.cs b/Ryujinx.HLE/HOS/Kernel/SvcThread.cs deleted file mode 100644 index 0121303d..00000000 --- a/Ryujinx.HLE/HOS/Kernel/SvcThread.cs +++ /dev/null @@ -1,464 +0,0 @@ -using ChocolArm64.State; -using Ryujinx.Common.Logging; - -using static Ryujinx.HLE.HOS.ErrorCode; - -namespace Ryujinx.HLE.HOS.Kernel -{ - partial class SvcHandler - { - private void CreateThread64(CpuThreadState threadState) - { - ulong entrypoint = threadState.X1; - ulong argsPtr = threadState.X2; - ulong stackTop = threadState.X3; - int priority = (int)threadState.X4; - int cpuCore = (int)threadState.X5; - - KernelResult result = CreateThread(entrypoint, argsPtr, stackTop, priority, cpuCore, out int handle); - - threadState.X0 = (ulong)result; - threadState.X1 = (ulong)handle; - } - - private KernelResult CreateThread( - ulong entrypoint, - ulong argsPtr, - ulong stackTop, - int priority, - int cpuCore, - out int handle) - { - handle = 0; - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - if (cpuCore == -2) - { - cpuCore = currentProcess.DefaultCpuCore; - } - - if ((uint)cpuCore >= KScheduler.CpuCoresCount || !currentProcess.IsCpuCoreAllowed(cpuCore)) - { - return KernelResult.InvalidCpuCore; - } - - if ((uint)priority >= KScheduler.PrioritiesCount || !currentProcess.IsPriorityAllowed(priority)) - { - return KernelResult.InvalidPriority; - } - - long timeout = KTimeManager.ConvertMillisecondsToNanoseconds(100); - - if (currentProcess.ResourceLimit != null && - !currentProcess.ResourceLimit.Reserve(LimitableResource.Thread, 1, timeout)) - { - return KernelResult.ResLimitExceeded; - } - - KThread thread = new KThread(_system); - - KernelResult result = currentProcess.InitializeThread( - thread, - entrypoint, - argsPtr, - stackTop, - priority, - cpuCore); - - if (result != KernelResult.Success) - { - currentProcess.ResourceLimit?.Release(LimitableResource.Thread, 1); - - return result; - } - - result = _process.HandleTable.GenerateHandle(thread, out handle); - - if (result != KernelResult.Success) - { - thread.Terminate(); - - currentProcess.ResourceLimit?.Release(LimitableResource.Thread, 1); - } - - return result; - } - - private void SvcStartThread(CpuThreadState threadState) - { - int handle = (int)threadState.X0; - - KThread thread = _process.HandleTable.GetObject(handle); - - if (thread != null) - { - KernelResult result = thread.Start(); - - if (result != KernelResult.Success) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error \"{result}\"."); - } - - threadState.X0 = (ulong)result; - } - else - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{handle:x8}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - } - } - - private void SvcExitThread(CpuThreadState threadState) - { - KThread currentThread = _system.Scheduler.GetCurrentThread(); - - _system.Scheduler.ExitThread(currentThread); - - currentThread.Exit(); - } - - private void SvcSleepThread(CpuThreadState threadState) - { - long timeout = (long)threadState.X0; - - Logger.PrintDebug(LogClass.KernelSvc, "Timeout = 0x" + timeout.ToString("x16")); - - KThread currentThread = _system.Scheduler.GetCurrentThread(); - - if (timeout < 1) - { - switch (timeout) - { - case 0: currentThread.Yield(); break; - case -1: currentThread.YieldWithLoadBalancing(); break; - case -2: currentThread.YieldAndWaitForLoadBalancing(); break; - } - } - else - { - currentThread.Sleep(timeout); - - threadState.X0 = 0; - } - } - - private void SvcGetThreadPriority(CpuThreadState threadState) - { - int handle = (int)threadState.X1; - - KThread thread = _process.HandleTable.GetKThread(handle); - - if (thread != null) - { - threadState.X0 = 0; - threadState.X1 = (ulong)thread.DynamicPriority; - } - else - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{handle:x8}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - } - } - - private void SvcSetThreadPriority(CpuThreadState threadState) - { - int handle = (int)threadState.X0; - int priority = (int)threadState.X1; - - Logger.PrintDebug(LogClass.KernelSvc, - "Handle = 0x" + handle .ToString("x8") + ", " + - "Priority = 0x" + priority.ToString("x8")); - - //TODO: NPDM check. - - KThread thread = _process.HandleTable.GetKThread(handle); - - if (thread == null) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{handle:x8}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - thread.SetPriority(priority); - - threadState.X0 = 0; - } - - private void SvcGetThreadCoreMask(CpuThreadState threadState) - { - int handle = (int)threadState.X2; - - Logger.PrintDebug(LogClass.KernelSvc, "Handle = 0x" + handle.ToString("x8")); - - KThread thread = _process.HandleTable.GetKThread(handle); - - if (thread != null) - { - threadState.X0 = 0; - threadState.X1 = (ulong)thread.PreferredCore; - threadState.X2 = (ulong)thread.AffinityMask; - } - else - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{handle:x8}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - } - } - - private void SetThreadCoreMask64(CpuThreadState threadState) - { - int handle = (int)threadState.X0; - int preferredCore = (int)threadState.X1; - long affinityMask = (long)threadState.X2; - - Logger.PrintDebug(LogClass.KernelSvc, - "Handle = 0x" + handle .ToString("x8") + ", " + - "PreferredCore = 0x" + preferredCore.ToString("x8") + ", " + - "AffinityMask = 0x" + affinityMask .ToString("x16")); - - KernelResult result = SetThreadCoreMask(handle, preferredCore, affinityMask); - - if (result != KernelResult.Success) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error \"{result}\"."); - } - - threadState.X0 = (ulong)result; - } - - private KernelResult SetThreadCoreMask(int handle, int preferredCore, long affinityMask) - { - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - if (preferredCore == -2) - { - preferredCore = currentProcess.DefaultCpuCore; - - affinityMask = 1 << preferredCore; - } - else - { - if ((currentProcess.Capabilities.AllowedCpuCoresMask | affinityMask) != - currentProcess.Capabilities.AllowedCpuCoresMask) - { - return KernelResult.InvalidCpuCore; - } - - if (affinityMask == 0) - { - return KernelResult.InvalidCombination; - } - - if ((uint)preferredCore > 3) - { - if ((preferredCore | 2) != -1) - { - return KernelResult.InvalidCpuCore; - } - } - else if ((affinityMask & (1 << preferredCore)) == 0) - { - return KernelResult.InvalidCombination; - } - } - - KThread thread = _process.HandleTable.GetKThread(handle); - - if (thread == null) - { - return KernelResult.InvalidHandle; - } - - return thread.SetCoreAndAffinityMask(preferredCore, affinityMask); - } - - private void SvcGetCurrentProcessorNumber(CpuThreadState threadState) - { - threadState.X0 = (ulong)_system.Scheduler.GetCurrentThread().CurrentCore; - } - - private void SvcGetThreadId(CpuThreadState threadState) - { - int handle = (int)threadState.X1; - - KThread thread = _process.HandleTable.GetKThread(handle); - - if (thread != null) - { - threadState.X0 = 0; - threadState.X1 = (ulong)thread.ThreadUid; - } - else - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{handle:x8}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - } - } - - private void SvcSetThreadActivity(CpuThreadState threadState) - { - int handle = (int)threadState.X0; - bool pause = (int)threadState.X1 == 1; - - KThread thread = _process.HandleTable.GetObject(handle); - - if (thread == null) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{handle:x8}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - if (thread.Owner != _system.Scheduler.GetCurrentProcess()) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread, it belongs to another process."); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - if (thread == _system.Scheduler.GetCurrentThread()) - { - Logger.PrintWarning(LogClass.KernelSvc, "Invalid thread, current thread is not accepted."); - - threadState.X0 = (ulong)KernelResult.InvalidThread; - - return; - } - - long result = thread.SetActivity(pause); - - if (result != 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{result:x}!"); - } - - threadState.X0 = (ulong)result; - } - - private void SvcGetThreadContext3(CpuThreadState threadState) - { - long position = (long)threadState.X0; - int handle = (int)threadState.X1; - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - KThread currentThread = _system.Scheduler.GetCurrentThread(); - - KThread thread = _process.HandleTable.GetObject(handle); - - if (thread == null) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{handle:x8}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - if (thread.Owner != currentProcess) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread, it belongs to another process."); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - if (currentThread == thread) - { - Logger.PrintWarning(LogClass.KernelSvc, "Invalid thread, current thread is not accepted."); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidThread); - - return; - } - - _memory.WriteUInt64(position + 0x0, thread.Context.ThreadState.X0); - _memory.WriteUInt64(position + 0x8, thread.Context.ThreadState.X1); - _memory.WriteUInt64(position + 0x10, thread.Context.ThreadState.X2); - _memory.WriteUInt64(position + 0x18, thread.Context.ThreadState.X3); - _memory.WriteUInt64(position + 0x20, thread.Context.ThreadState.X4); - _memory.WriteUInt64(position + 0x28, thread.Context.ThreadState.X5); - _memory.WriteUInt64(position + 0x30, thread.Context.ThreadState.X6); - _memory.WriteUInt64(position + 0x38, thread.Context.ThreadState.X7); - _memory.WriteUInt64(position + 0x40, thread.Context.ThreadState.X8); - _memory.WriteUInt64(position + 0x48, thread.Context.ThreadState.X9); - _memory.WriteUInt64(position + 0x50, thread.Context.ThreadState.X10); - _memory.WriteUInt64(position + 0x58, thread.Context.ThreadState.X11); - _memory.WriteUInt64(position + 0x60, thread.Context.ThreadState.X12); - _memory.WriteUInt64(position + 0x68, thread.Context.ThreadState.X13); - _memory.WriteUInt64(position + 0x70, thread.Context.ThreadState.X14); - _memory.WriteUInt64(position + 0x78, thread.Context.ThreadState.X15); - _memory.WriteUInt64(position + 0x80, thread.Context.ThreadState.X16); - _memory.WriteUInt64(position + 0x88, thread.Context.ThreadState.X17); - _memory.WriteUInt64(position + 0x90, thread.Context.ThreadState.X18); - _memory.WriteUInt64(position + 0x98, thread.Context.ThreadState.X19); - _memory.WriteUInt64(position + 0xa0, thread.Context.ThreadState.X20); - _memory.WriteUInt64(position + 0xa8, thread.Context.ThreadState.X21); - _memory.WriteUInt64(position + 0xb0, thread.Context.ThreadState.X22); - _memory.WriteUInt64(position + 0xb8, thread.Context.ThreadState.X23); - _memory.WriteUInt64(position + 0xc0, thread.Context.ThreadState.X24); - _memory.WriteUInt64(position + 0xc8, thread.Context.ThreadState.X25); - _memory.WriteUInt64(position + 0xd0, thread.Context.ThreadState.X26); - _memory.WriteUInt64(position + 0xd8, thread.Context.ThreadState.X27); - _memory.WriteUInt64(position + 0xe0, thread.Context.ThreadState.X28); - _memory.WriteUInt64(position + 0xe8, thread.Context.ThreadState.X29); - _memory.WriteUInt64(position + 0xf0, thread.Context.ThreadState.X30); - _memory.WriteUInt64(position + 0xf8, thread.Context.ThreadState.X31); - - _memory.WriteInt64(position + 0x100, thread.LastPc); - - _memory.WriteUInt64(position + 0x108, (ulong)thread.Context.ThreadState.Psr); - - _memory.WriteVector128(position + 0x110, thread.Context.ThreadState.V0); - _memory.WriteVector128(position + 0x120, thread.Context.ThreadState.V1); - _memory.WriteVector128(position + 0x130, thread.Context.ThreadState.V2); - _memory.WriteVector128(position + 0x140, thread.Context.ThreadState.V3); - _memory.WriteVector128(position + 0x150, thread.Context.ThreadState.V4); - _memory.WriteVector128(position + 0x160, thread.Context.ThreadState.V5); - _memory.WriteVector128(position + 0x170, thread.Context.ThreadState.V6); - _memory.WriteVector128(position + 0x180, thread.Context.ThreadState.V7); - _memory.WriteVector128(position + 0x190, thread.Context.ThreadState.V8); - _memory.WriteVector128(position + 0x1a0, thread.Context.ThreadState.V9); - _memory.WriteVector128(position + 0x1b0, thread.Context.ThreadState.V10); - _memory.WriteVector128(position + 0x1c0, thread.Context.ThreadState.V11); - _memory.WriteVector128(position + 0x1d0, thread.Context.ThreadState.V12); - _memory.WriteVector128(position + 0x1e0, thread.Context.ThreadState.V13); - _memory.WriteVector128(position + 0x1f0, thread.Context.ThreadState.V14); - _memory.WriteVector128(position + 0x200, thread.Context.ThreadState.V15); - _memory.WriteVector128(position + 0x210, thread.Context.ThreadState.V16); - _memory.WriteVector128(position + 0x220, thread.Context.ThreadState.V17); - _memory.WriteVector128(position + 0x230, thread.Context.ThreadState.V18); - _memory.WriteVector128(position + 0x240, thread.Context.ThreadState.V19); - _memory.WriteVector128(position + 0x250, thread.Context.ThreadState.V20); - _memory.WriteVector128(position + 0x260, thread.Context.ThreadState.V21); - _memory.WriteVector128(position + 0x270, thread.Context.ThreadState.V22); - _memory.WriteVector128(position + 0x280, thread.Context.ThreadState.V23); - _memory.WriteVector128(position + 0x290, thread.Context.ThreadState.V24); - _memory.WriteVector128(position + 0x2a0, thread.Context.ThreadState.V25); - _memory.WriteVector128(position + 0x2b0, thread.Context.ThreadState.V26); - _memory.WriteVector128(position + 0x2c0, thread.Context.ThreadState.V27); - _memory.WriteVector128(position + 0x2d0, thread.Context.ThreadState.V28); - _memory.WriteVector128(position + 0x2e0, thread.Context.ThreadState.V29); - _memory.WriteVector128(position + 0x2f0, thread.Context.ThreadState.V30); - _memory.WriteVector128(position + 0x300, thread.Context.ThreadState.V31); - - _memory.WriteInt32(position + 0x310, thread.Context.ThreadState.Fpcr); - _memory.WriteInt32(position + 0x314, thread.Context.ThreadState.Fpsr); - _memory.WriteInt64(position + 0x318, thread.Context.ThreadState.Tpidr); - - threadState.X0 = 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs b/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs deleted file mode 100644 index 11cfffe9..00000000 --- a/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs +++ /dev/null @@ -1,373 +0,0 @@ -using ChocolArm64.State; -using Ryujinx.Common.Logging; -using System.Collections.Generic; - -using static Ryujinx.HLE.HOS.ErrorCode; - -namespace Ryujinx.HLE.HOS.Kernel -{ - partial class SvcHandler - { - private void SvcWaitSynchronization(CpuThreadState threadState) - { - long handlesPtr = (long)threadState.X1; - int handlesCount = (int)threadState.X2; - long timeout = (long)threadState.X3; - - Logger.PrintDebug(LogClass.KernelSvc, - "HandlesPtr = 0x" + handlesPtr .ToString("x16") + ", " + - "HandlesCount = 0x" + handlesCount.ToString("x8") + ", " + - "Timeout = 0x" + timeout .ToString("x16")); - - if ((uint)handlesCount > 0x40) - { - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.CountOutOfRange); - - return; - } - - List syncObjs = new List(); - - for (int index = 0; index < handlesCount; index++) - { - int handle = _memory.ReadInt32(handlesPtr + index * 4); - - Logger.PrintDebug(LogClass.KernelSvc, $"Sync handle 0x{handle:x8}"); - - KSynchronizationObject syncObj = _process.HandleTable.GetObject(handle); - - if (syncObj == null) - { - break; - } - - syncObjs.Add(syncObj); - } - - int hndIndex = (int)threadState.X1; - - ulong high = threadState.X1 & (0xffffffffUL << 32); - - long result = _system.Synchronization.WaitFor(syncObjs.ToArray(), timeout, ref hndIndex); - - if (result != 0) - { - if (result == MakeError(ErrorModule.Kernel, KernelErr.Timeout) || - result == MakeError(ErrorModule.Kernel, KernelErr.Cancelled)) - { - Logger.PrintDebug(LogClass.KernelSvc, $"Operation failed with error 0x{result:x}!"); - } - else - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{result:x}!"); - } - } - - threadState.X0 = (ulong)result; - threadState.X1 = (uint)hndIndex | high; - } - - private void SvcCancelSynchronization(CpuThreadState threadState) - { - int threadHandle = (int)threadState.X0; - - Logger.PrintDebug(LogClass.KernelSvc, "ThreadHandle = 0x" + threadHandle.ToString("x8")); - - KThread thread = _process.HandleTable.GetKThread(threadHandle); - - if (thread == null) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{threadHandle:x8}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - thread.CancelSynchronization(); - - threadState.X0 = 0; - } - - private void SvcArbitrateLock(CpuThreadState threadState) - { - int ownerHandle = (int)threadState.X0; - long mutexAddress = (long)threadState.X1; - int requesterHandle = (int)threadState.X2; - - Logger.PrintDebug(LogClass.KernelSvc, - "OwnerHandle = 0x" + ownerHandle .ToString("x8") + ", " + - "MutexAddress = 0x" + mutexAddress .ToString("x16") + ", " + - "RequesterHandle = 0x" + requesterHandle.ToString("x8")); - - if (IsPointingInsideKernel(mutexAddress)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{mutexAddress:x16}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - if (IsAddressNotWordAligned(mutexAddress)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{mutexAddress:x16}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - long result = currentProcess.AddressArbiter.ArbitrateLock(ownerHandle, mutexAddress, requesterHandle); - - if (result != 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{result:x}!"); - } - - threadState.X0 = (ulong)result; - } - - private void SvcArbitrateUnlock(CpuThreadState threadState) - { - long mutexAddress = (long)threadState.X0; - - Logger.PrintDebug(LogClass.KernelSvc, "MutexAddress = 0x" + mutexAddress.ToString("x16")); - - if (IsPointingInsideKernel(mutexAddress)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{mutexAddress:x16}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - if (IsAddressNotWordAligned(mutexAddress)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{mutexAddress:x16}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - long result = currentProcess.AddressArbiter.ArbitrateUnlock(mutexAddress); - - if (result != 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{result:x}!"); - } - - threadState.X0 = (ulong)result; - } - - private void SvcWaitProcessWideKeyAtomic(CpuThreadState threadState) - { - long mutexAddress = (long)threadState.X0; - long condVarAddress = (long)threadState.X1; - int threadHandle = (int)threadState.X2; - long timeout = (long)threadState.X3; - - Logger.PrintDebug(LogClass.KernelSvc, - "MutexAddress = 0x" + mutexAddress .ToString("x16") + ", " + - "CondVarAddress = 0x" + condVarAddress.ToString("x16") + ", " + - "ThreadHandle = 0x" + threadHandle .ToString("x8") + ", " + - "Timeout = 0x" + timeout .ToString("x16")); - - if (IsPointingInsideKernel(mutexAddress)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{mutexAddress:x16}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - if (IsAddressNotWordAligned(mutexAddress)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Unaligned mutex address 0x{mutexAddress:x16}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - long result = currentProcess.AddressArbiter.WaitProcessWideKeyAtomic( - mutexAddress, - condVarAddress, - threadHandle, - timeout); - - if (result != 0) - { - if (result == MakeError(ErrorModule.Kernel, KernelErr.Timeout)) - { - Logger.PrintDebug(LogClass.KernelSvc, $"Operation failed with error 0x{result:x}!"); - } - else - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{result:x}!"); - } - } - - threadState.X0 = (ulong)result; - } - - private void SvcSignalProcessWideKey(CpuThreadState threadState) - { - long address = (long)threadState.X0; - int count = (int)threadState.X1; - - Logger.PrintDebug(LogClass.KernelSvc, - "Address = 0x" + address.ToString("x16") + ", " + - "Count = 0x" + count .ToString("x8")); - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - currentProcess.AddressArbiter.SignalProcessWideKey(address, count); - - threadState.X0 = 0; - } - - private void SvcWaitForAddress(CpuThreadState threadState) - { - long address = (long)threadState.X0; - ArbitrationType type = (ArbitrationType)threadState.X1; - int value = (int)threadState.X2; - long timeout = (long)threadState.X3; - - Logger.PrintDebug(LogClass.KernelSvc, - "Address = 0x" + address.ToString("x16") + ", " + - "Type = " + type .ToString() + ", " + - "Value = 0x" + value .ToString("x8") + ", " + - "Timeout = 0x" + timeout.ToString("x16")); - - if (IsPointingInsideKernel(address)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid address 0x{address:x16}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - if (IsAddressNotWordAligned(address)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Unaligned address 0x{address:x16}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - long result; - - switch (type) - { - case ArbitrationType.WaitIfLessThan: - result = currentProcess.AddressArbiter.WaitForAddressIfLessThan(address, value, false, timeout); - break; - - case ArbitrationType.DecrementAndWaitIfLessThan: - result = currentProcess.AddressArbiter.WaitForAddressIfLessThan(address, value, true, timeout); - break; - - case ArbitrationType.WaitIfEqual: - result = currentProcess.AddressArbiter.WaitForAddressIfEqual(address, value, timeout); - break; - - default: - result = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue); - break; - } - - if (result != 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{result:x}!"); - } - - threadState.X0 = (ulong)result; - } - - private void SvcSignalToAddress(CpuThreadState threadState) - { - long address = (long)threadState.X0; - SignalType type = (SignalType)threadState.X1; - int value = (int)threadState.X2; - int count = (int)threadState.X3; - - Logger.PrintDebug(LogClass.KernelSvc, - "Address = 0x" + address.ToString("x16") + ", " + - "Type = " + type .ToString() + ", " + - "Value = 0x" + value .ToString("x8") + ", " + - "Count = 0x" + count .ToString("x8")); - - if (IsPointingInsideKernel(address)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid address 0x{address:x16}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - - return; - } - - if (IsAddressNotWordAligned(address)) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Unaligned address 0x{address:x16}!"); - - threadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); - - return; - } - - KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); - - long result; - - switch (type) - { - case SignalType.Signal: - result = currentProcess.AddressArbiter.Signal(address, count); - break; - - case SignalType.SignalAndIncrementIfEqual: - result = currentProcess.AddressArbiter.SignalAndIncrementIfEqual(address, value, count); - break; - - case SignalType.SignalAndModifyIfEqual: - result = currentProcess.AddressArbiter.SignalAndModifyIfEqual(address, value, count); - break; - - default: - result = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue); - break; - } - - if (result != 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{result:x}!"); - } - - threadState.X0 = (ulong)result; - } - - private bool IsPointingInsideKernel(long address) - { - return ((ulong)address + 0x1000000000) < 0xffffff000; - } - - private bool IsAddressNotWordAligned(long address) - { - return (address & 3) != 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/ThreadSchedState.cs b/Ryujinx.HLE/HOS/Kernel/ThreadSchedState.cs deleted file mode 100644 index 37e5908a..00000000 --- a/Ryujinx.HLE/HOS/Kernel/ThreadSchedState.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - enum ThreadSchedState : ushort - { - LowMask = 0xf, - HighMask = 0xfff0, - ForcePauseMask = 0x70, - - ProcessPauseFlag = 1 << 4, - ThreadPauseFlag = 1 << 5, - ProcessDebugPauseFlag = 1 << 6, - KernelInitPauseFlag = 1 << 8, - - None = 0, - Paused = 1, - Running = 2, - TerminationPending = 3 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/ThreadType.cs b/Ryujinx.HLE/HOS/Kernel/ThreadType.cs deleted file mode 100644 index 0fe83423..00000000 --- a/Ryujinx.HLE/HOS/Kernel/ThreadType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Ryujinx.HLE.HOS.Kernel -{ - enum ThreadType - { - Dummy, - Kernel, - Kernel2, - User - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs b/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs new file mode 100644 index 00000000..89c1bf1f --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + enum ArbitrationType + { + WaitIfLessThan = 0, + DecrementAndWaitIfLessThan = 1, + WaitIfEqual = 2 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs b/Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs new file mode 100644 index 00000000..c2597990 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs @@ -0,0 +1,66 @@ +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class HleCoreManager + { + private class PausableThread + { + public ManualResetEvent Event { get; private set; } + + public bool IsExiting { get; set; } + + public PausableThread() + { + Event = new ManualResetEvent(false); + } + } + + private ConcurrentDictionary _threads; + + public HleCoreManager() + { + _threads = new ConcurrentDictionary(); + } + + public void Set(Thread thread) + { + GetThread(thread).Event.Set(); + } + + public void Reset(Thread thread) + { + GetThread(thread).Event.Reset(); + } + + public void Wait(Thread thread) + { + PausableThread pausableThread = GetThread(thread); + + if (!pausableThread.IsExiting) + { + pausableThread.Event.WaitOne(); + } + } + + public void Exit(Thread thread) + { + GetThread(thread).IsExiting = true; + } + + private PausableThread GetThread(Thread thread) + { + return _threads.GetOrAdd(thread, (key) => new PausableThread()); + } + + public void RemoveThread(Thread thread) + { + if (_threads.TryRemove(thread, out PausableThread pausableThread)) + { + pausableThread.Event.Set(); + pausableThread.Event.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs b/Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs new file mode 100644 index 00000000..835c2a2f --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs @@ -0,0 +1,149 @@ +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + partial class KScheduler + { + private const int RoundRobinTimeQuantumMs = 10; + + private int _currentCore; + + public bool MultiCoreScheduling { get; set; } + + public HleCoreManager CoreManager { get; private set; } + + private bool _keepPreempting; + + public void StartAutoPreemptionThread() + { + Thread preemptionThread = new Thread(PreemptCurrentThread); + + _keepPreempting = true; + + preemptionThread.Start(); + } + + public void ContextSwitch() + { + lock (CoreContexts) + { + if (MultiCoreScheduling) + { + int selectedCount = 0; + + for (int core = 0; core < CpuCoresCount; core++) + { + KCoreContext coreContext = CoreContexts[core]; + + if (coreContext.ContextSwitchNeeded && (coreContext.CurrentThread?.Context.IsCurrentThread() ?? false)) + { + coreContext.ContextSwitch(); + } + + if (coreContext.CurrentThread?.Context.IsCurrentThread() ?? false) + { + selectedCount++; + } + } + + if (selectedCount == 0) + { + CoreManager.Reset(Thread.CurrentThread); + } + else if (selectedCount == 1) + { + CoreManager.Set(Thread.CurrentThread); + } + else + { + throw new InvalidOperationException("Thread scheduled in more than one core!"); + } + } + else + { + KThread currentThread = CoreContexts[_currentCore].CurrentThread; + + bool hasThreadExecuting = currentThread != null; + + if (hasThreadExecuting) + { + //If this is not the thread that is currently executing, we need + //to request an interrupt to allow safely starting another thread. + if (!currentThread.Context.IsCurrentThread()) + { + currentThread.Context.RequestInterrupt(); + + return; + } + + CoreManager.Reset(currentThread.Context.Work); + } + + //Advance current core and try picking a thread, + //keep advancing if it is null. + for (int core = 0; core < 4; core++) + { + _currentCore = (_currentCore + 1) % CpuCoresCount; + + KCoreContext coreContext = CoreContexts[_currentCore]; + + coreContext.UpdateCurrentThread(); + + if (coreContext.CurrentThread != null) + { + coreContext.CurrentThread.ClearExclusive(); + + CoreManager.Set(coreContext.CurrentThread.Context.Work); + + coreContext.CurrentThread.Context.Execute(); + + break; + } + } + + //If nothing was running before, then we are on a "external" + //HLE thread, we don't need to wait. + if (!hasThreadExecuting) + { + return; + } + } + } + + CoreManager.Wait(Thread.CurrentThread); + } + + private void PreemptCurrentThread() + { + //Preempts current thread every 10 milliseconds on a round-robin fashion, + //when multi core scheduling is disabled, to try ensuring that all threads + //gets a chance to run. + while (_keepPreempting) + { + lock (CoreContexts) + { + KThread currentThread = CoreContexts[_currentCore].CurrentThread; + + currentThread?.Context.RequestInterrupt(); + } + + PreemptThreads(); + + Thread.Sleep(RoundRobinTimeQuantumMs); + } + } + + public void ExitThread(KThread thread) + { + thread.Context.StopExecution(); + + CoreManager.Exit(thread.Context.Work); + } + + public void RemoveThread(KThread thread) + { + CoreManager.RemoveThread(thread.Context.Work); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs new file mode 100644 index 00000000..faeea5c5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs @@ -0,0 +1,654 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KAddressArbiter + { + private const int HasListenersMask = 0x40000000; + + private Horizon _system; + + public List CondVarThreads; + public List ArbiterThreads; + + public KAddressArbiter(Horizon system) + { + _system = system; + + CondVarThreads = new List(); + ArbiterThreads = new List(); + } + + public KernelResult ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle) + { + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + _system.CriticalSection.Enter(); + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.Success; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + if (!KernelTransfer.UserToKernelInt32(_system, mutexAddress, out int mutexValue)) + { + _system.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + if (mutexValue != (ownerHandle | HasListenersMask)) + { + _system.CriticalSection.Leave(); + + return 0; + } + + KThread mutexOwner = currentProcess.HandleTable.GetObject(ownerHandle); + + if (mutexOwner == null) + { + _system.CriticalSection.Leave(); + + return KernelResult.InvalidHandle; + } + + currentThread.MutexAddress = mutexAddress; + currentThread.ThreadHandleForUserMutex = requesterHandle; + + mutexOwner.AddMutexWaiter(currentThread); + + currentThread.Reschedule(ThreadSchedState.Paused); + + _system.CriticalSection.Leave(); + _system.CriticalSection.Enter(); + + if (currentThread.MutexOwner != null) + { + currentThread.MutexOwner.RemoveMutexWaiter(currentThread); + } + + _system.CriticalSection.Leave(); + + return (KernelResult)currentThread.ObjSyncResult; + } + + public KernelResult ArbitrateUnlock(ulong mutexAddress) + { + _system.CriticalSection.Enter(); + + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + (KernelResult result, KThread newOwnerThread) = MutexUnlock(currentThread, mutexAddress); + + if (result != KernelResult.Success && newOwnerThread != null) + { + newOwnerThread.SignaledObj = null; + newOwnerThread.ObjSyncResult = result; + } + + _system.CriticalSection.Leave(); + + return result; + } + + public KernelResult WaitProcessWideKeyAtomic( + ulong mutexAddress, + ulong condVarAddress, + int threadHandle, + long timeout) + { + _system.CriticalSection.Enter(); + + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.TimedOut; + + if (currentThread.ShallBeTerminated || + currentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + _system.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + (KernelResult result, _) = MutexUnlock(currentThread, mutexAddress); + + if (result != KernelResult.Success) + { + _system.CriticalSection.Leave(); + + return result; + } + + currentThread.MutexAddress = mutexAddress; + currentThread.ThreadHandleForUserMutex = threadHandle; + currentThread.CondVarAddress = condVarAddress; + + CondVarThreads.Add(currentThread); + + if (timeout != 0) + { + currentThread.Reschedule(ThreadSchedState.Paused); + + if (timeout > 0) + { + _system.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + } + + _system.CriticalSection.Leave(); + + if (timeout > 0) + { + _system.TimeManager.UnscheduleFutureInvocation(currentThread); + } + + _system.CriticalSection.Enter(); + + if (currentThread.MutexOwner != null) + { + currentThread.MutexOwner.RemoveMutexWaiter(currentThread); + } + + CondVarThreads.Remove(currentThread); + + _system.CriticalSection.Leave(); + + return (KernelResult)currentThread.ObjSyncResult; + } + + private (KernelResult, KThread) MutexUnlock(KThread currentThread, ulong mutexAddress) + { + KThread newOwnerThread = currentThread.RelinquishMutex(mutexAddress, out int count); + + int mutexValue = 0; + + if (newOwnerThread != null) + { + mutexValue = newOwnerThread.ThreadHandleForUserMutex; + + if (count >= 2) + { + mutexValue |= HasListenersMask; + } + + newOwnerThread.SignaledObj = null; + newOwnerThread.ObjSyncResult = KernelResult.Success; + + newOwnerThread.ReleaseAndResume(); + } + + KernelResult result = KernelResult.Success; + + if (!KernelTransfer.KernelToUserInt32(_system, mutexAddress, mutexValue)) + { + result = KernelResult.InvalidMemState; + } + + return (result, newOwnerThread); + } + + public void SignalProcessWideKey(ulong address, int count) + { + Queue signaledThreads = new Queue(); + + _system.CriticalSection.Enter(); + + IOrderedEnumerable sortedThreads = CondVarThreads.OrderBy(x => x.DynamicPriority); + + foreach (KThread thread in sortedThreads.Where(x => x.CondVarAddress == address)) + { + TryAcquireMutex(thread); + + signaledThreads.Enqueue(thread); + + //If the count is <= 0, we should signal all threads waiting. + if (count >= 1 && --count == 0) + { + break; + } + } + + while (signaledThreads.TryDequeue(out KThread thread)) + { + CondVarThreads.Remove(thread); + } + + _system.CriticalSection.Leave(); + } + + private KThread TryAcquireMutex(KThread requester) + { + ulong address = requester.MutexAddress; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + currentProcess.CpuMemory.SetExclusive(0, (long)address); + + if (!KernelTransfer.UserToKernelInt32(_system, address, out int mutexValue)) + { + //Invalid address. + currentProcess.CpuMemory.ClearExclusive(0); + + requester.SignaledObj = null; + requester.ObjSyncResult = KernelResult.InvalidMemState; + + return null; + } + + while (true) + { + if (currentProcess.CpuMemory.TestExclusive(0, (long)address)) + { + if (mutexValue != 0) + { + //Update value to indicate there is a mutex waiter now. + currentProcess.CpuMemory.WriteInt32((long)address, mutexValue | HasListenersMask); + } + else + { + //No thread owning the mutex, assign to requesting thread. + currentProcess.CpuMemory.WriteInt32((long)address, requester.ThreadHandleForUserMutex); + } + + currentProcess.CpuMemory.ClearExclusiveForStore(0); + + break; + } + + currentProcess.CpuMemory.SetExclusive(0, (long)address); + + mutexValue = currentProcess.CpuMemory.ReadInt32((long)address); + } + + if (mutexValue == 0) + { + //We now own the mutex. + requester.SignaledObj = null; + requester.ObjSyncResult = KernelResult.Success; + + requester.ReleaseAndResume(); + + return null; + } + + mutexValue &= ~HasListenersMask; + + KThread mutexOwner = currentProcess.HandleTable.GetObject(mutexValue); + + if (mutexOwner != null) + { + //Mutex already belongs to another thread, wait for it. + mutexOwner.AddMutexWaiter(requester); + } + else + { + //Invalid mutex owner. + requester.SignaledObj = null; + requester.ObjSyncResult = KernelResult.InvalidHandle; + + requester.ReleaseAndResume(); + } + + return mutexOwner; + } + + public KernelResult WaitForAddressIfEqual(ulong address, int value, long timeout) + { + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + _system.CriticalSection.Enter(); + + if (currentThread.ShallBeTerminated || + currentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + _system.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.TimedOut; + + if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue)) + { + _system.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + if (currentValue == value) + { + if (timeout == 0) + { + _system.CriticalSection.Leave(); + + return KernelResult.TimedOut; + } + + currentThread.MutexAddress = address; + currentThread.WaitingInArbitration = true; + + InsertSortedByPriority(ArbiterThreads, currentThread); + + currentThread.Reschedule(ThreadSchedState.Paused); + + if (timeout > 0) + { + _system.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + + _system.CriticalSection.Leave(); + + if (timeout > 0) + { + _system.TimeManager.UnscheduleFutureInvocation(currentThread); + } + + _system.CriticalSection.Enter(); + + if (currentThread.WaitingInArbitration) + { + ArbiterThreads.Remove(currentThread); + + currentThread.WaitingInArbitration = false; + } + + _system.CriticalSection.Leave(); + + return (KernelResult)currentThread.ObjSyncResult; + } + + _system.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + public KernelResult WaitForAddressIfLessThan( + ulong address, + int value, + bool shouldDecrement, + long timeout) + { + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + _system.CriticalSection.Enter(); + + if (currentThread.ShallBeTerminated || + currentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + _system.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.TimedOut; + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + //If ShouldDecrement is true, do atomic decrement of the value at Address. + currentProcess.CpuMemory.SetExclusive(0, (long)address); + + if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue)) + { + _system.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + if (shouldDecrement) + { + while (currentValue < value) + { + if (currentProcess.CpuMemory.TestExclusive(0, (long)address)) + { + currentProcess.CpuMemory.WriteInt32((long)address, currentValue - 1); + + currentProcess.CpuMemory.ClearExclusiveForStore(0); + + break; + } + + currentProcess.CpuMemory.SetExclusive(0, (long)address); + + currentValue = currentProcess.CpuMemory.ReadInt32((long)address); + } + } + + currentProcess.CpuMemory.ClearExclusive(0); + + if (currentValue < value) + { + if (timeout == 0) + { + _system.CriticalSection.Leave(); + + return KernelResult.TimedOut; + } + + currentThread.MutexAddress = address; + currentThread.WaitingInArbitration = true; + + InsertSortedByPriority(ArbiterThreads, currentThread); + + currentThread.Reschedule(ThreadSchedState.Paused); + + if (timeout > 0) + { + _system.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + + _system.CriticalSection.Leave(); + + if (timeout > 0) + { + _system.TimeManager.UnscheduleFutureInvocation(currentThread); + } + + _system.CriticalSection.Enter(); + + if (currentThread.WaitingInArbitration) + { + ArbiterThreads.Remove(currentThread); + + currentThread.WaitingInArbitration = false; + } + + _system.CriticalSection.Leave(); + + return (KernelResult)currentThread.ObjSyncResult; + } + + _system.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + private void InsertSortedByPriority(List threads, KThread thread) + { + int nextIndex = -1; + + for (int index = 0; index < threads.Count; index++) + { + if (threads[index].DynamicPriority > thread.DynamicPriority) + { + nextIndex = index; + + break; + } + } + + if (nextIndex != -1) + { + threads.Insert(nextIndex, thread); + } + else + { + threads.Add(thread); + } + } + + public KernelResult Signal(ulong address, int count) + { + _system.CriticalSection.Enter(); + + WakeArbiterThreads(address, count); + + _system.CriticalSection.Leave(); + + return KernelResult.Success; + } + + public KernelResult SignalAndIncrementIfEqual(ulong address, int value, int count) + { + _system.CriticalSection.Enter(); + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + currentProcess.CpuMemory.SetExclusive(0, (long)address); + + if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue)) + { + _system.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + while (currentValue == value) + { + if (currentProcess.CpuMemory.TestExclusive(0, (long)address)) + { + currentProcess.CpuMemory.WriteInt32((long)address, currentValue + 1); + + currentProcess.CpuMemory.ClearExclusiveForStore(0); + + break; + } + + currentProcess.CpuMemory.SetExclusive(0, (long)address); + + currentValue = currentProcess.CpuMemory.ReadInt32((long)address); + } + + currentProcess.CpuMemory.ClearExclusive(0); + + if (currentValue != value) + { + _system.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + WakeArbiterThreads(address, count); + + _system.CriticalSection.Leave(); + + return KernelResult.Success; + } + + public KernelResult SignalAndModifyIfEqual(ulong address, int value, int count) + { + _system.CriticalSection.Enter(); + + int offset; + + //The value is decremented if the number of threads waiting is less + //or equal to the Count of threads to be signaled, or Count is zero + //or negative. It is incremented if there are no threads waiting. + int waitingCount = 0; + + foreach (KThread thread in ArbiterThreads.Where(x => x.MutexAddress == address)) + { + if (++waitingCount > count) + { + break; + } + } + + if (waitingCount > 0) + { + offset = waitingCount <= count || count <= 0 ? -1 : 0; + } + else + { + offset = 1; + } + + KProcess currentProcess = _system.Scheduler.GetCurrentProcess(); + + currentProcess.CpuMemory.SetExclusive(0, (long)address); + + if (!KernelTransfer.UserToKernelInt32(_system, address, out int currentValue)) + { + _system.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + while (currentValue == value) + { + if (currentProcess.CpuMemory.TestExclusive(0, (long)address)) + { + currentProcess.CpuMemory.WriteInt32((long)address, currentValue + offset); + + currentProcess.CpuMemory.ClearExclusiveForStore(0); + + break; + } + + currentProcess.CpuMemory.SetExclusive(0, (long)address); + + currentValue = currentProcess.CpuMemory.ReadInt32((long)address); + } + + currentProcess.CpuMemory.ClearExclusive(0); + + if (currentValue != value) + { + _system.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + WakeArbiterThreads(address, count); + + _system.CriticalSection.Leave(); + + return KernelResult.Success; + } + + private void WakeArbiterThreads(ulong address, int count) + { + Queue signaledThreads = new Queue(); + + foreach (KThread thread in ArbiterThreads.Where(x => x.MutexAddress == address)) + { + signaledThreads.Enqueue(thread); + + //If the count is <= 0, we should signal all threads waiting. + if (count >= 1 && --count == 0) + { + break; + } + } + + while (signaledThreads.TryDequeue(out KThread thread)) + { + thread.SignaledObj = null; + thread.ObjSyncResult = KernelResult.Success; + + thread.ReleaseAndResume(); + + thread.WaitingInArbitration = false; + + ArbiterThreads.Remove(thread); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs new file mode 100644 index 00000000..41473643 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + static class KConditionVariable + { + public static void Wait(Horizon system, LinkedList threadList, object mutex, long timeout) + { + KThread currentThread = system.Scheduler.GetCurrentThread(); + + system.CriticalSection.Enter(); + + Monitor.Exit(mutex); + + currentThread.Withholder = threadList; + + currentThread.Reschedule(ThreadSchedState.Paused); + + currentThread.WithholderNode = threadList.AddLast(currentThread); + + if (currentThread.ShallBeTerminated || + currentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + threadList.Remove(currentThread.WithholderNode); + + currentThread.Reschedule(ThreadSchedState.Running); + + currentThread.Withholder = null; + + system.CriticalSection.Leave(); + } + else + { + if (timeout > 0) + { + system.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + + system.CriticalSection.Leave(); + + if (timeout > 0) + { + system.TimeManager.UnscheduleFutureInvocation(currentThread); + } + } + + Monitor.Enter(mutex); + } + + public static void NotifyAll(Horizon system, LinkedList threadList) + { + system.CriticalSection.Enter(); + + LinkedListNode node = threadList.First; + + for (; node != null; node = threadList.First) + { + KThread thread = node.Value; + + threadList.Remove(thread.WithholderNode); + + thread.Withholder = null; + + thread.Reschedule(ThreadSchedState.Running); + } + + system.CriticalSection.Leave(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs new file mode 100644 index 00000000..81cd8883 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs @@ -0,0 +1,81 @@ +using Ryujinx.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KCoreContext + { + private KScheduler _scheduler; + + private HleCoreManager _coreManager; + + public bool ContextSwitchNeeded { get; private set; } + + public long LastContextSwitchTime { get; private set; } + + public long TotalIdleTimeTicks { get; private set; } //TODO + + public KThread CurrentThread { get; private set; } + public KThread SelectedThread { get; private set; } + + public KCoreContext(KScheduler scheduler, HleCoreManager coreManager) + { + _scheduler = scheduler; + _coreManager = coreManager; + } + + public void SelectThread(KThread thread) + { + SelectedThread = thread; + + if (SelectedThread != CurrentThread) + { + ContextSwitchNeeded = true; + } + } + + public void UpdateCurrentThread() + { + ContextSwitchNeeded = false; + + LastContextSwitchTime = PerformanceCounter.ElapsedMilliseconds; + + CurrentThread = SelectedThread; + + if (CurrentThread != null) + { + long currentTime = PerformanceCounter.ElapsedMilliseconds; + + CurrentThread.TotalTimeRunning += currentTime - CurrentThread.LastScheduledTime; + CurrentThread.LastScheduledTime = currentTime; + } + } + + public void ContextSwitch() + { + ContextSwitchNeeded = false; + + LastContextSwitchTime = PerformanceCounter.ElapsedMilliseconds; + + if (CurrentThread != null) + { + _coreManager.Reset(CurrentThread.Context.Work); + } + + CurrentThread = SelectedThread; + + if (CurrentThread != null) + { + long currentTime = PerformanceCounter.ElapsedMilliseconds; + + CurrentThread.TotalTimeRunning += currentTime - CurrentThread.LastScheduledTime; + CurrentThread.LastScheduledTime = currentTime; + + CurrentThread.ClearExclusive(); + + _coreManager.Set(CurrentThread.Context.Work); + + CurrentThread.Context.Execute(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs new file mode 100644 index 00000000..841d0d69 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs @@ -0,0 +1,93 @@ +using ChocolArm64; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KCriticalSection + { + private Horizon _system; + + public object LockObj { get; private set; } + + private int _recursionCount; + + public KCriticalSection(Horizon system) + { + _system = system; + + LockObj = new object(); + } + + public void Enter() + { + Monitor.Enter(LockObj); + + _recursionCount++; + } + + public void Leave() + { + if (_recursionCount == 0) + { + return; + } + + bool doContextSwitch = false; + + if (--_recursionCount == 0) + { + if (_system.Scheduler.ThreadReselectionRequested) + { + _system.Scheduler.SelectThreads(); + } + + Monitor.Exit(LockObj); + + if (_system.Scheduler.MultiCoreScheduling) + { + lock (_system.Scheduler.CoreContexts) + { + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + KCoreContext coreContext = _system.Scheduler.CoreContexts[core]; + + if (coreContext.ContextSwitchNeeded) + { + CpuThread currentHleThread = coreContext.CurrentThread?.Context; + + if (currentHleThread == null) + { + //Nothing is running, we can perform the context switch immediately. + coreContext.ContextSwitch(); + } + else if (currentHleThread.IsCurrentThread()) + { + //Thread running on the current core, context switch will block. + doContextSwitch = true; + } + else + { + //Thread running on another core, request a interrupt. + currentHleThread.RequestInterrupt(); + } + } + } + } + } + else + { + doContextSwitch = true; + } + } + else + { + Monitor.Exit(LockObj); + } + + if (doContextSwitch) + { + _system.Scheduler.ContextSwitch(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs new file mode 100644 index 00000000..5bdb9c1d --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KEvent + { + public KReadableEvent ReadableEvent { get; private set; } + public KWritableEvent WritableEvent { get; private set; } + + public KEvent(Horizon system) + { + ReadableEvent = new KReadableEvent(system, this); + WritableEvent = new KWritableEvent(this); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs new file mode 100644 index 00000000..9821de35 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs @@ -0,0 +1,64 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KReadableEvent : KSynchronizationObject + { + private KEvent _parent; + + private bool _signaled; + + public KReadableEvent(Horizon system, KEvent parent) : base(system) + { + _parent = parent; + } + + public override void Signal() + { + System.CriticalSection.Enter(); + + if (!_signaled) + { + _signaled = true; + + base.Signal(); + } + + System.CriticalSection.Leave(); + } + + public KernelResult Clear() + { + _signaled = false; + + return KernelResult.Success; + } + + public KernelResult ClearIfSignaled() + { + KernelResult result; + + System.CriticalSection.Enter(); + + if (_signaled) + { + _signaled = false; + + result = KernelResult.Success; + } + else + { + result = KernelResult.InvalidState; + } + + System.CriticalSection.Leave(); + + return result; + } + + public override bool IsSignaled() + { + return _signaled; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs new file mode 100644 index 00000000..60e15efa --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs @@ -0,0 +1,234 @@ +using Ryujinx.HLE.HOS.Kernel.Process; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + partial class KScheduler : IDisposable + { + public const int PrioritiesCount = 64; + public const int CpuCoresCount = 4; + + private const int PreemptionPriorityCores012 = 59; + private const int PreemptionPriorityCore3 = 63; + + private Horizon _system; + + public KSchedulingData SchedulingData { get; private set; } + + public KCoreContext[] CoreContexts { get; private set; } + + public bool ThreadReselectionRequested { get; set; } + + public KScheduler(Horizon system) + { + _system = system; + + SchedulingData = new KSchedulingData(); + + CoreManager = new HleCoreManager(); + + CoreContexts = new KCoreContext[CpuCoresCount]; + + for (int core = 0; core < CpuCoresCount; core++) + { + CoreContexts[core] = new KCoreContext(this, CoreManager); + } + } + + private void PreemptThreads() + { + _system.CriticalSection.Enter(); + + PreemptThread(PreemptionPriorityCores012, 0); + PreemptThread(PreemptionPriorityCores012, 1); + PreemptThread(PreemptionPriorityCores012, 2); + PreemptThread(PreemptionPriorityCore3, 3); + + _system.CriticalSection.Leave(); + } + + private void PreemptThread(int prio, int core) + { + IEnumerable scheduledThreads = SchedulingData.ScheduledThreads(core); + + KThread selectedThread = scheduledThreads.FirstOrDefault(x => x.DynamicPriority == prio); + + //Yield priority queue. + if (selectedThread != null) + { + SchedulingData.Reschedule(prio, core, selectedThread); + } + + IEnumerable SuitableCandidates() + { + foreach (KThread thread in SchedulingData.SuggestedThreads(core)) + { + int srcCore = thread.CurrentCore; + + if (srcCore >= 0) + { + KThread highestPrioSrcCore = SchedulingData.ScheduledThreads(srcCore).FirstOrDefault(); + + if (highestPrioSrcCore != null && highestPrioSrcCore.DynamicPriority < 2) + { + break; + } + + if (highestPrioSrcCore == thread) + { + continue; + } + } + + //If the candidate was scheduled after the current thread, then it's not worth it. + if (selectedThread == null || selectedThread.LastScheduledTime >= thread.LastScheduledTime) + { + yield return thread; + } + } + } + + //Select candidate threads that could run on this core. + //Only take into account threads that are not yet selected. + KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority == prio); + + if (dst != null) + { + SchedulingData.TransferToCore(prio, core, dst); + + selectedThread = dst; + } + + //If the priority of the currently selected thread is lower than preemption priority, + //then allow threads with lower priorities to be selected aswell. + if (selectedThread != null && selectedThread.DynamicPriority > prio) + { + Func predicate = x => x.DynamicPriority >= selectedThread.DynamicPriority; + + dst = SuitableCandidates().FirstOrDefault(predicate); + + if (dst != null) + { + SchedulingData.TransferToCore(dst.DynamicPriority, core, dst); + } + } + + ThreadReselectionRequested = true; + } + + public void SelectThreads() + { + ThreadReselectionRequested = false; + + for (int core = 0; core < CpuCoresCount; core++) + { + KThread thread = SchedulingData.ScheduledThreads(core).FirstOrDefault(); + + CoreContexts[core].SelectThread(thread); + } + + for (int core = 0; core < CpuCoresCount; core++) + { + //If the core is not idle (there's already a thread running on it), + //then we don't need to attempt load balancing. + if (SchedulingData.ScheduledThreads(core).Any()) + { + continue; + } + + int[] srcCoresHighestPrioThreads = new int[CpuCoresCount]; + + int srcCoresHighestPrioThreadsCount = 0; + + KThread dst = null; + + //Select candidate threads that could run on this core. + //Give preference to threads that are not yet selected. + foreach (KThread thread in SchedulingData.SuggestedThreads(core)) + { + if (thread.CurrentCore < 0 || thread != CoreContexts[thread.CurrentCore].SelectedThread) + { + dst = thread; + + break; + } + + srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = thread.CurrentCore; + } + + //Not yet selected candidate found. + if (dst != null) + { + //Priorities < 2 are used for the kernel message dispatching + //threads, we should skip load balancing entirely. + if (dst.DynamicPriority >= 2) + { + SchedulingData.TransferToCore(dst.DynamicPriority, core, dst); + + CoreContexts[core].SelectThread(dst); + } + + continue; + } + + //All candiates are already selected, choose the best one + //(the first one that doesn't make the source core idle if moved). + for (int index = 0; index < srcCoresHighestPrioThreadsCount; index++) + { + int srcCore = srcCoresHighestPrioThreads[index]; + + KThread src = SchedulingData.ScheduledThreads(srcCore).ElementAtOrDefault(1); + + if (src != null) + { + //Run the second thread on the queue on the source core, + //move the first one to the current core. + KThread origSelectedCoreSrc = CoreContexts[srcCore].SelectedThread; + + CoreContexts[srcCore].SelectThread(src); + + SchedulingData.TransferToCore(origSelectedCoreSrc.DynamicPriority, core, origSelectedCoreSrc); + + CoreContexts[core].SelectThread(origSelectedCoreSrc); + } + } + } + } + + public KThread GetCurrentThread() + { + lock (CoreContexts) + { + for (int core = 0; core < CpuCoresCount; core++) + { + if (CoreContexts[core].CurrentThread?.Context.IsCurrentThread() ?? false) + { + return CoreContexts[core].CurrentThread; + } + } + } + + throw new InvalidOperationException("Current thread is not scheduled!"); + } + + public KProcess GetCurrentProcess() + { + return GetCurrentThread().Owner; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _keepPreempting = false; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs new file mode 100644 index 00000000..83c4a079 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs @@ -0,0 +1,207 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KSchedulingData + { + private LinkedList[][] _scheduledThreadsPerPrioPerCore; + private LinkedList[][] _suggestedThreadsPerPrioPerCore; + + private long[] _scheduledPrioritiesPerCore; + private long[] _suggestedPrioritiesPerCore; + + public KSchedulingData() + { + _suggestedThreadsPerPrioPerCore = new LinkedList[KScheduler.PrioritiesCount][]; + _scheduledThreadsPerPrioPerCore = new LinkedList[KScheduler.PrioritiesCount][]; + + for (int prio = 0; prio < KScheduler.PrioritiesCount; prio++) + { + _suggestedThreadsPerPrioPerCore[prio] = new LinkedList[KScheduler.CpuCoresCount]; + _scheduledThreadsPerPrioPerCore[prio] = new LinkedList[KScheduler.CpuCoresCount]; + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + _suggestedThreadsPerPrioPerCore[prio][core] = new LinkedList(); + _scheduledThreadsPerPrioPerCore[prio][core] = new LinkedList(); + } + } + + _scheduledPrioritiesPerCore = new long[KScheduler.CpuCoresCount]; + _suggestedPrioritiesPerCore = new long[KScheduler.CpuCoresCount]; + } + + public IEnumerable SuggestedThreads(int core) + { + return Iterate(_suggestedThreadsPerPrioPerCore, _suggestedPrioritiesPerCore, core); + } + + public IEnumerable ScheduledThreads(int core) + { + return Iterate(_scheduledThreadsPerPrioPerCore, _scheduledPrioritiesPerCore, core); + } + + private IEnumerable Iterate(LinkedList[][] listPerPrioPerCore, long[] prios, int core) + { + long prioMask = prios[core]; + + int prio = CountTrailingZeros(prioMask); + + prioMask &= ~(1L << prio); + + while (prio < KScheduler.PrioritiesCount) + { + LinkedList list = listPerPrioPerCore[prio][core]; + + LinkedListNode node = list.First; + + while (node != null) + { + yield return node.Value; + + node = node.Next; + } + + prio = CountTrailingZeros(prioMask); + + prioMask &= ~(1L << prio); + } + } + + private int CountTrailingZeros(long value) + { + int count = 0; + + while (((value >> count) & 0xf) == 0 && count < 64) + { + count += 4; + } + + while (((value >> count) & 1) == 0 && count < 64) + { + count++; + } + + return count; + } + + public void TransferToCore(int prio, int dstCore, KThread thread) + { + bool schedulable = thread.DynamicPriority < KScheduler.PrioritiesCount; + + int srcCore = thread.CurrentCore; + + thread.CurrentCore = dstCore; + + if (srcCore == dstCore || !schedulable) + { + return; + } + + if (srcCore >= 0) + { + Unschedule(prio, srcCore, thread); + } + + if (dstCore >= 0) + { + Unsuggest(prio, dstCore, thread); + Schedule(prio, dstCore, thread); + } + + if (srcCore >= 0) + { + Suggest(prio, srcCore, thread); + } + } + + public void Suggest(int prio, int core, KThread thread) + { + if (prio >= KScheduler.PrioritiesCount) + { + return; + } + + thread.SiblingsPerCore[core] = SuggestedQueue(prio, core).AddFirst(thread); + + _suggestedPrioritiesPerCore[core] |= 1L << prio; + } + + public void Unsuggest(int prio, int core, KThread thread) + { + if (prio >= KScheduler.PrioritiesCount) + { + return; + } + + LinkedList queue = SuggestedQueue(prio, core); + + queue.Remove(thread.SiblingsPerCore[core]); + + if (queue.First == null) + { + _suggestedPrioritiesPerCore[core] &= ~(1L << prio); + } + } + + public void Schedule(int prio, int core, KThread thread) + { + if (prio >= KScheduler.PrioritiesCount) + { + return; + } + + thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddLast(thread); + + _scheduledPrioritiesPerCore[core] |= 1L << prio; + } + + public void SchedulePrepend(int prio, int core, KThread thread) + { + if (prio >= KScheduler.PrioritiesCount) + { + return; + } + + thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddFirst(thread); + + _scheduledPrioritiesPerCore[core] |= 1L << prio; + } + + public void Reschedule(int prio, int core, KThread thread) + { + LinkedList queue = ScheduledQueue(prio, core); + + queue.Remove(thread.SiblingsPerCore[core]); + + thread.SiblingsPerCore[core] = queue.AddLast(thread); + } + + public void Unschedule(int prio, int core, KThread thread) + { + if (prio >= KScheduler.PrioritiesCount) + { + return; + } + + LinkedList queue = ScheduledQueue(prio, core); + + queue.Remove(thread.SiblingsPerCore[core]); + + if (queue.First == null) + { + _scheduledPrioritiesPerCore[core] &= ~(1L << prio); + } + } + + private LinkedList SuggestedQueue(int prio, int core) + { + return _suggestedThreadsPerPrioPerCore[prio][core]; + } + + private LinkedList ScheduledQueue(int prio, int core) + { + return _scheduledThreadsPerPrioPerCore[prio][core]; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs new file mode 100644 index 00000000..450155ce --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs @@ -0,0 +1,136 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KSynchronization + { + private Horizon _system; + + public KSynchronization(Horizon system) + { + _system = system; + } + + public KernelResult WaitFor(KSynchronizationObject[] syncObjs, long timeout, out int handleIndex) + { + handleIndex = 0; + + KernelResult result = KernelResult.TimedOut; + + _system.CriticalSection.Enter(); + + //Check if objects are already signaled before waiting. + for (int index = 0; index < syncObjs.Length; index++) + { + if (!syncObjs[index].IsSignaled()) + { + continue; + } + + handleIndex = index; + + _system.CriticalSection.Leave(); + + return 0; + } + + if (timeout == 0) + { + _system.CriticalSection.Leave(); + + return result; + } + + KThread currentThread = _system.Scheduler.GetCurrentThread(); + + if (currentThread.ShallBeTerminated || + currentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + result = KernelResult.ThreadTerminating; + } + else if (currentThread.SyncCancelled) + { + currentThread.SyncCancelled = false; + + result = KernelResult.Cancelled; + } + else + { + LinkedListNode[] syncNodes = new LinkedListNode[syncObjs.Length]; + + for (int index = 0; index < syncObjs.Length; index++) + { + syncNodes[index] = syncObjs[index].AddWaitingThread(currentThread); + } + + currentThread.WaitingSync = true; + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = result; + + currentThread.Reschedule(ThreadSchedState.Paused); + + if (timeout > 0) + { + _system.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + + _system.CriticalSection.Leave(); + + currentThread.WaitingSync = false; + + if (timeout > 0) + { + _system.TimeManager.UnscheduleFutureInvocation(currentThread); + } + + _system.CriticalSection.Enter(); + + result = currentThread.ObjSyncResult; + + handleIndex = -1; + + for (int index = 0; index < syncObjs.Length; index++) + { + syncObjs[index].RemoveWaitingThread(syncNodes[index]); + + if (syncObjs[index] == currentThread.SignaledObj) + { + handleIndex = index; + } + } + } + + _system.CriticalSection.Leave(); + + return result; + } + + public void SignalObject(KSynchronizationObject syncObj) + { + _system.CriticalSection.Enter(); + + if (syncObj.IsSignaled()) + { + LinkedListNode node = syncObj.WaitingThreads.First; + + while (node != null) + { + KThread thread = node.Value; + + if ((thread.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused) + { + thread.SignaledObj = syncObj; + thread.ObjSyncResult = KernelResult.Success; + + thread.Reschedule(ThreadSchedState.Running); + } + + node = node.Next; + } + } + + _system.CriticalSection.Leave(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs new file mode 100644 index 00000000..3ad64024 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -0,0 +1,1030 @@ +using ChocolArm64; +using ChocolArm64.Memory; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KThread : KSynchronizationObject, IKFutureSchedulerObject + { + public CpuThread Context { get; private set; } + + public long AffinityMask { get; set; } + + public long ThreadUid { get; private set; } + + public long TotalTimeRunning { get; set; } + + public KSynchronizationObject SignaledObj { get; set; } + + public ulong CondVarAddress { get; set; } + + private ulong _entrypoint; + + public ulong MutexAddress { get; set; } + + public KProcess Owner { get; private set; } + + private ulong _tlsAddress; + + public long LastScheduledTime { get; set; } + + public LinkedListNode[] SiblingsPerCore { get; private set; } + + public LinkedList Withholder { get; set; } + public LinkedListNode WithholderNode { get; set; } + + public LinkedListNode ProcessListNode { get; set; } + + private LinkedList _mutexWaiters; + private LinkedListNode _mutexWaiterNode; + + public KThread MutexOwner { get; private set; } + + public int ThreadHandleForUserMutex { get; set; } + + private ThreadSchedState _forcePauseFlags; + + public KernelResult ObjSyncResult { get; set; } + + public int DynamicPriority { get; set; } + public int CurrentCore { get; set; } + public int BasePriority { get; set; } + public int PreferredCore { get; set; } + + private long _affinityMaskOverride; + private int _preferredCoreOverride; + private int _affinityOverrideCount; + + public ThreadSchedState SchedFlags { get; private set; } + + public bool ShallBeTerminated { get; private set; } + + public bool SyncCancelled { get; set; } + public bool WaitingSync { get; set; } + + private bool _hasExited; + + public bool WaitingInArbitration { get; set; } + + private KScheduler _scheduler; + + private KSchedulingData _schedulingData; + + public long LastPc { get; set; } + + public KThread(Horizon system) : base(system) + { + _scheduler = system.Scheduler; + _schedulingData = system.Scheduler.SchedulingData; + + SiblingsPerCore = new LinkedListNode[KScheduler.CpuCoresCount]; + + _mutexWaiters = new LinkedList(); + } + + public KernelResult Initialize( + ulong entrypoint, + ulong argsPtr, + ulong stackTop, + int priority, + int defaultCpuCore, + KProcess owner, + ThreadType type = ThreadType.User) + { + if ((uint)type > 3) + { + throw new ArgumentException($"Invalid thread type \"{type}\"."); + } + + PreferredCore = defaultCpuCore; + + AffinityMask |= 1L << defaultCpuCore; + + SchedFlags = type == ThreadType.Dummy + ? ThreadSchedState.Running + : ThreadSchedState.None; + + CurrentCore = PreferredCore; + + DynamicPriority = priority; + BasePriority = priority; + + ObjSyncResult = KernelResult.ThreadNotStarted; + + _entrypoint = entrypoint; + + if (type == ThreadType.User) + { + if (owner.AllocateThreadLocalStorage(out _tlsAddress) != KernelResult.Success) + { + return KernelResult.OutOfMemory; + } + + MemoryHelper.FillWithZeros(owner.CpuMemory, (long)_tlsAddress, KTlsPageInfo.TlsEntrySize); + } + + bool is64Bits; + + if (owner != null) + { + Owner = owner; + + owner.IncrementThreadCount(); + + is64Bits = (owner.MmuFlags & 1) != 0; + } + else + { + is64Bits = true; + } + + Context = new CpuThread(owner.Translator, owner.CpuMemory, (long)entrypoint); + + Context.ThreadState.X0 = argsPtr; + Context.ThreadState.X31 = stackTop; + + Context.ThreadState.CntfrqEl0 = 19200000; + Context.ThreadState.Tpidr = (long)_tlsAddress; + + owner.SubscribeThreadEventHandlers(Context); + + Context.WorkFinished += ThreadFinishedHandler; + + ThreadUid = System.GetThreadUid(); + + if (owner != null) + { + owner.AddThread(this); + + if (owner.IsPaused) + { + System.CriticalSection.Enter(); + + if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) + { + System.CriticalSection.Leave(); + + return KernelResult.Success; + } + + _forcePauseFlags |= ThreadSchedState.ProcessPauseFlag; + + CombineForcePauseFlags(); + + System.CriticalSection.Leave(); + } + } + + return KernelResult.Success; + } + + public KernelResult Start() + { + if (!System.KernelInitialized) + { + System.CriticalSection.Enter(); + + if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending) + { + _forcePauseFlags |= ThreadSchedState.KernelInitPauseFlag; + + CombineForcePauseFlags(); + } + + System.CriticalSection.Leave(); + } + + KernelResult result = KernelResult.ThreadTerminating; + + System.CriticalSection.Enter(); + + if (!ShallBeTerminated) + { + KThread currentThread = System.Scheduler.GetCurrentThread(); + + while (SchedFlags != ThreadSchedState.TerminationPending && + currentThread.SchedFlags != ThreadSchedState.TerminationPending && + !currentThread.ShallBeTerminated) + { + if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.None) + { + result = KernelResult.InvalidState; + + break; + } + + if (currentThread._forcePauseFlags == ThreadSchedState.None) + { + if (Owner != null && _forcePauseFlags != ThreadSchedState.None) + { + CombineForcePauseFlags(); + } + + SetNewSchedFlags(ThreadSchedState.Running); + + result = KernelResult.Success; + + break; + } + else + { + currentThread.CombineForcePauseFlags(); + + System.CriticalSection.Leave(); + System.CriticalSection.Enter(); + + if (currentThread.ShallBeTerminated) + { + break; + } + } + } + } + + System.CriticalSection.Leave(); + + return result; + } + + public void Exit() + { + System.CriticalSection.Enter(); + + _forcePauseFlags &= ~ThreadSchedState.ForcePauseMask; + + ExitImpl(); + + System.CriticalSection.Leave(); + } + + private void ExitImpl() + { + System.CriticalSection.Enter(); + + SetNewSchedFlags(ThreadSchedState.TerminationPending); + + _hasExited = true; + + Signal(); + + System.CriticalSection.Leave(); + } + + public KernelResult Sleep(long timeout) + { + System.CriticalSection.Enter(); + + if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) + { + System.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + SetNewSchedFlags(ThreadSchedState.Paused); + + if (timeout > 0) + { + System.TimeManager.ScheduleFutureInvocation(this, timeout); + } + + System.CriticalSection.Leave(); + + if (timeout > 0) + { + System.TimeManager.UnscheduleFutureInvocation(this); + } + + return 0; + } + + public void Yield() + { + System.CriticalSection.Enter(); + + if (SchedFlags != ThreadSchedState.Running) + { + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + + return; + } + + if (DynamicPriority < KScheduler.PrioritiesCount) + { + //Move current thread to the end of the queue. + _schedulingData.Reschedule(DynamicPriority, CurrentCore, this); + } + + _scheduler.ThreadReselectionRequested = true; + + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + } + + public void YieldWithLoadBalancing() + { + System.CriticalSection.Enter(); + + if (SchedFlags != ThreadSchedState.Running) + { + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + + return; + } + + int prio = DynamicPriority; + int core = CurrentCore; + + KThread nextThreadOnCurrentQueue = null; + + if (DynamicPriority < KScheduler.PrioritiesCount) + { + //Move current thread to the end of the queue. + _schedulingData.Reschedule(prio, core, this); + + Func predicate = x => x.DynamicPriority == prio; + + nextThreadOnCurrentQueue = _schedulingData.ScheduledThreads(core).FirstOrDefault(predicate); + } + + IEnumerable SuitableCandidates() + { + foreach (KThread thread in _schedulingData.SuggestedThreads(core)) + { + int srcCore = thread.CurrentCore; + + if (srcCore >= 0) + { + KThread selectedSrcCore = _scheduler.CoreContexts[srcCore].SelectedThread; + + if (selectedSrcCore == thread || ((selectedSrcCore?.DynamicPriority ?? 2) < 2)) + { + continue; + } + } + + //If the candidate was scheduled after the current thread, then it's not worth it, + //unless the priority is higher than the current one. + if (nextThreadOnCurrentQueue.LastScheduledTime >= thread.LastScheduledTime || + nextThreadOnCurrentQueue.DynamicPriority < thread.DynamicPriority) + { + yield return thread; + } + } + } + + KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority <= prio); + + if (dst != null) + { + _schedulingData.TransferToCore(dst.DynamicPriority, core, dst); + + _scheduler.ThreadReselectionRequested = true; + } + + if (this != nextThreadOnCurrentQueue) + { + _scheduler.ThreadReselectionRequested = true; + } + + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + } + + public void YieldAndWaitForLoadBalancing() + { + System.CriticalSection.Enter(); + + if (SchedFlags != ThreadSchedState.Running) + { + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + + return; + } + + int core = CurrentCore; + + _schedulingData.TransferToCore(DynamicPriority, -1, this); + + KThread selectedThread = null; + + if (!_schedulingData.ScheduledThreads(core).Any()) + { + foreach (KThread thread in _schedulingData.SuggestedThreads(core)) + { + if (thread.CurrentCore < 0) + { + continue; + } + + KThread firstCandidate = _schedulingData.ScheduledThreads(thread.CurrentCore).FirstOrDefault(); + + if (firstCandidate == thread) + { + continue; + } + + if (firstCandidate == null || firstCandidate.DynamicPriority >= 2) + { + _schedulingData.TransferToCore(thread.DynamicPriority, core, thread); + + selectedThread = thread; + } + + break; + } + } + + if (selectedThread != this) + { + _scheduler.ThreadReselectionRequested = true; + } + + System.CriticalSection.Leave(); + + System.Scheduler.ContextSwitch(); + } + + public void SetPriority(int priority) + { + System.CriticalSection.Enter(); + + BasePriority = priority; + + UpdatePriorityInheritance(); + + System.CriticalSection.Leave(); + } + + public KernelResult SetActivity(bool pause) + { + KernelResult result = KernelResult.Success; + + System.CriticalSection.Enter(); + + ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask; + + if (lowNibble != ThreadSchedState.Paused && lowNibble != ThreadSchedState.Running) + { + System.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + System.CriticalSection.Enter(); + + if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending) + { + if (pause) + { + //Pause, the force pause flag should be clear (thread is NOT paused). + if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0) + { + _forcePauseFlags |= ThreadSchedState.ThreadPauseFlag; + + CombineForcePauseFlags(); + } + else + { + result = KernelResult.InvalidState; + } + } + else + { + //Unpause, the force pause flag should be set (thread is paused). + if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0) + { + ThreadSchedState oldForcePauseFlags = _forcePauseFlags; + + _forcePauseFlags &= ~ThreadSchedState.ThreadPauseFlag; + + if ((oldForcePauseFlags & ~ThreadSchedState.ThreadPauseFlag) == ThreadSchedState.None) + { + ThreadSchedState oldSchedFlags = SchedFlags; + + SchedFlags &= ThreadSchedState.LowMask; + + AdjustScheduling(oldSchedFlags); + } + } + else + { + result = KernelResult.InvalidState; + } + } + } + + System.CriticalSection.Leave(); + System.CriticalSection.Leave(); + + return result; + } + + public void CancelSynchronization() + { + System.CriticalSection.Enter(); + + if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.Paused || !WaitingSync) + { + SyncCancelled = true; + } + else if (Withholder != null) + { + Withholder.Remove(WithholderNode); + + SetNewSchedFlags(ThreadSchedState.Running); + + Withholder = null; + + SyncCancelled = true; + } + else + { + SignaledObj = null; + ObjSyncResult = KernelResult.Cancelled; + + SetNewSchedFlags(ThreadSchedState.Running); + + SyncCancelled = false; + } + + System.CriticalSection.Leave(); + } + + public KernelResult SetCoreAndAffinityMask(int newCore, long newAffinityMask) + { + System.CriticalSection.Enter(); + + bool useOverride = _affinityOverrideCount != 0; + + //The value -3 is "do not change the preferred core". + if (newCore == -3) + { + newCore = useOverride ? _preferredCoreOverride : PreferredCore; + + if ((newAffinityMask & (1 << newCore)) == 0) + { + System.CriticalSection.Leave(); + + return KernelResult.InvalidCombination; + } + } + + if (useOverride) + { + _preferredCoreOverride = newCore; + _affinityMaskOverride = newAffinityMask; + } + else + { + long oldAffinityMask = AffinityMask; + + PreferredCore = newCore; + AffinityMask = newAffinityMask; + + if (oldAffinityMask != newAffinityMask) + { + int oldCore = CurrentCore; + + if (CurrentCore >= 0 && ((AffinityMask >> CurrentCore) & 1) == 0) + { + if (PreferredCore < 0) + { + CurrentCore = HighestSetCore(AffinityMask); + } + else + { + CurrentCore = PreferredCore; + } + } + + AdjustSchedulingForNewAffinity(oldAffinityMask, oldCore); + } + } + + System.CriticalSection.Leave(); + + return KernelResult.Success; + } + + private static int HighestSetCore(long mask) + { + for (int core = KScheduler.CpuCoresCount - 1; core >= 0; core--) + { + if (((mask >> core) & 1) != 0) + { + return core; + } + } + + return -1; + } + + private void CombineForcePauseFlags() + { + ThreadSchedState oldFlags = SchedFlags; + ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask; + + SchedFlags = lowNibble | _forcePauseFlags; + + AdjustScheduling(oldFlags); + } + + private void SetNewSchedFlags(ThreadSchedState newFlags) + { + System.CriticalSection.Enter(); + + ThreadSchedState oldFlags = SchedFlags; + + SchedFlags = (oldFlags & ThreadSchedState.HighMask) | newFlags; + + if ((oldFlags & ThreadSchedState.LowMask) != newFlags) + { + AdjustScheduling(oldFlags); + } + + System.CriticalSection.Leave(); + } + + public void ReleaseAndResume() + { + System.CriticalSection.Enter(); + + if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused) + { + if (Withholder != null) + { + Withholder.Remove(WithholderNode); + + SetNewSchedFlags(ThreadSchedState.Running); + + Withholder = null; + } + else + { + SetNewSchedFlags(ThreadSchedState.Running); + } + } + + System.CriticalSection.Leave(); + } + + public void Reschedule(ThreadSchedState newFlags) + { + System.CriticalSection.Enter(); + + ThreadSchedState oldFlags = SchedFlags; + + SchedFlags = (oldFlags & ThreadSchedState.HighMask) | + (newFlags & ThreadSchedState.LowMask); + + AdjustScheduling(oldFlags); + + System.CriticalSection.Leave(); + } + + public void AddMutexWaiter(KThread requester) + { + AddToMutexWaitersList(requester); + + requester.MutexOwner = this; + + UpdatePriorityInheritance(); + } + + public void RemoveMutexWaiter(KThread thread) + { + if (thread._mutexWaiterNode?.List != null) + { + _mutexWaiters.Remove(thread._mutexWaiterNode); + } + + thread.MutexOwner = null; + + UpdatePriorityInheritance(); + } + + public KThread RelinquishMutex(ulong mutexAddress, out int count) + { + count = 0; + + if (_mutexWaiters.First == null) + { + return null; + } + + KThread newMutexOwner = null; + + LinkedListNode currentNode = _mutexWaiters.First; + + do + { + //Skip all threads that are not waiting for this mutex. + while (currentNode != null && currentNode.Value.MutexAddress != mutexAddress) + { + currentNode = currentNode.Next; + } + + if (currentNode == null) + { + break; + } + + LinkedListNode nextNode = currentNode.Next; + + _mutexWaiters.Remove(currentNode); + + currentNode.Value.MutexOwner = newMutexOwner; + + if (newMutexOwner != null) + { + //New owner was already selected, re-insert on new owner list. + newMutexOwner.AddToMutexWaitersList(currentNode.Value); + } + else + { + //New owner not selected yet, use current thread. + newMutexOwner = currentNode.Value; + } + + count++; + + currentNode = nextNode; + } + while (currentNode != null); + + if (newMutexOwner != null) + { + UpdatePriorityInheritance(); + + newMutexOwner.UpdatePriorityInheritance(); + } + + return newMutexOwner; + } + + private void UpdatePriorityInheritance() + { + //If any of the threads waiting for the mutex has + //higher priority than the current thread, then + //the current thread inherits that priority. + int highestPriority = BasePriority; + + if (_mutexWaiters.First != null) + { + int waitingDynamicPriority = _mutexWaiters.First.Value.DynamicPriority; + + if (waitingDynamicPriority < highestPriority) + { + highestPriority = waitingDynamicPriority; + } + } + + if (highestPriority != DynamicPriority) + { + int oldPriority = DynamicPriority; + + DynamicPriority = highestPriority; + + AdjustSchedulingForNewPriority(oldPriority); + + if (MutexOwner != null) + { + //Remove and re-insert to ensure proper sorting based on new priority. + MutexOwner._mutexWaiters.Remove(_mutexWaiterNode); + + MutexOwner.AddToMutexWaitersList(this); + + MutexOwner.UpdatePriorityInheritance(); + } + } + } + + private void AddToMutexWaitersList(KThread thread) + { + LinkedListNode nextPrio = _mutexWaiters.First; + + int currentPriority = thread.DynamicPriority; + + while (nextPrio != null && nextPrio.Value.DynamicPriority <= currentPriority) + { + nextPrio = nextPrio.Next; + } + + if (nextPrio != null) + { + thread._mutexWaiterNode = _mutexWaiters.AddBefore(nextPrio, thread); + } + else + { + thread._mutexWaiterNode = _mutexWaiters.AddLast(thread); + } + } + + private void AdjustScheduling(ThreadSchedState oldFlags) + { + if (oldFlags == SchedFlags) + { + return; + } + + if (oldFlags == ThreadSchedState.Running) + { + //Was running, now it's stopped. + if (CurrentCore >= 0) + { + _schedulingData.Unschedule(DynamicPriority, CurrentCore, this); + } + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) + { + _schedulingData.Unsuggest(DynamicPriority, core, this); + } + } + } + else if (SchedFlags == ThreadSchedState.Running) + { + //Was stopped, now it's running. + if (CurrentCore >= 0) + { + _schedulingData.Schedule(DynamicPriority, CurrentCore, this); + } + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) + { + _schedulingData.Suggest(DynamicPriority, core, this); + } + } + } + + _scheduler.ThreadReselectionRequested = true; + } + + private void AdjustSchedulingForNewPriority(int oldPriority) + { + if (SchedFlags != ThreadSchedState.Running) + { + return; + } + + //Remove thread from the old priority queues. + if (CurrentCore >= 0) + { + _schedulingData.Unschedule(oldPriority, CurrentCore, this); + } + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) + { + _schedulingData.Unsuggest(oldPriority, core, this); + } + } + + //Add thread to the new priority queues. + KThread currentThread = _scheduler.GetCurrentThread(); + + if (CurrentCore >= 0) + { + if (currentThread == this) + { + _schedulingData.SchedulePrepend(DynamicPriority, CurrentCore, this); + } + else + { + _schedulingData.Schedule(DynamicPriority, CurrentCore, this); + } + } + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0) + { + _schedulingData.Suggest(DynamicPriority, core, this); + } + } + + _scheduler.ThreadReselectionRequested = true; + } + + private void AdjustSchedulingForNewAffinity(long oldAffinityMask, int oldCore) + { + if (SchedFlags != ThreadSchedState.Running || DynamicPriority >= KScheduler.PrioritiesCount) + { + return; + } + + //Remove from old queues. + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (((oldAffinityMask >> core) & 1) != 0) + { + if (core == oldCore) + { + _schedulingData.Unschedule(DynamicPriority, core, this); + } + else + { + _schedulingData.Unsuggest(DynamicPriority, core, this); + } + } + } + + //Insert on new queues. + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (((AffinityMask >> core) & 1) != 0) + { + if (core == CurrentCore) + { + _schedulingData.Schedule(DynamicPriority, core, this); + } + else + { + _schedulingData.Suggest(DynamicPriority, core, this); + } + } + } + + _scheduler.ThreadReselectionRequested = true; + } + + public override bool IsSignaled() + { + return _hasExited; + } + + public void SetEntryArguments(long argsPtr, int threadHandle) + { + Context.ThreadState.X0 = (ulong)argsPtr; + Context.ThreadState.X1 = (ulong)threadHandle; + } + + public void ClearExclusive() + { + Owner.CpuMemory.ClearExclusive(CurrentCore); + } + + public void TimeUp() + { + ReleaseAndResume(); + } + + public void PrintGuestStackTrace() + { + Owner.Debugger.PrintGuestStackTrace(Context.ThreadState); + } + + private void ThreadFinishedHandler(object sender, EventArgs e) + { + System.Scheduler.ExitThread(this); + + Terminate(); + + System.Scheduler.RemoveThread(this); + } + + public void Terminate() + { + Owner?.RemoveThread(this); + + if (_tlsAddress != 0 && Owner.FreeThreadLocalStorage(_tlsAddress) != KernelResult.Success) + { + throw new InvalidOperationException("Unexpected failure freeing thread local storage."); + } + + System.CriticalSection.Enter(); + + //Wake up all threads that may be waiting for a mutex being held + //by this thread. + foreach (KThread thread in _mutexWaiters) + { + thread.MutexOwner = null; + thread._preferredCoreOverride = 0; + thread.ObjSyncResult = KernelResult.InvalidState; + + thread.ReleaseAndResume(); + } + + System.CriticalSection.Leave(); + + Owner?.DecrementThreadCountAndTerminateIfZero(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs new file mode 100644 index 00000000..c9b2f40d --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs @@ -0,0 +1,24 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KWritableEvent + { + private KEvent _parent; + + public KWritableEvent(KEvent parent) + { + _parent = parent; + } + + public void Signal() + { + _parent.ReadableEvent.Signal(); + } + + public KernelResult Clear() + { + return _parent.ReadableEvent.Clear(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs b/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs new file mode 100644 index 00000000..e72b719b --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + enum SignalType + { + Signal = 0, + SignalAndIncrementIfEqual = 1, + SignalAndModifyIfEqual = 2 + } +} diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs b/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs new file mode 100644 index 00000000..c9eaa6b3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + enum ThreadSchedState : ushort + { + LowMask = 0xf, + HighMask = 0xfff0, + ForcePauseMask = 0x70, + + ProcessPauseFlag = 1 << 4, + ThreadPauseFlag = 1 << 5, + ProcessDebugPauseFlag = 1 << 6, + KernelInitPauseFlag = 1 << 8, + + None = 0, + Paused = 1, + Running = 2, + TerminationPending = 3 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs b/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs new file mode 100644 index 00000000..0b44b57f --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + enum ThreadType + { + Dummy, + Kernel, + Kernel2, + User + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/ProgramLoader.cs b/Ryujinx.HLE/HOS/ProgramLoader.cs index 00fc6b93..bb09db6e 100644 --- a/Ryujinx.HLE/HOS/ProgramLoader.cs +++ b/Ryujinx.HLE/HOS/ProgramLoader.cs @@ -1,7 +1,9 @@ using ChocolArm64.Memory; using Ryujinx.Common; using Ryujinx.Common.Logging; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Npdm; diff --git a/Ryujinx.HLE/HOS/ServiceCtx.cs b/Ryujinx.HLE/HOS/ServiceCtx.cs index f55e4546..005d16f3 100644 --- a/Ryujinx.HLE/HOS/ServiceCtx.cs +++ b/Ryujinx.HLE/HOS/ServiceCtx.cs @@ -1,6 +1,7 @@ using ChocolArm64.Memory; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using Ryujinx.HLE.HOS.Kernel.Process; using System.IO; namespace Ryujinx.HLE.HOS diff --git a/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs b/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs index 8ec42152..f143db2c 100644 --- a/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs +++ b/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs @@ -1,6 +1,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; using System; using System.Collections.Generic; diff --git a/Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs index db116f33..fdcd923d 100644 --- a/Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs +++ b/Ryujinx.HLE/HOS/Services/Am/IHomeMenuFunctions.cs @@ -1,6 +1,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; using System; using System.Collections.Generic; diff --git a/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs b/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs index 7c4aa16c..faf522e4 100644 --- a/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs +++ b/Ryujinx.HLE/HOS/Services/Am/ILibraryAppletAccessor.cs @@ -1,6 +1,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; using System; using System.Collections.Generic; diff --git a/Ryujinx.HLE/HOS/Services/Am/ISelfController.cs b/Ryujinx.HLE/HOS/Services/Am/ISelfController.cs index dc922037..e30e8d0d 100644 --- a/Ryujinx.HLE/HOS/Services/Am/ISelfController.cs +++ b/Ryujinx.HLE/HOS/Services/Am/ISelfController.cs @@ -1,6 +1,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; using System; using System.Collections.Generic; diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs index 93bda210..5270a041 100644 --- a/Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs +++ b/Ryujinx.HLE/HOS/Services/Aud/AudioOut/IAudioOut.cs @@ -1,7 +1,8 @@ using ChocolArm64.Memory; using Ryujinx.Audio; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; using System; using System.Collections.Generic; diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs index 0ad193ca..bc069436 100644 --- a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs +++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs @@ -3,7 +3,8 @@ using Ryujinx.Audio; using Ryujinx.Audio.Adpcm; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.Utilities; using System; using System.Collections.Generic; diff --git a/Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs b/Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs index 585d7e43..0d68fe13 100644 --- a/Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs +++ b/Ryujinx.HLE/HOS/Services/Aud/IAudioDevice.cs @@ -1,6 +1,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.SystemState; using System; using System.Collections.Generic; diff --git a/Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs b/Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs index 4b1440a0..a276c96e 100644 --- a/Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs +++ b/Ryujinx.HLE/HOS/Services/Aud/IAudioOutManager.cs @@ -2,7 +2,7 @@ using ChocolArm64.Memory; using Ryujinx.Audio; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Services.Aud.AudioOut; using System.Collections.Generic; using System.Text; diff --git a/Ryujinx.HLE/HOS/Services/Hid/IAppletResource.cs b/Ryujinx.HLE/HOS/Services/Hid/IAppletResource.cs index e82b824e..c92252d8 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/IAppletResource.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/IAppletResource.cs @@ -1,5 +1,6 @@ using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; using System; using System.Collections.Generic; diff --git a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs index 4e14943b..cc75e99b 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs @@ -1,6 +1,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.Input; using System; using System.Collections.Generic; @@ -1216,7 +1217,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid long appletResourceUserId = context.RequestData.ReadInt64(); Logger.PrintStub(LogClass.ServiceHid, $"Stubbed. AppletResourceUserId: {appletResourceUserId}"); - + return 0; } diff --git a/Ryujinx.HLE/HOS/Services/IpcService.cs b/Ryujinx.HLE/HOS/Services/IpcService.cs index ec43430e..8d2036ea 100644 --- a/Ryujinx.HLE/HOS/Services/IpcService.cs +++ b/Ryujinx.HLE/HOS/Services/IpcService.cs @@ -1,6 +1,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Ipc; using System; using System.Collections.Generic; using System.IO; diff --git a/Ryujinx.HLE/HOS/Services/Ldr/IRoInterface.cs b/Ryujinx.HLE/HOS/Services/Ldr/IRoInterface.cs index 1ee30a6c..202e6df5 100644 --- a/Ryujinx.HLE/HOS/Services/Ldr/IRoInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Ldr/IRoInterface.cs @@ -1,7 +1,9 @@ using ChocolArm64.Memory; using Ryujinx.Common; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Utilities; using System.Collections.Generic; diff --git a/Ryujinx.HLE/HOS/Services/Nfp/IUser.cs b/Ryujinx.HLE/HOS/Services/Nfp/IUser.cs index 9e19e77f..42ef4829 100644 --- a/Ryujinx.HLE/HOS/Services/Nfp/IUser.cs +++ b/Ryujinx.HLE/HOS/Services/Nfp/IUser.cs @@ -1,6 +1,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.Input; using System; using System.Collections.Generic; diff --git a/Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs b/Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs index 52adce9e..d2c646be 100644 --- a/Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs +++ b/Ryujinx.HLE/HOS/Services/Nifm/IRequest.cs @@ -1,6 +1,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; using System; using System.Collections.Generic; diff --git a/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs b/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs index 1ed97dde..3a00f514 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs @@ -1,7 +1,9 @@ using ChocolArm64.Memory; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Services.Nv.NvGpuAS; using Ryujinx.HLE.HOS.Services.Nv.NvGpuGpu; using Ryujinx.HLE.HOS.Services.Nv.NvHostChannel; diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs index 8e128a0d..2a5f1b18 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs @@ -1,7 +1,7 @@ using ChocolArm64.Memory; using Ryujinx.Common.Logging; using Ryujinx.Graphics.Memory; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Services.Nv.NvMap; using System; using System.Collections.Concurrent; diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs index a3b55309..a6681441 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs @@ -1,7 +1,7 @@ using ChocolArm64.Memory; using Ryujinx.Common.Logging; using Ryujinx.Graphics.Memory; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Services.Nv.NvGpuAS; using Ryujinx.HLE.HOS.Services.Nv.NvMap; using System; diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs index f13f7a68..ddef456c 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs @@ -1,6 +1,6 @@ using ChocolArm64.Memory; using Ryujinx.Common.Logging; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Process; using System; using System.Collections.Concurrent; using System.Text; diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs index 75a76b91..b1ae8307 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs @@ -1,7 +1,7 @@ using ChocolArm64.Memory; using Ryujinx.Common.Logging; using Ryujinx.Graphics.Memory; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.Utilities; using System.Collections.Concurrent; diff --git a/Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs b/Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs index c555c504..1bdff31a 100644 --- a/Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs +++ b/Ryujinx.HLE/HOS/Services/Pl/ISharedFontManager.cs @@ -1,6 +1,6 @@ using Ryujinx.HLE.HOS.Font; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; using System; using System.Collections.Generic; diff --git a/Ryujinx.HLE/HOS/Services/Psm/IPsmSession.cs b/Ryujinx.HLE/HOS/Services/Psm/IPsmSession.cs index 4763f2d5..0c304b41 100644 --- a/Ryujinx.HLE/HOS/Services/Psm/IPsmSession.cs +++ b/Ryujinx.HLE/HOS/Services/Psm/IPsmSession.cs @@ -1,6 +1,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Psm diff --git a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs index 2bfafd95..df551a41 100644 --- a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs @@ -1,5 +1,6 @@ using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Ipc; using System; using System.Collections.Generic; diff --git a/Ryujinx.HLE/HOS/Services/Vi/IApplicationDisplayService.cs b/Ryujinx.HLE/HOS/Services/Vi/IApplicationDisplayService.cs index 63e542d2..0b5705b9 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/IApplicationDisplayService.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/IApplicationDisplayService.cs @@ -1,6 +1,6 @@ using ChocolArm64.Memory; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; using System.Collections.Generic; using System; using System.IO; diff --git a/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs b/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs index 7ca3053d..adcedf4e 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/IHOSBinderDriver.cs @@ -1,6 +1,7 @@ using Ryujinx.Graphics.Gal; using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Services.Android; using System; using System.Collections.Generic; diff --git a/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs b/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs index 1b57b331..cce1b2b9 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs @@ -1,7 +1,7 @@ using Ryujinx.Common.Logging; using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Memory; -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Services.Nv.NvGpuAS; using Ryujinx.HLE.HOS.Services.Nv.NvMap; using System; diff --git a/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs b/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs index f46a7c30..1fd27505 100644 --- a/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs +++ b/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs @@ -1,4 +1,4 @@ -using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Services.Am; using System.Collections.Concurrent; -- cgit v1.2.3