Add sceKernelNanosleep to libKernel (#72)

Implements the sceKernelNanosleep export (NID QvsZxomvUHs) for both Gen4
and Gen5 targets. Reads the requested timespec from guest memory,
validates the pointer and tv_nsec range, sleeps for the requested
duration, and zeroes the optional remaining-time struct on completion.

Also fixes: reading rqtp as a guest pointer to a timespec (tv_sec/tv_nsec
int64 pair) instead of raw register values, and keeps the optimized
sceKernelUsleep short-sleep path untouched.

Co-authored-by: par274 <par274@users.noreply.github.com>
This commit is contained in:
j92580498-max
2026-07-11 23:42:26 +03:00
committed by GitHub
parent 5e76554514
commit de4fc1e1a8

View File

@@ -24,6 +24,7 @@ public static class KernelRuntimeCompatExports
internal const int ClockRealtimeFast = 10;
internal const int ClockMonotonicFast = 12;
private const int Efault = 14;
private const int Einval = 22;
private const ulong TlsErrnoOffset = 0x40;
private const ulong TlsStackChkGuardBaseOffset = 0x800;
private const ulong StackChkGuardFieldOffset = 0x10;
@@ -74,6 +75,77 @@ public static class KernelRuntimeCompatExports
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate ulong RdtscDelegate();
[SysAbiExport(
Nid = "QvsZxomvUHs",
ExportName = "sceKernelNanosleep",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelNanosleep(CpuContext ctx)
{
var requestAddress = ctx[CpuRegister.Rdi];
var remainAddress = ctx[CpuRegister.Rsi];
if (requestAddress == 0)
{
ctx[CpuRegister.Rax] = Einval;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
Span<byte> timespecBuffer = stackalloc byte[16];
if (!ctx.Memory.TryRead(requestAddress, timespecBuffer))
{
ctx[CpuRegister.Rax] = Efault;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var tvSec = BinaryPrimitives.ReadInt64LittleEndian(timespecBuffer);
var tvNsec = BinaryPrimitives.ReadInt64LittleEndian(timespecBuffer[sizeof(long)..]);
if (tvSec < 0 || tvNsec < 0 || tvNsec >= 1_000_000_000L)
{
ctx[CpuRegister.Rax] = Einval;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (tvSec == 0 && tvNsec == 0)
{
WriteRemainingTime(ctx, remainAddress, 0, 0);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
GuestThreadExecution.Scheduler?.Pump(ctx, "sceKernelNanosleep");
// TimeSpan resolution is 100 ns ticks, so sub-100 ns requests round up to
// a single tick rather than collapsing to a zero-length (no-op) sleep.
var totalTicks = tvSec * TimeSpan.TicksPerSecond + Math.Max(tvNsec / 100L, 1L);
try
{
Thread.Sleep(TimeSpan.FromTicks(totalTicks));
}
catch (ArgumentOutOfRangeException)
{
Thread.Sleep(TimeSpan.FromMilliseconds(int.MaxValue));
}
WriteRemainingTime(ctx, remainAddress, 0, 0);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static void WriteRemainingTime(CpuContext ctx, ulong remainAddress, long seconds, long nanoseconds)
{
if (remainAddress == 0)
{
return;
}
Span<byte> remainBuffer = stackalloc byte[16];
BinaryPrimitives.WriteInt64LittleEndian(remainBuffer, seconds);
BinaryPrimitives.WriteInt64LittleEndian(remainBuffer[sizeof(long)..], nanoseconds);
ctx.Memory.TryWrite(remainAddress, remainBuffer);
}
[SysAbiExport(
Nid = "1jfXLRVzisc",
ExportName = "sceKernelUsleep",