Refactor SVC handler (#540)

* Refactor SVC handler

* Get rid of KernelErr

* Split kernel code files into multiple folders
This commit is contained in:
gdkchan 2018-12-18 03:33:36 -02:00 committed by GitHub
parent 2534a7f10c
commit 0039bb6394
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
105 changed files with 1894 additions and 1982 deletions

View file

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Kernel.Common
{
interface IKFutureSchedulerObject
{
void TimeUp();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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