[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.
This commit is contained in:
Usif 2026-10-09 17:39:51 -07:00 committed by GitHub
parent f32266834c
commit 46a7d3a446
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 37 additions and 9 deletions

View File

@ -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);

View File

@ -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;
}

View File

@ -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()
{

View File

@ -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));