diff --git a/Ryujinx.HLE/Gpu/Memory/NvGpuVmm.cs b/Ryujinx.HLE/Gpu/Memory/NvGpuVmm.cs index 84dc739a15..164068ec2e 100644 --- a/Ryujinx.HLE/Gpu/Memory/NvGpuVmm.cs +++ b/Ryujinx.HLE/Gpu/Memory/NvGpuVmm.cs @@ -26,18 +26,6 @@ namespace Ryujinx.HLE.Gpu.Memory public AMemory Memory { get; private set; } - private struct MappedMemory - { - public long Size; - - public MappedMemory(long Size) - { - this.Size = Size; - } - } - - private ConcurrentDictionary Maps; - private NvGpuVmmCache Cache; private const long PteUnmapped = -1; @@ -49,8 +37,6 @@ namespace Ryujinx.HLE.Gpu.Memory { this.Memory = Memory; - Maps = new ConcurrentDictionary(); - Cache = new NvGpuVmmCache(); PageTable = new long[PTLvl0Size][]; @@ -60,18 +46,6 @@ namespace Ryujinx.HLE.Gpu.Memory { lock (PageTable) { - for (long Offset = 0; Offset < Size; Offset += PageSize) - { - if (GetPte(VA + Offset) != PteReserved) - { - return -1; - } - } - - MappedMemory Mapped = new MappedMemory(Size); - - Maps.AddOrUpdate(VA, Mapped, (Key, Old) => Mapped); - for (long Offset = 0; Offset < Size; Offset += PageSize) { SetPte(VA + Offset, PA + Offset); @@ -89,10 +63,6 @@ namespace Ryujinx.HLE.Gpu.Memory if (VA != -1) { - MappedMemory Mapped = new MappedMemory(Size); - - Maps.AddOrUpdate(VA, Mapped, (Key, Old) => Mapped); - for (long Offset = 0; Offset < Size; Offset += PageSize) { SetPte(VA + Offset, PA + Offset); @@ -103,18 +73,6 @@ namespace Ryujinx.HLE.Gpu.Memory } } - public bool Unmap(long VA) - { - if (Maps.TryRemove(VA, out MappedMemory Map)) - { - Free(VA, Map.Size); - - return true; - } - - return false; - } - public long ReserveFixed(long VA, long Size) { lock (PageTable) @@ -167,7 +125,9 @@ namespace Ryujinx.HLE.Gpu.Memory private long GetFreePosition(long Size, long Align = 1) { - long Position = 0; + //Note: Address 0 is not considered valid by the driver, + //when 0 is returned it's considered a mapping error. + long Position = PageSize; long FreeSize = 0; if (Align < 1) diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASCtx.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASCtx.cs new file mode 100644 index 0000000000..e718182a30 --- /dev/null +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASCtx.cs @@ -0,0 +1,198 @@ +using ChocolArm64.Memory; +using Ryujinx.HLE.Gpu.Memory; +using System.Collections.Generic; + +namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS +{ + class NvGpuASCtx + { + public NvGpuVmm Vmm { get; private set; } + + private class Range + { + public ulong Start { get; private set; } + public ulong End { get; private set; } + + public Range(long Position, long Size) + { + Start = (ulong)Position; + End = (ulong)Size + Start; + } + } + + private class MappedMemory : Range + { + public long PhysicalAddress { get; private set; } + public bool VaAllocated { get; private set; } + + public MappedMemory( + long Position, + long Size, + long PhysicalAddress, + bool VaAllocated) : base(Position, Size) + { + this.PhysicalAddress = PhysicalAddress; + this.VaAllocated = VaAllocated; + } + } + + private SortedList Maps; + private SortedList Reservations; + + public NvGpuASCtx(ServiceCtx Context) + { + Vmm = new NvGpuVmm(Context.Memory); + + Maps = new SortedList(); + Reservations = new SortedList(); + } + + public bool ValidateFixedBuffer(long Position, long Size) + { + long MapEnd = Position + Size; + + //Check if size is valid (0 is also not allowed). + if ((ulong)MapEnd <= (ulong)Position) + { + return false; + } + + //Check if address is page aligned. + if ((Position & NvGpuVmm.PageMask) != 0) + { + return false; + } + + //Check if region is reserved. + if (BinarySearch(Reservations, Position) == null) + { + return false; + } + + //Check for overlap with already mapped buffers. + Range Map = BinarySearchLt(Maps, MapEnd); + + if (Map != null && Map.End > (ulong)Position) + { + return false; + } + + return true; + } + + public void AddMap( + long Position, + long Size, + long PhysicalAddress, + bool VaAllocated) + { + Maps.Add(Position, new MappedMemory(Position, Size, PhysicalAddress, VaAllocated)); + } + + public bool RemoveMap(long Position, out long Size) + { + Size = 0; + + if (Maps.Remove(Position, out Range Value)) + { + MappedMemory Map = (MappedMemory)Value; + + if (Map.VaAllocated) + { + Size = (long)(Map.End - Map.Start); + } + + return true; + } + + return false; + } + + public bool TryGetMapPhysicalAddress(long Position, out long PhysicalAddress) + { + Range Map = BinarySearch(Maps, Position); + + if (Map != null) + { + PhysicalAddress = ((MappedMemory)Map).PhysicalAddress; + + return true; + } + + PhysicalAddress = 0; + + return false; + } + + public void AddReservation(long Position, long Size) + { + Reservations.Add(Position, new Range(Position, Size)); + } + + public bool RemoveReservation(long Position) + { + return Reservations.Remove(Position); + } + + private Range BinarySearch(SortedList Lst, long Position) + { + int Left = 0; + int Right = Lst.Count - 1; + + while (Left <= Right) + { + int Size = Right - Left; + + int Middle = Left + (Size >> 1); + + Range Rg = Lst.Values[Middle]; + + if ((ulong)Position >= Rg.Start && (ulong)Position < Rg.End) + { + return Rg; + } + + if ((ulong)Position < Rg.Start) + { + Right = Middle - 1; + } + else + { + Left = Middle + 1; + } + } + + return null; + } + + private Range BinarySearchLt(SortedList Lst, long Position) + { + Range LtRg = null; + + int Left = 0; + int Right = Lst.Count - 1; + + while (Left <= Right) + { + int Size = Right - Left; + + int Middle = Left + (Size >> 1); + + Range Rg = Lst.Values[Middle]; + + if ((ulong)Position < Rg.Start) + { + Right = Middle - 1; + } + else + { + Left = Middle + 1; + + LtRg = Rg; + } + } + + return LtRg; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASIoctl.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASIoctl.cs index d92fd4cddf..262f3c35d8 100644 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASIoctl.cs +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvGpuAS/NvGpuASIoctl.cs @@ -11,11 +11,13 @@ namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS { private const int FlagFixedOffset = 1; - private static ConcurrentDictionary Vmms; + private const int FlagRemapSubRange = 0x100; + + private static ConcurrentDictionary ASCtxs; static NvGpuASIoctl() { - Vmms = new ConcurrentDictionary(); + ASCtxs = new ConcurrentDictionary(); } public static int ProcessIoctl(ServiceCtx Context, int Cmd) @@ -52,29 +54,40 @@ namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS NvGpuASAllocSpace Args = AMemoryHelper.Read(Context.Memory, InputPosition); - NvGpuVmm Vmm = GetVmm(Context); + NvGpuASCtx ASCtx = GetASCtx(Context); ulong Size = (ulong)Args.Pages * (ulong)Args.PageSize; - if ((Args.Flags & FlagFixedOffset) != 0) - { - Args.Offset = Vmm.ReserveFixed(Args.Offset, (long)Size); - } - else - { - Args.Offset = Vmm.Reserve((long)Size, Args.Offset); - } + long oldoffs = Args.Offset; int Result = NvResult.Success; - if (Args.Offset < 0) + lock (ASCtx) { - Args.Offset = 0; + //Note: When the fixed offset flag is not set, + //the Offset field holds the alignment size instead. + if ((Args.Flags & FlagFixedOffset) != 0) + { + Args.Offset = ASCtx.Vmm.ReserveFixed(Args.Offset, (long)Size); + } + else + { + Args.Offset = ASCtx.Vmm.Reserve((long)Size, Args.Offset); + } - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"No memory to allocate size {Size:x16}!"); + if (Args.Offset < 0) + { + Args.Offset = 0; - Result = NvResult.OutOfMemory; + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Failed to allocate size {Size:x16}!"); + + Result = NvResult.OutOfMemory; + } + else + { + ASCtx.AddReservation(Args.Offset, (long)Size); + } } AMemoryHelper.Write(Context.Memory, OutputPosition, Args); @@ -89,14 +102,29 @@ namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS NvGpuASAllocSpace Args = AMemoryHelper.Read(Context.Memory, InputPosition); - NvGpuVmm Vmm = GetVmm(Context); + NvGpuASCtx ASCtx = GetASCtx(Context); - ulong Size = (ulong)Args.Pages * - (ulong)Args.PageSize; + int Result = NvResult.Success; - Vmm.Free(Args.Offset, (long)Size); + lock (ASCtx) + { + ulong Size = (ulong)Args.Pages * + (ulong)Args.PageSize; - return NvResult.Success; + if (ASCtx.RemoveReservation(Args.Offset)) + { + ASCtx.Vmm.Free(Args.Offset, (long)Size); + } + else + { + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, + $"Failed to free offset 0x{Args.Offset:x16} size 0x{Size:x16}!"); + + Result = NvResult.InvalidInput; + } + } + + return Result; } private static int UnmapBuffer(ServiceCtx Context) @@ -106,11 +134,21 @@ namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS NvGpuASUnmapBuffer Args = AMemoryHelper.Read(Context.Memory, InputPosition); - NvGpuVmm Vmm = GetVmm(Context); + NvGpuASCtx ASCtx = GetASCtx(Context); - if (!Vmm.Unmap(Args.Offset)) + lock (ASCtx) { - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid buffer offset {Args.Offset:x16}!"); + if (ASCtx.RemoveMap(Args.Offset, out long Size)) + { + if (Size != 0) + { + ASCtx.Vmm.Free(Args.Offset, Size); + } + } + else + { + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid buffer offset {Args.Offset:x16}!"); + } } return NvResult.Success; @@ -118,12 +156,14 @@ namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS private static int MapBufferEx(ServiceCtx Context) { + const string MapErrorMsg = "Failed to map fixed buffer with offset 0x{0:x16} and size 0x{1:x16}!"; + long InputPosition = Context.Request.GetBufferType0x21().Position; long OutputPosition = Context.Request.GetBufferType0x22().Position; NvGpuASMapBufferEx Args = AMemoryHelper.Read(Context.Memory, InputPosition); - NvGpuVmm Vmm = GetVmm(Context); + NvGpuASCtx ASCtx = GetASCtx(Context); NvMapHandle Map = NvMapIoctl.GetNvMapWithFb(Context, Args.NvMapHandle); @@ -134,7 +174,41 @@ namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS return NvResult.InvalidInput; } - long PA = Map.Address + Args.BufferOffset; + long oldoffs = Args.Offset; + + long PA; + + if ((Args.Flags & FlagRemapSubRange) != 0) + { + lock (ASCtx) + { + if (ASCtx.TryGetMapPhysicalAddress(Args.Offset, out PA)) + { + long VA = Args.Offset + Args.BufferOffset; + + PA += Args.BufferOffset; + + if (ASCtx.Vmm.Map(PA, VA, Args.MappingSize) < 0) + { + string Msg = string.Format(MapErrorMsg, VA, Args.MappingSize); + + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, Msg); + + return NvResult.InvalidInput; + } + + return NvResult.Success; + } + else + { + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Address 0x{Args.Offset:x16} not mapped!"); + + return NvResult.InvalidInput; + } + } + } + + PA = Map.Address + Args.BufferOffset; long Size = Args.MappingSize; @@ -145,40 +219,44 @@ namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS int Result = NvResult.Success; - //Note: When the fixed offset flag is not set, - //the Offset field holds the alignment size instead. - if ((Args.Flags & FlagFixedOffset) != 0) + lock (ASCtx) { - long MapEnd = Args.Offset + Args.MappingSize; + //Note: When the fixed offset flag is not set, + //the Offset field holds the alignment size instead. + bool VaAllocated = (Args.Flags & FlagFixedOffset) == 0; - if ((ulong)MapEnd <= (ulong)Args.Offset) + if (!VaAllocated) { - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Offset 0x{Args.Offset:x16} and size 0x{Args.MappingSize:x16} results in a overflow!"); + if (ASCtx.ValidateFixedBuffer(Args.Offset, Size)) + { + Args.Offset = ASCtx.Vmm.Map(PA, Args.Offset, Size); + } + else + { + string Msg = string.Format(MapErrorMsg, Args.Offset, Size); - return NvResult.InvalidInput; + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, Msg); + + Result = NvResult.InvalidInput; + } } - - if ((Args.Offset & NvGpuVmm.PageMask) != 0) + else { - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Offset 0x{Args.Offset:x16} is not page aligned!"); - - return NvResult.InvalidInput; + Args.Offset = ASCtx.Vmm.Map(PA, Size); } - Args.Offset = Vmm.Map(PA, Args.Offset, Size); - } - else - { - Args.Offset = Vmm.Map(PA, Size); - if (Args.Offset < 0) { Args.Offset = 0; - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"No memory to map size {Args.MappingSize:x16}!"); + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Failed to map size 0x{Size:x16}!"); Result = NvResult.InvalidInput; } + else + { + ASCtx.AddMap(Args.Offset, Size, PA, VaAllocated); + } } AMemoryHelper.Write(Context.Memory, OutputPosition, Args); @@ -216,7 +294,7 @@ namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS { NvGpuASRemap Args = AMemoryHelper.Read(Context.Memory, InputPosition); - NvGpuVmm Vmm = GetVmm(Context); + NvGpuVmm Vmm = GetASCtx(Context).Vmm; NvMapHandle Map = NvMapIoctl.GetNvMapWithFb(Context, Args.NvMapHandle); @@ -232,7 +310,8 @@ namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS if (Result < 0) { - Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Page 0x{Args.Offset:x16} size 0x{Args.Pages:x16} not allocated!"); + Context.Ns.Log.PrintWarning(LogClass.ServiceNv, + $"Page 0x{Args.Offset:x16} size 0x{Args.Pages:x16} not allocated!"); return NvResult.InvalidInput; } @@ -241,14 +320,14 @@ namespace Ryujinx.HLE.OsHle.Services.Nv.NvGpuAS return NvResult.Success; } - public static NvGpuVmm GetVmm(ServiceCtx Context) + public static NvGpuASCtx GetASCtx(ServiceCtx Context) { - return Vmms.GetOrAdd(Context.Process, (Key) => new NvGpuVmm(Context.Memory)); + return ASCtxs.GetOrAdd(Context.Process, (Key) => new NvGpuASCtx(Context)); } public static void UnloadProcess(Process Process) { - Vmms.TryRemove(Process, out _); + ASCtxs.TryRemove(Process, out _); } } } \ No newline at end of file diff --git a/Ryujinx.HLE/OsHle/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs b/Ryujinx.HLE/OsHle/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs index 411a579a2f..3e030643e7 100644 --- a/Ryujinx.HLE/OsHle/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs +++ b/Ryujinx.HLE/OsHle/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs @@ -88,7 +88,7 @@ namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostChannel NvHostChannelSubmitGpfifo Args = AMemoryHelper.Read(Context.Memory, InputPosition); - NvGpuVmm Vmm = NvGpuASIoctl.GetVmm(Context); + NvGpuVmm Vmm = NvGpuASIoctl.GetASCtx(Context).Vmm;; for (int Index = 0; Index < Args.NumEntries; Index++) { @@ -162,7 +162,7 @@ namespace Ryujinx.HLE.OsHle.Services.Nv.NvHostChannel NvHostChannelSubmitGpfifo Args = AMemoryHelper.Read(Context.Memory, InputPosition); - NvGpuVmm Vmm = NvGpuASIoctl.GetVmm(Context); + NvGpuVmm Vmm = NvGpuASIoctl.GetASCtx(Context).Vmm;; for (int Index = 0; Index < Args.NumEntries; Index++) {