diff --git a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs index 3cc0a200..87587d58 100644 --- a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs @@ -2459,7 +2459,20 @@ public static partial class KernelMemoryCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } - if (!TryFindAvailableDirectMemorySpanLocked(searchStart, searchEnd, alignment, out var candidate, out var rangeAvailable)) + bool foundSpan; + ulong candidate; + ulong rangeAvailable; + lock (_memoryGate) + { + foundSpan = TryFindAvailableDirectMemorySpanLocked( + searchStart, + searchEnd, + alignment, + out candidate, + out rangeAvailable); + } + + if (!foundSpan) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; } @@ -5909,9 +5922,12 @@ public static partial class KernelMemoryCompatExports var gapEnd = Math.Min(allocation.Start, effectiveEnd); if (candidate < gapEnd) { - spanStart = candidate; - spanLength = gapEnd - candidate; - return true; + var candidateLength = gapEnd - candidate; + if (candidateLength > spanLength) + { + spanStart = candidate; + spanLength = candidateLength; + } } if (allocation.Start >= effectiveEnd) @@ -5922,12 +5938,20 @@ public static partial class KernelMemoryCompatExports candidate = AlignUp(Math.Max(candidate, allocationEnd), alignment); if (candidate >= effectiveEnd) { - return false; + break; + } + } + + if (candidate < effectiveEnd) + { + var candidateLength = effectiveEnd - candidate; + if (candidateLength > spanLength) + { + spanStart = candidate; + spanLength = candidateLength; } } - spanStart = candidate; - spanLength = effectiveEnd - candidate; return spanLength != 0; } diff --git a/tests/SharpEmu.Libs.Tests/Kernel/KernelMemoryCompatExportsTests.cs b/tests/SharpEmu.Libs.Tests/Kernel/KernelMemoryCompatExportsTests.cs index 5e342083..d2d7510b 100644 --- a/tests/SharpEmu.Libs.Tests/Kernel/KernelMemoryCompatExportsTests.cs +++ b/tests/SharpEmu.Libs.Tests/Kernel/KernelMemoryCompatExportsTests.cs @@ -9,8 +9,20 @@ using Xunit; namespace SharpEmu.Libs.Tests.Kernel; +[CollectionDefinition(KernelMemoryCompatStateCollection.Name, DisableParallelization = true)] +public sealed class KernelMemoryCompatStateCollection +{ + public const string Name = "KernelMemoryCompatState"; +} + +[Collection(KernelMemoryCompatStateCollection.Name)] public sealed class KernelMemoryCompatExportsTests { + private const ulong GuestMemoryBase = 0x1_0000_0000; + private const ulong AllocationOutAddress = GuestMemoryBase + 0x100; + private const ulong SpanStartOutAddress = GuestMemoryBase + 0x108; + private const ulong SpanSizeOutAddress = GuestMemoryBase + 0x110; + [Fact] public void PosixStat_MissingFileReturnsMinusOne() { @@ -63,4 +75,93 @@ public sealed class KernelMemoryCompatExportsTests CultureInfo.CurrentCulture = previousCulture; } } + + [Fact] + public void AvailableDirectMemorySize_FragmentedRangeReturnsLargestAlignedSpan() + { + const ulong firstAllocationStart = 0x0020_0000; + const ulong firstAllocationLength = 0x0020_0000; + const ulong secondAllocationStart = 0x00C0_0000; + const ulong secondAllocationLength = 0x0040_0000; + var context = new CpuContext(new FakeCpuMemory(GuestMemoryBase, 0x1000), Generation.Gen5); + + try + { + AllocateDirectMemory(context, firstAllocationStart, firstAllocationLength); + AllocateDirectMemory(context, secondAllocationStart, secondAllocationLength); + + QueryAvailableDirectMemory(context, 0, 0x0100_0000, 0x4000); + + Assert.True(context.TryReadUInt64(SpanStartOutAddress, out var spanStart)); + Assert.True(context.TryReadUInt64(SpanSizeOutAddress, out var spanSize)); + Assert.Equal(0x0040_0000UL, spanStart); + Assert.Equal(0x0080_0000UL, spanSize); + } + finally + { + ReleaseDirectMemory(context, firstAllocationStart, firstAllocationLength); + ReleaseDirectMemory(context, secondAllocationStart, secondAllocationLength); + } + } + + [Fact] + public void AvailableDirectMemorySize_AppliesAlignmentBeforeComparingSpans() + { + const ulong allocationStart = 0x0070_0000; + const ulong allocationLength = 0x0010_0000; + var context = new CpuContext(new FakeCpuMemory(GuestMemoryBase, 0x1000), Generation.Gen5); + + try + { + AllocateDirectMemory(context, allocationStart, allocationLength); + + QueryAvailableDirectMemory(context, 0x0010_0000, 0x00C0_0000, 0x0040_0000); + + Assert.True(context.TryReadUInt64(SpanStartOutAddress, out var spanStart)); + Assert.True(context.TryReadUInt64(SpanSizeOutAddress, out var spanSize)); + Assert.Equal(0x0080_0000UL, spanStart); + Assert.Equal(0x0040_0000UL, spanSize); + } + finally + { + ReleaseDirectMemory(context, allocationStart, allocationLength); + } + } + + private static void AllocateDirectMemory(CpuContext context, ulong start, ulong length) + { + context[CpuRegister.Rdi] = start; + context[CpuRegister.Rsi] = start + length; + context[CpuRegister.Rdx] = length; + context[CpuRegister.Rcx] = 0x4000; + context[CpuRegister.R8] = 0; + context[CpuRegister.R9] = AllocationOutAddress; + + Assert.Equal(0, KernelMemoryCompatExports.KernelAllocateDirectMemory(context)); + Assert.True(context.TryReadUInt64(AllocationOutAddress, out var allocatedAddress)); + Assert.Equal(start, allocatedAddress); + } + + private static void QueryAvailableDirectMemory( + CpuContext context, + ulong searchStart, + ulong searchEnd, + ulong alignment) + { + context[CpuRegister.Rdi] = searchStart; + context[CpuRegister.Rsi] = searchEnd; + context[CpuRegister.Rdx] = alignment; + context[CpuRegister.Rcx] = SpanStartOutAddress; + context[CpuRegister.R8] = SpanSizeOutAddress; + + Assert.Equal(0, KernelMemoryCompatExports.KernelAvailableDirectMemorySize(context)); + } + + private static void ReleaseDirectMemory(CpuContext context, ulong start, ulong length) + { + context[CpuRegister.Rdi] = start; + context[CpuRegister.Rsi] = length; + + Assert.Equal(0, KernelMemoryCompatExports.KernelReleaseDirectMemory(context)); + } }