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

View File

@@ -1,126 +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 "fatal_config.hpp"
namespace ams::fatal::srv {
namespace {
/* Global config. */
constinit os::SdkMutex g_config_mutex;
constinit bool g_initialized_config;
constinit util::TypedStorage<FatalConfig> g_config;
FatalConfig &GetFatalConfigImpl() {
if (AMS_UNLIKELY(!g_initialized_config)) {
std::scoped_lock lk(g_config_mutex);
if (AMS_LIKELY(!g_initialized_config)) {
util::ConstructAt(g_config);
g_initialized_config = true;
}
}
return util::GetReference(g_config);
}
/* Event creator. */
os::NativeHandle GetFatalDirtyEventReadableHandle() {
Event evt;
R_ABORT_UNLESS(setsysAcquireFatalDirtyFlagEventHandle(std::addressof(evt)));
return evt.revent;
}
/* Global event. */
constinit os::SystemEventType g_fatal_dirty_event;
constinit os::MultiWaitHolderType g_fatal_dirty_multi_wait_holder;
constinit bool g_initialized_fatal_dirty_event;
}
os::MultiWaitHolderType *GetFatalDirtyMultiWaitHolder() {
if (AMS_UNLIKELY(!g_initialized_fatal_dirty_event)) {
os::AttachReadableHandleToSystemEvent(std::addressof(g_fatal_dirty_event), GetFatalDirtyEventReadableHandle(), true, os::EventClearMode_ManualClear);
os::InitializeMultiWaitHolder(std::addressof(g_fatal_dirty_multi_wait_holder), std::addressof(g_fatal_dirty_event));
os::SetMultiWaitHolderUserData(std::addressof(g_fatal_dirty_multi_wait_holder), reinterpret_cast<uintptr_t>(std::addressof(g_fatal_dirty_multi_wait_holder)));
g_initialized_fatal_dirty_event = true;
}
return std::addressof(g_fatal_dirty_multi_wait_holder);
}
void OnFatalDirtyEvent() {
os::ClearSystemEvent(std::addressof(g_fatal_dirty_event));
u64 flags_0, flags_1;
if (R_SUCCEEDED(setsysGetFatalDirtyFlags(std::addressof(flags_0), std::addressof(flags_1))) && (flags_0 & 1)) {
GetFatalConfigImpl().UpdateLanguageCode();
}
}
FatalConfig::FatalConfig() {
/* Get information from set. */
settings::system::GetSerialNumber(std::addressof(m_serial_number));
settings::system::GetFirmwareVersion(std::addressof(m_firmware_version));
setsysGetQuestFlag(std::addressof(m_quest_flag));
this->UpdateLanguageCode();
/* Read information from settings. */
settings::fwdbg::GetSettingsItemValue(std::addressof(m_transition_to_fatal), sizeof(m_transition_to_fatal), "fatal", "transition_to_fatal");
settings::fwdbg::GetSettingsItemValue(std::addressof(m_show_extra_info), sizeof(m_show_extra_info), "fatal", "show_extra_info");
u64 quest_interval_second;
settings::fwdbg::GetSettingsItemValue(std::addressof(quest_interval_second), sizeof(quest_interval_second), "fatal", "quest_reboot_interval_second");
m_quest_reboot_interval = TimeSpan::FromSeconds(quest_interval_second);
/* Atmosphere extension for automatic reboot. */
u64 auto_reboot_ms;
if (settings::fwdbg::GetSettingsItemValue(std::addressof(auto_reboot_ms), sizeof(auto_reboot_ms), "atmosphere", "fatal_auto_reboot_interval") == sizeof(auto_reboot_ms)) {
m_fatal_auto_reboot_interval = TimeSpan::FromMilliSeconds(auto_reboot_ms);
m_fatal_auto_reboot_enabled = auto_reboot_ms != 0;
}
/* Setup messages. */
{
m_error_msg = "Error Code: 2%03d-%04d (0x%x)\n";
m_error_desc = "An error has occurred.\n\n"
"Please press the POWER Button to restart the console normally, or a VOL button\n"
"to reboot to a payload (or RCM, if none is present). If you are unable to\n"
"restart the console, hold the POWER Button for 12 seconds to turn the console off.\n\n"
"If the problem persists, refer to the Nintendo Support Website.\n"
"support.nintendo.com/switch/error\n";
/* If you're running Atmosphere on a quest unit for some reason, talk to me on discord. */
m_quest_desc = "Please call 1-800-875-1852 for service.\n\n"
"Also, please be aware that running Atmosphere on a Quest device is not fully\n"
"supported. Perhaps try booting your device without Atmosphere before calling\n"
"an official Nintendo service hotline. If you encounter further issues, please\n"
"contact SciresM#0524 on Discord, or via some other means.\n";
/* TODO: Try to load dynamically? */
/* FsStorage message_storage; */
/* TODO: if (R_SUCCEEDED(fsOpenDataStorageByDataId(0x010000000000081D, "fatal_msg"))) { ... } */
}
}
const FatalConfig &GetFatalConfig() {
return GetFatalConfigImpl();
}
}

View File

@@ -1,95 +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>
namespace ams::fatal::srv {
class FatalConfig {
private:
settings::system::SerialNumber m_serial_number{};
settings::system::FirmwareVersion m_firmware_version{};
u64 m_language_code{};
TimeSpan m_quest_reboot_interval{};
bool m_transition_to_fatal{};
bool m_show_extra_info{};
bool m_quest_flag{};
const char *m_error_msg{};
const char *m_error_desc{};
const char *m_quest_desc{};
TimeSpan m_fatal_auto_reboot_interval{};
bool m_fatal_auto_reboot_enabled{};
public:
FatalConfig();
const settings::system::SerialNumber &GetSerialNumber() const {
return m_serial_number;
}
const settings::system::FirmwareVersion &GetFirmwareVersion() const {
return m_firmware_version;
}
void UpdateLanguageCode() {
setGetLanguageCode(&m_language_code);
}
u64 GetLanguageCode() const {
return m_language_code;
}
bool ShouldTransitionToFatal() const {
return m_transition_to_fatal;
}
bool ShouldShowExtraInfo() const {
return m_show_extra_info;
}
bool IsQuest() const {
return m_quest_flag;
}
bool IsFatalRebootEnabled() const {
return m_fatal_auto_reboot_enabled;
}
TimeSpan GetQuestRebootTimeoutInterval() const {
return m_quest_reboot_interval;
}
TimeSpan GetFatalRebootTimeoutInterval() const {
return m_fatal_auto_reboot_interval;
}
const char *GetErrorMessage() const {
return m_error_msg;
}
const char *GetErrorDescription() const {
if (this->IsQuest()) {
return m_quest_desc;
} else {
return m_error_desc;
}
}
};
os::MultiWaitHolderType *GetFatalDirtyMultiWaitHolder();
void OnFatalDirtyEvent();
const FatalConfig &GetFatalConfig();
}

View File

@@ -1,331 +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 "fatal_debug.hpp"
#include "fatal_config.hpp"
namespace ams::fatal::srv {
namespace {
constexpr u32 SvcSendSyncRequestInstruction = 0xD4000421;
struct StackFrame {
u64 fp;
u64 lr;
};
constexpr inline size_t MaxThreads = 0x60;
template<size_t MaxThreadCount>
class ThreadTlsMapImpl {
private:
std::pair<u64, u64> m_map[MaxThreadCount];
size_t m_index;
public:
constexpr ThreadTlsMapImpl() : m_map(), m_index(0) { /* ... */ }
constexpr void ResetThreadTlsMap() {
m_index = 0;
}
constexpr void SetThreadTls(u64 thread_id, u64 tls) {
if (m_index < util::size(m_map)) {
m_map[m_index++] = std::make_pair(thread_id, tls);
}
}
constexpr bool GetThreadTls(u64 *out, u64 thread_id) const {
for (size_t i = 0; i < m_index; ++i) {
if (m_map[i].first == thread_id) {
*out = m_map[i].second;
return true;
}
}
return false;
}
};
using ThreadTlsMap = ThreadTlsMapImpl<MaxThreads>;
constinit ThreadTlsMap g_thread_id_to_tls_map;
bool IsThreadFatalCaller(Result result, os::NativeHandle debug_handle, u64 thread_id, u64 thread_tls_addr, svc::ThreadContext *thread_ctx) {
/* Verify that the thread is running or waiting. */
{
u64 _;
u32 _thread_state;
if (R_FAILED(svc::GetDebugThreadParam(&_, &_thread_state, debug_handle, thread_id, svc::DebugThreadParam_State))) {
return false;
}
const auto thread_state = static_cast<svc::ThreadState>(_thread_state);
if (thread_state != svc::ThreadState_Waiting && thread_state != svc::ThreadState_Running) {
return false;
}
}
/* Get the thread context. */
if (R_FAILED(svc::GetDebugThreadContext(thread_ctx, debug_handle, thread_id, svc::ThreadContextFlag_All))) {
return false;
}
/* Try to read the current instruction. */
u32 insn;
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(insn)), debug_handle, thread_ctx->pc, sizeof(insn)))) {
return false;
}
/* If the instruction isn't svc::SendSyncRequest, it's not the fatal caller. */
if (insn != SvcSendSyncRequestInstruction) {
return false;
}
/* Read in the fatal caller's TLS. */
u8 thread_tls[sizeof(svc::ThreadLocalRegion::message_buffer)];
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(thread_tls), debug_handle, thread_tls_addr, sizeof(thread_tls)))) {
return false;
}
/* We want to parse the command the fatal caller sent. */
{
const auto request = hipcParseRequest(thread_tls);
const struct {
CmifInHeader header;
Result result;
} *in_data = decltype(in_data)(request.data.data_words);
static_assert(sizeof(*in_data) == 0x14, "InData!");
/* Fatal command takes in a PID, only one buffer max. */
if ((request.meta.type != CmifCommandType_Request && request.meta.type != CmifCommandType_RequestWithContext) ||
!request.meta.send_pid ||
request.meta.num_send_statics ||
request.meta.num_recv_statics ||
request.meta.num_recv_buffers ||
request.meta.num_exch_buffers ||
request.meta.num_copy_handles ||
request.meta.num_move_handles ||
request.meta.num_data_words < ((sizeof(*in_data) + 0x10) / sizeof(u32)))
{
return false;
}
if (in_data->header.magic != CMIF_IN_HEADER_MAGIC) {
return false;
}
if (in_data->header.version > 1) {
return false;
}
switch (in_data->header.command_id) {
case 0:
case 1:
if (request.meta.num_send_buffers != 0) {
return false;
}
break;
case 2:
if (request.meta.num_send_buffers != 1) {
return false;
}
break;
default:
return false;
}
if (in_data->result.GetValue() != result.GetValue()) {
return false;
}
}
/* We found our caller. */
return true;
}
bool TryGuessBaseAddress(u64 *out_base_address, os::NativeHandle debug_handle, u64 guess) {
svc::MemoryInfo mi;
svc::PageInfo pi;
if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), debug_handle, guess)) || mi.permission != svc::MemoryPermission_ReadExecute) {
return false;
}
/* Iterate backwards until we find the memory before the code region. */
while (mi.base_address > 0) {
if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), debug_handle, guess))) {
return false;
}
if (mi.state == svc::MemoryState_Free) {
/* Code region will be at the end of the unmapped region preceding it. */
*out_base_address = mi.base_address + mi.size;
return true;
}
guess = mi.base_address - 4;
}
return false;
}
u64 GetBaseAddress(const ThrowContext *throw_ctx, const svc::ThreadContext *thread_ctx, os::NativeHandle debug_handle) {
u64 base_address = 0;
if (TryGuessBaseAddress(std::addressof(base_address), debug_handle, thread_ctx->pc)) {
return base_address;
}
if (TryGuessBaseAddress(std::addressof(base_address), debug_handle, thread_ctx->lr)) {
return base_address;
}
for (size_t i = 0; i < throw_ctx->cpu_ctx.aarch64_ctx.stack_trace_size; i++) {
if (TryGuessBaseAddress(std::addressof(base_address), debug_handle, throw_ctx->cpu_ctx.aarch64_ctx.stack_trace[i])) {
return base_address;
}
}
return base_address;
}
}
void TryCollectDebugInformation(ThrowContext *ctx, os::ProcessId process_id) {
/* Try to debug the process. This may fail, if we called into ourself. */
os::NativeHandle debug_handle;
if (R_FAILED(svc::DebugActiveProcess(std::addressof(debug_handle), process_id.value))) {
return;
}
ON_SCOPE_EXIT { os::CloseNativeHandle(debug_handle); };
/* First things first, check if process is 64 bits, and get list of thread infos. */
g_thread_id_to_tls_map.ResetThreadTlsMap();
{
bool got_create_process = false;
svc::DebugEventInfo d;
while (R_SUCCEEDED(svc::GetDebugEvent(std::addressof(d), debug_handle))) {
switch (d.type) {
case svc::DebugEvent_CreateProcess:
ctx->cpu_ctx.architecture = (d.info.create_process.flags & 1) ? CpuContext::Architecture_Aarch64 : CpuContext::Architecture_Aarch32;
std::memcpy(ctx->proc_name, d.info.create_process.name, sizeof(d.info.create_process.name));
got_create_process = true;
break;
case svc::DebugEvent_CreateThread:
g_thread_id_to_tls_map.SetThreadTls(d.info.create_thread.thread_id, d.info.create_thread.tls_address);
break;
case svc::DebugEvent_Exception:
case svc::DebugEvent_ExitProcess:
case svc::DebugEvent_ExitThread:
break;
}
}
if (!got_create_process) {
return;
}
}
/* TODO: Try to collect information on 32-bit fatals. This shouldn't really matter for any real use case. */
if (ctx->cpu_ctx.architecture == CpuContext::Architecture_Aarch32) {
return;
}
/* Welcome to hell. Here, we try to identify which thread called into fatal. */
bool found_fatal_caller = false;
u64 thread_id = 0;
u64 thread_tls = 0;
svc::ThreadContext thread_ctx;
{
/* We start by trying to get a list of threads. */
s32 thread_count;
u64 thread_ids[0x60];
if (R_FAILED(svc::GetThreadList(std::addressof(thread_count), thread_ids, 0x60, debug_handle))) {
return;
}
/* We need to locate the thread that's called fatal. */
for (s32 i = 0; i < thread_count; i++) {
const u64 cur_thread_id = thread_ids[i];
u64 cur_thread_tls;
if (!g_thread_id_to_tls_map.GetThreadTls(std::addressof(cur_thread_tls), cur_thread_id)) {
continue;
}
if (IsThreadFatalCaller(ctx->result, debug_handle, cur_thread_id, cur_thread_tls, std::addressof(thread_ctx))) {
thread_id = cur_thread_id;
thread_tls = cur_thread_tls;
found_fatal_caller = true;
break;
}
}
if (!found_fatal_caller) {
return;
}
}
if (R_FAILED(svc::GetDebugThreadContext(std::addressof(thread_ctx), debug_handle, thread_id, svc::ThreadContextFlag_All))) {
return;
}
/* Set register states. */
ctx->cpu_ctx.aarch64_ctx.SetRegisterValue(aarch64::RegisterName_FP, thread_ctx.fp);
ctx->cpu_ctx.aarch64_ctx.SetRegisterValue(aarch64::RegisterName_LR, thread_ctx.lr);
ctx->cpu_ctx.aarch64_ctx.SetRegisterValue(aarch64::RegisterName_SP, thread_ctx.sp);
ctx->cpu_ctx.aarch64_ctx.SetRegisterValue(aarch64::RegisterName_PC, thread_ctx.pc);
/* Parse a stack trace. */
u64 cur_fp = thread_ctx.fp;
ctx->cpu_ctx.aarch64_ctx.stack_trace_size = 0;
for (unsigned int i = 0; i < aarch64::CpuContext::MaxStackTraceDepth; i++) {
/* Validate the current frame. */
if (cur_fp == 0 || (cur_fp & 0xF)) {
break;
}
/* Read a new frame. */
StackFrame cur_frame = {};
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(cur_frame)), debug_handle, cur_fp, sizeof(StackFrame)))) {
break;
}
/* Advance to the next frame. */
ctx->cpu_ctx.aarch64_ctx.stack_trace[ctx->cpu_ctx.aarch64_ctx.stack_trace_size++] = cur_frame.lr;
cur_fp = cur_frame.fp;
}
/* Try to read up to 0x100 of stack. */
ctx->stack_dump_base = 0;
for (size_t sz = 0x100; sz > 0; sz -= 0x10) {
if (R_SUCCEEDED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(ctx->stack_dump), debug_handle, thread_ctx.sp, sz))) {
ctx->stack_dump_base = thread_ctx.sp;
ctx->stack_dump_size = sz;
break;
}
}
/* Try to read the first 0x100 of TLS. */
if (R_SUCCEEDED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(ctx->tls_dump), debug_handle, thread_tls, sizeof(ctx->tls_dump)))) {
ctx->tls_address = thread_tls;
} else {
ctx->tls_address = 0;
std::memset(ctx->tls_dump, 0xCC, sizeof(ctx->tls_dump));
}
/* Parse the base address. */
ctx->cpu_ctx.aarch64_ctx.SetBaseAddress(GetBaseAddress(ctx, std::addressof(thread_ctx), debug_handle));
}
}

View File

@@ -1,23 +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>
namespace ams::fatal::srv {
void TryCollectDebugInformation(ThrowContext *ctx, os::ProcessId process_id);
}

View File

@@ -1,44 +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 "fatal_event_manager.hpp"
namespace ams::fatal::srv {
FatalEventManager::FatalEventManager() : m_lock() {
/* Just create all the events. */
for (size_t i = 0; i < FatalEventManager::NumFatalEvents; i++) {
R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(m_events[i]), os::EventClearMode_AutoClear, true));
}
}
Result FatalEventManager::GetEvent(const os::SystemEventType **out) {
std::scoped_lock lk{m_lock};
/* Only allow GetEvent to succeed NumFatalEvents times. */
R_UNLESS(m_num_events_gotten < FatalEventManager::NumFatalEvents, fatal::ResultTooManyEvents());
*out = std::addressof(m_events[m_num_events_gotten++]);
R_SUCCEED();
}
void FatalEventManager::SignalEvents() {
for (size_t i = 0; i < FatalEventManager::NumFatalEvents; i++) {
os::SignalSystemEvent(std::addressof(m_events[i]));
}
}
}

View File

@@ -1,36 +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>
namespace ams::fatal::srv {
class FatalEventManager {
NON_COPYABLE(FatalEventManager);
NON_MOVEABLE(FatalEventManager);
public:
static constexpr size_t NumFatalEvents = 3;
private:
os::SdkMutex m_lock;
size_t m_num_events_gotten = 0;
os::SystemEventType m_events[NumFatalEvents];
public:
FatalEventManager();
Result GetEvent(const os::SystemEventType **out);
void SignalEvents();
};
}

View File

@@ -1,260 +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 "fatal_config.hpp"
#include "fatal_font.hpp"
namespace ams::fatal::srv::font {
constinit lmem::HeapHandle g_font_heap_handle;
void SetHeapMemory(void *memory, size_t memory_size) {
g_font_heap_handle = lmem::CreateExpHeap(memory, memory_size, lmem::CreateOption_None);
}
void *AllocateForFont(size_t size) {
return lmem::AllocateFromExpHeap(g_font_heap_handle, size);
}
void DeallocateForFont(void *p) {
if (p != nullptr) {
return lmem::FreeToExpHeap(g_font_heap_handle, p);
}
}
}
#define STBTT_assert(x) AMS_ASSERT(x)
#define STBTT_malloc(x,u) ((void)(u),ams::fatal::srv::font::AllocateForFont(x))
#define STBTT_free(x,u) ((void)(u),ams::fatal::srv::font::DeallocateForFont(x))
#define STBTT_STATIC
#define STB_TRUETYPE_IMPLEMENTATION
#include "stb_truetype.h"
#undef STBTT_STATIC
#undef STB_TRUETYPE_IMPLEMENTATION
#undef STBTT_malloc
#undef STBTT_free
#undef STBTT_assert
/* Define color conversion macros. */
#define RGB888_TO_RGB565(r, g, b) ((((r >> 3) << 11) & 0xF800) | (((g >> 2) << 5) & 0x7E0) | ((b >> 3) & 0x1F))
#define RGB565_GET_R8(c) ((((c >> 11) & 0x1F) << 3) | ((c >> 13) & 7))
#define RGB565_GET_G8(c) ((((c >> 5) & 0x3F) << 2) | ((c >> 9) & 3))
#define RGB565_GET_B8(c) ((((c >> 0) & 0x1F) << 3) | ((c >> 2) & 7))
namespace ams::fatal::srv::font {
namespace {
/* Font state globals. */
u16 *g_frame_buffer = nullptr;
u32 (*g_unswizzle_func)(u32, u32) = nullptr;
u16 g_font_color = 0xFFFF; /* White. */
float g_font_line_pixels = 16.0f;
float g_font_size = 16.0f;
u32 g_line_x = 0, g_cur_x = 0, g_cur_y = 0;
u32 g_mono_adv = 0;
PlFontData g_font;
stbtt_fontinfo g_stb_font;
/* Helpers. */
u16 Blend(u16 color, u16 bg, u8 alpha) {
const u32 c_r = RGB565_GET_R8(color);
const u32 c_g = RGB565_GET_G8(color);
const u32 c_b = RGB565_GET_B8(color);
const u32 b_r = RGB565_GET_R8(bg);
const u32 b_g = RGB565_GET_G8(bg);
const u32 b_b = RGB565_GET_B8(bg);
const u32 r = ((alpha * c_r) + ((0xFF - alpha) * b_r)) / 0xFF;
const u32 g = ((alpha * c_g) + ((0xFF - alpha) * b_g)) / 0xFF;
const u32 b = ((alpha * c_b) + ((0xFF - alpha) * b_b)) / 0xFF;
return RGB888_TO_RGB565(r, g, b);
}
void DrawCodePoint(u32 codepoint, u32 x, u32 y) {
int width = 0, height = 0;
u8* imageptr = stbtt_GetCodepointBitmap(std::addressof(g_stb_font), g_font_size, g_font_size, codepoint, std::addressof(width), std::addressof(height), 0, 0);
ON_SCOPE_EXIT { DeallocateForFont(imageptr); };
for (int tmpy = 0; tmpy < height; tmpy++) {
for (int tmpx = 0; tmpx < width; tmpx++) {
/* Implement very simple blending, as the bitmap value is an alpha value. */
u16 *ptr = g_frame_buffer + g_unswizzle_func(x + tmpx, y + tmpy);
*ptr = Blend(g_font_color, *ptr, imageptr[width * tmpy + tmpx]);
}
}
}
void DrawString(const char *str, bool add_line, bool mono = false) {
const size_t len = strlen(str);
u32 cur_x = g_cur_x, cur_y = g_cur_y;
u32 prev_char = 0;
for (u32 i = 0; i < len; ) {
u32 cur_char;
ssize_t unit_count = decode_utf8(std::addressof(cur_char), reinterpret_cast<const u8 *>(str + i));
if (unit_count <= 0) break;
if (!g_mono_adv && i > 0) {
cur_x += g_font_size * stbtt_GetCodepointKernAdvance(std::addressof(g_stb_font), prev_char, cur_char);
}
i += unit_count;
if (cur_char == '\n') {
cur_x = g_line_x;
cur_y += g_font_line_pixels;
continue;
}
int adv_width, left_side_bearing;
stbtt_GetCodepointHMetrics(std::addressof(g_stb_font), cur_char, std::addressof(adv_width), std::addressof(left_side_bearing));
const u32 cur_width = static_cast<u32>(adv_width) * g_font_size;
int x0, y0, x1, y1;
stbtt_GetCodepointBitmapBoxSubpixel(std::addressof(g_stb_font), cur_char, g_font_size, g_font_size, 0, 0, std::addressof(x0), std::addressof(y0), std::addressof(x1), std::addressof(y1));
DrawCodePoint(cur_char, cur_x + x0 + ((mono && g_mono_adv > cur_width) ? ((g_mono_adv - cur_width) / 2) : 0), cur_y + y0);
cur_x += (mono ? g_mono_adv : cur_width);
prev_char = cur_char;
}
if (add_line) {
/* Advance to next line. */
g_cur_x = g_line_x;
g_cur_y = cur_y + g_font_line_pixels;
} else {
g_cur_x = cur_x;
g_cur_y = cur_y;
}
}
}
void PrintLine(const char *str) {
return DrawString(str, true);
}
void PrintFormatLine(const char *format, ...) {
char char_buf[0x400];
std::va_list va_arg;
va_start(va_arg, format);
util::VSNPrintf(char_buf, sizeof(char_buf), format, va_arg);
va_end(va_arg);
PrintLine(char_buf);
}
void Print(const char *str) {
return DrawString(str, false);
}
void PrintFormat(const char *format, ...) {
char char_buf[0x400];
std::va_list va_arg;
va_start(va_arg, format);
util::VSNPrintf(char_buf, sizeof(char_buf), format, va_arg);
va_end(va_arg);
Print(char_buf);
}
void PrintMonospaceU64(u64 x) {
char char_buf[0x400];
util::SNPrintf(char_buf, sizeof(char_buf), "%016lX", x);
DrawString(char_buf, false, true);
}
void PrintMonospaceU32(u32 x) {
char char_buf[0x400];
util::SNPrintf(char_buf, sizeof(char_buf), "%08X", x);
DrawString(char_buf, false, true);
}
void PrintMonospaceBlank(u32 width) {
char char_buf[0x400] = {0};
std::memset(char_buf, ' ', std::min(size_t(width), sizeof(char_buf)));
DrawString(char_buf, false, true);
}
void SetFontColor(u16 color) {
g_font_color = color;
}
void SetPosition(u32 x, u32 y) {
g_line_x = x;
g_cur_x = x;
g_cur_y = y;
}
u32 GetX() {
return g_cur_x;
}
u32 GetY() {
return g_cur_y;
}
void SetFontSize(float fsz) {
g_font_size = stbtt_ScaleForPixelHeight(std::addressof(g_stb_font), fsz * 1.375);
int ascent;
stbtt_GetFontVMetrics(std::addressof(g_stb_font), std::addressof(ascent),0,0);
g_font_line_pixels = ascent * g_font_size * 1.125;
int adv_width, left_side_bearing;
stbtt_GetCodepointHMetrics(std::addressof(g_stb_font), 'A', std::addressof(adv_width), std::addressof(left_side_bearing));
g_mono_adv = adv_width * g_font_size;
}
void AddSpacingLines(float num_lines) {
g_cur_x = g_line_x;
g_cur_y += static_cast<u32>(g_font_line_pixels * num_lines);
}
void ConfigureFontFramebuffer(u16 *fb, u32 (*unswizzle_func)(u32, u32)) {
g_frame_buffer = fb;
g_unswizzle_func = unswizzle_func;
}
Result InitializeSharedFont() {
R_TRY(plGetSharedFontByType(std::addressof(g_font), PlSharedFontType_Standard));
u8 *font_buffer = reinterpret_cast<u8 *>(g_font.address);
stbtt_InitFont(std::addressof(g_stb_font), font_buffer, stbtt_GetFontOffsetForIndex(font_buffer, 0));
SetFontSize(16.0f);
R_SUCCEED();
}
}

View File

@@ -1,39 +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>
namespace ams::fatal::srv::font {
Result InitializeSharedFont();
void ConfigureFontFramebuffer(u16 *fb, u32 (*unswizzle_func)(u32, u32));
void SetHeapMemory(void *memory, size_t memory_size);
void SetFontColor(u16 color);
void SetPosition(u32 x, u32 y);
u32 GetX();
u32 GetY();
void SetFontSize(float fsz);
void AddSpacingLines(float num_lines);
void PrintLine(const char *str);
void PrintFormatLine(const char *format, ...);
void Print(const char *str);
void PrintFormat(const char *format, ...);
void PrintMonospaceU64(u64 x);
void PrintMonospaceU32(u32 x);
void PrintMonospaceBlank(u32 width);
}

View File

@@ -1,192 +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 "fatal_service.hpp"
#include "fatal_config.hpp"
#include "fatal_repair.hpp"
#include "fatal_font.hpp"
/* Set libnx graphics globals. */
extern "C" {
u32 __nx_nv_transfermem_size = 0x40000;
ViLayerFlags __nx_vi_stray_layer_flags = (ViLayerFlags)0;
}
namespace ams {
namespace fatal::srv {
namespace {
constinit u8 g_fs_heap_memory[2_KB];
lmem::HeapHandle g_fs_heap_handle;
void *AllocateForFs(size_t size) {
return lmem::AllocateFromExpHeap(g_fs_heap_handle, size);
}
void DeallocateForFs(void *p, size_t size) {
AMS_UNUSED(size);
return lmem::FreeToExpHeap(g_fs_heap_handle, p);
}
void InitializeFsHeap() {
g_fs_heap_handle = lmem::CreateExpHeap(g_fs_heap_memory, sizeof(g_fs_heap_memory), lmem::CreateOption_ThreadSafe);
}
}
namespace {
using ServerOptions = sf::hipc::DefaultServerManagerOptions;
constexpr sm::ServiceName UserServiceName = sm::ServiceName::Encode("fatal:u");
constexpr size_t UserMaxSessions = 4;
constexpr sm::ServiceName PrivateServiceName = sm::ServiceName::Encode("fatal:p");
constexpr size_t PrivateMaxSessions = 4;
/* fatal:u, fatal:p. */
constexpr size_t NumServers = 2;
constexpr size_t NumSessions = UserMaxSessions + PrivateMaxSessions;
sf::hipc::ServerManager<NumServers, ServerOptions, NumSessions> g_server_manager;
constinit sf::UnmanagedServiceObject<fatal::impl::IService, fatal::srv::Service> g_user_service_object;
constinit sf::UnmanagedServiceObject<fatal::impl::IPrivateService, fatal::srv::Service> g_private_service_object;
void InitializeAndLoopIpcServer() {
/* Create services. */
R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_user_service_object.GetShared(), UserServiceName, UserMaxSessions));
R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_private_service_object.GetShared(), PrivateServiceName, PrivateMaxSessions));
/* Add dirty event holder. */
auto *dirty_event_holder = fatal::srv::GetFatalDirtyMultiWaitHolder();
g_server_manager.AddUserMultiWaitHolder(dirty_event_holder);
/* Loop forever, servicing our services. */
/* Because fatal has a user wait holder, we need to specify how to process manually. */
while (auto *signaled_holder = g_server_manager.WaitSignaled()) {
if (signaled_holder == dirty_event_holder) {
/* Dirty event holder was signaled. */
fatal::srv::OnFatalDirtyEvent();
g_server_manager.AddUserMultiWaitHolder(signaled_holder);
} else {
/* A server/session was signaled. Have the manager handle it. */
R_ABORT_UNLESS(g_server_manager.Process(signaled_holder));
}
}
}
}
}
namespace init {
namespace {
Result InitializePlatformServiceWithoutSessionCountForHomebrewCompatibility() {
/* NOTE: This implements a hack, to keep a session to pl:u without counting against the global limit. */
/* This is done because as of 16.0.0, pl:u is the only way to get shared font access, and there are */
/* not enough sessions for all reasonable the clients when homebrew gets involved. */
/* Please do not do similar things for other services; this is a hack which may not always work, */
/* and which could cause problems in other contexts where the ServerManager doesn't have enough */
/* slots in truth. */
/* Initialize pl. */
R_ABORT_UNLESS(plInitialize(::PlServiceType_User));
/* Get the service session for pl. */
Service *srv = plGetServiceSession();
/* Next, create a clone. */
/* Because this doesn't go through sm, this does not count against the session limit. */
Service clone;
R_TRY(serviceClone(srv, std::addressof(clone)));
/* Next, close the pl service session from sm. */
/* This decrements the used session count by one, since the session is from sm. */
serviceClose(srv);
/* HACK: replace the session with the clone we made, to restore functionality. */
*srv = clone;
return 0;
}
}
void InitializeSystemModule() {
/* Initialize heap. */
fatal::srv::InitializeFsHeap();
/* Initialize our connection to sm. */
R_ABORT_UNLESS(sm::Initialize());
/* Initialize fs. */
fs::InitializeForSystem();
fs::SetAllocator(fatal::srv::AllocateForFs, fatal::srv::DeallocateForFs);
fs::SetEnabledAutoAbort(false);
/* Initialize other services we need. */
R_ABORT_UNLESS(setInitialize());
R_ABORT_UNLESS(setsysInitialize());
R_ABORT_UNLESS(pminfoInitialize());
R_ABORT_UNLESS(i2cInitialize());
R_ABORT_UNLESS(bpcInitialize());
if (hos::GetVersion() >= hos::Version_8_0_0) {
R_ABORT_UNLESS(clkrstInitialize());
} else {
R_ABORT_UNLESS(pcvInitialize());
}
R_ABORT_UNLESS(psmInitialize());
R_ABORT_UNLESS(spsmInitialize());
R_ABORT_UNLESS(InitializePlatformServiceWithoutSessionCountForHomebrewCompatibility());
gpio::Initialize();
/* Mount the SD card. */
R_ABORT_UNLESS(fs::MountSdCard("sdmc"));
}
void FinalizeSystemModule() { /* ... */ }
void Startup() { /* ... */ }
}
void Main() {
/* Set thread name. */
os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(fatal, Main));
AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(fatal, Main));
/* Load shared font. */
R_ABORT_UNLESS(fatal::srv::font::InitializeSharedFont());
/* Check whether we should throw fatal due to repair process. */
fatal::srv::CheckRepairStatus();
/* Loop processing the IPC server. */
fatal::srv::InitializeAndLoopIpcServer();
}
}

View File

@@ -1,116 +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 "fatal_repair.hpp"
#include "fatal_service_for_self.hpp"
namespace ams::fatal::srv {
namespace {
bool IsInRepair() {
/* Before firmware 3.0.0, this wasn't implemented. */
if (hos::GetVersion() < hos::Version_3_0_0) {
return false;
}
bool in_repair;
return R_SUCCEEDED(setsysGetInRepairProcessEnableFlag(std::addressof(in_repair))) && in_repair;
}
bool IsInRepairWithoutVolHeld() {
if (IsInRepair()) {
gpio::GpioPadSession vol_btn;
if (R_FAILED(gpio::OpenSession(std::addressof(vol_btn), gpio::DeviceCode_ButtonVolUp))) {
return true;
}
/* Ensure we close even on early return. */
ON_SCOPE_EXIT { gpio::CloseSession(std::addressof(vol_btn)); };
/* Set direction input. */
gpio::SetDirection(std::addressof(vol_btn), gpio::Direction_Input);
/* Ensure that we're holding the volume button for a full second. */
auto start = os::GetSystemTick();
do {
if (gpio::GetValue(std::addressof(vol_btn)) != gpio::GpioValue_Low) {
return true;
}
/* Sleep for 100 ms. */
os::SleepThread(TimeSpan::FromMilliSeconds(100));
} while (os::ConvertToTimeSpan(os::GetSystemTick() - start) < TimeSpan::FromSeconds(1));
}
return false;
}
bool NeedsRunTimeReviser() {
/* Before firmware 5.0.0, this wasn't implemented. */
if (hos::GetVersion() < hos::Version_5_0_0) {
return false;
}
bool requires_time_reviser;
return R_SUCCEEDED(setsysGetRequiresRunRepairTimeReviser(std::addressof(requires_time_reviser))) && requires_time_reviser;
}
bool IsTimeReviserCartridgeInserted() {
FsGameCardHandle gc_hnd;
u8 gc_attr;
{
FsDeviceOperator devop;
if (R_FAILED(fsOpenDeviceOperator(std::addressof(devop)))) {
return false;
}
/* Ensure we close even on early return. */
ON_SCOPE_EXIT { fsDeviceOperatorClose(std::addressof(devop)); };
/* Check that a gamecard is inserted. */
bool inserted;
if (R_FAILED(fsDeviceOperatorIsGameCardInserted(std::addressof(devop), std::addressof(inserted))) || !inserted) {
return false;
}
/* Check that we can retrieve the gamecard's attributes. */
if (R_FAILED(fsDeviceOperatorGetGameCardHandle(std::addressof(devop), std::addressof(gc_hnd))) || R_FAILED(fsDeviceOperatorGetGameCardAttribute(std::addressof(devop), std::addressof(gc_hnd), std::addressof(gc_attr)))) {
return false;
}
}
/* Check that the gamecard is a repair tool. */
return (gc_attr & FsGameCardAttribute_RepairToolFlag);
}
bool IsInRepairWithoutTimeReviserCartridge() {
return NeedsRunTimeReviser() && IsTimeReviserCartridgeInserted();
}
}
void CheckRepairStatus() {
if (IsInRepairWithoutVolHeld()) {
ThrowFatalForSelf(ResultInRepairWithoutVolHeld());
}
if (IsInRepairWithoutTimeReviserCartridge()) {
ThrowFatalForSelf(ResultInRepairWithoutTimeReviserCartridge());
}
}
}

View File

@@ -1,23 +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>
namespace ams::fatal::srv {
void CheckRepairStatus();
}

View File

@@ -1,98 +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 "fatal_scoped_file.hpp"
namespace ams::fatal::srv {
namespace {
/* Convenience definitions. */
constexpr size_t MaximumLineLength = 0x20;
constinit os::SdkMutex g_format_lock;
constinit char g_format_buffer[2 * os::MemoryPageSize];
}
void ScopedFile::WriteString(const char *str) {
this->Write(str, std::strlen(str));
}
void ScopedFile::WriteFormat(const char *fmt, ...) {
/* Acquire exclusive access to the format buffer. */
std::scoped_lock lk(g_format_lock);
/* Format to the buffer. */
{
std::va_list vl;
va_start(vl, fmt);
util::VSNPrintf(g_format_buffer, sizeof(g_format_buffer), fmt, vl);
va_end(vl);
}
/* Write data. */
this->WriteString(g_format_buffer);
}
void ScopedFile::DumpMemory(const char *prefix, const void *data, size_t size) {
const u8 *data_u8 = reinterpret_cast<const u8 *>(data);
const int prefix_len = std::strlen(prefix);
size_t data_ofs = 0;
size_t remaining = size;
bool first = true;
while (remaining) {
const size_t cur_size = std::min(MaximumLineLength, remaining);
/* Print the line prefix. */
if (first) {
this->WriteFormat("%s", prefix);
first = false;
} else {
this->WriteFormat("%*s", prefix_len, "");
}
/* Dump up to 0x20 of hex memory. */
{
char hex[MaximumLineLength * 2 + 2] = {};
for (size_t i = 0; i < cur_size; i++) {
util::SNPrintf(hex + i * 2, 3, "%02X", data_u8[data_ofs++]);
}
hex[cur_size * 2 + 0] = '\n';
hex[cur_size * 2 + 1] = '\x00';
this->WriteString(hex);
}
/* Continue. */
remaining -= cur_size;
}
}
void ScopedFile::Write(const void *data, size_t size) {
/* If we're not open, we can't write. */
if (!this->IsOpen()) {
return;
}
/* Advance, if we write successfully. */
if (R_SUCCEEDED(fs::WriteFile(m_file, m_offset, data, size, fs::WriteOption::Flush))) {
m_offset += size;
}
}
}

View File

@@ -1,52 +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>
namespace ams::fatal::srv {
class ScopedFile {
NON_COPYABLE(ScopedFile);
NON_MOVEABLE(ScopedFile);
private:
fs::FileHandle m_file;
s64 m_offset;
bool m_opened;
public:
ScopedFile(const char *path) : m_file(), m_offset(), m_opened(false) {
if (R_SUCCEEDED(fs::CreateFile(path, 0))) {
m_opened = R_SUCCEEDED(fs::OpenFile(std::addressof(m_file), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend));
}
}
~ScopedFile() {
if (m_opened) {
fs::CloseFile(m_file);
}
}
bool IsOpen() const {
return m_opened;
}
void WriteString(const char *str);
void WriteFormat(const char *fmt, ...) __attribute__((format(printf, 2, 3)));
void DumpMemory(const char *prefix, const void *data, size_t size);
void Write(const void *data, size_t size);
};
}

View File

@@ -1,173 +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 "fatal_config.hpp"
#include "fatal_debug.hpp"
#include "fatal_service.hpp"
#include "fatal_service_for_self.hpp"
#include "fatal_event_manager.hpp"
#include "fatal_task.hpp"
namespace ams::fatal::srv {
namespace {
/* Service Context. */
class ServiceContext {
private:
os::Event m_erpt_event;
os::Event m_battery_event;
ThrowContext m_context;
FatalEventManager m_event_manager;
bool m_has_thrown;
private:
Result TrySetHasThrown() {
R_UNLESS(!m_has_thrown, fatal::ResultAlreadyThrown());
m_has_thrown = true;
R_SUCCEED();
}
public:
ServiceContext()
: m_erpt_event(os::EventClearMode_ManualClear), m_battery_event(os::EventClearMode_ManualClear),
m_context(std::addressof(m_erpt_event), std::addressof(m_battery_event)), m_has_thrown(false)
{
/* ... */
}
Result GetEvent(const os::SystemEventType **out) {
R_RETURN(m_event_manager.GetEvent(out));
}
Result GetThrowContext(Result *out_error, ncm::ProgramId *out_program_id, FatalPolicy *out_policy, CpuContext *out_ctx) {
/* Set the output. */
*out_error = m_context.result;
*out_program_id = m_context.throw_program_id;
*out_policy = m_context.policy;
*out_ctx = m_context.cpu_ctx;
R_SUCCEED();
}
Result ThrowFatal(Result result, os::ProcessId process_id) {
R_RETURN(this->ThrowFatalWithCpuContext(result, process_id, FatalPolicy_ErrorReportAndErrorScreen, {}));
}
Result ThrowFatalWithPolicy(Result result, os::ProcessId process_id, FatalPolicy policy) {
R_RETURN(this->ThrowFatalWithCpuContext(result, process_id, policy, {}));
}
Result ThrowFatalWithCpuContext(Result result, os::ProcessId process_id, FatalPolicy policy, const CpuContext &cpu_ctx);
};
/* Context global. */
ServiceContext g_context;
/* Throw implementation. */
Result ServiceContext::ThrowFatalWithCpuContext(Result result, os::ProcessId process_id, FatalPolicy policy, const CpuContext &cpu_ctx) {
/* We don't support Error-Report-only fatals. */
R_SUCCEED_IF(policy == FatalPolicy_ErrorReport);
/* Note that we've thrown fatal. */
R_TRY(this->TrySetHasThrown());
/* At this point we have exclusive access to m_context. */
m_context.result = result;
m_context.cpu_ctx = cpu_ctx;
m_context.policy = policy;
/* Cap the stack trace to a sane limit. */
if (cpu_ctx.architecture == CpuContext::Architecture_Aarch64) {
m_context.cpu_ctx.aarch64_ctx.stack_trace_size = std::max(size_t(m_context.cpu_ctx.aarch64_ctx.stack_trace_size), aarch64::CpuContext::MaxStackTraceDepth);
} else {
m_context.cpu_ctx.aarch32_ctx.stack_trace_size = std::max(size_t(m_context.cpu_ctx.aarch32_ctx.stack_trace_size), aarch32::CpuContext::MaxStackTraceDepth);
}
/* Get program id. */
pm::info::GetProgramId(std::addressof(m_context.throw_program_id), process_id);
m_context.is_creport = (m_context.throw_program_id == ncm::SystemProgramId::Creport);
if (!m_context.is_creport) {
m_context.program_id = m_context.throw_program_id;
/* On firmware version 2.0.0, use debugging SVCs to collect information. */
if (hos::GetVersion() >= hos::Version_2_0_0) {
fatal::srv::TryCollectDebugInformation(std::addressof(m_context), process_id);
}
} else {
/* We received info from creport. Parse program id from afsr0. */
if (cpu_ctx.architecture == CpuContext::Architecture_Aarch64) {
m_context.program_id = cpu_ctx.aarch64_ctx.GetProgramIdForAtmosphere();
} else {
m_context.program_id = cpu_ctx.aarch32_ctx.GetProgramIdForAtmosphere();
}
}
/* Decide whether to generate a report. */
m_context.generate_error_report = (policy == FatalPolicy_ErrorReportAndErrorScreen);
/* Adjust error code (ResultSuccess()/2000-0000 -> err::ResultSystemProgramAbort()/2162-0002). */
if (R_SUCCEEDED(m_context.result)) {
m_context.result = err::ResultSystemProgramAbort();
}
switch (policy) {
case FatalPolicy_ErrorReportAndErrorScreen:
case FatalPolicy_ErrorScreen:
/* Signal that we're throwing. */
m_event_manager.SignalEvents();
if (GetFatalConfig().ShouldTransitionToFatal()) {
RunTasks(std::addressof(m_context));
}
break;
/* N aborts here. Should we just return an error code? */
AMS_UNREACHABLE_DEFAULT_CASE();
}
R_SUCCEED();
}
}
Result ThrowFatalForSelf(Result result) {
R_RETURN(g_context.ThrowFatalWithPolicy(result, os::GetCurrentProcessId(), FatalPolicy_ErrorScreen));
}
Result Service::ThrowFatal(Result result, const sf::ClientProcessId &client_pid) {
R_RETURN(g_context.ThrowFatal(result, client_pid.GetValue()));
}
Result Service::ThrowFatalWithPolicy(Result result, const sf::ClientProcessId &client_pid, FatalPolicy policy) {
R_RETURN(g_context.ThrowFatalWithPolicy(result, client_pid.GetValue(), policy));
}
Result Service::ThrowFatalWithCpuContext(Result result, const sf::ClientProcessId &client_pid, FatalPolicy policy, const CpuContext &cpu_ctx) {
R_RETURN(g_context.ThrowFatalWithCpuContext(result, client_pid.GetValue(), policy, cpu_ctx));
}
Result Service::GetFatalEvent(sf::OutCopyHandle out_h) {
const os::SystemEventType *event;
R_TRY(g_context.GetEvent(std::addressof(event)));
out_h.SetValue(os::GetReadableHandleOfSystemEvent(event), false);
R_SUCCEED();
}
Result Service::GetFatalContext(sf::Out<Result> out_error, sf::Out<ncm::ProgramId> out_program_id, sf::Out<fatal::FatalPolicy> out_policy, sf::Out<fatal::CpuContext> out_ctx) {
R_RETURN(g_context.GetThrowContext(out_error.GetPointer(), out_program_id.GetPointer(), out_policy.GetPointer(), out_ctx.GetPointer()));
}
}

View File

@@ -1,33 +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>
namespace ams::fatal::srv {
class Service {
public:
Result ThrowFatal(Result error, const sf::ClientProcessId &client_pid);
Result ThrowFatalWithPolicy(Result error, const sf::ClientProcessId &client_pid, FatalPolicy policy);
Result ThrowFatalWithCpuContext(Result error, const sf::ClientProcessId &client_pid, FatalPolicy policy, const CpuContext &cpu_ctx);
Result GetFatalEvent(sf::OutCopyHandle out_h);
Result GetFatalContext(sf::Out<Result> out_error, sf::Out<ncm::ProgramId> out_program_id, sf::Out<fatal::FatalPolicy> out_policy, sf::Out<fatal::CpuContext> out_ctx);
};
static_assert(fatal::impl::IsIService<Service>);
static_assert(fatal::impl::IsIPrivateService<Service>);
}

View File

@@ -1,23 +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>
namespace ams::fatal::srv {
Result ThrowFatalForSelf(Result error_code);
}

View File

@@ -1,81 +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 "fatal_task.hpp"
#include "fatal_task_error_report.hpp"
#include "fatal_task_screen.hpp"
#include "fatal_task_sound.hpp"
#include "fatal_task_clock.hpp"
#include "fatal_task_power.hpp"
namespace ams::fatal::srv {
namespace {
class TaskThread {
NON_COPYABLE(TaskThread);
private:
os::ThreadType m_thread;
private:
static void RunTaskImpl(void *arg) {
ITask *task = reinterpret_cast<ITask *>(arg);
if (R_FAILED(task->Run())) {
/* TODO: Log task failure, somehow? */
}
}
public:
TaskThread() { /* ... */ }
void StartTask(ITask *task) {
R_ABORT_UNLESS(os::CreateThread(std::addressof(m_thread), RunTaskImpl, task, task->GetStack(), task->GetStackSize(), AMS_GET_SYSTEM_THREAD_PRIORITY(fatalsrv, FatalTaskThread), 3));
os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(fatalsrv, FatalTaskThread));
os::StartThread(std::addressof(m_thread));
}
};
class TaskManager {
NON_COPYABLE(TaskManager);
private:
static constexpr size_t MaxTasks = 8;
private:
TaskThread m_task_threads[MaxTasks];
size_t m_task_count = 0;
public:
TaskManager() { /* ... */ }
void StartTask(ITask *task) {
AMS_ABORT_UNLESS(m_task_count < MaxTasks);
m_task_threads[m_task_count++].StartTask(task);
}
};
/* Global task manager. */
TaskManager g_task_manager;
}
void RunTasks(const ThrowContext *ctx) {
g_task_manager.StartTask(GetErrorReportTask(ctx));
g_task_manager.StartTask(GetPowerControlTask(ctx));
g_task_manager.StartTask(GetShowFatalTask(ctx));
g_task_manager.StartTask(GetStopSoundTask(ctx));
g_task_manager.StartTask(GetBacklightControlTask(ctx));
g_task_manager.StartTask(GetAdjustClockTask(ctx));
g_task_manager.StartTask(GetPowerButtonObserveTask(ctx));
g_task_manager.StartTask(GetStateTransitionStopTask(ctx));
}
}

View File

@@ -1,56 +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 "fatal_service.hpp"
namespace ams::fatal::srv {
class ITask {
protected:
const ThrowContext *m_context = nullptr;
public:
void Initialize(const ThrowContext *context) {
m_context = context;
}
virtual Result Run() = 0;
virtual const char *GetName() const = 0;
virtual u8 *GetStack() = 0;
virtual size_t GetStackSize() const = 0;
};
template<size_t _StackSize>
class ITaskWithStack : public ITask {
public:
static constexpr size_t StackSize = _StackSize;
static_assert(util::IsAligned(StackSize, os::MemoryPageSize), "StackSize alignment");
protected:
alignas(os::MemoryPageSize) u8 m_stack_mem[StackSize] = {};
public:
virtual u8 *GetStack() override final {
return m_stack_mem;
}
virtual size_t GetStackSize() const override final {
return StackSize;
}
};
using ITaskWithDefaultStack = ITaskWithStack<0x2000>;
void RunTasks(const ThrowContext *ctx);
}

View File

@@ -1,83 +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 "fatal_task_clock.hpp"
namespace ams::fatal::srv {
namespace {
/* Task definition. */
class AdjustClockTask : public ITaskWithDefaultStack {
private:
Result AdjustClockForModule(PcvModule module, u32 hz);
Result AdjustClock();
public:
virtual Result Run() override;
virtual const char *GetName() const override {
return "AdjustClockTask";
}
};
/* Task global. */
AdjustClockTask g_adjust_clock_task;
/* Task implementation. */
Result AdjustClockTask::AdjustClockForModule(PcvModule module, u32 hz) {
if (hos::GetVersion() >= hos::Version_8_0_0) {
/* On 8.0.0+, convert to module id + use clkrst API. */
PcvModuleId module_id;
R_TRY(pcvGetModuleId(std::addressof(module_id), module));
ClkrstSession session;
R_TRY(clkrstOpenSession(std::addressof(session), module_id, 3));
ON_SCOPE_EXIT { clkrstCloseSession(std::addressof(session)); };
R_TRY(clkrstSetClockRate(std::addressof(session), hz));
} else {
/* On 1.0.0-7.0.1, use pcv API. */
R_TRY(pcvSetClockRate(module, hz));
}
R_SUCCEED();
}
Result AdjustClockTask::AdjustClock() {
/* Fatal sets the CPU to 1020MHz, the GPU to 307 MHz, and the EMC to 1331MHz. */
constexpr u32 CPU_CLOCK_1020MHZ = 0x3CCBF700L;
constexpr u32 GPU_CLOCK_307MHZ = 0x124F8000L;
constexpr u32 EMC_CLOCK_1331MHZ = 0x4F588000L;
R_TRY(AdjustClockForModule(PcvModule_CpuBus, CPU_CLOCK_1020MHZ));
R_TRY(AdjustClockForModule(PcvModule_GPU, GPU_CLOCK_307MHZ));
R_TRY(AdjustClockForModule(PcvModule_EMC, EMC_CLOCK_1331MHZ));
R_SUCCEED();
}
Result AdjustClockTask::Run() {
R_RETURN(AdjustClock());
}
}
ITask *GetAdjustClockTask(const ThrowContext *ctx) {
g_adjust_clock_task.Initialize(ctx);
return std::addressof(g_adjust_clock_task);
}
}

View File

@@ -1,23 +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 "fatal_task.hpp"
namespace ams::fatal::srv {
ITask *GetAdjustClockTask(const ThrowContext *ctx);
}

View File

@@ -1,178 +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 "fatal_config.hpp"
#include "fatal_task_error_report.hpp"
#include "fatal_scoped_file.hpp"
namespace ams::fatal::srv {
namespace {
/* Helpers. */
void TryEnsureReportDirectories() {
fs::EnsureDirectory("sdmc:/atmosphere/fatal_reports/dumps");
}
bool TryGetCurrentTimestamp(u64 *out) {
/* Clear output. */
*out = 0;
/* Check if we have time service. */
{
bool has_time_service = false;
if (R_FAILED(sm::HasService(std::addressof(has_time_service), sm::ServiceName::Encode("time:s"))) || !has_time_service) {
return false;
}
}
/* Try to get the current time. */
{
if (R_FAILED(::timeInitialize())) {
return false;
}
ON_SCOPE_EXIT { ::timeExit(); };
return R_SUCCEEDED(::timeGetCurrentTime(TimeType_LocalSystemClock, out));
}
}
/* Task definition. */
class ErrorReportTask : public ITaskWithDefaultStack {
private:
void SaveReportToSdCard();
public:
virtual Result Run() override;
virtual const char *GetName() const override {
return "WriteErrorReport";
}
};
/* Task globals. */
ErrorReportTask g_error_report_task;
/* Task Implementation. */
void ErrorReportTask::SaveReportToSdCard() {
char file_path[fs::EntryNameLengthMax + 1];
/* Try to Ensure path exists. */
TryEnsureReportDirectories();
/* Get a timestamp. */
u64 timestamp;
if (!TryGetCurrentTimestamp(std::addressof(timestamp))) {
timestamp = os::GetSystemTick().GetInt64Value();
}
/* Open report file. */
{
util::SNPrintf(file_path, sizeof(file_path) - 1, "sdmc:/atmosphere/fatal_reports/%011lu_%016lx.log", timestamp, static_cast<u64>(m_context->program_id));
ScopedFile file(file_path);
if (file.IsOpen()) {
file.WriteFormat("Atmosphère Fatal Report (v1.1):\n");
file.WriteFormat("Result: 0x%X (2%03d-%04d)\n\n", m_context->result.GetValue(), m_context->result.GetModule(), m_context->result.GetDescription());
file.WriteFormat("Program ID: %016lx\n", static_cast<u64>(m_context->program_id));
if (strlen(m_context->proc_name)) {
file.WriteFormat("Process Name: %s\n", m_context->proc_name);
}
file.WriteFormat("Firmware: %s (Atmosphère %u.%u.%u-%s)\n", GetFatalConfig().GetFirmwareVersion().display_version, ATMOSPHERE_RELEASE_VERSION, ams::GetGitRevision());
if (m_context->cpu_ctx.architecture == CpuContext::Architecture_Aarch32) {
file.WriteFormat("General Purpose Registers:\n");
for (size_t i = 0; i <= aarch32::RegisterName_PC; i++) {
if (m_context->cpu_ctx.aarch32_ctx.HasRegisterValue(static_cast<aarch32::RegisterName>(i))) {
file.WriteFormat( " %3s: %08x\n", aarch32::CpuContext::RegisterNameStrings[i], m_context->cpu_ctx.aarch32_ctx.r[i]);
}
}
file.WriteFormat("Start Address: %08x\n", m_context->cpu_ctx.aarch32_ctx.base_address);
file.WriteFormat("Stack Trace:\n");
for (unsigned int i = 0; i < m_context->cpu_ctx.aarch32_ctx.stack_trace_size; i++) {
file.WriteFormat(" ReturnAddress[%02u]: %08x\n", i, m_context->cpu_ctx.aarch32_ctx.stack_trace[i]);
}
} else {
file.WriteFormat("General Purpose Registers:\n");
for (size_t i = 0; i <= aarch64::RegisterName_PC; i++) {
if (m_context->cpu_ctx.aarch64_ctx.HasRegisterValue(static_cast<aarch64::RegisterName>(i))) {
file.WriteFormat( " %3s: %016lx\n", aarch64::CpuContext::RegisterNameStrings[i], m_context->cpu_ctx.aarch64_ctx.x[i]);
}
}
file.WriteFormat("Start Address: %016lx\n", m_context->cpu_ctx.aarch64_ctx.base_address);
file.WriteFormat("Stack Trace:\n");
for (unsigned int i = 0; i < m_context->cpu_ctx.aarch64_ctx.stack_trace_size; i++) {
file.WriteFormat(" ReturnAddress[%02u]: %016lx\n", i, m_context->cpu_ctx.aarch64_ctx.stack_trace[i]);
}
}
if (m_context->stack_dump_size != 0) {
file.WriteFormat("Stack Dump: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n");
for (size_t i = 0; i < 0x10; i++) {
const size_t ofs = i * 0x10;
file.WriteFormat(" %012lx %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
m_context->stack_dump_base + ofs, m_context->stack_dump[ofs + 0], m_context->stack_dump[ofs + 1], m_context->stack_dump[ofs + 2], m_context->stack_dump[ofs + 3], m_context->stack_dump[ofs + 4], m_context->stack_dump[ofs + 5], m_context->stack_dump[ofs + 6], m_context->stack_dump[ofs + 7],
m_context->stack_dump[ofs + 8], m_context->stack_dump[ofs + 9], m_context->stack_dump[ofs + 10], m_context->stack_dump[ofs + 11], m_context->stack_dump[ofs + 12], m_context->stack_dump[ofs + 13], m_context->stack_dump[ofs + 14], m_context->stack_dump[ofs + 15]);
}
}
if (m_context->tls_address != 0) {
file.WriteFormat("TLS Address: %016lx\n", m_context->tls_address);
file.WriteFormat("TLS Dump: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n");
for (size_t i = 0; i < 0x10; i++) {
const size_t ofs = i * 0x10;
file.WriteFormat(" %012lx %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
m_context->tls_address + ofs, m_context->tls_dump[ofs + 0], m_context->tls_dump[ofs + 1], m_context->tls_dump[ofs + 2], m_context->tls_dump[ofs + 3], m_context->tls_dump[ofs + 4], m_context->tls_dump[ofs + 5], m_context->tls_dump[ofs + 6], m_context->tls_dump[ofs + 7],
m_context->tls_dump[ofs + 8], m_context->tls_dump[ofs + 9], m_context->tls_dump[ofs + 10], m_context->tls_dump[ofs + 11], m_context->tls_dump[ofs + 12], m_context->tls_dump[ofs + 13], m_context->tls_dump[ofs + 14], m_context->tls_dump[ofs + 15]);
}
}
}
}
/* Dump data to file. */
{
util::SNPrintf(file_path, sizeof(file_path) - 1, "sdmc:/atmosphere/fatal_reports/dumps/%011lu_%016lx.bin", timestamp, static_cast<u64>(m_context->program_id));
ScopedFile file(file_path);
if (file.IsOpen()) {
file.Write(m_context->tls_dump, sizeof(m_context->tls_dump));
if (m_context->stack_dump_size) {
file.Write(m_context->stack_dump, m_context->stack_dump_size);
}
}
}
}
Result ErrorReportTask::Run() {
if (m_context->generate_error_report) {
/* Here, Nintendo creates an error report with erpt. AMS will not do that. */
}
/* Save report to SD card. */
if (!m_context->is_creport) {
this->SaveReportToSdCard();
}
/* Signal we're done with our job. */
m_context->erpt_event->Signal();
R_SUCCEED();
}
}
ITask *GetErrorReportTask(const ThrowContext *ctx) {
g_error_report_task.Initialize(ctx);
return std::addressof(g_error_report_task);
}
}

View File

@@ -1,23 +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 "fatal_task.hpp"
namespace ams::fatal::srv {
ITask *GetErrorReportTask(const ThrowContext *ctx);
}

View File

@@ -1,241 +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 "fatal_config.hpp"
#include "fatal_task_power.hpp"
namespace ams::fatal::srv {
namespace {
/* Task types. */
class PowerControlTask : public ITaskWithDefaultStack {
private:
bool TryShutdown();
void MonitorBatteryState();
public:
virtual Result Run() override;
virtual const char *GetName() const override {
return "PowerControlTask";
}
};
class PowerButtonObserveTask : public ITaskWithDefaultStack {
private:
void WaitForPowerButton();
public:
virtual Result Run() override;
virtual const char *GetName() const override {
return "PowerButtonObserveTask";
}
};
class StateTransitionStopTask : public ITaskWithDefaultStack {
public:
virtual Result Run() override;
virtual const char *GetName() const override {
return "StateTransitionStopTask";
}
};
class RebootTimingObserver {
private:
os::Tick m_start_tick;
TimeSpan m_interval;
bool m_flag;
public:
RebootTimingObserver(bool flag, TimeSpan iv) : m_start_tick(os::GetSystemTick()), m_interval(iv), m_flag(flag) {
/* ... */
}
bool IsRebootTiming() const {
auto current_tick = os::GetSystemTick();
return m_flag && (current_tick - m_start_tick).ToTimeSpan() >= m_interval;
}
};
/* Task globals. */
PowerControlTask g_power_control_task;
PowerButtonObserveTask g_power_button_observe_task;
StateTransitionStopTask g_state_transition_stop_task;
/* Task Implementations. */
bool PowerControlTask::TryShutdown() {
/* Set a timeout of 30 seconds. */
constexpr auto MaxShutdownWaitInterval = TimeSpan::FromSeconds(30);
auto start_tick = os::GetSystemTick();
bool perform_shutdown = true;
PsmBatteryVoltageState bv_state = PsmBatteryVoltageState_Normal;
while (true) {
auto cur_tick = os::GetSystemTick();
if ((cur_tick - start_tick).ToTimeSpan() > MaxShutdownWaitInterval) {
break;
}
if (R_FAILED(psmGetBatteryVoltageState(std::addressof(bv_state))) || bv_state == PsmBatteryVoltageState_NeedsShutdown) {
break;
}
if (bv_state == PsmBatteryVoltageState_Normal) {
perform_shutdown = false;
break;
}
/* Query voltage state every 1 seconds, for 30 seconds. */
os::SleepThread(TimeSpan::FromSeconds(1));
}
if (perform_shutdown) {
bpcShutdownSystem();
}
return perform_shutdown;
}
void PowerControlTask::MonitorBatteryState() {
PsmBatteryVoltageState bv_state = PsmBatteryVoltageState_Normal;
/* Check the battery state, and shutdown on low voltage. */
if (R_FAILED(psmGetBatteryVoltageState(std::addressof(bv_state))) || bv_state == PsmBatteryVoltageState_NeedsShutdown) {
/* Wait a second for the error report task to finish. */
m_context->erpt_event->TimedWait(TimeSpan::FromSeconds(1));
this->TryShutdown();
return;
}
/* Signal we've checked the battery at least once. */
m_context->battery_event->Signal();
/* Loop querying voltage state every 5 seconds. */
while (true) {
if (R_FAILED(psmGetBatteryVoltageState(std::addressof(bv_state)))) {
bv_state = PsmBatteryVoltageState_NeedsShutdown;
}
switch (bv_state) {
case PsmBatteryVoltageState_NeedsShutdown:
case PsmBatteryVoltageState_NeedsSleep:
{
bool shutdown = this->TryShutdown();
if (shutdown) {
return;
}
}
break;
default:
break;
}
os::SleepThread(TimeSpan::FromSeconds(5));
}
}
bool IsPowerButtonHeld() {
if (hos::GetVersion() >= hos::Version_14_0_0) {
bool held = false;
return R_SUCCEEDED(bpcGetPowerButton(std::addressof(held))) && held;
} else if (hos::GetVersion() >= hos::Version_2_0_0) {
BpcSleepButtonState state;
return R_SUCCEEDED(bpcGetSleepButtonState(std::addressof(state))) && state == BpcSleepButtonState_Held;
} else {
return false;
}
}
void PowerButtonObserveTask::WaitForPowerButton() {
/* Wait up to a second for error report generation to finish. */
m_context->erpt_event->TimedWait(TimeSpan::FromSeconds(1));
/* Force a reboot after some time if kiosk unit. */
const auto &config = GetFatalConfig();
RebootTimingObserver quest_reboot_helper(config.IsQuest(), config.GetQuestRebootTimeoutInterval());
RebootTimingObserver fatal_reboot_helper(config.IsFatalRebootEnabled(), config.GetFatalRebootTimeoutInterval());
bool check_vol_up = true, check_vol_down = true;
gpio::GpioPadSession vol_up_btn, vol_down_btn;
if (R_FAILED(gpio::OpenSession(std::addressof(vol_up_btn), gpio::DeviceCode_ButtonVolUp))) {
check_vol_up = false;
}
if (R_FAILED(gpio::OpenSession(std::addressof(vol_down_btn), gpio::DeviceCode_ButtonVolDn))) {
check_vol_down = false;
}
/* Ensure we close on early return. */
ON_SCOPE_EXIT { if (check_vol_up) { gpio::CloseSession(std::addressof(vol_up_btn)); } };
ON_SCOPE_EXIT { if (check_vol_down) { gpio::CloseSession(std::addressof(vol_down_btn)); } };
/* Set direction input. */
if (check_vol_up) {
gpio::SetDirection(std::addressof(vol_up_btn), gpio::Direction_Input);
}
if (check_vol_down) {
gpio::SetDirection(std::addressof(vol_down_btn), gpio::Direction_Input);
}
while (true) {
if (fatal_reboot_helper.IsRebootTiming() || (quest_reboot_helper.IsRebootTiming()) ||
(check_vol_up && gpio::GetValue(std::addressof(vol_up_btn)) == gpio::GpioValue_Low) ||
(check_vol_down && gpio::GetValue(std::addressof(vol_down_btn)) == gpio::GpioValue_Low) ||
IsPowerButtonHeld())
{
/* If any of the above conditions succeeded, we should reboot. */
bpcRebootSystem();
return;
}
/* Wait 100 ms between button checks. */
os::SleepThread(TimeSpan::FromMilliSeconds(100));
}
}
Result PowerControlTask::Run() {
this->MonitorBatteryState();
R_SUCCEED();
}
Result PowerButtonObserveTask::Run() {
this->WaitForPowerButton();
R_SUCCEED();
}
Result StateTransitionStopTask::Run() {
/* Nintendo ignores the output of this call... */
spsmPutErrorState();
R_SUCCEED();
}
}
ITask *GetPowerControlTask(const ThrowContext *ctx) {
g_power_control_task.Initialize(ctx);
return std::addressof(g_power_control_task);
}
ITask *GetPowerButtonObserveTask(const ThrowContext *ctx) {
g_power_button_observe_task.Initialize(ctx);
return std::addressof(g_power_button_observe_task);
}
ITask *GetStateTransitionStopTask(const ThrowContext *ctx) {
g_state_transition_stop_task.Initialize(ctx);
return std::addressof(g_state_transition_stop_task);
}
}

View File

@@ -1,25 +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 "fatal_task.hpp"
namespace ams::fatal::srv {
ITask *GetPowerControlTask(const ThrowContext *ctx);
ITask *GetPowerButtonObserveTask(const ThrowContext *ctx);
ITask *GetStateTransitionStopTask(const ThrowContext *ctx);
}

View File

@@ -1,567 +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 "fatal_task_screen.hpp"
#include "fatal_config.hpp"
#include "fatal_font.hpp"
namespace ams::fatal::srv {
/* Include Atmosphere logo into its own anonymous namespace. */
namespace {
#include "fatal_ams_logo.inc"
}
namespace {
/* Screen definitions. */
constexpr u32 FatalScreenWidth = 1280;
constexpr u32 FatalScreenHeight = 720;
constexpr u32 FatalScreenBpp = 2;
constexpr u32 FatalLayerZ = 100;
constexpr u32 FatalScreenWidthAlignedBytes = util::AlignUp(FatalScreenWidth * FatalScreenBpp, 64);
constexpr u32 FatalScreenWidthAligned = FatalScreenWidthAlignedBytes / FatalScreenBpp;
/* There should only be a single transfer memory (for nv). */
alignas(os::MemoryPageSize) constinit u8 g_nv_transfer_memory[0x40000];
/* There should only be a single (1280*768) framebuffer. */
constexpr size_t FrameBufferRequiredSizeBytes = FatalScreenWidthAlignedBytes * util::AlignUp(FatalScreenHeight, 128);
constexpr size_t FrameBufferRequiredSizePageAligned = util::AlignUp(FrameBufferRequiredSizeBytes, os::MemoryPageSize);
constexpr size_t FrameBufferRequiredSizeHeapAligned = util::AlignUp(FrameBufferRequiredSizeBytes, os::MemoryHeapUnitSize);
constinit u8 *g_framebuffer_pointer = nullptr;
void InitializeFrameBufferPointer() {
/* Try to get a framebuffer from heap. */
{
if (R_SUCCEEDED(os::SetMemoryHeapSize(FrameBufferRequiredSizeHeapAligned))) {
g_framebuffer_pointer = reinterpret_cast<u8 *>(os::GetMemoryHeapAddress());
return;
}
}
/* We couldn't use heap, so try insecure memory, from the system nonsecure pool. */
{
uintptr_t address = 0;
if (R_SUCCEEDED(os::AllocateInsecureMemory(std::addressof(address), FrameBufferRequiredSizePageAligned))) {
g_framebuffer_pointer = reinterpret_cast<u8 *>(address);
return;
}
}
/* Neither heap nor insecure is available, so we're going to have to try to raid the unsafe pool. */
{
/* First, increase the limit to an extremely high value. */
size_t large_size = std::max(128_MB, FrameBufferRequiredSizeHeapAligned);
while (svc::ResultLimitReached::Includes(svc::SetUnsafeLimit(large_size))) {
large_size *= 2;
}
/* Next, map some unsafe memory. */
uintptr_t address = 0;
if (R_SUCCEEDED(os::AllocateUnsafeMemory(std::addressof(address), FrameBufferRequiredSizePageAligned))) {
g_framebuffer_pointer = reinterpret_cast<u8 *>(address);
return;
}
}
}
}
}
extern "C" ::Result __nx_nv_create_tmem(TransferMemory *t, u32 *out_size, Permission perm) {
*out_size = sizeof(ams::fatal::srv::g_nv_transfer_memory);
return tmemCreateFromMemory(t, ams::fatal::srv::g_nv_transfer_memory, sizeof(ams::fatal::srv::g_nv_transfer_memory), perm);
}
namespace ams::fatal::srv {
namespace {
/* Pixel calculation helper. */
constexpr u32 GetPixelOffset(u32 x, u32 y) {
u32 tmp_pos = ((y & 127) / 16) + (x/32*8) + ((y/16/8)*(((FatalScreenWidthAligned/2)/16*8)));
tmp_pos *= 16*16 * 4;
tmp_pos += ((y%16)/8)*512 + ((x%32)/16)*256 + ((y%8)/2)*64 + ((x%16)/8)*32 + (y%2)*16 + (x%8)*2;//This line is a modified version of code from the Tegra X1 datasheet.
return tmp_pos / 2;
}
/* Task definitions. */
class ShowFatalTask : public ITaskWithStack<0x8000> {
private:
ViDisplay m_display;
ViLayer m_layer;
NWindow m_win;
NvMap m_map;
private:
Result SetupDisplayInternal();
Result SetupDisplayExternal();
Result PrepareScreenForDrawing();
void PreRenderFrameBuffer();
Result InitializeNativeWindow();
void DisplayPreRenderedFrame();
Result ShowFatal();
public:
virtual Result Run() override;
virtual const char *GetName() const override {
return "ShowFatal";
}
};
class BacklightControlTask : public ITaskWithDefaultStack {
private:
void TurnOnBacklight();
public:
virtual Result Run() override;
virtual const char *GetName() const override {
return "BacklightControlTask";
}
};
/* Task globals. */
ShowFatalTask g_show_fatal_task;
BacklightControlTask g_backlight_control_task;
/* Task implementations. */
Result ShowFatalTask::SetupDisplayInternal() {
ViDisplay temp_display;
/* Try to open the display. */
R_TRY_CATCH(viOpenDisplay("Internal", std::addressof(temp_display))) {
R_CONVERT(vi::ResultNotFound, ResultSuccess());
} R_END_TRY_CATCH;
/* Guarantee we close the display. */
ON_SCOPE_EXIT { viCloseDisplay(std::addressof(temp_display)); };
/* Turn on the screen. */
if (hos::GetVersion() >= hos::Version_3_0_0) {
R_TRY(viSetDisplayPowerState(std::addressof(temp_display), ViPowerState_On));
} else {
/* Prior to 3.0.0, the ViPowerState enum was different (0 = Off, 1 = On). */
R_TRY(viSetDisplayPowerState(std::addressof(temp_display), ViPowerState_On_Deprecated));
}
/* Set alpha to 1.0f. */
R_TRY(viSetDisplayAlpha(std::addressof(temp_display), 1.0f));
R_SUCCEED();
}
Result ShowFatalTask::SetupDisplayExternal() {
ViDisplay temp_display;
/* Try to open the display. */
R_TRY_CATCH(viOpenDisplay("External", std::addressof(temp_display))) {
R_CONVERT(vi::ResultNotFound, ResultSuccess());
} R_END_TRY_CATCH;
/* Guarantee we close the display. */
ON_SCOPE_EXIT { viCloseDisplay(std::addressof(temp_display)); };
/* Set alpha to 1.0f. */
R_TRY(viSetDisplayAlpha(std::addressof(temp_display), 1.0f));
R_SUCCEED();
}
Result ShowFatalTask::PrepareScreenForDrawing() {
/* Connect to vi. */
R_TRY(viInitialize(ViServiceType_Manager));
/* Close other content. */
viSetContentVisibility(false);
/* Setup the two displays. */
R_TRY(SetupDisplayInternal());
R_TRY(SetupDisplayExternal());
/* Open the default display. */
R_TRY(viOpenDefaultDisplay(std::addressof(m_display)));
/* Reset the display magnification to its default value. */
s32 display_width, display_height;
R_TRY(viGetDisplayLogicalResolution(std::addressof(m_display), std::addressof(display_width), std::addressof(display_height)));
/* viSetDisplayMagnification was added in 3.0.0. */
if (hos::GetVersion() >= hos::Version_3_0_0) {
R_TRY(viSetDisplayMagnification(std::addressof(m_display), 0, 0, display_width, display_height));
}
/* Create layer to draw to. */
R_TRY(viCreateLayer(std::addressof(m_display), std::addressof(m_layer)));
/* Setup the layer. */
{
/* Display a layer of 1280 x 720 at 1.5x magnification */
/* NOTE: N uses 2 (770x400) RGBA4444 buffers (tiled buffer + linear). */
/* We use a single 1280x720 tiled RGB565 buffer. */
constexpr s32 RawWidth = FatalScreenWidth;
constexpr s32 RawHeight = FatalScreenHeight;
constexpr s32 LayerWidth = ((RawWidth) * 3) / 2;
constexpr s32 LayerHeight = ((RawHeight) * 3) / 2;
const float layer_x = static_cast<float>((display_width - LayerWidth) / 2);
const float layer_y = static_cast<float>((display_height - LayerHeight) / 2);
R_TRY(viSetLayerSize(std::addressof(m_layer), LayerWidth, LayerHeight));
/* Set the layer's Z at display maximum, to be above everything else .*/
R_TRY(viSetLayerZ(std::addressof(m_layer), FatalLayerZ));
/* Center the layer in the screen. */
R_TRY(viSetLayerPosition(std::addressof(m_layer), layer_x, layer_y));
/* Create framebuffer. */
R_TRY(nwindowCreateFromLayer(std::addressof(m_win), std::addressof(m_layer)));
R_TRY(this->InitializeNativeWindow());
}
R_SUCCEED();
}
void ShowFatalTask::PreRenderFrameBuffer() {
const FatalConfig &config = GetFatalConfig();
/* Allocate a frame buffer. */
InitializeFrameBufferPointer();
AMS_ABORT_UNLESS(g_framebuffer_pointer != nullptr);
/* Pre-render the image into the static framebuffer. */
u16 *tiled_buf = reinterpret_cast<u16 *>(g_framebuffer_pointer);
/* Temporarily use the NV transfer memory as font backing heap. */
font::SetHeapMemory(g_nv_transfer_memory, sizeof(g_nv_transfer_memory));
ON_SCOPE_EXIT { std::memset(g_nv_transfer_memory, 0, sizeof(g_nv_transfer_memory)); };
/* Let the font manager know about our framebuffer. */
font::ConfigureFontFramebuffer(tiled_buf, GetPixelOffset);
font::SetFontColor(0xFFFF);
/* Draw a background. */
for (size_t i = 0; i < FrameBufferRequiredSizeBytes / sizeof(*tiled_buf); i++) {
tiled_buf[i] = AtmosphereLogoData[0];
}
/* Draw the atmosphere logo in the upper right corner. */
const u32 start_x = 32, start_y = 64;
for (size_t y = 0; y < AtmosphereLogoHeight; y++) {
for (size_t x = 0; x < AtmosphereLogoWidth; x++) {
tiled_buf[GetPixelOffset(FatalScreenWidth - AtmosphereLogoWidth - start_x + x, start_x + y)] = AtmosphereLogoData[y * AtmosphereLogoWidth + x];
}
}
/* Draw error message and firmware. */
font::SetPosition(start_x, start_y);
font::SetFontSize(16.0f);
font::PrintFormat(config.GetErrorMessage(), m_context->result.GetModule(), m_context->result.GetDescription(), m_context->result.GetValue());
font::AddSpacingLines(0.5f);
font::PrintFormatLine( "Program: %016lX", static_cast<u64>(m_context->program_id));
font::AddSpacingLines(0.5f);
font::PrintFormatLine("Firmware: %s (Atmosphère %u.%u.%u-%s)", config.GetFirmwareVersion().display_version, ATMOSPHERE_RELEASE_VERSION, ams::GetGitRevision());
font::AddSpacingLines(1.5f);
if (!exosphere::ResultVersionMismatch::Includes(m_context->result)) {
font::Print(config.GetErrorDescription());
} else {
/* Print a special message for atmosphere version mismatch. */
font::Print("Atmosphère version mismatch detected.\n\n"
"Please press the POWER Button to restart the console normally, or a VOL button\n"
"to reboot to a payload (or RCM, if none is present). If you are unable to\n"
"restart the console, hold the POWER Button for 12 seconds to turn the console off.\n\n"
"Please ensure that all Atmosphère components are updated.\n"
"github.com/Atmosphere-NX/Atmosphere/releases\n");
}
/* Add a line. */
for (size_t x = start_x; x < FatalScreenWidth - start_x; x++) {
tiled_buf[GetPixelOffset(x, font::GetY())] = 0xFFFF;
}
font::AddSpacingLines(1.5f);
u32 backtrace_y = font::GetY();
u32 backtrace_x = 0;
u32 pc_x = 0;
/* Note architecutre. */
const bool is_aarch32 = m_context->cpu_ctx.architecture == CpuContext::Architecture_Aarch32;
/* Print GPRs. */
font::SetFontSize(14.0f);
font::Print("General Purpose Registers ");
font::PrintLine("");
font::SetPosition(start_x, font::GetY());
font::AddSpacingLines(0.5f);
if (is_aarch32) {
for (size_t i = 0; i < (aarch32::RegisterName_GeneralPurposeCount / 2); i++) {
u32 x = font::GetX();
font::PrintFormat("%s:", aarch32::CpuContext::RegisterNameStrings[i]);
font::SetPosition(x + 47, font::GetY());
if (m_context->cpu_ctx.aarch32_ctx.HasRegisterValue(static_cast<aarch32::RegisterName>(i))) {
font::PrintMonospaceU32(m_context->cpu_ctx.aarch32_ctx.r[i]);
font::PrintMonospaceBlank(8);
} else {
font::PrintMonospaceBlank(16);
}
font::Print(" ");
pc_x = font::GetX();
font::PrintFormat("%s:", aarch32::CpuContext::RegisterNameStrings[i + (aarch32::RegisterName_GeneralPurposeCount / 2)]);
font::SetPosition(pc_x + 47, font::GetY());
if (m_context->cpu_ctx.aarch32_ctx.HasRegisterValue(static_cast<aarch32::RegisterName>(i + (aarch32::RegisterName_GeneralPurposeCount / 2)))) {
font::PrintMonospaceU32(m_context->cpu_ctx.aarch32_ctx.r[i + (aarch32::RegisterName_GeneralPurposeCount / 2)]);
font::PrintMonospaceBlank(8);
} else {
font::PrintMonospaceBlank(16);
}
if (i == (aarch32::RegisterName_GeneralPurposeCount / 2) - 1) {
font::Print(" ");
backtrace_x = font::GetX();
}
font::PrintLine("");
font::SetPosition(start_x, font::GetY());
}
} else {
for (size_t i = 0; i < aarch64::RegisterName_GeneralPurposeCount / 2; i++) {
u32 x = font::GetX();
font::PrintFormat("%s:", aarch64::CpuContext::RegisterNameStrings[i]);
font::SetPosition(x + 47, font::GetY());
if (m_context->cpu_ctx.aarch64_ctx.HasRegisterValue(static_cast<aarch64::RegisterName>(i))) {
font::PrintMonospaceU64(m_context->cpu_ctx.aarch64_ctx.x[i]);
} else {
font::PrintMonospaceBlank(16);
}
font::Print(" ");
pc_x = font::GetX();
font::PrintFormat("%s:", aarch64::CpuContext::RegisterNameStrings[i + (aarch64::RegisterName_GeneralPurposeCount / 2)]);
font::SetPosition(pc_x + 47, font::GetY());
if (m_context->cpu_ctx.aarch64_ctx.HasRegisterValue(static_cast<aarch64::RegisterName>(i + (aarch64::RegisterName_GeneralPurposeCount / 2)))) {
font::PrintMonospaceU64(m_context->cpu_ctx.aarch64_ctx.x[i + (aarch64::RegisterName_GeneralPurposeCount / 2)]);
} else {
font::PrintMonospaceBlank(16);
}
if (i == (aarch64::RegisterName_GeneralPurposeCount / 2) - 1) {
font::Print(" ");
backtrace_x = font::GetX();
}
font::PrintLine("");
font::SetPosition(start_x, font::GetY());
}
}
/* Print PC. */
{
font::SetPosition(pc_x, backtrace_y);
const u32 x = font::GetX();
font::Print("PC: ");
font::SetPosition(x + 47, font::GetY());
}
if (is_aarch32) {
font::PrintMonospaceU32(m_context->cpu_ctx.aarch32_ctx.pc);
} else {
font::PrintMonospaceU64(m_context->cpu_ctx.aarch64_ctx.pc);
}
/* Print Backtrace. */
u32 bt_size;
if (is_aarch32) {
bt_size = m_context->cpu_ctx.aarch32_ctx.stack_trace_size;
} else {
bt_size = m_context->cpu_ctx.aarch64_ctx.stack_trace_size;
}
font::SetPosition(backtrace_x, backtrace_y);
if (bt_size == 0) {
if (is_aarch32) {
font::Print("Start Address: ");
font::PrintMonospaceU32(m_context->cpu_ctx.aarch32_ctx.base_address);
font::PrintLine("");
} else {
font::Print("Start Address: ");
font::PrintMonospaceU64(m_context->cpu_ctx.aarch64_ctx.base_address);
font::PrintLine("");
}
} else {
if (is_aarch32) {
font::Print("Backtrace - Start Address: ");
font::PrintMonospaceU32(m_context->cpu_ctx.aarch32_ctx.base_address);
font::PrintLine("");
font::AddSpacingLines(0.5f);
for (u32 i = 0; i < aarch32::CpuContext::MaxStackTraceDepth / 2; i++) {
u32 bt_cur = 0, bt_next = 0;
if (i < m_context->cpu_ctx.aarch32_ctx.stack_trace_size) {
bt_cur = m_context->cpu_ctx.aarch32_ctx.stack_trace[i];
}
if (i + aarch32::CpuContext::MaxStackTraceDepth / 2 < m_context->cpu_ctx.aarch32_ctx.stack_trace_size) {
bt_next = m_context->cpu_ctx.aarch32_ctx.stack_trace[i + aarch32::CpuContext::MaxStackTraceDepth / 2];
}
if (i < m_context->cpu_ctx.aarch32_ctx.stack_trace_size) {
u32 x = font::GetX();
font::PrintFormat("BT[%02d]: ", i);
font::SetPosition(x + 72, font::GetY());
font::PrintMonospaceU32(bt_cur);
font::PrintMonospaceBlank(8);
font::Print(" ");
}
if (i + aarch32::CpuContext::MaxStackTraceDepth / 2 < m_context->cpu_ctx.aarch32_ctx.stack_trace_size) {
u32 x = font::GetX();
font::PrintFormat("BT[%02d]: ", i + aarch32::CpuContext::MaxStackTraceDepth / 2);
font::SetPosition(x + 72, font::GetY());
font::PrintMonospaceU32(bt_next);
font::PrintMonospaceBlank(8);
}
font::PrintLine("");
font::SetPosition(backtrace_x, font::GetY());
}
} else {
font::Print("Backtrace - Start Address: ");
font::PrintMonospaceU64(m_context->cpu_ctx.aarch64_ctx.base_address);
font::PrintLine("");
font::AddSpacingLines(0.5f);
for (u32 i = 0; i < aarch64::CpuContext::MaxStackTraceDepth / 2; i++) {
u64 bt_cur = 0, bt_next = 0;
if (i < m_context->cpu_ctx.aarch64_ctx.stack_trace_size) {
bt_cur = m_context->cpu_ctx.aarch64_ctx.stack_trace[i];
}
if (i + aarch64::CpuContext::MaxStackTraceDepth / 2 < m_context->cpu_ctx.aarch64_ctx.stack_trace_size) {
bt_next = m_context->cpu_ctx.aarch64_ctx.stack_trace[i + aarch64::CpuContext::MaxStackTraceDepth / 2];
}
if (i < m_context->cpu_ctx.aarch64_ctx.stack_trace_size) {
u32 x = font::GetX();
font::PrintFormat("BT[%02d]: ", i);
font::SetPosition(x + 72, font::GetY());
font::PrintMonospaceU64(bt_cur);
font::Print(" ");
}
if (i + aarch64::CpuContext::MaxStackTraceDepth / 2 < m_context->cpu_ctx.aarch64_ctx.stack_trace_size) {
u32 x = font::GetX();
font::PrintFormat("BT[%02d]: ", i + aarch64::CpuContext::MaxStackTraceDepth / 2);
font::SetPosition(x + 72, font::GetY());
font::PrintMonospaceU64(bt_next);
}
font::PrintLine("");
font::SetPosition(backtrace_x, font::GetY());
}
}
}
}
Result ShowFatalTask::InitializeNativeWindow() {
/* Setup nv driver. */
R_TRY(nvInitialize());
R_TRY(nvMapInit());
R_TRY(nvFenceInit());
/* Create nvmap. */
R_TRY(nvMapCreate(std::addressof(m_map), g_framebuffer_pointer, FrameBufferRequiredSizeBytes, 0x20000, NvKind_Pitch, true));
/* Setup graphics buffer. */
{
NvGraphicBuffer grbuf = {};
grbuf.header.num_ints = (sizeof(NvGraphicBuffer) - sizeof(NativeHandle)) / 4;
grbuf.unk0 = -1;
grbuf.magic = 0xDAFFCAFF;
grbuf.pid = 42;
grbuf.usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
grbuf.format = PIXEL_FORMAT_RGB_565;
grbuf.ext_format = PIXEL_FORMAT_RGB_565;
grbuf.num_planes = 1;
grbuf.planes[0].width = FatalScreenWidth;
grbuf.planes[0].height = FatalScreenHeight;
grbuf.planes[0].color_format = NvColorFormat_R5G6B5;
grbuf.planes[0].layout = NvLayout_BlockLinear;
grbuf.planes[0].kind = NvKind_Generic_16BX2;
grbuf.planes[0].block_height_log2 = 4;
grbuf.nvmap_id = nvMapGetId(std::addressof(m_map));
grbuf.stride = FatalScreenWidthAligned;
grbuf.total_size = FrameBufferRequiredSizeBytes;
grbuf.planes[0].pitch = FatalScreenWidthAlignedBytes;
grbuf.planes[0].size = FrameBufferRequiredSizeBytes;
grbuf.planes[0].offset = 0;
R_TRY(nwindowConfigureBuffer(std::addressof(m_win), 0, std::addressof(grbuf)));
}
R_SUCCEED();
}
void ShowFatalTask::DisplayPreRenderedFrame() {
s32 slot;
R_ABORT_UNLESS(nwindowDequeueBuffer(std::addressof(m_win), std::addressof(slot), nullptr));
dd::FlushDataCache(g_framebuffer_pointer, FrameBufferRequiredSizeBytes);
R_ABORT_UNLESS(nwindowQueueBuffer(std::addressof(m_win), m_win.cur_slot, NULL));
}
Result ShowFatalTask::ShowFatal() {
/* Pre-render the framebuffer. */
PreRenderFrameBuffer();
/* Prepare screen for drawing. */
R_ABORT_UNLESS(PrepareScreenForDrawing());
/* Display the pre-rendered frame. */
this->DisplayPreRenderedFrame();
R_SUCCEED();
}
Result ShowFatalTask::Run() {
/* Don't show the fatal error screen until we've verified the battery is okay. */
m_context->battery_event->Wait();
R_RETURN(ShowFatal());
}
void BacklightControlTask::TurnOnBacklight() {
R_ABORT_UNLESS(::lblInitialize());
::lblSwitchBacklightOn(0);
::lblExit();
}
Result BacklightControlTask::Run() {
TurnOnBacklight();
R_SUCCEED();
}
}
ITask *GetShowFatalTask(const ThrowContext *ctx) {
g_show_fatal_task.Initialize(ctx);
return std::addressof(g_show_fatal_task);
}
ITask *GetBacklightControlTask(const ThrowContext *ctx) {
g_backlight_control_task.Initialize(ctx);
return std::addressof(g_backlight_control_task);
}
}

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 "fatal_task.hpp"
namespace ams::fatal::srv {
ITask *GetShowFatalTask(const ThrowContext *ctx);
ITask *GetBacklightControlTask(const ThrowContext *ctx);
}

View File

@@ -1,95 +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 "fatal_task_sound.hpp"
namespace ams::fatal::srv {
namespace {
/* Task definition. */
class StopSoundTask : public ITaskWithDefaultStack {
private:
void StopSound();
public:
virtual Result Run() override;
virtual const char *GetName() const override {
return "SoundTask";
}
};
/* Task global. */
StopSoundTask g_stop_sound_task;
/* Task implementation. */
void StopSoundTask::StopSound() {
/* Talk to the ALC5639 over I2C, and disable audio output. */
{
I2cSession audio;
if (R_SUCCEEDED(i2cOpenSession(std::addressof(audio), I2cDevice_Alc5639))) {
ON_SCOPE_EXIT { i2csessionClose(std::addressof(audio)); };
struct {
u8 reg;
u16 val;
} __attribute__((packed)) cmd;
static_assert(sizeof(cmd) == 3, "I2C command definition!");
cmd.reg = 0x01;
cmd.val = 0xC8C8;
i2csessionSendAuto(std::addressof(audio), std::addressof(cmd), sizeof(cmd), I2cTransactionOption_All);
cmd.reg = 0x02;
cmd.val = 0xC8C8;
i2csessionSendAuto(std::addressof(audio), std::addressof(cmd), sizeof(cmd), I2cTransactionOption_All);
for (u8 reg = 97; reg <= 102; reg++) {
cmd.reg = reg;
cmd.val = 0;
i2csessionSendAuto(std::addressof(audio), std::addressof(cmd), sizeof(cmd), I2cTransactionOption_All);
}
}
}
/* Talk to the ALC5639 over GPIO, and disable audio output */
{
gpio::GpioPadSession audio;
if (R_SUCCEEDED(gpio::OpenSession(std::addressof(audio), gpio::DeviceCode_CodecLdoEnTemp))) {
ON_SCOPE_EXIT { gpio::CloseSession(std::addressof(audio)); };
/* Set direction output, sleep 200 ms so it can take effect. */
gpio::SetDirection(std::addressof(audio), gpio::Direction_Output);
os::SleepThread(TimeSpan::FromMilliSeconds(200));
/* Pull audio codec low. */
gpio::SetValue(std::addressof(audio), gpio::GpioValue_Low);
}
}
}
Result StopSoundTask::Run() {
StopSound();
R_SUCCEED();
}
}
ITask *GetStopSoundTask(const ThrowContext *ctx) {
g_stop_sound_task.Initialize(ctx);
return std::addressof(g_stop_sound_task);
}
}

View File

@@ -1,23 +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 "fatal_task.hpp"
namespace ams::fatal::srv {
ITask *GetStopSoundTask(const ThrowContext *ctx);
}