mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-12-22 20:31:14 +00:00
fatal: refactor into sts namespace
This commit is contained in:
parent
442ebff829
commit
39d041466d
38 changed files with 2176 additions and 1926 deletions
|
@ -115,19 +115,22 @@ namespace sts::creport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrashReport::GetFatalContext(FatalContext *out) const {
|
void CrashReport::GetFatalContext(FatalContext *_out) const {
|
||||||
|
static_assert(sizeof(*_out) == sizeof(sts::fatal::CpuContext));
|
||||||
|
sts::fatal::CpuContext *out = reinterpret_cast<sts::fatal::CpuContext *>(_out);
|
||||||
std::memset(out, 0, sizeof(*out));
|
std::memset(out, 0, sizeof(*out));
|
||||||
|
|
||||||
/* TODO: Support generating 32-bit fatal contexts? */
|
/* TODO: Support generating 32-bit fatal contexts? */
|
||||||
out->is_aarch32 = false;
|
out->architecture = fatal::CpuContext::Architecture_Aarch64;
|
||||||
out->type = static_cast<u32>(this->exception_info.type);
|
out->type = static_cast<u32>(this->exception_info.type);
|
||||||
|
|
||||||
for (size_t i = 0; i < 29; i++) {
|
for (size_t i = 0; i < fatal::aarch64::RegisterName_FP; i++) {
|
||||||
out->aarch64_ctx.x[i] = this->crashed_thread.GetGeneralPurposeRegister(i);
|
out->aarch64_ctx.SetRegisterValue(static_cast<fatal::aarch64::RegisterName>(i), this->crashed_thread.GetGeneralPurposeRegister(i));
|
||||||
}
|
}
|
||||||
out->aarch64_ctx.fp = this->crashed_thread.GetFP();
|
out->aarch64_ctx.SetRegisterValue(fatal::aarch64::RegisterName_FP, this->crashed_thread.GetFP());
|
||||||
out->aarch64_ctx.lr = this->crashed_thread.GetLR();
|
out->aarch64_ctx.SetRegisterValue(fatal::aarch64::RegisterName_LR, this->crashed_thread.GetLR());
|
||||||
out->aarch64_ctx.pc = this->crashed_thread.GetPC();
|
out->aarch64_ctx.SetRegisterValue(fatal::aarch64::RegisterName_SP, this->crashed_thread.GetSP());
|
||||||
|
out->aarch64_ctx.SetRegisterValue(fatal::aarch64::RegisterName_PC, this->crashed_thread.GetPC());
|
||||||
|
|
||||||
out->aarch64_ctx.stack_trace_size = this->crashed_thread.GetStackTraceSize();
|
out->aarch64_ctx.stack_trace_size = this->crashed_thread.GetStackTraceSize();
|
||||||
for (size_t i = 0; i < out->aarch64_ctx.stack_trace_size; i++) {
|
for (size_t i = 0; i < out->aarch64_ctx.stack_trace_size; i++) {
|
||||||
|
@ -135,11 +138,11 @@ namespace sts::creport {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->module_list.GetModuleCount()) {
|
if (this->module_list.GetModuleCount()) {
|
||||||
out->aarch64_ctx.start_address = this->module_list.GetModuleStartAddress(0);
|
out->aarch64_ctx.SetBaseAddress(this->module_list.GetModuleStartAddress(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For ams fatal, which doesn't use afsr0, pass title_id instead. */
|
/* For ams fatal, which doesn't use afsr0, pass title_id instead. */
|
||||||
out->aarch64_ctx.afsr0 = this->process_info.title_id;
|
out->aarch64_ctx.SetTitleIdForAtmosphere(ncm::TitleId{this->process_info.title_id});
|
||||||
}
|
}
|
||||||
|
|
||||||
void CrashReport::ProcessExceptions() {
|
void CrashReport::ProcessExceptions() {
|
||||||
|
|
|
@ -92,5 +92,12 @@
|
||||||
}, {
|
}, {
|
||||||
"type": "handle_table_size",
|
"type": "handle_table_size",
|
||||||
"value": 128
|
"value": 128
|
||||||
}]
|
},
|
||||||
|
{
|
||||||
|
"type": "debug_flags",
|
||||||
|
"value": {
|
||||||
|
"allow_debug": false,
|
||||||
|
"force_debug": true
|
||||||
|
}
|
||||||
|
}]
|
||||||
}
|
}
|
|
@ -14,11 +14,10 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
constexpr size_t AtmosphereLogoWidth = 0xA0;
|
||||||
#define AMS_LOGO_WIDTH 0xA0
|
constexpr size_t AtmosphereLogoHeight = 0x80;
|
||||||
#define AMS_LOGO_HEIGHT 0x80
|
|
||||||
|
|
||||||
static constexpr u16 AMS_LOGO_BIN[] = {
|
static constexpr u16 AtmosphereLogoData[] = {
|
||||||
0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9,
|
0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9,
|
||||||
0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9,
|
0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9,
|
||||||
0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9,
|
0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9,
|
||||||
|
@ -1301,4 +1300,4 @@ static constexpr u16 AMS_LOGO_BIN[] = {
|
||||||
0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9
|
0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(AMS_LOGO_BIN) == AMS_LOGO_WIDTH * AMS_LOGO_HEIGHT * sizeof(*AMS_LOGO_BIN), "Logo definition!");
|
static_assert(util::size(AtmosphereLogoData) == AtmosphereLogoWidth * AtmosphereLogoHeight, "Logo definition!");
|
|
@ -14,78 +14,82 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include "fatal_types.hpp"
|
|
||||||
#include "fatal_config.hpp"
|
#include "fatal_config.hpp"
|
||||||
|
|
||||||
static FatalConfig g_fatal_config = {};
|
namespace sts::fatal::srv {
|
||||||
|
|
||||||
static IEvent *g_fatal_settings_event = nullptr;
|
namespace {
|
||||||
|
|
||||||
FatalConfig *GetFatalConfig() {
|
/* Global config. */
|
||||||
return &g_fatal_config;
|
FatalConfig g_config;
|
||||||
}
|
|
||||||
|
|
||||||
static void UpdateLanguageCode() {
|
/* Event creator. */
|
||||||
setGetLanguageCode(&GetFatalConfig()->language_code);
|
IEvent *CreateFatalDirtyEvent() {
|
||||||
}
|
Event evt;
|
||||||
|
R_ASSERT(setsysBindFatalDirtyFlagEvent(&evt));
|
||||||
IEvent *GetFatalSettingsEvent() {
|
return LoadReadOnlySystemEvent(evt.revent, [](u64 timeout) {
|
||||||
if (g_fatal_settings_event == nullptr) {
|
u64 flags_0, flags_1;
|
||||||
Event evt;
|
if (R_SUCCEEDED(setsysGetFatalDirtyFlags(&flags_0, &flags_1)) && (flags_0 & 1)) {
|
||||||
if (R_FAILED(setsysBindFatalDirtyFlagEvent(&evt))) {
|
g_config.UpdateLanguageCode();
|
||||||
std::abort();
|
}
|
||||||
|
return ResultSuccess;
|
||||||
|
}, true);
|
||||||
}
|
}
|
||||||
g_fatal_settings_event = LoadReadOnlySystemEvent(evt.revent, [](u64 timeout) {
|
|
||||||
u64 flags_0, flags_1;
|
/* Global event. */
|
||||||
if (R_SUCCEEDED(setsysGetFatalDirtyFlags(&flags_0, &flags_1)) && (flags_0 & 1)) {
|
IEvent *g_fatal_dirty_event = CreateFatalDirtyEvent();
|
||||||
UpdateLanguageCode();
|
|
||||||
}
|
|
||||||
return ResultSuccess;
|
|
||||||
}, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return g_fatal_settings_event;
|
FatalConfig::FatalConfig() {
|
||||||
}
|
/* Clear this. */
|
||||||
|
std::memset(this, 0, sizeof(*this));
|
||||||
|
|
||||||
static void SetupConfigLanguages() {
|
/* Get information from set. */
|
||||||
FatalConfig *config = GetFatalConfig();
|
setsysGetSerialNumber(this->serial_number);
|
||||||
|
setsysGetFirmwareVersion(&this->firmware_version);
|
||||||
|
setsysGetFlag(SetSysFlag_Quest, &this->quest_flag);
|
||||||
|
this->UpdateLanguageCode();
|
||||||
|
|
||||||
/* Defaults. */
|
/* Read information from settings. */
|
||||||
config->error_msg = u8"Error Code: 2%03d-%04d (0x%x)\n";
|
setsysGetSettingsItemValue("fatal", "transition_to_fatal", &this->transition_to_fatal, sizeof(this->transition_to_fatal));
|
||||||
|
setsysGetSettingsItemValue("fatal", "show_extra_info", &this->show_extra_info, sizeof(this->show_extra_info));
|
||||||
|
setsysGetSettingsItemValue("fatal", "quest_reboot_interval_second", &this->quest_reboot_interval_second, sizeof(this->quest_reboot_interval_second));
|
||||||
|
|
||||||
if (config->quest_flag) {
|
/* Atmosphere extension for automatic reboot. */
|
||||||
config->error_desc = u8"Please call 1-800-875-1852 for service.\n";
|
if (R_SUCCEEDED(setsysGetSettingsItemValue("atmosphere", "fatal_auto_reboot_interval", &this->fatal_auto_reboot_interval, sizeof(this->fatal_auto_reboot_interval)))) {
|
||||||
} else {
|
this->fatal_auto_reboot_enabled = this->fatal_auto_reboot_interval != 0;
|
||||||
config->error_desc = u8"An error has occured.\n\n"
|
}
|
||||||
u8"Please press the POWER Button to restart the console normally, or a VOL button\n"
|
|
||||||
u8"to reboot to a payload (or RCM, if none is present). If you are unable to\n"
|
/* Setup messages. */
|
||||||
u8"restart the console, hold the POWER Button for 12 seconds to turn the console off.\n\n"
|
{
|
||||||
u8"If the problem persists, refer to the Nintendo Support Website.\n"
|
this->error_msg = u8"Error Code: 2%03d-%04d (0x%x)\n";
|
||||||
u8"support.nintendo.com/switch/error\n";
|
|
||||||
|
this->error_desc = u8"An error has occured.\n\n"
|
||||||
|
u8"Please press the POWER Button to restart the console normally, or a VOL button\n"
|
||||||
|
u8"to reboot to a payload (or RCM, if none is present). If you are unable to\n"
|
||||||
|
u8"restart the console, hold the POWER Button for 12 seconds to turn the console off.\n\n"
|
||||||
|
u8"If the problem persists, refer to the Nintendo Support Website.\n"
|
||||||
|
u8"support.nintendo.com/switch/error\n";
|
||||||
|
|
||||||
|
/* If you're running Atmosphere on a quest unit for some reason, talk to me on discord. */
|
||||||
|
this->quest_desc = u8"Please call 1-800-875-1852 for service.\n\n"
|
||||||
|
u8"Also, please be aware that running Atmosphere on a Quest device is not fully\n"
|
||||||
|
u8"supported. Perhaps try booting your device without Atmosphere before calling\n"
|
||||||
|
u8"an official Nintendo service hotline. If you encounter further issues, please\n"
|
||||||
|
u8"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"))) { ... } */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IEvent *GetFatalDirtyEvent() {
|
||||||
|
return g_fatal_dirty_event;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FatalConfig &GetFatalConfig() {
|
||||||
|
return g_config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: Try to load dynamically. */
|
|
||||||
/* FsStorage message_storage; */
|
|
||||||
/* TODO: if (R_SUCCEEDED(fsOpenDataStorageByDataId(0x010000000000081D, "fatal_msg"))) { ... } */
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitializeFatalConfig() {
|
|
||||||
FatalConfig *config = GetFatalConfig();
|
|
||||||
|
|
||||||
memset(config, 0, sizeof(*config));
|
|
||||||
setsysGetSerialNumber(config->serial_number);
|
|
||||||
setsysGetFirmwareVersion(&config->firmware_version);
|
|
||||||
UpdateLanguageCode();
|
|
||||||
|
|
||||||
setsysGetSettingsItemValue("fatal", "transition_to_fatal", &config->transition_to_fatal, sizeof(config->transition_to_fatal));
|
|
||||||
setsysGetSettingsItemValue("fatal", "show_extra_info", &config->show_extra_info, sizeof(config->show_extra_info));
|
|
||||||
setsysGetSettingsItemValue("fatal", "quest_reboot_interval_second", &config->quest_reboot_interval_second, sizeof(config->quest_reboot_interval_second));
|
|
||||||
|
|
||||||
setsysGetFlag(SetSysFlag_Quest, &config->quest_flag);
|
|
||||||
|
|
||||||
config->is_auto_reboot_enabled = R_SUCCEEDED(setsysGetSettingsItemValue("atmosphere", "fatal_auto_reboot_interval", &config->fatal_auto_reboot_interval, sizeof(config->fatal_auto_reboot_interval)));
|
|
||||||
config->is_auto_reboot_enabled &= (config->fatal_auto_reboot_interval != 0);
|
|
||||||
|
|
||||||
SetupConfigLanguages();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,22 +18,79 @@
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <stratosphere.hpp>
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
struct FatalConfig {
|
namespace sts::fatal::srv {
|
||||||
char serial_number[0x18];
|
|
||||||
SetSysFirmwareVersion firmware_version;
|
|
||||||
u64 language_code;
|
|
||||||
u64 quest_reboot_interval_second;
|
|
||||||
bool transition_to_fatal;
|
|
||||||
bool show_extra_info;
|
|
||||||
bool quest_flag;
|
|
||||||
const char *error_msg;
|
|
||||||
const char *error_desc;
|
|
||||||
const char *quest_desc;
|
|
||||||
u64 fatal_auto_reboot_interval;
|
|
||||||
bool is_auto_reboot_enabled;
|
|
||||||
};
|
|
||||||
|
|
||||||
IEvent *GetFatalSettingsEvent();
|
class FatalConfig {
|
||||||
FatalConfig *GetFatalConfig();
|
private:
|
||||||
|
char serial_number[0x18];
|
||||||
|
SetSysFirmwareVersion firmware_version;
|
||||||
|
u64 language_code;
|
||||||
|
u64 quest_reboot_interval_second;
|
||||||
|
bool transition_to_fatal;
|
||||||
|
bool show_extra_info;
|
||||||
|
bool quest_flag;
|
||||||
|
const char *error_msg;
|
||||||
|
const char *error_desc;
|
||||||
|
const char *quest_desc;
|
||||||
|
u64 fatal_auto_reboot_interval;
|
||||||
|
bool fatal_auto_reboot_enabled;
|
||||||
|
public:
|
||||||
|
FatalConfig();
|
||||||
|
|
||||||
void InitializeFatalConfig();
|
const char *GetSerialNumber() const {
|
||||||
|
return this->serial_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SetSysFirmwareVersion &GetFirmwareVersion() const {
|
||||||
|
return this->firmware_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateLanguageCode() {
|
||||||
|
setGetLanguageCode(&this->language_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 GetLanguageCode() const {
|
||||||
|
return this->language_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShouldTransitionToFatal() const {
|
||||||
|
return this->transition_to_fatal;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShouldShowExtraInfo() const {
|
||||||
|
return this->show_extra_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsQuest() const {
|
||||||
|
return this->quest_flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsFatalRebootEnabled() const {
|
||||||
|
return this->fatal_auto_reboot_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 GetQuestRebootTimeoutInterval() const {
|
||||||
|
return this->quest_reboot_interval_second * 1'000'000'000ul;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 GetFatalRebootTimeoutInterval() const {
|
||||||
|
return this->fatal_auto_reboot_interval * 1'000'000ul;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *GetErrorMessage() const {
|
||||||
|
return this->error_msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *GetErrorDescription() const {
|
||||||
|
if (this->IsQuest()) {
|
||||||
|
return this->quest_desc;
|
||||||
|
} else {
|
||||||
|
return this->error_desc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IEvent *GetFatalDirtyEvent();
|
||||||
|
const FatalConfig &GetFatalConfig();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -13,234 +13,101 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
#include <map>
|
#include <unordered_map>
|
||||||
#include <switch.h>
|
|
||||||
#include "fatal_debug.hpp"
|
#include "fatal_debug.hpp"
|
||||||
#include "fatal_config.hpp"
|
#include "fatal_config.hpp"
|
||||||
|
|
||||||
static bool IsAddressReadable(Handle debug_handle, u64 address, u64 size, MemoryInfo *o_mi) {
|
namespace sts::fatal::srv {
|
||||||
MemoryInfo mi;
|
|
||||||
u32 pi;
|
|
||||||
|
|
||||||
if (o_mi == NULL) {
|
namespace {
|
||||||
o_mi = &mi;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (R_FAILED(svcQueryDebugProcessMemory(o_mi, &pi, debug_handle, address))) {
|
constexpr u32 SvcSendSyncRequestInstruction = 0xD4000421;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Must be readable */
|
struct StackFrame {
|
||||||
if ((o_mi->perm & Perm_R) != Perm_R) {
|
u64 fp;
|
||||||
return false;
|
u64 lr;
|
||||||
}
|
};
|
||||||
|
|
||||||
/* Must have space for both userdata address and userdata size. */
|
bool IsThreadFatalCaller(u32 error_code, u32 debug_handle, u64 thread_id, u64 thread_tls_addr, ThreadContext *thread_ctx) {
|
||||||
if (address < o_mi->addr || o_mi->addr + o_mi->size < address + size) {
|
/* Verify that the thread is running or waiting. */
|
||||||
return false;
|
{
|
||||||
}
|
u64 _;
|
||||||
|
u32 _thread_state;
|
||||||
|
if (R_FAILED(svcGetDebugThreadParam(&_, &_thread_state, debug_handle, thread_id, DebugThreadParam_State))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
const svc::ThreadState thread_state = static_cast<svc::ThreadState>(_thread_state);
|
||||||
}
|
if (thread_state != svc::ThreadState::Waiting && thread_state != svc::ThreadState::Running) {
|
||||||
|
return false;
|
||||||
static bool CheckThreadIsFatalCaller(FatalThrowContext *ctx, u64 debug_handle, u64 thread_id, u64 thread_tls_addr, ThreadContext *thread_ctx) {
|
|
||||||
/* Verify that the thread is running or waiting. */
|
|
||||||
{
|
|
||||||
u64 _;
|
|
||||||
u32 thread_state;
|
|
||||||
if (R_FAILED(svcGetDebugThreadParam(&_, &thread_state, debug_handle, thread_id, DebugThreadParam_State))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thread_state > 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get the thread context. */
|
|
||||||
if (R_FAILED(svcGetDebugThreadContext(thread_ctx, debug_handle, thread_id, 0xF))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check if PC is readable. */
|
|
||||||
if (!IsAddressReadable(debug_handle, thread_ctx->pc.x, sizeof(u32), NULL)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Try to read the current instruction. */
|
|
||||||
u32 insn;
|
|
||||||
if (R_FAILED(svcReadDebugProcessMemory(&insn, debug_handle, thread_ctx->pc.x, sizeof(insn)))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If the instruction isn't svcSendSyncRequest, it's not the fatal caller. */
|
|
||||||
if (insn != 0xD4000421) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The fatal caller will have readable tls. */
|
|
||||||
if (!IsAddressReadable(debug_handle, thread_tls_addr, 0x100, NULL)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read in the fatal caller's tls. */
|
|
||||||
u8 thread_tls[0x100];
|
|
||||||
if (R_FAILED(svcReadDebugProcessMemory(thread_tls, debug_handle, thread_tls_addr, sizeof(thread_tls)))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Replace our tls with the fatal caller's. */
|
|
||||||
std::memcpy(armGetTls(), thread_tls, sizeof(thread_tls));
|
|
||||||
|
|
||||||
/* Parse the command that the thread sent. */
|
|
||||||
{
|
|
||||||
IpcParsedCommand r;
|
|
||||||
if (R_FAILED(ipcParse(&r))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fatal command takes in a PID, only one buffer max. */
|
|
||||||
if (!r.HasPid || r.NumStatics || r.NumStaticsOut || r.NumHandles) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u32 magic;
|
|
||||||
u32 version;
|
|
||||||
u64 cmd_id;
|
|
||||||
u32 err_code;
|
|
||||||
} *raw = (decltype(raw))(r.Raw);
|
|
||||||
|
|
||||||
if (raw->magic != SFCI_MAGIC) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (raw->cmd_id > 2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (raw->cmd_id != 2 && r.NumBuffers) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (raw->err_code != ctx->error_code) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We found our caller. */
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TryCollectDebugInformation(FatalThrowContext *ctx, u64 pid) {
|
|
||||||
Handle debug_handle;
|
|
||||||
if (R_SUCCEEDED(svcDebugActiveProcess(&debug_handle, pid))) {
|
|
||||||
/* Ensure we close the debugged process. */
|
|
||||||
ON_SCOPE_EXIT { svcCloseHandle(debug_handle); };
|
|
||||||
|
|
||||||
/* First things first, check if process is 64 bits, and get list of thread infos. */
|
|
||||||
std::unordered_map<u64, u64> thread_id_to_tls;
|
|
||||||
{
|
|
||||||
bool got_attach_process = false;
|
|
||||||
DebugEventInfo d;
|
|
||||||
while (R_SUCCEEDED(svcGetDebugEvent((u8 *)&d, debug_handle))) {
|
|
||||||
if (d.type == DebugEventType::AttachProcess) {
|
|
||||||
ctx->cpu_ctx.is_aarch32 = (d.info.attach_process.flags & 1) == 0;
|
|
||||||
memcpy(ctx->proc_name, d.info.attach_process.name, sizeof(d.info.attach_process.name));
|
|
||||||
got_attach_process = true;
|
|
||||||
} else if (d.type == DebugEventType::AttachThread) {
|
|
||||||
thread_id_to_tls[d.info.attach_thread.thread_id] = d.info.attach_thread.tls_address;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!got_attach_process) {
|
/* Get the thread context. */
|
||||||
return;
|
if (R_FAILED(svcGetDebugThreadContext(thread_ctx, debug_handle, thread_id, svc::ThreadContextFlag_All))) {
|
||||||
}
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Try to collect information on 32-bit fatals. This shouldn't really matter for any real use case. */
|
|
||||||
if (ctx->cpu_ctx.is_aarch32) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Welcome to hell. */
|
|
||||||
bool found_fatal_caller = false;
|
|
||||||
u64 thread_id = 0;
|
|
||||||
ThreadContext thread_ctx;
|
|
||||||
{
|
|
||||||
/* We start by trying to get a list of threads. */
|
|
||||||
u32 thread_count;
|
|
||||||
u64 thread_ids[0x60];
|
|
||||||
if (R_FAILED(svcGetThreadList(&thread_count, thread_ids, 0x60, debug_handle))) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We need to locate the thread that's called fatal. */
|
/* Try to read the current instruction. */
|
||||||
for (u32 i = 0; i < thread_count; i++) {
|
u32 insn;
|
||||||
const u64 cur_thread_id = thread_ids[i];
|
if (R_FAILED(svcReadDebugProcessMemory(&insn, debug_handle, thread_ctx->pc.x, sizeof(insn)))) {
|
||||||
if (thread_id_to_tls.find(cur_thread_id) == thread_id_to_tls.end()) {
|
return false;
|
||||||
continue;
|
}
|
||||||
|
|
||||||
|
/* If the instruction isn't svcSendSyncRequest, it's not the fatal caller. */
|
||||||
|
if (insn != SvcSendSyncRequestInstruction) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read in the fatal caller's TLS. */
|
||||||
|
u8 thread_tls[0x100];
|
||||||
|
if (R_FAILED(svcReadDebugProcessMemory(thread_tls, debug_handle, thread_tls_addr, sizeof(thread_tls)))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HACK: We want to parse the command the fatal caller sent. */
|
||||||
|
/* The easiest way to do this is to copy their TLS over ours, and parse ours. */
|
||||||
|
std::memcpy(armGetTls(), thread_tls, sizeof(thread_tls));
|
||||||
|
{
|
||||||
|
IpcParsedCommand r;
|
||||||
|
if (R_FAILED(ipcParse(&r))) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CheckThreadIsFatalCaller(ctx, debug_handle, cur_thread_id, thread_id_to_tls[cur_thread_id], &thread_ctx)) {
|
/* Fatal command takes in a PID, only one buffer max. */
|
||||||
thread_id = cur_thread_id;
|
if (!r.HasPid || r.NumStatics || r.NumStaticsOut || r.NumHandles) {
|
||||||
found_fatal_caller = true;
|
return false;
|
||||||
break;
|
}
|
||||||
|
|
||||||
|
struct {
|
||||||
|
u32 magic;
|
||||||
|
u32 version;
|
||||||
|
u64 cmd_id;
|
||||||
|
u32 err_code;
|
||||||
|
} *raw = (decltype(raw))(r.Raw);
|
||||||
|
|
||||||
|
if (raw->magic != SFCI_MAGIC) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (raw->cmd_id > 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (raw->cmd_id != 2 && r.NumBuffers) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (raw->err_code != error_code) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found_fatal_caller) {
|
|
||||||
return;
|
/* We found our caller. */
|
||||||
}
|
return true;
|
||||||
}
|
|
||||||
if (R_FAILED(svcGetDebugThreadContext(&thread_ctx, debug_handle, thread_id, 0xF))) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* So we found our caller. */
|
bool TryGuessBaseAddress(u64 *out_base_address, u32 debug_handle, u64 guess) {
|
||||||
for (u32 i = 0; i < 29; i++) {
|
|
||||||
/* GetDebugThreadContext won't give us any of these registers, because thread is in SVC :( */
|
|
||||||
ctx->has_gprs[i] = false;
|
|
||||||
}
|
|
||||||
for (u32 i = 29; i < NumAarch64Gprs; i++) {
|
|
||||||
ctx->has_gprs[i] = true;
|
|
||||||
}
|
|
||||||
ctx->cpu_ctx.aarch64_ctx.fp = thread_ctx.fp;
|
|
||||||
ctx->cpu_ctx.aarch64_ctx.lr = thread_ctx.lr;
|
|
||||||
ctx->cpu_ctx.aarch64_ctx.sp = thread_ctx.sp;
|
|
||||||
ctx->cpu_ctx.aarch64_ctx.pc = thread_ctx.pc.x;
|
|
||||||
|
|
||||||
|
|
||||||
/* Parse a stack trace. */
|
|
||||||
u64 cur_fp = thread_ctx.fp;
|
|
||||||
for (unsigned int i = 0; i < sizeof(ctx->cpu_ctx.aarch64_ctx.stack_trace)/sizeof(u64); i++) {
|
|
||||||
/* Validate the current frame. */
|
|
||||||
if (cur_fp == 0 || (cur_fp & 0xF)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read a new frame. */
|
|
||||||
StackFrame cur_frame;
|
|
||||||
if (R_FAILED(svcReadDebugProcessMemory(&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. */
|
|
||||||
for (size_t sz = 0x100; sz > 0; sz -= 0x10) {
|
|
||||||
if (IsAddressReadable(debug_handle, ctx->cpu_ctx.aarch64_ctx.sp, sz, nullptr)) {
|
|
||||||
if (R_SUCCEEDED(svcReadDebugProcessMemory(ctx->stack_dump, debug_handle, ctx->cpu_ctx.aarch64_ctx.sp, sz))) {
|
|
||||||
ctx->stack_dump_size = sz;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Helper to guess start address. */
|
|
||||||
auto TryGuessStartAddress = [&](u64 guess) {
|
|
||||||
MemoryInfo mi;
|
MemoryInfo mi;
|
||||||
u32 pi;
|
u32 pi;
|
||||||
if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, guess)) || mi.perm != Perm_Rx) {
|
if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, guess)) || mi.perm != Perm_Rx) {
|
||||||
|
@ -255,29 +122,146 @@ void TryCollectDebugInformation(FatalThrowContext *ctx, u64 pid) {
|
||||||
|
|
||||||
if (mi.type == MemType_Unmapped) {
|
if (mi.type == MemType_Unmapped) {
|
||||||
/* Code region will be at the end of the unmapped region preceding it. */
|
/* Code region will be at the end of the unmapped region preceding it. */
|
||||||
ctx->cpu_ctx.aarch64_ctx.start_address = mi.addr + mi.size;
|
*out_base_address = mi.addr + mi.size;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
guess -= 4;
|
guess = mi.addr - 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
/* Parse the starting address. */
|
u64 GetBaseAddress(const ThrowContext *throw_ctx, const ThreadContext *thread_ctx, u32 debug_handle) {
|
||||||
{
|
u64 base_address = 0;
|
||||||
if (TryGuessStartAddress(thread_ctx.pc.x)) {
|
|
||||||
return;
|
if (TryGuessBaseAddress(&base_address, debug_handle, thread_ctx->pc.x)) {
|
||||||
|
return base_address;
|
||||||
}
|
}
|
||||||
if (TryGuessStartAddress(thread_ctx.lr)) {
|
|
||||||
return;
|
if (TryGuessBaseAddress(&base_address, debug_handle, thread_ctx->lr)) {
|
||||||
|
return base_address;
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < ctx->cpu_ctx.aarch64_ctx.stack_trace_size; i++) {
|
|
||||||
if (TryGuessStartAddress(ctx->cpu_ctx.aarch64_ctx.stack_trace[i])) {
|
for (size_t i = 0; i < throw_ctx->cpu_ctx.aarch64_ctx.stack_trace_size; i++) {
|
||||||
return;
|
if (TryGuessBaseAddress(&base_address, debug_handle, throw_ctx->cpu_ctx.aarch64_ctx.stack_trace[i])) {
|
||||||
|
return base_address;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return base_address;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
void TryCollectDebugInformation(ThrowContext *ctx, u64 process_id) {
|
||||||
|
/* Try to debug the process. This may fail, if we called into ourself. */
|
||||||
|
AutoHandle debug_handle;
|
||||||
|
if (R_FAILED(svcDebugActiveProcess(debug_handle.GetPointer(), process_id))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* First things first, check if process is 64 bits, and get list of thread infos. */
|
||||||
|
std::unordered_map<u64, u64> thread_id_to_tls;
|
||||||
|
{
|
||||||
|
bool got_attach_process = false;
|
||||||
|
svc::DebugEventInfo d;
|
||||||
|
while (R_SUCCEEDED(svcGetDebugEvent(reinterpret_cast<u8 *>(&d), debug_handle.Get()))) {
|
||||||
|
switch (d.type) {
|
||||||
|
case svc::DebugEventType::AttachProcess:
|
||||||
|
ctx->cpu_ctx.architecture = (d.info.attach_process.flags & 1) ? CpuContext::Architecture_Aarch64 : CpuContext::Architecture_Aarch32;
|
||||||
|
std::memcpy(ctx->proc_name, d.info.attach_process.name, sizeof(d.info.attach_process.name));
|
||||||
|
got_attach_process = true;
|
||||||
|
break;
|
||||||
|
case svc::DebugEventType::AttachThread:
|
||||||
|
thread_id_to_tls[d.info.attach_thread.thread_id] = d.info.attach_thread.tls_address;
|
||||||
|
break;
|
||||||
|
case svc::DebugEventType::Exception:
|
||||||
|
case svc::DebugEventType::ExitProcess:
|
||||||
|
case svc::DebugEventType::ExitThread:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!got_attach_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;
|
||||||
|
ThreadContext thread_ctx;
|
||||||
|
{
|
||||||
|
/* We start by trying to get a list of threads. */
|
||||||
|
u32 thread_count;
|
||||||
|
u64 thread_ids[0x60];
|
||||||
|
if (R_FAILED(svcGetThreadList(&thread_count, thread_ids, 0x60, debug_handle.Get()))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We need to locate the thread that's called fatal. */
|
||||||
|
for (u32 i = 0; i < thread_count; i++) {
|
||||||
|
const u64 cur_thread_id = thread_ids[i];
|
||||||
|
if (thread_id_to_tls.find(cur_thread_id) == thread_id_to_tls.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsThreadFatalCaller(ctx->error_code, debug_handle.Get(), cur_thread_id, thread_id_to_tls[cur_thread_id], &thread_ctx)) {
|
||||||
|
thread_id = cur_thread_id;
|
||||||
|
found_fatal_caller = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found_fatal_caller) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (R_FAILED(svcGetDebugThreadContext(&thread_ctx, debug_handle.Get(), 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.x);
|
||||||
|
|
||||||
|
/* 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(svcReadDebugProcessMemory(&cur_frame, debug_handle.Get(), 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. */
|
||||||
|
for (size_t sz = 0x100; sz > 0; sz -= 0x10) {
|
||||||
|
if (R_SUCCEEDED(svcReadDebugProcessMemory(ctx->stack_dump, debug_handle.Get(), thread_ctx.sp, sz))) {
|
||||||
|
ctx->stack_dump_size = sz;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse the base address. */
|
||||||
|
ctx->cpu_ctx.aarch64_ctx.SetBaseAddress(GetBaseAddress(ctx, &thread_ctx, debug_handle.Get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -18,132 +18,8 @@
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <stratosphere.hpp>
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
#include "fatal_types.hpp"
|
namespace sts::fatal::srv {
|
||||||
|
|
||||||
void TryCollectDebugInformation(FatalThrowContext *ctx, u64 pid);
|
void TryCollectDebugInformation(ThrowContext *ctx, u64 process_id);
|
||||||
|
|
||||||
struct StackFrame {
|
|
||||||
u64 fp;
|
|
||||||
u64 lr;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AttachProcessInfo {
|
|
||||||
u64 title_id;
|
|
||||||
u64 process_id;
|
|
||||||
char name[0xC];
|
|
||||||
u32 flags;
|
|
||||||
u64 user_exception_context_address; /* 5.0.0+ */
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AttachThreadInfo {
|
|
||||||
u64 thread_id;
|
|
||||||
u64 tls_address;
|
|
||||||
u64 entrypoint;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* TODO: ExitProcessInfo */
|
|
||||||
/* TODO: ExitThreadInfo */
|
|
||||||
|
|
||||||
enum class DebugExceptionType : u32 {
|
|
||||||
UndefinedInstruction = 0,
|
|
||||||
InstructionAbort = 1,
|
|
||||||
DataAbort = 2,
|
|
||||||
AlignmentFault = 3,
|
|
||||||
DebuggerAttached = 4,
|
|
||||||
BreakPoint = 5,
|
|
||||||
UserBreak = 6,
|
|
||||||
DebuggerBreak = 7,
|
|
||||||
BadSvc = 8,
|
|
||||||
UnknownNine = 9,
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline const char *GetDebugExceptionTypeStr(DebugExceptionType type) {
|
|
||||||
switch (type) {
|
|
||||||
case DebugExceptionType::UndefinedInstruction:
|
|
||||||
return "Undefined Instruction";
|
|
||||||
case DebugExceptionType::InstructionAbort:
|
|
||||||
return "Instruction Abort";
|
|
||||||
case DebugExceptionType::DataAbort:
|
|
||||||
return "Data Abort";
|
|
||||||
case DebugExceptionType::AlignmentFault:
|
|
||||||
return "Alignment Fault";
|
|
||||||
case DebugExceptionType::DebuggerAttached:
|
|
||||||
return "Debugger Attached";
|
|
||||||
case DebugExceptionType::BreakPoint:
|
|
||||||
return "Break Point";
|
|
||||||
case DebugExceptionType::UserBreak:
|
|
||||||
return "User Break";
|
|
||||||
case DebugExceptionType::DebuggerBreak:
|
|
||||||
return "Debugger Break";
|
|
||||||
case DebugExceptionType::BadSvc:
|
|
||||||
return "Bad Svc";
|
|
||||||
case DebugExceptionType::UnknownNine:
|
|
||||||
return "Unknown Nine";
|
|
||||||
default:
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UndefinedInstructionInfo {
|
|
||||||
u32 insn;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DataAbortInfo {
|
|
||||||
u64 address;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AlignmentFaultInfo {
|
|
||||||
u64 address;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct UserBreakInfo {
|
|
||||||
u64 break_reason;
|
|
||||||
u64 address;
|
|
||||||
u64 size;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BadSvcInfo {
|
|
||||||
u32 id;
|
|
||||||
};
|
|
||||||
|
|
||||||
union SpecificExceptionInfo {
|
|
||||||
UndefinedInstructionInfo undefined_instruction;
|
|
||||||
DataAbortInfo data_abort;
|
|
||||||
AlignmentFaultInfo alignment_fault;
|
|
||||||
UserBreakInfo user_break;
|
|
||||||
BadSvcInfo bad_svc;
|
|
||||||
u64 raw;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ExceptionInfo {
|
|
||||||
DebugExceptionType type;
|
|
||||||
u64 address;
|
|
||||||
SpecificExceptionInfo specific;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
enum class DebugEventType : u32 {
|
|
||||||
AttachProcess = 0,
|
|
||||||
AttachThread = 1,
|
|
||||||
ExitProcess = 2,
|
|
||||||
ExitThread = 3,
|
|
||||||
Exception = 4
|
|
||||||
};
|
|
||||||
|
|
||||||
union DebugInfo {
|
|
||||||
AttachProcessInfo attach_process;
|
|
||||||
AttachThreadInfo attach_thread;
|
|
||||||
ExceptionInfo exception;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DebugEventInfo {
|
|
||||||
DebugEventType type;
|
|
||||||
u32 flags;
|
|
||||||
u64 thread_id;
|
|
||||||
union {
|
|
||||||
DebugInfo info;
|
|
||||||
u64 _[0x40/sizeof(u64)];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(DebugEventInfo) >= 0x50, "Incorrect DebugEventInfo definition!");
|
|
|
@ -14,39 +14,33 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include "fatal_types.hpp"
|
|
||||||
#include "fatal_event_manager.hpp"
|
#include "fatal_event_manager.hpp"
|
||||||
|
|
||||||
static FatalEventManager g_event_manager;
|
namespace sts::fatal::srv {
|
||||||
|
|
||||||
FatalEventManager *GetEventManager() {
|
FatalEventManager::FatalEventManager() {
|
||||||
return &g_event_manager;
|
/* Just create all the events. */
|
||||||
}
|
for (size_t i = 0; i < FatalEventManager::NumFatalEvents; i++) {
|
||||||
|
R_ASSERT(eventCreate(&this->events[i], true));
|
||||||
FatalEventManager::FatalEventManager() {
|
|
||||||
/* Just create all the events. */
|
|
||||||
for (size_t i = 0; i < FatalEventManager::NumFatalEvents; i++) {
|
|
||||||
if (R_FAILED(eventCreate(&this->events[i], true))) {
|
|
||||||
std::abort();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Result FatalEventManager::GetEvent(Handle *out) {
|
Result FatalEventManager::GetEvent(Handle *out) {
|
||||||
std::scoped_lock<HosMutex> lk{this->lock};
|
std::scoped_lock lk{this->lock};
|
||||||
|
|
||||||
/* Only allow GetEvent to succeed NumFatalEvents times. */
|
/* Only allow GetEvent to succeed NumFatalEvents times. */
|
||||||
if (this->events_gotten >= FatalEventManager::NumFatalEvents) {
|
if (this->num_events_gotten >= FatalEventManager::NumFatalEvents) {
|
||||||
return ResultFatalTooManyEvents;
|
return ResultFatalTooManyEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = this->events[this->num_events_gotten++].revent;
|
||||||
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
*out = this->events[this->events_gotten++].revent;
|
void FatalEventManager::SignalEvents() {
|
||||||
return ResultSuccess;
|
for (size_t i = 0; i < FatalEventManager::NumFatalEvents; i++) {
|
||||||
}
|
eventFire(&this->events[i]);
|
||||||
|
}
|
||||||
void FatalEventManager::SignalEvents() {
|
|
||||||
for (size_t i = 0; i < FatalEventManager::NumFatalEvents; i++) {
|
|
||||||
eventFire(&this->events[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,17 +18,19 @@
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <stratosphere.hpp>
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
class FatalEventManager {
|
namespace sts::fatal::srv {
|
||||||
private:
|
|
||||||
static constexpr size_t NumFatalEvents = 3;
|
|
||||||
|
|
||||||
HosMutex lock;
|
class FatalEventManager {
|
||||||
size_t events_gotten = 0;
|
private:
|
||||||
Event events[NumFatalEvents];
|
static constexpr size_t NumFatalEvents = 3;
|
||||||
public:
|
|
||||||
FatalEventManager();
|
|
||||||
Result GetEvent(Handle *out);
|
|
||||||
void SignalEvents();
|
|
||||||
};
|
|
||||||
|
|
||||||
FatalEventManager *GetEventManager();
|
HosMutex lock;
|
||||||
|
size_t num_events_gotten = 0;
|
||||||
|
Event events[NumFatalEvents];
|
||||||
|
public:
|
||||||
|
FatalEventManager();
|
||||||
|
Result GetEvent(Handle *out);
|
||||||
|
void SignalEvents();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -14,220 +14,228 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include "fatal_types.hpp"
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
#include <ft2build.h>
|
#include <ft2build.h>
|
||||||
#include FT_FREETYPE_H
|
#include FT_FREETYPE_H
|
||||||
|
|
||||||
#include "fatal_config.hpp"
|
#include "fatal_config.hpp"
|
||||||
#include "fatal_font.hpp"
|
#include "fatal_font.hpp"
|
||||||
|
|
||||||
static u16 *g_fb = nullptr;
|
/* Define color conversion macros. */
|
||||||
static u32 (*g_unswizzle_func)(u32, u32) = nullptr;
|
#define RGB888_TO_RGB565(r, g, b) ((((r >> 3) << 11) & 0xF800) | (((g >> 2) << 5) & 0x7E0) | ((b >> 3) & 0x1F))
|
||||||
static u16 g_font_color = 0xFFFF;
|
#define RGB565_GET_R8(c) ((((c >> 11) & 0x1F) << 3) | ((c >> 13) & 7))
|
||||||
static float g_font_sz = 16.0f;
|
#define RGB565_GET_G8(c) ((((c >> 5) & 0x3F) << 2) | ((c >> 9) & 3))
|
||||||
static u32 g_line_x = 0, g_cur_x = 0, g_cur_y = 0;
|
#define RGB565_GET_B8(c) ((((c >> 0) & 0x1F) << 3) | ((c >> 2) & 7))
|
||||||
|
|
||||||
static u32 g_mono_adv = 0;
|
namespace sts::fatal::srv::font {
|
||||||
|
|
||||||
static PlFontData g_font;
|
namespace {
|
||||||
static PlFontData g_fonts[PlSharedFontType_Total];
|
|
||||||
static FT_Library g_library;
|
|
||||||
static FT_Face g_face;
|
|
||||||
static FT_Error g_ft_err = 0;
|
|
||||||
|
|
||||||
static u16 Blend(u16 color, u16 bg, u8 alpha) {
|
/* Font state globals. */
|
||||||
const u32 c_r = RGB565_GET_R8(color);
|
u16 *g_frame_buffer = nullptr;
|
||||||
const u32 c_g = RGB565_GET_G8(color);
|
u32 (*g_unswizzle_func)(u32, u32) = nullptr;
|
||||||
const u32 c_b = RGB565_GET_B8(color);
|
u16 g_font_color = 0xFFFF; /* White. */
|
||||||
const u32 b_r = RGB565_GET_R8(bg);
|
float g_font_size = 16.0f;
|
||||||
const u32 b_g = RGB565_GET_G8(bg);
|
u32 g_line_x = 0, g_cur_x = 0, g_cur_y = 0;
|
||||||
const u32 b_b = RGB565_GET_B8(bg);
|
|
||||||
|
|
||||||
const u32 r = ((alpha * c_r) + ((0xFF - alpha) * b_r)) / 0xFF;
|
u32 g_mono_adv = 0;
|
||||||
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);
|
PlFontData g_font;
|
||||||
}
|
PlFontData g_fonts[PlSharedFontType_Total];
|
||||||
|
|
||||||
static void DrawGlyph(FT_Bitmap *bitmap, u32 x, u32 y) {
|
FT_Library g_library;
|
||||||
u8* imageptr = bitmap->buffer;
|
FT_Face g_face;
|
||||||
|
FT_Error g_ft_err = 0;
|
||||||
|
|
||||||
if (bitmap->pixel_mode!=FT_PIXEL_MODE_GRAY) return;
|
/* 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);
|
||||||
|
|
||||||
for (u32 tmpy = 0; tmpy < bitmap->rows; tmpy++) {
|
const u32 r = ((alpha * c_r) + ((0xFF - alpha) * b_r)) / 0xFF;
|
||||||
for (u32 tmpx = 0; tmpx < bitmap->width; tmpx++) {
|
const u32 g = ((alpha * c_g) + ((0xFF - alpha) * b_g)) / 0xFF;
|
||||||
/* Implement very simple blending, as the bitmap value is an alpha value. */
|
const u32 b = ((alpha * c_b) + ((0xFF - alpha) * b_b)) / 0xFF;
|
||||||
u16 *ptr = &g_fb[g_unswizzle_func(x + tmpx, y + tmpy)];
|
|
||||||
*ptr = Blend(g_font_color, *ptr, imageptr[tmpx]);
|
return RGB888_TO_RGB565(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawGlyph(FT_Bitmap *bitmap, u32 x, u32 y) {
|
||||||
|
u8* imageptr = bitmap->buffer;
|
||||||
|
|
||||||
|
if (bitmap->pixel_mode!=FT_PIXEL_MODE_GRAY) return;
|
||||||
|
|
||||||
|
for (u32 tmpy = 0; tmpy < bitmap->rows; tmpy++) {
|
||||||
|
for (u32 tmpx = 0; tmpx < bitmap->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[tmpx]);
|
||||||
|
}
|
||||||
|
imageptr += bitmap->pitch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawString(const char *str, bool add_line, bool mono = false) {
|
||||||
|
FT_UInt glyph_index;
|
||||||
|
FT_GlyphSlot slot = g_face->glyph;
|
||||||
|
|
||||||
|
const size_t len = strlen(str);
|
||||||
|
|
||||||
|
u32 cur_x = g_cur_x, cur_y = g_cur_y;
|
||||||
|
ON_SCOPE_EXIT {
|
||||||
|
if (add_line) {
|
||||||
|
/* Advance to next line. */
|
||||||
|
g_cur_x = g_line_x;
|
||||||
|
g_cur_y = cur_y + (g_face->size->metrics.height >> 6);
|
||||||
|
} else {
|
||||||
|
g_cur_x = cur_x;
|
||||||
|
g_cur_y = cur_y;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (u32 i = 0; i < len; ) {
|
||||||
|
u32 cur_char;
|
||||||
|
ssize_t unit_count = decode_utf8(&cur_char, reinterpret_cast<const u8 *>(&str[i]));
|
||||||
|
if (unit_count <= 0) break;
|
||||||
|
i += unit_count;
|
||||||
|
|
||||||
|
if (cur_char == '\n') {
|
||||||
|
cur_x = g_line_x;
|
||||||
|
cur_y += g_face->size->metrics.height >> 6;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
glyph_index = FT_Get_Char_Index(g_face, cur_char);
|
||||||
|
|
||||||
|
g_ft_err = FT_Load_Glyph(g_face, glyph_index, FT_LOAD_DEFAULT);
|
||||||
|
|
||||||
|
if (g_ft_err == 0) {
|
||||||
|
g_ft_err = FT_Render_Glyph(g_face->glyph, FT_RENDER_MODE_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_ft_err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawGlyph(&slot->bitmap, cur_x + slot->bitmap_left + ((mono && g_mono_adv > slot->advance.x) ? ((g_mono_adv - slot->advance.x) >> 7) : 0), cur_y - slot->bitmap_top);
|
||||||
|
|
||||||
|
cur_x += (mono ? g_mono_adv : slot->advance.x) >> 6;
|
||||||
|
cur_y += slot->advance.y >> 6;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
imageptr += bitmap->pitch;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static void DrawString(const char *str, bool add_line, bool mono = false) {
|
void PrintLine(const char *str) {
|
||||||
FT_UInt glyph_index;
|
return DrawString(str, true);
|
||||||
FT_GlyphSlot slot = g_face->glyph;
|
}
|
||||||
|
|
||||||
const size_t len = strlen(str);
|
void PrintFormatLine(const char *format, ...) {
|
||||||
|
char char_buf[0x400];
|
||||||
|
|
||||||
u32 cur_x = g_cur_x, cur_y = g_cur_y;
|
va_list va_arg;
|
||||||
ON_SCOPE_EXIT {
|
va_start(va_arg, format);
|
||||||
if (add_line) {
|
vsnprintf(char_buf, sizeof(char_buf), format, va_arg);
|
||||||
/* Advance to next line. */
|
va_end(va_arg);
|
||||||
g_cur_x = g_line_x;
|
|
||||||
g_cur_y = cur_y + (g_face->size->metrics.height >> 6);
|
|
||||||
} else {
|
|
||||||
g_cur_x = cur_x;
|
|
||||||
g_cur_y = cur_y;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (u32 i = 0; i < len; ) {
|
PrintLine(char_buf);
|
||||||
u32 cur_char;
|
}
|
||||||
ssize_t unit_count = decode_utf8(&cur_char, reinterpret_cast<const u8 *>(&str[i]));
|
|
||||||
if (unit_count <= 0) break;
|
|
||||||
i += unit_count;
|
|
||||||
|
|
||||||
if (cur_char == '\n') {
|
void Print(const char *str) {
|
||||||
cur_x = g_line_x;
|
return DrawString(str, false);
|
||||||
cur_y += g_face->size->metrics.height >> 6;
|
}
|
||||||
continue;
|
|
||||||
|
void PrintFormat(const char *format, ...) {
|
||||||
|
char char_buf[0x400];
|
||||||
|
|
||||||
|
va_list va_arg;
|
||||||
|
va_start(va_arg, format);
|
||||||
|
vsnprintf(char_buf, sizeof(char_buf), format, va_arg);
|
||||||
|
va_end(va_arg);
|
||||||
|
|
||||||
|
Print(char_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintMonospaceU64(u64 x) {
|
||||||
|
char char_buf[0x400];
|
||||||
|
snprintf(char_buf, sizeof(char_buf), "%016lX", x);
|
||||||
|
|
||||||
|
DrawString(char_buf, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintMonospaceU32(u32 x) {
|
||||||
|
char char_buf[0x400];
|
||||||
|
snprintf(char_buf, sizeof(char_buf), "%08X", x);
|
||||||
|
|
||||||
|
DrawString(char_buf, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintMonospaceBlank(u32 width) {
|
||||||
|
char char_buf[0x400] = {0};
|
||||||
|
for (size_t i = 0; i < width && i < sizeof(char_buf); i++) {
|
||||||
|
char_buf[i] = ' ';
|
||||||
}
|
}
|
||||||
|
|
||||||
glyph_index = FT_Get_Char_Index(g_face, cur_char);
|
DrawString(char_buf, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
g_ft_err = FT_Load_Glyph(g_face, glyph_index, FT_LOAD_DEFAULT);
|
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 = fsz;
|
||||||
|
g_ft_err = FT_Set_Char_Size(g_face, 0, static_cast<u32>(g_font_size * 64.0f), 96, 96);
|
||||||
|
|
||||||
|
g_ft_err = FT_Load_Glyph(g_face, FT_Get_Char_Index(g_face, 'A'), FT_LOAD_DEFAULT);
|
||||||
|
|
||||||
if (g_ft_err == 0) {
|
if (g_ft_err == 0) {
|
||||||
g_ft_err = FT_Render_Glyph(g_face->glyph, FT_RENDER_MODE_NORMAL);
|
g_ft_err = FT_Render_Glyph(g_face->glyph, FT_RENDER_MODE_NORMAL);
|
||||||
}
|
}
|
||||||
|
if (g_ft_err == 0) {
|
||||||
if (g_ft_err) {
|
g_mono_adv = g_face->glyph->advance.x;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawGlyph(&slot->bitmap, cur_x + slot->bitmap_left + ((mono && g_mono_adv > slot->advance.x) ? ((g_mono_adv - slot->advance.x) >> 7) : 0), cur_y - slot->bitmap_top);
|
|
||||||
|
|
||||||
cur_x += (mono ? g_mono_adv : slot->advance.x) >> 6;
|
|
||||||
cur_y += slot->advance.y >> 6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FontManager::PrintLine(const char *str) {
|
|
||||||
return DrawString(str, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FontManager::PrintFormatLine(const char *format, ...) {
|
|
||||||
char char_buf[0x400];
|
|
||||||
|
|
||||||
va_list va_arg;
|
|
||||||
va_start(va_arg, format);
|
|
||||||
vsnprintf(char_buf, sizeof(char_buf), format, va_arg);
|
|
||||||
va_end(va_arg);
|
|
||||||
|
|
||||||
PrintLine(char_buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FontManager::Print(const char *str) {
|
|
||||||
return DrawString(str, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FontManager::PrintFormat(const char *format, ...) {
|
|
||||||
char char_buf[0x400];
|
|
||||||
|
|
||||||
va_list va_arg;
|
|
||||||
va_start(va_arg, format);
|
|
||||||
vsnprintf(char_buf, sizeof(char_buf), format, va_arg);
|
|
||||||
va_end(va_arg);
|
|
||||||
|
|
||||||
Print(char_buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FontManager::PrintMonospaceU64(u64 x) {
|
|
||||||
char char_buf[0x400];
|
|
||||||
snprintf(char_buf, sizeof(char_buf), "%016lX", x);
|
|
||||||
|
|
||||||
DrawString(char_buf, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FontManager::PrintMonospaceU32(u32 x) {
|
|
||||||
char char_buf[0x400];
|
|
||||||
snprintf(char_buf, sizeof(char_buf), "%08X", x);
|
|
||||||
|
|
||||||
DrawString(char_buf, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FontManager::PrintMonospaceBlank(u32 width) {
|
|
||||||
char char_buf[0x400] = {0};
|
|
||||||
for (size_t i = 0; i < width && i < sizeof(char_buf); i++) {
|
|
||||||
char_buf[i] = ' ';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawString(char_buf, false, true);
|
void AddSpacingLines(float num_lines) {
|
||||||
}
|
g_cur_x = g_line_x;
|
||||||
|
g_cur_y += static_cast<u32>((static_cast<float>(g_face->size->metrics.height) * num_lines) / 64.0f);
|
||||||
void FontManager::SetFontColor(u16 color) {
|
|
||||||
g_font_color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FontManager::SetPosition(u32 x, u32 y) {
|
|
||||||
g_line_x = x;
|
|
||||||
g_cur_x = x;
|
|
||||||
g_cur_y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 FontManager::GetX() {
|
|
||||||
return g_cur_x;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 FontManager::GetY() {
|
|
||||||
return g_cur_y;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FontManager::SetFontSize(float fsz) {
|
|
||||||
g_font_sz = fsz;
|
|
||||||
g_ft_err = FT_Set_Char_Size(g_face, 0, static_cast<u32>(g_font_sz * 64.0f), 96, 96);
|
|
||||||
|
|
||||||
g_ft_err = FT_Load_Glyph(g_face, FT_Get_Char_Index(g_face, 'A'), FT_LOAD_DEFAULT);
|
|
||||||
|
|
||||||
if (g_ft_err == 0) {
|
|
||||||
g_ft_err = FT_Render_Glyph(g_face->glyph, FT_RENDER_MODE_NORMAL);
|
|
||||||
}
|
}
|
||||||
if (g_ft_err == 0) {
|
|
||||||
g_mono_adv = g_face->glyph->advance.x;
|
void ConfigureFontFramebuffer(u16 *fb, u32 (*unswizzle_func)(u32, u32)) {
|
||||||
|
g_frame_buffer = fb;
|
||||||
|
g_unswizzle_func = unswizzle_func;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result InitializeSharedFont() {
|
||||||
|
size_t total_fonts = 0;
|
||||||
|
|
||||||
|
R_TRY(plGetSharedFont(GetFatalConfig().GetLanguageCode(), g_fonts, PlSharedFontType_Total, &total_fonts));
|
||||||
|
R_TRY(plGetSharedFontByType(&g_font, PlSharedFontType_Standard));
|
||||||
|
|
||||||
|
g_ft_err = FT_Init_FreeType(&g_library);
|
||||||
|
if (g_ft_err) return g_ft_err;
|
||||||
|
|
||||||
|
g_ft_err = FT_New_Memory_Face(g_library, reinterpret_cast<const FT_Byte *>(g_font.address), g_font.size, 0, &g_face);
|
||||||
|
if (g_ft_err) return g_ft_err;
|
||||||
|
|
||||||
|
SetFontSize(g_font_size);
|
||||||
|
return g_ft_err;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FontManager::AddSpacingLines(float num_lines) {
|
|
||||||
g_cur_x = g_line_x;
|
|
||||||
g_cur_y += static_cast<u32>((static_cast<float>(g_face->size->metrics.height) * num_lines) / 64.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FontManager::ConfigureFontFramebuffer(u16 *fb, u32 (*unswizzle_func)(u32, u32)) {
|
|
||||||
g_fb = fb;
|
|
||||||
g_unswizzle_func = unswizzle_func;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FontManager::InitializeSharedFont() {
|
|
||||||
size_t total_fonts = 0;
|
|
||||||
|
|
||||||
R_TRY(plGetSharedFont(GetFatalConfig()->language_code, g_fonts, PlSharedFontType_Total, &total_fonts));
|
|
||||||
R_TRY(plGetSharedFontByType(&g_font, PlSharedFontType_Standard));
|
|
||||||
|
|
||||||
g_ft_err = FT_Init_FreeType(&g_library);
|
|
||||||
if (g_ft_err) return g_ft_err;
|
|
||||||
|
|
||||||
g_ft_err = FT_New_Memory_Face(g_library, reinterpret_cast<const FT_Byte *>(g_font.address), g_font.size, 0, &g_face);
|
|
||||||
if (g_ft_err) return g_ft_err;
|
|
||||||
|
|
||||||
SetFontSize(g_font_sz);
|
|
||||||
return g_ft_err;
|
|
||||||
}
|
|
|
@ -19,27 +19,23 @@
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <stratosphere.hpp>
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
#define RGB888_TO_RGB565(r, g, b) ((((r >> 3) << 11) & 0xF800) | (((g >> 2) << 5) & 0x7E0) | ((b >> 3) & 0x1F))
|
namespace sts::fatal::srv::font {
|
||||||
#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))
|
|
||||||
|
|
||||||
class FontManager {
|
Result InitializeSharedFont();
|
||||||
public:
|
void ConfigureFontFramebuffer(u16 *fb, u32 (*unswizzle_func)(u32, u32));
|
||||||
static Result InitializeSharedFont();
|
|
||||||
static void ConfigureFontFramebuffer(u16 *fb, u32 (*unswizzle_func)(u32, u32));
|
|
||||||
|
|
||||||
static void SetFontColor(u16 color);
|
void SetFontColor(u16 color);
|
||||||
static void SetPosition(u32 x, u32 y);
|
void SetPosition(u32 x, u32 y);
|
||||||
static u32 GetX();
|
u32 GetX();
|
||||||
static u32 GetY();
|
u32 GetY();
|
||||||
static void SetFontSize(float fsz);
|
void SetFontSize(float fsz);
|
||||||
static void AddSpacingLines(float num_lines);
|
void AddSpacingLines(float num_lines);
|
||||||
static void PrintLine(const char *str);
|
void PrintLine(const char *str);
|
||||||
static void PrintFormatLine(const char *format, ...);
|
void PrintFormatLine(const char *format, ...);
|
||||||
static void Print(const char *str);
|
void Print(const char *str);
|
||||||
static void PrintFormat(const char *format, ...);
|
void PrintFormat(const char *format, ...);
|
||||||
static void PrintMonospaceU64(u64 x);
|
void PrintMonospaceU64(u64 x);
|
||||||
static void PrintMonospaceU32(u32 x);
|
void PrintMonospaceU32(u32 x);
|
||||||
static void PrintMonospaceBlank(u32 width);
|
void PrintMonospaceBlank(u32 width);
|
||||||
};
|
|
||||||
|
}
|
||||||
|
|
|
@ -23,9 +23,7 @@
|
||||||
#include <atmosphere.h>
|
#include <atmosphere.h>
|
||||||
#include <stratosphere.hpp>
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
#include "fatal_types.hpp"
|
#include "fatal_service.hpp"
|
||||||
#include "fatal_private.hpp"
|
|
||||||
#include "fatal_user.hpp"
|
|
||||||
#include "fatal_config.hpp"
|
#include "fatal_config.hpp"
|
||||||
#include "fatal_repair.hpp"
|
#include "fatal_repair.hpp"
|
||||||
#include "fatal_font.hpp"
|
#include "fatal_font.hpp"
|
||||||
|
@ -123,24 +121,19 @@ void __appExit(void) {
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
/* Load settings from set:sys. */
|
|
||||||
InitializeFatalConfig();
|
|
||||||
|
|
||||||
/* Load shared font. */
|
/* Load shared font. */
|
||||||
if (R_FAILED(FontManager::InitializeSharedFont())) {
|
R_ASSERT(sts::fatal::srv::font::InitializeSharedFont());
|
||||||
std::abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check whether we should throw fatal due to repair process. */
|
/* Check whether we should throw fatal due to repair process. */
|
||||||
CheckRepairStatus();
|
sts::fatal::srv::CheckRepairStatus();
|
||||||
|
|
||||||
/* TODO: What's a good timeout value to use here? */
|
/* Create waitable manager. */
|
||||||
static auto s_server_manager = WaitableManager(1);
|
static auto s_server_manager = WaitableManager(1);
|
||||||
|
|
||||||
/* Create services. */
|
/* Create services. */
|
||||||
s_server_manager.AddWaitable(new ServiceServer<PrivateService>("fatal:p", 4));
|
s_server_manager.AddWaitable(new ServiceServer<sts::fatal::srv::PrivateService>("fatal:p", 4));
|
||||||
s_server_manager.AddWaitable(new ServiceServer<UserService>("fatal:u", 4));
|
s_server_manager.AddWaitable(new ServiceServer<sts::fatal::srv::UserService>("fatal:u", 4));
|
||||||
s_server_manager.AddWaitable(GetFatalSettingsEvent());
|
s_server_manager.AddWaitable(sts::fatal::srv::GetFatalDirtyEvent());
|
||||||
|
|
||||||
/* Loop forever, servicing our services. */
|
/* Loop forever, servicing our services. */
|
||||||
s_server_manager.Process();
|
s_server_manager.Process();
|
||||||
|
|
|
@ -14,93 +14,104 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include "fatal_types.hpp"
|
|
||||||
#include "fatal_repair.hpp"
|
#include "fatal_repair.hpp"
|
||||||
#include "fatal_throw.hpp"
|
#include "fatal_service_for_self.hpp"
|
||||||
|
|
||||||
static bool InRepairWithoutVolHeld() {
|
namespace sts::fatal::srv {
|
||||||
if (GetRuntimeFirmwareVersion() < FirmwareVersion_300) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool in_repair;
|
namespace {
|
||||||
if (R_FAILED(setsysGetFlag(SetSysFlag_InRepairProcessEnable, &in_repair)) || !in_repair) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
bool IsInRepair() {
|
||||||
GpioPadSession vol_btn;
|
/* Before firmware 3.0.0, this wasn't implemented. */
|
||||||
if (R_FAILED(gpioOpenSession(&vol_btn, GpioPadName_ButtonVolUp))) {
|
if (GetRuntimeFirmwareVersion() < FirmwareVersion_300) {
|
||||||
return true;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure we close even on early return. */
|
|
||||||
ON_SCOPE_EXIT { gpioPadClose(&vol_btn); };
|
|
||||||
|
|
||||||
/* Set direction input. */
|
|
||||||
gpioPadSetDirection(&vol_btn, GpioDirection_Input);
|
|
||||||
|
|
||||||
/* Ensure that we're holding the volume button for a full second. */
|
|
||||||
TimeoutHelper timeout_helper(1000000000UL);
|
|
||||||
while (!timeout_helper.TimedOut()) {
|
|
||||||
GpioValue val;
|
|
||||||
if (R_FAILED(gpioPadGetValue(&vol_btn, &val)) || val != GpioValue_Low) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sleep for 100 ms. */
|
bool in_repair;
|
||||||
svcSleepThread(100000000UL);
|
return R_SUCCEEDED(setsysGetFlag(SetSysFlag_InRepairProcessEnable, &in_repair)) && in_repair;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsInRepairWithoutVolHeld() {
|
||||||
|
if (IsInRepair()) {
|
||||||
|
GpioPadSession vol_btn;
|
||||||
|
if (R_FAILED(gpioOpenSession(&vol_btn, GpioPadName_ButtonVolUp))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure we close even on early return. */
|
||||||
|
ON_SCOPE_EXIT { gpioPadClose(&vol_btn); };
|
||||||
|
|
||||||
|
/* Set direction input. */
|
||||||
|
gpioPadSetDirection(&vol_btn, GpioDirection_Input);
|
||||||
|
|
||||||
|
/* Ensure that we're holding the volume button for a full second. */
|
||||||
|
TimeoutHelper timeout_helper(1'000'000'000ul);
|
||||||
|
while (!timeout_helper.TimedOut()) {
|
||||||
|
GpioValue val;
|
||||||
|
if (R_FAILED(gpioPadGetValue(&vol_btn, &val)) || val != GpioValue_Low) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sleep for 100 ms. */
|
||||||
|
svcSleepThread(100'000'000ul);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NeedsRunTimeReviser() {
|
||||||
|
/* Before firmware 5.0.0, this wasn't implemented. */
|
||||||
|
if (GetRuntimeFirmwareVersion() < FirmwareVersion_300) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool requires_time_reviser;
|
||||||
|
return R_SUCCEEDED(setsysGetFlag(SetSysFlag_RequiresRunRepairTimeReviser, &requires_time_reviser)) && requires_time_reviser;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsTimeReviserCartridgeInserted() {
|
||||||
|
FsGameCardHandle gc_hnd;
|
||||||
|
u8 gc_attr;
|
||||||
|
{
|
||||||
|
FsDeviceOperator devop;
|
||||||
|
if (R_FAILED(fsOpenDeviceOperator(&devop))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure we close even on early return. */
|
||||||
|
ON_SCOPE_EXIT { fsDeviceOperatorClose(&devop); };
|
||||||
|
|
||||||
|
/* Check that a gamecard is inserted. */
|
||||||
|
bool inserted;
|
||||||
|
if (R_FAILED(fsDeviceOperatorIsGameCardInserted(&devop, &inserted)) || !inserted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check that we can retrieve the gamecard's attributes. */
|
||||||
|
if (R_FAILED(fsDeviceOperatorGetGameCardHandle(&devop, &gc_hnd)) || R_FAILED(fsDeviceOperatorGetGameCardAttribute(&devop, &gc_hnd, &gc_attr))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check that the gamecard is a repair tool. */
|
||||||
|
return (gc_attr & FsGameCardAttribute_Repair) == FsGameCardAttribute_Repair;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsInRepairWithoutTimeReviserCartridge() {
|
||||||
|
return NeedsRunTimeReviser() && IsTimeReviserCartridgeInserted();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckRepairStatus() {
|
||||||
|
if (IsInRepairWithoutVolHeld()) {
|
||||||
|
ThrowFatalForSelf(ResultFatalInRepairWithoutVolHeld);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsInRepairWithoutTimeReviserCartridge()) {
|
||||||
|
ThrowFatalForSelf(ResultFatalInRepairWithoutTimeReviserCartridge);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool InRepairWithoutTimeReviserCartridge() {
|
|
||||||
if (GetRuntimeFirmwareVersion() < FirmwareVersion_500) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool requires_time_reviser;
|
|
||||||
if (R_FAILED(setsysGetFlag(SetSysFlag_RequiresRunRepairTimeReviser, &requires_time_reviser)) || !requires_time_reviser) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
FsGameCardHandle gc_hnd;
|
|
||||||
u8 gc_attr;
|
|
||||||
{
|
|
||||||
FsDeviceOperator devop;
|
|
||||||
if (R_FAILED(fsOpenDeviceOperator(&devop))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure we close even on early return. */
|
|
||||||
ON_SCOPE_EXIT { fsDeviceOperatorClose(&devop); };
|
|
||||||
|
|
||||||
/* Check that a gamecard is inserted. */
|
|
||||||
bool inserted;
|
|
||||||
if (R_FAILED(fsDeviceOperatorIsGameCardInserted(&devop, &inserted)) || !inserted) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check that we can retrieve the gamecard's attributes. */
|
|
||||||
if (R_FAILED(fsDeviceOperatorGetGameCardHandle(&devop, &gc_hnd)) || R_FAILED(fsDeviceOperatorGetGameCardAttribute(&devop, &gc_hnd, &gc_attr))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check that the gamecard is a repair tool. */
|
|
||||||
return (gc_attr & FsGameCardAttribute_Repair) == FsGameCardAttribute_Repair;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CheckRepairStatus() {
|
|
||||||
if (InRepairWithoutVolHeld()) {
|
|
||||||
ThrowFatalForSelf(ResultFatalInRepairWithoutVolHeld);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (InRepairWithoutTimeReviserCartridge()) {
|
|
||||||
ThrowFatalForSelf(ResultFatalInRepairWithoutTimeReviserCartridge);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,4 +18,8 @@
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <stratosphere.hpp>
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
void CheckRepairStatus();
|
namespace sts::fatal::srv {
|
||||||
|
|
||||||
|
void CheckRepairStatus();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
161
stratosphere/fatal/source/fatal_service.cpp
Normal file
161
stratosphere/fatal/source/fatal_service.cpp
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 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 "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 sts::fatal::srv {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
/* Service Context. */
|
||||||
|
class ServiceContext {
|
||||||
|
private:
|
||||||
|
ThrowContext context;
|
||||||
|
FatalEventManager event_manager;
|
||||||
|
bool has_thrown;
|
||||||
|
private:
|
||||||
|
Result TrySetHasThrown() {
|
||||||
|
if (this->has_thrown) {
|
||||||
|
return ResultFatalAlreadyThrown;
|
||||||
|
}
|
||||||
|
this->has_thrown = true;
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
ServiceContext() {
|
||||||
|
this->context.ClearState();
|
||||||
|
R_ASSERT(eventCreate(&this->context.erpt_event, false));
|
||||||
|
R_ASSERT(eventCreate(&this->context.battery_event, false));
|
||||||
|
this->has_thrown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result GetEvent(Handle *out) {
|
||||||
|
return this->event_manager.GetEvent(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ThrowFatal(u32 error_code, u64 process_id) {
|
||||||
|
return this->ThrowFatalWithCpuContext(error_code, process_id, FatalType_ErrorReportAndErrorScreen, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ThrowFatalWithPolicy(u32 error_code, u64 process_id, FatalType policy) {
|
||||||
|
return this->ThrowFatalWithCpuContext(error_code, process_id, policy, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ThrowFatalWithCpuContext(u32 error_code, u64 process_id, FatalType policy, const CpuContext &cpu_ctx);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Context global. */
|
||||||
|
ServiceContext g_context;
|
||||||
|
|
||||||
|
/* Throw implementation. */
|
||||||
|
Result ServiceContext::ThrowFatalWithCpuContext(u32 error_code, u64 process_id, FatalType policy, const CpuContext &cpu_ctx) {
|
||||||
|
/* We don't support Error Report only fatals. */
|
||||||
|
if (policy == FatalType_ErrorReport) {
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Note that we've thrown fatal. */
|
||||||
|
R_TRY(this->TrySetHasThrown());
|
||||||
|
|
||||||
|
/* At this point we have exclusive access to this->context. */
|
||||||
|
this->context.error_code = error_code;
|
||||||
|
this->context.cpu_ctx = cpu_ctx;
|
||||||
|
|
||||||
|
/* Cap the stack trace to a sane limit. */
|
||||||
|
if (cpu_ctx.architecture == CpuContext::Architecture_Aarch64) {
|
||||||
|
this->context.cpu_ctx.aarch64_ctx.stack_trace_size = std::max(size_t(this->context.cpu_ctx.aarch64_ctx.stack_trace_size), aarch64::CpuContext::MaxStackTraceDepth);
|
||||||
|
} else {
|
||||||
|
this->context.cpu_ctx.aarch32_ctx.stack_trace_size = std::max(size_t(this->context.cpu_ctx.aarch32_ctx.stack_trace_size), aarch32::CpuContext::MaxStackTraceDepth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get title id. */
|
||||||
|
pm::info::GetTitleId(&this->context.title_id, process_id);
|
||||||
|
this->context.is_creport = (this->context.title_id == ncm::TitleId::Creport);
|
||||||
|
|
||||||
|
if (!this->context.is_creport) {
|
||||||
|
/* On firmware version 2.0.0, use debugging SVCs to collect information. */
|
||||||
|
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200) {
|
||||||
|
fatal::srv::TryCollectDebugInformation(&this->context, process_id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* We received info from creport. Parse title id from afsr0. */
|
||||||
|
if (cpu_ctx.architecture == CpuContext::Architecture_Aarch64) {
|
||||||
|
this->context.title_id = cpu_ctx.aarch64_ctx.GetTitleIdForAtmosphere();
|
||||||
|
} else {
|
||||||
|
this->context.title_id = cpu_ctx.aarch32_ctx.GetTitleIdForAtmosphere();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Decide whether to generate a report. */
|
||||||
|
this->context.generate_error_report = (policy == FatalType_ErrorReportAndErrorScreen);
|
||||||
|
|
||||||
|
/* Adjust error code (2000-0000 -> 2162-0002). */
|
||||||
|
if (this->context.error_code == ResultSuccess) {
|
||||||
|
this->context.error_code = ResultErrSystemModuleAborted;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (policy) {
|
||||||
|
case FatalType_ErrorReportAndErrorScreen:
|
||||||
|
case FatalType_ErrorScreen:
|
||||||
|
/* Signal that we're throwing. */
|
||||||
|
this->event_manager.SignalEvents();
|
||||||
|
|
||||||
|
if (GetFatalConfig().ShouldTransitionToFatal()) {
|
||||||
|
RunTasks(&this->context);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* N aborts here. Should we just return an error code? */
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ThrowFatalForSelf(Result error_code) {
|
||||||
|
u64 process_id = 0;
|
||||||
|
R_ASSERT(svcGetProcessId(&process_id, CUR_PROCESS_HANDLE));
|
||||||
|
return g_context.ThrowFatalWithPolicy(static_cast<u32>(error_code), process_id, FatalType_ErrorScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result UserService::ThrowFatal(u32 error, PidDescriptor pid_desc) {
|
||||||
|
return g_context.ThrowFatal(error, pid_desc.pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result UserService::ThrowFatalWithPolicy(u32 error, PidDescriptor pid_desc, FatalType policy) {
|
||||||
|
return g_context.ThrowFatalWithPolicy(error, pid_desc.pid, policy);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result UserService::ThrowFatalWithCpuContext(u32 error, PidDescriptor pid_desc, FatalType policy, InBuffer<u8> _ctx) {
|
||||||
|
if (_ctx.num_elements < sizeof(CpuContext)) {
|
||||||
|
return g_context.ThrowFatalWithPolicy(error, pid_desc.pid, policy);
|
||||||
|
} else {
|
||||||
|
return g_context.ThrowFatalWithCpuContext(error, pid_desc.pid, policy, *reinterpret_cast<const CpuContext *>(_ctx.buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result PrivateService::GetFatalEvent(Out<CopiedHandle> out_h) {
|
||||||
|
return g_context.GetEvent(out_h.GetHandlePointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
58
stratosphere/fatal/source/fatal_service.hpp
Normal file
58
stratosphere/fatal/source/fatal_service.hpp
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 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 <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
namespace sts::fatal::srv {
|
||||||
|
|
||||||
|
class UserService final : public IServiceObject {
|
||||||
|
private:
|
||||||
|
enum class CommandId {
|
||||||
|
ThrowFatal = 0,
|
||||||
|
ThrowFatalWithPolicy = 1,
|
||||||
|
ThrowFatalWithCpuContext = 2,
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
/* Actual commands. */
|
||||||
|
Result ThrowFatal(u32 error, PidDescriptor pid_desc);
|
||||||
|
Result ThrowFatalWithPolicy(u32 error, PidDescriptor pid_desc, FatalType policy);
|
||||||
|
Result ThrowFatalWithCpuContext(u32 error, PidDescriptor pid_desc, FatalType policy, InBuffer<u8> _ctx);
|
||||||
|
public:
|
||||||
|
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||||
|
MAKE_SERVICE_COMMAND_META(UserService, ThrowFatal),
|
||||||
|
MAKE_SERVICE_COMMAND_META(UserService, ThrowFatalWithPolicy),
|
||||||
|
MAKE_SERVICE_COMMAND_META(UserService, ThrowFatalWithCpuContext),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class PrivateService final : public IServiceObject {
|
||||||
|
private:
|
||||||
|
enum class CommandId {
|
||||||
|
GetFatalEvent = 0,
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
/* Actual commands. */
|
||||||
|
Result GetFatalEvent(Out<CopiedHandle> out_h);
|
||||||
|
public:
|
||||||
|
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||||
|
MAKE_SERVICE_COMMAND_META(PrivateService, GetFatalEvent),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -16,9 +16,9 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "fatal_types.hpp"
|
namespace sts::fatal::srv {
|
||||||
|
|
||||||
Result ThrowFatalForSelf(u32 error);
|
Result ThrowFatalForSelf(Result error_code);
|
||||||
Result ThrowFatalImpl(u32 error, u64 pid, FatalType policy, FatalCpuContext *cpu_ctx);
|
|
||||||
|
}
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include "fatal_types.hpp"
|
|
||||||
#include "fatal_task.hpp"
|
#include "fatal_task.hpp"
|
||||||
|
|
||||||
#include "fatal_task_error_report.hpp"
|
#include "fatal_task_error_report.hpp"
|
||||||
|
@ -24,40 +24,64 @@
|
||||||
#include "fatal_task_clock.hpp"
|
#include "fatal_task_clock.hpp"
|
||||||
#include "fatal_task_power.hpp"
|
#include "fatal_task_power.hpp"
|
||||||
|
|
||||||
|
namespace sts::fatal::srv {
|
||||||
|
|
||||||
static constexpr size_t MaxTasks = 8;
|
namespace {
|
||||||
static HosThread g_task_threads[MaxTasks];
|
|
||||||
static size_t g_num_threads = 0;
|
|
||||||
|
|
||||||
|
class TaskThread {
|
||||||
|
NON_COPYABLE(TaskThread);
|
||||||
|
private:
|
||||||
|
static constexpr int TaskThreadPriority = 15;
|
||||||
|
private:
|
||||||
|
HosThread thread;
|
||||||
|
private:
|
||||||
|
static void RunTaskImpl(void *arg) {
|
||||||
|
ITask *task = reinterpret_cast<ITask *>(arg);
|
||||||
|
|
||||||
static void RunTaskThreadFunc(void *arg) {
|
if (R_FAILED(task->Run())) {
|
||||||
IFatalTask *task = reinterpret_cast<IFatalTask *>(arg);
|
/* TODO: Log task failure, somehow? */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
TaskThread() { /* ... */ }
|
||||||
|
void StartTask(ITask *task) {
|
||||||
|
R_ASSERT(this->thread.Initialize(&RunTaskImpl, task, task->GetStackSize(), TaskThreadPriority));
|
||||||
|
R_ASSERT(this->thread.Start());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TaskManager {
|
||||||
|
NON_COPYABLE(TaskManager);
|
||||||
|
private:
|
||||||
|
static constexpr size_t MaxTasks = 8;
|
||||||
|
private:
|
||||||
|
TaskThread task_threads[MaxTasks];
|
||||||
|
size_t task_count = 0;
|
||||||
|
public:
|
||||||
|
TaskManager() { /* ... */ }
|
||||||
|
void StartTask(ITask *task) {
|
||||||
|
if (this->task_count >= MaxTasks) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->task_threads[this->task_count++].StartTask(task);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Global task manager. */
|
||||||
|
TaskManager g_task_manager;
|
||||||
|
|
||||||
if (R_FAILED(task->Run())) {
|
|
||||||
/* TODO: Log task failure, somehow? */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Finish. */
|
void RunTasks(const ThrowContext *ctx) {
|
||||||
}
|
g_task_manager.StartTask(GetErrorReportTask(ctx));
|
||||||
|
g_task_manager.StartTask(GetPowerControlTask(ctx));
|
||||||
static void RunTask(IFatalTask *task, u32 stack_size = 0x4000) {
|
g_task_manager.StartTask(GetShowFatalTask(ctx));
|
||||||
if (g_num_threads >= MaxTasks) {
|
g_task_manager.StartTask(GetStopSoundTask(ctx));
|
||||||
std::abort();
|
g_task_manager.StartTask(GetBacklightControlTask(ctx));
|
||||||
|
g_task_manager.StartTask(GetAdjustClockTask(ctx));
|
||||||
|
g_task_manager.StartTask(GetPowerButtonObserveTask(ctx));
|
||||||
|
g_task_manager.StartTask(GetStateTransitionStopTask(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
HosThread *cur_thread = &g_task_threads[g_num_threads++];
|
|
||||||
|
|
||||||
cur_thread->Initialize(&RunTaskThreadFunc, task, stack_size, 15);
|
|
||||||
cur_thread->Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RunFatalTasks(FatalThrowContext *ctx, u64 title_id, bool error_report, Event *erpt_event, Event *battery_event) {
|
|
||||||
RunTask(new ErrorReportTask(ctx, title_id, error_report, erpt_event));
|
|
||||||
RunTask(new PowerControlTask(ctx, title_id, erpt_event, battery_event));
|
|
||||||
RunTask(new ShowFatalTask(ctx, title_id, battery_event), 0x10000);
|
|
||||||
RunTask(new StopSoundTask(ctx, title_id));
|
|
||||||
RunTask(new BacklightControlTask(ctx, title_id));
|
|
||||||
RunTask(new AdjustClockTask(ctx, title_id));
|
|
||||||
RunTask(new PowerButtonObserveTask(ctx, title_id, erpt_event));
|
|
||||||
RunTask(new StateTransitionStopTask(ctx, title_id));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,16 +17,29 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <stratosphere.hpp>
|
#include <stratosphere.hpp>
|
||||||
#include "fatal_types.hpp"
|
#include "fatal_service.hpp"
|
||||||
|
|
||||||
class IFatalTask {
|
namespace sts::fatal::srv {
|
||||||
protected:
|
|
||||||
FatalThrowContext *ctx;
|
|
||||||
u64 title_id;
|
|
||||||
public:
|
|
||||||
IFatalTask(FatalThrowContext *ctx, u64 tid) : ctx(ctx), title_id(tid) { }
|
|
||||||
virtual Result Run() = 0;
|
|
||||||
virtual const char *GetName() const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
void RunFatalTasks(FatalThrowContext *ctx, u64 title_id, bool error_report, Event *erpt_event, Event *battery_event);
|
class ITask {
|
||||||
|
public:
|
||||||
|
static constexpr size_t DefaultStackSize = 0x1000;
|
||||||
|
protected:
|
||||||
|
const ThrowContext *context = nullptr;
|
||||||
|
public:
|
||||||
|
void Initialize(const ThrowContext *context) {
|
||||||
|
this->context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result Run() = 0;
|
||||||
|
|
||||||
|
virtual const char *GetName() const = 0;
|
||||||
|
|
||||||
|
virtual size_t GetStackSize() const {
|
||||||
|
return DefaultStackSize;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void RunTasks(const ThrowContext *ctx);
|
||||||
|
|
||||||
|
}
|
|
@ -14,41 +14,70 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include "fatal_task_clock.hpp"
|
#include "fatal_task_clock.hpp"
|
||||||
|
|
||||||
Result AdjustClockTask::AdjustClockForModule(PcvModule module, u32 hz) {
|
namespace sts::fatal::srv {
|
||||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_800) {
|
|
||||||
/* On 8.0.0+, convert to module id + use clkrst API. */
|
|
||||||
PcvModuleId module_id;
|
|
||||||
R_TRY(pcvGetModuleId(&module_id, module));
|
|
||||||
|
|
||||||
ClkrstSession session;
|
|
||||||
R_TRY(clkrstOpenSession(&session, module_id, 3));
|
|
||||||
ON_SCOPE_EXIT { clkrstCloseSession(&session); };
|
|
||||||
|
|
||||||
R_TRY(clkrstSetClockRate(&session, hz));
|
namespace {
|
||||||
} else {
|
|
||||||
/* On 1.0.0-7.0.1, use pcv API. */
|
/* Task definition. */
|
||||||
R_TRY(pcvSetClockRate(module, hz));
|
class AdjustClockTask : public ITask {
|
||||||
|
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 (GetRuntimeFirmwareVersion() >= FirmwareVersion_800) {
|
||||||
|
/* On 8.0.0+, convert to module id + use clkrst API. */
|
||||||
|
PcvModuleId module_id;
|
||||||
|
R_TRY(pcvGetModuleId(&module_id, module));
|
||||||
|
|
||||||
|
ClkrstSession session;
|
||||||
|
R_TRY(clkrstOpenSession(&session, module_id, 3));
|
||||||
|
ON_SCOPE_EXIT { clkrstCloseSession(&session); };
|
||||||
|
|
||||||
|
R_TRY(clkrstSetClockRate(&session, hz));
|
||||||
|
} else {
|
||||||
|
/* On 1.0.0-7.0.1, use pcv API. */
|
||||||
|
R_TRY(pcvSetClockRate(module, hz));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result AdjustClockTask::Run() {
|
||||||
|
return AdjustClock();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ITask *GetAdjustClockTask(const ThrowContext *ctx) {
|
||||||
|
g_adjust_clock_task.Initialize(ctx);
|
||||||
|
return &g_adjust_clock_task;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result AdjustClockTask::Run() {
|
|
||||||
return AdjustClock();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,18 +15,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include "fatal_task.hpp"
|
#include "fatal_task.hpp"
|
||||||
|
|
||||||
class AdjustClockTask : public IFatalTask {
|
namespace sts::fatal::srv {
|
||||||
private:
|
|
||||||
Result AdjustClockForModule(PcvModule module, u32 hz);
|
ITask *GetAdjustClockTask(const ThrowContext *ctx);
|
||||||
Result AdjustClock();
|
|
||||||
public:
|
}
|
||||||
AdjustClockTask(FatalThrowContext *ctx, u64 title_id) : IFatalTask(ctx, title_id) { }
|
|
||||||
virtual Result Run() override;
|
|
||||||
virtual const char *GetName() const override {
|
|
||||||
return "AdjustClockTask";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
@ -17,133 +17,141 @@
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <switch.h>
|
|
||||||
#include <atmosphere/version.h>
|
#include <atmosphere/version.h>
|
||||||
|
|
||||||
#include "fatal_task_error_report.hpp"
|
|
||||||
#include "fatal_config.hpp"
|
#include "fatal_config.hpp"
|
||||||
|
#include "fatal_task_error_report.hpp"
|
||||||
|
|
||||||
void ErrorReportTask::EnsureReportDirectories() {
|
namespace sts::fatal::srv {
|
||||||
char path[FS_MAX_PATH];
|
|
||||||
strcpy(path, "sdmc:/atmosphere");
|
|
||||||
mkdir(path, S_IRWXU);
|
|
||||||
strcat(path, "/fatal_reports");
|
|
||||||
mkdir(path, S_IRWXU);
|
|
||||||
strcat(path, "/dumps");
|
|
||||||
mkdir(path, S_IRWXU);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ErrorReportTask::GetCurrentTime(u64 *out) {
|
namespace {
|
||||||
*out = 0;
|
|
||||||
|
|
||||||
/* Verify that pcv isn't dead. */
|
/* Helpers. */
|
||||||
{
|
void TryEnsureReportDirectories() {
|
||||||
bool has_time_service;
|
mkdir("sdmc:/atmosphere", S_IRWXU);
|
||||||
DoWithSmSession([&]() {
|
mkdir("sdmc:/atmosphere/fatal_reports", S_IRWXU);
|
||||||
Handle dummy;
|
mkdir("sdmc:/atmosphere/fatal_reports/dumps", S_IRWXU);
|
||||||
if (R_SUCCEEDED(smRegisterService(&dummy, "time:s", false, 0x20))) {
|
|
||||||
svcCloseHandle(dummy);
|
|
||||||
has_time_service = false;
|
|
||||||
} else {
|
|
||||||
has_time_service = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!has_time_service) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Try to get the current time. */
|
bool TryGetCurrentTimestamp(u64 *out) {
|
||||||
bool success = true;
|
/* Clear output. */
|
||||||
DoWithSmSession([&]() {
|
*out = 0;
|
||||||
success &= R_SUCCEEDED(timeInitialize());
|
|
||||||
});
|
|
||||||
if (success) {
|
|
||||||
success &= R_SUCCEEDED(timeGetCurrentTime(TimeType_LocalSystemClock, out));
|
|
||||||
timeExit();
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ErrorReportTask::SaveReportToSdCard() {
|
/* Check if we have time service. */
|
||||||
char file_path[FS_MAX_PATH];
|
{
|
||||||
|
bool has_time_service = false;
|
||||||
/* Ensure path exists. */
|
if (R_FAILED(sm::HasService(&has_time_service, sm::ServiceName::Encode("time:s"))) || !has_time_service) {
|
||||||
EnsureReportDirectories();
|
return false;
|
||||||
|
|
||||||
/* Get a timestamp. */
|
|
||||||
u64 timestamp;
|
|
||||||
if (!GetCurrentTime(×tamp)) {
|
|
||||||
timestamp = svcGetSystemTick();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Open report file. */
|
|
||||||
snprintf(file_path, sizeof(file_path) - 1, "sdmc:/atmosphere/fatal_reports/%011lu_%016lx.log", timestamp, this->title_id);
|
|
||||||
FILE *f_report = fopen(file_path, "w");
|
|
||||||
if (f_report != NULL) {
|
|
||||||
ON_SCOPE_EXIT { fclose(f_report); };
|
|
||||||
|
|
||||||
fprintf(f_report, "Atmosphère Fatal Report (v1.0):\n");
|
|
||||||
fprintf(f_report, "Result: 0x%X (2%03d-%04d)\n\n", this->ctx->error_code, R_MODULE(this->ctx->error_code), R_DESCRIPTION(this->ctx->error_code));
|
|
||||||
fprintf(f_report, "Title ID: %016lx\n", this->title_id);
|
|
||||||
if (strlen(this->ctx->proc_name)) {
|
|
||||||
fprintf(f_report, "Process Name: %s\n", this->ctx->proc_name);
|
|
||||||
}
|
|
||||||
fprintf(f_report, u8"Firmware: %s (Atmosphère %u.%u.%u-%s)\n", GetFatalConfig()->firmware_version.display_version, CURRENT_ATMOSPHERE_VERSION, GetAtmosphereGitRevision());
|
|
||||||
|
|
||||||
if (this->ctx->cpu_ctx.is_aarch32) {
|
|
||||||
fprintf(f_report, "General Purpose Registers:\n");
|
|
||||||
for (size_t i = 0; i < NumAarch32Gprs; i++) {
|
|
||||||
if (this->ctx->has_gprs[i]) {
|
|
||||||
fprintf(f_report, " %3s: %08x\n", Aarch32GprNames[i], this->ctx->cpu_ctx.aarch32_ctx.r[i]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fprintf(f_report, " PC: %08x\n", this->ctx->cpu_ctx.aarch32_ctx.pc);
|
|
||||||
fprintf(f_report, "Start Address: %08x\n", this->ctx->cpu_ctx.aarch32_ctx.start_address);
|
/* Try to get the current time. */
|
||||||
fprintf(f_report, "Stack Trace:\n");
|
{
|
||||||
for (unsigned int i = 0; i < this->ctx->cpu_ctx.aarch32_ctx.stack_trace_size; i++) {
|
sm::ScopedServiceHolder<timeInitialize, timeExit> time_holder;
|
||||||
fprintf(f_report, " ReturnAddress[%02u]: %08x\n", i, this->ctx->cpu_ctx.aarch32_ctx.stack_trace[i]);
|
return time_holder && R_SUCCEEDED(timeGetCurrentTime(TimeType_LocalSystemClock, out));
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
fprintf(f_report, "General Purpose Registers:\n");
|
|
||||||
for (size_t i = 0; i < NumAarch64Gprs; i++) {
|
/* Task definition. */
|
||||||
if (this->ctx->has_gprs[i]) {
|
class ErrorReportTask : public ITask {
|
||||||
fprintf(f_report, " %3s: %016lx\n", Aarch64GprNames[i], this->ctx->cpu_ctx.aarch64_ctx.x[i]);
|
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_MAX_PATH];
|
||||||
|
|
||||||
|
/* Try to Ensure path exists. */
|
||||||
|
TryEnsureReportDirectories();
|
||||||
|
|
||||||
|
/* Get a timestamp. */
|
||||||
|
u64 timestamp;
|
||||||
|
if (!TryGetCurrentTimestamp(×tamp)) {
|
||||||
|
timestamp = svcGetSystemTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Open report file. */
|
||||||
|
snprintf(file_path, sizeof(file_path) - 1, "sdmc:/atmosphere/fatal_reports/%011lu_%016lx.log", timestamp, static_cast<u64>(this->context->title_id));
|
||||||
|
FILE *f_report = fopen(file_path, "w");
|
||||||
|
if (f_report != NULL) {
|
||||||
|
ON_SCOPE_EXIT { fclose(f_report); };
|
||||||
|
|
||||||
|
fprintf(f_report, "Atmosphère Fatal Report (v1.0):\n");
|
||||||
|
fprintf(f_report, "Result: 0x%X (2%03d-%04d)\n\n", this->context->error_code, R_MODULE(this->context->error_code), R_DESCRIPTION(this->context->error_code));
|
||||||
|
fprintf(f_report, "Title ID: %016lx\n", static_cast<u64>(this->context->title_id));
|
||||||
|
if (strlen(this->context->proc_name)) {
|
||||||
|
fprintf(f_report, "Process Name: %s\n", this->context->proc_name);
|
||||||
|
}
|
||||||
|
fprintf(f_report, u8"Firmware: %s (Atmosphère %u.%u.%u-%s)\n", GetFatalConfig().GetFirmwareVersion().display_version, CURRENT_ATMOSPHERE_VERSION, GetAtmosphereGitRevision());
|
||||||
|
|
||||||
|
if (this->context->cpu_ctx.architecture == CpuContext::Architecture_Aarch32) {
|
||||||
|
fprintf(f_report, "General Purpose Registers:\n");
|
||||||
|
for (size_t i = 0; i <= aarch32::RegisterName_PC; i++) {
|
||||||
|
if (this->context->cpu_ctx.aarch32_ctx.HasRegisterValue(static_cast<aarch32::RegisterName>(i))) {
|
||||||
|
fprintf(f_report, " %3s: %08x\n", aarch32::CpuContext::RegisterNameStrings[i], this->context->cpu_ctx.aarch32_ctx.r[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(f_report, "Start Address: %08x\n", this->context->cpu_ctx.aarch32_ctx.base_address);
|
||||||
|
fprintf(f_report, "Stack Trace:\n");
|
||||||
|
for (unsigned int i = 0; i < this->context->cpu_ctx.aarch32_ctx.stack_trace_size; i++) {
|
||||||
|
fprintf(f_report, " ReturnAddress[%02u]: %08x\n", i, this->context->cpu_ctx.aarch32_ctx.stack_trace[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fprintf(f_report, "General Purpose Registers:\n");
|
||||||
|
for (size_t i = 0; i <= aarch64::RegisterName_PC; i++) {
|
||||||
|
if (this->context->cpu_ctx.aarch64_ctx.HasRegisterValue(static_cast<aarch64::RegisterName>(i))) {
|
||||||
|
fprintf(f_report, " %3s: %016lx\n", aarch64::CpuContext::RegisterNameStrings[i], this->context->cpu_ctx.aarch64_ctx.x[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(f_report, "Start Address: %016lx\n", this->context->cpu_ctx.aarch64_ctx.base_address);
|
||||||
|
fprintf(f_report, "Stack Trace:\n");
|
||||||
|
for (unsigned int i = 0; i < this->context->cpu_ctx.aarch64_ctx.stack_trace_size; i++) {
|
||||||
|
fprintf(f_report, " ReturnAddress[%02u]: %016lx\n", i, this->context->cpu_ctx.aarch64_ctx.stack_trace[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fprintf(f_report, " PC: %016lx\n", this->ctx->cpu_ctx.aarch64_ctx.pc);
|
|
||||||
fprintf(f_report, "Start Address: %016lx\n", this->ctx->cpu_ctx.aarch64_ctx.start_address);
|
if (this->context->stack_dump_size) {
|
||||||
fprintf(f_report, "Stack Trace:\n");
|
snprintf(file_path, sizeof(file_path) - 1, "sdmc:/atmosphere/fatal_reports/dumps/%011lu_%016lx.bin", timestamp, static_cast<u64>(this->context->title_id));
|
||||||
for (unsigned int i = 0; i < this->ctx->cpu_ctx.aarch64_ctx.stack_trace_size; i++) {
|
FILE *f_stackdump = fopen(file_path, "wb");
|
||||||
fprintf(f_report, " ReturnAddress[%02u]: %016lx\n", i, this->ctx->cpu_ctx.aarch64_ctx.stack_trace[i]);
|
if (f_stackdump == NULL) { return; }
|
||||||
|
ON_SCOPE_EXIT { fclose(f_stackdump); };
|
||||||
|
|
||||||
|
fwrite(this->context->stack_dump, this->context->stack_dump_size, 1, f_stackdump);
|
||||||
|
fflush(f_stackdump);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result ErrorReportTask::Run() {
|
||||||
|
if (this->context->generate_error_report) {
|
||||||
|
/* Here, Nintendo creates an error report with erpt. AMS will not do that. */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save report to SD card. */
|
||||||
|
if (!this->context->is_creport) {
|
||||||
|
this->SaveReportToSdCard();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Signal we're done with our job. */
|
||||||
|
eventFire(const_cast<Event *>(&this->context->erpt_event));
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->ctx->stack_dump_size) {
|
ITask *GetErrorReportTask(const ThrowContext *ctx) {
|
||||||
snprintf(file_path, sizeof(file_path) - 1, "sdmc:/atmosphere/fatal_reports/dumps/%011lu_%016lx.bin", timestamp, this->title_id);
|
g_error_report_task.Initialize(ctx);
|
||||||
FILE *f_stackdump = fopen(file_path, "wb");
|
return &g_error_report_task;
|
||||||
if (f_stackdump == NULL) { return; }
|
|
||||||
ON_SCOPE_EXIT { fclose(f_stackdump); };
|
|
||||||
|
|
||||||
fwrite(this->ctx->stack_dump, this->ctx->stack_dump_size, 1, f_stackdump);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ErrorReportTask::Run() {
|
|
||||||
if (this->create_report) {
|
|
||||||
/* Here, Nintendo creates an error report with erpt. AMS will not do that. */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Save report to SD card. */
|
|
||||||
if (!this->ctx->is_creport) {
|
|
||||||
SaveReportToSdCard();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Signal we're done with our job. */
|
|
||||||
eventFire(this->erpt_event);
|
|
||||||
|
|
||||||
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
|
@ -15,22 +15,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include "fatal_task.hpp"
|
#include "fatal_task.hpp"
|
||||||
|
|
||||||
class ErrorReportTask : public IFatalTask {
|
namespace sts::fatal::srv {
|
||||||
private:
|
|
||||||
bool create_report;
|
ITask *GetErrorReportTask(const ThrowContext *ctx);
|
||||||
Event *erpt_event;
|
|
||||||
private:
|
}
|
||||||
void EnsureReportDirectories();
|
|
||||||
bool GetCurrentTime(u64 *out);
|
|
||||||
void SaveReportToSdCard();
|
|
||||||
public:
|
|
||||||
ErrorReportTask(FatalThrowContext *ctx, u64 title_id, bool error_report, Event *evt) : IFatalTask(ctx, title_id), create_report(error_report), erpt_event(evt) { }
|
|
||||||
virtual Result Run() override;
|
|
||||||
virtual const char *GetName() const override {
|
|
||||||
return "WriteErrorReport";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
@ -14,149 +14,199 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include "fatal_task_power.hpp"
|
|
||||||
#include "fatal_config.hpp"
|
#include "fatal_config.hpp"
|
||||||
|
#include "fatal_task_power.hpp"
|
||||||
|
|
||||||
bool PowerControlTask::TryShutdown() {
|
namespace sts::fatal::srv {
|
||||||
/* Set a timeout of 30 seconds. */
|
|
||||||
TimeoutHelper timeout_helper(30000000000UL);
|
|
||||||
bool cancel_shutdown = false;
|
|
||||||
PsmBatteryVoltageState bv_state = PsmBatteryVoltageState_Normal;
|
|
||||||
|
|
||||||
while (true) {
|
namespace {
|
||||||
if (timeout_helper.TimedOut()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (R_FAILED(psmGetBatteryVoltageState(&bv_state)) || bv_state == PsmBatteryVoltageState_NeedsShutdown) {
|
/* Task types. */
|
||||||
break;
|
class PowerControlTask : public ITask {
|
||||||
}
|
private:
|
||||||
|
bool TryShutdown();
|
||||||
if (bv_state == PsmBatteryVoltageState_Normal) {
|
void MonitorBatteryState();
|
||||||
cancel_shutdown = true;
|
public:
|
||||||
break;
|
virtual Result Run() override;
|
||||||
}
|
virtual const char *GetName() const override {
|
||||||
|
return "PowerControlTask";
|
||||||
/* Query voltage state every 5 seconds, for 30 seconds. */
|
|
||||||
svcSleepThread(5000000000UL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cancel_shutdown) {
|
|
||||||
bpcShutdownSystem();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PowerControlTask::MonitorBatteryState() {
|
|
||||||
PsmBatteryVoltageState bv_state = PsmBatteryVoltageState_Normal;
|
|
||||||
|
|
||||||
/* Check the battery state, and shutdown on low voltage. */
|
|
||||||
if (R_FAILED(psmGetBatteryVoltageState(&bv_state)) || bv_state == PsmBatteryVoltageState_NeedsShutdown) {
|
|
||||||
/* Wait a second for the error report task to finish. */
|
|
||||||
eventWait(this->erpt_event, TimeoutHelper::NsToTick(1000000000UL));
|
|
||||||
this->TryShutdown();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Signal we've checked the battery at least once. */
|
|
||||||
eventFire(this->battery_event);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (R_FAILED(psmGetBatteryVoltageState(&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;
|
class PowerButtonObserveTask : public ITask {
|
||||||
|
private:
|
||||||
|
void WaitForPowerButton();
|
||||||
|
public:
|
||||||
|
virtual Result Run() override;
|
||||||
|
virtual const char *GetName() const override {
|
||||||
|
return "PowerButtonObserveTask";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class StateTransitionStopTask : public ITask {
|
||||||
|
public:
|
||||||
|
virtual Result Run() override;
|
||||||
|
virtual const char *GetName() const override {
|
||||||
|
return "StateTransitionStopTask";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* 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. */
|
||||||
|
TimeoutHelper timeout_helper(30'000'000'000ul);
|
||||||
|
|
||||||
|
bool perform_shutdown = true;
|
||||||
|
PsmBatteryVoltageState bv_state = PsmBatteryVoltageState_Normal;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (timeout_helper.TimedOut()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (R_FAILED(psmGetBatteryVoltageState(&bv_state)) || bv_state == PsmBatteryVoltageState_NeedsShutdown) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bv_state == PsmBatteryVoltageState_Normal) {
|
||||||
|
perform_shutdown = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Query voltage state every 5 seconds, for 30 seconds. */
|
||||||
|
svcSleepThread(5'000'000'000ul);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (perform_shutdown) {
|
||||||
|
bpcShutdownSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
return perform_shutdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Query voltage state every 5 seconds. */
|
void PowerControlTask::MonitorBatteryState() {
|
||||||
svcSleepThread(5000000000UL);
|
PsmBatteryVoltageState bv_state = PsmBatteryVoltageState_Normal;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PowerButtonObserveTask::WaitForPowerButton() {
|
/* Check the battery state, and shutdown on low voltage. */
|
||||||
/* Wait up to a second for error report generation to finish. */
|
if (R_FAILED(psmGetBatteryVoltageState(&bv_state)) || bv_state == PsmBatteryVoltageState_NeedsShutdown) {
|
||||||
eventWait(this->erpt_event, TimeoutHelper::NsToTick(1000000000UL));
|
/* Wait a second for the error report task to finish. */
|
||||||
|
eventWait(const_cast<Event *>(&this->context->erpt_event), TimeoutHelper::NsToTick(1'000'000'000ul));
|
||||||
|
this->TryShutdown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Force a reboot after some time if kiosk unit. */
|
/* Signal we've checked the battery at least once. */
|
||||||
const FatalConfig *config = GetFatalConfig();
|
eventFire(const_cast<Event *>(&this->context->battery_event));
|
||||||
TimeoutHelper reboot_helper(config->quest_reboot_interval_second * 1000000000UL);
|
|
||||||
|
|
||||||
TimeoutHelper auto_reboot_helper(config->fatal_auto_reboot_interval * 1000000);
|
/* Loop querying voltage state every 5 seconds. */
|
||||||
|
while (true) {
|
||||||
|
if (R_FAILED(psmGetBatteryVoltageState(&bv_state))) {
|
||||||
|
bv_state = PsmBatteryVoltageState_NeedsShutdown;
|
||||||
|
}
|
||||||
|
|
||||||
bool check_vol_up = true, check_vol_down = true;
|
switch (bv_state) {
|
||||||
GpioPadSession vol_up_btn, vol_down_btn;
|
case PsmBatteryVoltageState_NeedsShutdown:
|
||||||
if (R_FAILED(gpioOpenSession(&vol_up_btn, GpioPadName_ButtonVolUp))) {
|
case PsmBatteryVoltageState_NeedsSleep:
|
||||||
check_vol_up = false;
|
{
|
||||||
}
|
bool shutdown = this->TryShutdown();
|
||||||
if (R_FAILED(gpioOpenSession(&vol_down_btn, GpioPadName_ButtonVolDown))) {
|
if (shutdown) {
|
||||||
check_vol_down = false;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* Ensure we close on early return. */
|
svcSleepThread(5'000'000'000ul);
|
||||||
ON_SCOPE_EXIT { if (check_vol_up) { gpioPadClose(&vol_up_btn); } };
|
}
|
||||||
ON_SCOPE_EXIT { if (check_vol_down) { gpioPadClose(&vol_down_btn); } };
|
|
||||||
|
|
||||||
/* Set direction input. */
|
|
||||||
if (check_vol_up) {
|
|
||||||
gpioPadSetDirection(&vol_up_btn, GpioDirection_Input);
|
|
||||||
}
|
|
||||||
if (check_vol_down) {
|
|
||||||
gpioPadSetDirection(&vol_down_btn, GpioDirection_Input);
|
|
||||||
}
|
|
||||||
|
|
||||||
BpcSleepButtonState state;
|
|
||||||
GpioValue val;
|
|
||||||
while (true) {
|
|
||||||
if (config->is_auto_reboot_enabled && auto_reboot_helper.TimedOut() ) {
|
|
||||||
bpcRebootSystem();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (check_vol_up && R_SUCCEEDED(gpioPadGetValue(&vol_up_btn, &val)) && val == GpioValue_Low) {
|
void PowerButtonObserveTask::WaitForPowerButton() {
|
||||||
bpcRebootSystem();
|
/* Wait up to a second for error report generation to finish. */
|
||||||
|
eventWait(const_cast<Event *>(&this->context->erpt_event), TimeoutHelper::NsToTick(1'000'000'000ul));
|
||||||
|
|
||||||
|
/* Force a reboot after some time if kiosk unit. */
|
||||||
|
const auto &config = GetFatalConfig();
|
||||||
|
TimeoutHelper quest_reboot_helper(config.GetQuestRebootTimeoutInterval());
|
||||||
|
TimeoutHelper fatal_reboot_helper(config.GetFatalRebootTimeoutInterval());
|
||||||
|
|
||||||
|
bool check_vol_up = true, check_vol_down = true;
|
||||||
|
GpioPadSession vol_up_btn, vol_down_btn;
|
||||||
|
if (R_FAILED(gpioOpenSession(&vol_up_btn, GpioPadName_ButtonVolUp))) {
|
||||||
|
check_vol_up = false;
|
||||||
|
}
|
||||||
|
if (R_FAILED(gpioOpenSession(&vol_down_btn, GpioPadName_ButtonVolDown))) {
|
||||||
|
check_vol_down = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure we close on early return. */
|
||||||
|
ON_SCOPE_EXIT { if (check_vol_up) { gpioPadClose(&vol_up_btn); } };
|
||||||
|
ON_SCOPE_EXIT { if (check_vol_down) { gpioPadClose(&vol_down_btn); } };
|
||||||
|
|
||||||
|
/* Set direction input. */
|
||||||
|
if (check_vol_up) {
|
||||||
|
gpioPadSetDirection(&vol_up_btn, GpioDirection_Input);
|
||||||
|
}
|
||||||
|
if (check_vol_down) {
|
||||||
|
gpioPadSetDirection(&vol_down_btn, GpioDirection_Input);
|
||||||
|
}
|
||||||
|
|
||||||
|
BpcSleepButtonState state;
|
||||||
|
GpioValue val;
|
||||||
|
while (true) {
|
||||||
|
if ((config.IsFatalRebootEnabled() && fatal_reboot_helper.TimedOut()) ||
|
||||||
|
(check_vol_up && R_SUCCEEDED(gpioPadGetValue(&vol_up_btn, &val)) && val == GpioValue_Low) ||
|
||||||
|
(check_vol_down && R_SUCCEEDED(gpioPadGetValue(&vol_down_btn, &val)) && val == GpioValue_Low) ||
|
||||||
|
(R_SUCCEEDED(bpcGetSleepButtonState(&state)) && state == BpcSleepButtonState_Held) ||
|
||||||
|
(config.IsQuest() && quest_reboot_helper.TimedOut())) {
|
||||||
|
/* If any of the above conditions succeeded, we should reboot. */
|
||||||
|
bpcRebootSystem();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Wait 100 ms between button checks. */
|
||||||
|
svcSleepThread(100'000'000ul);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (check_vol_down && R_SUCCEEDED(gpioPadGetValue(&vol_down_btn, &val)) && val == GpioValue_Low) {
|
Result PowerControlTask::Run() {
|
||||||
bpcRebootSystem();
|
this->MonitorBatteryState();
|
||||||
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((R_SUCCEEDED(bpcGetSleepButtonState(&state)) && state == BpcSleepButtonState_Held) || (config->quest_flag && reboot_helper.TimedOut())) {
|
Result PowerButtonObserveTask::Run() {
|
||||||
bpcRebootSystem();
|
this->WaitForPowerButton();
|
||||||
return;
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result StateTransitionStopTask::Run() {
|
||||||
|
/* Nintendo ignores the output of this call... */
|
||||||
|
spsmPutErrorState();
|
||||||
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Wait 100 ms between button checks. */
|
|
||||||
svcSleepThread(100000000UL);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Result PowerControlTask::Run() {
|
ITask *GetPowerControlTask(const ThrowContext *ctx) {
|
||||||
MonitorBatteryState();
|
g_power_control_task.Initialize(ctx);
|
||||||
return ResultSuccess;
|
return &g_power_control_task;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result PowerButtonObserveTask::Run() {
|
ITask *GetPowerButtonObserveTask(const ThrowContext *ctx) {
|
||||||
WaitForPowerButton();
|
g_power_button_observe_task.Initialize(ctx);
|
||||||
return ResultSuccess;
|
return &g_power_button_observe_task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ITask *GetStateTransitionStopTask(const ThrowContext *ctx) {
|
||||||
|
g_state_transition_stop_task.Initialize(ctx);
|
||||||
|
return &g_state_transition_stop_task;
|
||||||
|
}
|
||||||
|
|
||||||
Result StateTransitionStopTask::Run() {
|
|
||||||
/* Nintendo ignores the output of this call... */
|
|
||||||
spsmPutErrorState();
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,43 +15,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include "fatal_task.hpp"
|
#include "fatal_task.hpp"
|
||||||
|
|
||||||
class PowerControlTask : public IFatalTask {
|
namespace sts::fatal::srv {
|
||||||
private:
|
|
||||||
Event *erpt_event;
|
|
||||||
Event *battery_event;
|
|
||||||
private:
|
|
||||||
bool TryShutdown();
|
|
||||||
void MonitorBatteryState();
|
|
||||||
public:
|
|
||||||
PowerControlTask(FatalThrowContext *ctx, u64 title_id, Event *er_evt, Event *bt_evt) : IFatalTask(ctx, title_id), erpt_event(er_evt), battery_event(bt_evt) { }
|
|
||||||
virtual Result Run() override;
|
|
||||||
virtual const char *GetName() const override {
|
|
||||||
return "PowerControlTask";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class PowerButtonObserveTask : public IFatalTask {
|
ITask *GetPowerControlTask(const ThrowContext *ctx);
|
||||||
private:
|
ITask *GetPowerButtonObserveTask(const ThrowContext *ctx);
|
||||||
Event *erpt_event;
|
ITask *GetStateTransitionStopTask(const ThrowContext *ctx);
|
||||||
private:
|
|
||||||
void WaitForPowerButton();
|
|
||||||
public:
|
|
||||||
PowerButtonObserveTask(FatalThrowContext *ctx, u64 title_id, Event *er_evt) : IFatalTask(ctx, title_id), erpt_event(er_evt) { }
|
|
||||||
virtual Result Run() override;
|
|
||||||
virtual const char *GetName() const override {
|
|
||||||
return "PowerButtonObserveTask";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class StateTransitionStopTask : public IFatalTask {
|
}
|
||||||
public:
|
|
||||||
StateTransitionStopTask(FatalThrowContext *ctx, u64 title_id) : IFatalTask(ctx, title_id) { }
|
|
||||||
virtual Result Run() override;
|
|
||||||
virtual const char *GetName() const override {
|
|
||||||
return "StateTransitionStopTask";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
@ -14,378 +14,434 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
#include <atmosphere/version.h>
|
#include <atmosphere/version.h>
|
||||||
|
|
||||||
#include "fatal_task_screen.hpp"
|
#include "fatal_task_screen.hpp"
|
||||||
#include "fatal_config.hpp"
|
#include "fatal_config.hpp"
|
||||||
#include "fatal_font.hpp"
|
#include "fatal_font.hpp"
|
||||||
#include "ams_logo.hpp"
|
|
||||||
|
|
||||||
static constexpr u32 FatalScreenWidth = 1280;
|
namespace sts::fatal::srv {
|
||||||
static constexpr u32 FatalScreenHeight = 720;
|
|
||||||
static constexpr u32 FatalScreenBpp = 2;
|
|
||||||
|
|
||||||
static constexpr u32 FatalScreenWidthAlignedBytes = (FatalScreenWidth * FatalScreenBpp + 63) & ~63;
|
/* Include Atmosphere logo into its own anonymous namespace. */
|
||||||
static constexpr u32 FatalScreenWidthAligned = FatalScreenWidthAlignedBytes / FatalScreenBpp;
|
|
||||||
|
|
||||||
u32 GetPixelOffset(uint32_t x, uint32_t y)
|
namespace {
|
||||||
{
|
|
||||||
u32 tmp_pos;
|
|
||||||
|
|
||||||
tmp_pos = ((y & 127) / 16) + (x/32*8) + ((y/16/8)*(((FatalScreenWidthAligned/2)/16*8)));
|
#include "fatal_ams_logo.inc"
|
||||||
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;
|
namespace {
|
||||||
}
|
|
||||||
|
/* Screen definitions. */
|
||||||
|
constexpr u32 FatalScreenWidth = 1280;
|
||||||
|
constexpr u32 FatalScreenHeight = 720;
|
||||||
|
constexpr u32 FatalScreenBpp = 2;
|
||||||
|
constexpr u32 FatalLayerZ = 100;
|
||||||
|
|
||||||
|
constexpr u32 FatalScreenWidthAlignedBytes = (FatalScreenWidth * FatalScreenBpp + 63) & ~63;
|
||||||
|
constexpr u32 FatalScreenWidthAligned = FatalScreenWidthAlignedBytes / FatalScreenBpp;
|
||||||
|
|
||||||
|
/* 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 ITask {
|
||||||
|
private:
|
||||||
|
ViDisplay display;
|
||||||
|
ViLayer layer;
|
||||||
|
NWindow win;
|
||||||
|
Framebuffer fb;
|
||||||
|
private:
|
||||||
|
Result SetupDisplayInternal();
|
||||||
|
Result SetupDisplayExternal();
|
||||||
|
Result PrepareScreenForDrawing();
|
||||||
|
Result ShowFatal();
|
||||||
|
public:
|
||||||
|
virtual Result Run() override;
|
||||||
|
virtual const char *GetName() const override {
|
||||||
|
return "ShowFatal";
|
||||||
|
}
|
||||||
|
virtual size_t GetStackSize() const override {
|
||||||
|
return 0x8000;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class BacklightControlTask : public ITask {
|
||||||
|
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", &temp_display)) {
|
||||||
|
R_CATCH(ResultViNotFound) {
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
} R_END_TRY_CATCH;
|
||||||
|
|
||||||
|
/* Guarantee we close the display. */
|
||||||
|
ON_SCOPE_EXIT { viCloseDisplay(&temp_display); };
|
||||||
|
|
||||||
|
/* Turn on the screen. */
|
||||||
|
R_TRY(viSetDisplayPowerState(&temp_display, ViPowerState_On));
|
||||||
|
|
||||||
|
/* Set alpha to 1.0f. */
|
||||||
|
R_TRY(viSetDisplayAlpha(&temp_display, 1.0f));
|
||||||
|
|
||||||
Result ShowFatalTask::SetupDisplayInternal() {
|
|
||||||
ViDisplay display;
|
|
||||||
/* Try to open the display. */
|
|
||||||
R_TRY_CATCH(viOpenDisplay("Internal", &display)) {
|
|
||||||
R_CATCH(ResultViNotFound) {
|
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
} R_END_TRY_CATCH;
|
|
||||||
|
|
||||||
/* Guarantee we close the display. */
|
Result ShowFatalTask::SetupDisplayExternal() {
|
||||||
ON_SCOPE_EXIT { viCloseDisplay(&display); };
|
ViDisplay temp_display;
|
||||||
|
/* Try to open the display. */
|
||||||
|
R_TRY_CATCH(viOpenDisplay("External", &temp_display)) {
|
||||||
|
R_CATCH(ResultViNotFound) {
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
} R_END_TRY_CATCH;
|
||||||
|
|
||||||
/* Turn on the screen. */
|
/* Guarantee we close the display. */
|
||||||
R_TRY(viSetDisplayPowerState(&display, ViPowerState_On));
|
ON_SCOPE_EXIT { viCloseDisplay(&temp_display); };
|
||||||
|
|
||||||
/* Set alpha to 1.0f. */
|
/* Set alpha to 1.0f. */
|
||||||
R_TRY(viSetDisplayAlpha(&display, 1.0f));
|
R_TRY(viSetDisplayAlpha(&temp_display, 1.0f));
|
||||||
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result ShowFatalTask::SetupDisplayExternal() {
|
|
||||||
ViDisplay display;
|
|
||||||
/* Try to open the display. */
|
|
||||||
R_TRY_CATCH(viOpenDisplay("External", &display)) {
|
|
||||||
R_CATCH(ResultViNotFound) {
|
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
} R_END_TRY_CATCH;
|
|
||||||
|
|
||||||
/* Guarantee we close the display. */
|
Result ShowFatalTask::PrepareScreenForDrawing() {
|
||||||
ON_SCOPE_EXIT { viCloseDisplay(&display); };
|
/* Connect to vi. */
|
||||||
|
R_TRY(viInitialize(ViServiceType_Manager));
|
||||||
|
|
||||||
/* Set alpha to 1.0f. */
|
/* Close other content. */
|
||||||
R_TRY(viSetDisplayAlpha(&display, 1.0f));
|
viSetContentVisibility(false);
|
||||||
|
|
||||||
return ResultSuccess;
|
/* Setup the two displays. */
|
||||||
}
|
R_TRY(SetupDisplayInternal());
|
||||||
|
R_TRY(SetupDisplayExternal());
|
||||||
|
|
||||||
Result ShowFatalTask::PrepareScreenForDrawing() {
|
/* Open the default display. */
|
||||||
/* Connect to vi. */
|
R_TRY(viOpenDefaultDisplay(&this->display));
|
||||||
R_TRY(viInitialize(ViServiceType_Manager));
|
|
||||||
|
|
||||||
/* Close other content. */
|
/* Reset the display magnification to its default value. */
|
||||||
viSetContentVisibility(false);
|
u32 display_width, display_height;
|
||||||
|
R_TRY(viGetDisplayLogicalResolution(&this->display, &display_width, &display_height));
|
||||||
|
|
||||||
/* Setup the two displays. */
|
/* viSetDisplayMagnification was added in 3.0.0. */
|
||||||
R_TRY(SetupDisplayInternal());
|
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_300) {
|
||||||
R_TRY(SetupDisplayExternal());
|
R_TRY(viSetDisplayMagnification(&this->display, 0, 0, display_width, display_height));
|
||||||
|
}
|
||||||
|
|
||||||
/* Open the default display. */
|
/* Create layer to draw to. */
|
||||||
R_TRY(viOpenDefaultDisplay(&this->display));
|
R_TRY(viCreateLayer(&this->display, &this->layer));
|
||||||
|
|
||||||
/* Reset the display magnification to its default value. */
|
/* Setup the layer. */
|
||||||
u32 display_width, display_height;
|
{
|
||||||
R_TRY(viGetDisplayLogicalResolution(&this->display, &display_width, &display_height));
|
/* 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 u32 raw_width = FatalScreenWidth;
|
||||||
|
constexpr u32 raw_height = FatalScreenHeight;
|
||||||
|
constexpr u32 layer_width = ((raw_width) * 3) / 2;
|
||||||
|
constexpr u32 layer_height = ((raw_height) * 3) / 2;
|
||||||
|
|
||||||
/* viSetDisplayMagnification was added in 3.0.0. */
|
const float layer_x = static_cast<float>((display_width - layer_width) / 2);
|
||||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_300) {
|
const float layer_y = static_cast<float>((display_height - layer_height) / 2);
|
||||||
R_TRY(viSetDisplayMagnification(&this->display, 0, 0, display_width, display_height));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create layer to draw to. */
|
R_TRY(viSetLayerSize(&this->layer, layer_width, layer_height));
|
||||||
R_TRY(viCreateLayer(&this->display, &this->layer));
|
|
||||||
|
|
||||||
/* Setup the layer. */
|
/* Set the layer's Z at display maximum, to be above everything else .*/
|
||||||
{
|
R_TRY(viSetLayerZ(&this->layer, FatalLayerZ));
|
||||||
/* 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 u32 raw_width = FatalScreenWidth;
|
|
||||||
constexpr u32 raw_height = FatalScreenHeight;
|
|
||||||
constexpr u32 layer_width = ((raw_width) * 3) / 2;
|
|
||||||
constexpr u32 layer_height = ((raw_height) * 3) / 2;
|
|
||||||
|
|
||||||
const float layer_x = static_cast<float>((display_width - layer_width) / 2);
|
/* Center the layer in the screen. */
|
||||||
const float layer_y = static_cast<float>((display_height - layer_height) / 2);
|
R_TRY(viSetLayerPosition(&this->layer, layer_x, layer_y));
|
||||||
u64 layer_z;
|
|
||||||
|
|
||||||
R_TRY(viSetLayerSize(&this->layer, layer_width, layer_height));
|
/* Create framebuffer. */
|
||||||
|
R_TRY(nwindowCreateFromLayer(&this->win, &this->layer));
|
||||||
|
R_TRY(framebufferCreate(&this->fb, &this->win, raw_width, raw_height, PIXEL_FORMAT_RGB_565, 1));
|
||||||
|
}
|
||||||
|
|
||||||
/* Set the layer's Z at display maximum, to be above everything else .*/
|
return ResultSuccess;
|
||||||
/* NOTE: Fatal hardcodes 100 here. */
|
|
||||||
if (R_SUCCEEDED(viGetDisplayMaximumZ(&this->display, &layer_z))) {
|
|
||||||
R_TRY(viSetLayerZ(&this->layer, layer_z));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Center the layer in the screen. */
|
Result ShowFatalTask::ShowFatal() {
|
||||||
R_TRY(viSetLayerPosition(&this->layer, layer_x, layer_y));
|
const FatalConfig &config = GetFatalConfig();
|
||||||
|
|
||||||
/* Create framebuffer. */
|
/* Prepare screen for drawing. */
|
||||||
R_TRY(nwindowCreateFromLayer(&this->win, &this->layer));
|
DoWithSmSession([&]() {
|
||||||
R_TRY(framebufferCreate(&this->fb, &this->win, raw_width, raw_height, PIXEL_FORMAT_RGB_565, 1));
|
R_ASSERT(PrepareScreenForDrawing());
|
||||||
}
|
});
|
||||||
|
|
||||||
return ResultSuccess;
|
/* Dequeue a buffer. */
|
||||||
}
|
u16 *tiled_buf = reinterpret_cast<u16 *>(framebufferBegin(&this->fb, NULL));
|
||||||
|
if (tiled_buf == nullptr) {
|
||||||
|
return ResultFatalNullGraphicsBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
Result ShowFatalTask::ShowFatal() {
|
/* Let the font manager know about our framebuffer. */
|
||||||
const FatalConfig *config = GetFatalConfig();
|
font::ConfigureFontFramebuffer(tiled_buf, GetPixelOffset);
|
||||||
|
font::SetFontColor(0xFFFF);
|
||||||
|
|
||||||
/* Prepare screen for drawing. */
|
/* Draw a background. */
|
||||||
DoWithSmSession([&]() {
|
for (size_t i = 0; i < this->fb.fb_size / sizeof(*tiled_buf); i++) {
|
||||||
if (R_FAILED(PrepareScreenForDrawing())) {
|
tiled_buf[i] = 0x39C9;
|
||||||
std::abort();
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Dequeue a buffer. */
|
/* Draw the atmosphere logo in the bottom right corner. */
|
||||||
u16 *tiled_buf = reinterpret_cast<u16 *>(framebufferBegin(&this->fb, NULL));
|
for (size_t y = 0; y < AtmosphereLogoHeight; y++) {
|
||||||
if (tiled_buf == nullptr) {
|
for (size_t x = 0; x < AtmosphereLogoWidth; x++) {
|
||||||
return ResultFatalNullGraphicsBuffer;
|
tiled_buf[GetPixelOffset(FatalScreenWidth - AtmosphereLogoWidth - 32 + x, 32 + y)] = AtmosphereLogoData[y * AtmosphereLogoWidth + x];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Let the font manager know about our framebuffer. */
|
/* TODO: Actually draw meaningful shit here. */
|
||||||
FontManager::ConfigureFontFramebuffer(tiled_buf, GetPixelOffset);
|
font::SetPosition(32, 64);
|
||||||
FontManager::SetFontColor(0xFFFF);
|
font::SetFontSize(16.0f);
|
||||||
|
font::PrintFormat(config.GetErrorMessage(), R_MODULE(this->context->error_code), R_DESCRIPTION(this->context->error_code), this->context->error_code);
|
||||||
/* Draw a background. */
|
font::AddSpacingLines(0.5f);
|
||||||
for (size_t i = 0; i < this->fb.fb_size / sizeof(*tiled_buf); i++) {
|
font::PrintFormatLine("Title: %016lX", static_cast<u64>(this->context->title_id));
|
||||||
tiled_buf[i] = 0x39C9;
|
font::AddSpacingLines(0.5f);
|
||||||
}
|
font::PrintFormatLine(u8"Firmware: %s (Atmosphère %u.%u.%u-%s)", config.GetFirmwareVersion().display_version, CURRENT_ATMOSPHERE_VERSION, GetAtmosphereGitRevision());
|
||||||
|
font::AddSpacingLines(1.5f);
|
||||||
/* Draw the atmosphere logo in the bottom right corner. */
|
if (this->context->error_code != ResultAtmosphereVersionMismatch) {
|
||||||
for (size_t y = 0; y < AMS_LOGO_HEIGHT; y++) {
|
font::Print(config.GetErrorDescription());
|
||||||
for (size_t x = 0; x < AMS_LOGO_WIDTH; x++) {
|
|
||||||
tiled_buf[GetPixelOffset(FatalScreenWidth - AMS_LOGO_WIDTH - 32 + x, 32 + y)] = AMS_LOGO_BIN[y * AMS_LOGO_WIDTH + x];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Actually draw meaningful shit here. */
|
|
||||||
FontManager::SetPosition(32, 64);
|
|
||||||
FontManager::SetFontSize(16.0f);
|
|
||||||
FontManager::PrintFormat(config->error_msg, R_MODULE(this->ctx->error_code), R_DESCRIPTION(this->ctx->error_code), this->ctx->error_code);
|
|
||||||
FontManager::AddSpacingLines(0.5f);
|
|
||||||
FontManager::PrintFormatLine("Title: %016lX", this->title_id);
|
|
||||||
FontManager::AddSpacingLines(0.5f);
|
|
||||||
FontManager::PrintFormatLine(u8"Firmware: %s (Atmosphère %u.%u.%u-%s)", GetFatalConfig()->firmware_version.display_version, CURRENT_ATMOSPHERE_VERSION, GetAtmosphereGitRevision());
|
|
||||||
FontManager::AddSpacingLines(1.5f);
|
|
||||||
if (this->ctx->error_code != ResultAtmosphereVersionMismatch) {
|
|
||||||
FontManager::Print(config->error_desc);
|
|
||||||
} else {
|
|
||||||
/* Print a special message for atmosphere version mismatch. */
|
|
||||||
FontManager::Print(u8"Atmosphère version mismatch detected.\n\n"
|
|
||||||
u8"Please press the POWER Button to restart the console normally, or a VOL button\n"
|
|
||||||
u8"to reboot to a payload (or RCM, if none is present). If you are unable to\n"
|
|
||||||
u8"restart the console, hold the POWER Button for 12 seconds to turn the console off.\n\n"
|
|
||||||
u8"Please ensure that all Atmosphère components are updated.\n"
|
|
||||||
u8"github.com/Atmosphere-NX/Atmosphere/releases\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add a line. */
|
|
||||||
for (size_t x = 32; x < FatalScreenWidth - 32; x++) {
|
|
||||||
tiled_buf[GetPixelOffset(x, FontManager::GetY())] = 0xFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
FontManager::AddSpacingLines(1.5f);
|
|
||||||
|
|
||||||
u32 backtrace_y = FontManager::GetY();
|
|
||||||
u32 backtrace_x = 0;
|
|
||||||
|
|
||||||
/* Print GPRs. */
|
|
||||||
FontManager::SetFontSize(14.0f);
|
|
||||||
FontManager::Print("General Purpose Registers ");
|
|
||||||
{
|
|
||||||
FontManager::SetPosition(FontManager::GetX() + 2, FontManager::GetY());
|
|
||||||
u32 x = FontManager::GetX();
|
|
||||||
FontManager::Print("PC: ");
|
|
||||||
FontManager::SetPosition(x + 47, FontManager::GetY());
|
|
||||||
}
|
|
||||||
if (this->ctx->cpu_ctx.is_aarch32) {
|
|
||||||
FontManager::PrintMonospaceU32(this->ctx->cpu_ctx.aarch32_ctx.pc);
|
|
||||||
} else {
|
|
||||||
FontManager::PrintMonospaceU64(this->ctx->cpu_ctx.aarch64_ctx.pc);
|
|
||||||
}
|
|
||||||
FontManager::PrintLine("");
|
|
||||||
FontManager::SetPosition(32, FontManager::GetY());
|
|
||||||
FontManager::AddSpacingLines(0.5f);
|
|
||||||
if (this->ctx->cpu_ctx.is_aarch32) {
|
|
||||||
for (size_t i = 0; i < (NumAarch32Gprs / 2); i++) {
|
|
||||||
u32 x = FontManager::GetX();
|
|
||||||
FontManager::PrintFormat("%s:", Aarch32GprNames[i]);
|
|
||||||
FontManager::SetPosition(x + 47, FontManager::GetY());
|
|
||||||
if (this->ctx->has_gprs[i]) {
|
|
||||||
FontManager::PrintMonospaceU32(this->ctx->cpu_ctx.aarch32_ctx.r[i]);
|
|
||||||
} else {
|
} else {
|
||||||
FontManager::PrintMonospaceBlank(8);
|
/* Print a special message for atmosphere version mismatch. */
|
||||||
|
font::Print(u8"Atmosphère version mismatch detected.\n\n"
|
||||||
|
u8"Please press the POWER Button to restart the console normally, or a VOL button\n"
|
||||||
|
u8"to reboot to a payload (or RCM, if none is present). If you are unable to\n"
|
||||||
|
u8"restart the console, hold the POWER Button for 12 seconds to turn the console off.\n\n"
|
||||||
|
u8"Please ensure that all Atmosphère components are updated.\n"
|
||||||
|
u8"github.com/Atmosphere-NX/Atmosphere/releases\n");
|
||||||
}
|
}
|
||||||
FontManager::Print(" ");
|
|
||||||
x = FontManager::GetX();
|
/* Add a line. */
|
||||||
FontManager::PrintFormat("%s:", Aarch32GprNames[i + (NumAarch32Gprs / 2)]);
|
for (size_t x = 32; x < FatalScreenWidth - 32; x++) {
|
||||||
FontManager::SetPosition(x + 47, FontManager::GetY());
|
tiled_buf[GetPixelOffset(x, font::GetY())] = 0xFFFF;
|
||||||
if (this->ctx->has_gprs[i + (NumAarch32Gprs / 2)]) {
|
}
|
||||||
FontManager::PrintMonospaceU32(this->ctx->cpu_ctx.aarch32_ctx.r[i + (NumAarch32Gprs / 2)]);
|
|
||||||
|
font::AddSpacingLines(1.5f);
|
||||||
|
|
||||||
|
u32 backtrace_y = font::GetY();
|
||||||
|
u32 backtrace_x = 0;
|
||||||
|
|
||||||
|
/* Note architecutre. */
|
||||||
|
const bool is_aarch32 = this->context->cpu_ctx.architecture == CpuContext::Architecture_Aarch32;
|
||||||
|
|
||||||
|
/* Print GPRs. */
|
||||||
|
font::SetFontSize(14.0f);
|
||||||
|
font::Print("General Purpose Registers ");
|
||||||
|
{
|
||||||
|
font::SetPosition(font::GetX() + 2, font::GetY());
|
||||||
|
u32 x = font::GetX();
|
||||||
|
font::Print("PC: ");
|
||||||
|
font::SetPosition(x + 47, font::GetY());
|
||||||
|
}
|
||||||
|
if (is_aarch32) {
|
||||||
|
font::PrintMonospaceU32(this->context->cpu_ctx.aarch32_ctx.pc);
|
||||||
} else {
|
} else {
|
||||||
FontManager::PrintMonospaceBlank(8);
|
font::PrintMonospaceU64(this->context->cpu_ctx.aarch64_ctx.pc);
|
||||||
|
}
|
||||||
|
font::PrintLine("");
|
||||||
|
font::SetPosition(32, 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 (this->context->cpu_ctx.aarch32_ctx.HasRegisterValue(static_cast<aarch32::RegisterName>(i))) {
|
||||||
|
font::PrintMonospaceU32(this->context->cpu_ctx.aarch32_ctx.r[i]);
|
||||||
|
} else {
|
||||||
|
font::PrintMonospaceBlank(8);
|
||||||
|
}
|
||||||
|
font::Print(" ");
|
||||||
|
x = font::GetX();
|
||||||
|
font::PrintFormat("%s:", aarch32::CpuContext::RegisterNameStrings[i + (aarch32::RegisterName_GeneralPurposeCount / 2)]);
|
||||||
|
font::SetPosition(x + 47, font::GetY());
|
||||||
|
if (this->context->cpu_ctx.aarch32_ctx.HasRegisterValue(static_cast<aarch32::RegisterName>(i + (aarch32::RegisterName_GeneralPurposeCount / 2)))) {
|
||||||
|
font::PrintMonospaceU32(this->context->cpu_ctx.aarch32_ctx.r[i + (aarch32::RegisterName_GeneralPurposeCount / 2)]);
|
||||||
|
} else {
|
||||||
|
font::PrintMonospaceBlank(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == (aarch32::RegisterName_GeneralPurposeCount / 2) - 1) {
|
||||||
|
font::Print(" ");
|
||||||
|
backtrace_x = font::GetX();
|
||||||
|
}
|
||||||
|
|
||||||
|
font::PrintLine("");
|
||||||
|
font::SetPosition(32, 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 (this->context->cpu_ctx.aarch64_ctx.HasRegisterValue(static_cast<aarch64::RegisterName>(i))) {
|
||||||
|
font::PrintMonospaceU64(this->context->cpu_ctx.aarch64_ctx.x[i]);
|
||||||
|
} else {
|
||||||
|
font::PrintMonospaceBlank(16);
|
||||||
|
}
|
||||||
|
font::Print(" ");
|
||||||
|
x = font::GetX();
|
||||||
|
font::PrintFormat("%s:", aarch64::CpuContext::RegisterNameStrings[i + (aarch64::RegisterName_GeneralPurposeCount / 2)]);
|
||||||
|
font::SetPosition(x + 47, font::GetY());
|
||||||
|
if (this->context->cpu_ctx.aarch64_ctx.HasRegisterValue(static_cast<aarch64::RegisterName>(i + (aarch64::RegisterName_GeneralPurposeCount / 2)))) {
|
||||||
|
font::PrintMonospaceU64(this->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(32, font::GetY());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i == (NumAarch32Gprs / 2) - 1) {
|
/* Print Backtrace. */
|
||||||
FontManager::Print(" ");
|
u32 bt_size;
|
||||||
backtrace_x = FontManager::GetX();
|
if (is_aarch32) {
|
||||||
|
bt_size = this->context->cpu_ctx.aarch32_ctx.stack_trace_size;
|
||||||
|
} else {
|
||||||
|
bt_size = this->context->cpu_ctx.aarch64_ctx.stack_trace_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
FontManager::PrintLine("");
|
|
||||||
FontManager::SetPosition(32, FontManager::GetY());
|
font::SetPosition(backtrace_x, backtrace_y);
|
||||||
|
if (bt_size == 0) {
|
||||||
|
if (is_aarch32) {
|
||||||
|
font::Print("Start Address: ");
|
||||||
|
font::PrintMonospaceU32(this->context->cpu_ctx.aarch32_ctx.base_address);
|
||||||
|
font::PrintLine("");
|
||||||
|
} else {
|
||||||
|
font::Print("Start Address: ");
|
||||||
|
font::PrintMonospaceU64(this->context->cpu_ctx.aarch64_ctx.base_address);
|
||||||
|
font::PrintLine("");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (is_aarch32) {
|
||||||
|
font::Print("Backtrace - Start Address: ");
|
||||||
|
font::PrintMonospaceU32(this->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 < this->context->cpu_ctx.aarch32_ctx.stack_trace_size) {
|
||||||
|
bt_cur = this->context->cpu_ctx.aarch32_ctx.stack_trace[i];
|
||||||
|
}
|
||||||
|
if (i + aarch32::CpuContext::MaxStackTraceDepth / 2 < this->context->cpu_ctx.aarch32_ctx.stack_trace_size) {
|
||||||
|
bt_next = this->context->cpu_ctx.aarch32_ctx.stack_trace[i + aarch32::CpuContext::MaxStackTraceDepth / 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < this->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::Print(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i + aarch32::CpuContext::MaxStackTraceDepth / 2 < this->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::PrintLine("");
|
||||||
|
font::SetPosition(backtrace_x, font::GetY());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
font::Print("Backtrace - Start Address: ");
|
||||||
|
font::PrintMonospaceU64(this->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 < this->context->cpu_ctx.aarch64_ctx.stack_trace_size) {
|
||||||
|
bt_cur = this->context->cpu_ctx.aarch64_ctx.stack_trace[i];
|
||||||
|
}
|
||||||
|
if (i + aarch64::CpuContext::MaxStackTraceDepth / 2 < this->context->cpu_ctx.aarch64_ctx.stack_trace_size) {
|
||||||
|
bt_next = this->context->cpu_ctx.aarch64_ctx.stack_trace[i + aarch64::CpuContext::MaxStackTraceDepth / 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < this->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 < this->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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Enqueue the buffer. */
|
||||||
|
framebufferEnd(&fb);
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
for (size_t i = 0; i < NumAarch64Gprs / 2; i++) {
|
|
||||||
u32 x = FontManager::GetX();
|
|
||||||
FontManager::PrintFormat("%s:", Aarch64GprNames[i]);
|
|
||||||
FontManager::SetPosition(x + 47, FontManager::GetY());
|
|
||||||
if (this->ctx->has_gprs[i]) {
|
|
||||||
FontManager::PrintMonospaceU64(this->ctx->cpu_ctx.aarch64_ctx.x[i]);
|
|
||||||
} else {
|
|
||||||
FontManager::PrintMonospaceBlank(16);
|
|
||||||
}
|
|
||||||
FontManager::Print(" ");
|
|
||||||
x = FontManager::GetX();
|
|
||||||
FontManager::PrintFormat("%s:", Aarch64GprNames[i + (NumAarch64Gprs / 2)]);
|
|
||||||
FontManager::SetPosition(x + 47, FontManager::GetY());
|
|
||||||
if (this->ctx->has_gprs[i + (NumAarch64Gprs / 2)]) {
|
|
||||||
FontManager::PrintMonospaceU64(this->ctx->cpu_ctx.aarch64_ctx.x[i + (NumAarch64Gprs / 2)]);
|
|
||||||
} else {
|
|
||||||
FontManager::PrintMonospaceBlank(16);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i == (NumAarch64Gprs / 2) - 1) {
|
Result ShowFatalTask::Run() {
|
||||||
FontManager::Print(" ");
|
/* Don't show the fatal error screen until we've verified the battery is okay. */
|
||||||
backtrace_x = FontManager::GetX();
|
eventWait(const_cast<Event *>(&this->context->battery_event), U64_MAX);
|
||||||
}
|
|
||||||
|
|
||||||
FontManager::PrintLine("");
|
return ShowFatal();
|
||||||
FontManager::SetPosition(32, FontManager::GetY());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BacklightControlTask::TurnOnBacklight() {
|
||||||
|
lblSwitchBacklightOn(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result BacklightControlTask::Run() {
|
||||||
|
TurnOnBacklight();
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Print Backtrace. */
|
ITask *GetShowFatalTask(const ThrowContext *ctx) {
|
||||||
u32 bt_size;
|
g_show_fatal_task.Initialize(ctx);
|
||||||
if (this->ctx->cpu_ctx.is_aarch32) {
|
return &g_show_fatal_task;
|
||||||
bt_size = this->ctx->cpu_ctx.aarch32_ctx.stack_trace_size;
|
|
||||||
} else {
|
|
||||||
bt_size = this->ctx->cpu_ctx.aarch64_ctx.stack_trace_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ITask *GetBacklightControlTask(const ThrowContext *ctx) {
|
||||||
FontManager::SetPosition(backtrace_x, backtrace_y);
|
g_backlight_control_task.Initialize(ctx);
|
||||||
if (bt_size == 0) {
|
return &g_backlight_control_task;
|
||||||
if (this->ctx->cpu_ctx.is_aarch32) {
|
|
||||||
FontManager::Print("Start Address: ");
|
|
||||||
FontManager::PrintMonospaceU32(this->ctx->cpu_ctx.aarch32_ctx.start_address);
|
|
||||||
FontManager::PrintLine("");
|
|
||||||
} else {
|
|
||||||
FontManager::Print("Start Address: ");
|
|
||||||
FontManager::PrintMonospaceU64(this->ctx->cpu_ctx.aarch64_ctx.start_address);
|
|
||||||
FontManager::PrintLine("");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this->ctx->cpu_ctx.is_aarch32) {
|
|
||||||
FontManager::Print("Backtrace - Start Address: ");
|
|
||||||
FontManager::PrintMonospaceU32(this->ctx->cpu_ctx.aarch32_ctx.start_address);
|
|
||||||
FontManager::PrintLine("");
|
|
||||||
FontManager::AddSpacingLines(0.5f);
|
|
||||||
for (u32 i = 0; i < Aarch32CpuContext::MaxStackTraceDepth / 2; i++) {
|
|
||||||
u32 bt_cur = 0, bt_next = 0;
|
|
||||||
if (i < this->ctx->cpu_ctx.aarch32_ctx.stack_trace_size) {
|
|
||||||
bt_cur = this->ctx->cpu_ctx.aarch32_ctx.stack_trace[i];
|
|
||||||
}
|
|
||||||
if (i + Aarch32CpuContext::MaxStackTraceDepth / 2 < this->ctx->cpu_ctx.aarch32_ctx.stack_trace_size) {
|
|
||||||
bt_next = this->ctx->cpu_ctx.aarch32_ctx.stack_trace[i + Aarch32CpuContext::MaxStackTraceDepth / 2];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i < this->ctx->cpu_ctx.aarch32_ctx.stack_trace_size) {
|
|
||||||
u32 x = FontManager::GetX();
|
|
||||||
FontManager::PrintFormat("BT[%02d]: ", i);
|
|
||||||
FontManager::SetPosition(x + 72, FontManager::GetY());
|
|
||||||
FontManager::PrintMonospaceU32(bt_cur);
|
|
||||||
FontManager::Print(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i + Aarch32CpuContext::MaxStackTraceDepth / 2 < this->ctx->cpu_ctx.aarch32_ctx.stack_trace_size) {
|
|
||||||
u32 x = FontManager::GetX();
|
|
||||||
FontManager::PrintFormat("BT[%02d]: ", i + Aarch32CpuContext::MaxStackTraceDepth / 2);
|
|
||||||
FontManager::SetPosition(x + 72, FontManager::GetY());
|
|
||||||
FontManager::PrintMonospaceU32(bt_next);
|
|
||||||
}
|
|
||||||
|
|
||||||
FontManager::PrintLine("");
|
|
||||||
FontManager::SetPosition(backtrace_x, FontManager::GetY());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
FontManager::Print("Backtrace - Start Address: ");
|
|
||||||
FontManager::PrintMonospaceU64(this->ctx->cpu_ctx.aarch64_ctx.start_address);
|
|
||||||
FontManager::PrintLine("");
|
|
||||||
FontManager::AddSpacingLines(0.5f);
|
|
||||||
for (u32 i = 0; i < Aarch64CpuContext::MaxStackTraceDepth / 2; i++) {
|
|
||||||
u64 bt_cur = 0, bt_next = 0;
|
|
||||||
if (i < this->ctx->cpu_ctx.aarch64_ctx.stack_trace_size) {
|
|
||||||
bt_cur = this->ctx->cpu_ctx.aarch64_ctx.stack_trace[i];
|
|
||||||
}
|
|
||||||
if (i + Aarch64CpuContext::MaxStackTraceDepth / 2 < this->ctx->cpu_ctx.aarch64_ctx.stack_trace_size) {
|
|
||||||
bt_next = this->ctx->cpu_ctx.aarch64_ctx.stack_trace[i + Aarch64CpuContext::MaxStackTraceDepth / 2];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i < this->ctx->cpu_ctx.aarch64_ctx.stack_trace_size) {
|
|
||||||
u32 x = FontManager::GetX();
|
|
||||||
FontManager::PrintFormat("BT[%02d]: ", i);
|
|
||||||
FontManager::SetPosition(x + 72, FontManager::GetY());
|
|
||||||
FontManager::PrintMonospaceU64(bt_cur);
|
|
||||||
FontManager::Print(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i + Aarch64CpuContext::MaxStackTraceDepth / 2 < this->ctx->cpu_ctx.aarch64_ctx.stack_trace_size) {
|
|
||||||
u32 x = FontManager::GetX();
|
|
||||||
FontManager::PrintFormat("BT[%02d]: ", i + Aarch64CpuContext::MaxStackTraceDepth / 2);
|
|
||||||
FontManager::SetPosition(x + 72, FontManager::GetY());
|
|
||||||
FontManager::PrintMonospaceU64(bt_next);
|
|
||||||
}
|
|
||||||
|
|
||||||
FontManager::PrintLine("");
|
|
||||||
FontManager::SetPosition(backtrace_x, FontManager::GetY());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Enqueue the buffer. */
|
|
||||||
framebufferEnd(&fb);
|
|
||||||
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result ShowFatalTask::Run() {
|
|
||||||
/* Don't show the fatal error screen until we've verified the battery is okay. */
|
|
||||||
eventWait(this->battery_event, U64_MAX);
|
|
||||||
|
|
||||||
return ShowFatal();
|
|
||||||
}
|
|
||||||
|
|
||||||
void BacklightControlTask::TurnOnBacklight() {
|
|
||||||
lblSwitchBacklightOn(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result BacklightControlTask::Run() {
|
|
||||||
TurnOnBacklight();
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,37 +15,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include "fatal_task.hpp"
|
#include "fatal_task.hpp"
|
||||||
|
|
||||||
class ShowFatalTask : public IFatalTask {
|
namespace sts::fatal::srv {
|
||||||
private:
|
|
||||||
Event *battery_event;
|
|
||||||
ViDisplay display;
|
|
||||||
ViLayer layer;
|
|
||||||
NWindow win;
|
|
||||||
Framebuffer fb;
|
|
||||||
private:
|
|
||||||
Result SetupDisplayInternal();
|
|
||||||
Result SetupDisplayExternal();
|
|
||||||
Result PrepareScreenForDrawing();
|
|
||||||
Result ShowFatal();
|
|
||||||
public:
|
|
||||||
ShowFatalTask(FatalThrowContext *ctx, u64 title_id, Event *evt) : IFatalTask(ctx, title_id), battery_event(evt) { }
|
|
||||||
virtual Result Run() override;
|
|
||||||
virtual const char *GetName() const override {
|
|
||||||
return "ShowFatal";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class BacklightControlTask : public IFatalTask {
|
ITask *GetShowFatalTask(const ThrowContext *ctx);
|
||||||
private:
|
ITask *GetBacklightControlTask(const ThrowContext *ctx);
|
||||||
void TurnOnBacklight();
|
|
||||||
public:
|
}
|
||||||
BacklightControlTask(FatalThrowContext *ctx, u64 title_id) : IFatalTask(ctx, title_id) { }
|
|
||||||
virtual Result Run() override;
|
|
||||||
virtual const char *GetName() const override {
|
|
||||||
return "BacklightControlTask";
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -14,60 +14,86 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include "fatal_task_sound.hpp"
|
#include "fatal_task_sound.hpp"
|
||||||
|
|
||||||
|
namespace sts::fatal::srv {
|
||||||
|
|
||||||
void StopSoundTask::StopSound() {
|
namespace {
|
||||||
/* Talk to the ALC5639 over I2C, and disable audio output. */
|
|
||||||
{
|
|
||||||
I2cSession audio;
|
|
||||||
if (R_SUCCEEDED(i2cOpenSession(&audio, I2cDevice_Alc5639))) {
|
|
||||||
struct {
|
|
||||||
u16 dev;
|
|
||||||
u8 val;
|
|
||||||
} __attribute__((packed)) cmd;
|
|
||||||
static_assert(sizeof(cmd) == 3, "I2C command definition!");
|
|
||||||
|
|
||||||
cmd.dev = 0xC801;
|
/* Task definition. */
|
||||||
cmd.val = 200;
|
class StopSoundTask : public ITask {
|
||||||
i2csessionSendAuto(&audio, &cmd, sizeof(cmd), I2cTransactionOption_All);
|
private:
|
||||||
|
void StopSound();
|
||||||
|
public:
|
||||||
|
virtual Result Run() override;
|
||||||
|
virtual const char *GetName() const override {
|
||||||
|
return "SoundTask";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
cmd.dev = 0xC802;
|
/* Task global. */
|
||||||
cmd.val = 200;
|
StopSoundTask g_stop_sound_task;
|
||||||
i2csessionSendAuto(&audio, &cmd, sizeof(cmd), I2cTransactionOption_All);
|
|
||||||
|
|
||||||
cmd.dev = 0xC802;
|
/* Task implementation. */
|
||||||
cmd.val = 200;
|
void StopSoundTask::StopSound() {
|
||||||
i2csessionSendAuto(&audio, &cmd, sizeof(cmd), I2cTransactionOption_All);
|
/* Talk to the ALC5639 over I2C, and disable audio output. */
|
||||||
|
{
|
||||||
|
I2cSession audio;
|
||||||
|
if (R_SUCCEEDED(i2cOpenSession(&audio, I2cDevice_Alc5639))) {
|
||||||
|
ON_SCOPE_EXIT { i2csessionClose(&audio); };
|
||||||
|
|
||||||
for (u16 dev = 97; dev <= 102; dev++) {
|
struct {
|
||||||
cmd.dev = dev;
|
u16 dev;
|
||||||
cmd.val = 0;
|
u8 val;
|
||||||
i2csessionSendAuto(&audio, &cmd, sizeof(cmd), I2cTransactionOption_All);
|
} __attribute__((packed)) cmd;
|
||||||
|
static_assert(sizeof(cmd) == 3, "I2C command definition!");
|
||||||
|
|
||||||
|
cmd.dev = 0xC801;
|
||||||
|
cmd.val = 200;
|
||||||
|
i2csessionSendAuto(&audio, &cmd, sizeof(cmd), I2cTransactionOption_All);
|
||||||
|
|
||||||
|
cmd.dev = 0xC802;
|
||||||
|
cmd.val = 200;
|
||||||
|
i2csessionSendAuto(&audio, &cmd, sizeof(cmd), I2cTransactionOption_All);
|
||||||
|
|
||||||
|
cmd.dev = 0xC802;
|
||||||
|
cmd.val = 200;
|
||||||
|
i2csessionSendAuto(&audio, &cmd, sizeof(cmd), I2cTransactionOption_All);
|
||||||
|
|
||||||
|
for (u16 dev = 97; dev <= 102; dev++) {
|
||||||
|
cmd.dev = dev;
|
||||||
|
cmd.val = 0;
|
||||||
|
i2csessionSendAuto(&audio, &cmd, sizeof(cmd), I2cTransactionOption_All);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i2csessionClose(&audio);
|
/* Talk to the ALC5639 over GPIO, and disable audio output */
|
||||||
|
{
|
||||||
|
GpioPadSession audio;
|
||||||
|
if (R_SUCCEEDED(gpioOpenSession(&audio, GpioPadName_AudioCodec))) {
|
||||||
|
ON_SCOPE_EXIT { gpioPadClose(&audio); };
|
||||||
|
|
||||||
|
/* Set direction output, sleep 200 ms so it can take effect. */
|
||||||
|
gpioPadSetDirection(&audio, GpioDirection_Output);
|
||||||
|
svcSleepThread(200000000UL);
|
||||||
|
|
||||||
|
/* Pull audio codec low. */
|
||||||
|
gpioPadSetValue(&audio, GpioValue_Low);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result StopSoundTask::Run() {
|
||||||
|
StopSound();
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Talk to the ALC5639 over GPIO, and disable audio output */
|
ITask *GetStopSoundTask(const ThrowContext *ctx) {
|
||||||
{
|
g_stop_sound_task.Initialize(ctx);
|
||||||
GpioPadSession audio;
|
return &g_stop_sound_task;
|
||||||
if (R_SUCCEEDED(gpioOpenSession(&audio, GpioPadName_AudioCodec))) {
|
|
||||||
/* Set direction output, sleep 200 ms so it can take effect. */
|
|
||||||
gpioPadSetDirection(&audio, GpioDirection_Output);
|
|
||||||
svcSleepThread(200000000UL);
|
|
||||||
|
|
||||||
/* Pull audio codec low. */
|
|
||||||
gpioPadSetValue(&audio, GpioValue_Low);
|
|
||||||
|
|
||||||
gpioPadClose(&audio);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Result StopSoundTask::Run() {
|
|
||||||
StopSound();
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,17 +15,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include "fatal_task.hpp"
|
#include "fatal_task.hpp"
|
||||||
|
|
||||||
class StopSoundTask : public IFatalTask {
|
namespace sts::fatal::srv {
|
||||||
private:
|
|
||||||
void StopSound();
|
ITask *GetStopSoundTask(const ThrowContext *ctx);
|
||||||
public:
|
|
||||||
StopSoundTask(FatalThrowContext *ctx, u64 title_id) : IFatalTask(ctx, title_id) { }
|
}
|
||||||
virtual Result Run() override;
|
|
||||||
virtual const char *GetName() const override {
|
|
||||||
return "SoundTask";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,121 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 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 <switch.h>
|
|
||||||
#include "fatal_throw.hpp"
|
|
||||||
#include "fatal_event_manager.hpp"
|
|
||||||
#include "fatal_task.hpp"
|
|
||||||
#include "fatal_config.hpp"
|
|
||||||
#include "fatal_debug.hpp"
|
|
||||||
|
|
||||||
static bool g_thrown = false;
|
|
||||||
|
|
||||||
static Result SetThrown() {
|
|
||||||
/* This should be fine, since fatal only has a single IPC thread. */
|
|
||||||
if (g_thrown) {
|
|
||||||
return ResultFatalAlreadyThrown;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_thrown = true;
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result ThrowFatalForSelf(u32 error) {
|
|
||||||
u64 pid = 0;
|
|
||||||
|
|
||||||
svcGetProcessId(&pid, CUR_PROCESS_HANDLE);
|
|
||||||
return ThrowFatalImpl(error, pid, FatalType_ErrorScreen, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result ThrowFatalImpl(u32 error, u64 pid, FatalType policy, FatalCpuContext *cpu_ctx) {
|
|
||||||
FatalThrowContext ctx = {0};
|
|
||||||
ctx.error_code = error;
|
|
||||||
if (cpu_ctx != nullptr) {
|
|
||||||
ctx.cpu_ctx = *cpu_ctx;
|
|
||||||
/* Assume if we're provided a context that it's complete. */
|
|
||||||
for (u32 i = 0; i < NumAarch64Gprs; i++) {
|
|
||||||
ctx.has_gprs[i] = true;
|
|
||||||
}
|
|
||||||
/* Cap the stack trace size at a sane limit. */
|
|
||||||
/* TODO: Better to set to zero, in order to manually collect debug info ourselves instead? */
|
|
||||||
if (cpu_ctx->is_aarch32) {
|
|
||||||
ctx.cpu_ctx.aarch32_ctx.stack_trace_size = std::max(ctx.cpu_ctx.aarch32_ctx.stack_trace_size, static_cast<u32>(Aarch32CpuContext::MaxStackTraceDepth));
|
|
||||||
} else {
|
|
||||||
ctx.cpu_ctx.aarch64_ctx.stack_trace_size = std::max(ctx.cpu_ctx.aarch64_ctx.stack_trace_size, static_cast<u32>(Aarch64CpuContext::MaxStackTraceDepth));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
std::memset(&ctx.cpu_ctx, 0, sizeof(ctx.cpu_ctx));
|
|
||||||
}
|
|
||||||
/* Reassign this unconditionally, for convenience. */
|
|
||||||
cpu_ctx = &ctx.cpu_ctx;
|
|
||||||
|
|
||||||
/* Get config. */
|
|
||||||
const FatalConfig *config = GetFatalConfig();
|
|
||||||
|
|
||||||
/* Get title id. On failure, it'll be zero. */
|
|
||||||
sts::ncm::TitleId title_id = sts::ncm::TitleId::Invalid;
|
|
||||||
sts::pm::info::GetTitleId(&title_id, pid);
|
|
||||||
ctx.is_creport = title_id == sts::ncm::TitleId::Creport;
|
|
||||||
|
|
||||||
/* Support for ams creport. TODO: Make this its own command? */
|
|
||||||
if (ctx.is_creport && !cpu_ctx->is_aarch32 && cpu_ctx->aarch64_ctx.afsr0 != 0) {
|
|
||||||
title_id = sts::ncm::TitleId{cpu_ctx->aarch64_ctx.afsr0};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Atmosphere extension: automatic debug info collection. */
|
|
||||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200 && !ctx.is_creport) {
|
|
||||||
if ((cpu_ctx->is_aarch32 && cpu_ctx->aarch32_ctx.stack_trace_size == 0) || (!cpu_ctx->is_aarch32 && cpu_ctx->aarch64_ctx.stack_trace_size == 0)) {
|
|
||||||
TryCollectDebugInformation(&ctx, pid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (policy) {
|
|
||||||
case FatalType_ErrorReport:
|
|
||||||
/* TODO: Don't write an error report. */
|
|
||||||
break;
|
|
||||||
case FatalType_ErrorReportAndErrorScreen:
|
|
||||||
case FatalType_ErrorScreen:
|
|
||||||
{
|
|
||||||
/* Ensure we only throw once. */
|
|
||||||
R_TRY(SetThrown());
|
|
||||||
|
|
||||||
/* Signal that fatal is about to happen. */
|
|
||||||
GetEventManager()->SignalEvents();
|
|
||||||
|
|
||||||
/* Create events. */
|
|
||||||
Event erpt_event;
|
|
||||||
Event battery_event;
|
|
||||||
if (R_FAILED(eventCreate(&erpt_event, false)) || R_FAILED(eventCreate(&battery_event, false))) {
|
|
||||||
std::abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Run tasks. */
|
|
||||||
if (config->transition_to_fatal) {
|
|
||||||
RunFatalTasks(&ctx, static_cast<u64>(title_id), policy == FatalType_ErrorReportAndErrorScreen, &erpt_event, &battery_event);
|
|
||||||
} else {
|
|
||||||
/* If flag is not set, don't show the fatal screen. */
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
/* N aborts here. Should we just return an error code? */
|
|
||||||
std::abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
|
@ -1,159 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 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 <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
static constexpr size_t NumAarch64Gprs = 32;
|
|
||||||
static constexpr size_t NumAarch32Gprs = 16;
|
|
||||||
|
|
||||||
struct Aarch64CpuContext {
|
|
||||||
using RegisterType = u64;
|
|
||||||
static constexpr size_t MaxStackTraceDepth = 0x20;
|
|
||||||
|
|
||||||
/* Registers, exception context. N left names for these fields in fatal .rodata. */
|
|
||||||
union {
|
|
||||||
RegisterType x[NumAarch64Gprs];
|
|
||||||
struct {
|
|
||||||
RegisterType _x[29];
|
|
||||||
RegisterType fp;
|
|
||||||
RegisterType lr;
|
|
||||||
RegisterType sp;
|
|
||||||
RegisterType pc;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
RegisterType pstate;
|
|
||||||
RegisterType afsr0;
|
|
||||||
RegisterType afsr1;
|
|
||||||
RegisterType esr;
|
|
||||||
RegisterType far;
|
|
||||||
|
|
||||||
/* Misc. */
|
|
||||||
RegisterType stack_trace[MaxStackTraceDepth];
|
|
||||||
RegisterType start_address;
|
|
||||||
RegisterType register_set_flags;
|
|
||||||
u32 stack_trace_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Aarch32CpuContext {
|
|
||||||
using RegisterType = u32;
|
|
||||||
static constexpr size_t MaxStackTraceDepth = 0x20;
|
|
||||||
|
|
||||||
/* Registers, exception context. N left names for these fields in fatal .rodata. */
|
|
||||||
union {
|
|
||||||
RegisterType r[NumAarch32Gprs];
|
|
||||||
struct {
|
|
||||||
RegisterType _r[11];
|
|
||||||
RegisterType fp;
|
|
||||||
RegisterType ip;
|
|
||||||
RegisterType sp;
|
|
||||||
RegisterType lr;
|
|
||||||
RegisterType pc;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
RegisterType pstate;
|
|
||||||
RegisterType afsr0;
|
|
||||||
RegisterType afsr1;
|
|
||||||
RegisterType esr;
|
|
||||||
RegisterType far;
|
|
||||||
|
|
||||||
/* Misc. Yes, stack_trace_size is really laid out differently than aarch64... */
|
|
||||||
RegisterType stack_trace[MaxStackTraceDepth];
|
|
||||||
u32 stack_trace_size;
|
|
||||||
RegisterType start_address;
|
|
||||||
RegisterType register_set_flags;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FatalCpuContext {
|
|
||||||
union {
|
|
||||||
Aarch64CpuContext aarch64_ctx;
|
|
||||||
Aarch32CpuContext aarch32_ctx;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool is_aarch32;
|
|
||||||
u32 type;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FatalThrowContext {
|
|
||||||
u32 error_code;
|
|
||||||
bool is_creport;
|
|
||||||
bool has_gprs[NumAarch64Gprs];
|
|
||||||
size_t stack_dump_size;
|
|
||||||
u8 stack_dump[0x100];
|
|
||||||
char proc_name[0xD];
|
|
||||||
FatalCpuContext cpu_ctx;
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(Aarch64CpuContext) == 0x248, "Aarch64CpuContext definition!");
|
|
||||||
static_assert(sizeof(Aarch32CpuContext) == 0xE0, "Aarch32CpuContext definition!");
|
|
||||||
static_assert(sizeof(FatalCpuContext) == 0x250, "FatalCpuContext definition!");
|
|
||||||
static_assert(std::is_pod_v<FatalCpuContext>, "FatalCpuContext definition!");
|
|
||||||
|
|
||||||
static constexpr const char *Aarch64GprNames[NumAarch64Gprs] = {
|
|
||||||
u8"X0",
|
|
||||||
u8"X1",
|
|
||||||
u8"X2",
|
|
||||||
u8"X3",
|
|
||||||
u8"X4",
|
|
||||||
u8"X5",
|
|
||||||
u8"X6",
|
|
||||||
u8"X7",
|
|
||||||
u8"X8",
|
|
||||||
u8"X9",
|
|
||||||
u8"X10",
|
|
||||||
u8"X11",
|
|
||||||
u8"X12",
|
|
||||||
u8"X13",
|
|
||||||
u8"X14",
|
|
||||||
u8"X15",
|
|
||||||
u8"X16",
|
|
||||||
u8"X17",
|
|
||||||
u8"X18",
|
|
||||||
u8"X19",
|
|
||||||
u8"X20",
|
|
||||||
u8"X21",
|
|
||||||
u8"X22",
|
|
||||||
u8"X23",
|
|
||||||
u8"X24",
|
|
||||||
u8"X25",
|
|
||||||
u8"X26",
|
|
||||||
u8"X27",
|
|
||||||
u8"X28",
|
|
||||||
u8"FP",
|
|
||||||
u8"LR",
|
|
||||||
u8"SP",
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr const char *Aarch32GprNames[NumAarch32Gprs] = {
|
|
||||||
u8"R0",
|
|
||||||
u8"R1",
|
|
||||||
u8"R2",
|
|
||||||
u8"R3",
|
|
||||||
u8"R4",
|
|
||||||
u8"R5",
|
|
||||||
u8"R6",
|
|
||||||
u8"R7",
|
|
||||||
u8"R8",
|
|
||||||
u8"R9",
|
|
||||||
u8"R10",
|
|
||||||
u8"FP",
|
|
||||||
u8"IP",
|
|
||||||
u8"LR",
|
|
||||||
u8"SP",
|
|
||||||
u8"PC",
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 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 <switch.h>
|
|
||||||
#include "fatal_user.hpp"
|
|
||||||
#include "fatal_throw.hpp"
|
|
||||||
#include "fatal_event_manager.hpp"
|
|
||||||
#include "fatal_task.hpp"
|
|
||||||
|
|
||||||
Result UserService::ThrowFatal(u32 error, PidDescriptor pid_desc) {
|
|
||||||
return ThrowFatalImpl(error, pid_desc.pid, FatalType_ErrorReportAndErrorScreen, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result UserService::ThrowFatalWithPolicy(u32 error, PidDescriptor pid_desc, FatalType policy) {
|
|
||||||
return ThrowFatalImpl(error, pid_desc.pid, policy, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result UserService::ThrowFatalWithCpuContext(u32 error, PidDescriptor pid_desc, FatalType policy, InBuffer<u8> _ctx) {
|
|
||||||
if (_ctx.num_elements < sizeof(FatalCpuContext)) {
|
|
||||||
return ThrowFatalImpl(error, pid_desc.pid, policy, nullptr);
|
|
||||||
} else {
|
|
||||||
return ThrowFatalImpl(error, pid_desc.pid, policy, reinterpret_cast<FatalCpuContext *>(_ctx.buffer));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 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 <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "fatal_types.hpp"
|
|
||||||
|
|
||||||
class UserService final : public IServiceObject {
|
|
||||||
private:
|
|
||||||
enum class CommandId {
|
|
||||||
ThrowFatal = 0,
|
|
||||||
ThrowFatalWithPolicy = 1,
|
|
||||||
ThrowFatalWithCpuContext = 2,
|
|
||||||
};
|
|
||||||
private:
|
|
||||||
/* Actual commands. */
|
|
||||||
Result ThrowFatal(u32 error, PidDescriptor pid_desc);
|
|
||||||
Result ThrowFatalWithPolicy(u32 error, PidDescriptor pid_desc, FatalType policy);
|
|
||||||
Result ThrowFatalWithCpuContext(u32 error, PidDescriptor pid_desc, FatalType policy, InBuffer<u8> _ctx);
|
|
||||||
public:
|
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
|
||||||
MAKE_SERVICE_COMMAND_META(UserService, ThrowFatal),
|
|
||||||
MAKE_SERVICE_COMMAND_META(UserService, ThrowFatalWithPolicy),
|
|
||||||
MAKE_SERVICE_COMMAND_META(UserService, ThrowFatalWithCpuContext),
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -45,6 +45,7 @@
|
||||||
|
|
||||||
#include "stratosphere/svc.hpp"
|
#include "stratosphere/svc.hpp"
|
||||||
#include "stratosphere/cfg.hpp"
|
#include "stratosphere/cfg.hpp"
|
||||||
|
#include "stratosphere/fatal.hpp"
|
||||||
#include "stratosphere/hid.hpp"
|
#include "stratosphere/hid.hpp"
|
||||||
#include "stratosphere/ncm.hpp"
|
#include "stratosphere/ncm.hpp"
|
||||||
#include "stratosphere/pm.hpp"
|
#include "stratosphere/pm.hpp"
|
||||||
|
|
|
@ -14,10 +14,7 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include "fatal_private.hpp"
|
|
||||||
#include "fatal_event_manager.hpp"
|
|
||||||
|
|
||||||
Result PrivateService::GetFatalEvent(Out<CopiedHandle> out_h) {
|
#include "fatal/fatal_types.hpp"
|
||||||
return GetEventManager()->GetEvent(out_h.GetHandlePointer());
|
|
||||||
}
|
|
|
@ -0,0 +1,340 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 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 <switch.h>
|
||||||
|
#include "../defines.hpp"
|
||||||
|
#include "../results.hpp"
|
||||||
|
#include "../ncm/ncm_types.hpp"
|
||||||
|
|
||||||
|
namespace sts::fatal {
|
||||||
|
|
||||||
|
namespace aarch64 {
|
||||||
|
|
||||||
|
enum RegisterName {
|
||||||
|
RegisterName_X0 = 0,
|
||||||
|
RegisterName_X1 = 1,
|
||||||
|
RegisterName_X2 = 2,
|
||||||
|
RegisterName_X3 = 3,
|
||||||
|
RegisterName_X4 = 4,
|
||||||
|
RegisterName_X5 = 5,
|
||||||
|
RegisterName_X6 = 6,
|
||||||
|
RegisterName_X7 = 7,
|
||||||
|
RegisterName_X8 = 8,
|
||||||
|
RegisterName_X9 = 9,
|
||||||
|
RegisterName_X10 = 10,
|
||||||
|
RegisterName_X11 = 11,
|
||||||
|
RegisterName_X12 = 12,
|
||||||
|
RegisterName_X13 = 13,
|
||||||
|
RegisterName_X14 = 14,
|
||||||
|
RegisterName_X15 = 15,
|
||||||
|
RegisterName_X16 = 16,
|
||||||
|
RegisterName_X17 = 17,
|
||||||
|
RegisterName_X18 = 18,
|
||||||
|
RegisterName_X19 = 19,
|
||||||
|
RegisterName_X20 = 20,
|
||||||
|
RegisterName_X21 = 21,
|
||||||
|
RegisterName_X22 = 22,
|
||||||
|
RegisterName_X23 = 23,
|
||||||
|
RegisterName_X24 = 24,
|
||||||
|
RegisterName_X25 = 25,
|
||||||
|
RegisterName_X26 = 26,
|
||||||
|
RegisterName_X27 = 27,
|
||||||
|
RegisterName_X28 = 28,
|
||||||
|
RegisterName_FP = 29,
|
||||||
|
RegisterName_LR = 30,
|
||||||
|
|
||||||
|
RegisterName_SP = 31,
|
||||||
|
RegisterName_PC = 32,
|
||||||
|
|
||||||
|
RegisterName_GeneralPurposeCount,
|
||||||
|
|
||||||
|
RegisterName_PState = 33,
|
||||||
|
RegisterName_Afsr0 = 34,
|
||||||
|
RegisterName_Afsr1 = 35,
|
||||||
|
RegisterName_Esr = 36,
|
||||||
|
RegisterName_Far = 37,
|
||||||
|
|
||||||
|
RegisterName_Count,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CpuContext {
|
||||||
|
using RegisterType = u64;
|
||||||
|
static constexpr size_t MaxStackTraceDepth = 0x20;
|
||||||
|
|
||||||
|
static constexpr const char *RegisterNameStrings[RegisterName_Count] = {
|
||||||
|
u8"X0",
|
||||||
|
u8"X1",
|
||||||
|
u8"X2",
|
||||||
|
u8"X3",
|
||||||
|
u8"X4",
|
||||||
|
u8"X5",
|
||||||
|
u8"X6",
|
||||||
|
u8"X7",
|
||||||
|
u8"X8",
|
||||||
|
u8"X9",
|
||||||
|
u8"X10",
|
||||||
|
u8"X11",
|
||||||
|
u8"X12",
|
||||||
|
u8"X13",
|
||||||
|
u8"X14",
|
||||||
|
u8"X15",
|
||||||
|
u8"X16",
|
||||||
|
u8"X17",
|
||||||
|
u8"X18",
|
||||||
|
u8"X19",
|
||||||
|
u8"X20",
|
||||||
|
u8"X21",
|
||||||
|
u8"X22",
|
||||||
|
u8"X23",
|
||||||
|
u8"X24",
|
||||||
|
u8"X25",
|
||||||
|
u8"X26",
|
||||||
|
u8"X27",
|
||||||
|
u8"X28",
|
||||||
|
u8"FP",
|
||||||
|
u8"LR",
|
||||||
|
u8"SP",
|
||||||
|
u8"PC",
|
||||||
|
u8"PState",
|
||||||
|
u8"Afsr0",
|
||||||
|
u8"Afsr1",
|
||||||
|
u8"Esr",
|
||||||
|
u8"Far",
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Registers, exception context. N left names for these fields in fatal .rodata. */
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
union {
|
||||||
|
RegisterType x[RegisterName_GeneralPurposeCount];
|
||||||
|
struct {
|
||||||
|
RegisterType _x[RegisterName_FP];
|
||||||
|
RegisterType fp;
|
||||||
|
RegisterType lr;
|
||||||
|
RegisterType sp;
|
||||||
|
RegisterType pc;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
RegisterType pstate;
|
||||||
|
RegisterType afsr0;
|
||||||
|
RegisterType afsr1;
|
||||||
|
RegisterType esr;
|
||||||
|
RegisterType far;
|
||||||
|
};
|
||||||
|
RegisterType registers[RegisterName_Count];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Misc. */
|
||||||
|
RegisterType stack_trace[MaxStackTraceDepth];
|
||||||
|
RegisterType base_address;
|
||||||
|
RegisterType register_set_flags;
|
||||||
|
u32 stack_trace_size;
|
||||||
|
|
||||||
|
void ClearState() {
|
||||||
|
std::memset(this, 0, sizeof(*this));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetTitleIdForAtmosphere(ncm::TitleId title_id) {
|
||||||
|
/* Right now, we mux title ID in through afsr when creport. */
|
||||||
|
/* TODO: Better way to do this? */
|
||||||
|
this->afsr0 = static_cast<RegisterType>(title_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
ncm::TitleId GetTitleIdForAtmosphere() const {
|
||||||
|
return ncm::TitleId{this->afsr0};
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetRegisterValue(RegisterName name, RegisterType value) {
|
||||||
|
this->registers[name] = value;
|
||||||
|
this->register_set_flags |= (RegisterType(1) << name);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasRegisterValue(RegisterName name) const {
|
||||||
|
return this->register_set_flags & (RegisterType(1) << name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetBaseAddress(RegisterType base_addr) {
|
||||||
|
this->base_address = base_addr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace aarch32 {
|
||||||
|
|
||||||
|
enum RegisterName {
|
||||||
|
RegisterName_R0 = 0,
|
||||||
|
RegisterName_R1 = 1,
|
||||||
|
RegisterName_R2 = 2,
|
||||||
|
RegisterName_R3 = 3,
|
||||||
|
RegisterName_R4 = 4,
|
||||||
|
RegisterName_R5 = 5,
|
||||||
|
RegisterName_R6 = 6,
|
||||||
|
RegisterName_R7 = 7,
|
||||||
|
RegisterName_R8 = 8,
|
||||||
|
RegisterName_R9 = 9,
|
||||||
|
RegisterName_R10 = 10,
|
||||||
|
RegisterName_FP = 11,
|
||||||
|
RegisterName_IP = 12,
|
||||||
|
RegisterName_LR = 13,
|
||||||
|
RegisterName_SP = 14,
|
||||||
|
RegisterName_PC = 15,
|
||||||
|
|
||||||
|
RegisterName_GeneralPurposeCount,
|
||||||
|
|
||||||
|
RegisterName_PState = 16,
|
||||||
|
RegisterName_Afsr0 = 17,
|
||||||
|
RegisterName_Afsr1 = 18,
|
||||||
|
RegisterName_Esr = 29,
|
||||||
|
RegisterName_Far = 20,
|
||||||
|
|
||||||
|
RegisterName_Count,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CpuContext {
|
||||||
|
using RegisterType = u32;
|
||||||
|
static constexpr size_t MaxStackTraceDepth = 0x20;
|
||||||
|
|
||||||
|
static constexpr const char *RegisterNameStrings[RegisterName_Count] = {
|
||||||
|
u8"R0",
|
||||||
|
u8"R1",
|
||||||
|
u8"R2",
|
||||||
|
u8"R3",
|
||||||
|
u8"R4",
|
||||||
|
u8"R5",
|
||||||
|
u8"R6",
|
||||||
|
u8"R7",
|
||||||
|
u8"R8",
|
||||||
|
u8"R9",
|
||||||
|
u8"R10",
|
||||||
|
u8"FP",
|
||||||
|
u8"IP",
|
||||||
|
u8"LR",
|
||||||
|
u8"SP",
|
||||||
|
u8"PC",
|
||||||
|
u8"PState",
|
||||||
|
u8"Afsr0",
|
||||||
|
u8"Afsr1",
|
||||||
|
u8"Esr",
|
||||||
|
u8"Far",
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Registers, exception context. N left names for these fields in fatal .rodata. */
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
union {
|
||||||
|
RegisterType r[RegisterName_GeneralPurposeCount];
|
||||||
|
struct {
|
||||||
|
RegisterType _x[RegisterName_FP];
|
||||||
|
RegisterType fp;
|
||||||
|
RegisterType ip;
|
||||||
|
RegisterType lr;
|
||||||
|
RegisterType sp;
|
||||||
|
RegisterType pc;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
RegisterType pstate;
|
||||||
|
RegisterType afsr0;
|
||||||
|
RegisterType afsr1;
|
||||||
|
RegisterType esr;
|
||||||
|
RegisterType far;
|
||||||
|
};
|
||||||
|
RegisterType registers[RegisterName_Count];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Misc. Yes, stack_trace_size is really laid out differently than aarch64... */
|
||||||
|
RegisterType stack_trace[MaxStackTraceDepth];
|
||||||
|
u32 stack_trace_size;
|
||||||
|
RegisterType base_address;
|
||||||
|
RegisterType register_set_flags;
|
||||||
|
|
||||||
|
void ClearState() {
|
||||||
|
std::memset(this, 0, sizeof(*this));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetTitleIdForAtmosphere(ncm::TitleId title_id) {
|
||||||
|
/* Right now, we mux title ID in through afsr when creport. */
|
||||||
|
/* TODO: Better way to do this? */
|
||||||
|
this->afsr0 = static_cast<RegisterType>(static_cast<u64>(title_id) >> 0);
|
||||||
|
this->afsr1 = static_cast<RegisterType>(static_cast<u64>(title_id) >> 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
ncm::TitleId GetTitleIdForAtmosphere() const {
|
||||||
|
return ncm::TitleId{(static_cast<u64>(this->afsr1) << 32ul) | (static_cast<u64>(this->afsr0) << 0ul)};
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetRegisterValue(RegisterName name, RegisterType value) {
|
||||||
|
this->registers[name] = value;
|
||||||
|
this->register_set_flags |= (RegisterType(1) << name);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasRegisterValue(RegisterName name) const {
|
||||||
|
return this->register_set_flags & (RegisterType(1) << name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetBaseAddress(RegisterType base_addr) {
|
||||||
|
this->base_address = base_addr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CpuContext {
|
||||||
|
enum Architecture {
|
||||||
|
Architecture_Aarch64 = 0,
|
||||||
|
Architecture_Aarch32 = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
union {
|
||||||
|
aarch64::CpuContext aarch64_ctx;
|
||||||
|
aarch32::CpuContext aarch32_ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
Architecture architecture;
|
||||||
|
u32 type;
|
||||||
|
|
||||||
|
void ClearState() {
|
||||||
|
std::memset(this, 0, sizeof(*this));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(std::is_pod<aarch64::CpuContext>::value && sizeof(aarch64::CpuContext) == 0x248, "aarch64::CpuContext definition!");
|
||||||
|
static_assert(std::is_pod<aarch32::CpuContext>::value && sizeof(aarch32::CpuContext) == 0xE0, "aarch32::CpuContext definition!");
|
||||||
|
static_assert(std::is_pod<CpuContext>::value && sizeof(CpuContext) == 0x250, "CpuContext definition!");
|
||||||
|
|
||||||
|
namespace srv {
|
||||||
|
|
||||||
|
struct ThrowContext {
|
||||||
|
u32 error_code;
|
||||||
|
ncm::TitleId title_id;
|
||||||
|
char proc_name[0xD];
|
||||||
|
bool is_creport;
|
||||||
|
CpuContext cpu_ctx;
|
||||||
|
bool generate_error_report;
|
||||||
|
Event erpt_event;
|
||||||
|
Event battery_event;
|
||||||
|
size_t stack_dump_size;
|
||||||
|
u8 stack_dump[0x100];
|
||||||
|
|
||||||
|
void ClearState() {
|
||||||
|
std::memset(this, 0, sizeof(*this));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,6 +23,7 @@
|
||||||
#include "results/creport_results.hpp"
|
#include "results/creport_results.hpp"
|
||||||
#include "results/debug_results.hpp"
|
#include "results/debug_results.hpp"
|
||||||
#include "results/dmnt_results.hpp"
|
#include "results/dmnt_results.hpp"
|
||||||
|
#include "results/err_results.hpp"
|
||||||
#include "results/fatal_results.hpp"
|
#include "results/fatal_results.hpp"
|
||||||
#include "results/fs_results.hpp"
|
#include "results/fs_results.hpp"
|
||||||
#include "results/hipc_results.hpp"
|
#include "results/hipc_results.hpp"
|
||||||
|
|
|
@ -16,18 +16,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
class PrivateService final : public IServiceObject {
|
static constexpr u32 Module_Err = 162;
|
||||||
private:
|
|
||||||
enum class CommandId {
|
static constexpr Result ResultErrApplicationAborted = MAKERESULT(Module_Err, 1);
|
||||||
GetFatalEvent = 0,
|
static constexpr Result ResultErrSystemModuleAborted = MAKERESULT(Module_Err, 2);
|
||||||
};
|
|
||||||
private:
|
|
||||||
/* Actual commands. */
|
|
||||||
Result GetFatalEvent(Out<CopiedHandle> out_h);
|
|
||||||
public:
|
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
|
||||||
MAKE_SERVICE_COMMAND_META(PrivateService, GetFatalEvent),
|
|
||||||
};
|
|
||||||
};
|
|
Loading…
Reference in a new issue