Compare commits
258 Commits
0.18.0
...
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 | ||
|
|
1545fa9d44 | ||
|
|
71add1add8 | ||
|
|
a96786fd2c | ||
|
|
74e4e70053 | ||
|
|
26b6216fa0 | ||
|
|
fe5c850e69 | ||
|
|
2b825d56dc | ||
|
|
13b17a5848 | ||
|
|
621520c30b | ||
|
|
846cc0b47a | ||
|
|
e82ad1cdc5 | ||
|
|
b40da8f445 | ||
|
|
a9c6476416 | ||
|
|
c1d93a9495 | ||
|
|
201b17f100 | ||
|
|
c0e3cee657 | ||
|
|
26d8db74f8 | ||
|
|
ee2e9d50fd | ||
|
|
fd1a39996e | ||
|
|
8eb65ab401 | ||
|
|
bcda834980 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -95,3 +95,5 @@ sept/sept-secondary/KEYS.py
|
||||
**/build_nintendo_nx_arm
|
||||
**/build_nintendo_nx_x64
|
||||
**/build_nintendo_nx_x86
|
||||
|
||||
stratosphere/test/
|
||||
|
||||
1
Makefile
1
Makefile
@@ -135,6 +135,7 @@ dist: dist-no-debug
|
||||
cp exosphere/program/sc7fw/sc7fw.elf atmosphere-$(AMSVER)-debug/exosphere-sc7fw.elf
|
||||
cp exosphere/program/rebootstub/rebootstub.elf atmosphere-$(AMSVER)-debug/exosphere-rebootstub.elf
|
||||
cp mesosphere/kernel_ldr/kernel_ldr.elf atmosphere-$(AMSVER)-debug/kernel_ldr.elf
|
||||
cp mesosphere/kernel/kernel.elf atmosphere-$(AMSVER)-debug/kernel.elf
|
||||
cp stratosphere/ams_mitm/ams_mitm.elf atmosphere-$(AMSVER)-debug/ams_mitm.elf
|
||||
cp stratosphere/boot/boot.elf atmosphere-$(AMSVER)-debug/boot.elf
|
||||
cp stratosphere/boot2/boot2.elf atmosphere-$(AMSVER)-debug/boot2.elf
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
# Changelog
|
||||
## 0.18.1
|
||||
+ A number of minor issues were fixed, including:
|
||||
+ The new `dns.mitm` module added in 0.18.0 no longer fatal errors when receiving port=nullptr.
|
||||
+ This fixes youtube ad-blocking, and possibly other usecases.
|
||||
+ A bug was fixed that caused ams.mitm to incorrectly cache data storages.
|
||||
+ This potentially broke DLC when using romfs mods, and could have caused other issues (e.g. with custom themes, and maybe other cases).
|
||||
+ A bug was fixed in power state control module registration.
|
||||
+ This might fix a weird edge case with system module dependencies on sleep/wake, but probably nobody should notice any differences.
|
||||
+ A bug was fixed where mesosphere sometimes treated virtual core IDs as though they were physical core IDs.
|
||||
+ This had zero impact, because for Switch virtual core == physical core, but it could have affected future platforms if it had remained unresolved.
|
||||
+ Several issues were fixed, and usability and stability were improved.
|
||||
## 0.18.0
|
||||
+ A new mitm module was added (`dns.mitm`).
|
||||
+ This provides a highly configurable mechanism for redirecting DNS resolution requests.
|
||||
|
||||
@@ -25,12 +25,6 @@ set_mitm enables intercepting requests to the system settings service. It curren
|
||||
+ `ns` system module and games (to allow for overriding game locales)
|
||||
+ All firmware debug settings requests (to allow modification of system settings not directly exposed to the user)
|
||||
|
||||
## dns_mitm
|
||||
|
||||
dns_mitm enables intercepting requests to dns resolution services, to enable redirecting requests for specified hostnames.
|
||||
|
||||
For documentation, see [here](../../features/dns_mitm.md).
|
||||
|
||||
### Firmware Version
|
||||
set_mitm intercepts the `GetFirmwareVersion` command, if the requester is `qlaunch` or `maintenance`.
|
||||
It modifies the `display_version` field of the returned system version, causing the version to display
|
||||
@@ -39,3 +33,8 @@ in settings as `#.#.#|AMS #.#.#|?` with `? = S` when running under system eMMC o
|
||||
### System Settings
|
||||
set_mitm intercepts the `GetSettingsItemValueSize` and `GetSettingsItemValue` commands for all requesters.
|
||||
It does so in order to enable user configuration of system settings, which are parsed from `/atmosphere/system_settings.ini` on boot. See [here](../../features/configurations.md) for more information on the system settings format.
|
||||
|
||||
## dns_mitm
|
||||
dns_mitm enables intercepting requests to dns resolution services, to enable redirecting requests for specified hostnames.
|
||||
|
||||
For documentation, see [here](../../features/dns_mitm.md).
|
||||
|
||||
@@ -11,6 +11,8 @@ In particular, hosts files parsed by DNS.mitm have the following extensions to t
|
||||
+ `*` is treated as a wildcard character, matching any collection of 0 or more characters wherever it occurs in a hostname.
|
||||
+ `%` is treated as a stand-in for the value of `nsd!environment_identifier`. This is always `lp1`, on production devices.
|
||||
|
||||
If multiple entries in a host file match a domain, the last-defined match is used.
|
||||
|
||||
Please note that homebrew may trigger a hosts file re-parse by sending the extension IPC command 65000 ("AtmosphereReloadHostsFile") to a connected `sfdnsres` session.
|
||||
|
||||
### Hosts file selection
|
||||
|
||||
@@ -35,9 +35,10 @@
|
||||
|
||||
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_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 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 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;
|
||||
size_t kernel_size;
|
||||
bool is_sd_kernel = false;
|
||||
void *thermosphere;
|
||||
const void *thermosphere;
|
||||
size_t thermosphere_size;
|
||||
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");
|
||||
}
|
||||
|
||||
package2->metadata.section_offsets[PACKAGE2_SECTION_UNUSED] = 0; /* base of DRAM */
|
||||
|
||||
/* Load Kernel from SD, if possible. */
|
||||
{
|
||||
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_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. */
|
||||
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];
|
||||
}
|
||||
|
||||
static size_t package2_get_thermosphere(void **thermosphere) {
|
||||
/*extern const uint8_t thermosphere_bin[];
|
||||
extern const uint32_t thermosphere_bin_size;*/
|
||||
/* TODO: enable when tested. */
|
||||
(*thermosphere) = NULL;
|
||||
return 0;
|
||||
static size_t package2_get_thermosphere(const void **thermosphere) {
|
||||
(*thermosphere) = thermosphere_bin;
|
||||
return thermosphere_bin_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;
|
||||
}
|
||||
|
||||
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. */
|
||||
/* We assume that the loading address doesn't need to be changed. */
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
uint8_t *data = package2->data;
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
#define PACKAGE2_MINVER_1100_CURRENT 0x10
|
||||
|
||||
#define NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS ((void *)(0xA9800000ull))
|
||||
#define DRAM_BASE_PHYSICAL (0x80000000)
|
||||
|
||||
typedef struct {
|
||||
union {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
[subrepo]
|
||||
remote = https://github.com/Atmosphere-NX/Atmosphere-libs
|
||||
branch = master
|
||||
commit = 90d85295bb9e7917475e3aee13fe20311d73df0e
|
||||
parent = 63440cab18f4a87e602d44d127cb3f70b652d05b
|
||||
commit = bc08912dd31bb172467add8e24b4f0adac431939
|
||||
parent = 71add1add8521e0c2115ec612c514400ac7ba688
|
||||
method = merge
|
||||
cmdver = 0.4.1
|
||||
|
||||
@@ -45,7 +45,21 @@
|
||||
|
||||
namespace ams::kern {
|
||||
|
||||
static_assert(cpu::NumCores <= static_cast<s32>(BITSIZEOF(u64)));
|
||||
static_assert(util::size(cpu::VirtualToPhysicalCoreMap) == BITSIZEOF(u64));
|
||||
namespace cpu {
|
||||
|
||||
static constexpr inline size_t NumVirtualCores = BITSIZEOF(u64);
|
||||
|
||||
static constexpr inline u64 VirtualCoreMask = [] {
|
||||
u64 mask = 0;
|
||||
for (size_t i = 0; i < NumVirtualCores; ++i) {
|
||||
mask |= (UINT64_C(1) << i);
|
||||
}
|
||||
return mask;
|
||||
}();
|
||||
|
||||
}
|
||||
|
||||
static_assert(cpu::NumCores <= cpu::NumVirtualCores);
|
||||
static_assert(util::size(cpu::VirtualToPhysicalCoreMap) == cpu::NumVirtualCores);
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace ams::kern {
|
||||
/* Most fields have already been cleared by our constructor. */
|
||||
|
||||
/* Initial processes may run on all cores. */
|
||||
m_core_mask = (1ul << cpu::NumCores) - 1;
|
||||
m_core_mask = cpu::VirtualCoreMask;
|
||||
|
||||
/* Initial processes may use any user priority they like. */
|
||||
m_priority_mask = ~0xFul;
|
||||
@@ -55,18 +55,17 @@ namespace ams::kern {
|
||||
const auto max_prio = cap.Get<CorePriority::LowestThreadPriority>();
|
||||
const auto min_prio = cap.Get<CorePriority::HighestThreadPriority>();
|
||||
|
||||
R_UNLESS(min_core <= max_core, svc::ResultInvalidCombination());
|
||||
R_UNLESS(min_prio <= max_prio, svc::ResultInvalidCombination());
|
||||
R_UNLESS(max_core < cpu::NumCores, svc::ResultInvalidCoreId());
|
||||
R_UNLESS(min_core <= max_core, svc::ResultInvalidCombination());
|
||||
R_UNLESS(min_prio <= max_prio, svc::ResultInvalidCombination());
|
||||
R_UNLESS(max_core < cpu::NumVirtualCores, svc::ResultInvalidCoreId());
|
||||
|
||||
MESOSPHERE_ASSERT(max_core < BITSIZEOF(u64));
|
||||
MESOSPHERE_ASSERT(max_prio < BITSIZEOF(u64));
|
||||
|
||||
/* Set core mask. */
|
||||
for (auto core_id = min_core; core_id <= max_core; core_id++) {
|
||||
m_core_mask |= (1ul << core_id);
|
||||
}
|
||||
MESOSPHERE_ASSERT((m_core_mask & ((1ul << cpu::NumCores) - 1)) == m_core_mask);
|
||||
MESOSPHERE_ASSERT((m_core_mask & cpu::VirtualCoreMask) == m_core_mask);
|
||||
|
||||
/* Set priority mask. */
|
||||
for (auto prio = min_prio; prio <= max_prio; prio++) {
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace ams::kern::svc {
|
||||
case ams::svc::InfoType_ThreadTickCount:
|
||||
{
|
||||
/* Verify the requested core is valid. */
|
||||
const bool core_valid = (info_subtype == static_cast<u64>(-1ul)) || (info_subtype < util::size(cpu::VirtualToPhysicalCoreMap));
|
||||
const bool core_valid = (info_subtype == static_cast<u64>(-1ul)) || (info_subtype < cpu::NumVirtualCores);
|
||||
R_UNLESS(core_valid, svc::ResultInvalidCombination());
|
||||
|
||||
/* Get the thread from its handle. */
|
||||
|
||||
@@ -21,8 +21,8 @@ namespace ams::kern::svc {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr bool IsValidCoreId(int32_t core_id) {
|
||||
return (0 <= core_id && core_id < static_cast<int32_t>(cpu::NumCores));
|
||||
constexpr bool IsValidVirtualCoreId(int32_t core_id) {
|
||||
return (0 <= core_id && core_id < static_cast<int32_t>(cpu::NumVirtualCores));
|
||||
}
|
||||
|
||||
void ExitProcess() {
|
||||
@@ -275,7 +275,7 @@ namespace ams::kern::svc {
|
||||
R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle());
|
||||
|
||||
/* Validate the core id. */
|
||||
R_UNLESS(IsValidCoreId(core_id), svc::ResultInvalidCoreId());
|
||||
R_UNLESS(IsValidVirtualCoreId(core_id), svc::ResultInvalidCoreId());
|
||||
R_UNLESS(((1ul << core_id) & process->GetCoreMask()) != 0, svc::ResultInvalidCoreId());
|
||||
|
||||
/* Validate the priority. */
|
||||
|
||||
@@ -21,8 +21,8 @@ namespace ams::kern::svc {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr bool IsValidCoreId(int32_t core_id) {
|
||||
return (0 <= core_id && core_id < static_cast<int32_t>(cpu::NumCores));
|
||||
constexpr bool IsValidVirtualCoreId(int32_t core_id) {
|
||||
return (0 <= core_id && core_id < static_cast<int32_t>(cpu::NumVirtualCores));
|
||||
}
|
||||
|
||||
Result CreateThread(ams::svc::Handle *out, ams::svc::ThreadFunc f, uintptr_t arg, uintptr_t stack_bottom, int32_t priority, int32_t core_id) {
|
||||
@@ -33,7 +33,7 @@ namespace ams::kern::svc {
|
||||
}
|
||||
|
||||
/* Validate arguments. */
|
||||
R_UNLESS(IsValidCoreId(core_id), svc::ResultInvalidCoreId());
|
||||
R_UNLESS(IsValidVirtualCoreId(core_id), svc::ResultInvalidCoreId());
|
||||
R_UNLESS(((1ul << core_id) & process.GetCoreMask()) != 0, svc::ResultInvalidCoreId());
|
||||
|
||||
R_UNLESS(ams::svc::HighestThreadPriority <= priority && priority <= ams::svc::LowestThreadPriority, svc::ResultInvalidPriority());
|
||||
@@ -168,7 +168,7 @@ namespace ams::kern::svc {
|
||||
R_UNLESS(affinity_mask != 0, svc::ResultInvalidCombination());
|
||||
|
||||
/* Validate the core id. */
|
||||
if (IsValidCoreId(core_id)) {
|
||||
if (IsValidVirtualCoreId(core_id)) {
|
||||
R_UNLESS(((1ul << core_id) & affinity_mask) != 0, svc::ResultInvalidCombination());
|
||||
} else {
|
||||
R_UNLESS(core_id == ams::svc::IdealCoreNoUpdate || core_id == ams::svc::IdealCoreDontCare, svc::ResultInvalidCoreId());
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
#include <stratosphere/spl.hpp>
|
||||
#include <stratosphere/time.hpp>
|
||||
#include <stratosphere/updater.hpp>
|
||||
#include <stratosphere/usb.hpp>
|
||||
#include <stratosphere/wec.hpp>
|
||||
|
||||
/* Include FS last. */
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
namespace ams::psc {
|
||||
|
||||
enum PmModuleId : u16 {
|
||||
enum PmModuleId : u32 {
|
||||
PmModuleId_Usb = 4,
|
||||
PmModuleId_Ethernet = 5,
|
||||
PmModuleId_Fgm = 6,
|
||||
|
||||
21
libraries/libstratosphere/include/stratosphere/usb.hpp
Normal file
21
libraries/libstratosphere/include/stratosphere/usb.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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/usb/usb_limits.hpp>
|
||||
#include <stratosphere/usb/usb_types.hpp>
|
||||
#include <stratosphere/usb/usb_device_types.hpp>
|
||||
#include <stratosphere/usb/usb_device.hpp>
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 <vapours.hpp>
|
||||
#include <stratosphere/usb/usb_limits.hpp>
|
||||
#include <stratosphere/usb/usb_types.hpp>
|
||||
#include <stratosphere/usb/usb_device_types.hpp>
|
||||
|
||||
#define AMS_USB_I_DS_ENDPOINT_INTERFACE_INFO(C, H) \
|
||||
AMS_SF_METHOD_INFO(C, H, 0, Result, PostBufferAsync, (sf::Out<u32> out_urb_id, u64 address, u32 size), (out_urb_id, address, size)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 1, Result, Cancel, (), ()) \
|
||||
AMS_SF_METHOD_INFO(C, H, 2, Result, GetCompletionEvent, (sf::OutCopyHandle out), (out)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 3, Result, GetUrbReport, (sf::Out<usb::UrbReport> out), (out)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 4, Result, Stall, (), ()) \
|
||||
AMS_SF_METHOD_INFO(C, H, 5, Result, SetZlt, (bool zlt), (zlt))
|
||||
|
||||
/* TODO: Deprecated interface? */
|
||||
|
||||
AMS_SF_DEFINE_INTERFACE(ams::usb::ds, IDsEndpoint, AMS_USB_I_DS_ENDPOINT_INTERFACE_INFO)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 <vapours.hpp>
|
||||
#include <stratosphere/usb/usb_limits.hpp>
|
||||
#include <stratosphere/usb/usb_types.hpp>
|
||||
#include <stratosphere/usb/usb_device_types.hpp>
|
||||
#include <stratosphere/usb/ds/usb_i_ds_endpoint.hpp>
|
||||
|
||||
#define AMS_USB_I_DS_INTERFACE_INTERFACE_INFO(C, H) \
|
||||
AMS_SF_METHOD_INFO(C, H, 0, Result, RegisterEndpoint, (u8 endpoint_address, sf::Out<sf::SharedPointer<usb::ds::IDsEndpoint>> out), (endpoint_address, out)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 1, Result, GetSetupEvent, (sf::OutCopyHandle out), (out)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 2, Result, GetSetupPacket, (const sf::OutBuffer &out), (out)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 3, Result, CtrlInAsync, (sf::Out<u32> out_urb_id, u64 address, u32 size), (out_urb_id, address, size)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 4, Result, CtrlOutAsync, (sf::Out<u32> out_urb_id, u64 address, u32 size), (out_urb_id, address, size)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 5, Result, GetCtrlInCompletionEvent, (sf::OutCopyHandle out), (out)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 6, Result, GetCtrlInUrbReport, (sf::Out<usb::UrbReport> out), (out)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 7, Result, GetCtrlOutCompletionEvent, (sf::OutCopyHandle out), (out)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 8, Result, GetCtrlOutUrbReport, (sf::Out<usb::UrbReport> out), (out)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 9, Result, CtrlStall, (), ()) \
|
||||
AMS_SF_METHOD_INFO(C, H, 10, Result, AppendConfigurationData, (u8 bInterfaceNumber, usb::UsbDeviceSpeed device_speed, const sf::InBuffer &data), (bInterfaceNumber, device_speed, data)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 65000, Result, Enable, (), ()) \
|
||||
AMS_SF_METHOD_INFO(C, H, 65001, Result, Disable, (), ())
|
||||
|
||||
/* TODO: Deprecated interface? */
|
||||
|
||||
AMS_SF_DEFINE_INTERFACE(ams::usb::ds, IDsInterface, AMS_USB_I_DS_INTERFACE_INTERFACE_INFO)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 <vapours.hpp>
|
||||
#include <stratosphere/usb/usb_limits.hpp>
|
||||
#include <stratosphere/usb/usb_types.hpp>
|
||||
#include <stratosphere/usb/usb_device_types.hpp>
|
||||
#include <stratosphere/usb/ds/usb_i_ds_interface.hpp>
|
||||
|
||||
#define AMS_USB_I_DS_SERVICE_INTERFACE_INFO(C, H) \
|
||||
AMS_SF_METHOD_INFO(C, H, 0, Result, Bind, (usb::ComplexId complex_id, sf::CopyHandle process_h), (complex_id, process_h)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 1, Result, RegisterInterface, (sf::Out<sf::SharedPointer<usb::ds::IDsInterface>> out, u8 bInterfaceNumber), (out, bInterfaceNumber)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 2, Result, GetStateChangeEvent, (sf::OutCopyHandle out), (out)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 3, Result, GetState, (sf::Out<usb::UsbState> out), (out)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 4, Result, ClearDeviceData, (), ()) \
|
||||
AMS_SF_METHOD_INFO(C, H, 5, Result, AddUsbStringDescriptor, (sf::Out<u8> out, const sf::InBuffer &desc), (out, desc)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 6, Result, DeleteUsbStringDescriptor, (u8 index), (index)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 7, Result, SetUsbDeviceDescriptor, (const sf::InBuffer &desc, usb::UsbDeviceSpeed speed), (desc, speed)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 8, Result, SetBinaryObjectStore, (const sf::InBuffer &bos), (bos)) \
|
||||
AMS_SF_METHOD_INFO(C, H, 9, Result, Enable, (), ()) \
|
||||
AMS_SF_METHOD_INFO(C, H, 10, Result, Disable, (), ())
|
||||
|
||||
/* TODO: Deprecated interface? */
|
||||
|
||||
AMS_SF_DEFINE_INTERFACE(ams::usb::ds, IDsService, AMS_USB_I_DS_SERVICE_INTERFACE_INFO)
|
||||
|
||||
#define AMS_USB_I_DS_ROOT_SERVICE_INTERFACE_INFO(C, H) \
|
||||
AMS_SF_METHOD_INFO(C, H, 0, Result, GetService, (sf::Out<sf::SharedPointer<usb::ds::IDsService>> out), (out))
|
||||
|
||||
AMS_SF_DEFINE_INTERFACE(ams::usb::ds, IDsRootService, AMS_USB_I_DS_ROOT_SERVICE_INTERFACE_INFO)
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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 <vapours.hpp>
|
||||
#include <stratosphere/os/os_system_event.hpp>
|
||||
#include <stratosphere/sf/sf_lmem_utility.hpp>
|
||||
#include <stratosphere/usb/usb_limits.hpp>
|
||||
#include <stratosphere/usb/usb_device_types.hpp>
|
||||
#include <stratosphere/usb/ds/usb_i_ds_service.hpp>
|
||||
|
||||
namespace ams::usb {
|
||||
|
||||
class DsInterface;
|
||||
class DsEndpoint;
|
||||
|
||||
class DsClient {
|
||||
friend class DsInterface;
|
||||
friend class DsEndpoint;
|
||||
private:
|
||||
/* NOTE: Nintendo uses a UnitHeap here on newer firmware versions. */
|
||||
/* For now, we'll use an ExpHeap and do it the old way. */
|
||||
sf::ExpHeapAllocator m_allocator{};
|
||||
u8 m_heap_buffer[32_KB];
|
||||
lmem::HeapHandle m_heap_handle{};
|
||||
sf::SharedPointer<ds::IDsRootService> m_root_service{};
|
||||
sf::SharedPointer<ds::IDsService> m_ds_service{};
|
||||
bool m_is_initialized{false};
|
||||
std::atomic<int> m_reference_count{0};
|
||||
os::SystemEventType m_state_change_event{};
|
||||
DsInterface *m_interfaces[DsLimitMaxInterfacesPerConfigurationCount]{};
|
||||
bool m_is_enabled{false};
|
||||
public:
|
||||
DsClient() = default;
|
||||
~DsClient() { /* ... */ }
|
||||
public:
|
||||
Result Initialize(ComplexId complex_id);
|
||||
Result Finalize();
|
||||
|
||||
bool IsInitialized();
|
||||
|
||||
Result EnableDevice();
|
||||
Result DisableDevice();
|
||||
|
||||
os::SystemEventType *GetStateChangeEvent();
|
||||
Result GetState(UsbState *out);
|
||||
|
||||
Result ClearDeviceData();
|
||||
|
||||
Result AddUsbStringDescriptor(u8 *out_index, UsbStringDescriptor *desc);
|
||||
Result DeleteUsbStringDescriptor(u8 index);
|
||||
|
||||
Result SetUsbDeviceDescriptor(UsbDeviceDescriptor *desc, UsbDeviceSpeed speed);
|
||||
|
||||
Result SetBinaryObjectStore(u8 *data, int size);
|
||||
private:
|
||||
Result AddInterface(DsInterface *intf, sf::SharedPointer<ds::IDsInterface> *out_srv, uint8_t bInterfaceNumber);
|
||||
Result DeleteInterface(uint8_t bInterfaceNumber);
|
||||
};
|
||||
|
||||
class DsInterface {
|
||||
friend class DsEndpoint;
|
||||
private:
|
||||
DsClient *m_client;
|
||||
sf::SharedPointer<ds::IDsInterface> m_interface;
|
||||
bool m_is_initialized;
|
||||
std::atomic<int> m_reference_count;
|
||||
os::SystemEventType m_setup_event;
|
||||
os::SystemEventType m_ctrl_in_completion_event;
|
||||
os::SystemEventType m_ctrl_out_completion_event;
|
||||
UrbReport m_report;
|
||||
u8 m_interface_num;
|
||||
DsEndpoint *m_endpoints[UsbLimitMaxEndpointsCount];
|
||||
public:
|
||||
DsInterface() : m_client(nullptr), m_is_initialized(false), m_reference_count(0) { /* ... */ }
|
||||
~DsInterface() { /* ... */ }
|
||||
public:
|
||||
Result Initialize(DsClient *client, u8 bInterfaceNumber);
|
||||
Result Finalize();
|
||||
|
||||
Result AppendConfigurationData(UsbDeviceSpeed speed, void *data, u32 size);
|
||||
|
||||
bool IsInitialized();
|
||||
|
||||
os::SystemEventType *GetSetupEvent();
|
||||
Result GetSetupPacket(UsbCtrlRequest *out);
|
||||
|
||||
Result Enable();
|
||||
Result Disable();
|
||||
|
||||
Result CtrlRead(u32 *out_transferred, void *dst, u32 size);
|
||||
Result CtrlWrite(u32 *out_transferred, void *dst, u32 size);
|
||||
Result CtrlDone();
|
||||
Result CtrlStall();
|
||||
private:
|
||||
Result AddEndpoint(DsEndpoint *ep, u8 bEndpointAddress, sf::SharedPointer<ds::IDsEndpoint> *out);
|
||||
Result DeleteEndpoint(u8 bEndpointAddress);
|
||||
|
||||
Result CtrlIn(u32 *out_transferred, void *dst, u32 size);
|
||||
Result CtrlOut(u32 *out_transferred, void *dst, u32 size);
|
||||
};
|
||||
|
||||
class DsEndpoint {
|
||||
private:
|
||||
bool m_is_initialized;
|
||||
bool m_is_new_format;
|
||||
std::atomic<int> m_reference_count;
|
||||
DsInterface *m_interface;
|
||||
sf::SharedPointer<ds::IDsEndpoint> m_endpoint;
|
||||
u8 m_address;
|
||||
os::SystemEventType m_completion_event;
|
||||
os::SystemEventType m_unknown_event;
|
||||
public:
|
||||
DsEndpoint() : m_is_initialized(false), m_is_new_format(false), m_reference_count(0) { /* ... */ }
|
||||
public:
|
||||
Result Initialize(DsInterface *interface, u8 bEndpointAddress);
|
||||
Result Finalize();
|
||||
|
||||
bool IsInitialized();
|
||||
|
||||
Result PostBuffer(u32 *out_transferred, void *buf, u32 size);
|
||||
Result PostBufferAsync(u32 *out_urb_id, void *buf, u32 size);
|
||||
|
||||
os::SystemEventType *GetCompletionEvent();
|
||||
|
||||
Result GetUrbReport(UrbReport *out);
|
||||
|
||||
Result Cancel();
|
||||
|
||||
Result SetZeroLengthTransfer(bool zlt);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 <vapours.hpp>
|
||||
#include <stratosphere/usb/usb_limits.hpp>
|
||||
#include <stratosphere/usb/usb_types.hpp>
|
||||
|
||||
namespace ams::usb {
|
||||
|
||||
constexpr inline u8 InterfaceNumberAuto = DsLimitMaxInterfacesPerConfigurationCount;
|
||||
constexpr inline u8 EndpointAddressAutoIn = UsbEndpointAddressMask_DirDevicetoHost;
|
||||
constexpr inline u8 EndpointAddressAutoOut = UsbEndpointAddressMask_DirHostToDevice;
|
||||
|
||||
enum UrbStatus {
|
||||
UrbStatus_Invalid = 0,
|
||||
UrbStatus_Pending = 1,
|
||||
UrbStatus_Running = 2,
|
||||
UrbStatus_Finished = 3,
|
||||
UrbStatus_Cancelled = 4,
|
||||
UrbStatus_Failed = 5,
|
||||
};
|
||||
|
||||
struct UrbReport {
|
||||
struct Report {
|
||||
u32 id;
|
||||
u32 requested_size;
|
||||
u32 transferred_size;
|
||||
UrbStatus status;
|
||||
} reports[DsLimitRingSize];
|
||||
u32 count;
|
||||
};
|
||||
|
||||
enum DsString {
|
||||
DsString_Max = 0x20,
|
||||
};
|
||||
|
||||
struct DsVidPidBcd {
|
||||
uint16_t idVendor;
|
||||
uint16_t idProduct;
|
||||
uint16_t bcdDevice;
|
||||
|
||||
char manufacturer[DsString_Max];
|
||||
char product[DsString_Max];
|
||||
char serial_number[DsString_Max];
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 <vapours.hpp>
|
||||
#include <stratosphere/dd/dd_device_address_space_common.hpp>
|
||||
|
||||
namespace ams::usb {
|
||||
|
||||
constexpr inline int HwLimitDmaBufferAlignmentSize = dd::DeviceAddressSpaceMemoryRegionAlignment;
|
||||
constexpr inline int HwLimitDataCacheLineSize = 0x40;
|
||||
constexpr inline int HwLimitMaxPortCount = 0x4;
|
||||
|
||||
constexpr inline int UsbLimitMaxEndpointsCount = 0x20;
|
||||
constexpr inline int UsbLimitMaxEndpointPairCount = 0x10;
|
||||
|
||||
constexpr inline int DsLimitMaxConfigurationsPerDeviceCount = 1;
|
||||
constexpr inline int DsLimitMaxInterfacesPerConfigurationCount = 4;
|
||||
constexpr inline int DsLimitMaxNameSize = 0x40;
|
||||
constexpr inline int DsLimitRingSize = 8;
|
||||
|
||||
}
|
||||
223
libraries/libstratosphere/include/stratosphere/usb/usb_types.hpp
Normal file
223
libraries/libstratosphere/include/stratosphere/usb/usb_types.hpp
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* 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 <vapours.hpp>
|
||||
#include <stratosphere/usb/usb_limits.hpp>
|
||||
|
||||
namespace ams::usb {
|
||||
|
||||
constexpr ALWAYS_INLINE bool IsDmaAligned(u64 address) {
|
||||
return util::IsAligned(address, static_cast<u64>(HwLimitDmaBufferAlignmentSize));
|
||||
}
|
||||
|
||||
enum ComplexId {
|
||||
ComplexId_Tegra21x = 2,
|
||||
};
|
||||
|
||||
enum UsbDescriptorType {
|
||||
UsbDescriptorType_Device = 1,
|
||||
UsbDescriptorType_Config = 2,
|
||||
UsbDescriptorType_String = 3,
|
||||
UsbDescriptorType_Interface = 4,
|
||||
UsbDescriptorType_Endpoint = 5,
|
||||
UsbDescriptorType_DeviceQualifier = 6,
|
||||
UsbDescriptorType_OtherSpeedConfig = 7,
|
||||
UsbDescriptorType_InterfacePower = 8,
|
||||
UsbDescriptorType_Otg = 9,
|
||||
UsbDescriptorType_Debug = 10,
|
||||
UsbDescriptorType_InterfaceAssociation = 11,
|
||||
UsbDescriptorType_Bos = 15,
|
||||
UsbDescriptorType_DeviceCapability = 16,
|
||||
|
||||
UsbDescriptorType_Hid = 33,
|
||||
UsbDescriptorType_Report = 34,
|
||||
UsbDescriptorType_Physical = 35,
|
||||
|
||||
UsbDescriptorType_Hub = 41,
|
||||
|
||||
UsbDescriptorType_EndpointCompanion = 48,
|
||||
UsbDescriptorType_IsocEndpointCompanion = 49,
|
||||
};
|
||||
|
||||
struct UsbDescriptorHeader {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
} PACKED;
|
||||
|
||||
struct UsbInterfaceDescriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint8_t bInterfaceNumber;
|
||||
uint8_t bAlternateSetting;
|
||||
uint8_t bNumEndpoints;
|
||||
uint8_t bInterfaceClass;
|
||||
uint8_t bInterfaceSubClass;
|
||||
uint8_t bInterfaceProtocol;
|
||||
uint8_t iInterface;
|
||||
} PACKED;
|
||||
|
||||
struct UsbEndpointDescriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint8_t bEndpointAddress;
|
||||
uint8_t bmAttributes;
|
||||
uint16_t wMaxPacketSize;
|
||||
uint8_t bInterval;
|
||||
} PACKED;
|
||||
|
||||
struct UsbDeviceDescriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint16_t bcdUSB;
|
||||
uint8_t bDeviceClass;
|
||||
uint8_t bDeviceSubClass;
|
||||
uint8_t bDeviceProtocol;
|
||||
uint8_t bMaxPacketSize0;
|
||||
uint16_t idVendor;
|
||||
uint16_t idProduct;
|
||||
uint16_t bcdDevice;
|
||||
uint8_t iManufacturer;
|
||||
uint8_t iProduct;
|
||||
uint8_t iSerialNumber;
|
||||
uint8_t bNumConfigurations;
|
||||
} PACKED;
|
||||
|
||||
struct UsbConfigDescriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint16_t wTotalLength;
|
||||
uint8_t bNumInterfaces;
|
||||
uint8_t bConfigurationValue;
|
||||
uint8_t iConfiguration;
|
||||
uint8_t bmAttributes;
|
||||
uint8_t bMaxPower;
|
||||
} PACKED;
|
||||
|
||||
struct UsbEndpointCompanionDescriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint8_t bMaxBurst;
|
||||
uint8_t bmAttributes;
|
||||
uint16_t wBytesPerInterval;
|
||||
} PACKED;
|
||||
|
||||
struct UsbStringDescriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint16_t wData[DsLimitMaxNameSize];
|
||||
} PACKED;
|
||||
|
||||
struct UsbCtrlRequest {
|
||||
uint8_t bmRequestType;
|
||||
uint8_t bRequest;
|
||||
uint16_t wValue;
|
||||
uint16_t wIndex;
|
||||
uint16_t wLength;
|
||||
} PACKED;
|
||||
|
||||
enum UsbState {
|
||||
UsbState_Detached = 0,
|
||||
UsbState_Attached = 1,
|
||||
UsbState_Powered = 2,
|
||||
UsbState_Default = 3,
|
||||
UsbState_Address = 4,
|
||||
UsbState_Configured = 5,
|
||||
UsbState_Suspended = 6,
|
||||
};
|
||||
|
||||
enum UsbDescriptorSize {
|
||||
UsbDescriptorSize_Interface = sizeof(UsbInterfaceDescriptor),
|
||||
UsbDescriptorSize_Endpoint = sizeof(UsbEndpointDescriptor),
|
||||
UsbDescriptorSize_Device = sizeof(UsbDeviceDescriptor),
|
||||
UsbDescriptorSize_Config = sizeof(UsbConfigDescriptor),
|
||||
UsbDescriptorSize_EndpointCompanion = sizeof(UsbEndpointCompanionDescriptor),
|
||||
};
|
||||
|
||||
enum UsbDeviceSpeed {
|
||||
UsbDeviceSpeed_Invalid = 0,
|
||||
UsbDeviceSpeed_Low = 1,
|
||||
UsbDeviceSpeed_Full = 2,
|
||||
UsbDeviceSpeed_High = 3,
|
||||
UsbDeviceSpeed_Super = 4,
|
||||
UsbDeviceSpeed_SuperPlus = 5,
|
||||
};
|
||||
|
||||
|
||||
enum UsbEndpointAddressMask {
|
||||
UsbEndpointAddressMask_EndpointNumber = (0xF << 0),
|
||||
|
||||
UsbEndpointAddressMask_Dir = (0x1 << 7),
|
||||
|
||||
UsbEndpointAddressMask_DirHostToDevice = (0x0 << 7),
|
||||
UsbEndpointAddressMask_DirDevicetoHost = (0x1 << 7),
|
||||
};
|
||||
|
||||
enum UsbEndpointAttributeMask {
|
||||
UsbEndpointAttributeMask_XferType = (0x3 << 0),
|
||||
|
||||
UsbEndpointAttributeMask_XferTypeControl = (0x0 << 0),
|
||||
UsbEndpointAttributeMask_XferTypeIsoc = (0x1 << 0),
|
||||
UsbEndpointAttributeMask_XferTypeBulk = (0x2 << 0),
|
||||
UsbEndpointAttributeMask_XferTypeInt = (0x3 << 0),
|
||||
};
|
||||
|
||||
enum UsbEndpointDirection {
|
||||
UsbEndpointDirection_Invalid = 0,
|
||||
UsbEndpointDirection_ToDevice = 1,
|
||||
UsbEndpointDirection_ToHost = 2,
|
||||
UsbEndpointDirection_Control = 3,
|
||||
};
|
||||
|
||||
constexpr inline u8 UsbGetEndpointNumber(const UsbEndpointDescriptor *desc) {
|
||||
return desc->bEndpointAddress & UsbEndpointAddressMask_EndpointNumber;
|
||||
}
|
||||
|
||||
constexpr inline bool UsbEndpointIsHostToDevice(const UsbEndpointDescriptor *desc) {
|
||||
return (desc->bEndpointAddress & UsbEndpointAddressMask_Dir) == UsbEndpointAddressMask_DirHostToDevice;
|
||||
}
|
||||
|
||||
constexpr inline bool UsbEndpointIsDeviceToHost(const UsbEndpointDescriptor *desc) {
|
||||
return (desc->bEndpointAddress & UsbEndpointAddressMask_Dir) == UsbEndpointAddressMask_DirDevicetoHost;
|
||||
}
|
||||
|
||||
constexpr inline u8 UsbGetEndpointAddress(u8 number, UsbEndpointDirection dir) {
|
||||
u8 val = static_cast<u8>(number & UsbEndpointAddressMask_EndpointNumber);
|
||||
if (dir == UsbEndpointDirection_ToHost) {
|
||||
val |= UsbEndpointAddressMask_DirDevicetoHost;
|
||||
} else {
|
||||
val |= UsbEndpointAddressMask_DirHostToDevice;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
constexpr inline UsbEndpointDirection GetUsbEndpointDirection(const UsbEndpointDescriptor *desc) {
|
||||
if (UsbEndpointIsDeviceToHost(desc)) {
|
||||
return UsbEndpointDirection_ToHost;
|
||||
} else {
|
||||
return UsbEndpointDirection_ToDevice;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr inline bool UsbEndpointIsValid(const UsbEndpointDescriptor *desc) {
|
||||
return desc != nullptr && desc->bLength >= UsbDescriptorSize_Endpoint && desc->bEndpointAddress != 0;
|
||||
}
|
||||
|
||||
constexpr inline void UsbMarkEndpointInvalid(UsbEndpointDescriptor *desc) {
|
||||
desc->bLength = 0;
|
||||
desc->bEndpointAddress = 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,14 +40,12 @@ namespace ams::hid {
|
||||
|
||||
/* Helper. */
|
||||
void InitializeHid() {
|
||||
R_ABORT_UNLESS(smInitialize());
|
||||
ON_SCOPE_EXIT { smExit(); };
|
||||
{
|
||||
sm::DoWithSession([&]() {
|
||||
R_ABORT_UNLESS(hidInitialize());
|
||||
hidInitializeNpad();
|
||||
R_ABORT_UNLESS(hidSetSupportedNpadIdType(NpadIdTypes, NumNpadIdTypes));
|
||||
R_ABORT_UNLESS(hidSetSupportedNpadStyleSet(HidNpadStyleSet_NpadStandard | HidNpadStyleTag_NpadSystemExt));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Result EnsureHidInitialized() {
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace ams::ncm {
|
||||
MakeContentPathFunction make_content_path_func;
|
||||
bool disabled;
|
||||
protected:
|
||||
ContentStorageImplBase() { /* ... */ }
|
||||
ContentStorageImplBase() : make_content_path_func(), disabled(false) { /* ... */ }
|
||||
protected:
|
||||
/* Helpers. */
|
||||
Result EnsureEnabled() const {
|
||||
|
||||
@@ -46,9 +46,9 @@ namespace ams::psc {
|
||||
Result PmModule::Initialize(const PmModuleId mid, const PmModuleId *dependencies, u32 dependency_count, os::EventClearMode clear_mode) {
|
||||
R_UNLESS(!this->initialized, psc::ResultAlreadyInitialized());
|
||||
|
||||
static_assert(sizeof(*dependencies) == sizeof(u16));
|
||||
static_assert(sizeof(*dependencies) == sizeof(u32));
|
||||
::PscPmModule module;
|
||||
R_TRY(::pscmGetPmModule(std::addressof(module), static_cast<::PscPmModuleId>(mid), reinterpret_cast<const u16 *>(dependencies), dependency_count, clear_mode == os::EventClearMode_AutoClear));
|
||||
R_TRY(::pscmGetPmModule(std::addressof(module), static_cast<::PscPmModuleId>(mid), reinterpret_cast<const u32 *>(dependencies), dependency_count, clear_mode == os::EventClearMode_AutoClear));
|
||||
|
||||
this->intf = RemoteObjectFactory::CreateSharedEmplaced<psc::sf::IPmModule, RemotePmModule>(module);
|
||||
this->system_event.AttachReadableHandle(module.event.revent, false, clear_mode);
|
||||
|
||||
45
libraries/libstratosphere/source/usb/impl/usb_util.hpp
Normal file
45
libraries/libstratosphere/source/usb/impl/usb_util.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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::usb::impl {
|
||||
|
||||
constexpr int GetEndpointIndex(u8 address) {
|
||||
int idx = address & UsbEndpointAddressMask_EndpointNumber;
|
||||
if ((address & UsbEndpointAddressMask_Dir) == UsbEndpointAddressMask_DirDevicetoHost) {
|
||||
idx += 0x10;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
class ScopedRefCount {
|
||||
NON_COPYABLE(ScopedRefCount);
|
||||
NON_MOVEABLE(ScopedRefCount);
|
||||
private:
|
||||
T &m_obj;
|
||||
public:
|
||||
ALWAYS_INLINE ScopedRefCount(T &o) : m_obj(o) {
|
||||
++m_obj;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ~ScopedRefCount() {
|
||||
--m_obj;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
803
libraries/libstratosphere/source/usb/usb_device.cpp
Normal file
803
libraries/libstratosphere/source/usb/usb_device.cpp
Normal file
@@ -0,0 +1,803 @@
|
||||
/*
|
||||
* 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 "usb_remote_ds_root_service.hpp"
|
||||
#include "usb_remote_ds_service.hpp"
|
||||
#include "impl/usb_util.hpp"
|
||||
|
||||
namespace ams::usb {
|
||||
|
||||
Result DsClient::Initialize(ComplexId complex_id) {
|
||||
/* Clear interfaces. */
|
||||
for (size_t i = 0; i < util::size(m_interfaces); ++i) {
|
||||
m_interfaces[i] = nullptr;
|
||||
}
|
||||
|
||||
/* Initialize heap. */
|
||||
m_heap_handle = lmem::CreateExpHeap(m_heap_buffer, sizeof(m_heap_buffer), lmem::CreateOption_None);
|
||||
R_UNLESS(m_heap_handle != nullptr, usb::ResultMemAllocFailure());
|
||||
|
||||
/* Attach our allocator. */
|
||||
m_allocator.Attach(m_heap_handle);
|
||||
|
||||
/* Connect to usb:ds. */
|
||||
/* NOTE: Here, Nintendo does m_domain.InitializeByDomain<...>(...); m_domain.SetSessionCount(1); */
|
||||
{
|
||||
Service srv;
|
||||
R_TRY(sm::GetService(std::addressof(srv), sm::ServiceName::Encode("usb:ds")));
|
||||
|
||||
R_ABORT_UNLESS(serviceConvertToDomain(std::addressof(srv)));
|
||||
|
||||
using Allocator = decltype(m_allocator);
|
||||
using ObjectFactory = sf::ObjectFactory<Allocator::Policy>;
|
||||
|
||||
if (hos::GetVersion() >= hos::Version_11_0_0) {
|
||||
m_root_service = ObjectFactory::CreateSharedEmplaced<ds::IDsRootService, RemoteDsRootService>(std::addressof(m_allocator), srv, std::addressof(m_allocator));
|
||||
|
||||
R_TRY(m_root_service->GetService(std::addressof(m_ds_service)));
|
||||
} else {
|
||||
m_ds_service = ObjectFactory::CreateSharedEmplaced<ds::IDsService, RemoteDsService>(std::addressof(m_allocator), srv, std::addressof(m_allocator));
|
||||
}
|
||||
}
|
||||
|
||||
/* Bind the client process. */
|
||||
R_TRY(m_ds_service->Bind(complex_id, dd::GetCurrentProcessHandle()));
|
||||
|
||||
/* Get the state change event. */
|
||||
sf::CopyHandle event_handle;
|
||||
R_TRY(m_ds_service->GetStateChangeEvent(std::addressof(event_handle)));
|
||||
|
||||
/* Attach the state change event handle to our event. */
|
||||
os::AttachReadableHandleToSystemEvent(std::addressof(m_state_change_event), event_handle.GetValue(), true, os::EventClearMode_ManualClear);
|
||||
|
||||
/* Mark ourselves as initialized. */
|
||||
m_is_initialized = true;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DsClient::Finalize() {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Disable and finalize all interfaces. */
|
||||
R_TRY(this->DisableDevice());
|
||||
for (size_t i = 0; i < util::size(m_interfaces); ++i) {
|
||||
if (m_interfaces[i] != nullptr) {
|
||||
R_TRY(m_interfaces[i]->Finalize());
|
||||
}
|
||||
}
|
||||
|
||||
/* Check our reference count .*/
|
||||
R_UNLESS(m_reference_count <= 1, usb::ResultResourceBusy());
|
||||
|
||||
/* Finalize members. */
|
||||
m_is_initialized = false;
|
||||
os::DestroySystemEvent(std::addressof(m_state_change_event));
|
||||
lmem::DestroyExpHeap(m_heap_handle);
|
||||
m_heap_handle = nullptr;
|
||||
|
||||
/* Destroy interface objects. */
|
||||
m_ds_service = nullptr;
|
||||
m_root_service = nullptr;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
bool DsClient::IsInitialized() {
|
||||
return m_is_initialized;
|
||||
}
|
||||
|
||||
Result DsClient::EnableDevice() {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Enable all interfaces. */
|
||||
if (hos::GetVersion() < hos::Version_11_0_0) {
|
||||
for (size_t i = 0; i < util::size(m_interfaces); ++i) {
|
||||
if (m_interfaces[i] != nullptr) {
|
||||
R_TRY(m_interfaces[i]->Enable());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Enable the device. */
|
||||
R_TRY(m_ds_service->Enable());
|
||||
|
||||
/* Mark disabled. */
|
||||
m_is_enabled = true;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DsClient::DisableDevice() {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Disable the device. */
|
||||
R_TRY(m_ds_service->Disable());
|
||||
|
||||
/* Disable all interfaces. */
|
||||
if (hos::GetVersion() < hos::Version_11_0_0) {
|
||||
for (size_t i = 0; i < util::size(m_interfaces); ++i) {
|
||||
if (m_interfaces[i] != nullptr) {
|
||||
R_TRY(m_interfaces[i]->Disable());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Mark disabled. */
|
||||
m_is_enabled = false;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
os::SystemEventType *DsClient::GetStateChangeEvent() {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
return m_is_initialized ? std::addressof(m_state_change_event) : nullptr;
|
||||
}
|
||||
|
||||
Result DsClient::GetState(UsbState *out) {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Check that we have a service. */
|
||||
AMS_ABORT_UNLESS(m_ds_service != nullptr);
|
||||
|
||||
return m_ds_service->GetState(out);
|
||||
}
|
||||
|
||||
Result DsClient::ClearDeviceData() {
|
||||
return m_ds_service->ClearDeviceData();
|
||||
}
|
||||
|
||||
Result DsClient::AddUsbStringDescriptor(u8 *out_index, UsbStringDescriptor *desc) {
|
||||
return m_ds_service->AddUsbStringDescriptor(out_index, sf::InBuffer(reinterpret_cast<const u8 *>(desc), sizeof(*desc)));
|
||||
}
|
||||
|
||||
Result DsClient::DeleteUsbStringDescriptor(u8 index) {
|
||||
return m_ds_service->DeleteUsbStringDescriptor(index);
|
||||
}
|
||||
|
||||
Result DsClient::SetUsbDeviceDescriptor(UsbDeviceDescriptor *desc, UsbDeviceSpeed speed) {
|
||||
return m_ds_service->SetUsbDeviceDescriptor(sf::InBuffer(reinterpret_cast<const u8 *>(desc), sizeof(*desc)), speed);
|
||||
}
|
||||
|
||||
Result DsClient::SetBinaryObjectStore(u8 *data, int size) {
|
||||
return m_ds_service->SetBinaryObjectStore(sf::InBuffer(reinterpret_cast<const u8 *>(data), size));
|
||||
}
|
||||
|
||||
Result DsClient::AddInterface(DsInterface *intf, sf::SharedPointer<ds::IDsInterface> *out_srv, uint8_t bInterfaceNumber) {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Check that we have a service. */
|
||||
AMS_ABORT_UNLESS(m_ds_service != nullptr);
|
||||
|
||||
/* Register the interface. */
|
||||
R_TRY(m_ds_service->RegisterInterface(out_srv, bInterfaceNumber));
|
||||
|
||||
/* Set interface. */
|
||||
m_interfaces[bInterfaceNumber] = intf;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DsClient::DeleteInterface(uint8_t bInterfaceNumber) {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Check that we have the interface. */
|
||||
R_UNLESS(m_interfaces[bInterfaceNumber] != nullptr, usb::ResultOperationDenied());
|
||||
|
||||
/* Clear the interface. */
|
||||
m_interfaces[bInterfaceNumber] = nullptr;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DsInterface::Initialize(DsClient *client, u8 bInterfaceNumber) {
|
||||
/* Check that we haven't already initialized. */
|
||||
R_UNLESS(!m_is_initialized, usb::ResultAlreadyInitialized());
|
||||
|
||||
/* Set our client. */
|
||||
m_client = client;
|
||||
|
||||
/* Clear all endpoints. */
|
||||
for (size_t i = 0; i < util::size(m_endpoints); ++i) {
|
||||
m_endpoints[i] = nullptr;
|
||||
}
|
||||
|
||||
/* Set our interface number. */
|
||||
R_UNLESS(bInterfaceNumber < util::size(m_client->m_interfaces), usb::ResultInvalidParameter());
|
||||
m_interface_num = bInterfaceNumber;
|
||||
|
||||
/* Add the interface. */
|
||||
R_TRY(m_client->AddInterface(this, std::addressof(m_interface), m_interface_num));
|
||||
|
||||
/* Ensure we cleanup if we fail after this. */
|
||||
auto intf_guard = SCOPE_GUARD { m_client->DeleteInterface(m_interface_num); m_interface = nullptr; };
|
||||
|
||||
/* Get events. */
|
||||
sf::CopyHandle setup_event_handle;
|
||||
sf::CopyHandle ctrl_in_event_handle;
|
||||
sf::CopyHandle ctrl_out_event_handle;
|
||||
R_TRY(m_interface->GetSetupEvent(std::addressof(setup_event_handle)));
|
||||
R_TRY(m_interface->GetCtrlInCompletionEvent(std::addressof(ctrl_in_event_handle)));
|
||||
R_TRY(m_interface->GetCtrlOutCompletionEvent(std::addressof(ctrl_out_event_handle)));
|
||||
|
||||
/* Attach events. */
|
||||
os::AttachReadableHandleToSystemEvent(std::addressof(m_setup_event), setup_event_handle.GetValue(), true, os::EventClearMode_ManualClear);
|
||||
os::AttachReadableHandleToSystemEvent(std::addressof(m_ctrl_in_completion_event), ctrl_in_event_handle.GetValue(), true, os::EventClearMode_ManualClear);
|
||||
os::AttachReadableHandleToSystemEvent(std::addressof(m_ctrl_out_completion_event), ctrl_out_event_handle.GetValue(), true, os::EventClearMode_ManualClear);
|
||||
|
||||
/* Increment our client's reference count. */
|
||||
++m_client->m_reference_count;
|
||||
|
||||
/* Set ourselves as initialized. */
|
||||
m_is_initialized = true;
|
||||
|
||||
intf_guard.Cancel();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DsInterface::Finalize() {
|
||||
/* Validate that we have a service. */
|
||||
R_ABORT_UNLESS(m_interface != nullptr);
|
||||
|
||||
/* We must be disabled. */
|
||||
R_UNLESS(!m_client->m_is_enabled, usb::ResultResourceBusy());
|
||||
|
||||
/* Finalize all endpoints. */
|
||||
for (size_t i = 0; i < util::size(m_endpoints); ++i) {
|
||||
if (m_endpoints[i] != nullptr) {
|
||||
R_TRY(m_endpoints[i]->Finalize());
|
||||
}
|
||||
}
|
||||
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Check our reference count .*/
|
||||
R_UNLESS(m_reference_count <= 1, usb::ResultResourceBusy());
|
||||
|
||||
/* Finalize members. */
|
||||
m_is_initialized = false;
|
||||
os::DestroySystemEvent(std::addressof(m_setup_event));
|
||||
os::DestroySystemEvent(std::addressof(m_ctrl_in_completion_event));
|
||||
os::DestroySystemEvent(std::addressof(m_ctrl_out_completion_event));
|
||||
|
||||
/* Delete ourselves from our cleint. */
|
||||
m_client->DeleteInterface(m_interface_num);
|
||||
|
||||
/* Destroy our service. */
|
||||
m_interface = nullptr;
|
||||
|
||||
/* Close our reference to our client. */
|
||||
--m_client->m_reference_count;
|
||||
m_client = nullptr;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DsInterface::AppendConfigurationData(UsbDeviceSpeed speed, void *data, u32 size) {
|
||||
return m_interface->AppendConfigurationData(m_interface_num, speed, sf::InBuffer(data, size));
|
||||
}
|
||||
|
||||
bool DsInterface::IsInitialized() {
|
||||
return m_is_initialized;
|
||||
}
|
||||
|
||||
os::SystemEventType *DsInterface::GetSetupEvent() {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
return m_is_initialized ? std::addressof(m_setup_event) : nullptr;
|
||||
}
|
||||
|
||||
Result DsInterface::GetSetupPacket(UsbCtrlRequest *out) {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Check that we have a service. */
|
||||
AMS_ABORT_UNLESS(m_interface != nullptr);
|
||||
|
||||
return m_interface->GetSetupPacket(sf::OutBuffer(out, sizeof(*out)));
|
||||
}
|
||||
|
||||
Result DsInterface::Enable() {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* If we're already enabled, nothing to do. */
|
||||
R_SUCCEED_IF(m_client->m_is_enabled);
|
||||
|
||||
/* Check that we have a service. */
|
||||
AMS_ABORT_UNLESS(m_interface != nullptr);
|
||||
|
||||
/* Perform the enable. */
|
||||
R_TRY(m_interface->Enable());
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DsInterface::Disable() {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* If we're already disabled, nothing to do. */
|
||||
R_SUCCEED_IF(!m_client->m_is_enabled);
|
||||
|
||||
/* Check that we have a service. */
|
||||
AMS_ABORT_UNLESS(m_interface != nullptr);
|
||||
|
||||
/* Perform the disable. */
|
||||
R_TRY(m_interface->Disable());
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DsInterface::AddEndpoint(DsEndpoint *ep, u8 bEndpointAddress, sf::SharedPointer<ds::IDsEndpoint> *out) {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Check that we're not already enabled. */
|
||||
R_UNLESS(!m_client->m_is_enabled, usb::ResultOperationDenied());
|
||||
|
||||
/* Check that we have a service. */
|
||||
AMS_ABORT_UNLESS(m_interface != nullptr);
|
||||
|
||||
/* Register the endpoint. */
|
||||
R_TRY(m_interface->RegisterEndpoint(bEndpointAddress, out));
|
||||
|
||||
/* Set the endpoint. */
|
||||
m_endpoints[impl::GetEndpointIndex(bEndpointAddress)] = ep;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DsInterface::DeleteEndpoint(u8 bEndpointAddress) {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Check that we're disabled and have the endpoint. */
|
||||
const auto index = impl::GetEndpointIndex(bEndpointAddress);
|
||||
R_UNLESS(!m_client->m_is_enabled, usb::ResultOperationDenied());
|
||||
R_UNLESS(m_endpoints[index] != nullptr, usb::ResultOperationDenied());
|
||||
|
||||
/* Clear the endpoint. */
|
||||
m_endpoints[index] = nullptr;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DsInterface::CtrlIn(u32 *out_transferred, void *dst, u32 size) {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Check that we're enabled. */
|
||||
R_UNLESS(m_client->m_is_enabled, usb::ResultOperationDenied());
|
||||
|
||||
/* Check that the data is aligned. */
|
||||
R_UNLESS(usb::IsDmaAligned(reinterpret_cast<u64>(dst)), usb::ResultAlignmentError());
|
||||
|
||||
/* If we should, flush cache. */
|
||||
if (size != 0) {
|
||||
dd::FlushDataCache(dst, size);
|
||||
}
|
||||
|
||||
/* Check that we have a service. */
|
||||
AMS_ABORT_UNLESS(m_interface != nullptr);
|
||||
|
||||
/* Perform the transfer. */
|
||||
u32 urb_id;
|
||||
R_TRY(m_interface->CtrlInAsync(std::addressof(urb_id), reinterpret_cast<u64>(dst), size));
|
||||
|
||||
/* Wait for control to finish. */
|
||||
os::WaitSystemEvent(std::addressof(m_ctrl_in_completion_event));
|
||||
os::ClearSystemEvent(std::addressof(m_ctrl_in_completion_event));
|
||||
|
||||
/* Get the urb report. */
|
||||
R_ABORT_UNLESS(m_interface->GetCtrlInUrbReport(std::addressof(m_report)));
|
||||
|
||||
/* Check the report is for our urb. */
|
||||
R_UNLESS(m_report.count == 1, usb::ResultInternalStateError());
|
||||
R_UNLESS(m_report.reports[0].id == urb_id, usb::ResultInternalStateError());
|
||||
|
||||
/* Set output bytes. */
|
||||
if (out_transferred != nullptr) {
|
||||
*out_transferred = m_report.reports[0].transferred_size;
|
||||
}
|
||||
|
||||
/* Handle the report. */
|
||||
switch (m_report.reports[0].status) {
|
||||
case UrbStatus_Cancelled:
|
||||
return usb::ResultInterrupted();
|
||||
case UrbStatus_Failed:
|
||||
return usb::ResultTransactionError();
|
||||
case UrbStatus_Finished:
|
||||
return ResultSuccess();
|
||||
default:
|
||||
return usb::ResultInternalStateError();
|
||||
}
|
||||
}
|
||||
|
||||
Result DsInterface::CtrlOut(u32 *out_transferred, void *dst, u32 size) {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Check that we're enabled. */
|
||||
R_UNLESS(m_client->m_is_enabled, usb::ResultOperationDenied());
|
||||
|
||||
/* Check that the data is aligned. */
|
||||
R_UNLESS(usb::IsDmaAligned(reinterpret_cast<u64>(dst)), usb::ResultAlignmentError());
|
||||
|
||||
/* If we should, invalidate cache. */
|
||||
if (size != 0) {
|
||||
dd::InvalidateDataCache(dst, size);
|
||||
}
|
||||
|
||||
/* Check that we have a service. */
|
||||
AMS_ABORT_UNLESS(m_interface != nullptr);
|
||||
|
||||
/* Perform the transfer. */
|
||||
u32 urb_id;
|
||||
R_TRY(m_interface->CtrlOutAsync(std::addressof(urb_id), reinterpret_cast<u64>(dst), size));
|
||||
|
||||
/* Wait for control to finish. */
|
||||
os::WaitSystemEvent(std::addressof(m_ctrl_out_completion_event));
|
||||
os::ClearSystemEvent(std::addressof(m_ctrl_out_completion_event));
|
||||
|
||||
/* Ensure that cache remains consistent. */
|
||||
ON_SCOPE_EXIT {
|
||||
if (size != 0) {
|
||||
dd::InvalidateDataCache(dst, size);
|
||||
}
|
||||
};
|
||||
|
||||
/* Get the urb report. */
|
||||
R_ABORT_UNLESS(m_interface->GetCtrlOutUrbReport(std::addressof(m_report)));
|
||||
|
||||
/* Check the report is for our urb. */
|
||||
R_UNLESS(m_report.count == 1, usb::ResultInternalStateError());
|
||||
R_UNLESS(m_report.reports[0].id == urb_id, usb::ResultInternalStateError());
|
||||
|
||||
/* Set output bytes. */
|
||||
if (out_transferred != nullptr) {
|
||||
*out_transferred = m_report.reports[0].transferred_size;
|
||||
}
|
||||
|
||||
/* Handle the report. */
|
||||
switch (m_report.reports[0].status) {
|
||||
case UrbStatus_Cancelled:
|
||||
return usb::ResultInterrupted();
|
||||
case UrbStatus_Failed:
|
||||
return usb::ResultTransactionError();
|
||||
case UrbStatus_Finished:
|
||||
return ResultSuccess();
|
||||
default:
|
||||
return usb::ResultInternalStateError();
|
||||
}
|
||||
}
|
||||
|
||||
Result DsInterface::CtrlRead(u32 *out_transferred, void *dst, u32 size) {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Do the data transfer. */
|
||||
Result result = this->CtrlOut(out_transferred, dst, size);
|
||||
|
||||
/* Do the status transfer. */
|
||||
if (R_SUCCEEDED(result)) {
|
||||
result = this->CtrlIn(nullptr, nullptr, 0);
|
||||
}
|
||||
|
||||
/* If we failed, stall. */
|
||||
if (R_FAILED(result)) {
|
||||
result = this->CtrlStall();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Result DsInterface::CtrlWrite(u32 *out_transferred, void *dst, u32 size) {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Do the data transfer. */
|
||||
Result result = this->CtrlIn(out_transferred, dst, size);
|
||||
|
||||
/* Do the status transfer. */
|
||||
if (R_SUCCEEDED(result)) {
|
||||
result = this->CtrlOut(nullptr, nullptr, 0);
|
||||
}
|
||||
|
||||
/* If we failed, stall. */
|
||||
if (R_FAILED(result)) {
|
||||
result = this->CtrlStall();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Result DsInterface::CtrlDone() {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Do the status transfer. */
|
||||
Result result = this->CtrlIn(nullptr, nullptr, 0);
|
||||
|
||||
/* If we failed, stall. */
|
||||
if (R_FAILED(result)) {
|
||||
result = this->CtrlStall();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Result DsInterface::CtrlStall() {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Check that we're enabled. */
|
||||
R_UNLESS(m_client->m_is_enabled, usb::ResultOperationDenied());
|
||||
|
||||
/* Check that we have a service. */
|
||||
AMS_ABORT_UNLESS(m_interface != nullptr);
|
||||
|
||||
return m_interface->CtrlStall();
|
||||
}
|
||||
|
||||
Result DsEndpoint::Initialize(DsInterface *interface, u8 bEndpointAddress) {
|
||||
/* Check that the interface is valid. */
|
||||
AMS_ABORT_UNLESS(interface != nullptr);
|
||||
|
||||
/* Check that we're not already initialized. */
|
||||
R_UNLESS(!m_is_initialized, usb::ResultAlreadyInitialized());
|
||||
|
||||
/* Set our interface. */
|
||||
m_interface = interface;
|
||||
|
||||
/* Add the endpoint. */
|
||||
R_TRY(m_interface->AddEndpoint(this, bEndpointAddress, std::addressof(m_endpoint)));
|
||||
|
||||
/* Set our address. */
|
||||
m_address = bEndpointAddress;
|
||||
|
||||
/* Ensure we clean up if we fail after this. */
|
||||
auto ep_guard = SCOPE_GUARD { m_interface->DeleteEndpoint(m_address); m_endpoint = nullptr; };
|
||||
|
||||
/* Get completion event. */
|
||||
sf::CopyHandle event_handle;
|
||||
R_TRY(m_endpoint->GetCompletionEvent(std::addressof(event_handle)));
|
||||
|
||||
/* Increment our interface's reference count. */
|
||||
++m_interface->m_reference_count;
|
||||
++m_interface->m_client->m_reference_count;
|
||||
|
||||
/* Attach our event. */
|
||||
os::AttachReadableHandleToSystemEvent(std::addressof(m_completion_event), event_handle, true, os::EventClearMode_ManualClear);
|
||||
|
||||
/* Mark initialized. */
|
||||
m_is_initialized = true;
|
||||
|
||||
ep_guard.Cancel();
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result DsEndpoint::Finalize() {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Cancel any pending transactions. */
|
||||
m_endpoint->Cancel();
|
||||
|
||||
/* Wait for us to be at one reference count. */
|
||||
while (m_reference_count > 1) {
|
||||
os::SleepThread(TimeSpan::FromMilliSeconds(25));
|
||||
}
|
||||
|
||||
/* Destroy our event. */
|
||||
os::DestroySystemEvent(std::addressof(m_completion_event));
|
||||
|
||||
/* Decrement our interface's reference count. */
|
||||
--m_interface->m_reference_count;
|
||||
--m_interface->m_client->m_reference_count;
|
||||
|
||||
/* Delete ourselves. */
|
||||
R_TRY(m_interface->DeleteEndpoint(m_address));
|
||||
|
||||
/* Clear ourselves. */
|
||||
m_interface = nullptr;
|
||||
m_endpoint = nullptr;
|
||||
|
||||
/* Mark uninitialized. */
|
||||
m_is_initialized = false;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
bool DsEndpoint::IsInitialized() {
|
||||
return m_is_initialized;
|
||||
}
|
||||
|
||||
Result DsEndpoint::PostBuffer(u32 *out_transferred, void *buf, u32 size) {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Post buffer. */
|
||||
u32 urb_id;
|
||||
R_TRY(this->PostBufferAsync(std::addressof(urb_id), buf, size));
|
||||
|
||||
/* Wait for completion. */
|
||||
os::WaitSystemEvent(std::addressof(m_completion_event));
|
||||
os::ClearSystemEvent(std::addressof(m_completion_event));
|
||||
|
||||
/* Get URB report. */
|
||||
UrbReport report;
|
||||
AMS_ABORT_UNLESS(m_endpoint != nullptr);
|
||||
R_ABORT_UNLESS(m_endpoint->GetUrbReport(std::addressof(report)));
|
||||
|
||||
/* Check the report is for our urb. */
|
||||
R_UNLESS(report.count == 1, usb::ResultInternalStateError());
|
||||
R_UNLESS(report.reports[0].id == urb_id, usb::ResultInternalStateError());
|
||||
|
||||
/* Set output bytes. */
|
||||
if (out_transferred != nullptr) {
|
||||
*out_transferred = report.reports[0].transferred_size;
|
||||
}
|
||||
|
||||
/* Handle the report. */
|
||||
switch (report.reports[0].status) {
|
||||
case UrbStatus_Cancelled:
|
||||
return usb::ResultInterrupted();
|
||||
case UrbStatus_Failed:
|
||||
return usb::ResultTransactionError();
|
||||
case UrbStatus_Finished:
|
||||
return ResultSuccess();
|
||||
default:
|
||||
return usb::ResultInternalStateError();
|
||||
}
|
||||
}
|
||||
|
||||
Result DsEndpoint::PostBufferAsync(u32 *out_urb_id, void *buf, u32 size) {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Check that the buffer is DMA aligned. */
|
||||
R_UNLESS(usb::IsDmaAligned(reinterpret_cast<u64>(buf)), usb::ResultAlignmentError());
|
||||
|
||||
/* Check that we have a service. */
|
||||
AMS_ABORT_UNLESS(m_endpoint != nullptr);
|
||||
|
||||
/* Post */
|
||||
u32 urb_id = 0;
|
||||
R_TRY(m_endpoint->PostBufferAsync(std::addressof(urb_id), reinterpret_cast<u64>(buf), size));
|
||||
|
||||
*out_urb_id = urb_id;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
os::SystemEventType *DsEndpoint::GetCompletionEvent() {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
return m_is_initialized ? std::addressof(m_completion_event) : nullptr;
|
||||
}
|
||||
|
||||
Result DsEndpoint::GetUrbReport(UrbReport *out) {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Check that we have a service. */
|
||||
AMS_ABORT_UNLESS(m_endpoint != nullptr);
|
||||
|
||||
return m_endpoint->GetUrbReport(out);
|
||||
}
|
||||
|
||||
Result DsEndpoint::Cancel() {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Check that we have a service. */
|
||||
AMS_ABORT_UNLESS(m_endpoint != nullptr);
|
||||
|
||||
return m_endpoint->Cancel();
|
||||
}
|
||||
|
||||
Result DsEndpoint::SetZeroLengthTransfer(bool zlt) {
|
||||
/* Create a scoped reference. */
|
||||
impl::ScopedRefCount scoped_ref(m_reference_count);
|
||||
|
||||
/* Check that we're initialized. */
|
||||
R_UNLESS(m_is_initialized, usb::ResultNotInitialized());
|
||||
|
||||
/* Check that we have a service. */
|
||||
AMS_ABORT_UNLESS(m_endpoint != nullptr);
|
||||
|
||||
return m_endpoint->SetZlt(zlt);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 "usb_remote_ds_endpoint.hpp"
|
||||
|
||||
namespace ams::usb {
|
||||
|
||||
Result RemoteDsEndpoint::PostBufferAsync(sf::Out<u32> out_urb_id, u64 address, u32 size) {
|
||||
const struct {
|
||||
u32 size;
|
||||
u64 address;
|
||||
} in = { size, address };
|
||||
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatchInOut(std::addressof(m_srv), 0, in, *out_urb_id);
|
||||
}
|
||||
|
||||
Result RemoteDsEndpoint::Cancel() {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatch(std::addressof(m_srv), 1);
|
||||
}
|
||||
|
||||
Result RemoteDsEndpoint::GetCompletionEvent(sf::OutCopyHandle out) {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatch(std::addressof(m_srv), 2,
|
||||
.out_handle_attrs = { SfOutHandleAttr_HipcCopy },
|
||||
.out_handles = out.GetHandlePointer(),
|
||||
);
|
||||
}
|
||||
|
||||
Result RemoteDsEndpoint::GetUrbReport(sf::Out<usb::UrbReport> out) {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatchOut(std::addressof(m_srv), 3, *out);
|
||||
}
|
||||
|
||||
Result RemoteDsEndpoint::Stall() {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatch(std::addressof(m_srv), 4);
|
||||
}
|
||||
|
||||
Result RemoteDsEndpoint::SetZlt(bool zlt) {
|
||||
const u8 in = zlt ? 1 : 0;
|
||||
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatchIn(std::addressof(m_srv), 5, in);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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::usb {
|
||||
|
||||
class RemoteDsEndpoint {
|
||||
private:
|
||||
Service m_srv;
|
||||
public:
|
||||
RemoteDsEndpoint(Service &srv) : m_srv(srv) { /* ... */ }
|
||||
virtual ~RemoteDsEndpoint() { serviceClose(std::addressof(m_srv)); }
|
||||
public:
|
||||
Result PostBufferAsync(sf::Out<u32> out_urb_id, u64 address, u32 size);
|
||||
Result Cancel();
|
||||
Result GetCompletionEvent(sf::OutCopyHandle out);
|
||||
Result GetUrbReport(sf::Out<usb::UrbReport> out);
|
||||
Result Stall();
|
||||
Result SetZlt(bool zlt);
|
||||
};
|
||||
static_assert(ds::IsIDsEndpoint<RemoteDsEndpoint>);
|
||||
|
||||
}
|
||||
139
libraries/libstratosphere/source/usb/usb_remote_ds_interface.cpp
Normal file
139
libraries/libstratosphere/source/usb/usb_remote_ds_interface.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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 "usb_remote_ds_interface.hpp"
|
||||
#include "usb_remote_ds_endpoint.hpp"
|
||||
|
||||
namespace ams::usb {
|
||||
|
||||
Result RemoteDsInterface::RegisterEndpoint(u8 endpoint_address, sf::Out<sf::SharedPointer<usb::ds::IDsEndpoint>> out) {
|
||||
Service srv;
|
||||
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
R_TRY(serviceDispatchIn(std::addressof(m_srv), 0, endpoint_address,
|
||||
.out_num_objects = 1,
|
||||
.out_objects = std::addressof(srv),
|
||||
));
|
||||
|
||||
*out = ObjectFactory::CreateSharedEmplaced<ds::IDsEndpoint, RemoteDsEndpoint>(m_allocator, srv);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RemoteDsInterface::GetSetupEvent(sf::OutCopyHandle out) {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatch(std::addressof(m_srv), 1,
|
||||
.out_handle_attrs = { SfOutHandleAttr_HipcCopy },
|
||||
.out_handles = out.GetHandlePointer(),
|
||||
);
|
||||
}
|
||||
|
||||
Result RemoteDsInterface::GetSetupPacket(const sf::OutBuffer &out) {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatch(std::addressof(m_srv), 2,
|
||||
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
|
||||
.buffers = { { out.GetPointer(), out.GetSize() } },
|
||||
);
|
||||
}
|
||||
|
||||
Result RemoteDsInterface::CtrlInAsync(sf::Out<u32> out_urb_id, u64 address, u32 size) {
|
||||
const struct {
|
||||
u32 size;
|
||||
u64 address;
|
||||
} in = { size, address };
|
||||
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatchInOut(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 3 : 5, in, *out_urb_id);
|
||||
}
|
||||
|
||||
Result RemoteDsInterface::CtrlOutAsync(sf::Out<u32> out_urb_id, u64 address, u32 size) {
|
||||
const struct {
|
||||
u32 size;
|
||||
u64 address;
|
||||
} in = { size, address };
|
||||
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatchInOut(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 4 : 6, in, *out_urb_id);
|
||||
}
|
||||
|
||||
Result RemoteDsInterface::GetCtrlInCompletionEvent(sf::OutCopyHandle out) {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatch(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 5 : 7,
|
||||
.out_handle_attrs = { SfOutHandleAttr_HipcCopy },
|
||||
.out_handles = out.GetHandlePointer(),
|
||||
);
|
||||
}
|
||||
|
||||
Result RemoteDsInterface::GetCtrlInUrbReport(sf::Out<usb::UrbReport> out) {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatchOut(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 6 : 8, *out);
|
||||
}
|
||||
|
||||
Result RemoteDsInterface::GetCtrlOutCompletionEvent(sf::OutCopyHandle out) {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatch(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 7 : 9,
|
||||
.out_handle_attrs = { SfOutHandleAttr_HipcCopy },
|
||||
.out_handles = out.GetHandlePointer(),
|
||||
);
|
||||
}
|
||||
|
||||
Result RemoteDsInterface::GetCtrlOutUrbReport(sf::Out<usb::UrbReport> out) {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatchOut(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 8 : 10, *out);
|
||||
}
|
||||
|
||||
Result RemoteDsInterface::CtrlStall() {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatch(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 9 : 11);
|
||||
}
|
||||
|
||||
Result RemoteDsInterface::AppendConfigurationData(u8 bInterfaceNumber, usb::UsbDeviceSpeed device_speed, const sf::InBuffer &data) {
|
||||
if (hos::GetVersion() >= hos::Version_11_0_0) {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatchIn(std::addressof(m_srv), 10, device_speed,
|
||||
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In },
|
||||
.buffers = { { data.GetPointer(), data.GetSize() } },
|
||||
);
|
||||
} else {
|
||||
const struct {
|
||||
u8 bInterfaceNumber;
|
||||
usb::UsbDeviceSpeed device_speed;
|
||||
} in = { bInterfaceNumber, device_speed };
|
||||
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatchIn(std::addressof(m_srv), 12, in,
|
||||
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In },
|
||||
.buffers = { { data.GetPointer(), data.GetSize() } },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Result RemoteDsInterface::Enable() {
|
||||
R_SUCCEED_IF(hos::GetVersion() >= hos::Version_11_0_0);
|
||||
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatch(std::addressof(m_srv), 3);
|
||||
}
|
||||
|
||||
Result RemoteDsInterface::Disable() {
|
||||
R_SUCCEED_IF(hos::GetVersion() >= hos::Version_11_0_0);
|
||||
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatch(std::addressof(m_srv), 3);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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::usb {
|
||||
|
||||
class RemoteDsInterface {
|
||||
private:
|
||||
using Allocator = sf::ExpHeapAllocator;
|
||||
using ObjectFactory = sf::ObjectFactory<Allocator::Policy>;
|
||||
private:
|
||||
Service m_srv;
|
||||
Allocator *m_allocator;
|
||||
public:
|
||||
RemoteDsInterface(Service &srv, sf::ExpHeapAllocator *allocator) : m_srv(srv), m_allocator(allocator) { /* ... */ }
|
||||
virtual ~RemoteDsInterface() { serviceClose(std::addressof(m_srv)); }
|
||||
public:
|
||||
Result RegisterEndpoint(u8 endpoint_address, sf::Out<sf::SharedPointer<usb::ds::IDsEndpoint>> out);
|
||||
Result GetSetupEvent(sf::OutCopyHandle out);
|
||||
Result GetSetupPacket(const sf::OutBuffer & out);
|
||||
Result CtrlInAsync(sf::Out<u32> out_urb_id, u64 address, u32 size);
|
||||
Result CtrlOutAsync(sf::Out<u32> out_urb_id, u64 address, u32 size);
|
||||
Result GetCtrlInCompletionEvent(sf::OutCopyHandle out);
|
||||
Result GetCtrlInUrbReport(sf::Out<usb::UrbReport> out);
|
||||
Result GetCtrlOutCompletionEvent(sf::OutCopyHandle out);
|
||||
Result GetCtrlOutUrbReport(sf::Out<usb::UrbReport> out);
|
||||
Result CtrlStall();
|
||||
Result AppendConfigurationData(u8 bInterfaceNumber, usb::UsbDeviceSpeed device_speed, const sf::InBuffer &data);
|
||||
Result Enable();
|
||||
Result Disable();
|
||||
};
|
||||
static_assert(ds::IsIDsInterface<RemoteDsInterface>);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 "usb_remote_ds_root_service.hpp"
|
||||
#include "usb_remote_ds_service.hpp"
|
||||
|
||||
namespace ams::usb {
|
||||
|
||||
Result RemoteDsRootService::GetService(sf::Out<sf::SharedPointer<usb::ds::IDsService>> out) {
|
||||
Service srv;
|
||||
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
R_TRY(serviceDispatch(std::addressof(m_srv), 0, .out_num_objects = 1, .out_objects = std::addressof(srv)));
|
||||
|
||||
*out = ObjectFactory::CreateSharedEmplaced<ds::IDsService, RemoteDsService>(m_allocator, srv, m_allocator);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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::usb {
|
||||
|
||||
class RemoteDsRootService {
|
||||
private:
|
||||
using Allocator = sf::ExpHeapAllocator;
|
||||
using ObjectFactory = sf::ObjectFactory<Allocator::Policy>;
|
||||
private:
|
||||
Service m_srv;
|
||||
Allocator *m_allocator;
|
||||
public:
|
||||
RemoteDsRootService(Service &srv, sf::ExpHeapAllocator *allocator) : m_srv(srv), m_allocator(allocator) { /* ... */ }
|
||||
virtual ~RemoteDsRootService() { serviceClose(std::addressof(m_srv)); }
|
||||
public:
|
||||
Result GetService(sf::Out<sf::SharedPointer<usb::ds::IDsService>> out);
|
||||
};
|
||||
static_assert(ds::IsIDsRootService<RemoteDsRootService>);
|
||||
|
||||
}
|
||||
114
libraries/libstratosphere/source/usb/usb_remote_ds_service.cpp
Normal file
114
libraries/libstratosphere/source/usb/usb_remote_ds_service.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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 "usb_remote_ds_service.hpp"
|
||||
#include "usb_remote_ds_interface.hpp"
|
||||
|
||||
namespace ams::usb {
|
||||
|
||||
Result RemoteDsService::Bind(usb::ComplexId complex_id, sf::CopyHandle process_h) {
|
||||
if (hos::GetVersion() >= hos::Version_11_0_0) {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
R_TRY(serviceDispatchIn(std::addressof(m_srv), 0, complex_id,
|
||||
.in_num_handles = 1,
|
||||
.in_handles = { process_h.GetValue() }
|
||||
));
|
||||
} else {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
R_TRY(serviceDispatchIn(std::addressof(m_srv), 0, complex_id));
|
||||
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
R_TRY(serviceDispatch(std::addressof(m_srv), 1,
|
||||
.in_num_handles = 1,
|
||||
.in_handles = { process_h.GetValue() })
|
||||
);
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RemoteDsService::RegisterInterface(sf::Out<sf::SharedPointer<usb::ds::IDsInterface>> out, u8 bInterfaceNumber) {
|
||||
Service srv;
|
||||
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
R_TRY(serviceDispatchIn(std::addressof(m_srv), (hos::GetVersion() >= hos::Version_11_0_0 ? 1 : 2), bInterfaceNumber,
|
||||
.out_num_objects = 1,
|
||||
.out_objects = std::addressof(srv),
|
||||
));
|
||||
|
||||
*out = ObjectFactory::CreateSharedEmplaced<ds::IDsInterface, RemoteDsInterface>(m_allocator, srv, m_allocator);
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result RemoteDsService::GetStateChangeEvent(sf::OutCopyHandle out) {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatch(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 2 : 3,
|
||||
.out_handle_attrs = { SfOutHandleAttr_HipcCopy },
|
||||
.out_handles = out.GetHandlePointer(),
|
||||
);
|
||||
}
|
||||
|
||||
Result RemoteDsService::GetState(sf::Out<usb::UsbState> out) {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatchOut(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 3 : 4, *out);
|
||||
}
|
||||
|
||||
Result RemoteDsService::ClearDeviceData() {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatch(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 4 : 5);
|
||||
}
|
||||
|
||||
Result RemoteDsService::AddUsbStringDescriptor(sf::Out<u8> out, const sf::InBuffer &desc) {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatchOut(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 5 : 6, *out,
|
||||
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In },
|
||||
.buffers = { { desc.GetPointer(), desc.GetSize() } },
|
||||
);
|
||||
}
|
||||
|
||||
Result RemoteDsService::DeleteUsbStringDescriptor(u8 index) {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatchIn(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 6 : 7, index);
|
||||
}
|
||||
|
||||
Result RemoteDsService::SetUsbDeviceDescriptor(const sf::InBuffer &desc, usb::UsbDeviceSpeed speed) {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatchIn(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 7 : 8, speed,
|
||||
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In },
|
||||
.buffers = { { desc.GetPointer(), desc.GetSize() } },
|
||||
);
|
||||
}
|
||||
|
||||
Result RemoteDsService::SetBinaryObjectStore(const sf::InBuffer &bos) {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatch(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 8 : 9,
|
||||
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In },
|
||||
.buffers = { { bos.GetPointer(), bos.GetSize() } },
|
||||
);
|
||||
}
|
||||
|
||||
Result RemoteDsService::Enable() {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatch(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 9 : 10);
|
||||
}
|
||||
|
||||
Result RemoteDsService::Disable() {
|
||||
serviceAssumeDomain(std::addressof(m_srv));
|
||||
return serviceDispatch(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 10 : 11);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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::usb {
|
||||
|
||||
class RemoteDsService {
|
||||
private:
|
||||
using Allocator = sf::ExpHeapAllocator;
|
||||
using ObjectFactory = sf::ObjectFactory<Allocator::Policy>;
|
||||
private:
|
||||
Service m_srv;
|
||||
Allocator *m_allocator;
|
||||
public:
|
||||
RemoteDsService(Service &srv, sf::ExpHeapAllocator *allocator) : m_srv(srv), m_allocator(allocator) { /* ... */ }
|
||||
virtual ~RemoteDsService() { serviceClose(std::addressof(m_srv)); }
|
||||
public:
|
||||
Result Bind(usb::ComplexId complex_id, sf::CopyHandle process_h);
|
||||
Result RegisterInterface(sf::Out<sf::SharedPointer<usb::ds::IDsInterface>> out, u8 bInterfaceNumber);
|
||||
Result GetStateChangeEvent(sf::OutCopyHandle out);
|
||||
Result GetState(sf::Out<usb::UsbState> out);
|
||||
Result ClearDeviceData();
|
||||
Result AddUsbStringDescriptor(sf::Out<u8> out, const sf::InBuffer &desc);
|
||||
Result DeleteUsbStringDescriptor(u8 index);
|
||||
Result SetUsbDeviceDescriptor(const sf::InBuffer &desc, usb::UsbDeviceSpeed speed);
|
||||
Result SetBinaryObjectStore(const sf::InBuffer &bos);
|
||||
Result Enable();
|
||||
Result Disable();
|
||||
};
|
||||
static_assert(ds::IsIDsService<RemoteDsService>);
|
||||
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
#define ATMOSPHERE_RELEASE_VERSION_MAJOR 0
|
||||
#define ATMOSPHERE_RELEASE_VERSION_MINOR 18
|
||||
#define ATMOSPHERE_RELEASE_VERSION_MICRO 0
|
||||
#define ATMOSPHERE_RELEASE_VERSION_MICRO 1
|
||||
|
||||
#define ATMOSPHERE_RELEASE_VERSION ATMOSPHERE_RELEASE_VERSION_MAJOR, ATMOSPHERE_RELEASE_VERSION_MINOR, ATMOSPHERE_RELEASE_VERSION_MICRO
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
#include <vapours/results/svc_results.hpp>
|
||||
#include <vapours/results/time_results.hpp>
|
||||
#include <vapours/results/updater_results.hpp>
|
||||
#include <vapours/results/usb_results.hpp>
|
||||
#include <vapours/results/vi_results.hpp>
|
||||
|
||||
/* Unofficial. */
|
||||
|
||||
37
libraries/libvapours/include/vapours/results/usb_results.hpp
Normal file
37
libraries/libvapours/include/vapours/results/usb_results.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 <vapours/results/results_common.hpp>
|
||||
|
||||
namespace ams::usb {
|
||||
|
||||
R_DEFINE_NAMESPACE_RESULT_MODULE(140);
|
||||
|
||||
R_DEFINE_ERROR_RESULT(NotInitialized, 0);
|
||||
R_DEFINE_ERROR_RESULT(AlreadyInitialized, 1);
|
||||
|
||||
R_DEFINE_ERROR_RANGE(InvalidParameter, 100, 199);
|
||||
R_DEFINE_ERROR_RESULT(AlignmentError, 103);
|
||||
|
||||
R_DEFINE_ERROR_RESULT(OperationDenied, 201);
|
||||
R_DEFINE_ERROR_RESULT(MemAllocFailure, 202);
|
||||
R_DEFINE_ERROR_RESULT(ResourceBusy, 206);
|
||||
R_DEFINE_ERROR_RESULT(InternalStateError, 207);
|
||||
|
||||
R_DEFINE_ERROR_RESULT(TransactionError, 401);
|
||||
R_DEFINE_ERROR_RESULT(Interrupted, 409);
|
||||
|
||||
}
|
||||
@@ -46,6 +46,9 @@
|
||||
#include <vapours/util/util_format_string.hpp>
|
||||
#include <vapours/util/util_range.hpp>
|
||||
|
||||
#include <vapours/util/util_fixed_map.hpp>
|
||||
#include <vapours/util/util_fixed_set.hpp>
|
||||
|
||||
#ifdef ATMOSPHERE_IS_STRATOSPHERE
|
||||
#include <vapours/util/util_mutex_utils.hpp>
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* 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 <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/util/util_fourcc.hpp>
|
||||
|
||||
namespace ams::util::impl {
|
||||
|
||||
class AvailableIndexFinder {
|
||||
private:
|
||||
static constexpr int MaxDepthOfBox = 5;
|
||||
|
||||
struct BitFlags64 {
|
||||
private:
|
||||
u64 m_data;
|
||||
public:
|
||||
constexpr ALWAYS_INLINE bool GetFlag(int index) const {
|
||||
AMS_ASSERT(index < 64);
|
||||
return ((m_data >> index) & UINT64_C(1)) != 0;
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE void SetFlag(int index) {
|
||||
AMS_ASSERT(index < 64);
|
||||
m_data |= (UINT64_C(1) << index);
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE void ClearFlag(int index) {
|
||||
AMS_ASSERT(index < 64);
|
||||
m_data &= ~(UINT64_C(1) << index);
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE bool IsFull() const {
|
||||
return m_data == ~(UINT64_C(0));
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE int FindIndexOfBitZero() const {
|
||||
AMS_ASSERT(!this->IsFull());
|
||||
return __builtin_ctzll(~m_data);
|
||||
}
|
||||
};
|
||||
private:
|
||||
int *m_p_current_index;
|
||||
int *m_p_map_index;
|
||||
void *m_buffer;
|
||||
int m_depth;
|
||||
BitFlags64 *m_flags;
|
||||
private:
|
||||
static constexpr int Pow64(int e) {
|
||||
switch (e) {
|
||||
case 0: return 0x1;
|
||||
case 1: return 0x40;
|
||||
case 2: return 0x1000;
|
||||
case 3: return 0x40000;
|
||||
case 4: return 0x1000000;
|
||||
case 5: return 0x40000000;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr u64 Roundup64(u64 value) {
|
||||
return (value + (64 - 1)) / 64;
|
||||
}
|
||||
|
||||
static constexpr size_t GetRequiredNumOfBox(int depth, size_t num_elements) {
|
||||
if (depth == 1) {
|
||||
return Roundup64(num_elements);
|
||||
} else if (depth == 2) {
|
||||
return Roundup64(num_elements) + Pow64(0);
|
||||
} else if (depth == 3) {
|
||||
return Roundup64(num_elements) + Pow64(0) + Pow64(1) + Pow64(2);
|
||||
} else if (depth == 4) {
|
||||
return Roundup64(num_elements) + Pow64(0) + Pow64(1) + Pow64(2) + Pow64(3);
|
||||
} else if (depth == 5) {
|
||||
return Roundup64(num_elements) + Pow64(0) + Pow64(1) + Pow64(2) + Pow64(3) + Pow64(4);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr int CalcOffset(int *arr, int depth) {
|
||||
int offset = 0;
|
||||
for (auto i = 0; i < depth; ++i) {
|
||||
offset += Pow64(i);
|
||||
}
|
||||
for (auto i = 0; i < depth; ++i) {
|
||||
offset += Pow64(i) - arr[depth - 1 - i];
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
public:
|
||||
static consteval int GetSignature() {
|
||||
return static_cast<int>(util::ReverseFourCC<'B', 'I', 'T', 'S'>::Code);
|
||||
}
|
||||
|
||||
static constexpr int GetNeedDepth(size_t num_elements) {
|
||||
if (num_elements <= 0x40) {
|
||||
return 1;
|
||||
} else if (num_elements <= 0x1000) {
|
||||
return 2;
|
||||
} else if (num_elements <= 0x40000) {
|
||||
return 3;
|
||||
} else if (num_elements <= 0x1000000) {
|
||||
return 4;
|
||||
} else if (num_elements <= 0x40000000) {
|
||||
return 5;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr size_t GetRequiredMemorySize(size_t num_elements) {
|
||||
return sizeof(BitFlags64) * GetRequiredNumOfBox(GetNeedDepth(num_elements), num_elements);
|
||||
}
|
||||
public:
|
||||
void Initialize(int *cur, int *map, u8 *buf) {
|
||||
const size_t num_elements = static_cast<size_t>(*map);
|
||||
AMS_ASSERT(num_elements <= static_cast<size_t>(Pow64(MaxDepthOfBox - 1)));
|
||||
|
||||
/* Set fields. */
|
||||
m_p_current_index = cur;
|
||||
m_p_map_index = map;
|
||||
m_buffer = buf;
|
||||
m_depth = GetNeedDepth(num_elements);
|
||||
|
||||
/* Validate fields. */
|
||||
AMS_ASSERT(m_depth > 0);
|
||||
|
||||
/* Setup memory. */
|
||||
std::memset(m_buffer, 0, GetRequiredMemorySize(num_elements));
|
||||
m_flags = reinterpret_cast<BitFlags64 *>(m_buffer);
|
||||
}
|
||||
|
||||
int AcquireIndex() {
|
||||
/* Validate pre-conditions. */
|
||||
AMS_ASSERT(*m_p_current_index < *m_p_map_index);
|
||||
|
||||
/* Build up arrays. */
|
||||
int table[MaxDepthOfBox];
|
||||
BitFlags64 *pos[MaxDepthOfBox];
|
||||
for (auto i = 0; i < m_depth; ++i) {
|
||||
/* Determine the position. */
|
||||
pos[i] = std::addressof(m_flags[CalcOffset(table, i)]);
|
||||
|
||||
/* Set table entry. */
|
||||
table[i] = pos[i]->FindIndexOfBitZero();
|
||||
AMS_ASSERT(table[i] != BITSIZEOF(u64));
|
||||
}
|
||||
|
||||
/* Validate that the index is not acquired. */
|
||||
AMS_ASSERT(!pos[m_depth - 1]->GetFlag(table[m_depth - 1]));
|
||||
|
||||
/* Acquire the index. */
|
||||
pos[m_depth - 1]->SetFlag(table[m_depth - 1]);
|
||||
|
||||
/* Validate that the index was acquired. */
|
||||
AMS_ASSERT(pos[m_depth - 1]->GetFlag(table[m_depth - 1]));
|
||||
|
||||
/* Update tracking flags. */
|
||||
for (auto i = m_depth - 1; i > 0; --i) {
|
||||
if (pos[i]->IsFull()) {
|
||||
pos[i - 1]->SetFlag(table[i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculate the index we acquired. */
|
||||
int index = 0, pow = 0;
|
||||
for (auto i = m_depth; i > 0; --i, ++pow) {
|
||||
index += Pow64(pow) * table[i - 1];
|
||||
}
|
||||
|
||||
/* Increment current index. */
|
||||
++(*m_p_current_index);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
void ReleaseIndex(int index) {
|
||||
/* Convert index to table. */
|
||||
int table[MaxDepthOfBox];
|
||||
for (auto i = 0; i < m_depth; ++i) {
|
||||
table[m_depth - 1 - i] = index % BITSIZEOF(u64);
|
||||
index /= BITSIZEOF(u64);
|
||||
}
|
||||
|
||||
/* Build up arrays. */
|
||||
BitFlags64 *pos[MaxDepthOfBox];
|
||||
for (auto i = 0; i < m_depth; ++i) {
|
||||
/* Determine the position. */
|
||||
pos[i] = std::addressof(m_flags[CalcOffset(table, i)]);
|
||||
}
|
||||
|
||||
/* Validate that the flag is set. */
|
||||
AMS_ASSERT(pos[m_depth - 1]->GetFlag(table[m_depth - 1]));
|
||||
|
||||
/* Clear the flags. */
|
||||
for (auto i = m_depth - 1; i >= 0; --i) {
|
||||
pos[i]->ClearFlag(table[i]);
|
||||
}
|
||||
|
||||
/* Decrement current index. */
|
||||
--(*m_p_current_index);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
69
libraries/libvapours/include/vapours/util/util_fixed_map.hpp
Normal file
69
libraries/libvapours/include/vapours/util/util_fixed_map.hpp
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/util/util_fixed_tree.hpp>
|
||||
|
||||
namespace ams::util {
|
||||
|
||||
template<typename Key, typename Value, typename Compare = std::less<Key>, size_t BufferAlignment = 8>
|
||||
class FixedMap {
|
||||
private:
|
||||
using KeyValuePair = std::pair<Key const, Value>;
|
||||
|
||||
struct LessTypeForMap {
|
||||
constexpr ALWAYS_INLINE bool operator()(const KeyValuePair &lhs, const KeyValuePair &rhs) const {
|
||||
return Compare{}(lhs.first, rhs.first);
|
||||
}
|
||||
};
|
||||
|
||||
using TreeType = ::ams::util::FixedTree<KeyValuePair, LessTypeForMap, KeyValuePair, BufferAlignment>;
|
||||
|
||||
using iterator = typename TreeType::iterator;
|
||||
using const_iterator = typename TreeType::const_iterator;
|
||||
public:
|
||||
static constexpr size_t GetRequiredMemorySize(size_t num_elements) {
|
||||
return TreeType::GetRequiredMemorySize(num_elements);
|
||||
}
|
||||
private:
|
||||
TreeType m_tree;
|
||||
public:
|
||||
FixedMap() : m_tree() { /* ... */ }
|
||||
|
||||
void Initialize(size_t num_elements, void *buffer, size_t buffer_size) {
|
||||
return m_tree.Initialize(num_elements, buffer, buffer_size);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE iterator begin() { return m_tree.begin(); }
|
||||
ALWAYS_INLINE const_iterator begin() const { return m_tree.begin(); }
|
||||
|
||||
ALWAYS_INLINE iterator end() { return m_tree.end(); }
|
||||
ALWAYS_INLINE const_iterator end() const { return m_tree.end(); }
|
||||
|
||||
ALWAYS_INLINE bool erase(const Key &key) { const KeyValuePair pair(key, Value{}); return m_tree.erase(pair); }
|
||||
|
||||
ALWAYS_INLINE iterator find(const Key &key) { const KeyValuePair pair(key, Value{}); return m_tree.find(pair); }
|
||||
ALWAYS_INLINE const_iterator find(const Key &key) const { const KeyValuePair pair(key, Value{}); return m_tree.find(pair); }
|
||||
|
||||
ALWAYS_INLINE std::pair<iterator, bool> insert(const KeyValuePair &pair) { return m_tree.insert(pair); }
|
||||
|
||||
ALWAYS_INLINE size_t size() const { return m_tree.size(); }
|
||||
|
||||
ALWAYS_INLINE void clear() { return m_tree.clear(); }
|
||||
};
|
||||
|
||||
}
|
||||
@@ -13,24 +13,16 @@
|
||||
* 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 "printk.h"
|
||||
#include "vsprintf.h"
|
||||
#pragma once
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/util/util_fixed_tree.hpp>
|
||||
|
||||
/**
|
||||
* Temporary stand-in main printk.
|
||||
*
|
||||
* TODO: This should print via UART, console framebuffer, and to a ring for
|
||||
* consumption by Horizon
|
||||
*/
|
||||
void printk(char *fmt, ...)
|
||||
{
|
||||
va_list list;
|
||||
char buf[512];
|
||||
va_start(list, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, list);
|
||||
namespace ams::util {
|
||||
|
||||
/* FIXME: print via UART */
|
||||
template<typename Element, typename Compare = std::less<Element>, size_t BufferAlignment = 8>
|
||||
class FixedSet : public ::ams::util::FixedTree<Element, Compare, const Element, BufferAlignment> {
|
||||
/* ... */
|
||||
};
|
||||
|
||||
va_end(list);
|
||||
}
|
||||
998
libraries/libvapours/include/vapours/util/util_fixed_tree.hpp
Normal file
998
libraries/libvapours/include/vapours/util/util_fixed_tree.hpp
Normal file
@@ -0,0 +1,998 @@
|
||||
/*
|
||||
* 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 <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/util/impl/util_available_index_finder.hpp>
|
||||
#include <vapours/util/util_alignment.hpp>
|
||||
|
||||
namespace ams::util {
|
||||
|
||||
template<typename Member, typename Compare, typename IteratorMember, size_t BufferAlignment = 8> requires std::convertible_to<Member &, IteratorMember &>
|
||||
class FixedTree {
|
||||
private:
|
||||
class IteratorBase;
|
||||
friend class IteratorBase;
|
||||
private:
|
||||
enum class Color : u8 {
|
||||
Red = 0,
|
||||
Black = 1,
|
||||
};
|
||||
|
||||
static constexpr inline int Index_Nil = -1;
|
||||
static constexpr inline int Index_Leaf = -2;
|
||||
static constexpr inline int Index_BeforeBegin = -3;
|
||||
static constexpr inline int Index_AfterEnd = -4;
|
||||
|
||||
static constexpr inline size_t max_size = 0x40000000;
|
||||
|
||||
struct Header {
|
||||
/* "Nintendo Red-Black tree" */
|
||||
static constexpr u32 Signature = util::ReverseFourCC<'N','N','R','B'>::Code;
|
||||
|
||||
u32 header_size;
|
||||
u32 header_signature;
|
||||
u32 _08;
|
||||
s32 max_elements;
|
||||
s32 cur_elements;
|
||||
s32 root_index;
|
||||
s32 left_most_index;
|
||||
s32 right_most_index;
|
||||
s32 index_signature;
|
||||
u32 buffer_size;
|
||||
u32 node_size;
|
||||
u32 element_size;
|
||||
u32 _30;
|
||||
u32 _34;
|
||||
u32 _38;
|
||||
u32 _3C;
|
||||
u32 _40;
|
||||
u32 _44;
|
||||
u32 _48;
|
||||
u32 _4C;
|
||||
|
||||
void InitializeHeader(u32 _08, s32 max_e, s32 cur_e, u32 ind_sig, u32 buf_sz, u32 node_sz, u32 e_sz, u32 _30, u32 _34, u32 _38, u32 _3C, u32 _40, u32 _44) {
|
||||
this->header_size = sizeof(Header);
|
||||
this->header_signature = Signature;
|
||||
this->_08 = _08;
|
||||
this->max_elements = max_e;
|
||||
this->cur_elements = cur_e;
|
||||
this->root_index = Index_Nil;
|
||||
this->left_most_index = Index_Nil;
|
||||
this->right_most_index = Index_Nil;
|
||||
this->index_signature = ind_sig;
|
||||
this->buffer_size = buf_sz;
|
||||
this->node_size = node_sz;
|
||||
this->element_size = e_sz;
|
||||
this->_30 = _30;
|
||||
this->_34 = _34;
|
||||
this->_38 = _38;
|
||||
this->_3C = _3C;
|
||||
this->_40 = _40;
|
||||
this->_44 = _44;
|
||||
this->_48 = 0;
|
||||
this->_4C = 0;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(Header) == 0x50);
|
||||
|
||||
struct IndexPair {
|
||||
int first;
|
||||
int last;
|
||||
};
|
||||
|
||||
struct Node {
|
||||
Member m_data;
|
||||
int m_parent;
|
||||
int m_right;
|
||||
int m_left;
|
||||
Color m_color;
|
||||
|
||||
void SetLeft(int l, Node *n, int p) {
|
||||
m_left = l;
|
||||
n->m_parent = p;
|
||||
}
|
||||
|
||||
void SetRight(int r, Node *n, int p) {
|
||||
m_right = r;
|
||||
n->m_parent = p;
|
||||
}
|
||||
};
|
||||
|
||||
class Iterator;
|
||||
class ConstIterator;
|
||||
|
||||
class IteratorBase {
|
||||
private:
|
||||
friend class ConstIterator;
|
||||
private:
|
||||
const FixedTree *m_this;
|
||||
int m_index;
|
||||
protected:
|
||||
constexpr ALWAYS_INLINE IteratorBase(const FixedTree *tree, int index) : m_this(tree), m_index(index) { /* ... */ }
|
||||
|
||||
constexpr bool IsEqualImpl(const IteratorBase &rhs) const {
|
||||
/* Validate pre-conditions. */
|
||||
AMS_ASSERT(m_this);
|
||||
|
||||
/* Check for tree equality. */
|
||||
if (m_this != rhs.m_this) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check for nil. */
|
||||
if (m_this->IsNil(m_index) && m_this->IsNil(rhs.m_index)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check for index equality. */
|
||||
return m_index == rhs.m_index;
|
||||
}
|
||||
|
||||
constexpr IteratorMember &DereferenceImpl() const {
|
||||
/* Validate pre-conditions. */
|
||||
AMS_ASSERT(m_this);
|
||||
|
||||
if (!m_this->IsNil(m_index)) {
|
||||
return m_this->m_nodes[m_index].m_data;
|
||||
} else {
|
||||
AMS_ASSERT(false);
|
||||
return m_this->GetNode(std::numeric_limits<int>::max())->m_data;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE IteratorBase &IncrementImpl() {
|
||||
/* Validate pre-conditions. */
|
||||
AMS_ASSERT(m_this);
|
||||
|
||||
this->OperateIndex(true);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE IteratorBase &DecrementImpl() {
|
||||
/* Validate pre-conditions. */
|
||||
AMS_ASSERT(m_this);
|
||||
|
||||
this->OperateIndex(false);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr void OperateIndex(bool increment) {
|
||||
if (increment) {
|
||||
/* We're incrementing. */
|
||||
if (m_index == Index_BeforeBegin) {
|
||||
m_index = 0;
|
||||
} else {
|
||||
m_index = m_this->UncheckedPP(m_index);
|
||||
if (m_this->IsNil(m_index)) {
|
||||
m_index = Index_AfterEnd;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* We're decrementing. */
|
||||
if (m_index == Index_AfterEnd) {
|
||||
m_index = static_cast<int>(m_this->size()) - 1;
|
||||
} else {
|
||||
m_index = m_this->UncheckedMM(m_index);
|
||||
if (m_this->IsNil(m_index)) {
|
||||
m_index = Index_BeforeBegin;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class Iterator : public IteratorBase {
|
||||
public:
|
||||
constexpr ALWAYS_INLINE Iterator(const FixedTree &tree) : IteratorBase(std::addressof(tree), tree.size() ? tree.GetLMost() : Index_Leaf) { /* ... */ }
|
||||
constexpr ALWAYS_INLINE Iterator(const FixedTree &tree, int index) : IteratorBase(std::addressof(tree), index) { /* ... */ }
|
||||
|
||||
constexpr ALWAYS_INLINE Iterator(const Iterator &rhs) = default;
|
||||
|
||||
constexpr ALWAYS_INLINE bool operator==(const Iterator &rhs) const {
|
||||
return this->IsEqualImpl(rhs);
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE bool operator!=(const Iterator &rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE IteratorMember &operator*() const {
|
||||
return static_cast<IteratorMember &>(this->DereferenceImpl());
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE IteratorMember *operator->() const {
|
||||
return std::addressof(this->operator *());
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE Iterator &operator++() {
|
||||
return static_cast<Iterator &>(this->IncrementImpl());
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE Iterator &operator--() {
|
||||
return static_cast<Iterator &>(this->DecrementImpl());
|
||||
}
|
||||
};
|
||||
|
||||
class ConstIterator : public IteratorBase {
|
||||
public:
|
||||
constexpr ALWAYS_INLINE ConstIterator(const FixedTree &tree) : IteratorBase(std::addressof(tree), tree.size() ? tree.GetLMost() : Index_Leaf) { /* ... */ }
|
||||
constexpr ALWAYS_INLINE ConstIterator(const FixedTree &tree, int index) : IteratorBase(std::addressof(tree), index) { /* ... */ }
|
||||
|
||||
constexpr ALWAYS_INLINE ConstIterator(const ConstIterator &rhs) = default;
|
||||
constexpr ALWAYS_INLINE ConstIterator(const Iterator &rhs) : IteratorBase(rhs.m_this, rhs.m_index) { /* ... */ }
|
||||
|
||||
constexpr ALWAYS_INLINE bool operator==(const ConstIterator &rhs) const {
|
||||
return this->IsEqualImpl(rhs);
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE bool operator!=(const ConstIterator &rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE const IteratorMember &operator*() const {
|
||||
return static_cast<const IteratorMember &>(this->DereferenceImpl());
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE const IteratorMember *operator->() const {
|
||||
return std::addressof(this->operator *());
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE ConstIterator &operator++() {
|
||||
return static_cast<ConstIterator &>(this->IncrementImpl());
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE ConstIterator &operator--() {
|
||||
return static_cast<ConstIterator &>(this->DecrementImpl());
|
||||
}
|
||||
};
|
||||
public:
|
||||
using iterator = Iterator;
|
||||
using const_iterator = ConstIterator;
|
||||
private:
|
||||
impl::AvailableIndexFinder m_index_finder;
|
||||
Node m_dummy_leaf;
|
||||
Node *m_p_dummy_leaf;
|
||||
u8 *m_buffer;
|
||||
Header *m_header;
|
||||
Node *m_nodes;
|
||||
iterator m_end_iterator;
|
||||
public:
|
||||
FixedTree() : m_end_iterator(*this, Index_Nil) {
|
||||
this->SetDummyMemory();
|
||||
}
|
||||
protected:
|
||||
void InitializeImpl(int num_elements, void *buffer, size_t buffer_size) {
|
||||
/* Check pre-conditions. */
|
||||
AMS_ASSERT(num_elements > 0);
|
||||
AMS_ASSERT(static_cast<size_t>(num_elements) <= max_size);
|
||||
AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(buffer), BufferAlignment));
|
||||
AMS_ASSERT(buffer_size == GetRequiredMemorySize(num_elements));
|
||||
|
||||
/* Set buffer. */
|
||||
m_buffer = static_cast<u8 *>(buffer);
|
||||
m_header = reinterpret_cast<Header *>(m_buffer);
|
||||
|
||||
/* Setup memory. */
|
||||
this->InitializeMemory(num_elements, buffer_size, impl::AvailableIndexFinder::GetSignature());
|
||||
|
||||
/* Check that buffer was set up correctly. */
|
||||
AMS_ASSERT(static_cast<u32>(buffer_size) == m_header->buffer_size);
|
||||
|
||||
/* Setup dummy leaf. */
|
||||
this->SetDummyMemory();
|
||||
}
|
||||
public:
|
||||
static constexpr size_t SizeOfNodes(size_t num_elements) {
|
||||
return util::AlignUp(sizeof(Node) * num_elements, BufferAlignment);
|
||||
}
|
||||
|
||||
static constexpr size_t SizeOfIndex(size_t num_elements) {
|
||||
return impl::AvailableIndexFinder::GetRequiredMemorySize(num_elements);
|
||||
}
|
||||
|
||||
static constexpr size_t GetRequiredMemorySize(size_t num_elements) {
|
||||
return sizeof(Header) + SizeOfNodes(num_elements) + SizeOfIndex(num_elements);
|
||||
}
|
||||
private:
|
||||
void SetDummyMemory() {
|
||||
m_dummy_leaf.m_color = Color::Black;
|
||||
m_dummy_leaf.m_parent = Index_Nil;
|
||||
m_dummy_leaf.m_left = Index_Leaf;
|
||||
m_dummy_leaf.m_right = Index_Leaf;
|
||||
m_p_dummy_leaf = std::addressof(m_dummy_leaf);
|
||||
}
|
||||
|
||||
void InitializeMemory(int num_elements, u32 buffer_size, u32 signature) {
|
||||
/* Initialize the header. */
|
||||
m_header->InitializeHeader(1, num_elements, 0, signature, buffer_size, sizeof(Node), sizeof(Member), 4, 4, 4, 4, 4, BufferAlignment);
|
||||
|
||||
/* Setup index finder. */
|
||||
m_index_finder.Initialize(std::addressof(m_header->cur_elements), std::addressof(m_header->max_elements), m_buffer + sizeof(*m_header) + SizeOfNodes(num_elements));
|
||||
|
||||
/* Set nodes array. */
|
||||
m_nodes = reinterpret_cast<Node *>(m_buffer + sizeof(*m_header));
|
||||
}
|
||||
|
||||
Node *GetNode(int index) const {
|
||||
if (index >= 0) {
|
||||
return m_nodes + index;
|
||||
} else {
|
||||
return m_p_dummy_leaf;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE bool IsNil(int index) const {
|
||||
return index < 0;
|
||||
}
|
||||
|
||||
constexpr ALWAYS_INLINE bool IsLeaf(int index) const {
|
||||
return index == Index_Leaf;
|
||||
}
|
||||
|
||||
int GetRoot() const { return m_header->root_index; }
|
||||
void SetRoot(int index) {
|
||||
if (index == Index_Leaf) {
|
||||
index = Index_Nil;
|
||||
}
|
||||
|
||||
m_header->root_index = index;
|
||||
}
|
||||
|
||||
int GetLMost() const { return m_header->left_most_index; }
|
||||
void SetLMost(int index) { m_header->left_most_index = index; }
|
||||
|
||||
int GetRMost() const { return m_header->right_most_index; }
|
||||
void SetRMost(int index) { m_header->right_most_index = index; }
|
||||
|
||||
int GetParent(int index) const {
|
||||
return this->GetNode(index)->m_parent;
|
||||
}
|
||||
|
||||
int AcquireIndex() { return m_index_finder.AcquireIndex(); }
|
||||
void ReleaseIndex(int index) { return m_index_finder.ReleaseIndex(index); }
|
||||
|
||||
int EraseByIndex(int target_index) {
|
||||
/* Setup tracking variables. */
|
||||
const auto next_index = this->UncheckedPP(target_index);
|
||||
auto *target_node = this->GetNode(target_index);
|
||||
|
||||
auto a_index = Index_Leaf;
|
||||
auto *a_node = this->GetNode(a_index);
|
||||
auto b_index = Index_Leaf;
|
||||
auto *b_node = this->GetNode(b_index);
|
||||
auto cur_index = target_index;
|
||||
auto *cur_node = this->GetNode(cur_index);
|
||||
|
||||
if (cur_node->m_left == Index_Leaf) {
|
||||
a_index = cur_node->m_right;
|
||||
a_node = this->GetNode(a_index);
|
||||
|
||||
m_p_dummy_leaf->m_parent = cur_index;
|
||||
} else {
|
||||
if (cur_node->m_right == Index_Leaf) {
|
||||
a_index = cur_node->m_left;
|
||||
} else {
|
||||
cur_index = next_index;
|
||||
cur_node = this->GetNode(cur_index);
|
||||
a_index = cur_node->m_right;
|
||||
}
|
||||
a_node = this->GetNode(a_index);
|
||||
|
||||
m_p_dummy_leaf->m_parent = cur_index;
|
||||
}
|
||||
|
||||
/* Ensure the a node is updated (redundant) */
|
||||
a_node = this->GetNode(a_index);
|
||||
|
||||
/* Update relevant metrics/links. */
|
||||
if (cur_index == target_index) {
|
||||
/* No left, but has right. */
|
||||
b_index = target_node->m_parent;
|
||||
b_node = this->GetNode(b_index);
|
||||
|
||||
if (a_index != Index_Leaf) {
|
||||
a_node->m_parent = b_index;
|
||||
}
|
||||
|
||||
if (this->GetRoot() == target_index) {
|
||||
this->SetRoot(a_index);
|
||||
} else if (b_node->m_left == target_index) {
|
||||
b_node->m_left = a_index;
|
||||
} else {
|
||||
b_node->m_right = a_index;
|
||||
}
|
||||
|
||||
if (this->GetLMost() == target_index) {
|
||||
this->SetLMost((a_index != Index_Leaf) ? this->FindMinInSubtree(a_index) : b_index);
|
||||
}
|
||||
|
||||
if (this->GetRMost() == target_index) {
|
||||
this->SetRMost((a_index != Index_Leaf) ? this->FindMaxInSubtree(a_index) : b_index);
|
||||
}
|
||||
} else {
|
||||
/* Has left or doesn't have right. */
|
||||
|
||||
/* Fix left links. */
|
||||
this->GetNode(target_node->m_left)->m_parent = cur_index;
|
||||
cur_node->m_left = target_node->m_left;
|
||||
|
||||
if (cur_index == target_node->m_right) {
|
||||
b_index = cur_index;
|
||||
b_node = this->GetNode(b_index);
|
||||
} else {
|
||||
b_index = cur_node->m_parent;
|
||||
b_node = this->GetNode(b_index);
|
||||
|
||||
if (!this->IsNil(a_index)) {
|
||||
a_node->m_parent = b_index;
|
||||
}
|
||||
|
||||
b_node->m_left = a_index;
|
||||
cur_node->m_right = target_node->m_right;
|
||||
|
||||
this->GetNode(target_node->m_right)->m_parent = cur_index;
|
||||
}
|
||||
|
||||
if (this->GetRoot() == target_index) {
|
||||
this->SetRoot(cur_index);
|
||||
} else {
|
||||
if (this->GetNode(target_node->m_parent)->m_left == target_index) {
|
||||
this->GetNode(target_node->m_parent)->m_left = cur_index;
|
||||
} else {
|
||||
this->GetNode(target_node->m_parent)->m_right = cur_index;
|
||||
}
|
||||
}
|
||||
|
||||
cur_node->m_parent = target_node->m_parent;
|
||||
std::swap(cur_node->m_color, target_node->m_color);
|
||||
}
|
||||
|
||||
/* Ensure the tree remains balanced. */
|
||||
if (target_node->m_color == Color::Black) {
|
||||
while (true) {
|
||||
if (a_index == this->GetRoot() || a_node->m_color != Color::Black) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (a_index == b_node->m_left) {
|
||||
cur_index = b_node->m_right;
|
||||
cur_node = this->GetNode(cur_index);
|
||||
|
||||
if (cur_node->m_color == Color::Red) {
|
||||
cur_node->m_color = Color::Black;
|
||||
b_node->m_color = Color::Red;
|
||||
AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black);
|
||||
|
||||
this->RotateLeft(b_index);
|
||||
|
||||
cur_index = b_node->m_right;
|
||||
cur_node = this->GetNode(cur_index);
|
||||
}
|
||||
|
||||
if (this->IsNil(cur_index)) {
|
||||
a_index = b_index;
|
||||
a_node = b_node;
|
||||
} else {
|
||||
if (this->GetNode(cur_node->m_left)->m_color != Color::Black || this->GetNode(cur_node->m_right)->m_color != Color::Black) {
|
||||
if (this->GetNode(cur_node->m_right)->m_color == Color::Black) {
|
||||
this->GetNode(cur_node->m_left)->m_color = Color::Black;
|
||||
cur_node->m_color = Color::Red;
|
||||
AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black);
|
||||
|
||||
this->RotateRight(cur_index);
|
||||
|
||||
cur_index = b_node->m_right;
|
||||
cur_node = this->GetNode(cur_index);
|
||||
}
|
||||
|
||||
cur_node->m_color = b_node->m_color;
|
||||
b_node->m_color = Color::Black;
|
||||
|
||||
this->GetNode(cur_node->m_right)->m_color = Color::Black;
|
||||
|
||||
this->RotateLeft(b_index);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
cur_node->m_color = Color::Red;
|
||||
AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black);
|
||||
|
||||
a_index = b_index;
|
||||
a_node = b_node;
|
||||
}
|
||||
} else {
|
||||
cur_index = b_node->m_left;
|
||||
cur_node = this->GetNode(cur_index);
|
||||
|
||||
if (cur_node->m_color == Color::Red) {
|
||||
cur_node->m_color = Color::Black;
|
||||
b_node->m_color = Color::Red;
|
||||
AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black);
|
||||
|
||||
this->RotateRight(b_index);
|
||||
|
||||
cur_index = b_node->m_left;
|
||||
cur_node = this->GetNode(cur_index);
|
||||
}
|
||||
|
||||
if (this->IsNil(cur_index)) {
|
||||
a_index = b_index;
|
||||
a_node = b_node;
|
||||
} else {
|
||||
if (this->GetNode(cur_node->m_right)->m_color != Color::Black || this->GetNode(cur_node->m_left)->m_color != Color::Black) {
|
||||
if (this->GetNode(cur_node->m_left)->m_color == Color::Black) {
|
||||
this->GetNode(cur_node->m_right)->m_color = Color::Black;
|
||||
cur_node->m_color = Color::Red;
|
||||
AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black);
|
||||
|
||||
this->RotateLeft(cur_index);
|
||||
|
||||
cur_index = b_node->m_left;
|
||||
cur_node = this->GetNode(cur_index);
|
||||
}
|
||||
|
||||
cur_node->m_color = b_node->m_color;
|
||||
b_node->m_color = Color::Black;
|
||||
|
||||
this->GetNode(cur_node->m_left)->m_color = Color::Black;
|
||||
|
||||
this->RotateRight(b_index);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
cur_node->m_color = Color::Red;
|
||||
AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black);
|
||||
|
||||
a_index = b_index;
|
||||
a_node = b_node;
|
||||
}
|
||||
}
|
||||
|
||||
b_index = a_node->m_parent;
|
||||
b_node = this->GetNode(b_index);
|
||||
}
|
||||
|
||||
a_node->m_color = Color::Black;
|
||||
}
|
||||
|
||||
/* Release the index. */
|
||||
this->ReleaseIndex(target_index);
|
||||
return target_index;
|
||||
}
|
||||
|
||||
int FindIndex(const Member &elem) const {
|
||||
return this->FindIndexSub(this->GetRoot(), elem);
|
||||
}
|
||||
|
||||
int FindIndexSub(int index, const Member &elem) const {
|
||||
if (index != Index_Nil) {
|
||||
auto *node = this->GetNode(index);
|
||||
if (Compare{}(elem, node->m_data)) {
|
||||
if (!this->IsLeaf(node->m_left)) {
|
||||
return this->FindIndexSub(node->m_left, elem);
|
||||
}
|
||||
} else {
|
||||
if (!Compare{}(node->m_data, elem)) {
|
||||
return index;
|
||||
}
|
||||
|
||||
if (!this->IsLeaf(node->m_right)) {
|
||||
return this->FindIndexSub(node->m_right, elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Index_Nil;
|
||||
}
|
||||
|
||||
int FindMaxInSubtree(int index) const {
|
||||
int max = index;
|
||||
for (auto *node = this->GetNode(index); !this->IsNil(node->m_right); node = this->GetNode(node->m_right)) {
|
||||
max = node->m_right;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
int FindMinInSubtree(int index) const {
|
||||
int min = index;
|
||||
for (auto *node = this->GetNode(index); !this->IsNil(node->m_left); node = this->GetNode(node->m_left)) {
|
||||
min = node->m_left;
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
int InsertAt(bool before, int parent, const Member &elem) {
|
||||
/* Get an index for the new element. */
|
||||
const auto index = this->AcquireIndex();
|
||||
|
||||
/* Create the node. */
|
||||
auto *node = this->GetNode(index);
|
||||
node->m_color = Color::Red;
|
||||
node->m_parent = parent;
|
||||
node->m_right = Index_Leaf;
|
||||
node->m_left = Index_Leaf;
|
||||
std::memcpy(reinterpret_cast<u8 *>(std::addressof(node->m_data)), reinterpret_cast<const u8 *>(std::addressof(elem)), sizeof(node->m_data));
|
||||
|
||||
/* Fix up the parent node. */
|
||||
auto *parent_node = this->GetNode(parent);
|
||||
if (before) {
|
||||
parent_node->m_left = index;
|
||||
if (parent == this->GetLMost()) {
|
||||
this->SetLMost(index);
|
||||
}
|
||||
} else {
|
||||
parent_node->m_right = index;
|
||||
if (parent == this->GetRMost()) {
|
||||
this->SetRMost(index);
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure the tree is balanced. */
|
||||
int cur_index = index;
|
||||
while (true) {
|
||||
auto *cur_node = this->GetNode(cur_index);
|
||||
if (this->GetNode(cur_node->m_parent)->m_color != Color::Red) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto *p_node = this->GetNode(cur_node->m_parent);
|
||||
auto *g_node = this->GetNode(p_node->m_parent);
|
||||
if (cur_node->m_parent == g_node->m_left) {
|
||||
if (auto *gr_node = this->GetNode(g_node->m_right); gr_node->m_color == Color::Red) {
|
||||
p_node->m_color = Color::Black;
|
||||
gr_node->m_color = Color::Black;
|
||||
g_node->m_color = Color::Red;
|
||||
AMS_ASSERT(m_p_dummy_leaf->m_color != Color::Red);
|
||||
|
||||
cur_index = p_node->m_parent;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cur_index == p_node->m_right) {
|
||||
cur_index = cur_node->m_parent;
|
||||
cur_node = this->GetNode(cur_index);
|
||||
this->RotateLeft(cur_index);
|
||||
}
|
||||
|
||||
p_node = this->GetNode(cur_node->m_parent);
|
||||
p_node->m_color = Color::Black;
|
||||
|
||||
g_node = this->GetNode(p_node->m_parent);
|
||||
g_node->m_color = Color::Red;
|
||||
|
||||
AMS_ASSERT(m_p_dummy_leaf->m_color != Color::Red);
|
||||
|
||||
this->RotateRight(p_node->m_parent);
|
||||
} else {
|
||||
if (auto *gl_node = this->GetNode(g_node->m_left); gl_node->m_color == Color::Red) {
|
||||
p_node->m_color = Color::Black;
|
||||
gl_node->m_color = Color::Black;
|
||||
g_node->m_color = Color::Red;
|
||||
AMS_ASSERT(m_p_dummy_leaf->m_color != Color::Red);
|
||||
|
||||
cur_index = p_node->m_parent;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cur_index == p_node->m_left) {
|
||||
cur_index = cur_node->m_parent;
|
||||
cur_node = this->GetNode(cur_index);
|
||||
this->RotateRight(cur_index);
|
||||
}
|
||||
|
||||
p_node = this->GetNode(cur_node->m_parent);
|
||||
p_node->m_color = Color::Black;
|
||||
|
||||
g_node = this->GetNode(p_node->m_parent);
|
||||
g_node->m_color = Color::Red;
|
||||
|
||||
AMS_ASSERT(m_p_dummy_leaf->m_color != Color::Red);
|
||||
|
||||
this->RotateLeft(p_node->m_parent);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set root color. */
|
||||
this->GetNode(this->GetRoot())->m_color = Color::Black;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
int InsertNoHint(bool before, const Member &elem) {
|
||||
int cur_index = this->GetRoot();
|
||||
int prev_index = Index_Nil;
|
||||
bool less = true;
|
||||
while (cur_index != Index_Nil && cur_index != Index_Leaf) {
|
||||
auto *node = this->GetNode(cur_index);
|
||||
prev_index = cur_index;
|
||||
|
||||
if (before) {
|
||||
less = Compare{}(node->m_data, elem);
|
||||
} else {
|
||||
less = Compare{}(elem, node->m_data);
|
||||
}
|
||||
|
||||
if (less) {
|
||||
cur_index = node->m_left;
|
||||
} else {
|
||||
cur_index = node->m_right;
|
||||
}
|
||||
}
|
||||
|
||||
if (cur_index == Index_Nil) {
|
||||
/* Create a new node. */
|
||||
const auto index = this->AcquireIndex();
|
||||
auto *node = this->GetNode(index);
|
||||
node->m_color = Color::Black;
|
||||
node->m_parent = Index_Nil;
|
||||
node->m_right = Index_Leaf;
|
||||
node->m_left = Index_Leaf;
|
||||
std::memcpy(reinterpret_cast<u8 *>(std::addressof(node->m_data)), reinterpret_cast<const u8 *>(std::addressof(elem)), sizeof(node->m_data));
|
||||
|
||||
this->SetRoot(index);
|
||||
this->SetLMost(index);
|
||||
this->SetRMost(index);
|
||||
|
||||
return index;
|
||||
} else {
|
||||
auto *compare_node = this->GetNode(prev_index);
|
||||
if (less) {
|
||||
if (prev_index == this->GetLMost()) {
|
||||
return this->InsertAt(less, prev_index, elem);
|
||||
} else {
|
||||
compare_node = this->GetNode(this->UncheckedMM(prev_index));
|
||||
}
|
||||
}
|
||||
|
||||
if (Compare{}(compare_node->m_data, elem)) {
|
||||
return this->InsertAt(less, prev_index, elem);
|
||||
} else {
|
||||
return Index_Nil;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RotateLeft(int index) {
|
||||
/* Determine indices. */
|
||||
const auto p_index = this->GetParent(index);
|
||||
const auto r_index = this->GetNode(index)->m_right;
|
||||
const auto l_index = this->GetNode(index)->m_left;
|
||||
const auto rl_index = this->GetNode(r_index)->m_left;
|
||||
const auto rr_index = this->GetNode(r_index)->m_right;
|
||||
|
||||
/* Get nodes. */
|
||||
auto *node = this->GetNode(index);
|
||||
auto *p_node = this->GetNode(p_index);
|
||||
auto *r_node = this->GetNode(r_index);
|
||||
auto *l_node = this->GetNode(l_index);
|
||||
auto *rl_node = this->GetNode(rl_index);
|
||||
auto *rr_node = this->GetNode(rr_index);
|
||||
|
||||
/* Perform the rotation. */
|
||||
if (p_index == Index_Nil) {
|
||||
r_node->m_parent = Index_Nil;
|
||||
m_header->root_index = r_index;
|
||||
} else if (p_node->m_left == index) {
|
||||
p_node->SetLeft(r_index, r_node, p_index);
|
||||
} else {
|
||||
p_node->SetRight(r_index, r_node, p_index);
|
||||
}
|
||||
r_node->SetLeft(index, node, r_index);
|
||||
r_node->SetRight(rr_index, rr_node, r_index);
|
||||
node->SetLeft(l_index, l_node, index);
|
||||
node->SetRight(rl_index, rl_node, index);
|
||||
}
|
||||
|
||||
void RotateRight(int index) {
|
||||
/* Determine indices. */
|
||||
const auto p_index = this->GetParent(index);
|
||||
const auto l_index = this->GetNode(index)->m_left;
|
||||
const auto ll_index = this->GetNode(l_index)->m_left;
|
||||
const auto lr_index = this->GetNode(l_index)->m_right;
|
||||
const auto r_index = this->GetNode(index)->m_right;
|
||||
|
||||
/* Get nodes. */
|
||||
auto *node = this->GetNode(index);
|
||||
auto *p_node = this->GetNode(p_index);
|
||||
auto *l_node = this->GetNode(l_index);
|
||||
auto *ll_node = this->GetNode(ll_index);
|
||||
auto *lr_node = this->GetNode(lr_index);
|
||||
auto *r_node = this->GetNode(r_index);
|
||||
|
||||
/* Perform the rotation. */
|
||||
if (p_index == Index_Nil) {
|
||||
l_node->m_parent = Index_Nil;
|
||||
m_header->root_index = l_index;
|
||||
} else if (p_node->m_left == index) {
|
||||
p_node->SetLeft(l_index, l_node, p_index);
|
||||
} else {
|
||||
p_node->SetRight(l_index, l_node, p_index);
|
||||
}
|
||||
l_node->SetLeft(ll_index, ll_node, l_index);
|
||||
l_node->SetRight(index, node, l_index);
|
||||
node->SetLeft(lr_index, lr_node, index);
|
||||
node->SetRight(r_index, r_node, index);
|
||||
}
|
||||
|
||||
int UncheckedMM(int index) const {
|
||||
auto *node = this->GetNode(index);
|
||||
if (this->IsNil(index)) {
|
||||
index = this->GetRMost();
|
||||
node = this->GetNode(index);
|
||||
} else if (this->IsNil(node->m_left)) {
|
||||
int parent = node->m_parent;
|
||||
Node *p;
|
||||
|
||||
for (p = this->GetNode(parent); !this->IsNil(parent) && index == p->m_left; p = this->GetNode(parent)) {
|
||||
index = parent;
|
||||
node = p;
|
||||
parent = p->m_parent;
|
||||
}
|
||||
|
||||
if (!this->IsNil(index)) {
|
||||
index = parent;
|
||||
node = p;
|
||||
}
|
||||
} else {
|
||||
index = this->FindMaxInSubtree(node->m_left);
|
||||
node = this->GetNode(index);
|
||||
}
|
||||
|
||||
if (this->IsNil(index)) {
|
||||
return Index_Leaf;
|
||||
} else {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
int UncheckedPP(int index) const {
|
||||
auto *node = this->GetNode(index);
|
||||
|
||||
if (!this->IsNil(index)) {
|
||||
if (this->IsNil(node->m_right)) {
|
||||
int parent = node->m_parent;
|
||||
Node *p;
|
||||
|
||||
for (p = this->GetNode(parent); !this->IsNil(parent) && index == p->m_right; p = this->GetNode(parent)) {
|
||||
index = parent;
|
||||
node = p;
|
||||
parent = p->m_parent;
|
||||
}
|
||||
|
||||
index = parent;
|
||||
node = p;
|
||||
} else {
|
||||
index = this->FindMinInSubtree(node->m_right);
|
||||
node = this->GetNode(index);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->IsNil(index)) {
|
||||
return Index_Leaf;
|
||||
} else {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
public:
|
||||
void Initialize(size_t num_elements, void *buffer, size_t buffer_size) {
|
||||
AMS_ASSERT(num_elements <= max_size);
|
||||
|
||||
return this->InitializeImpl(static_cast<int>(num_elements), buffer, buffer_size);
|
||||
}
|
||||
|
||||
iterator begin() { return iterator(*this); }
|
||||
const_iterator begin() const { return const_iterator(*this); }
|
||||
|
||||
iterator end() { return m_end_iterator; }
|
||||
const_iterator end() const { return m_end_iterator; }
|
||||
|
||||
size_t size() const { return m_header->cur_elements; }
|
||||
|
||||
void clear() {
|
||||
const auto num_elements = m_header->max_elements;
|
||||
const auto buffer_size = m_header->buffer_size;
|
||||
AMS_ASSERT(buffer_size == static_cast<u32>(GetRequiredMemorySize(num_elements)));
|
||||
|
||||
return this->InitializeMemory(num_elements, buffer_size, impl::AvailableIndexFinder::GetSignature());
|
||||
}
|
||||
|
||||
bool erase(const Member &elem) {
|
||||
const auto range = this->equal_range(elem);
|
||||
if (range.first != range.last) {
|
||||
this->EraseByIndex(range.first);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
iterator find(const Member &elem) {
|
||||
if (const auto index = this->FindIndex(elem); index >= 0) {
|
||||
return iterator(*this, index);
|
||||
} else {
|
||||
return this->end();
|
||||
}
|
||||
}
|
||||
|
||||
const_iterator find(const Member &elem) const {
|
||||
if (const auto index = this->FindIndex(elem); index >= 0) {
|
||||
return const_iterator(*this, index);
|
||||
} else {
|
||||
return this->end();
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<iterator, bool> insert(const Member &elem) {
|
||||
const auto index = this->InsertNoHint(false, elem);
|
||||
const auto it = iterator(*this, index);
|
||||
return std::make_pair(it, !this->IsNil(index));
|
||||
}
|
||||
|
||||
IndexPair equal_range(const Member &elem) {
|
||||
/* Get node to start iteration. */
|
||||
auto cur_index = this->GetRoot();
|
||||
auto cur_node = this->GetNode(cur_index);
|
||||
|
||||
auto min_index = Index_Leaf;
|
||||
auto min_node = this->GetNode(min_index);
|
||||
|
||||
auto max_index = Index_Leaf;
|
||||
auto max_node = this->GetNode(max_index);
|
||||
|
||||
/* Iterate until current is leaf, to find min/max. */
|
||||
while (cur_index != Index_Leaf) {
|
||||
if (Compare{}(cur_node->m_data, elem)) {
|
||||
cur_index = cur_node->m_right;
|
||||
cur_node = this->GetNode(cur_index);
|
||||
} else {
|
||||
if (max_index == Index_Leaf && Compare{}(elem, cur_node->m_data)) {
|
||||
max_index = cur_index;
|
||||
max_node = this->GetNode(max_index);
|
||||
}
|
||||
min_index = cur_index;
|
||||
min_node = this->GetNode(min_index);
|
||||
|
||||
cur_index = cur_node->m_left;
|
||||
cur_node = this->GetNode(cur_index);
|
||||
}
|
||||
}
|
||||
|
||||
/* Iterate again, to find correct range extent for max. */
|
||||
cur_index = (max_index == Index_Leaf) ? this->GetRoot() : max_node->m_left;
|
||||
cur_node = this->GetNode(cur_index);
|
||||
while (cur_index != Index_Leaf) {
|
||||
if (Compare{}(elem, cur_node->m_data)) {
|
||||
max_index = cur_index;
|
||||
max_node = cur_node;
|
||||
cur_index = cur_node->m_left;
|
||||
} else {
|
||||
cur_index = cur_node->m_right;
|
||||
}
|
||||
cur_node = this->GetNode(cur_index);
|
||||
}
|
||||
|
||||
AMS_UNUSED(min_node);
|
||||
return IndexPair{min_index, max_index};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -13,8 +13,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* Scope guard logic lovingly taken from Andrei Alexandrescu's "Systemic Error Handling in C++" */
|
||||
/* Scope guard logic lovingly taken from Andrei Alexandrescu's "Systemic Error Handling in C++" */
|
||||
#pragma once
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
|
||||
@@ -69,7 +69,21 @@ namespace ams::mitm::socket::resolver {
|
||||
"127.0.0.1 receive-%.dg.srv.nintendo.net receive-%.er.srv.nintendo.net\n";
|
||||
|
||||
constinit os::SdkMutex g_redirection_lock;
|
||||
std::unordered_map<std::string, ams::socket::InAddrT> g_redirection_map;
|
||||
std::vector<std::pair<std::string, ams::socket::InAddrT>> g_redirection_list;
|
||||
|
||||
void RemoveRedirection(const char *hostname) {
|
||||
for (auto it = g_redirection_list.begin(); it != g_redirection_list.end(); ++it) {
|
||||
if (std::strcmp(it->first.c_str(), hostname) == 0) {
|
||||
g_redirection_list.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddRedirection(const char *hostname, ams::socket::InAddrT addr) {
|
||||
RemoveRedirection(hostname);
|
||||
g_redirection_list.emplace(g_redirection_list.begin(), std::string(hostname), addr);
|
||||
}
|
||||
|
||||
constinit char g_specific_emummc_hosts_path[0x40] = {};
|
||||
|
||||
@@ -111,6 +125,8 @@ namespace ams::mitm::socket::resolver {
|
||||
current_address = 0;
|
||||
work = static_cast<u32>(c - '0');
|
||||
state = State::Ip1;
|
||||
} else if (c == '\n') {
|
||||
state = State::BeginLine;
|
||||
} else {
|
||||
state = State::IgnoredLine;
|
||||
}
|
||||
@@ -206,7 +222,7 @@ namespace ams::mitm::socket::resolver {
|
||||
AMS_ABORT_UNLESS(work < sizeof(current_hostname));
|
||||
current_hostname[work] = '\x00';
|
||||
|
||||
g_redirection_map[static_cast<const char *>(current_hostname)] = current_address;
|
||||
AddRedirection(current_hostname, current_address);
|
||||
work = 0;
|
||||
|
||||
if (c == '\n') {
|
||||
@@ -229,7 +245,7 @@ namespace ams::mitm::socket::resolver {
|
||||
AMS_ABORT_UNLESS(work < sizeof(current_hostname));
|
||||
current_hostname[work] = '\x00';
|
||||
|
||||
g_redirection_map[static_cast<const char *>(current_hostname)] = current_address;
|
||||
AddRedirection(current_hostname, current_address);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +309,7 @@ namespace ams::mitm::socket::resolver {
|
||||
std::scoped_lock lk(g_redirection_lock);
|
||||
|
||||
/* Clear the redirections map. */
|
||||
g_redirection_map.clear();
|
||||
g_redirection_list.clear();
|
||||
|
||||
/* Open log file. */
|
||||
::FsFile log_file;
|
||||
@@ -362,7 +378,7 @@ namespace ams::mitm::socket::resolver {
|
||||
|
||||
/* Print the redirections. */
|
||||
Log(log_file, "Redirections:\n");
|
||||
for (const auto &[host, address] : g_redirection_map) {
|
||||
for (const auto &[host, address] : g_redirection_list) {
|
||||
Log(log_file, " `%s` -> %u.%u.%u.%u\n", host.c_str(), (address >> 0) & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF, (address >> 24) & 0xFF);
|
||||
}
|
||||
}
|
||||
@@ -370,7 +386,7 @@ namespace ams::mitm::socket::resolver {
|
||||
bool GetRedirectedHostByName(ams::socket::InAddrT *out, const char *hostname) {
|
||||
std::scoped_lock lk(g_redirection_lock);
|
||||
|
||||
for (const auto &[host, address] : g_redirection_map) {
|
||||
for (const auto &[host, address] : g_redirection_list) {
|
||||
if (wildcardcmp(host.c_str(), hostname)) {
|
||||
*out = address;
|
||||
return true;
|
||||
|
||||
@@ -111,10 +111,12 @@ namespace ams::mitm::socket::resolver {
|
||||
R_UNLESS(GetRedirectedHostByName(std::addressof(redirect_addr), hostname), sm::mitm::ResultShouldForwardToSession());
|
||||
|
||||
u16 port = 0;
|
||||
for (const char *cur = reinterpret_cast<const char *>(srv.GetPointer()); *cur != 0; ++cur) {
|
||||
AMS_ABORT_UNLESS(std::isdigit(static_cast<unsigned char>(*cur)));
|
||||
port *= 10;
|
||||
port += *cur - '0';
|
||||
if (srv.GetPointer() != nullptr) {
|
||||
for (const char *cur = reinterpret_cast<const char *>(srv.GetPointer()); *cur != 0; ++cur) {
|
||||
AMS_ABORT_UNLESS(std::isdigit(static_cast<unsigned char>(*cur)));
|
||||
port *= 10;
|
||||
port += *cur - '0';
|
||||
}
|
||||
}
|
||||
|
||||
LogDebug("[%016lx]: Redirecting %s:%u to %u.%u.%u.%u\n", this->client_info.program_id.value, hostname, port, (redirect_addr >> 0) & 0xFF, (redirect_addr >> 8) & 0xFF, (redirect_addr >> 16) & 0xFF, (redirect_addr >> 24) & 0xFF);
|
||||
@@ -162,10 +164,12 @@ namespace ams::mitm::socket::resolver {
|
||||
R_UNLESS(GetRedirectedHostByName(std::addressof(redirect_addr), hostname), sm::mitm::ResultShouldForwardToSession());
|
||||
|
||||
u16 port = 0;
|
||||
for (const char *cur = reinterpret_cast<const char *>(srv.GetPointer()); *cur != 0; ++cur) {
|
||||
AMS_ABORT_UNLESS(std::isdigit(static_cast<unsigned char>(*cur)));
|
||||
port *= 10;
|
||||
port += *cur - '0';
|
||||
if (srv.GetPointer() != nullptr) {
|
||||
for (const char *cur = reinterpret_cast<const char *>(srv.GetPointer()); *cur != 0; ++cur) {
|
||||
AMS_ABORT_UNLESS(std::isdigit(static_cast<unsigned char>(*cur)));
|
||||
port *= 10;
|
||||
port += *cur - '0';
|
||||
}
|
||||
}
|
||||
|
||||
LogDebug("[%016lx]: Redirecting %s:%u to %u.%u.%u.%u\n", this->client_info.program_id.value, hostname, port, (redirect_addr >> 0) & 0xFF, (redirect_addr >> 8) & 0xFF, (redirect_addr >> 16) & 0xFF, (redirect_addr >> 24) & 0xFF);
|
||||
|
||||
@@ -380,7 +380,7 @@ namespace ams::mitm::fs {
|
||||
|
||||
/* Try to get a storage from the cache. */
|
||||
{
|
||||
std::shared_ptr<fs::IStorage> cached_storage = GetStorageCacheEntry(this->client_info.program_id);
|
||||
std::shared_ptr<fs::IStorage> cached_storage = GetStorageCacheEntry(data_id);
|
||||
if (cached_storage != nullptr) {
|
||||
out.SetValue(MakeSharedStorage(cached_storage), target_object_id);
|
||||
return ResultSuccess();
|
||||
@@ -403,7 +403,7 @@ namespace ams::mitm::fs {
|
||||
new_storage = std::move(layered_storage);
|
||||
}
|
||||
|
||||
SetStorageCacheEntry(this->client_info.program_id, &new_storage);
|
||||
SetStorageCacheEntry(data_id, &new_storage);
|
||||
out.SetValue(MakeSharedStorage(new_storage), target_object_id);
|
||||
}
|
||||
|
||||
|
||||
1
thermosphere/.gitignore
vendored
1
thermosphere/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
out
|
||||
@@ -9,13 +9,39 @@ endif
|
||||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/devkitA64/base_rules
|
||||
|
||||
export AMSLIBSDIR := $(TOPDIR)/../libraries
|
||||
|
||||
AMSBRANCH := $(shell git symbolic-ref --short HEAD)
|
||||
AMSHASH = $(shell git rev-parse --short=16 HEAD)
|
||||
AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
|
||||
|
||||
ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
|
||||
AMSREV := $(AMSREV)-dirty
|
||||
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
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
@@ -25,43 +51,62 @@ endif
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := $(notdir $(CURDIR))
|
||||
BUILD := build
|
||||
SOURCES := src src/lib
|
||||
SOURCES := src src/libc src/platform src/gdb $(PLATFORM_SOURCES)
|
||||
DATA := data
|
||||
INCLUDES := include ../common/include
|
||||
INCLUDES :=
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv8-a -mtune=cortex-a57
|
||||
DEFINES := -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
|
||||
|
||||
# Note: -ffixed-x18 and -mgeneral-regs-only are very important and must be enabled
|
||||
ARCH := -march=armv8-a -mtune=cortex-a57 -mgeneral-regs-only -ffixed-x18 -Wno-psabi
|
||||
DEFINES := $(PLATFORM_DEFINES)
|
||||
CFLAGS := \
|
||||
-g \
|
||||
-O2 \
|
||||
-ffunction-sections \
|
||||
-fdata-sections \
|
||||
-mgeneral-regs-only \
|
||||
-fomit-frame-pointer \
|
||||
-std=gnu11 \
|
||||
-Werror \
|
||||
-Wall \
|
||||
-Wno-main \
|
||||
$(ARCH) $(DEFINES)
|
||||
-g \
|
||||
-fmacro-prefix-map=$(TOPDIR)/src/= \
|
||||
-Os \
|
||||
-ffunction-sections \
|
||||
-fdata-sections \
|
||||
-fomit-frame-pointer \
|
||||
-fno-asynchronous-unwind-tables \
|
||||
-fno-unwind-tables \
|
||||
-fno-stack-protector \
|
||||
-fstrict-volatile-bitfields \
|
||||
-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)
|
||||
LDFLAGS = -specs=$(TOPDIR)/linker.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||
ASFLAGS := -g $(ARCH) $(DEFINES)
|
||||
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
|
||||
# include and lib
|
||||
#---------------------------------------------------------------------------------
|
||||
LIBDIRS :=
|
||||
LIBDIRS := $(AMSLIBSDIR)/libvapours
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
@@ -100,7 +145,7 @@ endif
|
||||
|
||||
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
|
||||
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 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)
|
||||
|
||||
.PHONY: $(BUILD) clean all
|
||||
.PHONY: $(BUILD) clean all qemu qemudbg
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
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):
|
||||
@[ -d $@ ] || mkdir -p $@
|
||||
@$(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
|
||||
#---------------------------------------------------------------------------------
|
||||
%.bin.o : %.bin
|
||||
%.bin.o %_bin.h: %.bin
|
||||
#---------------------------------------------------------------------------------
|
||||
@echo $(notdir $<)
|
||||
@$(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)
|
||||
ENTRY(_start)
|
||||
|
||||
PHDRS
|
||||
{
|
||||
main PT_LOAD;
|
||||
}
|
||||
|
||||
MEMORY
|
||||
{
|
||||
mainVa : ORIGIN = 0x7FFFE10000, LENGTH = 2M - 64K
|
||||
}
|
||||
|
||||
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);
|
||||
.rodata : {
|
||||
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))
|
||||
}
|
||||
|
||||
. = ALIGN(8);
|
||||
.data : {
|
||||
*(.data*)
|
||||
}
|
||||
/* Shit we keep in the elf but otherwise discard */
|
||||
.eh_frame_hdr (NOLOAD) : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) } >mainVa :NONE
|
||||
.eh_frame (NOLOAD) : { KEEP (*(.eh_frame)) *(.eh_frame.*) } >mainVa :NONE
|
||||
.gcc_except_table (NOLOAD) : { *(.gcc_except_table .gcc_except_table.*) } >mainVa :NONE
|
||||
.gnu_extab (NOLOAD) : { *(.gnu_extab*) } >mainVa :NONE
|
||||
.exception_ranges (NOLOAD) : { *(.exception_ranges .exception_ranges*) } >mainVa :NONE
|
||||
|
||||
/* Uninitialised data */
|
||||
. = ALIGN(8);
|
||||
PROVIDE(lds_bss_start = .);
|
||||
.bss (NOLOAD) : {
|
||||
*(.bss*) . = ALIGN(8);
|
||||
}
|
||||
PROVIDE(lds_bss_end = .);
|
||||
/* ==================
|
||||
==== Metadata ====
|
||||
================== */
|
||||
|
||||
/* EL2 stack */
|
||||
. = ALIGN(16);
|
||||
. += 0x10000; /* 64 KiB stack */
|
||||
el2_stack_end = .;
|
||||
/* Discard sections that difficult post-processing */
|
||||
/DISCARD/ : { *(.group .comment .note) }
|
||||
|
||||
/* Page align the end of binary */
|
||||
. = ALIGN(512);
|
||||
PROVIDE(lds_el2_thermo_end = .);
|
||||
/* Stabs debugging sections. */
|
||||
.stab 0 : { *(.stab) }
|
||||
.stabstr 0 : { *(.stabstr) }
|
||||
.stab.excl 0 : { *(.stab.excl) }
|
||||
.stab.exclstr 0 : { *(.stab.exclstr) }
|
||||
.stab.index 0 : { *(.stab.index) }
|
||||
.stab.indexstr 0 : { *(.stab.indexstr) }
|
||||
|
||||
/* EL1 stack */
|
||||
. = ALIGN(16);
|
||||
. += 0x10000; /* 64 KiB stack */
|
||||
el1_stack_end = .;
|
||||
/* DWARF debug sections.
|
||||
Symbols in the DWARF debugging sections are relative to the beginning
|
||||
of the section so we begin them at 0. */
|
||||
|
||||
lds_thermo_end = .;
|
||||
/* DWARF 1 */
|
||||
.debug 0 : { *(.debug) }
|
||||
.line 0 : { *(.line) }
|
||||
|
||||
/DISCARD/ : { *(.dynstr*) }
|
||||
/DISCARD/ : { *(.dynamic*) }
|
||||
/DISCARD/ : { *(.plt*) }
|
||||
/DISCARD/ : { *(.interp*) }
|
||||
/DISCARD/ : { *(.gnu*) }
|
||||
/* GNU DWARF 1 extensions */
|
||||
.debug_srcinfo 0 : { *(.debug_srcinfo) }
|
||||
.debug_sfnames 0 : { *(.debug_sfnames) }
|
||||
|
||||
/* DWARF 1.1 and DWARF 2 */
|
||||
.debug_aranges 0 : { *(.debug_aranges) }
|
||||
.debug_pubnames 0 : { *(.debug_pubnames) }
|
||||
|
||||
/* DWARF 2 */
|
||||
.debug_info 0 : { *(.debug_info) }
|
||||
.debug_abbrev 0 : { *(.debug_abbrev) }
|
||||
.debug_line 0 : { *(.debug_line) }
|
||||
.debug_frame 0 : { *(.debug_frame) }
|
||||
.debug_str 0 : { *(.debug_str) }
|
||||
.debug_loc 0 : { *(.debug_loc) }
|
||||
.debug_macinfo 0 : { *(.debug_macinfo) }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
%rename link old_link
|
||||
|
||||
*link:
|
||||
%(old_link) -T %:getenv(TOPDIR /linker.ld) --nmagic --gc-sections
|
||||
|
||||
*startfile:
|
||||
crti%O%s crtbegin%O%s
|
||||
%(old_link) -T %:getenv(TOPDIR /%:getenv(PLATFORM .mem)) -T %:getenv(TOPDIR /linker.ld) -no-pie --nmagic --gc-sections
|
||||
|
||||
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; }
|
||||
};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user