diff --git a/tests/TestSvc/Makefile b/tests/TestSvc/Makefile
index 1957b6cdb..bf1bef941 100644
--- a/tests/TestSvc/Makefile
+++ b/tests/TestSvc/Makefile
@@ -86,12 +86,21 @@ DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
-all : $(OUTPUT).kip
+all : $(OUTPUT).kip $(OUTPUT).nsp
+
+$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
+
+$(OUTPUT).nso : $(OUTPUT).elf
$(OUTPUT).kip : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
+$(OUTPUT).npdm : $(OUTPUT).npdm.json
+ @echo built ... $< $@
+ @npdmtool $< $@
+ @echo built ... $(notdir $@)
+
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
diff --git a/tests/TestSvc/TestSvc.npdm.json b/tests/TestSvc/TestSvc.npdm.json
new file mode 100644
index 000000000..9592ba60d
--- /dev/null
+++ b/tests/TestSvc/TestSvc.npdm.json
@@ -0,0 +1,147 @@
+{
+ "name": "TestSvc",
+ "title_id": "0x5555555555555555",
+ "title_id_range_min": "0x5555555555555555",
+ "title_id_range_max": "0x5555555555555555",
+ "main_thread_stack_size": "0x8000",
+ "main_thread_priority": 28,
+ "default_cpu_id": 3,
+ "process_category": 0,
+ "is_retail": true,
+ "pool_partition": 2,
+ "is_64_bit": true,
+ "address_space_type": 3,
+ "disable_device_address_space_merge": true,
+ "filesystem_access": {
+ "permissions": "0xFFFFFFFFFFFFFFFF"
+ },
+ "service_access": ["*"],
+ "service_host": ["*"],
+ "kernel_capabilities": [
+ {
+ "type": "kernel_flags",
+ "value": {
+ "highest_thread_priority": 63,
+ "lowest_thread_priority": 16,
+ "lowest_cpu_id": 0,
+ "highest_cpu_id": 3
+ }
+ },
+ {
+ "type": "handle_table_size",
+ "value": 0
+ },
+ {
+ "type": "syscalls",
+ "value": {
+ "svcUnknown00": "0x00",
+ "svcSetHeapSize": "0x01",
+ "svcSetMemoryPermission": "0x02",
+ "svcSetMemoryAttribute": "0x03",
+ "svcMapMemory": "0x04",
+ "svcUnmapMemory": "0x05",
+ "svcQueryMemory": "0x06",
+ "svcExitProcess": "0x07",
+ "svcCreateThread": "0x08",
+ "svcStartThread": "0x09",
+ "svcExitThread": "0x0A",
+ "svcSleepThread": "0x0B",
+ "svcGetThreadPriority": "0x0C",
+ "svcSetThreadPriority": "0x0D",
+ "svcGetThreadCoreMask": "0x0E",
+ "svcSetThreadCoreMask": "0x0F",
+ "svcGetCurrentProcessorNumber": "0x10",
+ "svcSignalEvent": "0x11",
+ "svcClearEvent": "0x12",
+ "svcMapSharedMemory": "0x13",
+ "svcUnmapSharedMemory": "0x14",
+ "svcCreateTransferMemory": "0x15",
+ "svcCloseHandle": "0x16",
+ "svcResetSignal": "0x17",
+ "svcWaitSynchronization": "0x18",
+ "svcCancelSynchronization": "0x19",
+ "svcArbitrateLock": "0x1A",
+ "svcArbitrateUnlock": "0x1B",
+ "svcWaitProcessWideKeyAtomic": "0x1C",
+ "svcSignalProcessWideKey": "0x1D",
+ "svcGetSystemTick": "0x1E",
+ "svcConnectToNamedPort": "0x1F",
+ "svcSendSyncRequestLight": "0x20",
+ "svcSendSyncRequest": "0x21",
+ "svcSendSyncRequestWithUserBuffer": "0x22",
+ "svcSendAsyncRequestWithUserBuffer": "0x23",
+ "svcGetProcessId": "0x24",
+ "svcGetThreadId": "0x25",
+ "svcBreak": "0x26",
+ "svcOutputDebugString": "0x27",
+ "svcReturnFromException": "0x28",
+ "svcGetInfo": "0x29",
+ "svcFlushEntireDataCache": "0x2A",
+ "svcFlushDataCache": "0x2B",
+ "svcMapPhysicalMemory": "0x2C",
+ "svcUnmapPhysicalMemory": "0x2D",
+ "svcGetDebugFutureThreadInfo": "0x2E",
+ "svcGetLastThreadInfo": "0x2F",
+ "svcGetResourceLimitLimitValue": "0x30",
+ "svcGetResourceLimitCurrentValue": "0x31",
+ "svcSetThreadActivity": "0x32",
+ "svcGetThreadContext3": "0x33",
+ "svcWaitForAddress": "0x34",
+ "svcSignalToAddress": "0x35",
+ "svcSynchronizePreemptionState": "0x36",
+ "svcGetResourceLimitPeakValue": "0x37",
+ "svcUnknown38": "0x38",
+ "svcUnknown39": "0x39",
+ "svcUnknown3a": "0x3A",
+ "svcUnknown3b": "0x3B",
+ "svcKernelDebug": "0x3C",
+ "svcChangeKernelTraceState": "0x3D",
+ "svcUnknown3e": "0x3E",
+ "svcUnknown3f": "0x3F",
+ "svcCreateSession": "0x40",
+ "svcAcceptSession": "0x41",
+ "svcReplyAndReceiveLight": "0x42",
+ "svcReplyAndReceive": "0x43",
+ "svcReplyAndReceiveWithUserBuffer": "0x44",
+ "svcCreateEvent": "0x45",
+ "svcUnknown46": "0x46",
+ "svcUnknown47": "0x47",
+ "svcMapPhysicalMemoryUnsafe": "0x48",
+ "svcUnmapPhysicalMemoryUnsafe": "0x49",
+ "svcSetUnsafeLimit": "0x4A",
+ "svcCreateCodeMemory": "0x4B",
+ "svcControlCodeMemory": "0x4C",
+ "svcSleepSystem": "0x4D",
+ "svcReadWriteRegister": "0x4E",
+ "svcSetProcessActivity": "0x4F",
+ "svcCreateSharedMemory": "0x50",
+ "svcMapTransferMemory": "0x51",
+ "svcUnmapTransferMemory": "0x52",
+ "svcQueryIoMapping": "0x55",
+ "svcDebugActiveProcess": "0x60",
+ "svcBreakDebugProcess": "0x61",
+ "svcTerminateDebugProcess": "0x62",
+ "svcGetDebugEvent": "0x63",
+ "svcContinueDebugEvent": "0x64",
+ "svcGetProcessList": "0x65",
+ "svcGetThreadList": "0x66",
+ "svcGetDebugThreadContext": "0x67",
+ "svcSetDebugThreadContext": "0x68",
+ "svcQueryDebugProcessMemory": "0x69",
+ "svcReadDebugProcessMemory": "0x6A",
+ "svcWriteDebugProcessMemory": "0x6B",
+ "svcSetHardwareBreakPoint": "0x6C",
+ "svcGetDebugThreadParam": "0x6D",
+ "svcGetSystemInfo": "0x6F",
+ "svcConnectToPort": "0x72",
+ "svcSetProcessMemoryPermission": "0x73",
+ "svcMapProcessMemory": "0x74",
+ "svcUnmapProcessMemory": "0x75",
+ "svcQueryProcessMemory": "0x76",
+ "svcMapProcessCodeMemory": "0x77",
+ "svcUnmapProcessCodeMemory": "0x78",
+ "svcCallSecureMonitor": "0x7F"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/TestSvc/source/test_main.cpp b/tests/TestSvc/source/test_main.cpp
index b01013832..7c400c9c3 100644
--- a/tests/TestSvc/source/test_main.cpp
+++ b/tests/TestSvc/source/test_main.cpp
@@ -54,6 +54,13 @@ namespace ams {
}
void Main() {
+ /* Ensure our thread priority and core mask is correct. */
+ {
+ auto * const cur_thread = os::GetCurrentThread();
+ os::SetThreadCoreMask(cur_thread, 3, (1ul << 3));
+ os::ChangeThreadPriority(cur_thread, 0);
+ }
+
/* Run tests. */
Catch::Session().run(os::GetHostArgc(), os::GetHostArgv());
diff --git a/tests/TestSvc/source/test_preemption_priority.cpp b/tests/TestSvc/source/test_preemption_priority.cpp
new file mode 100644
index 000000000..cb732aa8a
--- /dev/null
+++ b/tests/TestSvc/source/test_preemption_priority.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#include
+#include "util_common.hpp"
+#include "util_scoped_heap.hpp"
+
+namespace ams::test {
+
+ namespace {
+
+ constinit volatile bool g_spinloop;
+
+ void TestPreemptionPriorityThreadFunction(volatile bool *executed) {
+ /* While we should, note that we're executing. */
+ while (g_spinloop) {
+ __asm__ __volatile__("" ::: "memory");
+ *executed = true;
+ __asm__ __volatile__("" ::: "memory");
+ }
+
+ /* Exit the thread. */
+ svc::ExitThread();
+ }
+
+ }
+
+ CATCH_TEST_CASE( "The scheduler is preemptive at the preemptive priority and cooperative for all other priorities" ) {
+ /* Create heap. */
+ ScopedHeap heap(3 * os::MemoryPageSize);
+ CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(heap.GetAddress() + os::MemoryPageSize, os::MemoryPageSize, svc::MemoryPermission_None)));
+ ON_SCOPE_EXIT {
+ CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(heap.GetAddress() + os::MemoryPageSize, os::MemoryPageSize, svc::MemoryPermission_ReadWrite)));
+ };
+ const uintptr_t sp_0 = heap.GetAddress() + 1 * os::MemoryPageSize;
+ const uintptr_t sp_1 = heap.GetAddress() + 3 * os::MemoryPageSize;
+
+ for (s32 core = 0; core < NumCores; ++core) {
+ for (s32 priority = HighestTestPriority; priority <= LowestTestPriority; ++priority) {
+ svc::Handle thread_handles[2];
+ volatile bool thread_executed[2] = { false, false };
+
+ /* Start spinlooping. */
+ g_spinloop = true;
+
+ /* Create threads. */
+ CATCH_REQUIRE(R_SUCCEEDED(svc::CreateThread(thread_handles + 0, reinterpret_cast(&TestPreemptionPriorityThreadFunction), reinterpret_cast(thread_executed + 0), sp_0, priority, core)));
+ CATCH_REQUIRE(R_SUCCEEDED(svc::CreateThread(thread_handles + 1, reinterpret_cast(&TestPreemptionPriorityThreadFunction), reinterpret_cast(thread_executed + 1), sp_1, priority, core)));
+
+ /* Start threads. */
+ CATCH_REQUIRE(R_SUCCEEDED(svc::StartThread(thread_handles[0])));
+ CATCH_REQUIRE(R_SUCCEEDED(svc::StartThread(thread_handles[1])));
+
+ /* Wait long enough that we can be confident the threads have been balanced. */
+ svc::SleepThread(PreemptionTimeSpan.GetNanoSeconds() * 10);
+
+ /* Check that we're in a coherent state. */
+ if (IsPreemptionPriority(core, priority)) {
+ CATCH_REQUIRE(thread_executed[0] & thread_executed[1]);
+ } else {
+ CATCH_REQUIRE(thread_executed[0] ^ thread_executed[1]);
+ }
+
+ /* Stop spinlooping. */
+ g_spinloop = false;
+
+ /* Wait for threads to exit. */
+ s32 dummy;
+ CATCH_REQUIRE(R_SUCCEEDED(svc::WaitSynchronization(std::addressof(dummy), thread_handles + 0, 1, -1)));
+ CATCH_REQUIRE(R_SUCCEEDED(svc::WaitSynchronization(std::addressof(dummy), thread_handles + 1, 1, -1)));
+
+ /* Close thread handles. */
+ CATCH_REQUIRE(R_SUCCEEDED(svc::CloseHandle(thread_handles[0])));
+ CATCH_REQUIRE(R_SUCCEEDED(svc::CloseHandle(thread_handles[1])));
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/tests/TestSvc/source/test_set_heap_size.cpp b/tests/TestSvc/source/test_set_heap_size.cpp
index 36d05d197..52e6c0276 100644
--- a/tests/TestSvc/source/test_set_heap_size.cpp
+++ b/tests/TestSvc/source/test_set_heap_size.cpp
@@ -14,7 +14,7 @@
* along with this program. If not, see .
*/
#include
-#include "util_catch.hpp"
+#include "util_common.hpp"
#include "util_check_memory.hpp"
namespace ams::test {
diff --git a/tests/TestSvc/source/test_set_memory_permission.cpp b/tests/TestSvc/source/test_set_memory_permission.cpp
index 50177554a..9ba566f58 100644
--- a/tests/TestSvc/source/test_set_memory_permission.cpp
+++ b/tests/TestSvc/source/test_set_memory_permission.cpp
@@ -14,7 +14,7 @@
* along with this program. If not, see .
*/
#include
-#include "util_catch.hpp"
+#include "util_common.hpp"
#include "util_check_memory.hpp"
#include "util_scoped_heap.hpp"
diff --git a/tests/TestSvc/source/test_sleep_thread.cpp b/tests/TestSvc/source/test_sleep_thread.cpp
index 03cc5dbec..105e225bd 100644
--- a/tests/TestSvc/source/test_sleep_thread.cpp
+++ b/tests/TestSvc/source/test_sleep_thread.cpp
@@ -14,15 +14,136 @@
* along with this program. If not, see .
*/
#include
-
-#define CATCH_CONFIG_NOSTDOUT
-#define CATCH_CONFIG_PREFIX_ALL
-#define CATCH_CONFIG_DISABLE_EXCEPTIONS
-#define CATCH_CONFIG_NO_POSIX_SIGNALS
-#include "catch.hpp"
+#include "util_common.hpp"
+#include "util_scoped_heap.hpp"
namespace ams::test {
+ namespace {
+
+ constinit svc::Handle g_read_handles[3] = { svc::InvalidHandle, svc::InvalidHandle, svc::InvalidHandle };
+ constinit svc::Handle g_write_handles[3] = { svc::InvalidHandle, svc::InvalidHandle, svc::InvalidHandle };
+
+ constinit s64 g_thread_wait_ns;
+ constinit bool g_should_switch_threads;
+ constinit bool g_switched_threads;
+ constinit bool g_correct_switch_threads;
+
+ void WaitSynchronization(svc::Handle handle) {
+ s32 dummy;
+ R_ABORT_UNLESS(svc::WaitSynchronization(std::addressof(dummy), std::addressof(handle), 1, -1));
+ }
+
+ void TestYieldHigherOrSamePriorityThread() {
+ /* Wait to run. */
+ WaitSynchronization(g_read_handles[0]);
+
+ /* Reset our event. */
+ R_ABORT_UNLESS(svc::ClearEvent(g_read_handles[0]));
+
+ /* Signal the other thread's event. */
+ R_ABORT_UNLESS(svc::SignalEvent(g_write_handles[1]));
+
+ /* Wait, potentially yielding to the lower/same priority thread. */
+ g_switched_threads = false;
+ svc::SleepThread(g_thread_wait_ns);
+
+ /* Check whether we switched correctly. */
+ g_correct_switch_threads = g_should_switch_threads == g_switched_threads;
+
+ /* Exit. */
+ svc::ExitThread();
+ }
+
+ void TestYieldLowerOrSamePriorityThread() {
+ /* Signal thread the higher/same priority thread to run. */
+ R_ABORT_UNLESS(svc::SignalEvent(g_write_handles[0]));
+
+ /* Wait to run. */
+ WaitSynchronization(g_read_handles[1]);
+
+ /* Reset our event. */
+ R_ABORT_UNLESS(svc::ClearEvent(g_read_handles[1]));
+
+ /* We've switched to the lower/same priority thread. */
+ g_switched_threads = true;
+
+ /* Wait to be instructed to exit. */
+ WaitSynchronization(g_read_handles[2]);
+
+ /* Reset the exit signal. */
+ R_ABORT_UNLESS(svc::ClearEvent(g_read_handles[2]));
+
+ /* Exit. */
+ svc::ExitThread();
+ }
+
+ void TestYieldSamePriority(uintptr_t sp_higher, uintptr_t sp_lower) {
+ /* Test each core. */
+ for (s32 core = 0; core < NumCores; ++core) {
+ for (s32 priority = HighestTestPriority; priority <= LowestTestPriority && !IsPreemptionPriority(core, priority); ++priority) {
+
+ svc::Handle thread_handles[2];
+
+ /* Create threads. */
+ CATCH_REQUIRE(R_SUCCEEDED(svc::CreateThread(thread_handles + 0, reinterpret_cast(&TestYieldHigherOrSamePriorityThread), 0, sp_higher, priority, core)));
+ CATCH_REQUIRE(R_SUCCEEDED(svc::CreateThread(thread_handles + 1, reinterpret_cast(&TestYieldLowerOrSamePriorityThread), 0, sp_lower, priority, core)));
+
+ /* Start threads. */
+ CATCH_REQUIRE(R_SUCCEEDED(svc::StartThread(thread_handles[1])));
+ CATCH_REQUIRE(R_SUCCEEDED(svc::StartThread(thread_handles[0])));
+
+ /* Wait for higher priority thread. */
+ WaitSynchronization(thread_handles[0]);
+ CATCH_REQUIRE(R_SUCCEEDED(svc::CloseHandle(thread_handles[0])));
+
+ /* Signal the lower priority thread to exit. */
+ CATCH_REQUIRE(R_SUCCEEDED(svc::SignalEvent(g_write_handles[2])));
+
+ /* Wait for the lower priority thread. */
+ WaitSynchronization(thread_handles[1]);
+ CATCH_REQUIRE(R_SUCCEEDED(svc::CloseHandle(thread_handles[1])));
+
+ /* Check that the switch was correct. */
+ CATCH_REQUIRE(g_correct_switch_threads);
+ }
+ }
+ }
+
+ void TestYieldDifferentPriority(uintptr_t sp_higher, uintptr_t sp_lower) {
+ /* Test each core. */
+ for (s32 core = 0; core < NumCores; ++core) {
+ for (s32 priority = HighestTestPriority; priority < LowestTestPriority && !IsPreemptionPriority(core, priority); ++priority) {
+
+ svc::Handle thread_handles[2];
+
+ /* Create threads. */
+ CATCH_REQUIRE(R_SUCCEEDED(svc::CreateThread(thread_handles + 0, reinterpret_cast(&TestYieldHigherOrSamePriorityThread), 0, sp_higher, priority, core)));
+ CATCH_REQUIRE(R_SUCCEEDED(svc::CreateThread(thread_handles + 1, reinterpret_cast(&TestYieldLowerOrSamePriorityThread), 0, sp_lower, priority + 1, core)));
+
+ /* Start threads. */
+ CATCH_REQUIRE(R_SUCCEEDED(svc::StartThread(thread_handles[1])));
+ CATCH_REQUIRE(R_SUCCEEDED(svc::StartThread(thread_handles[0])));
+
+ /* Wait for higher priority thread. */
+ WaitSynchronization(thread_handles[0]);
+ CATCH_REQUIRE(R_SUCCEEDED(svc::CloseHandle(thread_handles[0])));
+
+ /* Signal the lower priority thread to exit. */
+ CATCH_REQUIRE(R_SUCCEEDED(svc::SignalEvent(g_write_handles[2])));
+
+ /* Wait for the lower priority thread. */
+ WaitSynchronization(thread_handles[1]);
+ CATCH_REQUIRE(R_SUCCEEDED(svc::CloseHandle(thread_handles[1])));
+
+ /* Check that the switch was correct. */
+ CATCH_REQUIRE(g_correct_switch_threads);
+ }
+ }
+ }
+
+ }
+
CATCH_TEST_CASE( "svc::SleepThread: Thread sleeps for time specified" ) {
for (s64 ns = 1; ns < TimeSpan::FromSeconds(1).GetNanoSeconds(); ns *= 2) {
const auto start = os::GetSystemTickOrdered();
@@ -34,4 +155,99 @@ namespace ams::test {
}
}
+ CATCH_TEST_CASE( "svc::SleepThread: Yield is behaviorally correct" ) {
+ /* Create events. */
+ for (size_t i = 0; i < util::size(g_write_handles); ++i) {
+ g_read_handles[i] = svc::InvalidHandle;
+ g_write_handles[i] = svc::InvalidHandle;
+ CATCH_REQUIRE(R_SUCCEEDED(svc::CreateEvent(g_write_handles + i, g_read_handles + i)));
+ }
+
+ ON_SCOPE_EXIT {
+ for (size_t i = 0; i < util::size(g_write_handles); ++i) {
+ CATCH_REQUIRE(R_SUCCEEDED(svc::CloseHandle(g_read_handles[i])));
+ CATCH_REQUIRE(R_SUCCEEDED(svc::CloseHandle(g_write_handles[i])));
+ g_read_handles[i] = svc::InvalidHandle;
+ g_write_handles[i] = svc::InvalidHandle;
+ }
+ };
+
+ /* Create heap. */
+ ScopedHeap heap(3 * os::MemoryPageSize);
+ CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(heap.GetAddress() + os::MemoryPageSize, os::MemoryPageSize, svc::MemoryPermission_None)));
+ ON_SCOPE_EXIT {
+ CATCH_REQUIRE(R_SUCCEEDED(svc::SetMemoryPermission(heap.GetAddress() + os::MemoryPageSize, os::MemoryPageSize, svc::MemoryPermission_ReadWrite)));
+ };
+ const uintptr_t sp_higher = heap.GetAddress() + 1 * os::MemoryPageSize;
+ const uintptr_t sp_lower = heap.GetAddress() + 3 * os::MemoryPageSize;
+
+ CATCH_SECTION("svc::SleepThread: Yields do not switch to a thread of lower priority.") {
+ /* Test yield without migration. */
+ {
+ /* Configure for yield test. */
+ g_should_switch_threads = false;
+ g_thread_wait_ns = static_cast(svc::YieldType_WithoutCoreMigration);
+
+ TestYieldDifferentPriority(sp_higher, sp_lower);
+ }
+
+ /* Test yield with migration. */
+ {
+ /* Configure for yield test. */
+ g_should_switch_threads = false;
+ g_thread_wait_ns = static_cast(svc::YieldType_WithoutCoreMigration);
+
+ TestYieldDifferentPriority(sp_higher, sp_lower);
+ }
+ }
+
+ CATCH_SECTION("svc::SleepThread: ToAnyThread switches to a thread of same or lower priority.") {
+ /* Test to same priority. */
+ {
+ /* Configure for yield test. */
+ g_should_switch_threads = true;
+ g_thread_wait_ns = static_cast(svc::YieldType_ToAnyThread);
+
+ TestYieldSamePriority(sp_higher, sp_lower);
+ }
+
+ /* Test to lower priority. */
+ {
+ /* Configure for yield test. */
+ g_should_switch_threads = true;
+ g_thread_wait_ns = static_cast(svc::YieldType_ToAnyThread);
+
+ TestYieldDifferentPriority(sp_higher, sp_lower);
+ }
+ }
+
+ CATCH_SECTION("svc::SleepThread: Yield switches to another thread of same priority.") {
+ /* Test yield without migration. */
+ {
+ /* Configure for yield test. */
+ g_should_switch_threads = true;
+ g_thread_wait_ns = static_cast(svc::YieldType_WithoutCoreMigration);
+
+ TestYieldSamePriority(sp_higher, sp_lower);
+ }
+
+ /* Test yield with migration. */
+ {
+ /* Configure for yield test. */
+ g_should_switch_threads = true;
+ g_thread_wait_ns = static_cast(svc::YieldType_WithCoreMigration);
+
+ TestYieldSamePriority(sp_higher, sp_lower);
+ }
+ }
+
+ CATCH_SECTION("svc::SleepThread: Yield with bogus timeout does not switch to another thread same priority") {
+ /* Configure for yield test. */
+ g_should_switch_threads = false;
+ g_thread_wait_ns = INT64_C(-5);
+
+ TestYieldSamePriority(sp_higher, sp_lower);
+ }
+ }
+
}
\ No newline at end of file
diff --git a/tests/TestSvc/source/util_common.hpp b/tests/TestSvc/source/util_common.hpp
new file mode 100644
index 000000000..ca8368de0
--- /dev/null
+++ b/tests/TestSvc/source/util_common.hpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#pragma once
+#include "util_catch.hpp"
+
+namespace ams::test {
+
+ static constexpr s32 NumCores = 4;
+ static constexpr s32 DpcManagerNormalThreadPriority = 59;
+ static constexpr s32 DpcManagerPreemptionThreadPriority = 63;
+
+ static constexpr s32 HighestTestPriority = 32;
+ static constexpr s32 LowestTestPriority = svc::LowestThreadPriority;
+ static_assert(HighestTestPriority < LowestTestPriority);
+
+ static constexpr TimeSpan PreemptionTimeSpan = TimeSpan::FromMilliSeconds(10);
+
+ constexpr inline bool IsPreemptionPriority(s32 core, s32 priority) {
+ return priority == ((core == (NumCores - 1)) ? DpcManagerPreemptionThreadPriority : DpcManagerNormalThreadPriority);
+ }
+
+}
\ No newline at end of file