From 46a7d3a44615806eef96a757b36796dc41ea6504 Mon Sep 17 00:00:00 2001 From: Usif <19160089+Im-Usif@users.noreply.github.com> Date: Fri, 9 Oct 2026 17:39:51 -0700 Subject: [PATCH] [Kernel] Keep oversized reservations at the guest's hint and sign-extend user event ids (#1073) * [Kernel] Keep an oversized reservation at the guest's hint instead of failing A 768 GB sceKernelReserveVirtualRange with a hint at 0x1000000000 cannot use the sparse aperture (it starts at 256 GB, and 256 GB plus 768 GB passes the 1008 GB user limit), so it needed one contiguous host placeholder. When a host allocation made before the startup reservation sits inside that window, the call fails with 0x8002000C and the guest stops at boot. Reserve such a range at the guest's own hint as a guest-only sparse range, as smaller large reservations already are. * [Kernel] Sign-extend the int id of user events into the event ident sceKernelAddUserEvent, sceKernelAddUserEventEdge, sceKernelDeleteUserEvent and sceKernelTriggerUserEvent take an int id, which the kernel stores in the uintptr_t ident of the event. A negative id such as -1 therefore reads back from sceKernelGetEventId as 0xFFFFFFFFFFFFFFFF. The exports used the zero-extended register (0x00000000FFFFFFFF), so a guest that treats a negative id as a wake-up event took its normal event path instead and never went back to wait. Use the sign-extended id in all four exports. --- .../Kernel/KernelEventQueueCompatExports.cs | 11 +++++++---- ...ernelMemoryCompatExports.BackedMappings.cs | 3 +++ .../Kernel/KernelBackedMemoryTests.cs | 19 +++++++++++++++++++ .../KernelEventQueueCompatExportsTests.cs | 13 ++++++++----- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/SharpEmu.Libs/Kernel/KernelEventQueueCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelEventQueueCompatExports.cs index 061afd8..8861fff 100644 --- a/src/SharpEmu.Libs/Kernel/KernelEventQueueCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelEventQueueCompatExports.cs @@ -558,6 +558,9 @@ public static class KernelEventQueueCompatExports : (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; } + // The user event id is an int that the kernel stores in a uintptr_t ident, so -1 becomes 0xFFFFFFFFFFFFFFFF. + private static ulong UserEventIdent(CpuContext ctx) => unchecked((ulong)(int)ctx[CpuRegister.Rsi]); + [SysAbiExport( Nid = "WDszmSbWuDk", ExportName = "sceKernelAddUserEventEdge", @@ -568,7 +571,7 @@ public static class KernelEventQueueCompatExports var handle = ctx[CpuRegister.Rdi]; var registered = RegisterEvent( handle, - ctx[CpuRegister.Rsi], + UserEventIdent(ctx), KernelEventFilterUser, 0, KernelEventFlagClear); @@ -588,7 +591,7 @@ public static class KernelEventQueueCompatExports var handle = ctx[CpuRegister.Rdi]; var registered = RegisterEvent( handle, - ctx[CpuRegister.Rsi], + UserEventIdent(ctx), KernelEventFilterUser, 0, flags: 0); @@ -608,7 +611,7 @@ public static class KernelEventQueueCompatExports var handle = ctx[CpuRegister.Rdi]; var deleted = DeleteRegisteredEvent( handle, - ctx[CpuRegister.Rsi], + UserEventIdent(ctx), KernelEventFilterUser); TraceEventQueue(ctx, "delete_user", handle); return deleted @@ -626,7 +629,7 @@ public static class KernelEventQueueCompatExports var handle = ctx[CpuRegister.Rdi]; var triggered = TriggerRegisteredEvent( handle, - ctx[CpuRegister.Rsi], + UserEventIdent(ctx), KernelEventFilterUser, userData: ctx[CpuRegister.Rdx]); TraceEventQueue(ctx, "trigger_user", handle); diff --git a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.BackedMappings.cs b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.BackedMappings.cs index 1ae18e6..80fae25 100644 --- a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.BackedMappings.cs +++ b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.BackedMappings.cs @@ -494,6 +494,9 @@ public static partial class KernelMemoryCompatExports if (candidate > UserAddressLimit - length) { candidate = AlignUp(SparseReservationBase, alignment); + // A range too large for the aperture can only live at the guest's own hint. + if (candidate > UserAddressLimit - length) + candidate = AlignUp(requested, alignment); if (candidate == 0 || candidate > UserAddressLimit - length) return false; } diff --git a/tests/SharpEmu.Libs.Tests/Kernel/KernelBackedMemoryTests.cs b/tests/SharpEmu.Libs.Tests/Kernel/KernelBackedMemoryTests.cs index 823ba48..643c478 100644 --- a/tests/SharpEmu.Libs.Tests/Kernel/KernelBackedMemoryTests.cs +++ b/tests/SharpEmu.Libs.Tests/Kernel/KernelBackedMemoryTests.cs @@ -318,6 +318,25 @@ public sealed class KernelBackedMemoryTests Assert.Equal((address, address + 0x4000), test.Query(address)); } + [Fact] + public void ReservationTooLargeForTheSparseApertureKeepsTheGuestHint() + { + using var test = new BackedKernelMemory(); + const ulong hint = 0x1000000000; + const ulong size = 0xC000000000; + test.Host.FailReserveHoleWhen = (_, requestedSize) => requestedSize >= size; + Assert.True(test.Context.TryWriteUInt64(test.Output, hint)); + test.Context[CpuRegister.Rdi] = test.Output; + test.Context[CpuRegister.Rsi] = size; + test.Context[CpuRegister.Rdx] = 0; + test.Context[CpuRegister.Rcx] = 0x200000; + + Assert.Equal(0, KernelRuntimeCompatExports.KernelReserveVirtualRange(test.Context)); + Assert.True(test.Context.TryReadUInt64(test.Output, out var address)); + Assert.Equal(hint, address); + Assert.Equal((hint, hint + size), test.Query(hint)); + } + [Fact] public void AddressSearchUsesTheFullReservationExtent() { diff --git a/tests/SharpEmu.Libs.Tests/Kernel/KernelEventQueueCompatExportsTests.cs b/tests/SharpEmu.Libs.Tests/Kernel/KernelEventQueueCompatExportsTests.cs index 22de16e..33f7a94 100644 --- a/tests/SharpEmu.Libs.Tests/Kernel/KernelEventQueueCompatExportsTests.cs +++ b/tests/SharpEmu.Libs.Tests/Kernel/KernelEventQueueCompatExportsTests.cs @@ -110,24 +110,27 @@ public sealed class KernelEventQueueCompatExportsTests // the queued event with the registered ident/filter and the trigger data. // DequeueEvents runs before the blocking path, so a pre-triggered queue // returns immediately without touching the guest thread scheduler. - [Fact] - public void CreateAddTriggerWait_DeliversTriggeredUserEvent() + // The id is an int that the kernel stores in a uintptr_t ident, so -1 (0xFFFFFFFF in the register) + // is delivered as 0xFFFFFFFFFFFFFFFF. + [Theory] + [InlineData(0x4242UL, 0x4242UL)] + [InlineData(0xFFFF_FFFFUL, ulong.MaxValue)] + public void CreateAddTriggerWait_DeliversTriggeredUserEvent(ulong id, ulong eventIdent) { - const ulong eventIdent = 0x4242; const ulong triggerData = 0x55AA_55AA; var handle = CreateEqueue(); // Register the user event on the queue. var (addCtx, _) = NewContextWithOutSlot(); addCtx[CpuRegister.Rdi] = handle; - addCtx[CpuRegister.Rsi] = eventIdent; + addCtx[CpuRegister.Rsi] = id; Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, KernelEventQueueCompatExports.KernelAddUserEvent(addCtx)); // Trigger it with a distinct data payload. var (triggerCtx, _) = NewContextWithOutSlot(); triggerCtx[CpuRegister.Rdi] = handle; - triggerCtx[CpuRegister.Rsi] = eventIdent; + triggerCtx[CpuRegister.Rsi] = id; triggerCtx[CpuRegister.Rdx] = triggerData; Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, KernelEventQueueCompatExports.KernelTriggerUserEvent(triggerCtx));