Compare commits
237 Commits
uart_mitm
...
thermosphe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
020cfb89c6 | ||
|
|
f40c064e80 | ||
|
|
6bcb5aca60 | ||
|
|
548367453b | ||
|
|
6790895487 | ||
|
|
41d98b5e48 | ||
|
|
b6dbdfe82d | ||
|
|
30b79c2fe7 | ||
|
|
3a79a7a961 | ||
|
|
cefd66e7af | ||
|
|
a8f28ab96d | ||
|
|
2986967f2a | ||
|
|
fc8a596409 | ||
|
|
36f48748a4 | ||
|
|
6f423fcfab | ||
|
|
fccadfdbf6 | ||
|
|
47f343cda6 | ||
|
|
987731ea43 | ||
|
|
d4bbb78a27 | ||
|
|
0126a6417f | ||
|
|
7ecb3a4aaf | ||
|
|
56d764d09c | ||
|
|
0cb5eab933 | ||
|
|
d15154f668 | ||
|
|
ea830bb5ab | ||
|
|
e8435784a7 | ||
|
|
0437867449 | ||
|
|
797cea0ac8 | ||
|
|
874d1432be | ||
|
|
e8bfe8a311 | ||
|
|
036883c30f | ||
|
|
5b56d05e11 | ||
|
|
4adb675072 | ||
|
|
77fbbb4c68 | ||
|
|
f6793139c1 | ||
|
|
37a889ccb2 | ||
|
|
ea7d161755 | ||
|
|
d72fc3e8b9 | ||
|
|
c7eaf71896 | ||
|
|
5a445e9394 | ||
|
|
613402121a | ||
|
|
2574f68484 | ||
|
|
1ee289f5f1 | ||
|
|
31e5ff7c1d | ||
|
|
02bbe1bb40 | ||
|
|
b21c75b22b | ||
|
|
c99a77a0c3 | ||
|
|
dd9b3ddb0d | ||
|
|
785b7e1a37 | ||
|
|
1eda049ada | ||
|
|
493a3c92e2 | ||
|
|
dad84ac017 | ||
|
|
eab46ab1b6 | ||
|
|
192d2db4a9 | ||
|
|
ff2c835b0a | ||
|
|
dd7f0b805b | ||
|
|
fdd5481f63 | ||
|
|
0b8d0035b9 | ||
|
|
bfa917edf5 | ||
|
|
61a972abf3 | ||
|
|
697e61850f | ||
|
|
2077062b79 | ||
|
|
b445fe1bf4 | ||
|
|
b65f11d205 | ||
|
|
5de560be30 | ||
|
|
be6253d6ad | ||
|
|
78eea8a373 | ||
|
|
53850a5976 | ||
|
|
788f331de0 | ||
|
|
edf2bbc30e | ||
|
|
e4d189eee3 | ||
|
|
e6fdd6bc98 | ||
|
|
3556c12960 | ||
|
|
67daf5a73e | ||
|
|
f1a241ffef | ||
|
|
bf7f077432 | ||
|
|
ebf8053b42 | ||
|
|
914790be01 | ||
|
|
036882f162 | ||
|
|
b0ae19a6f9 | ||
|
|
0b7efc0501 | ||
|
|
c67ff366ea | ||
|
|
63e3f40fa5 | ||
|
|
3fe7c7537e | ||
|
|
256201922b | ||
|
|
46c82e2d77 | ||
|
|
cb4d898579 | ||
|
|
7acd5a9ec7 | ||
|
|
7f7e4e8310 | ||
|
|
8f25d4f77f | ||
|
|
e1a8bdd495 | ||
|
|
ef23db21e6 | ||
|
|
46954a5359 | ||
|
|
6499d36722 | ||
|
|
66ba05b302 | ||
|
|
7a774adbc3 | ||
|
|
ce1df0ac23 | ||
|
|
fc5d81dca3 | ||
|
|
23ef4b94d6 | ||
|
|
e4de512e6f | ||
|
|
cf0b052590 | ||
|
|
0509fa57ca | ||
|
|
175f16627b | ||
|
|
f0b9162d5e | ||
|
|
02e2a1efa2 | ||
|
|
ed5736e8d2 | ||
|
|
b0ca29d18e | ||
|
|
36ca87491d | ||
|
|
9ef2532b9d | ||
|
|
cbf3b305ca | ||
|
|
c0252e07f6 | ||
|
|
71401b0731 | ||
|
|
ff1aac0ab5 | ||
|
|
984f6776c6 | ||
|
|
0e47f7f46b | ||
|
|
c00672654a | ||
|
|
8538fed043 | ||
|
|
1f2b8e7918 | ||
|
|
30a4a0d4c1 | ||
|
|
97c4595a3a | ||
|
|
5b545f89f5 | ||
|
|
310048a32c | ||
|
|
5473443057 | ||
|
|
78723164c1 | ||
|
|
58d52675cd | ||
|
|
bd36796d5f | ||
|
|
779aeaa538 | ||
|
|
5de05ed8a8 | ||
|
|
abeaa72f94 | ||
|
|
c89ce085a6 | ||
|
|
418cabbd53 | ||
|
|
744491ca33 | ||
|
|
9ebf3c9580 | ||
|
|
f23fb45956 | ||
|
|
61fec56c6e | ||
|
|
a665f49b93 | ||
|
|
3e8bd764d5 | ||
|
|
c64ccd86ee | ||
|
|
217c1ad054 | ||
|
|
0f0228e240 | ||
|
|
3ca3e094fe | ||
|
|
d1cd17a9df | ||
|
|
626f0ecb98 | ||
|
|
92a291cd41 | ||
|
|
906d6a4f20 | ||
|
|
6b8a843ffb | ||
|
|
72d1992eec | ||
|
|
1369697058 | ||
|
|
b6a130547a | ||
|
|
067770334e | ||
|
|
a7741c8576 | ||
|
|
dd96c8b32b | ||
|
|
68a1ce6dd2 | ||
|
|
388c245ce4 | ||
|
|
1086c0612c | ||
|
|
8dc9be9f8e | ||
|
|
018260645a | ||
|
|
a6d191bf4b | ||
|
|
1eb60a2a52 | ||
|
|
3d3a9925b9 | ||
|
|
501472324f | ||
|
|
b9d07fccd6 | ||
|
|
d42d9e60b9 | ||
|
|
28552da099 | ||
|
|
d56185e432 | ||
|
|
c42aef6ba7 | ||
|
|
03fe744bc4 | ||
|
|
e49a035455 | ||
|
|
0811572889 | ||
|
|
76a5e745e4 | ||
|
|
7130b6efd1 | ||
|
|
37b14bc4b8 | ||
|
|
13174e7458 | ||
|
|
ef79908594 | ||
|
|
3a13ab2e46 | ||
|
|
676a895cca | ||
|
|
cdf3bc6942 | ||
|
|
fe0662a75d | ||
|
|
f3ad62d1b8 | ||
|
|
27859a7541 | ||
|
|
e3b6d64f1b | ||
|
|
c17b81aaf6 | ||
|
|
176be2386d | ||
|
|
f9ec21e99e | ||
|
|
1775d59977 | ||
|
|
b2c5ef2611 | ||
|
|
0b69407f8e | ||
|
|
0a9a8c2f15 | ||
|
|
271d2a0ddb | ||
|
|
6289d2e398 | ||
|
|
f8266775f6 | ||
|
|
83c6e2f0e7 | ||
|
|
9bc0ed2f70 | ||
|
|
dc3f87a715 | ||
|
|
3649b94b5d | ||
|
|
a3da478089 | ||
|
|
ff9714d4f6 | ||
|
|
cc232ef4f8 | ||
|
|
b742b861ab | ||
|
|
eb27c36709 | ||
|
|
e0339049b3 | ||
|
|
e6c5eb3928 | ||
|
|
045f556f80 | ||
|
|
a11b0b6e0e | ||
|
|
3fa9133814 | ||
|
|
ecb4857cbb | ||
|
|
6d33ebceef | ||
|
|
4a5d05f32b | ||
|
|
b686af2008 | ||
|
|
a291bddcc1 | ||
|
|
ad6db14526 | ||
|
|
61b6f06766 | ||
|
|
16cfa1305d | ||
|
|
af8e0f2519 | ||
|
|
a560de8465 | ||
|
|
3009438e54 | ||
|
|
9af9408feb | ||
|
|
68469ea862 | ||
|
|
ffa216c8c7 | ||
|
|
1db0502b35 | ||
|
|
6665245640 | ||
|
|
9d6089dc86 | ||
|
|
70a9caa7e9 | ||
|
|
4952b3c9bf | ||
|
|
bcc72896fd | ||
|
|
b5c6b06dad | ||
|
|
4e0eef2784 | ||
|
|
ada6b180cc | ||
|
|
e6adccce6e | ||
|
|
f6e1cff5f8 | ||
|
|
88382f4fc3 | ||
|
|
66b047255b | ||
|
|
076c988796 | ||
|
|
4e6108839d | ||
|
|
1d58ba8d52 | ||
|
|
bd9152215f | ||
|
|
1f7a1f71d6 |
@@ -52,11 +52,6 @@
|
|||||||
; 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.
|
||||||
|
|||||||
@@ -38,14 +38,3 @@ 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).
|
|
||||||
|
|||||||
@@ -35,9 +35,10 @@
|
|||||||
|
|
||||||
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(void **thermosphere);
|
static size_t package2_get_thermosphere(const 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, void *data, size_t size);
|
static void package2_append_section(unsigned int id, package2_header_t *package2, const 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) {
|
||||||
@@ -50,7 +51,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;
|
||||||
void *thermosphere;
|
const void *thermosphere;
|
||||||
size_t thermosphere_size;
|
size_t thermosphere_size;
|
||||||
ini1_header_t *orig_ini1, *rebuilt_ini1;
|
ini1_header_t *orig_ini1, *rebuilt_ini1;
|
||||||
|
|
||||||
@@ -67,6 +68,8 @@ 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");
|
||||||
@@ -143,6 +146,9 @@ 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);
|
||||||
|
|
||||||
@@ -327,12 +333,9 @@ 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(void **thermosphere) {
|
static size_t package2_get_thermosphere(const void **thermosphere) {
|
||||||
/*extern const uint8_t thermosphere_bin[];
|
(*thermosphere) = thermosphere_bin;
|
||||||
extern const uint32_t thermosphere_bin_size;*/
|
return 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) {
|
||||||
@@ -353,7 +356,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, void *data, size_t size) {
|
static void package2_append_section(unsigned int id, package2_header_t *package2, const 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;
|
||||||
@@ -365,6 +368,22 @@ 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;
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
#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 {
|
||||||
|
|||||||
@@ -109,9 +109,6 @@ 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);
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
#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 {
|
||||||
|
|
||||||
@@ -38,7 +37,6 @@ namespace ams::mitm {
|
|||||||
ModuleId_NsMitm,
|
ModuleId_NsMitm,
|
||||||
ModuleId_DnsMitm,
|
ModuleId_DnsMitm,
|
||||||
ModuleId_Sysupdater,
|
ModuleId_Sysupdater,
|
||||||
ModuleId_UartMitm,
|
|
||||||
|
|
||||||
ModuleId_Count,
|
ModuleId_Count,
|
||||||
};
|
};
|
||||||
@@ -72,7 +70,6 @@ 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>(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -362,12 +362,6 @@ 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. */
|
||||||
|
|||||||
@@ -1,257 +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 <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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +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/>.
|
|
||||||
*/
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,322 +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 <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(×tamp0);
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,108 +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/>.
|
|
||||||
*/
|
|
||||||
#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>);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,77 +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 <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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
* @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
|
|
||||||
@@ -1,96 +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 <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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
1
thermosphere/.gitignore
vendored
1
thermosphere/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
out
|
|
||||||
@@ -9,13 +9,39 @@ 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
|
||||||
@@ -25,43 +51,62 @@ endif
|
|||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
TARGET := $(notdir $(CURDIR))
|
TARGET := $(notdir $(CURDIR))
|
||||||
BUILD := build
|
BUILD := build
|
||||||
SOURCES := src src/lib
|
SOURCES := src src/libc src/platform src/gdb $(PLATFORM_SOURCES)
|
||||||
DATA := data
|
DATA := data
|
||||||
INCLUDES := include ../common/include
|
INCLUDES :=
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
# options for code generation
|
# options for code generation
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
ARCH := -march=armv8-a -mtune=cortex-a57
|
# Note: -ffixed-x18 and -mgeneral-regs-only are very important and must be enabled
|
||||||
DEFINES := -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
|
ARCH := -march=armv8-a -mtune=cortex-a57 -mgeneral-regs-only -ffixed-x18 -Wno-psabi
|
||||||
|
DEFINES := $(PLATFORM_DEFINES)
|
||||||
CFLAGS := \
|
CFLAGS := \
|
||||||
-g \
|
-g \
|
||||||
-O2 \
|
-fmacro-prefix-map=$(TOPDIR)/src/= \
|
||||||
-ffunction-sections \
|
-Os \
|
||||||
-fdata-sections \
|
-ffunction-sections \
|
||||||
-mgeneral-regs-only \
|
-fdata-sections \
|
||||||
-fomit-frame-pointer \
|
-fomit-frame-pointer \
|
||||||
-std=gnu11 \
|
-fno-asynchronous-unwind-tables \
|
||||||
-Werror \
|
-fno-unwind-tables \
|
||||||
-Wall \
|
-fno-stack-protector \
|
||||||
-Wno-main \
|
-fstrict-volatile-bitfields \
|
||||||
$(ARCH) $(DEFINES)
|
-Wall \
|
||||||
|
-Werror \
|
||||||
|
-Wno-main \
|
||||||
|
$(ARCH) $(DEFINES)
|
||||||
|
|
||||||
CFLAGS += $(INCLUDE) -D__CCPLEX__
|
export CXXWRAPS := -Wl,--wrap,__cxa_pure_virtual \
|
||||||
|
-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
|
||||||
|
|
||||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
|
CFLAGS += $(INCLUDE)
|
||||||
|
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++2a
|
||||||
|
CFLAGS += -std=gnu11
|
||||||
|
|
||||||
ASFLAGS := -g $(ARCH)
|
ASFLAGS := -g $(ARCH) $(DEFINES)
|
||||||
LDFLAGS = -specs=$(TOPDIR)/linker.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
LDFLAGS = -specs=$(TOPDIR)/linker.specs -nostartfiles -nostdlib -g $(ARCH) $(CXXWRAPS) -Wl,-Map,$(notdir $*.map)
|
||||||
|
|
||||||
LIBS :=
|
LIBS := -lgcc
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
# 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 :=
|
LIBDIRS := $(AMSLIBSDIR)/libvapours
|
||||||
|
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
@@ -100,7 +145,7 @@ endif
|
|||||||
|
|
||||||
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
|
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
|
||||||
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||||
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
|
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
|
||||||
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
|
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
|
||||||
|
|
||||||
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
||||||
@@ -109,11 +154,29 @@ 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
|
.PHONY: $(BUILD) clean all qemu qemudbg
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
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
|
||||||
@@ -151,7 +214,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
|
%.bin.o %_bin.h: %.bin
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
@echo $(notdir $<)
|
@echo $(notdir $<)
|
||||||
@$(bin2o)
|
@$(bin2o)
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
Thermosphère
|
|
||||||
=====
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Thermosphère is a hypervisor for the Nintendo Switch.
|
|
||||||
@@ -1,55 +1,209 @@
|
|||||||
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
|
||||||
{
|
{
|
||||||
. = 0x800D0000;
|
__start_pa__ = ABSOLUTE(ORIGIN(main));
|
||||||
|
__temp_pa__ = ABSOLUTE(ORIGIN(temp));
|
||||||
|
__max_image_size__ = ABSOLUTE(LENGTH(main));
|
||||||
|
__max_temp_size__ = ABSOLUTE(LENGTH(temp) - 0x1000);
|
||||||
|
|
||||||
|
.text :
|
||||||
|
{
|
||||||
|
. = ALIGN(8);
|
||||||
|
__start__ = ABSOLUTE(.);
|
||||||
|
KEEP(*(.crt0*));
|
||||||
|
*(.text.unlikely .text.*_unlikely .text.unlikely.*)
|
||||||
|
*(.text.exit .text.exit.*)
|
||||||
|
*(.text.startup .text.startup.*)
|
||||||
|
*(.text.hot .text.hot.*)
|
||||||
|
*(.text .stub .text.* .gnu.linkonce.t.*)
|
||||||
|
. = ALIGN(0x800);
|
||||||
|
__vectors_start__ = ABSOLUTE(.);
|
||||||
|
KEEP(*(.vectors*));
|
||||||
|
__vectors_end__ = ABSOLUTE(.);
|
||||||
|
ASSERT(__vectors_end__ - __vectors_start__ <= 0x800, "Exception vectors section should be max 0x800 in size!");
|
||||||
|
. = ALIGN(8);
|
||||||
|
} >mainVa AT>main :main
|
||||||
|
|
||||||
|
.init :
|
||||||
|
{
|
||||||
|
KEEP( *(.init) )
|
||||||
|
. = ALIGN(8);
|
||||||
|
} >mainVa AT>main :main
|
||||||
|
|
||||||
|
.plt :
|
||||||
|
{
|
||||||
|
*(.plt)
|
||||||
|
*(.iplt)
|
||||||
|
. = ALIGN(8);
|
||||||
|
} >mainVa AT>main :main
|
||||||
|
|
||||||
|
|
||||||
|
.fini :
|
||||||
|
{
|
||||||
|
KEEP( *(.fini) )
|
||||||
|
. = ALIGN(8);
|
||||||
|
} >mainVa AT>main :main
|
||||||
|
|
||||||
|
.rodata :
|
||||||
|
{
|
||||||
|
*(.rodata .rodata.* .gnu.linkonce.r.*)
|
||||||
|
SORT(CONSTRUCTORS)
|
||||||
|
. = ALIGN(8);
|
||||||
|
} >mainVa AT>main :main
|
||||||
|
|
||||||
|
.got : { __got_start__ = ABSOLUTE(.); *(.got) *(.igot) } >mainVa AT>main :main
|
||||||
|
.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(4);
|
|
||||||
.text : {
|
|
||||||
PROVIDE(lds_thermo_start = .);
|
|
||||||
start.o (.text*)
|
|
||||||
*(.text*)
|
|
||||||
}
|
|
||||||
|
|
||||||
. = ALIGN(8);
|
. = ALIGN(8);
|
||||||
.rodata : {
|
|
||||||
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))
|
|
||||||
}
|
|
||||||
|
|
||||||
. = ALIGN(8);
|
/* Shit we keep in the elf but otherwise discard */
|
||||||
.data : {
|
.eh_frame_hdr (NOLOAD) : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) } >mainVa :NONE
|
||||||
*(.data*)
|
.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
|
||||||
|
|
||||||
/* Uninitialised data */
|
/* ==================
|
||||||
. = ALIGN(8);
|
==== Metadata ====
|
||||||
PROVIDE(lds_bss_start = .);
|
================== */
|
||||||
.bss (NOLOAD) : {
|
|
||||||
*(.bss*) . = ALIGN(8);
|
|
||||||
}
|
|
||||||
PROVIDE(lds_bss_end = .);
|
|
||||||
|
|
||||||
/* EL2 stack */
|
/* Discard sections that difficult post-processing */
|
||||||
. = ALIGN(16);
|
/DISCARD/ : { *(.group .comment .note) }
|
||||||
. += 0x10000; /* 64 KiB stack */
|
|
||||||
el2_stack_end = .;
|
|
||||||
|
|
||||||
/* Page align the end of binary */
|
/* Stabs debugging sections. */
|
||||||
. = ALIGN(512);
|
.stab 0 : { *(.stab) }
|
||||||
PROVIDE(lds_el2_thermo_end = .);
|
.stabstr 0 : { *(.stabstr) }
|
||||||
|
.stab.excl 0 : { *(.stab.excl) }
|
||||||
|
.stab.exclstr 0 : { *(.stab.exclstr) }
|
||||||
|
.stab.index 0 : { *(.stab.index) }
|
||||||
|
.stab.indexstr 0 : { *(.stab.indexstr) }
|
||||||
|
|
||||||
/* EL1 stack */
|
/* DWARF debug sections.
|
||||||
. = ALIGN(16);
|
Symbols in the DWARF debugging sections are relative to the beginning
|
||||||
. += 0x10000; /* 64 KiB stack */
|
of the section so we begin them at 0. */
|
||||||
el1_stack_end = .;
|
|
||||||
|
|
||||||
lds_thermo_end = .;
|
/* DWARF 1 */
|
||||||
|
.debug 0 : { *(.debug) }
|
||||||
|
.line 0 : { *(.line) }
|
||||||
|
|
||||||
/DISCARD/ : { *(.dynstr*) }
|
/* GNU DWARF 1 extensions */
|
||||||
/DISCARD/ : { *(.dynamic*) }
|
.debug_srcinfo 0 : { *(.debug_srcinfo) }
|
||||||
/DISCARD/ : { *(.plt*) }
|
.debug_sfnames 0 : { *(.debug_sfnames) }
|
||||||
/DISCARD/ : { *(.interp*) }
|
|
||||||
/DISCARD/ : { *(.gnu*) }
|
/* 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) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
%rename link old_link
|
%rename link old_link
|
||||||
|
|
||||||
*link:
|
*link:
|
||||||
%(old_link) -T %:getenv(TOPDIR /linker.ld) --nmagic --gc-sections
|
%(old_link) -T %:getenv(TOPDIR /%:getenv(PLATFORM .mem)) -T %:getenv(TOPDIR /linker.ld) -no-pie --nmagic --gc-sections
|
||||||
|
|
||||||
*startfile:
|
|
||||||
crti%O%s crtbegin%O%s
|
|
||||||
|
|||||||
6
thermosphere/qemu.mem
Normal file
6
thermosphere/qemu.mem
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
NULL : ORIGIN = 0, LENGTH = 0x1000
|
||||||
|
main : ORIGIN = 0x60000000, LENGTH = 64M /* QEMU's memory map changes dynamically? */
|
||||||
|
temp : ORIGIN = 0x64000000, LENGTH = 64M
|
||||||
|
}
|
||||||
54
thermosphere/src/abort.cpp
Normal file
54
thermosphere/src/abort.cpp
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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 (;;);
|
||||||
|
}
|
||||||
32
thermosphere/src/asm_macros.s
Normal file
32
thermosphere/src/asm_macros.s
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
156
thermosphere/src/cpu/hvisor_cpu_caches.cpp
Normal file
156
thermosphere/src/cpu/hvisor_cpu_caches.cpp
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
71
thermosphere/src/cpu/hvisor_cpu_caches.hpp
Normal file
71
thermosphere/src/cpu/hvisor_cpu_caches.hpp
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
}
|
||||||
110
thermosphere/src/cpu/hvisor_cpu_debug_register_pair.hpp
Normal file
110
thermosphere/src/cpu/hvisor_cpu_debug_register_pair.hpp
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* 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>);
|
||||||
|
}
|
||||||
118
thermosphere/src/cpu/hvisor_cpu_exception_sysregs.hpp
Normal file
118
thermosphere/src/cpu/hvisor_cpu_exception_sysregs.hpp
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* 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>);
|
||||||
|
|
||||||
|
}
|
||||||
69
thermosphere/src/cpu/hvisor_cpu_instructions.hpp
Normal file
69
thermosphere/src/cpu/hvisor_cpu_instructions.hpp
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
54
thermosphere/src/cpu/hvisor_cpu_interrupt_mask_guard.hpp
Normal file
54
thermosphere/src/cpu/hvisor_cpu_interrupt_mask_guard.hpp
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
198
thermosphere/src/cpu/hvisor_cpu_mmu.hpp
Normal file
198
thermosphere/src/cpu/hvisor_cpu_mmu.hpp
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
495
thermosphere/src/cpu/hvisor_cpu_sysreg_general.hpp
Normal file
495
thermosphere/src/cpu/hvisor_cpu_sysreg_general.hpp
Normal file
@@ -0,0 +1,495 @@
|
|||||||
|
/*
|
||||||
|
* 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; }
|
||||||
|
}
|
||||||
61
thermosphere/src/debug_log.c
Normal file
61
thermosphere/src/debug_log.c
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
30
thermosphere/src/debug_log.h
Normal file
30
thermosphere/src/debug_log.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* 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, ...);
|
||||||
263
thermosphere/src/debug_manager.c
Normal file
263
thermosphere/src/debug_manager.c
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
*/
|
||||||
84
thermosphere/src/debug_manager.h
Normal file
84
thermosphere/src/debug_manager.h
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
54
thermosphere/src/defines.hpp
Normal file
54
thermosphere/src/defines.hpp
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
129
thermosphere/src/drivers/arm/hvisor_drivers_arm_pl011.cpp
Normal file
129
thermosphere/src/drivers/arm/hvisor_drivers_arm_pl011.cpp
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
152
thermosphere/src/drivers/arm/hvisor_drivers_arm_pl011.hpp
Normal file
152
thermosphere/src/drivers/arm/hvisor_drivers_arm_pl011.hpp
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
131
thermosphere/src/drivers/tegra/hvisor_drivers_tegra_uart.cpp
Normal file
131
thermosphere/src/drivers/tegra/hvisor_drivers_tegra_uart.cpp
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
210
thermosphere/src/drivers/tegra/hvisor_drivers_tegra_uart.hpp
Normal file
210
thermosphere/src/drivers/tegra/hvisor_drivers_tegra_uart.hpp
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,654 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
375
thermosphere/src/exception_vectors.s
Normal file
375
thermosphere/src/exception_vectors.s
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
@@ -1,108 +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 <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;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,185 +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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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
|
|
||||||
556
thermosphere/src/gdb/debug.c
Normal file
556
thermosphere/src/gdb/debug.c
Normal file
@@ -0,0 +1,556 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
17
thermosphere/src/gdb/debug.h
Normal file
17
thermosphere/src/gdb/debug.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
133
thermosphere/src/gdb/hio.c
Normal file
133
thermosphere/src/gdb/hio.c
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* 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);*/
|
||||||
|
}
|
||||||
14
thermosphere/src/gdb/hio.h
Normal file
14
thermosphere/src/gdb/hio.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
226
thermosphere/src/gdb/hvisor_gdb_comms.cpp
Normal file
226
thermosphere/src/gdb/hvisor_gdb_comms.cpp
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
257
thermosphere/src/gdb/hvisor_gdb_context.cpp
Normal file
257
thermosphere/src/gdb/hvisor_gdb_context.cpp
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
230
thermosphere/src/gdb/hvisor_gdb_context.hpp
Normal file
230
thermosphere/src/gdb/hvisor_gdb_context.hpp
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
49
thermosphere/src/gdb/hvisor_gdb_defines_internal.hpp
Normal file
49
thermosphere/src/gdb/hvisor_gdb_defines_internal.hpp
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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)
|
||||||
101
thermosphere/src/gdb/hvisor_gdb_mem.cpp
Normal file
101
thermosphere/src/gdb/hvisor_gdb_mem.cpp
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
106
thermosphere/src/gdb/hvisor_gdb_packet_data.cpp
Normal file
106
thermosphere/src/gdb/hvisor_gdb_packet_data.cpp
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
199
thermosphere/src/gdb/hvisor_gdb_packet_data.hpp
Normal file
199
thermosphere/src/gdb/hvisor_gdb_packet_data.hpp
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
}
|
||||||
107
thermosphere/src/gdb/hvisor_gdb_query.cpp
Normal file
107
thermosphere/src/gdb/hvisor_gdb_query.cpp
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
}
|
||||||
227
thermosphere/src/gdb/hvisor_gdb_regs.cpp
Normal file
227
thermosphere/src/gdb/hvisor_gdb_regs.cpp
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
75
thermosphere/src/gdb/hvisor_gdb_remote_command.cpp
Normal file
75
thermosphere/src/gdb/hvisor_gdb_remote_command.cpp
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
92
thermosphere/src/gdb/hvisor_gdb_stop_points.cpp
Normal file
92
thermosphere/src/gdb/hvisor_gdb_stop_points.cpp
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
159
thermosphere/src/gdb/hvisor_gdb_thread.cpp
Normal file
159
thermosphere/src/gdb/hvisor_gdb_thread.cpp
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
thermosphere/src/gdb/hvisor_gdb_thread.hpp
Normal file
26
thermosphere/src/gdb/hvisor_gdb_thread.hpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
}
|
||||||
62
thermosphere/src/gdb/hvisor_gdb_vebose.cpp
Normal file
62
thermosphere/src/gdb/hvisor_gdb_vebose.cpp
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
143
thermosphere/src/gdb/hvisor_gdb_xfer.cpp
Normal file
143
thermosphere/src/gdb/hvisor_gdb_xfer.cpp
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
38
thermosphere/src/hvisor_core_context.cpp
Normal file
38
thermosphere/src/hvisor_core_context.cpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
111
thermosphere/src/hvisor_core_context.hpp
Normal file
111
thermosphere/src/hvisor_core_context.hpp
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* 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; }
|
||||||
|
};
|
||||||
|
}
|
||||||
183
thermosphere/src/hvisor_exception_dispatcher.cpp
Normal file
183
thermosphere/src/hvisor_exception_dispatcher.cpp
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
40
thermosphere/src/hvisor_exception_dispatcher.hpp
Normal file
40
thermosphere/src/hvisor_exception_dispatcher.hpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
}
|
||||||
144
thermosphere/src/hvisor_exception_stack_frame.hpp
Normal file
144
thermosphere/src/hvisor_exception_stack_frame.hpp
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* 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);*/
|
||||||
87
thermosphere/src/hvisor_fpu_register_cache.hpp
Normal file
87
thermosphere/src/hvisor_fpu_register_cache.hpp
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
58
thermosphere/src/hvisor_fpu_register_cache_impl.s
Normal file
58
thermosphere/src/hvisor_fpu_register_cache_impl.s
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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
|
||||||
61
thermosphere/src/hvisor_generic_timer.cpp
Normal file
61
thermosphere/src/hvisor_generic_timer.cpp
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
103
thermosphere/src/hvisor_generic_timer.hpp
Normal file
103
thermosphere/src/hvisor_generic_timer.hpp
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
183
thermosphere/src/hvisor_gicv2.hpp
Normal file
183
thermosphere/src/hvisor_gicv2.hpp
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
* 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>);
|
||||||
|
}
|
||||||
240
thermosphere/src/hvisor_guest_memory.cpp
Normal file
240
thermosphere/src/hvisor_guest_memory.cpp
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
35
thermosphere/src/hvisor_guest_memory.hpp
Normal file
35
thermosphere/src/hvisor_guest_memory.hpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
67
thermosphere/src/hvisor_guest_timers.hpp
Normal file
67
thermosphere/src/hvisor_guest_timers.hpp
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
76
thermosphere/src/hvisor_hw_breakpoint_manager.cpp
Normal file
76
thermosphere/src/hvisor_hw_breakpoint_manager.cpp
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
thermosphere/src/hvisor_hw_breakpoint_manager.hpp
Normal file
36
thermosphere/src/hvisor_hw_breakpoint_manager.hpp
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
};
|
||||||
|
}
|
||||||
127
thermosphere/src/hvisor_hw_stop_point_manager.cpp
Normal file
127
thermosphere/src/hvisor_hw_stop_point_manager.cpp
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
74
thermosphere/src/hvisor_hw_stop_point_manager.hpp
Normal file
74
thermosphere/src/hvisor_hw_stop_point_manager.hpp
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
34
thermosphere/src/hvisor_i_interrupt_task.hpp
Normal file
34
thermosphere/src/hvisor_i_interrupt_task.hpp
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 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) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
223
thermosphere/src/hvisor_irq_manager.cpp
Normal file
223
thermosphere/src/hvisor_irq_manager.cpp
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
123
thermosphere/src/hvisor_irq_manager.hpp
Normal file
123
thermosphere/src/hvisor_irq_manager.hpp
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
197
thermosphere/src/hvisor_memory_map.cpp
Normal file
197
thermosphere/src/hvisor_memory_map.cpp
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
99
thermosphere/src/hvisor_memory_map.hpp
Normal file
99
thermosphere/src/hvisor_memory_map.hpp
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
* Copyright (c) 2019-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,12 +13,14 @@
|
|||||||
* 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"
|
|
||||||
|
|
||||||
namespace ams::mitm::uart {
|
#include "defines.hpp"
|
||||||
|
|
||||||
DEFINE_MITM_MODULE_CLASS(0x4000, AMS_GET_SYSTEM_THREAD_PRIORITY(uart, IpcServer) - 1);
|
namespace ams::hvisor {
|
||||||
|
|
||||||
|
// Caller needs to disable interrupts
|
||||||
|
size_t SafeIoCopy(void *dst, const void *src, size_t size);
|
||||||
|
|
||||||
}
|
}
|
||||||
84
thermosphere/src/hvisor_safe_io_copy.s
Normal file
84
thermosphere/src/hvisor_safe_io_copy.s
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
182
thermosphere/src/hvisor_sw_breakpoint_manager.cpp
Normal file
182
thermosphere/src/hvisor_sw_breakpoint_manager.cpp
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
67
thermosphere/src/hvisor_sw_breakpoint_manager.hpp
Normal file
67
thermosphere/src/hvisor_sw_breakpoint_manager.hpp
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
95
thermosphere/src/hvisor_synchronization.cpp
Normal file
95
thermosphere/src/hvisor_synchronization.cpp
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
thermosphere/src/hvisor_synchronization.hpp
Normal file
62
thermosphere/src/hvisor_synchronization.hpp
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
747
thermosphere/src/hvisor_virtual_gic.cpp
Normal file
747
thermosphere/src/hvisor_virtual_gic.cpp
Normal file
@@ -0,0 +1,747 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
389
thermosphere/src/hvisor_virtual_gic.hpp
Normal file
389
thermosphere/src/hvisor_virtual_gic.hpp
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
};
|
||||||
|
}
|
||||||
135
thermosphere/src/hvisor_watchpoint_manager.cpp
Normal file
135
thermosphere/src/hvisor_watchpoint_manager.cpp
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
thermosphere/src/hvisor_watchpoint_manager.hpp
Normal file
40
thermosphere/src/hvisor_watchpoint_manager.hpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
};
|
||||||
|
}
|
||||||
65
thermosphere/src/initSystem.c
Normal file
65
thermosphere/src/initSystem.c
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
/* 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);
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
/* 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__ */
|
|
||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user