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 =
|
var expectedMutexTrylockBusy =
|
||||||
string.Equals(nid, "K-jXhbt2gn4", StringComparison.Ordinal) &&
|
string.Equals(nid, "K-jXhbt2gn4", StringComparison.Ordinal) &&
|
||||||
result == OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
|
result == OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
|
||||||
|
var expectedSemaphoreTrywaitAgain =
|
||||||
|
string.Equals(nid, "H2a+IN9TP0E", StringComparison.Ordinal) &&
|
||||||
|
result == OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
|
||||||
var expectedNetAcceptWouldBlock =
|
var expectedNetAcceptWouldBlock =
|
||||||
string.Equals(nid, "PIWqhn9oSxc", StringComparison.Ordinal) &&
|
string.Equals(nid, "PIWqhn9oSxc", StringComparison.Ordinal) &&
|
||||||
resultValue == unchecked((int)0x80410123);
|
resultValue == unchecked((int)0x80410123);
|
||||||
@@ -1451,6 +1454,7 @@ public sealed partial class DirectExecutionBackend
|
|||||||
!expectedTimedWaitTimeout &&
|
!expectedTimedWaitTimeout &&
|
||||||
!expectedEqueueTimeout &&
|
!expectedEqueueTimeout &&
|
||||||
!expectedMutexTrylockBusy &&
|
!expectedMutexTrylockBusy &&
|
||||||
|
!expectedSemaphoreTrywaitAgain &&
|
||||||
!expectedNetAcceptWouldBlock &&
|
!expectedNetAcceptWouldBlock &&
|
||||||
!expectedUserServiceNoEvent &&
|
!expectedUserServiceNoEvent &&
|
||||||
!expectedPrivacyInvalidParameter)
|
!expectedPrivacyInvalidParameter)
|
||||||
|
|||||||
@@ -428,6 +428,22 @@ public static class KernelSemaphoreCompatExports
|
|||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
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(
|
[SysAbiExport(
|
||||||
Nid = "YCV5dGGBcCo",
|
Nid = "YCV5dGGBcCo",
|
||||||
ExportName = "sem_wait",
|
ExportName = "sem_wait",
|
||||||
@@ -446,6 +462,13 @@ public static class KernelSemaphoreCompatExports
|
|||||||
return KernelWaitSema(ctx);
|
return KernelWaitSema(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "C36iRE0F5sE",
|
||||||
|
ExportName = "scePthreadSemWait",
|
||||||
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
|
LibraryName = "libKernel")]
|
||||||
|
public static int PthreadSemWait(CpuContext ctx) => PosixSemWait(ctx);
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "WBWzsRifCEA",
|
Nid = "WBWzsRifCEA",
|
||||||
ExportName = "sem_trywait",
|
ExportName = "sem_trywait",
|
||||||
@@ -463,6 +486,19 @@ public static class KernelSemaphoreCompatExports
|
|||||||
return KernelPollSema(ctx, handle, 1);
|
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(
|
[SysAbiExport(
|
||||||
Nid = "w5IHyvahg-o",
|
Nid = "w5IHyvahg-o",
|
||||||
ExportName = "sem_timedwait",
|
ExportName = "sem_timedwait",
|
||||||
@@ -499,6 +535,13 @@ public static class KernelSemaphoreCompatExports
|
|||||||
return KernelSignalSema(ctx, handle, 1);
|
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(
|
[SysAbiExport(
|
||||||
Nid = "Bq+LRV-N6Hk",
|
Nid = "Bq+LRV-N6Hk",
|
||||||
ExportName = "sem_getvalue",
|
ExportName = "sem_getvalue",
|
||||||
@@ -549,6 +592,13 @@ public static class KernelSemaphoreCompatExports
|
|||||||
return result;
|
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)
|
private static bool TryGetPosixSemaphoreHandle(CpuContext ctx, ulong semaphoreAddress, out uint handle)
|
||||||
{
|
{
|
||||||
handle = 0;
|
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