diff options
| author | gdkchan <gab.dark.100@gmail.com> | 2018-12-18 03:33:36 -0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-12-18 03:33:36 -0200 |
| commit | 0039bb639493b2d1e2764cae380311ba8e87704b (patch) | |
| tree | 63a912a95c8261775c2acb8a5b9ca0f10ad4ae33 /Ryujinx.HLE/HOS/Kernel/Common | |
| parent | 2534a7f10c627810e6e0272b4cc9758e90f733c1 (diff) | |
Refactor SVC handler (#540)
* Refactor SVC handler
* Get rid of KernelErr
* Split kernel code files into multiple folders
Diffstat (limited to 'Ryujinx.HLE/HOS/Kernel/Common')
| -rw-r--r-- | Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs | 7 | ||||
| -rw-r--r-- | Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs | 42 | ||||
| -rw-r--r-- | Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs | 147 | ||||
| -rw-r--r-- | Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs | 35 | ||||
| -rw-r--r-- | Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs | 143 | ||||
| -rw-r--r-- | Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs | 137 | ||||
| -rw-r--r-- | Ryujinx.HLE/HOS/Kernel/Common/KernelResult.cs | 32 | ||||
| -rw-r--r-- | Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs | 72 | ||||
| -rw-r--r-- | Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs | 13 | ||||
| -rw-r--r-- | Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs | 128 |
10 files changed, 756 insertions, 0 deletions
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<KThread> _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<KThread>(); + + _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<KThread> WaitingThreads; + + public KSynchronizationObject(Horizon system) : base(system) + { + WaitingThreads = new LinkedList<KThread>(); + } + + public LinkedListNode<KThread> AddWaitingThread(KThread thread) + { + return WaitingThreads.AddLast(thread); + } + + public void RemoveWaitingThread(LinkedListNode<KThread> 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<WaitingObject> _waitingObjects; + + private AutoResetEvent _waitEvent; + + private bool _keepRunning; + + public KTimeManager() + { + _waitingObjects = new List<WaitingObject>(); + + _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; + } + } +} |
