mirror of
				https://github.com/Atmosphere-NX/Atmosphere.git
				synced 2025-10-31 03:05:48 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			253 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			253 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * 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 <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| #include <stratosphere.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. */
 | |
|                     DOCTEST_CHECK(R_SUCCEEDED(svc::CreateThread(thread_handles + 0, reinterpret_cast<uintptr_t>(&TestYieldHigherOrSamePriorityThread), 0, sp_higher, priority, core)));
 | |
|                     DOCTEST_CHECK(R_SUCCEEDED(svc::CreateThread(thread_handles + 1, reinterpret_cast<uintptr_t>(&TestYieldLowerOrSamePriorityThread), 0, sp_lower, priority, core)));
 | |
| 
 | |
|                     /* Start threads. */
 | |
|                     DOCTEST_CHECK(R_SUCCEEDED(svc::StartThread(thread_handles[1])));
 | |
|                     DOCTEST_CHECK(R_SUCCEEDED(svc::StartThread(thread_handles[0])));
 | |
| 
 | |
|                     /* Wait for higher priority thread. */
 | |
|                     WaitSynchronization(thread_handles[0]);
 | |
|                     DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(thread_handles[0])));
 | |
| 
 | |
|                     /* Signal the lower priority thread to exit. */
 | |
|                     DOCTEST_CHECK(R_SUCCEEDED(svc::SignalEvent(g_write_handles[2])));
 | |
| 
 | |
|                     /* Wait for the lower priority thread. */
 | |
|                     WaitSynchronization(thread_handles[1]);
 | |
|                     DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(thread_handles[1])));
 | |
| 
 | |
|                     /* Check that the switch was correct. */
 | |
|                     DOCTEST_CHECK(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. */
 | |
|                     DOCTEST_CHECK(R_SUCCEEDED(svc::CreateThread(thread_handles + 0, reinterpret_cast<uintptr_t>(&TestYieldHigherOrSamePriorityThread), 0, sp_higher, priority, core)));
 | |
|                     DOCTEST_CHECK(R_SUCCEEDED(svc::CreateThread(thread_handles + 1, reinterpret_cast<uintptr_t>(&TestYieldLowerOrSamePriorityThread), 0, sp_lower, priority + 1, core)));
 | |
| 
 | |
|                     /* Start threads. */
 | |
|                     DOCTEST_CHECK(R_SUCCEEDED(svc::StartThread(thread_handles[1])));
 | |
|                     DOCTEST_CHECK(R_SUCCEEDED(svc::StartThread(thread_handles[0])));
 | |
| 
 | |
|                     /* Wait for higher priority thread. */
 | |
|                     WaitSynchronization(thread_handles[0]);
 | |
|                     DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(thread_handles[0])));
 | |
| 
 | |
|                     /* Signal the lower priority thread to exit. */
 | |
|                     DOCTEST_CHECK(R_SUCCEEDED(svc::SignalEvent(g_write_handles[2])));
 | |
| 
 | |
|                     /* Wait for the lower priority thread. */
 | |
|                     WaitSynchronization(thread_handles[1]);
 | |
|                     DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(thread_handles[1])));
 | |
| 
 | |
|                     /* Check that the switch was correct. */
 | |
|                     DOCTEST_CHECK(g_correct_switch_threads);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     DOCTEST_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();
 | |
|             svc::SleepThread(ns);
 | |
|             const auto end   = os::GetSystemTickOrdered();
 | |
| 
 | |
|             const s64 taken_ns = (end - start).ToTimeSpan().GetNanoSeconds();
 | |
|             DOCTEST_CHECK( taken_ns >= ns );
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     DOCTEST_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;
 | |
|             DOCTEST_CHECK(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) {
 | |
|                 DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(g_read_handles[i])));
 | |
|                 DOCTEST_CHECK(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);
 | |
|         DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(heap.GetAddress() + os::MemoryPageSize, os::MemoryPageSize, svc::MemoryPermission_None)));
 | |
|         ON_SCOPE_EXIT {
 | |
|             DOCTEST_CHECK(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;
 | |
| 
 | |
|         DOCTEST_SUBCASE("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<s64>(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<s64>(svc::YieldType_WithoutCoreMigration);
 | |
| 
 | |
|                 TestYieldDifferentPriority(sp_higher, sp_lower);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         DOCTEST_SUBCASE("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<s64>(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<s64>(svc::YieldType_ToAnyThread);
 | |
| 
 | |
|                 TestYieldDifferentPriority(sp_higher, sp_lower);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         DOCTEST_SUBCASE("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<s64>(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<s64>(svc::YieldType_WithCoreMigration);
 | |
| 
 | |
|                 TestYieldSamePriority(sp_higher, sp_lower);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         DOCTEST_SUBCASE("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);
 | |
|         }
 | |
|     }
 | |
| 
 | |
| } |