Revert "hoc-clk: add live vdd2, live boost clock and basic pwm dimming"

This reverts commit 15b7df8ef1.
This commit is contained in:
souldbminersmwc
2025-11-09 16:14:52 -05:00
parent 22ec140738
commit 21a3f953d7
3804 changed files with 435 additions and 570162 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,107 +0,0 @@
/*
* 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>
#define DOCTEST_CONFIG_IMPLEMENT
#include "util_test_framework.hpp"
namespace ams {
namespace {
constexpr size_t MallocBufferSize = 16_MB;
alignas(os::MemoryPageSize) constinit u8 g_malloc_buffer[MallocBufferSize];
}
namespace hos {
bool IsUnitTestProgramForSetVersion() { return true; }
}
namespace init {
void InitializeSystemModuleBeforeConstructors() {
/* Catch has global-ctors which allocate, so we need to do this earlier than normal. */
init::InitializeAllocator(g_malloc_buffer, sizeof(g_malloc_buffer));
}
void InitializeSystemModule() { /* ... */ }
void FinalizeSystemModule() { /* ... */ }
void Startup() { /* ... */ }
}
void NORETURN Exit(int rc) {
AMS_UNUSED(rc);
AMS_ABORT("Exit called by immortal process");
}
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. */
{
doctest::Context ctx;
ctx.applyCommandLine(os::GetHostArgc(), os::GetHostArgv());
ctx.run();
}
AMS_INFINITE_LOOP();
/* This can never be reached. */
AMS_ASSUME(false);
}
}
namespace doctest {
namespace {
class OutputDebugStringStream : public std::stringbuf {
public:
OutputDebugStringStream() = default;
~OutputDebugStringStream() { pubsync(); }
int sync() override {
const auto message = str();
return R_SUCCEEDED(ams::svc::OutputDebugString(message.c_str(), message.length())) ? 0 : -1;
}
};
}
std::ostream& get_cout() {
static std::ostream ret(new OutputDebugStringStream);
return ret;
}
std::ostream& get_cerr() {
return get_cout();
}
}

View File

@@ -1,91 +0,0 @@
/*
* 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 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();
}
}
DOCTEST_TEST_CASE( "The scheduler is preemptive at the preemptive priority and cooperative for all other priorities" ) {
/* 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_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. */
DOCTEST_CHECK(R_SUCCEEDED(svc::CreateThread(thread_handles + 0, reinterpret_cast<uintptr_t>(&TestPreemptionPriorityThreadFunction), reinterpret_cast<uintptr_t>(thread_executed + 0), sp_0, priority, core)));
DOCTEST_CHECK(R_SUCCEEDED(svc::CreateThread(thread_handles + 1, reinterpret_cast<uintptr_t>(&TestPreemptionPriorityThreadFunction), reinterpret_cast<uintptr_t>(thread_executed + 1), sp_1, priority, core)));
/* Start threads. */
DOCTEST_CHECK(R_SUCCEEDED(svc::StartThread(thread_handles[0])));
DOCTEST_CHECK(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)) {
DOCTEST_CHECK((thread_executed[0] & thread_executed[1]));
} else {
DOCTEST_CHECK((thread_executed[0] ^ thread_executed[1]));
}
/* Stop spinlooping. */
g_spinloop = false;
/* Wait for threads to exit. */
s32 dummy;
DOCTEST_CHECK(R_SUCCEEDED(svc::WaitSynchronization(std::addressof(dummy), thread_handles + 0, 1, -1)));
DOCTEST_CHECK(R_SUCCEEDED(svc::WaitSynchronization(std::addressof(dummy), thread_handles + 1, 1, -1)));
/* Close thread handles. */
DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(thread_handles[0])));
DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(thread_handles[1])));
}
}
}
}

View File

@@ -1,133 +0,0 @@
/*
* 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_check_memory.hpp"
namespace ams::test {
namespace {
size_t GetPhysicalMemorySizeMax() {
u64 v;
R_ABORT_UNLESS(svc::GetInfo(std::addressof(v), svc::InfoType_ResourceLimit, svc::InvalidHandle, 0));
const svc::Handle resource_limit = v;
ON_SCOPE_EXIT { svc::CloseHandle(resource_limit); };
s64 size;
R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(std::addressof(size), resource_limit, svc::LimitableResource_PhysicalMemoryMax));
return static_cast<size_t>(size);
}
size_t GetPhysicalMemorySizeAvailable() {
u64 v;
R_ABORT_UNLESS(svc::GetInfo(std::addressof(v), svc::InfoType_ResourceLimit, svc::InvalidHandle, 0));
const svc::Handle resource_limit = v;
ON_SCOPE_EXIT { svc::CloseHandle(resource_limit); };
s64 total;
R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(std::addressof(total), resource_limit, svc::LimitableResource_PhysicalMemoryMax));
s64 current;
R_ABORT_UNLESS(svc::GetResourceLimitCurrentValue(std::addressof(current), resource_limit, svc::LimitableResource_PhysicalMemoryMax));
return static_cast<size_t>(total - current);
}
}
DOCTEST_TEST_CASE("svc::SetHeapSize") {
svc::MemoryInfo mem_info;
svc::PageInfo page_info;
uintptr_t dummy;
/* Reset the heap. */
uintptr_t addr;
DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), 0)));
/* Ensure that we don't leak memory. */
const size_t initial_memory = GetPhysicalMemorySizeAvailable();
ON_SCOPE_EXIT { DOCTEST_CHECK(initial_memory == GetPhysicalMemorySizeAvailable()); };
DOCTEST_SUBCASE("Unaligned and too big sizes fail") {
for (size_t i = 1; i < svc::HeapSizeAlignment; i = util::AlignUp(i + 1, os::MemoryPageSize)){
DOCTEST_CHECK(svc::ResultInvalidSize::Includes(svc::SetHeapSize(std::addressof(dummy), i)));
}
DOCTEST_CHECK(svc::ResultInvalidSize::Includes(svc::SetHeapSize(std::addressof(dummy), 64_GB)));
}
DOCTEST_SUBCASE("Larger size than address space fails") {
DOCTEST_CHECK(svc::ResultOutOfMemory::Includes(svc::SetHeapSize(std::addressof(dummy), util::AlignUp(svc::AddressMemoryRegionHeap39Size + 1, svc::HeapSizeAlignment))));
}
DOCTEST_SUBCASE("Bounded by resource limit") {
DOCTEST_CHECK(svc::ResultLimitReached::Includes(svc::SetHeapSize(std::addressof(dummy), util::AlignUp(GetPhysicalMemorySizeMax() + 1, svc::HeapSizeAlignment))));
DOCTEST_CHECK(svc::ResultLimitReached::Includes(svc::SetHeapSize(std::addressof(dummy), util::AlignUp(GetPhysicalMemorySizeAvailable() + 1, svc::HeapSizeAlignment))));
}
DOCTEST_SUBCASE("SetHeapSize gives heap memory") {
DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), svc::HeapSizeAlignment)));
TestMemory(addr, svc::HeapSizeAlignment, svc::MemoryState_Normal, svc::MemoryPermission_ReadWrite, 0);
DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), 0)));
}
DOCTEST_SUBCASE("SetHeapSize cannot remove read-only heap") {
DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), svc::HeapSizeAlignment)));
DOCTEST_CHECK(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), addr)));
TestMemory(addr, svc::HeapSizeAlignment, svc::MemoryState_Normal, svc::MemoryPermission_ReadWrite, 0);
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(addr, svc::HeapSizeAlignment, svc::MemoryPermission_Read)));
TestMemory(addr, svc::HeapSizeAlignment, svc::MemoryState_Normal, svc::MemoryPermission_Read, 0);
DOCTEST_CHECK(svc::ResultInvalidCurrentMemory::Includes(svc::SetHeapSize(std::addressof(dummy), 0)));
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(addr, svc::HeapSizeAlignment, svc::MemoryPermission_ReadWrite)));
TestMemory(addr, svc::HeapSizeAlignment, svc::MemoryState_Normal, svc::MemoryPermission_ReadWrite, 0);
DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), 0)));
}
DOCTEST_SUBCASE("Heap memory does not survive unmap/re-map") {
DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), 2 * svc::HeapSizeAlignment)));
u8 * const heap = reinterpret_cast<u8 *>(addr);
std::memset(heap, 0xAA, svc::HeapSizeAlignment);
std::memset(heap + svc::HeapSizeAlignment, 0xBB, svc::HeapSizeAlignment);
DOCTEST_CHECK(heap[svc::HeapSizeAlignment] == 0xBB);
DOCTEST_CHECK(std::memcmp(heap + svc::HeapSizeAlignment, heap + svc::HeapSizeAlignment + 1, svc::HeapSizeAlignment - 1) == 0);
DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), svc::HeapSizeAlignment)));
DOCTEST_CHECK(heap[0] == 0xAA);
DOCTEST_CHECK(std::memcmp(heap, heap + 1, svc::HeapSizeAlignment - 1) == 0);
DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), 2 * svc::HeapSizeAlignment)));
DOCTEST_CHECK(heap[svc::HeapSizeAlignment] == 0x00);
DOCTEST_CHECK(std::memcmp(heap + svc::HeapSizeAlignment, heap + svc::HeapSizeAlignment + 1, svc::HeapSizeAlignment - 1) == 0);
DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), 0)));
}
}
}

View File

@@ -1,156 +0,0 @@
/*
* 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_check_memory.hpp"
#include "util_scoped_heap.hpp"
namespace ams::test {
namespace {
bool CanSetMemoryPermission(u8 state) {
return state == svc::MemoryState_CodeData || state == svc::MemoryState_AliasCodeData || state == svc::MemoryState_Normal;
}
}
alignas(os::MemoryPageSize) constinit u8 g_memory_permission_buffer[2 * os::MemoryPageSize];
DOCTEST_TEST_CASE("svc::SetMemoryPermission invalid arguments") {
const uintptr_t buffer = reinterpret_cast<uintptr_t>(g_memory_permission_buffer);
for (size_t i = 1; i < os::MemoryPageSize; ++i) {
DOCTEST_CHECK(svc::ResultInvalidAddress::Includes(svc::SetMemoryPermission(buffer + i, os::MemoryPageSize, svc::MemoryPermission_Read)));
DOCTEST_CHECK(svc::ResultInvalidSize::Includes(svc::SetMemoryPermission(buffer, os::MemoryPageSize + i, svc::MemoryPermission_Read)));
}
DOCTEST_CHECK(svc::ResultInvalidSize::Includes(svc::SetMemoryPermission(buffer, 0, svc::MemoryPermission_Read)));
{
const u64 vmem_end = util::AlignDown(std::numeric_limits<u64>::max(), os::MemoryPageSize);
DOCTEST_CHECK(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(vmem_end, 2 * os::MemoryPageSize, svc::MemoryPermission_Read)));
}
DOCTEST_CHECK(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(svc::AddressMap39End, os::MemoryPageSize, svc::MemoryPermission_Read)));
for (size_t i = 0; i < 0x100; ++i) {
const auto perm = static_cast<svc::MemoryPermission>(i);
if (perm == svc::MemoryPermission_None || perm == svc::MemoryPermission_Read || perm == svc::MemoryPermission_ReadWrite) {
continue;
}
DOCTEST_CHECK(svc::ResultInvalidNewMemoryPermission::Includes(svc::SetMemoryPermission(buffer, os::MemoryPageSize, perm)));
}
DOCTEST_CHECK(svc::ResultInvalidNewMemoryPermission::Includes(svc::SetMemoryPermission(buffer, os::MemoryPageSize, svc::MemoryPermission_ReadExecute)));
DOCTEST_CHECK(svc::ResultInvalidNewMemoryPermission::Includes(svc::SetMemoryPermission(buffer, os::MemoryPageSize, svc::MemoryPermission_Write)));
DOCTEST_CHECK(svc::ResultInvalidNewMemoryPermission::Includes(svc::SetMemoryPermission(buffer, os::MemoryPageSize, svc::MemoryPermission_DontCare)));
}
DOCTEST_TEST_CASE("svc::SetMemoryPermission works on specific states") {
/* Check that we have CodeData. */
const uintptr_t bss_buffer = reinterpret_cast<uintptr_t>(g_memory_permission_buffer);
TestMemory(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryState_CodeData, svc::MemoryPermission_ReadWrite, 0);
/* Create a heap. */
ScopedHeap scoped_heap(2 * svc::HeapSizeAlignment);
TestMemory(scoped_heap.GetAddress(), scoped_heap.GetSize(), svc::MemoryState_Normal, svc::MemoryPermission_ReadWrite, 0);
/* TODO: Ensure we have alias code data? */
uintptr_t addr = 0;
while (true) {
/* Get current mapping. */
svc::MemoryInfo mem_info;
svc::PageInfo page_info;
DOCTEST_CHECK(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), addr)));
/* Try to set permission. */
if (CanSetMemoryPermission(mem_info.state) && mem_info.attribute == 0) {
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(mem_info.base_address, mem_info.size, svc::MemoryPermission_ReadWrite)));
TestMemory(mem_info.base_address, mem_info.size, mem_info.state, svc::MemoryPermission_ReadWrite, mem_info.attribute);
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(mem_info.base_address, mem_info.size, mem_info.permission)));
} else {
DOCTEST_CHECK(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(mem_info.base_address, mem_info.size, svc::MemoryPermission_Read)));
}
const uintptr_t next_address = mem_info.base_address + mem_info.size;
if (next_address <= addr) {
break;
}
addr = next_address;
}
}
DOCTEST_TEST_CASE("svc::SetMemoryPermission allows for free movement between RW-, R--, ---") {
/* Define helper. */
auto test_set_memory_permission = [](uintptr_t address, size_t size){
/* Get the permission. */
svc::MemoryInfo mem_info;
svc::PageInfo page_info;
DOCTEST_CHECK(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), address)));
const svc::MemoryPermission legal_states[] = { svc::MemoryPermission_None, svc::MemoryPermission_Read, svc::MemoryPermission_ReadWrite };
for (const auto src_state : legal_states) {
for (const auto dst_state : legal_states) {
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(address, size, svc::MemoryPermission_None)));
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(address, size, src_state)));
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(address, size, dst_state)));
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(address, size, svc::MemoryPermission_None)));
}
}
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(address, size, mem_info.permission)));
};
/* Test that we can freely move about .bss buffers. */
test_set_memory_permission(reinterpret_cast<uintptr_t>(g_memory_permission_buffer), sizeof(g_memory_permission_buffer));
/* Create a heap. */
ScopedHeap scoped_heap(svc::HeapSizeAlignment);
TestMemory(scoped_heap.GetAddress(), scoped_heap.GetSize(), svc::MemoryState_Normal, svc::MemoryPermission_ReadWrite, 0);
/* Test that we can freely move about heap. */
test_set_memory_permission(scoped_heap.GetAddress(), scoped_heap.GetSize());
/* TODO: AliasCodeData */
}
DOCTEST_TEST_CASE("svc::SetMemoryPermission fails when the memory has non-zero attribute") {
const uintptr_t bss_buffer = reinterpret_cast<uintptr_t>(g_memory_permission_buffer);
TestMemory(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryState_CodeData, svc::MemoryPermission_ReadWrite, 0);
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_None)));
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_Read)));
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_ReadWrite)));
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryAttribute(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryAttribute_Uncached, svc::MemoryAttribute_Uncached)));
TestMemory(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryState_CodeData, svc::MemoryPermission_ReadWrite, svc::MemoryAttribute_Uncached);
DOCTEST_CHECK(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_None)));
DOCTEST_CHECK(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_Read)));
DOCTEST_CHECK(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_ReadWrite)));
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryAttribute(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryAttribute_Uncached, 0)));
TestMemory(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryState_CodeData, svc::MemoryPermission_ReadWrite, 0);
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_None)));
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_Read)));
DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_ReadWrite)));
}
}

View File

@@ -1,253 +0,0 @@
/*
* 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);
}
}
}

View File

@@ -1,43 +0,0 @@
/*
* 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/>.
*/
/* ams::test::TestThreadCreateRegistersOnFunctionEntry(void *ctx) */
.section .text._ZN3ams4test40TestThreadCreateRegistersOnFunctionEntryEPv, "ax", %progbits
.global _ZN3ams4test40TestThreadCreateRegistersOnFunctionEntryEPv
.type _ZN3ams4test40TestThreadCreateRegistersOnFunctionEntryEPv, %function
_ZN3ams4test40TestThreadCreateRegistersOnFunctionEntryEPv:
/* Save all registers to our context. */
stp x0, x1, [x0, #0x00]
stp x2, x3, [x0, #0x10]
stp x4, x5, [x0, #0x20]
stp x6, x7, [x0, #0x30]
stp x8, x9, [x0, #0x40]
stp x10, x11, [x0, #0x50]
stp x12, x13, [x0, #0x60]
stp x14, x15, [x0, #0x70]
stp x16, x17, [x0, #0x80]
stp x18, x19, [x0, #0x90]
stp x20, x21, [x0, #0xA0]
stp x22, x23, [x0, #0xB0]
stp x24, x25, [x0, #0xC0]
stp x26, x27, [x0, #0xD0]
stp x28, x29, [x0, #0xE0]
mov x1, sp
stp x30, x1, [x0, #0xF0]
/* Exit the thread. */
svc 0xa

View File

@@ -1,65 +0,0 @@
/*
* 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 {
void TestThreadCreateRegistersOnFunctionEntry(void *ctx);
DOCTEST_TEST_CASE( "Creating a thread results in fixed register contents." ) {
/* Create heap. */
ScopedHeap heap(os::MemoryPageSize);
/* Create register buffer. */
u64 thread_registers[32];
std::memset(thread_registers, 0xCC, sizeof(thread_registers));
/* Create thread. */
svc::Handle thread_handle;
DOCTEST_CHECK(R_SUCCEEDED(svc::CreateThread(std::addressof(thread_handle), reinterpret_cast<uintptr_t>(&TestThreadCreateRegistersOnFunctionEntry), reinterpret_cast<uintptr_t>(thread_registers), heap.GetAddress() + os::MemoryPageSize, HighestTestPriority, NumCores - 1)));
/* Start thread. */
DOCTEST_CHECK(R_SUCCEEDED(svc::StartThread(thread_handle)));
/* Wait for thread to exit. */
s32 dummy;
DOCTEST_CHECK(R_SUCCEEDED(svc::WaitSynchronization(std::addressof(dummy), std::addressof(thread_handle), 1, -1)));
/* Close thread handle. */
DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(thread_handle)));
/* Check thread initial registers. */
for (size_t i = 0; i < util::size(thread_registers); ++i) {
if (i == 0) {
/* X0 is argument. */
DOCTEST_CHECK(thread_registers[i] == reinterpret_cast<uintptr_t>(thread_registers));
} else if (i == 18) {
/* X18 is an odd cfi value. */
DOCTEST_CHECK(thread_registers[i] != 0);
DOCTEST_CHECK((thread_registers[i] & 0x1) != 0);
} else if (i == 31) {
/* SP is user-provided sp. */
DOCTEST_CHECK(thread_registers[i] == (heap.GetAddress() + os::MemoryPageSize));
} else {
/* All other registers are zero. */
DOCTEST_CHECK(thread_registers[i] == 0);
}
}
}
}

View File

@@ -1,28 +0,0 @@
/*
* 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 {
DOCTEST_TEST_CASE( "Setting a thread's disable count will cause it to become pinned." ) {
DoWithThreadPinning([]() {
__asm__ __volatile__("" ::: "memory");
});
}
}

View File

@@ -1,46 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include "util_test_framework.hpp"
namespace ams::test {
inline void TestMemory(uintptr_t address, svc::MemoryState state, svc::MemoryPermission perm, u32 attr) {
svc::MemoryInfo mem_info;
svc::PageInfo page_info;
DOCTEST_CHECK(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), address)));
DOCTEST_CHECK(mem_info.base_address <= address);
DOCTEST_CHECK(address < (mem_info.base_address + mem_info.size));
DOCTEST_CHECK(mem_info.state == state);
DOCTEST_CHECK(mem_info.permission == perm);
DOCTEST_CHECK(mem_info.attribute == attr);
}
inline void TestMemory(uintptr_t address, size_t size, svc::MemoryState state, svc::MemoryPermission perm, u32 attr) {
svc::MemoryInfo mem_info;
svc::PageInfo page_info;
DOCTEST_CHECK(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), address)));
DOCTEST_CHECK(mem_info.base_address <= address);
DOCTEST_CHECK(mem_info.base_address < (address + size));
DOCTEST_CHECK((address + size) <= (mem_info.base_address + mem_info.size));
DOCTEST_CHECK(mem_info.state == state);
DOCTEST_CHECK(mem_info.permission == perm);
DOCTEST_CHECK(mem_info.attribute == attr);
}
}

View File

@@ -1,101 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include "util_test_framework.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);
}
template<typename F>
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) + ::ams::svc::Tick(PreemptionTimeSpan) + 2;
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<bool>; }) {
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);
}
}

View File

@@ -1,48 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include "util_test_framework.hpp"
namespace ams::test {
class ScopedHeap {
NON_COPYABLE(ScopedHeap);
NON_MOVEABLE(ScopedHeap);
private:
uintptr_t m_address;
size_t m_size;
public:
explicit ScopedHeap(size_t size) {
this->SetHeapSize(size);
}
~ScopedHeap() {
const auto result = svc::SetHeapSize(std::addressof(m_address), 0);
DOCTEST_CHECK(R_SUCCEEDED(result));
}
void SetHeapSize(size_t size) {
m_size = util::AlignUp(size, svc::HeapSizeAlignment);
const auto result = svc::SetHeapSize(std::addressof(m_address), m_size);
DOCTEST_CHECK(R_SUCCEEDED(result));
}
uintptr_t GetAddress() const { return m_address; }
size_t GetSize() const { return m_size; }
};
}

View File

@@ -1,24 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#define CATCH_CONFIG_NOSTDOUT
#define DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES
#define DOCTEST_CONFIG_SUPER_FAST_ASSERTS
#define DOCTEST_CONFIG_NO_EXCEPTIONS
#define DOCTEST_CONFIG_NO_POSIX_SIGNALS
#include "doctest.h"