diff --git a/tests/TestSvc/source/test_thread_pinning.cpp b/tests/TestSvc/source/test_thread_pinning.cpp new file mode 100644 index 000000000..35fb9d4b5 --- /dev/null +++ b/tests/TestSvc/source/test_thread_pinning.cpp @@ -0,0 +1,28 @@ +/* + * 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 { + + DOCTEST_TEST_CASE( "Setting a thread's disable count will cause it to become pinned." ) { + DoWithThreadPinning([]() { + __asm__ __volatile__("" ::: "memory"); + }); + } + +} \ No newline at end of file diff --git a/tests/TestSvc/source/util_common.hpp b/tests/TestSvc/source/util_common.hpp index eeee4484b..0ba2e9c3b 100644 --- a/tests/TestSvc/source/util_common.hpp +++ b/tests/TestSvc/source/util_common.hpp @@ -32,4 +32,70 @@ namespace ams::test { return priority == ((core == (NumCores - 1)) ? DpcManagerPreemptionThreadPriority : DpcManagerNormalThreadPriority); } + template + void DoWithThreadPinning(F f) { + /* Get the thread local region. */ + auto * const tlr = svc::GetThreadLocalRegion(); + + /* Require that we're not currently pinned. */ + DOCTEST_CHECK((tlr->disable_count == 0)); + DOCTEST_CHECK((!tlr->interrupt_flag)); + + /* Request to pin ourselves. */ + tlr->disable_count = 1; + + /* Wait long enough that we can be confident preemption will occur, and therefore our interrupt flag will be set. */ + { + constexpr auto MinimumTicksToGuaranteeInterruptFlag = ::ams::svc::Tick(PreemptionTimeSpan) + 1; + + auto GetSystemTickForPinnedThread = []() ALWAYS_INLINE_LAMBDA -> ::ams::svc::Tick { + s64 v; + __asm__ __volatile__ ("mrs %x[v], cntpct_el0" : [v]"=r"(v) :: "memory"); + return ::ams::svc::Tick(v); + }; + + const auto start_tick = GetSystemTickForPinnedThread(); + while (true) { + if (tlr->interrupt_flag) { + break; + } + + if (const auto cur_tick = GetSystemTickForPinnedThread(); (cur_tick - start_tick) > MinimumTicksToGuaranteeInterruptFlag) { + break; + } + } + } + + /* We're pinned. Execute the user callback. */ + bool callback_succeeded = true; + { + if constexpr (requires { { f() } -> std::convertible_to; }) { + callback_succeeded = f(); + } else { + f(); + } + } + + /* Clear our disable count. */ + tlr->disable_count = 0; + + /* Get our interrupt flag. */ + const auto interrupt_flag_while_pinned = tlr->interrupt_flag; + + /* Unpin ourselves. */ + if (interrupt_flag_while_pinned) { + svc::SynchronizePreemptionState(); + } + + /* Get our interrupt flag. */ + const auto interrupt_flag_after_unpin = tlr->interrupt_flag; + + /* We have access to all SVCs again. Check that our pinning happened as expected. */ + DOCTEST_CHECK(interrupt_flag_while_pinned); + DOCTEST_CHECK(!interrupt_flag_after_unpin); + + /* Check that our callback succeeded. */ + DOCTEST_CHECK(callback_succeeded); + } + } \ No newline at end of file