diff --git a/ChocolArm64/CpuThread.cs b/ChocolArm64/CpuThread.cs index 11f41236e3..dac376a1db 100644 --- a/ChocolArm64/CpuThread.cs +++ b/ChocolArm64/CpuThread.cs @@ -18,7 +18,7 @@ namespace ChocolArm64 private int _isExecuting; - public CpuThread(Translator translator, MemoryManager memory, long entryPoint) + public CpuThread(Translator translator, MemoryManager memory, long entrypoint) { _translator = translator; Memory = memory; @@ -31,7 +31,7 @@ namespace ChocolArm64 Work = new Thread(delegate() { - translator.ExecuteSubroutine(this, entryPoint); + translator.ExecuteSubroutine(this, entrypoint); memory.RemoveMonitor(ThreadState.Core); diff --git a/ChocolArm64/Exceptions/VmmOutOfMemoryException.cs b/ChocolArm64/Exceptions/VmmOutOfMemoryException.cs deleted file mode 100644 index d6ddf75227..0000000000 --- a/ChocolArm64/Exceptions/VmmOutOfMemoryException.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace ChocolArm64.Exceptions -{ - public class VmmAccessException : Exception - { - private const string ExMsg = "Memory region at 0x{0} with size 0x{1} is not contiguous!"; - - public VmmAccessException() { } - - public VmmAccessException(long position, long size) : base(string.Format(ExMsg, position, size)) { } - } -} \ No newline at end of file diff --git a/ChocolArm64/Memory/MemoryHelper.cs b/ChocolArm64/Memory/MemoryHelper.cs index 2e721afa1c..1c436dd88a 100644 --- a/ChocolArm64/Memory/MemoryHelper.cs +++ b/ChocolArm64/Memory/MemoryHelper.cs @@ -26,22 +26,26 @@ namespace ChocolArm64.Memory { long size = Marshal.SizeOf(); - memory.EnsureRangeIsValid(position, size); + byte[] data = memory.ReadBytes(position, size); - IntPtr ptr = (IntPtr)memory.Translate(position); - - return Marshal.PtrToStructure(ptr); + fixed (byte* ptr = data) + { + return Marshal.PtrToStructure((IntPtr)ptr); + } } public unsafe static void Write(MemoryManager memory, long position, T value) where T : struct { long size = Marshal.SizeOf(); - memory.EnsureRangeIsValid(position, size); + byte[] data = new byte[size]; - IntPtr ptr = (IntPtr)memory.TranslateWrite(position); + fixed (byte* ptr = data) + { + Marshal.StructureToPtr(value, (IntPtr)ptr, false); + } - Marshal.StructureToPtr(value, ptr, false); + memory.WriteBytes(position, data); } public static string ReadAsciiString(MemoryManager memory, long position, long maxSize = -1) diff --git a/ChocolArm64/Memory/MemoryManager.cs b/ChocolArm64/Memory/MemoryManager.cs index ef3fb00646..68d9100b3c 100644 --- a/ChocolArm64/Memory/MemoryManager.cs +++ b/ChocolArm64/Memory/MemoryManager.cs @@ -1,5 +1,6 @@ using ChocolArm64.Events; using ChocolArm64.Exceptions; +using ChocolArm64.Instructions; using ChocolArm64.State; using System; using System.Collections.Concurrent; @@ -197,17 +198,41 @@ namespace ChocolArm64.Memory public ushort ReadUInt16(long position) { - return *((ushort*)Translate(position)); + if ((position & 1) == 0) + { + return *((ushort*)Translate(position)); + } + else + { + return (ushort)(ReadByte(position + 0) << 0 | + ReadByte(position + 1) << 8); + } } public uint ReadUInt32(long position) { - return *((uint*)Translate(position)); + if ((position & 3) == 0) + { + return *((uint*)Translate(position)); + } + else + { + return (uint)(ReadUInt16(position + 0) << 0 | + ReadUInt16(position + 2) << 16); + } } public ulong ReadUInt64(long position) { - return *((ulong*)Translate(position)); + if ((position & 7) == 0) + { + return *((ulong*)Translate(position)); + } + else + { + return (ulong)ReadUInt32(position + 0) << 0 | + (ulong)ReadUInt32(position + 4) << 32; + } } public Vector128 ReadVector8(long position) @@ -218,74 +243,117 @@ namespace ChocolArm64.Memory } else { - throw new PlatformNotSupportedException(); + Vector128 value = VectorHelper.VectorSingleZero(); + + value = VectorHelper.VectorInsertInt(ReadByte(position), value, 0, 0); + + return value; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector128 ReadVector16(long position) { - if (Sse2.IsSupported) + if (Sse2.IsSupported && (position & 1) == 0) { return Sse.StaticCast(Sse2.Insert(Sse2.SetZeroVector128(), ReadUInt16(position), 0)); } else { - throw new PlatformNotSupportedException(); + Vector128 value = VectorHelper.VectorSingleZero(); + + value = VectorHelper.VectorInsertInt(ReadUInt16(position), value, 0, 1); + + return value; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector128 ReadVector32(long position) { - if (Sse.IsSupported) + if (Sse.IsSupported && (position & 3) == 0) { return Sse.LoadScalarVector128((float*)Translate(position)); } else { - throw new PlatformNotSupportedException(); + Vector128 value = VectorHelper.VectorSingleZero(); + + value = VectorHelper.VectorInsertInt(ReadUInt32(position), value, 0, 2); + + return value; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector128 ReadVector64(long position) { - if (Sse2.IsSupported) + if (Sse2.IsSupported && (position & 7) == 0) { return Sse.StaticCast(Sse2.LoadScalarVector128((double*)Translate(position))); } else { - throw new PlatformNotSupportedException(); + Vector128 value = VectorHelper.VectorSingleZero(); + + value = VectorHelper.VectorInsertInt(ReadUInt64(position), value, 0, 3); + + return value; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector128 ReadVector128(long position) { - if (Sse.IsSupported) + if (Sse.IsSupported && (position & 15) == 0) { return Sse.LoadVector128((float*)Translate(position)); } else { - throw new PlatformNotSupportedException(); + Vector128 value = VectorHelper.VectorSingleZero(); + + value = VectorHelper.VectorInsertInt(ReadUInt64(position + 0), value, 0, 3); + value = VectorHelper.VectorInsertInt(ReadUInt64(position + 8), value, 1, 3); + + return value; } } public byte[] ReadBytes(long position, long size) { - if ((uint)size > int.MaxValue) + long endAddr = position + size; + + if ((ulong)size > int.MaxValue) { throw new ArgumentOutOfRangeException(nameof(size)); } - EnsureRangeIsValid(position, size); + if ((ulong)endAddr < (ulong)position) + { + throw new ArgumentOutOfRangeException(nameof(position)); + } byte[] data = new byte[size]; - Marshal.Copy((IntPtr)Translate(position), data, 0, (int)size); + int offset = 0; + + while ((ulong)position < (ulong)endAddr) + { + long pageLimit = (position + PageSize) & ~(long)PageMask; + + if ((ulong)pageLimit > (ulong)endAddr) + { + pageLimit = endAddr; + } + + int copySize = (int)(pageLimit - position); + + Marshal.Copy((IntPtr)Translate(position), data, offset, copySize); + + position += copySize; + offset += copySize; + } return data; } @@ -293,9 +361,36 @@ namespace ChocolArm64.Memory public void ReadBytes(long position, byte[] data, int startIndex, int size) { //Note: This will be moved later. - EnsureRangeIsValid(position, (uint)size); + long endAddr = position + size; - Marshal.Copy((IntPtr)Translate(position), data, startIndex, size); + if ((ulong)size > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + if ((ulong)endAddr < (ulong)position) + { + throw new ArgumentOutOfRangeException(nameof(position)); + } + + int offset = startIndex; + + while ((ulong)position < (ulong)endAddr) + { + long pageLimit = (position + PageSize) & ~(long)PageMask; + + if ((ulong)pageLimit > (ulong)endAddr) + { + pageLimit = endAddr; + } + + int copySize = (int)(pageLimit - position); + + Marshal.Copy((IntPtr)Translate(position), data, offset, copySize); + + position += copySize; + offset += copySize; + } } public void WriteSByte(long position, sbyte value) @@ -325,17 +420,41 @@ namespace ChocolArm64.Memory public void WriteUInt16(long position, ushort value) { - *((ushort*)TranslateWrite(position)) = value; + if ((position & 1) == 0) + { + *((ushort*)TranslateWrite(position)) = value; + } + else + { + WriteByte(position + 0, (byte)(value >> 0)); + WriteByte(position + 1, (byte)(value >> 8)); + } } public void WriteUInt32(long position, uint value) { - *((uint*)TranslateWrite(position)) = value; + if ((position & 3) == 0) + { + *((uint*)TranslateWrite(position)) = value; + } + else + { + WriteUInt16(position + 0, (ushort)(value >> 0)); + WriteUInt16(position + 2, (ushort)(value >> 16)); + } } public void WriteUInt64(long position, ulong value) { - *((ulong*)TranslateWrite(position)) = value; + if ((position & 7) == 0) + { + *((ulong*)TranslateWrite(position)) = value; + } + else + { + WriteUInt32(position + 0, (uint)(value >> 0)); + WriteUInt32(position + 4, (uint)(value >> 32)); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -351,7 +470,7 @@ namespace ChocolArm64.Memory } else { - throw new PlatformNotSupportedException(); + WriteByte(position, (byte)VectorHelper.VectorExtractIntZx(value, 0, 0)); } } @@ -364,46 +483,47 @@ namespace ChocolArm64.Memory } else { - throw new PlatformNotSupportedException(); + WriteUInt16(position, (ushort)VectorHelper.VectorExtractIntZx(value, 0, 1)); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVector32(long position, Vector128 value) { - if (Sse.IsSupported) + if (Sse.IsSupported && (position & 3) == 0) { Sse.StoreScalar((float*)TranslateWrite(position), value); } else { - throw new PlatformNotSupportedException(); + WriteUInt32(position, (uint)VectorHelper.VectorExtractIntZx(value, 0, 2)); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVector64(long position, Vector128 value) { - if (Sse2.IsSupported) + if (Sse2.IsSupported && (position & 7) == 0) { Sse2.StoreScalar((double*)TranslateWrite(position), Sse.StaticCast(value)); } else { - throw new PlatformNotSupportedException(); + WriteUInt64(position, VectorHelper.VectorExtractIntZx(value, 0, 3)); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteVector128(long position, Vector128 value) { - if (Sse.IsSupported) + if (Sse.IsSupported && (position & 15) == 0) { Sse.Store((float*)TranslateWrite(position), value); } else { - throw new PlatformNotSupportedException(); + WriteUInt64(position + 0, VectorHelper.VectorExtractIntZx(value, 0, 3)); + WriteUInt64(position + 8, VectorHelper.VectorExtractIntZx(value, 1, 3)); } } @@ -439,22 +559,48 @@ namespace ChocolArm64.Memory public void WriteBytes(long position, byte[] data, int startIndex, int size) { //Note: This will be moved later. - //Using Translate instead of TranslateWrite is on purpose. - EnsureRangeIsValid(position, (uint)size); + long endAddr = position + size; - Marshal.Copy(data, startIndex, (IntPtr)Translate(position), size); + if ((ulong)endAddr < (ulong)position) + { + throw new ArgumentOutOfRangeException(nameof(position)); + } + + int offset = startIndex; + + while ((ulong)position < (ulong)endAddr) + { + long pageLimit = (position + PageSize) & ~(long)PageMask; + + if ((ulong)pageLimit > (ulong)endAddr) + { + pageLimit = endAddr; + } + + int copySize = (int)(pageLimit - position); + + Marshal.Copy(data, offset, (IntPtr)TranslateWrite(position), copySize); + + position += copySize; + offset += copySize; + } } public void CopyBytes(long src, long dst, long size) { //Note: This will be moved later. - EnsureRangeIsValid(src, size); - EnsureRangeIsValid(dst, size); + if (IsContiguous(src, size) && + IsContiguous(dst, size)) + { + byte* srcPtr = Translate(src); + byte* dstPtr = TranslateWrite(dst); - byte* srcPtr = Translate(src); - byte* dstPtr = TranslateWrite(dst); - - Buffer.MemoryCopy(srcPtr, dstPtr, size, size); + Buffer.MemoryCopy(srcPtr, dstPtr, size, size); + } + else + { + WriteBytes(dst, ReadBytes(src, size)); + } } public void Map(long va, long pa, long size) @@ -703,14 +849,21 @@ Unmapped: } } - public IntPtr GetHostAddress(long position, long size) + public bool TryGetHostAddress(long position, long size, out IntPtr ptr) { - EnsureRangeIsValid(position, size); + if (IsContiguous(position, size)) + { + ptr = (IntPtr)Translate(position); - return (IntPtr)Translate(position); + return true; + } + + ptr = IntPtr.Zero; + + return false; } - internal void EnsureRangeIsValid(long position, long size) + private bool IsContiguous(long position, long size) { long endPos = position + size; @@ -724,12 +877,14 @@ Unmapped: if (pa != expectedPa) { - throw new VmmAccessException(position, size); + return false; } position += PageSize; expectedPa += PageSize; } + + return true; } public bool IsValidPosition(long position) diff --git a/Ryujinx.Common/BitUtils.cs b/Ryujinx.Common/BitUtils.cs new file mode 100644 index 0000000000..5c858029bd --- /dev/null +++ b/Ryujinx.Common/BitUtils.cs @@ -0,0 +1,104 @@ +namespace Ryujinx.Common +{ + public static class BitUtils + { + public static int AlignUp(int Value, int Size) + { + return (Value + (Size - 1)) & -Size; + } + + public static ulong AlignUp(ulong Value, int Size) + { + return (ulong)AlignUp((long)Value, Size); + } + + public static long AlignUp(long Value, int Size) + { + return (Value + (Size - 1)) & -(long)Size; + } + + public static int AlignDown(int Value, int Size) + { + return Value & -Size; + } + + public static ulong AlignDown(ulong Value, int Size) + { + return (ulong)AlignDown((long)Value, Size); + } + + public static long AlignDown(long Value, int Size) + { + return Value & -(long)Size; + } + + public static ulong DivRoundUp(ulong Value, uint Dividend) + { + return (Value + Dividend - 1) / Dividend; + } + + public static long DivRoundUp(long Value, int Dividend) + { + return (Value + Dividend - 1) / Dividend; + } + + public static bool IsPowerOfTwo32(int Value) + { + return Value != 0 && (Value & (Value - 1)) == 0; + } + + public static bool IsPowerOfTwo64(long Value) + { + return Value != 0 && (Value & (Value - 1)) == 0; + } + + public static int CountLeadingZeros32(int Value) + { + return (int)CountLeadingZeros((ulong)Value, 32); + } + + public static int CountLeadingZeros64(long Value) + { + return (int)CountLeadingZeros((ulong)Value, 64); + } + + private static readonly byte[] ClzNibbleTbl = { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }; + + private static ulong CountLeadingZeros(ulong Value, int Size) // Size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.). + { + if (Value == 0ul) + { + return (ulong)Size; + } + + int NibbleIdx = Size; + int PreCount, Count = 0; + + do + { + NibbleIdx -= 4; + PreCount = ClzNibbleTbl[(Value >> NibbleIdx) & 0b1111]; + Count += PreCount; + } + while (PreCount == 4); + + return (ulong)Count; + } + + public static long ReverseBits64(long Value) + { + return (long)ReverseBits64((ulong)Value); + } + + private static ulong ReverseBits64(ulong Value) + { + Value = ((Value & 0xaaaaaaaaaaaaaaaa) >> 1 ) | ((Value & 0x5555555555555555) << 1 ); + Value = ((Value & 0xcccccccccccccccc) >> 2 ) | ((Value & 0x3333333333333333) << 2 ); + Value = ((Value & 0xf0f0f0f0f0f0f0f0) >> 4 ) | ((Value & 0x0f0f0f0f0f0f0f0f) << 4 ); + Value = ((Value & 0xff00ff00ff00ff00) >> 8 ) | ((Value & 0x00ff00ff00ff00ff) << 8 ); + Value = ((Value & 0xffff0000ffff0000) >> 16) | ((Value & 0x0000ffff0000ffff) << 16); + + return (Value >> 32) | (Value << 32); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalVertexAttrib.cs b/Ryujinx.Graphics/Gal/GalVertexAttrib.cs index fa9a391f71..31b648df38 100644 --- a/Ryujinx.Graphics/Gal/GalVertexAttrib.cs +++ b/Ryujinx.Graphics/Gal/GalVertexAttrib.cs @@ -1,5 +1,3 @@ -using System; - namespace Ryujinx.Graphics.Gal { public struct GalVertexAttrib @@ -7,7 +5,7 @@ namespace Ryujinx.Graphics.Gal public int Index { get; private set; } public bool IsConst { get; private set; } public int Offset { get; private set; } - public IntPtr Pointer { get; private set; } + public byte[] Data { get; private set; } public GalVertexAttribSize Size { get; private set; } public GalVertexAttribType Type { get; private set; } @@ -18,14 +16,14 @@ namespace Ryujinx.Graphics.Gal int Index, bool IsConst, int Offset, - IntPtr Pointer, + byte[] Data, GalVertexAttribSize Size, GalVertexAttribType Type, bool IsBgra) { this.Index = Index; this.IsConst = IsConst; - this.Pointer = Pointer; + this.Data = Data; this.Offset = Offset; this.Size = Size; this.Type = Type; diff --git a/Ryujinx.Graphics/Gal/IGalConstBuffer.cs b/Ryujinx.Graphics/Gal/IGalConstBuffer.cs index 37545b2a99..0cdcc2371f 100644 --- a/Ryujinx.Graphics/Gal/IGalConstBuffer.cs +++ b/Ryujinx.Graphics/Gal/IGalConstBuffer.cs @@ -12,5 +12,6 @@ namespace Ryujinx.Graphics.Gal bool IsCached(long Key, long Size); void SetData(long Key, long Size, IntPtr HostAddress); + void SetData(long Key, byte[] Data); } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalRasterizer.cs b/Ryujinx.Graphics/Gal/IGalRasterizer.cs index 052e3f35f0..04f7aae50a 100644 --- a/Ryujinx.Graphics/Gal/IGalRasterizer.cs +++ b/Ryujinx.Graphics/Gal/IGalRasterizer.cs @@ -22,6 +22,7 @@ namespace Ryujinx.Graphics.Gal bool IsIboCached(long Key, long DataSize); void CreateVbo(long Key, int DataSize, IntPtr HostAddress); + void CreateVbo(long Key, byte[] Data); void CreateIbo(long Key, int DataSize, IntPtr HostAddress); void CreateIbo(long Key, int DataSize, byte[] Buffer); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs index e04190e042..a12681c7ce 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs @@ -44,6 +44,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL } } + public void SetData(long Key, byte[] Data) + { + if (Cache.TryGetValue(Key, out OGLStreamBuffer Buffer)) + { + Buffer.SetData(Data); + } + } + public bool TryGetUbo(long Key, out int UboHandle) { if (Cache.TryGetValue(Key, out OGLStreamBuffer Buffer)) diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs index e81cf8a384..ac12314cdf 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLPipeline.cs @@ -600,122 +600,125 @@ namespace Ryujinx.Graphics.Gal.OpenGL ThrowUnsupportedAttrib(Attrib); } - if (Attrib.Type == GalVertexAttribType.Unorm) + fixed (byte* Ptr = Attrib.Data) { - switch (Attrib.Size) + if (Attrib.Type == GalVertexAttribType.Unorm) { - case GalVertexAttribSize._8: - case GalVertexAttribSize._8_8: - case GalVertexAttribSize._8_8_8: - case GalVertexAttribSize._8_8_8_8: - GL.VertexAttrib4N((uint)Attrib.Index, (byte*)Attrib.Pointer); - break; + switch (Attrib.Size) + { + case GalVertexAttribSize._8: + case GalVertexAttribSize._8_8: + case GalVertexAttribSize._8_8_8: + case GalVertexAttribSize._8_8_8_8: + GL.VertexAttrib4N((uint)Attrib.Index, Ptr); + break; - case GalVertexAttribSize._16: - case GalVertexAttribSize._16_16: - case GalVertexAttribSize._16_16_16: - case GalVertexAttribSize._16_16_16_16: - GL.VertexAttrib4N((uint)Attrib.Index, (ushort*)Attrib.Pointer); - break; + case GalVertexAttribSize._16: + case GalVertexAttribSize._16_16: + case GalVertexAttribSize._16_16_16: + case GalVertexAttribSize._16_16_16_16: + GL.VertexAttrib4N((uint)Attrib.Index, (ushort*)Ptr); + break; - case GalVertexAttribSize._32: - case GalVertexAttribSize._32_32: - case GalVertexAttribSize._32_32_32: - case GalVertexAttribSize._32_32_32_32: - GL.VertexAttrib4N((uint)Attrib.Index, (uint*)Attrib.Pointer); - break; + case GalVertexAttribSize._32: + case GalVertexAttribSize._32_32: + case GalVertexAttribSize._32_32_32: + case GalVertexAttribSize._32_32_32_32: + GL.VertexAttrib4N((uint)Attrib.Index, (uint*)Ptr); + break; + } } - } - else if (Attrib.Type == GalVertexAttribType.Snorm) - { - switch (Attrib.Size) + else if (Attrib.Type == GalVertexAttribType.Snorm) { - case GalVertexAttribSize._8: - case GalVertexAttribSize._8_8: - case GalVertexAttribSize._8_8_8: - case GalVertexAttribSize._8_8_8_8: - GL.VertexAttrib4N((uint)Attrib.Index, (sbyte*)Attrib.Pointer); - break; + switch (Attrib.Size) + { + case GalVertexAttribSize._8: + case GalVertexAttribSize._8_8: + case GalVertexAttribSize._8_8_8: + case GalVertexAttribSize._8_8_8_8: + GL.VertexAttrib4N((uint)Attrib.Index, (sbyte*)Ptr); + break; - case GalVertexAttribSize._16: - case GalVertexAttribSize._16_16: - case GalVertexAttribSize._16_16_16: - case GalVertexAttribSize._16_16_16_16: - GL.VertexAttrib4N((uint)Attrib.Index, (short*)Attrib.Pointer); - break; + case GalVertexAttribSize._16: + case GalVertexAttribSize._16_16: + case GalVertexAttribSize._16_16_16: + case GalVertexAttribSize._16_16_16_16: + GL.VertexAttrib4N((uint)Attrib.Index, (short*)Ptr); + break; - case GalVertexAttribSize._32: - case GalVertexAttribSize._32_32: - case GalVertexAttribSize._32_32_32: - case GalVertexAttribSize._32_32_32_32: - GL.VertexAttrib4N((uint)Attrib.Index, (int*)Attrib.Pointer); - break; + case GalVertexAttribSize._32: + case GalVertexAttribSize._32_32: + case GalVertexAttribSize._32_32_32: + case GalVertexAttribSize._32_32_32_32: + GL.VertexAttrib4N((uint)Attrib.Index, (int*)Ptr); + break; + } } - } - else if (Attrib.Type == GalVertexAttribType.Uint) - { - switch (Attrib.Size) + else if (Attrib.Type == GalVertexAttribType.Uint) { - case GalVertexAttribSize._8: - case GalVertexAttribSize._8_8: - case GalVertexAttribSize._8_8_8: - case GalVertexAttribSize._8_8_8_8: - GL.VertexAttribI4((uint)Attrib.Index, (byte*)Attrib.Pointer); - break; + switch (Attrib.Size) + { + case GalVertexAttribSize._8: + case GalVertexAttribSize._8_8: + case GalVertexAttribSize._8_8_8: + case GalVertexAttribSize._8_8_8_8: + GL.VertexAttribI4((uint)Attrib.Index, Ptr); + break; - case GalVertexAttribSize._16: - case GalVertexAttribSize._16_16: - case GalVertexAttribSize._16_16_16: - case GalVertexAttribSize._16_16_16_16: - GL.VertexAttribI4((uint)Attrib.Index, (ushort*)Attrib.Pointer); - break; + case GalVertexAttribSize._16: + case GalVertexAttribSize._16_16: + case GalVertexAttribSize._16_16_16: + case GalVertexAttribSize._16_16_16_16: + GL.VertexAttribI4((uint)Attrib.Index, (ushort*)Ptr); + break; - case GalVertexAttribSize._32: - case GalVertexAttribSize._32_32: - case GalVertexAttribSize._32_32_32: - case GalVertexAttribSize._32_32_32_32: - GL.VertexAttribI4((uint)Attrib.Index, (uint*)Attrib.Pointer); - break; + case GalVertexAttribSize._32: + case GalVertexAttribSize._32_32: + case GalVertexAttribSize._32_32_32: + case GalVertexAttribSize._32_32_32_32: + GL.VertexAttribI4((uint)Attrib.Index, (uint*)Ptr); + break; + } } - } - else if (Attrib.Type == GalVertexAttribType.Sint) - { - switch (Attrib.Size) + else if (Attrib.Type == GalVertexAttribType.Sint) { - case GalVertexAttribSize._8: - case GalVertexAttribSize._8_8: - case GalVertexAttribSize._8_8_8: - case GalVertexAttribSize._8_8_8_8: - GL.VertexAttribI4((uint)Attrib.Index, (sbyte*)Attrib.Pointer); - break; + switch (Attrib.Size) + { + case GalVertexAttribSize._8: + case GalVertexAttribSize._8_8: + case GalVertexAttribSize._8_8_8: + case GalVertexAttribSize._8_8_8_8: + GL.VertexAttribI4((uint)Attrib.Index, (sbyte*)Ptr); + break; - case GalVertexAttribSize._16: - case GalVertexAttribSize._16_16: - case GalVertexAttribSize._16_16_16: - case GalVertexAttribSize._16_16_16_16: - GL.VertexAttribI4((uint)Attrib.Index, (short*)Attrib.Pointer); - break; + case GalVertexAttribSize._16: + case GalVertexAttribSize._16_16: + case GalVertexAttribSize._16_16_16: + case GalVertexAttribSize._16_16_16_16: + GL.VertexAttribI4((uint)Attrib.Index, (short*)Ptr); + break; - case GalVertexAttribSize._32: - case GalVertexAttribSize._32_32: - case GalVertexAttribSize._32_32_32: - case GalVertexAttribSize._32_32_32_32: - GL.VertexAttribI4((uint)Attrib.Index, (int*)Attrib.Pointer); - break; + case GalVertexAttribSize._32: + case GalVertexAttribSize._32_32: + case GalVertexAttribSize._32_32_32: + case GalVertexAttribSize._32_32_32_32: + GL.VertexAttribI4((uint)Attrib.Index, (int*)Ptr); + break; + } } - } - else if (Attrib.Type == GalVertexAttribType.Float) - { - switch (Attrib.Size) + else if (Attrib.Type == GalVertexAttribType.Float) { - case GalVertexAttribSize._32: - case GalVertexAttribSize._32_32: - case GalVertexAttribSize._32_32_32: - case GalVertexAttribSize._32_32_32_32: - GL.VertexAttrib4(Attrib.Index, (float*)Attrib.Pointer); - break; + switch (Attrib.Size) + { + case GalVertexAttribSize._32: + case GalVertexAttribSize._32_32: + case GalVertexAttribSize._32_32_32: + case GalVertexAttribSize._32_32_32_32: + GL.VertexAttrib4(Attrib.Index, (float*)Ptr); + break; - default: ThrowUnsupportedAttrib(Attrib); break; + default: ThrowUnsupportedAttrib(Attrib); break; + } } } } diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs index cd6292f7e7..c4015d020c 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs @@ -92,7 +92,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL { int Handle = GL.GenBuffer(); - VboCache.AddOrUpdate(Key, Handle, (uint)DataSize); + VboCache.AddOrUpdate(Key, Handle, DataSize); IntPtr Length = new IntPtr(DataSize); @@ -100,6 +100,18 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.BufferData(BufferTarget.ArrayBuffer, Length, HostAddress, BufferUsageHint.StreamDraw); } + public void CreateVbo(long Key, byte[] Data) + { + int Handle = GL.GenBuffer(); + + VboCache.AddOrUpdate(Key, Handle, Data.Length); + + IntPtr Length = new IntPtr(Data.Length); + + GL.BindBuffer(BufferTarget.ArrayBuffer, Handle); + GL.BufferData(BufferTarget.ArrayBuffer, Length, Data, BufferUsageHint.StreamDraw); + } + public void CreateIbo(long Key, int DataSize, IntPtr HostAddress) { int Handle = GL.GenBuffer(); @@ -116,7 +128,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL { int Handle = GL.GenBuffer(); - IboCache.AddOrUpdate(Key, Handle, (uint)DataSize); + IboCache.AddOrUpdate(Key, Handle, DataSize); IntPtr Length = new IntPtr(Buffer.Length); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs index 946394059e..411d33aab7 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs @@ -30,6 +30,13 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.BufferSubData(Target, IntPtr.Zero, (IntPtr)Size, HostAddress); } + public void SetData(byte[] Data) + { + GL.BindBuffer(Target, Handle); + + GL.BufferSubData(Target, IntPtr.Zero, (IntPtr)Data.Length, Data); + } + public void Dispose() { Dispose(true); diff --git a/Ryujinx.Graphics/Memory/NvGpuVmm.cs b/Ryujinx.Graphics/Memory/NvGpuVmm.cs index ec660bf0e9..ccb21950fc 100644 --- a/Ryujinx.Graphics/Memory/NvGpuVmm.cs +++ b/Ryujinx.Graphics/Memory/NvGpuVmm.cs @@ -243,9 +243,9 @@ namespace Ryujinx.Graphics.Memory return Cache.IsRegionModified(Memory, BufferType, PA, Size); } - public IntPtr GetHostAddress(long Position, long Size) + public bool TryGetHostAddress(long Position, long Size, out IntPtr Ptr) { - return Memory.GetHostAddress(GetPhysicalAddress(Position), Size); + return Memory.TryGetHostAddress(GetPhysicalAddress(Position), Size, out Ptr); } public byte ReadByte(long Position) diff --git a/Ryujinx.Graphics/NvGpuEngine3d.cs b/Ryujinx.Graphics/NvGpuEngine3d.cs index 4a0310fb3f..918409e2b8 100644 --- a/Ryujinx.Graphics/NvGpuEngine3d.cs +++ b/Ryujinx.Graphics/NvGpuEngine3d.cs @@ -615,9 +615,14 @@ namespace Ryujinx.Graphics if (Gpu.ResourceManager.MemoryRegionModified(Vmm, Key, Cb.Size, NvGpuBufferType.ConstBuffer)) { - IntPtr Source = Vmm.GetHostAddress(Cb.Position, Cb.Size); - - Gpu.Renderer.Buffer.SetData(Key, Cb.Size, Source); + if (Vmm.TryGetHostAddress(Cb.Position, Cb.Size, out IntPtr CbPtr)) + { + Gpu.Renderer.Buffer.SetData(Key, Cb.Size, CbPtr); + } + else + { + Gpu.Renderer.Buffer.SetData(Key, Vmm.ReadBytes(Cb.Position, Cb.Size)); + } } State.ConstBufferKeys[Stage][DeclInfo.Cbuf] = Key; @@ -660,9 +665,14 @@ namespace Ryujinx.Graphics { if (!UsesLegacyQuads) { - IntPtr DataAddress = Vmm.GetHostAddress(IbPosition, IbSize); - - Gpu.Renderer.Rasterizer.CreateIbo(IboKey, IbSize, DataAddress); + if (Vmm.TryGetHostAddress(IbPosition, IbSize, out IntPtr IbPtr)) + { + Gpu.Renderer.Rasterizer.CreateIbo(IboKey, IbSize, IbPtr); + } + else + { + Gpu.Renderer.Rasterizer.CreateIbo(IboKey, IbSize, Vmm.ReadBytes(IbPosition, IbSize)); + } } else { @@ -711,22 +721,22 @@ namespace Ryujinx.Graphics Attribs[ArrayIndex] = new List(); } - long VertexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNAddress + ArrayIndex * 4); + long VbPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNAddress + ArrayIndex * 4); + + bool IsConst = ((Packed >> 6) & 1) != 0; int Offset = (Packed >> 7) & 0x3fff; + GalVertexAttribSize Size = (GalVertexAttribSize)((Packed >> 21) & 0x3f); + GalVertexAttribType Type = (GalVertexAttribType)((Packed >> 27) & 0x7); + + bool IsRgba = ((Packed >> 31) & 1) != 0; + //Note: 16 is the maximum size of an attribute, //having a component size of 32-bits with 4 elements (a vec4). - IntPtr Pointer = Vmm.GetHostAddress(VertexPosition + Offset, 16); + byte[] Data = Vmm.ReadBytes(VbPosition + Offset, 16); - Attribs[ArrayIndex].Add(new GalVertexAttrib( - Attr, - ((Packed >> 6) & 0x1) != 0, - Offset, - Pointer, - (GalVertexAttribSize)((Packed >> 21) & 0x3f), - (GalVertexAttribType)((Packed >> 27) & 0x7), - ((Packed >> 31) & 0x1) != 0)); + Attribs[ArrayIndex].Add(new GalVertexAttrib(Attr, IsConst, Offset, Data, Size, Type, IsRgba)); } State.VertexBindings = new GalVertexBinding[32]; @@ -747,8 +757,8 @@ namespace Ryujinx.Graphics continue; } - long VertexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNAddress + Index * 4); - long VertexEndPos = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNEndAddr + Index * 2); + long VbPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNAddress + Index * 4); + long VbEndPos = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNEndAddr + Index * 2); int VertexDivisor = ReadRegister(NvGpuEngine3dReg.VertexArrayNDivisor + Index * 4); @@ -758,26 +768,31 @@ namespace Ryujinx.Graphics if (Instanced && VertexDivisor != 0) { - VertexPosition += Stride * (CurrentInstance / VertexDivisor); + VbPosition += Stride * (CurrentInstance / VertexDivisor); } - if (VertexPosition > VertexEndPos) + if (VbPosition > VbEndPos) { //Instance is invalid, ignore the draw call continue; } - long VboKey = Vmm.GetPhysicalAddress(VertexPosition); + long VboKey = Vmm.GetPhysicalAddress(VbPosition); - long VbSize = (VertexEndPos - VertexPosition) + 1; + long VbSize = (VbEndPos - VbPosition) + 1; bool VboCached = Gpu.Renderer.Rasterizer.IsVboCached(VboKey, VbSize); if (!VboCached || Gpu.ResourceManager.MemoryRegionModified(Vmm, VboKey, VbSize, NvGpuBufferType.Vertex)) { - IntPtr DataAddress = Vmm.GetHostAddress(VertexPosition, VbSize); - - Gpu.Renderer.Rasterizer.CreateVbo(VboKey, (int)VbSize, DataAddress); + if (Vmm.TryGetHostAddress(VbPosition, VbSize, out IntPtr VbPtr)) + { + Gpu.Renderer.Rasterizer.CreateVbo(VboKey, (int)VbSize, VbPtr); + } + else + { + Gpu.Renderer.Rasterizer.CreateVbo(VboKey, Vmm.ReadBytes(VbPosition, VbSize)); + } } State.VertexBindings[Index].Enabled = true; diff --git a/Ryujinx.HLE/Memory/DeviceMemory.cs b/Ryujinx.HLE/DeviceMemory.cs similarity index 95% rename from Ryujinx.HLE/Memory/DeviceMemory.cs rename to Ryujinx.HLE/DeviceMemory.cs index 3c5f2e5f78..edc7091197 100644 --- a/Ryujinx.HLE/Memory/DeviceMemory.cs +++ b/Ryujinx.HLE/DeviceMemory.cs @@ -1,22 +1,18 @@ using System; using System.Runtime.InteropServices; -namespace Ryujinx.HLE.Memory +namespace Ryujinx.HLE { class DeviceMemory : IDisposable { public const long RamSize = 4L * 1024 * 1024 * 1024; - public ArenaAllocator Allocator { get; private set; } - public IntPtr RamPointer { get; private set; } private unsafe byte* RamPtr; public unsafe DeviceMemory() { - Allocator = new ArenaAllocator(RamSize); - RamPointer = Marshal.AllocHGlobal(new IntPtr(RamSize)); RamPtr = (byte*)RamPointer; diff --git a/Ryujinx.HLE/FileSystem/SaveHelper.cs b/Ryujinx.HLE/FileSystem/SaveHelper.cs index b74d853c79..20138c8cd6 100644 --- a/Ryujinx.HLE/FileSystem/SaveHelper.cs +++ b/Ryujinx.HLE/FileSystem/SaveHelper.cs @@ -29,10 +29,7 @@ namespace Ryujinx.HLE.FileSystem if (SaveMetaData.TitleId == 0 && SaveMetaData.SaveDataType == SaveDataType.SaveData) { - if (Context.Process.MetaData != null) - { - CurrentTitleId = Context.Process.MetaData.ACI0.TitleId; - } + CurrentTitleId = Context.Process.TitleId; } string SaveAccount = SaveMetaData.UserId.IsZero() ? "savecommon" : SaveMetaData.UserId.ToString(); diff --git a/Ryujinx.HLE/HOS/Font/SharedFontManager.cs b/Ryujinx.HLE/HOS/Font/SharedFontManager.cs index 313db34571..55adf46a5c 100644 --- a/Ryujinx.HLE/HOS/Font/SharedFontManager.cs +++ b/Ryujinx.HLE/HOS/Font/SharedFontManager.cs @@ -1,4 +1,4 @@ -using LibHac; +using LibHac; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.Resource; diff --git a/Ryujinx.HLE/HOS/GlobalStateTable.cs b/Ryujinx.HLE/HOS/GlobalStateTable.cs index faf47b2ee5..d7d834533a 100644 --- a/Ryujinx.HLE/HOS/GlobalStateTable.cs +++ b/Ryujinx.HLE/HOS/GlobalStateTable.cs @@ -1,3 +1,4 @@ +using Ryujinx.HLE.HOS.Kernel; using System.Collections.Concurrent; using System.Collections.Generic; @@ -5,28 +6,28 @@ namespace Ryujinx.HLE.HOS { class GlobalStateTable { - private ConcurrentDictionary DictByProcess; + private ConcurrentDictionary DictByProcess; public GlobalStateTable() { - DictByProcess = new ConcurrentDictionary(); + DictByProcess = new ConcurrentDictionary(); } - public bool Add(Process Process, int Id, object Data) + public bool Add(KProcess Process, int Id, object Data) { IdDictionary Dict = DictByProcess.GetOrAdd(Process, (Key) => new IdDictionary()); return Dict.Add(Id, Data); } - public int Add(Process Process, object Data) + public int Add(KProcess Process, object Data) { IdDictionary Dict = DictByProcess.GetOrAdd(Process, (Key) => new IdDictionary()); return Dict.Add(Data); } - public object GetData(Process Process, int Id) + public object GetData(KProcess Process, int Id) { if (DictByProcess.TryGetValue(Process, out IdDictionary Dict)) { @@ -36,7 +37,7 @@ namespace Ryujinx.HLE.HOS return null; } - public T GetData(Process Process, int Id) + public T GetData(KProcess Process, int Id) { if (DictByProcess.TryGetValue(Process, out IdDictionary Dict)) { @@ -46,7 +47,7 @@ namespace Ryujinx.HLE.HOS return default(T); } - public object Delete(Process Process, int Id) + public object Delete(KProcess Process, int Id) { if (DictByProcess.TryGetValue(Process, out IdDictionary Dict)) { @@ -56,7 +57,7 @@ namespace Ryujinx.HLE.HOS return null; } - public ICollection DeleteProcess(Process Process) + public ICollection DeleteProcess(KProcess Process) { if (DictByProcess.TryRemove(Process, out IdDictionary Dict)) { diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index d967c8969b..46c276495a 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -11,32 +11,68 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; -using Nso = Ryujinx.HLE.Loaders.Executables.Nso; +using System.Reflection; +using System.Threading; + +using NxStaticObject = Ryujinx.HLE.Loaders.Executables.NxStaticObject; namespace Ryujinx.HLE.HOS { public class Horizon : IDisposable { + internal const int InitialKipId = 1; + internal const int InitialProcessId = 0x51; + internal const int HidSize = 0x40000; internal const int FontSize = 0x1100000; - private Switch Device; + private const int MemoryBlockAllocatorSize = 0x2710; - private ConcurrentDictionary Processes; + private const ulong UserSlabHeapBase = DramMemoryMap.SlabHeapBase; + private const ulong UserSlabHeapItemSize = KMemoryManager.PageSize; + private const ulong UserSlabHeapSize = 0x3de000; + + internal long PrivilegedProcessLowestId { get; set; } = 1; + internal long PrivilegedProcessHighestId { get; set; } = 8; + + internal Switch Device { get; private set; } public SystemStateMgr State { get; private set; } - internal KRecursiveLock CriticalSectionLock { get; private set; } + internal bool KernelInitialized { get; private set; } + + internal KResourceLimit ResourceLimit { get; private set; } + + internal KMemoryRegionManager[] MemoryRegions { get; private set; } + + internal KMemoryBlockAllocator LargeMemoryBlockAllocator { get; private set; } + internal KMemoryBlockAllocator SmallMemoryBlockAllocator { get; private set; } + + internal KSlabHeap UserSlabHeapPages { get; private set; } + + internal KCriticalSection CriticalSection { get; private set; } internal KScheduler Scheduler { get; private set; } internal KTimeManager TimeManager { get; private set; } - internal KAddressArbiter AddressArbiter { get; private set; } - internal KSynchronization Synchronization { get; private set; } - internal LinkedList Withholders { get; private set; } + internal KContextIdManager ContextIdManager { get; private set; } + + private long KipId; + private long ProcessId; + private long ThreadUid; + + internal CountdownEvent ThreadCounter; + + internal SortedDictionary Processes; + + internal ConcurrentDictionary AutoObjectNames; + + internal bool EnableVersionChecks { get; private set; } + + internal AppletStateMgr AppletState { get; private set; } internal KSharedMemory HidSharedMem { get; private set; } internal KSharedMemory FontSharedMem { get; private set; } @@ -57,38 +93,74 @@ namespace Ryujinx.HLE.HOS public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; } + internal long HidBaseAddress { get; private set; } + public Horizon(Switch Device) { this.Device = Device; - Processes = new ConcurrentDictionary(); - State = new SystemStateMgr(); - CriticalSectionLock = new KRecursiveLock(this); + ResourceLimit = new KResourceLimit(this); + + KernelInit.InitializeResourceLimit(ResourceLimit); + + MemoryRegions = KernelInit.GetMemoryRegions(); + + LargeMemoryBlockAllocator = new KMemoryBlockAllocator(MemoryBlockAllocatorSize * 2); + SmallMemoryBlockAllocator = new KMemoryBlockAllocator(MemoryBlockAllocatorSize); + + UserSlabHeapPages = new KSlabHeap( + UserSlabHeapBase, + UserSlabHeapItemSize, + UserSlabHeapSize); + + CriticalSection = new KCriticalSection(this); Scheduler = new KScheduler(this); TimeManager = new KTimeManager(); - AddressArbiter = new KAddressArbiter(this); - Synchronization = new KSynchronization(this); - Withholders = new LinkedList(); + ContextIdManager = new KContextIdManager(); + + KipId = InitialKipId; + ProcessId = InitialProcessId; Scheduler.StartAutoPreemptionThread(); - if (!Device.Memory.Allocator.TryAllocate(HidSize, out long HidPA) || - !Device.Memory.Allocator.TryAllocate(FontSize, out long FontPA)) - { - throw new InvalidOperationException(); - } + KernelInitialized = true; - HidSharedMem = new KSharedMemory(HidPA, HidSize); - FontSharedMem = new KSharedMemory(FontPA, FontSize); + ThreadCounter = new CountdownEvent(1); - Font = new SharedFontManager(Device, FontSharedMem.PA); + Processes = new SortedDictionary(); + + AutoObjectNames = new ConcurrentDictionary(); + + //Note: This is not really correct, but with HLE of services, the only memory + //region used that is used is Application, so we can use the other ones for anything. + KMemoryRegionManager Region = MemoryRegions[(int)MemoryRegion.NvServices]; + + ulong HidPa = Region.Address; + ulong FontPa = Region.Address + HidSize; + + HidBaseAddress = (long)(HidPa - DramMemoryMap.DramBase); + + KPageList HidPageList = new KPageList(); + KPageList FontPageList = new KPageList(); + + HidPageList .AddRange(HidPa, HidSize / KMemoryManager.PageSize); + FontPageList.AddRange(FontPa, FontSize / KMemoryManager.PageSize); + + HidSharedMem = new KSharedMemory(HidPageList, 0, 0, MemoryPermission.Read); + FontSharedMem = new KSharedMemory(FontPageList, 0, 0, MemoryPermission.Read); + + AppletState = new AppletStateMgr(this); + + AppletState.SetFocus(true); + + Font = new SharedFontManager(Device, (long)(FontPa - DramMemoryMap.DramBase)); VsyncEvent = new KEvent(this); @@ -120,13 +192,15 @@ namespace Ryujinx.HLE.HOS else { Logger.PrintWarning(LogClass.Loader, $"NPDM file not found, using default values!"); + + MetaData = GetDefaultNpdm(); } - Process MainProcess = MakeProcess(MetaData); + List StaticObjects = new List(); - void LoadNso(string FileName) + void LoadNso(string SearchPattern) { - foreach (string File in Directory.GetFiles(ExeFsDir, FileName)) + foreach (string File in Directory.GetFiles(ExeFsDir, SearchPattern)) { if (Path.GetExtension(File) != string.Empty) { @@ -137,33 +211,28 @@ namespace Ryujinx.HLE.HOS using (FileStream Input = new FileStream(File, FileMode.Open)) { - string Name = Path.GetFileNameWithoutExtension(File); + NxStaticObject StaticObject = new NxStaticObject(Input); - Nso Program = new Nso(Input, Name); - - MainProcess.LoadProgram(Program); + StaticObjects.Add(StaticObject); } } } - if (!(MainProcess.MetaData?.Is64Bits ?? true)) + if (!MetaData.Is64Bits) { throw new NotImplementedException("32-bit titles are unsupported!"); } - CurrentTitle = MainProcess.MetaData.ACI0.TitleId.ToString("x16"); + CurrentTitle = MetaData.ACI0.TitleId.ToString("x16"); LoadNso("rtld"); - - MainProcess.SetEmptyArgs(); - LoadNso("main"); LoadNso("subsdk*"); LoadNso("sdk"); ContentManager.LoadEntries(); - MainProcess.Run(); + ProgramLoader.LoadStaticObjects(this, MetaData, StaticObjects.ToArray()); } public void LoadXci(string XciFile) @@ -356,9 +425,11 @@ namespace Ryujinx.HLE.HOS else { Logger.PrintWarning(LogClass.Loader, $"NPDM file not found, using default values!"); + + MetaData = GetDefaultNpdm(); } - Process MainProcess = MakeProcess(MetaData); + List StaticObjects = new List(); void LoadNso(string Filename) { @@ -371,11 +442,9 @@ namespace Ryujinx.HLE.HOS Logger.PrintInfo(LogClass.Loader, $"Loading {Filename}..."); - string Name = Path.GetFileNameWithoutExtension(File.Name); + NxStaticObject StaticObject = new NxStaticObject(Exefs.OpenFile(File)); - Nso Program = new Nso(Exefs.OpenFile(File), Name); - - MainProcess.LoadProgram(Program); + StaticObjects.Add(StaticObject); } } @@ -401,69 +470,52 @@ namespace Ryujinx.HLE.HOS if (ControlNca != null) { - MainProcess.ControlData = ReadControlData(); + ReadControlData(); } else { - CurrentTitle = MainProcess.MetaData.ACI0.TitleId.ToString("x16"); + CurrentTitle = MetaData.ACI0.TitleId.ToString("x16"); } - if (!MainProcess.MetaData.Is64Bits) + if (!MetaData.Is64Bits) { - throw new NotImplementedException("32-bit titles are unsupported!"); + throw new NotImplementedException("32-bit titles are not supported!"); } LoadNso("rtld"); - - MainProcess.SetEmptyArgs(); - LoadNso("main"); LoadNso("subsdk"); LoadNso("sdk"); ContentManager.LoadEntries(); - MainProcess.Run(); + ProgramLoader.LoadStaticObjects(this, MetaData, StaticObjects.ToArray()); } public void LoadProgram(string FilePath) { + Npdm MetaData = GetDefaultNpdm(); + bool IsNro = Path.GetExtension(FilePath).ToLower() == ".nro"; - string Name = Path.GetFileNameWithoutExtension(FilePath); - string SwitchFilePath = Device.FileSystem.SystemPathToSwitchPath(FilePath); - - if (IsNro && (SwitchFilePath == null || !SwitchFilePath.StartsWith("sdmc:/"))) - { - string SwitchPath = $"sdmc:/switch/{Name}{Homebrew.TemporaryNroSuffix}"; - string TempPath = Device.FileSystem.SwitchPathToSystemPath(SwitchPath); - - string SwitchDir = Path.GetDirectoryName(TempPath); - - if (!Directory.Exists(SwitchDir)) - { - Directory.CreateDirectory(SwitchDir); - } - - File.Copy(FilePath, TempPath, true); - - FilePath = TempPath; - } - - Process MainProcess = MakeProcess(); - using (FileStream Input = new FileStream(FilePath, FileMode.Open)) { - MainProcess.LoadProgram(IsNro - ? (IExecutable)new Nro(Input, FilePath) - : (IExecutable)new Nso(Input, FilePath)); + IExecutable StaticObject = IsNro + ? (IExecutable)new NxRelocatableObject(Input) + : (IExecutable)new NxStaticObject(Input); + + ProgramLoader.LoadStaticObjects(this, MetaData, new IExecutable[] { StaticObject }); } + } - MainProcess.SetEmptyArgs(); + private Npdm GetDefaultNpdm() + { + Assembly Asm = Assembly.GetCallingAssembly(); - ContentManager.LoadEntries(); - - MainProcess.Run(IsNro); + using (Stream NpdmStream = Asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm")) + { + return new Npdm(NpdmStream); + } } public void LoadKeySet() @@ -507,51 +559,19 @@ namespace Ryujinx.HLE.HOS VsyncEvent.ReadableEvent.Signal(); } - private Process MakeProcess(Npdm MetaData = null) + internal long GetThreadUid() { - HasStarted = true; - - Process Process; - - lock (Processes) - { - int ProcessId = 0; - - while (Processes.ContainsKey(ProcessId)) - { - ProcessId++; - } - - Process = new Process(Device, ProcessId, MetaData); - - Processes.TryAdd(ProcessId, Process); - } - - InitializeProcess(Process); - - return Process; + return Interlocked.Increment(ref ThreadUid) - 1; } - private void InitializeProcess(Process Process) + internal long GetKipId() { - Process.AppletState.SetFocus(true); + return Interlocked.Increment(ref KipId) - 1; } - internal void ExitProcess(int ProcessId) + internal long GetProcessId() { - if (Processes.TryRemove(ProcessId, out Process Process)) - { - Process.Dispose(); - - if (Processes.Count == 0) - { - Scheduler.Dispose(); - - TimeManager.Dispose(); - - Device.Unload(); - } - } + return Interlocked.Increment(ref ProcessId) - 1; } public void EnableMultiCoreScheduling() @@ -579,10 +599,25 @@ namespace Ryujinx.HLE.HOS { if (Disposing) { - foreach (Process Process in Processes.Values) + //Force all threads to exit. + lock (Processes) { - Process.Dispose(); + foreach (KProcess Process in Processes.Values) + { + Process.StopAllThreads(); + } } + + //It's only safe to release resources once all threads + //have exited. + ThreadCounter.Signal(); + ThreadCounter.Wait(); + + Scheduler.Dispose(); + + TimeManager.Dispose(); + + Device.Unload(); } } } diff --git a/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs b/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs index ec27a6eaf9..860c8242e7 100644 --- a/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs +++ b/Ryujinx.HLE/HOS/Ipc/IpcHandler.cs @@ -8,8 +8,8 @@ namespace Ryujinx.HLE.HOS.Ipc static class IpcHandler { public static long IpcCall( - Switch Ns, - Process Process, + Switch Device, + KProcess Process, MemoryManager Memory, KSession Session, IpcMessage Request, @@ -31,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Ipc BinaryWriter ResWriter = new BinaryWriter(ResMS); ServiceCtx Context = new ServiceCtx( - Ns, + Device, Process, Memory, Session, diff --git a/Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs b/Ryujinx.HLE/HOS/Kernel/AddressSpaceType.cs index c97caf42a8..6f7b230e70 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 8a2d47f7b0..b584d7192b 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 0000000000..b20a83e2a9 --- /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 0bfa2710cd..6a424cf23d 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 Threads; + private class PausableThread + { + public ManualResetEvent Event { get; private set; } + + public bool IsExiting { get; set; } + + public PausableThread() + { + Event = new ManualResetEvent(false); + } + } + + private ConcurrentDictionary Threads; public HleCoreManager() { - Threads = new ConcurrentDictionary(); + Threads = new ConcurrentDictionary(); } - public ManualResetEvent GetThread(Thread Thread) + public void Set(Thread Thread) { - return Threads.GetOrAdd(Thread, (Key) => new ManualResetEvent(false)); + GetThread(Thread).Event.Set(); + } + + public void Reset(Thread Thread) + { + GetThread(Thread).Event.Reset(); + } + + public void Wait(Thread Thread) + { + PausableThread PausableThread = GetThread(Thread); + + if (!PausableThread.IsExiting) + { + PausableThread.Event.WaitOne(); + } + } + + public void Exit(Thread Thread) + { + GetThread(Thread).IsExiting = true; + } + + private PausableThread GetThread(Thread Thread) + { + return Threads.GetOrAdd(Thread, (Key) => new PausableThread()); } public void RemoveThread(Thread Thread) { - if (Threads.TryRemove(Thread, out 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 0000000000..a6053b1b12 --- /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 Images; + + private int Loaded; + + public HleProcessDebugger(KProcess Owner) + { + this.Owner = Owner; + + Images = new List(); + } + + public void PrintGuestStackTrace(CpuThreadState ThreadState) + { + EnsureLoaded(); + + StringBuilder Trace = new StringBuilder(); + + Trace.AppendLine("Guest stack trace:"); + + void AppendTrace(long Address) + { + Image Image = GetImage(Address, out int ImageIndex); + + if (Image == null || !TryGetSubName(Image, Address, out string SubName)) + { + SubName = $"Sub{Address:x16}"; + } + else if (SubName.StartsWith("_Z")) + { + SubName = Demangler.Parse(SubName); + } + + if (Image != null) + { + long Offset = Address - Image.BaseAddress; + + string ImageName = GetGuessedNsoNameFromIndex(ImageIndex); + + string ImageNameAndOffset = $"[{Owner.Name}] {ImageName}:0x{Offset:x8}"; + + Trace.AppendLine($" {ImageNameAndOffset} {SubName}"); + } + else + { + Trace.AppendLine($" [{Owner.Name}] ??? {SubName}"); + } + } + + long FramePointer = (long)ThreadState.X29; + + while (FramePointer != 0) + { + if ((FramePointer & 7) != 0 || + !Owner.CpuMemory.IsMapped(FramePointer) || + !Owner.CpuMemory.IsMapped(FramePointer + 8)) + { + break; + } + + //Note: This is the return address, we need to subtract one instruction + //worth of bytes to get the branch instruction address. + AppendTrace(Owner.CpuMemory.ReadInt64(FramePointer + 8) - 4); + + FramePointer = Owner.CpuMemory.ReadInt64(FramePointer); + } + + Logger.PrintInfo(LogClass.Cpu, Trace.ToString()); + } + + private bool TryGetSubName(Image Image, long Address, out string Name) + { + Address -= Image.BaseAddress; + + int Left = 0; + int Right = Image.Symbols.Length - 1; + + while (Left <= Right) + { + int Size = Right - Left; + + int Middle = Left + (Size >> 1); + + ElfSymbol Symbol = Image.Symbols[Middle]; + + long EndAddr = Symbol.Value + Symbol.Size; + + if ((ulong)Address >= (ulong)Symbol.Value && (ulong)Address < (ulong)EndAddr) + { + Name = Symbol.Name; + + return true; + } + + if ((ulong)Address < (ulong)Symbol.Value) + { + Right = Middle - 1; + } + else + { + Left = Middle + 1; + } + } + + Name = null; + + return false; + } + + private Image GetImage(long Address, out int Index) + { + lock (Images) + { + for (Index = Images.Count - 1; Index >= 0; Index--) + { + if ((ulong)Address >= (ulong)Images[Index].BaseAddress) + { + return Images[Index]; + } + } + } + + return null; + } + + private string GetGuessedNsoNameFromIndex(int Index) + { + if ((uint)Index > 11) + { + return "???"; + } + + if (Index == 0) + { + return "rtld"; + } + else if (Index == 1) + { + return "main"; + } + else if (Index == GetImagesCount() - 1) + { + return "sdk"; + } + else + { + return "subsdk" + (Index - 2); + } + } + + private int GetImagesCount() + { + lock (Images) + { + return Images.Count; + } + } + + private void EnsureLoaded() + { + if (Interlocked.CompareExchange(ref Loaded, 1, 0) == 0) + { + ScanMemoryForTextSegments(); + } + } + + private void ScanMemoryForTextSegments() + { + ulong OldAddress = 0; + ulong Address = 0; + + while (Address >= OldAddress) + { + KMemoryInfo Info = Owner.MemoryManager.QueryMemory(Address); + + if (Info.State == MemoryState.Reserved) + { + break; + } + + if (Info.State == MemoryState.CodeStatic && Info.Permission == MemoryPermission.ReadAndExecute) + { + LoadMod0Symbols(Owner.CpuMemory, (long)Info.Address); + } + + OldAddress = Address; + + Address = Info.Address + Info.Size; + } + } + + private void LoadMod0Symbols(MemoryManager Memory, long TextOffset) + { + long Mod0Offset = TextOffset + Memory.ReadUInt32(TextOffset + 4); + + if (Mod0Offset < TextOffset || !Memory.IsMapped(Mod0Offset) || (Mod0Offset & 3) != 0) + { + return; + } + + Dictionary Dynamic = new Dictionary(); + + int Mod0Magic = Memory.ReadInt32(Mod0Offset + 0x0); + + if (Mod0Magic != Mod0) + { + return; + } + + long DynamicOffset = Memory.ReadInt32(Mod0Offset + 0x4) + Mod0Offset; + long BssStartOffset = Memory.ReadInt32(Mod0Offset + 0x8) + Mod0Offset; + long BssEndOffset = Memory.ReadInt32(Mod0Offset + 0xc) + Mod0Offset; + long EhHdrStartOffset = Memory.ReadInt32(Mod0Offset + 0x10) + Mod0Offset; + long EhHdrEndOffset = Memory.ReadInt32(Mod0Offset + 0x14) + Mod0Offset; + long ModObjOffset = Memory.ReadInt32(Mod0Offset + 0x18) + Mod0Offset; + + while (true) + { + long TagVal = Memory.ReadInt64(DynamicOffset + 0); + long Value = Memory.ReadInt64(DynamicOffset + 8); + + DynamicOffset += 0x10; + + ElfDynamicTag Tag = (ElfDynamicTag)TagVal; + + if (Tag == ElfDynamicTag.DT_NULL) + { + break; + } + + Dynamic[Tag] = Value; + } + + if (!Dynamic.TryGetValue(ElfDynamicTag.DT_STRTAB, out long StrTab) || + !Dynamic.TryGetValue(ElfDynamicTag.DT_SYMTAB, out long SymTab) || + !Dynamic.TryGetValue(ElfDynamicTag.DT_SYMENT, out long SymEntSize)) + { + return; + } + + long StrTblAddr = TextOffset + StrTab; + long SymTblAddr = TextOffset + SymTab; + + List Symbols = new List(); + + while ((ulong)SymTblAddr < (ulong)StrTblAddr) + { + ElfSymbol Sym = GetSymbol(Memory, SymTblAddr, StrTblAddr); + + Symbols.Add(Sym); + + SymTblAddr += SymEntSize; + } + + lock (Images) + { + Images.Add(new Image(TextOffset, Symbols.OrderBy(x => x.Value).ToArray())); + } + } + + private ElfSymbol GetSymbol(MemoryManager Memory, long Address, long StrTblAddr) + { + int NameIndex = Memory.ReadInt32(Address + 0); + int Info = Memory.ReadByte (Address + 4); + int Other = Memory.ReadByte (Address + 5); + int SHIdx = Memory.ReadInt16(Address + 6); + long Value = Memory.ReadInt64(Address + 8); + long Size = Memory.ReadInt64(Address + 16); + + string Name = string.Empty; + + for (int Chr; (Chr = Memory.ReadByte(StrTblAddr + NameIndex++)) != 0;) + { + Name += (char)Chr; + } + + return new ElfSymbol(Name, Info, Other, SHIdx, Value, Size); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/HleScheduler.cs b/Ryujinx.HLE/HOS/Kernel/HleScheduler.cs index e0cb158c98..87dbe5538b 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 4a0f955fe3..cc637be0e0 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(); } - 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(OwnerHandle); + KThread MutexOwner = CurrentProcess.HandleTable.GetObject(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 SignaledThreads = new Queue(); - System.CriticalSectionLock.Lock(); + System.CriticalSection.Enter(); IOrderedEnumerable 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(MutexValue); + KThread MutexOwner = CurrentProcess.HandleTable.GetObject(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); - //If ShouldDecrement is true, do atomic decrement of the value at Address. - Memory.SetExclusive(0, Address); + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); - if (!UserToKernelInt32(Memory, Address, out int CurrentValue)) + //If ShouldDecrement is true, do atomic decrement of the value at Address. + CurrentProcess.CpuMemory.SetExclusive(0, Address); + + if (!KernelTransfer.UserToKernelInt32(System, Address, out int CurrentValue)) { - System.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(); - 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 + 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 0000000000..a91bf9a842 --- /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 0000000000..e3f8128bf2 --- /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 0000000000..1c95f81168 --- /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 ThreadList, object Mutex, long Timeout) + { + KThread CurrentThread = System.Scheduler.GetCurrentThread(); + + System.CriticalSection.Enter(); + + Monitor.Exit(Mutex); + + CurrentThread.Withholder = ThreadList; + + CurrentThread.Reschedule(ThreadSchedState.Paused); + + CurrentThread.WithholderNode = ThreadList.AddLast(CurrentThread); + + if (CurrentThread.ShallBeTerminated || + CurrentThread.SchedFlags == ThreadSchedState.TerminationPending) + { + ThreadList.Remove(CurrentThread.WithholderNode); + + CurrentThread.Reschedule(ThreadSchedState.Running); + + CurrentThread.Withholder = null; + + System.CriticalSection.Leave(); + } + else + { + if (Timeout > 0) + { + System.TimeManager.ScheduleFutureInvocation(CurrentThread, Timeout); + } + + System.CriticalSection.Leave(); + + if (Timeout > 0) + { + System.TimeManager.UnscheduleFutureInvocation(CurrentThread); + } + } + + Monitor.Enter(Mutex); + } + + public static void NotifyAll(Horizon System, LinkedList ThreadList) + { + System.CriticalSection.Enter(); + + LinkedListNode Node = ThreadList.First; + + for (; Node != null; Node = ThreadList.First) + { + KThread Thread = Node.Value; + + ThreadList.Remove(Thread.WithholderNode); + + Thread.Withholder = null; + + Thread.Reschedule(ThreadSchedState.Running); + } + + System.CriticalSection.Leave(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KContextIdManager.cs b/Ryujinx.HLE/HOS/Kernel/KContextIdManager.cs new file mode 100644 index 0000000000..03e7dddf7d --- /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 02354e16ad..638dde9eeb 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 similarity index 95% rename from Ryujinx.HLE/HOS/Kernel/KRecursiveLock.cs rename to Ryujinx.HLE/HOS/Kernel/KCriticalSection.cs index 30c1a88051..b02a11954d 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 similarity index 81% rename from Ryujinx.HLE/HOS/Kernel/KProcessHandleTable.cs rename to Ryujinx.HLE/HOS/Kernel/KHandleTable.cs index 682f08d4ff..e39dfb671d 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(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(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 0000000000..af393b68e9 --- /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 0000000000..7d66e2913b --- /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 6100741b5c..08190236e3 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 0000000000..08512e129c --- /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 9b73b32b1e..09ba88f2c1 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 4cfb2aa20a..0aa21e3f39 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 Blocks; private MemoryManager CpuMemory; - private ArenaAllocator Allocator; + private Horizon System; - public long AddrSpaceStart { get; private set; } - public long AddrSpaceEnd { get; private set; } + public ulong AddrSpaceStart { get; private set; } + public ulong AddrSpaceEnd { get; private set; } - public long CodeRegionStart { get; private set; } - public long CodeRegionEnd { get; private set; } + public ulong CodeRegionStart { get; private set; } + public ulong CodeRegionEnd { get; private set; } - public long MapRegionStart { get; private set; } - public long MapRegionEnd { get; private set; } + public ulong HeapRegionStart { get; private set; } + public ulong HeapRegionEnd { get; private set; } - public long HeapRegionStart { get; private set; } - public long HeapRegionEnd { get; private set; } + private ulong CurrentHeapAddr; - public long NewMapRegionStart { get; private set; } - public long NewMapRegionEnd { get; private set; } + public ulong AliasRegionStart { get; private set; } + public ulong AliasRegionEnd { get; private set; } - public long TlsIoRegionStart { get; private set; } - public long TlsIoRegionEnd { get; private set; } + public ulong StackRegionStart { get; private set; } + public ulong StackRegionEnd { get; private set; } - public long PersonalMmHeapUsage { get; private set; } + public ulong TlsIoRegionStart { get; private set; } + public ulong TlsIoRegionEnd { get; private set; } - private long CurrentHeapAddr; + private ulong HeapCapacity; - public KMemoryManager(Process Process) + public ulong PhysicalMemoryUsage { get; private set; } + + private MemoryRegion MemRegion; + + private bool AslrDisabled; + + public int AddrSpaceWidth { get; private set; } + + private bool IsKernel; + private bool AslrEnabled; + + private KMemoryBlockAllocator BlockAllocator; + + private int ContextId; + + private MersenneTwister RandomNumberGenerator; + + public KMemoryManager(Horizon System, MemoryManager CpuMemory) { - 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(); + } - 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)); } - AddrSpaceStart = 0; - AddrSpaceEnd = 1L << AddrSpaceWidth; + CodeRegionEnd = CodeRegionStart + CodeRegionSize; - CodeRegionEnd = CodeRegionStart + CodeRegionSize; - MapRegionStart = CodeRegionEnd; - MapRegionEnd = CodeRegionEnd + MapRegionSize; - HeapRegionStart = MapRegionEnd; - HeapRegionEnd = MapRegionEnd + HeapRegionSize; - NewMapRegionStart = HeapRegionEnd; - NewMapRegionEnd = HeapRegionEnd + NewMapRegionSize; - TlsIoRegionStart = NewMapRegionEnd; - TlsIoRegionEnd = NewMapRegionEnd + TlsIoRegionSize; + ulong MapBaseAddress; + ulong MapAvailableSize; - CurrentHeapAddr = HeapRegionStart; - - if (NewMapRegionSize == 0) + if (CodeRegionStart - BaseAddress >= AddrSpaceEnd - CodeRegionEnd) { - NewMapRegionStart = AddrSpaceStart; - NewMapRegionEnd = AddrSpaceEnd; + //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; } - Blocks = new LinkedList(); + ulong MapTotalSize = AliasRegion.Size + HeapRegion.Size + StackRegion.Size + TlsIoRegion.Size; - long AddrSpacePagesCount = (AddrSpaceEnd - AddrSpaceStart) / PageSize; + ulong AslrMaxOffset = MapAvailableSize - MapTotalSize; + + this.AslrEnabled = AslrEnabled; + + this.AddrSpaceStart = AddrSpaceStart; + this.AddrSpaceEnd = AddrSpaceEnd; + + this.BlockAllocator = BlockAllocator; + + if (MapAvailableSize < MapTotalSize) + { + return KernelResult.OutOfMemory; + } + + if (AslrEnabled) + { + AliasRegion.AslrOffset = GetRandomValue(0, AslrMaxOffset >> 21) << 21; + HeapRegion.AslrOffset = GetRandomValue(0, AslrMaxOffset >> 21) << 21; + StackRegion.AslrOffset = GetRandomValue(0, AslrMaxOffset >> 21) << 21; + TlsIoRegion.AslrOffset = GetRandomValue(0, AslrMaxOffset >> 21) << 21; + } + + //Regions are sorted based on ASLR offset. + //When ASLR is disabled, the order is Map, Heap, NewMap and TlsIo. + AliasRegion.Start = MapBaseAddress + AliasRegion.AslrOffset; + AliasRegion.End = AliasRegion.Start + AliasRegion.Size; + HeapRegion.Start = MapBaseAddress + HeapRegion.AslrOffset; + HeapRegion.End = HeapRegion.Start + HeapRegion.Size; + StackRegion.Start = MapBaseAddress + StackRegion.AslrOffset; + StackRegion.End = StackRegion.Start + StackRegion.Size; + TlsIoRegion.Start = MapBaseAddress + TlsIoRegion.AslrOffset; + TlsIoRegion.End = TlsIoRegion.Start + TlsIoRegion.Size; + + SortRegion(HeapRegion, AliasRegion); + + if (StackRegion.Size != 0) + { + SortRegion(StackRegion, AliasRegion); + SortRegion(StackRegion, HeapRegion); + } + else + { + StackRegion.Start = StackAndTlsIoStart; + StackRegion.End = StackAndTlsIoEnd; + } + + if (TlsIoRegion.Size != 0) + { + SortRegion(TlsIoRegion, AliasRegion); + SortRegion(TlsIoRegion, HeapRegion); + SortRegion(TlsIoRegion, StackRegion); + } + else + { + TlsIoRegion.Start = StackAndTlsIoStart; + TlsIoRegion.End = StackAndTlsIoEnd; + } + + AliasRegionStart = AliasRegion.Start; + AliasRegionEnd = AliasRegion.End; + HeapRegionStart = HeapRegion.Start; + HeapRegionEnd = HeapRegion.End; + StackRegionStart = StackRegion.Start; + StackRegionEnd = StackRegion.End; + TlsIoRegionStart = TlsIoRegion.Start; + TlsIoRegionEnd = TlsIoRegion.End; + + CurrentHeapAddr = HeapRegionStart; + HeapCapacity = 0; + PhysicalMemoryUsage = 0; + + 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(); - if (!Allocator.TryAllocate(Size, out long PA)) + ulong Size = PagesCount * PageSize; + + if (!ValidateRegionForState(Address, Size, State)) { - throw new InvalidOperationException(); + return KernelResult.InvalidMemState; } lock (Blocks) { - InsertBlock(Position, PagesCount, MemoryState.CodeStatic, MemoryPermission.ReadAndExecute); - - CpuMemory.Map(Position, PA, Size); - } - } - - public long MapProcessCodeMemory(long Dst, long Src, long Size) - { - lock (Blocks) - { - long PagesCount = Size / PageSize; - - bool Success = IsUnmapped(Dst, Size); - - Success &= CheckRange( - Src, - Size, - MemoryState.Mask, - MemoryState.Heap, - MemoryPermission.Mask, - MemoryPermission.ReadAndWrite, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out _, - out _, - out _); - - if (Success) + if (!IsUnmapped(Address, PagesCount * PageSize)) { - long PA = CpuMemory.GetPhysicalAddress(Src); + return KernelResult.InvalidMemState; + } - InsertBlock(Dst, PagesCount, MemoryState.CodeStatic, MemoryPermission.ReadAndExecute); - InsertBlock(Src, PagesCount, MemoryState.Heap, MemoryPermission.None); + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } - CpuMemory.Map(Dst, PA, Size); + KernelResult Result = MapPages(Address, PageList, Permission); - return 0; + if (Result == KernelResult.Success) + { + InsertBlock(Address, PagesCount, State, Permission); + } + + return Result; + } + } + + public KernelResult UnmapPages(ulong Address, KPageList PageList, MemoryState StateExpected) + { + ulong PagesCount = PageList.GetPagesCount(); + + ulong Size = PagesCount * PageSize; + + ulong EndAddr = Address + Size; + + ulong AddrSpacePagesCount = (AddrSpaceEnd - AddrSpaceStart) / PageSize; + + if (AddrSpaceStart > Address) + { + return KernelResult.InvalidMemState; + } + + if (AddrSpacePagesCount < PagesCount) + { + return KernelResult.InvalidMemState; + } + + if (EndAddr - 1 > AddrSpaceEnd - 1) + { + return KernelResult.InvalidMemState; + } + + lock (Blocks) + { + KPageList CurrentPageList = new KPageList(); + + AddVaRangeToPageList(CurrentPageList, Address, PagesCount); + + if (!CurrentPageList.IsEqual(PageList)) + { + return KernelResult.InvalidMemRange; + } + + if (CheckRange( + Address, + Size, + MemoryState.Mask, + StateExpected, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState State, + out _, + out _)) + { + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + KernelResult Result = MmuUnmap(Address, PagesCount); + + if (Result == KernelResult.Success) + { + InsertBlock(Address, PagesCount, MemoryState.Unmapped); + } + + return Result; + } + else + { + return KernelResult.InvalidMemState; } } - - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); } - public long UnmapProcessCodeMemory(long Dst, long Src, long Size) + public KernelResult MapNormalMemory(long Address, long Size, MemoryPermission Permission) { + //TODO. + return KernelResult.Success; + } + + public KernelResult MapIoMemory(long Address, long Size, MemoryPermission Permission) + { + //TODO. + return KernelResult.Success; + } + + public KernelResult AllocateOrMapPa( + ulong NeededPagesCount, + int Alignment, + ulong SrcPa, + bool Map, + ulong RegionStart, + ulong RegionPagesCount, + MemoryState State, + MemoryPermission Permission, + out ulong Address) + { + Address = 0; + + ulong RegionSize = RegionPagesCount * PageSize; + + ulong RegionEndAddr = RegionStart + RegionSize; + + if (!ValidateRegionForState(RegionStart, RegionSize, State)) + { + return KernelResult.InvalidMemState; + } + + if (RegionPagesCount <= NeededPagesCount) + { + return KernelResult.OutOfMemory; + } + + ulong ReservedPagesCount = IsKernel ? 1UL : 4UL; + lock (Blocks) { - long PagesCount = Size / PageSize; + if (AslrEnabled) + { + ulong TotalNeededSize = (ReservedPagesCount + NeededPagesCount) * PageSize; + ulong RemainingPages = RegionPagesCount - NeededPagesCount; + + ulong AslrMaxOffset = ((RemainingPages + ReservedPagesCount) * PageSize) / (ulong)Alignment; + + for (int Attempt = 0; Attempt < 8; Attempt++) + { + Address = BitUtils.AlignDown(RegionStart + GetRandomValue(0, AslrMaxOffset) * (ulong)Alignment, Alignment); + + ulong EndAddr = Address + TotalNeededSize; + + KMemoryInfo Info = FindBlock(Address).GetInfo(); + + if (Info.State != MemoryState.Unmapped) + { + continue; + } + + ulong CurrBaseAddr = Info.Address + ReservedPagesCount * PageSize; + ulong CurrEndAddr = Info.Address + Info.Size; + + if (Address >= RegionStart && + Address >= CurrBaseAddr && + EndAddr - 1 <= RegionEndAddr - 1 && + EndAddr - 1 <= CurrEndAddr - 1) + { + break; + } + } + + if (Address == 0) + { + ulong AslrPage = GetRandomValue(0, AslrMaxOffset); + + Address = FindFirstFit( + RegionStart + AslrPage * PageSize, + RegionPagesCount - AslrPage, + NeededPagesCount, + Alignment, + 0, + ReservedPagesCount); + } + } + + if (Address == 0) + { + Address = FindFirstFit( + RegionStart, + RegionPagesCount, + NeededPagesCount, + Alignment, + 0, + ReservedPagesCount); + } + + if (Address == 0) + { + return KernelResult.OutOfMemory; + } + + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + MemoryOperation Operation = Map + ? MemoryOperation.MapPa + : MemoryOperation.Allocate; + + KernelResult Result = DoMmuOperation( + Address, + NeededPagesCount, + SrcPa, + Map, + Permission, + Operation); + + if (Result != KernelResult.Success) + { + return Result; + } + + InsertBlock(Address, NeededPagesCount, State, Permission); + } + + return KernelResult.Success; + } + + public KernelResult MapNewProcessCode( + ulong Address, + ulong PagesCount, + MemoryState State, + MemoryPermission Permission) + { + ulong Size = PagesCount * PageSize; + + if (!ValidateRegionForState(Address, Size, State)) + { + return KernelResult.InvalidMemState; + } + + lock (Blocks) + { + if (!IsUnmapped(Address, Size)) + { + return KernelResult.InvalidMemState; + } + + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + KernelResult Result = DoMmuOperation( + Address, + PagesCount, + 0, + false, + Permission, + MemoryOperation.Allocate); + + if (Result == KernelResult.Success) + { + InsertBlock(Address, PagesCount, State, Permission); + } + + return Result; + } + } + + public KernelResult MapProcessCodeMemory(ulong Dst, ulong Src, ulong Size) + { + ulong PagesCount = Size / PageSize; + + lock (Blocks) + { bool Success = CheckRange( - Dst, - Size, - MemoryState.Mask, - MemoryState.CodeStatic, - MemoryPermission.None, - MemoryPermission.None, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out _, - out _, - out _); + Src, + Size, + MemoryState.Mask, + MemoryState.Heap, + MemoryPermission.Mask, + MemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState State, + out MemoryPermission Permission, + out _); - Success &= CheckRange( - Src, - Size, - MemoryState.Mask, - MemoryState.Heap, - MemoryPermission.Mask, - MemoryPermission.None, - MemoryAttribute.Mask, - MemoryAttribute.None, - MemoryAttribute.IpcAndDeviceMapped, - out _, - out _, - out _); + Success &= IsUnmapped(Dst, Size); if (Success) { + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } + + KPageList PageList = new KPageList(); + + AddVaRangeToPageList(PageList, Src, PagesCount); + + KernelResult Result = MmuChangePermission(Src, PagesCount, MemoryPermission.None); + + if (Result != KernelResult.Success) + { + return Result; + } + + Result = MapPages(Dst, PageList, MemoryPermission.None); + + if (Result != KernelResult.Success) + { + MmuChangePermission(Src, PagesCount, Permission); + + return Result; + } + + InsertBlock(Src, PagesCount, State, MemoryPermission.None, MemoryAttribute.Borrowed); + InsertBlock(Dst, PagesCount, MemoryState.ModCodeStatic); + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult UnmapProcessCodeMemory(ulong Dst, ulong Src, ulong Size) + { + ulong PagesCount = Size / PageSize; + + lock (Blocks) + { + bool Success = CheckRange( + Src, + Size, + MemoryState.Mask, + MemoryState.Heap, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.Borrowed, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _); + + Success &= CheckRange( + Dst, + PageSize, + MemoryState.UnmapProcessCodeMemoryAllowed, + MemoryState.UnmapProcessCodeMemoryAllowed, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState State, + out _, + out _); + + Success &= CheckRange( + Dst, + Size, + MemoryState.Mask, + State, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None); + + if (Success) + { + KernelResult Result = MmuUnmap(Dst, PagesCount); + + if (Result != KernelResult.Success) + { + return Result; + } + + //TODO: Missing some checks here. + + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } + InsertBlock(Dst, PagesCount, MemoryState.Unmapped); InsertBlock(Src, PagesCount, MemoryState.Heap, MemoryPermission.ReadAndWrite); - CpuMemory.Unmap(Dst, Size); - - return 0; + return KernelResult.Success; } - } - - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - } - - public void HleMapCustom(long Position, long Size, MemoryState State, MemoryPermission Permission) - { - long PagesCount = Size / PageSize; - - if (!Allocator.TryAllocate(Size, out long PA)) - { - throw new InvalidOperationException(); - } - - lock (Blocks) - { - InsertBlock(Position, PagesCount, State, Permission); - - CpuMemory.Map(Position, PA, Size); - } - } - - public long HleMapTlsPage() - { - bool HasTlsIoRegion = TlsIoRegionStart != TlsIoRegionEnd; - - long Position = HasTlsIoRegion ? TlsIoRegionStart : CodeRegionStart; - - lock (Blocks) - { - while (Position < (HasTlsIoRegion ? TlsIoRegionEnd : CodeRegionEnd)) + else { - if (FindBlock(Position).State == MemoryState.Unmapped) - { - InsertBlock(Position, 1, MemoryState.ThreadLocal, MemoryPermission.ReadAndWrite); - - if (!Allocator.TryAllocate(PageSize, out long PA)) - { - throw new InvalidOperationException(); - } - - CpuMemory.Map(Position, PA, PageSize); - - return Position; - } - - Position += PageSize; + return KernelResult.InvalidMemState; } - - throw new InvalidOperationException(); } } - 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)) { - if (!Allocator.TryAllocate(DiffSize, out long PA)) + return KernelResult.ResLimitExceeded; + } + + ulong PagesCount = DiffSize / PageSize; + + KMemoryRegionManager Region = GetMemoryRegionManager(); + + KernelResult Result = Region.AllocatePages(PagesCount, AslrDisabled, out KPageList PageList); + + void CleanUpForError() + { + if (PageList != null) { - return MakeError(ErrorModule.Kernel, KernelErr.OutOfMemory); + Region.FreePages(PageList); } - long PagesCount = DiffSize / PageSize; - - InsertBlock(CurrentHeapAddr, PagesCount, MemoryState.Heap, MemoryPermission.ReadAndWrite); - - CpuMemory.Map(CurrentHeapAddr, PA, DiffSize); + if (CurrentProcess.ResourceLimit != null && DiffSize != 0) + { + CurrentProcess.ResourceLimit.Release(LimitableResource.Memory, DiffSize); + } } + + if (Result != KernelResult.Success) + { + CleanUpForError(); + + return Result; + } + + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + CleanUpForError(); + + return KernelResult.OutOfResource; + } + + if (!IsUnmapped(CurrentHeapAddr, DiffSize)) + { + CleanUpForError(); + + return KernelResult.InvalidMemState; + } + + Result = DoMmuOperation( + CurrentHeapAddr, + PagesCount, + PageList, + MemoryPermission.ReadAndWrite, + MemoryOperation.MapVa); + + if (Result != KernelResult.Success) + { + CleanUpForError(); + + return Result; + } + + InsertBlock(CurrentHeapAddr, PagesCount, MemoryState.Heap, MemoryPermission.ReadAndWrite); } } else { //Shrink. - 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; - - InsertBlock(FreeAddr, PagesCount, MemoryState.Unmapped); - - FreePages(FreeAddr, PagesCount); - - CpuMemory.Unmap(FreeAddr, DiffSize); + return KernelResult.InvalidMemState; } + + ulong PagesCount = DiffSize / PageSize; + + KernelResult Result = MmuUnmap(FreeAddr, PagesCount); + + if (Result != KernelResult.Success) + { + return Result; + } + + CurrentProcess.ResourceLimit?.Release(LimitableResource.Memory, BitUtils.AlignDown(DiffSize, PageSize)); + + InsertBlock(FreeAddr, PagesCount, MemoryState.Unmapped); } } CurrentHeapAddr = HeapRegionStart + Size; - if (Success) - { - Position = HeapRegionStart; + Address = HeapRegionStart; - return 0; - } - - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); + return KernelResult.Success; } - public long GetHeapSize() + public ulong GetTotalHeapSize() + { + lock (Blocks) + { + return GetHeapSize() + PhysicalMemoryUsage; + } + } + + 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; + } + + ulong PagesCount = Size / PageSize; + + KPageList PageList = new KPageList(); + + AddVaRangeToPageList(PageList, Src, PagesCount); + + KernelResult Result = MmuChangePermission(Src, PagesCount, MemoryPermission.None); + + if (Result != KernelResult.Success) + { + return Result; + } + + Result = MapPages(Dst, PageList, MemoryPermission.ReadAndWrite); + + if (Result != KernelResult.Success) + { + if (MmuChangePermission(Src, PagesCount, MemoryPermission.ReadAndWrite) != KernelResult.Success) + { + throw new InvalidOperationException("Unexpected failure reverting memory permission."); + } + + return Result; + } InsertBlock(Src, PagesCount, SrcState, MemoryPermission.None, MemoryAttribute.Borrowed); + InsertBlock(Dst, PagesCount, MemoryState.Stack, MemoryPermission.ReadAndWrite); - InsertBlock(Dst, PagesCount, MemoryState.MappedMemory, MemoryPermission.ReadAndWrite); - - long PA = CpuMemory.GetPhysicalAddress(Src); - - CpuMemory.Map(Dst, PA, Size); + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; } } - - return Success ? 0 : MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); } - public long Unmap(long Src, long Dst, long Size) + public KernelResult UnmapForKernel(ulong Address, ulong PagesCount, MemoryState StateExpected) + { + ulong Size = PagesCount * PageSize; + + lock (Blocks) + { + if (CheckRange( + Address, + Size, + MemoryState.Mask, + StateExpected, + MemoryPermission.None, + MemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _)) + { + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + KernelResult Result = MmuUnmap(Address, PagesCount); + + if (Result == KernelResult.Success) + { + InsertBlock(Address, PagesCount, MemoryState.Unmapped); + } + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KernelResult Unmap(ulong Dst, ulong Src, ulong Size) { bool Success; @@ -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; + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } + + ulong PagesCount = Size / PageSize; + + KPageList SrcPageList = new KPageList(); + KPageList DstPageList = new KPageList(); + + AddVaRangeToPageList(SrcPageList, Src, PagesCount); + AddVaRangeToPageList(DstPageList, Dst, PagesCount); + + if (!DstPageList.IsEqual(SrcPageList)) + { + return KernelResult.InvalidMemRange; + } + + KernelResult Result = MmuUnmap(Dst, PagesCount); + + if (Result != KernelResult.Success) + { + return Result; + } + + Result = MmuChangePermission(Src, PagesCount, MemoryPermission.ReadAndWrite); + + if (Result != KernelResult.Success) + { + MapPages(Dst, DstPageList, DstPermission); + + return Result; + } InsertBlock(Src, PagesCount, SrcState, MemoryPermission.ReadAndWrite); - InsertBlock(Dst, PagesCount, MemoryState.Unmapped); - CpuMemory.Unmap(Dst, Size); + return KernelResult.Success; } - } - - return Success ? 0 : MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - } - - public long MapSharedMemory(KSharedMemory SharedMemory, MemoryPermission Permission, long Position) - { - lock (Blocks) - { - if (IsUnmapped(Position, SharedMemory.Size)) + else { - long PagesCount = SharedMemory.Size / PageSize; - - InsertBlock(Position, PagesCount, MemoryState.SharedMemory, Permission); - - CpuMemory.Map(Position, SharedMemory.PA, SharedMemory.Size); - - return 0; + return KernelResult.InvalidMemState; } } - - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); } - public long UnmapSharedMemory(long Position, long Size) + public KernelResult ReserveTransferMemory(ulong Address, ulong Size, MemoryPermission Permission) { 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; - - InsertBlock(Position, PagesCount, MemoryState.Unmapped); - - CpuMemory.Unmap(Position, Size); - - return 0; - } - } - - return MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm); - } - - public long ReserveTransferMemory(long Position, long 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; + } - InsertBlock(Position, PagesCount, State, MemoryPermission.ReadAndWrite); + ulong PagesCount = Size / PageSize; - return 0; + InsertBlock(Address, PagesCount, State, MemoryPermission.ReadAndWrite); + + 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) + 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.CodeMutable; - } - else if (State == MemoryState.ModCodeStatic) - { - State = MemoryState.ModCodeMutable; - } - else - { - throw new InvalidOperationException(); + 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."); + } } - long PagesCount = Size / PageSize; + if (NewState != OldState || Permission != OldPermission) + { + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } - InsertBlock(Position, PagesCount, State, Permission); + ulong PagesCount = Size / PageSize; - return 0; + MemoryOperation Operation = (Permission & MemoryPermission.Execute) != 0 + ? MemoryOperation.ChangePermsAndAttributes + : MemoryOperation.ChangePermRw; + + KernelResult Result = DoMmuOperation(Address, PagesCount, 0, false, Permission, Operation); + + if (Result != KernelResult.Success) + { + return Result; + } + + InsertBlock(Address, PagesCount, NewState, Permission); + } + + return KernelResult.Success; + } + else + { + return KernelResult.InvalidMemState; } } - - 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 BaseNode = FindBlockNode(Position); - - LinkedListNode Node = BaseNode; + LinkedListNode 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; - if (!Allocator.TryAllocate(RemainingSize, out long PA)) + ulong RemainingPages = RemainingSize / PageSize; + + 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 + KernelResult Result = Region.AllocatePages(RemainingPages, AslrDisabled, out KPageList PageList); + + void CleanUpForError() { - Info = Node.Value.GetInfo(); - - if (Info.State == MemoryState.Unmapped) + if (PageList != null) { - long CurrSize = GetSizeInRange(Info, Position, End); - - long MapPosition = Info.Position; - - if ((ulong)MapPosition < (ulong)Position) - { - MapPosition = Position; - } - - CpuMemory.Map(MapPosition, PA, CurrSize); - - PA += CurrSize; + Region.FreePages(PageList); } - Node = Node.Next; + CurrentProcess.ResourceLimit?.Release(LimitableResource.Memory, RemainingSize); } - while ((ulong)(Info.Position + Info.Size) < (ulong)End && Node != null); - PersonalMmHeapUsage += RemainingSize; + if (Result != KernelResult.Success) + { + CleanUpForError(); - long PagesCount = Size / PageSize; + return Result; + } + + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) + { + CleanUpForError(); + + return KernelResult.OutOfResource; + } + + MapPhysicalMemory(PageList, Address, EndAddr); + + 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 Node = FindBlockNode(CurrPosition); + LinkedListNode BaseNode = FindBlockNode(Address); + + LinkedListNode 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; } - PersonalMmHeapUsage -= HeapMappedSize; - - long PagesCount = Size / PageSize; - - InsertBlock(Position, PagesCount, MemoryState.Unmapped); - - FreePages(Position, PagesCount); - - CpuMemory.Unmap(Position, Size); - - return 0; - } - } - - private long GetSizeInRange(KMemoryInfo Info, long Start, long End) - { - long CurrEnd = Info.Size + Info.Position; - long CurrSize = Info.Size; - - if ((ulong)Info.Position < (ulong)Start) - { - CurrSize -= Start - Info.Position; - } - - if ((ulong)CurrEnd > (ulong)End) - { - CurrSize -= CurrEnd - End; - } - - return CurrSize; - } - - private void FreePages(long Position, long PagesCount) - { - for (long Page = 0; Page < PagesCount; Page++) - { - long VA = Position + Page * PageSize; - - if (!CpuMemory.IsMapped(VA)) + if (!BlockAllocator.CanAllocate(MaxBlocksNeededForInsertion)) { - continue; + return KernelResult.OutOfResource; } - long PA = CpuMemory.GetPhysicalAddress(VA); + //Try to unmap all the heap mapped memory inside range. + KernelResult Result = KernelResult.Success; - Allocator.Free(PA, PageSize); + Node = BaseNode; + + do + { + Info = Node.Value.GetInfo(); + + if (Info.State == MemoryState.Heap) + { + ulong BlockSize = GetSizeInRange(Info, Address, EndAddr); + ulong BlockAddress = GetAddrInRange(Info, Address); + + ulong BlockPagesCount = BlockSize / PageSize; + + Result = MmuUnmap(BlockAddress, BlockPagesCount); + + if (Result != KernelResult.Success) + { + //If we failed to unmap, we need to remap everything back again. + MapPhysicalMemory(PageList, Address, BlockAddress + BlockSize); + + break; + } + } + + Node = Node.Next; + } + while (Info.Address + Info.Size < EndAddr && Node != null); + + if (Result == KernelResult.Success) + { + GetMemoryRegionManager().FreePages(PageList); + + PhysicalMemoryUsage -= HeapMappedSize; + + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + CurrentProcess.ResourceLimit?.Release(LimitableResource.Memory, HeapMappedSize); + + ulong PagesCount = Size / PageSize; + + InsertBlock(Address, PagesCount, MemoryState.Unmapped); + } + + return Result; } } - public bool HleIsUnmapped(long Position, long Size) + private void MapPhysicalMemory(KPageList PageList, ulong Address, ulong EndAddr) { - bool Result = false; + KMemoryInfo Info; - lock (Blocks) + LinkedListNode Node = FindBlockNode(Address); + + LinkedListNode PageListNode = PageList.Nodes.First; + + KPageNode PageNode = PageListNode.Value; + + ulong SrcPa = PageNode.Address; + ulong SrcPaPages = PageNode.PagesCount; + + do { - Result = IsUnmapped(Position, Size); - } + Info = Node.Value.GetInfo(); - return Result; + if (Info.State == MemoryState.Unmapped) + { + ulong BlockSize = GetSizeInRange(Info, Address, EndAddr); + + ulong DstVaPages = BlockSize / PageSize; + + ulong DstVa = GetAddrInRange(Info, Address); + + while (DstVaPages > 0) + { + if (SrcPaPages == 0) + { + PageListNode = PageListNode.Next; + + PageNode = PageListNode.Value; + + SrcPa = PageNode.Address; + SrcPaPages = PageNode.PagesCount; + } + + ulong PagesCount = SrcPaPages; + + if (PagesCount > DstVaPages) + { + PagesCount = DstVaPages; + } + + DoMmuOperation( + DstVa, + PagesCount, + SrcPa, + true, + MemoryPermission.ReadAndWrite, + MemoryOperation.MapPa); + + DstVa += PagesCount * PageSize; + SrcPa += PagesCount * PageSize; + SrcPaPages -= PagesCount; + DstVaPages -= PagesCount; + } + } + + Node = Node.Next; + } + while (Info.Address + Info.Size < EndAddr && Node != null); } - private bool IsUnmapped(long Position, long Size) + private static ulong GetSizeInRange(KMemoryInfo Info, ulong Start, ulong End) + { + ulong EndAddr = Info.Size + Info.Address; + ulong Size = Info.Size; + + if (Info.Address < Start) + { + Size -= Start - Info.Address; + } + + if (EndAddr > End) + { + Size -= EndAddr - End; + } + + return Size; + } + + private static ulong GetAddrInRange(KMemoryInfo Info, ulong Start) + { + if (Info.Address < Start) + { + return Start; + } + + return Info.Address; + } + + private void AddVaRangeToPageList(KPageList PageList, ulong Start, ulong PagesCount) + { + ulong Address = Start; + + while (Address < Start + PagesCount * PageSize) + { + KernelResult Result = ConvertVaToPa(Address, out ulong Pa); + + if (Result != KernelResult.Success) + { + throw new InvalidOperationException("Unexpected failure translating virtual address."); + } + + PageList.AddRange(Pa, 1); + + Address += PageSize; + } + } + + private bool IsUnmapped(ulong Address, ulong Size) { return CheckRange( - 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; - ulong Start = (ulong)Position; - ulong End = (ulong)Size + Start; + LinkedListNode Node = FindBlockNode(Address); - if (End <= (ulong)(BlkInfo.Position + BlkInfo.Size)) + KMemoryInfo Info = Node.Value.GetInfo(); + + MemoryState FirstState = Info.State; + MemoryPermission FirstPermission = Info.Permission; + MemoryAttribute FirstAttribute = Info.Attribute; + + 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) { - OutState = BlkInfo.State; - OutPermission = BlkInfo.Permission; - OutAttribute = BlkInfo.Attribute & ~AttributeIgnoreMask; + break; + } + + //Check if this is the last block on the range, if so return success. + if (EndAddr <= Info.Address + Info.Size - 1) + { + OutState = FirstState; + OutPermission = FirstPermission; + OutAttribute = FirstAttribute & ~AttributeIgnoreMask; return true; } + + Node = Node.Next; } + while (Node != null); OutState = MemoryState.Unmapped; OutPermission = MemoryPermission.None; @@ -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 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 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); - ulong Start = (ulong)BasePosition; - ulong End = (ulong)PagesCount * PageSize + Start; + int OldCount = Blocks.Count; + + ulong EndAddr = PagesCount * PageSize + BaseAddress; LinkedListNode NewNode = null; @@ -1042,26 +1873,26 @@ namespace Ryujinx.HLE.HOS.Kernel LinkedListNode 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 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 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 FindBlockNode(long Position) + private KMemoryBlock FindBlock(ulong Address) { - ulong Addr = (ulong)Position; + return FindBlockNode(Address)?.Value; + } + private LinkedListNode FindBlockNode(ulong Address) + { lock (Blocks) { LinkedListNode 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 0000000000..1f334e655e --- /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 0000000000..10db0753f3 --- /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 0000000000..05162323fa --- /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 + { + public LinkedList Nodes { get; private set; } + + public KPageList() + { + Nodes = new LinkedList(); + } + + public KernelResult AddRange(ulong Address, ulong PagesCount) + { + if (PagesCount != 0) + { + if (Nodes.Last != null) + { + KPageNode LastNode = Nodes.Last.Value; + + if (LastNode.Address + LastNode.PagesCount * KMemoryManager.PageSize == Address) + { + Address = LastNode.Address; + PagesCount += LastNode.PagesCount; + + Nodes.RemoveLast(); + } + } + + Nodes.AddLast(new KPageNode(Address, PagesCount)); + } + + return KernelResult.Success; + } + + public ulong GetPagesCount() + { + ulong Sum = 0; + + foreach (KPageNode Node in Nodes) + { + Sum += Node.PagesCount; + } + + return Sum; + } + + public bool IsEqual(KPageList Other) + { + LinkedListNode ThisNode = Nodes.First; + LinkedListNode OtherNode = Other.Nodes.First; + + while (ThisNode != null && OtherNode != null) + { + if (ThisNode.Value.Address != OtherNode.Value.Address || + ThisNode.Value.PagesCount != OtherNode.Value.PagesCount) + { + return false; + } + + ThisNode = ThisNode.Next; + OtherNode = OtherNode.Next; + } + + return ThisNode == null && OtherNode == null; + } + + public IEnumerator GetEnumerator() + { + return Nodes.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KPageNode.cs b/Ryujinx.HLE/HOS/Kernel/KPageNode.cs new file mode 100644 index 0000000000..6cecab2e7f --- /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 0000000000..598f3a3218 --- /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 0000000000..094ef222fa --- /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 FullTlsPages; + private SortedDictionary FreeTlsPages; + + public int DefaultCpuCore { get; private set; } + + public bool Debug { get; private set; } + + public KResourceLimit ResourceLimit { get; private set; } + + public ulong PersonalMmHeapPagesCount { get; private set; } + + private ProcessState State; + + private object ProcessLock; + private object ThreadingLock; + + public KAddressArbiter AddressArbiter { get; private set; } + + public long[] RandomEntropy { get; private set; } + + private bool Signaled; + private bool UseSystemMemBlocks; + + public string Name { get; private set; } + + private int ThreadCount; + + public int MmuFlags { get; private set; } + + private MemoryRegion MemRegion; + + public KProcessCapabilities Capabilities { get; private set; } + + public long TitleId { get; private set; } + public long Pid { get; private set; } + + private long CreationTimestamp; + private ulong Entrypoint; + private ulong ImageSize; + private ulong MainThreadStackSize; + private ulong MemoryUsageCapacity; + private int Category; + + public KHandleTable HandleTable { get; private set; } + + public ulong UserExceptionContextAddress { get; private set; } + + private LinkedList Threads; + + public bool IsPaused { get; private set; } + + public Translator Translator { get; private set; } + + public MemoryManager CpuMemory { get; private set; } + + private SvcHandler SvcHandler; + + public HleProcessDebugger Debugger { get; private set; } + + public KProcess(Horizon System) : base(System) + { + ProcessLock = new object(); + ThreadingLock = new object(); + + CpuMemory = new MemoryManager(System.Device.Memory.RamPointer); + + CpuMemory.InvalidAccess += InvalidAccessHandler; + + AddressArbiter = new KAddressArbiter(System); + + MemoryManager = new KMemoryManager(System, CpuMemory); + + FullTlsPages = new SortedDictionary(); + FreeTlsPages = new SortedDictionary(); + + Capabilities = new KProcessCapabilities(); + + RandomEntropy = new long[KScheduler.CpuCoresCount]; + + Threads = new LinkedList(); + + Translator = new Translator(); + + Translator.CpuTrace += CpuTraceHandler; + + SvcHandler = new SvcHandler(System.Device, this); + + Debugger = new HleProcessDebugger(this); + } + + public KernelResult InitializeKip( + ProcessCreationInfo CreationInfo, + int[] Caps, + KPageList PageList, + KResourceLimit ResourceLimit, + MemoryRegion MemRegion) + { + 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 0000000000..dfbe1f3613 --- /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 d43fe8249e..bfb8e7e2db 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 0000000000..6fd70d0cad --- /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 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(); + + 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 3cfda4197f..3342f4a613 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 0000000000..42135cd8ba --- /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 cdd31667b8..a440438b9e 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; - public KSharedMemory(long PA, long Size) + private long OwnerPid; + + private MemoryPermission OwnerPermission; + private MemoryPermission UserPermission; + + public KSharedMemory( + KPageList PageList, + long OwnerPid, + MemoryPermission OwnerPermission, + MemoryPermission UserPermission) { - this.PA = PA; - this.Size = Size; + this.PageList = PageList; + this.OwnerPid = OwnerPid; + this.OwnerPermission = OwnerPermission; + this.UserPermission = UserPermission; + } + + public KernelResult MapIntoProcess( + KMemoryManager MemoryManager, + ulong Address, + ulong Size, + KProcess Process, + MemoryPermission Permission) + { + ulong PagesCountRounded = BitUtils.DivRoundUp(Size, KMemoryManager.PageSize); + + if (PageList.GetPagesCount() != PagesCountRounded) + { + return KernelResult.InvalidSize; + } + + MemoryPermission ExpectedPermission = Process.Pid == OwnerPid + ? OwnerPermission + : UserPermission; + + if (Permission != ExpectedPermission) + { + return KernelResult.InvalidPermission; + } + + return MemoryManager.MapPages(Address, PageList, MemoryState.SharedMemory, Permission); + } + + public KernelResult UnmapFromProcess( + KMemoryManager MemoryManager, + ulong Address, + ulong Size, + KProcess Process) + { + ulong PagesCountRounded = BitUtils.DivRoundUp(Size, KMemoryManager.PageSize); + + if (PageList.GetPagesCount() != PagesCountRounded) + { + return KernelResult.InvalidSize; + } + + return MemoryManager.UnmapPages(Address, PageList, MemoryState.SharedMemory); } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KSlabHeap.cs b/Ryujinx.HLE/HOS/Kernel/KSlabHeap.cs new file mode 100644 index 0000000000..2d6b3ca0f7 --- /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 Items; + + public KSlabHeap(ulong Pa, ulong ItemSize, ulong Size) + { + Items = new LinkedList(); + + int ItemsCount = (int)(Size / ItemSize); + + for (int Index = 0; Index < ItemsCount; Index++) + { + Items.AddLast(Pa); + + Pa += ItemSize; + } + } + + public bool TryGetItem(out ulong Pa) + { + lock (Items) + { + if (Items.First != null) + { + Pa = Items.First.Value; + + Items.RemoveFirst(); + + return true; + } + } + + Pa = 0; + + return false; + } + + public void Free(ulong Pa) + { + lock (Items) + { + Items.AddFirst(Pa); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KSynchronization.cs b/Ryujinx.HLE/HOS/Kernel/KSynchronization.cs index 57a6296c2b..19e700f4c2 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 28eac33069..5ba7784fc8 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 WaitingThreads; - protected Horizon System; - - public KSynchronizationObject(Horizon System) + public KSynchronizationObject(Horizon System) : base(System) { - this.System = System; - WaitingThreads = new LinkedList(); } diff --git a/Ryujinx.HLE/HOS/Kernel/KThread.cs b/Ryujinx.HLE/HOS/Kernel/KThread.cs index 73ee2322d0..88f144c81c 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 LastScheduledTicks { get; set; } + public long MutexAddress { get; set; } + + public KProcess Owner { get; private set; } + + private ulong TlsAddress; + + public long LastScheduledTime { get; set; } public LinkedListNode[] SiblingsPerCore { get; private set; } - private LinkedListNode WithholderNode; + public LinkedList Withholder { get; set; } + public LinkedListNode WithholderNode { get; set; } + + public LinkedListNode ProcessListNode { get; set; } private LinkedList MutexWaiters; private LinkedListNode 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[KScheduler.CpuCoresCount]; MutexWaiters = new LinkedList(); - - AffinityMask = 1 << ProcessorId; - - DynamicPriority = BasePriority = Priority; - - CurrentCore = PreferredCore; } - public long Start() + public KernelResult Initialize( + ulong Entrypoint, + ulong ArgsPtr, + ulong StackTop, + int Priority, + int DefaultCpuCore, + KProcess Owner, + ThreadType Type = ThreadType.User) { - long Result = MakeError(ErrorModule.Kernel, KernelErr.ThreadTerminating); + if ((uint)Type > 3) + { + throw new ArgumentException($"Invalid thread type \"{Type}\"."); + } - System.CriticalSectionLock.Lock(); + PreferredCore = DefaultCpuCore; + + 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 KernelResult Start() + { + if (!System.KernelInitialized) + { + System.CriticalSection.Enter(); + + if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending) + { + ForcePauseFlags |= ThreadSchedState.KernelInitPauseFlag; + + CombineForcePauseFlags(); + } + + System.CriticalSection.Leave(); + } + + KernelResult Result = KernelResult.ThreadTerminating; + + System.CriticalSection.Enter(); if (!ShallBeTerminated) { @@ -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(); + } - SetNewSchedFlags(ThreadSchedState.Running); + public void PrintGuestStackTrace() + { + Owner.Debugger.PrintGuestStackTrace(Context.ThreadState); + } - System.CriticalSectionLock.Unlock(); + private void ThreadFinishedHandler(object sender, EventArgs e) + { + System.Scheduler.ExitThread(this); + + Terminate(); + + System.Scheduler.RemoveThread(this); + } + + public void Terminate() + { + Owner?.RemoveThread(this); + + if (TlsAddress != 0 && Owner.FreeThreadLocalStorage(TlsAddress) != KernelResult.Success) + { + throw new InvalidOperationException("Unexpected failure freeing thread local storage."); + } + + System.CriticalSection.Enter(); + + //Wake up all threads that may be waiting for a mutex being held + //by this thread. + foreach (KThread Thread in MutexWaiters) + { + Thread.MutexOwner = null; + Thread.PreferredCoreOverride = 0; + Thread.ObjSyncResult = 0xfa01; + + Thread.ReleaseAndResume(); + } + + System.CriticalSection.Leave(); + + Owner?.DecrementThreadCountAndTerminateIfZero(); } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/KTimeManager.cs b/Ryujinx.HLE/HOS/Kernel/KTimeManager.cs index 47a3c86cfd..375789f057 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(); - 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; - WaitingObject Next = WaitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault(); - - 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 0000000000..18dc2decf4 --- /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 6ebffa7e49..5598f78d28 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 0000000000..efb514c120 --- /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 d9cbfc6735..9870d17549 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 0000000000..a3fabeaeba --- /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 0000000000..baab422238 --- /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 0000000000..b93501210d --- /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 0000000000..ea4f33c9ce --- /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 2c37723c18..e2ce27ef2f 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 0000000000..b90d54d264 --- /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 0000000000..dae1345aef --- /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 0000000000..98ff4207e7 --- /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 9b475d4ebf..cbc5e31c6f 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 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() { @@ -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 560ad4b3f7..b5845f0b69 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(Handle); + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + KSharedMemory SharedMemory = CurrentProcess.HandleTable.GetObject(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) + KernelResult Result = SharedMemory.MapIntoProcess( + CurrentProcess.MemoryManager, + Address, + Size, + CurrentProcess, + Permission); + + if (Result != KernelResult.Success) { - 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); - - if (Result != 0) - { - 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(Handle); + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + KSharedMemory SharedMemory = CurrentProcess.HandleTable.GetObject(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, $"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.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 54aef5d708..1c1d76f150 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(Handle); + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + KReadableEvent ReadableEvent = CurrentProcess.HandleTable.GetObject(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(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; + } - switch (InfoType) + private KernelResult GetInfo(uint Id, int Handle, long SubId, out long Value) + { + Value = 0; + + 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; + { + if (Handle != 0) + { + return KernelResult.InvalidHandle; + } + + if (SubId != 0) + { + return KernelResult.InvalidCombination; + } + + Value = System.Scheduler.GetCurrentProcess().Debug ? 1 : 0; + break; + } + + case 9: + { + if (Handle != 0) + { + return KernelResult.InvalidHandle; + } + + if (SubId != 0) + { + return KernelResult.InvalidCombination; + } + + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + if (CurrentProcess.ResourceLimit != null) + { + KHandleTable HandleTable = CurrentProcess.HandleTable; + KResourceLimit ResourceLimit = CurrentProcess.ResourceLimit; + + KernelResult Result = HandleTable.GenerateHandle(ResourceLimit, out int ResLimHandle); + + if (Result != KernelResult.Success) + { + return Result; + } + + Value = (uint)ResLimHandle; + } + + break; + } + + case 10: + { + if (Handle != 0) + { + return KernelResult.InvalidHandle; + } + + int CurrentCore = System.Scheduler.GetCurrentThread().CurrentCore; + + if (SubId != -1 && SubId != CurrentCore) + { + return KernelResult.InvalidCombination; + } + + Value = System.Scheduler.CoreContexts[CurrentCore].TotalIdleTimeTicks; + + break; + } case 11: - ThreadState.X1 = (ulong)Rng.Next() + ((ulong)Rng.Next() << 32); + { + if (Handle != 0) + { + return KernelResult.InvalidHandle; + } + + if ((ulong)SubId > 3) + { + return KernelResult.InvalidCombination; + } + + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + + Value = CurrentProcess.RandomEntropy[SubId]; + break; + } + + case 0xf0000002u: + { + if (SubId < -1 || SubId > 3) + { + return KernelResult.InvalidCombination; + } + + KThread Thread = System.Scheduler.GetCurrentProcess().HandleTable.GetKThread(Handle); + + if (Thread == null) + { + return KernelResult.InvalidHandle; + } + + KThread CurrentThread = System.Scheduler.GetCurrentThread(); + + int CurrentCore = CurrentThread.CurrentCore; + + if (SubId != -1 && SubId != CurrentCore) + { + return KernelResult.Success; + } + + KCoreContext CoreContext = System.Scheduler.CoreContexts[CurrentCore]; + + long TimeDelta = PerformanceCounter.ElapsedMilliseconds - CoreContext.LastContextSwitchTime; + + if (SubId != -1) + { + Value = KTimeManager.ConvertMillisecondsToTicks(TimeDelta); + } + else + { + long TotalTimeRunning = Thread.TotalTimeRunning; + + if (Thread == CurrentThread) + { + TotalTimeRunning += TimeDelta; + } + + Value = KTimeManager.ConvertMillisecondsToTicks(TotalTimeRunning); + } - case 12: - ThreadState.X1 = (ulong)Process.MemoryManager.AddrSpaceStart; break; + } - case 13: - ThreadState.X1 = (ulong)Process.MemoryManager.AddrSpaceEnd - - (ulong)Process.MemoryManager.AddrSpaceStart; - break; - - case 14: - ThreadState.X1 = (ulong)Process.MemoryManager.NewMapRegionStart; - break; - - case 15: - ThreadState.X1 = (ulong)Process.MemoryManager.NewMapRegionEnd - - (ulong)Process.MemoryManager.NewMapRegionStart; - break; - - case 16: - ThreadState.X1 = (ulong)(Process.MetaData?.SystemResourceSize ?? 0); - break; - - case 17: - ThreadState.X1 = (ulong)Process.MemoryManager.PersonalMmHeapUsage; - break; - - default: - Process.PrintStackTrace(ThreadState); - - 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 53a557de5e..ded8f8dc9e 100644 --- a/Ryujinx.HLE/HOS/Kernel/SvcThread.cs +++ b/Ryujinx.HLE/HOS/Kernel/SvcThread.cs @@ -7,48 +7,84 @@ 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); - - return; - } - - if (ProcessorId == -2) - { - //TODO: Get this value from the NPDM file. - ProcessorId = 0; - } - else if ((uint)ProcessorId > 3) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{ProcessorId:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId); - - return; - } - - int Handle = Process.MakeThread( - EntryPoint, - StackTop, - ArgsPtr, - Priority, - ProcessorId); - - ThreadState.X0 = 0; + ThreadState.X0 = (ulong)Result; ThreadState.X1 = (ulong)Handle; } + private KernelResult CreateThread( + ulong Entrypoint, + ulong ArgsPtr, + ulong StackTop, + int Priority, + int CpuCore, + out int Handle) + { + Handle = 0; + + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + if (CpuCore == -2) + { + CpuCore = CurrentProcess.DefaultCpuCore; + } + + if ((uint)CpuCore >= KScheduler.CpuCoresCount || !CurrentProcess.IsCpuCoreAllowed(CpuCore)) + { + return KernelResult.InvalidCpuCore; + } + + if ((uint)Priority >= KScheduler.PrioritiesCount || !CurrentProcess.IsPriorityAllowed(Priority)) + { + return KernelResult.InvalidPriority; + } + + long Timeout = KTimeManager.ConvertMillisecondsToNanoseconds(100); + + if (CurrentProcess.ResourceLimit != null && + !CurrentProcess.ResourceLimit.Reserve(LimitableResource.Thread, 1, Timeout)) + { + return KernelResult.ResLimitExceeded; + } + + KThread Thread = new KThread(System); + + KernelResult Result = CurrentProcess.InitializeThread( + Thread, + Entrypoint, + ArgsPtr, + StackTop, + Priority, + CpuCore); + + if (Result != KernelResult.Success) + { + CurrentProcess.ResourceLimit?.Release(LimitableResource.Thread, 1); + + return Result; + } + + Result = Process.HandleTable.GenerateHandle(Thread, out Handle); + + if (Result != KernelResult.Success) + { + Thread.Terminate(); + + CurrentProcess.ResourceLimit?.Release(LimitableResource.Thread, 1); + } + + return Result; + } + private void SvcStartThread(CpuThreadState ThreadState) { int Handle = (int)ThreadState.X0; @@ -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) - { - //TODO: Get this value from the NPDM file. - PrefferedCore = 0; + KernelResult Result = SetThreadCoreMask(Handle, PreferredCore, AffinityMask); - AffinityMask = 1 << PrefferedCore; + if (Result != KernelResult.Success) + { + Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error \"{Result}\"."); + } + + ThreadState.X0 = (ulong)Result; + } + + private KernelResult SetThreadCoreMask(int Handle, int PreferredCore, long AffinityMask) + { + KProcess CurrentProcess = System.Scheduler.GetCurrentProcess(); + + if (PreferredCore == -2) + { + PreferredCore = CurrentProcess.DefaultCpuCore; + + AffinityMask = 1 << PreferredCore; } else { - //TODO: Check allowed cores from NPDM file. - - if ((uint)PrefferedCore > 3) + if ((CurrentProcess.Capabilities.AllowedCpuCoresMask | AffinityMask) != + CurrentProcess.Capabilities.AllowedCpuCoresMask) { - if ((PrefferedCore | 2) != -1) + return KernelResult.InvalidCpuCore; + } + + if (AffinityMask == 0) + { + return KernelResult.InvalidCombination; + } + + if ((uint)PreferredCore > 3) + { + if ((PreferredCore | 2) != -1) { - Logger.PrintWarning(LogClass.KernelSvc, $"Invalid core id 0x{PrefferedCore:x8}!"); - - ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidCoreId); - - return; + 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; + return KernelResult.InvalidHandle; } - long Result = Thread.SetCoreAndAffinityMask(PrefferedCore, AffinityMask); - - if (Result != 0) - { - Logger.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!"); - } - - 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(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, $"Thread handle 0x{Handle:x8} is current thread!"); + Logger.PrintWarning(LogClass.KernelSvc, $"Invalid thread, it belongs to another process."); + + ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle); + + return; + } + + if (CurrentThread == Thread) + { + Logger.PrintWarning(LogClass.KernelSvc, "Invalid thread, current thread is not accepted."); ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidThread); diff --git a/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs b/Ryujinx.HLE/HOS/Kernel/SvcThreadSync.cs index 318bd290f9..3935df5d35 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(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 603446f3cf..37e5908a9c 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 0000000000..0fe83423cf --- /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 diff --git a/Ryujinx.HLE/HOS/Process.cs b/Ryujinx.HLE/HOS/Process.cs deleted file mode 100644 index 93b2d68d0c..0000000000 --- a/Ryujinx.HLE/HOS/Process.cs +++ /dev/null @@ -1,528 +0,0 @@ -using ChocolArm64; -using ChocolArm64.Events; -using ChocolArm64.Memory; -using ChocolArm64.State; -using LibHac; -using Ryujinx.Common.Logging; -using Ryujinx.HLE.Exceptions; -using Ryujinx.HLE.HOS.Diagnostics.Demangler; -using Ryujinx.HLE.HOS.Kernel; -using Ryujinx.HLE.HOS.Services.Nv; -using Ryujinx.HLE.HOS.SystemState; -using Ryujinx.HLE.Loaders; -using Ryujinx.HLE.Loaders.Executables; -using Ryujinx.HLE.Loaders.Npdm; -using Ryujinx.HLE.Utilities; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace Ryujinx.HLE.HOS -{ - class Process : IDisposable - { - private const int TickFreq = 19_200_000; - - public Switch Device { get; private set; } - - public bool NeedsHbAbi { get; private set; } - - public long HbAbiDataPosition { get; private set; } - - public int ProcessId { get; private set; } - - private Translator Translator; - - public MemoryManager Memory { get; private set; } - - public KMemoryManager MemoryManager { get; private set; } - - private List TlsPages; - - public Npdm MetaData { get; private set; } - - public Nacp ControlData { get; set; } - - public KProcessHandleTable HandleTable { get; private set; } - - public AppletStateMgr AppletState { get; private set; } - - private SvcHandler SvcHandler; - - private ConcurrentDictionary Threads; - - private List Executables; - - private long ImageBase; - - private bool Disposed; - - public Process(Switch Device, int ProcessId, Npdm MetaData) - { - this.Device = Device; - this.MetaData = MetaData; - this.ProcessId = ProcessId; - - Memory = new MemoryManager(Device.Memory.RamPointer); - - Memory.InvalidAccess += CpuInvalidAccessHandler; - - MemoryManager = new KMemoryManager(this); - - TlsPages = new List(); - - int HandleTableSize = 1024; - - if (MetaData != null) - { - foreach (KernelAccessControlItem Item in MetaData.ACI0.KernelAccessControl.Items) - { - if (Item.HasHandleTableSize) - { - HandleTableSize = Item.HandleTableSize; - - break; - } - } - } - - HandleTable = new KProcessHandleTable(Device.System, HandleTableSize); - - AppletState = new AppletStateMgr(Device.System); - - SvcHandler = new SvcHandler(Device, this); - - Threads = new ConcurrentDictionary(); - - Executables = new List(); - - ImageBase = MemoryManager.CodeRegionStart; - } - - public void LoadProgram(IExecutable Program) - { - if (Disposed) - { - throw new ObjectDisposedException(nameof(Process)); - } - - long ImageEnd = LoadProgram(Program, ImageBase); - - ImageBase = IntUtils.AlignUp(ImageEnd, KMemoryManager.PageSize); - } - - public long LoadProgram(IExecutable Program, long ExecutableBase) - { - if (Disposed) - { - throw new ObjectDisposedException(nameof(Process)); - } - - Logger.PrintInfo(LogClass.Loader, $"Image base at 0x{ExecutableBase:x16}."); - - Executable Executable = new Executable(Program, MemoryManager, Memory, ExecutableBase); - - Executables.Add(Executable); - - return Executable.ImageEnd; - } - - public void RemoveProgram(long ExecutableBase) - { - foreach (Executable Executable in Executables) - { - if (Executable.ImageBase == ExecutableBase) - { - Executables.Remove(Executable); - break; - } - } - } - - public void SetEmptyArgs() - { - //TODO: This should be part of Run. - ImageBase += KMemoryManager.PageSize; - } - - public bool Run(bool NeedsHbAbi = false) - { - if (Disposed) - { - throw new ObjectDisposedException(nameof(Process)); - } - - this.NeedsHbAbi = NeedsHbAbi; - - if (Executables.Count == 0) - { - return false; - } - - long MainStackTop = MemoryManager.CodeRegionEnd - KMemoryManager.PageSize; - - long MainStackSize = 1 * 1024 * 1024; - - long MainStackBottom = MainStackTop - MainStackSize; - - MemoryManager.HleMapCustom( - MainStackBottom, - MainStackSize, - MemoryState.MappedMemory, - MemoryPermission.ReadAndWrite); - - int Handle = MakeThread(Executables[0].ImageBase, MainStackTop, 0, 44, 0); - - if (Handle == -1) - { - return false; - } - - KThread MainThread = HandleTable.GetKThread(Handle); - - if (NeedsHbAbi) - { - HbAbiDataPosition = IntUtils.AlignUp(Executables[0].ImageEnd, KMemoryManager.PageSize); - - const long HbAbiDataSize = KMemoryManager.PageSize; - - MemoryManager.HleMapCustom( - HbAbiDataPosition, - HbAbiDataSize, - MemoryState.MappedMemory, - MemoryPermission.ReadAndWrite); - - string SwitchPath = Device.FileSystem.SystemPathToSwitchPath(Executables[0].FilePath); - - Homebrew.WriteHbAbiData(Memory, HbAbiDataPosition, Handle, SwitchPath); - - MainThread.Context.ThreadState.X0 = (ulong)HbAbiDataPosition; - MainThread.Context.ThreadState.X1 = ulong.MaxValue; - } - - MainThread.TimeUp(); - - return true; - } - - private int ThreadIdCtr = 1; - - public int MakeThread( - long EntryPoint, - long StackTop, - long ArgsPtr, - int Priority, - int ProcessorId) - { - if (Disposed) - { - throw new ObjectDisposedException(nameof(Process)); - } - - CpuThread CpuThread = new CpuThread(GetTranslator(), Memory, EntryPoint); - - long Tpidr = GetFreeTls(); - - int ThreadId = ThreadIdCtr++; //(int)((Tpidr - MemoryManager.TlsIoRegionStart) / 0x200) + 1; - - KThread Thread = new KThread(CpuThread, this, Device.System, ProcessorId, Priority, ThreadId); - - Thread.LastPc = EntryPoint; - - HandleTable.GenerateHandle(Thread, out int Handle); - - CpuThread.ThreadState.CntfrqEl0 = TickFreq; - CpuThread.ThreadState.Tpidr = Tpidr; - - CpuThread.ThreadState.X0 = (ulong)ArgsPtr; - CpuThread.ThreadState.X1 = (ulong)Handle; - CpuThread.ThreadState.X31 = (ulong)StackTop; - - CpuThread.ThreadState.Interrupt += InterruptHandler; - CpuThread.ThreadState.Break += BreakHandler; - CpuThread.ThreadState.SvcCall += SvcHandler.SvcCall; - CpuThread.ThreadState.Undefined += UndefinedHandler; - - CpuThread.WorkFinished += ThreadFinished; - - Threads.TryAdd(CpuThread.ThreadState.Tpidr, Thread); - - return Handle; - } - - private long GetFreeTls() - { - long Position; - - lock (TlsPages) - { - for (int Index = 0; Index < TlsPages.Count; Index++) - { - if (TlsPages[Index].TryGetFreeTlsAddr(out Position)) - { - return Position; - } - } - - long PagePosition = MemoryManager.HleMapTlsPage(); - - KTlsPageManager TlsPage = new KTlsPageManager(PagePosition); - - TlsPages.Add(TlsPage); - - TlsPage.TryGetFreeTlsAddr(out Position); - } - - return Position; - } - - private void InterruptHandler(object sender, EventArgs e) - { - Device.System.Scheduler.ContextSwitch(); - } - - private void BreakHandler(object sender, InstExceptionEventArgs e) - { - PrintStackTraceForCurrentThread(); - - throw new GuestBrokeExecutionException(); - } - - private void UndefinedHandler(object sender, InstUndefinedEventArgs e) - { - PrintStackTraceForCurrentThread(); - - throw new UndefinedInstructionException(e.Position, e.RawOpCode); - } - - public void EnableCpuTracing() - { - Translator.EnableCpuTrace = true; - } - - public void DisableCpuTracing() - { - Translator.EnableCpuTrace = false; - } - - private void CpuTraceHandler(object sender, CpuTraceEventArgs e) - { - Executable Exe = GetExecutable(e.Position); - - if (Exe == null) - { - return; - } - - if (!TryGetSubName(Exe, e.Position, out string SubName)) - { - SubName = string.Empty; - } - - long Offset = e.Position - Exe.ImageBase; - - string ExeNameWithAddr = $"{Exe.Name}:0x{Offset:x8}"; - - Logger.PrintDebug(LogClass.Cpu, ExeNameWithAddr + " " + SubName); - } - - private Translator GetTranslator() - { - if (Translator == null) - { - Translator = new Translator(); - - Translator.CpuTrace += CpuTraceHandler; - } - - return Translator; - } - - private void CpuInvalidAccessHandler(object sender, InvalidAccessEventArgs e) - { - PrintStackTraceForCurrentThread(); - } - - private void PrintStackTraceForCurrentThread() - { - foreach (KThread Thread in Threads.Values) - { - if (Thread.Context.IsCurrentThread()) - { - PrintStackTrace(Thread.Context.ThreadState); - - break; - } - } - } - - public void PrintStackTrace(CpuThreadState ThreadState) - { - StringBuilder Trace = new StringBuilder(); - - Trace.AppendLine("Guest stack trace:"); - - void AppendTrace(long Position) - { - Executable Exe = GetExecutable(Position); - - if (Exe == null) - { - return; - } - - if (!TryGetSubName(Exe, Position, out string SubName)) - { - SubName = $"Sub{Position:x16}"; - } - else if (SubName.StartsWith("_Z")) - { - SubName = Demangler.Parse(SubName); - } - - long Offset = Position - Exe.ImageBase; - - string ExeNameWithAddr = $"{Exe.Name}:0x{Offset:x8}"; - - Trace.AppendLine(" " + ExeNameWithAddr + " " + SubName); - } - - long FramePointer = (long)ThreadState.X29; - - while (FramePointer != 0) - { - AppendTrace(Memory.ReadInt64(FramePointer + 8)); - - FramePointer = Memory.ReadInt64(FramePointer); - } - - Logger.PrintInfo(LogClass.Cpu, Trace.ToString()); - } - - private bool TryGetSubName(Executable Exe, long Position, out string Name) - { - Position -= Exe.ImageBase; - - int Left = 0; - int Right = Exe.SymbolTable.Count - 1; - - while (Left <= Right) - { - int Size = Right - Left; - - int Middle = Left + (Size >> 1); - - ElfSym Symbol = Exe.SymbolTable[Middle]; - - long EndPosition = Symbol.Value + Symbol.Size; - - if ((ulong)Position >= (ulong)Symbol.Value && (ulong)Position < (ulong)EndPosition) - { - Name = Symbol.Name; - - return true; - } - - if ((ulong)Position < (ulong)Symbol.Value) - { - Right = Middle - 1; - } - else - { - Left = Middle + 1; - } - } - - Name = null; - - return false; - } - - private Executable GetExecutable(long Position) - { - string Name = string.Empty; - - for (int Index = Executables.Count - 1; Index >= 0; Index--) - { - if ((ulong)Position >= (ulong)Executables[Index].ImageBase) - { - return Executables[Index]; - } - } - - return null; - } - - private void ThreadFinished(object sender, EventArgs e) - { - if (sender is CpuThread Thread) - { - if (Threads.TryRemove(Thread.ThreadState.Tpidr, out KThread KernelThread)) - { - Device.System.Scheduler.RemoveThread(KernelThread); - } - } - - if (Threads.Count == 0) - { - Device.System.ExitProcess(ProcessId); - } - } - - public KThread GetThread(long Tpidr) - { - if (!Threads.TryGetValue(Tpidr, out KThread Thread)) - { - throw new InvalidOperationException(); - } - - return Thread; - } - - private void Unload() - { - if (Disposed || Threads.Count > 0) - { - return; - } - - Disposed = true; - - HandleTable.Destroy(); - - INvDrvServices.UnloadProcess(this); - - if (NeedsHbAbi && Executables.Count > 0 && Executables[0].FilePath.EndsWith(Homebrew.TemporaryNroSuffix)) - { - File.Delete(Executables[0].FilePath); - } - - Logger.PrintInfo(LogClass.Loader, $"Process {ProcessId} exiting..."); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing) - { - if (Threads.Count > 0) - { - foreach (KThread Thread in Threads.Values) - { - Device.System.Scheduler.StopThread(Thread); - } - } - else - { - Unload(); - } - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/ProgramLoader.cs b/Ryujinx.HLE/HOS/ProgramLoader.cs new file mode 100644 index 0000000000..ddacd3fd87 --- /dev/null +++ b/Ryujinx.HLE/HOS/ProgramLoader.cs @@ -0,0 +1,292 @@ +using ChocolArm64.Memory; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.HLE.Loaders.Npdm; + +namespace Ryujinx.HLE.HOS +{ + class ProgramLoader + { + private const bool AslrEnabled = true; + + private const int ArgsHeaderSize = 8; + private const int ArgsDataSize = 0x9000; + private const int ArgsTotalSize = ArgsHeaderSize + ArgsDataSize; + + public static bool LoadKernelInitalProcess(Horizon System, KernelInitialProcess Kip) + { + int EndOffset = Kip.DataOffset + Kip.Data.Length; + + if (Kip.BssSize != 0) + { + EndOffset = Kip.BssOffset + Kip.BssSize; + } + + int CodeSize = BitUtils.AlignUp(Kip.TextOffset + EndOffset, KMemoryManager.PageSize); + + int CodePagesCount = CodeSize / KMemoryManager.PageSize; + + ulong CodeBaseAddress = Kip.Addr39Bits ? 0x8000000UL : 0x200000UL; + + ulong CodeAddress = CodeBaseAddress + (ulong)Kip.TextOffset; + + int MmuFlags = 0; + + if (AslrEnabled) + { + //TODO: Randomization. + + MmuFlags |= 0x20; + } + + if (Kip.Addr39Bits) + { + MmuFlags |= (int)AddressSpaceType.Addr39Bits << 1; + } + + if (Kip.Is64Bits) + { + MmuFlags |= 1; + } + + ProcessCreationInfo CreationInfo = new ProcessCreationInfo( + Kip.Name, + Kip.ProcessCategory, + Kip.TitleId, + CodeAddress, + CodePagesCount, + MmuFlags, + 0, + 0); + + MemoryRegion MemRegion = Kip.IsService + ? MemoryRegion.Service + : MemoryRegion.Application; + + KMemoryRegionManager Region = System.MemoryRegions[(int)MemRegion]; + + KernelResult Result = Region.AllocatePages((ulong)CodePagesCount, false, out KPageList PageList); + + if (Result != KernelResult.Success) + { + Logger.PrintError(LogClass.Loader, $"Process initialization returned error \"{Result}\"."); + + return false; + } + + KProcess Process = new KProcess(System); + + Result = Process.InitializeKip( + CreationInfo, + Kip.Capabilities, + PageList, + System.ResourceLimit, + MemRegion); + + if (Result != KernelResult.Success) + { + Logger.PrintError(LogClass.Loader, $"Process initialization returned error \"{Result}\"."); + + return false; + } + + Result = LoadIntoMemory(Process, Kip, CodeBaseAddress); + + if (Result != KernelResult.Success) + { + Logger.PrintError(LogClass.Loader, $"Process initialization returned error \"{Result}\"."); + + return false; + } + + Result = Process.Start(Kip.MainThreadPriority, (ulong)Kip.MainThreadStackSize); + + if (Result != KernelResult.Success) + { + Logger.PrintError(LogClass.Loader, $"Process start returned error \"{Result}\"."); + + return false; + } + + System.Processes.Add(Process.Pid, Process); + + return true; + } + + public static bool LoadStaticObjects( + Horizon System, + Npdm MetaData, + IExecutable[] StaticObjects, + byte[] Arguments = null) + { + ulong ArgsStart = 0; + int ArgsSize = 0; + ulong CodeStart = 0x8000000; + int CodeSize = 0; + + ulong[] NsoBase = new ulong[StaticObjects.Length]; + + for (int Index = 0; Index < StaticObjects.Length; Index++) + { + IExecutable StaticObject = StaticObjects[Index]; + + int TextEnd = StaticObject.TextOffset + StaticObject.Text.Length; + int ROEnd = StaticObject.ROOffset + StaticObject.RO.Length; + int DataEnd = StaticObject.DataOffset + StaticObject.Data.Length + StaticObject.BssSize; + + int NsoSize = TextEnd; + + if ((uint)NsoSize < (uint)ROEnd) + { + NsoSize = ROEnd; + } + + if ((uint)NsoSize < (uint)DataEnd) + { + NsoSize = DataEnd; + } + + NsoSize = BitUtils.AlignUp(NsoSize, KMemoryManager.PageSize); + + NsoBase[Index] = CodeStart + (ulong)CodeSize; + + CodeSize += NsoSize; + + if (Arguments != null && ArgsSize == 0) + { + ArgsStart = (ulong)CodeSize; + + ArgsSize = BitUtils.AlignDown(Arguments.Length * 2 + ArgsTotalSize - 1, KMemoryManager.PageSize); + + CodeSize += ArgsSize; + } + } + + int CodePagesCount = CodeSize / KMemoryManager.PageSize; + + int PersonalMmHeapPagesCount = MetaData.PersonalMmHeapSize / KMemoryManager.PageSize; + + ProcessCreationInfo CreationInfo = new ProcessCreationInfo( + MetaData.TitleName, + MetaData.ProcessCategory, + MetaData.ACI0.TitleId, + CodeStart, + CodePagesCount, + MetaData.MmuFlags, + 0, + PersonalMmHeapPagesCount); + + KernelResult Result; + + KResourceLimit ResourceLimit = new KResourceLimit(System); + + long ApplicationRgSize = (long)System.MemoryRegions[(int)MemoryRegion.Application].Size; + + Result = ResourceLimit.SetLimitValue(LimitableResource.Memory, ApplicationRgSize); + Result |= ResourceLimit.SetLimitValue(LimitableResource.Thread, 608); + Result |= ResourceLimit.SetLimitValue(LimitableResource.Event, 700); + Result |= ResourceLimit.SetLimitValue(LimitableResource.TransferMemory, 128); + Result |= ResourceLimit.SetLimitValue(LimitableResource.Session, 894); + + if (Result != KernelResult.Success) + { + Logger.PrintError(LogClass.Loader, $"Process initialization failed setting resource limit values."); + + return false; + } + + KProcess Process = new KProcess(System); + + Result = Process.Initialize( + CreationInfo, + MetaData.ACI0.KernelAccessControl.Capabilities, + ResourceLimit, + MemoryRegion.Application); + + if (Result != KernelResult.Success) + { + Logger.PrintError(LogClass.Loader, $"Process initialization returned error \"{Result}\"."); + + return false; + } + + for (int Index = 0; Index < StaticObjects.Length; Index++) + { + Logger.PrintInfo(LogClass.Loader, $"Loading image {Index} at 0x{NsoBase[Index]:x16}..."); + + Result = LoadIntoMemory(Process, StaticObjects[Index], NsoBase[Index]); + + if (Result != KernelResult.Success) + { + Logger.PrintError(LogClass.Loader, $"Process initialization returned error \"{Result}\"."); + + return false; + } + } + + Result = Process.Start(MetaData.MainThreadPriority, (ulong)MetaData.MainThreadStackSize); + + if (Result != KernelResult.Success) + { + Logger.PrintError(LogClass.Loader, $"Process start returned error \"{Result}\"."); + + return false; + } + + System.Processes.Add(Process.Pid, Process); + + return true; + } + + private static KernelResult LoadIntoMemory(KProcess Process, IExecutable Image, ulong BaseAddress) + { + ulong TextStart = BaseAddress + (ulong)Image.TextOffset; + ulong ROStart = BaseAddress + (ulong)Image.ROOffset; + ulong DataStart = BaseAddress + (ulong)Image.DataOffset; + ulong BssStart = BaseAddress + (ulong)Image.BssOffset; + + ulong End = DataStart + (ulong)Image.Data.Length; + + if (Image.BssSize != 0) + { + End = BssStart + (ulong)Image.BssSize; + } + + Process.CpuMemory.WriteBytes((long)TextStart, Image.Text); + Process.CpuMemory.WriteBytes((long)ROStart, Image.RO); + Process.CpuMemory.WriteBytes((long)DataStart, Image.Data); + + MemoryHelper.FillWithZeros(Process.CpuMemory, (long)BssStart, Image.BssSize); + + KernelResult SetProcessMemoryPermission(ulong Address, ulong Size, MemoryPermission Permission) + { + if (Size == 0) + { + return KernelResult.Success; + } + + Size = BitUtils.AlignUp(Size, KMemoryManager.PageSize); + + return Process.MemoryManager.SetProcessMemoryPermission(Address, Size, Permission); + } + + KernelResult Result = SetProcessMemoryPermission(TextStart, (ulong)Image.Text.Length, MemoryPermission.ReadAndExecute); + + if (Result != KernelResult.Success) + { + return Result; + } + + Result = SetProcessMemoryPermission(ROStart, (ulong)Image.RO.Length, MemoryPermission.Read); + + if (Result != KernelResult.Success) + { + return Result; + } + + return SetProcessMemoryPermission(DataStart, End - DataStart, MemoryPermission.ReadAndWrite); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/ServiceCtx.cs b/Ryujinx.HLE/HOS/ServiceCtx.cs index a591673e2c..76c426bcab 100644 --- a/Ryujinx.HLE/HOS/ServiceCtx.cs +++ b/Ryujinx.HLE/HOS/ServiceCtx.cs @@ -8,7 +8,7 @@ namespace Ryujinx.HLE.HOS class ServiceCtx { public Switch Device { get; private set; } - public Process Process { get; private set; } + public KProcess Process { get; private set; } public MemoryManager Memory { get; private set; } public KSession Session { get; private set; } public IpcMessage Request { get; private set; } @@ -18,7 +18,7 @@ namespace Ryujinx.HLE.HOS public ServiceCtx( Switch Device, - Process Process, + KProcess Process, MemoryManager Memory, KSession Session, IpcMessage Request, diff --git a/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs b/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs index 6b012689c4..2feaf8fc02 100644 --- a/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs +++ b/Ryujinx.HLE/HOS/Services/Am/ICommonStateGetter.cs @@ -35,7 +35,7 @@ namespace Ryujinx.HLE.HOS.Services.Am public long GetEventHandle(ServiceCtx Context) { - KEvent Event = Context.Process.AppletState.MessageEvent; + KEvent Event = Context.Device.System.AppletState.MessageEvent; if (Context.Process.HandleTable.GenerateHandle(Event.ReadableEvent, out int Handle) != KernelResult.Success) { @@ -49,7 +49,7 @@ namespace Ryujinx.HLE.HOS.Services.Am public long ReceiveMessage(ServiceCtx Context) { - if (!Context.Process.AppletState.TryDequeueMessage(out MessageInfo Message)) + if (!Context.Device.System.AppletState.TryDequeueMessage(out MessageInfo Message)) { return MakeError(ErrorModule.Am, AmErr.NoMessages); } @@ -92,7 +92,7 @@ namespace Ryujinx.HLE.HOS.Services.Am public long GetCurrentFocusState(ServiceCtx Context) { - Context.ResponseData.Write((byte)Context.Process.AppletState.FocusState); + Context.ResponseData.Write((byte)Context.Device.System.AppletState.FocusState); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Ldr/IRoInterface.cs b/Ryujinx.HLE/HOS/Services/Ldr/IRoInterface.cs index 4d595fde27..f0899bd402 100644 --- a/Ryujinx.HLE/HOS/Services/Ldr/IRoInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Ldr/IRoInterface.cs @@ -1,4 +1,7 @@ -using Ryujinx.HLE.HOS.Ipc; +using ChocolArm64.Memory; +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Utilities; using System; @@ -62,17 +65,32 @@ namespace Ryujinx.HLE.HOS.Services.Ldr class NroInfo { - public Nro Executable { get; private set; } - public byte[] Hash { get; private set; } - public long NroAddress { get; private set; } - public long TotalSize { get; private set; } - public long NroMappedAddress { get; set; } + public NxRelocatableObject Executable { get; private set; } - public NroInfo(Nro Executable, byte[] Hash, long TotalSize) + public byte[] Hash { get; private set; } + public ulong NroAddress { get; private set; } + public ulong NroSize { get; private set; } + public ulong BssAddress { get; private set; } + public ulong BssSize { get; private set; } + public ulong TotalSize { get; private set; } + public ulong NroMappedAddress { get; set; } + + public NroInfo( + NxRelocatableObject Executable, + byte[] Hash, + ulong NroAddress, + ulong NroSize, + ulong BssAddress, + ulong BssSize, + ulong TotalSize) { this.Executable = Executable; this.Hash = Hash; - this.TotalSize = TotalSize; + this.NroAddress = NroAddress; + this.NroSize = NroSize; + this.BssAddress = BssAddress; + this.BssSize = BssSize; + this.TotalSize = TotalSize; } } @@ -174,7 +192,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldr return false; } - public long ParseNro(out NroInfo Res, ServiceCtx Context, long NroHeapAddress, long NroSize, long BssHeapAddress, long BssSize) + public long ParseNro(out NroInfo Res, ServiceCtx Context, ulong NroAddress, ulong NroSize, ulong BssAddress, ulong BssSize) { Res = null; @@ -182,28 +200,28 @@ namespace Ryujinx.HLE.HOS.Services.Ldr { return MakeError(ErrorModule.Loader, LoaderErr.MaxNro); } - else if (NroSize == 0 || NroHeapAddress + NroSize <= NroHeapAddress || (NroSize & 0xFFF) != 0) + else if (NroSize == 0 || NroAddress + NroSize <= NroAddress || (NroSize & 0xFFF) != 0) { return MakeError(ErrorModule.Loader, LoaderErr.BadSize); } - else if (BssSize != 0 && (BssHeapAddress + BssSize) <= BssHeapAddress) + else if (BssSize != 0 && BssAddress + BssSize <= BssAddress) { return MakeError(ErrorModule.Loader, LoaderErr.BadSize); } - else if ((NroHeapAddress & 0xFFF) != 0) + else if ((NroAddress & 0xFFF) != 0) { return MakeError(ErrorModule.Loader, LoaderErr.UnalignedAddress); } - uint Magic = Context.Memory.ReadUInt32(NroHeapAddress + 0x10); - uint NroFileSize = Context.Memory.ReadUInt32(NroHeapAddress + 0x18); + uint Magic = Context.Memory.ReadUInt32((long)NroAddress + 0x10); + uint NroFileSize = Context.Memory.ReadUInt32((long)NroAddress + 0x18); if (Magic != NroMagic || NroSize != NroFileSize) { return MakeError(ErrorModule.Loader, LoaderErr.InvalidNro); } - byte[] NroData = Context.Memory.ReadBytes(NroHeapAddress, NroSize); + byte[] NroData = Context.Memory.ReadBytes((long)NroAddress, (long)NroSize); byte[] NroHash = null; MemoryStream Stream = new MemoryStream(NroData); @@ -225,67 +243,106 @@ namespace Ryujinx.HLE.HOS.Services.Ldr Stream.Position = 0; - Nro Executable = new Nro(Stream, "memory", NroHeapAddress, BssHeapAddress); + NxRelocatableObject Executable = new NxRelocatableObject(Stream, NroAddress, BssAddress); // check if everything is page align. - if ((Executable.Text.Length & 0xFFF) != 0 || (Executable.RO.Length & 0xFFF) != 0 - || (Executable.Data.Length & 0xFFF) != 0 || (Executable.BssSize & 0xFFF) != 0) + if ((Executable.Text.Length & 0xFFF) != 0 || (Executable.RO.Length & 0xFFF) != 0 || + (Executable.Data.Length & 0xFFF) != 0 || (Executable.BssSize & 0xFFF) != 0) { return MakeError(ErrorModule.Loader, LoaderErr.InvalidNro); } // check if everything is contiguous. - if (Executable.ROOffset != Executable.TextOffset + Executable.Text.Length - || Executable.DataOffset != Executable.ROOffset + Executable.RO.Length - || NroFileSize != Executable.DataOffset + Executable.Data.Length) + if (Executable.ROOffset != Executable.TextOffset + Executable.Text.Length || + Executable.DataOffset != Executable.ROOffset + Executable.RO.Length || + NroFileSize != Executable.DataOffset + Executable.Data.Length) { return MakeError(ErrorModule.Loader, LoaderErr.InvalidNro); } // finally check the bss size match. - if (Executable.BssSize != BssSize) + if ((ulong)Executable.BssSize != BssSize) { return MakeError(ErrorModule.Loader, LoaderErr.InvalidNro); } - Res = new NroInfo(Executable, NroHash, Executable.Text.Length + Executable.RO.Length + Executable.Data.Length + Executable.BssSize); + int TotalSize = Executable.Text.Length + Executable.RO.Length + Executable.Data.Length + Executable.BssSize; + + Res = new NroInfo( + Executable, + NroHash, + NroAddress, + NroSize, + BssAddress, + BssSize, + (ulong)TotalSize); return 0; } - private long MapNro(ServiceCtx Context, NroInfo Info, out long NroMappedAddress) + private long MapNro(ServiceCtx Context, NroInfo Info, out ulong NroMappedAddress) { NroMappedAddress = 0; - long TargetAddress = Context.Process.MemoryManager.AddrSpaceStart; - long HeapRegionStart = Context.Process.MemoryManager.HeapRegionStart; - long HeapRegionEnd = Context.Process.MemoryManager.HeapRegionEnd; + KMemoryManager MemMgr = Context.Process.MemoryManager; - long MapRegionStart = Context.Process.MemoryManager.MapRegionStart; - long MapRegionEnd = Context.Process.MemoryManager.MapRegionEnd; + ulong TargetAddress = MemMgr.GetAddrSpaceBaseAddr(); while (true) { - if (TargetAddress + Info.TotalSize >= Context.Process.MemoryManager.AddrSpaceEnd) + if (TargetAddress + Info.TotalSize >= MemMgr.AddrSpaceEnd) { return MakeError(ErrorModule.Loader, LoaderErr.InvalidMemoryState); } - bool IsValidAddress = !(HeapRegionStart > 0 && HeapRegionStart <= TargetAddress + Info.TotalSize - 1 - && TargetAddress <= HeapRegionEnd - 1) - && !(MapRegionStart > 0 - && MapRegionStart <= TargetAddress + Info.TotalSize - 1 - && TargetAddress <= MapRegionEnd - 1); + KMemoryInfo MemInfo = MemMgr.QueryMemory(TargetAddress); - if (IsValidAddress && Context.Process.MemoryManager.HleIsUnmapped(TargetAddress, Info.TotalSize)) + if (MemInfo.State == MemoryState.Unmapped && MemInfo.Size >= Info.TotalSize) { - break; + if (!MemMgr.InsideHeapRegion (TargetAddress, Info.TotalSize) && + !MemMgr.InsideAliasRegion(TargetAddress, Info.TotalSize)) + { + break; + } } - TargetAddress += 0x1000; + TargetAddress += MemInfo.Size; } - Context.Process.LoadProgram(Info.Executable, TargetAddress); + KernelResult Result = MemMgr.MapProcessCodeMemory(TargetAddress, Info.NroAddress, Info.NroSize); + + if (Result != KernelResult.Success) + { + return MakeError(ErrorModule.Loader, LoaderErr.InvalidMemoryState); + } + + ulong BssTargetAddress = TargetAddress + Info.NroSize; + + if (Info.BssSize != 0) + { + Result = MemMgr.MapProcessCodeMemory(BssTargetAddress, Info.BssAddress, Info.BssSize); + + if (Result != KernelResult.Success) + { + MemMgr.UnmapProcessCodeMemory(TargetAddress, Info.NroAddress, Info.NroSize); + + return MakeError(ErrorModule.Loader, LoaderErr.InvalidMemoryState); + } + } + + Result = LoadNroIntoMemory(Context.Process, Info.Executable, TargetAddress); + + if (Result != KernelResult.Success) + { + MemMgr.UnmapProcessCodeMemory(TargetAddress, Info.NroAddress, Info.NroSize); + + if (Info.BssSize != 0) + { + MemMgr.UnmapProcessCodeMemory(BssTargetAddress, Info.BssAddress, Info.BssSize); + } + + return 0; + } Info.NroMappedAddress = TargetAddress; NroMappedAddress = TargetAddress; @@ -293,6 +350,41 @@ namespace Ryujinx.HLE.HOS.Services.Ldr return 0; } + private KernelResult LoadNroIntoMemory(KProcess Process, IExecutable RelocatableObject, ulong BaseAddress) + { + ulong TextStart = BaseAddress + (ulong)RelocatableObject.TextOffset; + ulong ROStart = BaseAddress + (ulong)RelocatableObject.ROOffset; + ulong DataStart = BaseAddress + (ulong)RelocatableObject.DataOffset; + + ulong BssStart = DataStart + (ulong)RelocatableObject.Data.Length; + + ulong BssEnd = BitUtils.AlignUp(BssStart + (ulong)RelocatableObject.BssSize, KMemoryManager.PageSize); + + Process.CpuMemory.WriteBytes((long)TextStart, RelocatableObject.Text); + Process.CpuMemory.WriteBytes((long)ROStart, RelocatableObject.RO); + Process.CpuMemory.WriteBytes((long)DataStart, RelocatableObject.Data); + + MemoryHelper.FillWithZeros(Process.CpuMemory, (long)BssStart, (int)(BssEnd - BssStart)); + + KernelResult Result; + + Result = Process.MemoryManager.SetProcessMemoryPermission(TextStart, ROStart - TextStart, MemoryPermission.ReadAndExecute); + + if (Result != KernelResult.Success) + { + return Result; + } + + Result = Process.MemoryManager.SetProcessMemoryPermission(ROStart, DataStart - ROStart, MemoryPermission.Read); + + if (Result != KernelResult.Success) + { + return Result; + } + + return Process.MemoryManager.SetProcessMemoryPermission(DataStart, BssEnd - DataStart, MemoryPermission.ReadAndWrite); + } + private long RemoveNrrInfo(long NrrAddress) { foreach (NrrInfo Info in NrrInfos) @@ -308,24 +400,46 @@ namespace Ryujinx.HLE.HOS.Services.Ldr return MakeError(ErrorModule.Loader, LoaderErr.BadNrrAddress); } - private long RemoveNroInfo(ServiceCtx Context, long NroMappedAddress, long NroHeapAddress) + private long RemoveNroInfo(ServiceCtx Context, ulong NroMappedAddress) { foreach (NroInfo Info in NroInfos) { - if (Info.NroMappedAddress == NroMappedAddress && Info.Executable.SourceAddress == NroHeapAddress) + if (Info.NroMappedAddress == NroMappedAddress) { NroInfos.Remove(Info); - Context.Process.RemoveProgram(Info.NroMappedAddress); + ulong TextSize = (ulong)Info.Executable.Text.Length; + ulong ROSize = (ulong)Info.Executable.RO.Length; + ulong DataSize = (ulong)Info.Executable.Data.Length; + ulong BssSize = (ulong)Info.Executable.BssSize; - long Result = Context.Process.MemoryManager.UnmapProcessCodeMemory(Info.NroMappedAddress, Info.Executable.SourceAddress, Info.TotalSize - Info.Executable.BssSize); + KernelResult Result = KernelResult.Success; - if (Result == 0 && Info.Executable.BssSize != 0) + if (Info.Executable.BssSize != 0) { - Result = Context.Process.MemoryManager.UnmapProcessCodeMemory(Info.NroMappedAddress + Info.TotalSize - Info.Executable.BssSize, Info.Executable.BssAddress, Info.Executable.BssSize); + Result = Context.Process.MemoryManager.UnmapProcessCodeMemory( + Info.NroMappedAddress + TextSize + ROSize + DataSize, + Info.Executable.BssAddress, + BssSize); } - return Result; + if (Result == KernelResult.Success) + { + Result = Context.Process.MemoryManager.UnmapProcessCodeMemory( + Info.NroMappedAddress + TextSize + ROSize, + Info.Executable.SourceAddress + TextSize + ROSize, + DataSize); + + if (Result == KernelResult.Success) + { + Result = Context.Process.MemoryManager.UnmapProcessCodeMemory( + Info.NroMappedAddress, + Info.Executable.SourceAddress, + TextSize + ROSize); + } + } + + return (long)Result; } } @@ -340,12 +454,12 @@ namespace Ryujinx.HLE.HOS.Services.Ldr // Zero Context.RequestData.ReadUInt64(); - long NroHeapAddress = Context.RequestData.ReadInt64(); - long NroSize = Context.RequestData.ReadInt64(); - long BssHeapAddress = Context.RequestData.ReadInt64(); - long BssSize = Context.RequestData.ReadInt64(); + ulong NroHeapAddress = Context.RequestData.ReadUInt64(); + ulong NroSize = Context.RequestData.ReadUInt64(); + ulong BssHeapAddress = Context.RequestData.ReadUInt64(); + ulong BssSize = Context.RequestData.ReadUInt64(); - long NroMappedAddress = 0; + ulong NroMappedAddress = 0; if (IsInitialized) { @@ -374,17 +488,19 @@ namespace Ryujinx.HLE.HOS.Services.Ldr { long Result = MakeError(ErrorModule.Loader, LoaderErr.BadInitialization); - long NroMappedAddress = Context.RequestData.ReadInt64(); - long NroHeapAddress = Context.RequestData.ReadInt64(); + // Zero + Context.RequestData.ReadUInt64(); + + ulong NroMappedAddress = Context.RequestData.ReadUInt64(); if (IsInitialized) { - if ((NroMappedAddress & 0xFFF) != 0 || (NroHeapAddress & 0xFFF) != 0) + if ((NroMappedAddress & 0xFFF) != 0) { return MakeError(ErrorModule.Loader, LoaderErr.UnalignedAddress); } - Result = RemoveNroInfo(Context, NroMappedAddress, NroHeapAddress); + Result = RemoveNroInfo(Context, NroMappedAddress); } return Result; diff --git a/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs b/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs index b8ae11cea1..6786d0e2c8 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs @@ -205,7 +205,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv return ((Cmd >> 31) & 1) != 0; } - public static void UnloadProcess(Process Process) + public static void UnloadProcess(KProcess Process) { Fds.DeleteProcess(Process); diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs index fed4104229..7fe3bbedb0 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvGpuAS/NvGpuASIoctl.cs @@ -1,6 +1,7 @@ using ChocolArm64.Memory; using Ryujinx.Common.Logging; using Ryujinx.Graphics.Memory; +using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Services.Nv.NvMap; using System; using System.Collections.Concurrent; @@ -13,11 +14,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS private const int FlagRemapSubRange = 0x100; - private static ConcurrentDictionary ASCtxs; + private static ConcurrentDictionary ASCtxs; static NvGpuASIoctl() { - ASCtxs = new ConcurrentDictionary(); + ASCtxs = new ConcurrentDictionary(); } public static int ProcessIoctl(ServiceCtx Context, int Cmd) @@ -321,7 +322,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvGpuAS return ASCtxs.GetOrAdd(Context.Process, (Key) => new NvGpuASCtx(Context)); } - public static void UnloadProcess(Process Process) + public static void UnloadProcess(KProcess Process) { ASCtxs.TryRemove(Process, out _); } diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs index 39f39d4569..d9f5602b4b 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs @@ -1,6 +1,7 @@ using ChocolArm64.Memory; using Ryujinx.Common.Logging; using Ryujinx.Graphics.Memory; +using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Services.Nv.NvGpuAS; using System; using System.Collections.Concurrent; @@ -21,11 +22,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostChannel } } - private static ConcurrentDictionary Channels; + private static ConcurrentDictionary Channels; static NvHostChannelIoctl() { - Channels = new ConcurrentDictionary(); + Channels = new ConcurrentDictionary(); } public static int ProcessIoctlGpu(ServiceCtx Context, int Cmd) @@ -194,7 +195,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostChannel return Cpp.Channels[Channel]; } - public static void UnloadProcess(Process Process) + public static void UnloadProcess(KProcess Process) { Channels.TryRemove(Process, out _); } diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs index 6cb1474105..bf92afb40d 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs @@ -1,5 +1,6 @@ using ChocolArm64.Memory; using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel; using System; using System.Collections.Concurrent; using System.Text; @@ -9,13 +10,13 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl { class NvHostCtrlIoctl { - private static ConcurrentDictionary UserCtxs; + private static ConcurrentDictionary UserCtxs; private static bool IsProductionMode = true; static NvHostCtrlIoctl() { - UserCtxs = new ConcurrentDictionary(); + UserCtxs = new ConcurrentDictionary(); if (Set.NxSettings.Settings.TryGetValue("nv!rmos_set_production_mode", out object ProductionModeSetting)) { @@ -390,7 +391,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvHostCtrl return UserCtxs.GetOrAdd(Context.Process, (Key) => new NvHostCtrlUserCtx()); } - public static void UnloadProcess(Process Process) + public static void UnloadProcess(KProcess Process) { UserCtxs.TryRemove(Process, out _); } diff --git a/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs index f5378ef7fb..adc523e501 100644 --- a/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs +++ b/Ryujinx.HLE/HOS/Services/Nv/NvMap/NvMapIoctl.cs @@ -1,6 +1,7 @@ using ChocolArm64.Memory; using Ryujinx.Common.Logging; using Ryujinx.Graphics.Memory; +using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.Utilities; using System.Collections.Concurrent; @@ -10,11 +11,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvMap { private const int FlagNotFreedYet = 1; - private static ConcurrentDictionary Maps; + private static ConcurrentDictionary Maps; static NvMapIoctl() { - Maps = new ConcurrentDictionary(); + Maps = new ConcurrentDictionary(); } public static int ProcessIoctl(ServiceCtx Context, int Cmd) @@ -130,10 +131,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvMap //When the address is zero, we need to allocate //our own backing memory for the NvMap. //TODO: Is this allocation inside the transfer memory? - if (!Context.Device.Memory.Allocator.TryAllocate((uint)Size, out Address)) - { - Result = NvResult.OutOfMemory; - } + Result = NvResult.OutOfMemory; } if (Result == NvResult.Success) @@ -294,7 +292,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvMap return null; } - public static void UnloadProcess(Process Process) + public static void UnloadProcess(KProcess Process) { Maps.TryRemove(Process, out _); } diff --git a/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs b/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs index 64e0b4a96d..facfbe60b9 100644 --- a/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs +++ b/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs @@ -275,8 +275,7 @@ namespace Ryujinx.HLE.HOS.Services.Android private long MakeReplyParcel(ServiceCtx Context, byte[] Data) { - long ReplyPos = Context.Request.ReceiveBuff[0].Position; - long ReplySize = Context.Request.ReceiveBuff[0].Size; + (long ReplyPos, long ReplySize) = Context.Request.GetBufferType0x22(); byte[] Reply = MakeParcel(Data, new byte[0]); diff --git a/Ryujinx.HLE/Homebrew.npdm b/Ryujinx.HLE/Homebrew.npdm new file mode 100644 index 0000000000..8141161465 Binary files /dev/null and b/Ryujinx.HLE/Homebrew.npdm differ diff --git a/Ryujinx.HLE/Loaders/Compression/BackwardsLz.cs b/Ryujinx.HLE/Loaders/Compression/BackwardsLz.cs new file mode 100644 index 0000000000..43cc601fdf --- /dev/null +++ b/Ryujinx.HLE/Loaders/Compression/BackwardsLz.cs @@ -0,0 +1,105 @@ +using System; +using System.IO; + +namespace Ryujinx.HLE.Loaders.Compression +{ + static class BackwardsLz + { + private class BackwardsReader + { + private Stream BaseStream; + + public BackwardsReader(Stream BaseStream) + { + this.BaseStream = BaseStream; + } + + public byte ReadByte() + { + BaseStream.Seek(-1, SeekOrigin.Current); + + byte Value = (byte)BaseStream.ReadByte(); + + BaseStream.Seek(-1, SeekOrigin.Current); + + return Value; + } + + public short ReadInt16() + { + return (short)((ReadByte() << 8) | (ReadByte() << 0)); + } + + public int ReadInt32() + { + return ((ReadByte() << 24) | + (ReadByte() << 16) | + (ReadByte() << 8) | + (ReadByte() << 0)); + } + } + + public static byte[] Decompress(Stream Input, int DecompressedLength) + { + long End = Input.Position; + + BackwardsReader Reader = new BackwardsReader(Input); + + int AdditionalDecLength = Reader.ReadInt32(); + int StartOffset = Reader.ReadInt32(); + int CompressedLength = Reader.ReadInt32(); + + Input.Seek(12 - StartOffset, SeekOrigin.Current); + + byte[] Dec = new byte[DecompressedLength]; + + int DecompressedLengthUnpadded = CompressedLength + AdditionalDecLength; + + int DecompressionStart = DecompressedLength - DecompressedLengthUnpadded; + + int DecPos = Dec.Length; + + byte Mask = 0; + byte Header = 0; + + while (DecPos > DecompressionStart) + { + if ((Mask >>= 1) == 0) + { + Header = Reader.ReadByte(); + Mask = 0x80; + } + + if ((Header & Mask) == 0) + { + Dec[--DecPos] = Reader.ReadByte(); + } + else + { + ushort Pair = (ushort)Reader.ReadInt16(); + + int Length = (Pair >> 12) + 3; + int Position = (Pair & 0xfff) + 3; + + DecPos -= Length; + + if (Length <= Position) + { + int SrcPos = DecPos + Position; + + Buffer.BlockCopy(Dec, SrcPos, Dec, DecPos, Length); + } + else + { + for (int Offset = 0; Offset < Length; Offset++) + { + Dec[DecPos + Offset] = Dec[DecPos + Position + Offset]; + } + } + } + } + + return Dec; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Elf/ElfDynamic.cs b/Ryujinx.HLE/Loaders/Elf/ElfDynamic.cs new file mode 100644 index 0000000000..fb0ea53e09 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Elf/ElfDynamic.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + struct ElfDynamic + { + public ElfDynamicTag Tag { get; private set; } + + public long Value { get; private set; } + + public ElfDynamic(ElfDynamicTag Tag, long Value) + { + this.Tag = Tag; + this.Value = Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfDynTag.cs b/Ryujinx.HLE/Loaders/Elf/ElfDynamicTag.cs similarity index 97% rename from Ryujinx.HLE/Loaders/ElfDynTag.cs rename to Ryujinx.HLE/Loaders/Elf/ElfDynamicTag.cs index 5915d4d123..9d7ad72e09 100644 --- a/Ryujinx.HLE/Loaders/ElfDynTag.cs +++ b/Ryujinx.HLE/Loaders/Elf/ElfDynamicTag.cs @@ -1,6 +1,6 @@ -namespace Ryujinx.HLE.Loaders +namespace Ryujinx.HLE.Loaders.Elf { - enum ElfDynTag + enum ElfDynamicTag { DT_NULL = 0, DT_NEEDED = 1, diff --git a/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs b/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs new file mode 100644 index 0000000000..3f3a2a7909 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs @@ -0,0 +1,40 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + struct ElfSymbol + { + public string Name { get; private set; } + + public ElfSymbolType Type { get; private set; } + public ElfSymbolBinding Binding { get; private set; } + public ElfSymbolVisibility Visibility { get; private set; } + + public bool IsFuncOrObject => + Type == ElfSymbolType.STT_FUNC || + Type == ElfSymbolType.STT_OBJECT; + + public bool IsGlobalOrWeak => + Binding == ElfSymbolBinding.STB_GLOBAL || + Binding == ElfSymbolBinding.STB_WEAK; + + public int SHIdx { get; private set; } + public long Value { get; private set; } + public long Size { get; private set; } + + public ElfSymbol( + string Name, + int Info, + int Other, + int SHIdx, + long Value, + long Size) + { + this.Name = Name; + this.Type = (ElfSymbolType)(Info & 0xf); + this.Binding = (ElfSymbolBinding)(Info >> 4); + this.Visibility = (ElfSymbolVisibility)Other; + this.SHIdx = SHIdx; + this.Value = Value; + this.Size = Size; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfSymBinding.cs b/Ryujinx.HLE/Loaders/Elf/ElfSymbolBinding.cs similarity index 58% rename from Ryujinx.HLE/Loaders/ElfSymBinding.cs rename to Ryujinx.HLE/Loaders/Elf/ElfSymbolBinding.cs index f1d249ec24..3c915311eb 100644 --- a/Ryujinx.HLE/Loaders/ElfSymBinding.cs +++ b/Ryujinx.HLE/Loaders/Elf/ElfSymbolBinding.cs @@ -1,6 +1,6 @@ -namespace Ryujinx.HLE.Loaders +namespace Ryujinx.HLE.Loaders.Elf { - enum ElfSymBinding + enum ElfSymbolBinding { STB_LOCAL = 0, STB_GLOBAL = 1, diff --git a/Ryujinx.HLE/Loaders/ElfSymType.cs b/Ryujinx.HLE/Loaders/Elf/ElfSymbolType.cs similarity index 76% rename from Ryujinx.HLE/Loaders/ElfSymType.cs rename to Ryujinx.HLE/Loaders/Elf/ElfSymbolType.cs index 478064bc4b..f22e6c45fc 100644 --- a/Ryujinx.HLE/Loaders/ElfSymType.cs +++ b/Ryujinx.HLE/Loaders/Elf/ElfSymbolType.cs @@ -1,6 +1,6 @@ -namespace Ryujinx.HLE.Loaders +namespace Ryujinx.HLE.Loaders.Elf { - enum ElfSymType + enum ElfSymbolType { STT_NOTYPE = 0, STT_OBJECT = 1, diff --git a/Ryujinx.HLE/Loaders/ElfSymVisibility.cs b/Ryujinx.HLE/Loaders/Elf/ElfSymbolVisibility.cs similarity index 65% rename from Ryujinx.HLE/Loaders/ElfSymVisibility.cs rename to Ryujinx.HLE/Loaders/Elf/ElfSymbolVisibility.cs index fe7243a7ae..4bec50a37f 100644 --- a/Ryujinx.HLE/Loaders/ElfSymVisibility.cs +++ b/Ryujinx.HLE/Loaders/Elf/ElfSymbolVisibility.cs @@ -1,6 +1,6 @@ -namespace Ryujinx.HLE.Loaders +namespace Ryujinx.HLE.Loaders.Elf { - enum ElfSymVisibility + enum ElfSymbolVisibility { STV_DEFAULT = 0, STV_INTERNAL = 1, diff --git a/Ryujinx.HLE/Loaders/ElfDyn.cs b/Ryujinx.HLE/Loaders/ElfDyn.cs deleted file mode 100644 index 3508e6e4e0..0000000000 --- a/Ryujinx.HLE/Loaders/ElfDyn.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Ryujinx.HLE.Loaders -{ - struct ElfDyn - { - public ElfDynTag Tag { get; private set; } - - public long Value { get; private set; } - - public ElfDyn(ElfDynTag Tag, long Value) - { - this.Tag = Tag; - this.Value = Value; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfRel.cs b/Ryujinx.HLE/Loaders/ElfRel.cs deleted file mode 100644 index cfc31d8910..0000000000 --- a/Ryujinx.HLE/Loaders/ElfRel.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Ryujinx.HLE.Loaders -{ - struct ElfRel - { - public long Offset { get; private set; } - public long Addend { get; private set; } - - public ElfSym Symbol { get; private set; } - public ElfRelType Type { get; private set; } - - public ElfRel(long Offset, long Addend, ElfSym Symbol, ElfRelType Type) - { - this.Offset = Offset; - this.Addend = Addend; - this.Symbol = Symbol; - this.Type = Type; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfRelType.cs b/Ryujinx.HLE/Loaders/ElfRelType.cs deleted file mode 100644 index 7da5eec363..0000000000 --- a/Ryujinx.HLE/Loaders/ElfRelType.cs +++ /dev/null @@ -1,128 +0,0 @@ -namespace Ryujinx.HLE.Loaders -{ - enum ElfRelType - { - R_AARCH64_NONE = 0, - R_AARCH64_ABS64 = 257, - R_AARCH64_ABS32 = 258, - R_AARCH64_ABS16 = 259, - R_AARCH64_PREL64 = 260, - R_AARCH64_PREL32 = 261, - R_AARCH64_PREL16 = 262, - R_AARCH64_MOVW_UABS_G0 = 263, - R_AARCH64_MOVW_UABS_G0_NC = 264, - R_AARCH64_MOVW_UABS_G1 = 265, - R_AARCH64_MOVW_UABS_G1_NC = 266, - R_AARCH64_MOVW_UABS_G2 = 267, - R_AARCH64_MOVW_UABS_G2_NC = 268, - R_AARCH64_MOVW_UABS_G3 = 269, - R_AARCH64_MOVW_SABS_G0 = 270, - R_AARCH64_MOVW_SABS_G1 = 271, - R_AARCH64_MOVW_SABS_G2 = 272, - R_AARCH64_LD_PREL_LO19 = 273, - R_AARCH64_ADR_PREL_LO21 = 274, - R_AARCH64_ADR_PREL_PG_HI21 = 275, - R_AARCH64_ADR_PREL_PG_HI21_NC = 276, - R_AARCH64_ADD_ABS_LO12_NC = 277, - R_AARCH64_LDST8_ABS_LO12_NC = 278, - R_AARCH64_TSTBR14 = 279, - R_AARCH64_CONDBR19 = 280, - R_AARCH64_JUMP26 = 282, - R_AARCH64_CALL26 = 283, - R_AARCH64_LDST16_ABS_LO12_NC = 284, - R_AARCH64_LDST32_ABS_LO12_NC = 285, - R_AARCH64_LDST64_ABS_LO12_NC = 286, - R_AARCH64_MOVW_PREL_G0 = 287, - R_AARCH64_MOVW_PREL_G0_NC = 288, - R_AARCH64_MOVW_PREL_G1 = 289, - R_AARCH64_MOVW_PREL_G1_NC = 290, - R_AARCH64_MOVW_PREL_G2 = 291, - R_AARCH64_MOVW_PREL_G2_NC = 292, - R_AARCH64_MOVW_PREL_G3 = 293, - R_AARCH64_LDST128_ABS_LO12_NC = 299, - R_AARCH64_MOVW_GOTOFF_G0 = 300, - R_AARCH64_MOVW_GOTOFF_G0_NC = 301, - R_AARCH64_MOVW_GOTOFF_G1 = 302, - R_AARCH64_MOVW_GOTOFF_G1_NC = 303, - R_AARCH64_MOVW_GOTOFF_G2 = 304, - R_AARCH64_MOVW_GOTOFF_G2_NC = 305, - R_AARCH64_MOVW_GOTOFF_G3 = 306, - R_AARCH64_GOTREL64 = 307, - R_AARCH64_GOTREL32 = 308, - R_AARCH64_GOT_LD_PREL19 = 309, - R_AARCH64_LD64_GOTOFF_LO15 = 310, - R_AARCH64_ADR_GOT_PAGE = 311, - R_AARCH64_LD64_GOT_LO12_NC = 312, - R_AARCH64_LD64_GOTPAGE_LO15 = 313, - R_AARCH64_TLSGD_ADR_PREL21 = 512, - R_AARCH64_TLSGD_ADR_PAGE21 = 513, - R_AARCH64_TLSGD_ADD_LO12_NC = 514, - R_AARCH64_TLSGD_MOVW_G1 = 515, - R_AARCH64_TLSGD_MOVW_G0_NC = 516, - R_AARCH64_TLSLD_ADR_PREL21 = 517, - R_AARCH64_TLSLD_ADR_PAGE21 = 518, - R_AARCH64_TLSLD_ADD_LO12_NC = 519, - R_AARCH64_TLSLD_MOVW_G1 = 520, - R_AARCH64_TLSLD_MOVW_G0_NC = 521, - R_AARCH64_TLSLD_LD_PREL19 = 522, - R_AARCH64_TLSLD_MOVW_DTPREL_G2 = 523, - R_AARCH64_TLSLD_MOVW_DTPREL_G1 = 524, - R_AARCH64_TLSLD_MOVW_DTPREL_G1_NC = 525, - R_AARCH64_TLSLD_MOVW_DTPREL_G0 = 526, - R_AARCH64_TLSLD_MOVW_DTPREL_G0_NC = 527, - R_AARCH64_TLSLD_ADD_DTPREL_HI12 = 528, - R_AARCH64_TLSLD_ADD_DTPREL_LO12 = 529, - R_AARCH64_TLSLD_ADD_DTPREL_LO12_NC = 530, - R_AARCH64_TLSLD_LDST8_DTPREL_LO12 = 531, - R_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC = 532, - R_AARCH64_TLSLD_LDST16_DTPREL_LO12 = 533, - R_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC = 534, - R_AARCH64_TLSLD_LDST32_DTPREL_LO12 = 535, - R_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC = 536, - R_AARCH64_TLSLD_LDST64_DTPREL_LO12 = 537, - R_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC = 538, - R_AARCH64_TLSIE_MOVW_GOTTPREL_G1 = 539, - R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC = 540, - R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21 = 541, - R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC = 542, - R_AARCH64_TLSIE_LD_GOTTPREL_PREL19 = 543, - R_AARCH64_TLSLE_MOVW_TPREL_G2 = 544, - R_AARCH64_TLSLE_MOVW_TPREL_G1 = 545, - R_AARCH64_TLSLE_MOVW_TPREL_G1_NC = 546, - R_AARCH64_TLSLE_MOVW_TPREL_G0 = 547, - R_AARCH64_TLSLE_MOVW_TPREL_G0_NC = 548, - R_AARCH64_TLSLE_ADD_TPREL_HI12 = 549, - R_AARCH64_TLSLE_ADD_TPREL_LO12 = 550, - R_AARCH64_TLSLE_ADD_TPREL_LO12_NC = 551, - R_AARCH64_TLSLE_LDST8_TPREL_LO12 = 552, - R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC = 553, - R_AARCH64_TLSLE_LDST16_TPREL_LO12 = 554, - R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC = 555, - R_AARCH64_TLSLE_LDST32_TPREL_LO12 = 556, - R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC = 557, - R_AARCH64_TLSLE_LDST64_TPREL_LO12 = 558, - R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC = 559, - R_AARCH64_TLSDESC_LD_PREL19 = 560, - R_AARCH64_TLSDESC_ADR_PREL21 = 561, - R_AARCH64_TLSDESC_ADR_PAGE21 = 562, - R_AARCH64_TLSDESC_LD64_LO12 = 563, - R_AARCH64_TLSDESC_ADD_LO12 = 564, - R_AARCH64_TLSDESC_OFF_G1 = 565, - R_AARCH64_TLSDESC_OFF_G0_NC = 566, - R_AARCH64_TLSDESC_LDR = 567, - R_AARCH64_TLSDESC_ADD = 568, - R_AARCH64_TLSDESC_CALL = 569, - R_AARCH64_TLSLE_LDST128_TPREL_LO12 = 570, - R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC = 571, - R_AARCH64_TLSLD_LDST128_DTPREL_LO12 = 572, - R_AARCH64_TLSLD_LDST128_DTPREL_LO12_NC = 573, - R_AARCH64_COPY = 1024, - R_AARCH64_GLOB_DAT = 1025, - R_AARCH64_JUMP_SLOT = 1026, - R_AARCH64_RELATIVE = 1027, - R_AARCH64_TLS_DTPMOD64 = 1028, - R_AARCH64_TLS_DTPREL64 = 1029, - R_AARCH64_TLS_TPREL64 = 1030, - R_AARCH64_TLSDESC = 1031 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/ElfSym.cs b/Ryujinx.HLE/Loaders/ElfSym.cs deleted file mode 100644 index 869938d357..0000000000 --- a/Ryujinx.HLE/Loaders/ElfSym.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Ryujinx.HLE.Loaders -{ - struct ElfSym - { - public string Name { get; private set; } - - public ElfSymType Type { get; private set; } - public ElfSymBinding Binding { get; private set; } - public ElfSymVisibility Visibility { get; private set; } - - public bool IsFuncOrObject => - Type == ElfSymType.STT_FUNC || - Type == ElfSymType.STT_OBJECT; - - public bool IsGlobalOrWeak => - Binding == ElfSymBinding.STB_GLOBAL || - Binding == ElfSymBinding.STB_WEAK; - - public int SHIdx { get; private set; } - public long Value { get; private set; } - public long Size { get; private set; } - - public ElfSym( - string Name, - int Info, - int Other, - int SHIdx, - long Value, - long Size) - { - this.Name = Name; - this.Type = (ElfSymType)(Info & 0xf); - this.Binding = (ElfSymBinding)(Info >> 4); - this.Visibility = (ElfSymVisibility)Other; - this.SHIdx = SHIdx; - this.Value = Value; - this.Size = Size; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Executable.cs b/Ryujinx.HLE/Loaders/Executable.cs deleted file mode 100644 index d4d79073ec..0000000000 --- a/Ryujinx.HLE/Loaders/Executable.cs +++ /dev/null @@ -1,205 +0,0 @@ -using ChocolArm64.Memory; -using Ryujinx.HLE.HOS; -using Ryujinx.HLE.HOS.Kernel; -using Ryujinx.HLE.Loaders.Executables; -using Ryujinx.HLE.Utilities; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; - -namespace Ryujinx.HLE.Loaders -{ - class Executable - { - private MemoryManager Memory; - - private List Dynamic; - - public ReadOnlyCollection SymbolTable; - - public string Name { get; private set; } - - public string FilePath { get; private set; } - - public long ImageBase { get; private set; } - public long ImageEnd { get; private set; } - - private KMemoryManager MemoryManager; - - public Executable(IExecutable Exe, KMemoryManager MemoryManager, MemoryManager Memory, long ImageBase) - { - Dynamic = new List(); - - FilePath = Exe.FilePath; - - if (FilePath != null) - { - Name = Path.GetFileNameWithoutExtension(FilePath.Replace(Homebrew.TemporaryNroSuffix, "")); - } - - this.Memory = Memory; - this.MemoryManager = MemoryManager; - this.ImageBase = ImageBase; - this.ImageEnd = ImageBase; - - long TextPosition = ImageBase + (uint)Exe.TextOffset; - long ROPosition = ImageBase + (uint)Exe.ROOffset; - long DataPosition = ImageBase + (uint)Exe.DataOffset; - - long TextSize = (uint)IntUtils.AlignUp(Exe.Text.Length, KMemoryManager.PageSize); - long ROSize = (uint)IntUtils.AlignUp(Exe.RO.Length, KMemoryManager.PageSize); - long DataSize = (uint)IntUtils.AlignUp(Exe.Data.Length, KMemoryManager.PageSize); - long BssSize = (uint)IntUtils.AlignUp(Exe.BssSize, KMemoryManager.PageSize); - - long DataAndBssSize = BssSize + DataSize; - - ImageEnd = DataPosition + DataAndBssSize; - - if (Exe.SourceAddress == 0) - { - MemoryManager.HleMapProcessCode(TextPosition, TextSize + ROSize + DataAndBssSize); - - MemoryManager.SetProcessMemoryPermission(ROPosition, ROSize, MemoryPermission.Read); - MemoryManager.SetProcessMemoryPermission(DataPosition, DataAndBssSize, MemoryPermission.ReadAndWrite); - - Memory.WriteBytes(TextPosition, Exe.Text); - Memory.WriteBytes(ROPosition, Exe.RO); - Memory.WriteBytes(DataPosition, Exe.Data); - } - else - { - long Result = MemoryManager.MapProcessCodeMemory(TextPosition, Exe.SourceAddress, TextSize + ROSize + DataSize); - - if (Result != 0) - { - throw new InvalidOperationException(); - } - - MemoryManager.SetProcessMemoryPermission(ROPosition, ROSize, MemoryPermission.Read); - MemoryManager.SetProcessMemoryPermission(DataPosition, DataSize, MemoryPermission.ReadAndWrite); - - if (Exe.BssAddress != 0 && Exe.BssSize != 0) - { - Result = MemoryManager.MapProcessCodeMemory(DataPosition + DataSize, Exe.BssAddress, BssSize); - - if (Result != 0) - { - throw new InvalidOperationException(); - } - - MemoryManager.SetProcessMemoryPermission(DataPosition + DataSize, BssSize, MemoryPermission.ReadAndWrite); - } - } - - if (Exe.Mod0Offset == 0) - { - return; - } - - long Mod0Offset = ImageBase + Exe.Mod0Offset; - - int Mod0Magic = Memory.ReadInt32(Mod0Offset + 0x0); - 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; - - ElfDynTag Tag = (ElfDynTag)TagVal; - - if (Tag == ElfDynTag.DT_NULL) - { - break; - } - - Dynamic.Add(new ElfDyn(Tag, Value)); - } - - long StrTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_STRTAB); - long SymTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_SYMTAB); - - long SymEntSize = GetFirstValue(ElfDynTag.DT_SYMENT); - - List Symbols = new List(); - - while ((ulong)SymTblAddr < (ulong)StrTblAddr) - { - ElfSym Sym = GetSymbol(SymTblAddr, StrTblAddr); - - Symbols.Add(Sym); - - SymTblAddr += SymEntSize; - } - - SymbolTable = Array.AsReadOnly(Symbols.OrderBy(x => x.Value).ToArray()); - } - - private ElfRel GetRelocation(long Position) - { - long Offset = Memory.ReadInt64(Position + 0); - long Info = Memory.ReadInt64(Position + 8); - long Addend = Memory.ReadInt64(Position + 16); - - int RelType = (int)(Info >> 0); - int SymIdx = (int)(Info >> 32); - - ElfSym Symbol = GetSymbol(SymIdx); - - return new ElfRel(Offset, Addend, Symbol, (ElfRelType)RelType); - } - - private ElfSym GetSymbol(int Index) - { - long StrTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_STRTAB); - long SymTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_SYMTAB); - - long SymEntSize = GetFirstValue(ElfDynTag.DT_SYMENT); - - long Position = SymTblAddr + Index * SymEntSize; - - return GetSymbol(Position, StrTblAddr); - } - - private ElfSym GetSymbol(long Position, long StrTblAddr) - { - int NameIndex = Memory.ReadInt32(Position + 0); - int Info = Memory.ReadByte(Position + 4); - int Other = Memory.ReadByte(Position + 5); - int SHIdx = Memory.ReadInt16(Position + 6); - long Value = Memory.ReadInt64(Position + 8); - long Size = Memory.ReadInt64(Position + 16); - - string Name = string.Empty; - - for (int Chr; (Chr = Memory.ReadByte(StrTblAddr + NameIndex++)) != 0;) - { - Name += (char)Chr; - } - - return new ElfSym(Name, Info, Other, SHIdx, Value, Size); - } - - private long GetFirstValue(ElfDynTag Tag) - { - foreach (ElfDyn Entry in Dynamic) - { - if (Entry.Tag == Tag) - { - return Entry.Value; - } - } - - return 0; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Executables/IExecutable.cs b/Ryujinx.HLE/Loaders/Executables/IExecutable.cs index 6f0952abdb..d3eefde600 100644 --- a/Ryujinx.HLE/Loaders/Executables/IExecutable.cs +++ b/Ryujinx.HLE/Loaders/Executables/IExecutable.cs @@ -1,20 +1,15 @@ namespace Ryujinx.HLE.Loaders.Executables { - public interface IExecutable + interface IExecutable { - string FilePath { get; } - byte[] Text { get; } byte[] RO { get; } byte[] Data { get; } - long SourceAddress { get; } - long BssAddress { get; } - - int Mod0Offset { get; } int TextOffset { get; } int ROOffset { get; } int DataOffset { get; } + int BssOffset { get; } int BssSize { get; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs b/Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs new file mode 100644 index 0000000000..1395d56f8c --- /dev/null +++ b/Ryujinx.HLE/Loaders/Executables/KernelInitialProcess.cs @@ -0,0 +1,149 @@ +using Ryujinx.HLE.Loaders.Compression; +using System.IO; + +namespace Ryujinx.HLE.Loaders.Executables +{ + class KernelInitialProcess : IExecutable + { + public string Name { get; private set; } + + public long TitleId { get; private set; } + + public int ProcessCategory { get; private set; } + + public byte MainThreadPriority { get; private set; } + public byte DefaultProcessorId { get; private set; } + + public bool Is64Bits { get; private set; } + public bool Addr39Bits { get; private set; } + public bool IsService { get; private set; } + + public byte[] Text { get; private set; } + public byte[] RO { get; private set; } + public byte[] Data { get; private set; } + + public int TextOffset { get; private set; } + public int ROOffset { get; private set; } + public int DataOffset { get; private set; } + public int BssOffset { get; private set; } + public int BssSize { get; private set; } + + public int MainThreadStackSize { get; private set; } + + public int[] Capabilities { get; private set; } + + private struct SegmentHeader + { + public int Offset { get; private set; } + public int DecompressedSize { get; private set; } + public int CompressedSize { get; private set; } + public int Attribute { get; private set; } + + public SegmentHeader( + int Offset, + int DecompressedSize, + int CompressedSize, + int Attribute) + { + this.Offset = Offset; + this.DecompressedSize = DecompressedSize; + this.CompressedSize = CompressedSize; + this.Attribute = Attribute; + } + } + + public KernelInitialProcess(Stream Input) + { + BinaryReader Reader = new BinaryReader(Input); + + string Magic = ReadString(Reader, 4); + + if (Magic != "KIP1") + { + + } + + Name = ReadString(Reader, 12); + + TitleId = Reader.ReadInt64(); + + ProcessCategory = Reader.ReadInt32(); + + MainThreadPriority = Reader.ReadByte(); + DefaultProcessorId = Reader.ReadByte(); + + byte Reserved = Reader.ReadByte(); + byte Flags = Reader.ReadByte(); + + Is64Bits = (Flags & 0x08) != 0; + Addr39Bits = (Flags & 0x10) != 0; + IsService = (Flags & 0x20) != 0; + + SegmentHeader[] Segments = new SegmentHeader[6]; + + for (int Index = 0; Index < Segments.Length; Index++) + { + Segments[Index] = new SegmentHeader( + Reader.ReadInt32(), + Reader.ReadInt32(), + Reader.ReadInt32(), + Reader.ReadInt32()); + } + + TextOffset = Segments[0].Offset; + ROOffset = Segments[1].Offset; + DataOffset = Segments[2].Offset; + BssOffset = Segments[3].Offset; + BssSize = Segments[3].DecompressedSize; + + MainThreadStackSize = Segments[1].Attribute; + + Capabilities = new int[8]; + + for (int Index = 0; Index < Capabilities.Length; Index++) + { + Capabilities[Index] = Reader.ReadInt32(); + } + + Input.Seek(0x100, SeekOrigin.Begin); + + Text = ReadSegment(Segments[0], Input); + RO = ReadSegment(Segments[1], Input); + Data = ReadSegment(Segments[2], Input); + } + + private byte[] ReadSegment(SegmentHeader Header, Stream Input) + { + long End = Input.Position + Header.CompressedSize; + + Input.Seek(End, SeekOrigin.Begin); + + byte[] Data = BackwardsLz.Decompress(Input, Header.DecompressedSize); + + Input.Seek(End, SeekOrigin.Begin); + + return Data; + } + + private static string ReadString(BinaryReader Reader, int MaxSize) + { + string Value = string.Empty; + + for (int Index = 0; Index < MaxSize; Index++) + { + char Chr = (char)Reader.ReadByte(); + + if (Chr == '\0') + { + Reader.BaseStream.Seek(MaxSize - Index - 1, SeekOrigin.Current); + + break; + } + + Value += Chr; + } + + return Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Executables/Nro.cs b/Ryujinx.HLE/Loaders/Executables/NxRelocatableObject.cs similarity index 85% rename from Ryujinx.HLE/Loaders/Executables/Nro.cs rename to Ryujinx.HLE/Loaders/Executables/NxRelocatableObject.cs index 6015da2132..20de5b5d93 100644 --- a/Ryujinx.HLE/Loaders/Executables/Nro.cs +++ b/Ryujinx.HLE/Loaders/Executables/NxRelocatableObject.cs @@ -2,10 +2,8 @@ using System.IO; namespace Ryujinx.HLE.Loaders.Executables { - class Nro : IExecutable + class NxRelocatableObject : IExecutable { - public string FilePath { get; private set; } - public byte[] Text { get; private set; } public byte[] RO { get; private set; } public byte[] Data { get; private set; } @@ -16,12 +14,13 @@ namespace Ryujinx.HLE.Loaders.Executables public int DataOffset { get; private set; } public int BssSize { get; private set; } - public long SourceAddress { get; private set; } - public long BssAddress { get; private set; } + public int BssOffset => DataOffset + Data.Length; - public Nro(Stream Input, string FilePath, long SourceAddress = 0, long BssAddress = 0) + public ulong SourceAddress { get; private set; } + public ulong BssAddress { get; private set; } + + public NxRelocatableObject(Stream Input, ulong SourceAddress = 0, ulong BssAddress = 0) { - this.FilePath = FilePath; this.SourceAddress = SourceAddress; this.BssAddress = BssAddress; diff --git a/Ryujinx.HLE/Loaders/Executables/Nso.cs b/Ryujinx.HLE/Loaders/Executables/NxStaticObject.cs similarity index 78% rename from Ryujinx.HLE/Loaders/Executables/Nso.cs rename to Ryujinx.HLE/Loaders/Executables/NxStaticObject.cs index c7b48a5f35..9fecb650cc 100644 --- a/Ryujinx.HLE/Loaders/Executables/Nso.cs +++ b/Ryujinx.HLE/Loaders/Executables/NxStaticObject.cs @@ -4,22 +4,18 @@ using System.IO; namespace Ryujinx.HLE.Loaders.Executables { - class Nso : IExecutable + class NxStaticObject : IExecutable { - public string FilePath { get; private set; } - public byte[] Text { get; private set; } public byte[] RO { get; private set; } public byte[] Data { get; private set; } - public int Mod0Offset { get; private set; } public int TextOffset { get; private set; } public int ROOffset { get; private set; } public int DataOffset { get; private set; } public int BssSize { get; private set; } - public long SourceAddress { get; private set; } - public long BssAddress { get; private set; } + public int BssOffset => DataOffset + Data.Length; [Flags] private enum NsoFlags @@ -32,13 +28,8 @@ namespace Ryujinx.HLE.Loaders.Executables HasDataHash = 1 << 5 } - public Nso(Stream Input, string FilePath) + public NxStaticObject(Stream Input) { - this.FilePath = FilePath; - - SourceAddress = 0; - BssAddress = 0; - BinaryReader Reader = new BinaryReader(Input); Input.Seek(0, SeekOrigin.Begin); @@ -89,7 +80,7 @@ namespace Ryujinx.HLE.Loaders.Executables Text = Reader.ReadBytes(TextSize); - if (Flags.HasFlag(NsoFlags.IsTextCompressed) || true) + if (Flags.HasFlag(NsoFlags.IsTextCompressed)) { Text = Lz4.Decompress(Text, TextDecSize); } @@ -99,7 +90,7 @@ namespace Ryujinx.HLE.Loaders.Executables RO = Reader.ReadBytes(ROSize); - if (Flags.HasFlag(NsoFlags.IsROCompressed) || true) + if (Flags.HasFlag(NsoFlags.IsROCompressed)) { RO = Lz4.Decompress(RO, RODecSize); } @@ -109,19 +100,10 @@ namespace Ryujinx.HLE.Loaders.Executables Data = Reader.ReadBytes(DataSize); - if (Flags.HasFlag(NsoFlags.IsDataCompressed) || true) + if (Flags.HasFlag(NsoFlags.IsDataCompressed)) { Data = Lz4.Decompress(Data, DataDecSize); } - - using (MemoryStream TextMS = new MemoryStream(Text)) - { - BinaryReader TextReader = new BinaryReader(TextMS); - - TextMS.Seek(4, SeekOrigin.Begin); - - Mod0Offset = TextReader.ReadInt32(); - } } } } \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Npdm/ApplicationType.cs b/Ryujinx.HLE/Loaders/Npdm/ApplicationType.cs deleted file mode 100644 index ad27903280..0000000000 --- a/Ryujinx.HLE/Loaders/Npdm/ApplicationType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.HLE.Loaders.Npdm -{ - enum ApplicationType - { - SystemModule, - Application, - Applet - } -} diff --git a/Ryujinx.HLE/Loaders/Npdm/FsPermissionBool.cs b/Ryujinx.HLE/Loaders/Npdm/FsPermissionBool.cs deleted file mode 100644 index 571b7b5a80..0000000000 --- a/Ryujinx.HLE/Loaders/Npdm/FsPermissionBool.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Ryujinx.HLE.Loaders.Npdm -{ - enum FsPermissionBool : ulong - { - BisCache = 0x8000000000000080, - EraseMmc = 0x8000000000000080, - GameCardCertificate = 0x8000000000000010, - GameCardIdSet = 0x8000000000000010, - GameCardDriver = 0x8000000000000200, - GameCardAsic = 0x8000000000000200, - SaveDataCreate = 0x8000000000002020, - SaveDataDelete0 = 0x8000000000000060, - SystemSaveDataCreate0 = 0x8000000000000028, - SystemSaveDataCreate1 = 0x8000000000000020, - SaveDataDelete1 = 0x8000000000004028, - SaveDataIterators0 = 0x8000000000000060, - SaveDataIterators1 = 0x8000000000004020, - SaveThumbnails = 0x8000000000020000, - PosixTime = 0x8000000000000400, - SaveDataExtraData = 0x8000000000004060, - GlobalMode = 0x8000000000080000, - SpeedEmulation = 0x8000000000080000, - NULL = 0, - PaddingFiles = 0xC000000000800000, - SaveData_Debug = 0xC000000001000000, - SaveData_SystemManagement = 0xC000000002000000, - Unknown0x16 = 0x8000000004000000, - Unknown0x17 = 0x8000000008000000, - Unknown0x18 = 0x8000000010000000, - Unknown0x19 = 0x8000000000000800, - Unknown0x1A = 0x8000000000004020 - } -} diff --git a/Ryujinx.HLE/Loaders/Npdm/FsPermissionRw.cs b/Ryujinx.HLE/Loaders/Npdm/FsPermissionRw.cs deleted file mode 100644 index ca21279b5e..0000000000 --- a/Ryujinx.HLE/Loaders/Npdm/FsPermissionRw.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Ryujinx.HLE.Loaders.Npdm -{ - enum FsPermissionRw : ulong - { - MountContentType2 = 0x8000000000000801, - MountContentType5 = 0x8000000000000801, - MountContentType3 = 0x8000000000000801, - MountContentType4 = 0x8000000000000801, - MountContentType6 = 0x8000000000000801, - MountContentType7 = 0x8000000000000801, - Unknown0x6 = 0x8000000000000000, - ContentStorageAccess = 0x8000000000000800, - ImageDirectoryAccess = 0x8000000000001000, - MountBisType28 = 0x8000000000000084, - MountBisType29 = 0x8000000000000080, - MountBisType30 = 0x8000000000008080, - MountBisType31 = 0x8000000000008080, - Unknown0xD = 0x8000000000000080, - SdCardAccess = 0xC000000000200000, - GameCardUser = 0x8000000000000010, - SaveDataAccess0 = 0x8000000000040020, - SystemSaveDataAccess0 = 0x8000000000000028, - SaveDataAccess1 = 0x8000000000000020, - SystemSaveDataAccess1 = 0x8000000000000020, - BisPartition0 = 0x8000000000010082, - BisPartition10 = 0x8000000000010080, - BisPartition20 = 0x8000000000010080, - BisPartition21 = 0x8000000000010080, - BisPartition22 = 0x8000000000010080, - BisPartition23 = 0x8000000000010080, - BisPartition24 = 0x8000000000010080, - BisPartition25 = 0x8000000000010080, - BisPartition26 = 0x8000000000000080, - BisPartition27 = 0x8000000000000084, - BisPartition28 = 0x8000000000000084, - BisPartition29 = 0x8000000000000080, - BisPartition30 = 0x8000000000000080, - BisPartition31 = 0x8000000000000080, - BisPartition32 = 0x8000000000000080, - Unknown0x23 = 0xC000000000200000, - GameCard_System = 0x8000000000000100, - MountContent_System = 0x8000000000100008, - HostAccess = 0xC000000000400000 - } -} diff --git a/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs b/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs index 0b45ebfbbd..cd3d325245 100644 --- a/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs +++ b/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs @@ -1,173 +1,23 @@ -using Ryujinx.HLE.Exceptions; -using System; -using System.Collections.ObjectModel; -using System.IO; +using System.IO; namespace Ryujinx.HLE.Loaders.Npdm { class KernelAccessControl { - public ReadOnlyCollection Items; + public int[] Capabilities { get; private set; } public KernelAccessControl(Stream Stream, int Offset, int Size) { Stream.Seek(Offset, SeekOrigin.Begin); + Capabilities = new int[Size / 4]; + BinaryReader Reader = new BinaryReader(Stream); - KernelAccessControlItem[] Items = new KernelAccessControlItem[Size / 4]; - - for (int Index = 0; Index < Size / 4; Index++) + for (int Index = 0; Index < Capabilities.Length; Index++) { - uint Descriptor = Reader.ReadUInt32(); - - //Ignore the descriptor. - if (Descriptor == 0xffffffff) - { - continue; - } - - Items[Index] = new KernelAccessControlItem(); - - int LowBits = 0; - - while ((Descriptor & 1) != 0) - { - Descriptor >>= 1; - - LowBits++; - } - - Descriptor >>= 1; - - switch (LowBits) - { - //Kernel flags. - case 3: - { - Items[Index].HasKernelFlags = true; - - Items[Index].HighestThreadPriority = (Descriptor >> 0) & 0x3f; - Items[Index].LowestThreadPriority = (Descriptor >> 6) & 0x3f; - Items[Index].LowestCpuId = (Descriptor >> 12) & 0xff; - Items[Index].HighestCpuId = (Descriptor >> 20) & 0xff; - - break; - } - - //Syscall mask. - case 4: - { - Items[Index].HasSvcFlags = true; - - Items[Index].AllowedSvcs = new bool[0x80]; - - int SysCallBase = (int)(Descriptor >> 24) * 0x18; - - for (int SysCall = 0; SysCall < 0x18 && SysCallBase + SysCall < 0x80; SysCall++) - { - Items[Index].AllowedSvcs[SysCallBase + SysCall] = (Descriptor & 1) != 0; - - Descriptor >>= 1; - } - - break; - } - - //Map IO/Normal. - case 6: - { - ulong Address = (Descriptor & 0xffffff) << 12; - bool IsRo = (Descriptor >> 24) != 0; - - if (Index == Size / 4 - 1) - { - throw new InvalidNpdmException("Invalid Kernel Access Control Descriptors!"); - } - - Descriptor = Reader.ReadUInt32(); - - if ((Descriptor & 0x7f) != 0x3f) - { - throw new InvalidNpdmException("Invalid Kernel Access Control Descriptors!"); - } - - Descriptor >>= 7; - - ulong MmioSize = (Descriptor & 0xffffff) << 12; - bool IsNormal = (Descriptor >> 24) != 0; - - Items[Index].NormalMmio.Add(new KernelAccessControlMmio(Address, MmioSize, IsRo, IsNormal)); - - Index++; - - break; - } - - //Map Normal Page. - case 7: - { - ulong Address = Descriptor << 12; - - Items[Index].PageMmio.Add(new KernelAccessControlMmio(Address, 0x1000, false, false)); - - break; - } - - //IRQ Pair. - case 11: - { - Items[Index].Irq.Add(new KernelAccessControlIrq( - (Descriptor >> 0) & 0x3ff, - (Descriptor >> 10) & 0x3ff)); - - break; - } - - //Application Type. - case 13: - { - Items[Index].HasApplicationType = true; - - Items[Index].ApplicationType = (int)Descriptor & 7; - - break; - } - - //Kernel Release Version. - case 14: - { - Items[Index].HasKernelVersion = true; - - Items[Index].KernelVersionRelease = (int)Descriptor; - - break; - } - - //Handle Table Size. - case 15: - { - Items[Index].HasHandleTableSize = true; - - Items[Index].HandleTableSize = (int)Descriptor; - - break; - } - - //Debug Flags. - case 16: - { - Items[Index].HasDebugFlags = true; - - Items[Index].AllowDebug = ((Descriptor >> 0) & 1) != 0; - Items[Index].ForceDebug = ((Descriptor >> 1) & 1) != 0; - - break; - } - } + Capabilities[Index] = Reader.ReadInt32(); } - - this.Items = Array.AsReadOnly(Items); } } } diff --git a/Ryujinx.HLE/Loaders/Npdm/KernelAccessControlIrq.cs b/Ryujinx.HLE/Loaders/Npdm/KernelAccessControlIrq.cs deleted file mode 100644 index 636713317a..0000000000 --- a/Ryujinx.HLE/Loaders/Npdm/KernelAccessControlIrq.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ryujinx.HLE.Loaders.Npdm -{ - struct KernelAccessControlIrq - { - public uint Irq0 { get; private set; } - public uint Irq1 { get; private set; } - - public KernelAccessControlIrq(uint Irq0, uint Irq1) - { - this.Irq0 = Irq0; - this.Irq1 = Irq1; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Npdm/KernelAccessControlMmio.cs b/Ryujinx.HLE/Loaders/Npdm/KernelAccessControlMmio.cs deleted file mode 100644 index 1ec79c8870..0000000000 --- a/Ryujinx.HLE/Loaders/Npdm/KernelAccessControlMmio.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Ryujinx.HLE.Loaders.Npdm -{ - struct KernelAccessControlMmio - { - public ulong Address { get; private set; } - public ulong Size { get; private set; } - public bool IsRo { get; private set; } - public bool IsNormal { get; private set; } - - public KernelAccessControlMmio( - ulong Address, - ulong Size, - bool IsRo, - bool IsNormal) - { - this.Address = Address; - this.Size = Size; - this.IsRo = IsRo; - this.IsNormal = IsNormal; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Npdm/KernelAccessItem.cs b/Ryujinx.HLE/Loaders/Npdm/KernelAccessItem.cs deleted file mode 100644 index 42015c3ea2..0000000000 --- a/Ryujinx.HLE/Loaders/Npdm/KernelAccessItem.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.HLE.Loaders.Npdm -{ - struct KernelAccessControlItem - { - public bool HasKernelFlags { get; set; } - public uint LowestThreadPriority { get; set; } - public uint HighestThreadPriority { get; set; } - public uint LowestCpuId { get; set; } - public uint HighestCpuId { get; set; } - - public bool HasSvcFlags { get; set; } - public bool[] AllowedSvcs { get; set; } - - public List NormalMmio { get; set; } - public List PageMmio { get; set; } - public List Irq { get; set; } - - public bool HasApplicationType { get; set; } - public int ApplicationType { get; set; } - - public bool HasKernelVersion { get; set; } - public int KernelVersionRelease { get; set; } - - public bool HasHandleTableSize { get; set; } - public int HandleTableSize { get; set; } - - public bool HasDebugFlags { get; set; } - public bool AllowDebug { get; set; } - public bool ForceDebug { get; set; } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Npdm/Npdm.cs b/Ryujinx.HLE/Loaders/Npdm/Npdm.cs index 8aacfd99ef..9c2fdb381b 100644 --- a/Ryujinx.HLE/Loaders/Npdm/Npdm.cs +++ b/Ryujinx.HLE/Loaders/Npdm/Npdm.cs @@ -1,5 +1,4 @@ using Ryujinx.HLE.Exceptions; -using Ryujinx.HLE.Utilities; using System.IO; using System.Text; @@ -12,15 +11,15 @@ namespace Ryujinx.HLE.Loaders.Npdm { private const int MetaMagic = 'M' << 0 | 'E' << 8 | 'T' << 16 | 'A' << 24; - public bool Is64Bits { get; private set; } - public int AddressSpaceWidth { get; private set; } - public byte MainThreadPriority { get; private set; } - public byte DefaultCpuId { get; private set; } - public int SystemResourceSize { get; private set; } - public int ProcessCategory { get; private set; } - public int MainEntrypointStackSize { get; private set; } - public string TitleName { get; private set; } - public byte[] ProductCode { get; private set; } + public byte MmuFlags { get; private set; } + public bool Is64Bits { get; private set; } + public byte MainThreadPriority { get; private set; } + public byte DefaultCpuId { get; private set; } + public int PersonalMmHeapSize { get; private set; } + public int ProcessCategory { get; private set; } + public int MainThreadStackSize { get; private set; } + public string TitleName { get; private set; } + public byte[] ProductCode { get; private set; } public ACI0 ACI0 { get; private set; } public ACID ACID { get; private set; } @@ -36,27 +35,22 @@ namespace Ryujinx.HLE.Loaders.Npdm Reader.ReadInt64(); - //MmuFlags, bit0: 64-bit instructions, bits1-3: address space width (1=64-bit, 2=32-bit). Needs to be <= 0xF. - byte MmuFlags = Reader.ReadByte(); + MmuFlags = Reader.ReadByte(); - Is64Bits = (MmuFlags & 1) != 0; - AddressSpaceWidth = (MmuFlags >> 1) & 7; + Is64Bits = (MmuFlags & 1) != 0; Reader.ReadByte(); - MainThreadPriority = Reader.ReadByte(); //(0-63). + MainThreadPriority = Reader.ReadByte(); DefaultCpuId = Reader.ReadByte(); Reader.ReadInt32(); - //System resource size (max size as of 5.x: 534773760). - SystemResourceSize = EndianSwap.Swap32(Reader.ReadInt32()); + PersonalMmHeapSize = Reader.ReadInt32(); - //ProcessCategory (0: regular title, 1: kernel built-in). Should be 0 here. - ProcessCategory = EndianSwap.Swap32(Reader.ReadInt32()); + ProcessCategory = Reader.ReadInt32(); - //Main entrypoint stack size. - MainEntrypointStackSize = Reader.ReadInt32(); + MainThreadStackSize = Reader.ReadInt32(); byte[] TempTitleName = Reader.ReadBytes(0x10); diff --git a/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs b/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs index 910eacb38b..b18538e5b4 100644 --- a/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs +++ b/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs @@ -28,8 +28,8 @@ namespace Ryujinx.HLE.Loaders.Npdm break; } - int Length = ((ControlByte & 0x07)) + 1; - bool RegisterAllowed = ((ControlByte & 0x80) != 0); + int Length = (ControlByte & 0x07) + 1; + bool RegisterAllowed = (ControlByte & 0x80) != 0; Services.Add(Encoding.ASCII.GetString(Reader.ReadBytes(Length), 0, Length), RegisterAllowed); diff --git a/Ryujinx.HLE/Loaders/Npdm/SvcName.cs b/Ryujinx.HLE/Loaders/Npdm/SvcName.cs deleted file mode 100644 index e519e05ec0..0000000000 --- a/Ryujinx.HLE/Loaders/Npdm/SvcName.cs +++ /dev/null @@ -1,134 +0,0 @@ -namespace Ryujinx.HLE.Loaders.Npdm -{ - enum SvcName - { - Reserved0, - SetHeapSize, - SetMemoryPermission, - SetMemoryAttribute, - MapMemory, - UnmapMemory, - QueryMemory, - ExitProcess, - CreateThread, - StartThread, - ExitThread, - SleepThread, - GetThreadPriority, - SetThreadPriority, - GetThreadCoreMask, - SetThreadCoreMask, - GetCurrentProcessorNumber, - SignalEvent, - ClearEvent, - MapSharedMemory, - UnmapSharedMemory, - CreateTransferMemory, - CloseHandle, - ResetSignal, - WaitSynchronization, - CancelSynchronization, - ArbitrateLock, - ArbitrateUnlock, - WaitProcessWideKeyAtomic, - SignalProcessWideKey, - GetSystemTick, - ConnectToNamedPort, - SendSyncRequestLight, - SendSyncRequest, - SendSyncRequestWithUserBuffer, - SendAsyncRequestWithUserBuffer, - GetProcessId, - GetThreadId, - Break, - OutputDebugString, - ReturnFromException, - GetInfo, - FlushEntireDataCache, - FlushDataCache, - MapPhysicalMemory, - UnmapPhysicalMemory, - GetFutureThreadInfo, - GetLastThreadInfo, - GetResourceLimitLimitValue, - GetResourceLimitCurrentValue, - SetThreadActivity, - GetThreadContext3, - WaitForAddress, - SignalToAddress, - Reserved1, - Reserved2, - Reserved3, - Reserved4, - Reserved5, - Reserved6, - DumpInfo, - DumpInfoNew, - Reserved7, - Reserved8, - CreateSession, - AcceptSession, - ReplyAndReceiveLight, - ReplyAndReceive, - ReplyAndReceiveWithUserBuffer, - CreateEvent, - Reserved9, - Reserved10, - MapPhysicalMemoryUnsafe, - UnmapPhysicalMemoryUnsafe, - SetUnsafeLimit, - CreateCodeMemory, - ControlCodeMemory, - SleepSystem, - ReadWriteRegister, - SetProcessActivity, - CreateSharedMemory, - MapTransferMemory, - UnmapTransferMemory, - CreateInterruptEvent, - QueryPhysicalAddress, - QueryIoMapping, - CreateDeviceAddressSpace, - AttachDeviceAddressSpace, - DetachDeviceAddressSpace, - MapDeviceAddressSpaceByForce, - MapDeviceAddressSpaceAligned, - MapDeviceAddressSpace, - UnmapDeviceAddressSpace, - InvalidateProcessDataCache, - StoreProcessDataCache, - FlushProcessDataCache, - DebugActiveProcess, - BreakDebugProcess, - TerminateDebugProcess, - GetDebugEvent, - ContinueDebugEvent, - GetProcessList, - GetThreadList, - GetDebugThreadContext, - SetDebugThreadContext, - QueryDebugProcessMemory, - ReadDebugProcessMemory, - WriteDebugProcessMemory, - SetHardwareBreakPoint, - GetDebugThreadParam, - Reserved11, - GetSystemInfo, - CreatePort, - ManageNamedPort, - ConnectToPort, - SetProcessMemoryPermission, - MapProcessMemory, - UnmapProcessMemory, - QueryProcessMemory, - MapProcessCodeMemory, - UnmapProcessCodeMemory, - CreateProcess, - StartProcess, - TerminateProcess, - GetProcessInfo, - CreateResourceLimit, - SetResourceLimitLimitValue, - CallSecureMonitor - } -} diff --git a/Ryujinx.HLE/Memory/ArenaAllocator.cs b/Ryujinx.HLE/Memory/ArenaAllocator.cs deleted file mode 100644 index 9bcb7873e0..0000000000 --- a/Ryujinx.HLE/Memory/ArenaAllocator.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.HLE.Memory -{ - class ArenaAllocator - { - private class Region - { - public long Position { get; set; } - public long Size { get; set; } - - public Region(long Position, long Size) - { - this.Position = Position; - this.Size = Size; - } - } - - private LinkedList FreeRegions; - - public long TotalAvailableSize { get; private set; } - public long TotalUsedSize { get; private set; } - - public ArenaAllocator(long ArenaSize) - { - TotalAvailableSize = ArenaSize; - - FreeRegions = new LinkedList(); - - FreeRegions.AddFirst(new Region(0, ArenaSize)); - } - - public bool TryAllocate(long Size, out long Position) - { - LinkedListNode Node = FreeRegions.First; - - while (Node != null) - { - Region Rg = Node.Value; - - if ((ulong)Rg.Size >= (ulong)Size) - { - Position = Rg.Position; - - Rg.Position += Size; - Rg.Size -= Size; - - if (Rg.Size == 0) - { - //Region is empty, just remove it. - FreeRegions.Remove(Node); - } - else if (Node.Previous != null) - { - //Re-sort based on size (smaller first). - Node = Node.Previous; - - FreeRegions.Remove(Node.Next); - - while (Node != null && (ulong)Node.Value.Size > (ulong)Rg.Size) - { - Node = Node.Previous; - } - - if (Node != null) - { - FreeRegions.AddAfter(Node, Rg); - } - else - { - FreeRegions.AddFirst(Rg); - } - } - - TotalUsedSize += Size; - - return true; - } - - Node = Node.Next; - } - - Position = 0; - - return false; - } - - public void Free(long Position, long Size) - { - long End = Position + Size; - - Region NewRg = new Region(Position, Size); - - LinkedListNode Node = FreeRegions.First; - LinkedListNode PrevSz = null; - - while (Node != null) - { - LinkedListNode NextNode = Node.Next; - - Region Rg = Node.Value; - - long RgEnd = Rg.Position + Rg.Size; - - if (Rg.Position == End) - { - //Current region position matches the end of the freed region, - //just merge the two and remove the current region from the list. - NewRg.Size += Rg.Size; - - FreeRegions.Remove(Node); - } - else if (RgEnd == Position) - { - //End of the current region matches the position of the freed region, - //just merge the two and remove the current region from the list. - NewRg.Position = Rg.Position; - NewRg.Size += Rg.Size; - - FreeRegions.Remove(Node); - } - else - { - if (PrevSz == null) - { - PrevSz = Node; - } - else if ((ulong)Rg.Size < (ulong)NewRg.Size && - (ulong)Rg.Size > (ulong)PrevSz.Value.Size) - { - PrevSz = Node; - } - } - - Node = NextNode; - } - - if (PrevSz != null && (ulong)PrevSz.Value.Size < (ulong)Size) - { - FreeRegions.AddAfter(PrevSz, NewRg); - } - else - { - FreeRegions.AddFirst(NewRg); - } - - TotalUsedSize -= Size; - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj index 71a0cf1c47..6285825a5c 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -14,10 +14,12 @@ + + diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 8de49ca4a8..5b3b36d0f7 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -4,7 +4,6 @@ using Ryujinx.Graphics.Gal; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; using Ryujinx.HLE.Input; -using Ryujinx.HLE.Memory; using System; using System.Threading; @@ -56,7 +55,7 @@ namespace Ryujinx.HLE Statistics = new PerformanceStatistics(); - Hid = new Hid(this, System.HidSharedMem.PA); + Hid = new Hid(this, System.HidBaseAddress); VsyncEvent = new AutoResetEvent(true); } diff --git a/Ryujinx.HLE/Utilities/WSAError.cs b/Ryujinx.HLE/Utilities/WSAError.cs index 55c04f2284..ff0896d47a 100644 --- a/Ryujinx.HLE/Utilities/WSAError.cs +++ b/Ryujinx.HLE/Utilities/WSAError.cs @@ -1,17 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Ryujinx.HLE.Utilities +namespace Ryujinx.HLE.Utilities { - public enum WSAError + enum WSAError { /* * All Windows Sockets error constants are biased by WSABASEERR from * the "normal" */ WSABASEERR = 10000, - + /* * Windows Sockets definitions of regular Microsoft C error constants */ diff --git a/Ryujinx/Ui/ConsoleLog.cs b/Ryujinx/Ui/ConsoleLog.cs index 1ecd4cde44..67eaa0e35f 100644 --- a/Ryujinx/Ui/ConsoleLog.cs +++ b/Ryujinx/Ui/ConsoleLog.cs @@ -26,7 +26,7 @@ namespace Ryujinx { LogLevel.Error, ConsoleColor.Red } }; - _messageQueue = new BlockingCollection(); + _messageQueue = new BlockingCollection(10); _consoleLock = new object(); @@ -58,7 +58,7 @@ namespace Ryujinx string formattedTime = e.Time.ToString(@"hh\:mm\:ss\.fff"); string currentThread = Thread.CurrentThread.ManagedThreadId.ToString("d4"); - + string message = formattedTime + " | " + currentThread + " " + e.Message; if (_logColors.TryGetValue(e.Level, out ConsoleColor color))