From 1f8bf41f0b45ed3580d6fd47a47a92a1e6e4df8c Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Fri, 22 Oct 2021 17:27:35 -0700 Subject: [PATCH] kern/test: add some scheduler tests (yields work correctly, all non-special priorities are cooperative/not pre-emptive --- tests/TestSvc/Makefile | 11 +- tests/TestSvc/TestSvc.npdm.json | 147 +++++++++++ tests/TestSvc/source/test_main.cpp | 7 + .../source/test_preemption_priority.cpp | 91 +++++++ tests/TestSvc/source/test_set_heap_size.cpp | 2 +- .../source/test_set_memory_permission.cpp | 2 +- tests/TestSvc/source/test_sleep_thread.cpp | 228 +++++++++++++++++- tests/TestSvc/source/util_common.hpp | 35 +++ 8 files changed, 514 insertions(+), 9 deletions(-) create mode 100644 tests/TestSvc/TestSvc.npdm.json create mode 100644 tests/TestSvc/source/test_preemption_priority.cpp create mode 100644 tests/TestSvc/source/util_common.hpp 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