mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 10:56:20 +08:00
[Kernel] Implement pthread semaphore exports (#424)
This commit is contained in:
@@ -1438,6 +1438,9 @@ public sealed partial class DirectExecutionBackend
|
||||
var expectedMutexTrylockBusy =
|
||||
string.Equals(nid, "K-jXhbt2gn4", StringComparison.Ordinal) &&
|
||||
result == OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
|
||||
var expectedSemaphoreTrywaitAgain =
|
||||
string.Equals(nid, "H2a+IN9TP0E", StringComparison.Ordinal) &&
|
||||
result == OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
|
||||
var expectedNetAcceptWouldBlock =
|
||||
string.Equals(nid, "PIWqhn9oSxc", StringComparison.Ordinal) &&
|
||||
resultValue == unchecked((int)0x80410123);
|
||||
@@ -1451,6 +1454,7 @@ public sealed partial class DirectExecutionBackend
|
||||
!expectedTimedWaitTimeout &&
|
||||
!expectedEqueueTimeout &&
|
||||
!expectedMutexTrylockBusy &&
|
||||
!expectedSemaphoreTrywaitAgain &&
|
||||
!expectedNetAcceptWouldBlock &&
|
||||
!expectedUserServiceNoEvent &&
|
||||
!expectedPrivacyInvalidParameter)
|
||||
|
||||
@@ -428,6 +428,22 @@ public static class KernelSemaphoreCompatExports
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "GEnUkDZoUwY",
|
||||
ExportName = "scePthreadSemInit",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadSemInit(CpuContext ctx)
|
||||
{
|
||||
// scePthreadSemInit(sem, flag, value, name) seems to only support private semaphores
|
||||
if (ctx[CpuRegister.Rsi] != 0)
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
return PosixSemInit(ctx);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "YCV5dGGBcCo",
|
||||
ExportName = "sem_wait",
|
||||
@@ -446,6 +462,13 @@ public static class KernelSemaphoreCompatExports
|
||||
return KernelWaitSema(ctx);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "C36iRE0F5sE",
|
||||
ExportName = "scePthreadSemWait",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadSemWait(CpuContext ctx) => PosixSemWait(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "WBWzsRifCEA",
|
||||
ExportName = "sem_trywait",
|
||||
@@ -463,6 +486,19 @@ public static class KernelSemaphoreCompatExports
|
||||
return KernelPollSema(ctx, handle, 1);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "H2a+IN9TP0E",
|
||||
ExportName = "scePthreadSemTrywait",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadSemTryWait(CpuContext ctx)
|
||||
{
|
||||
var result = PosixSemTryWait(ctx);
|
||||
return result == (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY
|
||||
? SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN)
|
||||
: result;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "w5IHyvahg-o",
|
||||
ExportName = "sem_timedwait",
|
||||
@@ -499,6 +535,13 @@ public static class KernelSemaphoreCompatExports
|
||||
return KernelSignalSema(ctx, handle, 1);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "aishVAiFaYM",
|
||||
ExportName = "scePthreadSemPost",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadSemPost(CpuContext ctx) => PosixSemPost(ctx);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Bq+LRV-N6Hk",
|
||||
ExportName = "sem_getvalue",
|
||||
@@ -549,6 +592,13 @@ public static class KernelSemaphoreCompatExports
|
||||
return result;
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "Vwc+L05e6oE",
|
||||
ExportName = "scePthreadSemDestroy",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libKernel")]
|
||||
public static int PthreadSemDestroy(CpuContext ctx) => PosixSemDestroy(ctx);
|
||||
|
||||
private static bool TryGetPosixSemaphoreHandle(CpuContext ctx, ulong semaphoreAddress, out uint handle)
|
||||
{
|
||||
handle = 0;
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Libs.Kernel;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Libs.Tests.Pthread;
|
||||
|
||||
public sealed class PthreadSemaphoreSemanticsTests
|
||||
{
|
||||
private const ulong MemoryBase = 0x1_0000_0000;
|
||||
private const ulong SemaphoreAddress = MemoryBase + 0x100;
|
||||
private const ulong ValueAddress = MemoryBase + 0x200;
|
||||
|
||||
[Theory]
|
||||
[InlineData("C36iRE0F5sE", "scePthreadSemWait")]
|
||||
[InlineData("aishVAiFaYM", "scePthreadSemPost")]
|
||||
[InlineData("H2a+IN9TP0E", "scePthreadSemTrywait")]
|
||||
[InlineData("GEnUkDZoUwY", "scePthreadSemInit")]
|
||||
[InlineData("Vwc+L05e6oE", "scePthreadSemDestroy")]
|
||||
public void RegistryResolvesPthreadSemaphoreExports(string nid, string exportName)
|
||||
{
|
||||
var manager = new ModuleManager();
|
||||
manager.RegisterExports(SharpEmu.Generated.SysAbiExportRegistry.CreateExports(
|
||||
Generation.Gen4 | Generation.Gen5));
|
||||
|
||||
Assert.True(manager.TryGetExport(nid, out var export));
|
||||
Assert.Equal(exportName, export.Name);
|
||||
Assert.Equal("libKernel", export.LibraryName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PthreadSemPostAndWaitShareSemaphoreState()
|
||||
{
|
||||
var context = CreateContext();
|
||||
InitializeSemaphore(context, initialCount: 0);
|
||||
|
||||
context[CpuRegister.Rdi] = SemaphoreAddress;
|
||||
Assert.Equal(0, KernelSemaphoreCompatExports.PthreadSemPost(context));
|
||||
Assert.Equal(0UL, context[CpuRegister.Rax]);
|
||||
Assert.Equal(1, ReadSemaphoreValue(context));
|
||||
|
||||
context[CpuRegister.Rdi] = SemaphoreAddress;
|
||||
Assert.Equal(0, KernelSemaphoreCompatExports.PthreadSemWait(context));
|
||||
Assert.Equal(0UL, context[CpuRegister.Rax]);
|
||||
Assert.Equal(0, ReadSemaphoreValue(context));
|
||||
|
||||
DestroySemaphore(context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PthreadSemTryWaitConsumesAvailableTokenAndReturnsTryAgainWhenEmpty()
|
||||
{
|
||||
var context = CreateContext();
|
||||
InitializeSemaphore(context, initialCount: 1);
|
||||
|
||||
context[CpuRegister.Rdi] = SemaphoreAddress;
|
||||
Assert.Equal(0, KernelSemaphoreCompatExports.PthreadSemTryWait(context));
|
||||
Assert.Equal(0, ReadSemaphoreValue(context));
|
||||
|
||||
context[CpuRegister.Rdi] = SemaphoreAddress;
|
||||
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN,
|
||||
KernelSemaphoreCompatExports.PthreadSemTryWait(context));
|
||||
Assert.Equal(unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN),
|
||||
context[CpuRegister.Rax]);
|
||||
|
||||
DestroySemaphore(context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PthreadSemInitRejectsNonPrivateFlag()
|
||||
{
|
||||
var context = CreateContext();
|
||||
context[CpuRegister.Rdi] = SemaphoreAddress;
|
||||
context[CpuRegister.Rsi] = 1;
|
||||
context[CpuRegister.Rdx] = 0;
|
||||
context[CpuRegister.Rcx] = 0;
|
||||
|
||||
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT,
|
||||
KernelSemaphoreCompatExports.PthreadSemInit(context));
|
||||
Assert.Equal(unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT),
|
||||
context[CpuRegister.Rax]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PthreadSemDestroyClearsSemaphoreAndRejectsSecondDestroy()
|
||||
{
|
||||
var context = CreateContext();
|
||||
InitializeSemaphore(context, initialCount: 0);
|
||||
|
||||
context[CpuRegister.Rdi] = SemaphoreAddress;
|
||||
Assert.Equal(0, KernelSemaphoreCompatExports.PthreadSemDestroy(context));
|
||||
Assert.True(context.TryReadUInt32(SemaphoreAddress, out var handle));
|
||||
Assert.Equal(0U, handle);
|
||||
|
||||
context[CpuRegister.Rdi] = SemaphoreAddress;
|
||||
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT,
|
||||
KernelSemaphoreCompatExports.PthreadSemDestroy(context));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void PthreadSemOperationRejectsInvalidSemaphore(bool post)
|
||||
{
|
||||
var context = CreateContext();
|
||||
context[CpuRegister.Rdi] = SemaphoreAddress;
|
||||
|
||||
var result = post
|
||||
? KernelSemaphoreCompatExports.PthreadSemPost(context)
|
||||
: KernelSemaphoreCompatExports.PthreadSemWait(context);
|
||||
|
||||
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT, result);
|
||||
Assert.Equal(unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT),
|
||||
context[CpuRegister.Rax]);
|
||||
}
|
||||
|
||||
private static CpuContext CreateContext() => new(new FakeCpuMemory(MemoryBase, 0x1000), Generation.Gen5);
|
||||
|
||||
private static void InitializeSemaphore(CpuContext context, uint initialCount)
|
||||
{
|
||||
context[CpuRegister.Rdi] = SemaphoreAddress;
|
||||
context[CpuRegister.Rsi] = 0;
|
||||
context[CpuRegister.Rdx] = initialCount;
|
||||
context[CpuRegister.Rcx] = 0;
|
||||
Assert.Equal(0, KernelSemaphoreCompatExports.PthreadSemInit(context));
|
||||
}
|
||||
|
||||
private static int ReadSemaphoreValue(CpuContext context)
|
||||
{
|
||||
context[CpuRegister.Rdi] = SemaphoreAddress;
|
||||
context[CpuRegister.Rsi] = ValueAddress;
|
||||
Assert.Equal(0, KernelSemaphoreCompatExports.PosixSemGetValue(context));
|
||||
Assert.True(context.TryReadUInt32(ValueAddress, out var value));
|
||||
return unchecked((int)value);
|
||||
}
|
||||
|
||||
private static void DestroySemaphore(CpuContext context)
|
||||
{
|
||||
context[CpuRegister.Rdi] = SemaphoreAddress;
|
||||
Assert.Equal(0, KernelSemaphoreCompatExports.PthreadSemDestroy(context));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user