Compare commits

..

8 Commits

Author SHA1 Message Date
yellows8
c9dd93687b docs: Added uart_mitm. 2021-02-19 15:08:23 -05:00
yellows8
8a9ddc30e0 uart.mitm: Abort on memalloc failure and adjust stack size. 2021-02-18 19:25:59 -05:00
yellows8
4cb8034ac8 uart.mitm: Log when the data for SendLogData is too large. 2021-02-18 14:36:21 -05:00
yellows8
296fb31358 uart.mitm: Implemented btsnoop timestamp handling. 2021-02-18 14:15:53 -05:00
yellows8
1dd9d46415 uart.mitm: Implemented fs writing with a seperate thread. 2021-02-18 11:05:47 -05:00
Michael Scire
2b3c7fd104 uart.mitm: update for new sf semantics 2021-02-16 11:38:59 -05:00
yellows8
6e1b0abf1d uart.mitm: comments, etc. 2021-02-16 11:38:58 -05:00
yellows8
d7adef3810 Added uart.mitm. 2021-02-16 11:38:58 -05:00
150 changed files with 3911 additions and 17384 deletions

View File

@@ -52,6 +52,11 @@
; Controls whether dns.mitm logs to the sd card for debugging ; Controls whether dns.mitm logs to the sd card for debugging
; 0 = Disabled, 1 = Enabled ; 0 = Disabled, 1 = Enabled
; enable_dns_mitm_debug_log = u8!0x0 ; enable_dns_mitm_debug_log = u8!0x0
; Controls whether to enable uart mitm
; for logging bluetooth HCI to btsnoop captures.
; This is only implemented for [7.0.0+].
; 0 = Do not enable, 1 = Enable.
; enable_uart_mitm = u8!0x0
[hbloader] [hbloader]
; Controls the size of the homebrew heap when running as applet. ; Controls the size of the homebrew heap when running as applet.
; If set to zero, all available applet memory is used as heap. ; If set to zero, all available applet memory is used as heap.

View File

@@ -38,3 +38,14 @@ It does so in order to enable user configuration of system settings, which are p
dns_mitm enables intercepting requests to dns resolution services, to enable redirecting requests for specified hostnames. dns_mitm enables intercepting requests to dns resolution services, to enable redirecting requests for specified hostnames.
For documentation, see [here](../../features/dns_mitm.md). For documentation, see [here](../../features/dns_mitm.md).
## uart_mitm
`uart_mitm` intercepts the uart service used by bluetooth, on 7.0.0+ when enabled by [system_settings.ini](../../features/configurations.md). This allows logging bluetooth traffic.
Usage of bluetooth devices will be less responsive when this is enabled.
Logs are written to directory `/atmosphere/uart_logs/{PosixTime}_{TickTimestamp}_{ProgramId}`, which then contains the following:
+ `cmd_log` Text log for uart IPortSession commands, and any warning messages.
+ `btsnoop_hci.log` Bluetooth HCI log in the btsnoop format. This file is not accessible while it's the current log being used with HOS running.
4 directories are created for each system-boot. btsnoop logging is disabled for the first 3, with only the 4th enabled (enabled when a certain HCI vendor command is detected).

View File

@@ -35,10 +35,9 @@
static void package2_decrypt(package2_header_t *package2); static void package2_decrypt(package2_header_t *package2);
static size_t package2_get_src_section(void **section, package2_header_t *package2, unsigned int id); static size_t package2_get_src_section(void **section, package2_header_t *package2, unsigned int id);
static size_t package2_get_thermosphere(const void **thermosphere); static size_t package2_get_thermosphere(void **thermosphere);
static ini1_header_t *package2_rebuild_ini1(ini1_header_t *ini1, uint32_t target_firmware, void *emummc, size_t emummc_size); static ini1_header_t *package2_rebuild_ini1(ini1_header_t *ini1, uint32_t target_firmware, void *emummc, size_t emummc_size);
static void package2_append_section(unsigned int id, package2_header_t *package2, const void *data, size_t size); static void package2_append_section(unsigned int id, package2_header_t *package2, void *data, size_t size);
static void package2_fixup_thermosphere_and_entrypoint(package2_header_t *package2);
static void package2_fixup_header_and_section_hashes(package2_header_t *package2, size_t size); static void package2_fixup_header_and_section_hashes(package2_header_t *package2, size_t size);
static inline size_t align_to_4(size_t s) { static inline size_t align_to_4(size_t s) {
@@ -51,7 +50,7 @@ void package2_rebuild_and_copy(package2_header_t *package2, uint32_t target_firm
void *kernel; void *kernel;
size_t kernel_size; size_t kernel_size;
bool is_sd_kernel = false; bool is_sd_kernel = false;
const void *thermosphere; void *thermosphere;
size_t thermosphere_size; size_t thermosphere_size;
ini1_header_t *orig_ini1, *rebuilt_ini1; ini1_header_t *orig_ini1, *rebuilt_ini1;
@@ -68,8 +67,6 @@ void package2_rebuild_and_copy(package2_header_t *package2, uint32_t target_firm
fatal_error(u8"Error: Package2 has no unused section for Thermosphère!\n"); fatal_error(u8"Error: Package2 has no unused section for Thermosphère!\n");
} }
package2->metadata.section_offsets[PACKAGE2_SECTION_UNUSED] = 0; /* base of DRAM */
/* Load Kernel from SD, if possible. */ /* Load Kernel from SD, if possible. */
{ {
size_t sd_kernel_size = get_file_size("atmosphere/kernel.bin"); size_t sd_kernel_size = get_file_size("atmosphere/kernel.bin");
@@ -146,9 +143,6 @@ void package2_rebuild_and_copy(package2_header_t *package2, uint32_t target_firm
package2_append_section(PACKAGE2_SECTION_INI1, rebuilt_package2, rebuilt_ini1, rebuilt_ini1->size); package2_append_section(PACKAGE2_SECTION_INI1, rebuilt_package2, rebuilt_ini1, rebuilt_ini1->size);
package2_append_section(PACKAGE2_SECTION_UNUSED, rebuilt_package2, thermosphere, thermosphere_size); package2_append_section(PACKAGE2_SECTION_UNUSED, rebuilt_package2, thermosphere, thermosphere_size);
/* Swap entrypoint if Thermosphère is present */
package2_fixup_thermosphere_and_entrypoint(rebuilt_package2);
/* Fix all necessary data in the header to accomodate for the new patches. */ /* Fix all necessary data in the header to accomodate for the new patches. */
package2_fixup_header_and_section_hashes(rebuilt_package2, rebuilt_package2_size); package2_fixup_header_and_section_hashes(rebuilt_package2, rebuilt_package2_size);
@@ -333,9 +327,12 @@ static size_t package2_get_src_section(void **section, package2_header_t *packag
return package2->metadata.section_sizes[id]; return package2->metadata.section_sizes[id];
} }
static size_t package2_get_thermosphere(const void **thermosphere) { static size_t package2_get_thermosphere(void **thermosphere) {
(*thermosphere) = thermosphere_bin; /*extern const uint8_t thermosphere_bin[];
return thermosphere_bin_size; extern const uint32_t thermosphere_bin_size;*/
/* TODO: enable when tested. */
(*thermosphere) = NULL;
return 0;
} }
static ini1_header_t *package2_rebuild_ini1(ini1_header_t *ini1, uint32_t target_firmware, void *emummc, size_t emummc_size) { static ini1_header_t *package2_rebuild_ini1(ini1_header_t *ini1, uint32_t target_firmware, void *emummc, size_t emummc_size) {
@@ -356,7 +353,7 @@ static ini1_header_t *package2_rebuild_ini1(ini1_header_t *ini1, uint32_t target
return merged; return merged;
} }
static void package2_append_section(unsigned int id, package2_header_t *package2, const void *data, size_t size) { static void package2_append_section(unsigned int id, package2_header_t *package2, void *data, size_t size) {
/* This function must be called in ascending order of id. */ /* This function must be called in ascending order of id. */
/* We assume that the loading address doesn't need to be changed. */ /* We assume that the loading address doesn't need to be changed. */
uint8_t *dst = package2->data; uint8_t *dst = package2->data;
@@ -368,22 +365,6 @@ static void package2_append_section(unsigned int id, package2_header_t *package2
package2->metadata.section_sizes[id] = align_to_4(size); package2->metadata.section_sizes[id] = align_to_4(size);
} }
static void package2_fixup_thermosphere_and_entrypoint(package2_header_t *package2) {
/* Return if Thermosphère is not present */
if (package2->metadata.section_sizes[PACKAGE2_SECTION_UNUSED] == 0) {
return;
}
uint8_t *dst = package2->data;
for (unsigned int i = 0; i < PACKAGE2_SECTION_UNUSED; i++) {
dst += package2->metadata.section_sizes[i];
}
/* Swap kernel entrypoint with Thermosphère */
*(uint64_t *)(dst + 8) = DRAM_BASE_PHYSICAL + package2->metadata.entrypoint;
package2->metadata.entrypoint = 0;
}
static void package2_fixup_header_and_section_hashes(package2_header_t *package2, size_t size) { static void package2_fixup_header_and_section_hashes(package2_header_t *package2, size_t size) {
uint8_t *data = package2->data; uint8_t *data = package2->data;

View File

@@ -59,7 +59,6 @@
#define PACKAGE2_MINVER_1100_CURRENT 0x10 #define PACKAGE2_MINVER_1100_CURRENT 0x10
#define NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS ((void *)(0xA9800000ull)) #define NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS ((void *)(0xA9800000ull))
#define DRAM_BASE_PHYSICAL (0x80000000)
typedef struct { typedef struct {
union { union {

View File

@@ -109,6 +109,9 @@ namespace ams::impl {
AMS_DEFINE_SYSTEM_THREAD(21, settings, Main); AMS_DEFINE_SYSTEM_THREAD(21, settings, Main);
AMS_DEFINE_SYSTEM_THREAD(21, settings, IpcServer); AMS_DEFINE_SYSTEM_THREAD(21, settings, IpcServer);
/* Bus. */
AMS_DEFINE_SYSTEM_THREAD(-12, uart, IpcServer);
/* erpt. */ /* erpt. */
AMS_DEFINE_SYSTEM_THREAD(21, erpt, Main); AMS_DEFINE_SYSTEM_THREAD(21, erpt, Main);
AMS_DEFINE_SYSTEM_THREAD(21, erpt, IpcServer); AMS_DEFINE_SYSTEM_THREAD(21, erpt, IpcServer);

View File

@@ -24,6 +24,7 @@
#include "ns_mitm/nsmitm_module.hpp" #include "ns_mitm/nsmitm_module.hpp"
#include "dns_mitm/dnsmitm_module.hpp" #include "dns_mitm/dnsmitm_module.hpp"
#include "sysupdater/sysupdater_module.hpp" #include "sysupdater/sysupdater_module.hpp"
#include "uart_mitm/uartmitm_module.hpp"
namespace ams::mitm { namespace ams::mitm {
@@ -37,6 +38,7 @@ namespace ams::mitm {
ModuleId_NsMitm, ModuleId_NsMitm,
ModuleId_DnsMitm, ModuleId_DnsMitm,
ModuleId_Sysupdater, ModuleId_Sysupdater,
ModuleId_UartMitm,
ModuleId_Count, ModuleId_Count,
}; };
@@ -70,6 +72,7 @@ namespace ams::mitm {
GetModuleDefinition<ns::MitmModule>(), GetModuleDefinition<ns::MitmModule>(),
GetModuleDefinition<socket::resolver::MitmModule>(), GetModuleDefinition<socket::resolver::MitmModule>(),
GetModuleDefinition<sysupdater::MitmModule>(), GetModuleDefinition<sysupdater::MitmModule>(),
GetModuleDefinition<uart::MitmModule>(),
}; };
} }

View File

@@ -362,6 +362,12 @@ namespace ams::settings::fwdbg {
/* 0 = Disabled, 1 = Enabled */ /* 0 = Disabled, 1 = Enabled */
R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_dns_mitm_debug_log", "u8!0x0")); R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_dns_mitm_debug_log", "u8!0x0"));
/* Controls whether to enable uart mitm */
/* for logging bluetooth HCI to btsnoop captures. */
/* This is only implemented for [7.0.0+]. */
/* 0 = Do not enable, 1 = Enable. */
R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_uart_mitm", "u8!0x0"));
/* Hbloader custom settings. */ /* Hbloader custom settings. */
/* Controls the size of the homebrew heap when running as applet. */ /* Controls the size of the homebrew heap when running as applet. */

View File

@@ -0,0 +1,257 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "uart_mitm_logger.hpp"
#include "../amsmitm_fs_utils.hpp"
namespace ams::mitm::uart {
alignas(os::ThreadStackAlignment) u8 g_logger_stack[0x1000];
std::shared_ptr<UartLogger> g_logger;
UartLogger::UartLogger() : m_request_event(os::EventClearMode_ManualClear), m_finish_event(os::EventClearMode_ManualClear), m_client_queue(m_client_queue_list, this->QueueSize), m_thread_queue(m_thread_queue_list, this->QueueSize) {
for (size_t i=0; i<this->QueueSize; i++) {
UartLogMessage *msg = &this->m_queue_list_msgs[i];
std::memset(msg, 0, sizeof(UartLogMessage));
msg->data = static_cast<u8 *>(std::malloc(this->QueueBufferSize));
AMS_ABORT_UNLESS(msg->data != nullptr);
std::memset(msg->data, 0, this->QueueBufferSize);
this->m_client_queue.Send(reinterpret_cast<uintptr_t>(msg));
}
/* Create and start the logger thread. */
R_ABORT_UNLESS(os::CreateThread(std::addressof(this->m_thread), this->ThreadEntry, this, g_logger_stack, sizeof(g_logger_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(uart, IpcServer) - 2));
os::StartThread(std::addressof(this->m_thread));
}
UartLogger::~UartLogger() {
/* Tell the logger thread to exit. */
UartLogMessage *msg=nullptr;
this->m_client_queue.Receive(reinterpret_cast<uintptr_t *>(&msg));
msg->type = 0;
this->m_finish_event.Clear();
this->m_thread_queue.Send(reinterpret_cast<uintptr_t>(msg));
this->m_request_event.Signal();
/* Wait on the logger thread, then destroy it. */
os::WaitThread(std::addressof(this->m_thread));
os::DestroyThread(std::addressof(this->m_thread));
for (size_t i=0; i<this->QueueSize; i++) {
UartLogMessage *msg = &this->m_queue_list_msgs[i];
std::free(msg->data);
msg->data = nullptr;
}
}
void UartLogger::ThreadFunction() {
bool exit_flag=false;
this->m_cache_count = 0;
this->m_cache_pos = 0;
std::memset(this->m_cache_list, 0, sizeof(this->m_cache_list));
std::memset(this->m_cache_buffer, 0, sizeof(this->m_cache_buffer));
while (!exit_flag) {
this->m_request_event.Wait();
/* Receive messages, process them, then Send them. */
UartLogMessage *msg=nullptr;
while (this->m_thread_queue.TryReceive(reinterpret_cast<uintptr_t *>(&msg))) {
if (msg->type==0) {
exit_flag = true;
}
else if (msg->type==1) {
this->WriteCache(msg);
}
else if (msg->type==2) {
this->WriteCmdLog(reinterpret_cast<const char*>(msg->data), reinterpret_cast<const char*>(&msg->data[std::strlen((const char*)msg->data)+1]), msg->file_pos);
}
else if (msg->type==3) {
this->FlushCache();
}
this->m_client_queue.Send(reinterpret_cast<uintptr_t>(msg));
}
this->m_request_event.Clear();
this->m_finish_event.Signal();
}
}
/* Wait for the thread to finish processing messages. */
void UartLogger::WaitFinished() {
/* Tell the thread to flush the cache. */
UartLogMessage *msg=nullptr;
this->m_client_queue.Receive(reinterpret_cast<uintptr_t *>(&msg));
msg->type = 3;
this->m_finish_event.Clear();
this->m_thread_queue.Send(reinterpret_cast<uintptr_t>(msg));
this->m_request_event.Signal();
/* Wait for processing to finish. */
m_finish_event.Wait();
}
/* Initialize the specified btsnoop log file. */
void UartLogger::InitializeDataLog(FsFile *f, size_t *datalog_pos) {
*datalog_pos = 0;
/* Setup the btsnoop header. */
struct {
char id[8];
u32 version;
u32 datalink_type;
} btsnoop_header = { .id = "btsnoop" };
u32 version = 1;
u32 datalink_type = 1002; /* HCI UART (H4) */
ams::util::StoreBigEndian(&btsnoop_header.version, version);
ams::util::StoreBigEndian(&btsnoop_header.datalink_type, datalink_type);
/* Write the btsnoop header to the datalog. */
this->WriteLog(f, datalog_pos, &btsnoop_header, sizeof(btsnoop_header));
}
/* Flush the cache into the file. */
void UartLogger::FlushCache() {
for (size_t i=0; i<this->m_cache_count; i++) {
UartLogMessage *cache_msg=&this->m_cache_list[i];
this->WriteLogPacket(cache_msg->datalog_file, cache_msg->file_pos, cache_msg->timestamp, cache_msg->dir, cache_msg->data, cache_msg->size);
}
this->m_cache_count = 0;
this->m_cache_pos = 0;
}
/* Write the specified message into the cache. */
/* dir: false = Send (host->controller), true = Receive (controller->host). */
void UartLogger::WriteCache(UartLogMessage *msg) {
if (this->m_cache_count >= this->CacheListSize || this->m_cache_pos + msg->size >= this->CacheBufferSize) {
this->FlushCache();
}
UartLogMessage *cache_msg=&this->m_cache_list[this->m_cache_count];
*cache_msg = *msg;
cache_msg->data = &this->m_cache_buffer[this->m_cache_pos];
std::memcpy(cache_msg->data, msg->data, msg->size);
this->m_cache_count++;
this->m_cache_pos+= msg->size;
}
/* Append the specified string to the text file. */
void UartLogger::WriteCmdLog(const char *path, const char *str, size_t *file_pos) {
Result rc=0;
FsFile file={};
size_t len = std::strlen(str);
rc = ams::mitm::fs::OpenAtmosphereSdFile(&file, path, FsOpenMode_Read | FsOpenMode_Write | FsOpenMode_Append);
if (R_SUCCEEDED(rc)) {
rc = fsFileWrite(&file, *file_pos, str, len, FsWriteOption_None);
}
if (R_SUCCEEDED(rc)) {
*file_pos += len;
}
fsFileClose(&file);
}
/* Append the specified data to the datalog file. */
void UartLogger::WriteLog(FsFile *f, size_t *datalog_pos, const void* buffer, size_t size) {
if (R_SUCCEEDED(fsFileWrite(f, *datalog_pos, buffer, size, FsWriteOption_None))) {
*datalog_pos += size;
}
}
/* Append the specified packet to the datalog via WriteLog. */
/* dir: false = Send (host->controller), true = Receive (controller->host). */
void UartLogger::WriteLogPacket(FsFile *f, size_t *datalog_pos, s64 timestamp, bool dir, const void* buffer, size_t size) {
struct {
u32 original_length;
u32 included_length;
u32 packet_flags;
u32 cumulative_drops;
s64 timestamp_microseconds;
} pkt_hdr = {};
u32 flags = 0;
if (dir) {
flags |= BIT(0);
}
ams::util::StoreBigEndian(&pkt_hdr.original_length, static_cast<u32>(size));
ams::util::StoreBigEndian(&pkt_hdr.included_length, static_cast<u32>(size));
ams::util::StoreBigEndian(&pkt_hdr.packet_flags, flags);
ams::util::StoreBigEndian(&pkt_hdr.timestamp_microseconds, timestamp);
this->WriteLog(f, datalog_pos, &pkt_hdr, sizeof(pkt_hdr));
this->WriteLog(f, datalog_pos, buffer, size);
}
/* Send the specified data to the Logger thread. */
/* dir: false = Send (host->controller), true = Receive (controller->host). */
bool UartLogger::SendLogData(FsFile *f, size_t *file_pos, s64 timestamp_base, s64 tick_base, bool dir, const void* buffer, size_t size) {
/* Ignore log data which is too large. */
if (size > this->QueueBufferSize) return false;
UartLogMessage *msg=nullptr;
this->m_client_queue.Receive(reinterpret_cast<uintptr_t *>(&msg));
AMS_ABORT_UNLESS(msg->data != nullptr);
/* Setup the msg and send it. */
msg->type = 1;
msg->dir = dir;
if (timestamp_base) {
msg->timestamp = (armTicksToNs(armGetSystemTick() - tick_base) / 1000) + timestamp_base;
}
else {
msg->timestamp = 0;
}
msg->datalog_file = f;
msg->file_pos = file_pos;
msg->size = size;
std::memcpy(msg->data, buffer, size);
this->m_finish_event.Clear();
this->m_thread_queue.Send(reinterpret_cast<uintptr_t>(msg));
this->m_request_event.Signal();
return true;
}
/* Send the specified text log to the Logger thread. */
void UartLogger::SendTextLogData(const char *path, size_t *file_pos, const char *str) {
/* Ignore log data which is too large. */
if (std::strlen(path)+1 + std::strlen(str)+1 > this->QueueBufferSize) return;
UartLogMessage *msg=nullptr;
this->m_client_queue.Receive(reinterpret_cast<uintptr_t *>(&msg));
AMS_ABORT_UNLESS(msg->data != nullptr);
/* Setup the msg and send it. */
msg->type = 2;
msg->file_pos = file_pos;
std::memcpy(msg->data, path, std::strlen(path)+1);
std::memcpy(&msg->data[std::strlen(path)+1], str, std::strlen(str)+1);
this->m_finish_event.Clear();
this->m_thread_queue.Send(reinterpret_cast<uintptr_t>(msg));
this->m_request_event.Signal();
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::mitm::uart {
struct UartLogMessage {
u8 type;
bool dir;
s64 timestamp;
FsFile *datalog_file;
size_t *file_pos;
size_t size;
u8 *data;
};
class UartLogger {
private:
ams::os::ThreadType m_thread;
os::Event m_request_event;
os::Event m_finish_event;
os::MessageQueue m_client_queue;
os::MessageQueue m_thread_queue;
static constexpr inline size_t QueueSize = 0x20;
static constexpr inline size_t QueueBufferSize = 0x400;
static constexpr inline size_t CacheListSize = 0x80;
static constexpr inline size_t CacheBufferSize = 0x1000;
uintptr_t m_client_queue_list[QueueSize];
uintptr_t m_thread_queue_list[QueueSize];
UartLogMessage m_queue_list_msgs[QueueSize];
size_t m_cache_count;
size_t m_cache_pos;
UartLogMessage m_cache_list[CacheListSize];
u8 m_cache_buffer[CacheBufferSize];
static void ThreadEntry(void *arg) { static_cast<UartLogger *>(arg)->ThreadFunction(); }
void ThreadFunction();
void FlushCache();
void WriteCache(UartLogMessage *msg);
void WriteCmdLog(const char *path, const char *str, size_t *file_pos);
void WriteLog(FsFile *f, size_t *datalog_pos, const void* buffer, size_t size);
void WriteLogPacket(FsFile *f, size_t *datalog_pos, s64 timestamp, bool dir, const void* buffer, size_t size);
public:
UartLogger();
~UartLogger();
void WaitFinished();
void InitializeDataLog(FsFile *f, size_t *datalog_pos);
bool SendLogData(FsFile *f, size_t *file_pos, s64 timestamp_base, s64 tick_base, bool dir, const void* buffer, size_t size);
void SendTextLogData(const char *path, size_t *file_pos, const char *str);
};
extern std::shared_ptr<UartLogger> g_logger;
}

View File

@@ -0,0 +1,322 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "uart_mitm_service.hpp"
#include "uart_mitm_logger.hpp"
#include "../amsmitm_fs_utils.hpp"
namespace ams::mitm::uart {
/* Helper functions. */
bool UartPortService::TryGetCurrentTimestamp(u64 *out) {
/* Clear output. */
*out = 0;
/* Check if we have time service. */
{
bool has_time_service = false;
if (R_FAILED(sm::HasService(&has_time_service, sm::ServiceName::Encode("time:s"))) || !has_time_service) {
return false;
}
}
/* Try to get the current time. */
{
sm::ScopedServiceHolder<timeInitialize, timeExit> time_holder;
return time_holder && R_SUCCEEDED(timeGetCurrentTime(TimeType_LocalSystemClock, out));
}
}
UartPortService::UartPortService(const sm::MitmProcessInfo &cl, std::unique_ptr<::UartPortSession> s) : m_client_info(cl), m_srv(std::move(s)) {
Result rc=0;
/* Get a timestamp. */
u64 timestamp0=0, timestamp1;
this->TryGetCurrentTimestamp(&timestamp0);
timestamp1 = armGetSystemTick();
/* Setup the btsnoop base timestamps. */
this->m_timestamp_base = timestamp0;
if (this->m_timestamp_base) {
this->m_timestamp_base = 0x00E03AB44A676000 + (this->m_timestamp_base - 946684800) * 1000000;
}
this->m_tick_base = timestamp1;
/* Setup/create the logging directory. */
std::snprintf(this->m_base_path, sizeof(this->m_base_path), "uart_logs/%011lu_%011lu_%016lx", timestamp0, timestamp1, static_cast<u64>(this->m_client_info.program_id));
ams::mitm::fs::CreateAtmosphereSdDirectory("uart_logs");
ams::mitm::fs::CreateAtmosphereSdDirectory(this->m_base_path);
/* Create/initialize the text cmd_log. */
char tmp_path[256];
std::snprintf(tmp_path, sizeof(tmp_path), "%s/%s", this->m_base_path, "cmd_log");
ams::mitm::fs::CreateAtmosphereSdFile(tmp_path, 0, 0);
this->m_cmdlog_pos = 0;
/* Initialize the Send cache-buffer. */
this->m_send_cache_buffer = static_cast<u8 *>(std::malloc(this->CacheBufferSize));
AMS_ABORT_UNLESS(this->m_send_cache_buffer != nullptr);
std::memset(this->m_send_cache_buffer, 0, this->CacheBufferSize);
this->m_send_cache_pos = 0;
/* Initialize the Receive cache-buffer. */
this->m_receive_cache_buffer = static_cast<u8 *>(std::malloc(this->CacheBufferSize));
AMS_ABORT_UNLESS(this->m_receive_cache_buffer != nullptr);
std::memset(this->m_receive_cache_buffer, 0, this->CacheBufferSize);
this->m_receive_cache_pos = 0;
/* Initialize the datalog. */
std::snprintf(tmp_path, sizeof(tmp_path), "%s/%s", this->m_base_path, "btsnoop_hci.log");
ams::mitm::fs::CreateAtmosphereSdFile(tmp_path, 0, 0);
rc = ams::mitm::fs::OpenAtmosphereSdFile(&this->m_datalog_file, tmp_path, FsOpenMode_Read | FsOpenMode_Write | FsOpenMode_Append);
/* Set datalog_ready to whether initialization was successful. */
this->m_datalog_ready = R_SUCCEEDED(rc);
if (this->m_datalog_ready) {
std::shared_ptr<UartLogger> logger = mitm::uart::g_logger;
logger->InitializeDataLog(&this->m_datalog_file, &this->m_datalog_pos);
}
/* This will be enabled by WriteUartData once a certain command is detected. */
/* If you want to log all HCI traffic during system-boot initialization, you can change this field to true. */
/* When changed to true, qlaunch will hang at a black-screen during system-boot, due to the bluetooth slowdown. */
this->m_data_logging_enabled = false;
}
/* Append the specified string to the text cmd_log file. */
void UartPortService::WriteCmdLog(const char *str) {
char tmp_path[256];
std::snprintf(tmp_path, sizeof(tmp_path), "%s/%s", this->m_base_path, "cmd_log");
std::shared_ptr<UartLogger> logger = mitm::uart::g_logger;
logger->SendTextLogData(tmp_path, &this->m_cmdlog_pos, str);
}
/* Log data from Send/Receive. */
/* dir: false = Send (host->controller), true = Receive (controller->host). */
void UartPortService::WriteUartData(bool dir, const void* buffer, size_t size) {
/* Select which cache buffer/pos to use via dir. */
u8 *cache_buffer = !dir ? this->m_send_cache_buffer : this->m_receive_cache_buffer;
size_t *cache_pos = !dir ? &this->m_send_cache_pos : &this->m_receive_cache_pos;
/* Verify that the input size is non-zero, and within cache buffer bounds. */
if (size && *cache_pos + size <= this->CacheBufferSize) {
struct {
u8 opcode[0x2];
u8 param_len;
} *hci_cmd = reinterpret_cast<decltype(hci_cmd)>(&cache_buffer[0x1]);
static_assert(sizeof(*hci_cmd) == 0x3);
struct {
u8 handle_flags[0x2];
u16 data_len;
} *hci_acl_data = reinterpret_cast<decltype(hci_acl_data)>(&cache_buffer[0x1]);
static_assert(sizeof(*hci_acl_data) == 0x4);
struct {
u8 handle_flags[0x2];
u8 data_len;
} *hci_sco_data = reinterpret_cast<decltype(hci_sco_data)>(&cache_buffer[0x1]);
static_assert(sizeof(*hci_sco_data) == 0x3);
struct {
u8 event_code;
u8 param_len;
} *hci_event = reinterpret_cast<decltype(hci_event)>(&cache_buffer[0x1]);
static_assert(sizeof(*hci_event) == 0x2);
struct {
u8 handle_flags[0x2];
u16 data_load_len : 14;
u8 rfu1 : 2;
} *hci_iso_data = reinterpret_cast<decltype(hci_iso_data)>(&cache_buffer[0x1]);
static_assert(sizeof(*hci_iso_data) == 0x4);
/* Copy the input data into the cache and update the pos. */
std::memcpy(&cache_buffer[*cache_pos], buffer, size);
(*cache_pos)+= size;
/* Process the packets in the cache. */
do {
size_t orig_pkt_len = 0x0;
size_t pkt_len = 0x1;
/* Determine which HCI packet this is, via the packet indicator. */
/* These are supported regardless of whether the official bluetooth-sysmodule supports it. */
if (cache_buffer[0] == 0x1) { /* HCI Command */
if (*cache_pos >= 0x1+sizeof(*hci_cmd)) {
orig_pkt_len = sizeof(*hci_cmd) + hci_cmd->param_len;
/* Check for the first command used in the port which is opened last by bluetooth-sysmodule. */
/* This is a vendor command. */
/* Once detected, data-logging will be enabled. */
if (!this->m_data_logging_enabled && hci_cmd->opcode[1] == 0xFC && hci_cmd->opcode[0] == 0x16) {
this->m_data_logging_enabled = true;
}
}
}
else if (cache_buffer[0] == 0x2) { /* HCI ACL Data */
if (*cache_pos >= 0x1+sizeof(*hci_acl_data)) {
orig_pkt_len = sizeof(*hci_acl_data) + hci_acl_data->data_len;
}
}
else if (cache_buffer[0] == 0x3) { /* HCI Synchronous Data (SCO) */
if (*cache_pos >= 0x1+sizeof(*hci_sco_data)) {
orig_pkt_len = sizeof(*hci_sco_data) + hci_sco_data->data_len;
}
}
else if (cache_buffer[0] == 0x4) { /* HCI Event */
if (*cache_pos >= 0x1+sizeof(*hci_event)) {
orig_pkt_len = sizeof(*hci_event) + hci_event->param_len;
}
}
else if (cache_buffer[0] == 0x5) { /* HCI ISO Data */
if (*cache_pos >= 0x1+sizeof(*hci_iso_data)) {
orig_pkt_len = sizeof(*hci_iso_data) + hci_iso_data->data_load_len;
}
}
else { /* Unknown HCI packet */
char str[256];
std::snprintf(str, sizeof(str), "WriteUartData(dir = %s): Unknown HCI packet indicator 0x%x, ignoring the packet and emptying the cache.\n", !dir ? "send" : "receive", cache_buffer[0]);
this->WriteCmdLog(str);
*cache_pos = 0;
}
/* If a full packet is available in the cache, update pkt_len. */
if (orig_pkt_len) {
if (*cache_pos >= 0x1+orig_pkt_len) {
pkt_len+= orig_pkt_len;
}
}
/* If a packet is available, log it and update the cache. */
if (pkt_len>0x1) {
/* Only write to the file if data-logging is enabled and initialized. */
if (this->m_data_logging_enabled && this->m_datalog_ready) {
std::shared_ptr<UartLogger> logger = mitm::uart::g_logger;
if (!logger->SendLogData(&this->m_datalog_file, &this->m_datalog_pos, this->m_timestamp_base, this->m_tick_base, dir, cache_buffer, pkt_len)) {
char str[256];
std::snprintf(str, sizeof(str), "WriteUartData(): SendLogData dropped packet with size = 0x%lx\n", pkt_len);
this->WriteCmdLog(str);
}
}
(*cache_pos)-= pkt_len;
if (*cache_pos) {
std::memmove(cache_buffer, &cache_buffer[pkt_len], *cache_pos);
}
}
/* Otherwise, exit the loop. */
else break;
} while(*cache_pos);
}
}
/* Forward OpenPort and write to the cmd_log. */
Result UartPortService::OpenPort(sf::Out<bool> out, u32 port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, sf::CopyHandle send_handle, sf::CopyHandle receive_handle, u64 send_buffer_length, u64 receive_buffer_length) {
Result rc = uartPortSessionOpenPortFwd(this->m_srv.get(), reinterpret_cast<bool *>(out.GetPointer()), port, baud_rate, flow_control_mode, device_variation, is_invert_tx, is_invert_rx, is_invert_rts, is_invert_cts, send_handle.GetValue(), receive_handle.GetValue(), send_buffer_length, receive_buffer_length);
svcCloseHandle(send_handle.GetValue());
svcCloseHandle(receive_handle.GetValue());
char str[256];
std::snprintf(str, sizeof(str), "OpenPort(port = 0x%x, baud_rate = %u, flow_control_mode = %u, device_variation = %u, is_invert_tx = %d, is_invert_rx = %d, is_invert_rts = %d, is_invert_cts = %d, send_buffer_length = 0x%lx, receive_buffer_length = 0x%lx): rc = 0x%x, out = %d\n", port, baud_rate, flow_control_mode, device_variation, is_invert_tx, is_invert_rx, is_invert_rts, is_invert_cts, send_buffer_length, receive_buffer_length, rc.GetValue(), out.GetValue());
this->WriteCmdLog(str);
return rc;
}
/* Forward OpenPortForDev and write to the cmd_log. */
Result UartPortService::OpenPortForDev(sf::Out<bool> out, u32 port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, sf::CopyHandle send_handle, sf::CopyHandle receive_handle, u64 send_buffer_length, u64 receive_buffer_length) {
Result rc = uartPortSessionOpenPortForDevFwd(this->m_srv.get(), reinterpret_cast<bool *>(out.GetPointer()), port, baud_rate, flow_control_mode, device_variation, is_invert_tx, is_invert_rx, is_invert_rts, is_invert_cts, send_handle.GetValue(), receive_handle.GetValue(), send_buffer_length, receive_buffer_length);
svcCloseHandle(send_handle.GetValue());
svcCloseHandle(receive_handle.GetValue());
char str[256];
std::snprintf(str, sizeof(str), "OpenPortForDev(port = 0x%x, baud_rate = %u, flow_control_mode = %u, device_variation = %u, is_invert_tx = %d, is_invert_rx = %d, is_invert_rts = %d, is_invert_cts = %d, send_buffer_length = 0x%lx, receive_buffer_length = 0x%lx): rc = 0x%x, out = %d\n", port, baud_rate, flow_control_mode, device_variation, is_invert_tx, is_invert_rx, is_invert_rts, is_invert_cts, send_buffer_length, receive_buffer_length, rc.GetValue(), out.GetValue());
this->WriteCmdLog(str);
return rc;
}
/* Forward GetWritableLength and write to the cmd_log. */
Result UartPortService::GetWritableLength(sf::Out<u64> out) {
Result rc = uartPortSessionGetWritableLength(this->m_srv.get(), reinterpret_cast<u64 *>(out.GetPointer()));
char str[256];
std::snprintf(str, sizeof(str), "GetWritableLength(): rc = 0x%x, out = 0x%lx\n", rc.GetValue(), out.GetValue());
this->WriteCmdLog(str);
return rc;
}
/* Forward Send and log the data if the out_size is non-zero. */
Result UartPortService::Send(sf::Out<u64> out_size, const sf::InAutoSelectBuffer &data) {
Result rc = uartPortSessionSend(this->m_srv.get(), data.GetPointer(), data.GetSize(), reinterpret_cast<u64 *>(out_size.GetPointer()));
if (R_SUCCEEDED(rc) && out_size.GetValue()) {
this->WriteUartData(false, data.GetPointer(), out_size.GetValue());
}
return rc;
}
/* Forward GetReadableLength and write to the cmd_log. */
Result UartPortService::GetReadableLength(sf::Out<u64> out) {
Result rc = uartPortSessionGetReadableLength(this->m_srv.get(), reinterpret_cast<u64 *>(out.GetPointer()));
char str[256];
std::snprintf(str, sizeof(str), "GetReadableLength(): rc = 0x%x, out = 0x%lx\n", rc.GetValue(), out.GetValue());
this->WriteCmdLog(str);
return rc;
}
/* Forward Receive and log the data if the out_size is non-zero. */
Result UartPortService::Receive(sf::Out<u64> out_size, const sf::OutAutoSelectBuffer &data) {
Result rc = uartPortSessionReceive(this->m_srv.get(), data.GetPointer(), data.GetSize(), reinterpret_cast<u64 *>(out_size.GetPointer()));
if (R_SUCCEEDED(rc) && out_size.GetValue()) {
this->WriteUartData(true, data.GetPointer(), out_size.GetValue());
}
return rc;
}
/* Forward BindPortEvent and write to the cmd_log. */
Result UartPortService::BindPortEvent(sf::Out<bool> out, sf::OutCopyHandle out_event_handle, UartPortEventType port_event_type, s64 threshold) {
Result rc = uartPortSessionBindPortEventFwd(this->m_srv.get(), port_event_type, threshold, reinterpret_cast<bool *>(out.GetPointer()), out_event_handle.GetHandlePointer());
char str[256];
std::snprintf(str, sizeof(str), "BindPortEvent(port_event_type = 0x%x, threshold = 0x%lx): rc = 0x%x, out = %d\n", port_event_type, threshold, rc.GetValue(), out.GetValue());
this->WriteCmdLog(str);
return rc;
}
/* Forward UnbindPortEvent and write to the cmd_log. */
Result UartPortService::UnbindPortEvent(sf::Out<bool> out, UartPortEventType port_event_type) {
Result rc = uartPortSessionUnbindPortEvent(this->m_srv.get(), port_event_type, reinterpret_cast<bool *>(out.GetPointer()));
char str[256];
std::snprintf(str, sizeof(str), "UnbindPortEvent(port_event_type = 0x%x): rc = 0x%x, out = %d\n", port_event_type, rc.GetValue(), out.GetValue());
this->WriteCmdLog(str);
return rc;
}
Result UartMitmService::CreatePortSession(sf::Out<sf::SharedPointer<impl::IPortSession>> out) {
/* Open a port interface. */
UartPortSession port;
R_TRY(uartCreatePortSessionFwd(this->forward_service.get(), &port));
const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(&port.s)};
out.SetValue(sf::CreateSharedObjectEmplaced<impl::IPortSession, UartPortService>(this->client_info, std::make_unique<UartPortSession>(port)), target_object_id);
return ResultSuccess();
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "uart_mitm_logger.hpp"
#include "uart_shim.h"
#define AMS_UART_IPORTSESSION_MITM_INTERFACE_INFO(C, H) \
AMS_SF_METHOD_INFO(C, H, 0, Result, OpenPort, (sf::Out<bool> out, u32 port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, sf::CopyHandle send_handle, sf::CopyHandle receive_handle, u64 send_buffer_length, u64 receive_buffer_length), (out, port, baud_rate, flow_control_mode, device_variation, is_invert_tx, is_invert_rx, is_invert_rts, is_invert_cts, send_handle, receive_handle, send_buffer_length, receive_buffer_length)) \
AMS_SF_METHOD_INFO(C, H, 1, Result, OpenPortForDev, (sf::Out<bool> out, u32 port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, sf::CopyHandle send_handle, sf::CopyHandle receive_handle, u64 send_buffer_length, u64 receive_buffer_length), (out, port, baud_rate, flow_control_mode, device_variation, is_invert_tx, is_invert_rx, is_invert_rts, is_invert_cts, send_handle, receive_handle, send_buffer_length, receive_buffer_length)) \
AMS_SF_METHOD_INFO(C, H, 2, Result, GetWritableLength, (sf::Out<u64> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 3, Result, Send, (sf::Out<u64> out_size, const sf::InAutoSelectBuffer &data), (out_size, data)) \
AMS_SF_METHOD_INFO(C, H, 4, Result, GetReadableLength, (sf::Out<u64> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 5, Result, Receive, (sf::Out<u64> out_size, const sf::OutAutoSelectBuffer &data), (out_size, data)) \
AMS_SF_METHOD_INFO(C, H, 6, Result, BindPortEvent, (sf::Out<bool> out, sf::OutCopyHandle out_event_handle, UartPortEventType port_event_type, s64 threshold), (out, out_event_handle, port_event_type, threshold)) \
AMS_SF_METHOD_INFO(C, H, 7, Result, UnbindPortEvent, (sf::Out<bool> out, UartPortEventType port_event_type), (out, port_event_type))
AMS_SF_DEFINE_INTERFACE(ams::mitm::uart::impl, IPortSession, AMS_UART_IPORTSESSION_MITM_INTERFACE_INFO)
#define AMS_UART_MITM_INTERFACE_INFO(C, H) \
AMS_SF_METHOD_INFO(C, H, 6, Result, CreatePortSession, (sf::Out<sf::SharedPointer<::ams::mitm::uart::impl::IPortSession>> out), (out))
AMS_SF_DEFINE_MITM_INTERFACE(ams::mitm::uart::impl, IUartMitmInterface, AMS_UART_MITM_INTERFACE_INFO)
namespace ams::mitm::uart {
class UartPortService {
private:
sm::MitmProcessInfo m_client_info;
std::unique_ptr<::UartPortSession> m_srv;
static constexpr inline size_t CacheBufferSize = 0x1000;
s64 m_timestamp_base;
s64 m_tick_base;
char m_base_path[256];
size_t m_cmdlog_pos;
size_t m_datalog_pos;
bool m_datalog_ready;
bool m_data_logging_enabled;
FsFile m_datalog_file;
u8 *m_send_cache_buffer;
u8 *m_receive_cache_buffer;
size_t m_send_cache_pos;
size_t m_receive_cache_pos;
bool TryGetCurrentTimestamp(u64 *out);
void WriteCmdLog(const char *str);
void WriteUartData(bool dir, const void* buffer, size_t size);
public:
UartPortService(const sm::MitmProcessInfo &cl, std::unique_ptr<::UartPortSession> s);
virtual ~UartPortService() {
std::shared_ptr<UartLogger> logger = mitm::uart::g_logger;
logger->WaitFinished();
uartPortSessionClose(this->m_srv.get());
fsFileClose(&this->m_datalog_file);
std::free(this->m_send_cache_buffer);
std::free(this->m_receive_cache_buffer);
}
public:
/* Actual command API. */
Result OpenPort(sf::Out<bool> out, u32 port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, sf::CopyHandle send_handle, sf::CopyHandle receive_handle, u64 send_buffer_length, u64 receive_buffer_length);
Result OpenPortForDev(sf::Out<bool> out, u32 port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, sf::CopyHandle send_handle, sf::CopyHandle receive_handle, u64 send_buffer_length, u64 receive_buffer_length);
Result GetWritableLength(sf::Out<u64> out);
Result Send(sf::Out<u64> out_size, const sf::InAutoSelectBuffer &data);
Result GetReadableLength(sf::Out<u64> out);
Result Receive(sf::Out<u64> out_size, const sf::OutAutoSelectBuffer &data);
Result BindPortEvent(sf::Out<bool> out, sf::OutCopyHandle out_event_handle, UartPortEventType port_event_type, s64 threshold);
Result UnbindPortEvent(sf::Out<bool> out, UartPortEventType port_event_type);
};
static_assert(impl::IsIPortSession<UartPortService>);
class UartMitmService : public sf::MitmServiceImplBase {
public:
using MitmServiceImplBase::MitmServiceImplBase;
public:
static bool ShouldMitm(const sm::MitmProcessInfo &client_info) {
/* We will mitm:
* - bluetooth, for logging HCI.
*/
return client_info.program_id == ncm::SystemProgramId::Bluetooth;
}
public:
Result CreatePortSession(sf::Out<sf::SharedPointer<impl::IPortSession>> out);
};
static_assert(impl::IsIUartMitmInterface<UartMitmService>);
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2018-2020 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 <string.h>
#include <switch.h>
#include "uart_shim.h"
/* Command forwarders. */
Result uartCreatePortSessionFwd(Service* s, UartPortSession* out) {
return serviceDispatch(s, 6,
.out_num_objects = 1,
.out_objects = &out->s,
);
}
// [7.0.0+]
static Result _uartPortSessionOpenPortFwd(UartPortSession* s, bool *out, u32 port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, Handle send_handle, Handle receive_handle, u64 send_buffer_length, u64 receive_buffer_length, u32 cmd_id) {
const struct {
u8 is_invert_tx;
u8 is_invert_rx;
u8 is_invert_rts;
u8 is_invert_cts;
u32 port;
u32 baud_rate;
u32 flow_control_mode;
u32 device_variation;
u32 pad;
u64 send_buffer_length;
u64 receive_buffer_length;
} in = { is_invert_tx!=0, is_invert_rx!=0, is_invert_rts!=0, is_invert_cts!=0, port, baud_rate, flow_control_mode, device_variation, 0, send_buffer_length, receive_buffer_length };
u8 tmp=0;
Result rc = serviceDispatchInOut(&s->s, cmd_id, in, tmp,
.in_num_handles = 2,
.in_handles = { send_handle, receive_handle },
);
if (R_SUCCEEDED(rc) && out) *out = tmp & 1;
return rc;
}
Result uartPortSessionOpenPortFwd(UartPortSession* s, bool *out, u32 port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, Handle send_handle, Handle receive_handle, u64 send_buffer_length, u64 receive_buffer_length) {
return _uartPortSessionOpenPortFwd(s, out, port, baud_rate, flow_control_mode, device_variation, is_invert_tx, is_invert_rx, is_invert_rts, is_invert_cts, send_handle, receive_handle, send_buffer_length, receive_buffer_length, 0);
}
Result uartPortSessionOpenPortForDevFwd(UartPortSession* s, bool *out, u32 port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, Handle send_handle, Handle receive_handle, u64 send_buffer_length, u64 receive_buffer_length) {
return _uartPortSessionOpenPortFwd(s, out, port, baud_rate, flow_control_mode, device_variation, is_invert_tx, is_invert_rx, is_invert_rts, is_invert_cts, send_handle, receive_handle, send_buffer_length, receive_buffer_length, 1);
}
Result uartPortSessionBindPortEventFwd(UartPortSession* s, UartPortEventType port_event_type, s64 threshold, bool *out, Handle *handle_out) {
const struct {
u32 port_event_type;
u32 pad;
u64 threshold;
} in = { port_event_type, 0, threshold };
u8 tmp=0;
Result rc = serviceDispatchInOut(&s->s, 6, in, tmp,
.out_handle_attrs = { SfOutHandleAttr_HipcCopy },
.out_handles = handle_out,
);
if (R_SUCCEEDED(rc) && out) *out = tmp & 1;
return rc;
}

View File

@@ -0,0 +1,23 @@
/**
* @file uart_shim.h
* @brief UART IPC wrapper.
* @author yellows8
* @copyright libnx Authors
*/
#pragma once
#include <switch.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Command forwarders. */
Result uartCreatePortSessionFwd(Service* s, UartPortSession* out);
Result uartPortSessionOpenPortFwd(UartPortSession* s, bool *out, u32 port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, Handle send_handle, Handle receive_handle, u64 send_buffer_length, u64 receive_buffer_length);
Result uartPortSessionOpenPortForDevFwd(UartPortSession* s, bool *out, u32 port, u32 baud_rate, UartFlowControlMode flow_control_mode, u32 device_variation, bool is_invert_tx, bool is_invert_rx, bool is_invert_rts, bool is_invert_cts, Handle send_handle, Handle receive_handle, u64 send_buffer_length, u64 receive_buffer_length);
Result uartPortSessionBindPortEventFwd(UartPortSession* s, UartPortEventType port_event_type, s64 threshold, bool *out, Handle *handle_out);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,96 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "../amsmitm_initialization.hpp"
#include "uartmitm_module.hpp"
#include "uart_mitm_service.hpp"
#include "uart_mitm_logger.hpp"
namespace ams::mitm::uart {
namespace {
enum PortIndex {
PortIndex_Mitm,
PortIndex_Count,
};
constexpr sm::ServiceName UartMitmServiceName = sm::ServiceName::Encode("uart");
struct ServerOptions {
static constexpr size_t PointerBufferSize = 0x1000;
static constexpr size_t MaxDomains = 0;
static constexpr size_t MaxDomainObjects = 0;
};
constexpr size_t MaxServers = 1;
constexpr size_t MaxSessions = 10;
class ServerManager final : public sf::hipc::ServerManager<MaxServers, ServerOptions, MaxSessions> {
private:
virtual Result OnNeedsToAccept(int port_index, Server *server) override;
};
ServerManager g_server_manager;
Result ServerManager::OnNeedsToAccept(int port_index, Server *server) {
/* Acknowledge the mitm session. */
std::shared_ptr<::Service> fsrv;
sm::MitmProcessInfo client_info;
server->AcknowledgeMitmSession(std::addressof(fsrv), std::addressof(client_info));
switch (port_index) {
case PortIndex_Mitm:
return this->AcceptMitmImpl(server, sf::CreateSharedObjectEmplaced<impl::IUartMitmInterface, UartMitmService>(decltype(fsrv)(fsrv), client_info), fsrv);
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
bool ShouldMitmUart() {
u8 en = 0;
if (settings::fwdbg::GetSettingsItemValue(&en, sizeof(en), "atmosphere", "enable_uart_mitm") == sizeof(en)) {
return (en != 0);
}
return false;
}
}
void MitmModule::ThreadFunction(void *arg) {
/* The OpenPort cmds had the params changed with 6.x/7.x, so only support 7.x+. */
if (hos::GetVersion() < hos::Version_7_0_0) {
return;
}
/* Wait until initialization is complete. */
mitm::WaitInitialized();
/* Only use uart-mitm if enabled by the sys-setting. */
if (!ShouldMitmUart()) {
return;
}
/* Create mitm servers. */
R_ABORT_UNLESS((g_server_manager.RegisterMitmServer<UartMitmService>(PortIndex_Mitm, UartMitmServiceName)));
mitm::uart::g_logger = std::make_shared<UartLogger>();
/* Loop forever, servicing our services. */
g_server_manager.LoopProcess();
mitm::uart::g_logger.reset();
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019-2020 Atmosphère-NX * Copyright (c) 2018-2020 Atmosphère-NX
* *
* This program is free software; you can redistribute it and/or modify it * This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License, * under the terms and conditions of the GNU General Public License,
@@ -13,13 +13,12 @@
* 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/>.
*/ */
#pragma once #pragma once
#include <stratosphere.hpp>
#include "../amsmitm_module.hpp"
#include "../hvisor_exception_stack_frame.hpp" namespace ams::mitm::uart {
namespace ams::hvisor::traps { DEFINE_MITM_MODULE_CLASS(0x4000, AMS_GET_SYSTEM_THREAD_PRIORITY(uart, IpcServer) - 1);
void HandleHvc(ExceptionStackFrame *frame, cpu::ExceptionSyndromeRegister esr);
} }

1
thermosphere/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
out

View File

@@ -9,39 +9,13 @@ endif
TOPDIR ?= $(CURDIR) TOPDIR ?= $(CURDIR)
include $(DEVKITPRO)/devkitA64/base_rules include $(DEVKITPRO)/devkitA64/base_rules
export AMSLIBSDIR := $(TOPDIR)/../libraries
AMSBRANCH := $(shell git symbolic-ref --short HEAD) AMSBRANCH := $(shell git symbolic-ref --short HEAD)
AMSHASH = $(shell git rev-parse --short=16 HEAD)
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD) AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null))) ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
AMSREV := $(AMSREV)-dirty AMSREV := $(AMSREV)-dirty
endif endif
ifeq ($(PLATFORM), qemu)
export PLATFORM := qemu
PLATFORM_SOURCES := src/platform/qemu
PLATFORM_DEFINES := -DPLATFORM_QEMU -DMAX_CORE=4 -DMAX_BCR=6 -DMAX_WCR=4
else ifeq ($(PLATFORM), tegra-t210-arm-tf)
export PLATFORM := tegra-t210-arm-tf
PLATFORM_SOURCES := src/platform/tegra
PLATFORM_DEFINES := -DPLATFORM_TEGRA -DPLATFORM_TEGRA_T210_ARM_TF -DMAX_CORE=4 -DMAX_BCR=6 -DMAX_WCR=4
else
export PLATFORM := tegra-t210-nintendo
PLATFORM_SOURCES := src/platform/tegra
PLATFORM_DEFINES := -DPLATFORM_TEGRA -D DPLATFORM_TEGRA_T210_NINTENDO -DMAX_CORE=4 -DMAX_BCR=6 -DMAX_WCR=4
endif
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# TARGET is the name of the output # TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed # BUILD is the directory where object files & intermediate files will be placed
@@ -51,62 +25,43 @@ endif
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR)) TARGET := $(notdir $(CURDIR))
BUILD := build BUILD := build
SOURCES := src src/libc src/platform src/gdb $(PLATFORM_SOURCES) SOURCES := src src/lib
DATA := data DATA := data
INCLUDES := INCLUDES := include ../common/include
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# options for code generation # options for code generation
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# Note: -ffixed-x18 and -mgeneral-regs-only are very important and must be enabled ARCH := -march=armv8-a -mtune=cortex-a57
ARCH := -march=armv8-a -mtune=cortex-a57 -mgeneral-regs-only -ffixed-x18 -Wno-psabi DEFINES := -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
DEFINES := $(PLATFORM_DEFINES)
CFLAGS := \ CFLAGS := \
-g \ -g \
-fmacro-prefix-map=$(TOPDIR)/src/= \ -O2 \
-Os \
-ffunction-sections \ -ffunction-sections \
-fdata-sections \ -fdata-sections \
-mgeneral-regs-only \
-fomit-frame-pointer \ -fomit-frame-pointer \
-fno-asynchronous-unwind-tables \ -std=gnu11 \
-fno-unwind-tables \
-fno-stack-protector \
-fstrict-volatile-bitfields \
-Wall \
-Werror \ -Werror \
-Wall \
-Wno-main \ -Wno-main \
$(ARCH) $(DEFINES) $(ARCH) $(DEFINES)
export CXXWRAPS := -Wl,--wrap,__cxa_pure_virtual \ CFLAGS += $(INCLUDE) -D__CCPLEX__
-Wl,--wrap,__cxa_throw \
-Wl,--wrap,__cxa_rethrow \
-Wl,--wrap,__cxa_allocate_exception \
-Wl,--wrap,__cxa_free_exception \
-Wl,--wrap,__cxa_begin_catch \
-Wl,--wrap,__cxa_end_catch \
-Wl,--wrap,__cxa_call_unexpected \
-Wl,--wrap,__cxa_call_terminate \
-Wl,--wrap,__gxx_personality_v0 \
-Wl,--wrap,_Unwind_Resume \
-Wl,--wrap,_Unwind_Resume \
-Wl,--wrap,_ZSt19__throw_logic_errorPKc \
-Wl,--wrap,_ZSt20__throw_length_errorPKc \
-Wl,--wrap,_ZNSt11logic_errorC2EPKc
CFLAGS += $(INCLUDE) CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++2a
CFLAGS += -std=gnu11
ASFLAGS := -g $(ARCH) $(DEFINES) ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(TOPDIR)/linker.specs -nostartfiles -nostdlib -g $(ARCH) $(CXXWRAPS) -Wl,-Map,$(notdir $*.map) LDFLAGS = -specs=$(TOPDIR)/linker.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lgcc LIBS :=
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing # list of directories containing libraries, this must be the top level containing
# include and lib # include and lib
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
LIBDIRS := $(AMSLIBSDIR)/libvapours LIBDIRS :=
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
@@ -154,29 +109,11 @@ export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
.PHONY: $(BUILD) clean all qemu qemudbg .PHONY: $(BUILD) clean all
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
all: $(BUILD) all: $(BUILD)
ifeq ($(PLATFORM), qemu)
export QEMU := qemu-system-aarch64
#export QEMU := ~/qemu/aarch64-softmmu/qemu-system-aarch64
QEMUFLAGS := -nographic -machine virt,virtualization=on,accel=tcg,gic-version=2 -cpu cortex-a57 -smp 4 -m 1024\
-kernel thermosphere.elf -d unimp,guest_errors -semihosting-config enable,target=native\
-chardev socket,id=uart,port=2222,host=0.0.0.0,server,nowait -chardev stdio,id=test -serial chardev:uart\
-monitor tcp:localhost:3333,server,nowait
qemu: all
@$(QEMU) $(QEMUFLAGS)
qemudbg: all
@$(QEMU) $(QEMUFLAGS) -s -S
endif
$(BUILD): $(BUILD):
@[ -d $@ ] || mkdir -p $@ @[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
@@ -214,7 +151,7 @@ $(OFILES_SRC) : $(HFILES_BIN)
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data # you need a rule like this for each extension you use as binary data
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
%.bin.o %_bin.h: %.bin %.bin.o : %.bin
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
@echo $(notdir $<) @echo $(notdir $<)
@$(bin2o) @$(bin2o)

6
thermosphere/README.md Normal file
View File

@@ -0,0 +1,6 @@
Thermosphère
=====
![License](https://img.shields.io/badge/License-GPLv2-blue.svg)
Thermosphère is a hypervisor for the Nintendo Switch.

View File

@@ -1,209 +1,55 @@
OUTPUT_FORMAT("elf64-littleaarch64")
OUTPUT_ARCH(aarch64) OUTPUT_ARCH(aarch64)
ENTRY(_start) ENTRY(_start)
PHDRS
{
main PT_LOAD;
}
MEMORY
{
mainVa : ORIGIN = 0x7FFFE10000, LENGTH = 2M - 64K
}
SECTIONS SECTIONS
{ {
__start_pa__ = ABSOLUTE(ORIGIN(main)); . = 0x800D0000;
__temp_pa__ = ABSOLUTE(ORIGIN(temp));
__max_image_size__ = ABSOLUTE(LENGTH(main));
__max_temp_size__ = ABSOLUTE(LENGTH(temp) - 0x1000);
.text : . = ALIGN(4);
{ .text : {
. = ALIGN(8); PROVIDE(lds_thermo_start = .);
__start__ = ABSOLUTE(.); start.o (.text*)
KEEP(*(.crt0*)); *(.text*)
*(.text.unlikely .text.*_unlikely .text.unlikely.*) }
*(.text.exit .text.exit.*)
*(.text.startup .text.startup.*) . = ALIGN(8);
*(.text.hot .text.hot.*) .rodata : {
*(.text .stub .text.* .gnu.linkonce.t.*) *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))
. = ALIGN(0x800); }
__vectors_start__ = ABSOLUTE(.);
KEEP(*(.vectors*)); . = ALIGN(8);
__vectors_end__ = ABSOLUTE(.); .data : {
ASSERT(__vectors_end__ - __vectors_start__ <= 0x800, "Exception vectors section should be max 0x800 in size!"); *(.data*)
. = ALIGN(8); }
} >mainVa AT>main :main
/* Uninitialised data */
.init : . = ALIGN(8);
{ PROVIDE(lds_bss_start = .);
KEEP( *(.init) ) .bss (NOLOAD) : {
. = ALIGN(8); *(.bss*) . = ALIGN(8);
} >mainVa AT>main :main }
PROVIDE(lds_bss_end = .);
.plt :
{ /* EL2 stack */
*(.plt) . = ALIGN(16);
*(.iplt) . += 0x10000; /* 64 KiB stack */
. = ALIGN(8); el2_stack_end = .;
} >mainVa AT>main :main
/* Page align the end of binary */
. = ALIGN(512);
.fini : PROVIDE(lds_el2_thermo_end = .);
{
KEEP( *(.fini) ) /* EL1 stack */
. = ALIGN(8); . = ALIGN(16);
} >mainVa AT>main :main . += 0x10000; /* 64 KiB stack */
el1_stack_end = .;
.rodata :
{ lds_thermo_end = .;
*(.rodata .rodata.* .gnu.linkonce.r.*)
SORT(CONSTRUCTORS) /DISCARD/ : { *(.dynstr*) }
. = ALIGN(8); /DISCARD/ : { *(.dynamic*) }
} >mainVa AT>main :main /DISCARD/ : { *(.plt*) }
/DISCARD/ : { *(.interp*) }
.got : { __got_start__ = ABSOLUTE(.); *(.got) *(.igot) } >mainVa AT>main :main /DISCARD/ : { *(.gnu*) }
.got.plt : { *(.got.plt) *(.igot.plt) __got_end__ = ABSOLUTE(.);} >mainVa AT>main :main
.preinit_array :
{
. = ALIGN(8);
PROVIDE (__preinit_array_start = ABSOLUTE(.));
KEEP (*(.preinit_array))
PROVIDE (__preinit_array_end = ABSOLUTE(.));
ASSERT(__preinit_array_end == __preinit_array_start, ".preinit_array not empty!");
. = ALIGN(8);
} >mainVa AT>main :main
.init_array :
{
PROVIDE (__init_array_start = ABSOLUTE(.));
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
PROVIDE (__init_array_end = ABSOLUTE(.));
ASSERT(__init_array_end == __init_array_start, ".init_array not empty!");
} >mainVa AT>main :main
.fini_array :
{
. = ALIGN(8);
PROVIDE (__fini_array_start = ABSOLUTE(.));
KEEP (*(.fini_array))
KEEP (*(SORT(.fini_array.*)))
PROVIDE (__fini_array_end = ABSOLUTE(.));
. = ALIGN(8);
ASSERT(__fini_array_end == __fini_array_start, ".fini_array not empty!");
} >mainVa AT>main :main
.ctors :
{
. = ALIGN(8);
KEEP (*crtbegin.o(.ctors)) /* MUST be first -- GCC requires it */
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
. = ALIGN(8);
} >mainVa AT>main :main
.dtors ALIGN(8) :
{
. = ALIGN(8);
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
. = ALIGN(8);
} >mainVa AT>main :main
.data ALIGN(8) :
{
*(.data .data.* .gnu.linkonce.d.*)
CONSTRUCTORS
. = ALIGN(8);
} >mainVa AT>main :main
.dynamic : { *(.dynamic) } >mainVa AT>main :main
.interp : { *(.interp) } >mainVa AT>main :main
.note.gnu.build-id : { *(.note.gnu.build-id) } >mainVa AT>main :main
.hash : { *(.hash) } >mainVa AT>main :main
.gnu.hash : { *(.gnu.hash) } >mainVa AT>main :main
.gnu.version : { *(.gnu.version) } >mainVa AT>main :main
.gnu.version_d : { *(.gnu.version_d) } >mainVa AT>main :main
.gnu.version_r : { *(.gnu.version_r) } >mainVa AT>main :main
.dynsym : { *(.dynsym) } >mainVa AT>main :main
.dynstr : { *(.dynstr) } >mainVa AT>main :main
.rela.dyn : { *(.rela.*); __main_end__ = ABSOLUTE(.);} >mainVa AT>main :main
.bss (NOLOAD) :
{
__bss_start__ = ABSOLUTE(.);
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
} >mainVa :NONE
.tempbss (NOLOAD) :
{
. = ALIGN(0x1000);
__real_bss_end__ = ABSOLUTE(.);
__image_size__ = ABSOLUTE(__real_bss_end__ - __start__);
/*ASSERT(__image_size__ <= __max_image_size__, "Image too big!");*/
*(.tempbss .tempbss.*)
. = ALIGN(0x1000);
__bss_end__ = ABSOLUTE(.);
__temp_size__ = ABSOLUTE(__bss_end__ - __real_bss_end__);
ASSERT(__temp_size__ <= __max_temp_size__, "tempbss too big!");
} >mainVa :NONE
. = ALIGN(8);
/* Shit we keep in the elf but otherwise discard */
.eh_frame_hdr (NOLOAD) : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) } >mainVa :NONE
.eh_frame (NOLOAD) : { KEEP (*(.eh_frame)) *(.eh_frame.*) } >mainVa :NONE
.gcc_except_table (NOLOAD) : { *(.gcc_except_table .gcc_except_table.*) } >mainVa :NONE
.gnu_extab (NOLOAD) : { *(.gnu_extab*) } >mainVa :NONE
.exception_ranges (NOLOAD) : { *(.exception_ranges .exception_ranges*) } >mainVa :NONE
/* ==================
==== Metadata ====
================== */
/* Discard sections that difficult post-processing */
/DISCARD/ : { *(.group .comment .note) }
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
} }

View File

@@ -1,4 +1,7 @@
%rename link old_link %rename link old_link
*link: *link:
%(old_link) -T %:getenv(TOPDIR /%:getenv(PLATFORM .mem)) -T %:getenv(TOPDIR /linker.ld) -no-pie --nmagic --gc-sections %(old_link) -T %:getenv(TOPDIR /linker.ld) --nmagic --gc-sections
*startfile:
crti%O%s crtbegin%O%s

View File

@@ -1,6 +0,0 @@
MEMORY
{
NULL : ORIGIN = 0, LENGTH = 0x1000
main : ORIGIN = 0x60000000, LENGTH = 64M /* QEMU's memory map changes dynamically? */
temp : ORIGIN = 0x64000000, LENGTH = 64M
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright (c) 2018-2020 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 "defines.hpp"
extern "C" {
/* Redefine abort to trigger these handlers. */
void abort();
/* Redefine C++ exception handlers. Requires wrap linker flag. */
#define WRAP_ABORT_FUNC(func) void NORETURN __wrap_##func(void) { abort(); __builtin_unreachable(); }
WRAP_ABORT_FUNC(__cxa_pure_virtual)
WRAP_ABORT_FUNC(__cxa_throw)
WRAP_ABORT_FUNC(__cxa_rethrow)
WRAP_ABORT_FUNC(__cxa_allocate_exception)
WRAP_ABORT_FUNC(__cxa_free_exception)
WRAP_ABORT_FUNC(__cxa_begin_catch)
WRAP_ABORT_FUNC(__cxa_end_catch)
WRAP_ABORT_FUNC(__cxa_call_unexpected)
WRAP_ABORT_FUNC(__cxa_call_terminate)
WRAP_ABORT_FUNC(__gxx_personality_v0)
WRAP_ABORT_FUNC(_ZSt19__throw_logic_errorPKc)
WRAP_ABORT_FUNC(_ZSt20__throw_length_errorPKc)
WRAP_ABORT_FUNC(_ZNSt11logic_errorC2EPKc)
/* TODO: We may wish to consider intentionally not defining an _Unwind_Resume wrapper. */
/* This would mean that a failure to wrap all exception functions is a linker error. */
WRAP_ABORT_FUNC(_Unwind_Resume)
#undef WRAP_ABORT_FUNC
}
/* Custom abort handler, so that std::abort will trigger these. */
void abort()
{
#ifndef PLATFORM_QEMU
__builtin_trap();
#endif
for (;;);
}

View File

@@ -1,32 +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/>.
*/
#define EXCEP_STACK_FRAME_SIZE 0x140
.macro FUNCTION name
.section .text.\name, "ax", %progbits
.global \name
.type \name, %function
.func \name
.cfi_sections .debug_frame
.cfi_startproc
\name:
.endm
.macro END_FUNCTION
.cfi_endproc
.endfunc
.endm

View File

@@ -1,156 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_cpu_caches.hpp"
#define DEFINE_CACHE_RANGE_FUNC(isn, name, cache, post)\
void name(const void *addr, size_t size)\
{\
u32 lineCacheSize = GetSmallest##cache##CacheLineSize();\
uintptr_t begin = reinterpret_cast<uintptr_t>(addr) & ~(lineCacheSize - 1);\
uintptr_t end = (reinterpret_cast<uintptr_t>(addr) + size + lineCacheSize - 1) & ~(lineCacheSize - 1);\
for (uintptr_t pos = begin; pos < end; pos += lineCacheSize) {\
__asm__ __volatile__ (isn ", %0" :: "r"(pos) : "memory");\
}\
post;\
}
namespace {
ALWAYS_INLINE void SelectCacheLevel(bool instructionCache, u32 level)
{
u32 ibit = instructionCache ? 1 : 0;
u32 lbits = (level & 7) << 1;
THERMOSPHERE_SET_SYSREG(csselr_el1, lbits | ibit);
ams::hvisor::cpu::isb();
}
[[gnu::optimize("O2")]] ALWAYS_INLINE void InvalidateDataCacheLevel(u32 level)
{
SelectCacheLevel(false, level);
u32 ccsidr = static_cast<u32>(THERMOSPHERE_GET_SYSREG(ccsidr_el1));
u32 numWays = 1 + ((ccsidr >> 3) & 0x3FF);
u32 numSets = 1 + ((ccsidr >> 13) & 0x7FFF);
u32 wayShift = __builtin_clz(numWays);
u32 setShift = (ccsidr & 7) + 4;
u32 lbits = (level & 7) << 1;
for (u32 way = 0; way < numWays; way++) {
for (u32 set = 0; set < numSets; set++) {
u64 val = ((u64)way << wayShift) | ((u64)set << setShift) | lbits;
__asm__ __volatile__ ("dc isw, %0" :: "r"(val) : "memory");
}
}
}
ALWAYS_INLINE void CleanInvalidateDataCacheLevel(u32 level)
{
SelectCacheLevel(false, level);
u32 ccsidr = static_cast<u32>(THERMOSPHERE_GET_SYSREG(ccsidr_el1));
u32 numWays = 1 + ((ccsidr >> 3) & 0x3FF);
u32 numSets = 1 + ((ccsidr >> 13) & 0x7FFF);
u32 wayShift = __builtin_clz(numWays);
u32 setShift = (ccsidr & 7) + 4;
u32 lbits = (level & 7) << 1;
for (u32 way = 0; way < numWays; way++) {
for (u32 set = 0; set < numSets; set++) {
u64 val = ((u64)way << wayShift) | ((u64)set << setShift) | lbits;
__asm__ __volatile__ ("dc cisw, %0" :: "r"(val) : "memory");
}
}
}
[[gnu::optimize("O2")]] ALWAYS_INLINE void InvalidateDataCacheLevels(u32 from, u32 to)
{
// Let's hope it doesn't generate a stack frame...
for (u32 level = from; level < to; level++) {
InvalidateDataCacheLevel(level);
}
ams::hvisor::cpu::dsbSy();
ams::hvisor::cpu::isb();
}
}
namespace ams::hvisor::cpu {
DEFINE_CACHE_RANGE_FUNC("dc civac", CleanInvalidateDataCacheRange, Data, dsbSy())
DEFINE_CACHE_RANGE_FUNC("dc cvau", CleanDataCacheRangePoU, Data, dsb())
DEFINE_CACHE_RANGE_FUNC("ic ivau", InvalidateInstructionCacheRangePoU, Instruction, dsb(); isb())
void HandleSelfModifyingCodePoU(const void *addr, size_t size)
{
// See docs for ctr_el0.{dic, idc}. It's unclear when these bits have been added, but they're
// RES0 if not implemented, so that's fine
u32 ctr = static_cast<u32>(THERMOSPHERE_GET_SYSREG(ctr_el0));
if (!(ctr & BIT(28))) {
CleanDataCacheRangePoU(addr, size);
}
if (!(ctr & BIT(29))) {
InvalidateInstructionCacheRangePoU(addr, size);
} else {
// Make sure we have at least a dsb/isb
dsb();
isb();
}
}
[[gnu::optimize("O2")]] void ClearSharedDataCachesOnBoot(void)
{
u32 clidr = static_cast<u32>(THERMOSPHERE_GET_SYSREG(clidr_el1));
u32 louis = (clidr >> 21) & 7;
u32 loc = (clidr >> 24) & 7;
InvalidateDataCacheLevels(louis, loc);
}
[[gnu::optimize("O2")]] void ClearLocalDataCacheOnBoot(void)
{
u32 clidr = static_cast<u32>(THERMOSPHERE_GET_SYSREG(clidr_el1));
u32 louis = (clidr >> 21) & 7;
InvalidateDataCacheLevels(0, louis);
}
/* Ok so:
- cache set/way ops can't really be virtualized
- since we have only one guest OS & don't care about security (for space limitations),
we do the following:
- ignore all cache s/w ops applying before the Level Of Unification Inner Shareable (L1, typically).
These clearly break coherency and should only be done once, on power on/off/suspend/resume only. And we already
do it ourselves...
- allow ops after the LoUIS, but do it ourselves and ignore the next (numSets*numWay - 1) requests. This is because
we have to handle Nintendo's dodgy code (check if SetWay == 0)
- transform all s/w cache ops into clean and invalidate
*/
void HandleTrappedSetWayOperation(u32 val)
{
u32 clidr = static_cast<u32>(THERMOSPHERE_GET_SYSREG(clidr_el1));
u32 louis = (clidr >> 21) & 7;
u32 level = val >> 1 & 7;
u32 setway = val >> 3;
if (level < louis) {
return;
}
if (setway == 0) {
CleanInvalidateDataCacheLevel(level);
dsbSy();
isb();
}
}
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_cpu_instructions.hpp"
#include "hvisor_cpu_sysreg_general.hpp"
namespace ams::hvisor::cpu {
inline u32 GetInstructionCachePolicy(void)
{
u32 ctr = static_cast<u32>(THERMOSPHERE_GET_SYSREG(ctr_el0));
return (ctr >> 14) & 3;
}
inline u32 GetSmallestInstructionCacheLineSize(void)
{
u32 ctr = static_cast<u32>(THERMOSPHERE_GET_SYSREG(ctr_el0));
u32 shift = ctr & 0xF;
// "log2 of the number of words"...
return 4 << shift;
}
inline u32 GetSmallestDataCacheLineSize(void)
{
u32 ctr = static_cast<u32>(THERMOSPHERE_GET_SYSREG(ctr_el0));
u32 shift = (ctr >> 16) & 0xF;
// "log2 of the number of words"...
return 4 << shift;
}
ALWAYS_INLINE void InvalidateInstructionCache(void)
{
__asm__ __volatile__ ("ic ialluis" ::: "memory");
cpu::isb();
}
ALWAYS_INLINE void InvalidateInstructionCacheLocal(void)
{
__asm__ __volatile__ ("ic iallu" ::: "memory");
cpu::isb();
}
void CleanInvalidateDataCacheRange(const void *addr, size_t size);
void CleanDataCacheRangePoU(const void *addr, size_t size);
void InvalidateInstructionCacheRangePoU(const void *addr, size_t size);
void HandleSelfModifyingCodePoU(const void *addr, size_t size);
void ClearSharedDataCachesOnBoot(void);
void ClearLocalDataCacheOnBoot(void);
// Dunno where else to put that
void HandleTrappedSetWayOperation(u32 val);
}

View File

@@ -1,110 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "../defines.hpp"
namespace ams::hvisor::cpu {
// TODO GCC 10, use enum class.
// Would be nice if gcc didn't take 9+ years to fix a trivial bug ("too small to fit")
struct DebugRegisterPair {
// For breakpoints only
/// BT[3:1] or res0. BT[0]/WT[0] is "is linked"
enum BreakpointType : u32 {
AddressMatch = 0,
VheContextIdMatch = 1,
ContextIdMatch = 3,
VmidMatch = 4,
VmidContextIdMatch = 5,
VmidVheContextIdMatch = 6,
FullVheContextIdMatch = 7,
};
// Note: some SSC HMC PMC combinations are invalid
// Refer to "Table D2-9 Summary of breakpoint HMC, SSC, and PMC encodings"
/// Security State Control
enum SecurityStateControl : u32 {
Both = 0,
NonSecure = 1,
Secure = 2,
SecureIfLowerOrBoth = 3,
};
/// Higher Mode Control
enum HigherModeControl : u32 {
LowerEl = 0,
HigherEl = 1,
};
/// Privilege Mode Control (called PAC for watchpoints)
enum PrivilegeModeControl : u32 {
NeitherEl1Nor0 = 0,
El1 = 1,
El0 = 2,
El1And0 = 3,
};
// Watchpoints only
enum LoadStoreControl : u32 {
NotAWatchpoint = 0,
Load = 1,
Store = 2,
LoadStore = 3,
};
// bas only 4 bits for breakpoints, other bits res0.
// lsc, mask only for watchpoints, res0 for breakpoints
// bt only from breakpoints, res0 for watchpoints
struct ControlRegister {
union {
struct {
bool enabled : 1;
PrivilegeModeControl pmc : 2;
LoadStoreControl lsc : 2;
u32 bas : 8;
HigherModeControl hmc : 1;
SecurityStateControl ssc : 2;
u32 lbn : 4;
bool linked : 1;
BreakpointType bt : 3;
u32 mask : 5;
u64 res0 : 35;
};
u64 raw;
};
};
ControlRegister cr;
u64 vr;
constexpr void SetDefaults()
{
cr.linked = false;
// NS EL1&0 only
cr.hmc = LowerEl;
cr.ssc = NonSecure;
cr.pmc = El1And0;
}
};
static_assert(std::is_standard_layout_v<DebugRegisterPair>);
static_assert(std::is_trivial_v<DebugRegisterPair>);
}

View File

@@ -1,118 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "../defines.hpp"
#include "hvisor_cpu_sysreg_general.hpp"
namespace ams::hvisor::cpu {
// FIXME GCC 10
struct ExceptionSyndromeRegister {
enum ExceptionClass : u32 {
Uncategorized = 0x0,
WFxTrap = 0x1,
CP15RTTrap = 0x3,
CP15RRTTrap = 0x4,
CP14RTTrap = 0x5,
CP14DTTrap = 0x6,
AdvSIMDFPAccessTrap = 0x7,
FPIDTrap = 0x8,
PACTrap = 0x9,
CP14RRTTrap = 0xC,
BranchTargetException = 0xD, // No official enum field name from Arm yet
IllegalState = 0xE,
SupervisorCallA32 = 0x11,
HypervisorCallA32 = 0x12,
MonitorCallA32 = 0x13,
SupervisorCallA64 = 0x15,
HypervisorCallA64 = 0x16,
MonitorCallA64 = 0x17,
SystemRegisterTrap = 0x18,
SVEAccessTrap = 0x19,
ERetTrap = 0x1A,
El3_ImplementationDefined = 0x1F,
InstructionAbortLowerEl = 0x20,
InstructionAbortSameEl = 0x21,
PCAlignment = 0x22,
DataAbortLowerEl = 0x24,
DataAbortSameEl = 0x25,
SPAlignment = 0x26,
FPTrappedExceptionA32 = 0x28,
FPTrappedExceptionA64 = 0x2C,
SError = 0x2F,
BreakpointLowerEl = 0x30,
BreakpointSameEl = 0x31,
SoftwareStepLowerEl = 0x32,
SoftwareStepSameEl = 0x33,
WatchpointLowerEl = 0x34,
WatchpointSameEl = 0x35,
SoftwareBreakpointA32 = 0x38,
VectorCatchA32 = 0x3A,
SoftwareBreakpointA64 = 0x3C,
};
u32 iss : 25; // Instruction Specific Syndrome
u32 il : 1; // Instruction Length (16 or 32-bit)
ExceptionClass ec : 6; // Exception Class
u32 res0 : 32;
constexpr size_t GetInstructionLength()
{
return il == 0 ? 2 : 4;
}
};
struct DataAbortIss {
u32 dfsc : 6; // Fault status code
u32 wnr : 1; // Write, not Read
u32 s1ptw : 1; // Stage1 page table walk fault
u32 cm : 1; // Cache maintenance
u32 ea : 1; // External abort
u32 fnv : 1; // FAR not Valid
u32 set : 2; // Synchronous error type
u32 vncr : 1; // vncr_el2 trap
u32 ar : 1; // Acquire/release. Bit 14
u32 sf : 1; // 64-bit register used
u32 srt : 5; // Syndrome register transfer (register used)
u32 sse : 1; // Syndrome sign extend
u32 sas : 2; // Syndrome access size. Bit 23
u32 isv : 1; // Instruction syndrome valid (ISS[23:14] valid)
constexpr bool HasValidFar()
{
return isv && !fnv;
}
constexpr size_t GetAccessSize()
{
return BITL(sas);
}
};
static_assert(std::is_standard_layout_v<ExceptionSyndromeRegister>);
static_assert(std::is_standard_layout_v<DataAbortIss>);
static_assert(std::is_trivial_v<ExceptionSyndromeRegister>);
static_assert(std::is_trivial_v<DataAbortIss>);
}

View File

@@ -1,69 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "../defines.hpp"
#define _ASM_ARITHMETIC_UNARY_HELPER(sz, regalloc, op) ({\
u##sz res;\
__asm__ __volatile__ (STRINGIZE(op) " %" STRINGIZE(regalloc) "[res], %" STRINGIZE(regalloc) "[val]" : [res] "=r" (res) : [val] "r" (val));\
res;\
})
#define DECLARE_SINGLE_ASM_INSN2(name, what) ALWAYS_INLINE void name() { __asm__ __volatile__ (what ::: "memory"); }
#define DECLARE_SINGLE_ASM_INSN(name) ALWAYS_INLINE void name() { __asm__ __volatile__ (STRINGIZE(name) ::: "memory"); }
namespace ams::hvisor::cpu {
template<typename T>
ALWAYS_INLINE static T rbit(T val)
{
static_assert(std::is_integral_v<T> && (sizeof(T) == 8 || sizeof(T) == 4));
if constexpr (sizeof(T) == 8) {
return _ASM_ARITHMETIC_UNARY_HELPER(64, x, rbit);
} else {
return _ASM_ARITHMETIC_UNARY_HELPER(32, w, rbit);
}
}
DECLARE_SINGLE_ASM_INSN(wfi)
DECLARE_SINGLE_ASM_INSN(wfe)
DECLARE_SINGLE_ASM_INSN(sevl)
DECLARE_SINGLE_ASM_INSN(sev)
DECLARE_SINGLE_ASM_INSN2(dmb, "dmb ish")
DECLARE_SINGLE_ASM_INSN2(dmbSy, "dmb sy")
DECLARE_SINGLE_ASM_INSN2(dsb, "dsb ish")
DECLARE_SINGLE_ASM_INSN2(dsbSy, "dsb sy")
DECLARE_SINGLE_ASM_INSN(isb)
DECLARE_SINGLE_ASM_INSN2(TlbInvalidateEl2Local, "tlbi alle2")
DECLARE_SINGLE_ASM_INSN2(TlbInvalidateEl2, "tlbi alle2is")
DECLARE_SINGLE_ASM_INSN2(TlbInvalidateEl1, "tlbi vmalle1is")
DECLARE_SINGLE_ASM_INSN2(TlbInvalidateEl1Stage12, "tlbi alle1is")
DECLARE_SINGLE_ASM_INSN2(TlbInvalidateEl1Stage12Local, "tlbi alle1")
ALWAYS_INLINE void TlbInvalidateEl2Page(uintptr_t addr)
{
__asm__ __volatile__ ("tlbi vae2is, %0" :: "r"(addr) : "memory");
}
}
#undef DECLARE_SINGLE_ASM_INSN
#undef DECLARE_SINGLE_ASM_INSN2
#undef _ASM_ARITHMETIC_UNARY_HELPER

View File

@@ -1,54 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_cpu_sysreg_general.hpp"
namespace ams::hvisor::cpu {
ALWAYS_INLINE u64 MaskIrq()
{
u64 daif = THERMOSPHERE_GET_SYSREG(daif);
THERMOSPHERE_SET_SYSREG_IMM(daifset, BIT(1));
return daif;
}
ALWAYS_INLINE u64 UnmaskIrq()
{
u64 daif = THERMOSPHERE_GET_SYSREG(daif);
THERMOSPHERE_SET_SYSREG_IMM(daifclr, BIT(1));
return daif;
}
ALWAYS_INLINE void RestoreInterruptFlags(u64 flags)
{
THERMOSPHERE_SET_SYSREG(daif, flags);
}
class InterruptMaskGuard final {
NON_COPYABLE(InterruptMaskGuard);
NON_MOVEABLE(InterruptMaskGuard);
private:
u64 m_flags;
public:
ALWAYS_INLINE InterruptMaskGuard() : m_flags(MaskIrq()) {}
ALWAYS_INLINE ~InterruptMaskGuard()
{
RestoreInterruptFlags(m_flags);
}
};
}

View File

@@ -1,198 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_cpu_sysreg_general.hpp"
namespace ams::hvisor::cpu {
// Assumes addr is valid, must be called with interrupts masked
inline uintptr_t Va2Pa(const void *vaddrEl2) {
uintptr_t va = reinterpret_cast<uintptr_t>(vaddrEl2);
__asm__ __volatile__("at s1e2r, %0" :: "r"(va) : "memory");
return (THERMOSPHERE_GET_SYSREG(par_el1) & MASK2L(47, 12)) | (va & MASKL(12));
}
enum MmuPteType : u64 {
MMU_ENTRY_FAULT = 0,
MMU_ENTRY_BLOCK = 1,
MMU_ENTRY_TABLE = 3,
// L3 (this definition allows for recursive page tables)
MMU_ENTRY_PAGE = 3,
};
// Multi-byte attributes...
constexpr u64 MMU_ATTRINDX(u64 idx) { return (idx & 8) << 2; }
constexpr u64 MMU_MEMATTR(u64 attr) { return (attr & 0xF) << 2; }
constexpr u64 MMU_SH(u64 sh) { return (sh & 3) << 8; }
// Attributes. They are defined in a way that allows recursive page tables (assuming PBHA isn't used)
enum MmuPteAttributes : u64 {
// Stage 1 Table only, the rest is block/page only
MMU_NS_TABLE = BITL(62),
MMU_AP_TABLE = BITL(61),
MMU_XN_TABLE = BITL(60),
MMU_PXN_TABLE = BITL(59),
MMU_UXN = BITL(54), // EL1&0 only
MMU_PXN = BITL(53), // EL1&0 only
MMU_XN = MMU_UXN,
MMU_XN0 = MMU_PXN, // Armv8.2, stage 2 only
MMU_CONTIGUOUS = BITL(52),
MMU_DBM = BITL(51), // stage 1 only
MMU_GP = BITL(50), // undocumented
// ARMv8.4-TTRem only
MMU_NT = BITL(16),
// EL1&0 only
MMU_NG = BITL(11),
MMU_AF = BITL(10),
// SH[1:0]
MMU_NON_SHAREABLE = MMU_SH(0),
MMU_OUTER_SHAREABLE = MMU_SH(2),
MMU_INNER_SHAREABLE = MMU_SH(3),
// AP[2:1], stage 1 only. AP[0] does not exist.
MMU_AP_PRIV_RW = 0 << 6,
MMU_AP_RW = 1 << 6,
MMU_AP_PRIV_RO = 2 << 6,
MMU_AP_RO = 3 << 6,
// S2AP[1:0], stage 2 only
MMU_S2AP_NONE = 0 << 6,
MMU_S2AP_RO = 1 << 6,
MMU_S2AP_WO = 2 << 6,
MMU_S2AP_RW = 3 << 6,
// NS, stage 1 only
MMU_NS = BITL(5),
// See above...
// MemAttr[3:0], stage 2 only (convenience defs). When combining, strongest memory type applies
MMU_MEMATTR_DEVICE_NGNRE = MMU_MEMATTR(2),
MMU_MEMATTR_UNCHANGED = MMU_MEMATTR(0xF),
// Other useful defines for stage 2:
MMU_SAME_SHAREABILITY = MMU_NON_SHAREABLE,
};
template<u32 Level, u32 AddressSpaceSize, bool IsMmuEnabled = false, TranslationGranuleSize GranuleSize = TranslationGranule_4K>
class MmuTableBuilder final {
private:
static constexpr u32 tgBitSize = GetTranslationGranuleBitSize(GranuleSize);
// tgBitSize - 3 = log2(tg / sizeof(u64))
static constexpr u32 levelShift = tgBitSize + (tgBitSize - 3) * (3 - Level);
static constexpr u32 levelBitSize = std::min(AddressSpaceSize - levelShift, tgBitSize - 3);
static constexpr u64 levelMask = MASKL(levelBitSize);
static constexpr size_t ComputeIndex(uintptr_t va)
{
return (va >> levelShift) & levelMask;
}
private:
u64 *m_pageTable = nullptr;
public:
using NextLevelBuilder = MmuTableBuilder<Level + 1, AddressSpaceSize, IsMmuEnabled, GranuleSize>;
static_assert(Level <= 3, "Invalid translation table level");
static_assert(AddressSpaceSize <= 48);
static_assert(AddressSpaceSize > levelShift, "Address space size mismatch with translation level");
static constexpr size_t blockSize = BITL(levelShift);
static constexpr size_t tableSize = BITL(levelBitSize);
public:
constexpr MmuTableBuilder(u64 *pageTable = nullptr) : m_pageTable{pageTable} {}
constexpr MmuTableBuilder &InitializeTable()
{
std::memset(m_pageTable, 0, 8 * tableSize);
// Fails to optimize before GCC 10: std::fill_n(m_pageTable, tableSize, MMU_ENTRY_FAULT);
return *this;
}
// Precondition: va and pa bits in range
constexpr NextLevelBuilder MapTable(uintptr_t va, uintptr_t pa, u64 *table, u64 attribs = 0) const
{
static_assert(Level < 3, "Level 3 is the last level of translation");
m_pageTable[ComputeIndex(va)] = pa | attribs | MMU_ENTRY_TABLE;
return NextLevelBuilder{table};
}
NextLevelBuilder MapTable(uintptr_t va, u64 *table, u64 attribs = 0) const
{
if constexpr (IsMmuEnabled) {
return MapTable(va, Va2Pa(table), table, attribs);
} else {
return MapTable(va, reinterpret_cast<uintptr_t>(table), table, attribs);
}
}
constexpr MmuTableBuilder &Unmap(uintptr_t va)
{
m_pageTable[ComputeIndex(va)] = MMU_ENTRY_FAULT;
return *this;
}
// Precondition: guardSize == 0 if Level == 0
constexpr MmuTableBuilder &UnmapRange(uintptr_t va, size_t size, size_t guardSize = 0)
{
for (size_t off = 0, offVa = 0; off < size; off += blockSize, offVa += blockSize + guardSize) {
Unmap(va + offVa);
}
return *this;
}
// Precondition: va and pa bits in range
constexpr MmuTableBuilder &MapBlock(uintptr_t va, uintptr_t pa, u64 attribs)
{
static_assert(Level > 0, "Can only map L1 tables at L0");
constexpr u64 entryType = Level == 3 ? MMU_ENTRY_PAGE : MMU_ENTRY_BLOCK;
m_pageTable[ComputeIndex(va)] = pa | attribs | MMU_AF | entryType;
return *this;
}
constexpr MmuTableBuilder &MapBlock(uintptr_t pa, u64 attribs)
{
return MapBlock(pa, pa, attribs);
}
// Precondition: size and guardSize are multiples of blockSize
constexpr MmuTableBuilder &MapBlockRange(uintptr_t va, uintptr_t pa, size_t size, u64 attribs, size_t guardSize = 0)
{
for (size_t off = 0, offVa = 0; off < size; off += blockSize, offVa += blockSize + guardSize) {
MapBlock(va + offVa, pa + off, attribs);
UnmapRange(va + offVa + blockSize, guardSize, 0);
}
return *this;
}
constexpr MmuTableBuilder &MapBlockRange(uintptr_t pa, size_t size, u64 attribs)
{
return MapBlockRange(pa, pa, attribs, size, 0);
}
};
}

View File

@@ -1,495 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "../preprocessor.h"
#include "../defines.hpp"
#define THERMOSPHERE_GET_SYSREG(r) ({\
u64 __val; \
__asm__ __volatile__("mrs %0, " STRINGIZE(r) : "=r" (__val) :: "memory"); \
__val; \
})
#define THERMOSPHERE_SET_SYSREG(reg, val)\
do {\
u64 temp_reg = (val);\
__asm__ __volatile__ ("msr " STRINGIZE(reg) ", %0" :: "r"(temp_reg) : "memory");\
} while(false)
#define THERMOSPHERE_SET_SYSREG_IMM(reg, imm)\
do {\
__asm__ __volatile__ ("msr " STRINGIZE(reg) ", %0" :: "I"(imm) : "memory", "cc");\
} while(false)
namespace ams::hvisor::cpu {
using SysregEncoding = std::array<u8, 5>;
constexpr u32 EncodeSysregIss(SysregEncoding reg)
{
auto [op0, op1, crn, crm, op2] = reg;
return op0 << 20 | op2 << 17 | op1 << 14 | crn << 10 | crm << 1;
}
constexpr u32 MakeMsrFromEncoding(SysregEncoding reg, u32 Rt)
{
auto [op0, op1, crn, crm, op2] = reg;
u32 enc = op0 << 19 | op1 << 16 | crn << 12 | crm << 8 | op2 << 5;
return 0xD5000000u | enc | (Rt & 0x1Fu);
}
constexpr u32 MakeMrsFromEncoding(SysregEncoding reg, u32 Rt)
{
auto [op0, op1, crn, crm, op2] = reg;
u32 enc = op0 << 19 | op1 << 16 | crn << 12 | crm << 8 | op2 << 5;
return 0xD5200000u | enc | (Rt & 0x1Fu);
}
// The list mostly includes EL1 registers as these are the one we're trapping
constexpr SysregEncoding dbgbvrN_el1(u8 n) { return {2, 0, 0, n, 4}; }
constexpr SysregEncoding dbgbcrN_el1(u8 n) { return {2, 0, 0, n, 5}; }
constexpr SysregEncoding dbgwvrN_el1(u8 n) { return {2, 0, 0, n, 6}; }
constexpr SysregEncoding dbgwcrN_el1(u8 n) { return {2, 0, 0, n, 7}; }
constexpr inline SysregEncoding dc_isw = {1, 0, 7, 6, 2};
constexpr inline SysregEncoding dc_csw = {1, 0, 7, 10, 2};
constexpr inline SysregEncoding dc_cisw = {1, 0, 7, 14, 2};
constexpr inline SysregEncoding osdtrrx_el1 = {2, 0, 0, 0, 2};
constexpr inline SysregEncoding mdccint_el1 = {2, 0, 0, 2, 0};
constexpr inline SysregEncoding mdscr_el1 = {2, 0, 0, 2, 2};
constexpr inline SysregEncoding osdtrtx_el1 = {2, 0, 0, 3, 2};
constexpr inline SysregEncoding oseccr_el1 = {2, 0, 0, 6, 2};
constexpr inline SysregEncoding mdrar_el1 = {2, 0, 1, 0, 0};
constexpr inline SysregEncoding oslar_el1 = {2, 0, 1, 0, 4};
constexpr inline SysregEncoding oslsr_el1 = {2, 0, 1, 1, 4};
constexpr inline SysregEncoding osdlr_el1 = {2, 0, 1, 3, 4};
constexpr inline SysregEncoding dbgprcr_el1 = {2, 0, 1, 4, 4};
constexpr inline SysregEncoding dbgclaimset_el1 = {2, 0, 7, 8, 6};
constexpr inline SysregEncoding dbgclaimclr_el1 = {2, 0, 7, 9, 6};
constexpr inline SysregEncoding dbgauthstatus_el1 = {2, 0, 7, 14, 6};
constexpr inline SysregEncoding mdccsr_el0 = {2, 3, 0, 1, 0};
constexpr inline SysregEncoding dbgdtr_el0 = {2, 3, 0, 4, 0};
constexpr inline SysregEncoding dbgdtrrx_el0 = {2, 3, 0, 5, 0};
constexpr inline SysregEncoding dbgdtrtx_el0 = {2, 3, 0, 5, 0};
constexpr inline SysregEncoding dbgvcr32_el2 = {2, 4, 0, 7, 0};
constexpr inline SysregEncoding midr_el1 = {3, 0, 0, 0, 0};
constexpr inline SysregEncoding mpidr_el1 = {3, 0, 0, 0, 5};
constexpr inline SysregEncoding revidr_el1 = {3, 0, 0, 0, 6};
constexpr inline SysregEncoding id_pfr0_el1 = {3, 0, 0, 1, 0};
constexpr inline SysregEncoding id_pfr1_el1 = {3, 0, 0, 1, 1};
constexpr inline SysregEncoding id_dfr0_el1 = {3, 0, 0, 1, 2};
constexpr inline SysregEncoding id_afr0_el1 = {3, 0, 0, 1, 3};
constexpr inline SysregEncoding id_mmfr0_el1 = {3, 0, 0, 1, 4};
constexpr inline SysregEncoding id_mmfr1_el1 = {3, 0, 0, 1, 5};
constexpr inline SysregEncoding id_mmfr2_el1 = {3, 0, 0, 1, 6};
constexpr inline SysregEncoding id_mmfr3_el1 = {3, 0, 0, 1, 7};
constexpr inline SysregEncoding id_isar0_el1 = {3, 0, 0, 2, 0};
constexpr inline SysregEncoding id_isar1_el1 = {3, 0, 0, 2, 1};
constexpr inline SysregEncoding id_isar2_el1 = {3, 0, 0, 2, 2};
constexpr inline SysregEncoding id_isar3_el1 = {3, 0, 0, 2, 3};
constexpr inline SysregEncoding id_isar4_el1 = {3, 0, 0, 2, 4};
constexpr inline SysregEncoding id_isar5_el1 = {3, 0, 0, 2, 5};
constexpr inline SysregEncoding id_mmfr4_el1 = {3, 0, 0, 2, 6};
constexpr inline SysregEncoding mvfr0_el1 = {3, 0, 0, 3, 0};
constexpr inline SysregEncoding mvfr1_el1 = {3, 0, 0, 3, 1};
constexpr inline SysregEncoding mvfr2_el1 = {3, 0, 0, 3, 2};
constexpr inline SysregEncoding id_aa64pfr0_el1 = {3, 0, 0, 4, 0};
constexpr inline SysregEncoding id_aa64pfr1_el1 = {3, 0, 0, 4, 1};
constexpr inline SysregEncoding id_aa64zfr0_el1 = {3, 0, 0, 4, 4};
constexpr inline SysregEncoding id_aa64dfr0_el1 = {3, 0, 0, 5, 0};
constexpr inline SysregEncoding id_aa64dfr1_el1 = {3, 0, 0, 5, 1};
constexpr inline SysregEncoding id_aa64afr0_el1 = {3, 0, 0, 5, 4};
constexpr inline SysregEncoding id_aa64afr1_el1 = {3, 0, 0, 5, 5};
constexpr inline SysregEncoding id_aa64isar0_el1 = {3, 0, 0, 6, 0};
constexpr inline SysregEncoding id_aa64isar1_el1 = {3, 0, 0, 6, 1};
constexpr inline SysregEncoding id_aa64mmfr0_el1 = {3, 0, 0, 7, 0};
constexpr inline SysregEncoding id_aa64mmfr1_el1 = {3, 0, 0, 7, 1};
constexpr inline SysregEncoding id_aa64mmfr2_el1 = {3, 0, 0, 7, 2};
constexpr inline SysregEncoding sctlr_el1 = {3, 0, 1, 0, 0};
constexpr inline SysregEncoding actlr_el1 = {3, 0, 1, 0, 1};
constexpr inline SysregEncoding cpacr_el1 = {3, 0, 1, 0, 2};
constexpr inline SysregEncoding zcr_el1 = {3, 0, 1, 2, 0};
constexpr inline SysregEncoding ttbr0_el1 = {3, 0, 2, 0, 0};
constexpr inline SysregEncoding ttbr1_el1 = {3, 0, 2, 0, 1};
constexpr inline SysregEncoding tcr_el1 = {3, 0, 2, 0, 2};
constexpr inline SysregEncoding apiakeylo_el1 = {3, 0, 2, 1, 0};
constexpr inline SysregEncoding apiakeyhi_el1 = {3, 0, 2, 1, 1};
constexpr inline SysregEncoding apibkeylo_el1 = {3, 0, 2, 1, 2};
constexpr inline SysregEncoding apibkeyhi_el1 = {3, 0, 2, 1, 3};
constexpr inline SysregEncoding apdakeylo_el1 = {3, 0, 2, 2, 0};
constexpr inline SysregEncoding apdakeyhi_el1 = {3, 0, 2, 2, 1};
constexpr inline SysregEncoding apdbkeylo_el1 = {3, 0, 2, 2, 2};
constexpr inline SysregEncoding apdbkeyhi_el1 = {3, 0, 2, 2, 3};
constexpr inline SysregEncoding apgakeylo_el1 = {3, 0, 2, 3, 0};
constexpr inline SysregEncoding apgakeyhi_el1 = {3, 0, 2, 3, 1};
constexpr inline SysregEncoding afsr0_el1 = {3, 0, 5, 1, 0};
constexpr inline SysregEncoding afsr1_el1 = {3, 0, 5, 1, 1};
constexpr inline SysregEncoding esr_el1 = {3, 0, 5, 2, 0};
constexpr inline SysregEncoding erridr_el1 = {3, 0, 5, 3, 0};
constexpr inline SysregEncoding errselr_el1 = {3, 0, 5, 3, 1};
constexpr inline SysregEncoding erxfr_el1 = {3, 0, 5, 4, 0};
constexpr inline SysregEncoding erxctlr_el1 = {3, 0, 5, 4, 1};
constexpr inline SysregEncoding erxstatus_el1 = {3, 0, 5, 4, 2};
constexpr inline SysregEncoding erxaddr_el1 = {3, 0, 5, 4, 3};
constexpr inline SysregEncoding erxmisc0_el1 = {3, 0, 5, 5, 0};
constexpr inline SysregEncoding erxmisc1_el1 = {3, 0, 5, 5, 1};
constexpr inline SysregEncoding far_el1 = {3, 0, 6, 0, 0};
constexpr inline SysregEncoding par_el1 = {3, 0, 7, 4, 0};
constexpr inline SysregEncoding pmsidr_el1 = {3, 0, 9, 9, 7};
constexpr inline SysregEncoding pmbidr_el1 = {3, 0, 9, 10, 7};
constexpr inline SysregEncoding pmscr_el1 = {3, 0, 9, 9, 0};
constexpr inline SysregEncoding pmscr_el2 = {3, 4, 9, 9, 0};
constexpr inline SysregEncoding pmsicr_el1 = {3, 0, 9, 9, 2};
constexpr inline SysregEncoding pmsirr_el1 = {3, 0, 9, 9, 3};
constexpr inline SysregEncoding pmsfcr_el1 = {3, 0, 9, 9, 4};
constexpr inline SysregEncoding pmsevfr_el1 = {3, 0, 9, 9, 5};
constexpr inline SysregEncoding pmslatfr_el1 = {3, 0, 9, 9, 6};
constexpr inline SysregEncoding pmblimitr_el1 = {3, 0, 9, 10, 0};
constexpr inline SysregEncoding pmbptr_el1 = {3, 0, 9, 10, 1};
constexpr inline SysregEncoding pmbsr_el1 = {3, 0, 9, 10, 3};
constexpr inline SysregEncoding pmintenset_el1 = {3, 0, 9, 14, 1};
constexpr inline SysregEncoding pmintenclr_el1 = {3, 0, 9, 14, 2};
constexpr inline SysregEncoding mair_el1 = {3, 0, 10, 2, 0};
constexpr inline SysregEncoding amair_el1 = {3, 0, 10, 3, 0};
constexpr inline SysregEncoding lorsa_el1 = {3, 0, 10, 4, 0};
constexpr inline SysregEncoding lorea_el1 = {3, 0, 10, 4, 1};
constexpr inline SysregEncoding lorn_el1 = {3, 0, 10, 4, 2};
constexpr inline SysregEncoding lorc_el1 = {3, 0, 10, 4, 3};
constexpr inline SysregEncoding lorid_el1 = {3, 0, 10, 4, 7};
constexpr inline SysregEncoding vbar_el1 = {3, 0, 12, 0, 0};
constexpr inline SysregEncoding disr_el1 = {3, 0, 12, 1, 1};
constexpr inline SysregEncoding contextidr_el1 = {3, 0, 13, 0, 1};
constexpr inline SysregEncoding tpidr_el1 = {3, 0, 13, 0, 4};
constexpr inline SysregEncoding cntkctl_el1 = {3, 0, 14, 1, 0};
constexpr inline SysregEncoding ccsidr_el1 = {3, 1, 0, 0, 0};
constexpr inline SysregEncoding clidr_el1 = {3, 1, 0, 0, 1};
constexpr inline SysregEncoding aidr_el1 = {3, 1, 0, 0, 7};
constexpr inline SysregEncoding csselr_el1 = {3, 2, 0, 0, 0};
constexpr inline SysregEncoding ctr_el0 = {3, 3, 0, 0, 1};
constexpr inline SysregEncoding dczid_el0 = {3, 3, 0, 0, 7};
constexpr inline SysregEncoding pmcr_el0 = {3, 3, 9, 12, 0};
constexpr inline SysregEncoding pmcntenset_el0 = {3, 3, 9, 12, 1};
constexpr inline SysregEncoding pmcntenclr_el0 = {3, 3, 9, 12, 2};
constexpr inline SysregEncoding pmovsclr_el0 = {3, 3, 9, 12, 3};
constexpr inline SysregEncoding pmswinc_el0 = {3, 3, 9, 12, 4};
constexpr inline SysregEncoding pmselr_el0 = {3, 3, 9, 12, 5};
constexpr inline SysregEncoding pmceid0_el0 = {3, 3, 9, 12, 6};
constexpr inline SysregEncoding pmceid1_el0 = {3, 3, 9, 12, 7};
constexpr inline SysregEncoding pmccntr_el0 = {3, 3, 9, 13, 0};
constexpr inline SysregEncoding pmxevtyper_el0 = {3, 3, 9, 13, 1};
constexpr inline SysregEncoding pmxevcntr_el0 = {3, 3, 9, 13, 2};
constexpr inline SysregEncoding pmuserenr_el0 = {3, 3, 9, 14, 0};
constexpr inline SysregEncoding pmovsset_el0 = {3, 3, 9, 14, 3};
constexpr inline SysregEncoding tpidr_el0 = {3, 3, 13, 0, 2};
constexpr inline SysregEncoding tpidrro_el0 = {3, 3, 13, 0, 3};
constexpr inline SysregEncoding cntfrq_el0 = {3, 3, 14, 0, 0};
constexpr inline SysregEncoding cntpct_el0 = {3, 3, 14, 0, 1};
constexpr inline SysregEncoding cntvct_el0 = {3, 3, 14, 0, 2};
constexpr inline SysregEncoding cntp_tval_el0 = {3, 3, 14, 2, 0};
constexpr inline SysregEncoding cntp_ctl_el0 = {3, 3, 14, 2, 1};
constexpr inline SysregEncoding cntp_cval_el0 = {3, 3, 14, 2, 2};
constexpr inline SysregEncoding cntv_tval_el0 = {3, 3, 14, 3, 0};
constexpr inline SysregEncoding cntv_ctl_el0 = {3, 3, 14, 3, 1};
constexpr inline SysregEncoding cntv_cval_el0 = {3, 3, 14, 3, 2};
constexpr inline SysregEncoding cntvoff_el2 = {3, 4, 14, 0, 3};
constexpr inline SysregEncoding cnthctl_el2 = {3, 4, 14, 1, 0};
constexpr inline SysregEncoding cnthp_cval_el2 = {3, 4, 14, 2, 2};
constexpr inline SysregEncoding pmccfiltr_el0 = {3, 3, 14, 15, 7};
constexpr inline SysregEncoding zcr_el2 = {3, 4, 1, 2, 0};
constexpr inline SysregEncoding dacr32_el2 = {3, 4, 3, 0, 0};
constexpr inline SysregEncoding ifsr32_el2 = {3, 4, 5, 0, 1};
constexpr inline SysregEncoding vsesr_el2 = {3, 4, 5, 2, 3};
constexpr inline SysregEncoding fpexc32_el2 = {3, 4, 5, 3, 0};
constexpr inline SysregEncoding zcr_el12 = {3, 5, 1, 2, 0};
enum SctlrFlags {
SCTLR_ELx_DSSBS = BITL(44),
SCTLR_ELx_ENIA = BITL(31),
SCTLR_ELx_ENIB = BITL(30),
SCTLR_ELx_ENDA = BITL(27),
SCTLR_ELx_EE = BITL(25),
SCTLR_ELx_IESB = BITL(21),
SCTLR_ELx_WXN = BITL(19),
SCTLR_ELx_ENDB = BITL(13),
SCTLR_ELx_I = BITL(12),
SCTLR_ELx_SA = BITL(3),
SCTLR_ELx_C = BITL(2),
SCTLR_ELx_A = BITL(1),
SCTLR_ELx_M = BITL(0),
SCTLR_EL1_UCI = BITL(26),
SCTLR_EL1_E0E = BITL(24),
SCTLR_EL1_SPAN = BITL(23),
SCTLR_EL1_NTWE = BITL(18),
SCTLR_EL1_NTWI = BITL(16),
SCTLR_EL1_UCT = BITL(15),
SCTLR_EL1_DZE = BITL(14),
SCTLR_EL1_UMA = BITL(9),
SCTLR_EL1_SED = BITL(8),
SCTLR_EL1_ITD = BITL(7),
SCTLR_EL1_CP15BEN = BITL(5),
SCTLR_EL1_SA0 = BITL(4),
SCTLR_EL2_RES1 = util::CombineBits<u64>(29, 28, 23, 22, 18, 16, 11, 5, 4),
SCTLR_EL2_RES0 = (0xFFFFEFFFull << 32) | util::CombineBits<u64>(
31, 30, 27, 26, 24, 20, 17, 15, 14, 13, 10, 9, 8, 7, 6
),
SCTLR_EL1_RES1 = util::CombineBits<u64>(29, 28, 22, 20, 11),
SCTLR_EL1_RES0 = (0xFFFFEFFFull << 32) | util::CombineBits<u64>(31, 30, 27, 17, 13, 10, 6),
};
// HCR Flags
enum HcrFlags {
HCR_FWB = BITL(46),
HCR_API = BITL(41),
HCR_APK = BITL(40),
HCR_TEA = BITL(37),
HCR_TERR = BITL(36),
HCR_TLOR = BITL(35),
HCR_E2H = BITL(34),
HCR_ID = BITL(33),
HCR_CD = BITL(32),
HCR_RW = BITL(31),
HCR_TRVM = BITL(30),
HCR_HCD = BITL(29),
HCR_TDZ = BITL(28),
HCR_TGE = BITL(27),
HCR_TVM = BITL(26),
HCR_TTLB = BITL(25),
HCR_TPU = BITL(24),
HCR_TPC = BITL(23),
HCR_TSW = BITL(22),
HCR_TAC = BITL(21),
HCR_TIDCP = BITL(20),
HCR_TSC = BITL(19),
HCR_TID3 = BITL(18),
HCR_TID2 = BITL(17),
HCR_TID1 = BITL(16),
HCR_TID0 = BITL(15),
HCR_TWE = BITL(14),
HCR_TWI = BITL(13),
HCR_DC = BITL(12),
HCR_BSU = (3ul << 10),
HCR_BSU_IS = BITL(10),
HCR_FB = BITL(9),
HCR_VSE = BITL(8),
HCR_VI = BITL(7),
HCR_VF = BITL(6),
HCR_AMO = BITL(5),
HCR_IMO = BITL(4),
HCR_FMO = BITL(3),
HCR_PTW = BITL(2),
HCR_SWI = BITL(1),
HCR_VM = BITL(0),
};
// CPTR flags
enum CptrFlags {
CPTR_TCPAC = BITL(31),
CPTR_TAM = BITL(30),
CPTR_TTA = BITL(20),
CPTR_TFP = BITL(10),
CPTR_TZ = BITL(8), // (EL2)
CPTR_EZ = BITL(8), // (EL3)
CPTR_RES1 = 0x000032FFul,
};
// MDCR flags (EL2)
enum MdcrEl2Flags {
MDCR_EL2_TPMS = BITL(14),
MDCR_EL2_E2PB_MASK = 3ul,
MDCR_EL2_E2PB_SHIFT = 12,
MDCR_EL2_TDRA = BITL(11),
MDCR_EL2_TDOSA = BITL(10),
MDCR_EL2_TDA = BITL(9),
MDCR_EL2_TDE = BITL(8),
MDCR_EL2_HPME = BITL(7),
MDCR_EL2_TPM = BITL(6),
MDCR_EL2_TPMCR = BITL(5),
MDCR_EL2_HPMN_MASK = 0x1Ful,
};
// Some MDSCR flags
enum MdscrFlags {
MDSCR_MDE = BITL(15),
MDSCR_KDE = BITL(13),
MDSCR_SS = BITL(0),
};
// Some CNTHCTL flags + shifts
enum CnthctlFlags {
CNTHCTL_EVNTI_MASK = 0xFul,
CNTHCTL_EVNTI_SHIFT = 4,
CNTHCTL_EVNTDIR = BITL(3),
CNTHCTL_EVNTEN = BITL(2),
CNTHCTL_EL1PCEN = BITL(1),
CNTHCTL_EL1PCTEN = BITL(0),
};
// PAR_EL1 flags, shifts, masks
enum ParFlags {
PAR_F = BITL(0),
// Successful translation:
PAR_ATTR_SHIFT = 56,
PAR_ATTR_MASK = 0xFFul,
PAR_PA_MASK = MASK2L(51, 12),// bits 51-48 RES0 if not implemented
PAR_NS = BITL(9),
PAR_SH_SHIFT = 7,
PAR_SH_MASK = 3ul,
// Faulting translation:
PAR_S = BITL(9),
PAR_PTW = BITL(8),
PAR_FST_SHIFT = 1,
PAR_FST_MASK = 0x3Ful,
};
// Some (S)PSR flags, masks, shifts
enum PsrFlags {
PSR_AA32_IT10_SHIFT = 25,
PSR_AA32_IT10_MASK = 3ul,
PSR_SS = BITL(21),
PSR_AA32_IT72_SHIFT = 10,
PSR_AA32_IT72_MASK = 0x3Ful,
PSR_DAIF_SHIFT = 6,
PSR_D = BITL(9),
PSR_A = BITL(8),
PSR_I = BITL(7),
PSR_F = BITL(6),
PSR_AA32_THUMB = BITL(5),
PSR_MODE32 = BITL(4),
PSR_EL_SHIFT = 2,
PSR_EL_MASK = 3ul,
PSR_SP_ELX = BITL(0),
};
// cnt*_ctl flags
enum CntCtlFlags {
CNTCTL_ISTATUS = BITL(2),
CNTCTL_IMASK = BITL(1),
CNTCTL_ENABLE = BITL(0),
};
// TCR_ELx flags
enum TcrFlags {
TCR_IRGN_NC = (0 << 8),
TCR_IRGN_WBWA = (1 << 8),
TCR_IRGN_WT = (2 << 8),
TCR_IRGN_WBNWA = (3 << 8),
TCR_IRGN_MASK = (3 << 8),
TCR_ORGN_NC = (0 << 10),
TCR_ORGN_WBWA = (1 << 10),
TCR_ORGN_WT = (2 << 10),
TCR_ORGN_WBNWA = (3 << 10),
TCR_ORGN_MASK = (3 << 10),
TCR_NOT_SHARED = (0 << 12),
TCR_SHARED_OUTER = (2 << 12),
TCR_SHARED_INNER = (3 << 12),
TCR_EPD1_DISABLE = BITL(23),
TCR_EL1_RSVD = BITL(31),
TCR_EL2_RSVD = (BITL(31) | BITL(23)),
VTCR_EL2_RSVD = BITL(31),
TCR_EL3_RSVD = (BITL(31) | BITL(23)),
};
// Could have used enum class here, but can't start identifiers with a digit...
enum TranslationGranuleSize : u64 {
TranslationGranule_4K = 0,
TranslationGranule_64K = 1,
TranslationGranule_16K = 2,
};
constexpr size_t GetTranslationGranuleBitSize(TranslationGranuleSize granuleSize)
{
switch (granuleSize) {
case TranslationGranule_4K: return 12;
case TranslationGranule_64K: return 16;
case TranslationGranule_16K: return 14;
default: return 0;
}
}
constexpr u64 TCR_TG0(TranslationGranuleSize granuleSize)
{
return (granuleSize & 3) << 14;
}
constexpr u64 TCR_T0SZ(size_t addressSpaceSize) { return (64ul - (addressSpaceSize & 0x3F)) << 0; }
constexpr u64 TCR_PS(u64 n) { return (n & 7) << 16; }
constexpr u64 VTCR_SL0(u64 n) { return (n & 3) << 6; }
}

View File

@@ -1,61 +0,0 @@
/*
* Copyright (c) 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 <stdio.h>
#include <string.h>
#include "debug_log.h"
#include "platform/uart.h"
#include "semihosting.h"
#include "utils.h"
#include "transport_interface.h"
#include "platform/uart.h"
#ifndef DLOG_USE_SEMIHOSTING_WRITE0
#define DLOG_USE_SEMIHOSTING_WRITE0 1
#endif
static TransportInterface *g_debugLogTransportInterface;
void debugLogInit(void)
{
if (!DLOG_USE_SEMIHOSTING_WRITE0) {
transportInterfaceCreate(TRANSPORT_INTERFACE_TYPE_UART, DEFAULT_UART, DEFAULT_UART_FLAGS, NULL, NULL, NULL);
}
}
void debugLogRaw(const char *str)
{
// Use semihosting if available (we assume qemu was launched with -semihosting), otherwise UART
if (DLOG_USE_SEMIHOSTING_WRITE0 && semihosting_connection_supported()) {
semihosting_write_string(str);
} else {
transportInterfaceWriteData(g_debugLogTransportInterface, str, strlen(str));
}
}
// NOTE: UNSAFE!
int debugLog(const char *fmt, ...)
{
char buf[128];
va_list args;
va_start(args, fmt);
int res = vsprintf(buf, fmt, args);
va_end(args);
debugLogRaw(buf);
return res;
}

View File

@@ -1,30 +0,0 @@
/*
* Copyright (c) 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 <stdarg.h>
#ifndef NDEBUG
#define DEBUG(...) debugLog(__VA_ARGS__)
#define DEBUGRAW(str) debugLogRaw(str)
#else
#define DEBUG(...)
#define DEBUGRAW(str)
#endif
void debugLogInit(void);
void debugLogRaw(const char *str);
int debugLog(const char *fmt, ...);

View File

@@ -1,263 +0,0 @@
/*
* Copyright (c) 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 <stdatomic.h>
#include <stdarg.h>
#include <string.h>
#include "debug_manager.h"
#include "core_ctx.h"
#include "irq.h"
#include "spinlock.h"
#include "single_step.h"
#include "gdb/debug.h"
GDBContext g_gdbContext = { 0 };
typedef struct DebugManager {
DebugEventInfo debugEventInfos[MAX_CORE];
uintptr_t steppingRangeStartAddrs[MAX_CORE];
uintptr_t steppingRangeEndAddrs[MAX_CORE];
ALIGN(64) atomic_uint pausedCoreList;
atomic_uint singleStepCoreList;
atomic_uint eventsSentList;
Barrier pauseBarrier;
atomic_bool reportingEnabled;
} DebugManager;
static DebugManager g_debugManager = { 0 };
static void debugManagerDoPauseCores(u32 coreList)
{
__builtin_prefetch(&g_debugManager.pausedCoreList, 1, 0);
u32 desiredList = coreList;
u32 remainingList = coreList;
u32 readList = atomic_load(&g_debugManager.pausedCoreList);
do {
desiredList |= readList;
remainingList &= ~readList;
} while (!atomic_compare_exchange_weak(&g_debugManager.pausedCoreList, &readList, desiredList));
if (remainingList & ~BIT(currentCoreCtx->coreId)) {
// We need to notify other cores...
u32 otherCores = remainingList & ~BIT(currentCoreCtx->coreId);
barrierInit(&g_debugManager.pauseBarrier, otherCores | BIT(currentCoreCtx->coreId));
generateSgiForList(ThermosphereSgi_DebugPause, otherCores);
barrierWait(&g_debugManager.pauseBarrier);
}
if (remainingList & BIT(currentCoreCtx->coreId)) {
currentCoreCtx->wasPaused = true;
}
__sev();
}
void debugManagerPauseSgiHandler(void)
{
currentCoreCtx->wasPaused = true;
barrierWait(&g_debugManager.pauseBarrier);
}
void debugManagerInit(TransportInterfaceType gdbIfaceType, u32 gdbIfaceId, u32 gdbIfaceFlags)
{
memset(&g_debugManager, 0, sizeof(DebugManager));
GDB_InitializeContext(&g_gdbContext, gdbIfaceType, gdbIfaceId, gdbIfaceFlags);
GDB_MigrateRxIrq(&g_gdbContext, currentCoreCtx->coreId);
}
bool debugManagerHandlePause(void)
{
u32 coreId = currentCoreCtx->coreId;
__builtin_prefetch(&g_debugManager.pausedCoreList, 0, 3);
if (atomic_load(&g_debugManager.pausedCoreList) & BIT(coreId)) {
unmaskIrq();
do {
__wfe();
} while (atomic_load(&g_debugManager.pausedCoreList) & BIT(coreId));
maskIrq();
if (!g_debugManager.debugEventInfos[coreId].handled) {
// Do we still have an unhandled debug event?
GDB_TrySignalDebugEvent(&g_gdbContext, &g_debugManager.debugEventInfos[coreId]);
return false;
}
}
currentCoreCtx->wasPaused = false;
// Single-step: if inactive and requested, start single step; cancel if active and not requested
u32 ssReqd = (atomic_load(&g_debugManager.singleStepCoreList) & BIT(currentCoreCtx->coreId)) != 0;
SingleStepState singleStepState = singleStepGetNextState(currentCoreCtx->guestFrame);
if (ssReqd) {
currentCoreCtx->steppingRangeStartAddr = g_debugManager.steppingRangeStartAddrs[coreId];
currentCoreCtx->steppingRangeEndAddr = g_debugManager.steppingRangeEndAddrs[coreId];
if(singleStepState == SingleStepState_Inactive) {
singleStepSetNextState(currentCoreCtx->guestFrame, SingleStepState_ActiveNotPending);
}
} else if (!ssReqd && singleStepState != SingleStepState_Inactive) {
singleStepSetNextState(currentCoreCtx->guestFrame, SingleStepState_Inactive);
}
return true;
}
void debugManagerSetReportingEnabled(bool enabled)
{
atomic_store(&g_debugManager.reportingEnabled, enabled);
}
bool debugManagerHasDebugEvent(u32 coreId)
{
bool isPaused = debugManagerIsCorePaused(coreId);
return isPaused && g_debugManager.debugEventInfos[coreId].type != DBGEVENT_NONE;
}
void debugManagerPauseCores(u32 coreList)
{
u64 flags = maskIrq();
debugManagerDoPauseCores(coreList);
restoreInterruptFlags(flags);
}
void debugManagerSetSingleStepCoreList(u32 coreList)
{
atomic_store(&g_debugManager.singleStepCoreList, coreList);
}
void debugManagerUnpauseCores(u32 coreList)
{
FOREACH_BIT (tmp, coreId, coreList) {
if (&g_debugManager.debugEventInfos[coreId].handled) {
// Discard already handled debug events
g_debugManager.debugEventInfos[coreId].type = DBGEVENT_NONE;
}
}
atomic_fetch_and(&g_debugManager.pausedCoreList, ~coreList);
__sev();
}
void debugManagerSetSteppingRange(u32 coreId, uintptr_t startAddr, uintptr_t endAddr)
{
g_debugManager.steppingRangeStartAddrs[coreId] = startAddr;
g_debugManager.steppingRangeEndAddrs[coreId] = endAddr;
}
u32 debugManagerGetPausedCoreList(void)
{
return atomic_load(&g_debugManager.pausedCoreList);
}
DebugEventInfo *debugManagerGetDebugEvent(u32 coreId)
{
return &g_debugManager.debugEventInfos[coreId];
}
void debugManagerReportEvent(DebugEventType type, ...)
{
u64 flags = maskIrq();
bool reportingEnabled = atomic_load(&g_debugManager.reportingEnabled);
if (!reportingEnabled && type != DBGEVENT_DEBUGGER_BREAK) {
restoreInterruptFlags(flags);
return;
}
u32 coreId = currentCoreCtx->coreId;
DebugEventInfo *info = &g_debugManager.debugEventInfos[coreId];
memset(info, 0 , sizeof(DebugEventInfo));
info->type = type;
info->coreId = coreId;
info->frame = currentCoreCtx->guestFrame;
va_list args;
va_start(args, type);
switch (type) {
case DBGEVENT_OUTPUT_STRING:
info->outputString.address = va_arg(args, uintptr_t);
info->outputString.size = va_arg(args, size_t);
break;
default:
break;
}
va_end(args);
// Now, pause ourselves and try to signal we have a debug event
debugManagerDoPauseCores(BIT(coreId));
if (reportingEnabled) {
exceptionEnterInterruptibleHypervisorCode();
unmaskIrq();
GDB_TrySignalDebugEvent(&g_gdbContext, info);
}
restoreInterruptFlags(flags);
}
void debugManagerBreakCores(u32 coreList)
{
u32 coreId = currentCoreCtx->coreId;
if (coreList & ~BIT(coreId)) {
generateSgiForList(ThermosphereSgi_ReportDebuggerBreak, coreList & ~BIT(coreId));
}
if ((coreList & BIT(coreId)) && !debugManagerHasDebugEvent(coreId)) {
debugManagerReportEvent(DBGEVENT_DEBUGGER_BREAK);
}
// Wait for all cores
__sevl();
do {
__wfe();
} while ((atomic_load(&g_debugManager.pausedCoreList) & coreList) != coreList);
}
void debugManagerContinueCores(u32 coreList)
{
u32 coreId = currentCoreCtx->coreId;
if (coreList & ~BIT(coreId)) {
generateSgiForList(ThermosphereSgi_DebuggerContinue, coreList & ~BIT(coreId));
}
if (coreList & BIT(coreId) && debugManagerIsCorePaused(coreId)) {
debugManagerUnpauseCores(BIT(coreId));
}
// Wait for all cores
__sevl();
do {
__wfe();
} while ((atomic_load(&g_debugManager.pausedCoreList) & coreList) != 0);
}
/* u64 mdcr = GET_SYSREG(mdcr_el2);
// Trap Debug Exceptions, and accesses to debug registers.
mdcr |= MDCR_EL2_TDE;
// Implied from TDE
mdcr |= MDCR_EL2_TDRA | MDCR_EL2_TDOSA | MDCR_EL2_TDA;
SET_SYSREG(mdcr_el2, mdcr);
*/

View File

@@ -1,84 +0,0 @@
/*
* Copyright (c) 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 "exceptions.h"
struct ExceptionStackFrame;
//#include "gdb/hvisor_context.h"
#include "transport_interface.h"
//extern GDBContext g_gdbContext;
typedef enum DebugEventType {
DBGEVENT_NONE = 0,
DBGEVENT_DEBUGGER_BREAK,
DBGEVENT_EXCEPTION,
DBGEVENT_CORE_ON,
DBGEVENT_CORE_OFF,
DBGEVENT_EXIT,
DBGEVENT_OUTPUT_STRING,
} DebugEventType;
typedef struct OutputStringDebugEventInfo {
uintptr_t address;
size_t size;
} OutputStringDebugEventInfo;
typedef struct DebugEventInfo {
DebugEventType type;
bool preprocessed;
bool handled;
u32 coreId;
struct ExceptionStackFrame *frame;
union {
OutputStringDebugEventInfo outputString;
};
} DebugEventInfo;
void debugManagerPauseSgiHandler(void);
void debugManagerInit(TransportInterfaceType gdbIfaceType, u32 gdbIfaceId, u32 gdbIfaceFlags);
void debugManagerSetReportingEnabled(bool enabled);
// Hypervisor interrupts will be serviced during the pause-wait
bool debugManagerHandlePause(void);
DebugEventInfo *debugManagerGetDebugEvent(u32 coreId);
bool debugManagerHasDebugEvent(u32 coreId);
// Note: these functions are not reentrant EXCEPT debugPauseCores(1 << currentCoreId)
// "Pause" makes sure all cores reaches the pause function before proceeding.
// "Unpause" doesn't synchronize, it just makes sure the core resumes & that "pause" can be called again.
void debugManagerPauseCores(u32 coreList);
void debugManagerUnpauseCores(u32 coreList);
void debugManagerSetSingleStepCoreList(u32 coreList);
void debugManagerSetSteppingRange(u32 coreId, uintptr_t startAddr, uintptr_t endAddr);
u32 debugManagerGetPausedCoreList(void);
void debugManagerReportEvent(DebugEventType type, ...);
void debugManagerBreakCores(u32 coreList);
void debugManagerContinueCores(u32 coreList);
static inline bool debugManagerIsCorePaused(u32 coreId)
{
return (debugManagerGetPausedCoreList() & BIT(coreId)) != 0;
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright (c) 2018-2020 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 <vapours/defines.hpp>
#include <vapours/util/util_bitutil.hpp>
#include <atomic>
#include <utility>
#include <optional>
#include <functional>
#include <tuple>
#include <array>
#include "preprocessor.h"
#include "debug_log.h"
#define SINGLETON(cl) \
NON_COPYABLE(cl);\
NON_MOVEABLE(cl);\
private:\
static cl instance;\
public:\
static cl &GetInstance() { return instance; }
#define SINGLETON_WITH_ATTRS(cl, attrs) \
NON_COPYABLE(cl);\
NON_MOVEABLE(cl);\
private:\
attrs static cl instance;\
public:\
static cl &GetInstance() { return instance; }
//FIXME
#ifndef ENSURE
#define ENSURE(...)
#endif
//FIXME
#ifndef ENSURE2
#define ENSURE2(...)
#endif

View File

@@ -1,129 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_drivers_arm_pl011.hpp"
namespace ams::hvisor::drivers::arm {
void PL011::Initialize(u32 baudRate, u32 clkRate) const
{
/* The TRM (DDI0183) reads:
Program the control registers as follows:
1. Disable the UART.
2. Wait for the end of transmission or reception of the current character.
3. Flush the transmit FIFO by disabling bit 4 (FEN) in the line control register
(UARTCLR_H).
4. Reprogram the control register.
5. Enable the UART.
*/
// First, disable the UART. Flush the receive FIFO, wait for tx to complete, and disable both FIFOs.
m_regs->cr &= ~CR_UARTEN;
while (!(m_regs->fr & FR_RXFE)) {
m_regs->dr;
}
while (m_regs->fr & FR_BUSY);
// This flushes the transmit FIFO:
m_regs->lcr_h &= ~LCR_H_FEN;
// Divisor = clkRate / (16 * baudRate). Integer part (16 bits) in IBRD, 6 fractional bits in FBRD (fixed point)
// This means the encoded divisor is 2^6 * divisor = 4*clkRate / baudRate
u32 rawDivisor = (4 * clkRate) / baudRate;
m_regs->ibrd = (rawDivisor >> 6) & 0xFFFF;
m_regs->fbrd = rawDivisor & 0x3F;
// Select FIFO fill levels for interrupts
m_regs->ifls = IFLS_RX4_8 | IFLS_TX4_8;
// FIFO Enabled / No Parity / 8 Data bit / One Stop Bit
m_regs->lcr_h = LCR_H_FEN | LCR_H_WLEN_8;
// Select the interrupts we want to have
// RX timeout and TX/RX fill interrupts
m_regs->imsc = RTI | RXI | RXI;
// Clear any pending errors
m_regs->ecr = 0;
// Clear all interrupts
m_regs->icr = ALL_INTERRUPTS_MASK;
// Enable tx, rx, and uart overall
m_regs->cr = CR_RXE | CR_TXE | CR_UARTEN;
}
void PL011::WriteData(const void *buffer, size_t size) const
{
const u8 *buf8 = reinterpret_cast<const u8 *>(buffer);
for (size_t i = 0; i < size; i++) {
while (m_regs->fr & FR_TXFF); // while TX FIFO full
m_regs->dr = buf8[i];
}
}
void PL011::ReadData(void *buffer, size_t size) const
{
u8 *buf8 = reinterpret_cast<u8 *>(buffer);
size_t i;
for (i = 0; i < size; i++) {
while (m_regs->fr & FR_RXFE);
buf8[i] = m_regs->dr;
}
}
size_t PL011::ReadDataMax(void *buffer, size_t maxSize) const
{
u8 *buf8 = reinterpret_cast<u8 *>(buffer);
size_t count = 0;
for (size_t i = 0; i < maxSize && !(m_regs->fr & FR_RXFE); i++) {
buf8[i] = m_regs->dr;
++count;
}
return count;
}
size_t PL011::ReadDataUntil(char *buffer, size_t maxSize, char delimiter) const
{
size_t count = 0;
for (size_t i = 0; i < maxSize; i++) {
while (m_regs->fr & FR_RXFE);
buffer[i] = m_regs->dr;
++count;
if (buffer[i] == delimiter) {
break;
}
}
return count;
}
void PL011::SetRxInterruptEnabled(bool enabled) const
{
constexpr u32 mask = RTI | RXI;
// We don't support any other interrupt here.
m_regs->imsc = enabled ? mask : 0;
}
}

View File

@@ -1,152 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "../../defines.hpp"
// AMBA PL011 driver
// Originally from
/*
* Copyright (c) 2013-2018, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
namespace ams::hvisor::drivers::arm {
class PL011 final {
private:
struct Registers {
u32 dr;
union {
u32 sr;
u32 ecr;
};
u32 _0x08, _0x0C, _0x10, _0x14;
u32 fr;
u32 _0x1C;
u32 ilpr;
u32 ibrd;
u32 fbrd;
u32 lcr_h;
u32 cr;
u32 ifls;
u32 imsc;
u32 ris;
u32 mis;
u32 icr;
u32 dmacr;
};
static_assert(std::is_standard_layout_v<Registers>);
static_assert(std::is_trivial_v<Registers>);
enum Mask : u32 {
DATA_ERROR_MASK = 0x0F00, // Data status bits
PL011_STATUS_ERROR_MASK = 0x0F, // Status reg bits
};
enum Error : u32 {
OE = BIT(3), // Overrun error
BE = BIT(2), // Break error
PE = BIT(1), // Parity error
FE = BIT(0), // Framing error
};
enum Interrupt : u32 {
OEI = BIT(10), // Overrun error interrupt
BEI = BIT(9), // Break error interrupt
PEI = BIT(8), // Parity error interrupt
FEI = BIT(7), // Framing error interrupt
RTI = BIT(6), // Receive timeout interrupt
TXI = BIT(5), // Transmit interrupt
RXI = BIT(4), // Receive interrupt
DSRMI = BIT(3), // DSR modem interrupt
DCDMI = BIT(2), // DCD modem interrupt
CTSMI = BIT(1), // CTS modem interrupt
RIMI = BIT(0), // RI modem interrupt
ALL_INTERRUPTS_MASK = MASK(11),
};
// Flag reg bits
enum FrFlags : u32 {
FR_RI = BIT(8), // Ring indicator
FR_TXFE = BIT(7), // Transmit FIFO empty
FR_RXFF = BIT(6), // Receive FIFO full
FR_TXFF = BIT(5), // Transmit FIFO full
FR_RXFE = BIT(4), // Receive FIFO empty
FR_BUSY = BIT(3), // UART busy
FR_DCD = BIT(2), // Data carrier detect
FR_DSR = BIT(1), // Data set ready
FR_CTS = BIT(0), // Clear to send
};
// Control reg bits
enum CrFlags : u32 {
CR_CTSEN = BIT(15), // CTS hardware flow control enable
CR_RTSEN = BIT(14), // RTS hardware flow control enable
CR_RTS = BIT(11), // Request to send
CR_DTR = BIT(10), // Data transmit ready.
CR_RXE = BIT(9), // Receive enable
CR_TXE = BIT(8), // Transmit enable
CR_LBE = BIT(7), // Loopback enable
CR_UARTEN = BIT(0), // UART Enable
};
// Line Control Register Bits
enum LcrFlags : u32 {
LCR_H_SPS = BIT(7), // Stick parity select
LCR_H_WLEN_8 = (3 << 5),
LCR_H_WLEN_7 = (2 << 5),
LCR_H_WLEN_6 = BIT(5),
LCR_H_WLEN_5 = (0 << 5),
LCR_H_FEN = BIT(4), // FIFOs Enable
LCR_H_STP2 = BIT(3), // Two stop bits select
LCR_H_EPS = BIT(2), // Even parity select
LCR_H_PEN = BIT(1), // Parity Enable
LCR_H_BRK = BIT(0), // Send break
};
// FIFO level select register
enum IflsLevels : u32 {
IFLS_RX1_8 = (0 << 3),
IFLS_RX2_8 = (1 << 3),
IFLS_RX4_8 = (2 << 3),
IFLS_RX6_8 = (3 << 3),
IFLS_RX7_8 = (4 << 3),
IFLS_TX1_8 = (0 << 0),
IFLS_TX2_8 = (1 << 0),
IFLS_TX4_8 = (2 << 0),
IFLS_TX6_8 = (3 << 0),
IFLS_TX7_8 = (4 << 0),
};
private:
// TODO friend
volatile Registers *m_regs = nullptr;
private:
void Initialize(u32 baudRate, u32 clkRate) const;
public:
void WriteData(const void *buffer, size_t size) const;
void ReadData(void *buffer, size_t size) const;
size_t ReadDataMax(void *buffer, size_t maxSize) const;
size_t ReadDataUntil(char *buffer, size_t maxSize, char delimiter) const;
void SetRxInterruptEnabled(bool enabled) const;
};
}

View File

@@ -1,131 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_drivers_tegra_uart.hpp"
#include "../../hvisor_generic_timer.hpp"
#include <chrono>
using namespace ams::hvisor;
namespace {
inline void WaitCycles(u32 baudRate, u32 num)
{
u32 t = (num * 1000000 + 16 * baudRate - 1) / (16 * baudRate);
GenericTimer::GetInstance().Wait(std::chrono::microseconds{t});
}
inline void WaitSyms(u32 baudRate, u32 num)
{
u32 t = (num * 1000000 + baudRate - 1) / baudRate;
GenericTimer::GetInstance().Wait(std::chrono::microseconds{t});
}
}
namespace ams::hvisor::drivers::tegra {
void Uart::Initialize(u32 baudRate, u32 clkRate, bool invertTx) const
{
// Calculate baud rate, round to nearest (clkRate / (16 * baudRate))
u32 divisor = (8 * baudRate + clkRate) / (16 * baudRate);
m_regs->lcr &= ~LCR_DLAB; // Disable DLAB.
m_regs->ier = 0; // Disable all interrupts.
m_regs->mcr = 0;
// Setup UART in FIFO mode
m_regs->lcr = LCR_DLAB | LCR_WD_LENGTH_8; // Enable DLAB and set word length 8.
m_regs->dll = divisor & 0xFF; // Divisor latch LSB.
m_regs->dlh = (divisor >> 8) & 0xFF; // Divisor latch MSB.
m_regs->lcr &= ~LCR_DLAB; // Disable DLAB.
m_regs->spr; // Dummy read.
WaitSyms(baudRate, 3); // Wait for 3 symbols at the new baudrate.
// Enable FIFO with default settings.
m_regs->fcr = FCR_FCR_EN_FIFO;
m_regs->irda_csr = invertTx ? IRDA_CSR_INVERT_TXD : 0; // Invert TX if needed
m_regs->spr; // Dummy read as mandated by TRM.
WaitCycles(baudRate, 3); // Wait for 3 baud cycles, as mandated by TRM (erratum).
// Flush FIFO.
WaitIdle(STATUS_TX_IDLE); // Make sure there's no data being written in TX FIFO (TRM).
m_regs->fcr |= FCR_RX_CLR | FCR_TX_CLR; // Clear TX and RX FIFOs.
WaitCycles(baudRate, 32); // Wait for 32 baud cycles (TRM, erratum).
// Wait for idle state (TRM).
WaitIdle(STATUS_TX_IDLE | STATUS_RX_IDLE);
}
void Uart::WriteData(const void *buffer, size_t size) const
{
const u8 *buf8 = reinterpret_cast<const u8 *>(buffer);
for (size_t i = 0; i < size; i++) {
while (!(m_regs->lsr & LSR_THRE)); // Wait until it's possible to send data.
m_regs->thr = buf8[i];
}
}
void Uart::ReadData(void *buffer, size_t size) const
{
u8 *buf8 = reinterpret_cast<u8 *>(buffer);
for (size_t i = 0; i < size; i++) {
while (!(m_regs->lsr & LSR_RDR)) // Wait until it's possible to receive data.
buf8[i] = m_regs->rbr;
}
}
size_t Uart::ReadDataMax(void *buffer, size_t maxSize) const
{
u8 *buf8 = reinterpret_cast<u8 *>(buffer);
size_t count = 0;
for (size_t i = 0; i < maxSize && (m_regs->lsr & LSR_RDR); i++) {
buf8[i] = m_regs->rbr;
++count;
}
return count;
}
size_t Uart::ReadDataUntil(char *buffer, size_t maxSize, char delimiter) const
{
size_t count = 0;
for (size_t i = 0; i < maxSize && (m_regs->lsr & LSR_RDR); i++) {
while (!(m_regs->lsr & LSR_RDR)) // Wait until it's possible to receive data.
buffer[i] = m_regs->rbr;
++count;
if (buffer[i] == delimiter) {
break;
}
}
return count;
}
void Uart::SetRxInterruptEnabled(bool enabled) const
{
constexpr u32 mask = IER_IE_RX_TIMEOUT | IER_IE_RHR;
// We don't support any other interrupt here.
m_regs->ier = enabled ? mask : 0;
}
}

View File

@@ -1,210 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "../../defines.hpp"
namespace ams::hvisor::drivers::tegra {
class Uart final {
private:
struct Registers {
union {
// UART_THR_DLAB_0
u32 thr;
u32 rbr;
u32 dll;
};
union {
// UART_IER_DLAB_0
u32 ier;
u32 dlh;
};
union {
// UART_IIR_FCR_0
u32 iir;
u32 fcr;
};
u32 lcr;
u32 mcr;
u32 lsr;
u32 msr;
u32 spr;
u32 irda_csr;
u32 rx_fifo_cfg;
u32 mie;
u32 vendor_status;
u8 _0x30[0x0C];
u32 asr;
};
static_assert(std::is_standard_layout_v<Registers>);
static_assert(std::is_trivial_v<Registers>);
// 36.3.12 UART_VENDOR_STATUS_0_0
enum VendorStatusFlags : u32 {
STATUS_TX_FIFO_COUNTER = 0b111111 << 24, // reflects number of current entries in TX FIFO
STATUS_RX_FIFO_COUNTER = 0b111111 << 16, // reflects number of current entries in RX FIFO
/*
This bit is set to 1 when write data is issued to the TX FIFO when
it is already full and gets cleared on register read (sticky bit until read):
0 = NO_OVERRUN
1 = OVERRUN
*/
STATUS_TX_OVERRUN = BIT(3),
/*
This bit is set to 1 when a read is issued to an empty FIFO and
gets cleared on register read (sticky bit until read):
0 = NO_UNDERRUN
1 = UNDERRUN
*/
STATUS_RX_UNDERRUN = BIT(2),
STATUS_RX_IDLE = BIT(1),
STATUS_TX_IDLE = BIT(0),
};
// 36.3.6 UART_LSR_0
enum LsrFlags : u32 {
LSR_RX_FIFO_EMPTY = BIT(9), // Receiver FIFO empty status
LSR_TX_FIFO_FULL = BIT(8), // Transmitter FIFO full status
LSR_FIFOE = BIT(7), // Receive FIFO Error
LSR_TMTY = BIT(6), // Transmit Shift Register empty status
LSR_THRE = BIT(5), // Transmit Holding Register is Empty -- OK to write data
LSR_BRK = BIT(4), // BREAK condition detected on line
LSR_FERR = BIT(3), // Framing Error
LSR_PERR = BIT(2), // Parity Error
LSR_OVRF = BIT(1), // Receiver Overrun Error
LSR_RDR = BIT(0), // Receiver Data Ready
};
// 36.3.4 UART_LCR_0
enum LcrFlags : u32 {
/*
STOP:
0 = Transmit 1 stop bit
1 = Transmit 2 stop bits (receiver always checks for 1 stop bit)
*/
LCR_DLAB = BIT(7), // Divisor Latch Access Bit (set to allow programming of the DLH, DLM Divisors)
LCR_SET_B = BIT(6), // Set BREAK condition -- Transmitter sends all zeroes to indicate BREAK
LCR_SET_P = BIT(5), // Set (force) parity to value in LCR[4]
LCR_EVEN = BIT(4), // Even parity format. There will always be an even number of 1s in the binary representation (PAR = 1)
LCR_PAR = BIT(3), // Parity enabled
LCR_STOP = BIT(2),
LCR_WD_LENGTH_5 = 0 << 0, // word length 5
LCR_WD_LENGTH_6 = 1 << 0, // word length 6
LCR_WD_LENGTH_7 = 2 << 0, // word length 7
LCR_WD_LENGTH_8 = 3 << 0, // word length 8
};
// 36.3.3 UART_IIR_FCR_0
enum FcrFlags : u32{
// RX_TRIG
FCR_RX_TRIG_MASK = 3 << 6,
FCR_RX_TRIG_FIFO_COUNT_GREATER_1 = 0 << 6,
FCR_RX_TRIG_FIFO_COUNT_GREATER_4 = 1 << 6,
FCR_RX_TRIG_FIFO_COUNT_GREATER_8 = 2 << 6,
FCR_RX_TRIG_FIFO_COUNT_GREATER_16 = 3 << 6,
// TX_TRIG
FCR_TX_TRIG_MASK = 3 << 4,
FCR_TX_TRIG_FIFO_COUNT_GREATER_16 = 0 << 4,
FCR_TX_TRIG_FIFO_COUNT_GREATER_8 = 1 << 4,
FCR_TX_TRIG_FIFO_COUNT_GREATER_4 = 2 << 4,
FCR_TX_TRIG_FIFO_COUNT_GREATER_1 = 3 << 4,
/*
DMA:
0 = DMA_MODE_0
1 = DMA_MODE_1
*/
FCR_DMA = BIT(3),
/*
RX/TX_CLR:
Clears the contents of the receive (resp. transmit) FIFO and resets
its counter logic to 0.
The receive (resp. transmit) shift register is not cleared or altered.
This bit returns to 0 after clearing the FIFOs.
*/
FCR_TX_CLR = BIT(2), // See above
FCR_RX_CLR = BIT(1), // See above
FCR_FCR_EN_FIFO = BIT(0), // Enable the transmit and receive FIFOs. This bit should be enabled
};
// 36.3.2 UART_IER_DLAB_0_0
enum IerFlags : u32 {
IER_IE_EORD = BIT(5), // Interrupt enable for Interrupt Enable for End of Received Data
IER_IE_RX_TIMEOUT = BIT(4), // Interrupt enable for RX FIFO timeout
IER_IE_MSI = BIT(3), // Interrupt enable for Modem Status Interrupt
IER_IE_RXS = BIT(2), // Interrupt enable for Receiver Line Status Interrupt
IER_IE_THR = BIT(1), // Interrupt enable for Transmitter Holding Register Empty interrupt
IER_IE_RHR = BIT(0), // Interrupt enable for Received Data Interrupt
};
// 6.3.3 UART_IIR_FCR_0
enum IirFlags : u32 {
IIR_EN_FIFO_MASK = 3 << 6,
IIR_MODE_16450 = 0 << 6,
IIR_MODE_16550 = 1 << 6,
IIR_IS_PRI2 = BIT(3), // Encoded Interrupt ID Refer to IIR[3:0] table
IIR_IS_PRI1 = BIT(2), // Encoded Interrupt ID Refer to IIR[3:0] table
IIR_IS_PRI0 = BIT(1), // Encoded Interrupt ID Refer to IIR[3:0] table [36.3.3]
IIR_IS_STA = BIT(0), // Interrupt Pending if ZERO
};
// 36.3.9 UART_IRDA_CSR_0
enum IrdaCsrFlags : u32{
IRDA_CSR_SIR_A = BIT(7),
IRDA_CSR_PWT_A_BAUD_PULSE_3_14 = 0 << 6,
IRDA_CSR_PWT_A_BAUD_PULSE_4_14 = 1 << 6,
IRDA_CSR_INVERT_RTS = BIT(3),
IRDA_CSR_INVERT_CTS = BIT(2),
IRDA_CSR_INVERT_TXD = BIT(1),
IRDA_CSR_INVERT_RXD = BIT(0),
};
private:
// TODO friend
volatile Registers *m_regs = nullptr;
private:
void Initialize(u32 baudRate, u32 clkRate, bool invertTx) const;
void WaitIdle(u32 status) const
{
if (status & STATUS_TX_IDLE) {
while (!(m_regs->lsr & LSR_TMTY));
}
if (status & STATUS_RX_IDLE) {
while (m_regs->lsr & LSR_RDR);
}
}
public:
void WriteData(const void *buffer, size_t size) const;
void ReadData(void *buffer, size_t size) const;
size_t ReadDataMax(void *buffer, size_t maxSize) const;
size_t ReadDataUntil(char *buffer, size_t maxSize, char delimiter) const;
void SetRxInterruptEnabled(bool enabled) const;
};
}

View File

@@ -1,654 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "../../../defines.hpp"
namespace ams::hvisor::drivers::tegra::t210 {
class Car final {
private:
struct Registers {
u32 rst_src; // _RST_SOURCE_0, 0x000
// _RST_DEVICES_L/H/U_0 0x4-0xc
u32 rst_dev_l;
u32 rst_dev_h;
u32 rst_dev_u;
// _CLK_OUT_ENB_L/H/U_0 0x10-0x18
u32 clk_out_enb_l;
u32 clk_out_enb_h;
u32 clk_out_enb_u;
u32 _0x1C;
u32 cclk_brst_pol; // _CCLK_BURST_POLICY_0, 0x020
u32 super_cclk_div; // _SUPER_CCLK_DIVIDER_0, 0x024
u32 sclk_brst_pol; // _SCLK_BURST_POLICY_0, 0x028
u32 super_sclk_div; // _SUPER_SCLK_DIVIDER_0, 0x02c
u32 clk_sys_rate; // _CLK_SYSTEM_RATE_0, 0x030
u32 prog_dly_clk; // _PROG_DLY_CLK_0, 0x034
u32 aud_sync_clk_rate; // _AUDIO_SYNC_CLK_RATE_0, 0x038
u32 _0x3C;
u32 cop_clk_skip_plcy; // _COP_CLK_SKIP_POLICY_0, 0x040
u32 clk_mask_arm; // _CLK_MASK_ARM_0, 0x044
u32 misc_clk_enb; // _MISC_CLK_ENB_0, 0x048
u32 clk_cpu_cmplx; // _CLK_CPU_CMPLX_0, 0x04c
u32 osc_ctrl; // _OSC_CTRL_0, 0x050
u32 pll_lfsr; // _PLL_LFSR_0, 0x054
u32 osc_freq_det; // _OSC_FREQ_DET_0, 0x058
u32 osc_freq_det_stat; // _OSC_FREQ_DET_STATUS_0, 0x05c
u32 _0x60[2];
u32 plle_ss_cntl; // _PLLE_SS_CNTL_0, 0x068
u32 plle_misc1; // _PLLE_MISC1_0, 0x06c
u32 _0x70[4];
// PLLC 0x80-0x8c
u32 pllc_base;
u32 pllc_out;
u32 pllc_misc0;
u32 pllc_misc1;
// PLLM 0x90-0x9c
u32 pllm_base;
u32 pllm_out;
u32 pllm_misc1;
u32 pllm_misc2;
// PLLP 0xa0-0xac
u32 pllp_base;
u32 pllp_outa;
u32 pllp_outb;
u32 pllp_misc;
// PLLA 0xb0-0xbc
u32 plla_base;
u32 plla_out;
u32 plla_misc0;
u32 plla_misc1;
// PLLU 0xc0-0xcc
u32 pllu_base;
u32 pllu_out;
u32 pllu_misc1;
u32 pllu_misc2;
// PLLD 0xd0-0xdc
u32 plld_base;
u32 plld_out;
u32 plld_misc1;
u32 plld_misc2;
// PLLX 0xe0-0xe4
u32 pllx_base;
u32 pllx_misc;
// PLLE 0xe8-0xf4
u32 plle_base;
u32 plle_misc;
u32 plle_ss_cntl1;
u32 plle_ss_cntl2;
u32 lvl2_clk_gate_ovra; // _LVL2_CLK_GATE_OVRA_0, 0x0f8
u32 lvl2_clk_gate_ovrb; // _LVL2_CLK_GATE_OVRB_0, 0x0fc
u32 clk_source_i2s2; // _CLK_SOURCE_I2S2_0, 0x100
u32 clk_source_i2s3; // _CLK_SOURCE_I2S3_0, 0x104
u32 clk_source_spdif_out; // _CLK_SOURCE_SPDIF_OUT_0, 0x108
u32 clk_source_spdif_in; // _CLK_SOURCE_SPDIF_IN_0, 0x10c
u32 clk_source_pwm; // _CLK_SOURCE_PWM_0, 0x110
u32 _0x114;
u32 clk_source_spi2; // _CLK_SOURCE_SPI2_0, 0x118
u32 clk_source_spi3; // _CLK_SOURCE_SPI3_0, 0x11c
u32 _0x120;
u32 clk_source_i2c1; // _CLK_SOURCE_I2C1_0, 0x124
u32 clk_source_i2c5; // _CLK_SOURCE_I2C5_0, 0x128
u32 _0x12c[2];
u32 clk_source_spi1; // _CLK_SOURCE_SPI1_0, 0x134
u32 clk_source_disp1; // _CLK_SOURCE_DISP1_0, 0x138
u32 clk_source_disp2; // _CLK_SOURCE_DISP2_0, 0x13c
u32 _0x140;
u32 clk_source_isp; // _CLK_SOURCE_ISP_0, 0x144
u32 clk_source_vi; // _CLK_SOURCE_VI_0, 0x148
u32 _0x14c;
u32 clk_source_sdmmc1; // _CLK_SOURCE_SDMMC1_0, 0x150
u32 clk_source_sdmmc2; // _CLK_SOURCE_SDMMC2_0, 0x154
u32 _0x158[3];
u32 clk_source_sdmmc4; // _CLK_SOURCE_SDMMC4_0, 0x164
u32 _0x168[4];
u32 clk_source_uarta; // _CLK_SOURCE_UARTA_0, 0x178
u32 clk_source_uartb; // _CLK_SOURCE_UARTB_0, 0x17c
u32 clk_source_host1x; // _CLK_SOURCE_HOST1X_0, 0x180
u32 _0x184[5];
u32 clk_source_i2c2; // _CLK_SOURCE_I2C2_0, 0x198
u32 clk_source_emc; // _CLK_SOURCE_EMC_0, 0x19c
u32 clk_source_uartc; // _CLK_SOURCE_UARTC_0, 0x1a0
u32 _0x1a4;
u32 clk_source_vi_sensor; // _CLK_SOURCE_VI_SENSOR_0, 0x1a8
u32 _0x1ac[2];
u32 clk_source_spi4; // _CLK_SOURCE_SPI4_0, 0x1b4
u32 clk_source_i2c3; // _CLK_SOURCE_I2C3_0, 0x1b8
u32 clk_source_sdmmc3; // _CLK_SOURCE_SDMMC3_0, 0x1bc
u32 clk_source_uartd; // _CLK_SOURCE_UARTD_0, 0x1c0
u32 _0x1c4[2];
u32 clk_source_owr; // _CLK_SOURCE_OWR_0, 0x1cc
u32 _0x1d0;
u32 clk_source_csite; // _CLK_SOURCE_CSITE_0, 0x1d4
u32 clk_source_i2s1; // _CLK_SOURCE_I2S1_0, 0x1d8
u32 clk_source_dtv; // _CLK_SOURCE_DTV_0, 0x1dc
u32 _0x1e0[5];
u32 clk_source_tsec; // _CLK_SOURCE_TSEC_0, 0x1f4
u32 _0x1f8;
u32 clk_spare2; // _CLK_SPARE2_0, 0x1fc
u32 _0x200[32];
u32 clk_out_enb_x; // _CLK_OUT_ENB_X_0, 0x280
u32 clk_enb_x_set; // _CLK_ENB_X_SET_0, 0x284
u32 clk_enb_x_clr; // _CLK_ENB_X_CLR_0, 0x288
u32 rst_devices_x; // _RST_DEVICES_X_0, 0x28c
u32 rst_dev_x_set; // _RST_DEV_X_SET_0, 0x290
u32 rst_dev_x_clr; // _RST_DEV_X_CLR_0, 0x294
u32 clk_out_enb_y; // _CLK_OUT_ENB_Y_0, 0x298
u32 clk_enb_y_set; // _CLK_ENB_Y_SET_0, 0x29c
u32 clk_enb_y_clr; // _CLK_ENB_Y_CLR_0, 0x2a0
u32 rst_devices_y; // _RST_DEVICES_Y_0, 0x2a4
u32 rst_dev_y_set; // _RST_DEV_Y_SET_0, 0x2a8
u32 rst_dev_y_clr; // _RST_DEV_Y_CLR_0, 0x2ac
u32 _0x2b0[17];
u32 dfll_base; // _DFLL_BASE_0, 0x2f4
u32 _0x2f8[2];
// _RST_DEV_L/H/U_SET_0 0x300-0x314
u32 rst_dev_l_set;
u32 rst_dev_l_clr;
u32 rst_dev_h_set;
u32 rst_dev_h_clr;
u32 rst_dev_u_set;
u32 rst_dev_u_clr;
u32 _0x318[2];
// _CLK_ENB_L/H/U_CLR_0 0x320-0x334
u32 clk_enb_l_set;
u32 clk_enb_l_clr;
u32 clk_enb_h_set;
u32 clk_enb_h_clr;
u32 clk_enb_u_set;
u32 clk_enb_u_clr;
u32 _0x338;
u32 ccplex_pg_sm_ovrd; // _CCPLEX_PG_SM_OVRD_0, 0x33c
u32 rst_cpu_cmplx_set; // _RST_CPU_CMPLX_SET_0, 0x340
u32 rst_cpu_cmplx_clr; // _RST_CPU_CMPLX_CLR_0, 0x344
// Additional (T30) registers
u32 clk_cpu_cmplx_set; // _CLK_CPU_CMPLX_SET_0, 0x348
u32 clk_cpu_cmplx_clr; // _CLK_CPU_CMPLX_SET_0, 0x34c
u32 _0x350[2];
u32 rst_dev_v; // _RST_DEVICES_V_0, 0x358
u32 rst_dev_w; // _RST_DEVICES_W_0, 0x35c
u32 clk_out_enb_v; // _CLK_OUT_ENB_V_0, 0x360
u32 clk_out_enb_w; // _CLK_OUT_ENB_W_0, 0x364
u32 cclkg_brst_pol; // _CCLKG_BURST_POLICY_0, 0x368
u32 super_cclkg_div; // _SUPER_CCLKG_DIVIDER_0, 0x36c
u32 cclklp_brst_pol; // _CCLKLP_BURST_POLICY_0, 0x370
u32 super_cclkp_div; // _SUPER_CCLKLP_DIVIDER_0, 0x374
u32 clk_cpug_cmplx; // _CLK_CPUG_CMPLX_0, 0x378
u32 clk_cpulp_cmplx; // _CLK_CPULP_CMPLX_0, 0x37c
u32 cpu_softrst_ctrl; // _CPU_SOFTRST_CTRL_0, 0x380
u32 cpu_softrst_ctrl1; // _CPU_SOFTRST_CTRL1_0, 0x384
u32 cpu_softrst_ctrl2; // _CPU_SOFTRST_CTRL2_0, 0x388
u32 _0x38c[5];
u32 lvl2_clk_gate_ovrc; // _LVL2_CLK_GATE_OVRC, 0x3a0
u32 lvl2_clk_gate_ovrd; // _LVL2_CLK_GATE_OVRD, 0x3a4
u32 _0x3a8[2];
u32 _0x3b0;
u32 clk_source_mselect; // _CLK_SOURCE_MSELECT_0, 0x3b4
u32 clk_source_tsensor; // _CLK_SOURCE_TSENSOR_0, 0x3b8
u32 clk_source_i2s4; // _CLK_SOURCE_I2S4_0, 0x3bc
u32 clk_source_i2s5; // _CLK_SOURCE_I2S5_0, 0x3c0
u32 clk_source_i2c4; // _CLK_SOURCE_I2C4_0, 0x3c4
u32 _0x3c8[2];
u32 clk_source_ahub; // _CLK_SOURCE_AHUB_0, 0x3d0
u32 _0x3d4[4];
u32 clk_source_hda2codec_2x; // _CLK_SOURCE_HDA2CODEC_2X_0, 0x3e4
u32 clk_source_actmon; // _CLK_SOURCE_ACTMON_0, 0x3e8
u32 clk_source_extperiph1; // _CLK_SOURCE_EXTPERIPH1_0, 0x3ec
u32 clk_source_extperiph2; // _CLK_SOURCE_EXTPERIPH2_0, 0x3f0
u32 clk_source_extperiph3; // _CLK_SOURCE_EXTPERIPH3_0, 0x3f4
u32 _0x3f8;
u32 clk_source_i2c_slow; // _CLK_SOURCE_I2C_SLOW_0, 0x3fc
u32 clk_source_sys; // _CLK_SOURCE_SYS_0, 0x400
u32 clk_source_ispb; // _CLK_SOURCE_ISPB_0, 0x404
u32 _0x408[2];
u32 clk_source_sor1; // _CLK_SOURCE_SOR1_0, 0x410
u32 clk_source_sor0; // _CLK_SOURCE_SOR0_0, 0x414
u32 _0x418[2];
u32 clk_source_sata_oob; // _CLK_SOURCE_SATA_OOB_0, 0x420
u32 clk_source_sata; // _CLK_SOURCE_SATA_0, 0x424
u32 clk_source_hda; // _CLK_SOURCE_HDA_0, 0x428
u32 _0x42c;
// _RST_DEV_V/W_SET_0 0x430-0x43c
u32 rst_dev_v_set;
u32 rst_dev_v_clr;
u32 rst_dev_w_set;
u32 rst_dev_w_clr;
// _CLK_ENB_V/W_CLR_0 0x440-0x44c
u32 clk_enb_v_set;
u32 clk_enb_v_clr;
u32 clk_enb_w_set;
u32 clk_enb_w_clr;
// Additional (T114+) registers
u32 rst_cpug_cmplx_set; // _RST_CPUG_CMPLX_SET_0, 0x450
u32 rst_cpug_cmplx_clr; // _RST_CPUG_CMPLX_CLR_0, 0x454
u32 rst_cpulp_cmplx_set; // _RST_CPULP_CMPLX_SET_0, 0x458
u32 rst_cpulp_cmplx_clr; // _RST_CPULP_CMPLX_CLR_0, 0x45c
u32 clk_cpug_cmplx_set; // _CLK_CPUG_CMPLX_SET_0, 0x460
u32 clk_cpug_cmplx_clr; // _CLK_CPUG_CMPLX_CLR_0, 0x464
u32 clk_cpulp_cmplx_set; // _CLK_CPULP_CMPLX_SET_0, 0x468
u32 clk_cpulp_cmplx_clr; // _CLK_CPULP_CMPLX_CLR_0, 0x46c
u32 cpu_cmplx_status; // _CPU_CMPLX_STATUS_0, 0x470
u32 _0x474;
u32 intstatus; // _INTSTATUS_0, 0x478
u32 intmask; // _INTMASK_0, 0x47c
u32 utmip_pll_cfg0; // _UTMIP_PLL_CFG0_0, 0x480
u32 utmip_pll_cfg1; // _UTMIP_PLL_CFG1_0, 0x484
u32 utmip_pll_cfg2; // _UTMIP_PLL_CFG2_0, 0x488
u32 plle_aux; // _PLLE_AUX_0, 0x48c
u32 sata_pll_cfg0; // _SATA_PLL_CFG0_0, 0x490
u32 sata_pll_cfg1; // _SATA_PLL_CFG1_0, 0x494
u32 pcie_pll_cfg0; // _PCIE_PLL_CFG0_0, 0x498
u32 prog_audio_dly_clk; // _PROG_AUDIO_DLY_CLK_0, 0x49c
u32 audio_sync_clk_i2s0; // _AUDIO_SYNC_CLK_I2S0_0, 0x4a0
u32 audio_sync_clk_i2s1; // _AUDIO_SYNC_CLK_I2S1_0, 0x4a4
u32 audio_sync_clk_i2s2; // _AUDIO_SYNC_CLK_I2S2_0, 0x4a8
u32 audio_sync_clk_i2s3; // _AUDIO_SYNC_CLK_I2S3_0, 0x4ac
u32 audio_sync_clk_i2s4; // _AUDIO_SYNC_CLK_I2S4_0, 0x4b0
u32 audio_sync_clk_spdif; // _AUDIO_SYNC_CLK_SPDIF_0, 0x4b4
u32 plld2_base; // _PLLD2_BASE_0, 0x4b8
u32 plld2_misc; // _PLLD2_MISC_0, 0x4bc
u32 utmip_pll_cfg3; // _UTMIP_PLL_CFG3_0, 0x4c0
u32 pllrefe_base; // _PLLREFE_BASE_0, 0x4c4
u32 pllrefe_misc; // _PLLREFE_MISC_0, 0x4c8
u32 pllrefe_out; // _PLLREFE_OUT_0, 0x4cc
u32 cpu_finetrim_byp; // _CPU_FINETRIM_BYP_0, 0x4d0
u32 cpu_finetrim_select; // _CPU_FINETRIM_SELECT_0, 0x4d4
u32 cpu_finetrim_dr; // _CPU_FINETRIM_DR_0, 0x4d8
u32 cpu_finetrim_df; // _CPU_FINETRIM_DF_0, 0x4dc
u32 cpu_finetrim_f; // _CPU_FINETRIM_F_0, 0x4e0
u32 cpu_finetrim_r; // _CPU_FINETRIM_R_0, 0x4e4
u32 pllc2_base; // _PLLC2_BASE_0, 0x4e8
u32 pllc2_misc0; // _PLLC2_MISC_0_0, 0x4ec
u32 pllc2_misc1; // _PLLC2_MISC_1_0, 0x4f0
u32 pllc2_misc2; // _PLLC2_MISC_2_0, 0x4f4
u32 pllc2_misc3; // _PLLC2_MISC_3_0, 0x4f8
u32 pllc3_base; // _PLLC3_BASE_0, 0x4fc
u32 pllc3_misc0; // _PLLC3_MISC_0_0, 0x500
u32 pllc3_misc1; // _PLLC3_MISC_1_0, 0x504
u32 pllc3_misc2; // _PLLC3_MISC_2_0, 0x508
u32 pllc3_misc3; // _PLLC3_MISC_3_0, 0x50c
u32 pllx_misc1; // _PLLX_MISC_1_0, 0x510
u32 pllx_misc2; // _PLLX_MISC_2_0, 0x514
u32 pllx_misc3; // _PLLX_MISC_3_0, 0x518
u32 xusbio_pll_cfg0; // _XUSBIO_PLL_CFG0_0, 0x51c
u32 xusbio_pll_cfg1; // _XUSBIO_PLL_CFG0_1, 0x520
u32 plle_aux1; // _PLLE_AUX1_0, 0x524
u32 pllp_reshift; // _PLLP_RESHIFT_0, 0x528
u32 utmipll_hw_pwrdn_cfg0; // _UTMIPLL_HW_PWRDN_CFG0_0, 0x52c
u32 pllu_hw_pwrdn_cfg0; // _PLLU_HW_PWRDN_CFG0_0, 0x530
u32 xusb_pll_cfg0; // _XUSB_PLL_CFG0_0, 0x534
u32 _0x538;
u32 clk_cpu_misc; // _CLK_CPU_MISC_0, 0x53c
u32 clk_cpug_misc; // _CLK_CPUG_MISC_0, 0x540
u32 clk_cpulp_misc; // _CLK_CPULP_MISC_0, 0x544
u32 pllx_hw_ctrl_cfg; // _PLLX_HW_CTRL_CFG_0, 0x548
u32 pllx_sw_ramp_cfg; // _PLLX_SW_RAMP_CFG_0, 0x54c
u32 pllx_hw_ctrl_status; // _PLLX_HW_CTRL_STATUS_0, 0x550
u32 lvl2_clk_gate_ovre; // _LVL2_CLK_GATE_OVRE, 0x554
u32 super_gr3d_clk_div; // _SUPER_GR3D_CLK_DIVIDER_0, 0x558
u32 spare_reg0; // _SPARE_REG0_0, 0x55c
u32 audio_sync_clk_dmic1; // _AUDIO_SYNC_CLK_DMIC1_0, 0x560
u32 audio_sync_clk_dmic2; // _AUDIO_SYNC_CLK_DMIC2_0, 0x564
u32 _0x568[2];
u32 plld2_ss_cfg; // _PLLD2_SS_CFG, 0x570
u32 plld2_ss_ctrl1; // _PLLD2_SS_CTRL1_0, 0x574
u32 plld2_ss_ctrl2; // _PLLD2_SS_CTRL2_0, 0x578
u32 _0x57c[5];
u32 plldp_base; // _PLLDP_BASE, 0x590
u32 plldp_misc; // _PLLDP_MISC, 0x594
u32 plldp_ss_cfg; // _PLLDP_SS_CFG, 0x598
u32 plldp_ss_ctrl1; // _PLLDP_SS_CTRL1_0, 0x59c
u32 plldp_ss_ctrl2; // _PLLDP_SS_CTRL2_0, 0x5a0
u32 pllc4_base; // _PLLC4_BASE_0, 0x5a4
u32 pllc4_misc; // _PLLC4_MISC_0, 0x5a8
u32 _0x5ac[6];
u32 clk_spare0; // _CLK_SPARE0_0, 0x5c4
u32 clk_spare1; // _CLK_SPARE1_0, 0x5c8
u32 gpu_isob_ctrl; // _GPU_ISOB_CTRL_0, 0x5cc
u32 pllc_misc2; // _PLLC_MISC_2_0, 0x5d0
u32 pllc_misc3; // _PLLC_MISC_3_0, 0x5d4
u32 plla_misc2; // _PLLA_MISC2_0, 0x5d8
u32 _0x5dc[2];
u32 pllc4_out; // _PLLC4_OUT_0, 0x5e4
u32 pllmb_base; // _PLLMB_BASE_0, 0x5e8
u32 pllmb_misc1; // _PLLMB_MISC1_0, 0x5ec
u32 pllx_misc4; // _PLLX_MISC_4_0, 0x5f0
u32 pllx_misc5; // _PLLX_MISC_5_0, 0x5f4
u32 _0x5f8[2];
u32 clk_source_xusb_core_host; // _CLK_SOURCE_XUSB_CORE_HOST_0, 0x600
u32 clk_source_xusb_falcon; // _CLK_SOURCE_XUSB_FALCON_0, 0x604
u32 clk_source_xusb_fs; // _CLK_SOURCE_XUSB_FS_0, 0x608
u32 clk_source_xusb_core_dev; // _CLK_SOURCE_XUSB_CORE_DEV_0, 0x60c
u32 clk_source_xusb_ss; // _CLK_SOURCE_XUSB_SS_0, 0x610
u32 clk_source_cilab; // _CLK_SOURCE_CILAB_0, 0x614
u32 clk_source_cilcd; // _CLK_SOURCE_CILCD_0, 0x618
u32 clk_source_cilef; // _CLK_SOURCE_CILEF_0, 0x61c
u32 clk_source_dsia_lp; // _CLK_SOURCE_DSIA_LP_0, 0x620
u32 clk_source_dsib_lp; // _CLK_SOURCE_DSIB_LP_0, 0x624
u32 clk_source_entropy; // _CLK_SOURCE_ENTROPY_0, 0x628
u32 clk_source_dvfs_ref; // _CLK_SOURCE_DVFS_REF_0, 0x62c
u32 clk_source_dvfs_soc; // _CLK_SOURCE_DVFS_SOC_0, 0x630
u32 _0x634[3];
u32 clk_source_emc_latency; // _CLK_SOURCE_EMC_LATENCY_0, 0x640
u32 clk_source_soc_therm; // _CLK_SOURCE_SOC_THERM_0, 0x644
u32 _0x648;
u32 clk_source_dmic1; // _CLK_SOURCE_DMIC1_0, 0x64c
u32 clk_source_dmic2; // _CLK_SOURCE_DMIC2_0, 0x650
u32 _0x654;
u32 clk_source_vi_sensor2; // _CLK_SOURCE_VI_SENSOR2_0, 0x658
u32 clk_source_i2c6; // _CLK_SOURCE_I2C6_0, 0x65c
u32 clk_source_mipibif; // _CLK_SOURCE_MIPIBIF_0, 0x660
u32 clk_source_emc_dll; // _CLK_SOURCE_EMC_DLL_0, 0x664
u32 _0x668;
u32 clk_source_uart_fst_mipi_cal; // _CLK_SOURCE_UART_FST_MIPI_CAL_0, 0x66c
u32 _0x670[2];
u32 clk_source_vic; // _CLK_SOURCE_VIC_0, 0x678
u32 pllp_outc; // _PLLP_OUTC_0, 0x67c
u32 pllp_misc1; // _PLLP_MISC1_0, 0x680
u32 _0x684[2];
u32 emc_div_clk_shaper_ctrl; // _EMC_DIV_CLK_SHAPER_CTRL_0, 0x68c
u32 emc_pllc_shaper_ctrl; // _EMC_PLLC_SHAPER_CTRL_0, 0x690
u32 clk_source_sdmmc_legacy_tm; // _CLK_SOURCE_SDMMC_LEGACY_TM_0, 0x694
u32 clk_source_nvdec; // _CLK_SOURCE_NVDEC_0, 0x698
u32 clk_source_nvjpg; // _CLK_SOURCE_NVJPG_0, 0x69c
u32 clk_source_nvenc; // _CLK_SOURCE_NVENC_0, 0x6a0
u32 plla1_base; // _PLLA1_BASE_0, 0x6a4
u32 plla1_misc0; // _PLLA1_MISC_0_0, 0x6a8
u32 plla1_misc1; // _PLLA1_MISC_1_0, 0x6ac
u32 plla1_misc2; // _PLLA1_MISC_2_0, 0x6b0
u32 plla1_misc3; // _PLLA1_MISC_3_0, 0x6b4
u32 audio_sync_clk_dmic3; // _AUDIO_SYNC_CLK_DMIC3_0, 0x6b8
u32 clk_source_dmic3; // _CLK_SOURCE_DMIC3_0, 0x6bc
u32 clk_source_ape; // _CLK_SOURCE_APE_0, 0x6c0
u32 clk_source_qspi; // _CLK_SOURCE_QSPI_0, 0x6c4
u32 clk_source_vi_i2c; // _CLK_SOURCE_VI_I2C_0, 0x6c8
u32 clk_source_usb2_hsic_trk; // _CLK_SOURCE_USB2_HSIC_TRK_0, 0x6cc
u32 clk_source_pex_sata_usb_rx_byp; // _CLK_SOURCE_PEX_SATA_USB_RX_BYP_0, 0x6d0
u32 clk_source_maud; // _CLK_SOURCE_MAUD_0, 0x6d4
u32 clk_source_tsecb; // _CLK_SOURCE_TSECB_0, 0x6d8
u32 clk_cpug_misc1; // _CLK_CPUG_MISC1_0, 0x6dc
u32 aclk_burst_policy; // _ACLK_BURST_POLICY_0, 0x6e0
u32 super_aclk_divider; // _SUPER_ACLK_DIVIDER_0, 0x6e4
u32 nvenc_super_clk_divider; // _NVENC_SUPER_CLK_DIVIDER_0, 0x6e8
u32 vi_super_clk_divider; // _VI_SUPER_CLK_DIVIDER_0, 0x6ec
u32 vic_super_clk_divider; // _VIC_SUPER_CLK_DIVIDER_0, 0x6f0
u32 nvdec_super_clk_divider; // _NVDEC_SUPER_CLK_DIVIDER_0, 0x6f4
u32 isp_super_clk_divider; // _ISP_SUPER_CLK_DIVIDER_0, 0x6f8
u32 ispb_super_clk_divider; // _ISPB_SUPER_CLK_DIVIDER_0, 0x6fc
u32 nvjpg_super_clk_divider; // _NVJPG_SUPER_CLK_DIVIDER_0, 0x700
u32 se_super_clk_divider; // _SE_SUPER_CLK_DIVIDER_0, 0x704
u32 tsec_super_clk_divider; // _TSEC_SUPER_CLK_DIVIDER_0, 0x708
u32 tsecb_super_clk_divider; // _TSECB_SUPER_CLK_DIVIDER_0, 0x70c
u32 clk_source_uartape; // _CLK_SOURCE_UARTAPE_0, 0x710
u32 clk_cpug_misc2; // _CLK_CPUG_MISC2_0, 0x714
u32 clk_source_dbgapb; // _CLK_SOURCE_DBGAPB_0, 0x718
u32 clk_ccplex_cc4_ret_clk_enb; // _CLK_CCPLEX_CC4_RET_CLK_ENB_0, 0x71c
u32 actmon_cpu_clk; // _ACTMON_CPU_CLK_0, 0x720
u32 clk_source_emc_safe; // _CLK_SOURCE_EMC_SAFE_0, 0x724
u32 sdmmc2_pllc4_out0_shaper_ctrl; // _SDMMC2_PLLC4_OUT0_SHAPER_CTRL_0, 0x728
u32 sdmmc2_pllc4_out1_shaper_ctrl; // _SDMMC2_PLLC4_OUT1_SHAPER_CTRL_0, 0x72c
u32 sdmmc2_pllc4_out2_shaper_ctrl; // _SDMMC2_PLLC4_OUT2_SHAPER_CTRL_0, 0x730
u32 sdmmc2_div_clk_shaper_ctrl; // _SDMMC2_DIV_CLK_SHAPER_CTRL_0, 0x734
u32 sdmmc4_pllc4_out0_shaper_ctrl; // _SDMMC4_PLLC4_OUT0_SHAPER_CTRL_0, 0x738
u32 sdmmc4_pllc4_out1_shaper_ctrl; // _SDMMC4_PLLC4_OUT1_SHAPER_CTRL_0, 0x73c
u32 sdmmc4_pllc4_out2_shaper_ctrl; // _SDMMC4_PLLC4_OUT2_SHAPER_CTRL_0, 0x740
u32 sdmmc4_div_clk_shaper_ctrl; // _SDMMC4_DIV_CLK_SHAPER_CTRL_0, 0x744
};
static_assert(std::is_standard_layout_v<Registers>);
static_assert(std::is_trivial_v<Registers>);
private:
static constexpr u32 clkRegOffsets[] = { 0x010, 0x014, 0x018, 0x360, 0x364, 0x280, 0x298 };
static constexpr u32 rstRegOffsets[] = { 0x004, 0x008, 0x00C, 0x358, 0x35C, 0x28C, 0x2A4 };
private:
volatile Registers *m_regs = nullptr;
// TODO friend
private:
vu32 *RegisterAt(u32 offset) const
{
return reinterpret_cast<vu32 *>(reinterpret_cast<uintptr_t>(m_regs) + offset);
}
public:
union Device {
struct {
u32 bank : 3;
u32 bitPos : 5;
u32 regOffset : 12;
u32 value : 3;
u32 : 0;
u32 divisor : 16;
};
u64 raw;
};
static_assert(std::is_standard_layout_v<Device>);
static_assert(std::is_trivial_v<Device>);
static_assert(sizeof(Device) == 8);
static constexpr Device uartA = {{
.bank = 5,
.bitPos = 6,
.regOffset = 0x178,
.value = 0,
.divisor = 0,
}};
static constexpr Device uartB = {{
.bank = 0,
.bitPos = 7,
.regOffset = 0x17C,
.value = 0,
.divisor = 0,
}};
static constexpr Device uartC = {{
.bank = 1,
.bitPos = 23,
.regOffset = 0x1A0,
.value = 0,
.divisor = 0,
}};
static constexpr Device uartD = {{
.bank = 2,
.bitPos = 1,
.regOffset = 0x1C0,
.value = 0,
.divisor = 0,
}};
static constexpr Device i2c1 = {{
.bank = 0,
.bitPos = 12,
.regOffset = 0x124,
.value = 6,
.divisor = 0,
}};
static constexpr Device i2c5 = {{
.bank = 1,
.bitPos = 15,
.regOffset = 0x128,
.value = 6,
.divisor = 0,
}};
static constexpr Device tzram = {{
.bank = 3,
.bitPos = 30,
}};
static constexpr Device se = {{
.bank = 3,
.bitPos = 31,
.regOffset = 0x42C,
.value = 0,
.divisor = 0,
}};
static constexpr Device host1x = {{
.bank = 0,
.bitPos = 28,
.regOffset = 0x180,
.value = 4,
.divisor = 3,
}};
static constexpr Device tsec = {{
.bank = 2,
.bitPos = 19,
.regOffset = 0x1F4,
.value = 0,
.divisor = 2,
}};
static constexpr Device sorSafe = {{
.bank = 6,
.bitPos = 30,
}};
static constexpr Device sor0 = {{
.bank = 5,
.bitPos = 22,
}};
static constexpr Device sor1 = {{
.bank = 5,
.bitPos = 30,
.regOffset = 0x410,
.value = 0,
.divisor = 2,
}};
static constexpr Device kfuse = {{
.bank = 1,
.bitPos = 8,
}};
static constexpr Device clDvfs = {{
.bank = 4,
.bitPos = 27,
}};
static constexpr Device bpmp = {{
.bank = 0,
.bitPos = 1,
}};
static constexpr Device actmon = {{
.bank = 3,
.bitPos = 23,
.regOffset = 0x3E8,
.value = 6,
.divisor = 0,
}};
static constexpr Device coresight = {{
.bank = 2,
.bitPos = 9,
.regOffset = 0x1D4,
.value = 0,
.divisor = 4,
}};
public:
void EnableClock(Device dev) const
{
// Configure default PLL and divisor
if (dev.regOffset != 0) {
*RegisterAt(dev.regOffset) = dev.value << 29 | dev.divisor;
}
// Enable the clock
*RegisterAt(clkRegOffsets[dev.bank]) |= BIT(dev.bitPos);
}
void DisableClock(Device dev) const
{
*RegisterAt(clkRegOffsets[dev.bank]) &= ~BIT(dev.bitPos);
}
void EnableReset(Device dev) const
{
*RegisterAt(rstRegOffsets[dev.bank]) |= BIT(dev.bitPos);
}
void DisableReset(Device dev) const
{
*RegisterAt(rstRegOffsets[dev.bank]) &= ~BIT(dev.bitPos);
}
void Enable(Device dev) const
{
EnableClock(dev);
DisableReset(dev);
}
void Disable(Device dev) const
{
EnableReset(dev);
DisableClock(dev);
}
void Reboot(Device dev) const {
Disable(dev);
// KFUSE needs a workaround
/*if (dev.raw == kfuse.raw) {
EnableClock(dev);
// Wait 100us
DisableReset(dev);
// Wait 200us
}*/
Enable(dev);
}
void SetFuseRegsEnabled(bool enabled) const
{
u32 mask = enabled ? BIT(28) : 0;
m_regs->misc_clk_enb = (m_regs->misc_clk_enb & ~BIT(28)) | mask;
}
};
}

View File

@@ -1,207 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "../../../defines.hpp"
namespace ams::hvisor::drivers::tegra::t210 {
class Gpio final {
private:
static constexpr size_t numBanks = 8;
static constexpr size_t portsPerBank = 4;
struct Bank {
u32 cnf[portsPerBank];
u32 oe[portsPerBank];
u32 out[portsPerBank];
u32 in[portsPerBank];
u32 int_sta[portsPerBank];
u32 int_enb[portsPerBank];
u32 int_lvl[portsPerBank];
u32 int_clr[portsPerBank];
u32 msk_cnf[portsPerBank];
u32 msk_oe[portsPerBank];
u32 msk_out[portsPerBank];
u32 db_ctrl_p[portsPerBank];
u32 msk_int_sta[portsPerBank];
u32 msk_int_enb[portsPerBank];
u32 msk_int_lvl[portsPerBank];
u32 db_cnt_p[portsPerBank];
};
struct Registers {
Bank bank[numBanks];
};
static_assert(std::is_standard_layout_v<Registers>);
static_assert(std::is_trivial_v<Registers>);
public:
enum Port {
PORT_A = 0,
PORT_B = 1,
PORT_C = 2,
PORT_D = 3,
PORT_E = 4,
PORT_F = 5,
PORT_G = 6,
PORT_H = 7,
PORT_I = 8,
PORT_J = 9,
PORT_K = 10,
PORT_L = 11,
PORT_M = 12,
PORT_N = 13,
PORT_O = 14,
PORT_P = 15,
PORT_Q = 16,
PORT_R = 17,
PORT_S = 18,
PORT_T = 19,
PORT_U = 20,
PORT_V = 21,
PORT_W = 22,
PORT_X = 23,
PORT_Y = 24,
PORT_Z = 25,
PORT_AA = 26,
PORT_BB = 27,
PORT_CC = 28,
PORT_DD = 29,
PORT_EE = 30,
PORT_FF = 31,
};
enum class Mode {
Sfio = 0,
Gpio = 1,
};
enum class Direction {
Tristate = 0, // Input
Driven = 1, // Output
Input = Tristate,
Output = Driven,
};
enum class Level {
Low = 0,
High = 1,
};
private:
// For msk_* fields
static constexpr u32 MakeMaskedWriteValue(u32 pos, bool value)
{
return BIT(8 + pos) | ((value ? 1 : 0) << pos);
}
static constexpr u32 MakeMaskedWriteValueContiguous(u32 pos, size_t n, bool value)
{
u32 msk = MASK2(pos + n - 1, pos);
return (msk << 8) | (value ? msk : 0);
}
private:
struct Pin {
Port port;
u8 pos;
};
static_assert(std::is_standard_layout_v<Pin>);
static_assert(std::is_trivial_v<Pin>);
static_assert(sizeof(Pin) <= 8);
public:
static constexpr Pin uart3Tx = {PORT_D, 1};
static constexpr Pin uart3Rx = {PORT_D, 2};
static constexpr Pin uart3Rts = {PORT_D, 3};
static constexpr Pin uart3Cts = {PORT_D, 4};
static constexpr Pin uart2Tx = {PORT_G, 0};
static constexpr Pin uart2Rx = {PORT_G, 1};
static constexpr Pin uart2Rts = {PORT_G, 2};
static constexpr Pin uart2Cts = {PORT_G, 3};
static constexpr Pin uart4Tx = {PORT_I, 4};
static constexpr Pin uart4Rx = {PORT_I, 5};
static constexpr Pin uart4Rts = {PORT_I, 6};
static constexpr Pin uart4Cts = {PORT_I, 7};
static constexpr Pin uart1Tx = {PORT_U, 0};
static constexpr Pin uart1Rx = {PORT_U, 1};
static constexpr Pin uart1Rts = {PORT_U, 2};
static constexpr Pin uart1Cts = {PORT_U, 3};
static constexpr Pin volUp = {PORT_X, 6};
static constexpr Pin volDown = {PORT_X, 7};
static constexpr Pin microSdCardDetect = {PORT_Z, 1};
static constexpr Pin microSdWriteProtect = {PORT_Z, 4};
static constexpr Pin microSdSupplyEnable = {PORT_E, 4};
static constexpr Pin lcdBlP5v = {PORT_I, 0};
static constexpr Pin lcdBlN5v = {PORT_I, 1};
static constexpr Pin lcdBlPwm = {PORT_V, 0};
static constexpr Pin lcdBlEn = {PORT_V, 1};
static constexpr Pin lcdBlRst = {PORT_V, 2};
private:
volatile Registers *m_regs = nullptr;
public:
void SetMode(Pin pin, Mode mode) const
{
m_regs->bank[pin.port / portsPerBank].msk_cnf[pin.port % portsPerBank] = MakeMaskedWriteValue(pin.pos, mode == Mode::Gpio);
}
void SetModeContiguous(Pin pin, size_t n, Mode mode) const
{
m_regs->bank[pin.port / portsPerBank].msk_cnf[pin.port % portsPerBank] = MakeMaskedWriteValueContiguous(pin.pos, n, mode == Mode::Gpio);
}
// Only valid for GPIO (not SFIO)
void SetDirection(Pin pin, Direction direction) const
{
m_regs->bank[pin.port / portsPerBank].msk_oe[pin.port % portsPerBank] = MakeMaskedWriteValue(pin.pos, direction == Direction::Output);
}
// Only valid for GPIO (not SFIO)
void SetDirectionContiguous(Pin pin, size_t n, Direction direction) const
{
m_regs->bank[pin.port / portsPerBank].msk_oe[pin.port % portsPerBank] = MakeMaskedWriteValueContiguous(pin.pos, n, direction == Direction::Output);
}
// Only valid for GPIO (not SFIO)
void Write(Pin pin, Level level) const
{
m_regs->bank[pin.port / portsPerBank].msk_out[pin.port % portsPerBank] = MakeMaskedWriteValue(pin.pos, level == Level::High);
}
// Only valid for GPIO (not SFIO)
Level Read(Pin pin) const
{
return static_cast<Level>((m_regs->bank[pin.port / portsPerBank].in[pin.port % portsPerBank] >> pin.pos) & 1);
}
void ConfigureUartPins()
{
constexpr Pin uartPins[] = {uart1Tx, uart2Tx, uart3Tx, uart4Tx};
// Set SFIO to all the 4 contiguous pins (tx, rx, rts, cts)
for (Pin pin : uartPins) {
SetModeContiguous(pin, 4, Mode::Sfio);
}
}
};
}

View File

@@ -1,248 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "../../../defines.hpp"
namespace ams::hvisor::drivers::tegra::t210 {
class Pinmux final {
private:
struct Registers {
u32 sdmmc1_clk;
u32 sdmmc1_cmd;
u32 sdmmc1_dat3;
u32 sdmmc1_dat2;
u32 sdmmc1_dat1;
u32 sdmmc1_dat0;
u32 _r18;
u32 sdmmc3_clk;
u32 sdmmc3_cmd;
u32 sdmmc3_dat0;
u32 sdmmc3_dat1;
u32 sdmmc3_dat2;
u32 sdmmc3_dat3;
u32 _r34;
u32 pex_l0_rst_n;
u32 pex_l0_clkreq_n;
u32 pex_wake_n;
u32 pex_l1_rst_n;
u32 pex_l1_clkreq_n;
u32 sata_led_active;
u32 spi1_mosi;
u32 spi1_miso;
u32 spi1_sck;
u32 spi1_cs0;
u32 spi1_cs1;
u32 spi2_mosi;
u32 spi2_miso;
u32 spi2_sck;
u32 spi2_cs0;
u32 spi2_cs1;
u32 spi4_mosi;
u32 spi4_miso;
u32 spi4_sck;
u32 spi4_cs0;
u32 qspi_sck;
u32 qspi_cs_n;
u32 qspi_io0;
u32 qspi_io1;
u32 qspi_io2;
u32 qspi_io3;
u32 _ra0;
u32 dmic1_clk;
u32 dmic1_dat;
u32 dmic2_clk;
u32 dmic2_dat;
u32 dmic3_clk;
u32 dmic3_dat;
u32 gen1_i2c_scl;
u32 gen1_i2c_sda;
u32 gen2_i2c_scl;
u32 gen2_i2c_sda;
u32 gen3_i2c_scl;
u32 gen3_i2c_sda;
u32 cam_i2c_scl;
u32 cam_i2c_sda;
u32 pwr_i2c_scl;
u32 pwr_i2c_sda;
u32 uart1_tx;
u32 uart1_rx;
u32 uart1_rts;
u32 uart1_cts;
u32 uart2_tx;
u32 uart2_rx;
u32 uart2_rts;
u32 uart2_cts;
u32 uart3_tx;
u32 uart3_rx;
u32 uart3_rts;
u32 uart3_cts;
u32 uart4_tx;
u32 uart4_rx;
u32 uart4_rts;
u32 uart4_cts;
u32 dap1_fs;
u32 dap1_din;
u32 dap1_dout;
u32 dap1_sclk;
u32 dap2_fs;
u32 dap2_din;
u32 dap2_dout;
u32 dap2_sclk;
u32 dap4_fs;
u32 dap4_din;
u32 dap4_dout;
u32 dap4_sclk;
u32 cam1_mclk;
u32 cam2_mclk;
u32 jtag_rtck;
u32 clk_32k_in;
u32 clk_32k_out;
u32 batt_bcl;
u32 clk_req;
u32 cpu_pwr_req;
u32 pwr_int_n;
u32 shutdown;
u32 core_pwr_req;
u32 aud_mclk;
u32 dvfs_pwm;
u32 dvfs_clk;
u32 gpio_x1_aud;
u32 gpio_x3_aud;
u32 pcc7;
u32 hdmi_cec;
u32 hdmi_int_dp_hpd;
u32 spdif_out;
u32 spdif_in;
u32 usb_vbus_en0;
u32 usb_vbus_en1;
u32 dp_hpd0;
u32 wifi_en;
u32 wifi_rst;
u32 wifi_wake_ap;
u32 ap_wake_bt;
u32 bt_rst;
u32 bt_wake_ap;
u32 ap_wake_nfc;
u32 nfc_en;
u32 nfc_int;
u32 gps_en;
u32 gps_rst;
u32 cam_rst;
u32 cam_af_en;
u32 cam_flash_en;
u32 cam1_pwdn;
u32 cam2_pwdn;
u32 cam1_strobe;
u32 lcd_te;
u32 lcd_bl_pwm;
u32 lcd_bl_en;
u32 lcd_rst;
u32 lcd_gpio1;
u32 lcd_gpio2;
u32 ap_ready;
u32 touch_rst;
u32 touch_clk;
u32 modem_wake_ap;
u32 touch_int;
u32 motion_int;
u32 als_prox_int;
u32 temp_alert;
u32 button_power_on;
u32 button_vol_up;
u32 button_vol_down;
u32 button_slide_sw;
u32 button_home;
u32 pa6;
u32 pe6;
u32 pe7;
u32 ph6;
u32 pk0;
u32 pk1;
u32 pk2;
u32 pk3;
u32 pk4;
u32 pk5;
u32 pk6;
u32 pk7;
u32 pl0;
u32 pl1;
u32 pz0;
u32 pz1;
u32 pz2;
u32 pz3;
u32 pz4;
u32 pz5;
};
static_assert(std::is_standard_layout_v<Registers>);
static_assert(std::is_trivial_v<Registers>);
enum Flags : u32 {
PREEMP_ENABLED = BIT(15),
DRIVE_1X = 0 << 13,
DRIVE_2X = 1 << 13,
DRIVE_3X = 2 << 13,
DRIVE_4X = 3 << 13,
SCHMT_ENABLED = BIT(12),
OD_ENABLED = BIT(11),
IO_HV_ENABLED = BIT(10),
HSM_ENABLED = BIT(9),
LPDR_ENABLED = BIT(8),
LOCKED = BIT(7),
INPUT = BIT(6),
PARKED = BIT(5),
TRISTATE = BIT(4),
PULL_NONE = 0 << 2,
PULL_DOWN = 1 << 2,
PULL_UP = 2 << 2,
SELECT_FUNCTION0 = 0 << 0,
SELECT_FUNCTION1 = 1 << 0,
SELECT_FUNCTION2 = 2 << 0,
SELECT_FUNCTION3 = 3 << 0,
};
private:
// TODO friend
volatile Registers *m_regs = nullptr;
public:
void ConfigureUartPins() const
{
m_regs->uart1_tx = 0 | 0 | PULL_NONE | SELECT_FUNCTION0;
m_regs->uart1_rx = INPUT | TRISTATE | PULL_UP | SELECT_FUNCTION0;
m_regs->uart1_rts = 0 | 0 | PULL_NONE | SELECT_FUNCTION0;
m_regs->uart1_cts = INPUT | TRISTATE | PULL_DOWN | SELECT_FUNCTION0;
m_regs->uart2_tx = 0 | 0 | PULL_NONE | SELECT_FUNCTION0;
m_regs->uart2_rx = INPUT | TRISTATE | PULL_NONE | SELECT_FUNCTION0;
m_regs->uart2_rts = 0 | 0 | PULL_DOWN | SELECT_FUNCTION0;
m_regs->uart2_cts = INPUT | TRISTATE | PULL_NONE | SELECT_FUNCTION0;
m_regs->uart3_tx = 0 | 0 | PULL_NONE | SELECT_FUNCTION0;
m_regs->uart3_rx = INPUT | TRISTATE | PULL_NONE | SELECT_FUNCTION0;
m_regs->uart3_rts = 0 | 0 | PULL_DOWN | SELECT_FUNCTION0;
m_regs->uart3_cts = INPUT | TRISTATE | PULL_NONE | SELECT_FUNCTION0;
m_regs->uart4_tx = 0 | 0 | PULL_DOWN | SELECT_FUNCTION0;
m_regs->uart4_rx = INPUT | TRISTATE | PULL_DOWN | SELECT_FUNCTION0;
m_regs->uart4_cts = 0 | 0 | PULL_DOWN | SELECT_FUNCTION0;
m_regs->uart4_rts = INPUT | TRISTATE | PULL_DOWN | SELECT_FUNCTION0;
}
};
}

View File

@@ -1,375 +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 "asm_macros.s"
/* Some macros taken from https://github.com/ARM-software/arm-trusted-firmware/blob/master/include/common/aarch64/asm_macros.S */
/*
* Copyright (c) 2013-2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* Declare the exception vector table, enforcing it is aligned on a
* 2KB boundary, as required by the ARMv8 architecture.
* Use zero bytes as the fill value to be stored in the padding bytes
* so that it inserts illegal AArch64 instructions. This increases
* security, robustness and potentially facilitates debugging.
*/
.macro vector_base label, section_name=.vectors
.section \section_name, "ax"
.align 11, 0
\label:
.endm
/*
* Create an entry in the exception vector table, enforcing it is
* aligned on a 128-byte boundary, as required by the ARMv8 architecture.
* Use zero bytes as the fill value to be stored in the padding bytes
* so that it inserts illegal AArch64 instructions. This increases
* security, robustness and potentially facilitates debugging.
*/
.macro vector_entry label, section_name=.vectors
.cfi_sections .debug_frame
.section \section_name, "ax"
.align 7, 0
.type \label, %function
.func \label
.cfi_startproc
\label:
.endm
/*
* This macro verifies that the given vector doesnt exceed the
* architectural limit of 32 instructions. This is meant to be placed
* immediately after the last instruction in the vector. It takes the
* vector entry as the parameter
*/
.macro check_vector_size since
.endfunc
.cfi_endproc
.if (. - \since) > (32 * 4)
.error "Vector exceeds 32 instructions"
.endif
.endm
.macro SAVE_MOST_REGISTERS
sub sp, sp, #EXCEP_STACK_FRAME_SIZE
stp x28, x29, [sp, #-0x20]
stp x30, xzr, [sp, #-0x10]
mrs x28, far_el2
mrs x29, cntpct_el0
bl _saveMostRegisters
.endm
.macro PIVOT_STACK_FOR_CRASH
// Note: replace sp_el1 with crashing sp (for convenience)
// (sp_el2 is not accessible at el2)
msr spsel, #0
stp x0, x1, [sp, #-0x10]
mov x0, sp
msr spsel, #1
mov x1, sp
mov sp, x0
msr sp_el1, x1
ldp x0, x1, [sp, #-0x10]
.endm
#define EXCEPTION_TYPE_HOST 0
#define EXCEPTION_TYPE_GUEST 1
#define EXCEPTION_TYPE_HOST_CRASH 2
.macro EXCEPTION_HANDLER_START name, type
vector_entry \name
.if \type == EXCEPTION_TYPE_HOST_CRASH
PIVOT_STACK_FOR_CRASH
.endif
SAVE_MOST_REGISTERS
mov x0, sp
.if \type == EXCEPTION_TYPE_GUEST
ldp x18, x19, [sp, #EXCEP_STACK_FRAME_SIZE]
msr sp_el0, x19
prfm pstl1keep, [x18]
mov w1, #1
.else
mov w1, #0
.endif
// ams::hvisor::ExceptionEntryPostprocess(ams::hvisor::ExceptionStackFrame*, bool)
bl _ZN3ams6hvisor25ExceptionEntryPostprocessEPNS0_19ExceptionStackFrameEb
.endm
.macro EXCEPTION_HANDLER_END name, type
.if \type != EXCEPTION_TYPE_HOST_CRASH
mov x0, sp
// ams::hvisor::ExceptionReturnPreprocess(ams::hvisor::ExceptionStackFrame*)
bl _ZN3ams6hvisor25ExceptionReturnPreprocessEPNS0_19ExceptionStackFrameE
b _restoreAllRegisters
.else
b .
.endif
check_vector_size \name
.endm
.macro UNKNOWN_EXCEPTION name
vector_entry \name
bl _unknownException
check_vector_size \name
.endm
/* Actual Vectors for Thermosphere. */
.global g_thermosphereVectors
vector_base g_thermosphereVectors
/* Current EL, SP0 */
vector_entry _synchSp0
// Safecpy
cbz x18, _handleSafecpy
// Used when we enable the MMU
msr elr_el2, x18
// Note: non-broadcasting TLB maintenance op
tlbi alle2
dsb ish
isb
eret
_handleSafecpy:
// Set x16 to 1
mov x16, #1
eret
check_vector_size _synchSp0
_unknownException:
PIVOT_STACK_FOR_CRASH
mov x0, x30
adr x1, g_thermosphereVectors + 4
sub x0, x0, x1
// ams::hvisor::HandleUnknownException(unsigned int)
bl _ZN3ams6hvisor22HandleUnknownExceptionEj
b .
UNKNOWN_EXCEPTION _irqSp0
/* To save space, insert in an unused vector segment. */
_saveMostRegisters:
stp x0, x1, [sp, #0x00]
stp x2, x3, [sp, #0x10]
stp x4, x5, [sp, #0x20]
stp x6, x7, [sp, #0x30]
stp x8, x9, [sp, #0x40]
stp x10, x11, [sp, #0x50]
stp x12, x13, [sp, #0x60]
stp x14, x15, [sp, #0x70]
stp x16, x17, [sp, #0x80]
stp x18, x19, [sp, #0x90]
stp x20, x21, [sp, #0xA0]
stp x22, x23, [sp, #0xB0]
stp x24, x25, [sp, #0xC0]
stp x26, x27, [sp, #0xD0]
mrs x20, sp_el1
mrs x21, sp_el0
mrs x22, elr_el2
mrs x23, spsr_el2
mrs x24, esr_el2
mov x25, x28 // far_el2
mov x26, x29 // cntpct_el0
// See SAVE_MOST_REGISTERS macro
ldp x28, x29, [sp, #-0x20]
ldp x19, xzr, [sp, #-0x10]
stp x28, x29, [sp, #0xE0]
stp x19, x20, [sp, #0xF0]
stp x21, x22, [sp, #0x100]
stp x23, x24, [sp, #0x110]
stp x25, x26, [sp, #0x120]
ret
UNKNOWN_EXCEPTION _fiqSp0
/* To save space, insert in an unused vector segment. */
// Accessed by start.s
.global _restoreAllRegisters
.type _restoreAllRegisters, %function
_restoreAllRegisters:
ldp x30, x20, [sp, #0xF0]
ldp x21, x22, [sp, #0x100]
ldp x23, xzr, [sp, #0x110]
msr sp_el1, x20
msr sp_el0, x21
msr elr_el2, x22
msr spsr_el2, x23
ldp x0, x1, [sp, #0x00]
ldp x2, x3, [sp, #0x10]
ldp x4, x5, [sp, #0x20]
ldp x6, x7, [sp, #0x30]
ldp x8, x9, [sp, #0x40]
ldp x10, x11, [sp, #0x50]
ldp x12, x13, [sp, #0x60]
ldp x14, x15, [sp, #0x70]
ldp x16, x17, [sp, #0x80]
ldp x18, x19, [sp, #0x90]
ldp x20, x21, [sp, #0xA0]
ldp x22, x23, [sp, #0xB0]
ldp x24, x25, [sp, #0xC0]
ldp x26, x27, [sp, #0xD0]
ldp x28, x29, [sp, #0xE0]
add sp, sp, #EXCEP_STACK_FRAME_SIZE
eret
UNKNOWN_EXCEPTION _serrorSp0
// To save space, insert in an unused vector segment.
// ams::hvisor::traps::CallSmc0(ams::hvisor::ExceptionStackFrame*):
.global _ZN3ams6hvisor5traps8CallSmc0EPNS0_19ExceptionStackFrameE
.type _ZN3ams6hvisor5traps8CallSmc0EPNS0_19ExceptionStackFrameE, %function
.func _ZN3ams6hvisor5traps8CallSmc0EPNS0_19ExceptionStackFrameE
.cfi_startproc
.cfi_sections .debug_frame
// ams::hvisor::callSmcTemplate[]
.global _ZN3ams6hvisor5traps15callSmcTemplateE
_ZN3ams6hvisor5traps15callSmcTemplateE:
_ZN3ams6hvisor5traps8CallSmc0EPNS0_19ExceptionStackFrameE:
stp x19, x20, [sp, #-0x10]!
mov x19, x0
ldp x0, x1, [x19, #0x00]
ldp x2, x3, [x19, #0x10]
ldp x4, x5, [x19, #0x20]
ldp x6, x7, [x19, #0x30]
_callSmcTemplateSmcInstruction:
smc #0
// Note that NN's secure monitor can return results in x4-x7, this differs from Arm's spec.
stp x0, x1, [x19, #0x00]
stp x2, x3, [x19, #0x10]
stp x4, x5, [x19, #0x20]
stp x6, x7, [x19, #0x30]
ldp x19, x20, [sp], #0x10
ret
_callSmcTemplateEnd:
.cfi_endproc
.endfunc
// ams::hvisor::traps::callSmcTemplateInstructionOffset
.global _ZN3ams6hvisor5traps32callSmcTemplateInstructionOffsetE
_ZN3ams6hvisor5traps32callSmcTemplateInstructionOffsetE:
.word _callSmcTemplateSmcInstruction - _ZN3ams6hvisor5traps15callSmcTemplateE
// ams::hvisor::traps::callSmcTemplateSize
.global _ZN3ams6hvisor5traps19callSmcTemplateSizeE
_ZN3ams6hvisor5traps19callSmcTemplateSizeE:
.word _callSmcTemplateEnd - _ZN3ams6hvisor5traps15callSmcTemplateE
// ams::hvisor::traps::CallSmc1(ams::hvisor::ExceptionStackFrame*):
.global _ZN3ams6hvisor5traps8CallSmc1EPNS0_19ExceptionStackFrameE
.type _ZN3ams6hvisor5traps8CallSmc1EPNS0_19ExceptionStackFrameE, %function
.func _ZN3ams6hvisor5traps8CallSmc1EPNS0_19ExceptionStackFrameE
.cfi_startproc
.cfi_sections .debug_frame
_ZN3ams6hvisor5traps8CallSmc1EPNS0_19ExceptionStackFrameE:
stp x19, x20, [sp, #-0x10]!
mov x19, x0
ldp x0, x1, [x19, #0x00]
ldp x2, x3, [x19, #0x10]
ldp x4, x5, [x19, #0x20]
ldp x6, x7, [x19, #0x30]
smc #1
// Note that NN's secure monitor can return results in x4-x7, this differs from Arm's spec.
stp x0, x1, [x19, #0x00]
stp x2, x3, [x19, #0x10]
stp x4, x5, [x19, #0x20]
stp x6, x7, [x19, #0x30]
ldp x19, x20, [sp], #0x10
ret
.cfi_endproc
.endfunc
/* Current EL, SPx */
EXCEPTION_HANDLER_START _synchSpx, EXCEPTION_TYPE_HOST
mov x0, sp
// ams::hvisor::HandleSameElSyncException(ams::hvisor::ExceptionStackFrame*):
bl _ZN3ams6hvisor25HandleSameElSyncExceptionEPNS0_19ExceptionStackFrameE
EXCEPTION_HANDLER_END _synchSpx
EXCEPTION_HANDLER_START _irqSpx, EXCEPTION_TYPE_HOST
mov x0, sp
mov w1, #0
mov w2, #0
// ams::hvisor::IrqManager::HandleInterrupt(ams::hvisor::ExceptionStackFrame*):
bl _ZN3ams6hvisor10IrqManager15HandleInterruptEPNS0_19ExceptionStackFrameE
EXCEPTION_HANDLER_END _irqSpx, EXCEPTION_TYPE_HOST
UNKNOWN_EXCEPTION _fiqSpx
UNKNOWN_EXCEPTION _serrorSpx
/* Lower EL, A64 */
EXCEPTION_HANDLER_START _synchA64, EXCEPTION_TYPE_GUEST
mov x0, sp
// ams::hvisor::HandleLowerElSyncException(ams::hvisor::ExceptionStackFrame*)
bl _ZN3ams6hvisor26HandleLowerElSyncExceptionEPNS0_19ExceptionStackFrameE
EXCEPTION_HANDLER_END _synchA64, EXCEPTION_TYPE_GUEST
EXCEPTION_HANDLER_START _irqA64, EXCEPTION_TYPE_GUEST
mov x0, sp
mov w1, #1
mov w2, #0
// ams::hvisor::IrqManager::HandleInterrupt(ams::hvisor::ExceptionStackFrame*):
bl _ZN3ams6hvisor10IrqManager15HandleInterruptEPNS0_19ExceptionStackFrameE
EXCEPTION_HANDLER_END _irqA64, EXCEPTION_TYPE_GUEST
UNKNOWN_EXCEPTION _fiqA64
UNKNOWN_EXCEPTION _serrorA64
/* Lower EL, A32 */
EXCEPTION_HANDLER_START _synchA32, EXCEPTION_TYPE_GUEST
mov x0, sp
// ams::hvisor::HandleLowerElSyncException(ams::hvisor::ExceptionStackFrame*)
bl _ZN3ams6hvisor26HandleLowerElSyncExceptionEPNS0_19ExceptionStackFrameE
EXCEPTION_HANDLER_END _synchA32, EXCEPTION_TYPE_GUEST
EXCEPTION_HANDLER_START _irqA32, EXCEPTION_TYPE_GUEST
mov x0, sp
mov w1, #1
mov w2, #1
// ams::hvisor::IrqManager::HandleInterrupt(ams::hvisor::ExceptionStackFrame*):
bl _ZN3ams6hvisor10IrqManager15HandleInterruptEPNS0_19ExceptionStackFrameE
EXCEPTION_HANDLER_END _irqA32, EXCEPTION_TYPE_GUEST
UNKNOWN_EXCEPTION _fiqA32
UNKNOWN_EXCEPTION _serrorA32

View File

@@ -0,0 +1,108 @@
/*
* Copyright (c) 2018-2020 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 <stdint.h>
#include <stddef.h>
#include "exceptions.h"
#include "lib/printk.h"
/**
* Simple debug function that prints all of our saved registers.
*/
static void print_registers(struct guest_state *regs)
{
// print x0-29
for(int i = 0; i < 30; i += 2) {
printk("x%d:\t0x%p\t", i, regs->x[i]);
printk("x%d:\t0x%p\n", i + 1, regs->x[i + 1]);
}
// print x30; don't bother with x31 (SP), as it's used by the stack that's
// storing this stuff; we really care about the saved SP anyways
printk("x30:\t0x%p\n", regs->x[30]);
// Special registers.
printk("pc:\t0x%p\tcpsr:\t0x%p\n", regs->pc, regs->cpsr);
printk("sp_el1:\t0x%p\tsp_el0:\t0x%p\n", regs->sp_el1, regs->sp_el0);
printk("elr_el1:0x%p\tspsr_el1:0x%p\n", regs->elr_el1, regs->spsr_el1);
// Note that we don't print ESR_EL2, as this isn't really part of the saved state.
}
/**
* Placeholder function that triggers whenever a vector happens we're not
* expecting. Currently prints out some debug information.
*/
void unhandled_vector(struct guest_state *regs)
{
printk("\nAn unexpected vector happened!\n");
print_registers(regs);
printk("\n\n");
}
/**
* Handles an HVC call.
*/
static void handle_hvc(struct guest_state *regs, int call_number)
{
switch(call_number) {
default:
printk("Got a HVC call from 64-bit code.\n");
printk("Calling instruction was: hvc %d\n\n", call_number);
printk("Calling context (you can use these regs as hypercall args!):\n");
print_registers(regs);
printk("\n\n");
break;
}
}
/**
* Placeholder function that triggers whenever a user event triggers a
* synchronous interrupt. Currently, we really only care about 'hvc',
* so that's all we're going to handle here.
*/
void handle_hypercall(struct guest_state *regs)
{
// This is demonstration code.
// In the future, you'd stick your hypercall table here.
switch (regs->esr_el2.ec) {
case HSR_EC_HVC64: {
// Read the hypercall number.
int hvc_nr = regs->esr_el2.iss & 0xFFFF;
// ... and handle the hypercall.
handle_hvc(regs, hvc_nr);
break;
}
default:
printk("Unexpected hypercall! ESR=%p\n", regs->esr_el2.bits);
print_registers(regs);
printk("\n\n");
break;
}
}

View File

@@ -0,0 +1,185 @@
/*
* Copyright (c) 2018-2020 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/>.
*/
#ifndef __EXCEPTION_H__
#define __EXCEPTION_H__
/**
* Borrowed fom Xen (not copyrightable as these are facts).
* Description of the EL2 exception syndrome register.
*/
#define HSR_EC_UNKNOWN 0x00
#define HSR_EC_WFI_WFE 0x01
#define HSR_EC_CP15_32 0x03
#define HSR_EC_CP15_64 0x04
#define HSR_EC_CP14_32 0x05 /* Trapped MCR or MRC access to CP14 */
#define HSR_EC_CP14_DBG 0x06 /* Trapped LDC/STC access to CP14 (only for debug registers) */
#define HSR_EC_CP 0x07 /* HCPTR-trapped access to CP0-CP13 */
#define HSR_EC_CP10 0x08
#define HSR_EC_JAZELLE 0x09
#define HSR_EC_BXJ 0x0a
#define HSR_EC_CP14_64 0x0c
#define HSR_EC_SVC32 0x11
#define HSR_EC_HVC32 0x12
#define HSR_EC_SMC32 0x13
#define HSR_EC_HVC64 0x16
#define HSR_EC_SMC64 0x17
#define HSR_EC_SYSREG 0x18
#define HSR_EC_INSTR_ABORT_LOWER_EL 0x20
#define HSR_EC_INSTR_ABORT_CURR_EL 0x21
#define HSR_EC_DATA_ABORT_LOWER_EL 0x24
#define HSR_EC_DATA_ABORT_CURR_EL 0x25
#define HSR_EC_BRK 0x3c
/**
* Borrowed fom Xen (not copyrightable as these are facts).
* Description of the EL2 exception syndrome register.
*/
union esr {
uint32_t bits;
struct {
unsigned long iss:25; /* Instruction Specific Syndrome */
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
};
/* Common to all conditional exception classes (0x0N, except 0x00). */
struct hsr_cond {
unsigned long iss:20; /* Instruction Specific Syndrome */
unsigned long cc:4; /* Condition Code */
unsigned long ccvalid:1;/* CC Valid */
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
} cond;
struct hsr_wfi_wfe {
unsigned long ti:1; /* Trapped instruction */
unsigned long sbzp:19;
unsigned long cc:4; /* Condition Code */
unsigned long ccvalid:1;/* CC Valid */
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
} wfi_wfe;
/* reg, reg0, reg1 are 4 bits on AArch32, the fifth bit is sbzp. */
struct hsr_cp32 {
unsigned long read:1; /* Direction */
unsigned long crm:4; /* CRm */
unsigned long reg:5; /* Rt */
unsigned long crn:4; /* CRn */
unsigned long op1:3; /* Op1 */
unsigned long op2:3; /* Op2 */
unsigned long cc:4; /* Condition Code */
unsigned long ccvalid:1;/* CC Valid */
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
} cp32; /* HSR_EC_CP15_32, CP14_32, CP10 */
struct hsr_cp64 {
unsigned long read:1; /* Direction */
unsigned long crm:4; /* CRm */
unsigned long reg1:5; /* Rt1 */
unsigned long reg2:5; /* Rt2 */
unsigned long sbzp2:1;
unsigned long op1:4; /* Op1 */
unsigned long cc:4; /* Condition Code */
unsigned long ccvalid:1;/* CC Valid */
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
} cp64; /* HSR_EC_CP15_64, HSR_EC_CP14_64 */
struct hsr_cp {
unsigned long coproc:4; /* Number of coproc accessed */
unsigned long sbz0p:1;
unsigned long tas:1; /* Trapped Advanced SIMD */
unsigned long res0:14;
unsigned long cc:4; /* Condition Code */
unsigned long ccvalid:1;/* CC Valid */
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
} cp; /* HSR_EC_CP */
struct hsr_sysreg {
unsigned long read:1; /* Direction */
unsigned long crm:4; /* CRm */
unsigned long reg:5; /* Rt */
unsigned long crn:4; /* CRn */
unsigned long op1:3; /* Op1 */
unsigned long op2:3; /* Op2 */
unsigned long op0:2; /* Op0 */
unsigned long res0:3;
unsigned long len:1; /* Instruction length */
unsigned long ec:6;
} sysreg; /* HSR_EC_SYSREG */
struct hsr_iabt {
unsigned long ifsc:6; /* Instruction fault status code */
unsigned long res0:1;
unsigned long s1ptw:1; /* Stage 2 fault during stage 1 translation */
unsigned long res1:1;
unsigned long eat:1; /* External abort type */
unsigned long res2:15;
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
} iabt; /* HSR_EC_INSTR_ABORT_* */
struct hsr_dabt {
unsigned long dfsc:6; /* Data Fault Status Code */
unsigned long write:1; /* Write / not Read */
unsigned long s1ptw:1; /* Stage 2 fault during stage 1 translation */
unsigned long cache:1; /* Cache Maintenance */
unsigned long eat:1; /* External Abort Type */
unsigned long sbzp0:4;
unsigned long ar:1; /* Acquire Release */
unsigned long sf:1; /* Sixty Four bit register */
unsigned long reg:5; /* Register */
unsigned long sign:1; /* Sign extend */
unsigned long size:2; /* Access Size */
unsigned long valid:1; /* Syndrome Valid */
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
} dabt; /* HSR_EC_DATA_ABORT_* */
struct hsr_brk {
unsigned long comment:16; /* Comment */
unsigned long res0:9;
unsigned long len:1; /* Instruction length */
unsigned long ec:6; /* Exception Class */
} brk;
};
/**
* Structure that stores the saved register values on a hypercall.
*/
struct guest_state {
uint64_t pc;
uint64_t cpsr;
uint64_t elr_el1;
uint64_t spsr_el1;
uint64_t sp_el0;
uint64_t sp_el1;
union esr esr_el2;
uint64_t x[31];
}
__attribute__((packed));
#endif

View File

@@ -1,556 +0,0 @@
/*
* This file is part of Luma3DS.
* Copyright (C) 2016-2019 Aurora Wright, TuxSH
*
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/
#define _GNU_SOURCE // for strchrnul
#include <stdio.h>
#include <string.h>
#include "../debug_manager.h"
#include "../watchpoints.h"
#include "debug.h"
#include "net.h"
#include "context.h"
#include "verbose.h"
#include "thread.h"
#include "mem.h"
#include "hio.h"
#include <stdlib.h>
#include <signal.h>
static bool GDB_PreprocessDebugEvent(GDBContext *ctx, DebugEventInfo *info)
{
u64 irqFlags = maskIrq();
bool shouldSignal;
switch (info->type) {
case DBGEVENT_CORE_ON: {
shouldSignal = ctx->catchThreadEvents;
if (!info->preprocessed) {
ctx->attachedCoreList |= BIT(info->coreId);
}
break;
}
case DBGEVENT_CORE_OFF: {
if (!info->preprocessed) {
u32 newLst = ctx->attachedCoreList & ~BIT(info->coreId);
if (ctx->selectedThreadId == info->coreId && newLst != 0) {
ctx->selectedThreadId = __builtin_ctz(newLst);
GDB_MigrateRxIrq(ctx, ctx->selectedThreadId);
}
ctx->attachedCoreList = newLst;
shouldSignal = ctx->catchThreadEvents || newLst == 0;
} else {
shouldSignal = ctx->catchThreadEvents || ctx->attachedCoreList == 0;
}
break;
}
default:
shouldSignal = true;
break;
}
info->preprocessed = true;
restoreInterruptFlags(irqFlags);
return shouldSignal;
}
static inline void GDB_MarkDebugEventAcked(GDBContext *ctx, const DebugEventInfo *info)
{
ctx->acknowledgedDebugEventCoreList |= BIT(info->coreId);
}
static int GDB_ParseExceptionFrame(char *out, const DebugEventInfo *info, int sig)
{
u32 coreId = info->coreId;
ExceptionStackFrame *frame = info->frame;
int n = sprintf(out, "T%02xthread:%x;core:%x;", sig, 1 + coreId, coreId);
// Dump the GPRs & sp & pc & cpsr (cpsr is 32-bit in the xml desc)
// For performance reasons, we don't include the FPU registers here
for (u32 i = 0; i < 31; i++) {
n += sprintf(out + n, "%x:%016lx;", i, __builtin_bswap64(ReadRegister(frame, i)));
}
n += sprintf(
out + n,
"1f:%016lx;20:%016lx;21:%08x;",
__builtin_bswap64(*exceptionGetSpPtr(frame)),
__builtin_bswap64(frame->elr_el2),
__builtin_bswap32((u32)frame->spsr_el2)
);
return n;
}
int GDB_SendStopReply(GDBContext *ctx, const DebugEventInfo *info, bool asNotification)
{
char *buf = ctx->buffer + 1;
int n;
bool invalid = false;
buf[0] = 0;
if (asNotification) {
strcpy(buf, "Stopped:");
}
n = strlen(buf);
// Even if the info is invalid:
ctx->lastDebugEvent = info;
ctx->sentDebugEventCoreList |= BIT(info->coreId);
switch(info->type) {
case DBGEVENT_DEBUGGER_BREAK: {
n += GDB_ParseExceptionFrame(buf + n, info, 0);
break;
}
case DBGEVENT_CORE_ON: {
if (ctx->catchThreadEvents) {
n += GDB_ParseExceptionFrame(buf + n, info, SIGTRAP);
strcat(buf, "create:;");
} else {
invalid = true;
}
break;
}
case DBGEVENT_CORE_OFF: {
if (ctx->attachedCoreList == 0) {
// All cores have exited, must report an exit
ctx->processExited = true;
ctx->processEnded = true;
strcat(buf, "W00");
} else if(ctx->catchThreadEvents) {
sprintf(buf, "w0;%x", info->coreId + 1);
} else {
invalid = true;
}
break;
}
case DBGEVENT_EXIT: {
// exited (no error / unhandled exception), SIGTERM (process terminated) * 2
static const char *processExitReplies[] = { "W00", "X0f" };
strcat(buf, processExitReplies[ctx->processExited ? 0 : 1]);
break;
}
case DBGEVENT_EXCEPTION: {
ExceptionClass ec = info->frame->esr_el2.ec;
// Aside from stage 2 translation faults and other pre-handled exceptions,
// the only notable exceptions we get are stop point/single step events from the debugee (basically classes 0x3x)
switch(ec) {
case Exception_BreakpointLowerEl: {
n += GDB_ParseExceptionFrame(buf + n, info, SIGTRAP);
strcat(buf, "hwbreak:;");
break;
}
case Exception_WatchpointLowerEl: {
static const char *kinds[] = { "", "r", "", "a" };
// Note: exception info doesn't provide us with the access size. Use 1.
bool wnr = (info->frame->esr_el2.iss & BIT(6)) != 0;
WatchpointLoadStoreControl dr = wnr ? WatchpointLoadStoreControl_Store : WatchpointLoadStoreControl_Load;
DebugControlRegister cr = retrieveWatchpointConfig(info->frame->far_el2, dr);
if (!cr.enabled) {
DEBUG("GDB: oops, unhandled watchpoint for core id %u, far=%016lx\n", info->coreId, info->frame->far_el2);
} else {
n += GDB_ParseExceptionFrame(buf + n, info, SIGTRAP);
sprintf(buf + n, "%swatch:%016lx;", kinds[cr.lsc], info->frame->far_el2);
}
break;
}
case Exception_SoftwareStepLowerEl: {
n += GDB_ParseExceptionFrame(buf + n, info, SIGTRAP);
break;
}
// Note: we don't really support 32-bit sw breakpoints, we'll still report them
// if the guest has inserted some of them manually...
case Exception_SoftwareBreakpointA64:
case Exception_SoftwareBreakpointA32: {
n += GDB_ParseExceptionFrame(buf + n, info, SIGTRAP);
strcat(buf, "swbreak:;");
break;
}
default: {
invalid = true;
DEBUG("GDB: oops, unhandled exception for core id %u\n", info->coreId);
break;
}
}
break;
}
case DBGEVENT_OUTPUT_STRING: {
if (!GDB_IsNonStop(ctx)) {
uintptr_t addr = info->outputString.address;
size_t remaining = info->outputString.size;
size_t sent = 0;
size_t total = 0;
while (remaining > 0) {
size_t pending = (GDB_BUF_LEN - 1) / 2;
pending = pending < remaining ? pending : remaining;
int res = GDB_SendMemory(ctx, "O", 1, addr + sent, pending);
if(res < 0 || res != 5 + 2 * pending)
break;
sent += pending;
remaining -= pending;
total += res;
}
return (int)total;
} else {
invalid = true;
break;
}
}
// TODO: HIO
default: {
invalid = true;
DEBUG("GDB: unknown exception type %u, core id %u\n", (u32)info->type, info->coreId);
break;
}
}
if (invalid) {
return 0;
} else if (asNotification) {
return GDB_SendNotificationPacket(ctx, buf, strlen(buf));
} else {
return GDB_SendPacket(ctx, buf, strlen(buf));
}
}
/*
Non-stop mode:
-> %Stop:<info>
<- $vStopped
-> $<info>
<- vStopped, etc.
-> $OK
If we're the first to try to send a notification, send it.
Otherwise don't, the core which will handle the GDB packets then will see the changes.
GDB can also send the "?" packet. This aborts the current notfication/vStopped sequence,
and asks to resend the events for each stopped core, no matter if already sent before.
Full-stop mode (default):
If we lose the race, we have to wait until we're continued to send the remaining events...
*/
int GDB_TrySignalDebugEvent(GDBContext *ctx, DebugEventInfo *info)
{
int ret = 0;
// Acquire the gdb lock/disable rx irq. We most likely block here.
GDB_AcquireContext(ctx);
// Need to put it here otherwise core on/off would never be seen
bool shouldSignal = GDB_PreprocessDebugEvent(ctx, info);
// Are we still paused & has the packet not been handled & are we allowed to send on our own?
if (shouldSignal && !ctx->sendOwnDebugEventDisallowed && !info->handled && debugManagerIsCorePaused(info->coreId)) {
bool nonStop = GDB_IsNonStop(ctx);
info->handled = true;
// Full-stop mode: stop other cores
if (!nonStop) {
debugManagerPauseCores(ctx->attachedCoreList & ~BIT(info->coreId));
}
ctx->sendOwnDebugEventDisallowed = true;
ret = GDB_SendStopReply(ctx, info, nonStop);
}
if (!shouldSignal) {
debugManagerContinueCores(BIT(currentCoreCtx->coreId));
}
GDB_ReleaseContext(ctx);
return ret;
}
void GDB_BreakAllCores(GDBContext *ctx)
{
if (GDB_IsNonStop(ctx)) {
debugManagerBreakCores(ctx->attachedCoreList);
} else {
// Break all cores too, but mark everything but the first has handled
debugManagerBreakCores(ctx->attachedCoreList);
u32 rem = ctx->attachedCoreList & ~BIT(currentCoreCtx->coreId);
FOREACH_BIT (tmp, coreId, rem) {
DebugEventInfo *info = debugManagerGetDebugEvent(coreId);
info->handled = true;
info->preprocessed = true;
}
}
}
GDB_DECLARE_VERBOSE_HANDLER(Stopped)
{
u32 coreList = debugManagerGetPausedCoreList() & ctx->attachedCoreList;
u32 remaining = coreList & ~ctx->sentDebugEventCoreList;
// Ack
if (ctx->lastDebugEvent != NULL) {
GDB_MarkDebugEventAcked(ctx, ctx->lastDebugEvent);
}
for (;;) {
if (remaining != 0) {
// Send one more debug event (marking it as handled)
u32 coreId = __builtin_ctz(remaining);
DebugEventInfo *info = debugManagerGetDebugEvent(coreId);
if (GDB_PreprocessDebugEvent(ctx, info)) {
ctx->sendOwnDebugEventDisallowed = true;
return GDB_SendStopReply(ctx, info, false);
} else {
remaining &= ~BIT(coreId);
}
} else {
// vStopped sequenced finished
ctx->sendOwnDebugEventDisallowed = false;
return GDB_ReplyOk(ctx);
}
}
}
GDB_DECLARE_HANDLER(GetStopReason)
{
if (!GDB_IsNonStop(ctx)) {
// Full-stop:
return GDB_SendStopReply(ctx, ctx->lastDebugEvent, false);
} else {
// Non-stop, start new vStopped sequence
ctx->sentDebugEventCoreList = 0;
ctx->acknowledgedDebugEventCoreList = 0;
ctx->lastDebugEvent = NULL;
ctx->sendOwnDebugEventDisallowed = true;
return GDB_HandleVerboseStopped(ctx);
}
}
GDB_DECLARE_HANDLER(Detach)
{
ctx->state = GDB_STATE_DETACHING;
return GDB_ReplyOk(ctx);
}
GDB_DECLARE_HANDLER(Kill)
{
ctx->state = GDB_STATE_DETACHING;
ctx->flags |= GDB_FLAG_TERMINATE;
return 0;
}
GDB_DECLARE_VERBOSE_HANDLER(CtrlC)
{
int ret = GDB_ReplyOk(ctx);
GDB_BreakAllCores(ctx);
return ret;
}
GDB_DECLARE_HANDLER(ContinueOrStepDeprecated)
{
char *addrStart = NULL;
char cmd = ctx->commandData[-1];
// This deprecated command should not be permitted in non-stop mode
/*if (GDB_IsNonStop(ctx)) {
return GDB_ReplyErrno(ctx, EPERM);
}*/
if(cmd == 'C' || cmd == 'S') {
// Check the presence of the two-digit signature, even if we ignore it.
u8 sg;
if (GDB_DecodeHex(&sg, ctx->commandData, 1) != 1) {
return GDB_ReplyErrno(ctx, EILSEQ);
}
// Check: [;addr] or [nothing]
if (ctx->commandData[2] != 0 && ctx->commandData[2] != ';') {
return GDB_ReplyErrno(ctx, EILSEQ);
}
if(ctx->commandData[2] == ';') {
addrStart = ctx->commandData + 3;
}
}
else {
// 'c', 's'
if (ctx->commandData[0] != 0) {
addrStart = ctx->commandData;
}
}
// Only support the simplest form, with no address
// Only degenerate clients will use ;addr, anyway (and the packets are deprecated in favor
// of vCont anyway)
if (addrStart != NULL) {
return GDB_ReplyErrno(ctx, ENOSYS);
}
u32 coreList = ctx->selectedThreadIdForContinuing == -1 ? ctx->attachedCoreList : BIT(ctx->selectedThreadIdForContinuing);
u32 ssMask = (cmd == 's' || cmd == 'S') ? coreList : 0;
FOREACH_BIT (tmp, coreId, ssMask) {
debugManagerSetSteppingRange(coreId, 0, 0);
}
u32 mask = ctx->acknowledgedDebugEventCoreList;
debugManagerSetSingleStepCoreList(ssMask & mask);
debugManagerUnpauseCores(coreList & mask);
return 0;
}
GDB_DECLARE_VERBOSE_HANDLER(Continue)
{
u32 parsedCoreList = 0;
u32 continueCoreList = 0;
u32 stepCoreList = 0;
u32 stopCoreList = 0;
char *cmd = ctx->commandData;
while (cmd != NULL) {
char *nextCmd;
char *threadIdPart;
int threadId;
u32 curMask = 0;
const char *cmdEnd;
// It it always fine if we set the single-stepping range to 0,0 by default
// Because the fields we set are the shadow fields copied to the real fields after debug unpause
uintptr_t ssStartAddr = 0;
uintptr_t ssEndAddr = 0;
// Locate next command, replace delimiter by NUL
nextCmd = strchr(cmd, ';');
if (nextCmd != NULL && *nextCmd == ';') {
*nextCmd++ = 0;
}
// Locate thread-id part, parse thread id
threadIdPart = strchr(cmd, ':');
if (threadIdPart != NULL) {
*threadIdPart++ = 0;
}
if (threadIdPart == NULL || strcmp(threadIdPart, "-1") == 0) {
// Default action...
threadId = -1;
curMask = ctx->attachedCoreList;
} else {
unsigned long id;
if(GDB_ParseHexIntegerList(&id, threadIdPart, 1, 0) == NULL) {
return GDB_ReplyErrno(ctx, EILSEQ);
} else if (id >= MAX_CORE + 1) {
return GDB_ReplyErrno(ctx, EINVAL);
}
threadId = id == 0 ? (int)currentCoreCtx->coreId : (int)id;
curMask = BIT(threadId - 1) & ctx->attachedCoreList;
}
// Parse the command itself
// Note that we may already have handled that thread in a previous command
curMask &= ~parsedCoreList;
switch (cmd[0]) {
case 'S':
case 'C': {
// Check the presence of the two-digit signature, even if we ignore it.
u8 sg;
if (GDB_DecodeHex(&sg, cmd + 1, 1) != 1) {
return GDB_ReplyErrno(ctx, EILSEQ);
}
stepCoreList |= cmd[0] == 'S' ? curMask : 0;
continueCoreList |= curMask;
cmdEnd = cmd + 3;
break;
}
case 's':
stepCoreList |= curMask;
continueCoreList |= curMask;
cmdEnd = cmd + 1;
break;
case 'c':
continueCoreList |= curMask;
cmdEnd = cmd + 1;
break;
case 't':
stopCoreList |= curMask;
cmdEnd = cmd + 1;
break;
case 'r': {
// Range step
unsigned long tmp[2];
cmdEnd = GDB_ParseHexIntegerList(tmp, cmd + 1, 2, 0);
if (cmdEnd == NULL) {
return GDB_ReplyErrno(ctx, EILSEQ);
}
ssStartAddr = tmp[0];
ssEndAddr = tmp[1];
stepCoreList |= curMask;
continueCoreList |= curMask;
break;
}
default:
return GDB_ReplyErrno(ctx, EILSEQ);
}
if (*cmdEnd != 0) {
// We've got garbage data...
return GDB_ReplyErrno(ctx, EILSEQ);
}
FOREACH_BIT (tmp, t, curMask) {
// Set/unset stepping range for all threads affected by this command
debugManagerSetSteppingRange(t, ssStartAddr, ssEndAddr);
}
parsedCoreList |= curMask;
cmd = nextCmd;
}
// "Note: In non-stop mode, a thread is considered running until GDB acknowledges
// an asynchronous stop notification for it with the vStopped packet (see Remote Non-Stop)."
u32 mask;
if (GDB_IsNonStop(ctx)) {
mask = ctx->acknowledgedDebugEventCoreList;
} else {
mask = ctx->attachedCoreList;
ctx->sendOwnDebugEventDisallowed = (continueCoreList & mask) == 0;
}
debugManagerSetSingleStepCoreList(stepCoreList & mask);
debugManagerBreakCores(stopCoreList & ~mask);
debugManagerContinueCores(continueCoreList & mask);
return 0;
}

View File

@@ -1,17 +0,0 @@
/*
* This file is part of Luma3DS.
* Copyright (C) 2016-2019 Aurora Wright, TuxSH
*
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/
#pragma once
#include "gdb_context.hpp"
#include "../core_ctx.h"
#include "../debug_manager.h"
int GDB_SendStopReply(GDBContext *ctx, const DebugEventInfo *info, bool asNotification);
int GDB_TrySignalDebugEvent(GDBContext *ctx, DebugEventInfo *info);
void GDB_BreakAllCores(GDBContext *ctx);

View File

@@ -1,133 +0,0 @@
/*
* This file is part of Luma3DS.
* Copyright (C) 2016-2019 Aurora Wright, TuxSH
*
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/
#include <string.h>
#include "hio.h"
#include "net.h"
#include "mem.h"
#include "debug.h"
/*
bool GDB_FetchPackedHioRequest(GDBContext *ctx, u32 addr)
{
u32 total = GDB_ReadTargetMemory(&ctx->currentHioRequest, ctx, addr, sizeof(PackedGdbHioRequest));
if (total != sizeof(PackedGdbHioRequest) || memcmp(&ctx->currentHioRequest.magic, "GDB\x00", 4) != 0)
{
memset(&ctx->currentHioRequest, 0, sizeof(PackedGdbHioRequest));
ctx->currentHioRequestTargetAddr = 0;
return false;
}
else
{
ctx->currentHioRequestTargetAddr = addr;
return true;
}
}
bool GDB_IsHioInProgress(GDBContext *ctx)
{
return ctx->currentHioRequestTargetAddr != 0;
}
int GDB_SendCurrentHioRequest(GDBContext *ctx)
{
char buf[256+1];
char tmp[32+1];
u32 nStr = 0;
sprintf(buf, "F%s", ctx->currentHioRequest.functionName);
for (u32 i = 0; i < 8 && ctx->currentHioRequest.paramFormat[i] != 0; i++)
{
switch (ctx->currentHioRequest.paramFormat[i])
{
case 'i':
case 'I':
case 'p':
sprintf(tmp, ",%lx", (u32)ctx->currentHioRequest.parameters[i]);
break;
case 'l':
case 'L':
sprintf(tmp, ",%llx", ctx->currentHioRequest.parameters[i]);
break;
case 's':
sprintf(tmp, ",%lx/%x", (u32)ctx->currentHioRequest.parameters[i], ctx->currentHioRequest.stringLengths[nStr++]);
break;
default:
tmp[0] = 0;
break;
}
strcat(buf, tmp);
}
return GDB_SendPacket(ctx, buf, strlen(buf));
}*/
GDB_DECLARE_HANDLER(HioReply)
{
return 0;
/* if (!GDB_IsHioInProgress(ctx))
return GDB_ReplyErrno(ctx, EPERM);
// Reply in the form of Fretcode,errno,Ctrl-C flag;call-specific attachment
// "Call specific attachement" is always empty, though.
const char *pos = ctx->commandData;
u64 retval;
if (*pos == 0 || *pos == ',')
return GDB_ReplyErrno(ctx, EILSEQ);
else if (*pos == '-')
{
pos++;
ctx->currentHioRequest.retval = -1ll;
}
else if (*pos == '+')
{
pos++;
ctx->currentHioRequest.retval = 1;
}
else
ctx->currentHioRequest.retval = 1;
pos = GDB_ParseHexIntegerList64(&retval, pos, 1, ',');
if (pos == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
ctx->currentHioRequest.retval *= retval;
ctx->currentHioRequest.gdbErrno = 0;
ctx->currentHioRequest.ctrlC = false;
if (*pos != 0)
{
u32 errno_;
// GDB protocol technically allows errno to have a +/- prefix but this will never happen.
pos = GDB_ParseHexIntegerList(&errno_, ++pos, 1, ',');
ctx->currentHioRequest.gdbErrno = (int)errno_;
if (pos == NULL)
return GDB_ReplyErrno(ctx, EILSEQ);
if (*pos != 0)
{
if (*pos != 'C')
return GDB_ReplyErrno(ctx, EILSEQ);
ctx->currentHioRequest.ctrlC = true;
}
}
memset(ctx->currentHioRequest.paramFormat, 0, sizeof(ctx->currentHioRequest.paramFormat));
u32 total = GDB_WriteTargetMemory(ctx, &ctx->currentHioRequest, ctx->currentHioRequestTargetAddr, sizeof(PackedGdbHioRequest));
memset(&ctx->currentHioRequest, 0, sizeof(PackedGdbHioRequest));
ctx->currentHioRequestTargetAddr = 0;
GDB_ContinueExecution(ctx);
return total == sizeof(PackedGdbHioRequest) ? 0 : GDB_ReplyErrno(ctx, EFAULT);*/
}

View File

@@ -1,14 +0,0 @@
/*
* This file is part of Luma3DS.
* Copyright (C) 2016-2019 Aurora Wright, TuxSH
*
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/
#pragma once
#include "context.h"
bool GDB_FetchPackedHioRequest(GDBContext *ctx, u32 addr);
bool GDB_IsHioInProgress(GDBContext *ctx);
int GDB_SendCurrentHioRequest(GDBContext *ctx);

View File

@@ -1,226 +0,0 @@
/*
* Copyright (c) 2019-2020 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 <cstdio>
#include "hvisor_gdb_defines_internal.hpp"
#include "hvisor_gdb_packet_data.hpp"
namespace {
void WriteAck(TransportInterface *iface)
{
char c = '+';
transportInterfaceWriteData(iface, &c, 1);
}
int WriteNack(TransportInterface *iface)
{
char c = '-';
transportInterfaceWriteData(iface, &c, 1);
return 1;
}
}
namespace ams::hvisor::gdb {
int Context::ReceivePacket()
{
char hdr;
bool ctrlC = false;
TransportInterface *iface = m_transportInterface;
// Read the first character...
transportInterfaceReadData(iface, &hdr, 1);
switch (hdr) {
case '+': {
// Ack, don't do anything else except maybe NoAckMode state transition
if (m_noAckSent) {
m_noAck = true;
m_noAckSent = false;
}
return 0;
}
case '-':
// Nack, return the previous packet
transportInterfaceWriteData(iface, m_buffer, m_lastSentPacketSize);
return m_lastSentPacketSize;
case '$':
// Normal packet, handled below
break;
case '\x03':
// Normal packet (Control-C), handled below
ctrlC = true;
break;
default:
// Oops, send a nack
DEBUG("Received a packed with an invalid header from GDB, hdr=%c\n", hdr);
return WriteNack(iface);
}
// We didn't get a nack past this point, read the remaining data if any
m_buffer[0] = hdr;
if (ctrlC) {
// Will never normally happen, but ok
if (m_state < State::Attached) {
DEBUG("Received connection from GDB, now attaching...\n");
Attach();
m_state = State::Attached;
}
return 1;
}
size_t delimPos = transportInterfaceReadDataUntil(iface, m_buffer + 1, 4 + GDB_BUF_LEN - 1, '#');
if (m_buffer[delimPos] != '#' || delimPos == 1) {
// The packet is malformed, send a nack. Refuse empty packets
return WriteNack(iface);
}
m_commandLetter = m_buffer[1];
m_commandData = std::string_view{m_buffer + 1, delimPos};
// Read the checksum
size_t checksumPos = delimPos + 1;
transportInterfaceReadData(iface, m_buffer + checksumPos, 2);
auto checksumOpt = DecodeHexByte(std::string_view{m_buffer + checksumPos, 2});
if (!checksumOpt || *checksumOpt != ComputeChecksum(m_commandData)) {
// Malformed or invalid checksum
return WriteNack(iface);
} else if (!m_noAck) {
WriteAck(iface);
}
// Remove command letter
m_commandData.remove_prefix(1);
// State transitions...
if (m_state < State::Attached) {
DEBUG("Received connection from GDB, now attaching...\n");
Attach();
m_state = State::Attached;
}
// Debug
/*m_buffer[checksumPos + 2] = '\0';
DEBUGRAW("->");
DEBUGRAW(m_buffer);
DEBUGRAW("\n");*/
return static_cast<int>(delimPos + 2);
}
int Context::DoSendPacket(size_t len)
{
transportInterfaceWriteData(m_transportInterface, m_buffer, len);
m_lastSentPacketSize = len;
// Debugging:
/*m_buffer[len] = 0;
DEBUGRAW("<-");
DEBUGRAW(ctx->buffer);
DEBUGRAW("\n");*/
return static_cast<int>(len);
}
int Context::SendPacket(std::string_view packetData, char hdr)
{
u8 checksum = ComputeChecksum(packetData);
if (packetData.data() != m_buffer + 1) {
std::memmove(m_buffer + 1, packetData.data(), packetData.size());
}
size_t checksumPos = 1 + packetData.size() + 1;
m_buffer[0] = '$';
m_buffer[checksumPos - 1] = '#';
EncodeHex(m_buffer + checksumPos, &checksum, 1);
return DoSendPacket(4 + packetData.size());
}
int Context::SendFormattedPacket(const char *packetDataFmt, ...)
{
va_list args;
va_start(args, packetDataFmt);
int n = vsprintf(m_buffer + 1, packetDataFmt, args);
va_end(args);
if (n < 0) {
return -1;
} else {
return SendPacket(std::string_view{m_buffer + 1, n});
}
}
int Context::SendHexPacket(const void *packetData, size_t len)
{
if (4 + 2 * len < GDB_BUF_LEN) {
return -1;
}
EncodeHex(m_buffer + 1, packetData, len);
return SendPacket(std::string_view{m_buffer + 1, 2 * len});
}
int Context::SendStreamData(std::string_view streamData, size_t offset, size_t length, bool forceEmptyLast)
{
size_t totalSize = streamData.size();
// GDB_BUF_LEN does not include the usual $#<1-byte checksum>
length = std::min(length, GDB_BUF_LEN - 1ul);
char letter;
if ((forceEmptyLast && offset >= totalSize) || (!forceEmptyLast && offset + length >= totalSize)) {
length = offset >= totalSize ? 0 : totalSize - offset;
letter = 'l';
} else {
letter = 'm';
}
// Note: ctx->buffer[0] = '$'
if (streamData.data() + offset != m_buffer + 2) {
memmove(m_buffer + 2, streamData.data() + offset, length);
}
m_buffer[1] = letter;
return SendPacket(std::string_view{m_buffer + 1, 1 + length});
}
int Context::ReplyOk()
{
return SendPacket("OK");
}
int Context::ReplyEmpty()
{
return SendPacket("");
}
int Context::ReplyErrno(int no)
{
u8 no8 = static_cast<u8>(no);
char resp[] = "E00";
EncodeHex(resp + 1, &no8, 1);
return SendPacket(resp);
}
}

View File

@@ -1,257 +0,0 @@
/*
* Copyright (c) 2019-2020 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/>.
*/
// Lots of code from:
/*
* This file is part of Luma3DS.
* Copyright (C) 2016-2019 Aurora Wright, TuxSH
*
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/
#include "hvisor_gdb_defines_internal.hpp"
#include "hvisor_gdb_packet_data.hpp"
#include "../hvisor_hw_breakpoint_manager.hpp"
#include "../hvisor_sw_breakpoint_manager.hpp"
#include "../hvisor_watchpoint_manager.hpp"
#include "../hvisor_fpu_register_cache.hpp"
#include "../debug_manager.h"
namespace {
TEMPORARY char g_gdbWorkBuffer[GDB_WORK_BUF_LEN];
TEMPORARY char g_gdbBuffer[GDB_BUF_LEN + 4 + 1];
}
namespace ams::hvisor::gdb {
void Context::Disconnect()
{
Detach();
auto *iface = m_transportInterface;
*this = {};
m_transportInterface = iface;
}
void Context::Initialize(TransportInterfaceType ifaceType, u32 ifaceId, u32 ifaceFlags)
{
m_workBuffer = g_gdbWorkBuffer;
m_buffer = g_gdbBuffer;
/*m_transportInterface = transportInterfaceCreate(
ifaceType,
ifaceId,
ifaceFlags,
GDB_ReceiveDataCallback,
GDB_ProcessDataCallback,
ctx
);*/
}
void Context::Attach()
{
// TODO: move the debug traps enable here?
m_attachedCoreList = CoreContext::GetActiveCoreMask();
// We're in full-stop mode at this point
// Break cores, but don't send the debug event (it will be fetched with '?')
// Initialize lastDebugEvent
debugManagerSetReportingEnabled(true);
m_sendOwnDebugEventDisallowed = true;
BreakAllCores();
DebugEventInfo *info = debugManagerGetDebugEvent(currentCoreCtx->GetCoreId());
info->preprocessed = true;
info->handled = true;
m_lastDebugEvent = info;
m_state = State::Attached;
m_sendOwnDebugEventDisallowed = false;
}
void Context::Detach()
{
WatchpointManager::GetInstance().RemoveAll();
HwBreakpointManager::GetInstance().RemoveAll();
SwBreakpointManager::GetInstance().RemoveAll(true);
// Reports to gdb are prevented because of "detaching" state?
// TODO: disable debug traps
m_currentHioRequestTargetAddr = 0;
memset(&m_currentHioRequest, 0, sizeof(PackedGdbHioRequest));
debugManagerSetReportingEnabled(false);
debugManagerContinueCores(CoreContext::GetActiveCoreMask());
}
void Context::MigrateRxIrq(u32 coreId) const
{
FpuRegisterCache::GetInstance().CleanInvalidate();
//transportInterfaceSetInterruptAffinity(ctx->transportInterface, BIT(coreId));
}
GDB_DEFINE_HANDLER(Unsupported)
{
return ReplyEmpty();
}
#define COMMAND_CASE(letter, method) case letter: return GDB_HANDLER(method)();
int Context::ProcessPacket()
{
m_commandLetter = m_commandData[0];
m_commandData.remove_prefix(1);
switch (m_commandLetter) {
COMMAND_CASE('?', GetStopReason)
//COMMAND_CASE('c', ContinueOrStepDeprecated)
//COMMAND_CASE('C', ContinueOrStepDeprecated)
COMMAND_CASE('D', Detach)
COMMAND_CASE('F', HioReply)
COMMAND_CASE('g', ReadRegisters)
COMMAND_CASE('G', WriteRegisters)
COMMAND_CASE('H', SetThreadId)
COMMAND_CASE('k', Kill)
COMMAND_CASE('m', ReadMemory)
COMMAND_CASE('M', WriteMemory)
COMMAND_CASE('p', ReadRegister)
COMMAND_CASE('P', WriteRegister)
COMMAND_CASE('q', Query)
COMMAND_CASE('Q', Query)
//COMMAND_CASE('s', ContinueOrStepDeprecated)
//COMMAND_CASE('S', ContinueOrStepDeprecated)
COMMAND_CASE('T', IsThreadAlive)
COMMAND_CASE('v', VerboseCommand)
COMMAND_CASE('X', WriteMemoryRaw)
COMMAND_CASE('z', ToggleStopPoint)
COMMAND_CASE('Z', ToggleStopPoint)
default:
return HandleUnsupported();
}
}
#undef COMMAND_CASE
/*
static const struct{
char command;
GDBCommandHandler handler;
} gdbCommandHandlers[] = {
{ '?', GDB_HANDLER(GetStopReason) },
//{ '!', GDB_HANDLER(EnableExtendedMode) }, // note: stubbed
//{ 'c', GDB_HANDLER(ContinueOrStepDeprecated) },
//{ 'C', GDB_HANDLER(ContinueOrStepDeprecated) },
{ 'D', GDB_HANDLER(Detach) },
{ 'F', GDB_HANDLER(HioReply) },
{ 'g', GDB_HANDLER(ReadRegisters) },
{ 'G', GDB_HANDLER(WriteRegisters) },
{ 'H', GDB_HANDLER(SetThreadId) },
{ 'k', GDB_HANDLER(Kill) },
{ 'm', GDB_HANDLER(ReadMemory) },
{ 'M', GDB_HANDLER(WriteMemory) },
{ 'p', GDB_HANDLER(ReadRegister) },
{ 'P', GDB_HANDLER(WriteRegister) },
{ 'q', GDB_HANDLER(ReadQuery) },
{ 'Q', GDB_HANDLER(WriteQuery) },
//{ 's', GDB_HANDLER(ContinueOrStepDeprecated) },
//{ 'S', GDB_HANDLER(ContinueOrStepDeprecated) },
{ 'T', GDB_HANDLER(IsThreadAlive) },
{ 'v', GDB_HANDLER(VerboseCommand) },
{ 'X', GDB_HANDLER(WriteMemoryRaw) },
{ 'z', GDB_HANDLER(ToggleStopPoint) },
{ 'Z', GDB_HANDLER(ToggleStopPoint) },
};
static inline GDBCommandHandler GDB_GetCommandHandler(char command)
{
static const u32 nbHandlers = sizeof(gdbCommandHandlers) / sizeof(gdbCommandHandlers[0]);
size_t i;
for (i = 0; i < nbHandlers && gdbCommandHandlers[i].command != command; i++);
return i < nbHandlers ? gdbCommandHandlers[i].handler : GDB_HANDLER(Unsupported);
}
static int GDB_ProcessPacket(GDBContext *ctx, size_t len)
{
int ret;
ENSURE(ctx->state != GDB_STATE_DISCONNECTED);
// Handle the packet...
if (ctx->buffer[0] == '\x03') {
GDB_BreakAllCores(ctx);
ret = 0;
} else {
GDBCommandHandler handler = GDB_GetCommandHandler(ctx->buffer[1]);
ctx->commandData = ctx->buffer + 2;
ret = handler(ctx);
}
// State changes...
if (ctx->state == GDB_STATE_DETACHING) {
return -1;
}
return ret;
}
static size_t GDB_ReceiveDataCallback(TransportInterface *iface, void *ctxVoid)
{
return (size_t)GDB_ReceivePacket((GDBContext *)ctxVoid);
}
static void GDB_ProcessDataCallback(TransportInterface *iface, void *ctxVoid, size_t sz)
{
int r = (int)sz;
GDBContext *ctx = (GDBContext *)ctxVoid;
if (r == -1) {
// Not sure if GDB has something to forcefully close connections over UART...
char c = '\x04'; // ctrl-D
transportInterfaceWriteData(iface, &c, 1);
GDB_Disconnect(ctx);
}
r = GDB_ProcessPacket(ctx, sz);
if (r == -1) {
GDB_Disconnect(ctx);
}
}
void GDB_AcquireContext(GDBContext *ctx)
{
transportInterfaceAcquire(ctx->transportInterface);
}
void GDB_ReleaseContext(GDBContext *ctx)
{
transportInterfaceRelease(ctx->transportInterface);
}
*/
}

View File

@@ -1,230 +0,0 @@
/*
* Copyright (c) 2019-2020 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/>.
*/
// Lots of code from:
/*
* This file is part of Luma3DS.
* Copyright (C) 2016-2019 Aurora Wright, TuxSH
*
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/
#pragma once
#include "../defines.hpp"
#include "../transport_interface.h"
#include <string_view>
#define _REENT_ONLY
#include <cerrno>
#define DECLARE_HANDLER(name) int Handle##name()
#define DECLARE_QUERY_HANDLER(name) DECLARE_HANDLER(Query##name)
#define DECLARE_VERBOSE_HANDLER(name) DECLARE_HANDLER(Verbose##name)
#define DECLARE_REMOTE_HANDLER(name) DECLARE_HANDLER(Remote##name)
#define DECLARE_XFER_HANDLER(name) int HandleXfer##name(bool write, std::string_view annex, size_t offset, size_t length)
struct DebugEventInfo;
namespace ams::hvisor::gdb {
struct PackedGdbHioRequest {
// TODO revamp
char magic[4]; // "GDB\x00"
u32 version;
// Request
char functionName[16+1];
char paramFormat[8+1];
u64 parameters[8];
size_t stringLengths[8];
// Return
s64 retval;
int gdbErrno;
bool ctrlC;
};
class Context final {
private:
enum class State {
Disconnected = 0,
Connected,
Attached,
Detaching
};
private:
// No need for a lock, it's in the transport interface layer...
TransportInterface *m_transportInterface = nullptr;
State m_state = State::Disconnected;
bool m_noAckSent = false;
bool m_noAck = false;
bool m_nonStop = false;
u32 m_attachedCoreList = 0;
int m_selectedCoreId = 0;
int m_selectedCoreIdForContinuing = 0;
u32 m_sentDebugEventCoreList = 0;
u32 m_acknowledgedDebugEventCoreList = 0;
bool m_sendOwnDebugEventDisallowed = 0;
bool m_catchThreadEvents = false;
bool m_processEnded = false;
bool m_processExited = false;
const struct DebugEventInfo *m_lastDebugEvent = nullptr;
uintptr_t m_currentHioRequestTargetAddr = 0ul;
PackedGdbHioRequest m_currentHioRequest{};
std::string_view m_targetXml{};
char m_commandLetter = '\0';
std::string_view m_commandData{};
size_t m_lastSentPacketSize = 0ul;
char *m_buffer = nullptr;
char *m_workBuffer = nullptr;
private:
Context(const Context &) = default;
Context &operator=(const Context &) = default;
Context(Context &&) = default;
Context &operator=(Context &&) = default;
private:
void MigrateRxIrq(u32 coreId) const;
int ProcessPacket();
void Disconnect();
// Debug
void BreakAllCores();
// Comms
int ReceivePacket();
int DoSendPacket(size_t len);
int SendPacket(std::string_view packetData, char hdr = '$');
int SendFormattedPacket(const char *packetDataFmt, ...);
int SendHexPacket(const void *packetData, size_t len);
int SendStreamData(std::string_view streamData, size_t offset, size_t length, bool forceEmptyLast);
int ReplyOk();
int ReplyEmpty();
int ReplyErrno(int no);
// Memory
int SendMemory(uintptr_t addr, size_t len, std::string_view prefix = {});
int WriteMemoryImpl(size_t (*decoder)(void *, const void *, size_t));
// Helpers
constexpr char *GetInPlaceOutputBuffer() const
{
return m_buffer + 1;
}
constexpr char *GetWorkBuffer() const
{
return m_workBuffer;
}
private:
// Meta
DECLARE_HANDLER(Unsupported);
DECLARE_HANDLER(Query);
DECLARE_QUERY_HANDLER(Xfer);
DECLARE_HANDLER(VerboseCommand);
// General queries
DECLARE_QUERY_HANDLER(Supported);
DECLARE_QUERY_HANDLER(StartNoAckMode);
DECLARE_QUERY_HANDLER(Attached);
// XML Transfer
DECLARE_XFER_HANDLER(Features);
// Resuming features enumeration
DECLARE_VERBOSE_HANDLER(ContinueSupported);
// "Threads"
// Capitalization in "GetTLSAddr" is intended.
DECLARE_HANDLER(SetThreadId);
DECLARE_HANDLER(IsThreadAlive);
DECLARE_QUERY_HANDLER(CurrentThreadId);
DECLARE_QUERY_HANDLER(fThreadInfo);
DECLARE_QUERY_HANDLER(sThreadInfo);
DECLARE_QUERY_HANDLER(ThreadEvents);
DECLARE_QUERY_HANDLER(ThreadExtraInfo);
DECLARE_QUERY_HANDLER(GetTLSAddr);
// Debug
DECLARE_VERBOSE_HANDLER(Stopped);
DECLARE_HANDLER(Detach);
DECLARE_HANDLER(Kill);
DECLARE_VERBOSE_HANDLER(CtrlC);
DECLARE_HANDLER(ContinueOrStepDeprecated);
DECLARE_VERBOSE_HANDLER(Continue);
DECLARE_HANDLER(GetStopReason);
// Stop points
DECLARE_HANDLER(ToggleStopPoint);
// Memory
DECLARE_HANDLER(ReadMemory);
DECLARE_HANDLER(WriteMemory);
DECLARE_HANDLER(WriteMemoryRaw);
DECLARE_QUERY_HANDLER(SearchMemory);
// Registers
DECLARE_HANDLER(ReadRegisters);
DECLARE_HANDLER(WriteRegisters);
DECLARE_HANDLER(ReadRegister);
DECLARE_HANDLER(WriteRegister);
// Hio
DECLARE_HANDLER(HioReply);
// Custom commands
DECLARE_QUERY_HANDLER(Rcmd);
public:
Context() = default;
void Initialize(TransportInterfaceType ifaceType, u32 ifaceId, u32 ifaceFlags);
void Attach();
void Detach();
void lock();
void unlock();
/* TODO: parent
void Acquire();
void Release();
*/
constexpr bool IsAttached() const
{
return m_state == State::Attached;
}
};
}
#undef DECLARE_HANDLER
#undef DECLARE_QUERY_HANDLER
#undef DECLARE_VERBOSE_HANDLER
#undef DECLARE_REMOTE_HANDLER
#undef DECLARE_XFER_HANDLER

View File

@@ -1,49 +0,0 @@
/*
* Copyright (c) 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/>.
*/
// Some code from:
/*
* This file is part of Luma3DS.
* Copyright (C) 2016-2019 Aurora Wright, TuxSH
*
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/
#pragma once
#include "hvisor_gdb_context.hpp"
// 512+24 is the ideal size as IDA will try to read exactly 0x100 bytes at a time.
// IDA seems to want additional bytes as well.
// 1024 is fine enough to put all regs in the 'T' stop reply packets
// Add 4 to this for the actual allocated size, for $#<checksum>, see below.
#define GDB_BUF_LEN 0x800
#define GDB_WORK_BUF_LEN 0x1000
#define GDB_HANDLER(name) Handle##name
#define GDB_QUERY_HANDLER(name) GDB_HANDLER(Query##name)
#define GDB_VERBOSE_HANDLER(name) GDB_HANDLER(Verbose##name)
#define GDB_REMOTE_COMMAND_HANDLER(name) GDB_HANDLER(RemoteCommand##name)
#define GDB_XFER_HANDLER(name) GDB_HANDLER(Xfer##name)
#define GDB_DEFINE_HANDLER(name) int Context::GDB_HANDLER(name)()
#define GDB_DEFINE_QUERY_HANDLER(name) GDB_DEFINE_HANDLER(Query##name)
#define GDB_DEFINE_VERBOSE_HANDLER(name) GDB_DEFINE_HANDLER(Verbose##name)
#define GDB_DEFINE_REMOTE_COMMAND_HANDLER(name) GDB_DEFINE_HANDLER(RemoteCommand##name)
#define GDB_DEFINE_XFER_HANDLER(name)\
int Context::GDB_XFER_HANDLER(name)(bool write, std::string_view annex, size_t offset, size_t length)
#define GDB_CHECK_NO_CMD_DATA() do { if (!m_commandData.empty()) return ReplyErrno(EILSEQ); } while (false)

View File

@@ -1,101 +0,0 @@
/*
* Copyright (c) 2019-2020 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/>.
*/
/*
* This file is part of Luma3DS.
* Copyright (C) 2016-2019 Aurora Wright, TuxSH
*
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/
#include "hvisor_gdb_defines_internal.hpp"
#include "hvisor_gdb_packet_data.hpp"
#include "../hvisor_guest_memory.hpp"
namespace ams::hvisor::gdb {
int Context::SendMemory(uintptr_t addr, size_t len, std::string_view prefix)
{
char *buf = GetInPlaceOutputBuffer();
char *membuf = GetWorkBuffer();
size_t prefixLen = prefix.size();
if(prefixLen + 2 * len > GDB_BUF_LEN) {
// gdb shouldn't send requests which responses don't fit in a packet
return prefixLen == 0 ? ReplyErrno(ENOMEM) : -1;
}
size_t total = GuestReadMemory(addr, len, membuf);
if (total == 0) {
return prefixLen == 0 ? ReplyErrno(EFAULT) : -EFAULT;
} else {
std::copy(prefix.begin(), prefix.end(), buf);
EncodeHex(buf + prefixLen, membuf, total);
return SendPacket(std::string_view{buf, prefixLen + 2 * total});
}
}
int Context::WriteMemoryImpl(size_t (*decoder)(void *, const void *, size_t))
{
char *workbuf = GetWorkBuffer();
auto [nread, addr, len] = ParseHexIntegerList<2>(m_commandData, ':');
if (nread == 0) {
return ReplyErrno(EILSEQ);
}
m_commandData.remove_prefix(nread);
if (len > m_commandData.length() / 2) {
// Data len field doesn't match what we got...
return ReplyErrno(ENOMEM);
}
size_t n = decoder(workbuf, m_commandData.data(), m_commandData.size());
if(n != len) {
// Decoding error...
return ReplyErrno(EILSEQ);
}
size_t total = GuestWriteMemory(addr, len, workbuf);
return total == len ? ReplyOk() : ReplyErrno(EFAULT);
}
GDB_DEFINE_HANDLER(ReadMemory)
{
auto [nparsed, addr, len] = ParseHexIntegerList<2>(m_commandData);
if (nparsed == 0) {
return ReplyErrno(EILSEQ);
}
return SendMemory(addr, len);
}
GDB_DEFINE_HANDLER(WriteMemory)
{
return WriteMemoryImpl(DecodeHex);
}
GDB_DEFINE_HANDLER(WriteMemoryRaw)
{
return WriteMemoryImpl(UnescapeBinaryData);
}
}

View File

@@ -1,106 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_gdb_packet_data.hpp"
namespace ams::hvisor::gdb {
u8 ComputeChecksum(std::string_view packetData)
{
return std::accumulate(packetData.cbegin(), packetData.cend(), u8{0u});
}
size_t EncodeHex(char *dst, const void *src, size_t len)
{
static const char *alphabet = "0123456789abcdef";
const u8 *src8 = reinterpret_cast<const u8 *>(src);
for (size_t i = 0; i < len; i++) {
dst[2 * i] = alphabet[(src8[i] & 0xF0) >> 4];
dst[2 * i + 1] = alphabet[src8[i] & 0x0F];
}
return 2 * len;
}
size_t DecodeHex(void *dst, std::string_view data)
{
size_t i = 0;
u8 *dst8 = reinterpret_cast<u8 *>(dst);
for (i = 0; i < data.size() / 2; i++) {
auto bOpt = DecodeHexByte(data);
if (!bOpt) {
return i;
}
dst8[i] = *bOpt;
data.remove_prefix(2);
}
return i;
}
size_t DecodeHex(void *dst, const void *src, size_t len)
{
return DecodeHex(dst, std::string_view{reinterpret_cast<const char *>(src), len});
}
size_t EscapeBinaryData(size_t *encodedCount, void *dst, const void *src, size_t len, size_t maxLen)
{
u8 *dst8 = reinterpret_cast<u8 *>(dst);
const u8 *src8 = reinterpret_cast<const u8 *>(src);
len = std::min(len, maxLen);
u8 *dstMax = dst8 + len;
while (dst8 < dstMax) {
if (*src8 == '$' || *src8 == '#' || *src8 == '}' || *src8 == '*') {
if (dst8 + 1 >= dstMax) {
break;
}
*dst8++ = '}';
*dst8++ = *src8++ ^ 0x20;
}
else {
*dst8++ = *src8++;
}
}
*encodedCount = dst8 - reinterpret_cast<u8 *>(dst);
return src8 - reinterpret_cast<const u8 *>(src);
}
size_t UnescapeBinaryData(void *dst, const void *src, size_t len)
{
u8 *dst8 = reinterpret_cast<u8 *>(dst);
const u8 *src8 = reinterpret_cast<const u8 *>(src);
const u8 *srcEnd = src8 + len;
while (src8 < srcEnd) {
if (*src8 == '}') {
src8++;
*dst8++ = *src8++ ^ 0x20;
} else {
*dst8++ = *src8++;
}
}
return dst8 - reinterpret_cast<u8 *>(dst);
}
}

View File

@@ -1,199 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "../defines.hpp"
#include <string_view>
namespace ams::hvisor::gdb {
constexpr unsigned long DecodeHexDigit(char src)
{
switch (src) {
case '0' ... '9': return 0 + (src - '0');
case 'a' ... 'f': return 10 + (src - 'a');
case 'A' ... 'F': return 10 + (src - 'A');
default:
return 16;
}
}
constexpr auto ParseInteger(std::string_view str, u32 base = 0, bool allowPrefix = true)
{
unsigned long res = 0;
long mult = 1;
auto errval = std::tuple{0ul, 0ul};
size_t total = 0;
if ((base == 0 && !allowPrefix) || base > 16 || str.empty()) {
return errval;
}
// Check for +, -
if (str[0] == '+') {
if (!allowPrefix) {
return errval;
}
str.remove_prefix(1);
++total;
} else if (str[0] == '-') {
if (!allowPrefix) {
return errval;
}
str.remove_prefix(1);
mult = -1;
++total;
}
if (str.empty()) {
// Oops
return errval;
}
// Now, check for 0x or leading 0
if (str.size() >= 2 && str[0] == '0' && str[1] == 'x') {
if (!allowPrefix || (base != 16 && base != 0)) {
return errval;
} else {
str.remove_prefix(2);
base = 16;
total += 2;
}
} else if (base == 0 && str[0] == '0') {
base = 8;
} else if (base == 0) {
base = 10;
}
if (str.empty()) {
// Oops
return errval;
}
auto it = str.begin();
for (; it != str.end(); ++it) {
unsigned long v = DecodeHexDigit(*it);
if (v >= base) {
break;
}
res *= base;
res += v;
++total;
}
return std::tuple{total, res * mult};
}
template<size_t N>
constexpr auto ParseIntegerList(std::string_view str, u32 base, bool allowPrefix, char sep, char lastSep = '\0')
{
// First element is parsed size
std::array<unsigned long, 1+N> res{ 0 };
size_t total = 0;
for (size_t i = 0; i < N && !str.empty(); i++) {
auto [nread, val] = ParseInteger(str, base, allowPrefix);
// Parse failure
if (nread == 0) {
return res;
}
str.remove_prefix(nread);
// Check separators
if (i != N - 1) {
if (str.empty() || str[0] != sep) {
return res;
}
str.remove_prefix(1);
++total;
} else if (i == N - 1) {
if ((lastSep == '\0') && !str.empty()) {
return res;
} else if (lastSep != '\0') {
if (str.empty() || str[0] != lastSep) {
return res;
}
str.remove_prefix(1);
++total;
}
}
total += nread;
res[1 + i] = val;
}
res[0] = total;
return res;
}
template<size_t N>
constexpr auto ParseHexIntegerList(std::string_view str, char lastSep = '\0')
{
return ParseIntegerList<N>(str, 16, false, ',', lastSep);
}
template<size_t N>
constexpr auto SplitString(std::string_view data, char delim)
{
static_assert(N != 0);
std::array<std::string_view, N> res = {};
size_t delimPos = 0;
for (size_t i = 0; i < N - 1; i++) {
delimPos = data.find(delim);
if (delimPos == std::string_view::npos) {
return res;
}
res[i] = std::string_view{data.data(), delimPos};
data.remove_prefix(delimPos + 1);
}
res[N - 1] = data;
return res;
}
constexpr std::optional<u8> DecodeHexByte(std::string_view data)
{
if (data.size() < 2) {
return {};
}
auto v1 = DecodeHexDigit(data[0]);
auto v2 = DecodeHexDigit(data[1]);
if (v1 >= 16 || v2 >= 16) {
return {};
}
return (v1 << 4) | v2;
}
u8 ComputeChecksum(std::string_view packetData);
size_t EncodeHex(char *dst, const void *src, size_t len);
size_t DecodeHex(void *dst, std::string_view data);
size_t DecodeHex(void *dst, const void *src, size_t len);
size_t EscapeBinaryData(size_t *encodedCount, void *dst, const void *src, size_t len, size_t maxLen);
size_t UnescapeBinaryData(void *dst, const void *src, size_t len);
}

View File

@@ -1,107 +0,0 @@
/*
* Copyright (c) 2019-2020 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/>.
*/
/*
* This file is part of Luma3DS.
* Copyright (C) 2016-2019 Aurora Wright, TuxSH
*
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/
#include "hvisor_gdb_defines_internal.hpp"
#include "hvisor_gdb_packet_data.hpp"
namespace ams::hvisor::gdb {
GDB_DEFINE_QUERY_HANDLER(Supported)
{
// Ignore what gdb sent...
return SendFormattedPacket(
"PacketSize=%x;"
"qXfer:features:read+;"
"QStartNoAckMode+;QThreadEvents+"
"vContSupported+;swbreak+;hwbreak+",
GDB_BUF_LEN
);
}
GDB_DEFINE_QUERY_HANDLER(StartNoAckMode)
{
GDB_CHECK_NO_CMD_DATA();
m_noAckSent = true;
return ReplyOk();
}
GDB_DEFINE_QUERY_HANDLER(Attached)
{
GDB_CHECK_NO_CMD_DATA();
return SendPacket("1");
}
#define QUERY_CMD_CASE2(name, fun) if (cmdName==name) { return GDB_QUERY_HANDLER(fun)(); } else
#define QUERY_CMD_CASE(fun) QUERY_CMD_CASE2(STRINGIZE(fun), fun)
GDB_DEFINE_HANDLER(Query)
{
// Extract name
char delim = ':';
size_t delimPos = m_commandData.find_first_of(":,");
std::string_view cmdName = m_commandData;
if (delimPos != std::string_view::npos) {
delim = m_commandData[delimPos];
cmdName.remove_suffix(cmdName.size() - delimPos);
m_commandData.remove_prefix(delimPos + 1);
}
// Only 2 commands are delimited by a comma, all with lowercase 'q' prefix
// We don't handle qP nor qL
if (delim != ':') {
if (m_commandLetter != 'q') {
return ReplyErrno(EILSEQ);
} else if (cmdName != "Rcmd" && cmdName != "ThreadExtraInfo") {
return ReplyErrno(EILSEQ);
}
}
if (m_commandLetter == 'q') {
QUERY_CMD_CASE(Supported)
QUERY_CMD_CASE(Xfer)
QUERY_CMD_CASE(Attached)
QUERY_CMD_CASE(fThreadInfo)
QUERY_CMD_CASE(sThreadInfo)
QUERY_CMD_CASE(ThreadExtraInfo)
QUERY_CMD_CASE2("C", CurrentThreadId)
QUERY_CMD_CASE(Rcmd)
/*default :*/{
return HandleUnsupported();
}
} else {
QUERY_CMD_CASE(StartNoAckMode)
QUERY_CMD_CASE(ThreadEvents)
/*default :*/{
return HandleUnsupported();
}
}
}
#undef QUERY_CMD_CASE
#undef QUERY_CMD_CASE2
}

View File

@@ -1,227 +0,0 @@
/*
* Copyright (c) 2019-2020 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/>.
*/
/*
* This file is part of Luma3DS.
* Copyright (C) 2016-2019 Aurora Wright, TuxSH
*
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/
#include "hvisor_gdb_defines_internal.hpp"
#include "hvisor_gdb_packet_data.hpp"
#include "../hvisor_exception_stack_frame.hpp"
#include "../hvisor_fpu_register_cache.hpp"
namespace {
auto GetRegisterPointerAndSize(unsigned long id, ams::hvisor::ExceptionStackFrame *frame, ams::hvisor::FpuRegisterCache::Storage &fpuRegStorage)
{
void *outPtr = nullptr;
size_t outSz = 0;
switch (id) {
case 0 ... 30:
outPtr = &frame->x[id];
outSz = 8;
break;
case 31:
outPtr = &frame->GetSpRef();
outSz = 8;
break;
case 32:
outPtr = &frame->spsr_el2;
outSz = 4;
break;
case 33 ... 64:
outPtr = &fpuRegStorage.q[id - 33];
outSz = 16;
break;
case 65:
outPtr = &fpuRegStorage.fpsr;
outSz = 4;
break;
case 66:
outPtr = &fpuRegStorage.fpcr;
outSz = 4;
break;
default:
__builtin_unreachable();
break;
}
return std::tuple{outPtr, outSz};
}
}
namespace ams::hvisor::gdb {
// Note: GDB treats cpsr, fpsr, fpcr as 32-bit integers...
GDB_DEFINE_HANDLER(ReadRegisters)
{
ENSURE(m_selectedCoreId == currentCoreCtx->GetCoreId());
GDB_CHECK_NO_CMD_DATA();
ExceptionStackFrame *frame = currentCoreCtx->GetGuestFrame();
auto &fpuRegStorage = FpuRegisterCache::GetInstance().ReadRegisters();
char *buf = GetInPlaceOutputBuffer();
size_t n = 0;
struct {
u64 sp;
u64 pc;
u32 cpsr;
} cpuSprs = {
.sp = frame->GetSpRef(),
.pc = frame->elr_el2,
.cpsr = static_cast<u32>(frame->spsr_el2),
};
u32 fpuSprs[2] = {
static_cast<u32>(fpuRegStorage.fpsr),
static_cast<u32>(fpuRegStorage.fpcr),
};
n += EncodeHex(buf + n, frame->x, sizeof(frame->x));
n += EncodeHex(buf + n, &cpuSprs, 8+8+4);
n += EncodeHex(buf + n, fpuRegStorage.q, sizeof(fpuRegStorage.q));
n += EncodeHex(buf + n, fpuSprs, sizeof(fpuSprs));
return SendPacket(std::string_view{buf, n});
}
GDB_DEFINE_HANDLER(WriteRegisters)
{
ENSURE(m_selectedCoreId == currentCoreCtx->GetCoreId());
ExceptionStackFrame *frame = currentCoreCtx->GetGuestFrame();
auto &fpuRegStorage = FpuRegisterCache::GetInstance().ReadRegisters();
char *tmp = GetWorkBuffer();
size_t n = 0;
struct {
u64 sp;
u64 pc;
u32 cpsr;
} cpuSprs;
u32 fpuSprs[2];
struct {
void *dst;
size_t sz;
} infos[4] = {
{ frame->x, sizeof(frame->x) },
{ &cpuSprs, 8+8+4 },
{ fpuRegStorage.q, sizeof(fpuRegStorage.q) },
{ fpuSprs, sizeof(fpuSprs) },
};
// Parse & return on error
for (const auto &info: infos) {
// Fuck std::string_view.substr throwing exceptions
if (DecodeHex(tmp + n, m_commandData.data(), info.sz) != info.sz) {
return ReplyErrno(EILSEQ);
}
m_commandData.remove_prefix(2 * info.sz);
n += info.sz;
}
// Copy. Note: we don't check if cpsr (spsr_el2) was modified to return to EL2...
n = 0;
for (const auto &info: infos) {
std::copy(tmp + n, tmp + n + info.sz, info.dst);
n += info.sz;
}
frame->GetSpRef() = cpuSprs.sp;
frame->elr_el2 = cpuSprs.pc;
frame->spsr_el2 = cpuSprs.cpsr;
fpuRegStorage.fpsr = fpuSprs[0];
fpuRegStorage.fpcr = fpuSprs[1];
FpuRegisterCache::GetInstance().CommitRegisters();
return ReplyOk();
}
GDB_DEFINE_HANDLER(ReadRegister)
{
ENSURE(m_selectedCoreId == currentCoreCtx->GetCoreId());
ExceptionStackFrame *frame = currentCoreCtx->GetGuestFrame();
FpuRegisterCache::Storage *fpuRegStorage = nullptr;
auto [nread, gdbRegNum] = ParseHexIntegerList<1>(m_commandData);
if (nread == 0) {
return ReplyErrno(EILSEQ);
}
// Check the register number
if (gdbRegNum >= 31 + 3 + 32 + 2) {
return ReplyErrno(EINVAL);
}
if (gdbRegNum > 31 + 3) {
// FPU register -- must read the FPU registers first
fpuRegStorage = &FpuRegisterCache::GetInstance().ReadRegisters();
}
return std::apply(SendHexPacket, GetRegisterPointerAndSize(gdbRegNum, frame, *fpuRegStorage));
}
GDB_DEFINE_HANDLER(WriteRegister)
{
ENSURE(m_selectedCoreId == currentCoreCtx->GetCoreId());
char *tmp = GetWorkBuffer();
ExceptionStackFrame *frame = currentCoreCtx->GetGuestFrame();
auto &fpuRegStorage = FpuRegisterCache::GetInstance().GetStorageRef();
auto [nread, gdbRegNum] = ParseHexIntegerList<1>(m_commandData, '=');
if (nread == 0) {
return ReplyErrno(EILSEQ);
}
m_commandData.remove_prefix(nread);
// Check the register number
if (gdbRegNum >= 31 + 3 + 32 + 2) {
return ReplyErrno(EINVAL);
}
auto [regPtr, sz] = GetRegisterPointerAndSize(gdbRegNum, frame, fpuRegStorage);
// Decode, check for errors
if (m_commandData.size() != 2 * sz || DecodeHex(tmp, m_commandData) != sz) {
return ReplyErrno(EILSEQ);
}
std::copy(tmp, tmp + sz, regPtr);
if (gdbRegNum > 31 + 3) {
// FPU register -- must commit the FPU registers
FpuRegisterCache::GetInstance().CommitRegisters();
}
return ReplyOk();
}
}

View File

@@ -1,75 +0,0 @@
/*
* Copyright (c) 2019-2020 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/>.
*/
/*
* This file is part of Luma3DS.
* Copyright (C) 2016-2019 Aurora Wright, TuxSH
*
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/
#include "hvisor_gdb_defines_internal.hpp"
#include "hvisor_gdb_packet_data.hpp"
namespace {
constexpr std::string_view SkipSpaces(std::string_view str)
{
size_t n = str.find_first_not_of("\t\v\n\f\r ");
if (n == std::string_view::npos) {
return {};
} else {
str.remove_prefix(n);
return str;
}
}
}
namespace ams::hvisor::gdb {
GDB_DEFINE_QUERY_HANDLER(Rcmd)
{
char *buf = GetInPlaceOutputBuffer();
size_t encodedLen = m_commandData.size();
if (encodedLen == 0 || encodedLen % 2 != 0) {
ReplyErrno(EILSEQ);
}
// Decode in place
if (DecodeHex(buf, m_commandData) != encodedLen / 2) {
ReplyErrno(EILSEQ);
}
// Extract command name, data
m_commandData = std::string_view{buf, encodedLen / 2};
size_t nameSize = m_commandData.find_first_of("\t\v\n\f\r ");
std::string_view commandName = m_commandData;
if (nameSize != std::string_view::npos) {
commandName.remove_suffix(commandName.size() - nameSize);
m_commandData.remove_prefix(nameSize);
m_commandData = SkipSpaces(m_commandData);
} else {
m_commandData = std::string_view{};
}
// Nothing implemented yet
(void)commandName;
return SendHexPacket("Unrecognized command.\n");
}
}

View File

@@ -1,92 +0,0 @@
/*
* Copyright (c) 2019-2020 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/>.
*/
/*
* This file is part of Luma3DS.
* Copyright (C) 2016-2019 Aurora Wright, TuxSH
*
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/
#include "hvisor_gdb_defines_internal.hpp"
#include "hvisor_gdb_packet_data.hpp"
#include "../hvisor_hw_breakpoint_manager.hpp"
#include "../hvisor_sw_breakpoint_manager.hpp"
#include "../hvisor_watchpoint_manager.hpp"
namespace ams::hvisor::gdb {
GDB_DEFINE_HANDLER(ToggleStopPoint)
{
bool add = m_commandLetter == 'Z';
auto [nread, kind, addr, size] = ParseHexIntegerList<3>(m_commandData, ';');
if (nread == 0) {
return ReplyErrno(EILSEQ);
}
m_commandData.remove_prefix(nread);
// We don't support cond_list
bool persist = m_commandData == "cmds:1";
// In theory we should reject leading zeroes in "kind". Oh well...
int res;
static const cpu::DebugRegisterPair::LoadStoreControl kinds[3] = {
cpu::DebugRegisterPair::Store,
cpu::DebugRegisterPair::Load,
cpu::DebugRegisterPair::LoadStore,
};
auto &hwBpMgr = HwBreakpointManager::GetInstance();
auto &swBpMgr = SwBreakpointManager::GetInstance();
auto &wpMgr = WatchpointManager::GetInstance();
switch(kind) {
// Software breakpoint
case 0: {
if(size != 4) {
return ReplyErrno(EINVAL);
}
res = add ? swBpMgr.Add(addr, persist) : swBpMgr.Remove(addr, false);
return res == 0 ? ReplyOk() : ReplyErrno(-res);
}
// Hardware breakpoint
case 1: {
if(size != 4) {
return ReplyErrno(EINVAL);
}
res = add ? hwBpMgr.Add(addr) : hwBpMgr.Remove(addr);
return res == 0 ? ReplyOk() : ReplyErrno(-res);
}
// Watchpoints
case 2:
case 3:
case 4: {
res = add ? wpMgr.Add(addr, size, kinds[kind - 2]) : wpMgr.Remove(addr, size, kinds[kind - 2]);
return res == 0 ? ReplyOk() : ReplyErrno(-res);
}
default: {
return ReplyEmpty();
}
}
}
}

View File

@@ -1,159 +0,0 @@
/*
* Copyright (c) 2019-2020 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 <cstdio>
#include "hvisor_gdb_thread.hpp"
#include "hvisor_gdb_defines_internal.hpp"
#include "hvisor_gdb_packet_data.hpp"
#include "../hvisor_core_context.hpp"
namespace ams::hvisor::gdb {
int ConvertTidToCoreId(unsigned long tid)
{
switch (tid) {
case ULONG_MAX:
return -1;
case 0:
return currentCoreCtx->GetCoreId();
default:
return currentCoreCtx->GetCoreId() - 1;
}
}
std::optional<int> ParseConvertExactlyOneTid(std::string_view str)
{
if (str.size() == 2 && str[0] == '-' && str[1] == '1') {
return -1;
} else {
auto [n, tid] = ParseHexIntegerList<1>(str);
if (n != 0 && tid < MAX_CORE + 1) {
return ConvertTidToCoreId(tid);
} else {
return {};
}
}
}
// Hg<tid>, Hc<tid>
GDB_DEFINE_HANDLER(SetThreadId)
{
if (!m_commandData.starts_with('g') && !m_commandData.starts_with('c')) {
return ReplyErrno(EINVAL);
}
char kind = m_commandData[0];
m_commandData.remove_prefix(1);
auto coreIdOpt = ParseConvertExactlyOneTid(m_commandData);
if (!coreIdOpt) {
return ReplyErrno(EILSEQ);
}
int coreId = *coreIdOpt;
if (kind == 'g') {
if (coreId = -1) {
return ReplyErrno(EINVAL);
}
m_selectedCoreId = coreId;
MigrateRxIrq(m_selectedCoreId);
} else {
m_selectedCoreIdForContinuing = coreId;
}
return ReplyOk();
}
GDB_DEFINE_HANDLER(IsThreadAlive)
{
int coreId = ParseConvertExactlyOneTid(m_commandData).value_or(-1);
if (coreId < 0) {
return ReplyErrno(EILSEQ);
}
// Is the core off?
if (m_attachedCoreList & BIT(coreId)) {
return ReplyOk();
} else {
return ReplyErrno(ESRCH);
}
}
GDB_DEFINE_QUERY_HANDLER(CurrentThreadId)
{
GDB_CHECK_NO_CMD_DATA();
return SendFormattedPacket("QC%x", 1 + currentCoreCtx->GetCoreId());
}
GDB_DEFINE_QUERY_HANDLER(fThreadInfo)
{
GDB_CHECK_NO_CMD_DATA();
// We have made our GDB packet big enough to list all the thread ids (coreIds + 1 for each coreId)
char *buf = GetInPlaceOutputBuffer();
size_t n = 0;
for (int coreId: util::BitsOf{m_attachedCoreList}) {
n += sprintf(buf + n, "%lx,", 1u + coreId);
}
// Remove trailing comma
--n;
return SendStreamData(std::string_view{buf, n}, 0, n, true);
}
GDB_DEFINE_QUERY_HANDLER(sThreadInfo)
{
GDB_CHECK_NO_CMD_DATA();
// We have made our GDB packet big enough to list all the thread ids (coreIds + 1 for each coreId) in fThreadInfo
// Note: we assume GDB doesn't accept notifications during the sequence transfer...
return SendPacket("l");
}
GDB_DEFINE_QUERY_HANDLER(ThreadEvents)
{
if (m_commandData.size() != 1) {
return ReplyErrno(EILSEQ);
}
switch (m_commandData[0]) {
case '0':
m_catchThreadEvents = false;
return ReplyOk();
case '1':
m_catchThreadEvents = true;
return ReplyOk();
default:
return ReplyErrno(EILSEQ);
}
}
GDB_DEFINE_QUERY_HANDLER(ThreadExtraInfo)
{
int coreId = ParseConvertExactlyOneTid(m_commandData).value_or(-1);
if (coreId < 0) {
return ReplyErrno(EILSEQ);
}
size_t n = sprintf(m_workBuffer, "TODO");
return SendHexPacket(m_workBuffer, n);
}
}

View File

@@ -1,26 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_gdb_context.hpp"
namespace ams::hvisor::gdb {
int ConvertTidToCoreId(unsigned long tid);
std::optional<int> ParseConvertExactlyOneTid(std::string_view str);
}

View File

@@ -1,62 +0,0 @@
/*
* Copyright (c) 2019-2020 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/>.
*/
/*
* This file is part of Luma3DS.
* Copyright (C) 2016-2019 Aurora Wright, TuxSH
*
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/
#include "hvisor_gdb_defines_internal.hpp"
#include "hvisor_gdb_packet_data.hpp"
namespace ams::hvisor::gdb {
GDB_DEFINE_HANDLER(VerboseCommand)
{
// Extract name
char delim = ':';
size_t delimPos = m_commandData.find_first_of(";:");
std::string_view cmdName = m_commandData;
if (delimPos != std::string_view::npos) {
delim = m_commandData[delimPos];
cmdName.remove_suffix(cmdName.size() - delimPos);
m_commandData.remove_prefix(delimPos + 1);
}
if (cmdName == "Cont?") {
GDB_VERBOSE_HANDLER(ContinueSupported)();
} else if (cmdName == "Cont") {
GDB_VERBOSE_HANDLER(Continue)();
} else if (cmdName == "CtrlC") {
GDB_VERBOSE_HANDLER(CtrlC)();
} else if (cmdName == "MustReplyEmpty") {
return HandleUnsupported();
} else if (cmdName == "Stopped") {
return GDB_VERBOSE_HANDLER(Stopped)();
} else {
return HandleUnsupported(); // No handler found!
}
}
GDB_DEFINE_VERBOSE_HANDLER(ContinueSupported)
{
return SendPacket("vCont;c;C;s;S;t;r");
}
}

View File

@@ -1,143 +0,0 @@
/*
* Copyright (c) 2019-2020 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/>.
*/
/*
* This file is part of Luma3DS.
* Copyright (C) 2016-2019 Aurora Wright, TuxSH
*
* SPDX-License-Identifier: (MIT OR GPL-2.0-or-later)
*/
#include "hvisor_gdb_defines_internal.hpp"
#include "hvisor_gdb_packet_data.hpp"
namespace {
std::string_view GenerateTargetXml(char *buf)
{
int pos;
const char *hdr = "<?xml version=\"1.0\"?><!DOCTYPE feature SYSTEM \"gdb-target.dtd\"><target>";
const char *cpuDescBegin = "<feature name=\"org.gnu.gdb.aarch64.core\">";
const char *cpuDescEnd =
"<reg name=\"sp\" bitsize=\"64\" type=\"data_ptr\"/>"
"<reg name=\"pc\" bitsize=\"64\" type=\"code_ptr\"/>"
"<reg name=\"cpsr\" bitsize=\"32\"/></feature>";
const char *fpuDescBegin =
"<feature name=\"org.gnu.gdb.aarch64.fpu\"><vector id=\"v2d\" type=\"ieee_double\" count=\"2\"/>"
"<vector id=\"v2u\" type=\"uint64\" count=\"2\"/><vector id=\"v2i\" type=\"int64\" count=\"2\"/>"
"<vector id=\"v4f\" type=\"ieee_single\" count=\"4\"/><vector id=\"v4u\" type=\"uint32\" count=\"4\"/>"
"<vector id=\"v4i\" type=\"int32\" count=\"4\"/><vector id=\"v8u\" type=\"uint16\" count=\"8\"/>"
"<vector id=\"v8i\" type=\"int16\" count=\"8\"/><vector id=\"v16u\" type=\"uint8\" count=\"16\"/>"
"<vector id=\"v16i\" type=\"int8\" count=\"16\"/><vector id=\"v1u\" type=\"uint128\" count=\"1\"/>"
"<vector id=\"v1i\" type=\"int128\" count=\"1\"/><union id=\"vnd\"><field name=\"f\" type=\"v2d\"/>"
"<field name=\"u\" type=\"v2u\"/><field name=\"s\" type=\"v2i\"/></union><union id=\"vns\">"
"<field name=\"f\" type=\"v4f\"/><field name=\"u\" type=\"v4u\"/><field name=\"s\" type=\"v4i\"/></union>"
"<union id=\"vnh\"><field name=\"u\" type=\"v8u\"/><field name=\"s\" type=\"v8i\"/></union><union id=\"vnb\">"
"<field name=\"u\" type=\"v16u\"/><field name=\"s\" type=\"v16i\"/></union><union id=\"vnq\">"
"<field name=\"u\" type=\"v1u\"/><field name=\"s\" type=\"v1i\"/></union><union id=\"aarch64v\">"
"<field name=\"d\" type=\"vnd\"/><field name=\"s\" type=\"vns\"/><field name=\"h\" type=\"vnh\"/>"
"<field name=\"b\" type=\"vnb\"/><field name=\"q\" type=\"vnq\"/></union>";
const char *fpuDescEnd = "<reg name=\"fpsr\" bitsize=\"32\"/>\r\n<reg name=\"fpcr\" bitsize=\"32\"/>\r\n</feature>";
const char *footer = "</target>";
std::strcpy(buf, hdr);
// CPU registers
std::strcat(buf, cpuDescBegin);
pos = static_cast<int>(std::strlen(buf));
for (u32 i = 0; i < 31; i++) {
pos += std::sprintf(buf + pos, "<reg name=\"x%u\" bitsize=\"64\"/>", i);
}
std::strcat(buf, cpuDescEnd);
std::strcat(buf, fpuDescBegin);
pos = static_cast<int>(std::strlen(buf));
for (u32 i = 0; i < 32; i++) {
pos += std::sprintf(buf + pos, "<reg name=\"v%u\" bitsize=\"128\" type=\"aarch64v\"/>", i);
}
std::strcat(buf, fpuDescEnd);
std::strcat(buf, footer);
return std::string_view{buf};
}
}
namespace ams::hvisor::gdb {
GDB_DEFINE_XFER_HANDLER(Features)
{
if (write || annex != "target.xml") {
return ReplyEmpty();
}
// Generate the target xml on-demand
// This is a bit whack, we rightfully assume that GDB won't sent any other command during the stream transfer
if (m_targetXml.empty()) {
m_targetXml = GenerateTargetXml(m_workBuffer);
}
int n = SendStreamData(m_targetXml, offset, length, false);
// Transfer ended
if(offset + length >= m_targetXml.size()) {
m_targetXml = {};
}
return n;
}
GDB_DEFINE_QUERY_HANDLER(Xfer)
{
// e.g. qXfer:features:read:annex:offset,length
// Split
auto [cmd, directionStr, annex, offsetlen] = SplitString<4>(m_commandData, ':');
if (offsetlen.empty()) {
return ReplyErrno(EILSEQ);
}
// Check direction
bool isWrite;
if (directionStr == "read") {
isWrite = false;
} else if (directionStr == "write") {
isWrite = true;
} else {
return ReplyErrno(EILSEQ);
}
// Get offset and length
auto [nread, off, len] = ParseHexIntegerList<2>(offsetlen, isWrite ? ':' : '\0');
if (nread == 0) {
return ReplyErrno(EILSEQ);
}
// Get data/nothing
m_commandData = offsetlen;
m_commandData.remove_prefix(nread);
// Run command
if (cmd == "features") {
return GDB_XFER_HANDLER(Features)(isWrite, annex, off, len);
} else {
return HandleUnsupported();
}
}
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_core_context.hpp"
#include "cpu/hvisor_cpu_instructions.hpp"
namespace ams::hvisor {
std::array<CoreContext, MAX_CORE> CoreContext::instances{};
std::atomic<u32> CoreContext::activeCoreMask{};
bool CoreContext::coldboot = true;
void CoreContext::InitializeCoreInstance(u32 coreId, bool isBootCore, u64 argument)
{
CoreContext &instance = instances[coreId];
instance.m_coreId = coreId;
instance.m_bootCore = isBootCore;
instance.m_kernelArgument = argument;
if (isBootCore && instance.m_kernelEntrypoint == 0) {
instance.m_kernelEntrypoint = initialKernelEntrypoint;
}
currentCoreCtx = &instance;
cpu::dmb();
}
}

View File

@@ -1,111 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "defines.hpp"
namespace ams::hvisor {
struct ExceptionStackFrame;
class CoreContext;
register CoreContext *currentCoreCtx asm("x18");
class alignas(64) CoreContext final {
// This should be 64-byte big
NON_COPYABLE(CoreContext);
NON_MOVEABLE(CoreContext);
private:
static std::array<CoreContext, MAX_CORE> instances;
static std::atomic<u32> activeCoreMask;
static bool coldboot; // "coldboot" to be 'true' on init & thus not in BSS
// start.s
static uintptr_t initialKernelEntrypoint;
private:
ExceptionStackFrame *m_guestFrame = nullptr;
u64 m_kernelArgument = 0;
uintptr_t m_kernelEntrypoint = 0;
u32 m_coreId = 0;
bool m_bootCore = false;
// Debug features
bool m_wasPaused = false;
uintptr_t m_steppingRangeStartAddr = 0;
uintptr_t m_steppingRangeEndAddr = 0;
// Timer stuff
u64 m_totalTimeInHypervisor = 0;
u64 m_emulPtimerCval = 0;
private:
constexpr CoreContext() = default;
public:
static void InitializeCoreInstance(u32 coreId, bool isBootCore, u64 argument);
static CoreContext &GetInstanceFor(u32 coreId) { return instances[coreId]; }
static u32 GetActiveCoreMask() { return activeCoreMask.load(); }
static u32 SetCurrentCoreActive()
{
activeCoreMask |= BIT(currentCoreCtx->m_coreId);
}
static bool IsColdboot() { return coldboot; }
public:
constexpr ExceptionStackFrame *GetGuestFrame() const { return m_guestFrame; }
constexpr void SetGuestFrame(ExceptionStackFrame *frame) { m_guestFrame = frame; }
constexpr u64 GetKernelArgument() const { return m_kernelArgument; }
constexpr u64 GetKernelEntrypoint() const { return m_kernelEntrypoint; }
constexpr u32 GetCoreId() const { return m_coreId; }
constexpr bool IsBootCore() const { return m_bootCore; }
constexpr u64 SetKernelEntrypoint(uintptr_t ep, bool warmboot = false)
{
if (warmboot) {
// No race possible, only possible transition is 1->0 and we only really check IsColdboot() at init time
// And CPU_SUSPEND should only be called with only one core left.
coldboot = false;
}
m_kernelEntrypoint = ep;
}
constexpr bool WasPaused() const { return m_wasPaused; }
constexpr void SetPausedFlag(bool wasPaused) { m_wasPaused = wasPaused; }
constexpr auto GetSteppingRange() const
{
return std::tuple{m_steppingRangeStartAddr, m_steppingRangeEndAddr};
}
constexpr void SetSteppingRange(uintptr_t startAddr, uintptr_t endAddr)
{
m_steppingRangeStartAddr = startAddr;
m_steppingRangeEndAddr = endAddr;
}
constexpr u64 GetTotalTimeInHypervisor() const { return m_totalTimeInHypervisor; }
constexpr void IncrementTotalTimeInHypervisor(u64 timeDelta) { m_totalTimeInHypervisor += timeDelta; }
constexpr u64 GetEmulPtimerCval() const { return m_emulPtimerCval; }
constexpr void SetEmulPtimerCval(u64 cval) { m_emulPtimerCval = cval; }
};
}

View File

@@ -1,183 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_exception_dispatcher.hpp"
#include "hvisor_irq_manager.hpp"
#include "hvisor_fpu_register_cache.hpp"
#include "hvisor_guest_timers.hpp"
#include "hvisor_generic_timer.hpp"
#include "hvisor_memory_map.hpp"
#include "traps/hvisor_traps_data_abort.hpp"
#include "traps/hvisor_traps_hvc.hpp"
#include "traps/hvisor_traps_single_step.hpp"
#include "traps/hvisor_traps_smc.hpp"
#include "traps/hvisor_traps_sysreg.hpp"
#include "debug_manager.h"
namespace ams::hvisor {
void EnableGeneralTraps(void)
{
u64 hcr = THERMOSPHERE_GET_SYSREG(hcr_el2);
// Trap SMC instructions
hcr |= cpu::HCR_TSC;
// Trap set/way isns
hcr |= cpu::HCR_TSW;
// Reroute physical IRQs to EL2
hcr |= cpu::HCR_IMO;
// Make sure HVC is enabled
hcr &= ~cpu::HCR_HCD;
THERMOSPHERE_SET_SYSREG(hcr_el2, hcr);
EnableGuestTimerTraps();
}
void DumpStackFrame(ExceptionStackFrame *frame, bool sameEl)
{
#ifndef NDEBUG
uintptr_t stackTop = MemoryMap::GetStackTopVa(currentCoreCtx->GetCoreId());
for (u32 i = 0; i < 30; i += 2) {
DEBUG("x%u\t\t%016llx\t\tx%u\t\t%016llx\n", i, frame->x[i], i + 1, frame->x[i + 1]);
}
DEBUG("x30\t\t%016llx\n\n", frame->x[30]);
DEBUG("elr_el2\t\t%016llx\n", frame->elr_el2);
DEBUG("spsr_el2\t%016llx\n", frame->spsr_el2);
DEBUG("far_el2\t\t%016llx\n", frame->far_el2);
if (sameEl) {
DEBUG("sp_el2\t\t%016llx\n", frame->sp_el2);
} else {
DEBUG("sp_el1\t\t%016llx\n", frame->sp_el1);
}
DEBUG("sp_el0\t\t%016llx\n", frame->sp_el0);
DEBUG("cntpct_el0\t%016llx\n", frame->cntpct_el0);
if (frame == currentCoreCtx->GetGuestFrame()) {
DEBUG("cntp_ctl_el0\t%016llx\n", frame->cntp_ctl_el0);
DEBUG("cntv_ctl_el0\t%016llx\n", frame->cntv_ctl_el0);
} else if ((frame->sp_el2 & ~0xFFFul) + 0x1000 == stackTop) {
// Try to dump the stack (comment if this crashes)
u64 *sp = reinterpret_cast<u64 *>(frame->sp_el2);
u64 *spEnd = sp + 0x20;
u64 *spMax = reinterpret_cast<u64 *>((frame->sp_el2 + 0xFFF) & ~0xFFFul);
DEBUG("Stack trace:\n");
while (sp < spEnd && sp < spMax) {
DEBUG("\t%016lx\n", *sp++);
}
} else {
DEBUG("Stack overflow/double fault detected!\n");
}
#else
(void)frame;
(void)sameEl;
#endif
}
void ExceptionEntryPostprocess(ExceptionStackFrame *frame, bool isLowerEl)
{
if (isLowerEl) {
currentCoreCtx->SetGuestFrame(frame);
frame->cntp_ctl_el0 = THERMOSPHERE_GET_SYSREG(cntp_ctl_el0);
frame->cntv_ctl_el0 = THERMOSPHERE_GET_SYSREG(cntv_ctl_el0);
}
}
void ExceptionReturnPreprocess(ExceptionStackFrame *frame)
{
if (frame == currentCoreCtx->GetGuestFrame()) {
if (currentCoreCtx->WasPaused()) {
// Were we paused & are we about to return to the guest?
IrqManager::EnterInterruptibleHypervisorCode();
while (!debugManagerHandlePause());
FpuRegisterCache::GetInstance().CleanInvalidate;
}
// Update virtual counter
u64 ticksNow = GenericTimer::GetSystemTick();
currentCoreCtx->IncrementTotalTimeInHypervisor(ticksNow - frame->cntpct_el0);
UpdateVirtualOffsetSysreg();
// Restore timer interrupt config
THERMOSPHERE_SET_SYSREG(cntp_ctl_el0, frame->cntp_ctl_el0);
THERMOSPHERE_SET_SYSREG(cntv_ctl_el0, frame->cntv_ctl_el0);
}
}
void HandleLowerElSyncException(ExceptionStackFrame *frame)
{
auto esr = frame->esr_el2;
switch (esr.ec) {
case cpu::ExceptionSyndromeRegister::CP15RTTrap:
traps::HandleMcrMrcCP15Trap(frame, esr);
break;
case cpu::ExceptionSyndromeRegister::CP15RRTTrap:
traps::HandleMcrrMrrcCP15Trap(frame, esr);
break;
case cpu::ExceptionSyndromeRegister::CP14RTTrap:
case cpu::ExceptionSyndromeRegister::CP14DTTrap:
case cpu::ExceptionSyndromeRegister::CP14RRTTrap:
// A32 stub: Skip instruction, read 0 if necessary (there are debug regs at EL0)
traps::HandleA32CP14Trap(frame, esr);
break;
case cpu::ExceptionSyndromeRegister::HypervisorCallA64:
traps::HandleHvc(frame, esr);
break;
case cpu::ExceptionSyndromeRegister::MonitorCallA64:
traps::HandleSmc(frame, esr);
break;
case cpu::ExceptionSyndromeRegister::SystemRegisterTrap:
traps::HandleMsrMrsTrap(frame, esr);
break;
case cpu::ExceptionSyndromeRegister::DataAbortLowerEl:
// Basically, stage2 translation faults
traps::HandleLowerElDataAbort(frame, esr);
break;
case cpu::ExceptionSyndromeRegister::SoftwareStepLowerEl:
traps::HandleSingleStep(frame, esr);
break;
case cpu::ExceptionSyndromeRegister::BreakpointLowerEl:
case cpu::ExceptionSyndromeRegister::WatchpointLowerEl:
case cpu::ExceptionSyndromeRegister::SoftwareBreakpointA64:
case cpu::ExceptionSyndromeRegister::SoftwareBreakpointA32:
debugManagerReportEvent(DBGEVENT_EXCEPTION);
break;
default:
DEBUG("Lower EL sync exception, EC = 0x%02llx IL=%llu ISS=0x%06llx\n", (u64)esr.ec, esr.il, esr.iss);
DumpStackFrame(frame, false);
break;
}
}
void HandleSameElSyncException(ExceptionStackFrame *frame)
{
auto esr = frame->esr_el2;
DEBUG("Same EL sync exception on core %x, EC = 0x%02x IL=%llu ISS=0x%06llx\n", currentCoreCtx->GetCoreId(), esr.ec, esr.il, esr.iss);
DumpStackFrame(frame, true);
}
void HandleUnknownException(u32 offset)
{
DEBUG("Unknown exception on core %x! (offset 0x%03lx)\n", currentCoreCtx->GetCoreId(), offset);
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_exception_stack_frame.hpp"
#include "cpu/hvisor_cpu_exception_sysregs.hpp"
namespace ams::hvisor {
void EnableGeneralTraps(void);
void DumpStackFrame(const ExceptionStackFrame *frame, bool sameEl);
// Called on exception entry (avoids overflowing a vector section)
void ExceptionEntryPostprocess(ExceptionStackFrame *frame, bool isLowerEl);
// Called on exception return (avoids overflowing a vector section)
void ExceptionReturnPreprocess(ExceptionStackFrame *frame);
void HandleLowerElSyncException(ExceptionStackFrame *frame);
void HandleSameElSyncException(ExceptionStackFrame *frame);
void HandleUnknownException(u32 offset);
}

View File

@@ -1,144 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "cpu/hvisor_cpu_exception_sysregs.hpp"
namespace ams::hvisor {
struct alignas(16) ExceptionStackFrame {
u64 x[31]; // x0 .. x30
union {
u64 sp_el1;
u64 sp_el2;
};
u64 sp_el0;
u64 elr_el2;
u64 spsr_el2;
cpu::ExceptionSyndromeRegister esr_el2;
u64 far_el2;
u64 cntpct_el0;
u64 cntp_ctl_el0;
u64 cntv_ctl_el0;
constexpr bool IsA32() const { return (spsr_el2 & cpu::PSR_MODE32) != 0; }
constexpr bool IsThumb() const { return IsA32() && (spsr_el2 & cpu::PSR_AA32_THUMB) != 0; }
constexpr u32 GetT32ItFlags() const
{
u64 it10 = (spsr_el2 >> cpu::PSR_AA32_IT10_MASK) & cpu::PSR_AA32_IT10_MASK;
u64 it72 = (spsr_el2 >> cpu::PSR_AA32_IT72_MASK) & cpu::PSR_AA32_IT72_MASK;
return it72 << 2 | it10;
}
constexpr void SetT32ItFlags(u32 flags)
{
spsr_el2 &= ~(cpu::PSR_AA32_IT72_MASK << cpu::PSR_AA32_IT72_SHIFT);
spsr_el2 &= ~(cpu::PSR_AA32_IT10_MASK << cpu::PSR_AA32_IT10_SHIFT);
u64 it10 = flags & cpu::PSR_AA32_IT10_MASK;
u64 it72 = (flags >> 2) & cpu::PSR_AA32_IT72_MASK;
spsr_el2 |= it72 << cpu::PSR_AA32_IT72_SHIFT;
spsr_el2 |= it10 << cpu::PSR_AA32_IT10_SHIFT;
}
constexpr bool EvaluateConditionCode(u32 conditionCode) const
{
u64 spsr = spsr_el2;
if (conditionCode == 14) {
// AL
return true;
} else if (conditionCode == 15) {
// Invalid encoding
return false;
}
// NZCV
bool n = (spsr & BIT(31)) != 0;
bool z = (spsr & BIT(30)) != 0;
bool c = (spsr & BIT(29)) != 0;
bool v = (spsr & BIT(28)) != 0;
bool tableHalf[] = {
// EQ, CS, MI, VS, HI, GE, GT
z, c, n, v, c && !z, n == v, !z && n == v,
};
return (conditionCode & 1) == 0 ? tableHalf[conditionCode / 2] : !tableHalf[conditionCode / 2];
}
constexpr void AdvanceItState()
{
u32 it = GetT32ItFlags();
// Just in case EL0 is executing A32 (& not sure if fully supported)
if (!IsThumb() || it == 0) {
return;
}
// Last instruction of the block => wipe, otherwise advance
SetT32ItFlags((it & 7) == 0 ? 0 : (it & 0xE0) | ((it << 1) & 0x1F));
}
constexpr void SkipInstruction(size_t size)
{
AdvanceItState();
elr_el2 += size;
}
template<typename T = u64>
constexpr T ReadRegister(u32 id) const
{
static_assert(std::is_integral_v<T> && std::is_unsigned_v<T>);
return id == 31 ? static_cast<T>(0u) /* xzr */ : static_cast<T>(x[id]);
}
constexpr void WriteRegister(u32 id, u64 val)
{
if (id != 31) {
// If not xzr
x[id] = val;
}
}
constexpr u64 &GetSpRef()
{
// Note: the return value is more or less meaningless if we took an exception from A32...
// We try our best to reflect which privilege level the exception was took from, nonetheless
bool spEl0 = false;
u64 m = spsr_el2 & 0xF;
if (IsA32()) {
spEl0 = m == 0;
} else {
u64 el = m >> 2;
spEl0 = el == 0 || (m & 1) == 0; // note: frame->sp_el2 is aliased to frame->sp_el1
}
return spEl0 ? sp_el0 : sp_el1;
}
};
static_assert(offsetof(ExceptionStackFrame, far_el2) == 0x120, "Wrong definition for ExceptionStackFrame");
static_assert(sizeof(ExceptionStackFrame) == 0x140, "Wrong size for ExceptionStackFrame");
static_assert(std::is_standard_layout_v<ExceptionStackFrame>);
static_assert(std::is_trivial_v<ExceptionStackFrame>);
}
/*void dumpStackFrame(const ExceptionStackFrame *frame, bool sameEl);
void exceptionEnterInterruptibleHypervisorCode(void);*/

View File

@@ -1,87 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "defines.hpp"
#include "hvisor_core_context.hpp"
namespace ams::hvisor {
class FpuRegisterCache final {
SINGLETON_WITH_ATTRS(FpuRegisterCache, TEMPORARY);
public:
struct Storage {
u128 q[32];
u64 fpsr;
u64 fpcr;
};
static_assert(std::is_standard_layout_v<Storage>);
private:
static void ReloadRegisters(const Storage *storage);
static void DumpRegisters(Storage *storage);
private:
Storage m_storage{};
u32 m_coreId = 0;
bool m_valid = false;
bool m_dirty = false;
public:
constexpr void TakeOwnership()
{
if (m_coreId != currentCoreCtx->GetCoreId()) {
m_valid = false;
m_dirty = false;
}
m_coreId = currentCoreCtx->GetCoreId();
}
Storage &GetStorageRef()
{
return m_storage;
}
Storage &ReadRegisters()
{
if (!m_valid) {
DumpRegisters(&m_storage);
m_valid = true;
}
return m_storage;
}
constexpr void CommitRegisters()
{
m_dirty = true;
// Because the caller rewrote the entire cache in the event it didn't read it before:
m_valid = true;
}
void CleanInvalidate()
{
if (m_dirty && m_coreId == currentCoreCtx->GetCoreId()) {
ReloadRegisters(&m_storage);
m_dirty = false;
}
m_valid = false;
}
public:
constexpr FpuRegisterCache() = default;
};
}

View File

@@ -1,58 +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 "asm_macros.s"
.macro LDSTORE_QREGS, op
\op q0, q1, [x0], 0x20
\op q2, q3, [x0], 0x20
\op q4, q5, [x0], 0x20
\op q6, q7, [x0], 0x20
\op q8, q9, [x0], 0x20
\op q10, q11, [x0], 0x20
\op q12, q13, [x0], 0x20
\op q14, q15, [x0], 0x20
\op q16, q17, [x0], 0x20
\op q18, q19, [x0], 0x20
\op q20, q21, [x0], 0x20
\op q22, q23, [x0], 0x20
\op q24, q25, [x0], 0x20
\op q26, q27, [x0], 0x20
\op q28, q29, [x0], 0x20
\op q30, q31, [x0], 0x20
.endm
FUNCTION _ZN3ams3hyp16FpuRegisterCache15ReloadRegistersEPKNS1_7StorageE
dmb ish
LDSTORE_QREGS ldp
ldp x1, x2, [x0]
msr fpsr, x1
msr fpcr, x2
dsb ish
isb
ret
END_FUNCTION
FUNCTION _ZN3ams3hyp16FpuRegisterCache13DumpRegistersEPNS1_7StorageE
dsb ish
isb
LDSTORE_QREGS stp
mrs x1, fpsr
mrs x2, fpcr
stp x1, x2, [x0]
dmb ish
ret
END_FUNCTION

View File

@@ -1,61 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_generic_timer.hpp"
#include "hvisor_irq_manager.hpp"
#include "hvisor_core_context.hpp"
#include "cpu/hvisor_cpu_interrupt_mask_guard.hpp"
#include "cpu/hvisor_cpu_instructions.hpp"
#include <mutex>
namespace ams::hvisor {
void GenericTimer::Initialize()
{
Configure(false, false);
if (currentCoreCtx->IsBootCore()) {
m_timerFreq = THERMOSPHERE_GET_SYSREG(cntfrq_el0);
}
IrqManager::GetInstance().Register(*this, irqId, true);
}
std::optional<bool> GenericTimer::InterruptTopHalfHandler(u32 irqId, u32)
{
if (irqId != GenericTimer::irqId) {
return std::nullopt;
}
// Mask the timer interrupt until reprogrammed
Configure(false, false);
return false;
}
void GenericTimer::WaitTicks(s64 ticks)
{
IrqManager::EnterInterruptibleHypervisorCode();
auto flags = cpu::UnmaskIrq();
SetTimeoutTicks(ticks);
do {
cpu::wfi();
} while (!GetInterruptStatus());
cpu::RestoreInterruptFlags(flags);
}
}

View File

@@ -1,103 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "defines.hpp"
#include "hvisor_i_interrupt_task.hpp"
#include "cpu/hvisor_cpu_sysreg_general.hpp"
#include "preprocessor.h"
#include "platform/interrupt_config.h"
#include <chrono>
namespace ams::hvisor {
class GenericTimer final : public IInterruptTask {
SINGLETON(GenericTimer);
private:
static constexpr u32 irqId = GIC_IRQID_NS_PHYS_HYP_TIMER;
private:
static void Configure(bool enabled, bool interruptMasked)
{
u64 ebit = enabled ? cpu::CNTCTL_ENABLE : 0;
u64 mbit = interruptMasked ? cpu::CNTCTL_IMASK: 0;
THERMOSPHERE_SET_SYSREG(cnthp_ctl_el2, mbit | ebit);
}
static bool GetInterruptStatus()
{
return (THERMOSPHERE_GET_SYSREG(cnthp_ctl_el2) & cpu::CNTCTL_ISTATUS) != 0;
}
private:
u64 m_timerFreq = 0;
private:
constexpr GenericTimer() = default;
public:
static s64 GetSystemTick()
{
return static_cast<s64>(THERMOSPHERE_GET_SYSREG(cntpct_el0));
}
static void SetTimeoutTicks(s64 ticks)
{
THERMOSPHERE_SET_SYSREG(cnthp_cval_el2, GetSystemTick() + ticks);
Configure(true, false);
}
static void WaitTicks(s64 ticks);
public:
void Initialize();
std::optional<bool> InterruptTopHalfHandler(u32 irqId, u32) final;
constexpr u64 GetTimerFrequency() const { return m_timerFreq; }
template<typename SecondRatio = std::ratio<1>>
auto GetSystemTime() const
{
auto tick = GetSystemTick();
return (tick * SecondRatio::den) / (m_timerFreq * SecondRatio::num);
}
std::chrono::nanoseconds GetSystemTimeNs() const
{
return std::chrono::nanoseconds{GetSystemTime<std::nano>()};
}
template<typename Duration>
void SetTimeout(Duration d) const
{
using SecondRatio = typename Duration::period;
auto v = (d.count() * m_timerFreq * SecondRatio::num) / SecondRatio::den;
SetTimeoutTicks(v);
}
template<typename Duration>
void Wait(Duration d) const
{
using SecondRatio = typename Duration::period;
auto v = (d.count() * m_timerFreq * SecondRatio::num) / SecondRatio::den;
WaitTicks(v);
}
};
}

View File

@@ -1,183 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "defines.hpp"
namespace ams::hvisor {
struct GicV2Distributor {
static constexpr u32 maxIrqId = 1019;
static constexpr u32 spuriousGrpNeedAckIrqId = 1022;
static constexpr u32 spuriousIrqId = 1023;
u32 ctlr;
u32 typer;
u32 iidr;
u8 _0x0c[0x80 - 0x0C];
// Note: in reality only 512 interrupts max. are defined (nor "reserved") on Gicv2
u32 igroupr[1024 / 32];
u32 isenabler[1024 / 32];
u32 icenabler[1024 / 32];
u32 ispendr[1024 / 32];
u32 icpendr[1024 / 32];
u32 isactiver[1024 / 32];
u32 icactiver[1024 / 32];
u8 ipriorityr[1024]; // can be accessed as u8 or u32
u8 itargetsr[1024]; // can be accessed as u8 or u32
u32 icfgr[1024 / 16];
u8 impldef_d00[0xF00 - 0xD00];
u32 sgir;
u8 _0xf04[0xF10 - 0xF04];
u8 cpendsgir[16];
u8 spendsgir[16];
u8 _0xf30[0xFE8 - 0xF30];
u32 icpidr2;
u8 _0xfec[0x1000 - 0xFEC];
enum SgirTargetListFilter : u32 {
ForwardToTargetList = 0,
ForwardToAllOthers = 1,
ForwardToSelf = 2,
};
static constexpr int GetCfgrShift(u32 id) {
return 2 * (id % 16);
}
};
struct GicV2Controller {
u32 ctlr;
u32 pmr;
u32 bpr;
u32 iar;
u32 eoir;
u32 rpr;
u32 hppir;
u32 abpr;
u32 aiar;
u32 aeoir;
u32 ahppir;
u8 _0x2c[0x40 - 0x2C];
u8 impldef_40[0xD0 - 0x40];
u32 apr[4];
u32 nsapr[4];
u8 _0xf0[0xFC - 0xF0];
u32 iidr;
u8 _0x100[0x1000 - 0x100];
u32 dir;
u8 _0x1004[0x2000 - 0x1004];
};
// GICH
struct GicV2VirtualInterfaceController {
union HypervisorControlRegister {
struct {
u32 en : 1;
u32 uie : 1;
u32 lrenpie : 1;
u32 npie : 1;
u32 vgrp0eie : 1;
u32 vgrp0die : 1;
u32 vgrp1eie : 1;
u32 vgrp1die : 1;
u32 _8 : 19;
u32 eoiCount : 5;
};
u32 raw;
};
union VmControlRegister {
struct {
u32 enableGrp0 : 1;
u32 enableGrp1 : 1;
u32 ackCtl : 1;
u32 fiqEn : 1;
u32 cbpr : 1;
u32 _5 : 4;
u32 eoiMode : 1;
u32 _10 : 8;
u32 abpr : 3;
u32 bpr : 3;
u32 _24 : 3;
u32 pmr : 5;
};
u32 raw;
};
union MaintenanceIntStatRegister {
struct {
u32 eoi : 1;
u32 u : 1;
u32 lrenp : 1;
u32 np : 1;
u32 vgrp0e : 1;
u32 vgrp0d : 1;
u32 vgrp1e : 1;
u32 vgrp1d : 1;
u32 _8 : 24;
};
u32 raw;
};
union ListRegister {
struct {
u32 virtualId : 10;
u32 physicalId : 10; // note: different encoding if hw = 0 (can't represent it in struct)
u32 sbz2 : 3;
u32 priority : 5;
u32 pending : 1;
u32 active : 1;
u32 grp1 : 1;
u32 hw : 1;
};
u32 raw;
};
HypervisorControlRegister hcr;
u32 vtr;
VmControlRegister vmcr;
u8 _0x0c[0x10 - 0xC];
MaintenanceIntStatRegister misr;
u8 _0x14[0x20 - 0x14];
u32 eisr0;
u32 eisr1;
u8 _0x28[0x30 - 0x28];
u32 elsr0;
u32 elsr1;
u8 _0x38[0xF0 - 0x38];
u32 apr;
u8 _0xf4[0x100 - 0xF4];
ListRegister lr[64];
};
struct GicV2VirtualInterface : public GicV2Controller {
// Allowed because no non-static members
static constexpr u32 numPriorityLevels = 32;
static constexpr u8 idlePriorityLevel = 0xF8;
};
static_assert(std::is_standard_layout_v<GicV2Distributor>);
static_assert(std::is_standard_layout_v<GicV2Controller>);
static_assert(std::is_standard_layout_v<GicV2VirtualInterfaceController>);
static_assert(std::is_standard_layout_v<GicV2VirtualInterface>);
static_assert(std::is_trivial_v<GicV2Distributor>);
static_assert(std::is_trivial_v<GicV2Controller>);
static_assert(std::is_trivial_v<GicV2VirtualInterfaceController>);
static_assert(std::is_trivial_v<GicV2VirtualInterface>);
}

View File

@@ -1,240 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_virtual_gic.hpp"
#include "hvisor_safe_io_copy.hpp"
#include "cpu/hvisor_cpu_caches.hpp"
#include "cpu/hvisor_cpu_interrupt_mask_guard.hpp"
using namespace ams::hvisor;
using namespace ams::hvisor::cpu;
namespace {
template<typename T>
T ReadBufferValue(const void *buf, size_t off)
{
static_assert(std::is_unsigned_v<T> && sizeof(T) <= 4);
T ret;
std::memcpy(&ret, reinterpret_cast<const u8 *>(buf) + off, sizeof(T));
return ret;
}
template<typename T>
void WriteBufferValue(void *buf, size_t off, T val)
{
static_assert(std::is_unsigned_v<T> && sizeof(T) <= 4);
std::memcpy(reinterpret_cast<u8 *>(buf) + off, T, sizeof(T));
}
size_t GuestReadWriteGicd(size_t offset, size_t size, void *readBuf, const void *writeBuf)
{
auto &vgic = VirtualGic::GetInstance();
if (readBuf != nullptr) {
size_t readOffset = 0;
size_t rem = size;
while (rem > 0) {
if ((offset + readOffset) % 4 == 0 && rem >= 4) {
// All accesses of this kind are valid
WriteBufferValue<u32>(readBuf, readOffset, vgic.ReadGicdRegister(offset + readOffset, 4));
readOffset += 4;
rem -= 4;
} else if ((offset + readOffset) % 2 == 0 && rem >= 2) {
// All accesses of this kind would be translated to ldrh and are thus invalid. Abort.
return readOffset;
} else if (VirtualGic::ValidateGicdRegisterAccess(offset + readOffset, 1)) {
// Valid byte access
WriteBufferValue<u8>(readBuf, readOffset, vgic.ReadGicdRegister(offset + readOffset, 1));
readOffset += 1;
rem -= 1;
} else {
// Invalid byte access
return readOffset;
}
}
}
if (writeBuf != nullptr) {
size_t writeOffset = 0;
size_t rem = size;
while (rem > 0) {
if ((offset + writeOffset) % 4 == 0 && rem >= 4) {
// All accesses of this kind are valid
vgic.WriteGicdRegister(ReadBufferValue<u32>(writeBuf, writeOffset), offset + writeOffset, 4);
writeOffset += 4;
rem -= 4;
} else if ((offset + writeOffset) % 2 == 0 && rem >= 2) {
// All accesses of this kind would be translated to ldrh and are thus invalid. Abort.
return writeOffset;
} else if (VirtualGic::ValidateGicdRegisterAccess(offset + writeOffset, 1)) {
// Valid byte access
vgic.WriteGicdRegister(ReadBufferValue<u8>(writeBuf, writeOffset), offset + writeOffset, 1);
writeOffset += 1;
rem -= 1;
} else {
// Invalid byte access
return writeOffset;
}
}
}
return size;
}
size_t GuestReadWriteDeviceMemory(void *addr, size_t size, void *readBuf, const void *writeBuf)
{
if (readBuf != nullptr) {
size_t sz = SafeIoCopy(readBuf, addr, size);
if (sz < size) {
return sz;
}
}
if (writeBuf != nullptr) {
size_t sz = SafeIoCopy(addr, writeBuf, size);
if (sz < size) {
return sz;
}
}
// Translation tables must be on Normal memory & Device memory isn't cacheable, so we don't have
// that kind of thing to handle...
return size;
}
size_t GuestReadWriteNormalMemory(void *addr, size_t size, void *readBuf, const void *writeBuf)
{
if (readBuf != nullptr) {
std::memcpy(readBuf, addr, size);
}
if (writeBuf != nullptr) {
std::memcpy(addr, writeBuf, size);
// We may have written to executable memory or to translation tables...
// & the page may have various aliases.
// We need to ensure cache & TLB coherency.
CleanDataCacheRangePoU(addr, size);
u32 policy = GetInstructionCachePolicy();
if (policy == 1 || policy == 2) {
// AVIVT, VIVT
InvalidateInstructionCache();
} else {
// VPIPT, PIPT
// Ez coherency, just do range operations...
InvalidateInstructionCacheRangePoU(addr, size);
}
TlbInvalidateEl1();
dsb();
isb();
}
return size;
}
size_t GuestReadWriteMemoryPage(uintptr_t addr, size_t size, void *readBuf, const void *writeBuf)
{
InterruptMaskGuard ig{};
size_t offset = addr & 0xFFFul;
// Translate the VA, stages 1&2
__asm__ __volatile__ ("at s12e1r, %0" :: "r"(addr) : "memory");
u64 par = THERMOSPHERE_GET_SYSREG(par_el1);
if (par & PAR_F) {
// The translation failed. Why?
if (par & PAR_S) {
// Stage 2 fault. Could be an attempt to access the GICD, let's see what the IPA is...
__asm__ __volatile__ ("at s1e1r, %0" :: "r"(addr) : "memory");
par = THERMOSPHERE_GET_SYSREG(par_el1);
if ((par & PAR_F) != 0 || (par & PAR_PA_MASK) != VirtualGic::gicdPhysicalAddress) {
// The guest doesn't have access to it...
// Read as 0, write ignored
if (readBuf != NULL) {
std::memset(readBuf, 0, size);
}
} else {
// GICD mmio
size = GuestReadWriteGicd(offset, size, readBuf, writeBuf);
}
} else {
// Oops, couldn't read/write anything (stage 1 fault)
size = 0;
}
} else {
/*
Translation didn't fail.
To avoid "B2.8 Mismatched memory attributes" we must use the same effective
attributes & shareability as the guest.
Note that par_el1 reports the effective shareablity of device and noncacheable memory as inner shareable.
In fact, the VMSAv8-64 section in the Armv8 ARM reads:
"The shareability field is only relevant if the memory is a Normal Cacheable memory type. All Device and Normal
Non-cacheable memory regions are always treated as Outer Shareable, regardless of the translation table
shareability attributes."
There's one corner case where we can't avoid it: another core is running,
changes the attributes (other than permissions) of the page, and issues
a broadcasting TLB maintenance instructions and/or accesses the page with the altered
attribute itself. We don't handle this corner case -- just don't read/write that kind of memory...
*/
u64 memAttribs = (par >> PAR_ATTR_SHIFT) & PAR_ATTR_MASK;
u64 shrb = (par >> PAR_SH_SHIFT) & PAR_SH_MASK;
uintptr_t pa = par & PAR_PA_MASK;
uintptr_t va = MemoryMap::MapGuestPage(pa, memAttribs, shrb);
void *vaddr = reinterpret_cast<void *>(va + offset);
if (memAttribs & 0xF0) {
// Normal memory, or unpredictable
size = GuestReadWriteNormalMemory(vaddr, size, readBuf, writeBuf);
} else {
// Device memory, or unpredictable
size = GuestReadWriteDeviceMemory(vaddr, size, readBuf, writeBuf);
}
MemoryMap::UnmapGuestPage();
}
return size;
}
}
namespace ams::hvisor {
size_t GuestReadWriteMemory(uintptr_t addr, size_t size, void *readBuf, const void *writeBuf)
{
uintptr_t curAddr = addr;
size_t remainingAmount = size;
u8 *rb8 = reinterpret_cast<u8 *>(readBuf);
const u8 *wb8 = reinterpret_cast<const u8 *>(writeBuf);
while (remainingAmount > 0) {
size_t expectedAmount = ((curAddr & ~0xFFFul) + 0x1000) - curAddr;
expectedAmount = expectedAmount > remainingAmount ? remainingAmount : expectedAmount;
size_t actualAmount = GuestReadWriteMemoryPage(curAddr, expectedAmount, rb8, wb8);
curAddr += actualAmount;
rb8 += actualAmount;
wb8 += actualAmount;
remainingAmount -= actualAmount;
if (actualAmount != expectedAmount) {
break;
}
}
return curAddr - addr;
}
}

View File

@@ -1,35 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "defines.hpp"
namespace ams::hvisor {
size_t GuestReadWriteMemory(uintptr_t addr, size_t size, void *readBuf, const void *writeBuf);
inline size_t GuestReadMemory(uintptr_t addr, size_t size, void *buf)
{
return GuestReadWriteMemory(addr, size, buf, NULL);
}
inline size_t GuestWriteMemory(uintptr_t addr, size_t size, const void *buf)
{
return GuestReadWriteMemory(addr, size, NULL, buf);
}
}

View File

@@ -1,67 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_core_context.hpp"
#include "hvisor_exception_stack_frame.hpp"
#include "cpu/hvisor_cpu_sysreg_general.hpp"
namespace ams::hvisor {
inline u64 ComputeCntvct(const ExceptionStackFrame *frame)
{
return frame->cntpct_el0 - currentCoreCtx->GetTotalTimeInHypervisor();
}
inline void WriteEmulatedPhysicalCompareValue(ExceptionStackFrame *frame, u64 val)
{
// We lied about the value of cntpct, so we need to compute the time delta
// the guest actually intended to use...
u64 vct = ComputeCntvct(frame);
currentCoreCtx->SetEmulPtimerCval(val);
THERMOSPHERE_SET_SYSREG(cntp_cval_el0, frame->cntpct_el0 + (val - vct));
}
inline bool CheckRescheduleEmulatedPtimer(ExceptionStackFrame *frame)
{
// Evaluate if the timer has really expired in the PoV of the guest kernel.
// If not, reschedule (add missed time delta) it & exit early
u64 cval = currentCoreCtx->GetEmulPtimerCval();
u64 vct = ComputeCntvct(frame);
if (cval > vct) {
// It has not: reschedule the timer
// Note: this isn't 100% precise esp. on QEMU so it may take a few tries...
WriteEmulatedPhysicalCompareValue(frame, cval);
return false;
}
return true;
}
ALWAYS_INLINE void EnableGuestTimerTraps(void)
{
// Disable event streams, trap everything
u64 cnthctl = 0;
THERMOSPHERE_SET_SYSREG(cnthctl_el2, cnthctl);
}
ALWAYS_INLINE void UpdateVirtualOffsetSysreg(void)
{
THERMOSPHERE_SET_SYSREG(cntvoff_el2, currentCoreCtx->GetTotalTimeInHypervisor());
}
}

View File

@@ -1,76 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_hw_breakpoint_manager.hpp"
#include "cpu/hvisor_cpu_instructions.hpp"
#define _REENT_ONLY
#include <cerrno>
// Can't use two THERMOSPHERE_SAVE_SYSREG as it prevents ldp from being generated
#define SAVE_BREAKPOINT(i, _)\
__asm__ __volatile__ (\
"msr " STRINGIZE(dbgbvr##i##_el1) ", %0\n"\
"msr " STRINGIZE(dbgbcr##i##_el1) ", %1"\
:\
: "r"(m_stopPoints[i].vr), "r"(m_stopPoints[i].cr.raw)\
: "memory"\
);
namespace ams::hvisor {
HwBreakpointManager HwBreakpointManager::instance{};
void HwBreakpointManager::Reload() const
{
cpu::dmb();
EVAL(REPEAT(MAX_BCR, SAVE_BREAKPOINT, ~));
cpu::dsb();
cpu::isb();
}
bool HwBreakpointManager::FindPredicate(const cpu::DebugRegisterPair &pair, uintptr_t addr, size_t, cpu::DebugRegisterPair::LoadStoreControl) const
{
return pair.vr == addr;
}
// Note: A32/T32/T16 support intentionnally left out
// Note: addresses are supposed to be well-formed regarding the sign extension bits
int HwBreakpointManager::Add(uintptr_t addr)
{
// Reject misaligned addresses
if (addr & 3) {
return -EINVAL;
}
cpu::DebugRegisterPair bp{};
bp.cr.bt = cpu::DebugRegisterPair::AddressMatch;
bp.cr.bas = 0xF; // mandated
bp.vr = addr;
return AddImpl(addr, 0, bp);
}
int HwBreakpointManager::Remove(uintptr_t addr)
{
// Reject misaligned addresses
if (addr & 3) {
return -EINVAL;
}
return RemoveImpl(addr, 0, cpu::DebugRegisterPair::NotAWatchpoint);
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_hw_stop_point_manager.hpp"
namespace ams::hvisor {
class HwBreakpointManager final : public HwStopPointManager {
SINGLETON(HwBreakpointManager);
private:
bool FindPredicate(const cpu::DebugRegisterPair &pair, uintptr_t addr, size_t, cpu::DebugRegisterPair::LoadStoreControl) const final;
void Reload() const final;
private:
constexpr HwBreakpointManager() : HwStopPointManager(MAX_BCR, IrqManager::ReloadHwBreakpointsSgi) {}
public:
int Add(uintptr_t addr);
int Remove(uintptr_t addr);
};
}

View File

@@ -1,127 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_hw_stop_point_manager.hpp"
#include "hvisor_core_context.hpp"
#include "cpu/hvisor_cpu_instructions.hpp"
#include "cpu/hvisor_cpu_interrupt_mask_guard.hpp"
#include <mutex>
#define _REENT_ONLY
#include <cerrno>
namespace ams::hvisor {
void HwStopPointManager::DoReloadOnAllCores() const
{
cpu::InterruptMaskGuard mg{};
cpu::dmb();
Reload();
m_reloadBarrier.Reset(CoreContext::GetActiveCoreMask());
IrqManager::GenerateSgiForAllOthers(m_irqId);
m_reloadBarrier.Join();
}
cpu::DebugRegisterPair *HwStopPointManager::Allocate()
{
size_t pos = __builtin_ffs(m_freeBitmap);
if (pos == 0) {
return nullptr;
} else {
m_freeBitmap &= ~BIT(pos - 1);
m_usedBitmap |= BIT(pos - 1);
return &m_stopPoints[pos - 1];
}
}
void HwStopPointManager::Free(size_t pos)
{
m_stopPoints[pos] = {};
m_freeBitmap |= BIT(pos);
m_usedBitmap &= ~BIT(pos);
}
const cpu::DebugRegisterPair *HwStopPointManager::Find(uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl dir) const
{
for (auto bit: util::BitsOf{m_usedBitmap}) {
auto *p = &m_stopPoints[bit];
if (FindPredicate(*p, addr, size, dir)) {
return p;
}
}
return nullptr;
}
int HwStopPointManager::AddImpl(uintptr_t addr, size_t size, cpu::DebugRegisterPair preconfiguredPair)
{
std::scoped_lock lk{m_lock};
auto lsc = preconfiguredPair.cr.lsc;
if (m_freeBitmap == 0) {
// Oops
return -EBUSY;
}
if (Find(addr, size, lsc) != nullptr) {
// Already exists
return -EEXIST;
}
auto *regs = Allocate();
regs->SetDefaults();
// Apply preconfig
regs->cr.raw |= preconfiguredPair.cr.raw;
regs->vr = preconfiguredPair.vr;
regs->cr.enabled = true;
DoReloadOnAllCores();
return 0;
}
int HwStopPointManager::RemoveImpl(uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl dir)
{
std::scoped_lock lk{m_lock};
const auto *p = Find(addr, size, dir);
if (p == nullptr) {
return -ENOENT;
}
Free(p - m_stopPoints.data());
return 0;
}
void HwStopPointManager::RemoveAll()
{
std::scoped_lock lk{m_lock};
m_freeBitmap |= m_usedBitmap;
m_usedBitmap = 0;
std::fill(m_stopPoints.begin(), m_stopPoints.end(), cpu::DebugRegisterPair{});
DoReloadOnAllCores();
}
std::optional<bool> HwStopPointManager::InterruptTopHalfHandler(u32 irqId, u32)
{
if (irqId != m_irqId) {
return {};
}
Reload();
return false;
}
}

View File

@@ -1,74 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "defines.hpp"
#include "cpu/hvisor_cpu_debug_register_pair.hpp"
#include "hvisor_irq_manager.hpp"
namespace ams::hvisor {
class HwStopPointManager : public IInterruptTask {
NON_COPYABLE(HwStopPointManager);
NON_MOVEABLE(HwStopPointManager);
protected:
static constexpr size_t maxStopPoints = std::max(MAX_BCR, MAX_WCR);
protected:
mutable RecursiveSpinlock m_lock{};
mutable Barrier m_reloadBarrier{};
u16 m_freeBitmap;
u16 m_usedBitmap = 0;
std::array<cpu::DebugRegisterPair, maxStopPoints> m_stopPoints{};
IrqManager::ThermosphereSgi m_irqId;
protected:
void DoReloadOnAllCores() const;
cpu::DebugRegisterPair *Allocate();
void Free(size_t pos);
const cpu::DebugRegisterPair *Find(uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl dir) const;
virtual bool FindPredicate(const cpu::DebugRegisterPair &pair, uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl direction) const = 0;
virtual void Reload() const = 0;
int AddImpl(uintptr_t addr, size_t size, cpu::DebugRegisterPair preconfiguredPair);
int RemoveImpl(uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl direction);
protected:
constexpr HwStopPointManager(size_t numStopPoints, IrqManager::ThermosphereSgi irqId) :
m_freeBitmap(MASK(numStopPoints)), m_irqId(irqId)
{
}
public:
void RemoveAll();
std::optional<bool> InterruptTopHalfHandler(u32 irqId, u32) final;
void ReloadOnAllCores() const
{
m_lock.lock();
DoReloadOnAllCores();
m_lock.unlock();
}
void Initialize()
{
IrqManager::GetInstance().Register(*this, m_irqId, false);
}
};
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "defines.hpp"
#include <vapours/util/util_intrusive_list.hpp>
namespace ams::hvisor {
class IInterruptTask : public util::IntrusiveListBaseNode<IInterruptTask> {
NON_COPYABLE(IInterruptTask);
NON_MOVEABLE(IInterruptTask);
protected:
constexpr IInterruptTask() = default;
public:
virtual std::optional<bool> InterruptTopHalfHandler(u32 irqId, u32 srcCore) = 0;
virtual void InterruptBottomHalfHandler(u32 irqId, u32 srcCore) {}
};
}

View File

@@ -1,223 +0,0 @@
/*
* Copyright (c) 2019-2020 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 <mutex>
#include "hvisor_irq_manager.hpp"
#include "hvisor_virtual_gic.hpp"
#include "hvisor_core_context.hpp"
#include "hvisor_guest_timers.hpp"
#include "cpu/hvisor_cpu_interrupt_mask_guard.hpp"
#include "platform/interrupt_config.h"
#include "transport_interface.h"
#include "timer.h"
//#include "debug_manager.h"
namespace {
inline bool CheckGuestTimerInterrupts(ams::hvisor::ExceptionStackFrame *frame, u32 irqId)
{
// A thing that might have happened is losing the race vs disabling the guest interrupts
// Another thing is that the virtual timer might have fired before us updating voff when executing a top half?
if (irqId == TIMER_IRQID(NS_VIRT_TIMER)) {
u64 cval = THERMOSPHERE_GET_SYSREG(cntp_cval_el0);
return cval <= ams::hvisor::ComputeCntvct(frame);
} else if (irqId == TIMER_IRQID(NS_PHYS_TIMER)) {
return ams::hvisor::CheckRescheduleEmulatedPtimer(frame);
} else {
return true;
}
}
}
namespace ams::hvisor {
bool IrqManager::IsGuestInterrupt(u32 id)
{
// We don't care about the interrupts we don't use
// Special interrupts id (eg. spurious interrupt id 1023) are also reserved to us
// because the virtual interface hw itself will generate it for the guest.
bool ret = id <= GicV2Distributor::maxIrqId && id != GIC_IRQID_MAINTENANCE && id != GIC_IRQID_NS_PHYS_HYP_TIMER;
ret = ret && transportInterfaceFindByIrqId(id) == NULL;
return ret;
}
void IrqManager::InitializeGic()
{
// Reinits the GICD and GICC (for non-secure mode, obviously)
if (currentCoreCtx->IsBootCore() && currentCoreCtx->IsColdboot()) {
// Disable interrupt handling & global interrupt distribution
gicd->ctlr = 0;
// Get some info
m_numSharedInterrupts = 32 * (gicd->typer & 0x1F); // number of interrupt lines / 32
// unimplemented priority bits (lowest significant) are RAZ/WI
gicd->ipriorityr[0] = 0xFF;
m_priorityShift = 8 - __builtin_popcount(gicd->ipriorityr[0]);
m_numPriorityLevels = static_cast<u8>(BIT(__builtin_popcount(gicd->ipriorityr[0])));
m_numCpuInterfaces = static_cast<u8>(1 + ((gicd->typer >> 5) & 7));
}
// Only one core will reset the GIC state for the shared peripheral interrupts
u32 numInterrupts = 32;
if (currentCoreCtx->IsBootCore()) {
numInterrupts += m_numSharedInterrupts;
}
// Filter all interrupts
gicc->pmr = 0;
// Disable interrupt preemption
gicc->bpr = 7;
// Note: the GICD I...n regs are banked for private interrupts
// Disable all interrupts, clear active status, clear pending status
for (u32 i = 0; i < numInterrupts / 32; i++) {
gicd->icenabler[i] = 0xFFFFFFFF;
gicd->icactiver[i] = 0xFFFFFFFF;
gicd->icpendr[i] = 0xFFFFFFFF;
}
// Set priorities to lowest
for (u32 i = 0; i < numInterrupts; i++) {
gicd->ipriorityr[i] = 0xFF;
}
// Reset icfgr, itargetsr for shared peripheral interrupts
for (u32 i = 32 / 16; i < numInterrupts / 16; i++) {
gicd->icfgr[i] = 0x55555555;
}
for (u32 i = 32; i < numInterrupts; i++) {
gicd->itargetsr[i] = 0;
}
// Now, reenable interrupts
// Enable the distributor
if (currentCoreCtx->IsBootCore()) {
gicd->ctlr = 1;
}
// Enable the CPU interface. Set EOIModeNS=1 (split prio drop & deactivate priority)
gicc->ctlr = BIT(9) | 1;
// Disable interrupt filtering
gicc->pmr = 0xFF;
}
void IrqManager::DoConfigureInterrupt(u32 id, u8 prio, bool isLevelSensitive)
{
ClearInterruptEnabled(id);
ClearInterruptPending(id);
if (id >= 32) {
SetInterruptMode(id, !isLevelSensitive);
SetInterruptTargets(id, 0xFF); // all possible processors
}
SetInterruptPriority(id, prio);
SetInterruptEnabled(id);
}
void IrqManager::Initialize()
{
cpu::InterruptMaskGuard mg{};
std::scoped_lock lk{m_lock};
InitializeGic();
DoConfigureInterrupt(GIC_IRQID_MAINTENANCE, hostPriority, true);
VirtualGic::GetInstance().Initialize();
}
void IrqManager::Register(IInterruptTask &task, u32 id, bool isLevelSensitive, u8 prio)
{
cpu::InterruptMaskGuard mg{};
std::scoped_lock lk{m_lock};
DoConfigureInterrupt(id, prio, isLevelSensitive);
if (!task.IsLinked()) {
m_interruptTaskList.push_back(task);
}
}
void IrqManager::SetInterruptAffinity(u32 id, u8 affinity)
{
cpu::InterruptMaskGuard mg{};
std::scoped_lock lk{m_lock};
SetInterruptTargets(id, affinity);
}
void IrqManager::HandleInterrupt(ExceptionStackFrame *frame)
{
// Acknowledge the interrupt. Interrupt goes from pending to active.
u32 iar = AcknowledgeIrq();
u32 irqId = iar & 0x3FF;
u32 srcCore = (iar >> 10) & 7;
IInterruptTask *taskForBottomHalf;
//DEBUG("EL2 [core %d]: Received irq %x\n", (int)currentCoreCtx->coreId, irqId);
if (irqId == GicV2Distributor::spuriousIrqId) {
// Spurious interrupt received
return;
} else if (!CheckGuestTimerInterrupts(frame, irqId)) {
// Deactivate the interrupt, return ASAP
DropCurrentInterruptPriority(iar);
DeactivateCurrentInterrupt(iar);
return;
} else {
// Everything else
std::scoped_lock lk{instance.m_lock};
VirtualGic &vgic = VirtualGic::GetInstance();
if (irqId >= 16 && IsGuestInterrupt(irqId)) {
// Guest interrupts
taskForBottomHalf = nullptr;
DropCurrentInterruptPriority(iar);
vgic.EnqueuePhysicalIrq(irqId);
} else {
// Host interrupts
// Try all handlers and see which one fits
for (IInterruptTask &task: instance.m_interruptTaskList) {
auto b = task.InterruptTopHalfHandler(irqId, srcCore);
if (b) {
taskForBottomHalf = *b ? &task : nullptr;
break;
}
}
DropCurrentInterruptPriority(iar);
DeactivateCurrentInterrupt(iar);
}
vgic.UpdateState();
}
if (taskForBottomHalf != nullptr) {
// Unmasking the irq signal is left at the discretion of the bottom half handler
EnterInterruptibleHypervisorCode();
taskForBottomHalf->InterruptBottomHalfHandler(irqId, srcCore);
}
}
}

View File

@@ -1,123 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_gicv2.hpp"
#include "hvisor_synchronization.hpp"
#include "hvisor_i_interrupt_task.hpp"
#include "hvisor_exception_stack_frame.hpp"
#include "hvisor_memory_map.hpp"
#include "cpu/hvisor_cpu_sysreg_general.hpp"
namespace ams::hvisor {
class IrqManager final {
SINGLETON(IrqManager);
friend class VirtualGic;
private:
static constexpr u8 hostPriority = 0;
static constexpr u8 guestPriority = 1;
static inline volatile auto *const gicd = reinterpret_cast<volatile GicV2Distributor *>(MemoryMap::gicdVa);
static inline volatile auto *const gicc = reinterpret_cast<volatile GicV2Controller *>(MemoryMap::giccVa);
static inline volatile auto *const gich = reinterpret_cast<volatile GicV2VirtualInterfaceController *>(MemoryMap::gichVa);
static bool IsGuestInterrupt(u32 id);
static u32 GetTypeRegister() { return gicd->typer; }
static void SetInterruptEnabled(u32 id) { gicd->isenabler[id / 32] = BIT(id % 32); }
static void ClearInterruptEnabled(u32 id) { gicd->icenabler[id / 32] = BIT(id % 32); }
static bool IsInterruptPending(u32 id) { return (gicd->ispendr[id / 32] & BIT(id % 32)) != 0;}
static void ClearInterruptPending(u32 id) { gicd->icpendr[id / 32] = BIT(id % 32); }
static void ClearInterruptActive(u32 id) { gicd->icactiver[id / 32] = BIT(id % 32); }
static void SetInterruptShiftedPriority(u32 id, u8 prio) { gicd->ipriorityr[id] = prio; }
static void SetInterruptTargets(u32 id, u8 targetList) { gicd->itargetsr[id] = targetList; }
static bool IsInterruptEdgeTriggered(u32 id)
{
return ((gicd->icfgr[id / 16] >> GicV2Distributor::GetCfgrShift(id)) & 2) != 0;
}
static void SetInterruptMode(u32 id, bool isEdgeTriggered)
{
u32 cfgw = gicd->icfgr[id / 16];
cfgw &= ~(2 << GicV2Distributor::GetCfgrShift(id));
cfgw |= (isEdgeTriggered ? 2 : 0) << GicV2Distributor::GetCfgrShift(id);
gicd->icfgr[id / 16] = cfgw;
}
static u32 AcknowledgeIrq() { return gicc->iar; }
static void DropCurrentInterruptPriority(u32 iar) { gicc->eoir = iar; }
static void DeactivateCurrentInterrupt(u32 iar) { gicc->dir = iar; }
private:
using InterruptTaskList = util::IntrusiveListBaseTraits<IInterruptTask>::ListType;
mutable RecursiveSpinlock m_lock{};
InterruptTaskList m_interruptTaskList{};
u32 m_numSharedInterrupts = 0;
u8 m_priorityShift = 0;
u8 m_numPriorityLevels = 0;
u8 m_numCpuInterfaces = 0;
private:
void SetInterruptPriority(u32 id, u8 prio) { SetInterruptShiftedPriority(id, prio << m_priorityShift); }
void InitializeGic();
void DoConfigureInterrupt(u32 id, u8 prio, bool isLevelSensitive);
private:
constexpr IrqManager() = default;
public:
enum ThermosphereSgi : u32 {
VgicUpdateSgi = 0,
ReloadHwBreakpointsSgi,
ReloadWatchpointsSgi,
ApplyRevertSwBreakpointSgi,
DebugPauseSgi,
ReportDebuggerBreakSgi,
DebuggerContinueSgi,
MaxSgi,
};
static void GenerateSgiForList(ThermosphereSgi id, u32 coreList)
{
gicd->sgir = GicV2Distributor::ForwardToTargetList << 24 | coreList << 16 | id;
}
static void GenerateSgiForAllOthers(ThermosphereSgi id)
{
gicd->sgir = GicV2Distributor::ForwardToAllOthers << 24 | id;
}
static void EnterInterruptibleHypervisorCode()
{
// We don't want the guest to spam us with its timer interrupts. Disable the timers.
THERMOSPHERE_SET_SYSREG(cntp_ctl_el0, 0);
THERMOSPHERE_SET_SYSREG(cntv_ctl_el0, 0);
}
static void HandleInterrupt(ExceptionStackFrame *frame);
public:
void Initialize();
void Register(IInterruptTask &task, u32 id, bool isLevelSensitive, u8 prio = IrqManager::hostPriority);
void SetInterruptAffinity(u32 id, u8 affinityMask);
};
}

View File

@@ -1,197 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_memory_map.hpp"
#include "hvisor_core_context.hpp"
#include "cpu/hvisor_cpu_mmu.hpp"
#include "cpu/hvisor_cpu_instructions.hpp"
namespace ams::hvisor {
uintptr_t MemoryMap::currentPlatformMmioPage = MemoryMap::mmioPlatBaseVa;
void MemoryMap::SetupMmu(const MemoryMap::LoadImageLayout *layout)
{
using namespace cpu;
constexpr u64 normalAttribs = MMU_INNER_SHAREABLE | MMU_ATTRINDX(Memtype_Normal);
constexpr u64 deviceAttribs = MMU_XN | MMU_INNER_SHAREABLE | MMU_ATTRINDX(Memtype_Device_nGnRE);
/*
Layout in physmem:
Location1
Image (code and data incl. BSS), which size is page-aligned
Location2
tempbss
MMU table (taken from temp physmem)
Layout in vmem:
Location1
Image
padding
tempbss
Location2
Crash stacks
{guard page, stack} * numCores
Location3 (all L1, L2, L3 bits set):
MMU table
We map the table into itself at the entry which index has all bits set.
This is called "recursive page tables" and means (assuming 39-bit addr space) that:
- the table will reuse itself as L2 table for the 0x7FC0000000+ range
- the table will reuse itself as L3 table for the 0x7FFFE00000+ range
- the table itself will be accessible at 0x7FFFFFF000
*/
using Builder = MmuTableBuilder<3, addressSpaceSize>;
uintptr_t mmuTablePa = layout->tempPa + layout->maxTempSize;
uintptr_t tempVa = imageVa + layout->imageSize;
uintptr_t crashStacksPa = layout->tempPa + layout->tempSize;
uintptr_t stacksPa = crashStacksPa + crashStacksSize;
Builder{reinterpret_cast<u64 *>(mmuTablePa)}
.InitializeTable()
// Image & tempbss & crash stacks
.MapBlockRange(imageVa, layout->startPa, layout->imageSize, normalAttribs)
.MapBlockRange(tempVa, layout->tempPa, layout->tempSize, normalAttribs)
.MapBlockRange(crashStacksBottomVa, crashStacksPa, crashStacksSize, normalAttribs)
// Stacks, each with a guard page
.MapBlockRange(stacksBottomVa, stacksPa, 0x1000ul * MAX_CORE, normalAttribs, 0x1000)
// GICD, GICC, GICH
.MapBlock(gicdVa, MEMORY_MAP_PA_GICD, deviceAttribs)
.MapBlockRange(giccVa, MEMORY_MAP_PA_GICC, 0x2000, deviceAttribs)
.MapBlock(gichVa, MEMORY_MAP_PA_GICH, deviceAttribs)
// Recursive page mapping
.MapBlock(ttblVa, mmuTablePa, normalAttribs)
;
}
std::array<uintptr_t, 2> MemoryMap::EnableMmuGetStacks(const MemoryMap::LoadImageLayout *layout, u32 coreId)
{
using namespace cpu;
uintptr_t mmuTablePa = layout->tempPa + layout->maxTempSize;
u32 ps = THERMOSPHERE_GET_SYSREG(id_aa64mmfr0_el1) & 0xF;
/*
- PA size: from ID_AA64MMFR0_EL1
- Granule size: 4KB
- Shareability attribute for memory associated with translation table walks using TTBR0_EL2:
Inner Shareable
- Outer cacheability attribute for memory associated with translation table walks using TTBR0_EL2:
Normal memory, Outer Write-Back Read-Allocate Write-Allocate Cacheable
- Inner cacheability attribute for memory associated with translation table walks using TTBR0_EL2:
Normal memory, Inner Write-Back Read-Allocate Write-Allocate Cacheable
- T0SZ = 39
*/
u64 tcr = TCR_EL2_RSVD | TCR_PS(ps) | TCR_TG0(TranslationGranule_4K) | TCR_SHARED_INNER | TCR_ORGN_WBWA | TCR_IRGN_WBWA | TCR_T0SZ(addressSpaceSize);
/*
- Attribute 0: Device-nGnRnE memory
- Attribute 1: Normal memory, Inner and Outer Write-Back Read-Allocate Write-Allocate Non-transient
- Attribute 2: Device-nGnRE memory
- Attribute 3: Normal memory, Inner and Outer Noncacheable
- Other attributes: Device-nGnRnE memory
*/
constexpr u64 mair = 0x44FF0400;
// Set VBAR because we *will* crash (instruction abort because of the value of pc) when enabling the MMU
THERMOSPHERE_SET_SYSREG(vbar_el2, layout->vbar);
// MMU regs config
THERMOSPHERE_SET_SYSREG(ttbr0_el2, mmuTablePa);
THERMOSPHERE_SET_SYSREG(tcr_el2, tcr);
THERMOSPHERE_SET_SYSREG(mair_el2, mair);
dsb();
isb();
// TLB invalidation
// Whether this does anything before MMU is enabled is impldef, apparently
TlbInvalidateEl2Local();
dsb();
isb();
// Enable MMU & enable caching. We will crash.
u64 sctlr = THERMOSPHERE_GET_SYSREG(sctlr_el2);
sctlr |= SCTLR_ELx_I | SCTLR_ELx_C | SCTLR_ELx_M;
THERMOSPHERE_SET_SYSREG(sctlr_el2, sctlr);
dsb();
isb();
// crashStackTop is fragile, check if crashStacksSize is suitable for MAX_CORE
uintptr_t stackTop = stacksBottomVa + 0x2000 * coreId + 0x1000;
uintptr_t crashStackTop = crashStacksBottomVa + (crashStacksSize / MAX_CORE) * (1 + coreId);
return std::array{stackTop, crashStackTop};
}
uintptr_t MemoryMap::MapPlatformMmio(uintptr_t pa, size_t size)
{
using namespace cpu;
using Builder = MmuTableBuilder<3, addressSpaceSize, true>;
constexpr u64 deviceAttribs = MMU_XN | MMU_INNER_SHAREABLE | MMU_ATTRINDX(Memtype_Device_nGnRE);
uintptr_t va = currentPlatformMmioPage;
size = (size + 0xFFF) & ~0xFFFul;
Builder{reinterpret_cast<u64 *>(ttblVa)}.MapBlockRange(currentPlatformMmioPage, va, size, deviceAttribs);
currentPlatformMmioPage += size;
return va;
}
uintptr_t MemoryMap::MapGuestPage(uintptr_t pa, u64 memAttribs, u64 shareability)
{
using namespace cpu;
using Builder = MmuTableBuilder<3, addressSpaceSize, true>;
u64 attribs = MMU_XN | MMU_SH(shareability) | MMU_ATTRINDX(Memtype_Guest_Slot);
uintptr_t va = guestMemVa + 0x2000 * currentCoreCtx->GetCoreId(); // one guard page
// Update mair_el2
u64 mair = THERMOSPHERE_GET_SYSREG(mair_el2);
mair |= memAttribs << (8 * Memtype_Guest_Slot);
THERMOSPHERE_SET_SYSREG(mair_el2, mair);
isb();
Builder{reinterpret_cast<u64 *>(ttblVa)}.MapBlock(va, pa, attribs);
TlbInvalidateEl2Page(va);
dsb();
isb();
}
void MemoryMap::UnmapGuestPage()
{
using namespace cpu;
using Builder = MmuTableBuilder<3, addressSpaceSize, true>;
uintptr_t va = guestMemVa + 0x2000 * currentCoreCtx->GetCoreId();
dsb();
isb();
Builder{reinterpret_cast<u64 *>(ttblVa)}.Unmap(va);
TlbInvalidateEl2Page(va);
dsb();
isb();
// Update mair_el2
u64 mair = THERMOSPHERE_GET_SYSREG(mair_el2);
mair &= ~(0xFF << (8 * Memtype_Guest_Slot));
THERMOSPHERE_SET_SYSREG(mair_el2, mair);
isb();
}
}

View File

@@ -1,99 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "defines.hpp"
namespace ams::hvisor {
class MemoryMap final {
NON_COPYABLE(MemoryMap);
NON_MOVEABLE(MemoryMap);
private:
// Maps to AttrIndx[2:0]
enum MemType {
Memtype_Device_nGnRnE = 0,
Memtype_Normal = 1,
Memtype_Device_nGnRE = 2,
Memtype_Normal_Uncacheable = 3,
Memtype_Guest_Slot = 4,
};
struct LoadImageLayout {
uintptr_t startPa;
size_t imageSize; // "image" includes "real" BSS but not tempbss
uintptr_t tempPa;
size_t maxTempSize;
size_t tempSize;
uintptr_t vbar;
};
static_assert(std::is_standard_layout_v<LoadImageLayout>);
static_assert(std::is_trivial_v<LoadImageLayout>);
private:
static LoadImageLayout imageLayout;
static uintptr_t currentPlatformMmioPage;
public:
static constexpr u32 addressSpaceSize = 39;
// The following come from the fact we're using a recursive page table:
static constexpr uintptr_t selfL2VaRange = 0x7FC0000000ul; // = 511 << 31
static constexpr uintptr_t selfL3VaRange = 0x7FFFE00000ul; // = 511 << 31 | 511 << 21
static constexpr uintptr_t ttblVa = 0x7FFFFFF000ul; // = 511 << 31 | 511 << 21 | 511 << 12
static constexpr uintptr_t maxVa = 0x7FFFFFFFFFul; // = all 39 bits set
static constexpr size_t crashStacksSize = 0x1000ul;
// Do not use the first 0x10000 to allow for L1/L2 mappings...
static constexpr uintptr_t imageVa = selfL3VaRange + 0x10000;
static constexpr uintptr_t crashStacksBottomVa = selfL3VaRange + 0x40000;
static constexpr uintptr_t crashStacksTopVa = crashStacksBottomVa + crashStacksSize;
static constexpr uintptr_t guestMemVa = selfL3VaRange + 0x50000;
static constexpr uintptr_t stacksBottomVa = selfL3VaRange + 0x60000;
static constexpr uintptr_t mmioBaseVa = selfL3VaRange + 0x80000;
static constexpr uintptr_t gicdVa = mmioBaseVa + 0x0000;
static constexpr uintptr_t giccVa = mmioBaseVa + 0x1000;
static constexpr uintptr_t gichVa = mmioBaseVa + 0x3000;
static constexpr uintptr_t mmioPlatBaseVa = selfL3VaRange + 0x90000;
static uintptr_t GetStartPa() { return imageLayout.startPa; }
// Called before MMU is enabled. EnableMmu must not use a stack frame
static void SetupMmu(const LoadImageLayout *layout);
static std::array<uintptr_t, 2> EnableMmuGetStacks(const LoadImageLayout *layout, u32 coreId);
static constexpr uintptr_t GetStackTopVa(u32 coreId)
{
return stacksBottomVa + 0x2000 * coreId + 0x1000;
}
// Caller is expected to invalidate TLB + barrier at some point
static uintptr_t MapPlatformMmio(uintptr_t pa, size_t size);
// Caller is expected to disable interrupts, etc, etc.
static uintptr_t MapGuestPage(uintptr_t pa, u64 memAttribs, u64 shareability);
static void UnmapGuestPage();
public:
constexpr MemoryMap() = delete;
};
}

View File

@@ -1,26 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "defines.hpp"
namespace ams::hvisor {
// Caller needs to disable interrupts
size_t SafeIoCopy(void *dst, const void *src, size_t size);
}

View File

@@ -1,84 +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 "asm_macros.s"
// ams::hvisor::SafeIoCopy(void*, void const*, unsigned long)
FUNCTION _ZN3ams6hvisor10SafeIoCopyEPvPKvm
// Caller needs to mask interrupts
// See _synchSp0 exception handler
msr spsel, #0
mov x9, x18
mov x18, #0
mov x16, #0
// x0-x2 parameters
// x3: remainder
// x4: offset
cbz x2, 2f
mov x3, x2
mov x4, #0
mov x5, #0
// while (remainder > 0) { offset += increment; test alignment etc. } return offset;
1:
// Dispatcher
add x5, x1, x4
add x6, x0, x4
orr x7, x5, x6
tst x7, #3
// ((src + off)|(dst + off)) & 3 == 0 ? remainder > 3 : eq
ccmp x3, #3, #0, eq
bhi 3f
// same thing but for 2-byte alignment
cmp x3, #1
cset w8, hi
bics wzr, w8, w5
bne 4f
// 8-bit load, if the load and/or store crashes, x16 = 1 (same thing for the other load/stores)
ldrb w5, [x5]
strb w5, [x6]
cbnz x16, 2f
add x4, x4, #1
subs x3, x3, #1
bne 1b
2:
// Return
msr spsel, #1
mov x18, x9
mov x0, x4
ret
3:
// 32-bit load
ldr w5, [x5]
str w5, [x6]
cbnz x16, 2b
add x4, x4, #4
subs x3, x3, #4
bne 1b
b 2b
4:
// 16-bit load
ldrh w5, [x5]
strh w5, [x6]
cbnz x16, 2b
add x4, x4, #2
subs x3, x3, #2
bne 1b
b 2b
END_FUNCTION

View File

@@ -1,182 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_sw_breakpoint_manager.hpp"
#include "hvisor_core_context.hpp"
#include "hvisor_guest_memory.hpp"
#include "cpu/hvisor_cpu_instructions.hpp"
#include "cpu/hvisor_cpu_interrupt_mask_guard.hpp"
#include <mutex>
#define _REENT_ONLY
#include <cerrno>
/*
Consider the following:
- Breakpoints are based on VA
- Translation tables may change
- Translation tables may differ from core to core
We also define sw breakpoints on invalid addresses (for one or more cores) UNPREDICTABLE.
*/
namespace ams::hvisor {
SwBreakpointManager SwBreakpointManager::instance{};
size_t SwBreakpointManager::FindClosest(uintptr_t addr) const
{
auto endit = m_breakpoints.cbegin() + m_numBreakpoints;
auto it = std::lower_bound(
m_breakpoints.cbegin(),
endit,
addr,
[] (const Breakpoint &a, const Breakpoint &b) {
return a.address < b.address;
}
);
return it == endit ? m_numBreakpoints : static_cast<size_t>(it - m_breakpoints.cbegin());
}
bool SwBreakpointManager::DoApply(size_t id)
{
Breakpoint &bp = m_breakpoints[id];
u32 brkInst = 0xD4200000 | (bp.uid << 5);
size_t sz = GuestReadWriteMemory(bp.address, 4, &bp.savedInstruction, &brkInst);
bp.applied = sz == 4;
return sz == 4;
}
bool SwBreakpointManager::DoRevert(size_t id)
{
Breakpoint &bp = m_breakpoints[id];
size_t sz = GuestWriteMemory(bp.address, 4, &bp.savedInstruction);
bp.applied = sz != 4;
return sz == 4;
}
std::optional<bool> SwBreakpointManager::InterruptTopHalfHandler(u32 irqId, u32)
{
if (irqId != IrqManager::ApplyRevertSwBreakpointSgi) {
return {};
}
m_applyBarrier.Join();
return false;
}
bool SwBreakpointManager::ApplyOrRevert(size_t id, bool apply)
{
cpu::InterruptMaskGuard mg{};
m_applyBarrier.Reset(CoreContext::GetActiveCoreMask());
IrqManager::GenerateSgiForAllOthers(IrqManager::ApplyRevertSwBreakpointSgi);
if (apply) {
DoApply(id);
} else {
DoRevert(id);
}
m_applyBarrier.Join();
}
// TODO apply revert handlers
int SwBreakpointManager::Add(uintptr_t addr, bool persistent)
{
if ((addr & 3) != 0) {
return -EINVAL;
}
std::scoped_lock lk{m_lock};
if (m_numBreakpoints == MAX_SW_BREAKPOINTS) {
return -EBUSY;
}
size_t id = FindClosest(addr);
if (id != m_numBreakpoints && m_breakpoints[id].uid != 0) {
return -EEXIST;
}
// Insert
for(size_t i = m_numBreakpoints; i > id && i != 0; i--) {
m_breakpoints[i] = m_breakpoints[i - 1];
}
++m_numBreakpoints;
Breakpoint &bp = m_breakpoints[id];
bp.address = addr;
bp.persistent = persistent;
bp.applied = false;
bp.uid = static_cast<u16>(0x2000 + m_bpUniqueCounter++);
return ApplyOrRevert(id, true) ? 0 : -EFAULT;
}
int SwBreakpointManager::Remove(uintptr_t addr, bool keepPersistent)
{
if ((addr & 3) != 0) {
return -EINVAL;
}
std::scoped_lock lk{m_lock};
if (m_numBreakpoints == MAX_SW_BREAKPOINTS) {
return -EBUSY;
}
size_t id = FindClosest(addr);
if (id == m_numBreakpoints || m_breakpoints[id].uid == 0) {
return -ENOENT;
}
Breakpoint &bp = m_breakpoints[id];
bool ok = true;
if (!keepPersistent || !bp.persistent) {
ok = ApplyOrRevert(id, false);
}
for(size_t i = id; i < m_numBreakpoints - 1; i++) {
m_breakpoints[i] = m_breakpoints[i + 1];
}
m_breakpoints[--m_numBreakpoints] = {};
return ok ? 0 : -EFAULT;
}
int SwBreakpointManager::RemoveAll(bool keepPersistent)
{
std::scoped_lock lk{m_lock};
bool ok = true;
for (size_t id = 0; id < m_numBreakpoints; id++) {
Breakpoint &bp = m_breakpoints[id];
if (!keepPersistent || !bp.persistent) {
ok = ok && ApplyOrRevert(id, false);
}
}
std::fill_n(m_breakpoints.begin(), m_breakpoints.end(), Breakpoint{});
m_numBreakpoints = 0;
m_bpUniqueCounter = 0;
return ok ? 0 : -EFAULT;
}
}

View File

@@ -1,67 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "defines.hpp"
#include "hvisor_irq_manager.hpp"
#define MAX_SW_BREAKPOINTS 16
namespace ams::hvisor {
class SwBreakpointManager : public IInterruptTask {
SINGLETON(SwBreakpointManager);
private:
struct Breakpoint {
uintptr_t address;
u32 savedInstruction;
u16 uid;
bool persistent;
bool applied;
};
private:
mutable RecursiveSpinlock m_lock{};
mutable Barrier m_applyBarrier{};
u32 m_bpUniqueCounter = 0;
size_t m_numBreakpoints = 0;
std::array<Breakpoint, MAX_SW_BREAKPOINTS> m_breakpoints{};
private:
size_t FindClosest(uintptr_t addr) const;
bool DoApply(size_t id);
bool DoRevert(size_t id);
bool ApplyOrRevert(size_t id, bool apply);
private:
constexpr SwBreakpointManager() = default;
public:
int Add(uintptr_t addr, bool persistent);
int Remove(uintptr_t addr, bool keepPersistent);
int RemoveAll(bool keepPersistent);
std::optional<bool> InterruptTopHalfHandler(u32 irqId, u32) final;
void Initialize()
{
IrqManager::GetInstance().Register(*this, IrqManager::ApplyRevertSwBreakpointSgi, false);
}
};
}

View File

@@ -1,95 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_synchronization.hpp"
#include "hvisor_core_context.hpp"
namespace ams::hvisor {
void Spinlock::lock()
{
u32 tmp1;
const u32 tmp2 = 1;
__asm__ __volatile__(
"prfm pstl1keep, %[val] \n"
"sevl \n"
"1: \n"
" wfe \n"
" 2: \n"
" ldaxr %[tmp1], %[val] \n"
" cbnz %[tmp1], 1b \n"
" stxr %[tmp1], %[tmp2], %[val] \n"
" cbnz %[tmp1], 2b \n"
: [tmp1] "=&r"(tmp1), [val] "+Q" (m_val)
: [tmp2] "r"(tmp2)
: "cc", "memory"
);
}
void Spinlock::unlock() noexcept
{
__asm__ __volatile__("stlr wzr, %[val]" : [val] "=Q" (m_val) :: "memory");
}
void Barrier::Join()
{
const u32 mask = BIT(currentCoreCtx->GetCoreId());
u32 newval, tmp;
__asm__ __volatile__(
"prfm pstl1keep, %[val] \n"
/* Fetch-and */
"1: \n"
" ldaxr %[newval], %[val] \n"
" bic %[newval], %[newval], %[mask] \n"
" stlxr %[tmp], %[newval], %[val] \n"
" cbnz %[tmp], 1b \n"
/* Check if now/already 0, wait if not */
"cbz %[newval], 3f \n"
/* Event will be signaled if the stlxr succeeds for another core... */
"2: \n"
" wfe \n"
" ldaxr %[newval], %[val] \n"
" cbnz %[newval], 2b \n"
"3: \n"
: [newval] "=&r"(newval), [tmp] "=&r" (tmp), [val] "+Q" (m_val)
: [mask] "r"(mask)
: "cc", "memory"
);
}
void RecursiveSpinlock::lock()
{
u32 tag = currentCoreCtx->GetCoreId() + 1;
if (AMS_LIKELY(tag != m_tag)) {
m_spinlock.lock();
m_tag = tag;
m_count = 1;
} else {
++m_count;
}
}
void RecursiveSpinlock::unlock() noexcept
{
if (AMS_LIKELY(--m_count == 0)) {
m_tag = 0;
m_spinlock.unlock();
}
}
}

View File

@@ -1,62 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "defines.hpp"
namespace ams::hvisor {
class Spinlock final {
NON_COPYABLE(Spinlock);
NON_MOVEABLE(Spinlock);
private:
u32 m_val = 0;
public:
constexpr Spinlock() = default;
void lock();
void unlock() noexcept;
};
class Barrier final {
NON_COPYABLE(Barrier);
NON_MOVEABLE(Barrier);
private:
u32 m_val = 0;
public:
constexpr Barrier() = default;
void Join();
constexpr void Reset(u32 val)
{
m_val = val;
}
};
class RecursiveSpinlock final {
NON_COPYABLE(RecursiveSpinlock);
NON_MOVEABLE(RecursiveSpinlock);
private:
Spinlock m_spinlock{};
u32 m_tag = 0;
u32 m_count = 0;
public:
constexpr RecursiveSpinlock() = default;
void lock();
void unlock() noexcept;
};
}

View File

@@ -1,747 +0,0 @@
/*
* Copyright (c) 2019-2020 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 <mutex>
#include "hvisor_virtual_gic.hpp"
#include "cpu/hvisor_cpu_instructions.hpp"
#include "platform/interrupt_config.h" // TODO remove
#define GICDOFF(field) (offsetof(GicV2Distributor, field))
namespace ams::hvisor {
VirtualGic::VirqQueue::iterator VirtualGic::VirqQueue::insert(VirtualGic::VirqQueue::iterator pos, VirtualGic::VirqState &elem)
{
// Insert before
ENSURE(!elem.IsQueued());
// Empty list
if (begin() == end()) {
m_first = m_last = &elem;
elem.listPrev = elem.listNext = virqListEndIndex;
return begin();
}
if (pos == end()) {
// Insert after last
VirqState &prev = back();
elem.listPrev = GetStateIndex(prev);
elem.listNext = prev.listNext;
prev.listNext = GetStateIndex(elem);
m_last = &elem;
} else {
u32 idx = GetStateIndex(elem);
u32 posidx = GetStateIndex(*pos);
u32 previdx = elem.listPrev;
elem.listNext = posidx;
elem.listPrev = previdx;
pos->listPrev = idx;
if (pos == begin()) {
m_first = &elem;
} else {
--pos;
pos->listNext = idx;
}
}
return iterator{&elem, m_storage};
}
VirtualGic::VirqQueue::iterator VirtualGic::VirqQueue::insert(VirtualGic::VirqState &elem)
{
// Insert in a stable sorted way
// Lower priority number is higher; we sort by descending priority, ie. ascending priority number
// Put the interrupts that were previously in the LR before the one which don't
return insert(
std::find_if(begin(), end(), [&a = elem](const VirqState &b) {
return a.priority == b.priority ? a.handled && !b.handled : a.priority < b.priority;
}),
elem
);
}
void VirtualGic::SetInterruptEnabledState(u32 id)
{
VirqState &state = GetVirqState(id);
if (id < 16 || !IrqManager::IsGuestInterrupt(id) || state.enabled) {
// Nothing to do...
// Also, ignore for SGIs
return;
}
// Similar effects to setting the target list to non-0 when it was 0...
if (state.IsPending()) {
NotifyOtherCoreList(state.targetList);
}
state.enabled = true;
IrqManager::SetInterruptEnabled(id);
}
void VirtualGic::ClearInterruptEnabledState(u32 id)
{
VirqState &state = GetVirqState(id);
if (id < 16 || !IrqManager::IsGuestInterrupt(id) || !state.enabled) {
// Nothing to do...
// Also, ignore for SGIs
return;
}
// Similar effects to setting the target list to 0, we may need to notify the core
// handling the interrupt if it's pending
if (state.handled) {
NotifyOtherCoreList(BIT(state.coreId));
}
state.enabled = false;
IrqManager::ClearInterruptEnabled(id);
}
void VirtualGic::SetInterruptPriorityByte(u32 id, u8 priority)
{
if (!IrqManager::IsGuestInterrupt(id)) {
return;
}
// 32 priority levels max, bits [7:3]
priority >>= priorityShift;
if (id >= 16) {
// Ensure we have the correct priority on the physical distributor...
IrqManager::GetInstance().SetInterruptPriority(id, IrqManager::guestPriority);
}
VirqState &state = GetVirqState(id);
if (priority == state.priority) {
// Nothing to do...
return;
}
state.priority = priority;
u32 targets = state.targetList;
if (targets != 0 && state.IsPending()) {
NotifyOtherCoreList(targets);
}
}
void VirtualGic::SetInterruptTargets(u32 id, u8 coreList)
{
// Ignored for SGIs and PPIs, and non-guest interrupts
if (id < 32 || !IrqManager::IsGuestInterrupt(id)) {
return;
}
// Interrupt not pending (inactive or active-only): nothing much to do (see reference manual)
// Otherwise, we may need to migrate the interrupt.
// In our model, while a physical interrupt can be pending on multiple cores, we decide that a pending SPI
// can only be handled on a single core (either it's in a LR, or in the global list), therefore we need to
// send a signal to (oldList XOR newList) to either put the interrupt back in the global list or potentially handle it
// Note that we take into account that the interrupt may be disabled.
VirqState &state = GetVirqState(id);
if (state.IsPending()) {
u8 oldList = state.targetList;
u8 diffList = (oldList ^ coreList) & CoreContext::GetActiveCoreMask();
if (diffList != 0) {
NotifyOtherCoreList(diffList);
}
}
state.targetList = coreList;
IrqManager::SetInterruptTargets(id, state.targetList);
}
void VirtualGic::SetInterruptConfigBits(u32 id, u32 config)
{
// Ignored for SGIs, implementation defined for PPIs
if (id < 32 || !IrqManager::IsGuestInterrupt(id)) {
return;
}
VirqState &state = GetVirqState(id);
// Expose bit(2n) as nonprogrammable to the guest no matter what the physical distributor actually behaves
bool newEdgeTriggered = ((config & 2) << GicV2Distributor::GetCfgrShift(id)) != 0;
if (state.edgeTriggered != newEdgeTriggered) {
state.edgeTriggered = newEdgeTriggered;
IrqManager::SetInterruptMode(id, newEdgeTriggered);
}
}
void VirtualGic::SetSgiPendingState(u32 id, u32 coreId, u32 srcCoreId)
{
VirqState &state = GetVirqState(coreId, id);
m_incomingSgiPendingSources[coreId][id] |= BIT(srcCoreId);
if (!state.handled && !state.IsQueued()) {
// The SGI was inactive on the target core...
state.SetPending();
state.srcCoreId = srcCoreId;
m_incomingSgiPendingSources[coreId][id] &= ~BIT(srcCoreId);
m_virqPendingQueue.insert(state);
NotifyOtherCoreList(BIT(coreId));
}
}
void VirtualGic::SendSgi(u32 id, GicV2Distributor::SgirTargetListFilter filter, u32 coreList)
{
switch (filter) {
case GicV2Distributor::ForwardToTargetList:
// Forward to coreList
break;
case GicV2Distributor::ForwardToAllOthers:
// Forward to all but current core
coreList = ~BIT(currentCoreCtx->GetCoreId());
break;
case GicV2Distributor::ForwardToSelf:
// Forward to current core only
coreList = BIT(currentCoreCtx->GetCoreId());
break;
default:
DEBUG("Emulated GCID_SGIR: invalid TargetListFilter value!\n");
return;
}
coreList &= CoreContext::GetActiveCoreMask();
for (u32 dstCore: util::BitsOf{coreList}) {
SetSgiPendingState(id, dstCore, currentCoreCtx->GetCoreId());
}
}
bool VirtualGic::ValidateGicdRegisterAccess(size_t offset, size_t sz)
{
// ipriorityr, itargetsr, *pendsgir are byte-accessible
// Report a fault on accessing fields for
if (
!(offset >= GICDOFF(ipriorityr) && offset < GICDOFF(ipriorityr) + GicV2Distributor::maxIrqId) &&
!(offset >= GICDOFF(itargetsr) && offset < GICDOFF(itargetsr) + GicV2Distributor::maxIrqId) &&
!(offset >= GICDOFF(cpendsgir) && offset < GICDOFF(cpendsgir) + 16) &&
!(offset >= GICDOFF(spendsgir) && offset < GICDOFF(spendsgir) + 16)
) {
return (offset & 3) == 0 && sz == 4;
} else {
return sz == 1 || (sz == 4 && ((offset & 3) != 0));
}
}
void VirtualGic::WriteGicdRegister(u32 val, size_t offset, size_t sz)
{
static constexpr auto maxIrqId = GicV2Distributor::maxIrqId;
std::scoped_lock lk{IrqManager::GetInstance().m_lock};
switch (offset) {
case GICDOFF(typer):
case GICDOFF(iidr):
case GICDOFF(icpidr2):
case GICDOFF(itargetsr) ... GICDOFF(itargetsr) + 31:
// Write ignored (read-only registers)
break;
case GICDOFF(icfgr) ... GICDOFF(icfgr) + 31/4:
// Write ignored because of an implementation-defined choice
break;
case GICDOFF(igroupr) ... GICDOFF(igroupr) + maxIrqId/8:
// Write ignored because we don't implement Group 1 here
break;
case GICDOFF(ispendr) ... GICDOFF(ispendr) + maxIrqId/8:
case GICDOFF(icpendr) ... GICDOFF(icpendr) + maxIrqId/8:
case GICDOFF(isactiver) ... GICDOFF(isactiver) + maxIrqId/8:
case GICDOFF(icactiver) ... GICDOFF(icactiver) + maxIrqId/8:
case GICDOFF(cpendsgir) ... GICDOFF(cpendsgir) + 15:
case GICDOFF(spendsgir) ... GICDOFF(spendsgir) + 15:
// Write ignored, not implemented (at least not yet, TODO)
break;
case GICDOFF(ctlr): {
SetDistributorControlRegister(val);
break;
}
case GICDOFF(isenabler) ... GICDOFF(isenabler) + maxIrqId/8: {
u32 base = 8 * static_cast<u32>(offset - GICDOFF(isenabler));
for(u32 pos: util::BitsOf{val}) {
SetInterruptEnabledState(base + pos);
}
break;
}
case GICDOFF(icenabler) ... GICDOFF(icenabler) + maxIrqId/8: {
u32 base = 8 * static_cast<u32>(offset - GICDOFF(icenabler));
for(u32 pos: util::BitsOf{val}) {
SetInterruptEnabledState(base + pos);
}
break;
}
case GICDOFF(ipriorityr) ... GICDOFF(ipriorityr) + maxIrqId: {
u32 base = static_cast<u32>(offset - GICDOFF(ipriorityr));
for (u32 i = 0; i < static_cast<u32>(sz); i++) {
SetInterruptPriorityByte(base + i, static_cast<u8>(val));
val >>= 8;
}
break;
}
case GICDOFF(itargetsr) + 32 ... GICDOFF(itargetsr) + maxIrqId: {
u32 base = static_cast<u32>(offset - GICDOFF(itargetsr));
for (u32 i = 0; i < static_cast<u32>(sz); i++) {
SetInterruptTargets(base + i, static_cast<u8>(val));
val >>= 8;
}
break;
}
case GICDOFF(icfgr) + 32/4 ... GICDOFF(icfgr) + maxIrqId/4: {
u32 base = 4 * static_cast<u32>(offset & 0xFF);
for (u32 i = 0; i < 16; i++) {
SetInterruptConfigBits(base + i, val & 3);
val >>= 2;
}
break;
}
case GICDOFF(sgir): {
SendSgi(val & 0xF, static_cast<GicV2Distributor::SgirTargetListFilter>((val >> 24) & 3), (val >> 16) & 0xFF);
break;
}
default:
DEBUG("Write to GICD reserved/implementation-defined register offset=0x%03lx value=0x%08lx", offset, val);
break;
}
UpdateState();
}
u32 VirtualGic::ReadGicdRegister(size_t offset, size_t sz)
{
static constexpr auto maxIrqId = GicV2Distributor::maxIrqId;
std::scoped_lock lk{IrqManager::GetInstance().m_lock};
//DEBUG("gicd read off 0x%03llx sz %lx\n", offset, sz);
u32 val = 0;
switch (offset) {
case GICDOFF(icfgr) ... GICDOFF(icfgr) + 31/4:
// RAZ because of an implementation-defined choice
break;
case GICDOFF(igroupr) ... GICDOFF(igroupr) + maxIrqId/8:
// RAZ because we don't implement Group 1 here
break;
case GICDOFF(ispendr) ... GICDOFF(ispendr) + maxIrqId/8:
case GICDOFF(icpendr) ... GICDOFF(icpendr) + maxIrqId/8:
case GICDOFF(isactiver) ... GICDOFF(isactiver) + maxIrqId/8:
case GICDOFF(icactiver) ... GICDOFF(icactiver) + maxIrqId/8:
case GICDOFF(cpendsgir) ... GICDOFF(cpendsgir) + 15:
case GICDOFF(spendsgir) ... GICDOFF(spendsgir) + 15:
// RAZ, not implemented (at least not yet, TODO)
break;
case GICDOFF(ctlr): {
val = GetDistributorControlRegister();
break;
}
case GICDOFF(typer): {
val = GetDistributorTypeRegister();
break;
}
case GICDOFF(iidr): {
val = GetDistributorImplementerIdentificationRegister();
break;
}
case GICDOFF(isenabler) ... GICDOFF(isenabler) + maxIrqId/8:
case GICDOFF(icenabler) ... GICDOFF(icenabler) + maxIrqId/8: {
u32 base = 8 * static_cast<u32>(offset & 0x7F);
for (u32 i = 0; i < 32; i++) {
val |= GetInterruptEnabledState(base + i) ? BIT(i) : 0;
}
break;
}
case GICDOFF(ipriorityr) ... GICDOFF(ipriorityr) + maxIrqId: {
u32 base = static_cast<u32>(offset - GICDOFF(ipriorityr));
for (u32 i = 0; i < sz; i++) {
val |= GetInterruptPriorityByte(base + i) << (8 * i);
}
break;
}
case GICDOFF(itargetsr) ... GICDOFF(itargetsr) + maxIrqId: {
u32 base = static_cast<u32>(offset - GICDOFF(itargetsr));
for (u32 i = 0; i < sz; i++) {
val |= GetInterruptTargets(base + i) << (8 * i);
}
break;
}
case GICDOFF(icfgr) + 32/4 ... GICDOFF(icfgr) + maxIrqId/4: {
u32 base = 4 * static_cast<u32>(offset & 0xFF);
for (u32 i = 0; i < 16; i++) {
val |= GetInterruptConfigBits(base + i) << (2 * i);
}
break;
}
case GICDOFF(sgir):
// Write-only register
DEBUG("Read from write-only register GCID_SGIR\n");
break;
case GICDOFF(icpidr2): {
val = GetPeripheralId2Register();
break;
}
default:
DEBUG("Read from GICD reserved/implementation-defined register offset=0x%03lx\n", offset);
break;
}
UpdateState();
return val;
}
void VirtualGic::ResampleVirqLevel(VirtualGic::VirqState &state)
{
/*
For hardware interrupts, we have kept the interrupt active on the physical GICD
For level-sensitive interrupts, we need to check if they're also still physically pending (resampling).
If not, there's nothing to service anymore, and therefore we have to deactivate them, so that
we're notified when they become pending again.
*/
if (state.edgeTriggered || !state.IsPending()) {
// Nothing to do for edge-triggered interrupts and non-pending interrupts
return;
}
u32 irqId = state.irqId;
// Can't do anything for level-sensitive PPIs from other cores either
if (irqId < 32 && state.coreId != currentCoreCtx->coreId) {
return;
}
bool lineLevel = IrqManager::IsInterruptPending(irqId);
if (!lineLevel) {
IrqManager::ClearInterruptActive(irqId);
state.ClearPendingLine();
}
}
void VirtualGic::CleanupPendingQueue()
{
// SGIs are pruned elsewhere
// Resample line level for level-sensitive interrupts
for (VirqState &state: m_virqPendingQueue) {
ResampleVirqLevel(state);
}
// Cleanup the list
m_virqPendingQueue.erase_if([](const VirqState &state) { return !state.IsPending(); });
}
size_t VirtualGic::ChoosePendingInterrupts(VirtualGic::VirqState *chosen[], size_t maxNum)
{
size_t numChosen = 0;
auto pred = [](const VirqState &state) {
if (state.irqId < 32 && state.coreId != currentCoreCtx->GetCoreId()) {
// We can't handle SGIs/PPIs of other cores.
return false;
}
return state.enabled && (state.irqId < 32 || (state.targetList & BIT(currentCoreCtx->GetCoreId())) != 0);
};
for (VirqState &state: m_virqPendingQueue) {
if (pred(state)) {
chosen[numChosen++] = &state;
}
}
for (size_t i = 0; i < numChosen; i++) {
chosen[i]->handled = true;
chosen[i]->coreId = currentCoreCtx->GetCoreId();
m_virqPendingQueue.erase(*chosen[i]);
}
}
void VirtualGic::PushListRegisters(VirqState *chosen[], size_t num)
{
for (size_t i = 0; i < num; i++) {
VirqState &state = *chosen[i];
u32 irqId = state.irqId;
GicV2VirtualInterfaceController::ListRegister lr = {0};
lr.grp1 = false; // group0
lr.priority = state.priority;
lr.virtualId = irqId;
// We only add new pending interrupts here...
lr.pending = true;
lr.active = false;
// We don't support guests setting the pending latch, so the logic is probably simpler...
if (irqId < 16) {
// SGI
lr.physicalId = BIT(9) /* EOI notification bit */ | state.srcCoreId;
// ^ IDK how kvm gets away with not setting the EOI notif bits in some cases,
// what they do seems to be prone to drop interrupts, etc.
lr.hw = false; // software
} else {
// Actual physical interrupt
lr.hw = true;
lr.physicalId = irqId;
}
volatile auto *freeLr = AllocateListRegister();
ENSURE(freeLr != nullptr);
freeLr->raw = lr.raw;
}
}
bool VirtualGic::UpdateListRegister(volatile GicV2VirtualInterfaceController::ListRegister *lr)
{
GicV2VirtualInterfaceController::ListRegister lrCopy = { .raw = lr->raw };
u32 irqId = lrCopy.virtualId;
// Note: this give priority to multi-SGIs than can be immediately handled
// Update the state
VirqState &state = GetVirqState(irqId);
ENSURE(state.handled);
u32 srcCoreId = state.coreId;
u32 coreId = currentCoreCtx->GetCoreId();
state.active = lrCopy.active;
if (lrCopy.active) {
// We don't dequeue active interrupts
if (irqId < 16) {
// We can allow SGIs to be marked active-pending if it's been made pending from the same source again
// For hw interrupts, the active-pending state is tracked in the real GICD
if (m_incomingSgiPendingSources[coreId][irqId] & BIT(srcCoreId)) {
lrCopy.pending = true;
m_incomingSgiPendingSources[coreId][irqId] &= ~BIT(srcCoreId);
}
}
// If the vIRQ goes from pending to active, it has been acknowledged: clear line level and pending latch
// SGIs are always edge-triggered, so line level doesn't matter & that's why we handle them above to simplify the code
if (!lrCopy.pending) {
state.ClearPendingOnAck();
}
lr->raw = lrCopy.raw;
return true;
} else if (lrCopy.pending) {
// New interrupts might have come, pending status might have been changed, etc.
// We need to put the interrupt back in the pending list (which we clean up afterwards)
state.handled = false;
m_virqPendingQueue.insert(state);
lr->raw = 0;
return false;
} else {
// Interrupt is inactive. This means it has been acked and handled.
// SGIs are always edge-triggered, so line level doesn't matter & that's why we handle them above to simplify the code
if (irqId < 16) {
// Special case for multi-SGIs if they can be immediately handled
if (m_incomingSgiPendingSources[coreId][irqId] != 0) {
srcCoreId = __builtin_ctz(m_incomingSgiPendingSources[coreId][irqId]);
state.srcCoreId = srcCoreId;
m_incomingSgiPendingSources[coreId][irqId] &= ~BIT(srcCoreId);
lrCopy.physicalId = BIT(9) /* EOI notification bit */ | srcCoreId;
lrCopy.pending = true;
lr->raw = lrCopy.raw;
}
}
if (!lrCopy.pending) {
// Inactive interrupt, cleanup
state.ClearPendingOnAck();
state.handled = false;
lr->raw = 0;
return false;
} else {
return true;
}
}
}
void VirtualGic::UpdateState()
{
GicV2VirtualInterfaceController::HypervisorControlRegister hcr = { .raw = gich->hcr.raw };
u32 coreId = currentCoreCtx->GetCoreId();
// First, put back inactive interrupts into the queue, handle some SGI stuff
// Need to handle the LRs in reverse order to keep list stability
u64 usedMap = cpu::rbit(m_usedLrMap[coreId]);
for (auto pos: util::BitsOf{usedMap}) {
if (!UpdateListRegister(&gich->lr[63 - pos])) {
usedMap &= ~BITL(pos);
}
}
m_usedLrMap[coreId] = cpu::rbit(usedMap);
// Then, clean the list up
CleanupPendingQueue();
size_t numFreeLr = GetNumberOfFreeListRegisters();
VirqState *chosen[64];
// Choose interrupts...
size_t numChosen = ChoosePendingInterrupts(chosen, numFreeLr);
// ...and push them
PushListRegisters(chosen, numChosen);
// Enable underflow interrupt when appropriate to do so
hcr.uie = m_numListRegisters - GetNumberOfFreeListRegisters() > 1;
gich->hcr.raw = hcr.raw;
}
std::optional<bool> VirtualGic::InterruptTopHalfHandler(u32 irqId, u32)
{
if (irqId == IrqManager::VgicUpdateSgi) {
// This SGI is just there to trigger the state update
return false;
} else if (irqId != GIC_IRQID_MAINTENANCE) {
return {};
}
// Maintenance interrupt handler:
GicV2VirtualInterfaceController::MaintenanceIntStatRegister misr = { .raw = gich->misr.raw };
// Force GICV_CTRL to behave like ns-GICC_CTLR, with group 1 being replaced by group 0
// Ensure we aren't spammed by maintenance interrupts, either.
if (misr.vgrp0e || misr.vgrp0d || misr.vgrp1e || misr.vgrp1d) {
GicV2VirtualInterfaceController::VmControlRegister vmcr = { .raw = gich->vmcr.raw };
vmcr.cbpr = 0;
vmcr.fiqEn = 0;
vmcr.ackCtl = 0;
vmcr.enableGrp1 = 0;
gich->vmcr.raw = vmcr.raw;
}
if (misr.vgrp0e) {
DEBUG("EL2 [core %d]: Group 0 enabled maintenance interrupt\n", (int)currentCoreCtx->GetCoreId());
gich->hcr.vgrp0eie = false;
gich->hcr.vgrp0die = true;
} else if (misr.vgrp0d) {
DEBUG("EL2 [core %d]: Group 0 disabled maintenance interrupt\n", (int)currentCoreCtx->GetCoreId());
gich->hcr.vgrp0eie = true;
gich->hcr.vgrp0die = false;
}
// Already handled the following 2 above:
if (misr.vgrp1e) {
DEBUG("EL2 [core %d]: Group 1 enabled maintenance interrupt\n", (int)currentCoreCtx->GetCoreId());
}
if (misr.vgrp1d) {
DEBUG("EL2 [core %d]: Group 1 disabled maintenance interrupt\n", (int)currentCoreCtx->GetCoreId());
}
if (misr.eoi) {
//DEBUG("EL2 [core %d]: SGI EOI maintenance interrupt\n", currentCoreCtx->GetCoreId());
}
if (misr.u) {
//DEBUG("EL2 [core %d]: Underflow maintenance interrupt\n", currentCoreCtx->GetCoreId());
}
ENSURE2(!misr.lrenp, "List Register Entry Not Present maintenance interrupt!\n");
// The rest should be handled by the main loop...
return false;
}
void VirtualGic::EnqueuePhysicalIrq(u32 id)
{
VirqState &state = GetVirqState(id);
state.SetPending();
m_virqPendingQueue.insert(state);
}
void VirtualGic::Initialize()
{
if (currentCoreCtx->IsBootCore()) {
m_virqPendingQueue.Initialize(m_virqStates.data());
m_numListRegisters = static_cast<u8>(1 + (gich->vtr & 0x3F));
// All fields are reset to 0 on reset and deep sleep exit
for (VirqState &state: m_virqStates) {
state.listPrev = state.listNext = virqListInvalidIndex;
state.priority = lowestPriority;
}
// SPIs (+ reserved interrupts just in case)
for (u32 i = 32; i < 1024; i++) {
GetVirqState(0, i).irqId = i;
}
// SGIs, PPIs
for (u32 coreId = 0; coreId < MAX_CORE; coreId++) {
for (u32 i = 0; i < 32; i++) {
VirqState &state = GetVirqState(coreId, i);
state.coreId = coreId;
state.irqId = i;
if (i < 16) {
state.edgeTriggered = true;
state.enabled = true;
} else {
state.edgeTriggered = IrqManager::IsInterruptEdgeTriggered(i);
}
}
}
// All guest interrupts are initially configured as disabled
// All guest SPIs are initially configured as level-sensitive with no targets
}
auto &mgr = IrqManager::GetInstance();
mgr.Register(*this, GIC_IRQID_MAINTENANCE, true);
mgr.Register(*this, IrqManager::VgicUpdateSgi, false);
// Clear the list registers (they reset to 0, though)
for (u8 i = 0; i < m_numListRegisters; i++) {
gich->lr[i].raw = 0;
}
// Enable a few maintenance interrupts. Enable the virtual interface.
GicV2VirtualInterfaceController::HypervisorControlRegister hcr = {};
hcr.vgrp1eie = true,
hcr.vgrp0eie = true,
hcr.lrenpie = true,
hcr.en = true,
gich->hcr.raw = hcr.raw;
}
}

View File

@@ -1,389 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "defines.hpp"
#include "hvisor_core_context.hpp"
#include "cpu/hvisor_cpu_exception_sysregs.hpp"
#include "hvisor_irq_manager.hpp"
namespace ams::hvisor {
class VirtualGic final : public IInterruptTask {
SINGLETON_WITH_ATTRS(VirtualGic, TEMPORARY);
private:
// For convenience, although they're already defined in irq manager header:
static inline volatile auto *const gicd = IrqManager::gicd;
static inline volatile auto *const gich = IrqManager::gich;
// Architectural properties
static constexpr u32 priorityShift = 3;
static constexpr u32 numPriorityLevels = BIT(8 - priorityShift);
static constexpr u32 lowestPriority = numPriorityLevels - 1;
// List managament constants
static constexpr u32 spiEndIndex = GicV2Distributor::maxIrqId + 1 - 32;
static constexpr u32 maxNumIntStates = spiEndIndex + MAX_CORE * 32;
static constexpr u32 virqListEndIndex = maxNumIntStates;
static constexpr u32 virqListInvalidIndex = virqListEndIndex + 1;
private:
struct VirqState {
u32 listPrev : 11;
u32 listNext : 11;
u32 irqId : 10;
u32 priority : 5;
bool pending : 1;
bool active : 1;
bool handled : 1;
bool pendingLatch : 1;
bool edgeTriggered : 1;
u32 coreId : 3;
u32 targetList : 8;
u32 srcCoreId : 3;
bool enabled : 1;
u64 : 0;
constexpr bool IsPending() const
{
return pendingLatch || (!edgeTriggered && pending);
}
constexpr void SetPending()
{
if (!edgeTriggered) {
pending = true;
} else {
pendingLatch = true;
}
}
constexpr bool ClearPendingLine()
{
// Don't clear pending latch status
pending = false;
}
constexpr bool ClearPendingOnAck()
{
// On ack, both pending line status and latch are cleared
pending = false;
pendingLatch = false;
}
constexpr bool IsQueued() const
{
return listPrev != virqListInvalidIndex && listNext != virqListInvalidIndex;
}
};
class VirqQueue final {
private:
VirqState *m_first = nullptr;
VirqState *m_last = nullptr;
VirqState *m_storage = nullptr;
public:
template<bool isConst>
class Iterator {
friend class Iterator<true>;
friend class VirqQueue;
private:
VirqState *m_node = nullptr;
VirqState *m_storage = nullptr;
private:
explicit constexpr Iterator(VirqState *node, VirqState *storage) : m_node{node}, m_storage{storage} {}
public:
// allow implicit const->non-const
constexpr Iterator(const Iterator<false> &other) : m_node{other.m_storage}, m_storage{other.m_storage} {}
constexpr Iterator() = default;
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = VirqState;
using difference_type = ptrdiff_t;
using pointer = typename std::conditional<isConst, const VirqState *, VirqState *>::type;
using reference = typename std::conditional<isConst, const VirqState &, VirqState &>::type;
constexpr bool operator==(const Iterator &other) const { return m_node == other.m_node; }
constexpr bool operator!=(const Iterator &other) const { return !(*this == other); }
constexpr reference operator*() { return *m_node; }
constexpr pointer operator->() { return m_node; }
constexpr Iterator &operator++()
{
m_node = &m_storage[m_node->listNext];
return *this;
}
constexpr Iterator &operator--()
{
m_node = &m_storage[m_node->listPrev];
return *this;
}
constexpr Iterator &operator++(int)
{
const Iterator v{*this};
++(*this);
return v;
}
constexpr Iterator &operator--(int)
{
const Iterator v{*this};
--(*this);
return v;
}
};
private:
constexpr u32 GetStateIndex(VirqState &elem) { return static_cast<u32>(&elem - &m_storage[0]); }
public:
using pointer = VirqState *;
using const_pointer = const VirqState *;
using reference = VirqState &;
using const_reference = const VirqState &;
using value_type = VirqState;
using size_type = size_t;
using difference_type = ptrdiff_t;
using iterator = Iterator<false>;
using const_iterator = Iterator<true>;
constexpr void Initialize(VirqState *storage) { m_storage = storage; }
constexpr VirqState &front() { return *m_first; };
constexpr const VirqState &front() const { return *m_first; };
constexpr VirqState &back() { return *m_last; };
constexpr const VirqState &back() const { return *m_last; };
constexpr const_iterator cbegin() const { return const_iterator{m_first, m_storage}; }
constexpr const_iterator cend() const { return const_iterator{&m_storage[virqListEndIndex], m_storage}; }
constexpr const_iterator begin() const { return cbegin(); }
constexpr const_iterator end() const { return cend(); }
constexpr iterator begin() { return iterator{m_first, m_storage}; }
constexpr iterator end() { return iterator{&m_storage[virqListEndIndex], m_storage}; }
iterator insert(iterator pos, VirqState &elem);
iterator insert(VirqState &elem);
constexpr iterator erase(iterator startPos, iterator endPos)
{
VirqState &prev = m_storage[startPos->listPrev];
VirqState &next = *endPos;
u32 nextPos = GetStateIndex(*endPos);
ENSURE(startPos->IsQueued());
if (startPos->listPrev != virqListEndIndex) {
prev.listNext = nextPos;
} else {
m_first = &next;
}
if (nextPos != virqListEndIndex) {
next.listPrev = startPos->listPrev;
} else {
m_last = &prev;
}
for (iterator it = startPos; it != endPos; ++it) {
it->listPrev = it->listNext = virqListInvalidIndex;
}
}
constexpr iterator erase(iterator pos) { return erase(pos, std::next(pos)); }
constexpr iterator erase(VirqState &pos) { return erase(iterator{&pos, m_storage}); }
template<typename Pred>
void erase_if(Pred p)
{
for (iterator it = begin(); l = end(); i != l) {
if(p(*it)) {
it = erase(it);
} else {
++it;
}
}
}
constexpr void Initialize(VirqState *storage)
{
m_storage = storage;
m_first = m_last = &(*end());
}
};
private:
static void NotifyOtherCoreList(u32 coreList)
{
coreList &= ~BIT(currentCoreCtx->GetCoreId());
if (coreList != 0) {
IrqManager::GenerateSgiForList(IrqManager::VgicUpdateSgi, coreList);
}
}
static void NotifyAllOtherCores()
{
IrqManager::GenerateSgiForAllOthers(IrqManager::VgicUpdateSgi);
}
static u64 GetEmptyListStatusRegister()
{
return static_cast<u64>(gich->elsr1) << 32 | static_cast<u64>(gich->elsr0);
}
static u64 GetNumberOfFreeListRegisters()
{
return __builtin_popcountll(GetEmptyListStatusRegister());
}
private:
std::array<VirqState, maxNumIntStates> m_virqStates{};
std::array<std::array<u8, 32>, MAX_CORE> m_incomingSgiPendingSources{};
std::array<u64, MAX_CORE> m_usedLrMap{};
VirqQueue m_virqPendingQueue{};
bool m_distributorEnabled = false;
u8 m_numListRegisters = 0;
private:
constexpr VirqState &GetVirqState(u32 coreId, u32 id)
{
if (id >= 32) {
return m_virqStates[id - 32];
} else if (id <= GicV2Distributor::maxIrqId) {
return m_virqStates[spiEndIndex + 32 * coreId + id];
}
}
VirqState &GetVirqState(u32 id) { return GetVirqState(currentCoreCtx->GetCoreId(), id); }
void SetDistributorControlRegister(u32 value)
{
// We implement a virtual distributor/interface w/o security extensions.
// Moreover, we forward all interrupts as Group 0 so that non-secure code that assumes GICv2
// *with* security extensions (and thus all interrupts fw as group 1 there) still works (bit are in the same positions).
// We don't implement Group 1 interrupts, either (so that's similar to GICv1).
bool old = m_distributorEnabled;
m_distributorEnabled = (value & 1) != 0;
// Enable bit is actually just a global enable bit for all irq forwarding, other functions of the GICD aren't affected by it
if (old != m_distributorEnabled) {
NotifyAllOtherCores();
}
}
u32 GetDistributorControlRegister(void)
{
return m_distributorEnabled ? 1 : 0;
}
u32 GetDistributorTypeRegister(void)
{
// See above comment.
// Therefore, LSPI = 0, SecurityExtn = 0, rest = from physical distributor
return IrqManager::GetTypeRegister() & 0x7F;
}
u32 GetDistributorImplementerIdentificationRegister(void)
{
u32 iidr = 'A' << 24; // Product Id: Atmosphère (?)
iidr |= 2 << 16; // Major revision 2 (GICv2)
iidr |= 0 << 12; // Minor revision 0
iidr |= 0x43B; // Implementer: Arm (value copied from physical GICD)
return iidr;
}
bool GetInterruptEnabledState(u32 id)
{
// SGIs are always enabled
return id < 16 || (IrqManager::IsGuestInterrupt(id) && GetVirqState(currentCoreCtx->GetCoreId(), id).enabled);
}
u8 GetInterruptPriorityByte(u32 id)
{
return IrqManager::IsGuestInterrupt(id) ? GetVirqState(currentCoreCtx->GetCoreId(), id).priority << priorityShift : 0;
}
u8 GetInterruptTargets(u16 id)
{
return id < 32 || (IrqManager::IsGuestInterrupt(id) && GetVirqState(currentCoreCtx->GetCoreId(), id).targetList);
}
u32 GetInterruptConfigBits(u16 id)
{
u32 oneNModel = id < 32 || !IrqManager::IsGuestInterrupt(id) ? 0 : 1;
return (IrqManager::IsGuestInterrupt(id) && GetVirqState(id).edgeTriggered) ? 2 | oneNModel : oneNModel;
}
u32 GetPeripheralId2Register(void)
{
return 2u << 4;
}
void SetInterruptEnabledState(u32 id);
void ClearInterruptEnabledState(u32 id);
void SetInterruptPriorityByte(u32 id, u8 priority);
void SetInterruptTargets(u32 id, u8 coreList);
void SetInterruptConfigBits(u32 id, u32 config);
void SetSgiPendingState(u32 id, u32 coreId, u32 srcCoreId);
void SendSgi(u32 id, GicV2Distributor::SgirTargetListFilter filter, u32 coreList);
void ResampleVirqLevel(VirqState &state);
void CleanupPendingQueue();
size_t ChoosePendingInterrupts(VirqState *chosen[], size_t maxNum);
volatile GicV2VirtualInterfaceController::ListRegister *AllocateListRegister(void)
{
u32 ff = __builtin_ffsll(GetEmptyListStatusRegister());
if (ff == 0) {
return nullptr;
} else {
m_usedLrMap[currentCoreCtx->GetCoreId()] |= BITL(ff - 1);
return &gich->lr[ff - 1];
}
}
void PushListRegisters(VirqState *chosen[], size_t num);
bool UpdateListRegister(volatile GicV2VirtualInterfaceController::ListRegister *lr);
private:
constexpr VirtualGic() = default;
public:
// For convenience (when trapping lower-el data aborts):
static constexpr uintptr_t gicdPhysicalAddress = 0; // fixme pls MEMORY_MAP_PA_GICD;
public:
static bool ValidateGicdRegisterAccess(size_t offset, size_t sz);
public:
void WriteGicdRegister(u32 val, size_t offset, size_t sz);
u32 ReadGicdRegister(size_t offset, size_t sz);
// Must be called by irqManager only...
// not sure if I should have made IrqManager a friend of this class
void UpdateState();
std::optional<bool> InterruptTopHalfHandler(u32 irqId, u32) final;
void EnqueuePhysicalIrq(u32 id);
void Initialize();
};
}

View File

@@ -1,135 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_watchpoint_manager.hpp"
#include "cpu/hvisor_cpu_instructions.hpp"
#include <mutex>
#define _REENT_ONLY
#include <cerrno>
// Can't use two THERMOSPHERE_SAVE_SYSREG as it prevents ldp from being generated
#define SAVE_WATCHPOINT(i, _)\
__asm__ __volatile__ (\
"msr " STRINGIZE(dbgwvr##i##_el1) ", %0\n"\
"msr " STRINGIZE(dbgwcr##i##_el1) ", %1"\
:\
: "r"(m_stopPoints[i].vr), "r"(m_stopPoints[i].cr.raw)\
: "memory"\
);
namespace {
constexpr bool IsRangeMaskWatchpoint(uintptr_t addr, size_t size)
{
// size needs to be a power of 2, at least 8 (we'll only allow 16+ though), addr needs to be aligned.
bool ret = (size & (size - 1)) == 0 && size >= 16 && (addr & (size - 1)) == 0;
return ret;
}
constexpr bool CheckWatchpointAddressAndSizeParams(uintptr_t addr, size_t size)
{
if (size == 0) {
return false;
} else if (size > 8) {
return IsRangeMaskWatchpoint(addr, size);
} else {
return ((addr + size) & ~7ul) == (addr & ~7ul);
}
}
}
namespace ams::hvisor {
WatchpointManager WatchpointManager::instance{};
void WatchpointManager::Reload() const
{
cpu::dmb();
EVAL(REPEAT(MAX_WCR, SAVE_WATCHPOINT, ~));
cpu::dsb();
cpu::isb();
}
bool WatchpointManager::FindPredicate(const cpu::DebugRegisterPair &pair, uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl direction) const
{
size_t off;
size_t sz;
size_t nmask;
if (pair.cr.mask != 0) {
off = 0;
sz = MASK(pair.cr.mask);
nmask = ~sz;
} else {
off = __builtin_ffs(pair.cr.bas) - 1;
sz = __builtin_popcount(pair.cr.bas);
nmask = ~7ul;
}
if (size != 0) {
// Strict watchpoint check
if (addr == pair.vr + off && direction == pair.cr.lsc && sz == size) {
return true;
}
} else {
// Return first wp that could have triggered the exception
if ((addr & nmask) == pair.vr && (direction & pair.cr.lsc) != 0) {
return true;
}
}
return false;
}
cpu::DebugRegisterPair WatchpointManager::RetrieveWatchpointConfig(uintptr_t addr, cpu::DebugRegisterPair::LoadStoreControl direction) const
{
std::scoped_lock lk{m_lock};
const cpu::DebugRegisterPair *p = Find(addr, 0, direction);
return p != nullptr ? *p : cpu::DebugRegisterPair{};
}
int WatchpointManager::Add(uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl direction)
{
if (!CheckWatchpointAddressAndSizeParams(addr, size)) {
return -EINVAL;
}
cpu::DebugRegisterPair wp{};
wp.cr.lsc = direction;
if (IsRangeMaskWatchpoint(addr, size)) {
wp.vr = addr;
wp.cr.bas = 0xFF; // TRM-mandated
wp.cr.mask = static_cast<u32>(__builtin_ffsl(size) - 1);
} else {
size_t off = addr & 7ull;
wp.vr = addr & ~7ul;
wp.cr.bas = MASK2(off + size - 1, off);
}
return AddImpl(addr, size, wp);
}
int WatchpointManager::Remove(uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl direction)
{
if (!CheckWatchpointAddressAndSizeParams(addr, size)) {
return -EINVAL;
}
return RemoveImpl(addr, size, direction);
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright (c) 2019-2020 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 "hvisor_hw_stop_point_manager.hpp"
namespace ams::hvisor {
class WatchpointManager final : public HwStopPointManager {
SINGLETON(WatchpointManager);
private:
bool FindPredicate(const cpu::DebugRegisterPair &pair, uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl direction) const final;
void Reload() const final;
private:
constexpr WatchpointManager() : HwStopPointManager(MAX_WCR, IrqManager::ReloadWatchpointsSgi) {}
public:
virtual void ReloadOnAllCores() const;
static void ReloadOnAllCoresSgiHandler();
cpu::DebugRegisterPair RetrieveWatchpointConfig(uintptr_t addr, cpu::DebugRegisterPair::LoadStoreControl direction) const;
int Add(uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl direction);
int Remove(uintptr_t addr, size_t size, cpu::DebugRegisterPair::LoadStoreControl direction);
};
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright (c) 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 <string.h>
#include "core_ctx.h"
#include "platform/stage2.h"
#include "platform/devices.h"
#include "sysreg.h"
#include "utils.h"
// BSS includes real bss and tmp bss
extern u8 __bss_start__[], __real_bss_end__[], __bss_end__[];
static void initSysregs(void)
{
// Set system to sane defaults, aarch64 for el1, mmu&caches initially disabled for EL1, etc.
SET_SYSREG(hcr_el2, 0x80000000);
SET_SYSREG(dacr32_el2, 0xFFFFFFFF); // unused
SET_SYSREG(sctlr_el1, 0x00C50838);
SET_SYSREG(mdcr_el2, 0x00000000);
SET_SYSREG(mdscr_el1, 0x00000000);
// Timer stuff
SET_SYSREG(cntvoff_el2, 0x00000000);
SET_SYSREG(cnthctl_el2, 0x00000003); // Don't trap anything for now; event streams disabled
SET_SYSREG(cntkctl_el1, 0x00000003); // Don't trap anything for now; event streams disabled
SET_SYSREG(cntp_ctl_el0, 0x00000000);
SET_SYSREG(cntv_ctl_el0, 0x00000000);
__dsb_local();
__isb();
}
void initSystem(u32 coreId, bool isBootCore, u64 argument)
{
coreCtxInit(coreId, isBootCore, argument);
initSysregs();
if (isBootCore) {
if (!currentCoreCtx->warmboot) {
memset(__bss_start__, 0, __real_bss_end__ - __bss_start__);
}
memset(__real_bss_end__, 0, __bss_end__ - __real_bss_end__);
}
stage2ConfigureAndEnable();
if (isBootCore) {
devicesMapAllExtra();
}
}

272
thermosphere/src/lib/ini.c Normal file
View File

@@ -0,0 +1,272 @@
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "ini.h"
#if !INI_USE_STACK
#include <stdlib.h>
#endif
#define MAX_SECTION 50
#define MAX_NAME 50
/* Used by ini_parse_string() to keep track of string parsing state. */
typedef struct {
const char* ptr;
size_t num_left;
} ini_parse_string_ctx;
/* Strip whitespace chars off end of given string, in place. Return s. */
static char* rstrip(char* s)
{
char* p = s + strlen(s);
while (p > s && isspace((unsigned char)(*--p)))
*p = '\0';
return s;
}
/* Return pointer to first non-whitespace char in given string. */
static char* lskip(const char* s)
{
while (*s && isspace((unsigned char)(*s)))
s++;
return (char*)s;
}
/* Return pointer to first char (of chars) or inline comment in given string,
or pointer to null at end of string if neither found. Inline comment must
be prefixed by a whitespace character to register as a comment. */
static char* find_chars_or_comment(const char* s, const char* chars)
{
#if INI_ALLOW_INLINE_COMMENTS
int was_space = 0;
while (*s && (!chars || !strchr(chars, *s)) &&
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
was_space = isspace((unsigned char)(*s));
s++;
}
#else
while (*s && (!chars || !strchr(chars, *s))) {
s++;
}
#endif
return (char*)s;
}
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
static char* strncpy0(char* dest, const char* src, size_t size)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-truncation"
strncpy(dest, src, size - 1);
#pragma GCC diagnostic pop
dest[size - 1] = '\0';
return dest;
}
/* See documentation in header file. */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user)
{
/* Uses a fair bit of stack (use heap instead if you need to) */
#if INI_USE_STACK
char line[INI_MAX_LINE];
int max_line = INI_MAX_LINE;
#else
char* line;
int max_line = INI_INITIAL_ALLOC;
#endif
#if INI_ALLOW_REALLOC
char* new_line;
int offset;
#endif
char section[MAX_SECTION] = "";
char prev_name[MAX_NAME] = "";
char* start;
char* end;
char* name;
char* value;
int lineno = 0;
int error = 0;
#if !INI_USE_STACK
line = (char*)malloc(INI_INITIAL_ALLOC);
if (!line) {
return -2;
}
#endif
#if INI_HANDLER_LINENO
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
#else
#define HANDLER(u, s, n, v) handler(u, s, n, v)
#endif
/* Scan through stream line by line */
while (reader(line, max_line, stream) != NULL) {
#if INI_ALLOW_REALLOC
offset = strlen(line);
while (offset == max_line - 1 && line[offset - 1] != '\n') {
max_line *= 2;
if (max_line > INI_MAX_LINE)
max_line = INI_MAX_LINE;
new_line = realloc(line, max_line);
if (!new_line) {
free(line);
return -2;
}
line = new_line;
if (reader(line + offset, max_line - offset, stream) == NULL)
break;
if (max_line >= INI_MAX_LINE)
break;
offset += strlen(line + offset);
}
#endif
lineno++;
start = line;
#if INI_ALLOW_BOM
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
(unsigned char)start[1] == 0xBB &&
(unsigned char)start[2] == 0xBF) {
start += 3;
}
#endif
start = lskip(rstrip(start));
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
/* Start-of-line comment */
}
#if INI_ALLOW_MULTILINE
else if (*prev_name && *start && start > line) {
/* Non-blank line with leading whitespace, treat as continuation
of previous name's value (as per Python configparser). */
if (!HANDLER(user, section, prev_name, start) && !error)
error = lineno;
}
#endif
else if (*start == '[') {
/* A "[section]" line */
end = find_chars_or_comment(start + 1, "]");
if (*end == ']') {
*end = '\0';
strncpy0(section, start + 1, sizeof(section));
*prev_name = '\0';
}
else if (!error) {
/* No ']' found on section line */
error = lineno;
}
}
else if (*start) {
/* Not a comment, must be a name[=:]value pair */
end = find_chars_or_comment(start, "=:");
if (*end == '=' || *end == ':') {
*end = '\0';
name = rstrip(start);
value = end + 1;
#if INI_ALLOW_INLINE_COMMENTS
end = find_chars_or_comment(value, NULL);
if (*end)
*end = '\0';
#endif
value = lskip(value);
rstrip(value);
/* Valid name[=:]value pair found, call handler */
strncpy0(prev_name, name, sizeof(prev_name));
if (!HANDLER(user, section, name, value) && !error)
error = lineno;
}
else if (!error) {
/* No '=' or ':' found on name[=:]value line */
error = lineno;
}
}
#if INI_STOP_ON_FIRST_ERROR
if (error)
break;
#endif
}
#if !INI_USE_STACK
free(line);
#endif
return error;
}
/* See documentation in header file. */
int ini_parse_file(FILE* file, ini_handler handler, void* user)
{
return ini_parse_stream((ini_reader)fgets, file, handler, user);
}
/* See documentation in header file. */
int ini_parse(const char* filename, ini_handler handler, void* user)
{
FILE* file;
int error;
file = fopen(filename, "r");
if (!file)
return -1;
error = ini_parse_file(file, handler, user);
fclose(file);
return error;
}
/* An ini_reader function to read the next line from a string buffer. This
is the fgets() equivalent used by ini_parse_string(). */
static char* ini_reader_string(char* str, int num, void* stream) {
ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
const char* ctx_ptr = ctx->ptr;
size_t ctx_num_left = ctx->num_left;
char* strp = str;
char c;
if (ctx_num_left == 0 || num < 2)
return NULL;
while (num > 1 && ctx_num_left != 0) {
c = *ctx_ptr++;
ctx_num_left--;
*strp++ = c;
if (c == '\n')
break;
num--;
}
*strp = '\0';
ctx->ptr = ctx_ptr;
ctx->num_left = ctx_num_left;
return str;
}
/* See documentation in header file. */
int ini_parse_string(const char* string, ini_handler handler, void* user) {
ini_parse_string_ctx ctx;
ctx.ptr = string;
ctx.num_left = strlen(string);
return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
user);
}

130
thermosphere/src/lib/ini.h Normal file
View File

@@ -0,0 +1,130 @@
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#ifndef __INI_H__
#define __INI_H__
/* Make this header file easier to include in C++ code */
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
/* Nonzero if ini_handler callback should accept lineno parameter. */
#ifndef INI_HANDLER_LINENO
#define INI_HANDLER_LINENO 0
#endif
/* Typedef for prototype of handler function. */
#if INI_HANDLER_LINENO
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value,
int lineno);
#else
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value);
#endif
/* Typedef for prototype of fgets-style reader function. */
typedef char* (*ini_reader)(char* str, int num, void* stream);
/* Parse given INI-style file. May have [section]s, name=value pairs
(whitespace stripped), and comments starting with ';' (semicolon). Section
is "" if name=value pair parsed before any section heading. name:value
pairs are also supported as a concession to Python's configparser.
For each name=value pair parsed, call handler function with given user
pointer as well as section, name, and value (data only valid for duration
of handler call). Handler should return nonzero on success, zero on error.
Returns 0 on success, line number of first error on parse error (doesn't
stop on first error), -1 on file open error, or -2 on memory allocation
error (only when INI_USE_STACK is zero).
*/
int ini_parse(const char* filename, ini_handler handler, void* user);
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
close the file when it's finished -- the caller must do that. */
int ini_parse_file(FILE* file, ini_handler handler, void* user);
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
filename. Used for implementing custom or string-based I/O (see also
ini_parse_string). */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user);
/* Same as ini_parse(), but takes a zero-terminated string with the INI data
instead of a file. Useful for parsing INI data from a network socket or
already in memory. */
int ini_parse_string(const char* string, ini_handler handler, void* user);
/* Nonzero to allow multi-line value parsing, in the style of Python's
configparser. If allowed, ini_parse() will call the handler with the same
name for each subsequent line parsed. */
#ifndef INI_ALLOW_MULTILINE
#define INI_ALLOW_MULTILINE 1
#endif
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
#ifndef INI_ALLOW_BOM
#define INI_ALLOW_BOM 1
#endif
/* Chars that begin a start-of-line comment. Per Python configparser, allow
both ; and # comments at the start of a line by default. */
#ifndef INI_START_COMMENT_PREFIXES
#define INI_START_COMMENT_PREFIXES ";#"
#endif
/* Nonzero to allow inline comments (with valid inline comment characters
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
Python 3.2+ configparser behaviour. */
#ifndef INI_ALLOW_INLINE_COMMENTS
#define INI_ALLOW_INLINE_COMMENTS 1
#endif
#ifndef INI_INLINE_COMMENT_PREFIXES
#define INI_INLINE_COMMENT_PREFIXES ";"
#endif
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK
#define INI_USE_STACK 1
#endif
/* Maximum line length for any line in INI file (stack or heap). Note that
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
#ifndef INI_MAX_LINE
#define INI_MAX_LINE 200
#endif
/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
zero. */
#ifndef INI_ALLOW_REALLOC
#define INI_ALLOW_REALLOC 0
#endif
/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
is zero. */
#ifndef INI_INITIAL_ALLOC
#define INI_INITIAL_ALLOC 200
#endif
/* Stop parsing on first error (default is to keep parsing). */
#ifndef INI_STOP_ON_FIRST_ERROR
#define INI_STOP_ON_FIRST_ERROR 0
#endif
#ifdef __cplusplus
}
#endif
#endif /* __INI_H__ */

Some files were not shown because too many files have changed in this diff Show More