diff options
Diffstat (limited to 'Ryujinx.HLE/HOS/Kernel')
56 files changed, 6399 insertions, 1116 deletions
diff --git a/Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs b/Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs index c97caf42..6f7b230e 100644 --- a/Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs +++ b/Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs @@ -4,7 +4,7 @@ namespace Ryujinx.HLE.HOS.Kernel { Addr32Bits = 0, Addr36Bits = 1, - Addr36BitsNoMap = 2, + 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 index 8a2d47f7..b584d719 100644 --- a/Ryujinx.HLE/HOS/Kernel/ArbitrationType.cs +++ b/Ryujinx.HLE/HOS/Kernel/ArbitrationType.cs @@ -6,4 +6,4 @@ namespace Ryujinx.HLE.HOS.Kernel DecrementAndWaitIfLessThan = 1, WaitIfEqual = 2 } -} +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/DramMemoryMap.cs b/Ryujinx.HLE/HOS/Kernel/DramMemoryMap.cs new file mode 100644 index 00000000..b20a83e2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/DramMemoryMap.cs @@ -0,0 +1,15 @@ +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 index 0bfa2710..6a424cf2 100644 --- a/Ryujinx.HLE/HOS/Kernel/HleCoreManager.cs +++ b/Ryujinx.HLE/HOS/Kernel/HleCoreManager.cs @@ -5,24 +5,61 @@ namespace Ryujinx.HLE.HOS.Kernel { class HleCoreManager { - private ConcurrentDictionary<Thread, ManualResetEvent> Threads; + private class PausableThread + { + public ManualResetEvent Event { get; private set; } + + public bool IsExiting { get; set; } + + public PausableThread() + { + Event = new ManualResetEvent(false); + } + } + + private ConcurrentDictionary<Thread, PausableThread> Threads; public HleCoreManager() { - Threads = new ConcurrentDictionary<Thread, ManualResetEvent>(); + Threads = new ConcurrentDictionary<Thread, PausableThread>(); + } + + 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; } - public ManualResetEvent GetThread(Thread Thread) + private PausableThread GetThread(Thread Thread) { - return Threads.GetOrAdd(Thread, (Key) => new ManualResetEvent(false)); + return Threads.GetOrAdd(Thread, (Key) => new PausableThread()); } public void RemoveThread(Thread Thread) { - if (Threads.TryRemove(Thread, out ManualResetEvent Event)) + if (Threads.TryRemove(Thread, out PausableThread PausableThread)) { - Event.Set(); - Event.Dispose(); + PausableThread.Event.Set(); + PausableThread.Event.Dispose(); } } } diff --git a/Ryujinx.HLE/HOS/Kernel/HleProcessDebugger.cs b/Ryujinx.HLE/HOS/Kernel/HleProcessDebugger.cs new file mode 100644 index 00000000..a6053b1b --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/HleProcessDebugger.cs @@ -0,0 +1,310 @@ +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) + { + this.BaseAddress = BaseAddress; + this.Symbols = Symbols; + } + } + + private List<Image> Images; + + private int Loaded; + + public HleProcessDebugger(KProcess Owner) + { + this.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/HleScheduler.cs b/Ryujinx.HLE/HOS/Kernel/HleScheduler.cs index e0cb158c..87dbe553 100644 --- a/Ryujinx.HLE/HOS/Kernel/HleScheduler.cs +++ b/Ryujinx.HLE/HOS/Kernel/HleScheduler.cs @@ -11,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Kernel public bool MultiCoreScheduling { get; set; } - private HleCoreManager CoreManager; + public HleCoreManager CoreManager { get; private set; } private bool KeepPreempting; @@ -49,11 +49,11 @@ namespace Ryujinx.HLE.HOS.Kernel if (SelectedCount == 0) { - CoreManager.GetThread(Thread.CurrentThread).Reset(); + CoreManager.Reset(Thread.CurrentThread); } else if (SelectedCount == 1) { - CoreManager.GetThread(Thread.CurrentThread).Set(); + CoreManager.Set(Thread.CurrentThread); } else { @@ -77,7 +77,7 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - CoreManager.GetThread(CurrentThread.Context.Work).Reset(); + CoreManager.Reset(CurrentThread.Context.Work); } //Advance current core and try picking a thread, @@ -94,7 +94,7 @@ namespace Ryujinx.HLE.HOS.Kernel { CoreContext.CurrentThread.ClearExclusive(); - CoreManager.GetThread(CoreContext.CurrentThread.Context.Work).Set(); + CoreManager.Set(CoreContext.CurrentThread.Context.Work); CoreContext.CurrentThread.Context.Execute(); @@ -111,7 +111,7 @@ namespace Ryujinx.HLE.HOS.Kernel } } - CoreManager.GetThread(Thread.CurrentThread).WaitOne(); + CoreManager.Wait(Thread.CurrentThread); } private void PreemptCurrentThread() @@ -134,11 +134,11 @@ namespace Ryujinx.HLE.HOS.Kernel } } - public void StopThread(KThread Thread) + public void ExitThread(KThread Thread) { Thread.Context.StopExecution(); - CoreManager.GetThread(Thread.Context.Work).Set(); + CoreManager.Exit(Thread.Context.Work); } public void RemoveThread(KThread Thread) diff --git a/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs b/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs index 4a0f955f..cc637be0 100644 --- a/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs +++ b/Ryujinx.HLE/HOS/Kernel/KAddressArbiter.cs @@ -1,4 +1,3 @@ -using ChocolArm64.Memory; using System.Collections.Generic; using System.Linq; @@ -23,39 +22,36 @@ namespace Ryujinx.HLE.HOS.Kernel ArbiterThreads = new List<KThread>(); } - public long ArbitrateLock( - Process Process, - MemoryManager Memory, - int OwnerHandle, - long MutexAddress, - int RequesterHandle) + public long ArbitrateLock(int OwnerHandle, long MutexAddress, int RequesterHandle) { - System.CriticalSectionLock.Lock(); - KThread CurrentThread = System.Scheduler.GetCurrentThread(); + System.CriticalSection.Enter(); + CurrentThread.SignaledObj = null; CurrentThread.ObjSyncResult = 0; - if (!UserToKernelInt32(Memory, MutexAddress, out int MutexValue)) + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + if (!KernelTransfer.UserToKernelInt32(System, MutexAddress, out int MutexValue)) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);; } if (MutexValue != (OwnerHandle | HasListenersMask)) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return 0; } - KThread MutexOwner = Process.HandleTable.GetObject<KThread>(OwnerHandle); + KThread MutexOwner = CurrentProcess.HandleTable.GetObject<KThread>(OwnerHandle); if (MutexOwner == null) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); } @@ -67,26 +63,26 @@ namespace Ryujinx.HLE.HOS.Kernel CurrentThread.Reschedule(ThreadSchedState.Paused); - System.CriticalSectionLock.Unlock(); - System.CriticalSectionLock.Lock(); + System.CriticalSection.Leave(); + System.CriticalSection.Enter(); if (CurrentThread.MutexOwner != null) { CurrentThread.MutexOwner.RemoveMutexWaiter(CurrentThread); } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return (uint)CurrentThread.ObjSyncResult; } - public long ArbitrateUnlock(MemoryManager Memory, long MutexAddress) + public long ArbitrateUnlock(long MutexAddress) { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); KThread CurrentThread = System.Scheduler.GetCurrentThread(); - (long Result, KThread NewOwnerThread) = MutexUnlock(Memory, CurrentThread, MutexAddress); + (long Result, KThread NewOwnerThread) = MutexUnlock(CurrentThread, MutexAddress); if (Result != 0 && NewOwnerThread != null) { @@ -94,19 +90,18 @@ namespace Ryujinx.HLE.HOS.Kernel NewOwnerThread.ObjSyncResult = (int)Result; } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return Result; } public long WaitProcessWideKeyAtomic( - MemoryManager Memory, - long MutexAddress, - long CondVarAddress, - int ThreadHandle, - long Timeout) + long MutexAddress, + long CondVarAddress, + int ThreadHandle, + long Timeout) { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); KThread CurrentThread = System.Scheduler.GetCurrentThread(); @@ -116,16 +111,16 @@ namespace Ryujinx.HLE.HOS.Kernel if (CurrentThread.ShallBeTerminated || CurrentThread.SchedFlags == ThreadSchedState.TerminationPending) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); } - (long Result, _) = MutexUnlock(Memory, CurrentThread, MutexAddress); + (long Result, _) = MutexUnlock(CurrentThread, MutexAddress); if (Result != 0) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return Result; } @@ -146,14 +141,14 @@ namespace Ryujinx.HLE.HOS.Kernel } } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); if (Timeout > 0) { System.TimeManager.UnscheduleFutureInvocation(CurrentThread); } - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); if (CurrentThread.MutexOwner != null) { @@ -162,12 +157,12 @@ namespace Ryujinx.HLE.HOS.Kernel CondVarThreads.Remove(CurrentThread); - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return (uint)CurrentThread.ObjSyncResult; } - private (long, KThread) MutexUnlock(MemoryManager Memory, KThread CurrentThread, long MutexAddress) + private (long, KThread) MutexUnlock(KThread CurrentThread, long MutexAddress) { KThread NewOwnerThread = CurrentThread.RelinquishMutex(MutexAddress, out int Count); @@ -190,7 +185,7 @@ namespace Ryujinx.HLE.HOS.Kernel long Result = 0; - if (!KernelToUserInt32(Memory, MutexAddress, MutexValue)) + if (!KernelTransfer.KernelToUserInt32(System, MutexAddress, MutexValue)) { Result = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); } @@ -198,17 +193,17 @@ namespace Ryujinx.HLE.HOS.Kernel return (Result, NewOwnerThread); } - public void SignalProcessWideKey(Process Process, MemoryManager Memory, long Address, int Count) + public void SignalProcessWideKey(long Address, int Count) { Queue<KThread> SignaledThreads = new Queue<KThread>(); - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); IOrderedEnumerable<KThread> SortedThreads = CondVarThreads.OrderBy(x => x.DynamicPriority); foreach (KThread Thread in SortedThreads.Where(x => x.CondVarAddress == Address)) { - TryAcquireMutex(Process, Memory, Thread); + TryAcquireMutex(Thread); SignaledThreads.Enqueue(Thread); @@ -224,19 +219,21 @@ namespace Ryujinx.HLE.HOS.Kernel CondVarThreads.Remove(Thread); } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); } - private KThread TryAcquireMutex(Process Process, MemoryManager Memory, KThread Requester) + private KThread TryAcquireMutex(KThread Requester) { long Address = Requester.MutexAddress; - Memory.SetExclusive(0, Address); + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); - if (!UserToKernelInt32(Memory, Address, out int MutexValue)) + CurrentProcess.CpuMemory.SetExclusive(0, Address); + + if (!KernelTransfer.UserToKernelInt32(System, Address, out int MutexValue)) { //Invalid address. - Memory.ClearExclusive(0); + CurrentProcess.CpuMemory.ClearExclusive(0); Requester.SignaledObj = null; Requester.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -246,27 +243,27 @@ namespace Ryujinx.HLE.HOS.Kernel while (true) { - if (Memory.TestExclusive(0, Address)) + if (CurrentProcess.CpuMemory.TestExclusive(0, Address)) { if (MutexValue != 0) { //Update value to indicate there is a mutex waiter now. - Memory.WriteInt32(Address, MutexValue | HasListenersMask); + CurrentProcess.CpuMemory.WriteInt32(Address, MutexValue | HasListenersMask); } else { //No thread owning the mutex, assign to requesting thread. - Memory.WriteInt32(Address, Requester.ThreadHandleForUserMutex); + CurrentProcess.CpuMemory.WriteInt32(Address, Requester.ThreadHandleForUserMutex); } - Memory.ClearExclusiveForStore(0); + CurrentProcess.CpuMemory.ClearExclusiveForStore(0); break; } - Memory.SetExclusive(0, Address); + CurrentProcess.CpuMemory.SetExclusive(0, Address); - MutexValue = Memory.ReadInt32(Address); + MutexValue = CurrentProcess.CpuMemory.ReadInt32(Address); } if (MutexValue == 0) @@ -282,7 +279,7 @@ namespace Ryujinx.HLE.HOS.Kernel MutexValue &= ~HasListenersMask; - KThread MutexOwner = Process.HandleTable.GetObject<KThread>(MutexValue); + KThread MutexOwner = CurrentProcess.HandleTable.GetObject<KThread>(MutexValue); if (MutexOwner != null) { @@ -301,16 +298,16 @@ namespace Ryujinx.HLE.HOS.Kernel return MutexOwner; } - public long WaitForAddressIfEqual(MemoryManager Memory, long Address, int Value, long Timeout) + public long WaitForAddressIfEqual(long Address, int Value, long Timeout) { KThread CurrentThread = System.Scheduler.GetCurrentThread(); - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); if (CurrentThread.ShallBeTerminated || CurrentThread.SchedFlags == ThreadSchedState.TerminationPending) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); } @@ -318,9 +315,9 @@ namespace Ryujinx.HLE.HOS.Kernel CurrentThread.SignaledObj = null; CurrentThread.ObjSyncResult = (int)MakeError(ErrorModule.Kernel, KernelErr.Timeout); - if (!UserToKernelInt32(Memory, Address, out int CurrentValue)) + if (!KernelTransfer.UserToKernelInt32(System, Address, out int CurrentValue)) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); } @@ -329,7 +326,7 @@ namespace Ryujinx.HLE.HOS.Kernel { if (Timeout == 0) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.Timeout); } @@ -346,14 +343,14 @@ namespace Ryujinx.HLE.HOS.Kernel System.TimeManager.ScheduleFutureInvocation(CurrentThread, Timeout); } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); if (Timeout > 0) { System.TimeManager.UnscheduleFutureInvocation(CurrentThread); } - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); if (CurrentThread.WaitingInArbitration) { @@ -362,31 +359,26 @@ namespace Ryujinx.HLE.HOS.Kernel CurrentThread.WaitingInArbitration = false; } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return CurrentThread.ObjSyncResult; } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); } - public long WaitForAddressIfLessThan( - MemoryManager Memory, - long Address, - int Value, - bool ShouldDecrement, - long Timeout) + public long WaitForAddressIfLessThan(long Address, int Value, bool ShouldDecrement, long Timeout) { KThread CurrentThread = System.Scheduler.GetCurrentThread(); - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); if (CurrentThread.ShallBeTerminated || CurrentThread.SchedFlags == ThreadSchedState.TerminationPending) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); } @@ -394,12 +386,14 @@ namespace Ryujinx.HLE.HOS.Kernel 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. - Memory.SetExclusive(0, Address); + CurrentProcess.CpuMemory.SetExclusive(0, Address); - if (!UserToKernelInt32(Memory, Address, out int CurrentValue)) + if (!KernelTransfer.UserToKernelInt32(System, Address, out int CurrentValue)) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); } @@ -408,28 +402,28 @@ namespace Ryujinx.HLE.HOS.Kernel { while (CurrentValue < Value) { - if (Memory.TestExclusive(0, Address)) + if (CurrentProcess.CpuMemory.TestExclusive(0, Address)) { - Memory.WriteInt32(Address, CurrentValue - 1); + CurrentProcess.CpuMemory.WriteInt32(Address, CurrentValue - 1); - Memory.ClearExclusiveForStore(0); + CurrentProcess.CpuMemory.ClearExclusiveForStore(0); break; } - Memory.SetExclusive(0, Address); + CurrentProcess.CpuMemory.SetExclusive(0, Address); - CurrentValue = Memory.ReadInt32(Address); + CurrentValue = CurrentProcess.CpuMemory.ReadInt32(Address); } } - Memory.ClearExclusive(0); + CurrentProcess.CpuMemory.ClearExclusive(0); if (CurrentValue < Value) { if (Timeout == 0) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.Timeout); } @@ -446,14 +440,14 @@ namespace Ryujinx.HLE.HOS.Kernel System.TimeManager.ScheduleFutureInvocation(CurrentThread, Timeout); } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); if (Timeout > 0) { System.TimeManager.UnscheduleFutureInvocation(CurrentThread); } - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); if (CurrentThread.WaitingInArbitration) { @@ -462,12 +456,12 @@ namespace Ryujinx.HLE.HOS.Kernel CurrentThread.WaitingInArbitration = false; } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return CurrentThread.ObjSyncResult; } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); } @@ -498,63 +492,65 @@ namespace Ryujinx.HLE.HOS.Kernel public long Signal(long Address, int Count) { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); WakeArbiterThreads(Address, Count); - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return 0; } - public long SignalAndIncrementIfEqual(MemoryManager Memory, long Address, int Value, int Count) + public long SignalAndIncrementIfEqual(long Address, int Value, int Count) { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); + + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); - Memory.SetExclusive(0, Address); + CurrentProcess.CpuMemory.SetExclusive(0, Address); - if (!UserToKernelInt32(Memory, Address, out int CurrentValue)) + if (!KernelTransfer.UserToKernelInt32(System, Address, out int CurrentValue)) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); } while (CurrentValue == Value) { - if (Memory.TestExclusive(0, Address)) + if (CurrentProcess.CpuMemory.TestExclusive(0, Address)) { - Memory.WriteInt32(Address, CurrentValue + 1); + CurrentProcess.CpuMemory.WriteInt32(Address, CurrentValue + 1); - Memory.ClearExclusiveForStore(0); + CurrentProcess.CpuMemory.ClearExclusiveForStore(0); break; } - Memory.SetExclusive(0, Address); + CurrentProcess.CpuMemory.SetExclusive(0, Address); - CurrentValue = Memory.ReadInt32(Address); + CurrentValue = CurrentProcess.CpuMemory.ReadInt32(Address); } - Memory.ClearExclusive(0); + CurrentProcess.CpuMemory.ClearExclusive(0); if (CurrentValue != Value) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); } WakeArbiterThreads(Address, Count); - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return 0; } - public long SignalAndModifyIfEqual(MemoryManager Memory, long Address, int Value, int Count) + public long SignalAndModifyIfEqual(long Address, int Value, int Count) { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); int Offset; @@ -580,43 +576,45 @@ namespace Ryujinx.HLE.HOS.Kernel Offset = 1; } - Memory.SetExclusive(0, Address); + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); - if (!UserToKernelInt32(Memory, Address, out int CurrentValue)) + CurrentProcess.CpuMemory.SetExclusive(0, Address); + + if (!KernelTransfer.UserToKernelInt32(System, Address, out int CurrentValue)) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); } while (CurrentValue == Value) { - if (Memory.TestExclusive(0, Address)) + if (CurrentProcess.CpuMemory.TestExclusive(0, Address)) { - Memory.WriteInt32(Address, CurrentValue + Offset); + CurrentProcess.CpuMemory.WriteInt32(Address, CurrentValue + Offset); - Memory.ClearExclusiveForStore(0); + CurrentProcess.CpuMemory.ClearExclusiveForStore(0); break; } - Memory.SetExclusive(0, Address); + CurrentProcess.CpuMemory.SetExclusive(0, Address); - CurrentValue = Memory.ReadInt32(Address); + CurrentValue = CurrentProcess.CpuMemory.ReadInt32(Address); } - Memory.ClearExclusive(0); + CurrentProcess.CpuMemory.ClearExclusive(0); if (CurrentValue != Value) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); } WakeArbiterThreads(Address, Count); - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return 0; } @@ -648,31 +646,5 @@ namespace Ryujinx.HLE.HOS.Kernel ArbiterThreads.Remove(Thread); } } - - private bool UserToKernelInt32(MemoryManager Memory, long Address, out int Value) - { - if (Memory.IsMapped(Address)) - { - Value = Memory.ReadInt32(Address); - - return true; - } - - Value = 0; - - return false; - } - - private bool KernelToUserInt32(MemoryManager Memory, long Address, int Value) - { - if (Memory.IsMapped(Address)) - { - Memory.WriteInt32ToSharedAddr(Address, Value); - - return true; - } - - return false; - } } } diff --git a/Ryujinx.HLE/HOS/Kernel/KAutoObject.cs b/Ryujinx.HLE/HOS/Kernel/KAutoObject.cs new file mode 100644 index 00000000..a91bf9a8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KAutoObject.cs @@ -0,0 +1,42 @@ +namespace Ryujinx.HLE.HOS.Kernel +{ + class KAutoObject + { + protected Horizon System; + + public KAutoObject(Horizon System) + { + this.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 new file mode 100644 index 00000000..e3f8128b --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KClientPort.cs @@ -0,0 +1,31 @@ +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) + { + this.MaxSessions = MaxSessions; + this.Parent = Parent; + } + + public new static KernelResult RemoveName(Horizon System, string Name) + { + KAutoObject FoundObj = KAutoObject.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 new file mode 100644 index 00000000..1c95f811 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KConditionVariable.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel +{ + static class KConditionVariable + { + public static void Wait(Horizon System, LinkedList<KThread> 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<KThread> ThreadList) + { + System.CriticalSection.Enter(); + + LinkedListNode<KThread> 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 new file mode 100644 index 00000000..03e7dddf --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KContextIdManager.cs @@ -0,0 +1,83 @@ +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 index 02354e16..638dde9e 100644 --- a/Ryujinx.HLE/HOS/Kernel/KCoreContext.cs +++ b/Ryujinx.HLE/HOS/Kernel/KCoreContext.cs @@ -1,5 +1,4 @@ using Ryujinx.Common; -using System; namespace Ryujinx.HLE.HOS.Kernel { @@ -11,6 +10,10 @@ namespace Ryujinx.HLE.HOS.Kernel 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; } @@ -24,11 +27,6 @@ namespace Ryujinx.HLE.HOS.Kernel { SelectedThread = Thread; - if (Thread != null) - { - Thread.LastScheduledTicks = PerformanceCounter.ElapsedMilliseconds; - } - if (SelectedThread != CurrentThread) { ContextSwitchNeeded = true; @@ -39,25 +37,42 @@ namespace Ryujinx.HLE.HOS.Kernel { 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.GetThread(CurrentThread.Context.Work).Reset(); + 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.GetThread(CurrentThread.Context.Work).Set(); + CoreManager.Set(CurrentThread.Context.Work); CurrentThread.Context.Execute(); } diff --git a/Ryujinx.HLE/HOS/Kernel/KRecursiveLock.cs b/Ryujinx.HLE/HOS/Kernel/KCriticalSection.cs index 30c1a880..b02a1195 100644 --- a/Ryujinx.HLE/HOS/Kernel/KRecursiveLock.cs +++ b/Ryujinx.HLE/HOS/Kernel/KCriticalSection.cs @@ -3,7 +3,7 @@ using System.Threading; namespace Ryujinx.HLE.HOS.Kernel { - class KRecursiveLock + class KCriticalSection { private Horizon System; @@ -11,21 +11,21 @@ namespace Ryujinx.HLE.HOS.Kernel private int RecursionCount; - public KRecursiveLock(Horizon System) + public KCriticalSection(Horizon System) { this.System = System; LockObj = new object(); } - public void Lock() + public void Enter() { Monitor.Enter(LockObj); RecursionCount++; } - public void Unlock() + public void Leave() { if (RecursionCount == 0) { diff --git a/Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs b/Ryujinx.HLE/HOS/Kernel/KHandleTable.cs index 682f08d4..e39dfb67 100644 --- a/Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs +++ b/Ryujinx.HLE/HOS/Kernel/KHandleTable.cs @@ -2,7 +2,7 @@ using System; namespace Ryujinx.HLE.HOS.Kernel { - class KProcessHandleTable + class KHandleTable { private const int SelfThreadHandle = (0x1ffff << 15) | 0; private const int SelfProcessHandle = (0x1ffff << 15) | 1; @@ -20,12 +20,24 @@ namespace Ryujinx.HLE.HOS.Kernel private ushort IdCounter; - private object LockObj; - - public KProcessHandleTable(Horizon System, int Size = 1024) + public KHandleTable(Horizon System) { this.System = System; - this.Size = Size; + } + + public KernelResult Initialize(int Size) + { + if ((uint)Size > 1024) + { + return KernelResult.OutOfMemory; + } + + if (Size < 1) + { + Size = 1024; + } + + this.Size = Size; IdCounter = 1; @@ -48,14 +60,14 @@ namespace Ryujinx.HLE.HOS.Kernel NextFreeEntry = TableHead; - LockObj = new object(); + return KernelResult.Success; } public KernelResult GenerateHandle(object Obj, out int Handle) { Handle = 0; - lock (LockObj) + lock (Table) { if (ActiveSlotsCount >= Size) { @@ -95,12 +107,12 @@ namespace Ryujinx.HLE.HOS.Kernel return false; } - int Index = (Handle >> 0) & 0x7fff; + int Index = (Handle >> 0) & 0x7fff; int HandleId = (Handle >> 15); bool Result = false; - lock (LockObj) + lock (Table) { if (HandleId != 0 && Index < Size) { @@ -125,10 +137,10 @@ namespace Ryujinx.HLE.HOS.Kernel public T GetObject<T>(int Handle) { - int Index = (Handle >> 0) & 0x7fff; + int Index = (Handle >> 0) & 0x7fff; int HandleId = (Handle >> 15); - lock (LockObj) + lock (Table) { if ((Handle >> 30) == 0 && HandleId != 0) { @@ -156,9 +168,21 @@ namespace Ryujinx.HLE.HOS.Kernel } } + public KProcess GetKProcess(int Handle) + { + if (Handle == SelfProcessHandle) + { + return System.Scheduler.GetCurrentProcess(); + } + else + { + return GetObject<KProcess>(Handle); + } + } + public void Destroy() { - lock (LockObj) + lock (Table) { for (int Index = 0; Index < Size; Index++) { diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryArrange.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryArrange.cs new file mode 100644 index 00000000..af393b68 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KMemoryArrange.cs @@ -0,0 +1,22 @@ +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) + { + this.Service = Service; + this.NvServices = NvServices; + this.Applet = Applet; + this.Application = Application; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryArrangeRegion.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryArrangeRegion.cs new file mode 100644 index 00000000..7d66e291 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KMemoryArrangeRegion.cs @@ -0,0 +1,16 @@ +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) + { + this.Address = Address; + this.Size = Size; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs index 6100741b..08190236 100644 --- a/Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs +++ b/Ryujinx.HLE/HOS/Kernel/KMemoryBlock.cs @@ -2,8 +2,8 @@ namespace Ryujinx.HLE.HOS.Kernel { class KMemoryBlock { - public long BasePosition { get; set; } - public long PagesCount { get; set; } + public ulong BaseAddress { get; set; } + public ulong PagesCount { get; set; } public MemoryState State { get; set; } public MemoryPermission Permission { get; set; } @@ -13,25 +13,25 @@ namespace Ryujinx.HLE.HOS.Kernel public int DeviceRefCount { get; set; } public KMemoryBlock( - long BasePosition, - long PagesCount, + ulong BaseAddress, + ulong PagesCount, MemoryState State, MemoryPermission Permission, MemoryAttribute Attribute) { - this.BasePosition = BasePosition; - this.PagesCount = PagesCount; - this.State = State; - this.Attribute = Attribute; - this.Permission = Permission; + this.BaseAddress = BaseAddress; + this.PagesCount = PagesCount; + this.State = State; + this.Attribute = Attribute; + this.Permission = Permission; } public KMemoryInfo GetInfo() { - long Size = PagesCount * KMemoryManager.PageSize; + ulong Size = PagesCount * KMemoryManager.PageSize; return new KMemoryInfo( - BasePosition, + BaseAddress, Size, State, Permission, diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryBlockAllocator.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryBlockAllocator.cs new file mode 100644 index 00000000..08512e12 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KMemoryBlockAllocator.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Kernel +{ + class KMemoryBlockAllocator + { + private ulong CapacityElements; + + public int Count { get; set; } + + public KMemoryBlockAllocator(ulong CapacityElements) + { + this.CapacityElements = CapacityElements; + } + + public bool CanAllocate(int Count) + { + return (ulong)(this.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 index 9b73b32b..09ba88f2 100644 --- a/Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs +++ b/Ryujinx.HLE/HOS/Kernel/KMemoryInfo.cs @@ -2,8 +2,8 @@ namespace Ryujinx.HLE.HOS.Kernel { class KMemoryInfo { - public long Position { get; private set; } - public long Size { get; private set; } + public ulong Address { get; private set; } + public ulong Size { get; private set; } public MemoryState State { get; private set; } public MemoryPermission Permission { get; private set; } @@ -13,15 +13,15 @@ namespace Ryujinx.HLE.HOS.Kernel public int DeviceRefCount { get; private set; } public KMemoryInfo( - long Position, - long Size, + ulong Address, + ulong Size, MemoryState State, MemoryPermission Permission, MemoryAttribute Attribute, int IpcRefCount, int DeviceRefCount) { - this.Position = Position; + this.Address = Address; this.Size = Size; this.State = State; this.Attribute = Attribute; diff --git a/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs b/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs index 4cfb2aa2..0aa21e3f 100644 --- a/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs +++ b/Ryujinx.HLE/HOS/Kernel/KMemoryManager.cs @@ -1,331 +1,858 @@ using ChocolArm64.Memory; -using Ryujinx.HLE.Memory; +using Ryujinx.Common; using System; using System.Collections.Generic; -using static Ryujinx.HLE.HOS.ErrorCode; - 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<KMemoryBlock> Blocks; private MemoryManager CpuMemory; - private ArenaAllocator Allocator; + 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 long AddrSpaceStart { get; private set; } - public long AddrSpaceEnd { get; private set; } + public ulong AliasRegionStart { get; private set; } + public ulong AliasRegionEnd { get; private set; } - public long CodeRegionStart { get; private set; } - public long CodeRegionEnd { get; private set; } + public ulong StackRegionStart { get; private set; } + public ulong StackRegionEnd { get; private set; } - public long MapRegionStart { get; private set; } - public long MapRegionEnd { get; private set; } + public ulong TlsIoRegionStart { get; private set; } + public ulong TlsIoRegionEnd { get; private set; } - public long HeapRegionStart { get; private set; } - public long HeapRegionEnd { get; private set; } + private ulong HeapCapacity; - public long NewMapRegionStart { get; private set; } - public long NewMapRegionEnd { get; private set; } + public ulong PhysicalMemoryUsage { get; private set; } - public long TlsIoRegionStart { get; private set; } - public long TlsIoRegionEnd { get; private set; } + private MemoryRegion MemRegion; - public long PersonalMmHeapUsage { get; private set; } + private bool AslrDisabled; - private long CurrentHeapAddr; + public int AddrSpaceWidth { get; private set; } - public KMemoryManager(Process Process) + private bool IsKernel; + private bool AslrEnabled; + + private KMemoryBlockAllocator BlockAllocator; + + private int ContextId; + + private MersenneTwister RandomNumberGenerator; + + public KMemoryManager(Horizon System, MemoryManager CpuMemory) { - CpuMemory = Process.Memory; - Allocator = Process.Device.Memory.Allocator; + this.System = System; + this.CpuMemory = CpuMemory; - long CodeRegionSize; - long MapRegionSize; - long HeapRegionSize; - long NewMapRegionSize; - long TlsIoRegionSize; - int AddrSpaceWidth; + Blocks = new LinkedList<KMemoryBlock>(); + } - AddressSpaceType AddrType = AddressSpaceType.Addr39Bits; + private static readonly int[] AddrSpaceSizes = new int[] { 32, 36, 32, 39 }; - if (Process.MetaData != null) + public KernelResult InitializeForProcess( + AddressSpaceType AddrSpaceType, + bool AslrEnabled, + bool AslrDisabled, + MemoryRegion MemRegion, + ulong Address, + ulong Size, + KMemoryBlockAllocator BlockAllocator) + { + if ((uint)AddrSpaceType > (uint)AddressSpaceType.Addr39Bits) { - AddrType = (AddressSpaceType)Process.MetaData.AddressSpaceWidth; + throw new ArgumentException(nameof(AddrSpaceType)); } - switch (AddrType) + 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: - CodeRegionStart = 0x200000; - CodeRegionSize = 0x3fe00000; - MapRegionSize = 0x40000000; - HeapRegionSize = 0x40000000; - NewMapRegionSize = 0; - TlsIoRegionSize = 0; - AddrSpaceWidth = 32; + 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: - CodeRegionStart = 0x8000000; - CodeRegionSize = 0x78000000; - MapRegionSize = 0x180000000; - HeapRegionSize = 0x180000000; - NewMapRegionSize = 0; - TlsIoRegionSize = 0; - AddrSpaceWidth = 36; + 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.Addr36BitsNoMap: - CodeRegionStart = 0x200000; - CodeRegionSize = 0x3fe00000; - MapRegionSize = 0; - HeapRegionSize = 0x80000000; - NewMapRegionSize = 0; - TlsIoRegionSize = 0; - AddrSpaceWidth = 36; + 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: - CodeRegionStart = 0x8000000; - CodeRegionSize = 0x80000000; - MapRegionSize = 0x1000000000; - HeapRegionSize = 0x180000000; - NewMapRegionSize = 0x80000000; - TlsIoRegionSize = 0x1000000000; - AddrSpaceWidth = 39; + 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 InvalidOperationException(); + 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; } - AddrSpaceStart = 0; - AddrSpaceEnd = 1L << AddrSpaceWidth; + ulong MapTotalSize = AliasRegion.Size + HeapRegion.Size + StackRegion.Size + TlsIoRegion.Size; + + ulong AslrMaxOffset = MapAvailableSize - MapTotalSize; - CodeRegionEnd = CodeRegionStart + CodeRegionSize; - MapRegionStart = CodeRegionEnd; - MapRegionEnd = CodeRegionEnd + MapRegionSize; - HeapRegionStart = MapRegionEnd; - HeapRegionEnd = MapRegionEnd + HeapRegionSize; - NewMapRegionStart = HeapRegionEnd; - NewMapRegionEnd = HeapRegionEnd + NewMapRegionSize; - TlsIoRegionStart = NewMapRegionEnd; - TlsIoRegionEnd = NewMapRegionEnd + TlsIoRegionSize; + this.AslrEnabled = AslrEnabled; - CurrentHeapAddr = HeapRegionStart; + this.AddrSpaceStart = AddrSpaceStart; + this.AddrSpaceEnd = AddrSpaceEnd; - if (NewMapRegionSize == 0) + this.BlockAllocator = BlockAllocator; + + if (MapAvailableSize < MapTotalSize) { - NewMapRegionStart = AddrSpaceStart; - NewMapRegionEnd = AddrSpaceEnd; + return KernelResult.OutOfMemory; } - Blocks = new LinkedList<KMemoryBlock>(); + 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; - long AddrSpacePagesCount = (AddrSpaceEnd - AddrSpaceStart) / PageSize; + 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; + + this.MemRegion = MemRegion; + this.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 void HleMapProcessCode(long Position, long Size) + public KernelResult MapPages( + ulong Address, + KPageList PageList, + MemoryState State, + MemoryPermission Permission) { - long PagesCount = Size / PageSize; + ulong PagesCount = PageList.GetPagesCount(); + + ulong Size = PagesCount * PageSize; - if (!Allocator.TryAllocate(Size, out long PA)) + if (!ValidateRegionForState(Address, Size, State)) { - throw new InvalidOperationException(); + return KernelResult.InvalidMemState; } lock (Blocks) { - InsertBlock(Position, PagesCount, MemoryState.CodeStatic, MemoryPermission.ReadAndExecute); + 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); + } - CpuMemory.Map(Position, PA, Size); + return Result; } } - public long MapProcessCodeMemory(long Dst, long Src, long Size) + 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) { - long PagesCount = Size / PageSize; + KPageList CurrentPageList = new KPageList(); - bool Success = IsUnmapped(Dst, Size); + AddVaRangeToPageList(CurrentPageList, Address, PagesCount); - Success &= CheckRange( - Src, - Size, - MemoryState.Mask, - MemoryState.Heap, - MemoryPermission.Mask, - MemoryPermission.ReadAndWrite, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out _, - out _, - out _); + if (!CurrentPageList.IsEqual(PageList)) + { + return KernelResult.InvalidMemRange; + } - if (Success) + if (CheckRange( + Address, + Size, + MemoryState.Mask, + StateExpected, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState State, + out _, + out _)) { - long PA = CpuMemory.GetPhysicalAddress(Src); + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } - InsertBlock(Dst, PagesCount, MemoryState.CodeStatic, MemoryPermission.ReadAndExecute); - InsertBlock(Src, PagesCount, MemoryState.Heap, MemoryPermission.None); + KernelResult Result = MmuUnmap(Address, PagesCount); - CpuMemory.Map(Dst, PA, Size); + if (Result == KernelResult.Success) + { + InsertBlock(Address, PagesCount, MemoryState.Unmapped); + } - return 0; + return Result; + } + else + { + return KernelResult.InvalidMemState; } } + } + + public KernelResult MapNormalMemory(long Address, long Size, MemoryPermission Permission) + { + //TODO. + return KernelResult.Success; + } - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + public KernelResult MapIoMemory(long Address, long Size, MemoryPermission Permission) + { + //TODO. + return KernelResult.Success; } - public long UnmapProcessCodeMemory(long Dst, long Src, long Size) + 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) { - long PagesCount = Size / PageSize; + if (AslrEnabled) + { + ulong TotalNeededSize = (ReservedPagesCount + NeededPagesCount) * PageSize; - bool Success = CheckRange( - Dst, - Size, - MemoryState.Mask, - MemoryState.CodeStatic, - MemoryPermission.None, - MemoryPermission.None, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out _, - out _, - out _); + ulong RemainingPages = RegionPagesCount - NeededPagesCount; - Success &= CheckRange( - Src, - Size, - MemoryState.Mask, - MemoryState.Heap, - MemoryPermission.Mask, - MemoryPermission.None, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out _, - out _, - out _); + ulong AslrMaxOffset = ((RemainingPages + ReservedPagesCount) * PageSize) / (ulong)Alignment; - if (Success) + 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) { - InsertBlock(Dst, PagesCount, MemoryState.Unmapped); - InsertBlock(Src, PagesCount, MemoryState.Heap, MemoryPermission.ReadAndWrite); + Address = FindFirstFit( + RegionStart, + RegionPagesCount, + NeededPagesCount, + Alignment, + 0, + ReservedPagesCount); + } - CpuMemory.Unmap(Dst, Size); + if (Address == 0) + { + return KernelResult.OutOfMemory; + } + + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + MemoryOperation Operation = Map + ? MemoryOperation.MapPa + : MemoryOperation.Allocate; - return 0; + KernelResult Result = DoMmuOperation( + Address, + NeededPagesCount, + SrcPa, + Map, + Permission, + Operation); + + if (Result != KernelResult.Success) + { + return Result; } + + InsertBlock(Address, NeededPagesCount, State, Permission); } - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + return KernelResult.Success; } - public void HleMapCustom(long Position, long Size, MemoryState State, MemoryPermission Permission) + public KernelResult MapNewProcessCode( + ulong Address, + ulong PagesCount, + MemoryState State, + MemoryPermission Permission) { - long PagesCount = Size / PageSize; + ulong Size = PagesCount * PageSize; - if (!Allocator.TryAllocate(Size, out long PA)) + if (!ValidateRegionForState(Address, Size, State)) { - throw new InvalidOperationException(); + return KernelResult.InvalidMemState; } lock (Blocks) { - InsertBlock(Position, PagesCount, State, Permission); + if (!IsUnmapped(Address, Size)) + { + return KernelResult.InvalidMemState; + } - CpuMemory.Map(Position, PA, Size); + 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 long HleMapTlsPage() + public KernelResult MapProcessCodeMemory(ulong Dst, ulong Src, ulong Size) { - bool HasTlsIoRegion = TlsIoRegionStart != TlsIoRegionEnd; - - long Position = HasTlsIoRegion ? TlsIoRegionStart : CodeRegionStart; + ulong PagesCount = Size / PageSize; lock (Blocks) { - while (Position < (HasTlsIoRegion ? TlsIoRegionEnd : CodeRegionEnd)) + 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 (FindBlock(Position).State == MemoryState.Unmapped) + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) { - InsertBlock(Position, 1, MemoryState.ThreadLocal, MemoryPermission.ReadAndWrite); + return KernelResult.OutOfResource; + } - if (!Allocator.TryAllocate(PageSize, out long PA)) - { - throw new InvalidOperationException(); - } + KPageList PageList = new KPageList(); + + AddVaRangeToPageList(PageList, Src, PagesCount); - CpuMemory.Map(Position, PA, PageSize); + 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 Position; + return Result; } - Position += PageSize; + 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 _); - throw new InvalidOperationException(); + 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 long TrySetHeapSize(long Size, out long Position) + public KernelResult SetHeapSize(ulong Size, out ulong Address) { - Position = 0; + Address = 0; - if ((ulong)Size > (ulong)(HeapRegionEnd - HeapRegionStart)) + if (Size > HeapRegionEnd - HeapRegionStart) { - return MakeError(ErrorModule.Kernel, KernelErr.OutOfMemory); + return KernelResult.OutOfMemory; } - bool Success = false; + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); - long CurrentHeapSize = GetHeapSize(); + ulong CurrentHeapSize = GetHeapSize(); - if ((ulong)CurrentHeapSize <= (ulong)Size) + if (CurrentHeapSize <= Size) { //Expand. - long DiffSize = Size - CurrentHeapSize; + ulong DiffSize = Size - CurrentHeapSize; lock (Blocks) { - if (Success = IsUnmapped(CurrentHeapAddr, DiffSize)) + 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 (!Allocator.TryAllocate(DiffSize, out long PA)) + if (PageList != null) + { + Region.FreePages(PageList); + } + + if (CurrentProcess.ResourceLimit != null && DiffSize != 0) { - return MakeError(ErrorModule.Kernel, KernelErr.OutOfMemory); + CurrentProcess.ResourceLimit.Release(LimitableResource.Memory, DiffSize); } + } + + if (Result != KernelResult.Success) + { + CleanUpForError(); + + return Result; + } - long PagesCount = DiffSize / PageSize; + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + CleanUpForError(); - InsertBlock(CurrentHeapAddr, PagesCount, MemoryState.Heap, MemoryPermission.ReadAndWrite); + return KernelResult.OutOfResource; + } - CpuMemory.Map(CurrentHeapAddr, PA, DiffSize); + 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. - long FreeAddr = HeapRegionStart + Size; - long DiffSize = CurrentHeapSize - Size; + ulong FreeAddr = HeapRegionStart + Size; + ulong DiffSize = CurrentHeapSize - Size; lock (Blocks) { - Success = CheckRange( + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + if (!CheckRange( FreeAddr, DiffSize, MemoryState.Mask, @@ -337,48 +864,66 @@ namespace Ryujinx.HLE.HOS.Kernel MemoryAttribute.IpcAndDeviceMapped, out _, out _, - out _); - - if (Success) + out _)) { - long PagesCount = DiffSize / PageSize; + return KernelResult.InvalidMemState; + } - InsertBlock(FreeAddr, PagesCount, MemoryState.Unmapped); + ulong PagesCount = DiffSize / PageSize; - FreePages(FreeAddr, PagesCount); + KernelResult Result = MmuUnmap(FreeAddr, PagesCount); - CpuMemory.Unmap(FreeAddr, DiffSize); + if (Result != KernelResult.Success) + { + return Result; } + + CurrentProcess.ResourceLimit?.Release(LimitableResource.Memory, BitUtils.AlignDown(DiffSize, PageSize)); + + InsertBlock(FreeAddr, PagesCount, MemoryState.Unmapped); } } CurrentHeapAddr = HeapRegionStart + Size; - if (Success) - { - Position = HeapRegionStart; + Address = HeapRegionStart; - return 0; - } + return KernelResult.Success; + } - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + public ulong GetTotalHeapSize() + { + lock (Blocks) + { + return GetHeapSize() + PhysicalMemoryUsage; + } } - public long GetHeapSize() + private ulong GetHeapSize() { return CurrentHeapAddr - HeapRegionStart; } - public long SetMemoryAttribute( - long Position, - long Size, + 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( - Position, + Address, Size, MemoryState.AttributeChangeAllowed, MemoryState.AttributeChangeAllowed, @@ -391,35 +936,42 @@ namespace Ryujinx.HLE.HOS.Kernel out MemoryPermission Permission, out MemoryAttribute Attribute)) { - long PagesCount = Size / PageSize; + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong PagesCount = Size / PageSize; Attribute &= ~AttributeMask; Attribute |= AttributeMask & AttributeValue; - InsertBlock(Position, PagesCount, State, Permission, Attribute); + InsertBlock(Address, PagesCount, State, Permission, Attribute); - return 0; + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; } } - - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); } - public KMemoryInfo QueryMemory(long Position) + public KMemoryInfo QueryMemory(ulong Address) { - if ((ulong)Position >= (ulong)AddrSpaceStart && - (ulong)Position < (ulong)AddrSpaceEnd) + if (Address >= AddrSpaceStart && + Address < AddrSpaceEnd) { lock (Blocks) { - return FindBlock(Position).GetInfo(); + return FindBlock(Address).GetInfo(); } } else { return new KMemoryInfo( AddrSpaceEnd, - -AddrSpaceEnd, + ~AddrSpaceEnd + 1, MemoryState.Reserved, MemoryPermission.None, MemoryAttribute.None, @@ -428,7 +980,7 @@ namespace Ryujinx.HLE.HOS.Kernel } } - public long Map(long Src, long Dst, long Size) + public KernelResult Map(ulong Dst, ulong Src, ulong Size) { bool Success; @@ -452,22 +1004,90 @@ namespace Ryujinx.HLE.HOS.Kernel if (Success) { - long PagesCount = Size / PageSize; + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } - InsertBlock(Src, PagesCount, SrcState, MemoryPermission.None, MemoryAttribute.Borrowed); + ulong PagesCount = Size / PageSize; - InsertBlock(Dst, PagesCount, MemoryState.MappedMemory, MemoryPermission.ReadAndWrite); + KPageList PageList = new KPageList(); - long PA = CpuMemory.GetPhysicalAddress(Src); + AddVaRangeToPageList(PageList, Src, PagesCount); - CpuMemory.Map(Dst, PA, Size); + 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); - return Success ? 0 : MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + if (Result == KernelResult.Success) + { + InsertBlock(Address, PagesCount, MemoryState.Unmapped); + } + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } } - public long Unmap(long Src, long Dst, long Size) + public KernelResult Unmap(ulong Dst, ulong Src, ulong Size) { bool Success; @@ -491,87 +1111,70 @@ namespace Ryujinx.HLE.HOS.Kernel Dst, Size, MemoryState.Mask, - MemoryState.MappedMemory, + MemoryState.Stack, MemoryPermission.None, MemoryPermission.None, MemoryAttribute.Mask, MemoryAttribute.None, MemoryAttribute.IpcAndDeviceMapped, out _, - out _, + out MemoryPermission DstPermission, out _); if (Success) { - long PagesCount = Size / PageSize; - - InsertBlock(Src, PagesCount, SrcState, MemoryPermission.ReadAndWrite); + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } - InsertBlock(Dst, PagesCount, MemoryState.Unmapped); + ulong PagesCount = Size / PageSize; - CpuMemory.Unmap(Dst, Size); - } - } + KPageList SrcPageList = new KPageList(); + KPageList DstPageList = new KPageList(); - return Success ? 0 : MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - } + AddVaRangeToPageList(SrcPageList, Src, PagesCount); + AddVaRangeToPageList(DstPageList, Dst, PagesCount); - public long MapSharedMemory(KSharedMemory SharedMemory, MemoryPermission Permission, long Position) - { - lock (Blocks) - { - if (IsUnmapped(Position, SharedMemory.Size)) - { - long PagesCount = SharedMemory.Size / PageSize; - - InsertBlock(Position, PagesCount, MemoryState.SharedMemory, Permission); + if (!DstPageList.IsEqual(SrcPageList)) + { + return KernelResult.InvalidMemRange; + } - CpuMemory.Map(Position, SharedMemory.PA, SharedMemory.Size); + KernelResult Result = MmuUnmap(Dst, PagesCount); - return 0; - } - } + if (Result != KernelResult.Success) + { + return Result; + } - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - } + Result = MmuChangePermission(Src, PagesCount, MemoryPermission.ReadAndWrite); - public long UnmapSharedMemory(long Position, long Size) - { - lock (Blocks) - { - if (CheckRange( - Position, - Size, - MemoryState.Mask, - MemoryState.SharedMemory, - MemoryPermission.None, - MemoryPermission.None, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out MemoryState State, - out _, - out _)) - { - long PagesCount = Size / PageSize; + if (Result != KernelResult.Success) + { + MapPages(Dst, DstPageList, DstPermission); - InsertBlock(Position, PagesCount, MemoryState.Unmapped); + return Result; + } - CpuMemory.Unmap(Position, Size); + InsertBlock(Src, PagesCount, SrcState, MemoryPermission.ReadAndWrite); + InsertBlock(Dst, PagesCount, MemoryState.Unmapped); - return 0; + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; } } - - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); } - public long ReserveTransferMemory(long Position, long Size, MemoryPermission Permission) + public KernelResult ReserveTransferMemory(ulong Address, ulong Size, MemoryPermission Permission) { lock (Blocks) { if (CheckRange( - Position, + Address, Size, MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated, MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated, @@ -584,25 +1187,34 @@ namespace Ryujinx.HLE.HOS.Kernel out _, out MemoryAttribute Attribute)) { - long PagesCount = Size / PageSize; + //TODO: Missing checks. + + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong PagesCount = Size / PageSize; Attribute |= MemoryAttribute.Borrowed; - InsertBlock(Position, PagesCount, State, Permission, Attribute); + InsertBlock(Address, PagesCount, State, Permission, Attribute); - return 0; + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; } } - - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); } - public long ResetTransferMemory(long Position, long Size) + public KernelResult ResetTransferMemory(ulong Address, ulong Size) { lock (Blocks) { if (CheckRange( - Position, + Address, Size, MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated, MemoryState.TransferMemoryAllowed | MemoryState.IsPoolAllocated, @@ -615,23 +1227,30 @@ namespace Ryujinx.HLE.HOS.Kernel out _, out _)) { - long PagesCount = Size / PageSize; + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong PagesCount = Size / PageSize; - InsertBlock(Position, PagesCount, State, MemoryPermission.ReadAndWrite); + InsertBlock(Address, PagesCount, State, MemoryPermission.ReadAndWrite); - return 0; + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; } } - - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); } - public long SetProcessMemoryPermission(long Position, long Size, MemoryPermission Permission) + public KernelResult SetProcessMemoryPermission(ulong Address, ulong Size, MemoryPermission Permission) { lock (Blocks) { if (CheckRange( - Position, + Address, Size, MemoryState.ProcessPermissionChangeAllowed, MemoryState.ProcessPermissionChangeAllowed, @@ -640,47 +1259,73 @@ namespace Ryujinx.HLE.HOS.Kernel MemoryAttribute.Mask, MemoryAttribute.None, MemoryAttribute.IpcAndDeviceMapped, - out MemoryState State, - out _, + out MemoryState OldState, + out MemoryPermission OldPermission, out _)) { - if (State == MemoryState.CodeStatic) - { - State = MemoryState.CodeMutable; - } - else if (State == MemoryState.ModCodeStatic) + MemoryState NewState = OldState; + + //If writing into the code region is allowed, then we need + //to change it to mutable. + if ((Permission & MemoryPermission.Write) != 0) { - State = MemoryState.ModCodeMutable; + 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."); + } } - else + + if (NewState != OldState || Permission != OldPermission) { - throw new InvalidOperationException(); - } + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } - long PagesCount = Size / PageSize; + ulong PagesCount = Size / PageSize; - InsertBlock(Position, PagesCount, State, Permission); + MemoryOperation Operation = (Permission & MemoryPermission.Execute) != 0 + ? MemoryOperation.ChangePermsAndAttributes + : MemoryOperation.ChangePermRw; - return 0; + 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; } } - - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); } - public long MapPhysicalMemory(long Position, long Size) + public KernelResult MapPhysicalMemory(ulong Address, ulong Size) { - long End = Position + Size; + ulong EndAddr = Address + Size; lock (Blocks) { - long MappedSize = 0; + ulong MappedSize = 0; KMemoryInfo Info; - LinkedListNode<KMemoryBlock> BaseNode = FindBlockNode(Position); - - LinkedListNode<KMemoryBlock> Node = BaseNode; + LinkedListNode<KMemoryBlock> Node = FindBlockNode(Address); do { @@ -688,57 +1333,66 @@ namespace Ryujinx.HLE.HOS.Kernel if (Info.State != MemoryState.Unmapped) { - MappedSize += GetSizeInRange(Info, Position, End); + MappedSize += GetSizeInRange(Info, Address, EndAddr); } Node = Node.Next; } - while ((ulong)(Info.Position + Info.Size) < (ulong)End && Node != null); + while (Info.Address + Info.Size < EndAddr && Node != null); if (MappedSize == Size) { - return 0; + return KernelResult.Success; } - long RemainingSize = Size - MappedSize; + ulong RemainingSize = Size - MappedSize; + + ulong RemainingPages = RemainingSize / PageSize; - if (!Allocator.TryAllocate(RemainingSize, out long PA)) + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + if (CurrentProcess.ResourceLimit != null && + !CurrentProcess.ResourceLimit.Reserve(LimitableResource.Memory, RemainingSize)) { - return MakeError(ErrorModule.Kernel, KernelErr.OutOfMemory); + return KernelResult.ResLimitExceeded; } - Node = BaseNode; + KMemoryRegionManager Region = GetMemoryRegionManager(); - do - { - Info = Node.Value.GetInfo(); + KernelResult Result = Region.AllocatePages(RemainingPages, AslrDisabled, out KPageList PageList); - if (Info.State == MemoryState.Unmapped) + void CleanUpForError() + { + if (PageList != null) { - long CurrSize = GetSizeInRange(Info, Position, End); + Region.FreePages(PageList); + } - long MapPosition = Info.Position; + CurrentProcess.ResourceLimit?.Release(LimitableResource.Memory, RemainingSize); + } - if ((ulong)MapPosition < (ulong)Position) - { - MapPosition = Position; - } + if (Result != KernelResult.Success) + { + CleanUpForError(); - CpuMemory.Map(MapPosition, PA, CurrSize); + return Result; + } - PA += CurrSize; - } + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + CleanUpForError(); - Node = Node.Next; + return KernelResult.OutOfResource; } - while ((ulong)(Info.Position + Info.Size) < (ulong)End && Node != null); - PersonalMmHeapUsage += RemainingSize; + MapPhysicalMemory(PageList, Address, EndAddr); - long PagesCount = Size / PageSize; + PhysicalMemoryUsage += RemainingSize; + + ulong PagesCount = Size / PageSize; InsertBlock( - Position, + Address, PagesCount, MemoryState.Unmapped, MemoryPermission.None, @@ -748,22 +1402,26 @@ namespace Ryujinx.HLE.HOS.Kernel MemoryAttribute.None); } - return 0; + return KernelResult.Success; } - public long UnmapPhysicalMemory(long Position, long Size) + public KernelResult UnmapPhysicalMemory(ulong Address, ulong Size) { - long End = Position + Size; + ulong EndAddr = Address + Size; lock (Blocks) { - long HeapMappedSize = 0; + //Scan, ensure that the region can be unmapped (all blocks are heap or + //already unmapped), fill pages list for freeing memory. + ulong HeapMappedSize = 0; - long CurrPosition = Position; + KPageList PageList = new KPageList(); KMemoryInfo Info; - LinkedListNode<KMemoryBlock> Node = FindBlockNode(CurrPosition); + LinkedListNode<KMemoryBlock> BaseNode = FindBlockNode(Address); + + LinkedListNode<KMemoryBlock> Node = BaseNode; do { @@ -773,90 +1431,200 @@ namespace Ryujinx.HLE.HOS.Kernel { if (Info.Attribute != MemoryAttribute.None) { - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + return KernelResult.InvalidMemState; } - HeapMappedSize += GetSizeInRange(Info, Position, End); + 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 MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + return KernelResult.InvalidMemState; } Node = Node.Next; } - while ((ulong)(Info.Position + Info.Size) < (ulong)End && Node != null); + while (Info.Address + Info.Size < EndAddr && Node != null); if (HeapMappedSize == 0) { - return 0; + return KernelResult.Success; + } + + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; } - PersonalMmHeapUsage -= HeapMappedSize; + //Try to unmap all the heap mapped memory inside range. + KernelResult Result = KernelResult.Success; - long PagesCount = Size / PageSize; + Node = BaseNode; - InsertBlock(Position, PagesCount, MemoryState.Unmapped); + do + { + Info = Node.Value.GetInfo(); + + if (Info.State == MemoryState.Heap) + { + ulong BlockSize = GetSizeInRange(Info, Address, EndAddr); + ulong BlockAddress = GetAddrInRange(Info, Address); - FreePages(Position, PagesCount); + ulong BlockPagesCount = BlockSize / PageSize; - CpuMemory.Unmap(Position, Size); + Result = MmuUnmap(BlockAddress, BlockPagesCount); - return 0; + 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 long GetSizeInRange(KMemoryInfo Info, long Start, long End) + private void MapPhysicalMemory(KPageList PageList, ulong Address, ulong EndAddr) { - long CurrEnd = Info.Size + Info.Position; - long CurrSize = Info.Size; + KMemoryInfo Info; - if ((ulong)Info.Position < (ulong)Start) - { - CurrSize -= Start - Info.Position; - } + LinkedListNode<KMemoryBlock> Node = FindBlockNode(Address); - if ((ulong)CurrEnd > (ulong)End) + LinkedListNode<KPageNode> PageListNode = PageList.Nodes.First; + + KPageNode PageNode = PageListNode.Value; + + ulong SrcPa = PageNode.Address; + ulong SrcPaPages = PageNode.PagesCount; + + do { - CurrSize -= CurrEnd - End; - } + 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; + } + } - return CurrSize; + Node = Node.Next; + } + while (Info.Address + Info.Size < EndAddr && Node != null); } - private void FreePages(long Position, long PagesCount) + private static ulong GetSizeInRange(KMemoryInfo Info, ulong Start, ulong End) { - for (long Page = 0; Page < PagesCount; Page++) + ulong EndAddr = Info.Size + Info.Address; + ulong Size = Info.Size; + + if (Info.Address < Start) { - long VA = Position + Page * PageSize; + Size -= Start - Info.Address; + } - if (!CpuMemory.IsMapped(VA)) - { - continue; - } + if (EndAddr > End) + { + Size -= EndAddr - End; + } - long PA = CpuMemory.GetPhysicalAddress(VA); + return Size; + } - Allocator.Free(PA, PageSize); + private static ulong GetAddrInRange(KMemoryInfo Info, ulong Start) + { + if (Info.Address < Start) + { + return Start; } + + return Info.Address; } - public bool HleIsUnmapped(long Position, long Size) + private void AddVaRangeToPageList(KPageList PageList, ulong Start, ulong PagesCount) { - bool Result = false; + ulong Address = Start; - lock (Blocks) + while (Address < Start + PagesCount * PageSize) { - Result = IsUnmapped(Position, Size); - } + KernelResult Result = ConvertVaToPa(Address, out ulong Pa); - return Result; + if (Result != KernelResult.Success) + { + throw new InvalidOperationException("Unexpected failure translating virtual address."); + } + + PageList.AddRange(Pa, 1); + + Address += PageSize; + } } - private bool IsUnmapped(long Position, long Size) + private bool IsUnmapped(ulong Address, ulong Size) { return CheckRange( - Position, + Address, Size, MemoryState.Mask, MemoryState.Unmapped, @@ -871,8 +1639,8 @@ namespace Ryujinx.HLE.HOS.Kernel } private bool CheckRange( - long Position, - long Size, + ulong Address, + ulong Size, MemoryState StateMask, MemoryState StateExpected, MemoryPermission PermissionMask, @@ -884,24 +1652,44 @@ namespace Ryujinx.HLE.HOS.Kernel out MemoryPermission OutPermission, out MemoryAttribute OutAttribute) { - KMemoryInfo BlkInfo = FindBlock(Position).GetInfo(); + ulong EndAddr = Address + Size - 1; + + LinkedListNode<KMemoryBlock> Node = FindBlockNode(Address); + + KMemoryInfo Info = Node.Value.GetInfo(); - ulong Start = (ulong)Position; - ulong End = (ulong)Size + Start; + MemoryState FirstState = Info.State; + MemoryPermission FirstPermission = Info.Permission; + MemoryAttribute FirstAttribute = Info.Attribute; - if (End <= (ulong)(BlkInfo.Position + BlkInfo.Size)) + do { - if ((BlkInfo.Attribute & AttributeMask) == AttributeExpected && - (BlkInfo.State & StateMask) == StateExpected && - (BlkInfo.Permission & PermissionMask) == PermissionExpected) + 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 = BlkInfo.State; - OutPermission = BlkInfo.Permission; - OutAttribute = BlkInfo.Attribute & ~AttributeIgnoreMask; + OutState = FirstState; + OutPermission = FirstPermission; + OutAttribute = FirstAttribute & ~AttributeIgnoreMask; return true; } + + Node = Node.Next; } + while (Node != null); OutState = MemoryState.Unmapped; OutPermission = MemoryPermission.None; @@ -910,9 +1698,48 @@ namespace Ryujinx.HLE.HOS.Kernel 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<KMemoryBlock> 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( - long BasePosition, - long PagesCount, + ulong BaseAddress, + ulong PagesCount, MemoryState OldState, MemoryPermission OldPermission, MemoryAttribute OldAttribute, @@ -923,10 +1750,11 @@ namespace Ryujinx.HLE.HOS.Kernel //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 Start = (ulong)BasePosition; - ulong End = (ulong)PagesCount * PageSize + Start; + ulong EndAddr = PagesCount * PageSize + BaseAddress; LinkedListNode<KMemoryBlock> Node = Blocks.First; @@ -937,10 +1765,10 @@ namespace Ryujinx.HLE.HOS.Kernel KMemoryBlock CurrBlock = Node.Value; - ulong CurrStart = (ulong)CurrBlock.BasePosition; - ulong CurrEnd = (ulong)CurrBlock.PagesCount * PageSize + CurrStart; + ulong CurrBaseAddr = CurrBlock.BaseAddress; + ulong CurrEndAddr = CurrBlock.PagesCount * PageSize + CurrBaseAddr; - if (Start < CurrEnd && CurrStart < End) + if (BaseAddress < CurrEndAddr && CurrBaseAddr < EndAddr) { MemoryAttribute CurrBlockAttr = CurrBlock.Attribute | MemoryAttribute.IpcAndDeviceMapped; @@ -953,36 +1781,36 @@ namespace Ryujinx.HLE.HOS.Kernel continue; } - if (CurrStart >= Start && CurrEnd <= End) + if (CurrBaseAddr >= BaseAddress && CurrEndAddr <= EndAddr) { CurrBlock.State = NewState; CurrBlock.Permission = NewPermission; CurrBlock.Attribute &= ~MemoryAttribute.IpcAndDeviceMapped; CurrBlock.Attribute |= NewAttribute; } - else if (CurrStart >= Start) + else if (CurrBaseAddr >= BaseAddress) { - CurrBlock.BasePosition = (long)End; + CurrBlock.BaseAddress = EndAddr; - CurrBlock.PagesCount = (long)((CurrEnd - End) / PageSize); + CurrBlock.PagesCount = (CurrEndAddr - EndAddr) / PageSize; - long NewPagesCount = (long)((End - CurrStart) / PageSize); + ulong NewPagesCount = (EndAddr - CurrBaseAddr) / PageSize; NewNode = Blocks.AddBefore(Node, new KMemoryBlock( - (long)CurrStart, + CurrBaseAddr, NewPagesCount, NewState, NewPermission, NewAttribute)); } - else if (CurrEnd <= End) + else if (CurrEndAddr <= EndAddr) { - CurrBlock.PagesCount = (long)((Start - CurrStart) / PageSize); + CurrBlock.PagesCount = (BaseAddress - CurrBaseAddr) / PageSize; - long NewPagesCount = (long)((CurrEnd - Start) / PageSize); + ulong NewPagesCount = (CurrEndAddr - BaseAddress) / PageSize; NewNode = Blocks.AddAfter(Node, new KMemoryBlock( - BasePosition, + BaseAddress, NewPagesCount, NewState, NewPermission, @@ -990,19 +1818,19 @@ namespace Ryujinx.HLE.HOS.Kernel } else { - CurrBlock.PagesCount = (long)((Start - CurrStart) / PageSize); + CurrBlock.PagesCount = (BaseAddress - CurrBaseAddr) / PageSize; - long NextPagesCount = (long)((CurrEnd - End) / PageSize); + ulong NextPagesCount = (CurrEndAddr - EndAddr) / PageSize; NewNode = Blocks.AddAfter(Node, new KMemoryBlock( - BasePosition, + BaseAddress, PagesCount, NewState, NewPermission, NewAttribute)); Blocks.AddAfter(NewNode, new KMemoryBlock( - (long)End, + EndAddr, NextPagesCount, CurrBlock.State, CurrBlock.Permission, @@ -1016,21 +1844,24 @@ namespace Ryujinx.HLE.HOS.Kernel Node = NextNode; } + + BlockAllocator.Count += Blocks.Count - OldCount; } private void InsertBlock( - long BasePosition, - long PagesCount, + 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(BasePosition, PagesCount, State, Permission, Attribute); + KMemoryBlock Block = new KMemoryBlock(BaseAddress, PagesCount, State, Permission, Attribute); + + int OldCount = Blocks.Count; - ulong Start = (ulong)BasePosition; - ulong End = (ulong)PagesCount * PageSize + Start; + ulong EndAddr = PagesCount * PageSize + BaseAddress; LinkedListNode<KMemoryBlock> NewNode = null; @@ -1042,26 +1873,26 @@ namespace Ryujinx.HLE.HOS.Kernel LinkedListNode<KMemoryBlock> NextNode = Node.Next; - ulong CurrStart = (ulong)CurrBlock.BasePosition; - ulong CurrEnd = (ulong)CurrBlock.PagesCount * PageSize + CurrStart; + ulong CurrBaseAddr = CurrBlock.BaseAddress; + ulong CurrEndAddr = CurrBlock.PagesCount * PageSize + CurrBaseAddr; - if (Start < CurrEnd && CurrStart < End) + if (BaseAddress < CurrEndAddr && CurrBaseAddr < EndAddr) { - if (Start >= CurrStart && End <= CurrEnd) + if (BaseAddress >= CurrBaseAddr && EndAddr <= CurrEndAddr) { Block.Attribute |= CurrBlock.Attribute & MemoryAttribute.IpcAndDeviceMapped; } - if (Start > CurrStart && End < CurrEnd) + if (BaseAddress > CurrBaseAddr && EndAddr < CurrEndAddr) { - CurrBlock.PagesCount = (long)((Start - CurrStart) / PageSize); + CurrBlock.PagesCount = (BaseAddress - CurrBaseAddr) / PageSize; - long NextPagesCount = (long)((CurrEnd - End) / PageSize); + ulong NextPagesCount = (CurrEndAddr - EndAddr) / PageSize; NewNode = Blocks.AddAfter(Node, Block); Blocks.AddAfter(NewNode, new KMemoryBlock( - (long)End, + EndAddr, NextPagesCount, CurrBlock.State, CurrBlock.Permission, @@ -1069,20 +1900,20 @@ namespace Ryujinx.HLE.HOS.Kernel break; } - else if (Start <= CurrStart && End < CurrEnd) + else if (BaseAddress <= CurrBaseAddr && EndAddr < CurrEndAddr) { - CurrBlock.BasePosition = (long)End; + CurrBlock.BaseAddress = EndAddr; - CurrBlock.PagesCount = (long)((CurrEnd - End) / PageSize); + CurrBlock.PagesCount = (CurrEndAddr - EndAddr) / PageSize; if (NewNode == null) { NewNode = Blocks.AddBefore(Node, Block); } } - else if (Start > CurrStart && End >= CurrEnd) + else if (BaseAddress > CurrBaseAddr && EndAddr >= CurrEndAddr) { - CurrBlock.PagesCount = (long)((Start - CurrStart) / PageSize); + CurrBlock.PagesCount = (BaseAddress - CurrBaseAddr) / PageSize; if (NewNode == null) { @@ -1109,14 +1940,15 @@ namespace Ryujinx.HLE.HOS.Kernel } MergeEqualStateNeighbours(NewNode); + + BlockAllocator.Count += Blocks.Count - OldCount; } private void MergeEqualStateNeighbours(LinkedListNode<KMemoryBlock> Node) { KMemoryBlock Block = Node.Value; - ulong Start = (ulong)Block.BasePosition; - ulong End = (ulong)Block.PagesCount * PageSize + Start; + ulong EndAddr = Block.PagesCount * PageSize + Block.BaseAddress; if (Node.Previous != null) { @@ -1126,9 +1958,7 @@ namespace Ryujinx.HLE.HOS.Kernel { Blocks.Remove(Node.Previous); - Block.BasePosition = Previous.BasePosition; - - Start = (ulong)Block.BasePosition; + Block.BaseAddress = Previous.BaseAddress; } } @@ -1140,31 +1970,84 @@ namespace Ryujinx.HLE.HOS.Kernel { Blocks.Remove(Node.Next); - End = (ulong)(Next.BasePosition + Next.PagesCount * PageSize); + EndAddr = Next.BaseAddress + Next.PagesCount * PageSize; } } - Block.PagesCount = (long)((End - Start) / PageSize); + Block.PagesCount = (EndAddr - Block.BaseAddress) / PageSize; } - private static bool BlockStateEquals(KMemoryBlock LHS, KMemoryBlock RHS) + 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; + return Lhs.State == Rhs.State && + Lhs.Permission == Rhs.Permission && + Lhs.Attribute == Rhs.Attribute && + Lhs.DeviceRefCount == Rhs.DeviceRefCount && + Lhs.IpcRefCount == Rhs.IpcRefCount; } - private KMemoryBlock FindBlock(long Position) + private ulong FindFirstFit( + ulong RegionStart, + ulong RegionPagesCount, + ulong NeededPagesCount, + int Alignment, + ulong ReservedStart, + ulong ReservedPagesCount) { - return FindBlockNode(Position)?.Value; + ulong ReservedSize = ReservedPagesCount * PageSize; + + ulong TotalNeededSize = ReservedSize + NeededPagesCount * PageSize; + + ulong RegionEndAddr = RegionStart + RegionPagesCount * PageSize; + + LinkedListNode<KMemoryBlock> 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 LinkedListNode<KMemoryBlock> FindBlockNode(long Position) + private KMemoryBlock FindBlock(ulong Address) { - ulong Addr = (ulong)Position; + return FindBlockNode(Address)?.Value; + } + private LinkedListNode<KMemoryBlock> FindBlockNode(ulong Address) + { lock (Blocks) { LinkedListNode<KMemoryBlock> Node = Blocks.First; @@ -1173,10 +2056,9 @@ namespace Ryujinx.HLE.HOS.Kernel { KMemoryBlock Block = Node.Value; - ulong Start = (ulong)Block.BasePosition; - ulong End = (ulong)Block.PagesCount * PageSize + Start; + ulong CurrEndAddr = Block.PagesCount * PageSize + Block.BaseAddress; - if (Start <= Addr && End - 1 >= Addr) + if (Block.BaseAddress <= Address && CurrEndAddr - 1 >= Address) { return Node; } @@ -1187,5 +2069,390 @@ namespace Ryujinx.HLE.HOS.Kernel 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 new file mode 100644 index 00000000..1f334e65 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KMemoryRegionBlock.cs @@ -0,0 +1,43 @@ +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 new file mode 100644 index 00000000..10db0753 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KMemoryRegionManager.cs @@ -0,0 +1,428 @@ +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]; + + this.Address = Address; + this.Size = Size; + this.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 new file mode 100644 index 00000000..05162323 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KPageList.cs @@ -0,0 +1,80 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel +{ + class KPageList : IEnumerable<KPageNode> + { + public LinkedList<KPageNode> Nodes { get; private set; } + + public KPageList() + { + Nodes = new LinkedList<KPageNode>(); + } + + 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<KPageNode> ThisNode = Nodes.First; + LinkedListNode<KPageNode> 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<KPageNode> 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 new file mode 100644 index 00000000..6cecab2e --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KPageNode.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Kernel +{ + struct KPageNode + { + public ulong Address; + public ulong PagesCount; + + public KPageNode(ulong Address, ulong PagesCount) + { + this.Address = Address; + this.PagesCount = PagesCount; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KPort.cs b/Ryujinx.HLE/HOS/Kernel/KPort.cs new file mode 100644 index 00000000..598f3a32 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KPort.cs @@ -0,0 +1,26 @@ +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); + + this.IsLight = IsLight; + this.NameAddress = NameAddress; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/KProcess.cs new file mode 100644 index 00000000..094ef222 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KProcess.cs @@ -0,0 +1,1013 @@ +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<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) + { + this.ResourceLimit = ResourceLimit; + this.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) + { + this.ResourceLimit = ResourceLimit; + this.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; + } + + this.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, InvalidAccessEventArgs 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 new file mode 100644 index 00000000..dfbe1f36 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KProcessCapabilities.cs @@ -0,0 +1,311 @@ +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; + } + + this.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; + } + + this.HandleTableSize = HandleTableSize; + + break; + } + + case 0x10000: + { + int DebuggingFlags = Cap >> 19; + + if ((uint)DebuggingFlags > 3) + { + return KernelResult.ReservedValue; + } + + this.DebuggingFlags &= ~3; + this.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 index d43fe824..bfb8e7e2 100644 --- a/Ryujinx.HLE/HOS/Kernel/KReadableEvent.cs +++ b/Ryujinx.HLE/HOS/Kernel/KReadableEvent.cs @@ -13,7 +13,7 @@ namespace Ryujinx.HLE.HOS.Kernel public override void Signal() { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); if (!Signaled) { @@ -22,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Kernel base.Signal(); } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); } public KernelResult Clear() @@ -36,7 +36,7 @@ namespace Ryujinx.HLE.HOS.Kernel { KernelResult Result; - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); if (Signaled) { @@ -49,7 +49,7 @@ namespace Ryujinx.HLE.HOS.Kernel Result = KernelResult.InvalidState; } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return Result; } diff --git a/Ryujinx.HLE/HOS/Kernel/KResourceLimit.cs b/Ryujinx.HLE/HOS/Kernel/KResourceLimit.cs new file mode 100644 index 00000000..6fd70d0c --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KResourceLimit.cs @@ -0,0 +1,146 @@ +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<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>(); + + this.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) + { + this.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 index 3cfda419..3342f4a6 100644 --- a/Ryujinx.HLE/HOS/Kernel/KScheduler.cs +++ b/Ryujinx.HLE/HOS/Kernel/KScheduler.cs @@ -38,14 +38,14 @@ namespace Ryujinx.HLE.HOS.Kernel private void PreemptThreads() { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); PreemptThread(PreemptionPriorityCores012, 0); PreemptThread(PreemptionPriorityCores012, 1); PreemptThread(PreemptionPriorityCores012, 2); PreemptThread(PreemptionPriorityCore3, 3); - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); } private void PreemptThread(int Prio, int Core) @@ -82,7 +82,7 @@ namespace Ryujinx.HLE.HOS.Kernel } //If the candidate was scheduled after the current thread, then it's not worth it. - if (SelectedThread == null || SelectedThread.LastScheduledTicks >= Thread.LastScheduledTicks) + if (SelectedThread == null || SelectedThread.LastScheduledTime >= Thread.LastScheduledTime) { yield return Thread; } @@ -212,6 +212,11 @@ namespace Ryujinx.HLE.HOS.Kernel throw new InvalidOperationException("Current thread is not scheduled!"); } + public KProcess GetCurrentProcess() + { + return GetCurrentThread().Owner; + } + public void Dispose() { Dispose(true); diff --git a/Ryujinx.HLE/HOS/Kernel/KServerPort.cs b/Ryujinx.HLE/HOS/Kernel/KServerPort.cs new file mode 100644 index 00000000..42135cd8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KServerPort.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Kernel +{ + class KServerPort : KSynchronizationObject + { + private KPort Parent; + + public KServerPort(Horizon System) : base(System) { } + + public void Initialize(KPort Parent) + { + this.Parent = Parent; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs b/Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs index cdd31667..a440438b 100644 --- a/Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs +++ b/Ryujinx.HLE/HOS/Kernel/KSharedMemory.cs @@ -1,14 +1,68 @@ +using Ryujinx.Common; + namespace Ryujinx.HLE.HOS.Kernel { class KSharedMemory { - public long PA { get; private set; } - public long Size { get; private set; } + private KPageList PageList; + + private long OwnerPid; + + private MemoryPermission OwnerPermission; + private MemoryPermission UserPermission; + + public KSharedMemory( + KPageList PageList, + long OwnerPid, + MemoryPermission OwnerPermission, + MemoryPermission UserPermission) + { + this.PageList = PageList; + this.OwnerPid = OwnerPid; + this.OwnerPermission = OwnerPermission; + this.UserPermission = UserPermission; + } - public KSharedMemory(long PA, long Size) + public KernelResult MapIntoProcess( + KMemoryManager MemoryManager, + ulong Address, + ulong Size, + KProcess Process, + MemoryPermission Permission) { - this.PA = PA; - this.Size = Size; + 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 new file mode 100644 index 00000000..2d6b3ca0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KSlabHeap.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel +{ + class KSlabHeap + { + private LinkedList<ulong> Items; + + public KSlabHeap(ulong Pa, ulong ItemSize, ulong Size) + { + Items = new LinkedList<ulong>(); + + 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 index 57a6296c..19e700f4 100644 --- a/Ryujinx.HLE/HOS/Kernel/KSynchronization.cs +++ b/Ryujinx.HLE/HOS/Kernel/KSynchronization.cs @@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Kernel { long Result = MakeError(ErrorModule.Kernel, KernelErr.Timeout); - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); //Check if objects are already signaled before waiting. for (int Index = 0; Index < SyncObjs.Length; Index++) @@ -29,14 +29,14 @@ namespace Ryujinx.HLE.HOS.Kernel HndIndex = Index; - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return 0; } if (Timeout == 0) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return Result; } @@ -74,7 +74,7 @@ namespace Ryujinx.HLE.HOS.Kernel System.TimeManager.ScheduleFutureInvocation(CurrentThread, Timeout); } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); CurrentThread.WaitingSync = false; @@ -83,7 +83,7 @@ namespace Ryujinx.HLE.HOS.Kernel System.TimeManager.UnscheduleFutureInvocation(CurrentThread); } - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); Result = (uint)CurrentThread.ObjSyncResult; @@ -100,14 +100,14 @@ namespace Ryujinx.HLE.HOS.Kernel } } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return Result; } public void SignalObject(KSynchronizationObject SyncObj) { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); if (SyncObj.IsSignaled()) { @@ -117,7 +117,7 @@ namespace Ryujinx.HLE.HOS.Kernel { KThread Thread = Node.Value; - if ((Thread.SchedFlags & ThreadSchedState.LowNibbleMask) == ThreadSchedState.Paused) + if ((Thread.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused) { Thread.SignaledObj = SyncObj; Thread.ObjSyncResult = 0; @@ -129,7 +129,7 @@ namespace Ryujinx.HLE.HOS.Kernel } } - System.CriticalSectionLock.Unlock(); + 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 index 28eac330..5ba7784f 100644 --- a/Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs +++ b/Ryujinx.HLE/HOS/Kernel/KSynchronizationObject.cs @@ -2,16 +2,12 @@ using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Kernel { - class KSynchronizationObject + class KSynchronizationObject : KAutoObject { public LinkedList<KThread> WaitingThreads; - protected Horizon System; - - public KSynchronizationObject(Horizon System) + public KSynchronizationObject(Horizon System) : base(System) { - this.System = System; - WaitingThreads = new LinkedList<KThread>(); } diff --git a/Ryujinx.HLE/HOS/Kernel/KThread.cs b/Ryujinx.HLE/HOS/Kernel/KThread.cs index 73ee2322..88f144c8 100644 --- a/Ryujinx.HLE/HOS/Kernel/KThread.cs +++ b/Ryujinx.HLE/HOS/Kernel/KThread.cs @@ -1,4 +1,5 @@ using ChocolArm64; +using ChocolArm64.Memory; using System; using System.Collections.Generic; using System.Linq; @@ -13,20 +14,30 @@ namespace Ryujinx.HLE.HOS.Kernel public long AffinityMask { get; set; } - public int ThreadId { get; private set; } + public long ThreadUid { get; private set; } - public KSynchronizationObject SignaledObj; + public long TotalTimeRunning { get; set; } + + public KSynchronizationObject SignaledObj { get; set; } public long CondVarAddress { get; set; } - public long MutexAddress { get; set; } - public Process Owner { get; private set; } + private ulong Entrypoint; + + public long MutexAddress { get; set; } + + public KProcess Owner { get; private set; } - public long LastScheduledTicks { get; set; } + private ulong TlsAddress; + + public long LastScheduledTime { get; set; } public LinkedListNode<KThread>[] SiblingsPerCore { get; private set; } - private LinkedListNode<KThread> WithholderNode; + public LinkedList<KThread> Withholder { get; set; } + public LinkedListNode<KThread> WithholderNode { get; set; } + + public LinkedListNode<KThread> ProcessListNode { get; set; } private LinkedList<KThread> MutexWaiters; private LinkedListNode<KThread> MutexWaiterNode; @@ -65,38 +76,131 @@ namespace Ryujinx.HLE.HOS.Kernel public long LastPc { get; set; } - public KThread( - CpuThread Thread, - Process Process, - Horizon System, - int ProcessorId, - int Priority, - int ThreadId) : base(System) + public KThread(Horizon System) : base(System) { - this.ThreadId = ThreadId; - - Context = Thread; - Owner = Process; - PreferredCore = ProcessorId; Scheduler = System.Scheduler; SchedulingData = System.Scheduler.SchedulingData; SiblingsPerCore = new LinkedListNode<KThread>[KScheduler.CpuCoresCount]; MutexWaiters = new LinkedList<KThread>(); + } + + 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}\"."); + } - AffinityMask = 1 << ProcessorId; + PreferredCore = DefaultCpuCore; - DynamicPriority = BasePriority = Priority; + AffinityMask |= 1L << DefaultCpuCore; + + SchedFlags = Type == ThreadType.Dummy + ? ThreadSchedState.Running + : ThreadSchedState.None; CurrentCore = PreferredCore; + + DynamicPriority = Priority; + BasePriority = Priority; + + ObjSyncResult = 0x7201; + + this.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) + { + this.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 long Start() + public KernelResult Start() { - long Result = MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); + if (!System.KernelInitialized) + { + System.CriticalSection.Enter(); + + if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending) + { + ForcePauseFlags |= ThreadSchedState.KernelInitPauseFlag; + + CombineForcePauseFlags(); + } - System.CriticalSectionLock.Lock(); + System.CriticalSection.Leave(); + } + + KernelResult Result = KernelResult.ThreadTerminating; + + System.CriticalSection.Enter(); if (!ShallBeTerminated) { @@ -106,9 +210,9 @@ namespace Ryujinx.HLE.HOS.Kernel CurrentThread.SchedFlags != ThreadSchedState.TerminationPending && !CurrentThread.ShallBeTerminated) { - if ((SchedFlags & ThreadSchedState.LowNibbleMask) != ThreadSchedState.None) + if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.None) { - Result = MakeError(ErrorModule.Kernel, KernelErr.InvalidState); + Result = KernelResult.InvalidState; break; } @@ -122,7 +226,7 @@ namespace Ryujinx.HLE.HOS.Kernel SetNewSchedFlags(ThreadSchedState.Running); - Result = 0; + Result = KernelResult.Success; break; } @@ -130,8 +234,8 @@ namespace Ryujinx.HLE.HOS.Kernel { CurrentThread.CombineForcePauseFlags(); - System.CriticalSectionLock.Unlock(); - System.CriticalSectionLock.Lock(); + System.CriticalSection.Leave(); + System.CriticalSection.Enter(); if (CurrentThread.ShallBeTerminated) { @@ -141,25 +245,25 @@ namespace Ryujinx.HLE.HOS.Kernel } } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return Result; } public void Exit() { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); - ForcePauseFlags &= ~ThreadSchedState.ExceptionalMask; + ForcePauseFlags &= ~ThreadSchedState.ForcePauseMask; ExitImpl(); - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); } private void ExitImpl() { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); SetNewSchedFlags(ThreadSchedState.TerminationPending); @@ -167,16 +271,16 @@ namespace Ryujinx.HLE.HOS.Kernel Signal(); - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); } public long Sleep(long Timeout) { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); } @@ -188,7 +292,7 @@ namespace Ryujinx.HLE.HOS.Kernel System.TimeManager.ScheduleFutureInvocation(this, Timeout); } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); if (Timeout > 0) { @@ -200,11 +304,11 @@ namespace Ryujinx.HLE.HOS.Kernel public void Yield() { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); if (SchedFlags != ThreadSchedState.Running) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); System.Scheduler.ContextSwitch(); @@ -219,27 +323,27 @@ namespace Ryujinx.HLE.HOS.Kernel Scheduler.ThreadReselectionRequested = true; - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); System.Scheduler.ContextSwitch(); } public void YieldWithLoadBalancing() { - System.CriticalSectionLock.Lock(); - - int Prio = DynamicPriority; - int Core = CurrentCore; + System.CriticalSection.Enter(); if (SchedFlags != ThreadSchedState.Running) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); System.Scheduler.ContextSwitch(); return; } + int Prio = DynamicPriority; + int Core = CurrentCore; + KThread NextThreadOnCurrentQueue = null; if (DynamicPriority < KScheduler.PrioritiesCount) @@ -270,7 +374,7 @@ namespace Ryujinx.HLE.HOS.Kernel //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.LastScheduledTicks >= Thread.LastScheduledTicks || + if (NextThreadOnCurrentQueue.LastScheduledTime >= Thread.LastScheduledTime || NextThreadOnCurrentQueue.DynamicPriority < Thread.DynamicPriority) { yield return Thread; @@ -292,18 +396,18 @@ namespace Ryujinx.HLE.HOS.Kernel Scheduler.ThreadReselectionRequested = true; } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); System.Scheduler.ContextSwitch(); } public void YieldAndWaitForLoadBalancing() { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); if (SchedFlags != ThreadSchedState.Running) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); System.Scheduler.ContextSwitch(); @@ -348,47 +452,47 @@ namespace Ryujinx.HLE.HOS.Kernel Scheduler.ThreadReselectionRequested = true; } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); System.Scheduler.ContextSwitch(); } public void SetPriority(int Priority) { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); BasePriority = Priority; UpdatePriorityInheritance(); - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); } public long SetActivity(bool Pause) { long Result = 0; - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); - ThreadSchedState LowNibble = SchedFlags & ThreadSchedState.LowNibbleMask; + ThreadSchedState LowNibble = SchedFlags & ThreadSchedState.LowMask; if (LowNibble != ThreadSchedState.Paused && LowNibble != ThreadSchedState.Running) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); return MakeError(ErrorModule.Kernel, KernelErr.InvalidState); } - System.CriticalSectionLock.Lock(); + 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.ForcePauseFlag) == 0) + if ((ForcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0) { - ForcePauseFlags |= ThreadSchedState.ForcePauseFlag; + ForcePauseFlags |= ThreadSchedState.ThreadPauseFlag; CombineForcePauseFlags(); } @@ -400,17 +504,17 @@ namespace Ryujinx.HLE.HOS.Kernel else { //Unpause, the force pause flag should be set (thread is paused). - if ((ForcePauseFlags & ThreadSchedState.ForcePauseFlag) != 0) + if ((ForcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0) { ThreadSchedState OldForcePauseFlags = ForcePauseFlags; - ForcePauseFlags &= ~ThreadSchedState.ForcePauseFlag; + ForcePauseFlags &= ~ThreadSchedState.ThreadPauseFlag; - if ((OldForcePauseFlags & ~ThreadSchedState.ForcePauseFlag) == ThreadSchedState.None) + if ((OldForcePauseFlags & ~ThreadSchedState.ThreadPauseFlag) == ThreadSchedState.None) { ThreadSchedState OldSchedFlags = SchedFlags; - SchedFlags &= ThreadSchedState.LowNibbleMask; + SchedFlags &= ThreadSchedState.LowMask; AdjustScheduling(OldSchedFlags); } @@ -422,27 +526,27 @@ namespace Ryujinx.HLE.HOS.Kernel } } - System.CriticalSectionLock.Unlock(); - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); + System.CriticalSection.Leave(); return Result; } public void CancelSynchronization() { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); - if ((SchedFlags & ThreadSchedState.LowNibbleMask) != ThreadSchedState.Paused || !WaitingSync) + if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.Paused || !WaitingSync) { SyncCancelled = true; } - else if (WithholderNode != null) + else if (Withholder != null) { - System.Withholders.Remove(WithholderNode); + Withholder.Remove(WithholderNode); SetNewSchedFlags(ThreadSchedState.Running); - WithholderNode = null; + Withholder = null; SyncCancelled = true; } @@ -456,12 +560,12 @@ namespace Ryujinx.HLE.HOS.Kernel SyncCancelled = false; } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); } - public long SetCoreAndAffinityMask(int NewCore, long NewAffinityMask) + public KernelResult SetCoreAndAffinityMask(int NewCore, long NewAffinityMask) { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); bool UseOverride = AffinityOverrideCount != 0; @@ -472,9 +576,9 @@ namespace Ryujinx.HLE.HOS.Kernel if ((NewAffinityMask & (1 << NewCore)) == 0) { - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); - return MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue); + return KernelResult.InvalidCombination; } } @@ -510,9 +614,9 @@ namespace Ryujinx.HLE.HOS.Kernel } } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); - return 0; + return KernelResult.Success; } private static int HighestSetCore(long Mask) @@ -531,7 +635,7 @@ namespace Ryujinx.HLE.HOS.Kernel private void CombineForcePauseFlags() { ThreadSchedState OldFlags = SchedFlags; - ThreadSchedState LowNibble = SchedFlags & ThreadSchedState.LowNibbleMask; + ThreadSchedState LowNibble = SchedFlags & ThreadSchedState.LowMask; SchedFlags = LowNibble | ForcePauseFlags; @@ -540,33 +644,33 @@ namespace Ryujinx.HLE.HOS.Kernel private void SetNewSchedFlags(ThreadSchedState NewFlags) { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); ThreadSchedState OldFlags = SchedFlags; - SchedFlags = (OldFlags & ThreadSchedState.HighNibbleMask) | NewFlags; + SchedFlags = (OldFlags & ThreadSchedState.HighMask) | NewFlags; - if ((OldFlags & ThreadSchedState.LowNibbleMask) != NewFlags) + if ((OldFlags & ThreadSchedState.LowMask) != NewFlags) { AdjustScheduling(OldFlags); } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); } public void ReleaseAndResume() { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); - if ((SchedFlags & ThreadSchedState.LowNibbleMask) == ThreadSchedState.Paused) + if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused) { - if (WithholderNode != null) + if (Withholder != null) { - System.Withholders.Remove(WithholderNode); + Withholder.Remove(WithholderNode); SetNewSchedFlags(ThreadSchedState.Running); - WithholderNode = null; + Withholder = null; } else { @@ -574,21 +678,21 @@ namespace Ryujinx.HLE.HOS.Kernel } } - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); } public void Reschedule(ThreadSchedState NewFlags) { - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); ThreadSchedState OldFlags = SchedFlags; - SchedFlags = (OldFlags & ThreadSchedState.HighNibbleMask) | - (NewFlags & ThreadSchedState.LowNibbleMask); + SchedFlags = (OldFlags & ThreadSchedState.HighMask) | + (NewFlags & ThreadSchedState.LowMask); AdjustScheduling(OldFlags); - System.CriticalSectionLock.Unlock(); + System.CriticalSection.Leave(); } public void AddMutexWaiter(KThread Requester) @@ -866,18 +970,61 @@ namespace Ryujinx.HLE.HOS.Kernel return HasExited; } + public void SetEntryArguments(long ArgsPtr, int ThreadHandle) + { + Context.ThreadState.X0 = (ulong)ArgsPtr; + Context.ThreadState.X1 = (ulong)ThreadHandle; + } + public void ClearExclusive() { - Owner.Memory.ClearExclusive(CurrentCore); + Owner.CpuMemory.ClearExclusive(CurrentCore); } public void TimeUp() { - System.CriticalSectionLock.Lock(); + 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(); + } - SetNewSchedFlags(ThreadSchedState.Running); + System.CriticalSection.Leave(); - System.CriticalSectionLock.Unlock(); + Owner?.DecrementThreadCountAndTerminateIfZero(); } } }
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KTimeManager.cs b/Ryujinx.HLE/HOS/Kernel/KTimeManager.cs index 47a3c86c..375789f0 100644 --- a/Ryujinx.HLE/HOS/Kernel/KTimeManager.cs +++ b/Ryujinx.HLE/HOS/Kernel/KTimeManager.cs @@ -1,6 +1,6 @@ +using Ryujinx.Common; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; @@ -25,18 +25,12 @@ namespace Ryujinx.HLE.HOS.Kernel private AutoResetEvent WaitEvent; - private Stopwatch Counter; - private bool KeepRunning; public KTimeManager() { WaitingObjects = new List<WaitingObject>(); - Counter = new Stopwatch(); - - Counter.Start(); - KeepRunning = true; Thread Work = new Thread(WaitAndCheckScheduledObjects); @@ -46,26 +40,36 @@ namespace Ryujinx.HLE.HOS.Kernel public void ScheduleFutureInvocation(IKFutureSchedulerObject Object, long Timeout) { + long TimePoint = PerformanceCounter.ElapsedMilliseconds + ConvertNanosecondsToMilliseconds(Timeout); + lock (WaitingObjects) { - long TimePoint = Counter.ElapsedMilliseconds + ConvertNanosecondsToMilliseconds(Timeout); - WaitingObjects.Add(new WaitingObject(Object, TimePoint)); } WaitEvent.Set(); } - private long ConvertNanosecondsToMilliseconds(long Timeout) + public static long ConvertNanosecondsToMilliseconds(long Time) { - Timeout /= 1000000; + Time /= 1000000; - if ((ulong)Timeout > int.MaxValue) + if ((ulong)Time > int.MaxValue) { return int.MaxValue; } - return Timeout; + 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) @@ -82,26 +86,31 @@ namespace Ryujinx.HLE.HOS.Kernel { while (KeepRunning) { - Monitor.Enter(WaitingObjects); - - WaitingObject Next = WaitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault(); + WaitingObject Next; - Monitor.Exit(WaitingObjects); + lock (WaitingObjects) + { + Next = WaitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault(); + } if (Next != null) { - long TimePoint = Counter.ElapsedMilliseconds; + long TimePoint = PerformanceCounter.ElapsedMilliseconds; if (Next.TimePoint > TimePoint) { WaitEvent.WaitOne((int)(Next.TimePoint - TimePoint)); } - Monitor.Enter(WaitingObjects); + bool TimeUp = PerformanceCounter.ElapsedMilliseconds >= Next.TimePoint; - bool TimeUp = Counter.ElapsedMilliseconds >= Next.TimePoint && WaitingObjects.Remove(Next); - - Monitor.Exit(WaitingObjects); + if (TimeUp) + { + lock (WaitingObjects) + { + TimeUp = WaitingObjects.Remove(Next); + } + } if (TimeUp) { diff --git a/Ryujinx.HLE/HOS/Kernel/KTlsPageInfo.cs b/Ryujinx.HLE/HOS/Kernel/KTlsPageInfo.cs new file mode 100644 index 00000000..18dc2dec --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KTlsPageInfo.cs @@ -0,0 +1,73 @@ +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) + { + this.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/KTransferMemory.cs b/Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs index 6ebffa7e..5598f78d 100644 --- a/Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs +++ b/Ryujinx.HLE/HOS/Kernel/KTransferMemory.cs @@ -2,13 +2,13 @@ namespace Ryujinx.HLE.HOS.Kernel { class KTransferMemory { - public long Position { get; private set; } - public long Size { get; private set; } + public ulong Address { get; private set; } + public ulong Size { get; private set; } - public KTransferMemory(long Position, long Size) + public KTransferMemory(ulong Address, ulong Size) { - this.Position = Position; - this.Size = Size; + this.Address = Address; + this.Size = Size; } } }
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KernelInit.cs b/Ryujinx.HLE/HOS/Kernel/KernelInit.cs new file mode 100644 index 00000000..efb514c1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KernelInit.cs @@ -0,0 +1,136 @@ +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 index d9cbfc67..9870d175 100644 --- a/Ryujinx.HLE/HOS/Kernel/KernelResult.cs +++ b/Ryujinx.HLE/HOS/Kernel/KernelResult.cs @@ -2,9 +2,30 @@ namespace Ryujinx.HLE.HOS.Kernel { enum KernelResult { - Success = 0, - HandleTableFull = 0xd201, - InvalidHandle = 0xe401, - InvalidState = 0xfa01 + 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 new file mode 100644 index 00000000..a3fabeae --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/KernelTransfer.cs @@ -0,0 +1,71 @@ +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 new file mode 100644 index 00000000..baab4222 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/LimitableResource.cs @@ -0,0 +1,13 @@ +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/MemoryOperation.cs b/Ryujinx.HLE/HOS/Kernel/MemoryOperation.cs new file mode 100644 index 00000000..b9350121 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/MemoryOperation.cs @@ -0,0 +1,12 @@ +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/MemoryRegion.cs b/Ryujinx.HLE/HOS/Kernel/MemoryRegion.cs new file mode 100644 index 00000000..ea4f33c9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/MemoryRegion.cs @@ -0,0 +1,10 @@ +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 index 2c37723c..e2ce27ef 100644 --- a/Ryujinx.HLE/HOS/Kernel/MemoryState.cs +++ b/Ryujinx.HLE/HOS/Kernel/MemoryState.cs @@ -15,7 +15,7 @@ namespace Ryujinx.HLE.HOS.Kernel ModCodeStatic = 0x00DD7E08, ModCodeMutable = 0x03FFBD09, IpcBuffer0 = 0x005C3C0A, - MappedMemory = 0x005C3C0B, + Stack = 0x005C3C0B, ThreadLocal = 0x0040200C, TransferMemoryIsolated = 0x015C3C0D, TransferMemory = 0x005C380E, diff --git a/Ryujinx.HLE/HOS/Kernel/MersenneTwister.cs b/Ryujinx.HLE/HOS/Kernel/MersenneTwister.cs new file mode 100644 index 00000000..b90d54d2 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/MersenneTwister.cs @@ -0,0 +1,128 @@ +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/ProcessCreationInfo.cs b/Ryujinx.HLE/HOS/Kernel/ProcessCreationInfo.cs new file mode 100644 index 00000000..dae1345a --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/ProcessCreationInfo.cs @@ -0,0 +1,37 @@ +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) + { + this.Name = Name; + this.Category = Category; + this.TitleId = TitleId; + this.CodeAddress = CodeAddress; + this.CodePagesCount = CodePagesCount; + this.MmuFlags = MmuFlags; + this.ResourceLimitHandle = ResourceLimitHandle; + this.PersonalMmHeapPagesCount = PersonalMmHeapPagesCount; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/ProcessState.cs b/Ryujinx.HLE/HOS/Kernel/ProcessState.cs new file mode 100644 index 00000000..98ff4207 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/ProcessState.cs @@ -0,0 +1,14 @@ +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/SvcHandler.cs b/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs index 9b475d4e..cbc5e31c 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcHandler.cs @@ -1,8 +1,8 @@ using ChocolArm64.Events; using ChocolArm64.Memory; using ChocolArm64.State; -using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.Common.Logging; using System; using System.Collections.Generic; @@ -15,7 +15,7 @@ namespace Ryujinx.HLE.HOS.Kernel private Dictionary<int, SvcFunc> SvcFuncs; private Switch Device; - private Process Process; + private KProcess Process; private Horizon System; private MemoryManager Memory; @@ -39,9 +39,7 @@ namespace Ryujinx.HLE.HOS.Kernel } } - private static Random Rng; - - public SvcHandler(Switch Device, Process Process) + public SvcHandler(Switch Device, KProcess Process) { SvcFuncs = new Dictionary<int, SvcFunc>() { @@ -51,14 +49,14 @@ namespace Ryujinx.HLE.HOS.Kernel { 0x05, SvcUnmapMemory }, { 0x06, SvcQueryMemory }, { 0x07, SvcExitProcess }, - { 0x08, SvcCreateThread }, + { 0x08, CreateThread64 }, { 0x09, SvcStartThread }, { 0x0a, SvcExitThread }, { 0x0b, SvcSleepThread }, { 0x0c, SvcGetThreadPriority }, { 0x0d, SvcSetThreadPriority }, { 0x0e, SvcGetThreadCoreMask }, - { 0x0f, SvcSetThreadCoreMask }, + { 0x0f, SetThreadCoreMask64 }, { 0x10, SvcGetCurrentProcessorNumber }, { 0x11, SignalEvent64 }, { 0x12, ClearEvent64 }, @@ -77,36 +75,34 @@ namespace Ryujinx.HLE.HOS.Kernel { 0x1f, SvcConnectToNamedPort }, { 0x21, SvcSendSyncRequest }, { 0x22, SvcSendSyncRequestWithUserBuffer }, + { 0x24, GetProcessId64 }, { 0x25, SvcGetThreadId }, { 0x26, SvcBreak }, { 0x27, SvcOutputDebugString }, - { 0x29, SvcGetInfo }, + { 0x29, GetInfo64 }, { 0x2c, SvcMapPhysicalMemory }, { 0x2d, SvcUnmapPhysicalMemory }, { 0x32, SvcSetThreadActivity }, { 0x33, SvcGetThreadContext3 }, { 0x34, SvcWaitForAddress }, { 0x35, SvcSignalToAddress }, - { 0x45, CreateEvent64 } + { 0x45, CreateEvent64 }, + { 0x65, GetProcessList64 }, + { 0x6f, GetSystemInfo64 }, + { 0x70, CreatePort64 }, + { 0x71, ManageNamedPort64 } }; this.Device = Device; this.Process = Process; - this.System = Process.Device.System; - this.Memory = Process.Memory; - } - - static SvcHandler() - { - Rng = new Random(); + this.System = Device.System; + this.Memory = Process.CpuMemory; } public void SvcCall(object sender, InstExceptionEventArgs e) { CpuThreadState ThreadState = (CpuThreadState)sender; - Process.GetThread(ThreadState.Tpidr).LastPc = e.Position; - if (SvcFuncs.TryGetValue(e.Id, out SvcFunc Func)) { Logger.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} called."); @@ -117,7 +113,7 @@ namespace Ryujinx.HLE.HOS.Kernel } else { - Process.PrintStackTrace(ThreadState); + //Process.PrintStackTrace(ThreadState); throw new NotImplementedException($"0x{e.Id:x4}"); } diff --git a/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs b/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs index 560ad4b3..b5845f0b 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcMemory.cs @@ -11,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Kernel { ulong Size = ThreadState.X1; - if ((Size & 0xFFFFFFFE001FFFFF) != 0) + if ((Size & 0xfffffffe001fffff) != 0) { Logger.PrintWarning(LogClass.KernelSvc, $"Heap size 0x{Size:x16} is not aligned!"); @@ -20,24 +20,24 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - long Result = Process.MemoryManager.TrySetHeapSize((long)Size, out long Position); + KernelResult Result = Process.MemoryManager.SetHeapSize(Size, out ulong Position); ThreadState.X0 = (ulong)Result; - if (Result == 0) + if (Result == KernelResult.Success) { - ThreadState.X1 = (ulong)Position; + ThreadState.X1 = Position; } else { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error \"{Result}\"."); } } private void SvcSetMemoryAttribute(CpuThreadState ThreadState) { - long Position = (long)ThreadState.X0; - long Size = (long)ThreadState.X1; + ulong Position = ThreadState.X0; + ulong Size = ThreadState.X1; if (!PageAligned(Position)) { @@ -72,19 +72,19 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - long Result = Process.MemoryManager.SetMemoryAttribute( + KernelResult Result = Process.MemoryManager.SetMemoryAttribute( Position, Size, AttributeMask, AttributeValue); - if (Result != 0) + if (Result != KernelResult.Success) { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error \"{Result}\"."); } else { - Memory.StopObservingRegion(Position, Size); + Memory.StopObservingRegion((long)Position, (long)Size); } ThreadState.X0 = (ulong)Result; @@ -92,9 +92,9 @@ namespace Ryujinx.HLE.HOS.Kernel private void SvcMapMemory(CpuThreadState ThreadState) { - long Dst = (long)ThreadState.X0; - long Src = (long)ThreadState.X1; - long Size = (long)ThreadState.X2; + ulong Dst = ThreadState.X0; + ulong Src = ThreadState.X1; + ulong Size = ThreadState.X2; if (!PageAligned(Src | Dst)) { @@ -114,7 +114,7 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - if ((ulong)(Src + Size) <= (ulong)Src || (ulong)(Dst + Size) <= (ulong)Dst) + if (Src + Size <= Src || Dst + Size <= Dst) { Logger.PrintWarning(LogClass.KernelSvc, "Addresses outside of range!"); @@ -123,7 +123,9 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - if (!InsideAddrSpace(Src, Size)) + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + if (!CurrentProcess.MemoryManager.InsideAddrSpace(Src, Size)) { Logger.PrintWarning(LogClass.KernelSvc, $"Src address 0x{Src:x16} out of range!"); @@ -132,7 +134,9 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - if (!InsideNewMapRegion(Dst, Size)) + 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!"); @@ -141,9 +145,9 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - long Result = Process.MemoryManager.Map(Src, Dst, Size); + KernelResult Result = Process.MemoryManager.Map(Dst, Src, Size); - if (Result != 0) + if (Result != KernelResult.Success) { Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } @@ -153,9 +157,9 @@ namespace Ryujinx.HLE.HOS.Kernel private void SvcUnmapMemory(CpuThreadState ThreadState) { - long Dst = (long)ThreadState.X0; - long Src = (long)ThreadState.X1; - long Size = (long)ThreadState.X2; + ulong Dst = ThreadState.X0; + ulong Src = ThreadState.X1; + ulong Size = ThreadState.X2; if (!PageAligned(Src | Dst)) { @@ -175,7 +179,7 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - if ((ulong)(Src + Size) <= (ulong)Src || (ulong)(Dst + Size) <= (ulong)Dst) + if (Src + Size <= Src || Dst + Size <= Dst) { Logger.PrintWarning(LogClass.KernelSvc, "Addresses outside of range!"); @@ -184,7 +188,9 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - if (!InsideAddrSpace(Src, Size)) + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + if (!CurrentProcess.MemoryManager.InsideAddrSpace(Src, Size)) { Logger.PrintWarning(LogClass.KernelSvc, $"Src address 0x{Src:x16} out of range!"); @@ -193,7 +199,9 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - if (!InsideNewMapRegion(Dst, Size)) + 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!"); @@ -202,9 +210,9 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - long Result = Process.MemoryManager.Unmap(Src, Dst, Size); + KernelResult Result = Process.MemoryManager.Unmap(Dst, Src, Size); - if (Result != 0) + if (Result != KernelResult.Success) { Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } @@ -214,19 +222,19 @@ namespace Ryujinx.HLE.HOS.Kernel private void SvcQueryMemory(CpuThreadState ThreadState) { - long InfoPtr = (long)ThreadState.X0; - long Position = (long)ThreadState.X2; + long InfoPtr = (long)ThreadState.X0; + ulong Position = ThreadState.X2; KMemoryInfo BlkInfo = Process.MemoryManager.QueryMemory(Position); - Memory.WriteInt64(InfoPtr + 0x00, BlkInfo.Position); - Memory.WriteInt64(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); + 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; @@ -234,13 +242,13 @@ namespace Ryujinx.HLE.HOS.Kernel private void SvcMapSharedMemory(CpuThreadState ThreadState) { - int Handle = (int)ThreadState.X0; - long Position = (long)ThreadState.X1; - long Size = (long)ThreadState.X2; + int Handle = (int)ThreadState.X0; + ulong Address = ThreadState.X1; + ulong Size = ThreadState.X2; - if (!PageAligned(Position)) + if (!PageAligned(Address)) { - Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Address:x16} is not page aligned!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -256,9 +264,9 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - if ((ulong)(Position + Size) <= (ulong)Position) + if (Address + Size <= Address) { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Address:x16} / size 0x{Size:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -276,7 +284,9 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - KSharedMemory SharedMemory = Process.HandleTable.GetObject<KSharedMemory>(Handle); + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + KSharedMemory SharedMemory = CurrentProcess.HandleTable.GetObject<KSharedMemory>(Handle); if (SharedMemory == null) { @@ -287,29 +297,27 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - if (!InsideAddrSpace(Position, Size) || InsideMapRegion(Position, Size) || InsideHeapRegion(Position, Size)) + if (CurrentProcess.MemoryManager.IsInvalidRegion (Address, Size) || + CurrentProcess.MemoryManager.InsideHeapRegion (Address, Size) || + CurrentProcess.MemoryManager.InsideAliasRegion(Address, Size)) { - Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} out of range!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Address:x16} out of range!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); return; } - if (SharedMemory.Size != Size) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} does not match shared memory size 0x{SharedMemory.Size:16}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize); - - return; - } - - long Result = Process.MemoryManager.MapSharedMemory(SharedMemory, Permission, Position); + KernelResult Result = SharedMemory.MapIntoProcess( + CurrentProcess.MemoryManager, + Address, + Size, + CurrentProcess, + Permission); - if (Result != 0) + if (Result != KernelResult.Success) { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error \"{Result}\"."); } ThreadState.X0 = (ulong)Result; @@ -317,13 +325,13 @@ namespace Ryujinx.HLE.HOS.Kernel private void SvcUnmapSharedMemory(CpuThreadState ThreadState) { - int Handle = (int)ThreadState.X0; - long Position = (long)ThreadState.X1; - long Size = (long)ThreadState.X2; + int Handle = (int)ThreadState.X0; + ulong Address = ThreadState.X1; + ulong Size = ThreadState.X2; - if (!PageAligned(Position)) + if (!PageAligned(Address)) { - Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Address:x16} is not page aligned!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -339,16 +347,18 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - if ((ulong)(Position + Size) <= (ulong)Position) + if (Address + Size <= Address) { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Address:x16} / size 0x{Size:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); return; } - KSharedMemory SharedMemory = Process.HandleTable.GetObject<KSharedMemory>(Handle); + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + KSharedMemory SharedMemory = CurrentProcess.HandleTable.GetObject<KSharedMemory>(Handle); if (SharedMemory == null) { @@ -359,20 +369,26 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - if (!InsideAddrSpace(Position, Size) || InsideMapRegion(Position, Size) || InsideHeapRegion(Position, Size)) + if (CurrentProcess.MemoryManager.IsInvalidRegion (Address, Size) || + CurrentProcess.MemoryManager.InsideHeapRegion (Address, Size) || + CurrentProcess.MemoryManager.InsideAliasRegion(Address, Size)) { - Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} out of range!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Address:x16} out of range!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); return; } - long Result = Process.MemoryManager.UnmapSharedMemory(Position, Size); + KernelResult Result = SharedMemory.UnmapFromProcess( + CurrentProcess.MemoryManager, + Address, + Size, + CurrentProcess); - if (Result != 0) + if (Result != KernelResult.Success) { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error \"{Result}\"."); } ThreadState.X0 = (ulong)Result; @@ -380,12 +396,12 @@ namespace Ryujinx.HLE.HOS.Kernel private void SvcCreateTransferMemory(CpuThreadState ThreadState) { - long Position = (long)ThreadState.X1; - long Size = (long)ThreadState.X2; + ulong Address = ThreadState.X1; + ulong Size = ThreadState.X2; - if (!PageAligned(Position)) + if (!PageAligned(Address)) { - Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Address:x16} is not page aligned!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -401,9 +417,9 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - if ((ulong)(Position + Size) <= (ulong)Position) + if (Address + Size <= Address) { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Address:x16} / size 0x{Size:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); @@ -421,9 +437,9 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - Process.MemoryManager.ReserveTransferMemory(Position, Size, Permission); + Process.MemoryManager.ReserveTransferMemory(Address, Size, Permission); - KTransferMemory TransferMemory = new KTransferMemory(Position, Size); + KTransferMemory TransferMemory = new KTransferMemory(Address, Size); KernelResult Result = Process.HandleTable.GenerateHandle(TransferMemory, out int Handle); @@ -433,12 +449,12 @@ namespace Ryujinx.HLE.HOS.Kernel private void SvcMapPhysicalMemory(CpuThreadState ThreadState) { - long Position = (long)ThreadState.X0; - long Size = (long)ThreadState.X1; + ulong Address = ThreadState.X0; + ulong Size = ThreadState.X1; - if (!PageAligned(Position)) + if (!PageAligned(Address)) { - Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Address:x16} is not page aligned!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -454,27 +470,39 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - if ((ulong)(Position + Size) <= (ulong)Position) + if (Address + Size <= Address) { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Address:x16} / size 0x{Size:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); return; } - if (!InsideAddrSpace(Position, Size)) + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + if ((CurrentProcess.PersonalMmHeapPagesCount & 0xfffffffffffff) == 0) { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid address {Position:x16}!"); + 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; } - long Result = Process.MemoryManager.MapPhysicalMemory(Position, Size); + KernelResult Result = Process.MemoryManager.MapPhysicalMemory(Address, Size); - if (Result != 0) + if (Result != KernelResult.Success) { Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } @@ -484,12 +512,12 @@ namespace Ryujinx.HLE.HOS.Kernel private void SvcUnmapPhysicalMemory(CpuThreadState ThreadState) { - long Position = (long)ThreadState.X0; - long Size = (long)ThreadState.X1; + ulong Address = ThreadState.X0; + ulong Size = ThreadState.X1; - if (!PageAligned(Position)) + if (!PageAligned(Address)) { - Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Address 0x{Address:x16} is not page aligned!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress); @@ -505,27 +533,39 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - if ((ulong)(Position + Size) <= (ulong)Position) + if (Address + Size <= Address) { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Address:x16} / size 0x{Size:x16}!"); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); return; } - if (!InsideAddrSpace(Position, Size)) + 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 {Position:x16}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid address {Address:x16}."); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); return; } - long Result = Process.MemoryManager.UnmapPhysicalMemory(Position, Size); + KernelResult Result = Process.MemoryManager.UnmapPhysicalMemory(Address, Size); - if (Result != 0) + if (Result != KernelResult.Success) { Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); } @@ -533,45 +573,9 @@ namespace Ryujinx.HLE.HOS.Kernel ThreadState.X0 = (ulong)Result; } - private static bool PageAligned(long Position) + private static bool PageAligned(ulong Position) { return (Position & (KMemoryManager.PageSize - 1)) == 0; } - - private bool InsideAddrSpace(long Position, long Size) - { - ulong Start = (ulong)Position; - ulong End = (ulong)Size + Start; - - return Start >= (ulong)Process.MemoryManager.AddrSpaceStart && - End < (ulong)Process.MemoryManager.AddrSpaceEnd; - } - - private bool InsideMapRegion(long Position, long Size) - { - ulong Start = (ulong)Position; - ulong End = (ulong)Size + Start; - - return Start >= (ulong)Process.MemoryManager.MapRegionStart && - End < (ulong)Process.MemoryManager.MapRegionEnd; - } - - private bool InsideHeapRegion(long Position, long Size) - { - ulong Start = (ulong)Position; - ulong End = (ulong)Size + Start; - - return Start >= (ulong)Process.MemoryManager.HeapRegionStart && - End < (ulong)Process.MemoryManager.HeapRegionEnd; - } - - private bool InsideNewMapRegion(long Position, long Size) - { - ulong Start = (ulong)Position; - ulong End = (ulong)Size + Start; - - return Start >= (ulong)Process.MemoryManager.NewMapRegionStart && - End < (ulong)Process.MemoryManager.NewMapRegionEnd; - } } }
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs b/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs index 54aef5d7..1c1d76f1 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcSystem.cs @@ -1,5 +1,6 @@ using ChocolArm64.Memory; using ChocolArm64.State; +using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.HOS.Ipc; @@ -13,13 +14,9 @@ namespace Ryujinx.HLE.HOS.Kernel { partial class SvcHandler { - private const int AllowedCpuIdBitmask = 0b1111; - - private const bool EnableProcessDebugging = false; - private void SvcExitProcess(CpuThreadState ThreadState) { - Device.System.ExitProcess(Process.ProcessId); + System.Scheduler.GetCurrentProcess().Terminate(); } private void SignalEvent64(CpuThreadState ThreadState) @@ -106,7 +103,7 @@ namespace Ryujinx.HLE.HOS.Kernel else if (Obj is KTransferMemory TransferMemory) { Process.MemoryManager.ResetTransferMemory( - TransferMemory.Position, + TransferMemory.Address, TransferMemory.Size); } @@ -120,18 +117,28 @@ namespace Ryujinx.HLE.HOS.Kernel private KernelResult ResetSignal(int Handle) { - KReadableEvent ReadableEvent = Process.HandleTable.GetObject<KReadableEvent>(Handle); + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + KReadableEvent ReadableEvent = CurrentProcess.HandleTable.GetObject<KReadableEvent>(Handle); KernelResult Result; - //TODO: KProcess support. if (ReadableEvent != null) { Result = ReadableEvent.ClearIfSignaled(); } else { - Result = KernelResult.InvalidHandle; + KProcess Process = CurrentProcess.HandleTable.GetKProcess(Handle); + + if (Process != null) + { + Result = Process.ClearIfNotExited(); + } + else + { + Result = KernelResult.InvalidHandle; + } } if (Result == KernelResult.InvalidState) @@ -187,17 +194,13 @@ namespace Ryujinx.HLE.HOS.Kernel private void SendSyncRequest(CpuThreadState ThreadState, long MessagePtr, long Size, int Handle) { - KThread CurrThread = Process.GetThread(ThreadState.Tpidr); - byte[] MessageData = Memory.ReadBytes(MessagePtr, Size); KSession Session = Process.HandleTable.GetObject<KSession>(Handle); if (Session != null) { - //Process.Scheduler.Suspend(CurrThread); - - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); KThread CurrentThread = System.Scheduler.GetCurrentThread(); @@ -214,7 +217,9 @@ namespace Ryujinx.HLE.HOS.Kernel Message, MessagePtr)); - System.CriticalSectionLock.Unlock(); + System.ThreadCounter.AddCount(); + + System.CriticalSection.Leave(); ThreadState.X0 = (ulong)CurrentThread.ObjSyncResult; } @@ -238,25 +243,65 @@ namespace Ryujinx.HLE.HOS.Kernel 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) { - Process.PrintStackTrace(ThreadState); + CurrentThread.PrintGuestStackTrace(); throw new GuestBrokeExecutionException(); } else { - Logger.PrintInfo(LogClass.KernelSvc, "Debugger triggered"); - Process.PrintStackTrace(ThreadState); + Logger.PrintInfo(LogClass.KernelSvc, "Debugger triggered."); + + CurrentThread.PrintGuestStackTrace(); } } @@ -272,98 +317,243 @@ namespace Ryujinx.HLE.HOS.Kernel ThreadState.X0 = 0; } - private void SvcGetInfo(CpuThreadState ThreadState) + private void GetInfo64(CpuThreadState ThreadState) { long StackPtr = (long)ThreadState.X0; - int InfoType = (int)ThreadState.X1; - long Handle = (long)ThreadState.X2; - int InfoId = (int)ThreadState.X3; + uint Id = (uint)ThreadState.X1; + int Handle = (int)ThreadState.X2; + long SubId = (long)ThreadState.X3; - //Fail for info not available on older Kernel versions. - if (InfoType == 18 || - InfoType == 19 || - InfoType == 20 || - InfoType == 21 || - InfoType == 22) - { - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue); + KernelResult Result = GetInfo(Id, Handle, SubId, out long Value); - return; - } + ThreadState.X0 = (ulong)Result; + ThreadState.X1 = (ulong)Value; + } + + private KernelResult GetInfo(uint Id, int Handle, long SubId, out long Value) + { + Value = 0; - switch (InfoType) + switch (Id) { case 0: - ThreadState.X1 = AllowedCpuIdBitmask; - break; - + case 1: case 2: - ThreadState.X1 = (ulong)Process.MemoryManager.MapRegionStart; - break; - case 3: - ThreadState.X1 = (ulong)Process.MemoryManager.MapRegionEnd - - (ulong)Process.MemoryManager.MapRegionStart; - break; - case 4: - ThreadState.X1 = (ulong)Process.MemoryManager.HeapRegionStart; - break; - case 5: - ThreadState.X1 = (ulong)Process.MemoryManager.HeapRegionEnd - - (ulong)Process.MemoryManager.HeapRegionStart; - break; - case 6: - ThreadState.X1 = (ulong)Process.Device.Memory.Allocator.TotalAvailableSize; - break; - case 7: - ThreadState.X1 = (ulong)Process.Device.Memory.Allocator.TotalUsedSize; + 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: - ThreadState.X1 = EnableProcessDebugging ? 1 : 0; - break; + { + if (Handle != 0) + { + return KernelResult.InvalidHandle; + } - case 11: - ThreadState.X1 = (ulong)Rng.Next() + ((ulong)Rng.Next() << 32); - break; + if (SubId != 0) + { + return KernelResult.InvalidCombination; + } - case 12: - ThreadState.X1 = (ulong)Process.MemoryManager.AddrSpaceStart; - break; + Value = System.Scheduler.GetCurrentProcess().Debug ? 1 : 0; - case 13: - ThreadState.X1 = (ulong)Process.MemoryManager.AddrSpaceEnd - - (ulong)Process.MemoryManager.AddrSpaceStart; break; + } - case 14: - ThreadState.X1 = (ulong)Process.MemoryManager.NewMapRegionStart; - 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; + } - case 15: - ThreadState.X1 = (ulong)Process.MemoryManager.NewMapRegionEnd - - (ulong)Process.MemoryManager.NewMapRegionStart; 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; - case 16: - ThreadState.X1 = (ulong)(Process.MetaData?.SystemResourceSize ?? 0); 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]; - case 17: - ThreadState.X1 = (ulong)Process.MemoryManager.PersonalMmHeapUsage; 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; + } - default: - Process.PrintStackTrace(ThreadState); + 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; + } - throw new NotImplementedException($"SvcGetInfo: {InfoType} 0x{Handle:x8} {InfoId}"); + default: return KernelResult.InvalidEnumValue; } - ThreadState.X0 = 0; + return KernelResult.Success; } private void CreateEvent64(CpuThreadState State) @@ -397,5 +587,241 @@ namespace Ryujinx.HLE.HOS.Kernel 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 index 53a557de..ded8f8dc 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcThread.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcThread.cs @@ -7,46 +7,82 @@ namespace Ryujinx.HLE.HOS.Kernel { partial class SvcHandler { - private void SvcCreateThread(CpuThreadState ThreadState) + private void CreateThread64(CpuThreadState ThreadState) { - long EntryPoint = (long)ThreadState.X1; - long ArgsPtr = (long)ThreadState.X2; - long StackTop = (long)ThreadState.X3; - int Priority = (int)ThreadState.X4; - int ProcessorId = (int)ThreadState.X5; + ulong Entrypoint = ThreadState.X1; + ulong ArgsPtr = ThreadState.X2; + ulong StackTop = ThreadState.X3; + int Priority = (int)ThreadState.X4; + int CpuCore = (int)ThreadState.X5; - if ((uint)Priority > 0x3f) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid priority 0x{Priority:x8}!"); + KernelResult Result = CreateThread(Entrypoint, ArgsPtr, StackTop, Priority, CpuCore, out int Handle); - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPriority); + ThreadState.X0 = (ulong)Result; + ThreadState.X1 = (ulong)Handle; + } - return; + 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 (ProcessorId == -2) + if ((uint)CpuCore >= KScheduler.CpuCoresCount || !CurrentProcess.IsCpuCoreAllowed(CpuCore)) { - //TODO: Get this value from the NPDM file. - ProcessorId = 0; + return KernelResult.InvalidCpuCore; } - else if ((uint)ProcessorId > 3) + + if ((uint)Priority >= KScheduler.PrioritiesCount || !CurrentProcess.IsPriorityAllowed(Priority)) { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{ProcessorId:x8}!"); + return KernelResult.InvalidPriority; + } - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId); + long Timeout = KTimeManager.ConvertMillisecondsToNanoseconds(100); - return; + if (CurrentProcess.ResourceLimit != null && + !CurrentProcess.ResourceLimit.Reserve(LimitableResource.Thread, 1, Timeout)) + { + return KernelResult.ResLimitExceeded; } - int Handle = Process.MakeThread( - EntryPoint, - StackTop, + KThread Thread = new KThread(System); + + KernelResult Result = CurrentProcess.InitializeThread( + Thread, + Entrypoint, ArgsPtr, + StackTop, Priority, - ProcessorId); + CpuCore); - ThreadState.X0 = 0; - ThreadState.X1 = (ulong)Handle; + 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) @@ -57,11 +93,11 @@ namespace Ryujinx.HLE.HOS.Kernel if (Thread != null) { - long Result = Thread.Start(); + KernelResult Result = Thread.Start(); - if (Result != 0) + if (Result != KernelResult.Success) { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error \"{Result}\"."); } ThreadState.X0 = (ulong)Result; @@ -78,9 +114,9 @@ namespace Ryujinx.HLE.HOS.Kernel { KThread CurrentThread = System.Scheduler.GetCurrentThread(); - CurrentThread.Exit(); + System.Scheduler.ExitThread(CurrentThread); - System.Scheduler.StopThread(CurrentThread); + CurrentThread.Exit(); } private void SvcSleepThread(CpuThreadState ThreadState) @@ -176,46 +212,60 @@ namespace Ryujinx.HLE.HOS.Kernel } } - private void SvcSetThreadCoreMask(CpuThreadState ThreadState) + private void SetThreadCoreMask64(CpuThreadState ThreadState) { int Handle = (int)ThreadState.X0; - int PrefferedCore = (int)ThreadState.X1; + int PreferredCore = (int)ThreadState.X1; long AffinityMask = (long)ThreadState.X2; Logger.PrintDebug(LogClass.KernelSvc, "Handle = 0x" + Handle .ToString("x8") + ", " + - "PrefferedCore = 0x" + PrefferedCore.ToString("x8") + ", " + + "PreferredCore = 0x" + PreferredCore.ToString("x8") + ", " + "AffinityMask = 0x" + AffinityMask .ToString("x16")); - if (PrefferedCore == -2) + KernelResult Result = SetThreadCoreMask(Handle, PreferredCore, AffinityMask); + + if (Result != KernelResult.Success) { - //TODO: Get this value from the NPDM file. - PrefferedCore = 0; + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error \"{Result}\"."); + } - AffinityMask = 1 << PrefferedCore; + 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 { - //TODO: Check allowed cores from NPDM file. - - if ((uint)PrefferedCore > 3) + if ((CurrentProcess.Capabilities.AllowedCpuCoresMask | AffinityMask) != + CurrentProcess.Capabilities.AllowedCpuCoresMask) { - if ((PrefferedCore | 2) != -1) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{PrefferedCore:x8}!"); + return KernelResult.InvalidCpuCore; + } - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId); + if (AffinityMask == 0) + { + return KernelResult.InvalidCombination; + } - return; + if ((uint)PreferredCore > 3) + { + if ((PreferredCore | 2) != -1) + { + return KernelResult.InvalidCpuCore; } } - else if ((AffinityMask & (1 << PrefferedCore)) == 0) + else if ((AffinityMask & (1 << PreferredCore)) == 0) { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid core mask 0x{AffinityMask:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue); - - return; + return KernelResult.InvalidCombination; } } @@ -223,26 +273,15 @@ namespace Ryujinx.HLE.HOS.Kernel if (Thread == null) { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{Handle:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); - - return; - } - - long Result = Thread.SetCoreAndAffinityMask(PrefferedCore, AffinityMask); - - if (Result != 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); + return KernelResult.InvalidHandle; } - ThreadState.X0 = (ulong)Result; + return Thread.SetCoreAndAffinityMask(PreferredCore, AffinityMask); } private void SvcGetCurrentProcessorNumber(CpuThreadState ThreadState) { - ThreadState.X0 = (ulong)Process.GetThread(ThreadState.Tpidr).CurrentCore; + ThreadState.X0 = (ulong)System.Scheduler.GetCurrentThread().CurrentCore; } private void SvcGetThreadId(CpuThreadState ThreadState) @@ -254,7 +293,7 @@ namespace Ryujinx.HLE.HOS.Kernel if (Thread != null) { ThreadState.X0 = 0; - ThreadState.X1 = (ulong)Thread.ThreadId; + ThreadState.X1 = (ulong)Thread.ThreadUid; } else { @@ -280,15 +319,24 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - if (Thread.Owner != Process) + if (Thread.Owner != System.Scheduler.GetCurrentProcess()) { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread owner process!"); + 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) @@ -304,6 +352,9 @@ namespace Ryujinx.HLE.HOS.Kernel 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<KThread>(Handle); if (Thread == null) @@ -315,9 +366,18 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - if (Process.GetThread(ThreadState.Tpidr) == Thread) + 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, $"Thread handle 0x{Handle:x8} is current thread!"); + Logger.PrintWarning(LogClass.KernelSvc, "Invalid thread, current thread is not accepted."); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidThread); diff --git a/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs b/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs index 318bd290..3935df5d 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs @@ -32,6 +32,8 @@ namespace Ryujinx.HLE.HOS.Kernel { int Handle = Memory.ReadInt32(HandlesPtr + Index * 4); + Logger.PrintDebug(LogClass.KernelSvc, $"Sync handle 0x{Handle:x8}"); + KSynchronizationObject SyncObj = Process.HandleTable.GetObject<KSynchronizationObject>(Handle); if (SyncObj == null) @@ -116,12 +118,9 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - long Result = System.AddressArbiter.ArbitrateLock( - Process, - Memory, - OwnerHandle, - MutexAddress, - RequesterHandle); + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + long Result = CurrentProcess.AddressArbiter.ArbitrateLock(OwnerHandle, MutexAddress, RequesterHandle); if (Result != 0) { @@ -155,7 +154,9 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - long Result = System.AddressArbiter.ArbitrateUnlock(Memory, MutexAddress); + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + long Result = CurrentProcess.AddressArbiter.ArbitrateUnlock(MutexAddress); if (Result != 0) { @@ -196,8 +197,9 @@ namespace Ryujinx.HLE.HOS.Kernel return; } - long Result = System.AddressArbiter.WaitProcessWideKeyAtomic( - Memory, + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + long Result = CurrentProcess.AddressArbiter.WaitProcessWideKeyAtomic( MutexAddress, CondVarAddress, ThreadHandle, @@ -227,7 +229,9 @@ namespace Ryujinx.HLE.HOS.Kernel "Address = 0x" + Address.ToString("x16") + ", " + "Count = 0x" + Count .ToString("x8")); - System.AddressArbiter.SignalProcessWideKey(Process, Memory, Address, Count); + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + CurrentProcess.AddressArbiter.SignalProcessWideKey(Address, Count); ThreadState.X0 = 0; } @@ -263,20 +267,22 @@ namespace Ryujinx.HLE.HOS.Kernel return; } + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + long Result; switch (Type) { case ArbitrationType.WaitIfLessThan: - Result = System.AddressArbiter.WaitForAddressIfLessThan(Memory, Address, Value, false, Timeout); + Result = CurrentProcess.AddressArbiter.WaitForAddressIfLessThan(Address, Value, false, Timeout); break; case ArbitrationType.DecrementAndWaitIfLessThan: - Result = System.AddressArbiter.WaitForAddressIfLessThan(Memory, Address, Value, true, Timeout); + Result = CurrentProcess.AddressArbiter.WaitForAddressIfLessThan(Address, Value, true, Timeout); break; case ArbitrationType.WaitIfEqual: - Result = System.AddressArbiter.WaitForAddressIfEqual(Memory, Address, Value, Timeout); + Result = CurrentProcess.AddressArbiter.WaitForAddressIfEqual(Address, Value, Timeout); break; default: @@ -323,20 +329,22 @@ namespace Ryujinx.HLE.HOS.Kernel return; } + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + long Result; switch (Type) { case SignalType.Signal: - Result = System.AddressArbiter.Signal(Address, Count); + Result = CurrentProcess.AddressArbiter.Signal(Address, Count); break; case SignalType.SignalAndIncrementIfEqual: - Result = System.AddressArbiter.SignalAndIncrementIfEqual(Memory, Address, Value, Count); + Result = CurrentProcess.AddressArbiter.SignalAndIncrementIfEqual(Address, Value, Count); break; case SignalType.SignalAndModifyIfEqual: - Result = System.AddressArbiter.SignalAndModifyIfEqual(Memory, Address, Value, Count); + Result = CurrentProcess.AddressArbiter.SignalAndModifyIfEqual(Address, Value, Count); break; default: diff --git a/Ryujinx.HLE/HOS/Kernel/ThreadSchedState.cs b/Ryujinx.HLE/HOS/Kernel/ThreadSchedState.cs index 603446f3..37e5908a 100644 --- a/Ryujinx.HLE/HOS/Kernel/ThreadSchedState.cs +++ b/Ryujinx.HLE/HOS/Kernel/ThreadSchedState.cs @@ -1,11 +1,15 @@ namespace Ryujinx.HLE.HOS.Kernel { - enum ThreadSchedState : byte + enum ThreadSchedState : ushort { - LowNibbleMask = 0xf, - HighNibbleMask = 0xf0, - ExceptionalMask = 0x70, - ForcePauseFlag = 0x20, + LowMask = 0xf, + HighMask = 0xfff0, + ForcePauseMask = 0x70, + + ProcessPauseFlag = 1 << 4, + ThreadPauseFlag = 1 << 5, + ProcessDebugPauseFlag = 1 << 6, + KernelInitPauseFlag = 1 << 8, None = 0, Paused = 1, diff --git a/Ryujinx.HLE/HOS/Kernel/ThreadType.cs b/Ryujinx.HLE/HOS/Kernel/ThreadType.cs new file mode 100644 index 00000000..0fe83423 --- /dev/null +++ b/Ryujinx.HLE/HOS/Kernel/ThreadType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Kernel +{ + enum ThreadType + { + Dummy, + Kernel, + Kernel2, + User + } +}
\ No newline at end of file |
