aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/HOS/Kernel/Process
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2018-12-18 03:33:36 -0200
committerGitHub <noreply@github.com>2018-12-18 03:33:36 -0200
commit0039bb639493b2d1e2764cae380311ba8e87704b (patch)
tree63a912a95c8261775c2acb8a5b9ca0f10ad4ae33 /Ryujinx.HLE/HOS/Kernel/Process
parent2534a7f10c627810e6e0272b4cc9758e90f733c1 (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/Process')
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs311
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs83
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs17
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs209
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs1017
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs314
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs75
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs61
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs37
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs14
10 files changed, 2138 insertions, 0 deletions
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<Image> _images;
+
+ private int _loaded;
+
+ public HleProcessDebugger(KProcess owner)
+ {
+ _owner = owner;
+
+ _images = new List<Image>();
+ }
+
+ 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<ElfDynamicTag, long> dynamic = new Dictionary<ElfDynamicTag, long>();
+
+ 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<ElfSymbol> symbols = new List<ElfSymbol>();
+
+ 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<T>(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<KThread>(handle);
+ }
+ }
+
+ public KProcess GetKProcess(int handle)
+ {
+ if (handle == SelfProcessHandle)
+ {
+ return _system.Scheduler.GetCurrentProcess();
+ }
+ else
+ {
+ return GetObject<KProcess>(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<ulong, KTlsPageInfo> _fullTlsPages;
+ private SortedDictionary<ulong, KTlsPageInfo> _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<KThread> _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<ulong, KTlsPageInfo>();
+ _freeTlsPages = new SortedDictionary<ulong, KTlsPageInfo>();
+
+ Capabilities = new KProcessCapabilities();
+
+ RandomEntropy = new long[KScheduler.CpuCoresCount];
+
+ _threads = new LinkedList<KThread>();
+
+ 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