diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
deleted file mode 100644
index 3eaf2a46..00000000
--- a/.github/FUNDING.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-# Donate to us!
-
-https://ko-fi.com/souldbminer
diff --git a/.gitignore b/.gitignore
index 76dc4eee..ccaedb2d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ build
Source/hekate
dist/*
!dist/.gitignore
+Status-Monitor-Overlay.ovl*
\ No newline at end of file
diff --git a/COMPILING.md b/COMPILING.md
index 78b7331b..99a062e7 100644
--- a/COMPILING.md
+++ b/COMPILING.md
@@ -5,4 +5,6 @@ Horizon OC Compilation Instructions
3. Clone the Horizon OC develop branch (``git clone https://github.com/Horizon-OC/Horizon-OC.git --recurse-submodules``)
4. Run ``./build.sh`` in the root directory
- If you want to compile with extensions, append ``--ext``
+ - To skip components, append ``-s``/``--skip`` followed by one or more of: ``ldr`` (loader), ``clk`` (hoc-clk), ``hek`` (hekate), ``btb`` (benchmark toolbox), ``exos`` (exosphere), ``sm`` (status-monitor download), ``zip`` (dist.zip packaging). Example: ``./build.sh --ext -s ldr hek``
+ - Building the custom Hekate (``--ext`` without skipping ``hek``) additionally requires devkitARM with ``DEVKITARM`` exported
- Installing ``ccache`` is optional; if it is on your PATH it is used automatically to speed up rebuilds
diff --git a/README.md b/README.md
index 46bd07f8..54efce12 100644
--- a/README.md
+++ b/README.md
@@ -34,9 +34,9 @@ It enables advanced CPU, GPU, and RAM tuning with user-friendly configuration to
## Default clocks
-* **CPU:** Up to 1963MHz (Mariko) / 1785MHz (Erista)
-* **GPU:** Up to 1075MHz (Mariko) / 921MHz (Erista)
-* **RAM:** Up to 1866/2133MHz (Mariko) / 1600MHz (Erista)
+* **CPU:** 1963MHz (Mariko) / 1785MHz (Erista)
+* **GPU:** 1152MHz (Mariko) / 921MHz (Erista)
+* **RAM:** 2133MHz (Mariko) / 1600MHz (Erista)
* Over/undervolting support
* Built-in configurator
* Compatible with most homebrew
@@ -51,7 +51,7 @@ It enables advanced CPU, GPU, and RAM tuning with user-friendly configuration to
* [Atmosphere](https://github.com/Atmosphere-NX/Atmosphere)
* [Ultrahand Overlay](https://github.com/ppkantorski/Ultrahand-Overlay)
-2. Download and extract the **Horizon OC Package** to the root of your SD card.
+2. Download and extract **Horizon OC** to the root of your SD card.
3. If using **Hekate**, edit `hekate_ipl.ini` to include:
```
@@ -83,13 +83,23 @@ We don't accept donations. Put your spare cash into a charity instead :)
Refer to COMPILATION.md
---
-## Clock table
+
+Higher clocks are achievable via undervolting or further configuration, refer to this table for more details.
+The averages are only for reference. Each ram is different and must be tuned independently. Refer to the guide for more details!
+
+
+Clock table
### MEM clocks (mhz)
-
-* 3200 → max on mariko, JEDEC.
+* 3400 → max on mariko.
+* 3366
+* 3333
+* 3300
+* 3266
+* 3233
+* 3200 → JEDEC, Hynix NEE/x267 Average
* 3166
-* 3133
+* 3133 → Micron WT:B Average
* 3100
* 3066
* 3033
@@ -98,20 +108,20 @@ Refer to COMPILATION.md
* 2933 → JEDEC.
* 2900
* 2866
-* 2833
+* 2833 → Samsung AA-MGCL Average
* 2800
* 2766
-* 2733
+* 2733 → Samsung AM-MGCJ/Micron WT:E Average
* 2700
-* 2666 → JEDEC.
+* 2666 → JEDEC, Micron WT:F Average
* 2633
* 2600
-* 2566
+* 2566 → Samsung AB-MGCL Average
* 2533
* 2500
* 2466
* 2433
-* 2400 → max on erista, JEDEC.
+* 2400 → max on erista, JEDEC, Hynix NME Average
* 2366
* 2333
* 2300
@@ -124,7 +134,7 @@ Refer to COMPILATION.md
* 2066
* 2033
* 2000
-* 1996 → JEDEC standard
+* 1996 → JEDEC, Erista Average
* 1966
* 1933
* 1900
@@ -138,12 +148,18 @@ Refer to COMPILATION.md
* 1633
* 1600 → official docked, boost mode, Erista JEDEC standard max (3200 Modules), JEDEC.
* 1331 → official handheld, JEDEC.
-* 1065
-* 800
-* 665
+* 1065 → Erista only
+* 800 → Erista only
+* 665 → Erista only
+* 408 → Erista only
+* 204 → sleep mode
+* 102 → Erista only
+* 68 → Erista only
+* 40 → Erista only
### CPU clocks (mhz)
-* 2703 → mariko absolute max, dangerous
+* 2805 → mariko absolute max, dangerous
+* 2703
* 2601 → unsafe
* 2499
* 2397 → mariko safe max with UV (low speedo)
@@ -166,9 +182,13 @@ Refer to COMPILATION.md
* 612 → sleep mode
### GPU clocks (mhz)
-* 1536 → absolute max clock on mariko. Dangerous
-* 1497 → max mariko unsafe clock
+* 1574 → absolute max clock on mariko. Dangerous
+* 1536
+* 1497 → max mariko unsafe clock
+* 1459
+* 1420
* 1382 → Max mariko "safe" clock
+* 1344
* 1305
* 1267 → NVIDIA T214(mariko) rating
* 1228 → mariko High UV safe clock
@@ -195,12 +215,15 @@ Refer to COMPILATION.md
3. On Mariko, cap with hiOpt is 614MHz, with hiOpt-15mV it is 691MHz and with High UV it's 768MHz
4. Clocks higher than 768MHz on erista need the official charger is plugged in.
+
+
---
## Credits
-* **Lightos's Cat** - Cat
+* **Lightos's Cats** - Cats
* **Souldbminer** - hoc-clk and loader development
* **Lightos** - Loader patches development, hoc-clk development, guides
+* **HAZEL** - Perfect DRAM timings help
* **TDRR** - HOC Logo Design
* **tetetete-ctrl** - Website design
* **SciresM** - Atmosphere CFW
@@ -217,6 +240,9 @@ Refer to COMPILATION.md
* **Dominatorul, Samybigio, Arcdelta, Miki, Happy, Winnerboi77, Blaise, Alvise, agjeococh, frost, letum00, and Xenshen** - Testing
* **Samybigio2011, Miki** - Italian translations
* **angelblaster** - Korean translations
-* **q1332348216-glitch** - Chinese translations
+* **q1332348216-glitch, nangongjing1, xinluochenjiang** - Chinese translations
* **th3-ne0undr5c0r** - French translations
+* **redraz** - Russian translations
+* **Wivi** - Portuguese translations
+* **Niklas080208** - JSON language support for sysmodule notifications.
* **Nvidia** - [Tegra X1 Technical Reference Manual](https://developer.nvidia.com/embedded/dlc/tegra-x1-technical-reference-manual), soctherm driver, L4T
diff --git a/Source/Atmosphere-Patches/diff/README.MD b/Source/Atmosphere-Patches/diff/README.MD
new file mode 100644
index 00000000..e43f3005
--- /dev/null
+++ b/Source/Atmosphere-Patches/diff/README.MD
@@ -0,0 +1 @@
+This contains a diff all modifications done to exosphere
diff --git a/Source/Atmosphere-Patches/diff/secmon_memory_layout.diff b/Source/Atmosphere-Patches/diff/secmon_memory_layout.diff
new file mode 100644
index 00000000..1cb6ab99
--- /dev/null
+++ b/Source/Atmosphere-Patches/diff/secmon_memory_layout.diff
@@ -0,0 +1,57 @@
+131,156c131,159
+< #define AMS_SECMON_FOREACH_DEVICE_REGION(HANDLER, ...) \
+< HANDLER(GicDistributor, Empty, UINT64_C(0x50041000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(GicCpuInterface, GicDistributor, UINT64_C(0x50042000), UINT64_C(0x2000), true, ## __VA_ARGS__) \
+< HANDLER(Uart, GicCpuInterface, UINT64_C(0x70006000), UINT64_C(0x1000), false, ## __VA_ARGS__) \
+< HANDLER(ClkRst, Uart, UINT64_C(0x60006000), UINT64_C(0x1000), false, ## __VA_ARGS__) \
+< HANDLER(RtcPmc, ClkRst, UINT64_C(0x7000E000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(Timer, RtcPmc, UINT64_C(0x60005000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(System, Timer, UINT64_C(0x6000C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(SecurityEngine, System, UINT64_C(0x70012000), UINT64_C(0x2000), true, ## __VA_ARGS__) \
+< HANDLER(SecurityEngine2, SecurityEngine, UINT64_C(0x70412000), UINT64_C(0x2000), true, ## __VA_ARGS__) \
+< HANDLER(SysCtr0, SecurityEngine2, UINT64_C(0x700F0000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(MemoryController, SysCtr0, UINT64_C(0x70019000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(FuseKFuse, MemoryController, UINT64_C(0x7000F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(ApbMisc, FuseKFuse, UINT64_C(0x70000000), UINT64_C(0x4000), true, ## __VA_ARGS__) \
+< HANDLER(FlowController, ApbMisc, UINT64_C(0x60007000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(BootloaderParams, FlowController, UINT64_C(0x40000000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(I2c5, BootloaderParams, UINT64_C(0x7000D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(Gpio, I2c5, UINT64_C(0x6000D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(I2c1, Gpio, UINT64_C(0x7000C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(ExceptionVectors, I2c1, UINT64_C(0x6000F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(MemoryController0, ExceptionVectors, UINT64_C(0x7001C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(MemoryController1, MemoryController0, UINT64_C(0x7001D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(Sdmmc, MemoryController1, UINT64_C(0x700B0000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(Disp1, Sdmmc, UINT64_C(0x54200000), UINT64_C(0x3000), true, ## __VA_ARGS__) \
+< HANDLER(Dsi, Disp1, UINT64_C(0x54300000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+< HANDLER(MipiCal, Dsi, UINT64_C(0x700E3000), UINT64_C(0x1000), true, ## __VA_ARGS__)
+---
+> #define AMS_SECMON_FOREACH_DEVICE_REGION(HANDLER, ...) \
+> HANDLER(GicDistributor, Empty, UINT64_C(0x50041000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(GicCpuInterface, GicDistributor, UINT64_C(0x50042000), UINT64_C(0x2000), true, ## __VA_ARGS__) \
+> HANDLER(Uart, GicCpuInterface, UINT64_C(0x70006000), UINT64_C(0x1000), false, ## __VA_ARGS__) \
+> HANDLER(ClkRst, Uart, UINT64_C(0x60006000), UINT64_C(0x1000), false, ## __VA_ARGS__) \
+> HANDLER(RtcPmc, ClkRst, UINT64_C(0x7000E000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(Timer, RtcPmc, UINT64_C(0x60005000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(System, Timer, UINT64_C(0x6000C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(SecurityEngine, System, UINT64_C(0x70012000), UINT64_C(0x2000), true, ## __VA_ARGS__) \
+> HANDLER(SecurityEngine2, SecurityEngine, UINT64_C(0x70412000), UINT64_C(0x2000), true, ## __VA_ARGS__) \
+> HANDLER(SysCtr0, SecurityEngine2, UINT64_C(0x700F0000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(MemoryController, SysCtr0, UINT64_C(0x70019000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(ExternalMemoryController, MemoryController, UINT64_C(0x7001b000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(FuseKFuse, ExternalMemoryController, UINT64_C(0x7000F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(ApbMisc, FuseKFuse, UINT64_C(0x70000000), UINT64_C(0x4000), true, ## __VA_ARGS__) \
+> HANDLER(FlowController, ApbMisc, UINT64_C(0x60007000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(BootloaderParams, FlowController, UINT64_C(0x40000000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(I2c5, BootloaderParams, UINT64_C(0x7000D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(Gpio, I2c5, UINT64_C(0x6000D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(I2c1, Gpio, UINT64_C(0x7000C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(ExceptionVectors, I2c1, UINT64_C(0x6000F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(MemoryController0, ExceptionVectors, UINT64_C(0x7001C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(MemoryController1, MemoryController0, UINT64_C(0x7001D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(ExternalMemoryController0, MemoryController1, UINT64_C(0x7001E000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(ExternalMemoryController1, ExternalMemoryController0, UINT64_C(0x7001F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(Sdmmc, ExternalMemoryController1, UINT64_C(0x700B0000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(Disp1, Sdmmc, UINT64_C(0x54200000), UINT64_C(0x3000), true, ## __VA_ARGS__) \
+> HANDLER(Dsi, Disp1, UINT64_C(0x54300000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+> HANDLER(MipiCal, Dsi, UINT64_C(0x700E3000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
diff --git a/Source/Atmosphere-Patches/diff/secmon_setup.diff b/Source/Atmosphere-Patches/diff/secmon_setup.diff
new file mode 100644
index 00000000..5b796e03
--- /dev/null
+++ b/Source/Atmosphere-Patches/diff/secmon_setup.diff
@@ -0,0 +1,29 @@
+1221c1221
+< const auto target_fw = GetTargetFirmware();
+---
+> // const auto target_fw = GetTargetFirmware();
+1223,1226c1223,1226
+< if (target_fw >= TargetFirmware_2_0_0) {
+< /* Set the PMC secure. */
+< reg::ReadWrite(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0, SLAVE_SECURITY_REG_BITS_ENUM(0, PMC, ENABLE));
+< }
+---
+> // if (target_fw >= TargetFirmware_2_0_0) {
+> // /* Set the PMC secure. */ I REFUSE
+> // reg::ReadWrite(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0, SLAVE_SECURITY_REG_BITS_ENUM(0, PMC, ENABLE));
+> // }
+1228,1233c1228,1234
+< if (target_fw >= TargetFirmware_4_0_0) {
+< /* Set the MC secure. */
+< reg::ReadWrite(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG1_0, SLAVE_SECURITY_REG_BITS_ENUM(1, MC0, ENABLE),
+< SLAVE_SECURITY_REG_BITS_ENUM(1, MC1, ENABLE),
+< SLAVE_SECURITY_REG_BITS_ENUM(1, MCB, ENABLE));
+< }
+---
+> // if (target_fw >= TargetFirmware_4_0_0) {
+> // /* Set the MC secure. */ I REFUSE
+> // reg::ReadWrite(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG1_0, SLAVE_SECURITY_REG_BITS_ENUM(1, MC0, ENABLE),
+> // SLAVE_SECURITY_REG_BITS_ENUM(1, MC1, ENABLE),
+> // SLAVE_SECURITY_REG_BITS_ENUM(1, MCB, ENABLE));
+> // }
+> return;
diff --git a/Source/Atmosphere-Patches/diff/secmon_smc_handler.diff b/Source/Atmosphere-Patches/diff/secmon_smc_handler.diff
new file mode 100644
index 00000000..ee25f585
--- /dev/null
+++ b/Source/Atmosphere-Patches/diff/secmon_smc_handler.diff
@@ -0,0 +1,2 @@
+154a155
+> { 0xF0000005, Restriction_None, SmcShowError },
diff --git a/Source/Atmosphere-Patches/diff/secmon_smc_register_access.diff b/Source/Atmosphere-Patches/diff/secmon_smc_register_access.diff
new file mode 100644
index 00000000..e4c4375a
--- /dev/null
+++ b/Source/Atmosphere-Patches/diff/secmon_smc_register_access.diff
@@ -0,0 +1,22 @@
+100a101,105
+> #include "secmon_define_emc_access_table.inc"
+> #include "secmon_define_emc01_access_table.inc"
+> #include "secmon_define_flow_controller_access_table.inc"
+> #include "secmon_define_exception_vectors_access_table.inc"
+> #include "secmon_define_rtc_pmc_access_table.inc"
+104,107c109,118
+< { PmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDevicePmc.GetAddress(), PmcAccessTable::Address, PmcAccessTable::Size, },
+< { McAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceMemoryController.GetAddress(), McAccessTable::Address, McAccessTable::Size, },
+< { Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController0.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController0.GetAddress(), Mc01AccessTable::Size, },
+< { Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController1.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController1.GetAddress(), Mc01AccessTable::Size, },
+---
+> { PmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDevicePmc.GetAddress(), PmcAccessTable::Address, PmcAccessTable::Size, },
+> { McAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceMemoryController.GetAddress(), McAccessTable::Address, McAccessTable::Size, },
+> { EmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceExternalMemoryController.GetAddress(), EmcAccessTable::Address, EmcAccessTable::Size, },
+> { Emc01AccessTable::ReducedAccessTable.data(), Emc01AccessTable::Address + MemoryRegionVirtualDeviceExternalMemoryController0.GetAddress(), Emc01AccessTable::Address + MemoryRegionPhysicalDeviceExternalMemoryController0.GetAddress(), Emc01AccessTable::Size, },
+> { Emc01AccessTable::ReducedAccessTable.data(), Emc01AccessTable::Address + MemoryRegionVirtualDeviceExternalMemoryController1.GetAddress(), Emc01AccessTable::Address + MemoryRegionPhysicalDeviceExternalMemoryController1.GetAddress(), Emc01AccessTable::Size, },
+> { FlowControllerAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceFlowController.GetAddress(), FlowControllerAccessTable::Address, FlowControllerAccessTable::Size, },
+> { ExceptionVectorsAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceExceptionVectors.GetAddress(), ExceptionVectorsAccessTable::Address, ExceptionVectorsAccessTable::Size, },
+> { RtcPmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceRtcPmc.GetAddress(), RtcPmcAccessTable::Address, RtcPmcAccessTable::Size, },
+> { Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController0.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController0.GetAddress(), Mc01AccessTable::Size, },
+> { Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController1.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController1.GetAddress(), Mc01AccessTable::Size, },
diff --git a/Source/Atmosphere-Patches/secmon_define_emc01_access_table.inc b/Source/Atmosphere-Patches/secmon_define_emc01_access_table.inc
new file mode 100644
index 00000000..3a34e1dc
--- /dev/null
+++ b/Source/Atmosphere-Patches/secmon_define_emc01_access_table.inc
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#define __ACCESS_TABLE_NAME__ Emc01AccessTable
+#define __ACCESS_TABLE_ADDRESS__ 0
+#define __ACCESS_TABLE_INC__ "secmon_emc01_access_table_data.inc"
+
+#include "secmon_define_access_table.inc"
+
+#undef __ACCESS_TABLE_INC__
+#undef __ACCESS_TABLE_ADDRESS__
+#undef __ACCESS_TABLE_NAME__
diff --git a/Source/Atmosphere-Patches/secmon_emc01_access_table_data.inc b/Source/Atmosphere-Patches/secmon_emc01_access_table_data.inc
new file mode 100644
index 00000000..a3718c5e
--- /dev/null
+++ b/Source/Atmosphere-Patches/secmon_emc01_access_table_data.inc
@@ -0,0 +1,1040 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+SetRegisterAllowed(0x0);
+SetRegisterAllowed(0x4);
+SetRegisterAllowed(0x8);
+SetRegisterAllowed(0xC);
+SetRegisterAllowed(0x10);
+SetRegisterAllowed(0x14);
+SetRegisterAllowed(0x18);
+SetRegisterAllowed(0x1C);
+SetRegisterAllowed(0x20);
+SetRegisterAllowed(0x24);
+SetRegisterAllowed(0x28);
+SetRegisterAllowed(0x2C);
+SetRegisterAllowed(0x30);
+SetRegisterAllowed(0x34);
+SetRegisterAllowed(0x38);
+SetRegisterAllowed(0x3C);
+SetRegisterAllowed(0x40);
+SetRegisterAllowed(0x44);
+SetRegisterAllowed(0x48);
+SetRegisterAllowed(0x4C);
+SetRegisterAllowed(0x50);
+SetRegisterAllowed(0x54);
+SetRegisterAllowed(0x58);
+SetRegisterAllowed(0x5C);
+SetRegisterAllowed(0x60);
+SetRegisterAllowed(0x64);
+SetRegisterAllowed(0x68);
+SetRegisterAllowed(0x6C);
+SetRegisterAllowed(0x70);
+SetRegisterAllowed(0x74);
+SetRegisterAllowed(0x78);
+SetRegisterAllowed(0x7C);
+SetRegisterAllowed(0x80);
+SetRegisterAllowed(0x84);
+SetRegisterAllowed(0x88);
+SetRegisterAllowed(0x8C);
+SetRegisterAllowed(0x90);
+SetRegisterAllowed(0x94);
+SetRegisterAllowed(0x98);
+SetRegisterAllowed(0x9C);
+SetRegisterAllowed(0xA0);
+SetRegisterAllowed(0xA4);
+SetRegisterAllowed(0xA8);
+SetRegisterAllowed(0xAC);
+SetRegisterAllowed(0xB0);
+SetRegisterAllowed(0xB4);
+SetRegisterAllowed(0xB8);
+SetRegisterAllowed(0xBC);
+SetRegisterAllowed(0xC0);
+SetRegisterAllowed(0xC4);
+SetRegisterAllowed(0xC8);
+SetRegisterAllowed(0xCC);
+SetRegisterAllowed(0xD0);
+SetRegisterAllowed(0xD4);
+SetRegisterAllowed(0xD8);
+SetRegisterAllowed(0xDC);
+SetRegisterAllowed(0xE0);
+SetRegisterAllowed(0xE4);
+SetRegisterAllowed(0xE8);
+SetRegisterAllowed(0xEC);
+SetRegisterAllowed(0xF0);
+SetRegisterAllowed(0xF4);
+SetRegisterAllowed(0xF8);
+SetRegisterAllowed(0xFC);
+SetRegisterAllowed(0x100);
+SetRegisterAllowed(0x104);
+SetRegisterAllowed(0x108);
+SetRegisterAllowed(0x10C);
+SetRegisterAllowed(0x110);
+SetRegisterAllowed(0x114);
+SetRegisterAllowed(0x118);
+SetRegisterAllowed(0x11C);
+SetRegisterAllowed(0x120);
+SetRegisterAllowed(0x124);
+SetRegisterAllowed(0x128);
+SetRegisterAllowed(0x12C);
+SetRegisterAllowed(0x130);
+SetRegisterAllowed(0x134);
+SetRegisterAllowed(0x138);
+SetRegisterAllowed(0x13C);
+SetRegisterAllowed(0x140);
+SetRegisterAllowed(0x144);
+SetRegisterAllowed(0x148);
+SetRegisterAllowed(0x14C);
+SetRegisterAllowed(0x150);
+SetRegisterAllowed(0x154);
+SetRegisterAllowed(0x158);
+SetRegisterAllowed(0x15C);
+SetRegisterAllowed(0x160);
+SetRegisterAllowed(0x164);
+SetRegisterAllowed(0x168);
+SetRegisterAllowed(0x16C);
+SetRegisterAllowed(0x170);
+SetRegisterAllowed(0x174);
+SetRegisterAllowed(0x178);
+SetRegisterAllowed(0x17C);
+SetRegisterAllowed(0x180);
+SetRegisterAllowed(0x184);
+SetRegisterAllowed(0x188);
+SetRegisterAllowed(0x18C);
+SetRegisterAllowed(0x190);
+SetRegisterAllowed(0x194);
+SetRegisterAllowed(0x198);
+SetRegisterAllowed(0x19C);
+SetRegisterAllowed(0x1A0);
+SetRegisterAllowed(0x1A4);
+SetRegisterAllowed(0x1A8);
+SetRegisterAllowed(0x1AC);
+SetRegisterAllowed(0x1B0);
+SetRegisterAllowed(0x1B4);
+SetRegisterAllowed(0x1B8);
+SetRegisterAllowed(0x1BC);
+SetRegisterAllowed(0x1C0);
+SetRegisterAllowed(0x1C4);
+SetRegisterAllowed(0x1C8);
+SetRegisterAllowed(0x1CC);
+SetRegisterAllowed(0x1D0);
+SetRegisterAllowed(0x1D4);
+SetRegisterAllowed(0x1D8);
+SetRegisterAllowed(0x1DC);
+SetRegisterAllowed(0x1E0);
+SetRegisterAllowed(0x1E4);
+SetRegisterAllowed(0x1E8);
+SetRegisterAllowed(0x1EC);
+SetRegisterAllowed(0x1F0);
+SetRegisterAllowed(0x1F4);
+SetRegisterAllowed(0x1F8);
+SetRegisterAllowed(0x1FC);
+SetRegisterAllowed(0x200);
+SetRegisterAllowed(0x204);
+SetRegisterAllowed(0x208);
+SetRegisterAllowed(0x20C);
+SetRegisterAllowed(0x210);
+SetRegisterAllowed(0x214);
+SetRegisterAllowed(0x218);
+SetRegisterAllowed(0x21C);
+SetRegisterAllowed(0x220);
+SetRegisterAllowed(0x224);
+SetRegisterAllowed(0x228);
+SetRegisterAllowed(0x22C);
+SetRegisterAllowed(0x230);
+SetRegisterAllowed(0x234);
+SetRegisterAllowed(0x238);
+SetRegisterAllowed(0x23C);
+SetRegisterAllowed(0x240);
+SetRegisterAllowed(0x244);
+SetRegisterAllowed(0x248);
+SetRegisterAllowed(0x24C);
+SetRegisterAllowed(0x250);
+SetRegisterAllowed(0x254);
+SetRegisterAllowed(0x258);
+SetRegisterAllowed(0x25C);
+SetRegisterAllowed(0x260);
+SetRegisterAllowed(0x264);
+SetRegisterAllowed(0x268);
+SetRegisterAllowed(0x26C);
+SetRegisterAllowed(0x270);
+SetRegisterAllowed(0x274);
+SetRegisterAllowed(0x278);
+SetRegisterAllowed(0x27C);
+SetRegisterAllowed(0x280);
+SetRegisterAllowed(0x284);
+SetRegisterAllowed(0x288);
+SetRegisterAllowed(0x28C);
+SetRegisterAllowed(0x290);
+SetRegisterAllowed(0x294);
+SetRegisterAllowed(0x298);
+SetRegisterAllowed(0x29C);
+SetRegisterAllowed(0x2A0);
+SetRegisterAllowed(0x2A4);
+SetRegisterAllowed(0x2A8);
+SetRegisterAllowed(0x2AC);
+SetRegisterAllowed(0x2B0);
+SetRegisterAllowed(0x2B4);
+SetRegisterAllowed(0x2B8);
+SetRegisterAllowed(0x2BC);
+SetRegisterAllowed(0x2C0);
+SetRegisterAllowed(0x2C4);
+SetRegisterAllowed(0x2C8);
+SetRegisterAllowed(0x2CC);
+SetRegisterAllowed(0x2D0);
+SetRegisterAllowed(0x2D4);
+SetRegisterAllowed(0x2D8);
+SetRegisterAllowed(0x2DC);
+SetRegisterAllowed(0x2E0);
+SetRegisterAllowed(0x2E4);
+SetRegisterAllowed(0x2E8);
+SetRegisterAllowed(0x2EC);
+SetRegisterAllowed(0x2F0);
+SetRegisterAllowed(0x2F4);
+SetRegisterAllowed(0x2F8);
+SetRegisterAllowed(0x2FC);
+SetRegisterAllowed(0x300);
+SetRegisterAllowed(0x304);
+SetRegisterAllowed(0x308);
+SetRegisterAllowed(0x30C);
+SetRegisterAllowed(0x310);
+SetRegisterAllowed(0x314);
+SetRegisterAllowed(0x318);
+SetRegisterAllowed(0x31C);
+SetRegisterAllowed(0x320);
+SetRegisterAllowed(0x324);
+SetRegisterAllowed(0x328);
+SetRegisterAllowed(0x32C);
+SetRegisterAllowed(0x330);
+SetRegisterAllowed(0x334);
+SetRegisterAllowed(0x338);
+SetRegisterAllowed(0x33C);
+SetRegisterAllowed(0x340);
+SetRegisterAllowed(0x344);
+SetRegisterAllowed(0x348);
+SetRegisterAllowed(0x34C);
+SetRegisterAllowed(0x350);
+SetRegisterAllowed(0x354);
+SetRegisterAllowed(0x358);
+SetRegisterAllowed(0x35C);
+SetRegisterAllowed(0x360);
+SetRegisterAllowed(0x364);
+SetRegisterAllowed(0x368);
+SetRegisterAllowed(0x36C);
+SetRegisterAllowed(0x370);
+SetRegisterAllowed(0x374);
+SetRegisterAllowed(0x378);
+SetRegisterAllowed(0x37C);
+SetRegisterAllowed(0x380);
+SetRegisterAllowed(0x384);
+SetRegisterAllowed(0x388);
+SetRegisterAllowed(0x38C);
+SetRegisterAllowed(0x390);
+SetRegisterAllowed(0x394);
+SetRegisterAllowed(0x398);
+SetRegisterAllowed(0x39C);
+SetRegisterAllowed(0x3A0);
+SetRegisterAllowed(0x3A4);
+SetRegisterAllowed(0x3A8);
+SetRegisterAllowed(0x3AC);
+SetRegisterAllowed(0x3B0);
+SetRegisterAllowed(0x3B4);
+SetRegisterAllowed(0x3B8);
+SetRegisterAllowed(0x3BC);
+SetRegisterAllowed(0x3C0);
+SetRegisterAllowed(0x3C4);
+SetRegisterAllowed(0x3C8);
+SetRegisterAllowed(0x3CC);
+SetRegisterAllowed(0x3D0);
+SetRegisterAllowed(0x3D4);
+SetRegisterAllowed(0x3D8);
+SetRegisterAllowed(0x3DC);
+SetRegisterAllowed(0x3E0);
+SetRegisterAllowed(0x3E4);
+SetRegisterAllowed(0x3E8);
+SetRegisterAllowed(0x3EC);
+SetRegisterAllowed(0x3F0);
+SetRegisterAllowed(0x3F4);
+SetRegisterAllowed(0x3F8);
+SetRegisterAllowed(0x3FC);
+SetRegisterAllowed(0x400);
+SetRegisterAllowed(0x404);
+SetRegisterAllowed(0x408);
+SetRegisterAllowed(0x40C);
+SetRegisterAllowed(0x410);
+SetRegisterAllowed(0x414);
+SetRegisterAllowed(0x418);
+SetRegisterAllowed(0x41C);
+SetRegisterAllowed(0x420);
+SetRegisterAllowed(0x424);
+SetRegisterAllowed(0x428);
+SetRegisterAllowed(0x42C);
+SetRegisterAllowed(0x430);
+SetRegisterAllowed(0x434);
+SetRegisterAllowed(0x438);
+SetRegisterAllowed(0x43C);
+SetRegisterAllowed(0x440);
+SetRegisterAllowed(0x444);
+SetRegisterAllowed(0x448);
+SetRegisterAllowed(0x44C);
+SetRegisterAllowed(0x450);
+SetRegisterAllowed(0x454);
+SetRegisterAllowed(0x458);
+SetRegisterAllowed(0x45C);
+SetRegisterAllowed(0x460);
+SetRegisterAllowed(0x464);
+SetRegisterAllowed(0x468);
+SetRegisterAllowed(0x46C);
+SetRegisterAllowed(0x470);
+SetRegisterAllowed(0x474);
+SetRegisterAllowed(0x478);
+SetRegisterAllowed(0x47C);
+SetRegisterAllowed(0x480);
+SetRegisterAllowed(0x484);
+SetRegisterAllowed(0x488);
+SetRegisterAllowed(0x48C);
+SetRegisterAllowed(0x490);
+SetRegisterAllowed(0x494);
+SetRegisterAllowed(0x498);
+SetRegisterAllowed(0x49C);
+SetRegisterAllowed(0x4A0);
+SetRegisterAllowed(0x4A4);
+SetRegisterAllowed(0x4A8);
+SetRegisterAllowed(0x4AC);
+SetRegisterAllowed(0x4B0);
+SetRegisterAllowed(0x4B4);
+SetRegisterAllowed(0x4B8);
+SetRegisterAllowed(0x4BC);
+SetRegisterAllowed(0x4C0);
+SetRegisterAllowed(0x4C4);
+SetRegisterAllowed(0x4C8);
+SetRegisterAllowed(0x4CC);
+SetRegisterAllowed(0x4D0);
+SetRegisterAllowed(0x4D4);
+SetRegisterAllowed(0x4D8);
+SetRegisterAllowed(0x4DC);
+SetRegisterAllowed(0x4E0);
+SetRegisterAllowed(0x4E4);
+SetRegisterAllowed(0x4E8);
+SetRegisterAllowed(0x4EC);
+SetRegisterAllowed(0x4F0);
+SetRegisterAllowed(0x4F4);
+SetRegisterAllowed(0x4F8);
+SetRegisterAllowed(0x4FC);
+SetRegisterAllowed(0x500);
+SetRegisterAllowed(0x504);
+SetRegisterAllowed(0x508);
+SetRegisterAllowed(0x50C);
+SetRegisterAllowed(0x510);
+SetRegisterAllowed(0x514);
+SetRegisterAllowed(0x518);
+SetRegisterAllowed(0x51C);
+SetRegisterAllowed(0x520);
+SetRegisterAllowed(0x524);
+SetRegisterAllowed(0x528);
+SetRegisterAllowed(0x52C);
+SetRegisterAllowed(0x530);
+SetRegisterAllowed(0x534);
+SetRegisterAllowed(0x538);
+SetRegisterAllowed(0x53C);
+SetRegisterAllowed(0x540);
+SetRegisterAllowed(0x544);
+SetRegisterAllowed(0x548);
+SetRegisterAllowed(0x54C);
+SetRegisterAllowed(0x550);
+SetRegisterAllowed(0x554);
+SetRegisterAllowed(0x558);
+SetRegisterAllowed(0x55C);
+SetRegisterAllowed(0x560);
+SetRegisterAllowed(0x564);
+SetRegisterAllowed(0x568);
+SetRegisterAllowed(0x56C);
+SetRegisterAllowed(0x570);
+SetRegisterAllowed(0x574);
+SetRegisterAllowed(0x578);
+SetRegisterAllowed(0x57C);
+SetRegisterAllowed(0x580);
+SetRegisterAllowed(0x584);
+SetRegisterAllowed(0x588);
+SetRegisterAllowed(0x58C);
+SetRegisterAllowed(0x590);
+SetRegisterAllowed(0x594);
+SetRegisterAllowed(0x598);
+SetRegisterAllowed(0x59C);
+SetRegisterAllowed(0x5A0);
+SetRegisterAllowed(0x5A4);
+SetRegisterAllowed(0x5A8);
+SetRegisterAllowed(0x5AC);
+SetRegisterAllowed(0x5B0);
+SetRegisterAllowed(0x5B4);
+SetRegisterAllowed(0x5B8);
+SetRegisterAllowed(0x5BC);
+SetRegisterAllowed(0x5C0);
+SetRegisterAllowed(0x5C4);
+SetRegisterAllowed(0x5C8);
+SetRegisterAllowed(0x5CC);
+SetRegisterAllowed(0x5D0);
+SetRegisterAllowed(0x5D4);
+SetRegisterAllowed(0x5D8);
+SetRegisterAllowed(0x5DC);
+SetRegisterAllowed(0x5E0);
+SetRegisterAllowed(0x5E4);
+SetRegisterAllowed(0x5E8);
+SetRegisterAllowed(0x5EC);
+SetRegisterAllowed(0x5F0);
+SetRegisterAllowed(0x5F4);
+SetRegisterAllowed(0x5F8);
+SetRegisterAllowed(0x5FC);
+SetRegisterAllowed(0x600);
+SetRegisterAllowed(0x604);
+SetRegisterAllowed(0x608);
+SetRegisterAllowed(0x60C);
+SetRegisterAllowed(0x610);
+SetRegisterAllowed(0x614);
+SetRegisterAllowed(0x618);
+SetRegisterAllowed(0x61C);
+SetRegisterAllowed(0x620);
+SetRegisterAllowed(0x624);
+SetRegisterAllowed(0x628);
+SetRegisterAllowed(0x62C);
+SetRegisterAllowed(0x630);
+SetRegisterAllowed(0x634);
+SetRegisterAllowed(0x638);
+SetRegisterAllowed(0x63C);
+SetRegisterAllowed(0x640);
+SetRegisterAllowed(0x644);
+SetRegisterAllowed(0x648);
+SetRegisterAllowed(0x64C);
+SetRegisterAllowed(0x650);
+SetRegisterAllowed(0x654);
+SetRegisterAllowed(0x658);
+SetRegisterAllowed(0x65C);
+SetRegisterAllowed(0x660);
+SetRegisterAllowed(0x664);
+SetRegisterAllowed(0x668);
+SetRegisterAllowed(0x66C);
+SetRegisterAllowed(0x670);
+SetRegisterAllowed(0x674);
+SetRegisterAllowed(0x678);
+SetRegisterAllowed(0x67C);
+SetRegisterAllowed(0x680);
+SetRegisterAllowed(0x684);
+SetRegisterAllowed(0x688);
+SetRegisterAllowed(0x68C);
+SetRegisterAllowed(0x690);
+SetRegisterAllowed(0x694);
+SetRegisterAllowed(0x698);
+SetRegisterAllowed(0x69C);
+SetRegisterAllowed(0x6A0);
+SetRegisterAllowed(0x6A4);
+SetRegisterAllowed(0x6A8);
+SetRegisterAllowed(0x6AC);
+SetRegisterAllowed(0x6B0);
+SetRegisterAllowed(0x6B4);
+SetRegisterAllowed(0x6B8);
+SetRegisterAllowed(0x6BC);
+SetRegisterAllowed(0x6C0);
+SetRegisterAllowed(0x6C4);
+SetRegisterAllowed(0x6C8);
+SetRegisterAllowed(0x6CC);
+SetRegisterAllowed(0x6D0);
+SetRegisterAllowed(0x6D4);
+SetRegisterAllowed(0x6D8);
+SetRegisterAllowed(0x6DC);
+SetRegisterAllowed(0x6E0);
+SetRegisterAllowed(0x6E4);
+SetRegisterAllowed(0x6E8);
+SetRegisterAllowed(0x6EC);
+SetRegisterAllowed(0x6F0);
+SetRegisterAllowed(0x6F4);
+SetRegisterAllowed(0x6F8);
+SetRegisterAllowed(0x6FC);
+SetRegisterAllowed(0x700);
+SetRegisterAllowed(0x704);
+SetRegisterAllowed(0x708);
+SetRegisterAllowed(0x70C);
+SetRegisterAllowed(0x710);
+SetRegisterAllowed(0x714);
+SetRegisterAllowed(0x718);
+SetRegisterAllowed(0x71C);
+SetRegisterAllowed(0x720);
+SetRegisterAllowed(0x724);
+SetRegisterAllowed(0x728);
+SetRegisterAllowed(0x72C);
+SetRegisterAllowed(0x730);
+SetRegisterAllowed(0x734);
+SetRegisterAllowed(0x738);
+SetRegisterAllowed(0x73C);
+SetRegisterAllowed(0x740);
+SetRegisterAllowed(0x744);
+SetRegisterAllowed(0x748);
+SetRegisterAllowed(0x74C);
+SetRegisterAllowed(0x750);
+SetRegisterAllowed(0x754);
+SetRegisterAllowed(0x758);
+SetRegisterAllowed(0x75C);
+SetRegisterAllowed(0x760);
+SetRegisterAllowed(0x764);
+SetRegisterAllowed(0x768);
+SetRegisterAllowed(0x76C);
+SetRegisterAllowed(0x770);
+SetRegisterAllowed(0x774);
+SetRegisterAllowed(0x778);
+SetRegisterAllowed(0x77C);
+SetRegisterAllowed(0x780);
+SetRegisterAllowed(0x784);
+SetRegisterAllowed(0x788);
+SetRegisterAllowed(0x78C);
+SetRegisterAllowed(0x790);
+SetRegisterAllowed(0x794);
+SetRegisterAllowed(0x798);
+SetRegisterAllowed(0x79C);
+SetRegisterAllowed(0x7A0);
+SetRegisterAllowed(0x7A4);
+SetRegisterAllowed(0x7A8);
+SetRegisterAllowed(0x7AC);
+SetRegisterAllowed(0x7B0);
+SetRegisterAllowed(0x7B4);
+SetRegisterAllowed(0x7B8);
+SetRegisterAllowed(0x7BC);
+SetRegisterAllowed(0x7C0);
+SetRegisterAllowed(0x7C4);
+SetRegisterAllowed(0x7C8);
+SetRegisterAllowed(0x7CC);
+SetRegisterAllowed(0x7D0);
+SetRegisterAllowed(0x7D4);
+SetRegisterAllowed(0x7D8);
+SetRegisterAllowed(0x7DC);
+SetRegisterAllowed(0x7E0);
+SetRegisterAllowed(0x7E4);
+SetRegisterAllowed(0x7E8);
+SetRegisterAllowed(0x7EC);
+SetRegisterAllowed(0x7F0);
+SetRegisterAllowed(0x7F4);
+SetRegisterAllowed(0x7F8);
+SetRegisterAllowed(0x7FC);
+SetRegisterAllowed(0x800);
+SetRegisterAllowed(0x804);
+SetRegisterAllowed(0x808);
+SetRegisterAllowed(0x80C);
+SetRegisterAllowed(0x810);
+SetRegisterAllowed(0x814);
+SetRegisterAllowed(0x818);
+SetRegisterAllowed(0x81C);
+SetRegisterAllowed(0x820);
+SetRegisterAllowed(0x824);
+SetRegisterAllowed(0x828);
+SetRegisterAllowed(0x82C);
+SetRegisterAllowed(0x830);
+SetRegisterAllowed(0x834);
+SetRegisterAllowed(0x838);
+SetRegisterAllowed(0x83C);
+SetRegisterAllowed(0x840);
+SetRegisterAllowed(0x844);
+SetRegisterAllowed(0x848);
+SetRegisterAllowed(0x84C);
+SetRegisterAllowed(0x850);
+SetRegisterAllowed(0x854);
+SetRegisterAllowed(0x858);
+SetRegisterAllowed(0x85C);
+SetRegisterAllowed(0x860);
+SetRegisterAllowed(0x864);
+SetRegisterAllowed(0x868);
+SetRegisterAllowed(0x86C);
+SetRegisterAllowed(0x870);
+SetRegisterAllowed(0x874);
+SetRegisterAllowed(0x878);
+SetRegisterAllowed(0x87C);
+SetRegisterAllowed(0x880);
+SetRegisterAllowed(0x884);
+SetRegisterAllowed(0x888);
+SetRegisterAllowed(0x88C);
+SetRegisterAllowed(0x890);
+SetRegisterAllowed(0x894);
+SetRegisterAllowed(0x898);
+SetRegisterAllowed(0x89C);
+SetRegisterAllowed(0x8A0);
+SetRegisterAllowed(0x8A4);
+SetRegisterAllowed(0x8A8);
+SetRegisterAllowed(0x8AC);
+SetRegisterAllowed(0x8B0);
+SetRegisterAllowed(0x8B4);
+SetRegisterAllowed(0x8B8);
+SetRegisterAllowed(0x8BC);
+SetRegisterAllowed(0x8C0);
+SetRegisterAllowed(0x8C4);
+SetRegisterAllowed(0x8C8);
+SetRegisterAllowed(0x8CC);
+SetRegisterAllowed(0x8D0);
+SetRegisterAllowed(0x8D4);
+SetRegisterAllowed(0x8D8);
+SetRegisterAllowed(0x8DC);
+SetRegisterAllowed(0x8E0);
+SetRegisterAllowed(0x8E4);
+SetRegisterAllowed(0x8E8);
+SetRegisterAllowed(0x8EC);
+SetRegisterAllowed(0x8F0);
+SetRegisterAllowed(0x8F4);
+SetRegisterAllowed(0x8F8);
+SetRegisterAllowed(0x8FC);
+SetRegisterAllowed(0x900);
+SetRegisterAllowed(0x904);
+SetRegisterAllowed(0x908);
+SetRegisterAllowed(0x90C);
+SetRegisterAllowed(0x910);
+SetRegisterAllowed(0x914);
+SetRegisterAllowed(0x918);
+SetRegisterAllowed(0x91C);
+SetRegisterAllowed(0x920);
+SetRegisterAllowed(0x924);
+SetRegisterAllowed(0x928);
+SetRegisterAllowed(0x92C);
+SetRegisterAllowed(0x930);
+SetRegisterAllowed(0x934);
+SetRegisterAllowed(0x938);
+SetRegisterAllowed(0x93C);
+SetRegisterAllowed(0x940);
+SetRegisterAllowed(0x944);
+SetRegisterAllowed(0x948);
+SetRegisterAllowed(0x94C);
+SetRegisterAllowed(0x950);
+SetRegisterAllowed(0x954);
+SetRegisterAllowed(0x958);
+SetRegisterAllowed(0x95C);
+SetRegisterAllowed(0x960);
+SetRegisterAllowed(0x964);
+SetRegisterAllowed(0x968);
+SetRegisterAllowed(0x96C);
+SetRegisterAllowed(0x970);
+SetRegisterAllowed(0x974);
+SetRegisterAllowed(0x978);
+SetRegisterAllowed(0x97C);
+SetRegisterAllowed(0x980);
+SetRegisterAllowed(0x984);
+SetRegisterAllowed(0x988);
+SetRegisterAllowed(0x98C);
+SetRegisterAllowed(0x990);
+SetRegisterAllowed(0x994);
+SetRegisterAllowed(0x998);
+SetRegisterAllowed(0x99C);
+SetRegisterAllowed(0x9A0);
+SetRegisterAllowed(0x9A4);
+SetRegisterAllowed(0x9A8);
+SetRegisterAllowed(0x9AC);
+SetRegisterAllowed(0x9B0);
+SetRegisterAllowed(0x9B4);
+SetRegisterAllowed(0x9B8);
+SetRegisterAllowed(0x9BC);
+SetRegisterAllowed(0x9C0);
+SetRegisterAllowed(0x9C4);
+SetRegisterAllowed(0x9C8);
+SetRegisterAllowed(0x9CC);
+SetRegisterAllowed(0x9D0);
+SetRegisterAllowed(0x9D4);
+SetRegisterAllowed(0x9D8);
+SetRegisterAllowed(0x9DC);
+SetRegisterAllowed(0x9E0);
+SetRegisterAllowed(0x9E4);
+SetRegisterAllowed(0x9E8);
+SetRegisterAllowed(0x9EC);
+SetRegisterAllowed(0x9F0);
+SetRegisterAllowed(0x9F4);
+SetRegisterAllowed(0x9F8);
+SetRegisterAllowed(0x9FC);
+SetRegisterAllowed(0xA00);
+SetRegisterAllowed(0xA04);
+SetRegisterAllowed(0xA08);
+SetRegisterAllowed(0xA0C);
+SetRegisterAllowed(0xA10);
+SetRegisterAllowed(0xA14);
+SetRegisterAllowed(0xA18);
+SetRegisterAllowed(0xA1C);
+SetRegisterAllowed(0xA20);
+SetRegisterAllowed(0xA24);
+SetRegisterAllowed(0xA28);
+SetRegisterAllowed(0xA2C);
+SetRegisterAllowed(0xA30);
+SetRegisterAllowed(0xA34);
+SetRegisterAllowed(0xA38);
+SetRegisterAllowed(0xA3C);
+SetRegisterAllowed(0xA40);
+SetRegisterAllowed(0xA44);
+SetRegisterAllowed(0xA48);
+SetRegisterAllowed(0xA4C);
+SetRegisterAllowed(0xA50);
+SetRegisterAllowed(0xA54);
+SetRegisterAllowed(0xA58);
+SetRegisterAllowed(0xA5C);
+SetRegisterAllowed(0xA60);
+SetRegisterAllowed(0xA64);
+SetRegisterAllowed(0xA68);
+SetRegisterAllowed(0xA6C);
+SetRegisterAllowed(0xA70);
+SetRegisterAllowed(0xA74);
+SetRegisterAllowed(0xA78);
+SetRegisterAllowed(0xA7C);
+SetRegisterAllowed(0xA80);
+SetRegisterAllowed(0xA84);
+SetRegisterAllowed(0xA88);
+SetRegisterAllowed(0xA8C);
+SetRegisterAllowed(0xA90);
+SetRegisterAllowed(0xA94);
+SetRegisterAllowed(0xA98);
+SetRegisterAllowed(0xA9C);
+SetRegisterAllowed(0xAA0);
+SetRegisterAllowed(0xAA4);
+SetRegisterAllowed(0xAA8);
+SetRegisterAllowed(0xAAC);
+SetRegisterAllowed(0xAB0);
+SetRegisterAllowed(0xAB4);
+SetRegisterAllowed(0xAB8);
+SetRegisterAllowed(0xABC);
+SetRegisterAllowed(0xAC0);
+SetRegisterAllowed(0xAC4);
+SetRegisterAllowed(0xAC8);
+SetRegisterAllowed(0xACC);
+SetRegisterAllowed(0xAD0);
+SetRegisterAllowed(0xAD4);
+SetRegisterAllowed(0xAD8);
+SetRegisterAllowed(0xADC);
+SetRegisterAllowed(0xAE0);
+SetRegisterAllowed(0xAE4);
+SetRegisterAllowed(0xAE8);
+SetRegisterAllowed(0xAEC);
+SetRegisterAllowed(0xAF0);
+SetRegisterAllowed(0xAF4);
+SetRegisterAllowed(0xAF8);
+SetRegisterAllowed(0xAFC);
+SetRegisterAllowed(0xB00);
+SetRegisterAllowed(0xB04);
+SetRegisterAllowed(0xB08);
+SetRegisterAllowed(0xB0C);
+SetRegisterAllowed(0xB10);
+SetRegisterAllowed(0xB14);
+SetRegisterAllowed(0xB18);
+SetRegisterAllowed(0xB1C);
+SetRegisterAllowed(0xB20);
+SetRegisterAllowed(0xB24);
+SetRegisterAllowed(0xB28);
+SetRegisterAllowed(0xB2C);
+SetRegisterAllowed(0xB30);
+SetRegisterAllowed(0xB34);
+SetRegisterAllowed(0xB38);
+SetRegisterAllowed(0xB3C);
+SetRegisterAllowed(0xB40);
+SetRegisterAllowed(0xB44);
+SetRegisterAllowed(0xB48);
+SetRegisterAllowed(0xB4C);
+SetRegisterAllowed(0xB50);
+SetRegisterAllowed(0xB54);
+SetRegisterAllowed(0xB58);
+SetRegisterAllowed(0xB5C);
+SetRegisterAllowed(0xB60);
+SetRegisterAllowed(0xB64);
+SetRegisterAllowed(0xB68);
+SetRegisterAllowed(0xB6C);
+SetRegisterAllowed(0xB70);
+SetRegisterAllowed(0xB74);
+SetRegisterAllowed(0xB78);
+SetRegisterAllowed(0xB7C);
+SetRegisterAllowed(0xB80);
+SetRegisterAllowed(0xB84);
+SetRegisterAllowed(0xB88);
+SetRegisterAllowed(0xB8C);
+SetRegisterAllowed(0xB90);
+SetRegisterAllowed(0xB94);
+SetRegisterAllowed(0xB98);
+SetRegisterAllowed(0xB9C);
+SetRegisterAllowed(0xBA0);
+SetRegisterAllowed(0xBA4);
+SetRegisterAllowed(0xBA8);
+SetRegisterAllowed(0xBAC);
+SetRegisterAllowed(0xBB0);
+SetRegisterAllowed(0xBB4);
+SetRegisterAllowed(0xBB8);
+SetRegisterAllowed(0xBBC);
+SetRegisterAllowed(0xBC0);
+SetRegisterAllowed(0xBC4);
+SetRegisterAllowed(0xBC8);
+SetRegisterAllowed(0xBCC);
+SetRegisterAllowed(0xBD0);
+SetRegisterAllowed(0xBD4);
+SetRegisterAllowed(0xBD8);
+SetRegisterAllowed(0xBDC);
+SetRegisterAllowed(0xBE0);
+SetRegisterAllowed(0xBE4);
+SetRegisterAllowed(0xBE8);
+SetRegisterAllowed(0xBEC);
+SetRegisterAllowed(0xBF0);
+SetRegisterAllowed(0xBF4);
+SetRegisterAllowed(0xBF8);
+SetRegisterAllowed(0xBFC);
+SetRegisterAllowed(0xC00);
+SetRegisterAllowed(0xC04);
+SetRegisterAllowed(0xC08);
+SetRegisterAllowed(0xC0C);
+SetRegisterAllowed(0xC10);
+SetRegisterAllowed(0xC14);
+SetRegisterAllowed(0xC18);
+SetRegisterAllowed(0xC1C);
+SetRegisterAllowed(0xC20);
+SetRegisterAllowed(0xC24);
+SetRegisterAllowed(0xC28);
+SetRegisterAllowed(0xC2C);
+SetRegisterAllowed(0xC30);
+SetRegisterAllowed(0xC34);
+SetRegisterAllowed(0xC38);
+SetRegisterAllowed(0xC3C);
+SetRegisterAllowed(0xC40);
+SetRegisterAllowed(0xC44);
+SetRegisterAllowed(0xC48);
+SetRegisterAllowed(0xC4C);
+SetRegisterAllowed(0xC50);
+SetRegisterAllowed(0xC54);
+SetRegisterAllowed(0xC58);
+SetRegisterAllowed(0xC5C);
+SetRegisterAllowed(0xC60);
+SetRegisterAllowed(0xC64);
+SetRegisterAllowed(0xC68);
+SetRegisterAllowed(0xC6C);
+SetRegisterAllowed(0xC70);
+SetRegisterAllowed(0xC74);
+SetRegisterAllowed(0xC78);
+SetRegisterAllowed(0xC7C);
+SetRegisterAllowed(0xC80);
+SetRegisterAllowed(0xC84);
+SetRegisterAllowed(0xC88);
+SetRegisterAllowed(0xC8C);
+SetRegisterAllowed(0xC90);
+SetRegisterAllowed(0xC94);
+SetRegisterAllowed(0xC98);
+SetRegisterAllowed(0xC9C);
+SetRegisterAllowed(0xCA0);
+SetRegisterAllowed(0xCA4);
+SetRegisterAllowed(0xCA8);
+SetRegisterAllowed(0xCAC);
+SetRegisterAllowed(0xCB0);
+SetRegisterAllowed(0xCB4);
+SetRegisterAllowed(0xCB8);
+SetRegisterAllowed(0xCBC);
+SetRegisterAllowed(0xCC0);
+SetRegisterAllowed(0xCC4);
+SetRegisterAllowed(0xCC8);
+SetRegisterAllowed(0xCCC);
+SetRegisterAllowed(0xCD0);
+SetRegisterAllowed(0xCD4);
+SetRegisterAllowed(0xCD8);
+SetRegisterAllowed(0xCDC);
+SetRegisterAllowed(0xCE0);
+SetRegisterAllowed(0xCE4);
+SetRegisterAllowed(0xCE8);
+SetRegisterAllowed(0xCEC);
+SetRegisterAllowed(0xCF0);
+SetRegisterAllowed(0xCF4);
+SetRegisterAllowed(0xCF8);
+SetRegisterAllowed(0xCFC);
+SetRegisterAllowed(0xD00);
+SetRegisterAllowed(0xD04);
+SetRegisterAllowed(0xD08);
+SetRegisterAllowed(0xD0C);
+SetRegisterAllowed(0xD10);
+SetRegisterAllowed(0xD14);
+SetRegisterAllowed(0xD18);
+SetRegisterAllowed(0xD1C);
+SetRegisterAllowed(0xD20);
+SetRegisterAllowed(0xD24);
+SetRegisterAllowed(0xD28);
+SetRegisterAllowed(0xD2C);
+SetRegisterAllowed(0xD30);
+SetRegisterAllowed(0xD34);
+SetRegisterAllowed(0xD38);
+SetRegisterAllowed(0xD3C);
+SetRegisterAllowed(0xD40);
+SetRegisterAllowed(0xD44);
+SetRegisterAllowed(0xD48);
+SetRegisterAllowed(0xD4C);
+SetRegisterAllowed(0xD50);
+SetRegisterAllowed(0xD54);
+SetRegisterAllowed(0xD58);
+SetRegisterAllowed(0xD5C);
+SetRegisterAllowed(0xD60);
+SetRegisterAllowed(0xD64);
+SetRegisterAllowed(0xD68);
+SetRegisterAllowed(0xD6C);
+SetRegisterAllowed(0xD70);
+SetRegisterAllowed(0xD74);
+SetRegisterAllowed(0xD78);
+SetRegisterAllowed(0xD7C);
+SetRegisterAllowed(0xD80);
+SetRegisterAllowed(0xD84);
+SetRegisterAllowed(0xD88);
+SetRegisterAllowed(0xD8C);
+SetRegisterAllowed(0xD90);
+SetRegisterAllowed(0xD94);
+SetRegisterAllowed(0xD98);
+SetRegisterAllowed(0xD9C);
+SetRegisterAllowed(0xDA0);
+SetRegisterAllowed(0xDA4);
+SetRegisterAllowed(0xDA8);
+SetRegisterAllowed(0xDAC);
+SetRegisterAllowed(0xDB0);
+SetRegisterAllowed(0xDB4);
+SetRegisterAllowed(0xDB8);
+SetRegisterAllowed(0xDBC);
+SetRegisterAllowed(0xDC0);
+SetRegisterAllowed(0xDC4);
+SetRegisterAllowed(0xDC8);
+SetRegisterAllowed(0xDCC);
+SetRegisterAllowed(0xDD0);
+SetRegisterAllowed(0xDD4);
+SetRegisterAllowed(0xDD8);
+SetRegisterAllowed(0xDDC);
+SetRegisterAllowed(0xDE0);
+SetRegisterAllowed(0xDE4);
+SetRegisterAllowed(0xDE8);
+SetRegisterAllowed(0xDEC);
+SetRegisterAllowed(0xDF0);
+SetRegisterAllowed(0xDF4);
+SetRegisterAllowed(0xDF8);
+SetRegisterAllowed(0xDFC);
+SetRegisterAllowed(0xE00);
+SetRegisterAllowed(0xE04);
+SetRegisterAllowed(0xE08);
+SetRegisterAllowed(0xE0C);
+SetRegisterAllowed(0xE10);
+SetRegisterAllowed(0xE14);
+SetRegisterAllowed(0xE18);
+SetRegisterAllowed(0xE1C);
+SetRegisterAllowed(0xE20);
+SetRegisterAllowed(0xE24);
+SetRegisterAllowed(0xE28);
+SetRegisterAllowed(0xE2C);
+SetRegisterAllowed(0xE30);
+SetRegisterAllowed(0xE34);
+SetRegisterAllowed(0xE38);
+SetRegisterAllowed(0xE3C);
+SetRegisterAllowed(0xE40);
+SetRegisterAllowed(0xE44);
+SetRegisterAllowed(0xE48);
+SetRegisterAllowed(0xE4C);
+SetRegisterAllowed(0xE50);
+SetRegisterAllowed(0xE54);
+SetRegisterAllowed(0xE58);
+SetRegisterAllowed(0xE5C);
+SetRegisterAllowed(0xE60);
+SetRegisterAllowed(0xE64);
+SetRegisterAllowed(0xE68);
+SetRegisterAllowed(0xE6C);
+SetRegisterAllowed(0xE70);
+SetRegisterAllowed(0xE74);
+SetRegisterAllowed(0xE78);
+SetRegisterAllowed(0xE7C);
+SetRegisterAllowed(0xE80);
+SetRegisterAllowed(0xE84);
+SetRegisterAllowed(0xE88);
+SetRegisterAllowed(0xE8C);
+SetRegisterAllowed(0xE90);
+SetRegisterAllowed(0xE94);
+SetRegisterAllowed(0xE98);
+SetRegisterAllowed(0xE9C);
+SetRegisterAllowed(0xEA0);
+SetRegisterAllowed(0xEA4);
+SetRegisterAllowed(0xEA8);
+SetRegisterAllowed(0xEAC);
+SetRegisterAllowed(0xEB0);
+SetRegisterAllowed(0xEB4);
+SetRegisterAllowed(0xEB8);
+SetRegisterAllowed(0xEBC);
+SetRegisterAllowed(0xEC0);
+SetRegisterAllowed(0xEC4);
+SetRegisterAllowed(0xEC8);
+SetRegisterAllowed(0xECC);
+SetRegisterAllowed(0xED0);
+SetRegisterAllowed(0xED4);
+SetRegisterAllowed(0xED8);
+SetRegisterAllowed(0xEDC);
+SetRegisterAllowed(0xEE0);
+SetRegisterAllowed(0xEE4);
+SetRegisterAllowed(0xEE8);
+SetRegisterAllowed(0xEEC);
+SetRegisterAllowed(0xEF0);
+SetRegisterAllowed(0xEF4);
+SetRegisterAllowed(0xEF8);
+SetRegisterAllowed(0xEFC);
+SetRegisterAllowed(0xF00);
+SetRegisterAllowed(0xF04);
+SetRegisterAllowed(0xF08);
+SetRegisterAllowed(0xF0C);
+SetRegisterAllowed(0xF10);
+SetRegisterAllowed(0xF14);
+SetRegisterAllowed(0xF18);
+SetRegisterAllowed(0xF1C);
+SetRegisterAllowed(0xF20);
+SetRegisterAllowed(0xF24);
+SetRegisterAllowed(0xF28);
+SetRegisterAllowed(0xF2C);
+SetRegisterAllowed(0xF30);
+SetRegisterAllowed(0xF34);
+SetRegisterAllowed(0xF38);
+SetRegisterAllowed(0xF3C);
+SetRegisterAllowed(0xF40);
+SetRegisterAllowed(0xF44);
+SetRegisterAllowed(0xF48);
+SetRegisterAllowed(0xF4C);
+SetRegisterAllowed(0xF50);
+SetRegisterAllowed(0xF54);
+SetRegisterAllowed(0xF58);
+SetRegisterAllowed(0xF5C);
+SetRegisterAllowed(0xF60);
+SetRegisterAllowed(0xF64);
+SetRegisterAllowed(0xF68);
+SetRegisterAllowed(0xF6C);
+SetRegisterAllowed(0xF70);
+SetRegisterAllowed(0xF74);
+SetRegisterAllowed(0xF78);
+SetRegisterAllowed(0xF7C);
+SetRegisterAllowed(0xF80);
+SetRegisterAllowed(0xF84);
+SetRegisterAllowed(0xF88);
+SetRegisterAllowed(0xF8C);
+SetRegisterAllowed(0xF90);
+SetRegisterAllowed(0xF94);
+SetRegisterAllowed(0xF98);
+SetRegisterAllowed(0xF9C);
+SetRegisterAllowed(0xFA0);
+SetRegisterAllowed(0xFA4);
+SetRegisterAllowed(0xFA8);
+SetRegisterAllowed(0xFAC);
+SetRegisterAllowed(0xFB0);
+SetRegisterAllowed(0xFB4);
+SetRegisterAllowed(0xFB8);
+SetRegisterAllowed(0xFBC);
+SetRegisterAllowed(0xFC0);
+SetRegisterAllowed(0xFC4);
+SetRegisterAllowed(0xFC8);
+SetRegisterAllowed(0xFCC);
+SetRegisterAllowed(0xFD0);
+SetRegisterAllowed(0xFD4);
+SetRegisterAllowed(0xFD8);
+SetRegisterAllowed(0xFDC);
+SetRegisterAllowed(0xFE0);
+SetRegisterAllowed(0xFE4);
+SetRegisterAllowed(0xFE8);
+SetRegisterAllowed(0xFEC);
+SetRegisterAllowed(0xFF0);
+SetRegisterAllowed(0xFF4);
+SetRegisterAllowed(0xFF8);
+SetRegisterAllowed(0xFFC);
diff --git a/Source/Atmosphere-Patches/secmon_memory_layout.hpp b/Source/Atmosphere-Patches/secmon_memory_layout.hpp
index 7c0e499a..ba3e3ab8 100644
--- a/Source/Atmosphere-Patches/secmon_memory_layout.hpp
+++ b/Source/Atmosphere-Patches/secmon_memory_layout.hpp
@@ -151,7 +151,9 @@ namespace ams::secmon {
HANDLER(ExceptionVectors, I2c1, UINT64_C(0x6000F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
HANDLER(MemoryController0, ExceptionVectors, UINT64_C(0x7001C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
HANDLER(MemoryController1, MemoryController0, UINT64_C(0x7001D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
- HANDLER(Sdmmc, MemoryController1, UINT64_C(0x700B0000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+ HANDLER(ExternalMemoryController0, MemoryController1, UINT64_C(0x7001E000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+ HANDLER(ExternalMemoryController1, ExternalMemoryController0, UINT64_C(0x7001F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
+ HANDLER(Sdmmc, ExternalMemoryController1, UINT64_C(0x700B0000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
HANDLER(Disp1, Sdmmc, UINT64_C(0x54200000), UINT64_C(0x3000), true, ## __VA_ARGS__) \
HANDLER(Dsi, Disp1, UINT64_C(0x54300000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
HANDLER(MipiCal, Dsi, UINT64_C(0x700E3000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
diff --git a/Source/Atmosphere-Patches/secmon_setup.cpp b/Source/Atmosphere-Patches/secmon_setup.cpp
new file mode 100644
index 00000000..ca761033
--- /dev/null
+++ b/Source/Atmosphere-Patches/secmon_setup.cpp
@@ -0,0 +1,1226 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#include
+#include "secmon_setup.hpp"
+#include "secmon_error.hpp"
+#include "secmon_map.hpp"
+#include "secmon_cpu_context.hpp"
+#include "secmon_mariko_fatal_error.hpp"
+#include "secmon_interrupt_handler.hpp"
+#include "secmon_misc.hpp"
+#include "smc/secmon_random_cache.hpp"
+#include "smc/secmon_smc_power_management.hpp"
+#include "smc/secmon_smc_se_lock.hpp"
+
+namespace ams::secmon {
+
+ namespace {
+
+ constexpr inline const uintptr_t TIMER = secmon::MemoryRegionVirtualDeviceTimer.GetAddress();
+ constexpr inline const uintptr_t SYSTEM = secmon::MemoryRegionVirtualDeviceSystem.GetAddress();
+ constexpr inline const uintptr_t APB_MISC = secmon::MemoryRegionVirtualDeviceApbMisc.GetAddress();
+ constexpr inline const uintptr_t FLOW_CTLR = secmon::MemoryRegionVirtualDeviceFlowController.GetAddress();
+ constexpr inline const uintptr_t PMC = secmon::MemoryRegionVirtualDevicePmc.GetAddress();
+ constexpr inline const uintptr_t MC = secmon::MemoryRegionVirtualDeviceMemoryController.GetAddress();
+ constexpr inline const uintptr_t EVP = secmon::MemoryRegionVirtualDeviceExceptionVectors.GetAddress();
+ constexpr inline const uintptr_t CLK_RST = secmon::MemoryRegionVirtualDeviceClkRst.GetAddress();
+
+ alignas(8) constinit u8 g_se_aes_key_slot_test_vector[se::AesBlockSize] = {};
+
+ struct Carveout {
+ uintptr_t address;
+ size_t size;
+ };
+
+ constinit Carveout g_kernel_carveouts[KernelCarveoutCount] = {
+ { secmon::MemoryRegionDramDefaultKernelCarveout.GetAddress(), secmon::MemoryRegionDramDefaultKernelCarveout.GetSize(), },
+ { 0, 0, },
+ };
+
+ constinit bool g_is_cold_boot = true;
+
+ constinit se::StickyBits ExpectedSeStickyBits = {
+ .se_security = (1 << 0), /* SE_HARD_SETTING */
+ .tzram_security = 0,
+ .crypto_security_perkey = (1 << pkg1::AesKeySlot_UserEnd) - 1,
+ .crypto_keytable_access = {
+ (0 << 7) | (1 << 6) | (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (1 << 1) | (0 << 0), /* 0: User keyslot. KEY. KEYUSE, UIVUPDATE, OIVUPDATE, KEYUPDATE enabled. UIVREAD, OIVREAD, KEYREAD disabled. */
+ (0 << 7) | (1 << 6) | (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (1 << 1) | (0 << 0), /* 1: User keyslot. KEY. KEYUSE, UIVUPDATE, OIVUPDATE, KEYUPDATE enabled. UIVREAD, OIVREAD, KEYREAD disabled. */
+ (0 << 7) | (1 << 6) | (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (1 << 1) | (0 << 0), /* 2: User keyslot. KEY. KEYUSE, UIVUPDATE, OIVUPDATE, KEYUPDATE enabled. UIVREAD, OIVREAD, KEYREAD disabled. */
+ (0 << 7) | (1 << 6) | (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (1 << 1) | (0 << 0), /* 3: User keyslot. KEY. KEYUSE, UIVUPDATE, OIVUPDATE, KEYUPDATE enabled. UIVREAD, OIVREAD, KEYREAD disabled. */
+ (0 << 7) | (1 << 6) | (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (1 << 1) | (0 << 0), /* 4: User keyslot. KEY. KEYUSE, UIVUPDATE, OIVUPDATE, KEYUPDATE enabled. UIVREAD, OIVREAD, KEYREAD disabled. */
+ (0 << 7) | (1 << 6) | (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (1 << 1) | (0 << 0), /* 5: User keyslot. KEY. KEYUSE, UIVUPDATE, OIVUPDATE, KEYUPDATE enabled. UIVREAD, OIVREAD, KEYREAD disabled. */
+ (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0), /* 6: Unused keyslot. KEK. KEYUSE, UIVUPDATE, UIVREAD, OIVUPDATE, OIVREAD, KEYUPDATE, KEYREAD disabled. */
+ (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0), /* 7: Unused keyslot. KEK. KEYUSE, UIVUPDATE, UIVREAD, OIVUPDATE, OIVREAD, KEYUPDATE, KEYREAD disabled. */
+ (0 << 7) | (0 << 6) | (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (1 << 1) | (0 << 0), /* 8: Temp keyslot. KEY. UIVUPDATE, OIVUPDATE, KEYUPDATE enabled. KEYUSE, UIVREAD, OIVREAD, KEYREAD disabled. */
+ (0 << 7) | (0 << 6) | (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (1 << 1) | (0 << 0), /* 9: SmcTemp keyslot. KEY. UIVUPDATE, OIVUPDATE, KEYUPDATE enabled. KEYUSE, UIVREAD, OIVREAD, KEYREAD disabled. */
+ (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0), /* 10: Wrap1 keyslot. KEK. KEYUSE, UIVUPDATE, UIVREAD, OIVUPDATE, OIVREAD, KEYUPDATE, KEYREAD disabled. */
+ (0 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0), /* 11: Wrap2 keyslot. KEY. KEYUSE, UIVUPDATE, UIVREAD, OIVUPDATE, OIVREAD, KEYUPDATE, KEYREAD disabled. */
+ (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0), /* 12: DMaster keyslot. KEK. KEYUSE, UIVUPDATE, UIVREAD, OIVUPDATE, OIVREAD, KEYUPDATE, KEYREAD disabled. */
+ (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0), /* 13: Master keyslot. KEK. KEYUSE, UIVUPDATE, UIVREAD, OIVUPDATE, OIVREAD, KEYUPDATE, KEYREAD disabled. */
+ (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0), /* 14: Unused keyslot. KEK. KEYUSE, UIVUPDATE, UIVREAD, OIVUPDATE, OIVREAD, KEYUPDATE, KEYREAD disabled. */
+ (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0), /* 13: Device keyslot. KEK. KEYUSE, UIVUPDATE, UIVREAD, OIVUPDATE, OIVREAD, KEYUPDATE, KEYREAD disabled. */
+ },
+ .rsa_security_perkey = 0,
+ .rsa_keytable_access = {
+ (0 << 2) | (1 << 1) | (0 << 0), /* KEYUSE/KEYREAD disabled, KEYUPDATE enabled. */
+ (0 << 2) | (1 << 1) | (0 << 0), /* KEYUSE/KEYREAD disabled, KEYUPDATE enabled. */
+ },
+ };
+
+ void InitializeConfigurationContext() {
+ /* Get the global context. */
+ auto &ctx = ::ams::secmon::impl::GetConfigurationContext();
+
+ /* Clear the context to zero. */
+ std::memset(std::addressof(ctx), 0, sizeof(ctx));
+
+ /* If the storage context is valid, we want to copy it to the global context. */
+ if (const auto &storage_ctx = *MemoryRegionPhysicalDramMonitorConfiguration.GetPointer(); storage_ctx.IsValid()) {
+ ctx.secmon_cfg.CopyFrom(storage_ctx);
+ ctx.emummc_cfg = storage_ctx.emummc_cfg;
+ } else {
+ /* If we don't have a valid storage context, we can just use the default one. */
+ ctx.secmon_cfg = DefaultSecureMonitorConfiguration;
+ }
+
+ /* Cache the fuse info for quick access. */
+ ctx.secmon_cfg.SetFuseInfo();
+ }
+
+ void GenerateSecurityEngineAesKeySlotTestVector(void *dst, size_t size) {
+ /* Clear the output. */
+ AMS_ABORT_UNLESS(size == se::AesBlockSize);
+ std::memset(dst, 0, se::AesBlockSize);
+
+ /* Ensure output is seen as cleared by the se. */
+ hw::FlushDataCache(dst, se::AesBlockSize);
+ hw::DataSynchronizationBarrierInnerShareable();
+
+ /* Declare a block. */
+ alignas(8) u8 empty_block[se::AesBlockSize];
+
+ /* Iteratively transform an empty block. */
+ #define TRANSFORM_WITH_KEY(key) \
+ __builtin_memset(empty_block, 0, sizeof(empty_block)); \
+ se::SetEncryptedAesKey256(pkg1::AesKeySlot_Temporary, key, empty_block, sizeof(empty_block)); \
+ se::DecryptAes128(dst, se::AesBlockSize, pkg1::AesKeySlot_Temporary, dst, se::AesBlockSize)
+
+ TRANSFORM_WITH_KEY(pkg1::AesKeySlot_RandomForUserWrap);
+ TRANSFORM_WITH_KEY(pkg1::AesKeySlot_RandomForKeyStorageWrap);
+ TRANSFORM_WITH_KEY(pkg1::AesKeySlot_Master);
+ TRANSFORM_WITH_KEY(pkg1::AesKeySlot_DeviceMaster);
+ TRANSFORM_WITH_KEY(pkg1::AesKeySlot_Device);
+
+ TRANSFORM_WITH_KEY(pkg1::AesKeySlot_RandomForUserWrap);
+ TRANSFORM_WITH_KEY(pkg1::AesKeySlot_RandomForKeyStorageWrap);
+ TRANSFORM_WITH_KEY(pkg1::AesKeySlot_Master);
+ TRANSFORM_WITH_KEY(pkg1::AesKeySlot_DeviceMaster);
+ TRANSFORM_WITH_KEY(pkg1::AesKeySlot_Device);
+
+ /* Ensure output is seen correctly by the cpu. */
+ hw::FlushDataCache(dst, se::AesBlockSize);
+ hw::DataSynchronizationBarrierInnerShareable();
+
+ /* Clear the temporary key slot. */
+ se::ClearAesKeySlot(pkg1::AesKeySlot_Temporary);
+ }
+
+ void VerifySecurityEngineStickyBits() {
+ /* On mariko, an extra sticky bit is set. */
+ if (GetSocType() == fuse::SocType_Mariko) {
+ ExpectedSeStickyBits.se_security |= (1 << 5);
+ } else /* if (GetSocType() == fuse::SocType_Erista) */ {
+ /* Erista does not support DST_KEYTABLE_ONLY, and so all keys will have the bit clear. */
+ for (size_t i = 0; i < util::size(ExpectedSeStickyBits.crypto_keytable_access); ++i) {
+ ExpectedSeStickyBits.crypto_keytable_access[i] &= ~(1 << 7);
+ }
+ }
+
+ if (!se::ValidateStickyBits(ExpectedSeStickyBits)) {
+ SetError(pkg1::ErrorInfo_InvalidSecurityEngineStickyBits);
+ AMS_ABORT("Invalid sticky bits");
+ }
+ }
+
+ void VerifySecurityEngineAesKeySlotTestVector() {
+ alignas(8) u8 test_vector[se::AesBlockSize];
+ GenerateSecurityEngineAesKeySlotTestVector(test_vector, sizeof(test_vector));
+
+ AMS_ABORT_UNLESS(crypto::IsSameBytes(g_se_aes_key_slot_test_vector, test_vector, se::AesBlockSize));
+ }
+
+ void ClearAesKeySlots() {
+ /* Clear all non-secure monitor keys. */
+ for (int i = 0; i < pkg1::AesKeySlot_SecmonStart; ++i) {
+ se::ClearAesKeySlot(i);
+ }
+
+ /* Clear the secure-monitor temporary keys. */
+ se::ClearAesKeySlot(pkg1::AesKeySlot_Temporary);
+ se::ClearAesKeySlot(pkg1::AesKeySlot_Smc);
+ }
+
+ void ClearRsaKeySlots() {
+ /* Clear all rsa keyslots. */
+ for (int i = 0; i < se::RsaKeySlotCount; ++i) {
+ se::ClearRsaKeySlot(i);
+ }
+ }
+
+ void SetupKernelCarveouts() {
+ #define MC_ENABLE_CLIENT_ACCESS(INDEX, WHICH) MC_REG_BITS_ENUM(CLIENT_ACCESS##INDEX##_##WHICH, ENABLE)
+
+ constexpr u32 ClientAccess0 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(0, PTCR),
+ MC_ENABLE_CLIENT_ACCESS(0, DISPLAY0A),
+ MC_ENABLE_CLIENT_ACCESS(0, DISPLAY0AB),
+ MC_ENABLE_CLIENT_ACCESS(0, DISPLAY0B),
+ MC_ENABLE_CLIENT_ACCESS(0, DISPLAY0BB),
+ MC_ENABLE_CLIENT_ACCESS(0, DISPLAY0C),
+ MC_ENABLE_CLIENT_ACCESS(0, DISPLAY0CB),
+ MC_ENABLE_CLIENT_ACCESS(0, AFIR),
+ MC_ENABLE_CLIENT_ACCESS(0, DISPLAYHC),
+ MC_ENABLE_CLIENT_ACCESS(0, DISPLAYHCB),
+ MC_ENABLE_CLIENT_ACCESS(0, HDAR),
+ MC_ENABLE_CLIENT_ACCESS(0, HOST1XDMAR),
+ MC_ENABLE_CLIENT_ACCESS(0, HOST1XR),
+ MC_ENABLE_CLIENT_ACCESS(0, NVENCSRD),
+ MC_ENABLE_CLIENT_ACCESS(0, PPCSAHBDMAR),
+ MC_ENABLE_CLIENT_ACCESS(0, PPCSAHBSLVR));
+
+ constexpr u32 ClientAccess1 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(1, MPCORER),
+ MC_ENABLE_CLIENT_ACCESS(1, NVENCSWR),
+ MC_ENABLE_CLIENT_ACCESS(1, AFIW),
+ MC_ENABLE_CLIENT_ACCESS(1, HDAW),
+ MC_ENABLE_CLIENT_ACCESS(1, HOST1XW),
+ MC_ENABLE_CLIENT_ACCESS(1, MPCOREW),
+ MC_ENABLE_CLIENT_ACCESS(1, PPCSAHBDMAW),
+ MC_ENABLE_CLIENT_ACCESS(1, PPCSAHBSLVW));
+
+ constexpr u32 ClientAccess2 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(2, XUSB_HOSTR),
+ MC_ENABLE_CLIENT_ACCESS(2, XUSB_HOSTW),
+ MC_ENABLE_CLIENT_ACCESS(2, XUSB_DEVR),
+ MC_ENABLE_CLIENT_ACCESS(2, XUSB_DEVW));
+
+ constexpr u32 ClientAccess2_100 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(2, XUSB_HOSTR),
+ MC_ENABLE_CLIENT_ACCESS(2, XUSB_HOSTW),
+ MC_ENABLE_CLIENT_ACCESS(2, XUSB_DEVR),
+ MC_ENABLE_CLIENT_ACCESS(2, XUSB_DEVW),
+ MC_ENABLE_CLIENT_ACCESS(2, TSECSRD),
+ MC_ENABLE_CLIENT_ACCESS(2, TSECSWR));
+
+ constexpr u32 ClientAccess3 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(3, SDMMCRA),
+ MC_ENABLE_CLIENT_ACCESS(3, SDMMCRAA),
+ MC_ENABLE_CLIENT_ACCESS(3, SDMMCRAB),
+ MC_ENABLE_CLIENT_ACCESS(3, SDMMCWA),
+ MC_ENABLE_CLIENT_ACCESS(3, SDMMCWAA),
+ MC_ENABLE_CLIENT_ACCESS(3, SDMMCWAB),
+ MC_ENABLE_CLIENT_ACCESS(3, VICSRD),
+ MC_ENABLE_CLIENT_ACCESS(3, VICSWR),
+ MC_ENABLE_CLIENT_ACCESS(3, DISPLAYD),
+ MC_ENABLE_CLIENT_ACCESS(3, APER),
+ MC_ENABLE_CLIENT_ACCESS(3, APEW),
+ MC_ENABLE_CLIENT_ACCESS(3, NVJPGSRD),
+ MC_ENABLE_CLIENT_ACCESS(3, NVJPGSWR));
+
+ constexpr u32 ClientAccess3_100 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(3, SDMMCRA),
+ MC_ENABLE_CLIENT_ACCESS(3, SDMMCRAA),
+ MC_ENABLE_CLIENT_ACCESS(3, SDMMCRAB),
+ MC_ENABLE_CLIENT_ACCESS(3, SDMMCWA),
+ MC_ENABLE_CLIENT_ACCESS(3, SDMMCWAA),
+ MC_ENABLE_CLIENT_ACCESS(3, SDMMCWAB),
+ MC_ENABLE_CLIENT_ACCESS(3, VICSRD),
+ MC_ENABLE_CLIENT_ACCESS(3, VICSWR),
+ MC_ENABLE_CLIENT_ACCESS(3, DISPLAYD),
+ MC_ENABLE_CLIENT_ACCESS(3, NVDECSRD),
+ MC_ENABLE_CLIENT_ACCESS(3, NVDECSWR),
+ MC_ENABLE_CLIENT_ACCESS(3, APER),
+ MC_ENABLE_CLIENT_ACCESS(3, APEW),
+ MC_ENABLE_CLIENT_ACCESS(3, NVJPGSRD),
+ MC_ENABLE_CLIENT_ACCESS(3, NVJPGSWR));
+
+ constexpr u32 ClientAccess4 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(4, SESRD),
+ MC_ENABLE_CLIENT_ACCESS(4, SESWR));
+
+ constexpr u32 ClientAccess4_800 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(4, SESRD),
+ MC_ENABLE_CLIENT_ACCESS(4, SESWR),
+ MC_ENABLE_CLIENT_ACCESS(4, TSECRDB),
+ MC_ENABLE_CLIENT_ACCESS(4, TSECWRB));
+
+
+ constexpr u32 ClientAccess4_100 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(4, SESRD),
+ MC_ENABLE_CLIENT_ACCESS(4, SESWR));
+
+ #undef MC_ENABLE_CLIENT_ACCESS
+
+ constexpr u32 ForceInternalAccess0 = reg::Encode(MC_REG_BITS_ENUM(CLIENT_ACCESS0_AVPCARM7R, ENABLE));
+ constexpr u32 ForceInternalAccess0_100 = 0;
+
+ constexpr u32 ForceInternalAccess1 = reg::Encode(MC_REG_BITS_ENUM(CLIENT_ACCESS1_AVPCARM7W, ENABLE));
+ constexpr u32 ForceInternalAccess1_100 = 0;
+
+ constexpr u32 CarveoutConfig = reg::Encode(MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_IS_WPR, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_FORCE_APERTURE_ID_MATCH, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ALLOW_APERTURE_ID_MISMATCH, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_RD_EN, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_WR_EN, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_SEND_CFG_TO_GPU, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS),
+ MC_REG_BITS_VALUE(SECURITY_CARVEOUT_CFG0_APERTURE_ID, 0),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL3, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL2, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL1, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL0, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL3, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL2, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL1, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL0, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ADDRESS_TYPE, ANY_ADDRESS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_LOCK_MODE, LOCKED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_PROTECT_MODE, TZ_SECURE));
+
+ constexpr u32 CarveoutConfig_100 = reg::Encode(MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_IS_WPR, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_FORCE_APERTURE_ID_MATCH, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ALLOW_APERTURE_ID_MISMATCH, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_RD_EN, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_WR_EN, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_SEND_CFG_TO_GPU, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS),
+ MC_REG_BITS_VALUE(SECURITY_CARVEOUT_CFG0_APERTURE_ID, 0),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL3, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL2, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL1, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL0, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL3, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL2, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL1, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL0, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ADDRESS_TYPE, ANY_ADDRESS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_LOCK_MODE, LOCKED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_PROTECT_MODE, TZ_SECURE));
+
+ const u32 target_fw = GetTargetFirmware();
+ u32 client_access_2;
+ u32 client_access_3;
+ u32 client_access_4;
+ u32 carveout_config;
+ if (target_fw >= TargetFirmware_8_1_0) {
+ client_access_2 = ClientAccess2;
+ client_access_3 = ClientAccess3;
+ client_access_4 = ClientAccess4;
+ carveout_config = CarveoutConfig;
+ } else if (target_fw >= TargetFirmware_8_0_0) {
+ client_access_2 = ClientAccess2;
+ client_access_3 = ClientAccess3;
+ client_access_4 = ClientAccess4_800;
+ carveout_config = CarveoutConfig;
+ } else {
+ client_access_2 = ClientAccess2_100;
+ client_access_3 = ClientAccess3_100;
+ client_access_4 = ClientAccess4_100;
+ carveout_config = CarveoutConfig_100;
+ }
+
+ /* Configure carveout 4. */
+ reg::Write(MC + MC_SECURITY_CARVEOUT4_BOM, g_kernel_carveouts[0].address >> 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT4_BOM_HI, g_kernel_carveouts[0].address >> 32);
+ reg::Write(MC + MC_SECURITY_CARVEOUT4_SIZE_128KB, g_kernel_carveouts[0].size / 128_KB);
+ reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_ACCESS0, ClientAccess0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_ACCESS1, ClientAccess1);
+ reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_ACCESS2, client_access_2);
+ reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_ACCESS3, client_access_3);
+ reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_ACCESS4, client_access_4);
+ reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS0, (target_fw >= TargetFirmware_4_0_0) ? ForceInternalAccess0 : ForceInternalAccess0_100);
+ reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS1, (target_fw >= TargetFirmware_4_0_0) ? ForceInternalAccess1 : ForceInternalAccess1_100);
+ reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS2, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS3, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS4, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT4_CFG0, carveout_config);
+
+ /* Configure carveout 5. */
+ reg::Write(MC + MC_SECURITY_CARVEOUT5_BOM, g_kernel_carveouts[1].address >> 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT5_BOM_HI, g_kernel_carveouts[1].address >> 32);
+ reg::Write(MC + MC_SECURITY_CARVEOUT5_SIZE_128KB, g_kernel_carveouts[1].size / 128_KB);
+ reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_ACCESS0, ClientAccess0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_ACCESS1, ClientAccess1);
+ reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_ACCESS2, client_access_2);
+ reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_ACCESS3, client_access_3);
+ reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_ACCESS4, client_access_4);
+ reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS0, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS1, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS2, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS3, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS4, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT5_CFG0, carveout_config);
+ }
+
+ void ConfigureSlaveSecurity() {
+ u32 reg0, reg1, reg2;
+ if (GetTargetFirmware() > TargetFirmware_1_0_0) {
+ reg0 = reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(0, SATA_AUX, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(0, DTV, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(0, QSPI, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(0, SE, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(0, SATA, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(0, LA, ENABLE));
+
+ reg1 = reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(1, SPI1, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(1, SPI2, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(1, SPI3, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(1, SPI5, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(1, SPI6, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(1, I2C6, ENABLE));
+
+ reg2 = reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(2, SDMMC3, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(2, DDS, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(2, DP2, ENABLE));
+
+ const auto hw_type = GetHardwareType();
+
+ /* Switch Lite can't use HDMI, so set CEC to secure on hoag. */
+ if (hw_type == fuse::HardwareType_Hoag) {
+ reg0 |= reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(0, CEC, ENABLE));
+ }
+
+ /* Icosa, Iowa, and Aula all set I2C4 to be secure. */
+ if (hw_type == fuse::HardwareType_Icosa && hw_type == fuse::HardwareType_Iowa && hw_type == fuse::HardwareType_Aula) {
+ reg1 |= reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(1, I2C4, ENABLE));
+
+ }
+
+ /* Hoag additionally sets UART_B to secure. */
+ if (hw_type == fuse::HardwareType_Hoag) {
+ reg1 |= reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(1, UART_B, ENABLE));
+ }
+
+ /* Copper and Calcio lack a lot of hardware, so set the corresponding registers to secure for them. */
+ if (hw_type == fuse::HardwareType_Calcio || hw_type == fuse::HardwareType_Copper) {
+ reg1 |= reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(1, UART_B, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(1, UART_C, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(1, SPI4, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(1, I2C2, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(1, I2C3, ENABLE));
+
+ /* Copper/Calcio have no gamecard reader, and thus set SDMMC2 as secure. */
+ reg2 |= reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(2, SDMMC2, ENABLE));
+ }
+
+ /* Mariko hardware types (not Icosa or Copper) additionally set mariko-only mmio (SE2, PKA1, FEK) as secure. */
+ if (hw_type != fuse::HardwareType_Icosa && hw_type != fuse::HardwareType_Copper) {
+ reg2 |= reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(2, SE2, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(2, PKA1, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(2, FEK, ENABLE));
+ }
+ } else {
+ reg0 = reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(0, SATA_AUX, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(0, DTV, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(0, QSPI, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(0, SATA, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(0, LA, ENABLE));
+
+ reg1 = reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(1, SPI1, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(1, SPI2, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(1, SPI3, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(1, SPI5, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(1, SPI6, ENABLE),
+ SLAVE_SECURITY_REG_BITS_ENUM(1, I2C6, ENABLE));
+
+ reg2 = reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(2, DDS, ENABLE),
+ REG_BITS_VALUE(5, 1, 1), /* Note: Bit 5 is not documented in TRM. */
+ REG_BITS_VALUE(4, 1, 1)); /* Note: Bit 4 is not documented in TRM. */
+ }
+
+ reg::Write(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0, reg0);
+ reg::Write(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG1_0, reg1);
+ reg::Write(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG2_0, reg2);
+ }
+
+ void SetupSecureRegisters() {
+ /* Configure timers 5-8 and watchdog timers 0-3 as secure. */
+ reg::Write(TIMER + TIMER_SHARED_TIMER_SECURE_CFG, TIMER_REG_BITS_ENUM(SHARED_TIMER_SECURE_CFG_TMR5, ENABLE),
+ TIMER_REG_BITS_ENUM(SHARED_TIMER_SECURE_CFG_TMR6, ENABLE),
+ TIMER_REG_BITS_ENUM(SHARED_TIMER_SECURE_CFG_TMR7, ENABLE),
+ TIMER_REG_BITS_ENUM(SHARED_TIMER_SECURE_CFG_TMR8, ENABLE),
+ TIMER_REG_BITS_ENUM(SHARED_TIMER_SECURE_CFG_WDT0, ENABLE),
+ TIMER_REG_BITS_ENUM(SHARED_TIMER_SECURE_CFG_WDT1, ENABLE),
+ TIMER_REG_BITS_ENUM(SHARED_TIMER_SECURE_CFG_WDT2, ENABLE),
+ TIMER_REG_BITS_ENUM(SHARED_TIMER_SECURE_CFG_WDT3, ENABLE));
+
+ /* Lock cluster switching, to prevent usage of the A53 cores. */
+ reg::Write(FLOW_CTLR + FLOW_CTLR_BPMP_CLUSTER_CONTROL, FLOW_REG_BITS_ENUM(BPMP_CLUSTER_CONTROL_ACTIVE_CLUSTER_LOCK, ENABLE),
+ FLOW_REG_BITS_ENUM(BPMP_CLUSTER_CONTROL_CLUSTER_SWITCH_ENABLE, DISABLE),
+ FLOW_REG_BITS_ENUM(BPMP_CLUSTER_CONTROL_ACTIVE_CLUSTER, FAST));
+
+ /* Enable flow controller debug qualifier for legacy FIQs. */
+ reg::Write(FLOW_CTLR + FLOW_CTLR_FLOW_DBG_QUAL, FLOW_REG_BITS_ENUM(FLOW_DBG_QUAL_FIQ2CCPLEX_ENABLE, ENABLE));
+
+ /* Configure the PMC to disable deep power-down. */
+ reg::Write(PMC + APBDEV_PMC_DPD_ENABLE, PMC_REG_BITS_ENUM(DPD_ENABLE_TSC_MULT_EN, DISABLE),
+ PMC_REG_BITS_ENUM(DPD_ENABLE_ON, DISABLE));
+
+ /* Configure the video protect region. */
+ reg::Write(MC + MC_VIDEO_PROTECT_GPU_OVERRIDE_0, 1);
+ reg::Write(MC + MC_VIDEO_PROTECT_GPU_OVERRIDE_1, 0);
+ reg::Write(MC + MC_VIDEO_PROTECT_BOM, 0);
+ reg::Write(MC + MC_VIDEO_PROTECT_SIZE_MB, 0);
+ reg::Write(MC + MC_VIDEO_PROTECT_REG_CTRL, MC_REG_BITS_ENUM(VIDEO_PROTECT_REG_CTRL_VIDEO_PROTECT_ALLOW_TZ_WRITE, DISABLED),
+ MC_REG_BITS_ENUM(VIDEO_PROTECT_REG_CTRL_VIDEO_PROTECT_WRITE_ACCESS, DISABLED));
+
+ /* Configure the SEC carveout. */
+ reg::Write(MC + MC_SEC_CARVEOUT_BOM, 0);
+ reg::Write(MC + MC_SEC_CARVEOUT_SIZE_MB, 0);
+ reg::Write(MC + MC_SEC_CARVEOUT_REG_CTRL, MC_REG_BITS_ENUM(SEC_CARVEOUT_REG_CTRL_SEC_CARVEOUT_WRITE_ACCESS, DISABLED));
+
+ /* Configure the MTS carveout. */
+ reg::Write(MC + MC_MTS_CARVEOUT_BOM, 0);
+ reg::Write(MC + MC_MTS_CARVEOUT_SIZE_MB, 0);
+ reg::Write(MC + MC_MTS_CARVEOUT_ADR_HI, 0);
+ reg::Write(MC + MC_MTS_CARVEOUT_REG_CTRL, MC_REG_BITS_ENUM(MTS_CARVEOUT_REG_CTRL_MTS_CARVEOUT_WRITE_ACCESS, DISABLED));
+
+ /* Configure the security carveout. */
+ reg::Write(MC + MC_SECURITY_CFG0, MC_REG_BITS_VALUE(SECURITY_CFG0_SECURITY_BOM, 0));
+ reg::Write(MC + MC_SECURITY_CFG1, MC_REG_BITS_VALUE(SECURITY_CFG1_SECURITY_SIZE, 0));
+ reg::Write(MC + MC_SECURITY_CFG3, MC_REG_BITS_VALUE(SECURITY_CFG3_SECURITY_BOM_HI, 3));
+
+ /* Configure security carveout 1. */
+ reg::Write(MC + MC_SECURITY_CARVEOUT1_BOM, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT1_BOM_HI, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT1_SIZE_128KB, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_ACCESS0, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_ACCESS1, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_ACCESS2, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_ACCESS3, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_ACCESS4, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS0, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS1, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS2, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS3, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS4, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT1_CFG0, MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_IS_WPR, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_FORCE_APERTURE_ID_MATCH, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ALLOW_APERTURE_ID_MISMATCH, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_RD_EN, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_WR_EN, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_SEND_CFG_TO_GPU, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS),
+ MC_REG_BITS_VALUE(SECURITY_CARVEOUT_CFG0_APERTURE_ID, 0),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL3, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL2, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL1, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL0, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL3, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL2, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL1, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL0, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ADDRESS_TYPE, UNTRANSLATED_ONLY),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_LOCK_MODE, LOCKED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_PROTECT_MODE, LOCKBIT_SECURE));
+
+ /* Security carveout 2 will be configured later by SetupGpuCarveout, after magic values are written to configure gpu/tsec. */
+
+ /* Configure carveout 3. */
+ reg::Write(MC + MC_SECURITY_CARVEOUT3_BOM, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT3_BOM_HI, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT3_SIZE_128KB, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_ACCESS0, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_ACCESS1, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_ACCESS2, MC_REG_BITS_ENUM (CLIENT_ACCESS2_GPUSRD, ENABLE),
+ MC_REG_BITS_ENUM (CLIENT_ACCESS2_GPUSWR, ENABLE));
+ reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_ACCESS3, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_ACCESS4, MC_REG_BITS_ENUM (CLIENT_ACCESS4_GPUSRD2, ENABLE),
+ MC_REG_BITS_ENUM (CLIENT_ACCESS4_GPUSWR2, ENABLE));
+ reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS0, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS1, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS2, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS3, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS4, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT3_CFG0, MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_IS_WPR, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_FORCE_APERTURE_ID_MATCH, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ALLOW_APERTURE_ID_MISMATCH, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_RD_EN, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_WR_EN, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_SEND_CFG_TO_GPU, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS),
+ MC_REG_BITS_VALUE(SECURITY_CARVEOUT_CFG0_APERTURE_ID, 3),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL3, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL2, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL1, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL0, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL3, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL2, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL1, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL0, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ADDRESS_TYPE, UNTRANSLATED_ONLY),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_LOCK_MODE, LOCKED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_PROTECT_MODE, LOCKBIT_SECURE));
+
+ /* If we're cold-booting and on 1.0.0, alter the default carveout size. */
+ if (g_is_cold_boot && GetTargetFirmware() <= TargetFirmware_1_0_0) {
+ g_kernel_carveouts[0].size = 200 * 128_KB;
+ }
+
+ /* NOTE: Here Nintendo configures the two kernel carveouts; we will do this later, to allow fusee to continue using AVP_CACHE. */
+ /* SetupKernelCarveouts(); */
+
+ /* Configure slave register security. */
+ ConfigureSlaveSecurity();
+ }
+
+ void SetupSmmu() {
+ /* Turn on SMMU translation for all devices. */
+ reg::Write(MC + MC_SMMU_TRANSLATION_ENABLE_0, ~0u);
+ reg::Write(MC + MC_SMMU_TRANSLATION_ENABLE_1, ~0u);
+ reg::Write(MC + MC_SMMU_TRANSLATION_ENABLE_2, ~0u);
+ reg::Write(MC + MC_SMMU_TRANSLATION_ENABLE_3, ~0u);
+ reg::Write(MC + MC_SMMU_TRANSLATION_ENABLE_4, ~0u);
+
+ /* On modern firmware, configure ASIDs 1-3 as secure, and all others as non-secure. */
+ if (GetTargetFirmware() >= TargetFirmware_4_0_0) {
+ reg::Write(MC + MC_SMMU_ASID_SECURITY, MC_REG_BITS_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_1, SECURE),
+ MC_REG_BITS_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_2, SECURE),
+ MC_REG_BITS_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_3, SECURE));
+ } else {
+ /* Legacy firmware accesses the MC directly, though, and so correspondingly we must allow ASIDs to be edited by non-secure world. */
+ reg::Write(MC + MC_SMMU_ASID_SECURITY, 0);
+ }
+
+ reg::Write(MC + MC_SMMU_ASID_SECURITY_1, 0);
+ reg::Write(MC + MC_SMMU_ASID_SECURITY_2, 0);
+ reg::Write(MC + MC_SMMU_ASID_SECURITY_3, 0);
+ reg::Write(MC + MC_SMMU_ASID_SECURITY_4, 0);
+ reg::Write(MC + MC_SMMU_ASID_SECURITY_5, 0);
+ reg::Write(MC + MC_SMMU_ASID_SECURITY_6, 0);
+ reg::Write(MC + MC_SMMU_ASID_SECURITY_7, 0);
+
+ /* Initialize the PTB registers to zero .*/
+ reg::Write(MC + MC_SMMU_PTB_ASID, 0);
+ reg::Write(MC + MC_SMMU_PTB_DATA, 0);
+
+ /* Configure the TLB and PTC, then read TLB_CONFIG to ensure configuration takes. */
+ reg::Write(MC + MC_SMMU_TLB_CONFIG, MC_REG_BITS_ENUM (SMMU_TLB_CONFIG_TLB_HIT_UNDER_MISS, ENABLE),
+ MC_REG_BITS_ENUM (SMMU_TLB_CONFIG_TLB_ROUND_ROBIN_ARBITRATION, ENABLE),
+ MC_REG_BITS_VALUE(SMMU_TLB_CONFIG_TLB_ACTIVE_LINES, 0x30));
+
+ reg::Write(MC + MC_SMMU_PTC_CONFIG, MC_REG_BITS_ENUM (SMMU_PTC_CONFIG_PTC_CACHE_ENABLE, ENABLE),
+ MC_REG_BITS_VALUE(SMMU_PTC_CONFIG_PTC_REQ_LIMIT, 8),
+ MC_REG_BITS_VALUE(SMMU_PTC_CONFIG_PTC_INDEX_MAP, 0x3F));
+
+ reg::Read (MC + MC_SMMU_TLB_CONFIG);
+
+ /* Flush the entire page table cache, and read TLB_CONFIG to ensure the flush takes. */
+ reg::Write(MC + MC_SMMU_PTC_FLUSH_0, 0);
+ reg::Read (MC + MC_SMMU_TLB_CONFIG);
+
+ /* Flush the entire translation lookaside buffer, and read TLB_CONFIG to ensure the flush takes. */
+ reg::Write(MC + MC_SMMU_TLB_FLUSH, 0);
+ reg::Read (MC + MC_SMMU_TLB_CONFIG);
+
+ /* Enable the SMMU, and read TLB_CONFIG to ensure the enable takes. */
+ reg::Write(MC + MC_SMMU_CONFIG, MC_REG_BITS_ENUM (SMMU_CONFIG_SMMU_ENABLE, ENABLE));
+ reg::Read (MC + MC_SMMU_TLB_CONFIG);
+ }
+
+ void SetupSecureEl2AndEl1SystemRegisters() {
+ /* Setup actlr_el2 and actlr_el3. */
+ {
+ util::BitPack32 actlr = {};
+
+ actlr.Set(1); /* Enable access to cpuactlr from lower EL. */
+ actlr.Set(1); /* Enable access to cpuectlr from lower EL. */
+ actlr.Set(1); /* Enable access to l2ctlr from lower EL. */
+ actlr.Set(1); /* Enable access to l2actlr from lower EL. */
+ actlr.Set(1); /* Enable access to l2ectlr from lower EL. */
+
+ HW_CPU_SET_ACTLR_EL3(actlr);
+ HW_CPU_SET_ACTLR_EL2(actlr);
+ }
+
+ /* Setup hcr_el2. */
+ {
+ util::BitPack64 hcr = {};
+
+ hcr.Set(1); /* EL1 is aarch64 mode. */
+
+ HW_CPU_SET_HCR_EL2(hcr);
+ }
+
+ /* Configure all domain access permissions as manager. */
+ HW_CPU_SET_DACR32_EL2(~0u);
+
+ /* Setup sctlr_el1. */
+ {
+ util::BitPack64 sctlr = { hw::SctlrEl1::Res1 };
+
+ sctlr.Set(0); /* Globally disable the MMU. */
+ sctlr.Set(0); /* Disable alignment fault checking. */
+ sctlr.Set(0); /* Globally disable the data and unified caches. */
+ sctlr.Set(1); /* Enable stack alignment checking. */
+ sctlr.Set(1); /* Enable el0 stack alignment checking. */
+ sctlr.Set(1); /* Enable cp15 barrier operations. */
+ sctlr.Set(0); /* Disable ThumbEE. */
+ sctlr.Set(0); /* Enable itd instructions. */
+ sctlr.Set(0); /* Enable setend instruction. */
+ sctlr.Set(0); /* Disable el0 interrupt mask access. */
+ sctlr.Set(0); /* Globally disable the instruction cache. */
+ sctlr.Set(0); /* Disable el0 access to dc zva instruction. */
+ sctlr.Set(1); /* wfi instructions in el0 trap. */
+ sctlr.Set(1); /* wfe instructions in el0 trap. */
+ sctlr.Set(0); /* Do not force writable pages to be ExecuteNever. */
+ sctlr.Set(0); /* Data accesses in el0 are little endian. */
+ sctlr.Set(0); /* Exceptions should be little endian. */
+ sctlr.Set(0); /* Disable el0 access to dc cvau, dc civac, dc cvac, ic ivau. */
+
+ HW_CPU_SET_SCTLR_EL1(sctlr);
+ }
+
+ /* Setup sctlr_el2. */
+ {
+ util::BitPack64 sctlr = { hw::SctlrEl2::Res1 };
+
+ sctlr.Set(0); /* Globally disable the MMU. */
+ sctlr.Set(0); /* Disable alignment fault checking. */
+ sctlr.Set(0); /* Globally disable the data and unified caches. */
+ sctlr.Set(1); /* Enable stack alignment checking. */
+ sctlr.Set(0); /* Globally disable the instruction cache. */
+ sctlr.Set(0); /* Do not force writable pages to be ExecuteNever. */
+ sctlr.Set(0); /* Exceptions should be little endian. */
+
+ HW_CPU_SET_SCTLR_EL2(sctlr);
+ }
+
+ /* Ensure instruction consistency. */
+ hw::InstructionSynchronizationBarrier();
+ }
+
+ void SetupNonSecureSystemRegisters(u32 tsc_frequency) {
+ /* Set cntfrq_el0. */
+ HW_CPU_SET_CNTFRQ_EL0(tsc_frequency);
+
+ /* Set cnthctl_el2. */
+ {
+ util::BitPack32 cnthctl = {};
+
+ cnthctl.Set(1); /* Do not trap accesses to cntpct_el0. */
+ cnthctl.Set(1); /* Do not trap accesses to cntp_ctl_el0, cntp_cval_el0, and cntp_tval_el0. */
+ cnthctl.Set(0); /* Disable the event stream. */
+ cnthctl.Set(0); /* Trigger events on 0 -> 1 transition. */
+ cnthctl.Set(0); /* Select bit0 of cntpct_el0 as the event stream trigger. */
+
+ HW_CPU_SET_CNTHCTL_EL2(cnthctl);
+ }
+
+ /* Ensure instruction consistency. */
+ hw::InstructionSynchronizationBarrier();
+ }
+
+ void SetupGpuCarveout() {
+ /* Configure carveout 2. */
+ reg::Write(MC + MC_SECURITY_CARVEOUT2_BOM, static_cast(MemoryRegionDramGpuCarveout.GetAddress() >> 0));
+ reg::Write(MC + MC_SECURITY_CARVEOUT2_BOM_HI, static_cast(MemoryRegionDramGpuCarveout.GetAddress() >> BITSIZEOF(u32)));
+ reg::Write(MC + MC_SECURITY_CARVEOUT2_SIZE_128KB, MemoryRegionDramGpuCarveout.GetSize() / 128_KB);
+ reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_ACCESS0, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_ACCESS1, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_ACCESS2, MC_REG_BITS_ENUM (CLIENT_ACCESS2_GPUSRD, ENABLE),
+ MC_REG_BITS_ENUM (CLIENT_ACCESS2_GPUSWR, ENABLE),
+ MC_REG_BITS_ENUM (CLIENT_ACCESS2_TSECSRD, ENABLE));
+ reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_ACCESS3, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_ACCESS4, MC_REG_BITS_ENUM (CLIENT_ACCESS4_GPUSRD2, ENABLE),
+ MC_REG_BITS_ENUM (CLIENT_ACCESS4_GPUSWR2, ENABLE));
+ reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS0, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS1, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS2, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS3, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS4, 0);
+ reg::Write(MC + MC_SECURITY_CARVEOUT2_CFG0, MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_IS_WPR, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_FORCE_APERTURE_ID_MATCH, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ALLOW_APERTURE_ID_MISMATCH, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_RD_EN, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_WR_EN, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_SEND_CFG_TO_GPU, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS),
+ MC_REG_BITS_VALUE(SECURITY_CARVEOUT_CFG0_APERTURE_ID, 2),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL3, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL2, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL1, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL0, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL3, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL2, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL1, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL0, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ADDRESS_TYPE, UNTRANSLATED_ONLY),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_LOCK_MODE, LOCKED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_PROTECT_MODE, LOCKBIT_SECURE));
+ }
+
+ void DisableArc() {
+ /* Configure IRAM top/bottom to point to memory ends (disabling redirection). */
+ reg::Write(MC + MC_IRAM_BOM, MC_REG_BITS_VALUE(IRAM_BOM_IRAM_BOM, (~0u)));
+ reg::Write(MC + MC_IRAM_TOM, MC_REG_BITS_VALUE(IRAM_TOM_IRAM_TOM, ( 0u)));
+
+ /* Lock the IRAM aperture. */
+ reg::ReadWrite(MC + MC_IRAM_REG_CTRL, MC_REG_BITS_ENUM(IRAM_REG_CTRL_IRAM_CFG_WRITE_ACCESS, DISABLED));
+
+ /* Disable the ARC clock gate override. */
+ reg::ReadWrite(CLK_RST + CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRD, CLK_RST_REG_BITS_ENUM(LVL2_CLK_GATE_OVRD_ARC_CLK_OVR_ON, OFF));
+
+ /* Read IRAM REG CTRL to make sure our writes take. */
+ reg::Read(MC + MC_IRAM_REG_CTRL);
+ }
+
+ void DisableUntranslatedDeviceMemoryAccess() {
+ /* If we can (mariko only), disable GMMU accesses that bypass the SMMU. */
+ /* Additionally, force all untranslated acccesses to hit one of the carveouts. */
+ if (GetSocType() == fuse::SocType_Mariko) {
+ reg::Write(MC + MC_UNTRANSLATED_REGION_CHECK, MC_REG_BITS_ENUM(UNTRANSLATED_REGION_CHECK_UNTRANSLATED_REGION_CHECK_ACCESS, DISABLED),
+ MC_REG_BITS_ENUM(UNTRANSLATED_REGION_CHECK_REQUIRE_UNTRANSLATED_CLIENTS_HIT_CARVEOUT, ENABLED),
+ MC_REG_BITS_ENUM(UNTRANSLATED_REGION_CHECK_REQUIRE_UNTRANSLATED_GPU_HIT_CARVEOUT, ENABLED));
+ }
+ }
+
+ void FinalizeCarveoutSecureScratchRegisters() {
+ /* Define carveout scratch values. */
+ constexpr uintptr_t WarmbootCarveoutAddress = MemoryRegionDram.GetAddress();
+ constexpr size_t WarmbootCarveoutSize = 128_KB;
+
+ #define MC_ENABLE_CLIENT_ACCESS(INDEX, WHICH) MC_REG_BITS_ENUM(CLIENT_ACCESS##INDEX##_##WHICH, ENABLE)
+
+ constexpr u32 WarmbootCarveoutClientAccess0 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(0, AVPCARM7R),
+ MC_ENABLE_CLIENT_ACCESS(0, PPCSAHBSLVR));
+
+ constexpr u32 WarmbootCarveoutClientAccess1 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(1, AVPCARM7W));
+
+ #undef MC_ENABLE_CLIENT_ACCESS
+
+ constexpr u32 WarmbootCarveoutForceInternalAccess0 = reg::Encode(MC_REG_BITS_ENUM(CLIENT_ACCESS0_AVPCARM7R, ENABLE),
+ MC_REG_BITS_ENUM(CLIENT_ACCESS0_PPCSAHBSLVR, ENABLE));
+
+ constexpr u32 WarmbootCarveoutForceInternalAccess1 = reg::Encode(MC_REG_BITS_ENUM(CLIENT_ACCESS1_AVPCARM7W, ENABLE));
+
+ constexpr u32 WarmbootCarveoutConfig = reg::Encode(MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_IS_WPR, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_FORCE_APERTURE_ID_MATCH, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ALLOW_APERTURE_ID_MISMATCH, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_RD_EN, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_WR_EN, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_SEND_CFG_TO_GPU, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS),
+ MC_REG_BITS_VALUE(SECURITY_CARVEOUT_CFG0_APERTURE_ID, 0),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL3, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL2, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL1, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL0, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL3, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL2, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL1, DISABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL0, ENABLED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ADDRESS_TYPE, ANY_ADDRESS),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_LOCK_MODE, UNLOCKED),
+ MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_PROTECT_MODE, LOCKBIT_SECURE));
+
+ /* Save the carveout values into secure scratch. */
+
+ /* Save MC_SECURITY_CARVEOUT4_BOM. */
+ reg::ReadWrite(PMC + APBDEV_PMC_SECURE_SCRATCH51, REG_BITS_VALUE( 0, 15, WarmbootCarveoutAddress >> 17));
+
+ /* Save MC_SECURITY_CARVEOUT4_BOM_HI. */
+ reg::ReadWrite(PMC + APBDEV_PMC_SECURE_SCRATCH16, REG_BITS_VALUE(30, 2, WarmbootCarveoutAddress >> 32));
+
+ /* Save MC_SECURITY_CARVEOUT4_SIZE_128KB. */
+ reg::ReadWrite(PMC + APBDEV_PMC_SECURE_SCRATCH55, REG_BITS_VALUE(12, 12, WarmbootCarveoutSize / 128_KB));
+
+ /* Save MC_SECURITY_CARVEOUT4_CLIENT_ACCESS. */
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH74, WarmbootCarveoutClientAccess0);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH75, WarmbootCarveoutClientAccess1);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH76, 0);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH77, 0);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH78, 0);
+
+ /* Save MC_SECURITY_CARVEOUT4_FORCE_INTERNAL_ACCESS. */
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH99, WarmbootCarveoutForceInternalAccess0);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH100, WarmbootCarveoutForceInternalAccess1);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH101, 0);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH102, 0);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH103, 0);
+
+ /* Save MC_SECURITY_CARVEOUT4_CFG0. */
+ reg::ReadWrite(PMC + APBDEV_PMC_SECURE_SCRATCH39, REG_BITS_VALUE(0, 27, WarmbootCarveoutConfig));
+ }
+
+ void ValidateResetExpected() {
+ /* We're coming out of reset, so check that we expected to come out of reset. */
+ if (!IsResetExpected()) {
+ secmon::SetError(pkg1::ErrorInfo_UnexpectedReset);
+ AMS_ABORT("unexpected reset");
+ }
+ SetResetExpected(false);
+ }
+
+ void ExitChargerHiZMode() {
+ /* Setup I2c-1. */
+ pinmux::SetupI2c1();
+ clkrst::EnableI2c1Clock();
+
+ /* Initialize I2c-1. */
+ i2c::Initialize(i2c::Port_1);
+
+ /* Exit Hi-Z mode. */
+ charger::ExitHiZMode();
+
+ /* Disable clock to I2c-1. */
+ clkrst::DisableI2c1Clock();
+ }
+
+ bool IsExitLp0() {
+ return reg::Read(MC + MC_SECURITY_CFG3) == 0;
+ }
+
+ void SetupLogForBoot() {
+ log::Initialize(secmon::GetLogPort(), secmon::GetLogBaudRate(), secmon::GetLogFlags());
+ log::SendText("OHAYO\n", 6);
+ log::Flush();
+ }
+
+ void LogExitLp0() {
+ /* NOTE: Nintendo only does this on dev, but we will always do it. */
+ if (true /* !pkg1::IsProduction() */) {
+ SetupLogForBoot();
+ }
+ }
+
+ void SetupForLp0Exit() {
+ /* Exit HiZ mode in charger, if we need to. */
+ const auto target_fw = GetTargetFirmware();
+ const bool force_exit_hiz_mode = (target_fw < TargetFirmware_4_0_0) || (target_fw < TargetFirmware_8_0_0 && fuse::GetHardwareType() == fuse::HardwareType_Icosa);
+ if (force_exit_hiz_mode || smc::IsChargerHiZModeEnabled()) {
+ ExitChargerHiZMode();
+ }
+
+ /* Refill the random cache, which is volatile and thus wiped on warmboot. */
+ smc::FillRandomCache();
+
+ /* Unlock the security engine. */
+ secmon::smc::UnlockSecurityEngine();
+ }
+
+ }
+
+ void Setup1() {
+ /* Load the global configuration context. */
+ InitializeConfigurationContext();
+
+ /* Initialize uart for logging. */
+ SetupLogForBoot();
+
+ /* Initialize the security engine. */
+ se::Initialize();
+
+ /* Initialize the gic. */
+ gic::InitializeCommon();
+ }
+
+ void Setup1ForWarmboot() {
+ /* Initialize the security engine. */
+ se::Initialize();
+
+ /* Initialize the gic. */
+ gic::InitializeCommon();
+ }
+
+ void SaveSecurityEngineAesKeySlotTestVector() {
+ GenerateSecurityEngineAesKeySlotTestVector(g_se_aes_key_slot_test_vector, sizeof(g_se_aes_key_slot_test_vector));
+ }
+
+ void SetupSocSecurity() {
+ /* Set the fuse visibility. */
+ clkrst::SetFuseVisibility(true);
+
+ /* Set fuses as only secure-writable. */
+ fuse::SetWriteSecureOnly();
+
+ /* Lockout the fuses. */
+ fuse::Lockout();
+
+ /* Set the security engine to secure mode. */
+ se::SetSecure(true);
+
+ /* Verify the security engine's sticky bits. */
+ VerifySecurityEngineStickyBits();
+
+ /* Verify the security engine's Aes slots contain correct contents. */
+ VerifySecurityEngineAesKeySlotTestVector();
+
+ /* Clear aes keyslots. */
+ ClearAesKeySlots();
+
+ /* Clear rsa keyslots. */
+ ClearRsaKeySlots();
+
+ /* Overwrite keys that we want to be random with random contents. */
+ se::InitializeRandom();
+ se::ConfigureAutomaticContextSave();
+ se::SetRandomKey(pkg1::AesKeySlot_Temporary);
+ se::GenerateSrk();
+ se::SetRandomKey(pkg1::AesKeySlot_TzramSaveKek);
+
+ /* Initialize pmc secure scratch. */
+ if (GetSocType() == fuse::SocType_Erista) {
+ pmc::InitializeRandomScratch();
+ }
+ pmc::LockSecureRegister(pmc::SecureRegister_Srk);
+
+ /* Setup secure registers. */
+ SetupSecureRegisters();
+
+ /* Setup the smmu. */
+ SetupSmmu();
+
+ /* Clear the cpu reset vector. */
+ reg::Write(EVP + EVP_CPU_RESET_VECTOR, 0);
+
+ /* Configure the SB registers to our start address. */
+ constexpr u32 ResetVectorLow = static_cast((PhysicalTzramProgramResetVector >> 0));
+ constexpr u32 ResetVectorHigh = static_cast((PhysicalTzramProgramResetVector >> BITSIZEOF(u32)));
+
+ /* Write our reset vector to the secure boot registers. */
+ reg::Write(secmon::MemoryRegionVirtualDeviceSystem.GetAddress() + SB_AA64_RESET_LOW, ResetVectorLow | 1);
+ reg::Write(secmon::MemoryRegionVirtualDeviceSystem.GetAddress() + SB_AA64_RESET_HIGH, ResetVectorHigh);
+
+ /* Disable non-secure writes to the reset vector. */
+ reg::Write(secmon::MemoryRegionVirtualDeviceSystem.GetAddress() + SB_CSR, SB_REG_BITS_ENUM(CSR_NS_RST_VEC_WR_DIS, DISABLE));
+
+ /* Read back SB_CSR to make sure our non-secure write disable takes. */
+ reg::Read(secmon::MemoryRegionVirtualDeviceSystem.GetAddress() + SB_CSR);
+
+ /* Write our reset vector to scratch registers used by warmboot, and lock those scratch registers. */
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH34, ResetVectorLow);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH35, ResetVectorHigh);
+ pmc::LockSecureRegister(pmc::SecureRegister_ResetVector);
+
+ /* Setup the security engine interrupt. */
+ constexpr int SecurityEngineInterruptId = 90;
+ constexpr u8 SecurityEngineInterruptCoreMask = (1 << 3);
+ gic::SetPriority (SecurityEngineInterruptId, gic::HighestPriority);
+ gic::SetInterruptGroup(SecurityEngineInterruptId, 0);
+ gic::SetEnable (SecurityEngineInterruptId, true);
+ gic::SetSpiTargetCpu (SecurityEngineInterruptId, SecurityEngineInterruptCoreMask);
+ gic::SetSpiMode (SecurityEngineInterruptId, gic::InterruptMode_Level);
+
+ /* Setup the activity monitor interrupt. */
+ constexpr int ActivityMonitorInterruptId = 77;
+ constexpr u8 ActivityMonitorInterruptCoreMask = (1 << 3);
+ gic::SetPriority (ActivityMonitorInterruptId, gic::HighestPriority);
+ gic::SetInterruptGroup(ActivityMonitorInterruptId, 0);
+ gic::SetEnable (ActivityMonitorInterruptId, true);
+ gic::SetSpiTargetCpu (ActivityMonitorInterruptId, ActivityMonitorInterruptCoreMask);
+ gic::SetSpiMode (ActivityMonitorInterruptId, gic::InterruptMode_Level);
+
+ /* Setup the mariko fatal error interrupt. */
+ constexpr u8 MarikoFatalInterruptCoreMask = 0b1111;
+ gic::SetPriority (MarikoFatalErrorInterruptId, gic::HighestPriority);
+ gic::SetInterruptGroup(MarikoFatalErrorInterruptId, 0);
+ gic::SetEnable (MarikoFatalErrorInterruptId, true);
+ gic::SetSpiTargetCpu (MarikoFatalErrorInterruptId, 0);
+ gic::SetSpiMode (MarikoFatalErrorInterruptId, gic::InterruptMode_Level);
+
+ /* If we're coldboot, perform one-time setup. */
+ if (g_is_cold_boot) {
+ /* Register all interrupt handlers. */
+ SetInterruptHandler(SecurityEngineInterruptId, SecurityEngineInterruptCoreMask, se::HandleInterrupt);
+ SetInterruptHandler(ActivityMonitorInterruptId, ActivityMonitorInterruptCoreMask, actmon::HandleInterrupt);
+ SetInterruptHandler(MarikoFatalErrorInterruptId, MarikoFatalInterruptCoreMask, secmon::HandleMarikoFatalErrorInterrupt);
+
+ /* We're expecting the other cores to come out of reset. */
+ for (int i = 1; i < NumCores; ++i) {
+ SetResetExpected(i, true);
+ }
+
+ /* We only coldboot once. */
+ g_is_cold_boot = false;
+ }
+ }
+
+ void SetupSocSecurityWarmboot() {
+ /* Check that we're allowed to continue. */
+ ValidateResetExpected();
+
+ /* Unmap the tzram identity mapping. */
+ UnmapTzram();
+
+ /* If we're exiting LP0, there's a little more work for us to do. */
+ if (IsExitLp0()) {
+ /* Log that we're exiting LP0. */
+ LogExitLp0();
+
+ /* Perform initial setup. */
+ Setup1ForWarmboot();
+
+ /* Generate a random srk. */
+ se::GenerateSrk();
+
+ /* Setup the Soc security. */
+ SetupSocSecurity();
+
+ /* Set the PMC and MC as secure-only. */
+ SetupPmcAndMcSecure();
+
+ /* Perform Lp0-exit specific init. */
+ SetupForLp0Exit();
+
+ /* Setup the Soc protections. */
+ SetupSocProtections();
+ }
+
+ /* Perform remaining CPU initialization. */
+ SetupCpuCoreContext();
+ SetupCpuSErrorDebug();
+ }
+
+ void SetupSocProtections() {
+ /* Setup the GPU carveout. */
+ SetupGpuCarveout();
+
+ /* Configure the two kernel carveouts. */
+ SetupKernelCarveouts();
+
+ /* Disable the ARC. */
+ DisableArc();
+
+ /* Disable untranslated memory accesses by devices. */
+ DisableUntranslatedDeviceMemoryAccess();
+
+ /* Further protections aren't applied on <= 1.0.0. */
+ if (GetTargetFirmware() <= TargetFirmware_1_0_0) {
+ return;
+ }
+
+ /* Finalize and lock the carveout scratch registers. */
+ FinalizeCarveoutSecureScratchRegisters();
+ pmc::LockSecureRegister(pmc::SecureRegister_Carveout);
+
+ }
+
+ void SetupPmcAndMcSecure() {
+ // const auto target_fw = GetTargetFirmware();
+
+ // if (target_fw >= TargetFirmware_2_0_0) {
+ // /* Set the PMC secure. */ I REFUSE
+ // reg::ReadWrite(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0, SLAVE_SECURITY_REG_BITS_ENUM(0, PMC, ENABLE));
+ // }
+
+ // if (target_fw >= TargetFirmware_4_0_0) {
+ // /* Set the MC secure. */ I REFUSE
+ // reg::ReadWrite(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG1_0, SLAVE_SECURITY_REG_BITS_ENUM(1, MC0, ENABLE),
+ // SLAVE_SECURITY_REG_BITS_ENUM(1, MC1, ENABLE),
+ // SLAVE_SECURITY_REG_BITS_ENUM(1, MCB, ENABLE));
+ // }
+ return;
+ }
+
+ void SetupCpuCoreContext() {
+ /* Get the tsc frequency. */
+ const u32 tsc_frequency = reg::Read(MemoryRegionVirtualDeviceSysCtr0.GetAddress() + SYSCTR0_CNTFID0);
+
+ /* Setup the secure EL2/EL1 system registers. */
+ SetupSecureEl2AndEl1SystemRegisters();
+
+ /* Setup the non-secure system registers. */
+ SetupNonSecureSystemRegisters(tsc_frequency);
+
+ /* Reset the cpu flow controller registers. */
+ flow::ResetCpuRegisters(hw::GetCurrentCoreId());
+
+ /* Initialize the core unique gic registers. */
+ gic::InitializeCoreUnique();
+
+ /* Configure cpu fiq. */
+ constexpr int FiqInterruptId = 28;
+ gic::SetPriority (FiqInterruptId, gic::HighestPriority);
+ gic::SetInterruptGroup(FiqInterruptId, 0);
+ gic::SetEnable (FiqInterruptId, true);
+
+ /* Restore the cpu's debug registers. */
+ RestoreDebugRegisters();
+ }
+
+ void SetupCpuSErrorDebug() {
+ /* Get whether we should enable SError debug. */
+ const auto &bc_data = secmon::GetBootConfig().data;
+ const bool enabled = bc_data.IsDevelopmentFunctionEnabled() && bc_data.IsSErrorDebugEnabled();
+
+ /* Get and set scr_el3. */
+ {
+ util::BitPack32 scr;
+ HW_CPU_GET_SCR_EL3(scr);
+
+ scr.Set(enabled ? 0 : 1);
+ HW_CPU_SET_SCR_EL3(scr);
+ }
+
+ /* Prevent reordering instructions around this call. */
+ hw::InstructionSynchronizationBarrier();
+ }
+
+ void SetKernelCarveoutRegion(int index, uintptr_t address, size_t size) {
+ /* Configure the carveout. */
+ auto &carveout = g_kernel_carveouts[index];
+ carveout.address = address;
+ carveout.size = size;
+
+ SetupKernelCarveouts();
+ }
+
+}
\ No newline at end of file
diff --git a/Source/Atmosphere-Patches/secmon_smc_power_management.cpp b/Source/Atmosphere-Patches/secmon_smc_power_management.cpp
new file mode 100644
index 00000000..7747bb09
--- /dev/null
+++ b/Source/Atmosphere-Patches/secmon_smc_power_management.cpp
@@ -0,0 +1,567 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#include
+#include "../secmon_cache.hpp"
+#include "../secmon_cpu_context.hpp"
+#include "../secmon_error.hpp"
+#include "../secmon_misc.hpp"
+#include "secmon_smc_power_management.hpp"
+#include "secmon_smc_se_lock.hpp"
+
+#include "sc7fw_bin.h"
+
+namespace ams::secmon {
+
+ /* Declare assembly functionality. */
+ void *GetCoreExceptionStackVirtual();
+
+}
+
+namespace ams::secmon::smc {
+
+ /* Declare assembly power-management functionality. */
+ void PivotStackAndInvoke(void *stack, void (*function)());
+ void FinalizePowerOff();
+
+ namespace {
+
+ constexpr inline const uintptr_t PMC = MemoryRegionVirtualDevicePmc.GetAddress();
+ constexpr inline const uintptr_t APB_MISC = MemoryRegionVirtualDeviceApbMisc.GetAddress();
+ constexpr inline const uintptr_t GPIO = MemoryRegionVirtualDeviceGpio.GetAddress();
+ constexpr inline const uintptr_t CLK_RST = MemoryRegionVirtualDeviceClkRst.GetAddress();
+ constexpr inline const uintptr_t EVP = secmon::MemoryRegionVirtualDeviceExceptionVectors.GetAddress();
+ constexpr inline const uintptr_t FLOW_CTLR = MemoryRegionVirtualDeviceFlowController.GetAddress();
+ constexpr inline const uintptr_t AHB_ARBC = MemoryRegionVirtualDeviceSystem.GetAddress();
+
+ constexpr inline uintptr_t CommonSmcStackTop = MemoryRegionVirtualTzramVolatileData.GetEndAddress() - (0x80 * (NumCores - 1));
+
+ enum PowerStateType {
+ PowerStateType_StandBy = 0,
+ PowerStateType_PowerDown = 1,
+ };
+
+ enum PowerStateId {
+ PowerStateId_Sc7 = 27,
+ };
+
+ /* http://infocenter.arm.com/help/topic/com.arm.doc.den0022d/Power_State_Coordination_Interface_PDD_v1_1_DEN0022D.pdf Page 46 */
+ struct SuspendCpuPowerState {
+ using StateId = util::BitPack32::Field< 0, 16, PowerStateId>;
+ using StateType = util::BitPack32::Field<16, 1, PowerStateType>;
+ using PowerLevel = util::BitPack32::Field<24, 2, u32>;
+ };
+
+ constinit bool g_charger_hi_z_mode_enabled = false;
+
+ constinit const reg::BitsMask CpuPowerGateStatusMasks[NumCores] = {
+ PMC_REG_BITS_MASK(PWRGATE_STATUS_CE0),
+ PMC_REG_BITS_MASK(PWRGATE_STATUS_CE1),
+ PMC_REG_BITS_MASK(PWRGATE_STATUS_CE2),
+ PMC_REG_BITS_MASK(PWRGATE_STATUS_CE3),
+ };
+
+ constinit const APBDEV_PMC_PWRGATE_TOGGLE_PARTID CpuPowerGateTogglePartitionIds[NumCores] = {
+ APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE0,
+ APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE1,
+ APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE2,
+ APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE3,
+ };
+
+ bool IsCpuPoweredOn(const reg::BitsMask mask) {
+ return reg::HasValue(PMC + APBDEV_PMC_PWRGATE_STATUS, REG_BITS_VALUE_FROM_MASK(mask, APBDEV_PMC_PWRGATE_STATUS_STATUS_ON));
+ }
+
+ void PowerOnCpu(const reg::BitsMask mask, u32 toggle_partid) {
+ /* If the cpu is already on, we have nothing to do. */
+ if (IsCpuPoweredOn(mask)) {
+ return;
+ }
+
+ /* Wait until nothing is being powergated. */
+ int timeout = 5000;
+ while (true) {
+ if (reg::HasValue(PMC + APBDEV_PMC_PWRGATE_TOGGLE, PMC_REG_BITS_ENUM(PWRGATE_TOGGLE_START, DISABLE))) {
+ break;
+ }
+
+ util::WaitMicroSeconds(1);
+ if ((--timeout) < 0) {
+ /* NOTE: Nintendo doesn't do any error handling here... */
+ return;
+ }
+ }
+
+ /* Toggle on the cpu partition. */
+ reg::Write(PMC + APBDEV_PMC_PWRGATE_TOGGLE, PMC_REG_BITS_ENUM (PWRGATE_TOGGLE_START, ENABLE),
+ PMC_REG_BITS_VALUE(PWRGATE_TOGGLE_PARTID, toggle_partid));
+
+ /* Wait up to 5000 us for the powergate to complete. */
+ timeout = 5000;
+ while (true) {
+ if (IsCpuPoweredOn(mask)) {
+ break;
+ }
+
+ util::WaitMicroSeconds(1);
+ if ((--timeout) < 0) {
+ /* NOTE: Nintendo doesn't do any error handling here... */
+ return;
+ }
+ }
+ }
+
+ void ResetCpu(int which_core) {
+ reg::Write(CLK_RST + CLK_RST_CONTROLLER_RST_CPUG_CMPLX_SET, REG_BITS_VALUE(which_core + 0x00, 1, 1), /* CPURESETn */
+ REG_BITS_VALUE(which_core + 0x10, 1, 1)); /* CORERESETn */
+ }
+
+ void StartCpu(int which_core) {
+ reg::Write(CLK_RST + CLK_RST_CONTROLLER_RST_CPUG_CMPLX_CLR, REG_BITS_VALUE(which_core + 0x00, 1, 1), /* CPURESETn */
+ REG_BITS_VALUE(which_core + 0x10, 1, 1)); /* CORERESETn */
+ }
+
+ void PowerOffCpuImpl() {
+ /* Get the current core id. */
+ const auto core_id = hw::GetCurrentCoreId();
+
+ /* Configure the flow controller to prepare for shutting down the current core. */
+ flow::SetCpuCsr(core_id, FLOW_CTLR_CPUN_CSR_ENABLE_EXT_POWERGATE_CPU_ONLY);
+ flow::SetHaltCpuEvents(core_id, false);
+ flow::SetCc4Ctrl(core_id, 0);
+
+ /* Save the core's context for restoration on next power-on. */
+ SaveDebugRegisters();
+ SetCoreOff();
+
+ /* Ensure there are no pending memory transactions prior to our power-down. */
+ FlushEntireDataCache();
+
+ /* Finalize our powerdown and wait for an interrupt. */
+ FinalizePowerOff();
+ }
+
+ void ValidateSocStateForSuspend() {
+ /* Validate that all other cores are off. */
+ AMS_ABORT_UNLESS(reg::HasValue(PMC + APBDEV_PMC_PWRGATE_STATUS, PMC_REG_BITS_VALUE(PWRGATE_STATUS_CE123, 0)));
+
+ /* Validate that the bpmp is appropriately halted. */
+ const bool jtag = IsJtagEnabled();
+ AMS_ABORT_UNLESS(reg::Read(FLOW_CTLR + FLOW_CTLR_HALT_COP_EVENTS) == reg::Encode(FLOW_REG_BITS_ENUM (HALT_COP_EVENTS_MODE, FLOW_MODE_STOP),
+ FLOW_REG_BITS_ENUM_SEL(HALT_COP_EVENTS_JTAG, jtag, ENABLED, DISABLED)));
+
+ /* Further validations aren't guaranteed on < 6.0.0. */
+ if (GetTargetFirmware() < TargetFirmware_6_0_0) {
+ return;
+ }
+
+ /* Validate that USB2, APB-DMA, AHB-DMA are held in reset. */
+ AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_RST_DEVICES_H, CLK_RST_REG_BITS_ENUM(RST_DEVICES_H_SWR_USB2_RST, ENABLE),
+ CLK_RST_REG_BITS_ENUM(RST_DEVICES_H_SWR_APBDMA_RST, ENABLE),
+ CLK_RST_REG_BITS_ENUM(RST_DEVICES_H_SWR_AHBDMA_RST, ENABLE)));
+
+ /* Validate that USBD is held in reset. */
+ AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_USBD_RST, ENABLE)));
+
+ /* Validate that AHB-DMA, USB, USB2, COP are not allowed to arbitrate on the AHB. */
+ AMS_ABORT_UNLESS(reg::HasValue(AHB_ARBC + AHB_ARBITRATION_DISABLE, AHB_REG_BITS_ENUM(ARBITRATION_DISABLE_COP, DISABLE),
+ AHB_REG_BITS_ENUM(ARBITRATION_DISABLE_AHBDMA, DISABLE),
+ AHB_REG_BITS_ENUM(ARBITRATION_DISABLE_USB, DISABLE),
+ AHB_REG_BITS_ENUM(ARBITRATION_DISABLE_USB2, DISABLE)));
+
+ /* Validate that the GPIO controller has clock enabled. */
+ AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_GPIO, ENABLE)));
+
+ /* Validate that both FUSE and KFUSE have clock enabled. */
+ AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_CLK_OUT_ENB_H, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_H_CLK_ENB_FUSE, ENABLE),
+ CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_H_CLK_ENB_KFUSE, ENABLE)));
+
+ /* Validate that all of IRAM has clock enabled. */
+ AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_CLK_OUT_ENB_U, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_IRAMA, ENABLE),
+ CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_IRAMB, ENABLE),
+ CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_IRAMC, ENABLE),
+ CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_IRAMD, ENABLE)));
+
+ /* Validate that ACTMON has clock enabled. */
+ AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_CLK_OUT_ENB_V, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_V_CLK_ENB_ACTMON, ENABLE)));
+
+ /* Validate that ENTROPY has clock enabled. */
+ AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_CLK_OUT_ENB_W, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_W_CLK_ENB_ENTROPY, ENABLE)));
+ }
+
+ void GenerateCryptographicallyRandomBytes(void * const dst, int size) {
+ /* Flush the region we're about to fill to ensure consistency with the SE. */
+ hw::FlushDataCache(dst, size);
+ hw::DataSynchronizationBarrierInnerShareable();
+
+ /* Generate random bytes. */
+ se::GenerateRandomBytes(dst, size);
+ hw::DataSynchronizationBarrierInnerShareable();
+
+ /* Flush to ensure the CPU sees consistent data for the region. */
+ hw::FlushDataCache(dst, size);
+ hw::DataSynchronizationBarrierInnerShareable();
+ }
+
+ void SaveSecureContextForErista() {
+ /* Generate a random key source. */
+ util::AlignedBuffer key_source;
+ GenerateCryptographicallyRandomBytes(key_source, se::AesBlockSize);
+
+ const u32 * const key_source_32 = reinterpret_cast(static_cast(key_source));
+
+ /* Ensure that the key source registers are not locked. */
+ AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_KeySourceReadWrite) != pmc::LockState::Locked);
+
+ /* Write the key source, lock writes to the key source, and verify that the key source is write-locked. */
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH24, key_source_32[0]);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH25, key_source_32[1]);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH26, key_source_32[2]);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH27, key_source_32[3]);
+ pmc::LockSecureRegister(pmc::SecureRegister_KeySourceWrite);
+ AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_KeySourceWrite) == pmc::LockState::Locked);
+
+ /* Verify the key source is correct in registers, and read-lock the key source registers. */
+ AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH24) == key_source_32[0]);
+ AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH25) == key_source_32[1]);
+ AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH26) == key_source_32[2]);
+ AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH27) == key_source_32[3]);
+ pmc::LockSecureRegister(pmc::SecureRegister_KeySourceRead);
+
+ /* Ensure that the key source registers are locked. */
+ AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_KeySourceReadWrite) == pmc::LockState::Locked);
+
+ /* Generate a random kek into keyslot 2. */
+ se::SetRandomKey(pkg1::AesKeySlot_TzramSaveKek);
+
+ /* Verify that the se is in a validate state, context save, and validate again. */
+ {
+ se::ValidateErrStatus();
+ ON_SCOPE_EXIT { se::ValidateErrStatus(); };
+
+ {
+ /* Transition to non-secure mode for the duration of the context save operation. */
+ se::SetSecure(false);
+ ON_SCOPE_EXIT { se::SetSecure(true); };
+
+ /* Get a pointer to the context storage. */
+ se::Context * const context = MemoryRegionVirtualDramSecureDataStoreSecurityEngineState.GetPointer();
+ static_assert(MemoryRegionVirtualDramSecureDataStoreSecurityEngineState.GetSize() == sizeof(*context));
+
+ /* Save the context. */
+ se::SaveContext(context);
+
+ /* Ensure that the cpu sees consistent data. */
+ hw::FlushDataCache(context, sizeof(*context));
+ hw::DataSynchronizationBarrierInnerShareable();
+
+ /* Write the context pointer to pmc scratch, so that the bootrom will restore it on wake. */
+ reg::Write(PMC + APBDEV_PMC_SCRATCH43, MemoryRegionPhysicalDramSecureDataStoreSecurityEngineState.GetAddress());
+ }
+ }
+
+ /* Clear keyslot 3, and then derive the save key. */
+ se::ClearAesKeySlot(pkg1::AesKeySlot_TzramSaveKey);
+ se::SetEncryptedAesKey256(pkg1::AesKeySlot_TzramSaveKey, pkg1::AesKeySlot_TzramSaveKek, key_source, se::AesBlockSize);
+
+ /* Declare a temporary block to be used as both iv and mac. */
+ u32 temp_block[se::AesBlockSize / sizeof(u32)] = {};
+
+ /* Ensure that the SE sees consistent data for tzram. */
+ const void * const tzram_save_src = MemoryRegionVirtualTzramReadOnlyAlias.GetPointer() + MemoryRegionVirtualTzramVolatileData.GetSize() + MemoryRegionVirtualTzramVolatileStack.GetSize();
+ void * const tzram_save_dst = MemoryRegionVirtualIramSc7Work.GetPointer();
+ constexpr size_t TzramSaveSize = MemoryRegionVirtualDramSecureDataStoreTzram.GetSize();
+
+ hw::FlushDataCache(tzram_save_src, TzramSaveSize);
+ hw::FlushDataCache(tzram_save_dst, TzramSaveSize);
+ hw::DataSynchronizationBarrierInnerShareable();
+
+ /* Encrypt tzram using our random key. */
+ se::EncryptAes256Cbc(tzram_save_dst, TzramSaveSize, pkg1::AesKeySlot_TzramSaveKey, tzram_save_src, TzramSaveSize, temp_block, se::AesBlockSize);
+ hw::FlushDataCache(tzram_save_dst, TzramSaveSize);
+ hw::DataSynchronizationBarrierInnerShareable();
+
+ /* Copy the data from work space to the secure storage destination. */
+ void * const tzram_store_dst = MemoryRegionVirtualDramSecureDataStoreTzram.GetPointer();
+ std::memcpy(tzram_store_dst, tzram_save_dst, TzramSaveSize);
+ hw::FlushDataCache(tzram_store_dst, TzramSaveSize);
+ hw::DataSynchronizationBarrierInnerShareable();
+
+ /* Compute cmac of tzram into our temporary block. */
+ se::ComputeAes256Cmac(temp_block, se::AesBlockSize, pkg1::AesKeySlot_TzramSaveKey, tzram_save_src, TzramSaveSize);
+
+ /* Ensure that the cmac registers are not locked. */
+ AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_CmacReadWrite) != pmc::LockState::Locked);
+
+ /* Write the cmac, lock writes to the cmac, and verify that the cmac is write-locked. */
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH112, temp_block[0]);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH113, temp_block[1]);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH114, temp_block[2]);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH115, temp_block[3]);
+ pmc::LockSecureRegister(pmc::SecureRegister_CmacWrite);
+ AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_CmacWrite) == pmc::LockState::Locked);
+
+ /* Verify the key source is correct in registers, and read-lock the key source registers. */
+ AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH112) == temp_block[0]);
+ AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH113) == temp_block[1]);
+ AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH114) == temp_block[2]);
+ AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH115) == temp_block[3]);
+ pmc::LockSecureRegister(pmc::SecureRegister_CmacRead);
+
+ /* Ensure that the key source registers are locked. */
+ AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_CmacReadWrite) == pmc::LockState::Locked);
+ }
+
+ void SaveSecureContextForMariko() {
+ /* Save security engine context to TZRAM SE carveout (inaccessible to cpu). */
+ se::SaveContextAutomatic();
+
+ /* Save TZRAM to shadow-TZRAM in always-on power domain. */
+ se::SaveTzramAutomatic();
+ }
+
+ void SaveSecureContext() {
+ /* Save the appropriate secure context. */
+ const auto soc_type = GetSocType();
+ if (soc_type == fuse::SocType_Erista) {
+ SaveSecureContextForErista();
+ } else /* if (soc_type == fuse::SocType_Mariko) */ {
+ SaveSecureContextForMariko();
+ }
+
+ /* Save the debug code. */
+ #if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING)
+ {
+ const void * const debug_code_src = MemoryRegionVirtualDebugCode.GetPointer();
+ void * const debug_code_dst = MemoryRegionVirtualDramDebugDataStore.GetPointer();
+ std::memcpy(debug_code_dst, debug_code_src, MemoryRegionVirtualDebugCode.GetSize());
+ hw::FlushDataCache(debug_code_dst, MemoryRegionVirtualDebugCode.GetSize());
+ hw::DataSynchronizationBarrierInnerShareable();
+ }
+ #endif
+ }
+
+ void LoadAndStartSc7BpmpFirmware() {
+ /* Set BPMP reset. */
+ reg::Write(CLK_RST + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SET_SET_COP_RST, ENABLE));
+
+ /* Set the PMC as insecure, so that the BPMP firmware can access it. */
+ reg::ReadWrite(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0, SLAVE_SECURITY_REG_BITS_ENUM(0, PMC, DISABLE));
+
+ /* Set the exception vectors for the bpmp. RESET should point to RESET, all others should point to generic exception/panic. */
+ constexpr const u32 Sc7FirmwareResetVector = static_cast(MemoryRegionPhysicalIramSc7Firmware.GetAddress() + 0x0);
+ constexpr const u32 Sc7FirmwarePanicVector = static_cast(MemoryRegionPhysicalIramSc7Firmware.GetAddress() + 0x4);
+
+ reg::Write(EVP + EVP_COP_RESET_VECTOR, Sc7FirmwareResetVector);
+ reg::Write(EVP + EVP_COP_UNDEF_VECTOR, Sc7FirmwarePanicVector);
+ reg::Write(EVP + EVP_COP_SWI_VECTOR, Sc7FirmwarePanicVector);
+ reg::Write(EVP + EVP_COP_PREFETCH_ABORT_VECTOR, Sc7FirmwarePanicVector);
+ reg::Write(EVP + EVP_COP_DATA_ABORT_VECTOR, Sc7FirmwarePanicVector);
+ reg::Write(EVP + EVP_COP_RSVD_VECTOR, Sc7FirmwarePanicVector);
+ reg::Write(EVP + EVP_COP_IRQ_VECTOR, Sc7FirmwarePanicVector);
+ reg::Write(EVP + EVP_COP_FIQ_VECTOR, Sc7FirmwarePanicVector);
+
+ /* Disable activity monitor bpmp monitoring, so that we don't panic upon bpmp wake. */
+ actmon::StopMonitoringBpmp();
+
+ /* Load the bpmp firmware. */
+ void * const sc7fw_load_address = MemoryRegionVirtualIramSc7Firmware.GetPointer();
+ std::memcpy(sc7fw_load_address, sc7fw_bin, sc7fw_bin_size);
+ hw::FlushDataCache(sc7fw_load_address, sc7fw_bin_size);
+ hw::DataSynchronizationBarrierInnerShareable();
+
+ /* Ensure that the bpmp firmware was loaded. */
+ AMS_ABORT_UNLESS(crypto::IsSameBytes(sc7fw_load_address, sc7fw_bin, sc7fw_bin_size));
+
+ /* Clear BPMP reset. */
+ reg::Write(CLK_RST + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_CLR_CLR_COP_RST, ENABLE));
+
+ /* Start the bpmp. */
+ reg::Write(FLOW_CTLR + FLOW_CTLR_HALT_COP_EVENTS, FLOW_REG_BITS_ENUM(HALT_COP_EVENTS_MODE, FLOW_MODE_NONE));
+ }
+
+ void SaveSecureContextAndSuspend() {
+ /* Ensure there are no pending memory transactions before we continue */
+ FlushEntireDataCache();
+ hw::DataSynchronizationBarrierInnerShareable();
+
+ /* Save all secure context (security engine state + tzram). */
+ SaveSecureContext();
+
+ /* Load and start the sc7 firmware on the bpmp. */
+ if (GetTargetFirmware() >= TargetFirmware_2_0_0) {
+ LoadAndStartSc7BpmpFirmware();
+ }
+
+ /* Log our suspension. */
+ /* NOTE: Nintendo only does this on dev, but we will always do it. */
+ if (true /* !pkg1::IsProduction() */) {
+ log::Initialize(secmon::GetLogPort(), secmon::GetLogBaudRate(), secmon::GetLogFlags());
+ log::SendText("OYASUMI\n", 8);
+ log::Flush();
+ }
+
+ /* If we're on erista, configure the bootrom to allow our custom warmboot firmware. */
+ if (GetSocType() == fuse::SocType_Erista) {
+ reg::Write(PMC + APBDEV_PMC_SCRATCH31, 0x2202E012);
+ reg::Write(PMC + APBDEV_PMC_SCRATCH32, 0x6001DC28);
+ }
+
+ /* Finalize our powerdown and wait for an interrupt. */
+ FinalizePowerOff();
+ }
+
+ SmcResult SuspendCpuImpl(SmcArguments &args) {
+ /* Decode arguments. */
+ const util::BitPack32 power_state = { static_cast(args.r[1]) };
+ const uintptr_t entry_point = args.r[2];
+ const uintptr_t context_id = args.r[3];
+
+ const auto state_type = power_state.Get();
+ const auto state_id = power_state.Get();
+
+ const auto core_id = hw::GetCurrentCoreId();
+
+ /* Validate arguments. */
+ SMC_R_UNLESS(state_type == PowerStateType_PowerDown, PsciDenied);
+ SMC_R_UNLESS(state_id == PowerStateId_Sc7, PsciDenied);
+
+ /* Orchestrate charger transition to Hi-Z mode if needed. */
+ if (IsChargerHiZModeEnabled()) {
+ /* Ensure we can do comms over i2c-1. */
+ clkrst::EnableI2c1Clock();
+
+ /* If the charger isn't in hi-z mode, perform a transition. */
+ if (!charger::IsHiZMode()) {
+ charger::EnterHiZMode();
+
+ /* Wait up to 50ms for the transition to complete. */
+ const auto start_time = util::GetMicroSeconds();
+ auto current_time = start_time;
+ while ((current_time - start_time) <= 50'000) {
+ if (auto intr_status = reg::Read(GPIO + 0x634); (intr_status & 1) == 0) {
+ /* Wait 256 us to ensure the transition completes. */
+ util::WaitMicroSeconds(256);
+ break;
+ }
+ current_time = util::GetMicroSeconds();
+ }
+ }
+
+ /* Disable i2c-1, since we're done communicating over it. */
+ clkrst::DisableI2c1Clock();
+ }
+
+ /* Enable wake event detection. */
+ pmc::EnableWakeEventDetection();
+
+ /* Ensure that i2c-5 is usable for communicating with the pmic. */
+ clkrst::EnableI2c5Clock();
+ i2c::Initialize(i2c::Port_5);
+
+ /* Orchestrate sleep entry with the pmic. */
+ pmic::EnableSleep();
+
+ /* Ensure that the soc is in a state valid for us to suspend. */
+ if (GetTargetFirmware() >= TargetFirmware_2_0_0) {
+ ValidateSocStateForSuspend();
+ }
+
+ /* Configure the pmc for sc7 entry. */
+ pmc::ConfigureForSc7Entry();
+
+ /* Configure the flow controller for sc7 entry. */
+ flow::SetCc4Ctrl(core_id, 0);
+ flow::SetHaltCpuEvents(core_id, false);
+ flow::ClearL2FlushControl();
+ flow::SetCpuCsr(core_id, FLOW_CTLR_CPUN_CSR_ENABLE_EXT_POWERGATE_CPU_TURNOFF_CPURAIL);
+
+ /* Save the entry context. */
+ SetEntryContext(core_id, entry_point, context_id);
+
+ /* Configure the cpu context for reset. */
+ SaveDebugRegisters();
+ SetCoreOff();
+ SetResetExpected(true);
+
+ /* Switch to use the common smc stack (all other cores are off), and perform suspension. */
+ PivotStackAndInvoke(reinterpret_cast(CommonSmcStackTop), SaveSecureContextAndSuspend);
+
+ /* This code will never be reached. */
+ __builtin_unreachable();
+ }
+
+ }
+
+ void PowerOffCpu() {
+ /* Get the current core id. */
+ const auto core_id = hw::GetCurrentCoreId();
+
+ /* Note that we're expecting a reset for the current core. */
+ SetResetExpected(true);
+
+ /* If we're on the final core, shut down directly. Otherwise, invoke with special stack. */
+ if (core_id == NumCores - 1) {
+ PowerOffCpuImpl();
+ } else {
+ PivotStackAndInvoke(GetCoreExceptionStackVirtual(), PowerOffCpuImpl);
+ }
+
+ /* This code will never be reached. */
+ __builtin_unreachable();
+ }
+
+ SmcResult SmcPowerOffCpu(SmcArguments &args) {
+ AMS_UNUSED(args);
+
+ PowerOffCpu();
+ }
+
+ SmcResult SmcPowerOnCpu(SmcArguments &args) {
+ /* Get and validate the core to power on. */
+ const int which_core = args.r[1];
+ SMC_R_UNLESS(0 <= which_core && which_core < NumCores, PsciInvalidParameters);
+
+ /* Ensure the core isn't already on. */
+ SMC_R_UNLESS(!IsCoreOn(which_core), PsciAlreadyOn);
+
+ /* Save the entry context. */
+ SetEntryContext(which_core, args.r[2], args.r[3]);
+
+ /* Reset the cpu. */
+ ResetCpu(which_core);
+
+ /* Turn on the core. */
+ PowerOnCpu(CpuPowerGateStatusMasks[which_core], CpuPowerGateTogglePartitionIds[which_core]);
+
+ /* Start the core. */
+ StartCpu(which_core);
+
+ return SmcResult::PsciSuccess;
+ }
+
+ SmcResult SmcSuspendCpu(SmcArguments &args) {
+ return LockSecurityEngineAndInvoke(args, SuspendCpuImpl);
+ }
+
+ bool IsChargerHiZModeEnabled() {
+ return g_charger_hi_z_mode_enabled;
+ }
+
+ void SetChargerHiZModeEnabled(bool en) {
+ g_charger_hi_z_mode_enabled = en;
+ }
+
+}
diff --git a/Source/Atmosphere-Patches/secmon_smc_register_access.cpp b/Source/Atmosphere-Patches/secmon_smc_register_access.cpp
index 88757041..4bd7c47e 100644
--- a/Source/Atmosphere-Patches/secmon_smc_register_access.cpp
+++ b/Source/Atmosphere-Patches/secmon_smc_register_access.cpp
@@ -99,16 +99,19 @@ namespace ams::secmon::smc {
#include "secmon_define_pmc_access_table.inc"
#include "secmon_define_mc_access_table.inc"
#include "secmon_define_emc_access_table.inc"
+ #include "secmon_define_emc01_access_table.inc"
#include "secmon_define_rtc_pmc_access_table.inc"
#include "secmon_define_mc01_access_table.inc"
constexpr const AccessTableEntry AccessTables[] = {
- { PmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDevicePmc.GetAddress(), PmcAccessTable::Address, PmcAccessTable::Size, },
- { McAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceMemoryController.GetAddress(), McAccessTable::Address, McAccessTable::Size, },
- { EmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceExternalMemoryController.GetAddress(), EmcAccessTable::Address, EmcAccessTable::Size, },
- { RtcPmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceRtcPmc.GetAddress(), RtcPmcAccessTable::Address, RtcPmcAccessTable::Size, },
- { Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController0.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController0.GetAddress(), Mc01AccessTable::Size, },
- { Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController1.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController1.GetAddress(), Mc01AccessTable::Size, },
+ { PmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDevicePmc.GetAddress(), PmcAccessTable::Address, PmcAccessTable::Size, },
+ { McAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceMemoryController.GetAddress(), McAccessTable::Address, McAccessTable::Size, },
+ { EmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceExternalMemoryController.GetAddress(), EmcAccessTable::Address, EmcAccessTable::Size, },
+ { Emc01AccessTable::ReducedAccessTable.data(), Emc01AccessTable::Address + MemoryRegionVirtualDeviceExternalMemoryController0.GetAddress(), Emc01AccessTable::Address + MemoryRegionPhysicalDeviceExternalMemoryController0.GetAddress(), Emc01AccessTable::Size, },
+ { Emc01AccessTable::ReducedAccessTable.data(), Emc01AccessTable::Address + MemoryRegionVirtualDeviceExternalMemoryController1.GetAddress(), Emc01AccessTable::Address + MemoryRegionPhysicalDeviceExternalMemoryController1.GetAddress(), Emc01AccessTable::Size, },
+ { RtcPmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceRtcPmc.GetAddress(), RtcPmcAccessTable::Address, RtcPmcAccessTable::Size, },
+ { Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController0.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController0.GetAddress(), Mc01AccessTable::Size, },
+ { Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController1.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController1.GetAddress(), Mc01AccessTable::Size, },
};
constexpr bool IsAccessAllowed(const AccessTableEntry &entry, uintptr_t address) {
@@ -170,12 +173,12 @@ namespace ams::secmon::smc {
/* This is "probably" to fuck with hackers who got access to the SMC and are trying to get control of the */
/* BPMP to exploit jamais vu, deja vu, or other related DMA/wake-from-sleep vulnerabilities. */
constexpr uintptr_t MC = MemoryRegionPhysicalDeviceMemoryController.GetAddress();
- SMC_R_UNLESS((address == (MC + MC_SMMU_AVPC_ASID) || address == (MC + MC_SMMU_PPCS1_ASID)), InvalidArgument);
+ // SMC_R_UNLESS((address == (MC + MC_SMMU_AVPC_ASID) || address == (MC + MC_SMMU_PPCS1_ASID)), InvalidArgument);
- /* For backwards compatibility, we'll allow access to these devices on 1.0.0. */
- if (GetTargetFirmware() < TargetFirmware_2_0_0) {
+ // /* For backwards compatibility, we'll allow access to these devices on 1.0.0. */
+ // if (GetTargetFirmware() < TargetFirmware_2_0_0) {
virtual_address = MemoryRegionVirtualDeviceMemoryController.GetAddress() + (address - MC);
- }
+ // }
}
/* Perform the read or write, if we should. */
diff --git a/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp b/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp
index 0baf0b56..fd96684e 100644
--- a/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp
@@ -13,6 +13,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
+
#include
#include "ldr_capabilities.hpp"
#include "ldr_content_management.hpp"
@@ -126,8 +127,7 @@ namespace ams::ldr {
bool g_is_ptm;
/* Global Zstd decompression context. */
- constexpr size_t ZstdDctxWorkspaceSize = 0x176E8;
- alignas(8) u8 g_zstd_dctx_workspace[ZstdDctxWorkspaceSize];
+ alignas(8) u8 g_zstd_dctx_workspace[util::DecompressZstdWithBicWorkBufferSizeDefault];
Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) {
/* No version verification is done before 8.1.0. */
@@ -181,8 +181,8 @@ namespace ams::ldr {
return (MakeProgramInfoFlag(static_cast(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Application;
}
- Npdm::AddressSpaceType GetAddressSpaceType(const Meta *meta) {
- return static_cast((meta->npdm->flags & Npdm::MetaFlag_AddressSpaceTypeMask) >> Npdm::MetaFlag_AddressSpaceTypeShift);
+ Npdm::ProcessAddressSpace GetProcessAddressSpace(const Meta *meta) {
+ return static_cast((meta->npdm->flags0 & Npdm::MetaFlag0_ProcessAddressSpaceMask) >> Npdm::MetaFlag0_ProcessAddressSpaceShift);
}
Acid::PoolPartition GetPoolPartition(const Meta *meta) {
@@ -225,7 +225,7 @@ namespace ams::ldr {
/* Read NSO header. */
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, 0, g_nso_headers + ctx.nso_count, sizeof(NsoHeader)));
- R_UNLESS(read_size == sizeof(NsoHeader), ldr::ResultInvalidNso());
+ R_UNLESS_LOG(read_size == sizeof(NsoHeader), ldr::ResultInvalidNso(), "[ldr] NSO header truncated!\n");
/* Note nso is present. */
switch (i) {
@@ -264,7 +264,7 @@ namespace ams::ldr {
Result CheckAutoLoad(const AutoLoadModuleContext &ctx, u32 acid_flags) {
/* We must always have a main. */
- R_UNLESS(ctx.ali.has_main, ldr::ResultInvalidNso());
+ R_UNLESS_LOG(ctx.ali.has_main, ldr::ResultInvalidNso(), "[ldr] Missing main!\n");
/* Validate flags and extents for all present NSOs. */
for (int i = 0; i < ctx.nso_count; ++i) {
@@ -272,11 +272,11 @@ namespace ams::ldr {
/* All NSOs must not be --X. */
/* This is "probably" not checked on Ounce? */
- R_UNLESS((hdr.flags & NsoHeader::Flag_PreventCodeReads) == 0, ldr::ResultInvalidNso());
+ R_UNLESS_LOG((hdr.flags & NsoHeader::Flag_PreventCodeReads) == 0, ldr::ResultInvalidNso(), "[ldr] NSO[%d] --x not allowed!\n", i);
/* Zstd compression only allowed on main, and only when both rtld+sdk are present. */
if (i != ctx.main_nso_idx || ctx.rtld_idx < 0 || ctx.sdk_nso_idx < 0) {
- R_UNLESS((hdr.flags & NsoHeader::Flag_UseZbicCompression) == 0, ldr::ResultInvalidNso());
+ R_UNLESS_LOG((hdr.flags & NsoHeader::Flag_UseZbicCompression) == 0, ldr::ResultInvalidNso(), "[ldr] NSO[%d] zbic not allowed!\n", i);
}
/* NSOs must have page-aligned segments. */
@@ -299,13 +299,13 @@ namespace ams::ldr {
const bool has_browser_dll = (acid_flags & Acid::AcidFlag_LoadBrowserCoreDll) != 0;
if (ctx.ali.has_rtld || ctx.ali.has_sdk) {
/* If we have sdk we must have rtld. */
- R_UNLESS(ctx.ali.has_rtld, ldr::ResultInvalidNso());
+ R_UNLESS_LOG(ctx.ali.has_rtld, ldr::ResultInvalidNso(), "[ldr] Missing rtld!\n");
/* If we have rtld, we must not have browser core dll. */
- R_UNLESS(!has_browser_dll, ldr::ResultInvalidNso());
+ R_UNLESS_LOG(!has_browser_dll, ldr::ResultInvalidNso(), "[ldr] BrowserCoreDll must not be present!\n");
} else {
/* We must not have both subsdk and browser dll. */
- R_UNLESS(!(ctx.ali.has_subsdk && has_browser_dll), ldr::ResultInvalidNso());
+ R_UNLESS_LOG(!(ctx.ali.has_subsdk && has_browser_dll), ldr::ResultInvalidNso(), "[ldr] Can't have both subsdk and BrowserCoreDll!\n");
}
R_SUCCEED();
@@ -394,6 +394,8 @@ namespace ams::ldr {
/* If the signature check fails, we need to check if this is allowable. */
if (!is_signature_valid) {
+ AMS_LOG("[ldr] invalid signature!\n");
+
/* We have to enforce signature checks on prod and when we have a signature to check on dev. */
R_UNLESS(IsDevelopmentForAcidProductionCheck(), ldr::ResultInvalidNcaSignature());
R_UNLESS(!code_verification_data.has_data, ldr::ResultInvalidNcaSignature());
@@ -407,52 +409,56 @@ namespace ams::ldr {
R_SUCCEED();
}
- Result GetCreateProcessFlags(u32 *out, const Meta *meta, const u32 ldr_flags) {
- const u8 meta_flags = meta->npdm->flags;
+ Result GetCreateProcessParameterFlags(u32 *out, const Meta *meta, const u32 ldr_flags) {
+ const u8 meta_flags0 = meta->npdm->flags0;
+ const u8 meta_flags1 = meta->npdm->flags1;
u32 flags = 0;
/* Set Is64Bit. */
- if (meta_flags & Npdm::MetaFlag_Is64Bit) {
- flags |= svc::CreateProcessFlag_Is64Bit;
+ if (meta_flags0 & Npdm::MetaFlag0_Is64BitInstruction) {
+ flags |= svc::CreateProcessParameterFlag_64Bit;
}
- /* Set AddressSpaceType. */
- switch (GetAddressSpaceType(meta)) {
- case Npdm::AddressSpaceType_32Bit:
- flags |= svc::CreateProcessFlag_AddressSpace32Bit;
+ /* Set ProcessAddressSpace. */
+ switch (GetProcessAddressSpace(meta)) {
+ case Npdm::ProcessAddressSpace_32Bit:
+ flags |= svc::CreateProcessParameterFlag_AddressSpace32Bit;
break;
- case Npdm::AddressSpaceType_64BitDeprecated:
- flags |= svc::CreateProcessFlag_AddressSpace64BitDeprecated;
+ case Npdm::ProcessAddressSpace_64Bit36:
+ flags |= svc::CreateProcessParameterFlag_AddressSpace64Bit36;
break;
- case Npdm::AddressSpaceType_32BitWithoutAlias:
- flags |= svc::CreateProcessFlag_AddressSpace32BitWithoutAlias;
+ case Npdm::ProcessAddressSpace_32BitNoReserved:
+ flags |= svc::CreateProcessParameterFlag_AddressSpace32BitNoReserved;
break;
- case Npdm::AddressSpaceType_64Bit:
- flags |= svc::CreateProcessFlag_AddressSpace64Bit;
+ case Npdm::ProcessAddressSpace_64Bit39:
+ flags |= svc::CreateProcessParameterFlag_AddressSpace64Bit39;
+ break;
+ case Npdm::ProcessAddressSpace_64Bit42:
+ flags |= svc::CreateProcessParameterFlag_AddressSpace64Bit42;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
}
/* Set Enable Debug. */
- if (ldr_flags & CreateProcessFlag_EnableDebug) {
- flags |= svc::CreateProcessFlag_EnableDebug;
+ if (ldr_flags & CreateProcessParameterFlag_EnableJitDebug) {
+ flags |= svc::CreateProcessParameterFlag_EnableJitDebug;
}
/* Set Enable ASLR. */
- if (!(ldr_flags & CreateProcessFlag_DisableAslr)) {
- flags |= svc::CreateProcessFlag_EnableAslr;
+ if (!(ldr_flags & CreateProcessParameterFlag_DisableAslr)) {
+ flags |= svc::CreateProcessParameterFlag_EnableAslr;
}
/* Set Is Application. */
if (IsApplication(meta)) {
- flags |= svc::CreateProcessFlag_IsApplication;
+ flags |= svc::CreateProcessParameterFlag_IsApplication;
/* 7.0.0+: Set OptimizeMemoryAllocation if relevant. */
if (hos::GetVersion() >= hos::Version_7_0_0) {
- if (meta_flags & Npdm::MetaFlag_OptimizeMemoryAllocation) {
- flags |= svc::CreateProcessFlag_OptimizeMemoryAllocation;
+ if (meta_flags0 & Npdm::MetaFlag0_OptimizeMemoryAllocation) {
+ flags |= svc::CreateProcessParameterFlag_OptimizeMemoryAllocation;
}
}
}
@@ -464,19 +470,19 @@ namespace ams::ldr {
switch (GetPoolPartition(meta)) {
case Acid::PoolPartition_Application:
if (IsApplet(meta)) {
- flags |= svc::CreateProcessFlag_PoolPartitionApplet;
+ flags |= svc::CreateProcessParameterFlag_PoolPartitionApplet;
} else {
- flags |= svc::CreateProcessFlag_PoolPartitionApplication;
+ flags |= svc::CreateProcessParameterFlag_PoolPartitionApplication;
}
break;
case Acid::PoolPartition_Applet:
- flags |= svc::CreateProcessFlag_PoolPartitionApplet;
+ flags |= svc::CreateProcessParameterFlag_PoolPartitionApplet;
break;
case Acid::PoolPartition_System:
- flags |= svc::CreateProcessFlag_PoolPartitionSystem;
+ flags |= svc::CreateProcessParameterFlag_PoolPartitionSystem;
break;
case Acid::PoolPartition_SystemNonSecure:
- flags |= svc::CreateProcessFlag_PoolPartitionSystemNonSecure;
+ flags |= svc::CreateProcessParameterFlag_PoolPartitionSystemNonSecure;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
@@ -484,18 +490,23 @@ namespace ams::ldr {
} else if (hos::GetVersion() >= hos::Version_4_0_0) {
/* On 4.0.0+, the corresponding bit was simply "UseSecureMemory". */
if (meta->acid->flags & Acid::AcidFlag_DeprecatedUseSecureMemory) {
- flags |= svc::CreateProcessFlag_DeprecatedUseSecureMemory;
+ flags |= svc::CreateProcessParameterFlag_DeprecatedUseSecureMemory;
}
}
- /* 11.0.0+/meso Set Disable DAS merge. */
- if (meta_flags & Npdm::MetaFlag_DisableDeviceAddressSpaceMerge) {
- flags |= svc::CreateProcessFlag_DisableDeviceAddressSpaceMerge;
+ /* 11.0.0+/meso Set Disable DAS Merge. */
+ if (meta_flags0 & Npdm::MetaFlag0_DisableDeviceAddressSpaceMerge) {
+ flags |= svc::CreateProcessParameterFlag_DisableDeviceAddressSpaceMerge;
}
- /* 18.0.0+/meso Set Alias region extra size. */
- if (meta_flags & Npdm::MetaFlag_EnableAliasRegionExtraSize) {
- flags |= svc::CreateProcessFlag_EnableAliasRegionExtraSize;
+ /* 18.0.0+/meso Set Enable Address Sanitizer . */
+ if (meta_flags0 & Npdm::MetaFlag0_EnableAddressSanitizer) {
+ flags |= svc::CreateProcessParameterFlag_EnableAddressSanitizer;
+ }
+
+ /* 23.0.0+/meso Set Enable Shadow Stack. */
+ if (meta_flags1 & Npdm::MetaFlag1_EnableShadowStack) {
+ flags |= svc::CreateProcessParameterFlag_EnableShadowStack;
}
*out = flags;
@@ -513,7 +524,7 @@ namespace ams::ldr {
out->reslimit = resource_limit;
/* Set flags. */
- R_TRY(GetCreateProcessFlags(std::addressof(out->flags), meta, flags));
+ R_TRY(GetCreateProcessParameterFlags(std::addressof(out->flags), meta, flags));
/* 3.0.0+ System Resource Size. */
if (hos::GetVersion() >= hos::Version_3_0_0) {
@@ -523,7 +534,7 @@ namespace ams::ldr {
/* Validate system resource usage. */
if (meta->npdm->system_resource_size) {
/* Process must be 64-bit. */
- R_UNLESS((out->flags & svc::CreateProcessFlag_AddressSpace64Bit), ldr::ResultInvalidMeta());
+ R_UNLESS((out->flags & svc::CreateProcessParameterFlag_AddressSpace64Bit39), ldr::ResultInvalidMeta());
/* Process must be application or applet. */
R_UNLESS(IsApplication(meta) || IsApplet(meta), ldr::ResultInvalidMeta());
@@ -596,25 +607,29 @@ namespace ams::ldr {
uintptr_t aslr_start = 0;
size_t aslr_size = 0;
if (hos::GetVersion() >= hos::Version_2_0_0) {
- switch (out_param->flags & svc::CreateProcessFlag_AddressSpaceMask) {
- case svc::CreateProcessFlag_AddressSpace32Bit:
- case svc::CreateProcessFlag_AddressSpace32BitWithoutAlias:
+ switch (out_param->flags & svc::CreateProcessParameterFlag_AddressSpaceMask) {
+ case svc::CreateProcessParameterFlag_AddressSpace32Bit:
+ case svc::CreateProcessParameterFlag_AddressSpace32BitNoReserved:
aslr_start = svc::AddressSmallMap32Start;
aslr_size = svc::AddressSmallMap32Size;
break;
- case svc::CreateProcessFlag_AddressSpace64BitDeprecated:
+ case svc::CreateProcessParameterFlag_AddressSpace64Bit36:
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
break;
- case svc::CreateProcessFlag_AddressSpace64Bit:
+ case svc::CreateProcessParameterFlag_AddressSpace64Bit39:
aslr_start = svc::AddressMap39Start;
aslr_size = svc::AddressMap39Size;
break;
+ case svc::CreateProcessParameterFlag_AddressSpace64Bit42:
+ aslr_start = svc::AddressMap42Start;
+ aslr_size = svc::AddressMap42Size;
+ break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
} else {
/* On 1.0.0, only 2 address space types existed. */
- if (out_param->flags & svc::CreateProcessFlag_AddressSpace64BitDeprecated) {
+ if (out_param->flags & svc::CreateProcessParameterFlag_AddressSpace64Bit36) {
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
} else {
@@ -627,7 +642,7 @@ namespace ams::ldr {
/* Set Create Process output. */
uintptr_t aslr_slide = 0;
size_t free_size = (aslr_size - total_size);
- if (out_param->flags & svc::CreateProcessFlag_EnableAslr) {
+ if (out_param->flags & svc::CreateProcessParameterFlag_EnableAslr) {
aslr_slide = GenerateSecureRandom(free_size / os::MemoryBlockUnitSize) * os::MemoryBlockUnitSize;
}
@@ -649,10 +664,13 @@ namespace ams::ldr {
R_SUCCEED();
}
- Result LoadAutoLoadModuleSegment(fs::FileHandle file, size_t file_offset, size_t compressed_size, size_t segment_size, bool is_compressed, bool is_zstd, uintptr_t map_base, uintptr_t map_end) {
+ Result LoadAutoLoadModuleSegment(fs::FileHandle file, size_t file_offset, size_t compressed_size, size_t segment_size, bool is_compressed, bool is_zbic, uintptr_t map_base, uintptr_t map_end) {
/* Select read size based on compression. */
size_t file_size = is_compressed ? compressed_size : segment_size;
+ AMS_LOG("[ldr] Loading segment @ 0x%016lx: compressed=%d, file_size=0x%08lx, compressed_size=0x%08lx, segment_size=0x%08lx\n",
+ map_base, is_compressed, file_size, compressed_size, segment_size);
+
/* Validate size. */
R_UNLESS(file_size <= segment_size, ldr::ResultInvalidNso());
R_UNLESS(file_size <= std::numeric_limits::max(), ldr::ResultInvalidNso());
@@ -662,19 +680,19 @@ namespace ams::ldr {
uintptr_t load_address = is_compressed ? map_end - compressed_size : map_base;
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, file_offset, reinterpret_cast(load_address), file_size));
- R_UNLESS(read_size == file_size, ldr::ResultInvalidNso());
+ R_UNLESS_LOG(read_size == file_size, ldr::ResultInvalidNso(), "[ldr] Couldn't read segment from file!\n");
/* Uncompress if necessary. */
R_SUCCEED_IF(!is_compressed);
auto compressed_data_buf = reinterpret_cast(load_address);
- if (is_zstd) {
- bool decompressed = util::DecompressZstdForLoader(reinterpret_cast(g_zstd_dctx_workspace), ZstdDctxWorkspaceSize, reinterpret_cast(map_base), static_cast(map_end - map_base), segment_size, compressed_data_buf, file_size);
- R_UNLESS(decompressed, ldr::ResultInvalidNso());
+ if (is_zbic) {
+ bool decompressed = util::DecompressZstdWithBic(reinterpret_cast(g_zstd_dctx_workspace), sizeof(g_zstd_dctx_workspace), reinterpret_cast(map_base), static_cast(map_end - map_base), segment_size, compressed_data_buf, file_size);
+ R_UNLESS_LOG(decompressed, ldr::ResultInvalidNso(), "[ldr] Failed to decompress segment with zbic!\n");
} else {
bool decompressed = (util::DecompressLZ4(reinterpret_cast(map_base), segment_size, compressed_data_buf, file_size) == static_cast(segment_size));
- R_UNLESS(decompressed, ldr::ResultInvalidNso());
+ R_UNLESS_LOG(decompressed, ldr::ResultInvalidNso(), "[ldr] Failed to decompress segment with lz4!\n");
}
R_SUCCEED();
@@ -689,12 +707,12 @@ namespace ams::ldr {
crypto::GenerateSha256(hash, sizeof(hash),
reinterpret_cast(map_address + nso_header->segments[segment].dst_offset),
nso_header->segments[segment].size);
- R_UNLESS(std::memcmp(hash, nso_header->segment_hashes[segment], sizeof(hash)) == 0, ldr::ResultInvalidNso());
+ R_UNLESS_LOG(std::memcmp(hash, nso_header->segment_hashes[segment], sizeof(hash)) == 0, ldr::ResultInvalidNso(), "[ldr] Invalid segment hash!\n");
R_SUCCEED();
}
Result LoadAutoLoadModule(os::NativeHandle process_handle, fs::FileHandle file, const NsoHeader *nso_header, uintptr_t nso_address, size_t nso_size, size_t map_size) {
- const bool is_zstd = (nso_header->flags & NsoHeader::Flag_UseZbicCompression) != 0;
+ const bool is_zbic = (nso_header->flags & NsoHeader::Flag_UseZbicCompression) != 0;
const size_t module_size = static_cast(nso_header->rw_dst_offset) + util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize);
const size_t arena_size = (nso_size > module_size) ? (nso_size - module_size) : 0;
@@ -711,11 +729,11 @@ namespace ams::ldr {
/* Load NSO segments. */
R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Text].file_offset, nso_header->text_compressed_size, nso_header->text_size,
- (nso_header->flags & NsoHeader::Flag_CompressedText) != 0, is_zstd, map_address + nso_header->text_dst_offset, map_end));
+ (nso_header->flags & NsoHeader::Flag_CompressedText) != 0, is_zbic, map_address + nso_header->text_dst_offset, map_end));
R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Ro].file_offset, nso_header->ro_compressed_size, nso_header->ro_size,
- (nso_header->flags & NsoHeader::Flag_CompressedRo) != 0, is_zstd, map_address + nso_header->ro_dst_offset, map_end));
+ (nso_header->flags & NsoHeader::Flag_CompressedRo) != 0, is_zbic, map_address + nso_header->ro_dst_offset, map_end));
R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Rw].file_offset, nso_header->rw_compressed_size, nso_header->rw_size,
- (nso_header->flags & NsoHeader::Flag_CompressedRw) != 0, is_zstd, map_address + nso_header->rw_dst_offset, map_end));
+ (nso_header->flags & NsoHeader::Flag_CompressedRw) != 0, is_zbic, map_address + nso_header->rw_dst_offset, map_end));
/* Clear unused space to zero. */
const size_t text_end = static_cast(nso_header->text_dst_offset) + static_cast(nso_header->text_size);
@@ -778,15 +796,17 @@ namespace ams::ldr {
for (int i = 0; i < ctx.nso_count; i++) {
const NsoIndex nso_idx = static_cast(ctx.ali.nso_indices[i]);
+ const bool is_zbic = (ctx.headers[i].flags & NsoHeader::Flag_UseZbicCompression) != 0;
+ const size_t map_size = is_zbic ? (total_end - process_info->nso_address[i]) : process_info->nso_size[i];
+
+ AMS_LOG("[ldr] module[%d]: idx=%d, path='%s', zbic=%d\n", i, (int)nso_idx, GetNsoPath(nso_idx), is_zbic);
fs::FileHandle file;
R_TRY(fs::OpenFile(std::addressof(file), GetNsoPath(nso_idx), fs::OpenMode_Read));
ON_SCOPE_EXIT { fs::CloseFile(file); };
- const bool is_zstd = (ctx.headers[i].flags & NsoHeader::Flag_UseZbicCompression) != 0;
- const size_t map_size = is_zstd ? (total_end - process_info->nso_address[i]) : process_info->nso_size[i];
-
- R_TRY(LoadAutoLoadModule(process_info->process_handle, file, ctx.headers + i, process_info->nso_address[i], process_info->nso_size[i], map_size));
+ R_TRY(LoadAutoLoadModule(process_info->process_handle, file, ctx.headers + i,
+ process_info->nso_address[i], process_info->nso_size[i], map_size));
}
/* Load arguments, if present. */
@@ -813,12 +833,6 @@ namespace ams::ldr {
}
Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) {
- /* Append extra .bss for 64LUT */
- /* TODO: REMOVE THIS. */
- if (g_is_pcv && ctx.main_nso_idx >= 0) {
- g_nso_headers[ctx.main_nso_idx].bss_size += static_cast(hoc::pcv::HocPcvScratchSize);
- }
-
/* Get CreateProcessParameter. */
svc::CreateProcessParameter param;
R_TRY(GetCreateProcessParameter(std::addressof(param), meta, flags, resource_limit));
@@ -833,7 +847,7 @@ namespace ams::ldr {
/* Set the output handle, and ensure that if we fail after this point we clean it up. */
out->process_handle = process_handle;
out->code_address = param.code_address;
- ON_RESULT_FAILURE { svc::CloseHandle(process_handle); };
+ ON_RESULT_FAILURE { R_DISCARD(svc::CloseHandle(process_handle)); };
/* Load all auto load modules. */
R_RETURN(LoadAutoLoadModules(out, ctx, argument));
@@ -891,8 +905,8 @@ namespace ams::ldr {
os::ProcessId process_id = os::GetProcessId(info.process_handle);
/* Register new process. */
- const auto as_type = GetAddressSpaceType(std::addressof(meta));
- RoManager::GetInstance().RegisterProcess(pin_id, process_id, meta.aci->program_id, as_type == Npdm::AddressSpaceType_64Bit || as_type == Npdm::AddressSpaceType_64BitDeprecated);
+ const auto as_type = GetProcessAddressSpace(std::addressof(meta));
+ RoManager::GetInstance().RegisterProcess(pin_id, process_id, meta.aci->program_id, as_type == Npdm::ProcessAddressSpace_64Bit39 || as_type == Npdm::ProcessAddressSpace_64Bit36);
/* Register all NSOs. */
for (int i = 0; i < ctx.nso_count; i++) {
@@ -903,7 +917,7 @@ namespace ams::ldr {
/* If we're overriding for HBL, perform HTML document redirection. */
if (override_status.IsHbl()) {
/* Don't validate result, failure is okay. */
- RedirectHtmlDocumentPathForHbl(loc);
+ R_DISCARD(RedirectHtmlDocumentPathForHbl(loc));
}
/* Clear the external code for the program. */
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp
index 5ba70a8f..423ee84f 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp
@@ -26,7 +26,7 @@
#define DISABLED 0
#define DEACTIVATED_GPU_FREQ 2000
#define GPU_MIN_MIN_VOLT 480000
-#define CPU_MAX_MAX_VOLT 1200000
+#define CPU_MAX_MAX_VOLT 1235000
namespace ams::ldr::hoc {
@@ -36,14 +36,14 @@ volatile CustomizeTable C = {
.hpMode = DISABLED,
.commonEmcMemVolt = 1175000, /* LPDDR4(X) JEDEC Specification */
-.eristaEmcMaxClock = 1600000, /* Maximum HB-MGCH ram rating */
+.eristaEmcMaxClock = 1600000, /* Erista ram rating*/
/* Available: 33MHz step rate, 66MHz step rate, 100MHz step rate, 133MHz step rate and jedec. */
/* Jedec freqs are 1333MHz, 1600MHz, 1866MHz, 2133MHz, 2400MHz, 2666MHz, 2933MHz, 3200MHz. */
-.stepMode = StepMode_33MHz,
+.stepMode = StepMode_Jedec,
-.marikoEmcMaxClock = 2133000, /* Requires the EMC DVFS 63-entry patches (EMC DVFS Count / EMC SoC LUT). */
-.marikoEmcVddqVolt = 600000,
+.marikoEmcMaxClock = 2133000, /* Should work for every ram*/
+.marikoEmcVddqVolt = 640000, /* Optimal for DRAM OC */
.emcDvbShift = 0,
.marikoSocVmax = 0, /* 0 = stock limits (1450 - 1597 is 1050mV, 1598-1708 is 1025mV, 1709+ is 1000mV). */
@@ -100,13 +100,14 @@ volatile CustomizeTable C = {
/* 1120mV is NVIDIA rating */
.marikoCpuMaxVolt = 1120,
-/* Supported values: 1963500, 2091000, 2193000, 2295000, 2397000, 2499000, 2601000, 2703000. */
-/* 1963500 is official rating of T214/Mariko, fully safe. */
-/* 2091000-2295000 is a slight OC which should work on all units, but no guarantees. */
-/* 2397000 is the max safe OC for most average units with tuned undervolt. */
-/* 2499000 should be used with caution. */
-/* 2601000 exceeds pmic limit on most consoles. */
-/* 2703000 is potentially dangerous and not advised. */
+/* Supported values: 1963500, 2091000, 2193000, 2295000, 2397000, 2499000, 2601000, 2703000, 2805000. */
+/* 1963500 is official rating of T214/Mariko, fully safe. */
+/* 2091000-2295000 is a slight OC which should work on all units, but no guarantees. */
+/* 2397000 is the max safe OC for most average units with tuned undervolt. */
+/* 2499000 should be used with caution. */
+/* 2601000 exceeds pmic limit on most consoles. */
+/* 2703000 is potentially dangerous and not advised. */
+/* 2805000 can only be ran on the best of consoles and may be dangerous due to the voltage requirement */
.marikoCpuMaxClock = 1963500,
.eristaCpuBoostClock = 1785000, /* Default boost clock */
@@ -132,7 +133,7 @@ volatile CustomizeTable C = {
AUTO /* 153 */,
AUTO /* 192 */,
AUTO /* 230 */,
- AUTO /* 269 */,
+ AUTO /* 268 */,
AUTO /* 307 */,
AUTO /* 346 */,
AUTO /* 384 */,
@@ -181,6 +182,7 @@ volatile CustomizeTable C = {
DEACTIVATED_GPU_FREQ /* 1459 (Disabled by default) */,
DEACTIVATED_GPU_FREQ /* 1497 (Disabled by default) */,
DEACTIVATED_GPU_FREQ /* 1536 (Disabled by default) */,
+ DEACTIVATED_GPU_FREQ /* 1574 (Disabled by default) */,
},
/* Override soc voltage brackets. */
@@ -219,6 +221,16 @@ volatile CustomizeTable C = {
.fineTune_t6_tRTW = 0,
.fineTune_t7_tWTR = 0,
+/* Debugging options */
+
+/* PCV Log Verbosity */
+/* 0xFF: Nothing */
+/* 0: Basic logging */
+/* 1: More advanced logging */
+/* 2: All logging excluding MMIO */
+/* 3: All logging including MMIO */
+.pcvLogVerbosity = 2,
+
/* You shouldn't have to anything past here. */
.eristaCpuDvfsTable = {
{ 204000, { 721094, }, { } },
@@ -318,6 +330,7 @@ volatile CustomizeTable C = {
{ 2499000, { 1580725, -35815, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2601000, { 1702903, -36675, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2703000, { 1770375, -37515, 113, }, { CPU_MAX_MAX_VOLT, } },
+ { 2805000, { 1838820, -37707, 113, }, { CPU_MAX_MAX_VOLT, } },
},
.marikoCpuDvfsTable1581Tbreak {
@@ -346,6 +359,7 @@ volatile CustomizeTable C = {
{ 2499000, { 1736856, -35286, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2601000, { 1787838, -35967, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2703000, { 1838820, -36648, 113, }, { CPU_MAX_MAX_VOLT, } },
+ { 2805000, { 1908891, -37707, 113, }, { CPU_MAX_MAX_VOLT, } },
},
.marikoCpuDvfsTable1683Tbreak {
@@ -374,6 +388,7 @@ volatile CustomizeTable C = {
{ 2499000, { 1736856, -35286, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2601000, { 1787838, -35967, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2703000, { 1838820, -36648, 113, }, { CPU_MAX_MAX_VOLT, } },
+ { 2805000, { 1908891, -37707, 113, }, { CPU_MAX_MAX_VOLT, } },
},
.marikoCpuDvfsTableExtreme {
@@ -402,6 +417,7 @@ volatile CustomizeTable C = {
{ 2499000, { 1580725, -35815, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2601000, { 1702903, -36675, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2703000, { 1775375, -37515, 113, }, { CPU_MAX_MAX_VOLT, } },
+ { 2805000, { 1838820, -37707, 113, }, { CPU_MAX_MAX_VOLT, } },
},
.eristaGpuDvfsTable = {
@@ -571,13 +587,14 @@ volatile CustomizeTable C = {
{ 1152000, { }, { 1180029, -14534, -830, 0, 1469, 110 } },
{ 1228800, { }, { 1238293, -16383, -859, 0, 3722, 313 } },
{ 1267200, { }, { 1276399, -17475, -867, 0, 3681, 559 } },
- { 1305600, { }, { } },
- { 1344000, { }, { } },
- { 1382400, { }, { } },
- { 1420800, { }, { } },
- { 1459200, { }, { } },
- { 1497600, { }, { } },
- { 1536000, { }, { } },
+ { 1305600, { }, { 0, 0, 0, 0, 0, 0 } },
+ { 1344000, { }, { 0, 0, 0, 0, 0, 0 } },
+ { 1382400, { }, { 0, 0, 0, 0, 0, 0 } },
+ { 1420800, { }, { 0, 0, 0, 0, 0, 0 } },
+ { 1459200, { }, { 0, 0, 0, 0, 0, 0 } },
+ { 1497600, { }, { 0, 0, 0, 0, 0, 0 } },
+ { 1536000, { }, { 0, 0, 0, 0, 0, 0 } },
+ { 1574400, { }, { 0, 0, 0, 0, 0, 0 } },
},
.marikoGpuDvfsTableHighUV = {
@@ -605,6 +622,7 @@ volatile CustomizeTable C = {
{ 1459200, { }, { 0, 0, 0, 0, 0, 0 } },
{ 1497600, { }, { 0, 0, 0, 0, 0, 0 } },
{ 1536000, { }, { 0, 0, 0, 0, 0, 0 } },
+ { 1574400, { }, { 0, 0, 0, 0, 0, 0 } },
},
};
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp
index a898e20b..e5e9051e 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp
@@ -47,14 +47,14 @@ enum ReadLatency: u32 {
RL_2133 = 40,
RL_1866 = 36,
RL_1600 = 32,
- RL_1331 = 28,
+ RL_1333 = 28,
};
enum WriteLatency: u32 {
WL_2133 = 18,
WL_1866 = 16,
WL_1600 = 14,
- WL_1331 = 12,
+ WL_1333 = 12,
};
using CustomizeCpuDvfsTable = pcv::cvb_entry_t[pcv::DvfsTableEntryLimit];
@@ -126,13 +126,15 @@ struct CustomizeTable {
s32 commonGpuVoltOffset;
u32 eristaGpuVoltArray[27];
- u32 marikoGpuVoltArray[24];
+ u32 marikoGpuVoltArray[25];
s32 marikoSocVoltArray[28];
u32 fineTune_t6_tRTW;
u32 fineTune_t7_tWTR;
- u32 reserved[60];
+ u32 pcvLogVerbosity;
+
+ u32 reserved[57];
CustomizeCpuDvfsTable eristaCpuDvfsTable;
CustomizeCpuDvfsTable eristaCpuDvfsTableSLT;
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_value.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_value.hpp
index 7b23d587..9ec9f726 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_value.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_value.hpp
@@ -1,7 +1,7 @@
/*
* Copyright (c) 2023 hanai3Bi
*
- * Copyright (c) 2025 Lightos_
+ * Copyright (c) Lightos_
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
@@ -20,7 +20,7 @@
#include "oc_common.hpp"
-namespace ams::ldr::hoc {
+namespace ams::ldr::hoc::pcv {
#define MAX(A, B) std::max(A, B)
#define MIN(A, B) std::min(A, B)
#define CEIL(A) std::ceil(A)
@@ -30,6 +30,9 @@ namespace ams::ldr::hoc {
#define PACK_U32(high, low) ((static_cast(high) << 16) | (static_cast(low) & 0xFFFF))
#define PACK_U32_NIBBLE_HIGH_BYTE_LOW(high, low) ((static_cast(high & 0xF) << 28) | (static_cast(low) & 0xFF))
+ #define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg))
+ #define GET_CYCLE_FLOOR(PARAM) u32(FLOOR(double(PARAM) / tCK_avg))
+
/* Burst latency, not to be confused with base latency (tWRL). */
const u32 BL = 16;
@@ -38,6 +41,10 @@ namespace ams::ldr::hoc {
/* DQS output access time from CK_t/CK_c. */
const double tDQSCK_max = 3.5;
+
+ /* Turn-around time RDQS to WDQS for WDQS control case. */
+ const double tDQSTA = 0.25;
+
/* Write preamble. (tCK) */
const u32 tWPRE = 2;
@@ -56,10 +63,24 @@ namespace ams::ldr::hoc {
/* DQ-to-DQS offset(max) (ns) */
const double tDQS2DQ_max = 0.8;
+ /* Internal READ to PRECHARGE command delay */
+ const double tRTP = 7.5;
+
/* Write recovery time. */
const u32 tWR = 18;
- namespace pcv::erista {
+ /* Write-to-Read delay. */
+ const u32 tWTRStock = 10;
+
+ /* Round trace routing delay(?) Guessed based on tK1 (ns) */
+ const double roundTripDelay = 0.5;
+
+ const double tODTon = 1.5;
+
+ inline u32 RL;
+ inline u32 WL;
+
+ namespace erista {
const std::array tRCD_values = { 18, 17, 16, 15, 14, 13, 12, 11 };
const std::array tRP_values = { 18, 17, 16, 15, 14, 13, 12, 11 };
const std::array tRAS_values = { 42, 36, 34, 32, 30, 28, 26, 24, 22, 20 };
@@ -83,13 +104,20 @@ namespace ams::ldr::hoc {
const u32 tFAW = static_cast(tRRD * 4.0);
const double tRPab = tRPpb + 3;
- inline u32 RL;
- inline u32 WL;
-
inline u32 tR2P;
inline u32 tR2W;
inline u32 rext;
+ inline u32 rdv;
+ inline u32 qpop;
+ inline u32 quse_width;
+ inline u32 quse;
+ inline u32 einput_duration;
+ inline u32 einput;
+ inline u32 qrst;
+ inline u32 ibdly;
+ inline u32 qsafe;
+
inline u32 tW2P;
inline u32 tWTPDEN;
inline u32 tW2R;
@@ -107,9 +135,17 @@ namespace ams::ldr::hoc {
inline double pdex2mrr;
inline u8 mrw2;
+
+ namespace {
+ /* Delay of entering soc? Guessed based on tK1. (ns) */
+ double chipDelay = 1.655;
+ }
+
+ /* Soc->dram->soc delay(?) */
+ const double flyByTime = chipDelay + roundTripDelay;
}
- namespace pcv::mariko {
+ namespace mariko {
const std::array tRCD_values = { 18, 17, 16, 15, 14, 13, 12, 11 };
const std::array tRP_values = { 18, 17, 16, 15, 14, 13, 12, 11 };
const std::array tRAS_values = { 42, 36, 34, 32, 30, 28, 26, 24, 22, 20 };
@@ -132,9 +168,6 @@ namespace ams::ldr::hoc {
inline u32 refresh_raw;
- inline u32 RL;
- inline u32 WL;
-
inline u32 tR2P;
inline u32 tR2W;
inline u32 tRTM;
@@ -167,9 +200,17 @@ namespace ams::ldr::hoc {
inline u32 tCLKSTOP;
- inline u32 pdex2mrr;
+ inline double pdex2mrr;
inline u8 mrw2;
+
+ namespace {
+ /* Delay of entering soc? Guessed based on tK1. (ns) */
+ const double chipDelay = 1.109;
+ }
+
+ /* Soc->dram->soc delay(?) */
+ const double flyByTime = chipDelay + roundTripDelay;
}
}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/nvsrv/nvsrv.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/nvsrv/nvsrv.cpp
new file mode 100644
index 00000000..274e47e3
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/nvsrv/nvsrv.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) Souldbminer, Lightos and Horizon OC Contributors
+ *
+ * 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 .
+ */
+
+#include "nvsrv.hpp"
+
+namespace ams::ldr::hoc::nvsrv {
+
+ namespace {
+
+ /* NvServices device names */
+ constexpr char DbgGpuPath[] = "/dev/nvhost-dbg-gpu";
+ constexpr char ProfGpuPath[] = "/dev/nvhost-prof-gpu";
+
+ /* Permission offset to patch */
+ constexpr size_t DevTablePermOffset = 0x68;
+
+ uintptr_t FindBytes(uintptr_t start, uintptr_t end, const void *needle, size_t len) {
+ const u8 *pat = reinterpret_cast(needle);
+ for (uintptr_t addr = start; addr + len <= end; ++addr) {
+ if (std::memcmp(reinterpret_cast(addr), pat, len) == 0) {
+ return addr;
+ }
+ }
+ return 0;
+ }
+
+ /* Unlock the devices */
+ Result UnlockDevicePermission(uintptr_t mapped_nso, size_t nso_size, uintptr_t nso_address, const char *path, [[maybe_unused]] const char *description) {
+ const size_t path_len = std::strlen(path) + 1;
+
+ const uintptr_t str_addr = FindBytes(mapped_nso, mapped_nso + nso_size, path, path_len);
+ if (str_addr == 0) {
+ LOGGING("nvsrv: %s path string not found (nso_size=%lx)", description, static_cast(nso_size));
+ R_THROW(ldr::ResultInvalidNvsrvDevTable());
+ }
+
+ const uintptr_t str_offset = str_addr - mapped_nso;
+ const uintptr_t candidates[] = { str_offset, nso_address + str_offset };
+
+ uintptr_t entry_addr = 0;
+ for (uintptr_t ptr = mapped_nso; entry_addr == 0 && ptr + sizeof(uintptr_t) <= mapped_nso + nso_size; ptr += sizeof(uintptr_t)) {
+ const uintptr_t val = *reinterpret_cast(ptr);
+ for (uintptr_t cand : candidates) {
+ if (val == cand) {
+ entry_addr = ptr;
+ break;
+ }
+ }
+ }
+ if (entry_addr == 0) {
+ LOGGING("nvsrv: %s str@+%lx has no referencing table entry", description,
+ static_cast(str_offset));
+ R_THROW(ldr::ResultInvalidNvsrvDevTable());
+ }
+
+ const uintptr_t perm_addr = entry_addr + DevTablePermOffset;
+ if (perm_addr + sizeof(uintptr_t) > mapped_nso + nso_size) {
+ LOGGING("nvsrv: %s entry@+%lx perm field out of bounds", description, static_cast(entry_addr - mapped_nso));
+ R_THROW(ldr::ResultInvalidNvsrvDevTable());
+ }
+
+ uintptr_t *perm = reinterpret_cast(perm_addr);
+ LOGGING("nvsrv: %s entry@+%lx perm@+%lx was %lx", description,
+ static_cast(entry_addr - mapped_nso),
+ static_cast(perm_addr - mapped_nso),
+ static_cast(*perm));
+
+ PATCH_OFFSET(perm, static_cast(0));
+ R_SUCCEED();
+ }
+
+ }
+
+ void Patch(uintptr_t mapped_nso, size_t nso_size, uintptr_t nso_address) {
+ struct {
+ const char *path;
+ const char *description;
+ } targets[] = {
+ { DbgGpuPath, "dbg-gpu" },
+ { ProfGpuPath, "prof-gpu" },
+ };
+
+ for (auto &t : targets) {
+ [[maybe_unused]] const Result res = UnlockDevicePermission(mapped_nso, nso_size, nso_address, t.path, t.description);
+ LOGGING("nvsrv: %s unlock%s", t.description, R_SUCCEEDED(res) ? "ed" : " skipped");
+ }
+ }
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/nvsrv/nvsrv.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/nvsrv/nvsrv.hpp
new file mode 100644
index 00000000..4c551a33
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/nvsrv/nvsrv.hpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
+ *
+ * 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 .
+ */
+
+#pragma once
+
+#include "../oc_common.hpp"
+
+namespace ams::ldr::hoc::nvsrv {
+ void Patch(uintptr_t mapped_nso, size_t nso_size, uintptr_t nso_address);
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp
index 9440aaf8..566cf14b 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp
@@ -31,9 +31,6 @@
#define HOC_UART_LOG 0
#endif
-#define HOC_PCV_NVLOG_PATCH 1
-#define HOC_PCV_FORCE_VERBOSITY 1
-
#include "customize.hpp"
#include "oc_log.hpp"
@@ -63,37 +60,45 @@
*(offset) = value;
namespace ams::ldr {
- R_DEFINE_ERROR_RESULT(OutOfRange, 1000);
- R_DEFINE_ERROR_RESULT(InvalidMemPllmEntry, 1001);
- R_DEFINE_ERROR_RESULT(InvalidMtcMagic, 1002);
- R_DEFINE_ERROR_RESULT(InvalidMtcTable, 1003);
- R_DEFINE_ERROR_RESULT(InvalidDvbTable, 1004);
- R_DEFINE_ERROR_RESULT(InvalidCpuFreqVddEntry, 1005);
- R_DEFINE_ERROR_RESULT(InvalidCpuVoltDfllEntry, 1006);
- R_DEFINE_ERROR_RESULT(InvalidCpuDvfs, 1007);
- R_DEFINE_ERROR_RESULT(InvalidCpuMinVolt, 1008);
- R_DEFINE_ERROR_RESULT(InvalidGpuDvfs, 1009);
- R_DEFINE_ERROR_RESULT(InvalidGpuFreqMaxPattern, 1010);
- R_DEFINE_ERROR_RESULT(InvalidGpuPllEntry, 1011);
- R_DEFINE_ERROR_RESULT(InvalidRegulatorEntry, 1012);
- R_DEFINE_ERROR_RESULT(UninitializedPatcher, 1013);
- R_DEFINE_ERROR_RESULT(UnsuccessfulPatcher, 1014);
- R_DEFINE_ERROR_RESULT(SafetyCheckFailure, 1015);
- R_DEFINE_ERROR_RESULT(InvalidMtcTablePattern, 1016);
- R_DEFINE_ERROR_RESULT(InvalidSocVoltPattern, 1017);
- R_DEFINE_ERROR_RESULT(InvalidSocVoltLimit, 1018);
- R_DEFINE_ERROR_RESULT(InvalidEmcDvfsCount, 1019);
- R_DEFINE_ERROR_RESULT(InvalidEmcSocLut, 1020);
- R_DEFINE_ERROR_RESULT(InvalidEmcRateList, 1021);
- R_DEFINE_ERROR_RESULT(InvalidNvLogRedirect, 1022);
- R_DEFINE_ERROR_RESULT(InvalidBusFreqReloc, 1023);
- R_DEFINE_ERROR_RESULT(HookArenaOutOfMemory, 1024);
- R_DEFINE_ERROR_RESULT(HookPayloadTooLarge, 1025);
- R_DEFINE_ERROR_RESULT(HookRelocationUnsupported, 1026);
- R_DEFINE_ERROR_RESULT(HookSiteInvalid, 1027);
- R_DEFINE_ERROR_RESULT(HookPayloadEscapes, 1028);
- R_DEFINE_ERROR_RESULT(HookDataOutOfMemory, 1029);
- R_DEFINE_ERROR_RESULT(HookUnavailable, 1030);
+ R_DEFINE_ERROR_RESULT(OutOfRange, 1000);
+ R_DEFINE_ERROR_RESULT(InvalidMemPllmEntry, 1001);
+ R_DEFINE_ERROR_RESULT(InvalidMtcMagic, 1002);
+ R_DEFINE_ERROR_RESULT(InvalidMtcTable, 1003);
+ R_DEFINE_ERROR_RESULT(InvalidDvbTable, 1004);
+ R_DEFINE_ERROR_RESULT(InvalidCpuFreqVddEntry, 1005);
+ R_DEFINE_ERROR_RESULT(InvalidCpuVoltDfllEntry, 1006);
+ R_DEFINE_ERROR_RESULT(InvalidCpuDvfs, 1007);
+ R_DEFINE_ERROR_RESULT(InvalidCpuMinVolt, 1008);
+ R_DEFINE_ERROR_RESULT(InvalidGpuDvfs, 1009);
+ R_DEFINE_ERROR_RESULT(InvalidGpuFreqMaxPattern, 1010);
+ R_DEFINE_ERROR_RESULT(InvalidGpuPllEntry, 1011);
+ R_DEFINE_ERROR_RESULT(InvalidRegulatorEntry, 1012);
+ R_DEFINE_ERROR_RESULT(UninitializedPatcher, 1013);
+ R_DEFINE_ERROR_RESULT(UnsuccessfulPatcher, 1014);
+ R_DEFINE_ERROR_RESULT(SafetyCheckFailure, 1015);
+ R_DEFINE_ERROR_RESULT(InvalidMtcTablePattern, 1016);
+ R_DEFINE_ERROR_RESULT(InvalidSocVoltPattern, 1017);
+ R_DEFINE_ERROR_RESULT(InvalidSocVoltLimit, 1018);
+ R_DEFINE_ERROR_RESULT(InvalidEmcDvfsCount, 1019);
+ R_DEFINE_ERROR_RESULT(InvalidEmcSocLut, 1020);
+ R_DEFINE_ERROR_RESULT(InvalidEmcRateList, 1021);
+ R_DEFINE_ERROR_RESULT(InvalidNvLogRedirect, 1022);
+ R_DEFINE_ERROR_RESULT(InvalidBusFreqReloc, 1023);
+ R_DEFINE_ERROR_RESULT(HookArenaOutOfMemory, 1024);
+ R_DEFINE_ERROR_RESULT(HookPayloadTooLarge, 1025);
+ R_DEFINE_ERROR_RESULT(HookRelocationUnsupported, 1026);
+ R_DEFINE_ERROR_RESULT(HookSiteInvalid, 1027);
+ R_DEFINE_ERROR_RESULT(HookPayloadEscapes, 1028);
+ R_DEFINE_ERROR_RESULT(HookDataOutOfMemory, 1029);
+ R_DEFINE_ERROR_RESULT(HookUnavailable, 1030);
+ R_DEFINE_ERROR_RESULT(InvalidNvsrvDevTable, 1031);
+ R_DEFINE_ERROR_RESULT(InvalidForceVerbosityPattern, 1032);
+ R_DEFINE_ERROR_RESULT(InvalidCpuLutMaxAsmPattern, 1033);
+ R_DEFINE_ERROR_RESULT(InvalidCpuLutHook, 1034);
+ R_DEFINE_ERROR_RESULT(InvalidDvcoRateConfig, 1035);
+ R_DEFINE_ERROR_RESULT(InvalidVoltCode, 1036);
+ R_DEFINE_ERROR_RESULT(InvalidVoltLutIndex, 1037);
+ R_DEFINE_ERROR_RESULT(InvalidHookCave, 1038);
}
namespace ams::ldr::hoc {
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_loader.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_loader.hpp
index 4396226a..717f95bb 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/oc_loader.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_loader.hpp
@@ -21,3 +21,4 @@
#include "oc_common.hpp"
#include "pcv/pcv.hpp"
#include "ptm/ptm.hpp"
+#include "nvsrv/nvsrv.hpp"
\ No newline at end of file
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.cpp
index 6fccc8e4..1335f1a6 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.cpp
@@ -155,4 +155,63 @@ namespace ams::ldr::hoc {
while(true) { }
#endif
}
+
+ namespace {
+ constexpr size_t SdLogBufferSize = 0x1000;
+ char sdLogBuffer[SdLogBufferSize];
+ size_t workingLength = 0;
+ }
+
+ void SdLog(const char *data, ...) {
+ if (workingLength >= SdLogBufferSize - 1) {
+ return;
+ }
+
+ va_list args;
+ va_start(args, data);
+ s32 res = vsnprintf(sdLogBuffer + workingLength, SdLogBufferSize - workingLength, data, args);
+ va_end(args);
+
+ if (res < 0) {
+ return;
+ }
+
+ workingLength = std::min(workingLength + static_cast(res), SdLogBufferSize - 1);
+ }
+
+ void SaveLog() {
+ static bool saved = false;
+ if (workingLength == 0 || saved) {
+ return;
+ }
+
+ if (!cfg::IsSdCardInitialized()) {
+ return;
+ }
+
+ if (R_FAILED(fs::MountSdCard("sdmc"))) {
+ return;
+ }
+
+ ON_SCOPE_EXIT { fs::Unmount("sdmc"); };
+
+ constexpr const char *Path = "sdmc:/config/horizon-oc/ldr_log.txt";
+
+ R_DISCARD(fs::DeleteFile(Path));
+ if (R_FAILED(fs::CreateFile(Path, workingLength))) {
+ return;
+ }
+
+ fs::FileHandle file;
+ if (R_FAILED(fs::OpenFile(std::addressof(file), Path, fs::OpenMode_Write))) {
+ return;
+ }
+ ON_SCOPE_EXIT { fs::CloseFile(file); };
+
+ if (R_FAILED(fs::WriteFile(file, 0, sdLogBuffer, workingLength, fs::WriteOption::Flush))) {
+ return;
+ }
+ saved = true;
+ }
+
}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.hpp
index 6522ee13..8a1ed243 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.hpp
@@ -23,6 +23,8 @@ namespace ams::ldr::hoc {
Result SmcCopyToIram(uintptr_t dest, const void *src, u32 size);
void Log(const char *data, ...);
+ void SdLog(const char *data, ...);
+ void SaveLog();
void ViewLog();
/* Emit a formatted line over the kernel debug UART via svcOutputDebugString.
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/common/calculate_timings_common.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/common/calculate_timings_common.cpp
new file mode 100644
index 00000000..e55a0520
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/common/calculate_timings_common.cpp
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) Lightos_
+ *
+ * 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 .
+ */
+
+#include "../../oc_common.hpp"
+#include "../../mtc_timing_value.hpp"
+
+namespace ams::ldr::hoc::pcv {
+
+ namespace {
+ void SwitchLatency(volatile u32 &latency, u32 index, u32 latencyStep) {
+ latency += index * latencyStep;
+ }
+
+ void AutoLatency(volatile u32 &latency, u32 freq, u32 latencyStep) {
+ if (freq > 1600'000 && freq <= 1866'000) { /* 1866tRWL */
+ latency += latencyStep * 2;
+ } else { /* 2133tRWL */
+ latency += latencyStep * 3;
+ }
+ }
+
+ static s32 GetMaxLatencyIndex(volatile u32 *latencyArray, u32 latencySize) {
+ s32 maxIndex = -1;
+ for (u32 i = 0; i < latencySize; ++i) {
+ if (latencyArray[i]) {
+ maxIndex = i;
+ }
+ }
+
+ return maxIndex;
+ }
+
+ void HandleLatency(u32 freq, volatile u32 &latency, volatile u32 *latencyArray, u32 indexMax, u32 latencyStep) {
+ for (u32 i = 0; i <= indexMax; ++i) {
+ if (latencyArray[i] != 0 && freq <= latencyArray[i]) {
+ SwitchLatency(latency, i, latencyStep);
+ return;
+ }
+ }
+
+ SwitchLatency(latency, indexMax, latencyStep);
+ }
+
+ u32 RoundUpToEven(u32 value) {
+ return value + (value % 2);
+ }
+
+ double GetTckAvg(u32 khz) {
+ return 1000'000.0 / static_cast(khz);
+ }
+ }
+
+ void HandleLatency(u32 freq) {
+ static s32 rlIndexMax = GetMaxLatencyIndex(C.readLatency, std::size(C.readLatency));
+ static s32 wlIndexMax = GetMaxLatencyIndex(C.writeLatency, std::size(C.writeLatency));
+ constexpr u32 ReadLatencyStep = 4;
+ constexpr u32 WriteLatencyStep = 2;
+ bool autoLatencyRead = false, autoLatencyWrite = false;
+
+ if (rlIndexMax == -1) {
+ AutoLatency(RL, freq, ReadLatencyStep);
+ autoLatencyRead = true;
+ }
+
+ if (wlIndexMax == -1) {
+ AutoLatency(WL, freq, WriteLatencyStep);
+ autoLatencyWrite = true;
+ }
+
+ if (autoLatencyRead && autoLatencyWrite) {
+ return;
+ }
+
+ if (!autoLatencyRead) {
+ HandleLatency(freq, RL, C.readLatency, rlIndexMax, ReadLatencyStep);
+ }
+
+ if (!autoLatencyWrite) {
+ HandleLatency(freq, WL, C.writeLatency, wlIndexMax, WriteLatencyStep);
+ }
+ }
+
+ u32 GetRlFrequency() {
+ switch (RL) {
+ case RL_1333:
+ return 1333'000;
+ case RL_1600:
+ return 1600'000;
+ case RL_1866:
+ return 1866'000;
+ case RL_2133:
+ return 2133'000;
+ default:
+ return 2133'000;
+ }
+ }
+
+ u32 GetWlFrequency() {
+ switch (WL) {
+ case WL_1333:
+ return 1333'000;
+ case WL_1600:
+ return 1600'000;
+ case WL_1866:
+ return 1866'000;
+ case WL_2133:
+ return 2133'000;
+ default:
+ return 2133'000;
+ }
+ }
+
+ u32 GetOdtLon() {
+ /* Set A. */
+ switch (WL) {
+ case WL_1333:
+ return 4;
+ case WL_1600:
+ case WL_1866:
+ return 6;
+ case WL_2133:
+ return 8;
+ default:
+ return 8;
+ }
+ }
+
+ u32 CalculateNWr() {
+ const u32 latencyFrequency = GetWlFrequency();
+ const double tCK = GetTckAvg(latencyFrequency);
+ const u32 nWr = CEIL(tWR / tCK);
+
+ return RoundUpToEven(nWr);
+ }
+
+ u32 CalculateNTRTP() {
+ const u32 latencyFrequency = GetRlFrequency();
+ const double tCK = GetTckAvg(latencyFrequency);
+
+ return CEIL(tRTP / tCK);
+ }
+
+ u8 CalculateMrw2() {
+ static const u8 rlMapDBI[8] = {
+ 6, 12, 16, 22, 28, 32, 36, 40
+ };
+
+ static const u8 wlMapSetA[8] = {
+ 4, 6, 8, 10, 12, 14, 16, 18
+ };
+
+ u32 rlIndex = 0;
+ u32 wlIndex = 0;
+
+ for (u32 i = 0; i < std::size(rlMapDBI); ++i) {
+ if (rlMapDBI[i] == RL) {
+ rlIndex = i;
+ break;
+ }
+ }
+
+ for (u32 i = 0; i < std::size(wlMapSetA); ++i) {
+ if (wlMapSetA[i] == WL) {
+ wlIndex = i;
+ break;
+ }
+ }
+
+ /* DBI is always enabled. */
+ return static_cast(((rlIndex & 0x7) | ((wlIndex & 0x7) << 3) | ((0 & 0x1) << 6)));
+ }
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/timing_tables.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/common/calculate_timings_common.hpp
similarity index 74%
rename from Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/timing_tables.hpp
rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/common/calculate_timings_common.hpp
index d682000e..76bda88e 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/timing_tables.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/common/calculate_timings_common.hpp
@@ -15,17 +15,20 @@
*/
#pragma once
-#include
-namespace ams::ldr::hoc::pcv::mariko {
+namespace ams::ldr::hoc::pcv {
- struct ReplacePatch {
+ struct RextPatch {
u32 freq;
u32 rext;
};
- extern const ReplacePatch g_rext_table[];
- extern const u32 g_rext_table_size;
- const ReplacePatch *FindRext();
+ void HandleLatency(u32 freq);
+ u32 GetRlFrequency();
+ u32 GetWlFrequency();
+ u32 GetOdtLon();
+ u32 CalculateNWr();
+ u32 CalculateNTRTP();
+ u8 CalculateMrw2();
}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.cpp
index fc6ae8f8..e10a5fea 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.cpp
@@ -15,130 +15,172 @@
*/
#include "../../mtc_timing_value.hpp"
+#include "../common/calculate_timings_common.hpp"
namespace ams::ldr::hoc::pcv::erista {
- void SwitchLatency(volatile u32 &latency, u32 index, u32 latencyStep) {
- latency += index * latencyStep;
- }
+ namespace {
+ /* Dumped from bpmp fw mtc table (l4t). */
+ const RextPatch gRextTable[] = {
+ {1'633'000, 0x17}, {1'728'000, 0x17}, {1'795'200, 0x19},
+ {1'862'400, 0x1A}, {1'894'400, 0x1A}, {1'932'800, 0x1A},
+ {1'958'400, 0x1A}, {1'996'800, 0x1A}, {2'035'200, 0x1A},
+ {2'064'000, 0x19}, {2'099'200, 0x19}, {2'131'200, 0x1A},
+ {2'163'200, 0x1A}, {2'188'800, 0x1A}, {2'227'200, 0x1A},
+ {2'265'600, 0x1B}, {2'291'200, 0x1B}, {2'329'600, 0x1A},
+ {2'361'600, 0x1A},
+ };
- static s32 GetMaxLatencyIndex(volatile u32 *latencyArray, u32 latencySize) {
- s32 maxIndex = -1;
- for (u32 i = 0; i < latencySize; ++i) {
- if (latencyArray[i]) {
- maxIndex = i;
+ const RextPatch *LookupRext(u32 freq) {
+ for (u32 i = 0; i < std::size(gRextTable); i++) {
+ if (gRextTable[i].freq >= freq) {
+ return &gRextTable[i];
+ }
}
+ return nullptr;
}
- return maxIndex;
- }
-
- void AutoLatency(volatile u32 &latency, u32 freq, u32 latencyStep) {
- if (freq > 1600'000 && freq <= 1866'000) { /* 1866tRWL */
- latency += latencyStep * 2;
- } else { /* 2133tRWL */
- latency += latencyStep * 3;
- }
- }
-
- void HandleLatency(u32 freq, volatile u32 &latency, volatile u32 *latencyArray, u32 indexMax, u32 latencyStep) {
- for (u32 i = 0; i <= indexMax; ++i) {
- if (latencyArray[i] != 0 && freq <= latencyArray[i]) {
- SwitchLatency(latency, i, latencyStep);
+ void GetRext(u32 freq) {
+ if (auto r = LookupRext(freq)) {
+ rext = r->rext;
return;
}
+
+ /* Fallback */
+ rext = 0x1A;
}
- SwitchLatency(latency, indexMax, latencyStep);
- }
+ void CalculateReadTimings(u32 freq, double tCK_avg) {
+ tR2P = CalculateNTRTP();
- void HandleLatency(u32 freq) {
- static s32 rlIndexMax = GetMaxLatencyIndex(C.readLatency, std::size(C.readLatency));
- static s32 wlIndexMax = GetMaxLatencyIndex(C.writeLatency, std::size(C.writeLatency));
- constexpr u32 ReadLatencyStep = 4;
- constexpr u32 WriteLatencyStep = 2;
- bool autoLatencyRead = false, autoLatencyWrite = false;
+ double fullRoundDelay = flyByTime + tDQSCK_max;
- if (rlIndexMax == -1) {
- AutoLatency(RL, freq, ReadLatencyStep);
- autoLatencyRead = true;
+ /* Read to write turnaround. */
+ /* Switch seemingly uses odt. */
+ tR2W = RL + (BL / 2) + CEIL((fullRoundDelay / tCK_avg) + tDQSTA) - GET_CYCLE_FLOOR(tODTon) - GetOdtLon() + tWPRE - (C.t6_tRTW * 3) + finetRTW;
+
+ /* Read data valid: Read command -> data ready to be latched into the registers. */
+ rdv = RL + GET_CYCLE_CEIL(fullRoundDelay) + 18;
+
+ /* Read command -> data poppable at the pad macros, 16 cycles ahead of rdv. */
+ qpop = rdv - 16;
+
+ constexpr double TrimmerStepPs = 3.0;
+ constexpr double MaxTrimmerSteps = 95.0;
+
+ double tCkAvgPs = tCK_avg * 1000.0;
+ double tckDerated = tCkAvgPs * 0.85;
+ u32 derateStepCount = MIN(FLOOR(tckDerated / TrimmerStepPs), MaxTrimmerSteps);
+ double deratedNsDelay = (derateStepCount * TrimmerStepPs) / 1000.0;
+
+ /* readEyeEndDerated, nv call this quseExtra. */
+ u32 quseExtra = GET_CYCLE_CEIL(fullRoundDelay - deratedNsDelay);
+
+ /* The amount of deration from the perspective of a full round trip. */
+ u32 readEyeEndDerateMargin = GET_CYCLE_CEIL(fullRoundDelay) - quseExtra;
+
+ u32 tQuse = FLOOR((flyByTime + 0.94) / tCK_avg);
+ u32 readEyeStart = tQuse + RL + 1;
+
+ u32 einputMinimal = readEyeStart + readEyeEndDerateMargin - 2;
+
+ /* Data valid window, quse aligns with the start of the read eye (probably). */
+ /* DQS needs to be logically and'ed with quse in order to be valid. */
+ /* einputMinimal satisfies this. */
+ quse = einputMinimal;
+
+ /* Difference between the two eye edges. */
+ quse_width = quseExtra - tQuse + 4;
+
+ /* input receiver takes time to enable. */
+ u32 einputLatency = GET_CYCLE_CEIL(10.0);
+
+ /* Enable input receiver. */
+ einput = einputMinimal - einputLatency;
+
+ /* Duration of input receiver being enabled? */
+ einput_duration = einputLatency + quse_width + 4;
+
+ /* qrst_duration is CEIL(a / tCK_avg + b) */
+ /* a and b were derived through a brute forcer. */
+ u32 qrst_duration = CEIL(1.4 / tCK_avg + 3.5);
+ u32 qrstLow = MAX(static_cast(einput - qrst_duration - 2), static_cast(0));
+
+ /* quse reset? */
+ qrst = PACK_U32(qrst_duration, qrstLow);
+
+ /* Qsafe, safe to reset quse? */
+ qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput);
+
+ /* Input buffer delay? */
+ ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0);
+
+ GetRext(freq);
}
- if (wlIndexMax == -1) {
- AutoLatency(WL, freq, WriteLatencyStep);
- autoLatencyWrite = true;
+ void CalculateWriteTimings(double tCK_avg) {
+ /* Write to read turnaround. */
+ /* According to jedec specifications: nWR >= tWTR + nRTP */
+ /* Therefore tWTR >= nWR - tRTP. */
+ const u32 nTWTR = GET_CYCLE_CEIL(tWTRStock);
+ const u32 nWr = CalculateNWr();
+ u32 autoPrecharcetWTRMin = nWr - tR2P;
+
+ /* tWTR reduction is applied in a weird way to not break existing setups. */
+ tW2R = WL + 1 + MAX(autoPrecharcetWTRMin, nTWTR) - GET_CYCLE_CEIL(tWTR) + finetWTR;
+ tW2P = (CEIL(WL * 1.7303) * 2) - 5;
+ tWTPDEN = CEIL(((1.8 / tCK_avg) + MAX(RL + (2.7 / tCK_avg), static_cast(tW2P))) + (BL / 2));
+
+ wdv = WL;
+ wsv = WL - 2;
+ wev = WL - 4;
+
+ /* Output buffer delay? */
+ u32 obdlyHigh = 3 / FLOOR(MIN(static_cast(2), tCK_avg * (WL - 7)));
+ u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
+ obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
}
- if (autoLatencyRead && autoLatencyWrite) {
- return;
- }
+ void CalculatePowerDownTimings(double tCK_avg) {
+ pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
+ tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
- if (!autoLatencyRead) {
- HandleLatency(freq, RL, C.readLatency, rlIndexMax, ReadLatencyStep);
- }
-
- if (!autoLatencyWrite) {
- HandleLatency(freq, WL, C.writeLatency, wlIndexMax, WriteLatencyStep);
+ const double tMMRI = tRCD + (tCK_avg * 3);
+ pdex2mrr = tMMRI + 10;
}
}
- void CalculateMrw2() {
- static const u8 rlMapDBI[8] = {
- 6, 12, 16, 22, 28, 32, 36, 40
+ u8 GetNWrIndex() {
+ static const u8 nWrMap[8] = {
+ 6, 10, 16, 20, 24, 30, 34, 40
};
- static const u8 wlMapSetA[8] = {
- 4, 6, 8, 10, 12, 14, 16, 18
- };
+ const u32 nWr = CalculateNWr();
- u32 rlIndex = 0;
- u32 wlIndex = 0;
-
- for (u32 i = 0; i < std::size(rlMapDBI); ++i) {
- if (rlMapDBI[i] == 32) {
- rlIndex = i;
- break;
+ for (u8 i = 0; i < std::size(nWrMap); ++i) {
+ if (nWrMap[i] >= nWr) {
+ return i;
}
}
- for (u32 i = 0; i < std::size(wlMapSetA); ++i) {
- if (wlMapSetA[i] == WL) {
- wlIndex = i;
- break;
- }
- }
-
- /* DBI is always enabled. */
- mrw2 = static_cast(((rlIndex & 0x7) | ((wlIndex & 0x7) << 3) | ((0 & 0x1) << 6)));
+ return static_cast(std::size(nWrMap) - 1);
}
void CalculateTimings(double tCK_avg, u32 freq) {
- RL = RL_1331;
- WL = WL_1331;
+ RL = RL_1333;
+ WL = WL_1333;
+ /* I don't understand why, I don't want to know why, but for some fucking reason erista doesn't handle RL 40. */
+ /* This should not be required but for these fuck damn reasons any type of dram oc will crash instantly with RL 40. */
+ /* But it works fine in l4t? Even when copying the tables it still doesn't work??? WTF */
+ C.readLatency[std::size(C.readLatency) - 1] = 0;
HandleLatency(freq);
- CalculateMrw2();
+ RL = std::min(static_cast(RL_1866), RL);
- tR2P = CEIL((RL * 0.426) - 2.0);
- tR2W = FLOOR(FLOOR((5.0 / tCK_avg) + ((FLOOR(48.0 / WL) - 0.478) * 3.0)) / 1.501) + RL - (C.t6_tRTW * 3) + finetRTW;
-
- tW2P = (CEIL(WL * 1.7303) * 2) - 5;
- tWTPDEN = CEIL(((1.803 / tCK_avg) + MAX(RL + (2.694 / tCK_avg), static_cast(tW2P))) + (BL / 2));
- tW2R = FLOOR(MAX((5.020 / tCK_avg) + 1.130, WL - MAX(-CEIL(0.258 * (WL - RL)), 1.964)) * 1.964) + WL - CEIL(tWTR / tCK_avg) + finetWTR;
-
- wdv = WL;
- wsv = WL - 2;
- wev = 0xA + (WL - 14);
-
- u32 obdlyHigh = 3 / FLOOR(MIN(static_cast(2), tCK_avg * (WL - 7)));
- u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
- obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
-
- pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
- tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
-
- const double tMMRI = tRCD + (tCK_avg * 3);
- pdex2mrr = tMMRI + 10;
+ CalculateReadTimings(freq, tCK_avg);
+ CalculateWriteTimings(tCK_avg);
+ CalculatePowerDownTimings(tCK_avg);
+ mrw2 = CalculateMrw2();
}
}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.hpp
index 7a61386d..668f44fc 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.hpp
@@ -18,6 +18,7 @@
namespace ams::ldr::hoc::pcv::erista {
+ u8 GetNWrIndex();
void CalculateTimings(double tCK_avg, u32 freq);
}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.cpp
index 937af7d7..bb17e547 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.cpp
@@ -56,13 +56,13 @@ namespace ams::ldr::hoc::pcv::erista {
Result GpuFreqMaxAsm(u32 *ptr32) {
// Check if both two instructions match the pattern
u32 ins1 = *ptr32, ins2 = *(ptr32 + 1);
- if (!(asm_compare_no_rd(ins1, GpuAsmPattern[0]) && asm_compare_no_rd(ins2, GpuAsmPattern[1]))) {
+ if (!(_asm::Ignoring(ins1, GpuAsmPattern[0], _asm::field::Rd) && _asm::Ignoring(ins2, GpuAsmPattern[1], _asm::field::Rd))) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
// Both instructions should operate on the same register
- u8 rd = asm_get_rd(ins1);
- if (rd != asm_get_rd(ins2)) {
+ u8 rd = _asm::Get(ins1, _asm::field::Rd);
+ if (rd != _asm::Get(ins2, _asm::field::Rd)) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
@@ -83,8 +83,8 @@ namespace ams::ldr::hoc::pcv::erista {
}
u32 asm_patch[2] = {
- asm_set_rd(asm_set_imm16(GpuAsmPattern[0], max_clock), rd),
- asm_set_rd(asm_set_imm16(GpuAsmPattern[1], max_clock >> 16), rd)
+ _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, rd}, {_asm::field::Imm16, max_clock}),
+ _asm::Encode(_asm::op::MovkW, {_asm::field::Rd, rd}, {_asm::field::Imm16, max_clock >> 16}, {_asm::field::Hw, 1})
};
PATCH_OFFSET(ptr32, asm_patch[0]);
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.hpp
index cc964628..9decf0db 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.hpp
@@ -51,7 +51,7 @@ namespace ams::ldr::hoc::pcv::erista {
inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 };
inline bool GpuMaxClockPatternFn(u32 *ptr32) {
- return asm_compare_no_rd(*ptr32, GpuAsmPattern[0]);
+ return _asm::Ignoring(*ptr32, GpuAsmPattern[0], _asm::field::Rd);
};
constexpr cvb_entry_t GpuCvbTableDefault[] = {
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.cpp
index 6b2d4321..12dd90a3 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.cpp
@@ -46,10 +46,9 @@ namespace ams::ldr::hoc::pcv::erista {
#define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE) \
TABLE->burst_regs.PARAM = VALUE; \
TABLE->shadow_regs_ca_train.PARAM = VALUE; \
+ TABLE->shadow_regs_quse_train.PARAM = VALUE; \
TABLE->shadow_regs_rdwr_train.PARAM = VALUE;
- #define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg))
-
/* Ram power down */
/* B31: DRAM_CLKSTOP_PD */
/* B30: DRAM_CLKSTOP_SR */
@@ -66,14 +65,6 @@ namespace ams::ldr::hoc::pcv::erista {
refresh_raw = MIN(refresh_raw, static_cast(0xFFFF));
}
- if (table->rate_khz > 3200000) {
- rext = 30;
- } else if (table->rate_khz >= 2133001) {
- rext = 28;
- } else {
- rext = 26;
- }
-
u32 trefbw = refresh_raw + 0x40;
trefbw = MIN(trefbw, static_cast(0x3FFF));
@@ -112,37 +103,37 @@ namespace ams::ldr::hoc::pcv::erista {
WRITE_PARAM_ALL_REG(table, emc_pchg2pden, GET_CYCLE_CEIL(1.763));
WRITE_PARAM_ALL_REG(table, emc_ar2pden, GET_CYCLE_CEIL(1.75));
WRITE_PARAM_ALL_REG(table, emc_pdex2cke, GET_CYCLE_CEIL(1.05));
- WRITE_PARAM_ALL_REG(table, emc_act2pden, GET_CYCLE_CEIL(14.0));
+ WRITE_PARAM_ALL_REG(table, emc_act2pden, GET_CYCLE_CEIL(1.75));
WRITE_PARAM_ALL_REG(table, emc_cke2pden, GET_CYCLE_CEIL(8.499));
WRITE_PARAM_ALL_REG(table, emc_pdex2mrr, GET_CYCLE_CEIL(pdex2mrr));
WRITE_PARAM_ALL_REG(table, emc_rw2pden, tWTPDEN);
/* Accept imperfection or prepare for suffering. */
// #if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
- // WRITE_PARAM_ALL_REG(table, emc_einput, einput);
- // WRITE_PARAM_ALL_REG(table, emc_einput_duration, einput_duration);
+ WRITE_PARAM_ALL_REG(table, emc_einput, einput);
+ WRITE_PARAM_ALL_REG(table, emc_einput_duration, einput_duration);
WRITE_PARAM_ALL_REG(table, emc_obdly, obdly);
- // WRITE_PARAM_ALL_REG(table, emc_ibdly, ibdly);
- // WRITE_PARAM_ALL_REG(table, emc_wdv_mask, wdv);
- // WRITE_PARAM_ALL_REG(table, emc_quse_width, quse_width);
- // WRITE_PARAM_ALL_REG(table, emc_quse, quse);
+ WRITE_PARAM_ALL_REG(table, emc_ibdly, ibdly);
+ WRITE_PARAM_ALL_REG(table, emc_wdv_mask, wdv);
+ WRITE_PARAM_ALL_REG(table, emc_quse_width, quse_width);
+ WRITE_PARAM_ALL_REG(table, emc_quse, quse);
WRITE_PARAM_ALL_REG(table, emc_wdv, wdv);
WRITE_PARAM_ALL_REG(table, emc_wsv, wsv);
WRITE_PARAM_ALL_REG(table, emc_wev, wev);
- // WRITE_PARAM_ALL_REG(table, emc_qrst, qrst);
- // WRITE_PARAM_ALL_REG(table, emc_tr_qrst, qrst);
- // WRITE_PARAM_ALL_REG(table, emc_qsafe, qsafe);
- // WRITE_PARAM_ALL_REG(table, emc_tr_qsafe, qsafe);
- // WRITE_PARAM_ALL_REG(table, emc_tr_qpop, qpop);
- // WRITE_PARAM_ALL_REG(table, emc_qpop, qpop);
- // WRITE_PARAM_ALL_REG(table, emc_rdv, rdv);
- // WRITE_PARAM_ALL_REG(table, emc_tr_rdv_mask, rdv + 2);
- // WRITE_PARAM_ALL_REG(table, emc_rdv_early, rdv - 2);
- // WRITE_PARAM_ALL_REG(table, emc_rdv_early_mask, rdv);
- // WRITE_PARAM_ALL_REG(table, emc_rdv_mask, rdv + 2);
- // WRITE_PARAM_ALL_REG(table, emc_tr_rdv, rdv);
+ WRITE_PARAM_ALL_REG(table, emc_qrst, qrst);
+ WRITE_PARAM_ALL_REG(table, emc_tr_qrst, qrst);
+ WRITE_PARAM_ALL_REG(table, emc_qsafe, qsafe);
+ WRITE_PARAM_ALL_REG(table, emc_tr_qsafe, qsafe);
+ WRITE_PARAM_ALL_REG(table, emc_tr_qpop, qpop);
+ WRITE_PARAM_ALL_REG(table, emc_qpop, qpop);
+ WRITE_PARAM_ALL_REG(table, emc_rdv, rdv);
+ WRITE_PARAM_ALL_REG(table, emc_tr_rdv_mask, rdv + 2);
+ WRITE_PARAM_ALL_REG(table, emc_rdv_early, rdv - 2);
+ WRITE_PARAM_ALL_REG(table, emc_rdv_early_mask, rdv);
+ WRITE_PARAM_ALL_REG(table, emc_rdv_mask, rdv + 2);
+ WRITE_PARAM_ALL_REG(table, emc_tr_rdv, rdv);
table->emc_mrw2 = (table->emc_mrw2 & ~0xFFu) | static_cast(mrw2);
- // table->dram_timings.rl = RL;
+ table->dram_timings.rl = RL;
// #endif
constexpr double MC_ARB_DIV = 4.0;
@@ -233,8 +224,7 @@ namespace ams::ldr::hoc::pcv::erista {
const u32 tRFCabStock = tRFC_values[0] * 2;
table->dram_timings.t_rfc = tRFCabStock;
- table->emc_mrw2 = (table->emc_mrw2 & ~0xFFu) | static_cast(mrw2);
- table->emc_mrw = (table->emc_mrw & ~0x70u) | 0x40; /* nWR */
+ table->emc_mrw = (table->emc_mrw & ~0x70u) | (GetNWrIndex() << 4);
table->emc_cfg_2 = 0x11083D;
table->min_volt = std::clamp(900 + (C.emcDvbShift * 25), 900, 1050);
}
@@ -447,27 +437,28 @@ namespace ams::ldr::hoc::pcv::erista {
R_UNLESS(ptr - MovOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern());
u32 adrp = *(ptr - AddrpOffset);
- R_UNLESS(AsmCompareAdrpNoImm(adrp, MtcAdrpAsm), ldr::ResultInvalidMtcTablePattern());
+ R_UNLESS(_asm::Ignoring(adrp, MtcAdrpAsm, _asm::field::ImmAdrpHi, _asm::field::ImmAdrpLo), ldr::ResultInvalidMtcTablePattern());
- /* Check for the branch instruction above the cbz to ensure we are patching the right location*/
+ /* Check for the branch instruction above the cbz to ensure we are patching the right location. */
+ /* Only the opcode is checked; the call target varies by firmware (0x97ffae64 on one build). */
u32 bl = *(ptr - BlOffset);
- R_UNLESS(AsmBlCompareOpcodeOnly(bl, MtcBlIns), ldr::ResultInvalidMtcTablePattern());
+ R_UNLESS(_asm::IsOp(bl, _asm::op::Bl, _asm::field::Imm26), ldr::ResultInvalidMtcTablePattern());
/* Check for the mov that actually sets the mtc table count. */
u32 mov = *(ptr - MovOffset);
bool foundMov = false;
- foundMov = asm_compare_no_rd(mov, MtcMovAsm);
+ foundMov = _asm::Ignoring(mov, MtcMovAsm, _asm::field::Rd);
if (!foundMov) {
mov = *(ptr + MovOffsetOld);
/* Check old firmware offset. */
- foundMov = asm_compare_no_rd(mov, MtcMovAsm);
+ foundMov = _asm::Ignoring(mov, MtcMovAsm, _asm::field::Rd);
}
R_UNLESS(foundMov, ldr::ResultInvalidMtcTablePattern());
constexpr u32 PrologueWindow = 140;
- u32 *functionPrologue = FindFnPrologue(ptr, PrologueWindow, nsoStart);
+ u32 *functionPrologue = _asm::FindFnPrologue(ptr, PrologueWindow, nsoStart);
R_UNLESS(functionPrologue != nullptr, ldr::ResultInvalidMtcTablePattern());
getMtcTableCache.getEristaMtcTableFnSite = functionPrologue;
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.hpp
index 5c1c5100..fa2a2bba 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.hpp
@@ -73,12 +73,11 @@ namespace ams::ldr::hoc::pcv::erista {
constexpr u32 MtcBrAsm = 0xD61F0140;
constexpr u32 MtcMovAsm = 0x52800148;
constexpr u32 MtcAdrpAsm = 0xD0000081;
- constexpr u32 MtcBlIns = 0x97ffae64;
constexpr u32 MtcAddAsm = 0x91131821;
ALWAYS_INLINE bool MemMtcGetGetTablePatternFn(u32 *ptr) {
/* This builds an address that gets returned, so the register must be x0 by convention. */
- return AsmCompareAddNoImm12(*ptr, MtcAddAsm);
+ return _asm::Ignoring(*ptr, MtcAddAsm, _asm::field::Imm12);
}
Result MemFreqMtcTable(u32 *ptr);
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.cpp
index fab24dbf..4b433ce7 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.cpp
@@ -14,191 +14,207 @@
* along with this program. If not, see .
*/
-#include
#include "../../mtc_timing_value.hpp"
-#include "timing_tables.hpp"
+#include "../common/calculate_timings_common.hpp"
namespace ams::ldr::hoc::pcv::mariko {
- void GetRext() {
- if (auto r = FindRext()) {
- rext = r->rext;
- return;
- }
+ namespace {
+ const RextPatch gRextTable[] = {
+ {1'633'000, 0x17}, {1'666'000, 0x18}, {1'700'000, 0x18},
+ {1'733'000, 0x18}, {1'766'000, 0x18}, {1'800'000, 0x1A},
+ {1'833'000, 0x1A}, {1'866'000, 0x1A}, {1'900'000, 0x19},
+ {1'933'000, 0x19}, {1'966'000, 0x19}, {2'100'000, 0x1A},
+ {2'133'000, 0x1A}, {2'166'000, 0x19}, {2'200'000, 0x19},
+ {2'233'000, 0x19}, {2'266'000, 0x1A}, {2'300'000, 0x1B},
+ {2'333'000, 0x1B}, {2'366'000, 0x1B}, {2'400'000, 0x1B},
+ {2'433'000, 0x1B}, {2'466'000, 0x1B}, {2'500'000, 0x1A},
+ {2'533'000, 0x1C}, {2'566'000, 0x1B}, {2'600'000, 0x1B},
+ {2'633'000, 0x1B}, {2'666'000, 0x1B}, {2'700'000, 0x1C},
+ {2'733'000, 0x1C}, {2'766'000, 0x1D}, {2'800'000, 0x1D},
+ {2'833'000, 0x1D}, {2'866'000, 0x1D}, {2'900'000, 0x1D},
+ {2'933'000, 0x1C}, {2'966'000, 0x1D}, {3'000'000, 0x1D},
+ {3'033'000, 0x1D}, {3'066'000, 0x1D}, {3'100'000, 0x1D},
+ {3'133'000, 0x1D}, {3'166'000, 0x1C}, {3'200'000, 0x1C},
+ {3'233'000, 0x1E}, {3'266'000, 0x1F}, {3'300'000, 0x1E},
+ {3'333'000, 0x1F}, {3'366'000, 0x1F}, {3'400'000, 0x1F},
+ {3'433'000, 0x1F}, {3'466'000, 0x1F}, {3'500'000, 0x1E},
+ {3'533'000, 0x1E}, {3'566'000, 0x1E}, {3'600'000, 0x1E},
+ {3'633'000, 0x1E}, {3'666'000, 0x1E}, {3'700'000, 0x1F},
+ };
- /* > 3200 */
- rext = 0x1E;
- }
-
- void SwitchLatency(volatile u32 &latency, u32 index, u32 latencyStep) {
- latency += index * latencyStep;
- }
-
- static s32 GetMaxLatencyIndex(volatile u32 *latencyArray, u32 latencySize) {
- s32 maxIndex = -1;
- for (u32 i = 0; i < latencySize; ++i) {
- if (latencyArray[i]) {
- maxIndex = i;
+ const RextPatch *LookupRext(u32 freq) {
+ for (u32 i = 0; i < std::size(gRextTable); i++) {
+ if (gRextTable[i].freq >= freq) {
+ return &gRextTable[i];
+ }
}
+ return nullptr;
}
- return maxIndex;
- }
-
- void AutoLatency(volatile u32 &latency, u32 freq, u32 latencyStep) {
- if (freq > 1600'000 && freq <= 1862'400) { /* 1866tRWL */
- latency += latencyStep * 2;
- } else { /* 2133tRWL */
- latency += latencyStep * 3;
- }
- }
-
- void HandleLatency(u32 freq, volatile u32 &latency, volatile u32 *latencyArray, u32 indexMax, u32 latencyStep) {
- for (u32 i = 0; i <= indexMax; ++i) {
- if (latencyArray[i] != 0 && freq <= latencyArray[i]) {
- SwitchLatency(latency, i, latencyStep);
+ void GetRext(u32 freq) {
+ if (auto r = LookupRext(freq)) {
+ rext = r->rext;
return;
}
+
+ /* Fallback */
+ rext = 0x1E;
}
- SwitchLatency(latency, indexMax, latencyStep);
- }
+ void CalculateActivatePrechargeTimings(bool lowFreq) {
+ tRCD = tRCD_values[lowFreq ? C.low_t1_tRCD : C.t1_tRCD];
+ tRPpb = tRP_values[lowFreq ? C.low_t2_tRP : C.t2_tRP];
+ tRAS = tRAS_values[lowFreq ? C.low_t3_tRAS : C.t3_tRAS];
- void HandleLatency(u32 freq) {
- static s32 rlIndexMax = GetMaxLatencyIndex(C.readLatency, std::size(C.readLatency));
- static s32 wlIndexMax = GetMaxLatencyIndex(C.writeLatency, std::size(C.writeLatency));
- constexpr u32 ReadLatencyStep = 4;
- constexpr u32 WriteLatencyStep = 2;
- bool autoLatencyRead = false, autoLatencyWrite = false;
-
- if (rlIndexMax == -1) {
- AutoLatency(RL, freq, ReadLatencyStep);
- autoLatencyRead = true;
+ tRC = tRAS + tRPpb;
+ tRPab = tRPpb + 3;
}
- if (wlIndexMax == -1) {
- AutoLatency(WL, freq, WriteLatencyStep);
- autoLatencyWrite = true;
+ void CalculateActivateWindowTimings(bool lowFreq) {
+ tRRD = tRRD_values[lowFreq ? C.low_t4_tRRD : C.t4_tRRD];
+ tFAW = static_cast(tRRD * 4.0);
}
- if (autoLatencyRead && autoLatencyWrite) {
- return;
+ void CalculateReadTimings(u32 freq, double tCK_avg, bool lowFreq) {
+ u32 tRTW = lowFreq ? C.low_t6_tRTW : C.t6_tRTW;
+ s32 finetRTW = C.fineTune_t6_tRTW;
+
+ double fullRoundDelay = flyByTime + tDQSCK_max;
+
+ /* Read to precharge delay. */
+ tR2P = CalculateNTRTP();
+
+ /* Read to write turnaround. */
+ /* Switch seemingly uses odt. */
+ tR2W = RL + (BL / 2) + CEIL((fullRoundDelay / tCK_avg) + tDQSTA) - GET_CYCLE_FLOOR(tODTon) - GetOdtLon() - (tRTW * 3) + finetRTW;
+ tRTM = FLOOR((10.0 + RL) + (3.502 / tCK_avg)) + GET_CYCLE_FLOOR(7.489);
+ tRATM = CEIL((tRTM - 10.0) + (RL * 0.426));
+
+ /* Read data valid: Read command -> data ready to be latched into the registers. */
+ rdv = RL + GET_CYCLE_CEIL(fullRoundDelay) + 16;
+
+ /* Read command -> data poppable at the pad macros, 14 cycles ahead of rdv. */
+ qpop = rdv - 14;
+
+ constexpr double TrimmerStepPs = 3.0;
+ constexpr double MaxTrimmerSteps = 96.0;
+
+ double tCkAvgPs = tCK_avg * 1000.0;
+ double tckDerated = tCkAvgPs * 0.85;
+ u32 derateStepCount = MIN(FLOOR(tckDerated / TrimmerStepPs), MaxTrimmerSteps);
+ double deratedNsDelay = (derateStepCount * TrimmerStepPs) / 1000.0;
+ /* readEyeEndDerated, nv call this quseExtra. */
+ u32 quseExtra = GET_CYCLE_CEIL(fullRoundDelay - deratedNsDelay);
+
+ /* The amount of deration from the perspective of a full round trip. */
+ u32 readEyeEndDerateMargin = GET_CYCLE_CEIL(fullRoundDelay) - quseExtra;
+
+ u32 tQuse = FLOOR((flyByTime + 0.94) / tCK_avg);
+ u32 readEyeStart = tQuse + RL + 1;
+
+ u32 einputMinimal = readEyeStart + readEyeEndDerateMargin - 2;
+
+ /* Data valid window, quse aligns with the start of the read eye (probably). */
+ /* DQS needs to be logically and'ed with quse in order to be valid. */
+ /* einputMinimal satisfies this. */
+ quse = einputMinimal;
+
+ /* input receiver takes time to enable. */
+ u32 einputLatency = GET_CYCLE_CEIL(10.0);
+
+ /* Enable input receiver. */
+ einput = einputMinimal - einputLatency;
+
+ /* Difference between the two eye edges. */
+ quse_width = quseExtra - tQuse + 4;
+
+ /* Duration of input receiver being enabled? */
+ einput_duration = einputLatency + quse_width + 4;
+
+ /* qrst_duration is CEIL(a / tCK_avg + b) */
+ /* a and b were derived through a brute forcer. */
+ u32 qrst_duration = CEIL(1.4 / tCK_avg + 3.5);
+ u32 qrstLow = MAX(static_cast(einput - qrst_duration - 2), static_cast(0));
+
+ /* quse reset? */
+ qrst = PACK_U32(qrst_duration, qrstLow);
+
+ /* Input buffer delay? */
+ ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0);
+
+ /* Qsafe, safe to reset quse? */
+ qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput);
+
+ GetRext(freq);
}
- if (!autoLatencyRead) {
- HandleLatency(freq, RL, C.readLatency, rlIndexMax, ReadLatencyStep);
+ void CalculateWriteTimings(double tCK_avg, bool lowFreq) {
+ u32 tWTR = 10 - tWTR_values[lowFreq ? C.low_t7_tWTR : C.t7_tWTR];
+ s32 finetWTR = C.fineTune_t7_tWTR;
+
+ /* Write to read turnaround. */
+ /* According to jedec specifications: nWR >= tWTR + nRTP */
+ /* Therefore tWTR >= nWR - tRTP. */
+ const u32 nTWTR = GET_CYCLE_CEIL(tWTRStock);
+ const u32 nWr = CalculateNWr();
+ u32 autoPrecharcetWTRMin = nWr - tR2P;
+
+ /* tWTR reduction is applied in a weird way to not break existing setups. */
+ tW2R = WL + 1 + MAX(autoPrecharcetWTRMin, nTWTR) - GET_CYCLE_CEIL(tWTR) + finetWTR;
+ tW2P = (CEIL(WL * 1.7303) * 2) - 5;
+ tWTM = CEIL(WL + ((7.570 / tCK_avg) + 8.753));
+ tWATM = (tWTM + (FLOOR(WL / 0.816) * 2.0)) - 4.0;
+
+ wdv = WL;
+ wsv = WL - 2;
+ wev = WL - 4;
+
+ /* Output buffer delay? */
+ u32 obdlyHigh = 3 / FLOOR(MIN(static_cast(2), tCK_avg * (WL - 7)));
+ u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
+ obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
}
- if (!autoLatencyWrite) {
- HandleLatency(freq, WL, C.writeLatency, wlIndexMax, WriteLatencyStep);
- }
- }
+ void CalculateRefreshTimings(double tCK_avg, bool lowFreq) {
+ tRFCpb = tRFC_values[lowFreq ? C.low_t5_tRFC : C.t5_tRFC];
+ tRFCab = tRFCpb * 2;
+ tXSR = static_cast(tRFCab + 7.5);
- void CalculateMrw2() {
- static const u8 rlMapDBI[8] = {
- 6, 12, 16, 22, 28, 32, 36, 40
- };
-
- static const u8 wlMapSetA[8] = {
- 4, 6, 8, 10, 12, 14, 16, 18
- };
-
- u32 rlIndex = 0;
- u32 wlIndex = 0;
-
- for (u32 i = 0; i < std::size(rlMapDBI); ++i) {
- if (rlMapDBI[i] == RL) {
- rlIndex = i;
- break;
+ u32 tREFI = lowFreq ? C.low_t8_tREFI : C.t8_tREFI;
+ refresh_raw = 0xFFFF;
+ if (tREFI != 6) {
+ refresh_raw = GET_CYCLE_CEIL(tREFpb_values[tREFI]) - 0x40;
+ refresh_raw = MIN(refresh_raw, static_cast(0xFFFF));
}
}
- for (u32 i = 0; i < std::size(wlMapSetA); ++i) {
- if (wlMapSetA[i] == WL) {
- wlIndex = i;
- break;
- }
- }
+ void CalculatePowerDownTimings(double tCK_avg) {
+ tWTPDEN = CEIL(((1.8 / tCK_avg) + MAX(RL + (2.7 / tCK_avg), static_cast(tW2P))) + (BL / 2));
+ pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
- /* DBI is always enabled. */
- mrw2 = static_cast(((rlIndex & 0x7) | ((wlIndex & 0x7) << 3) | ((0 & 0x1) << 6)));
+ tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
+
+ double tMMRI = tRCD + (tCK_avg * 3);
+ pdex2mrr = tMMRI + 10;
+ }
}
void CalculateTimings(double tCK_avg, u32 freq) {
- RL = RL_1331;
- WL = WL_1331;
+ RL = RL_1333;
+ WL = WL_1333;
HandleLatency(freq);
- GetRext();
-
- /* At 1333WL, for some reason (incorrect ram timing config in mtc table?), tRP causes crashes at high reductions - 2 seems to be the most common limit. */
- /* This is a lazy workaround until I find the issue... */
const bool lowFreq = freq < C.timingEmcTbreak;
- tRCD = tRCD_values[lowFreq ? C.low_t1_tRCD : C.t1_tRCD];
- tRPpb = tRP_values[lowFreq ? C.low_t2_tRP : C.t2_tRP];
- tRAS = tRAS_values[lowFreq ? C.low_t3_tRAS : C.t3_tRAS];
- tRRD = tRRD_values[lowFreq ? C.low_t4_tRRD : C.t4_tRRD];
- tRFCpb = tRFC_values[lowFreq ? C.low_t5_tRFC : C.t5_tRFC];
-
- u32 tRTW = lowFreq ? C.low_t6_tRTW : C.t6_tRTW;
- u32 tWTR = 10 - tWTR_values[lowFreq ? C.low_t7_tWTR : C.t7_tWTR];
-
- s32 finetRTW = C.fineTune_t6_tRTW;
- s32 finetWTR = C.fineTune_t7_tWTR;
-
- u32 tREFI = lowFreq ? C.low_t8_tREFI : C.t8_tREFI;
- refresh_raw = 0xFFFF;
- if (tREFI != 6) {
- refresh_raw = CEIL(tREFpb_values[tREFI] / tCK_avg) - 0x40;
- refresh_raw = MIN(refresh_raw, static_cast(0xFFFF));
- }
-
- tRC = tRAS + tRPpb;
- tRFCab = tRFCpb * 2;
- tXSR = static_cast(tRFCab + 7.5);
- tFAW = static_cast(tRRD * 4.0);
- tRPab = tRPpb + 3;
-
- tR2P = CEIL((RL * 0.426) - 2.0);
- tR2W = FLOOR(FLOOR((5.0 / tCK_avg) + ((FLOOR(48.0 / WL) - 0.478) * 3.0)) / 1.501) + RL - (tRTW * 3) + finetRTW;
- tRTM = FLOOR((10.0 + RL) + (3.502 / tCK_avg)) + FLOOR(7.489 / tCK_avg);
- tRATM = CEIL((tRTM - 10.0) + (RL * 0.426));
-
- rdv = RL + FLOOR((5.105 / tCK_avg) + 17.017);
- qpop = rdv - 14;
- quse_width = CEIL(((4.897 / tCK_avg) - FLOOR(2.538 / tCK_avg)) + 3.782);
- quse = FLOOR(RL + ((5.082 / tCK_avg) + FLOOR(2.560 / tCK_avg))) - CEIL(4.820 / tCK_avg);
- einput_duration = FLOOR(9.936 / tCK_avg) + 5.0 + quse_width;
- einput = quse - CEIL(9.928 / tCK_avg);
- u32 qrst_duration = FLOOR(8.399 - tCK_avg);
- u32 qrstLow = MAX(static_cast(einput - qrst_duration - 2), static_cast(0));
- qrst = PACK_U32(qrst_duration, qrstLow);
- ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0);
- qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput);
- tW2P = (CEIL(WL * 1.7303) * 2) - 5;
- tWTPDEN = CEIL(((1.803 / tCK_avg) + MAX(RL + (2.694 / tCK_avg), static_cast(tW2P))) + (BL / 2));
- tW2R = FLOOR(MAX((5.020 / tCK_avg) + 1.130, WL - MAX(-CEIL(0.258 * (WL - RL)), 1.964)) * 1.964) + WL - CEIL(tWTR / tCK_avg) + finetWTR;
- tWTM = CEIL(WL + ((7.570 / tCK_avg) + 8.753));
- tWATM = (tWTM + (FLOOR(WL / 0.816) * 2.0)) - 4.0;
-
- wdv = WL;
- wsv = WL - 2;
- wev = 0xA + (WL - 14);
-
- u32 obdlyHigh = 3 / FLOOR(MIN(static_cast(2), tCK_avg * (WL - 7)));
- u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
- obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
-
- pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
-
- tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
-
- u32 tMMRI = tRCD + (tCK_avg * 3);
- pdex2mrr = tMMRI + 10;
-
- CalculateMrw2();
+ CalculateActivatePrechargeTimings(lowFreq);
+ CalculateActivateWindowTimings(lowFreq);
+ CalculateReadTimings(freq, tCK_avg, lowFreq);
+ CalculateWriteTimings(tCK_avg, lowFreq);
+ CalculateRefreshTimings(tCK_avg, lowFreq);
+ CalculatePowerDownTimings(tCK_avg);
+ mrw2 = CalculateMrw2();
}
}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.hpp
index 8659d3f2..c908a220 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.hpp
@@ -21,4 +21,3 @@ namespace ams::ldr::hoc::pcv::mariko {
void CalculateTimings(double tCK_avg, u32 freq);
}
-
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.cpp
index c2644620..253e0707 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.cpp
@@ -5,7 +5,7 @@
*
* Copyright (c) B3711
*
- * Copyright (c) Souldbminer and Horizon OC Contributors
+ * Copyright (c) Souldbminer, Lightos and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
@@ -27,506 +27,42 @@
#include "pcv_mariko_cpu.hpp"
#include "pcv_mariko_gpu.hpp"
#include "pcv_mariko_mtc.hpp"
+#include "pcv_mariko_soc.hpp"
+#include "pcv_mariko_log.hpp"
+#include "pcv_mariko_cave.hpp"
#include "calculate_timings_mariko.hpp"
namespace ams::ldr::hoc::pcv::mariko {
u32 *nsoStart;
+ size_t g_nso_size = 0;
- namespace {
- size_t g_nso_size = 0;
- uintptr_t g_cave_cursor = 0;
- }
+ DEFINE_HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData);
- static uintptr_t CaveReserve(size_t count) {
- if (g_pcv_cave == 0 || g_cave_cursor == 0) {
- return 0;
- }
- if (g_cave_cursor + count * sizeof(u32) > g_pcv_cave + g_pcv_cave_size) {
- return 0;
- }
- const uintptr_t entry = g_cave_cursor;
- g_cave_cursor += count * sizeof(u32);
- return entry;
- }
+ Result InstallHooks() {
+ R_TRY(CaveInstall());
- #if HOC_UART_LOG
- /* Redirect pcv's NvLog() calls to UART */
- Result NvLogUartRedirect(u32 *ptr) {
- const uintptr_t mapped_nso = reinterpret_cast(nsoStart);
- const size_t nso_size = g_nso_size;
- const uintptr_t textEnd = g_pcv_cave; /* .text ends where the cave begins */
- const uintptr_t vsnprintf_addr = reinterpret_cast(ptr);
+ R_TRY(Hooks().CheckEnabled());
- /* NvLog via the VDD_SOC log */
- static const char Fmt[] = "%s(%s): DVFS request VDD_SOC %d mV\n";
- constexpr size_t FmtLen = sizeof(Fmt) - 1;
- uintptr_t strAddr = 0;
- {
- const char *hay = reinterpret_cast(mapped_nso);
- for (size_t i = 0; i + FmtLen <= nso_size; ++i) {
- if (std::memcmp(hay + i, Fmt, FmtLen) == 0) { strAddr = mapped_nso + i; break; }
- }
- }
- if (strAddr == 0) {
- LOGGING("NvLogRedirect: fmt string not found (vsnprintf@+%lx)", vsnprintf_addr - mapped_nso);
- R_THROW(ldr::ResultInvalidNvLogRedirect());
- }
+ R_TRY(Hooks().CopyPayload());
- uintptr_t nvlog_addr = 0;
- for (u32 *p = nsoStart; reinterpret_cast(p + 2) <= textEnd; ++p) {
- const uintptr_t pc = reinterpret_cast(p);
- if (!AsmIsAdrp(p[0])) {
- continue;
- }
- const uintptr_t adrpPage = (pc & ~static_cast(0xFFFu)) + static_cast(AsmAdrpPageOffset(p[0]));
- if (adrpPage != (strAddr & ~static_cast(0xFFFu))) {
- continue;
- }
- const u32 reg = asm_get_rd(p[0]);
- if (!(AsmIsAddImm64(p[1]) && asm_get_rd(p[1]) == reg && AsmGetRn(p[1]) == reg && AsmGetImm12(p[1]) == (strAddr & 0xFFFu))) {
- continue;
- }
- for (u32 k = 2; k <= 12 && (pc + (k + 1) * 4) <= textEnd; ++k) {
- if (AsmIsBl(p[k])) { nvlog_addr = AsmBranchTarget(p[k], pc + k * 4); break; }
- }
- if (nvlog_addr != 0) {
- break;
- }
- }
- if (nvlog_addr == 0 || nvlog_addr < mapped_nso || nvlog_addr >= textEnd) {
- LOGGING("NvLogRedirect: NvLog entry not found (fmt@+%lx)", strAddr - mapped_nso);
- R_THROW(ldr::ResultInvalidNvLogRedirect());
- }
+ auto *data = Hooks().BindData(m_HookPayloadData);
+ R_UNLESS(data != nullptr, ldr::ResultHookDataOutOfMemory());
- const uintptr_t helper = CaveReserve(40);
- if (helper == 0) {
- LOGGING("NvLogRedirect: cave unavailable (cave=%lx size=%lx)",
- static_cast(g_pcv_cave), static_cast(g_pcv_cave_size));
- R_THROW(ldr::ResultInvalidNvLogRedirect());
- }
- u32 *t = reinterpret_cast(helper);
- size_t n = 0;
- auto emit = [&](u32 ins) { t[n] = ins; ++n; };
+ R_TRY(SharedClkBusInstallHooks(data));
- emit(AsmMakeSubImm64(31, 31, 0x200));
- emit(AsmMakeStpImm64(0, 1, 31, 0x100));
- emit(AsmMakeStpImm64(2, 3, 31, 0x110));
- emit(AsmMakeStpImm64(4, 5, 31, 0x120));
- emit(AsmMakeStpImm64(6, 7, 31, 0x130));
- emit(AsmMakeStpqImm(0, 1, 31, 0x140));
- emit(AsmMakeStpqImm(2, 3, 31, 0x160));
- emit(AsmMakeStpqImm(4, 5, 31, 0x180));
- emit(AsmMakeStpqImm(6, 7, 31, 0x1A0));
- emit(AsmMakeStrImm64(30, 31, 0x1E0));
- emit(AsmMakeAddImm64(9, 31, 0x200)); emit(AsmMakeStrImm64(9, 31, 0x1C0)); /* __stack */
- emit(AsmMakeAddImm64(9, 31, 0x140)); emit(AsmMakeStrImm64(9, 31, 0x1C8)); /* __gr_top */
- emit(AsmMakeAddImm64(9, 31, 0x1C0)); emit(AsmMakeStrImm64(9, 31, 0x1D0)); /* __vr_top */
- emit(AsmMakeMovnW(9, 0x37)); emit(AsmMakeStrImm32(9, 31, 0x1D8)); /* __gr_offs = -56 */
- emit(AsmMakeMovnW(9, 0x7F)); emit(AsmMakeStrImm32(9, 31, 0x1DC)); /* __vr_offs = -128 */
- emit(AsmMakeAddImm64(0, 31, 0x00)); /* mov x0,sp (buf) */
- emit(AsmMakeMovzW(1, 0x100)); /* size = 0x100 */
- emit(AsmMakeLdrImm64(2, 31, 0x100)); /* fmt (saved x0) */
- emit(AsmMakeAddImm64(3, 31, 0x1C0)); /* ap */
- emit(AsmMakeBl(helper + n * 4, vsnprintf_addr));
- emit(AsmMakeMovReg(1, 0)); /* len = retval */
- emit(AsmMakeCmpImm32(1, 0x100));
- { const size_t at = n; emit(AsmMakeBCond(helper + at * 4, helper + (at + 2) * 4, 0x3u)); } /* b.lo +2 */
- emit(AsmMakeMovzW(1, 0xFF)); /* clamp len */
- emit(AsmMakeAddImm64(0, 31, 0x00)); /* mov x0,sp (str) */
- emit(AsmMakeSvc(0x27)); /* svcOutputDebugString */
- emit(AsmMakeLdrImm64(30, 31, 0x1E0));
- emit(AsmMakeAddImm64(31, 31, 0x200));
- emit(RetIns);
+ R_TRY(LutWriterInstallHooks(data));
- /* Redirect the call sites as patching the actual function causes crash */
- const uintptr_t roStart = g_pcv_cave + g_pcv_cave_size; /* module .rodata start */
- size_t patchedSites = 0;
- if (HOC_PCV_NVLOG_PATCH) {
- for (u32 *p = nsoStart; reinterpret_cast(p + 1) <= textEnd; ++p) {
- if (!AsmIsBl(*p)) {
- continue;
- }
- const uintptr_t pc = reinterpret_cast(p);
- if (AsmBranchTarget(*p, pc) != nvlog_addr) {
- continue;
- }
- bool isFmtCall = false;
- for (u32 j = 1; j <= 8 && reinterpret_cast(p - j) >= reinterpret_cast(nsoStart); ++j) {
- const u32 w = *(p - j);
- if (AsmIsAdrp(w) && asm_get_rd(w) == 0) { /* adrp x0, */
- const uintptr_t wpc = pc - j * 4;
- const uintptr_t tgtPage = (wpc & ~static_cast(0xFFFu)) + static_cast(AsmAdrpPageOffset(w));
- if (tgtPage >= (roStart & ~static_cast(0xFFFu))) { isFmtCall = true; break; }
- }
- }
- if (isFmtCall) {
- PATCH_OFFSET(p, AsmMakeBl(pc, helper));
- ++patchedSites;
- }
- }
- }
+#if HOC_UART_LOG
+ R_TRY(ForceVebosityInstallHooks(data));
+#endif
- LOGGING("NvLogRedirect: stub@+%lx vsnprintf@+%lx helper@+%lx instr=%zu sites=%zu",
- nvlog_addr - mapped_nso, vsnprintf_addr - mapped_nso, helper - mapped_nso, n, patchedSites);
- R_SUCCEED();
- }
- #endif
-
- /* Relocate C2/C3Bus to avoid issues*/
- Result BusFreqReloc(u32 *ptr) {
- const u32 busReg = AsmGetRn(ptr[0]); /* ldr Xbuf,[Xbus,#0x10] : bus struct pointer */
- const u32 bufReg = asm_get_rd(ptr[0]); /* : freq-buffer arg */
- const u32 bufOff = AsmGetLdStImm64Off(ptr[0]); /* : bus->freqBuf offset */
- const u32 cntReg = asm_get_rd(ptr[1]); /* add Xcnt,Xbus,#0x18 : arg2 (&count) */
- const u32 railReg = asm_get_rd(ptr[2]); /* str Xrail,[Xbus,#0x50]: arg0 (rail) */
- u32 *call = ptr + 3; /* the bl to relocate */
- const uintptr_t realFn = AsmBranchTarget(*call, reinterpret_cast(call));
-
- /* Pick 3 scratch registers */
- u32 s[3], sc = 0;
- for (u32 r = 9; r <= 15 && sc < 3; ++r) {
- if (r != busReg && r != bufReg && r != cntReg && r != railReg) {
- s[sc++] = r;
- }
- }
- R_UNLESS(sc == 3, ldr::ResultInvalidBusFreqReloc());
-
- const uintptr_t tramp = CaveReserve(9);
- R_UNLESS(tramp != 0, ldr::ResultInvalidBusFreqReloc());
-
- const uintptr_t region = g_pcv_scratch + HocBusFreqBufOffset; /* [0]=counter, +0x10 + i*0x400 = bufs */
- u32 *t = reinterpret_cast(tramp);
- size_t n = 0;
- auto emit = [&](u32 ins) { t[n] = ins; ++n; };
- emit(AsmMakeAdrp(tramp + n * 4, region, s[0])); /* adrp s0, */
- emit(AsmMakeAddImm64(s[0], s[0], region & 0xFFFu)); /* add s0,s0,#lo */
- emit(AsmMakeLdrImm32(s[1], s[0], 0x00)); /* s1 = counter */
- emit(AsmMakeAddImm64(s[2], s[1], 1)); /* s2 = counter+1 */
- emit(AsmMakeStrImm32(s[2], s[0], 0x00)); /* counter++ */
- emit(AsmMakeAddImm64(s[0], s[0], 0x10)); /* s0 = region+0x10 (buffers) */
- emit(AsmMakeAddShiftedReg64(bufReg, s[0], s[1], 10)); /* Xbuf = s0 + counter*0x400 */
- emit(AsmMakeStrImm64(bufReg, busReg, bufOff)); /* bus[freqBuf] = Xbuf */
- emit(AsmMakeB(tramp + n * 4, realFn)); /* tail-call the real function */
-
- PATCH_OFFSET(call, AsmMakeBl(reinterpret_cast(call), tramp));
- const uintptr_t base = reinterpret_cast(nsoStart);
- (void) base;
- LOGGING("BusFreqReloc: call@+%lx -> tramp@+%lx realfn@+%lx (bus=x%u buf=x%u off=0x%x scratch=x%u,x%u,x%u)",
- reinterpret_cast(call) - base, tramp - base, realFn - base, busReg, bufReg, bufOff, s[0], s[1], s[2]);
- R_SUCCEED();
- }
-
-
- #if HOC_UART_LOG
- /* Force GetEffectiveVerbosityLevel to return a non-zero level so all NvLog runs. */
- Result ForceVerbosity(u32 *ptr) {
- PATCH_OFFSET(&ptr[0], AsmMakeMovzW(0, static_cast(HOC_PCV_FORCE_VERBOSITY))); /* movz w0,#level */
- PATCH_OFFSET(&ptr[1], RetIns); /* ret */
- R_SUCCEED();
- }
- #endif
-
- /* Widen InitDram for a >32-entry EMC DVFS list. Freq array can be dropped to free 264 bytes, relocate the Soc LUT to that space */
- Result EmcSocLutReloc(u32 *ptr) {
- constexpr u32 Window = 48;
-
- u32 *freqStore = ScanAssembly(ptr - Window, Window, EmcSocFreqStoreAsm, asm_compare_no_rd); /* str x?,[x8,#0x18] */
- u32 *voltStore = ScanAssembly(ptr - Window, Window, EmcSocVoltStoreAsm, asm_compare_no_rd); /* str w?,[x8,#0x48] */
- u32 *readLoad = ScanAssembly(ptr - Window, Window, EmcSocReadLoadAsm, asm_compare_no_rd); /* ldr w?,[x9,#0x48] */
- R_UNLESS(freqStore && voltStore && readLoad, ldr::ResultInvalidEmcSocLut());
-
- u32 *voltBase = voltStore - 2; /* `add Xb,Xsrc,Xi,LSL#2` (a cmn sits between it and store) */
- R_UNLESS(AsmIsAddShiftedReg64(*voltBase) && asm_get_rd(*voltBase) == AsmGetRn(*voltStore),
- ldr::ResultInvalidEmcSocLut());
-
- /* adrp Xl ; add Xl,Xl,#off ; ... ; str Xl,[rail,#0x120] */
- const u32 lutReg = asm_get_rd(ptr[0]);
- const u32 railReg = AsmGetRn(ptr[0]);
- R_UNLESS(AsmIsAdrp(ptr[-3]) && asm_get_rd(ptr[-3]) == lutReg, ldr::ResultInvalidEmcSocLut());
- R_UNLESS(AsmIsAddImm64(ptr[-2]) && asm_get_rd(ptr[-2]) == lutReg && AsmGetRn(ptr[-2]) == lutReg,
- ldr::ResultInvalidEmcSocLut());
-
- const u32 srcBase = AsmGetRn(*voltBase); /* rail ptr at +0x20 */
- const u32 wBase = asm_get_rd(*voltBase); /* base reg */
- const u32 wIdx = AsmGetRm(*voltBase); /* loop index */
-
- PATCH_OFFSET(freqStore, NopIns); /* Unneeded */
- PATCH_OFFSET(voltBase, AsmMakeLdrImm64(wBase, srcBase, 0x20)); /* ldr Xb,[Xsrc,#0x20] (rail) */
- PATCH_OFFSET(voltStore - 1, AsmMakeAddImm64(wBase, wBase, 0x18)); /* add Xb,Xb,#0x18 (was cmn) */
- PATCH_OFFSET(voltStore, AsmSetLdStRegOffset(*voltStore, wIdx)); /* str Wv,[Xb,Xi,LSL#2] -> rail+0x18+i*4 */
- PATCH_OFFSET(voltStore + 1, NopIns); /* Unneeded */
-
- /* rail+0x18 as the socMinLut pointer. */
- PATCH_OFFSET(ptr - 3, NopIns);
- PATCH_OFFSET(ptr - 2, AsmMakeAddImm64(lutReg, railReg, 0x18));/* add Xl,rail,#0x18 */
-
- /* Drop the abort branch in case of a bad read */
- for (u32 i = 1; i <= 4; ++i) {
- if (AsmIsBCond(readLoad[i])) {
- PATCH_OFFSET(&readLoad[i], NopIns);
- break;
- }
- }
- R_SUCCEED();
- }
-
- Result EmcDvfsCountLimit(u32 *ptr) {
- R_UNLESS(EmcDvfsCountPatternFn(ptr), ldr::ResultInvalidEmcDvfsCount());
-
- /* cmp w?,#0x21 -> cmp w?,#EmcDvfsTableEntryCount */
- PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast(EmcDvfsTableEntryCount)));
- R_SUCCEED();
- }
-
- Result EmcRateListLimit(u32 *ptr) {
- /* ptr = cmp w?,#0x20 ; ptr[1] = csel w?,w?,w?,lt (w? = min(maxCount, 32)) ; ptr[2] = bl */
- R_UNLESS(EmcRateListPatternFn(ptr), ldr::ResultInvalidEmcRateList());
-
- /* The csel's Rm holds the 32 cap. */
- const u32 capReg = AsmGetRm(ptr[1]);
- const u32 capMov = AsmMakeMovzW(capReg, 0x20); /* movz w,#0x20 */
-
- u32 *movPtr = nullptr;
- for (u32 i = 1; i <= 16; ++i) {
- if (*(ptr - i) == capMov) {
- movPtr = ptr - i;
- break;
- }
- }
- R_UNLESS(movPtr, ldr::ResultInvalidEmcRateList());
-
- /* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount). */
- PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast(EmcDvfsTableEntryCount))); /* cmp w?,#64 */
- PATCH_OFFSET(movPtr, asm_set_imm16(*movPtr, static_cast(EmcDvfsTableEntryCount))); /* movz w?,#64 */
- R_SUCCEED();
- }
-
- Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) {
- struct {
- u8 reg;
- u8 val;
- } __attribute__((packed)) cmd;
-
- I2cSession _session;
- R_TRY(i2cOpenSession(&_session, dev));
-
- cmd.reg = reg;
- cmd.val = val;
- Result res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All);
- i2csessionClose(&_session);
- return res;
- }
-
- Result EmcVddqVolt(u32 *ptr) {
- regulator *entry = reinterpret_cast(reinterpret_cast(ptr) - offsetof(regulator, type_2_3.default_uv));
-
- constexpr u32 uv_step = 5'000;
- constexpr u32 uv_min = 250'000;
-
- auto validator = [entry]() {
- R_UNLESS(entry->id == 2, ldr::ResultInvalidRegulatorEntry());
- R_UNLESS(entry->type == 3, ldr::ResultInvalidRegulatorEntry());
- R_UNLESS(entry->type_2_3.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry());
- R_UNLESS(entry->type_2_3.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry());
- R_SUCCEED();
- };
-
- R_TRY(validator());
-
- u32 emc_uv = C.marikoEmcVddqVolt;
-
- if (!emc_uv) {
- R_SKIP();
- }
-
- if (emc_uv % uv_step) {
- emc_uv = (emc_uv + uv_step - 1) / uv_step * uv_step; // rounding
- }
-
- PATCH_OFFSET(ptr, emc_uv);
-
- i2cInitialize();
- Result resultI2C = I2cSet_U8(I2cDevice_Max77812_2, 0x25, (emc_uv - uv_min) / uv_step);
- i2cExit();
-
- R_SUCCEED();
-
- return resultI2C;
- }
-
- Result GetSocSpeedo(u32 &socSpeedo) {
- constexpr u64 FusePhysicalAddress = 0x7000F000;
- u64 virtualAddress = 0;
- constexpr u64 Size = 0x1000;
-
- u64 outSize;
- /* TODO: use svc::QueryMemoryMapping instead. */
- R_TRY(svcQueryMemoryMapping(&virtualAddress, &outSize, FusePhysicalAddress, Size));
-
- constexpr u32 FuseOffset = 2048;
- constexpr u32 SocSpeedoOffset = 308;
- socSpeedo = *reinterpret_cast(virtualAddress + FuseOffset + SocSpeedoOffset);
-
- R_SUCCEED();
- }
-
- u32 GetSocProcessId(u32 socSpeedo) {
- if (socSpeedo <= 1597) {
- return 0;
- }
-
- if (socSpeedo <= 1708) {
- return 1;
- }
-
- /* >= 1709. */
- return 2;
- }
-
- Result SocVoltAsm(u32 *compareSpeedos) {
- constexpr u32 VoltageScanLimit = 10;
- /* Might actually be speedo id. */
- u32 *writeProcessId = ScanAssembly(compareSpeedos, VoltageScanLimit, SocVoltWriteProcessIdAsm, asm_compare_no_rd);
- R_UNLESS(writeProcessId != nullptr, ldr::ResultInvalidSocVoltPattern());
- u8 writeProcessIdRd = asm_get_rd(*writeProcessId);
-
- /* This writes 1050mV. */
- u32 *writeVoltage = ScanAssembly(writeProcessId, VoltageScanLimit, SocVoltWriteVoltageAsm, asm_compare_no_rd);
- R_UNLESS(writeVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
- u8 writeVoltageRd = asm_get_rd(*writeVoltage);
-
- /* A csel instruction is used to select the soc voltage limit register. */
- /* We care about its destination register since that is used for verification. */
- constexpr u32 VoltageSelectScanLimit = 24;
- u32 *selectVoltage = ScanAssembly(writeVoltage, VoltageSelectScanLimit, SocVoltSelectRegisterAsm, AsmCompareCselNoReg);
- R_UNLESS(selectVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
- /* Todo: check rm and rn? */
- u8 selectVoltageRd = asm_get_rd(*selectVoltage);
-
- /* rdCsel is then multiplied by 1000 to convert to uV. */
- /* This is pretty far down the function. */
- constexpr u32 MultiplierScanLimit = 200;
- u32 *multiplier = ScanAssembly(selectVoltage, MultiplierScanLimit, SocVoltMultiplyVoltsAsm, AsmCompareMullNoReg);
- R_UNLESS(multiplier != nullptr, ldr::ResultInvalidSocVoltPattern());
- u8 multiplierRn = AsmGetMullRn(*multiplier);
- u8 multiplierRm = AsmGetMullRm(*multiplier);
- /* One of the two registers has to be rdCsel. */
- R_UNLESS((multiplierRn == selectVoltageRd) || (multiplierRm == selectVoltageRd), ldr::ResultInvalidSocVoltPattern());
- u8 multiplierRd = asm_get_rd(*multiplier);
-
- /* Subs instruction is then used to verify against absolute limit. */
- u32 limitValidationPattern = AsmSubsSetRn(SocVoltValidateLimitAsm, multiplierRd);
- u32 *limitValidation = ScanAssembly(multiplier, VoltageScanLimit, limitValidationPattern, AsmSubsCompareNoReg);
- R_UNLESS(limitValidation != nullptr, ldr::ResultInvalidSocVoltPattern());
-
- /* There is a b.gt instruction right after (checks for socVoltageCap < socVoltageMax). */
- u32 *branchToAbort = limitValidation + 1;
- R_UNLESS(AsmCompareBrConNoImm19(*branchToAbort, SocVoltBranchToAbortAsm), ldr::ResultInvalidSocVoltPattern());
-
- if (!C.marikoSocVmax || C.marikoSocVmax <= 1000) {
- R_SKIP();
- }
-
- /* Adjust 1598 speedo minimum to ensure it always goes down process id 0 branch. */
- /* 2200 should be high enough :D */
- u32 compareSpeedosPatch = AsmSubsSetImm12(*compareSpeedos, 2200);
- PATCH_OFFSET(compareSpeedos, compareSpeedosPatch);
-
- u32 socSpeedo = 0;
- R_TRY(GetSocSpeedo(socSpeedo));
-
- /* Adjust processId from 0 to [process id of switch booting this]. */
- /* We're overwriting the orr instruction entirly. */
- u32 processId = GetSocProcessId(socSpeedo);
- u32 writeProcessIdPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, processId), writeProcessIdRd);
- PATCH_OFFSET(writeProcessId, writeProcessIdPatch);
-
- /* Adjust voltage limit. */
- u32 voltageLimitPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, C.marikoSocVmax), writeVoltageRd);
- PATCH_OFFSET(writeVoltage, voltageLimitPatch);
-
- /* Branches to an abort if limits are invalid -- we patch the branch instruction with NOP. */
- PATCH_OFFSET(branchToAbort, NopIns);
-
- R_SUCCEED();
- }
-
- Result SocVoltLimit(u32 *ptr) {
- R_UNLESS(!std::memcmp(ptr - SocVoltLimitMaxDefaultIndex, socVoltLimitArray, sizeof(socVoltLimitArray)), ldr::ResultInvalidSocVoltLimit());
- if (!C.marikoSocVmax || C.marikoSocVmax <= SocVoltLimitOfficial) {
- R_SKIP();
- }
-
- constexpr u32 Step = 25;
- u32 maxVolt = C.marikoSocVmax;
- if (maxVolt % Step) {
- maxVolt = maxVolt / Step * Step; /* Round. */
- }
-
- u32 volt = SocVoltLimitOfficial;
- for (u32 i = 1; i < DvfsTableEntryCount - SocVoltLimitMaxDefaultIndex && volt < maxVolt; ++i) {
- volt += Step;
- PATCH_OFFSET(ptr + i, volt);
- }
-
- R_SUCCEED();
- }
-
- Result EmcRateSessLimit(u32 *ptr) {
- u32 movzI = 0;
- R_UNLESS(EmcRateSessFindClamp(ptr, nullptr, nullptr, &movzI), ldr::ResultInvalidEmcRateList());
-
- /* Reject cmd11 GetDvfsTable. */
- for (u32 i = 1; i <= 24; ++i) {
- const u32 w = ptr[i];
- if (AsmIsSubX29Imm(w) && AsmGetImm12(w) >= 0x20u) { /* sub x?,x29,#>=0x20 */
- R_THROW(ldr::ResultInvalidEmcRateList());
- }
- }
-
- /* mov x,x2 */
- u32 descReg = 0xFFu;
- for (u32 i = 1; i <= 24; ++i) {
- if (AsmIsMovReg(ptr[i], 2)) { descReg = asm_get_rd(ptr[i]); break; }
- }
- R_UNLESS(descReg != 0xFFu, ldr::ResultInvalidEmcRateList());
-
- /* Repoint the duplicated-imm pair */
- u32 *adds[8]; u32 addImm[8]; u32 nAdds = 0;
- for (u32 i = 1; i <= 24 && nAdds < 8; ++i) {
- const u32 w = ptr[i];
- if (AsmIsAddSpImm(w)) { /* add x?,sp,#imm12 (shift 0) */
- adds[nAdds] = ptr + i;
- addImm[nAdds] = AsmGetImm12(w);
- ++nAdds;
- }
- }
- u32 patched = 0;
- for (u32 a = 0; a < nAdds; ++a) {
- bool dup = false;
- for (u32 b = 0; b < nAdds; ++b) {
- if (a != b && addImm[a] == addImm[b]) { dup = true; break; }
- }
- if (dup) {
- PATCH_OFFSET(adds[a], AsmMakeLdrImm64(asm_get_rd(*adds[a]), descReg, 0)); /* ldr x?,[x] */
- ++patched;
- }
- }
- R_UNLESS(patched == 2, ldr::ResultInvalidEmcRateList());
-
- /* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount) */
- PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast(EmcDvfsTableEntryCount))); /* cmp w?,#64 */
- PATCH_OFFSET(ptr + movzI, asm_set_imm16(*(ptr + movzI), static_cast(EmcDvfsTableEntryCount))); /* movz w?,#64 */
R_SUCCEED();
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
- nsoStart = reinterpret_cast(mapped_nso);
-
- g_pcv_scratch = mapped_nso + nso_size - HocPcvScratchSize;
- g_nso_size = nso_size;
- g_cave_cursor = g_pcv_cave; /* start the .text-cave bump allocator (0 if unavailable) */
+ nsoStart = reinterpret_cast(mapped_nso);
+ g_nso_size = nso_size;
MtcGenerateFreqTables();
@@ -559,6 +95,12 @@ namespace ams::ldr::hoc::pcv::mariko {
{ "Bus Freq Reloc", &BusFreqReloc, 1, &BusFreqRelocPatternFn },
{ "SOC Volt Asm", &SocVoltAsm, 1, &SocVoltPatternFn },
{ "SOC Volt Limit", &SocVoltLimit, 1, nullptr, SocVoltLimitOfficial },
+ { "CPU Lut Max Asm", &CpuLutMaxAsm, 1, &CpuLutAsmPatternFn },
+ { "CPU Lut Max Asm2", &CpuLutMaxAsm2, 1, &CpuLutAsmPatternFn2 },
+ { "CPU Lut Writer", &LutWriterFind, 1, &LutWriterPatternFn },
+ { "CPU Dvco Rate Cfg", &CpuLutDvcoRateCfg, 1, &CpuLutDvcoRateCfgPatternFn },
+ { "Hook Cave", &DeadDestructorCaveFind, 0, &CaveFlagRunPatternFn, 0, 0, true },
+
/* Debugging patches */
#if HOC_UART_LOG
{ "NvLog Redirect", &NvLogUartRedirect, 1, &NvLogVsnprintfPatternFn, 0, 0, true },
@@ -583,6 +125,10 @@ namespace ams::ldr::hoc::pcv::mariko {
CRASH(entry.description);
}
}
+
+ if (R_FAILED(InstallHooks())) {
+ panic::SmcError(panic::Patch);
+ }
}
}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.hpp
index 33b7ed09..d94273ca 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.hpp
@@ -23,408 +23,27 @@
#include "../../oc_common.hpp"
#include "../pcv_common.hpp"
#include "../pcv_asm.hpp"
+#include "../pcv_hook.hpp"
namespace ams::ldr::hoc::pcv::mariko {
- extern u32 *nsoStart;
+ constexpr u32 BusFreqTableCount = 2;
- constexpr cvb_entry_t CpuCvbTableDefault[] = {
- { 204000, { 721589, -12695, 27 }, { } },
- { 306000, { 747134, -14195, 27 }, { } },
- { 408000, { 776324, -15705, 27 }, { } },
- { 510000, { 809160, -17205, 27 }, { } },
- { 612000, { 845641, -18715, 27 }, { } },
- { 714000, { 885768, -20215, 27 }, { } },
- { 816000, { 929540, -21725, 27 }, { } },
- { 918000, { 976958, -23225, 27 }, { } },
- { 1020000, { 1028021, -24725, 27 }, { 1120000 } },
- { 1122000, { 1082730, -26235, 27 }, { 1120000 } },
- { 1224000, { 1141084, -27735, 27 }, { 1120000 } },
- { 1326000, { 1203084, -29245, 27 }, { 1120000 } },
- { 1428000, { 1268729, -30745, 27 }, { 1120000 } },
- { 1581000, { 1374032, -33005, 27 }, { 1120000 } },
- { 1683000, { 1448791, -34505, 27 }, { 1120000 } },
- { 1785000, { 1527196, -36015, 27 }, { 1120000 } },
- { 1887000, { 1609246, -37515, 27 }, { 1120000 } },
- { 1963500, { 1675751, -38635, 27 }, { 1120000 } },
- { },
+ struct HookPayloadData {
+ struct {
+ u32 *next; /* Save the bytes. */
+ uintptr_t originalFnCallback;
+ u32 table[BusFreqTableCount][EmcDvfsTableEntryCount]; /* Original bus table size: 32. */
+ } busData;
+ struct {
+ uintptr_t orig; /* ClDvfsLut64ExpandImpl to CLDVFS LUT writer trampoline. */
+ u32 table[ClDvfsLutSize];
+ } lut64;
+#if HOC_UART_LOG
+ u32 verbosityLevel;
+#endif
};
-
- constexpr u32 CpuClkOfficial = 1963'500;
- constexpr u32 CpuVoltOfficial = 1120;
- constexpr u32 CpuHighVminOfficial = 850;
- constexpr u32 CpuVminOfficial = 620;
- constexpr u32 CpuTune0Low = 0xFFCF;
-
- static const u32 cpuVoltagePatchValues[] = { 850, 38, 1120, 1000, 100, 1000, 0 };
- static const s32 cpuVoltagePatchOffsets[] = { -2, -1, 5, 6, 7, 8, 9 };
- static_assert(sizeof(cpuVoltagePatchValues) == sizeof(cpuVoltagePatchOffsets), "Invalid cpuVoltagePatch size");
-
- static const u32 cpuVoltThermalData[] = { 620, 1120, 20000, 620, 1120, 70000, 950, 1132, 0, 950, 1227, 0 };
-
- static const u32 allowedCpuMaxFrequencies[] = { 1'963'500, 2'091'000, 2'193'000, 2'295'000, 2'397'000, 2'499'000, 2'601'000, 2'703'000, };
-
- constexpr cvb_entry_t GpuCvbTableDefault[] = {
- // GPUB01_NA_CVB_TABLE
- { 76800, {}, { 610000, } },
- { 153600, {}, { 610000, } },
- { 230400, {}, { 610000, } },
- { 307200, {}, { 610000, } },
- { 384000, {}, { 610000, } },
- { 460800, {}, { 610000, } },
- { 537600, {}, { 801688, -10900, -163, 298, -10599, 162, } },
- { 614400, {}, { 824214, -5743, -452, 238, -6325, 81, } },
- { 691200, {}, { 848830, -3903, -552, 119, -4030, -2, } },
- { 768000, {}, { 891575, -4409, -584, 0, -2849, 39, } },
- { 844800, {}, { 940071, -5367, -602, -60, -63, -93, } },
- { 921600, {}, { 986765, -6637, -614, -179, 1905, -13, } },
- { 998400, {}, { 1098475, -13529, -497, -179, 3626, 9, } },
- { 1075200, {}, { 1163644, -12688, -648, 0, 1077, 40, } },
- { 1152000, {}, { 1204812, -9908, -830, 0, 1469, 110, } },
- { 1228800, {}, { 1277303, -11675, -859, 0, 3722, 313, } },
- { 1267200, {}, { 1335531, -12567, -867, 0, 3681, 559, } },
- { },
- };
-
- constexpr u32 GpuClkPllMax = 1300'000'000;
- constexpr u32 GpuClkPllLimit = 2'600'000;
- constexpr u32 GpuVminOfficial = 610;
-
- static const u32 gpuDVFSPattern[] = { 1050, 1000, 100, 1000, 10, };
- static const u32 gpuVoltThermalPattern[] = { 800, 1120, 0, 610, 1120, 20000, 610, 1120, 30000, 610, 1120, 50000, 610, 1120, 70000, 610, 1120, 90000, };
- static_assert(sizeof(gpuVoltThermalPattern) == 72, "Invalid gpuVoltThermalPattern");
-
- /* GPU Max Clock asm Pattern:
- *
- * MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11)
- * sf | opc | | hw | imm16 | Rd
- * #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
- * 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1
- *
- * MOVK W11, #0xE, LSL#16 16 0xE 0xB (11)
- * sf | opc | | hw | imm16 | Rd
- * #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
- * 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1
- */
- inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 };
-
- inline bool GpuMaxClockPatternFn(u32 *ptr32) {
- return asm_compare_no_rd(*ptr32, GpuAsmPattern[0]);
- }
-
- struct DvbEntry {
- u64 freq;
- u32 volt[4] = {};
- };
-
- constexpr DvbEntry EmcDvbTableDefault[] = {
- { 204000, { 637, 637, 637, } },
- { 408000, { 637, 637, 637, } },
- { 800000, { 637, 637, 637, } },
- { 1065600, { 637, 637, 637, } },
- { 1331200, { 650, 637, 637, } },
- { 1600000, { 675, 650, 637, } },
- };
-
- /* Movz */
- /*
- SF | OPC | HW | Imm16 | RD
- 31 | 30 29 28 27 26 25 24 23 | 22 21 | 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 | 4 3 2 1 0
- */
- constexpr u32 SocVoltCompareSpeedoAsm = 0x7118FAFF; /* subs imm, compares to >=1598 max speedo and then goes down process id 1 route. */
- constexpr u32 SocVoltWriteProcessIdAsm = 0x2A1F03F4; /* orr, writes id 0. */
- constexpr u32 SocVoltWriteVoltageAsm = 0x52808358; /* Movz imm, writes 1050mV. */
- constexpr u32 SocVoltSelectRegisterAsm = 0x1A9A3118; /* Csel, selects the voltage -- we need the register of this. */
- constexpr u32 SocVoltMultiplyVoltsAsm = 0x1B1A7F0B; /* Mul, converts from mV -> uV */
- constexpr u32 SocVoltValidateLimitAsm = 0x6B0A017F; /* Subs, checks limits */
- constexpr u32 SocVoltBranchToAbortAsm = 0x540020AC; /* B.ge Branches to abort if limits are invalid. */
-
- ALWAYS_INLINE bool SocVoltPatternFn(u32 *ptr) {
- return asm_compare_no_rd(*ptr, SocVoltCompareSpeedoAsm);
- }
-
- constexpr u32 SocVoltLimitOfficial = 1050;
- constexpr u32 SocVoltLimitMaxDefaultIndex = 17;
- static const u32 socVoltLimitArray[DvfsTableEntryCount] = { 637, 650, 675, 700, 725, 750, 775, 800, 825, 850, 875, 900, 925, 950, 975, 1000, 1025, 1050, };
-
- constexpr u32 EmcListDefault[] = { 204000, 1331200, 1600000, };
- constexpr u32 EmcListSizeDefault = std::size(EmcListDefault);
- constexpr u32 EmcListEndDefault = EmcListSizeDefault - 1;
-
- constexpr u32 EmcClkOSAlt = 1331'200;
- constexpr u32 EmcClkPllmLimit = 2133'000'000;
- constexpr u32 EmcVddqDefault = 600'000;
- constexpr u32 MemVdd2Default = 1100'000;
-
- constexpr u32 MTC_TABLE_REV = 3;
- constexpr u32 MtcTableCountDefault = 3;
-
- constexpr size_t MtcFullTableSize = sizeof(MarikoMtcTable) * MtcTableCountDefault;
- constexpr u32 MtcFullTableCount = 17;
-
- /* These dramids were copied from Hekate -- see /bdk/mem/sdram.h */
- enum DramId : u64 {
- HOAG_4GB_HYNIX_H9HCNNNBKMMLXR_NEE = 3,
- AULA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE = 5,
- IOWA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE = 6,
-
- IOWA_4GB_SAMSUNG_K4U6E3S4AM_MGCJ = 8,
- IOWA_8GB_SAMSUNG_K4UBE3D4AM_MGCJ = 9,
- IOWA_4GB_HYNIX_H9HCNNNBKMMLHR_NME = 10,
- IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTE = 11,
-
- HOAG_4GB_SAMSUNG_K4U6E3S4AM_MGCJ = 12,
- HOAG_8GB_SAMSUNG_K4UBE3D4AM_MGCJ = 13,
- HOAG_4GB_HYNIX_H9HCNNNBKMMLHR_NME = 14,
- HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTE = 15,
-
- IOWA_4GB_SAMSUNG_K4U6E3S4AA_MGCL = 17,
- IOWA_8GB_SAMSUNG_K4UBE3D4AA_MGCL = 18,
- HOAG_4GB_SAMSUNG_K4U6E3S4AA_MGCL = 19,
-
- IOWA_4GB_SAMSUNG_K4U6E3S4AB_MGCL = 20,
- HOAG_4GB_SAMSUNG_K4U6E3S4AB_MGCL = 21,
- AULA_4GB_SAMSUNG_K4U6E3S4AB_MGCL = 22,
-
- HOAG_8GB_SAMSUNG_K4UBE3D4AA_MGCL = 23,
- AULA_4GB_SAMSUNG_K4U6E3S4AA_MGCL = 24,
-
- IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTF = 25,
- HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTF = 26,
- AULA_4GB_MICRON_MT53E512M32D2NP_046_WTF = 27,
-
- AULA_8GB_SAMSUNG_K4UBE3D4AA_MGCL = 28,
-
- IOWA_4GB_HYNIX_H54G46CYRBX267 = 29,
- HOAG_4GB_HYNIX_H54G46CYRBX267 = 30,
- AULA_4GB_HYNIX_H54G46CYRBX267 = 31,
-
- IOWA_4GB_MICRON_MT53E512M32D1NP_046_WTB = 32,
- HOAG_4GB_MICRON_MT53E512M32D1NP_046_WTB = 33,
- AULA_4GB_MICRON_MT53E512M32D1NP_046_WTB = 34,
- };
-
- enum MtcTableIndex {
- T210b0SdevEmcDvfsTableS4gb01 = 0, /* (Unused) Samsung 4Gb */
- T210b0SdevEmcDvfsTableS4gb03 = 1, /* Samsung AM-MGCJ 4Gb */
- T210b0SdevEmcDvfsTableS8gb03 = 2, /* (Unused) Samsung 4Gb */
- T210b0SdevEmcDvfsTableH4gb03 = 3, /* Hynix NME 4Gb */
- T210b0SdevEmcDvfsTableM4gb03 = 4, /* Micron WT:F 4Gb */
- T210b0SdevEmcDvfsTableS4gbY01 = 5, /* (Unused) Samsung 4Gb */
- T210b0SdevEmcDvfsTableS1y4gbY01 = 6, /* (Unused) Samsung 4Gb */
- T210b0SdevEmcDvfsTableS1y8gbY01 = 7, /* (Unused) Samsung 4Gb */
- T210b0SdevEmcDvfsTableS1y4gbX03 = 8, /* Samsung AA-MGCL 4Gb */
- T210b0SdevEmcDvfsTableS1y8gbX03 = 9, /* Samsung AA-MGCL 8Gb */
- T210b0SdevEmcDvfsTableS1y4gb01 = 10, /* (Unused) Samsung 4Gb */
- T210b0SdevEmcDvfsTableM1y4gb01 = 11, /* Micron WT:E 4Gb */
- T210b0SdevEmcDvfsTableH1y4gb01 = 12, /* Hynix NEE 4Gb */
- T210b0SdevEmcDvfsTableS1y8gb04 = 13, /* Samsung AM-MGCJ 8Gb */
- T210b0SdevEmcDvfsTableS1z4gb01 = 14, /* Samsung AB-MGCL 4Gb */
- T210b0SdevEmcDvfsTableH1a4gb01 = 15, /* Hynix x267 4Gb */
- T210b0SdevEmcDvfsTableM1a4gb01 = 16, /* Micron WT:B 8Gb */
- MtcTableIndex_Invalid = 17,
- };
-
- struct MtcDramIndex {
- DramId dramId;
- MtcTableIndex index;
- };
-
- const inline MtcDramIndex mtcIndexTable[] = {
- { HOAG_4GB_HYNIX_H9HCNNNBKMMLXR_NEE, T210b0SdevEmcDvfsTableH1y4gb01, },
- { AULA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE, T210b0SdevEmcDvfsTableH1y4gb01, },
- { IOWA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE, T210b0SdevEmcDvfsTableH1y4gb01, },
- { IOWA_4GB_SAMSUNG_K4U6E3S4AM_MGCJ, T210b0SdevEmcDvfsTableS4gb03, },
- { IOWA_8GB_SAMSUNG_K4UBE3D4AM_MGCJ, T210b0SdevEmcDvfsTableS1y8gb04, },
- { IOWA_4GB_HYNIX_H9HCNNNBKMMLHR_NME, T210b0SdevEmcDvfsTableH4gb03, },
- { IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTE, T210b0SdevEmcDvfsTableM1y4gb01, },
- { HOAG_4GB_SAMSUNG_K4U6E3S4AM_MGCJ, T210b0SdevEmcDvfsTableS4gb03, },
- { HOAG_8GB_SAMSUNG_K4UBE3D4AM_MGCJ, T210b0SdevEmcDvfsTableS1y8gb04, },
- { HOAG_4GB_HYNIX_H9HCNNNBKMMLHR_NME, T210b0SdevEmcDvfsTableH4gb03, },
- { HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTE, T210b0SdevEmcDvfsTableM1y4gb01, },
- { IOWA_4GB_SAMSUNG_K4U6E3S4AA_MGCL, T210b0SdevEmcDvfsTableS1y4gbX03, },
- { IOWA_8GB_SAMSUNG_K4UBE3D4AA_MGCL, T210b0SdevEmcDvfsTableS1y8gbX03, },
- { HOAG_4GB_SAMSUNG_K4U6E3S4AA_MGCL, T210b0SdevEmcDvfsTableS1y4gbX03, },
- { IOWA_4GB_SAMSUNG_K4U6E3S4AB_MGCL, T210b0SdevEmcDvfsTableS1z4gb01, },
- { HOAG_4GB_SAMSUNG_K4U6E3S4AB_MGCL, T210b0SdevEmcDvfsTableS1y8gb04, },
- { AULA_4GB_SAMSUNG_K4U6E3S4AB_MGCL, T210b0SdevEmcDvfsTableS1y8gb04, },
- { HOAG_8GB_SAMSUNG_K4UBE3D4AA_MGCL, T210b0SdevEmcDvfsTableS1y8gbX03, },
- { AULA_4GB_SAMSUNG_K4U6E3S4AA_MGCL, T210b0SdevEmcDvfsTableS1y4gbX03, },
- { IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTF, T210b0SdevEmcDvfsTableM4gb03, },
- { HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTF, T210b0SdevEmcDvfsTableM4gb03, },
- { AULA_4GB_MICRON_MT53E512M32D2NP_046_WTF, T210b0SdevEmcDvfsTableM4gb03, },
- { AULA_8GB_SAMSUNG_K4UBE3D4AA_MGCL, T210b0SdevEmcDvfsTableS1y8gbX03, },
- { IOWA_4GB_HYNIX_H54G46CYRBX267, T210b0SdevEmcDvfsTableH1a4gb01, },
- { HOAG_4GB_HYNIX_H54G46CYRBX267, T210b0SdevEmcDvfsTableH1a4gb01, },
- { AULA_4GB_HYNIX_H54G46CYRBX267, T210b0SdevEmcDvfsTableH1a4gb01, },
- { IOWA_4GB_MICRON_MT53E512M32D1NP_046_WTB, T210b0SdevEmcDvfsTableM1a4gb01, },
- { HOAG_4GB_MICRON_MT53E512M32D1NP_046_WTB, T210b0SdevEmcDvfsTableM1a4gb01, },
- { AULA_4GB_MICRON_MT53E512M32D1NP_046_WTB, T210b0SdevEmcDvfsTableM1a4gb01, },
- };
-
- /*
- 710006abfc 40 01 1f d6 br x10
- */
-
- /*
- 710006ac28 a0 03 00 90 adrp x0,0x71000de000
- 710006ac2c 00 80 16 91 add x0=>SdevEmcDvfsTableS4gb01,x0,#0x5a0
- */
-
- /* Br */
- /*
- | Z | OP | Fixed | A | M | RN | RM
- 31 30 29 28 27 26 25 | 24 23 | 22 | 21 20 19 18 17 16 15 14 13 12 |11 | 10 | 9 8 7 6 5 | 4 3 2 1 0
- 1 1 0 1 0 1 1 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 Rn 0 0 0 0 0
- Z op A M Rm
- */
-
- /* Adrp */
- /*
- OP | ImmLow | | ImmHigh | RD
- 31 | 30 29 | 28 27 26 25 24 | 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 | 4 3 2 1 0
- */
-
- /* ADD (immediate) */
- /*
- SF | OP | S | Fixed value | Sh | Imm12 | RN | RD
- 31 | 30 | 29 | 28 27 26 25 24 23 | 22 | 21 20 19 18 17 16 15 14 13 12 11 10 | 9 8 7 6 5 | 4 3 2 1 0
- */
-
- constexpr u32 MtcBrAsm = 0xD61F0140;
- constexpr u32 MtcMovAsm = 0x52800068;
- constexpr u32 MtcAdrpAsm = 0x900003A0;
- constexpr u32 MtcAddAsm = 0x91168000;
-
- ALWAYS_INLINE bool MemMtcGetGetTablePatternFn(u32 *ptr) {
- /* This builds an address that gets returned, so the register must be x0 by convention. */
- return AsmCompareAddNoImm12(*ptr, MtcAddAsm);
- }
-
- constexpr u32 EmcCountCmpAsm = 0x7100851F; /* cmp w?,#0x21 (subs wzr,w?,#0x21) */
-
- /*
- str ,[,#0x120] ; volt-array pointer
- str w?, [,#0x154] ; num_freqs
- */
- constexpr u32 EmcSocLutPtrStoreAsm = 0xF9009009; /* str x?,[x0,#0x120] (anchor) */
- constexpr u32 EmcSocLutCountStoreAsm = 0xB9015408; /* str w?,[x0,#0x154] (anchor) */
- constexpr u32 EmcSocFreqStoreAsm = 0xF9000D00; /* str x?,[x8,#0x18] */
- constexpr u32 EmcSocVoltStoreAsm = 0xB9004900; /* str w?,[x8,#0x48] (socMinLut[i]) */
- constexpr u32 EmcSocReadLoadAsm = 0xB9404929; /* ldr w?,[x9,#0x48] (socMinLut readback) */
-
- inline bool EmcDvfsCountPatternFn(u32 *ptr) {
- /* Local context: cbz w?, ; cmp w?,#0x21 ; b.cs */
- return asm_compare_no_rd(*ptr, EmcCountCmpAsm) && AsmCompareBrConNoImm19(*(ptr + 1), 0x54000002) /* b.cs */
- && AsmCbzCompareOpcodeOnly(*(ptr - 1), 0x34000000); /* cbz */
- }
-
- inline bool EmcSocLutPatternFn(u32 *ptr) {
- return asm_compare_no_rd(*ptr, EmcSocLutPtrStoreAsm) /* str x?,[x0,#0x120] */
- && asm_compare_no_rd(*(ptr + 1), EmcSocLutCountStoreAsm); /* str w?,[x0,#0x154] */
- }
-
- /*
- mov w?,#0x20 ; the 32 cap
- cmp w?,#0x20
- csel w?,w?,w?,lt ; w? = min(maxCount, 32)
- bl TegraGetEmcDvfsFreqTable
-
- cmp w?,#0x20 ; (maxCount)
- csel w?,,,lt ; min(maxCount, 32)
- bl
- */
- constexpr u32 EmcRateCapCmpAsm = 0x710082FF; /* cmp w?,#0x20 */
- constexpr u32 EmcRateCapCselAsm = 0x1A80B000; /* csel w?,w?,w?,lt */
-
- inline bool EmcRateListPatternFn(u32 *ptr) {
- return AsmSubsCompareNoReg(*ptr, EmcRateCapCmpAsm) /* cmp w?,#0x20 */
- && AsmCompareCselNoReg(*(ptr + 1), EmcRateCapCselAsm) /* csel w?,w?,w?,lt */
- && (AsmGetRn(*ptr) == AsmGetRn(*(ptr + 1))) /* min(reg, 0x20) */
- && AsmBlCompareOpcodeOnly(*(ptr + 2), 0x94000000); /* bl */
- }
-
- constexpr u32 EmcRateSessCmpAsm = 0x710082FF; /* cmp w?,#0x20 */
- constexpr u32 EmcRateSessMovAsm = 0x52800400; /* movz w?,#0x20 */
- constexpr u32 EmcRateSessCselAsm = 0x1A80B000; /* csel w?,w?,w?,lt (opcode + cond) */
-
- inline bool EmcRateSessFindClamp(u32 *ptr, u32 *out_c, u32 *out_cap, u32 *out_movz_i) {
- if (!AsmSubsCompareNoReg(ptr[0], EmcRateSessCmpAsm)) return false; /* cmp w,#0x20 */
- const u32 c = AsmGetRn(ptr[0]);
- for (u32 i = 1; i <= 14; ++i) {
- const u32 w = ptr[i];
- if (AsmCompareCselNoReg(w, EmcRateSessCselAsm) && AsmGetRn(w) == c && asm_get_rd(w) == c) {
- const u32 cap = AsmGetRm(w);
- for (u32 j = 1; j < i; ++j) {
- if ((ptr[j] & 0xFFFFFFE0u) == EmcRateSessMovAsm && asm_get_rd(ptr[j]) == cap) {
- if (out_c) *out_c = c;
- if (out_cap) *out_cap = cap;
- if (out_movz_i) *out_movz_i = j;
- return true;
- }
- }
- return false;
- }
- }
- return false;
- }
-
- inline bool EmcRateSessPatternFn(u32 *ptr) {
- return EmcRateSessFindClamp(ptr, nullptr, nullptr, nullptr);
- }
-
- inline bool BusFreqRelocPatternFn(u32 *ptr) {
- if (g_pcv_scratch == 0 || g_pcv_cave == 0) {
- return false;
- }
- if (reinterpret_cast(ptr + 4) > g_pcv_cave) { /* the call site lives in .text */
- return false;
- }
- if (!(AsmIsLdrImm64(ptr[0]) && AsmGetLdStImm64Off(ptr[0]) == 0x10)) return false; /* ldr Xbuf,[Xbus,#0x10] */
- if (!(AsmIsAddImm64(ptr[1]) && AsmGetImm12(ptr[1]) == 0x18)) return false; /* add Xcnt,Xbus,#0x18 */
- if (!(AsmIsStrImm64(ptr[2]) && AsmGetLdStImm64Off(ptr[2]) == 0x50)) return false; /* str Xrail,[Xbus,#0x50]*/
- if (!AsmIsBl(ptr[3])) return false; /* bl GetDvfsRailUnique */
- const u32 bus = AsmGetRn(ptr[0]);
- return AsmGetRn(ptr[1]) == bus && AsmGetRn(ptr[2]) == bus;
- }
-
- inline bool ForceVerbosityPatternFn(u32 *ptr) {
- if (HOC_PCV_FORCE_VERBOSITY == 0 || g_pcv_cave == 0) {
- return false;
- }
- if (reinterpret_cast(ptr + 11) > g_pcv_cave) { /* .text only */
- return false;
- }
- if (ptr[0] != 0xA9BE7BFDu || ptr[1] != 0xF9000BF3u || ptr[2] != 0x910003FDu) return false; /* stp/str/mov x29,sp */
- if (!(AsmIsAddImm64(ptr[3]) && asm_get_rd(ptr[3]) == 0 && AsmGetRn(ptr[3]) == 29)) return false; /* add x0,x29,#imm */
- if (!(AsmIsAddImm64(ptr[4]) && asm_get_rd(ptr[4]) == 19 && AsmGetRn(ptr[4]) == 29)) return false; /* add x19,x29,#imm */
- if (AsmGetImm12(ptr[3]) != AsmGetImm12(ptr[4]) || !AsmIsBl(ptr[5])) return false;
- for (u32 j = 6; j <= 10; ++j) {
- if (ptr[j] == 0x7100001Fu) { /* cmp w0,#0 */
- return true;
- }
- }
- return false;
- }
-
- /* vsnprintf(buf,size,fmt,va_list) prologue */
- inline constexpr u32 NvLogVsnSig[] = { 0xD10483FFu, 0xA9107BFDu, 0xF9008BFCu, 0x910403FDu, 0xF100003Fu };
-
- inline bool NvLogVsnprintfPatternFn(u32 *ptr) {
- if (HOC_UART_LOG == 0 || g_pcv_cave == 0) {
- return false;
- }
- if (reinterpret_cast(ptr + std::size(NvLogVsnSig)) > g_pcv_cave) { /* must sit in .text */
- return false;
- }
- for (size_t k = 0; k < std::size(NvLogVsnSig); ++k) {
- if (ptr[k] != NvLogVsnSig[k]) {
- return false;
- }
- }
- return true;
- }
-
+ DECLARE_HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData);
void Patch(uintptr_t mapped_nso, size_t nso_size);
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cave.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cave.cpp
new file mode 100644
index 00000000..e468d64b
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cave.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
+ *
+ * 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 .
+ */
+
+#include "pcv_mariko_cave.hpp"
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ namespace {
+
+ struct {
+ u32 *site = nullptr;
+ u32 *end = nullptr;
+ } caveCache;
+
+ bool HasDirectCaller(const u32 *target, const u32 *begin, const u32 *end) {
+ for (const u32 *ptr = begin; ptr < end; ++ptr) {
+ const u32 top6 = *ptr & 0xFC000000u;
+ if (top6 != _asm::op::B && top6 != _asm::op::Bl) {
+ continue;
+ }
+
+ if (_asm::Target26(*ptr, reinterpret_cast(ptr)) == reinterpret_cast(target)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ }
+
+ Result DeadDestructorCaveFind(u32 *ptr) {
+ u32 *textEnd = reinterpret_cast(g_pcv_cave);
+
+ u32 *site = _asm::FindFnPrologue(ptr, CavePrologueMargin, nsoStart);
+ R_UNLESS(site != nullptr, ldr::ResultInvalidHookCave());
+
+ u32 *end = _asm::FindFnEnd(site, textEnd);
+ R_UNLESS(end != nullptr, ldr::ResultInvalidHookCave());
+
+ /* Ensure this function is never called. */
+ R_UNLESS(!HasDirectCaller(site, nsoStart, textEnd), ldr::ResultInvalidHookCave());
+
+ caveCache.site = site;
+ caveCache.end = end;
+ R_SUCCEED();
+ }
+
+ Result CaveInstall() {
+ if (Hooks().CaveFree() >= HookContext::RequiredCaveSize()) {
+ R_SUCCEED();
+ }
+
+ R_UNLESS(caveCache.site != nullptr, ldr::ResultInvalidHookCave());
+
+ const uintptr_t start = util::AlignUp(reinterpret_cast(caveCache.site + 1), sizeof(u64));
+ const uintptr_t end = reinterpret_cast(caveCache.end);
+ R_UNLESS(start < end && end - start >= HookContext::RequiredCaveSize(), ldr::ResultHookPayloadTooLarge());
+
+ /* Insert a break in case this function gets called for whatever reason. */
+ *caveCache.site = _asm::BrkIns;
+
+ Hooks().SetCave(start, end - start);
+ R_SUCCEED();
+ }
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cave.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cave.hpp
new file mode 100644
index 00000000..7d177b49
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cave.hpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
+ *
+ * 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 .
+ */
+
+#pragma once
+
+#include "../pcv.hpp"
+#include "../pcv_asm.hpp"
+#include "../pcv_hook.hpp"
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ extern u32 *nsoStart;
+
+ constexpr u32 CaveFlagChunkCount = 4;
+ constexpr u32 CaveFlagChunkInsCount = 4;
+ constexpr u32 CaveFlagLdrbDelta = 0x30;
+ constexpr u32 CavePrologueMargin = 8;
+
+ /* This function consists of multiple chunks. */
+ /* Each chunk does a load, a comparison and a conditional branch. The next chunk's address setup (adrp) follows right after it. */
+ inline bool IsFlagCheckChunk(const u32 *ptr) {
+ return _asm::IsOp(ptr[0], _asm::op::LdrbImm, _asm::field::Rt, _asm::field::Rn, _asm::field::Off1)
+ && _asm::Ignoring(ptr[1], _asm::CmpImm32(0, 1), _asm::field::Rn)
+ && _asm::Ignoring(ptr[2], _asm::Encode(_asm::op::BCond, {_asm::field::BCond, _asm::cond::Eq}), _asm::field::Imm19);
+ }
+
+ inline bool IsChunkDeltaValid(const u32 *prev, const u32 *next) {
+ return IsFlagCheckChunk(next) && _asm::Get(*next, _asm::field::Off1) == _asm::Get(*prev, _asm::field::Off1) + CaveFlagLdrbDelta;
+ }
+
+ inline bool ValidateChunkConsistency(const u32 *first) {
+ for (u32 i = 1; i < CaveFlagChunkCount; ++i) {
+ if (!IsChunkDeltaValid(first + (i - 1) * CaveFlagChunkInsCount, first + i * CaveFlagChunkInsCount)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ inline bool CaveFlagRunPatternFn(u32 *ptr) {
+ if (Hooks().CaveFree() >= HookContext::RequiredCaveSize()) {
+ return false;
+ }
+ /* In case we don't have enough cave memory, we need to commit this warcrime. */
+
+ /* Ensure we don't dereference invalid memory. */
+ u32 *textEnd = reinterpret_cast(g_pcv_cave);
+ if (ptr - CaveFlagChunkInsCount < nsoStart || ptr + CaveFlagChunkCount * CaveFlagChunkInsCount > textEnd) {
+ return false;
+ }
+
+ /* Validate the current chunk and the previous chunk and ensure the delta of the ldrb instructions is consistent. */
+ if (!IsFlagCheckChunk(ptr) || IsChunkDeltaValid(ptr - CaveFlagChunkInsCount, ptr)) {
+ return false;
+ }
+
+ return ValidateChunkConsistency(ptr);
+ }
+
+ Result DeadDestructorCaveFind(u32 *ptr);
+ Result CaveInstall();
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.cpp
index 01400c9d..d106668a 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.cpp
@@ -5,7 +5,7 @@
*
* Copyright (c) B3711
*
- * Copyright (c) Souldbminer and Horizon OC Contributors
+ * Copyright (c) Souldbminer, Lightos and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
@@ -21,20 +21,187 @@
*/
#include "../pcv.hpp"
+#include "pcv_mariko_cpu.hpp"
namespace ams::ldr::hoc::pcv::mariko {
- u32 CapCpuClock() {
- u32 cpuCap = allowedCpuMaxFrequencies[0];
+ namespace {
+ u32 CapCpuClock() {
+ u32 cpuCap = allowedCpuMaxFrequencies[0];
- for (u32 freq : allowedCpuMaxFrequencies) {
- if (C.marikoCpuMaxClock >= freq) {
- cpuCap = freq;
- } else {
- break;
+ for (u32 freq : allowedCpuMaxFrequencies) {
+ if (C.marikoCpuMaxClock >= freq) {
+ cpuCap = freq;
+ } else {
+ break;
+ }
+ }
+ return cpuCap;
+ }
+
+ /* TODO: Reduce code duplication. */
+ cvb_entry_t *GetDvfsTable() {
+ switch (C.tableConf) {
+ case TBREAK_1683: return const_cast(C.marikoCpuDvfsTable1683Tbreak);
+ case TBREAK_1581: return const_cast(C.marikoCpuDvfsTable1581Tbreak);
+ case EXTREME_TABLE: return const_cast(C.marikoCpuDvfsTableExtreme);
+ case DEFAULT_TABLE:
+ default:
+ return const_cast(mariko::CpuCvbTableDefault);
}
}
- return cpuCap;
+
+ /* Todo: Reduce code duplication of this. */
+ Result GetCpuSpeedo(u32 &cpuSpeedo) {
+ constexpr u64 FusePhysicalAddress = 0x7000F000;
+ u64 virtualAddress = 0;
+ constexpr u64 Size = 0x1000;
+
+ u64 outSize;
+ /* TODO: use svc::QueryMemoryMapping instead. */
+ R_TRY(svcQueryMemoryMapping(&virtualAddress, &outSize, FusePhysicalAddress, Size));
+
+ constexpr u32 FuseOffset = 2048;
+ constexpr u32 CpuSpeedoOffset = 0x114;
+ cpuSpeedo = *reinterpret_cast(virtualAddress + FuseOffset + CpuSpeedoOffset);
+
+ R_SUCCEED();
+ }
+
+ u32 CalculateCvbVoltage(cvb_entry_t *entry, u32 speedo, u32 speedoScale) {
+ float speedDiv = static_cast(speedo) / speedoScale;
+ return ((entry->cvb_dfll_param.c2 * speedDiv + entry->cvb_dfll_param.c1) * speedDiv + entry->cvb_dfll_param.c0);
+ }
+
+ u32 DivRoundUp(u32 n, u32 d) {
+ return (n + d - 1) / d;
+ }
+
+ u32 RoundCvbVoltage(u32 mv, u32 vScale, u32 stepUv) {
+ u32 step = (stepUv ? stepUv : 1000) * vScale;
+
+ u32 uv = mv * 1000;
+ uv = DivRoundUp(uv, step) * stepUv;
+ return uv / 1000;
+ }
+
+ u32 EffVmin() { return C.marikoCpuLowVmin ? C.marikoCpuLowVmin : CpuVminOfficial; }
+ u32 EffVmax() { return C.marikoCpuMaxVolt ? C.marikoCpuMaxVolt : CpuVoltOfficial; }
+
+ u32 ClampVoltage(u32 mv) {
+ return std::clamp(mv, EffVmin(), EffVmax());
+ }
+
+ constexpr u32 PmicBaseMv = 250;
+ constexpr u32 PmicStepMv = 5;
+ constexpr u32 PmicVoltCount = 256;
+
+ u32 GetExactVoltCode(u32 mv) {
+ u32 alignMult = mv / PmicStepMv;
+
+ for (u32 i = 0; i < PmicVoltCount; ++i) {
+ u32 regMult = (PmicBaseMv + i * PmicStepMv) / PmicStepMv;
+ if (alignMult == regMult) {
+ return i;
+ }
+ }
+
+ return UINT32_MAX;
+ }
+
+ u32 GetVoltCode(u32 mv) {
+ u32 alignMult = DivRoundUp(mv, PmicStepMv);
+
+ for (u32 i = 0; i < PmicVoltCount; i++) {
+ u32 regMult = (PmicBaseMv + i * PmicStepMv) / PmicStepMv;
+ if (alignMult <= regMult) {
+ return i;
+ }
+ }
+
+ return UINT32_MAX;
+ }
+
+ Result BuildLut(u32 *lut, const u32 *voltArray) {
+ const u32 vMin = EffVmin();
+ const u32 vMax = *std::max_element(voltArray, voltArray + ClDvfsLutSize);
+ R_UNLESS(vMin <= vMax, ldr::ResultInvalidCpuMinVolt());
+
+ u32 voltage = vMin;
+ u32 code = GetExactVoltCode(voltage);
+ R_UNLESS(code != UINT32_MAX, ldr::ResultInvalidVoltCode());
+ lut[0] = code;
+
+ u32 j = 1;
+ for (u32 i = 0; i < ClDvfsLutSize; ++i) {
+ for (;;) {
+ R_UNLESS(j < ClDvfsLutSize, ldr::ResultInvalidVoltLutIndex());
+
+ voltage += std::max(1u, (vMax - voltage) / (ClDvfsLutSize - j));
+ if (voltage >= voltArray[i]) {
+ break;
+ }
+
+ code = GetVoltCode(voltage);
+ R_UNLESS(code != UINT32_MAX, ldr::ResultInvalidVoltCode());
+ if (code != lut[j - 1]) {
+ lut[j++] = code;
+ }
+ }
+
+ voltage = (j == ClDvfsLutSize - 1) ? vMax : voltArray[i];
+ code = GetExactVoltCode(voltage);
+ R_UNLESS(code != UINT32_MAX, ldr::ResultInvalidVoltCode());
+
+ if (code != lut[j - 1]) {
+ R_UNLESS(j < ClDvfsLutSize, ldr::ResultInvalidVoltLutIndex());
+ lut[j++] = code;
+ }
+
+ if (voltage >= vMax) {
+ break;
+ }
+ }
+
+ for (u32 k = j; k < ClDvfsLutSize; ++k) {
+ lut[k] = lut[j - 1];
+ }
+
+ R_SUCCEED();
+ }
+
+ /* Since doing the proper lut creation in pcv is wasteful, we'll do it ourselves */
+ Result FillLutTable(HookPayloadData *data) {
+ /* Step 1: Get the speedo. */
+ u32 cpuSpeedo = 0;
+ R_TRY(GetCpuSpeedo(cpuSpeedo));
+
+ /* Step 2: Select the appropriate table. */
+ cvb_entry_t *table = GetDvfsTable();
+ constexpr u32 SpeedoScale = 100;
+ constexpr u32 VoltageScale = 1000;
+ constexpr u32 StepUv = 5000;
+
+ /* Step 3: Fill the voltage array. */
+ u32 voltArray[ClDvfsLutSize] = {};
+ for (u32 i = 0; i < DvfsTableEntryLimit; ++i) {
+ u32 voltage = CalculateCvbVoltage(&table[i], cpuSpeedo, SpeedoScale);
+ voltage = RoundCvbVoltage(voltage, VoltageScale, StepUv);
+ voltage = ClampVoltage(voltage);
+
+ voltArray[i] = voltage;
+ }
+
+ /* Step 4: Build the lut table. */
+ u32 lut[ClDvfsLutSize] = {};
+ R_TRY(BuildLut(lut, voltArray));
+
+ /* Step 5: Copy it. */
+ std::memcpy(data->lut64.table, lut, sizeof(lut));
+
+ /* Step 6: Finally done. */
+ R_SUCCEED();
+ }
}
Result CpuFreqVdd(u32 *ptr) {
@@ -227,6 +394,10 @@ namespace ams::ldr::hoc::pcv::mariko {
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff);
break;
+ case 13:
+ PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27FF7FF);
+ break;
default:
break;
}
@@ -234,4 +405,213 @@ namespace ams::ldr::hoc::pcv::mariko {
R_SUCCEED();
}
+ /* Little helper for ORR:LSL pairs which are found in here */
+ inline bool IsOrrLsl(u32 w) {
+ if (!_asm::IsOp(w, _asm::op::OrrShifted32, _asm::field::Rd, _asm::field::Rn, _asm::field::Rm, _asm::field::Shift, _asm::field::Imm6))
+ return false;
+ return _asm::Get(w, _asm::field::Shift) == 0; /* 0=LSL, 1=LSR, 2=ASR, 3=ROR */
+ }
+
+ /* Probably overcomplicated but I wanted to have a rock-solid way to do this regardless of FW version */
+ Result CpuLutMaxAsm(u32* ptr) {
+ /* Check for the dual ORR-LSL pairs*/
+ R_UNLESS(IsOrrLsl(ptr[2]), ldr::ResultInvalidCpuLutMaxAsmPattern());
+ R_UNLESS(IsOrrLsl(ptr[3]), ldr::ResultInvalidCpuLutMaxAsmPattern());
+ /* We can safely assume that we are in the right place now */
+
+ /* Safety check */
+ R_UNLESS(ptr - 10 >= nsoStart, ldr::ResultInvalidCpuLutMaxAsmPattern());
+
+ /* Search for the STRB */
+ u32* strb = _asm::ScanAssembly(ptr - 10, 10, _asm::Encode(_asm::op::StrbImm,
+ {_asm::field::Rt, 0}, {_asm::field::Rn, 0}, {_asm::field::Off1, 0x25D}), _asm::field::Rt,
+ _asm::field::Rn);
+ R_UNLESS(strb != nullptr, ldr::ResultInvalidCpuLutMaxAsmPattern());
+
+ /* Probably a cleaner way to do this?*/
+ u32 *prod = strb + 2;
+ const u32 rd = _asm::Get(*prod, _asm::field::Rd);
+ if (_asm::IsOp(*prod, _asm::op::Ubfm32, _asm::field::Rd, _asm::field::Rn, _asm::field::Immr, _asm::field::Imm6)) {
+ /* replacement must carry LSL#16 to make up for ubfiz's remoal */
+ const u32 nw = _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, rd}, {_asm::field::Imm16, 0x3F}, {_asm::field::Hw, 1});
+ PATCH_OFFSET(prod, nw); /* movz wD,#0x3F,LSL#16 */
+ } else {
+ /* Imm12 is 7 for 0xff*/
+ R_UNLESS(_asm::IsOp(*prod, _asm::op::AndImm32, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12) && _asm::Get(*prod, _asm::field::Imm12) == 7, ldr::ResultInvalidCpuLutMaxAsmPattern());
+ /* No need to do the lsl */
+ const u32 nw = _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, rd}, {_asm::field::Imm16, 0x3F});
+ PATCH_OFFSET(prod, nw); /* movz wD,#0x3F */
+ }
+
+ /* Set SAFE to entry 32 */
+ PATCH_OFFSET(ptr + 1, _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, _asm::Get(ptr[1], _asm::field::Rd)}, {_asm::field::Imm16, 32}));
+
+ R_SUCCEED();
+ }
+
+ namespace {
+ struct {
+ u32 *site = nullptr; /* LUT writer prologue. */
+ } lutWriterCache;
+ }
+
+ HOOK_PAYLOAD_FN void CpuLutWriterExpandImpl(u64 param) {
+ HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData);
+ reinterpret_cast(data->lut64.orig)(param);
+
+ /* *(param+8) = CLDVFS mapping */
+ const u64 base = *reinterpret_cast(param + 8);
+ u32 *lut = reinterpret_cast(base + 0x200);
+
+ for (u32 i = 0; i < ClDvfsLutSize; ++i) {
+ lut[i] = data->lut64.table[i];
+ }
+ __asm__ volatile("dsb st" ::: "memory");
+ }
+
+ Result LutWriterFind(u32 *ptr) {
+ R_UNLESS(LutWriterPatternFn(ptr), ldr::ResultInvalidCpuLutHook());
+
+ u32 *prog = _asm::FindFnPrologue(ptr, 140, nsoStart);
+ R_UNLESS(prog != nullptr, ldr::ResultInvalidCpuLutHook());
+
+ /* Prolouge must hold MMIO base, so obtain it. */
+ bool ok = false;
+ for (u32 i = 0; i < 8; ++i) {
+ if (_asm::IsOp(prog[i], _asm::op::LdrImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Off8)
+ && _asm::Get(prog[i], _asm::field::Rn) == 0
+ && _asm::Get(prog[i], _asm::field::Off8) == 0x8) {
+ ok = true;
+ break;
+ }
+ }
+
+ R_UNLESS(ok, ldr::ResultInvalidCpuLutHook());
+ lutWriterCache.site = prog;
+
+ R_SUCCEED();
+ }
+
+ Result LutWriterInstallHooks(HookPayloadData *data) {
+ R_UNLESS(lutWriterCache.site != nullptr, ldr::ResultInvalidCpuLutHook());
+
+ R_TRY(FillLutTable(data));
+
+ uintptr_t orig = 0;
+ R_TRY(INSTALL_IMPL_HOOK_ORIG(lutWriterCache.site, CpuLutWriterExpandImpl, &orig));
+ data->lut64.orig = orig;
+
+ R_SUCCEED();
+ }
+
+ Result CpuLutMaxAsm2(u32* ptr) {
+ /* movk w?,#0xFFC0,LSL#16 */
+ R_UNLESS(_asm::IsOp(ptr[1], _asm::op::MovkW, _asm::field::Rd, _asm::field::Imm16, _asm::field::Hw)
+ && _asm::Get(ptr[1], _asm::field::Imm16) == 0xFFC0
+ && _asm::Get(ptr[1], _asm::field::Hw) == 1, ldr::ResultInvalidCpuLutMaxAsmPattern());
+
+ /* and x, x, #0xff */
+ R_UNLESS(_asm::IsOp(ptr[2], _asm::op::AndImm32, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12) ||
+ _asm::Get(ptr[2], _asm::field::Imm12) != 7, ldr::ResultInvalidCpuLutMaxAsmPattern());
+
+ const u32 rd = _asm::Get(ptr[2], _asm::field::Rd);
+ PATCH_OFFSET(ptr + 2, _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, rd}, {_asm::field::Imm16, 0x3F})); /* movz wD,#0x3F */
+
+ R_SUCCEED();
+ }
+
+
+ /* FW 20.4.0
+ 71000571dc 01 01 00 d0 adrp param_2=>aCldvfssetdvcor,aSSRequestedDKh+0x1f = "ClDvfsSetDvcoRateMin"
+ = " %s divider %#x diff %d\n"
+ 71000571e0 21 40 27 91 add param_2=>aCldvfssetdvcor,param_2,#0x9d0 = "ClDvfsSetDvcoRateMin"
+ 71000571e4 c3 9f ff 97 bl nn::pcv::NvLog undefined NvLog(char * fmt, ...)
+ loc_71000571E8 XREF[1]: 71000571b4(j)
+ 71000571e8 69 aa 42 a9 ldp x9,x10,[x19, #0x28]
+ 71000571ec 4a fd 41 d3 lsr x10,x10,#0x1
+ 71000571f0 68 26 41 f9 ldr x8,[x19, #0x248] Anchor
+ 71000571f4 0b 0d 0a cb sub x11,x8,x10, LSL #0x3 Target
+ 71000571f8 6b 4e 01 f9 str x11,[x19, #0x298] Verify1
+ 71000571fc 2c 0d 40 f9 ldr x12,[x9, #0x18] Verify2
+ 7100057200 7f 01 0c eb cmp x11,x12
+ 7100057204 6b 81 8c 9a csel x11,x11,x12,hi
+ 7100057208 6b 4e 01 f9 str x11,[x19, #0x298]
+ 710005720c 2b c9 40 f9 ldr x11,[x9, #0x190]
+ 7100057210 6b 52 01 f9 str x11,[x19, #0x2a0]
+ 7100057214 8b 00 00 b4 cbz x11,loc_7100057224
+ 7100057218 f4 4f 41 a9 ldp x20,x19,[sp, #local_10]
+ 710005721c fd 7b c2 a8 ldp x29=>local_20,x30,[sp], #0x20
+ 7100057220 c0 03 5f d6 ret
+ loc_7100057224 XREF[1]: 7100057214(j)
+ 7100057224 08 15 0a 8b add x8,x8,x10, LSL #0x5 Target
+ 7100057228 2a 55 81 b9 ldrsw x10,[x9, #0x154] Anchor
+ 710005722c 68 52 01 f9 str x8,[x19, #0x2a0] Verify1
+
+ */
+
+ Result CpuLutDvcoRateCfg(u32* ptr) {
+ /* Anchor via scanning to prevent ordering issues */
+ u32 *sub = _asm::ScanAssembly(ptr + 1, 4,
+ _asm::Encode(_asm::op::SubShifted64, {_asm::field::Rd, 0}, {_asm::field::Rn, 0}, {_asm::field::Rm, 0},
+ {_asm::field::Shift, 0}, {_asm::field::Imm6, 3}),
+ _asm::field::Rd, _asm::field::Rn, _asm::field::Rm);
+ R_UNLESS(sub != nullptr, ldr::ResultInvalidDvcoRateConfig());
+
+ /* Verify */
+ R_UNLESS(_asm::IsOp(sub[1], _asm::op::StrImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Off8)
+ && _asm::Get(sub[1], _asm::field::Off8) == 0x298, ldr::ResultInvalidDvcoRateConfig());
+
+ /* 22.x uses pre-index ldr x?,[x?,#0x18]! here (0xF8418D6D class). */
+ const bool isLdr18 = _asm::IsOp(sub[2], _asm::op::LdrImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Off8)
+ && _asm::Get(sub[2], _asm::field::Off8) == 0x18;
+ const bool isLdrPre18 = _asm::IsOp(sub[2], _asm::op::LdrPreImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Imm9)
+ && _asm::Get(sub[2], _asm::field::Imm9) == 0x18;
+ R_UNLESS(isLdr18 || isLdrPre18, ldr::ResultInvalidDvcoRateConfig());
+
+ /* Patch the target */
+ PATCH_OFFSET(sub, _asm::Encode(_asm::op::SubShifted64,
+ {_asm::field::Rd, _asm::Get(*sub, _asm::field::Rd)},
+ {_asm::field::Rn, _asm::Get(*sub, _asm::field::Rn)},
+ {_asm::field::Rm, _asm::Get(*sub, _asm::field::Rm)},
+ {_asm::field::Shift, 0}, {_asm::field::Imm6, 4}));
+
+ /* Search for the second anchor */
+ u32* ldrsw = _asm::ScanAssembly(sub, 20,
+ _asm::Encode(_asm::op::LdrsWImm64, {_asm::field::Rt, 0}, {_asm::field::Rn, 0}, {_asm::field::Off4, 0x154}),
+ _asm::field::Rt, _asm::field::Rn);
+
+ R_UNLESS(ldrsw != nullptr, ldr::ResultInvalidDvcoRateConfig());
+
+ /* Validate the str #0x2a0 */
+ auto isCorrectStr = [](u32 w) {
+ return _asm::IsOp(w, _asm::op::StrImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Off8)
+ && _asm::Get(w, _asm::field::Off8) == 0x2a0;
+ };
+ R_UNLESS(isCorrectStr(ldrsw[1]) || isCorrectStr(ldrsw[2]), ldr::ResultInvalidDvcoRateConfig());
+
+ /* Validate the target in both directuins */
+ /* Due to codegen difference */
+ u32 *add = nullptr;
+ if (_asm::IsOp(ldrsw[-1], _asm::op::AddShifted64, _asm::field::Rd, _asm::field::Rn, _asm::field::Rm, _asm::field::Shift, _asm::field::Imm6)
+ && _asm::Get(ldrsw[-1], _asm::field::Shift) == 0
+ && _asm::Get(ldrsw[-1], _asm::field::Imm6) == 5) {
+ add = ldrsw - 1;
+ }
+ else if (_asm::IsOp(ldrsw[1], _asm::op::AddShifted64, _asm::field::Rd, _asm::field::Rn, _asm::field::Rm, _asm::field::Shift, _asm::field::Imm6)
+ && _asm::Get(ldrsw[1], _asm::field::Shift) == 0
+ && _asm::Get(ldrsw[1], _asm::field::Imm6) == 5) {
+ add = ldrsw + 1;
+ }
+
+ R_UNLESS(add != nullptr, ldr::ResultInvalidDvcoRateConfig());
+
+ /* Patch the second target */
+ PATCH_OFFSET(add, _asm::Encode(_asm::op::AddShifted64,
+ {_asm::field::Rd, _asm::Get(*add, _asm::field::Rd)},
+ {_asm::field::Rn, _asm::Get(*add, _asm::field::Rn)},
+ {_asm::field::Rm, _asm::Get(*add, _asm::field::Rm)},
+ {_asm::field::Shift, 0}, {_asm::field::Imm6, 6}));
+
+ R_SUCCEED();
+ }
}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.hpp
index f6a570d4..52a400cf 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.hpp
@@ -5,7 +5,7 @@
*
* Copyright (c) B3711
*
- * Copyright (c) Souldbminer and Horizon OC Contributors
+ * Copyright (c) Souldbminer, Lightos and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
@@ -23,12 +23,89 @@
#pragma once
#include "../pcv.hpp"
+#include "../pcv_asm.hpp"
+#include "../pcv_hook.hpp"
namespace ams::ldr::hoc::pcv::mariko {
+ extern u32 *nsoStart;
+
+ /* Maybe a cleaner way to do this? */
+ inline bool LutWriterPatternFn(u32 *ptr) {
+ if (_asm::IsOp(*ptr, _asm::op::AndImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Immr, _asm::field::Imm6)
+ && _asm::Get(*ptr, _asm::field::Immr) == 61 && _asm::Get(*ptr, _asm::field::Imm6) == 7)
+ return true;
+ if (_asm::IsOp(*ptr, _asm::op::AndImm32, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12)
+ && _asm::Get(*ptr, _asm::field::Imm12) == 0x747)
+ return true;
+ return false;
+ }
+
+ HOOK_PAYLOAD_FN void CpuLutWriterExpandImpl(u64 param);
+ Result LutWriterFind(u32 *ptr);
+ Result LutWriterInstallHooks(HookPayloadData *data);
+
+ constexpr cvb_entry_t CpuCvbTableDefault[] = {
+ { 204000, { 721589, -12695, 27 }, { } },
+ { 306000, { 747134, -14195, 27 }, { } },
+ { 408000, { 776324, -15705, 27 }, { } },
+ { 510000, { 809160, -17205, 27 }, { } },
+ { 612000, { 845641, -18715, 27 }, { } },
+ { 714000, { 885768, -20215, 27 }, { } },
+ { 816000, { 929540, -21725, 27 }, { } },
+ { 918000, { 976958, -23225, 27 }, { } },
+ { 1020000, { 1028021, -24725, 27 }, { 1120000 } },
+ { 1122000, { 1082730, -26235, 27 }, { 1120000 } },
+ { 1224000, { 1141084, -27735, 27 }, { 1120000 } },
+ { 1326000, { 1203084, -29245, 27 }, { 1120000 } },
+ { 1428000, { 1268729, -30745, 27 }, { 1120000 } },
+ { 1581000, { 1374032, -33005, 27 }, { 1120000 } },
+ { 1683000, { 1448791, -34505, 27 }, { 1120000 } },
+ { 1785000, { 1527196, -36015, 27 }, { 1120000 } },
+ { 1887000, { 1609246, -37515, 27 }, { 1120000 } },
+ { 1963500, { 1675751, -38635, 27 }, { 1120000 } },
+ { },
+ };
+
+ constexpr u32 CpuClkOfficial = 1963'500;
+ constexpr u32 CpuVoltOfficial = 1120;
+ constexpr u32 CpuHighVminOfficial = 850;
+ constexpr u32 CpuVminOfficial = 620;
+ constexpr u32 CpuTune0Low = 0xFFCF;
+
+ static const u32 cpuVoltagePatchValues[] = { 850, 38, 1120, 1000, 100, 1000, 0 };
+ static const s32 cpuVoltagePatchOffsets[] = { -2, -1, 5, 6, 7, 8, 9 };
+ static_assert(sizeof(cpuVoltagePatchValues) == sizeof(cpuVoltagePatchOffsets), "Invalid cpuVoltagePatch size");
+
+ static const u32 cpuVoltThermalData[] = { 620, 1120, 20000, 620, 1120, 70000, 950, 1132, 0, 950, 1227, 0 };
+
+ /* Refer to customize.cpp for more information */
+ static const u32 allowedCpuMaxFrequencies[] = { 1'963'500, 2'091'000, 2'193'000, 2'295'000, 2'397'000, 2'499'000, 2'601'000, 2'703'000, 2'805'000 };
+
Result CpuFreqVdd(u32 *ptr);
Result CpuVoltDVFS(u32 *ptr);
Result CpuVoltThermals(u32 *ptr);
Result CpuVoltDfll(u32 *ptr);
+ Result CpuLutMaxAsm(u32* ptr);
+ Result CpuLutMaxAsm2(u32* ptr);
+ Result CpuLutDvcoRateCfg(u32* ptr);
+
+ ALWAYS_INLINE bool CpuLutAsmPatternFn(u32* ptr) {
+ /* Check if it is a DSB ST, a common indication of CL-DVFS related functions */
+ return *ptr == _asm::Dsb(_asm::barrier::St);
+ }
+
+ ALWAYS_INLINE bool CpuLutAsmPatternFn2(u32* ptr) {
+ /* Note: We are doing multiple checks here as it is the cleanest method */
+ return _asm::IsOp(*ptr, _asm::op::MovzW, _asm::field::Rd, _asm::field::Imm16, _asm::field::Hw)
+ && _asm::Get(*ptr, _asm::field::Imm16) == 0xC0FF
+ && _asm::Get(*ptr, _asm::field::Hw) == 0;
+ }
+
+ ALWAYS_INLINE bool CpuLutDvcoRateCfgPatternFn(u32* ptr) {
+ /* Again, cleaner to ensure we are on the correct instruction here */
+ return _asm::IsOp(*ptr, _asm::op::LdrImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Off8)
+ && _asm::Get(*ptr, _asm::field::Off8) == 0x248;
+ }
}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.cpp
index 5b6c1286..2826f485 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.cpp
@@ -5,7 +5,7 @@
*
* Copyright (c) B3711
*
- * Copyright (c) Souldbminer and Horizon OC Contributors
+ * Copyright (c) Souldbminer, Lightos and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
@@ -22,6 +22,7 @@
#include "../pcv.hpp"
#include "../pcv_asm.hpp"
+#include "pcv_mariko_gpu.hpp"
namespace ams::ldr::hoc::pcv::mariko {
@@ -62,19 +63,28 @@ namespace ams::ldr::hoc::pcv::mariko {
PATCH_OFFSET(ptr + 12, C.marikoGpuVmin);
}
+ // if (C.marikoGpuVmax) {
+ // PATCH_OFFSET(ptr - 2, C.marikoGpuVmax);
+ // PATCH_OFFSET(ptr + 1, C.marikoGpuVmax);
+ // PATCH_OFFSET(ptr + 4, C.marikoGpuVmax);
+ // PATCH_OFFSET(ptr + 7, C.marikoGpuVmax);
+ // PATCH_OFFSET(ptr + 10, C.marikoGpuVmax);
+ // PATCH_OFFSET(ptr + 13, C.marikoGpuVmax);
+ // }
+
R_SUCCEED();
}
Result GpuFreqMaxAsm(u32 *ptr32) {
// Check if both two instructions match the pattern
u32 ins1 = *ptr32, ins2 = *(ptr32 + 1);
- if (!(asm_compare_no_rd(ins1, GpuAsmPattern[0]) && asm_compare_no_rd(ins2, GpuAsmPattern[1]))) {
+ if (!(_asm::Ignoring(ins1, GpuAsmPattern[0], _asm::field::Rd) && _asm::Ignoring(ins2, GpuAsmPattern[1], _asm::field::Rd))) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
// Both instructions should operate on the same register
- u8 rd = asm_get_rd(ins1);
- if (rd != asm_get_rd(ins2)) {
+ u8 rd = _asm::Get(ins1, _asm::field::Rd);
+ if (rd != _asm::Get(ins2, _asm::field::Rd)) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
@@ -101,8 +111,8 @@ namespace ams::ldr::hoc::pcv::mariko {
}
u32 asm_patch[2] = {
- asm_set_rd(asm_set_imm16(GpuAsmPattern[0], max_clock), rd),
- asm_set_rd(asm_set_imm16(GpuAsmPattern[1], max_clock >> 16), rd)
+ _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, rd}, {_asm::field::Imm16, max_clock}),
+ _asm::Encode(_asm::op::MovkW, {_asm::field::Rd, rd}, {_asm::field::Imm16, max_clock >> 16}, {_asm::field::Hw, 1})
};
PATCH_OFFSET(ptr32, asm_patch[0]);
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.hpp
index f5e4c33f..4f87b6ab 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.hpp
@@ -5,7 +5,7 @@
*
* Copyright (c) B3711
*
- * Copyright (c) Souldbminer and Horizon OC Contributors
+ * Copyright (c) Souldbminer, Lightos and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
@@ -26,6 +26,54 @@
namespace ams::ldr::hoc::pcv::mariko {
+ constexpr cvb_entry_t GpuCvbTableDefault[] = {
+ // GPUB01_NA_CVB_TABLE
+ { 76800, {}, { 610000, } },
+ { 153600, {}, { 610000, } },
+ { 230400, {}, { 610000, } },
+ { 307200, {}, { 610000, } },
+ { 384000, {}, { 610000, } },
+ { 460800, {}, { 610000, } },
+ { 537600, {}, { 801688, -10900, -163, 298, -10599, 162, } },
+ { 614400, {}, { 824214, -5743, -452, 238, -6325, 81, } },
+ { 691200, {}, { 848830, -3903, -552, 119, -4030, -2, } },
+ { 768000, {}, { 891575, -4409, -584, 0, -2849, 39, } },
+ { 844800, {}, { 940071, -5367, -602, -60, -63, -93, } },
+ { 921600, {}, { 986765, -6637, -614, -179, 1905, -13, } },
+ { 998400, {}, { 1098475, -13529, -497, -179, 3626, 9, } },
+ { 1075200, {}, { 1163644, -12688, -648, 0, 1077, 40, } },
+ { 1152000, {}, { 1204812, -9908, -830, 0, 1469, 110, } },
+ { 1228800, {}, { 1277303, -11675, -859, 0, 3722, 313, } },
+ { 1267200, {}, { 1335531, -12567, -867, 0, 3681, 559, } },
+ { },
+ };
+
+ constexpr u32 GpuClkPllMax = 1300'000'000;
+ constexpr u32 GpuClkPllLimit = 2'600'000;
+ constexpr u32 GpuVminOfficial = 610;
+
+ static const u32 gpuDVFSPattern[] = { 1050, 1000, 100, 1000, 10, };
+ static const u32 gpuVoltThermalPattern[] = { 800, 1120, 0, 610, 1120, 20000, 610, 1120, 30000, 610, 1120, 50000, 610, 1120, 70000, 610, 1120, 90000, };
+ static_assert(sizeof(gpuVoltThermalPattern) == 72, "Invalid gpuVoltThermalPattern");
+
+ /* GPU Max Clock asm Pattern:
+ *
+ * MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11)
+ * sf | opc | | hw | imm16 | Rd
+ * #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
+ * 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1
+ *
+ * MOVK W11, #0xE, LSL#16 16 0xE 0xB (11)
+ * sf | opc | | hw | imm16 | Rd
+ * #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
+ * 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1
+ */
+ inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 };
+
+ inline bool GpuMaxClockPatternFn(u32 *ptr32) {
+ return _asm::Ignoring(*ptr32, GpuAsmPattern[0], _asm::field::Rd);
+ }
+
Result GpuVoltDVFS(u32 *ptr);
Result GpuVoltThermals(u32 *ptr);
Result GpuFreqMaxAsm(u32 *ptr32);
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_log.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_log.cpp
new file mode 100644
index 00000000..0377aade
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_log.cpp
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) Souldbminer, Lightos and Horizon OC Contributors
+ *
+ * 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 .
+ */
+
+#include "../pcv.hpp"
+#include "pcv_mariko_log.hpp"
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ /* Todo: Remove this bs. */
+ [[maybe_unused]] static uintptr_t CaveReserve(size_t count) {
+ return reinterpret_cast(Hooks().Reserve(count));
+ }
+
+ #if HOC_UART_LOG
+ namespace {
+ struct {
+ uintptr_t vsnprintf_addr = 0;
+ uintptr_t nvlog_addr = 0;
+ } nvLogCache;
+ }
+
+ Result NvLogUartRedirect(u32 *ptr) {
+ const uintptr_t mapped_nso = reinterpret_cast(nsoStart);
+ const size_t nso_size = g_nso_size;
+ const uintptr_t textEnd = g_pcv_cave; /* .text ends where the cave begins */
+ const uintptr_t vsnprintf_addr = reinterpret_cast(ptr);
+
+ {
+ using namespace _asm::field;
+ R_UNLESS(_asm::Ignoring(ptr[1], NvLogStpFpLrAsm, PairOff8), ldr::ResultInvalidNvLogRedirect()); /* stp x29,x30,[sp,#imm] */
+ R_UNLESS(_asm::Ignoring(ptr[2], NvLogStrSpillAsm, Rt, Off8), ldr::ResultInvalidNvLogRedirect()); /* str x?,[sp,#imm] */
+ R_UNLESS(_asm::Ignoring(ptr[3], NvLogMovFpAsm, Imm12), ldr::ResultInvalidNvLogRedirect()); /* add x29,sp,#imm */
+ R_UNLESS(ptr[4] == NvLogCmpSizeAsm, ldr::ResultInvalidNvLogRedirect()); /* cmp x1,#0 */
+ }
+
+ /* NvLog via the VDD_SOC log */
+ static const char Fmt[] = "%s(%s): DVFS request VDD_SOC %d mV\n";
+ constexpr size_t FmtLen = sizeof(Fmt) - 1;
+ uintptr_t strAddr = 0;
+ {
+ const char *hay = reinterpret_cast(mapped_nso);
+ for (size_t i = 0; i + FmtLen <= nso_size; ++i) {
+ if (std::memcmp(hay + i, Fmt, FmtLen) == 0) { strAddr = mapped_nso + i; break; }
+ }
+ }
+ if (strAddr == 0) {
+ LOGGING("NvLogRedirect: fmt string not found (vsnprintf@+%lx)", vsnprintf_addr - mapped_nso);
+ R_THROW(ldr::ResultInvalidNvLogRedirect());
+ }
+
+ uintptr_t nvlog_addr = 0;
+ for (u32 *p = nsoStart; reinterpret_cast(p + 2) <= textEnd; ++p) {
+ const uintptr_t pc = reinterpret_cast(p);
+ if (!_asm::IsOp(p[0], _asm::op::Adrp, _asm::field::Rd, _asm::field::ImmAdrpHi, _asm::field::ImmAdrpLo)) {
+ continue;
+ }
+ if (_asm::TargetAdrp(p[0], pc) != (strAddr & ~static_cast(0xFFFu))) {
+ continue;
+ }
+ const u32 adrpReg = _asm::Get(p[0], _asm::field::Rd);
+ if (!(_asm::IsOp(p[1], _asm::op::AddImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12)
+ && _asm::Get(p[1], _asm::field::Rd) == adrpReg && _asm::Get(p[1], _asm::field::Rn) == adrpReg
+ && _asm::Get(p[1], _asm::field::Imm12) == (strAddr & 0xFFFu))) {
+ continue;
+ }
+ for (u32 k = 2; k <= 12 && (pc + (k + 1) * 4) <= textEnd; ++k) {
+ if (_asm::IsOp(p[k], _asm::op::Bl, _asm::field::Imm26)) { nvlog_addr = _asm::Target26(p[k], pc + k * 4); break; }
+ }
+ if (nvlog_addr != 0) {
+ break;
+ }
+ }
+ if (nvlog_addr == 0 || nvlog_addr < mapped_nso || nvlog_addr >= textEnd) {
+ LOGGING("NvLogRedirect: NvLog entry not found (fmt@+%lx)", strAddr - mapped_nso);
+ R_THROW(ldr::ResultInvalidNvLogRedirect());
+ }
+
+ nvLogCache.vsnprintf_addr = vsnprintf_addr;
+ nvLogCache.nvlog_addr = nvlog_addr;
+ R_SUCCEED();
+ }
+
+ namespace {
+ Result InstallNvLogRedirect() {
+ const uintptr_t mapped_nso = reinterpret_cast(nsoStart);
+ const uintptr_t textEnd = g_pcv_cave;
+ const uintptr_t vsnprintf_addr = nvLogCache.vsnprintf_addr;
+ const uintptr_t nvlog_addr = nvLogCache.nvlog_addr;
+
+ const uintptr_t helper = CaveReserve(40);
+ if (helper == 0) {
+ LOGGING("NvLogRedirect: cave unavailable (cave=%lx size=%lx)",
+ static_cast(g_pcv_cave), static_cast(g_pcv_cave_size));
+ R_THROW(ldr::ResultInvalidNvLogRedirect());
+ }
+ u32 *t = reinterpret_cast(helper);
+ size_t n = 0;
+ auto emit = [&](u32 ins) { t[n] = ins; ++n; };
+
+ using namespace _asm::field;
+ constexpr u32 SP = _asm::reg::Sp;
+
+ emit(_asm::Encode(_asm::op::SubImm64, {Rd, SP}, {Rn, SP}, {Imm12, 0x200}));
+ emit(_asm::Encode(_asm::op::StpImm64, {Rt, 0}, {Rt2, 1}, {Rn, SP}, {PairOff8, 0x100}));
+ emit(_asm::Encode(_asm::op::StpImm64, {Rt, 2}, {Rt2, 3}, {Rn, SP}, {PairOff8, 0x110}));
+ emit(_asm::Encode(_asm::op::StpImm64, {Rt, 4}, {Rt2, 5}, {Rn, SP}, {PairOff8, 0x120}));
+ emit(_asm::Encode(_asm::op::StpImm64, {Rt, 6}, {Rt2, 7}, {Rn, SP}, {PairOff8, 0x130}));
+ emit(_asm::Encode(_asm::op::StpImmQ, {Rt, 0}, {Rt2, 1}, {Rn, SP}, {PairOff16, 0x140}));
+ emit(_asm::Encode(_asm::op::StpImmQ, {Rt, 2}, {Rt2, 3}, {Rn, SP}, {PairOff16, 0x160}));
+ emit(_asm::Encode(_asm::op::StpImmQ, {Rt, 4}, {Rt2, 5}, {Rn, SP}, {PairOff16, 0x180}));
+ emit(_asm::Encode(_asm::op::StpImmQ, {Rt, 6}, {Rt2, 7}, {Rn, SP}, {PairOff16, 0x1A0}));
+ emit(_asm::Encode(_asm::op::StrImm64, {Rt, _asm::reg::Lr}, {Rn, SP}, {Off8, 0x1E0}));
+ emit(_asm::Encode(_asm::op::AddImm64, {Rd, 9}, {Rn, SP}, {Imm12, 0x200})); emit(_asm::Encode(_asm::op::StrImm64, {Rt, 9}, {Rn, SP}, {Off8, 0x1C0})); /* __stack */
+ emit(_asm::Encode(_asm::op::AddImm64, {Rd, 9}, {Rn, SP}, {Imm12, 0x140})); emit(_asm::Encode(_asm::op::StrImm64, {Rt, 9}, {Rn, SP}, {Off8, 0x1C8})); /* __gr_top */
+ emit(_asm::Encode(_asm::op::AddImm64, {Rd, 9}, {Rn, SP}, {Imm12, 0x1C0})); emit(_asm::Encode(_asm::op::StrImm64, {Rt, 9}, {Rn, SP}, {Off8, 0x1D0})); /* __vr_top */
+ emit(_asm::Encode(_asm::op::MovnW, {Rd, 9}, {Imm16, 0x37})); emit(_asm::Encode(_asm::op::StrImm32, {Rt, 9}, {Rn, SP}, {Off4, 0x1D8})); /* __gr_offs = -56 */
+ emit(_asm::Encode(_asm::op::MovnW, {Rd, 9}, {Imm16, 0x7F})); emit(_asm::Encode(_asm::op::StrImm32, {Rt, 9}, {Rn, SP}, {Off4, 0x1DC})); /* __vr_offs = -128 */
+ emit(_asm::Encode(_asm::op::AddImm64, {Rd, 0}, {Rn, SP}, {Imm12, 0x00})); /* mov x0,sp (buf) */
+ emit(_asm::Encode(_asm::op::MovzW, {Rd, 1}, {Imm16, 0x100})); /* size = 0x100 */
+ emit(_asm::Encode(_asm::op::LdrImm64, {Rt, 2}, {Rn, SP}, {Off8, 0x100})); /* fmt (saved x0) */
+ emit(_asm::Encode(_asm::op::AddImm64, {Rd, 3}, {Rn, SP}, {Imm12, 0x1C0})); /* ap */
+ emit(_asm::Retarget26(_asm::op::Bl, helper + n * 4, vsnprintf_addr));
+ emit(_asm::MovReg64(1, 0)); /* len = retval */
+ emit(_asm::CmpImm32(1, 0x100));
+ { /* b.lo +2 */
+ const size_t at = n;
+ emit(_asm::Retarget19(_asm::Encode(_asm::op::BCond, {BCond, _asm::cond::Lo}), helper + at * 4, helper + (at + 2) * 4));
+ }
+ emit(_asm::Encode(_asm::op::MovzW, {Rd, 1}, {Imm16, 0xFF})); /* clamp len */
+ emit(_asm::Encode(_asm::op::AddImm64, {Rd, 0}, {Rn, SP}, {Imm12, 0x00})); /* mov x0,sp (str) */
+ emit(_asm::Encode(_asm::op::Svc, {Imm16, 0x27})); /* svcOutputDebugString */
+ emit(_asm::Encode(_asm::op::LdrImm64, {Rt, _asm::reg::Lr}, {Rn, SP}, {Off8, 0x1E0}));
+ emit(_asm::Encode(_asm::op::AddImm64, {Rd, SP}, {Rn, SP}, {Imm12, 0x200}));
+ emit(_asm::RetIns);
+
+ /* Redirect the call sites as patching the actual function causes crash */
+ const uintptr_t roStart = g_pcv_cave + g_pcv_cave_size; /* module .rodata start */
+ size_t patchedSites = 0;
+ for (u32 *p = nsoStart; reinterpret_cast(p + 1) <= textEnd; ++p) {
+ if (!_asm::IsOp(*p, _asm::op::Bl, _asm::field::Imm26)) {
+ continue;
+ }
+ const uintptr_t pc = reinterpret_cast(p);
+ if (_asm::Target26(*p, pc) != nvlog_addr) {
+ continue;
+ }
+ bool isFmtCall = false;
+ for (u32 j = 1; j <= 8 && reinterpret_cast(p - j) >= reinterpret_cast(nsoStart); ++j) {
+ const u32 w = *(p - j);
+ if (_asm::IsOp(w, _asm::op::Adrp, _asm::field::Rd, _asm::field::ImmAdrpHi, _asm::field::ImmAdrpLo) && _asm::Get(w, _asm::field::Rd) == 0) { /* adrp x0, */
+ const uintptr_t tgtPage = _asm::TargetAdrp(w, pc - j * 4);
+ if (tgtPage >= (roStart & ~static_cast(0xFFFu))) { isFmtCall = true; break; }
+ }
+ }
+ if (isFmtCall) {
+ PATCH_OFFSET(p, _asm::Retarget26(_asm::op::Bl, pc, helper));
+ ++patchedSites;
+ }
+ }
+
+ LOGGING("NvLogRedirect: stub@+%lx vsnprintf@+%lx helper@+%lx instr=%zu sites=%zu", nvlog_addr - mapped_nso, vsnprintf_addr - mapped_nso, helper - mapped_nso, n, patchedSites);
+ R_SUCCEED();
+ }
+ }
+
+ namespace {
+ struct {
+ u32 *sites[3] = {};
+ u32 count = 0;
+ } forceVerbosityCache;
+ }
+
+ HOOK_PAYLOAD_FN u32 ForceVerbosityImpl() {
+ HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData);
+ return data->verbosityLevel;
+ }
+
+ /* Force GetEffectiveVerbosityLevel to return a non-zero level so all NvLog runs. */
+ Result ForceVerbosity(u32 *ptr) {
+ using namespace _asm::field;
+
+ R_UNLESS(_asm::IsOp(ptr[1], _asm::op::StrImm64, Rt, Rn, Off8) && _asm::Get(ptr[1], Rn) == _asm::reg::Sp,
+ ldr::ResultInvalidForceVerbosityPattern()); /* str x?,[sp,#imm] */
+ R_UNLESS(_asm::IsOp(ptr[2], _asm::op::AddImm64, Rd, Rn, Imm12)
+ && _asm::Get(ptr[2], Rd) == _asm::reg::Fp && _asm::Get(ptr[2], Rn) == _asm::reg::Sp,
+ ldr::ResultInvalidForceVerbosityPattern()); /* add x29,sp,#imm */
+ R_UNLESS(_asm::IsOp(ptr[3], _asm::op::AddImm64, Rn, Imm12)
+ && _asm::Get(ptr[3], Rd) == 0 && _asm::Get(ptr[3], Rn) == _asm::reg::Fp,
+ ldr::ResultInvalidForceVerbosityPattern()); /* add x0,x29,#imm */
+ R_UNLESS(_asm::IsOp(ptr[4], _asm::op::AddImm64, Rd, Rn, Imm12) && _asm::Get(ptr[4], Rn) == _asm::reg::Fp,
+ ldr::ResultInvalidForceVerbosityPattern()); /* add x?,x29,#imm */
+ R_UNLESS(_asm::Get(ptr[3], Imm12) == _asm::Get(ptr[4], Imm12)
+ && _asm::IsOp(ptr[5], _asm::op::Bl, Imm26),
+ ldr::ResultInvalidForceVerbosityPattern());
+
+ bool foundCmp = false;
+ for (u32 j = 6; j <= 10; ++j) {
+ if (ptr[j] == 0x7100001Fu) { /* cmp w0,#0 */
+ foundCmp = true;
+ break;
+ }
+ }
+ R_UNLESS(foundCmp, ldr::ResultInvalidForceVerbosityPattern());
+
+ R_UNLESS(forceVerbosityCache.count < std::size(forceVerbosityCache.sites), ldr::ResultInvalidForceVerbosityPattern());
+ forceVerbosityCache.sites[forceVerbosityCache.count++] = ptr;
+ R_SUCCEED();
+ }
+
+ Result ForceVebosityInstallHooks(HookPayloadData *data) {
+ if (nvLogCache.nvlog_addr != 0) {
+ R_TRY(InstallNvLogRedirect());
+ }
+
+ if (forceVerbosityCache.count != 0 && C.pcvLogVerbosity != 0xff) {
+ data->verbosityLevel = C.pcvLogVerbosity;
+ for (u32 i = 0; i < forceVerbosityCache.count; ++i) {
+ R_DISCARD(INSTALL_IMPL_HOOK(forceVerbosityCache.sites[i], ForceVerbosityImpl));
+ }
+ }
+
+ R_SUCCEED();
+ }
+#endif
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_log.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_log.hpp
new file mode 100644
index 00000000..f9855e19
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_log.hpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) Souldbminer, Lightos and Horizon OC Contributors
+ *
+ * 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 .
+ */
+
+#pragma once
+
+#include "../pcv.hpp"
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ extern u32 *nsoStart;
+ extern size_t g_nso_size;
+
+ inline bool ForceVerbosityPatternFn(u32 *ptr) {
+ if (g_pcv_cave == 0) {
+ return false;
+ }
+ if (reinterpret_cast(ptr + 11) > g_pcv_cave) { /* .text only */
+ return false;
+ }
+ return _asm::IsFramePushPre(*ptr);
+ }
+
+ /*
+ sub sp, sp, #imm
+ stp x29,x30,[sp,#imm]
+ str x?, [sp,#imm]
+ add x29,sp, #imm
+ subs xzr, x1, #0
+ */
+ constexpr u32 NvLogSubSpAsm = 0xD10003FFu; /* sub sp,sp,#0 */
+ constexpr u32 NvLogStpFpLrAsm = 0xA9007BFDu; /* stp x29,x30,[sp,#0] */
+ constexpr u32 NvLogStrSpillAsm = 0xF90003E0u; /* str x?,[sp,#0] */
+ constexpr u32 NvLogMovFpAsm = 0x910003FDu; /* add x29,sp,#0 */
+ constexpr u32 NvLogCmpSizeAsm = 0xF100003Fu; /* subs xzr,x1,#0 */
+
+ inline bool NvLogVsnprintfPatternFn(u32 *ptr) {
+ if (HOC_UART_LOG == 0 || g_pcv_cave == 0) {
+ return false;
+ }
+ if (reinterpret_cast(ptr + 5) > g_pcv_cave) { /* must sit in .text */
+ return false;
+ }
+ return _asm::Ignoring(*ptr, NvLogSubSpAsm, _asm::field::Imm12);
+ }
+
+ Result NvLogUartRedirect(u32 *ptr);
+ HOOK_PAYLOAD_FN u32 ForceVerbosityImpl();
+ Result ForceVerbosity(u32 *ptr);
+ Result ForceVebosityInstallHooks(HookPayloadData *data);
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.cpp
index 7dacbdbe..49fadc9b 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.cpp
@@ -5,7 +5,7 @@
*
* Copyright (c) B3711
*
- * Copyright (c) Souldbminer and Horizon OC Contributors
+ * Copyright (c) Souldbminer, Lightos and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
@@ -23,6 +23,7 @@
#include "../pcv.hpp"
#include "../../mtc_timing_value.hpp"
#include "calculate_timings_mariko.hpp"
+#include "pcv_mariko_mtc.hpp"
namespace ams::ldr::hoc::pcv::mariko {
@@ -35,6 +36,8 @@ namespace ams::ldr::hoc::pcv::mariko {
* https://developer.nvidia.com/embedded/dlc/tegra-x1-technical-reference-manual
* Section 18.11.1: MC Registers
* Section 18.11.2: EMC Registers
+ *
+ * Additional information from reverse-engineering public Tegra K1 (T124) memory characterization tools.
*/
#define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE) \
@@ -44,8 +47,6 @@ namespace ams::ldr::hoc::pcv::mariko {
const double tCK_avg = 1000'000.0 / table->rate_khz;
- #define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg))
-
/* Ram power down */
/* B31: DRAM_CLKSTOP_PD */
/* B30: DRAM_CLKSTOP_SR */
@@ -56,13 +57,12 @@ namespace ams::ldr::hoc::pcv::mariko {
WRITE_PARAM_ALL_REG(table, emc_cfg, 0xF3200000);
}
+ CalculateTimings(tCK_avg, table->rate_khz);
+
u32 trefbw = refresh_raw + 0x40;
trefbw = MIN(trefbw, static_cast(0x3FFF));
-
const u32 dyn_self_ref_control = (static_cast(7605.0 / tCK_avg) + 260) | (table->burst_regs.emc_dyn_self_ref_control & 0xffff0000);
- CalculateTimings(tCK_avg, table->rate_khz);
-
WRITE_PARAM_ALL_REG(table, emc_rd_rcd, GET_CYCLE_CEIL(tRCD));
WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(tRCD));
WRITE_PARAM_ALL_REG(table, emc_rc, MIN(GET_CYCLE_CEIL(tRC), static_cast(0xB9)));
@@ -235,6 +235,9 @@ namespace ams::ldr::hoc::pcv::mariko {
const u32 tRFCabStock = tRFC_values[0] * 2;
table->dram_timings.t_rfc = tRFCabStock;
+ /* dram_timings.rl is used on Erista to calculate specific RL related timings during memory training for a workaround. */
+ /* See fusee or minerva for more details. */
+ /* This is unused on Mariko but we will set it regardless for completion. */
table->dram_timings.rl = RL;
table->emc_mrw2 = (table->emc_mrw2 & ~0xFFu) | static_cast(mrw2);
table->emc_mrw = (table->emc_mrw & ~0x70u) | 0x40; /* nWR */
@@ -298,8 +301,18 @@ namespace ams::ldr::hoc::pcv::mariko {
}
}
+ void AppendMaxEmc(std::vector &emcList) {
+ if (emcList.back() != C.marikoEmcMaxClock) {
+ emcList.push_back(static_cast(C.marikoEmcMaxClock));
+ }
+ }
+
+ void TruncateToLutLimit(std::vector &emcList, size_t limit) {
+ emcList.resize(std::min(emcList.size(), limit));
+ }
+
void MtcGenerateJedecTable() {
- const u32 jedecFreqs[] = { 1866000, 1996000, 2133000, 2400000, 2666000, 2933000, 3200000 };
+ const u32 jedecFreqs[] = { 1866000, 1996800, 2133000, 2400000, 2666000, 2933000, 3200000 };
constexpr u32 JedecFreqCount = std::size(jedecFreqs);
for (u32 i = 0; i < JedecFreqCount; ++i) {
@@ -310,11 +323,8 @@ namespace ams::ldr::hoc::pcv::mariko {
}
}
- if (newEmcList.back() != C.marikoEmcMaxClock) {
- newEmcList.push_back(static_cast(C.marikoEmcMaxClock));
- }
-
- newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
+ AppendMaxEmc(newEmcList);
+ TruncateToLutLimit(newEmcList, EmcDvfsTableEntryLimit);
}
void MtcGenerate133StepTable() {
@@ -329,11 +339,8 @@ namespace ams::ldr::hoc::pcv::mariko {
}
}
- if (newEmcList.back() != C.marikoEmcMaxClock) {
- newEmcList.push_back(static_cast(C.marikoEmcMaxClock));
- }
-
- newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
+ AppendMaxEmc(newEmcList);
+ TruncateToLutLimit(newEmcList, EmcDvfsTableEntryLimit);
}
void MtcGenerate33StepTable() {
@@ -356,11 +363,8 @@ namespace ams::ldr::hoc::pcv::mariko {
}
}
- if (newEmcList.back() != C.marikoEmcMaxClock) {
- newEmcList.push_back(static_cast(C.marikoEmcMaxClock));
- }
-
- newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
+ AppendMaxEmc(newEmcList);
+ TruncateToLutLimit(newEmcList, EmcDvfsTableEntryLimit);
}
void MtcGenerateFreqTables() {
@@ -399,15 +403,13 @@ namespace ams::ldr::hoc::pcv::mariko {
u32 newFreq = EmcClkOSLimit + stepIndex * stepRate;
newFreq = (newFreq / RoundHz) * RoundHz;
if (newFreq > C.marikoEmcMaxClock) {
- if (newEmcList.back() != C.marikoEmcMaxClock) {
- newEmcList.push_back(static_cast(C.marikoEmcMaxClock));
- }
+ AppendMaxEmc(newEmcList);
break;
}
newEmcList.push_back(newFreq);
}
- newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
+ TruncateToLutLimit(newEmcList, EmcDvfsTableEntryLimit);
}
Result VerifyMtcTable(MarikoMtcTable *tableStart, u32 expectedFreq) {
@@ -475,7 +477,6 @@ namespace ams::ldr::hoc::pcv::mariko {
Result MemFreqMtcTable(u32 *ptr) {
static const DramId dramId = [] {
DramId id = GetDramId();
- id = HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTF;
return id;
}();
@@ -522,6 +523,24 @@ namespace ams::ldr::hoc::pcv::mariko {
R_SUCCEED();
}
+ void Get32LutEmcList(std::vector &emcList) {
+ /* No need to reduce outside of 33MHz stepmode. */
+ if (C.stepMode != StepMode_33MHz || emcList.size() <= DvfsTableEntryLimit) {
+ return;
+ }
+
+ /* Add every other oc entry. */
+ std::vector reduced(emcList.begin(), emcList.begin() + mariko::MtcTableCountDefault);
+ for (size_t i = mariko::MtcTableCountDefault; i < emcList.size(); i += 2) {
+ reduced.push_back(emcList[i]);
+ }
+
+ TruncateToLutLimit(reduced, DvfsTableEntryLimit);
+ AppendMaxEmc(reduced);
+
+ emcList = std::move(reduced);
+ }
+
Result MemFreqDvbTable(u32 *ptr) {
DvbEntry *default_end = reinterpret_cast(ptr);
DvbEntry *new_start = default_end + 1;
@@ -535,9 +554,9 @@ namespace ams::ldr::hoc::pcv::mariko {
R_SKIP();
}
- s32 max0 = 1050;
- s32 max1 = 1025;
- s32 max2 = 1000;
+ s32 max0 = 1050;
+ s32 max1 = 1025;
+ s32 max2 = 1000;
if (C.marikoSocVmax && C.marikoSocVmax > 1000) {
max0 = C.marikoSocVmax;
@@ -612,12 +631,15 @@ namespace ams::ldr::hoc::pcv::mariko {
#undef DVB
#undef DVB_OC
- const size_t dvbCount = std::min(newEmcList.size(), DvbTableCapacity);
- DvbEntry emcDvbTableOc[DvbTableCapacity] = {};
+ std::vector reducedEmcList = newEmcList;
+ Get32LutEmcList(reducedEmcList);
+
+ const size_t dvbCount = std::min(reducedEmcList.size(), DvfsTableEntryCount);
+ DvbEntry emcDvbTableOc[DvfsTableEntryCount] = {};
u32 bracketIndex = 0;
for (size_t i = 0; i < dvbCount; ++i) {
- const u32 freq = (i == dvbCount - 1) ? static_cast(newEmcList.back()) : newEmcList[i];
+ const u32 freq = reducedEmcList[i];
while (freq >= emcDvbOcTableBrackets[bracketIndex + 1].freq) {
++bracketIndex;
}
@@ -627,7 +649,7 @@ namespace ams::ldr::hoc::pcv::mariko {
}
/* Clear the entire 32-entry region */
- std::memset(mem_dvb_table_head, 0, DvbTableCapacity * sizeof(DvbEntry));
+ std::memset(mem_dvb_table_head, 0, DvfsTableEntryCount * sizeof(DvbEntry));
std::memcpy(mem_dvb_table_head, emcDvbTableOc, dvbCount * sizeof(DvbEntry));
R_SUCCEED();
@@ -651,26 +673,78 @@ namespace ams::ldr::hoc::pcv::mariko {
R_UNLESS(ptr - BrOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern());
u32 adrp = *(ptr - AddpOffset);
- R_UNLESS(AsmCompareAdrpNoImm(adrp, MtcAdrpAsm), ldr::ResultInvalidMtcTablePattern());
+ R_UNLESS(_asm::Ignoring(adrp, MtcAdrpAsm, _asm::field::ImmAdrpHi, _asm::field::ImmAdrpLo), ldr::ResultInvalidMtcTablePattern());
/* We don't check for matching register because both registers must be x0 in order to pass the previous checks. */
/* The correct instructions will always be x0 since the mtcTable pointer is returned. */
/* Pray this does not break. */
u32 br = *(ptr - BrOffset);
- R_UNLESS(AsmCompareBrNoRd(br, MtcBrAsm), ldr::ResultInvalidMtcTablePattern());
+ R_UNLESS(_asm::Ignoring(br, MtcBrAsm, _asm::field::Rn), ldr::ResultInvalidMtcTablePattern());
/* Pray this does not break either. */
u32 mov = *(ptr - MovOffset);
- R_UNLESS(asm_compare_no_rd(mov, MtcMovAsm), ldr::ResultInvalidMtcTablePattern());
+ R_UNLESS(_asm::Ignoring(mov, MtcMovAsm, _asm::field::Rd), ldr::ResultInvalidMtcTablePattern());
- u8 movRd = asm_get_rd(mov);
- u32 movCountPatch = asm_set_rd(asm_set_imm16(MtcMovAsm, newEmcList.size()), movRd);
+ u8 movRd = _asm::Get(mov, _asm::field::Rd);
+ u32 movCountPatch = _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, movRd}, {_asm::field::Imm16, newEmcList.size()});
- PATCH_OFFSET(ptr - BrOffset, NopIns);
+ PATCH_OFFSET(ptr - BrOffset, _asm::NopIns);
PATCH_OFFSET(ptr - MovOffset, movCountPatch);
R_SUCCEED();
}
+ Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) {
+ struct {
+ u8 reg;
+ u8 val;
+ } __attribute__((packed)) cmd;
+
+ I2cSession _session;
+ R_TRY(i2cOpenSession(&_session, dev));
+
+ cmd.reg = reg;
+ cmd.val = val;
+ Result res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All);
+ i2csessionClose(&_session);
+
+ return res;
+ }
+
+ Result EmcVddqVolt(u32 *ptr) {
+ regulator *entry = reinterpret_cast(reinterpret_cast(ptr) - offsetof(regulator, type_2_3.default_uv));
+
+ constexpr u32 uv_step = 5'000;
+ constexpr u32 uv_min = 250'000;
+
+ auto validator = [entry]() {
+ R_UNLESS(entry->id == 2, ldr::ResultInvalidRegulatorEntry());
+ R_UNLESS(entry->type == 3, ldr::ResultInvalidRegulatorEntry());
+ R_UNLESS(entry->type_2_3.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry());
+ R_UNLESS(entry->type_2_3.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry());
+ R_SUCCEED();
+ };
+
+ R_TRY(validator());
+
+ u32 emc_uv = C.marikoEmcVddqVolt;
+
+ if (!emc_uv) {
+ R_SKIP();
+ }
+
+ if (emc_uv % uv_step) {
+ emc_uv = (emc_uv + uv_step - 1) / uv_step * uv_step; // rounding
+ }
+
+ PATCH_OFFSET(ptr, emc_uv);
+
+ i2cInitialize();
+ Result resultI2C = I2cSet_U8(I2cDevice_Max77812_2, 0x25, (emc_uv - uv_min) / uv_step);
+ i2cExit();
+
+ return resultI2C;
+ }
+
}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.hpp
index 99531813..f9c2826c 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.hpp
@@ -5,7 +5,7 @@
*
* Copyright (c) B3711
*
- * Copyright (c) Souldbminer and Horizon OC Contributors
+ * Copyright (c) Souldbminer, Lightos and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
@@ -26,10 +26,181 @@
namespace ams::ldr::hoc::pcv::mariko {
+ extern u32 *nsoStart;
+
+ struct DvbEntry {
+ u64 freq;
+ u32 volt[4] = {};
+ };
+
+ constexpr DvbEntry EmcDvbTableDefault[] = {
+ { 204000, { 637, 637, 637, } },
+ { 408000, { 637, 637, 637, } },
+ { 800000, { 637, 637, 637, } },
+ { 1065600, { 637, 637, 637, } },
+ { 1331200, { 650, 637, 637, } },
+ { 1600000, { 675, 650, 637, } },
+ };
+
+ constexpr u32 EmcListDefault[] = { 204000, 1331200, 1600000, };
+ constexpr u32 EmcListSizeDefault = std::size(EmcListDefault);
+ constexpr u32 EmcListEndDefault = EmcListSizeDefault - 1;
+
+ constexpr u32 EmcClkOSAlt = 1331'200;
+ constexpr u32 EmcClkPllmLimit = 2133'000'000;
+ constexpr u32 EmcVddqDefault = 600'000;
+ constexpr u32 MemVdd2Default = 1100'000;
+
+ constexpr u32 MTC_TABLE_REV = 3;
+ constexpr u32 MtcTableCountDefault = 3;
+
+ constexpr size_t MtcFullTableSize = sizeof(MarikoMtcTable) * MtcTableCountDefault;
+ constexpr u32 MtcFullTableCount = 17;
+
+ /* These dramids were copied from Hekate -- see /bdk/mem/sdram.h */
+ enum DramId : u64 {
+ HOAG_4GB_HYNIX_H9HCNNNBKMMLXR_NEE = 3,
+ AULA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE = 5,
+ IOWA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE = 6,
+
+ IOWA_4GB_SAMSUNG_K4U6E3S4AM_MGCJ = 8,
+ IOWA_8GB_SAMSUNG_K4UBE3D4AM_MGCJ = 9,
+ IOWA_4GB_HYNIX_H9HCNNNBKMMLHR_NME = 10,
+ IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTE = 11,
+
+ HOAG_4GB_SAMSUNG_K4U6E3S4AM_MGCJ = 12,
+ HOAG_8GB_SAMSUNG_K4UBE3D4AM_MGCJ = 13,
+ HOAG_4GB_HYNIX_H9HCNNNBKMMLHR_NME = 14,
+ HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTE = 15,
+
+ IOWA_4GB_SAMSUNG_K4U6E3S4AA_MGCL = 17,
+ IOWA_8GB_SAMSUNG_K4UBE3D4AA_MGCL = 18,
+ HOAG_4GB_SAMSUNG_K4U6E3S4AA_MGCL = 19,
+
+ IOWA_4GB_SAMSUNG_K4U6E3S4AB_MGCL = 20,
+ HOAG_4GB_SAMSUNG_K4U6E3S4AB_MGCL = 21,
+ AULA_4GB_SAMSUNG_K4U6E3S4AB_MGCL = 22,
+
+ HOAG_8GB_SAMSUNG_K4UBE3D4AA_MGCL = 23,
+ AULA_4GB_SAMSUNG_K4U6E3S4AA_MGCL = 24,
+
+ IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTF = 25,
+ HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTF = 26,
+ AULA_4GB_MICRON_MT53E512M32D2NP_046_WTF = 27,
+
+ AULA_8GB_SAMSUNG_K4UBE3D4AA_MGCL = 28,
+
+ IOWA_4GB_HYNIX_H54G46CYRBX267 = 29,
+ HOAG_4GB_HYNIX_H54G46CYRBX267 = 30,
+ AULA_4GB_HYNIX_H54G46CYRBX267 = 31,
+
+ IOWA_4GB_MICRON_MT53E512M32D1NP_046_WTB = 32,
+ HOAG_4GB_MICRON_MT53E512M32D1NP_046_WTB = 33,
+ AULA_4GB_MICRON_MT53E512M32D1NP_046_WTB = 34,
+ };
+
+ enum MtcTableIndex {
+ T210b0SdevEmcDvfsTableS4gb01 = 0, /* (Unused) Samsung 4Gb */
+ T210b0SdevEmcDvfsTableS4gb03 = 1, /* Samsung AM-MGCJ 4Gb */
+ T210b0SdevEmcDvfsTableS8gb03 = 2, /* (Unused) Samsung 4Gb */
+ T210b0SdevEmcDvfsTableH4gb03 = 3, /* Hynix NME 4Gb */
+ T210b0SdevEmcDvfsTableM4gb03 = 4, /* Micron WT:F 4Gb */
+ T210b0SdevEmcDvfsTableS4gbY01 = 5, /* (Unused) Samsung 4Gb */
+ T210b0SdevEmcDvfsTableS1y4gbY01 = 6, /* (Unused) Samsung 4Gb */
+ T210b0SdevEmcDvfsTableS1y8gbY01 = 7, /* (Unused) Samsung 4Gb */
+ T210b0SdevEmcDvfsTableS1y4gbX03 = 8, /* Samsung AA-MGCL 4Gb */
+ T210b0SdevEmcDvfsTableS1y8gbX03 = 9, /* Samsung AA-MGCL 8Gb */
+ T210b0SdevEmcDvfsTableS1y4gb01 = 10, /* (Unused) Samsung 4Gb */
+ T210b0SdevEmcDvfsTableM1y4gb01 = 11, /* Micron WT:E 4Gb */
+ T210b0SdevEmcDvfsTableH1y4gb01 = 12, /* Hynix NEE 4Gb */
+ T210b0SdevEmcDvfsTableS1y8gb04 = 13, /* Samsung AM-MGCJ 8Gb */
+ T210b0SdevEmcDvfsTableS1z4gb01 = 14, /* Samsung AB-MGCL 4Gb */
+ T210b0SdevEmcDvfsTableH1a4gb01 = 15, /* Hynix x267 4Gb */
+ T210b0SdevEmcDvfsTableM1a4gb01 = 16, /* Micron WT:B 8Gb */
+ MtcTableIndex_Invalid = 17,
+ };
+
+ struct MtcDramIndex {
+ DramId dramId;
+ MtcTableIndex index;
+ };
+
+ const inline MtcDramIndex mtcIndexTable[] = {
+ { HOAG_4GB_HYNIX_H9HCNNNBKMMLXR_NEE, T210b0SdevEmcDvfsTableH1y4gb01, },
+ { AULA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE, T210b0SdevEmcDvfsTableH1y4gb01, },
+ { IOWA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE, T210b0SdevEmcDvfsTableH1y4gb01, },
+ { IOWA_4GB_SAMSUNG_K4U6E3S4AM_MGCJ, T210b0SdevEmcDvfsTableS4gb03, },
+ { IOWA_8GB_SAMSUNG_K4UBE3D4AM_MGCJ, T210b0SdevEmcDvfsTableS1y8gb04, },
+ { IOWA_4GB_HYNIX_H9HCNNNBKMMLHR_NME, T210b0SdevEmcDvfsTableH4gb03, },
+ { IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTE, T210b0SdevEmcDvfsTableM1y4gb01, },
+ { HOAG_4GB_SAMSUNG_K4U6E3S4AM_MGCJ, T210b0SdevEmcDvfsTableS4gb03, },
+ { HOAG_8GB_SAMSUNG_K4UBE3D4AM_MGCJ, T210b0SdevEmcDvfsTableS1y8gb04, },
+ { HOAG_4GB_HYNIX_H9HCNNNBKMMLHR_NME, T210b0SdevEmcDvfsTableH4gb03, },
+ { HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTE, T210b0SdevEmcDvfsTableM1y4gb01, },
+ { IOWA_4GB_SAMSUNG_K4U6E3S4AA_MGCL, T210b0SdevEmcDvfsTableS1y4gbX03, },
+ { IOWA_8GB_SAMSUNG_K4UBE3D4AA_MGCL, T210b0SdevEmcDvfsTableS1y8gbX03, },
+ { HOAG_4GB_SAMSUNG_K4U6E3S4AA_MGCL, T210b0SdevEmcDvfsTableS1y4gbX03, },
+ { IOWA_4GB_SAMSUNG_K4U6E3S4AB_MGCL, T210b0SdevEmcDvfsTableS1z4gb01, },
+ { HOAG_4GB_SAMSUNG_K4U6E3S4AB_MGCL, T210b0SdevEmcDvfsTableS1y8gb04, },
+ { AULA_4GB_SAMSUNG_K4U6E3S4AB_MGCL, T210b0SdevEmcDvfsTableS1y8gb04, },
+ { HOAG_8GB_SAMSUNG_K4UBE3D4AA_MGCL, T210b0SdevEmcDvfsTableS1y8gbX03, },
+ { AULA_4GB_SAMSUNG_K4U6E3S4AA_MGCL, T210b0SdevEmcDvfsTableS1y4gbX03, },
+ { IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTF, T210b0SdevEmcDvfsTableM4gb03, },
+ { HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTF, T210b0SdevEmcDvfsTableM4gb03, },
+ { AULA_4GB_MICRON_MT53E512M32D2NP_046_WTF, T210b0SdevEmcDvfsTableM4gb03, },
+ { AULA_8GB_SAMSUNG_K4UBE3D4AA_MGCL, T210b0SdevEmcDvfsTableS1y8gbX03, },
+ { IOWA_4GB_HYNIX_H54G46CYRBX267, T210b0SdevEmcDvfsTableH1a4gb01, },
+ { HOAG_4GB_HYNIX_H54G46CYRBX267, T210b0SdevEmcDvfsTableH1a4gb01, },
+ { AULA_4GB_HYNIX_H54G46CYRBX267, T210b0SdevEmcDvfsTableH1a4gb01, },
+ { IOWA_4GB_MICRON_MT53E512M32D1NP_046_WTB, T210b0SdevEmcDvfsTableM1a4gb01, },
+ { HOAG_4GB_MICRON_MT53E512M32D1NP_046_WTB, T210b0SdevEmcDvfsTableM1a4gb01, },
+ { AULA_4GB_MICRON_MT53E512M32D1NP_046_WTB, T210b0SdevEmcDvfsTableM1a4gb01, },
+ };
+
+ /*
+ 710006abfc 40 01 1f d6 br x10
+ */
+
+ /*
+ 710006ac28 a0 03 00 90 adrp x0,0x71000de000
+ 710006ac2c 00 80 16 91 add x0=>SdevEmcDvfsTableS4gb01,x0,#0x5a0
+ */
+
+ /* Br */
+ /*
+ | Z | OP | Fixed | A | M | RN | RM
+ 31 30 29 28 27 26 25 | 24 23 | 22 | 21 20 19 18 17 16 15 14 13 12 |11 | 10 | 9 8 7 6 5 | 4 3 2 1 0
+ 1 1 0 1 0 1 1 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 Rn 0 0 0 0 0
+ Z op A M Rm
+ */
+
+ /* Adrp */
+ /*
+ OP | ImmLow | | ImmHigh | RD
+ 31 | 30 29 | 28 27 26 25 24 | 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 | 4 3 2 1 0
+ */
+
+ /* ADD (immediate) */
+ /*
+ SF | OP | S | Fixed value | Sh | Imm12 | RN | RD
+ 31 | 30 | 29 | 28 27 26 25 24 23 | 22 | 21 20 19 18 17 16 15 14 13 12 11 10 | 9 8 7 6 5 | 4 3 2 1 0
+ */
+
+ constexpr u32 MtcBrAsm = 0xD61F0140;
+ constexpr u32 MtcMovAsm = 0x52800068;
+ constexpr u32 MtcAdrpAsm = 0x900003A0;
+ constexpr u32 MtcAddAsm = 0x91168000;
+
+ ALWAYS_INLINE bool MemMtcGetGetTablePatternFn(u32 *ptr) {
+ /* This builds an address that gets returned, so the register must be x0 by convention. */
+ return _asm::Ignoring(*ptr, MtcAddAsm, _asm::field::Imm12);
+ }
+
void MtcGenerateFreqTables();
Result MemFreqMtcTable(u32 *ptr);
Result MemFreqDvbTable(u32 *ptr);
Result MemFreqMax(u32 *ptr);
Result MemMtcTableAsm(u32 *ptr);
+ Result EmcVddqVolt(u32 *ptr);
}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_soc.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_soc.cpp
new file mode 100644
index 00000000..e053248f
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_soc.cpp
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) Souldbminer, Lightos and Horizon OC Contributors
+ *
+ * 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 .
+ */
+
+#include "../pcv.hpp"
+#include "pcv_mariko_soc.hpp"
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ namespace {
+
+ struct {
+ u32 *site = nullptr;
+ } busInitCache;
+
+ Result GetSocSpeedo(u32 &socSpeedo) {
+ constexpr u64 FusePhysicalAddress = 0x7000F000;
+ u64 virtualAddress = 0;
+ constexpr u64 Size = 0x1000;
+
+ u64 outSize;
+ /* TODO: use svc::QueryMemoryMapping instead. */
+ R_TRY(svcQueryMemoryMapping(&virtualAddress, &outSize, FusePhysicalAddress, Size));
+
+ constexpr u32 FuseOffset = 2048;
+ constexpr u32 SocSpeedoOffset = 0x134;
+ socSpeedo = *reinterpret_cast(virtualAddress + FuseOffset + SocSpeedoOffset);
+
+ R_SUCCEED();
+ }
+
+ u32 GetSocProcessId(u32 socSpeedo) {
+ if (socSpeedo <= 1597) {
+ return 0;
+ }
+
+ if (socSpeedo <= 1708) {
+ return 1;
+ }
+
+ /* >= 1709. */
+ return 2;
+ }
+
+ }
+
+ Result SocVoltAsm(u32 *compareSpeedos) {
+ using namespace _asm::field;
+
+ constexpr u32 VoltageScanLimit = 10;
+ /* Might actually be speedo id. */
+ u32 *writeProcessId = _asm::ScanAssembly(compareSpeedos, VoltageScanLimit, SocVoltWriteProcessIdAsm, Rd);
+ R_UNLESS(writeProcessId != nullptr, ldr::ResultInvalidSocVoltPattern());
+ u8 writeProcessIdRd = _asm::Get(*writeProcessId, Rd);
+
+ /* This writes 1050mV. */
+ u32 *writeVoltage = _asm::ScanAssembly(writeProcessId, VoltageScanLimit, SocVoltWriteVoltageAsm, Rd);
+ R_UNLESS(writeVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
+ u8 writeVoltageRd = _asm::Get(*writeVoltage, Rd);
+
+ /* A csel instruction is used to select the soc voltage limit register. */
+ /* We care about its destination register since that is used for verification. */
+ constexpr u32 VoltageSelectScanLimit = 24;
+ u32 *selectVoltage = _asm::ScanAssembly(writeVoltage, VoltageSelectScanLimit, SocVoltSelectRegisterAsm, Rd, Rn, Rm);
+ R_UNLESS(selectVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
+ /* Todo: check rm and rn? */
+ u8 selectVoltageRd = _asm::Get(*selectVoltage, Rd);
+
+ /* rdCsel is then multiplied by 1000 to convert to uV. */
+ /* This is pretty far down the function. */
+ constexpr u32 MultiplierScanLimit = 200;
+ u32 *multiplier = _asm::ScanAssembly(selectVoltage, MultiplierScanLimit, SocVoltMultiplyVoltsAsm, Rd, Rn, Rm);
+ R_UNLESS(multiplier != nullptr, ldr::ResultInvalidSocVoltPattern());
+ u8 multiplierRn = _asm::Get(*multiplier, Rn);
+ u8 multiplierRm = _asm::Get(*multiplier, Rm);
+ /* One of the two registers has to be rdCsel. */
+ R_UNLESS((multiplierRn == selectVoltageRd) || (multiplierRm == selectVoltageRd), ldr::ResultInvalidSocVoltPattern());
+ u8 multiplierRd = _asm::Get(*multiplier, Rd);
+
+ /* Subs instruction is then used to verify against absolute limit. */
+ /* NB: Rn is written into the pattern and then ignored by the comparison; only Rm is matched. */
+ u32 limitValidationPattern = _asm::Set(SocVoltValidateLimitAsm, Rn, multiplierRd);
+ u32 *limitValidation = _asm::ScanAssembly(multiplier, VoltageScanLimit, limitValidationPattern, Rd, Rn);
+ R_UNLESS(limitValidation != nullptr, ldr::ResultInvalidSocVoltPattern());
+
+ /* There is a b.gt instruction right after (checks for socVoltageCap < socVoltageMax). */
+ u32 *branchToAbort = limitValidation + 1;
+ R_UNLESS(_asm::Ignoring(*branchToAbort, SocVoltBranchToAbortAsm, Imm19), ldr::ResultInvalidSocVoltPattern());
+
+ if (!C.marikoSocVmax || C.marikoSocVmax <= 1000) {
+ R_SKIP();
+ }
+
+ /* Adjust 1598 speedo minimum to ensure it always goes down process id 0 branch. */
+ /* 2200 should be high enough :D */
+ u32 compareSpeedosPatch = _asm::Set(*compareSpeedos, Imm12, 2200);
+ PATCH_OFFSET(compareSpeedos, compareSpeedosPatch);
+
+ u32 socSpeedo = 0;
+ R_TRY(GetSocSpeedo(socSpeedo));
+
+ /* Adjust processId from 0 to [process id of switch booting this]. */
+ /* We're overwriting the orr instruction entirly. */
+ u32 processId = GetSocProcessId(socSpeedo);
+ u32 writeProcessIdPatch = _asm::Encode(_asm::op::MovzW, {Rd, writeProcessIdRd}, {Imm16, processId});
+ PATCH_OFFSET(writeProcessId, writeProcessIdPatch);
+
+ /* Adjust voltage limit. */
+ u32 voltageLimitPatch = _asm::Encode(_asm::op::MovzW, {Rd, writeVoltageRd}, {Imm16, C.marikoSocVmax});
+ PATCH_OFFSET(writeVoltage, voltageLimitPatch);
+
+ /* Branches to an abort if limits are invalid -- we patch the branch instruction with NOP. */
+ PATCH_OFFSET(branchToAbort, _asm::NopIns);
+
+ R_SUCCEED();
+ }
+
+ Result SocVoltLimit(u32 *ptr) {
+ R_UNLESS(!std::memcmp(ptr - SocVoltLimitMaxDefaultIndex, socVoltLimitArray, sizeof(socVoltLimitArray)), ldr::ResultInvalidSocVoltLimit());
+ if (!C.marikoSocVmax || C.marikoSocVmax <= SocVoltLimitOfficial) {
+ R_SKIP();
+ }
+
+ constexpr u32 Step = 25;
+ u32 maxVolt = C.marikoSocVmax;
+ if (maxVolt % Step) {
+ maxVolt = maxVolt / Step * Step; /* Round. */
+ }
+
+ u32 volt = SocVoltLimitOfficial;
+ for (u32 i = 1; i < DvfsTableEntryCount - SocVoltLimitMaxDefaultIndex && volt < maxVolt; ++i) {
+ volt += Step;
+ PATCH_OFFSET(ptr + i, volt);
+ }
+
+ R_SUCCEED();
+ }
+
+ HOOK_PAYLOAD_FN u64 BusFreqExtendImpl(SharedClkBus *bus) {
+ HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData);
+
+ u32 *table = data->busData.next;
+ bus->allowedFreqTable = table;
+ data->busData.next = table + EmcDvfsTableEntryCount;
+
+ /* Call the original function. */
+ return reinterpret_cast(data->busData.originalFnCallback)(bus);
+ }
+
+ /* Relocate and extend C2/C3Bus to avoid issues (likely buffer overflow) */
+ Result BusFreqReloc(u32 *ptr) {
+ using namespace _asm::field;
+
+ const u32 bus = _asm::Get(ptr[0], Rn);
+ R_UNLESS(_asm::IsOp(ptr[1], _asm::op::AddImm64, Rd, Rn, Imm12) && _asm::Get(ptr[1], Imm12) == 0x18 && _asm::Get(ptr[1], Rn) == bus,
+ ldr::ResultInvalidBusFreqReloc()); /* add Xcnt,Xbus,#0x18 */
+ R_UNLESS(_asm::IsOp(ptr[2], _asm::op::StrImm64, Rt, Rn, Off8) && _asm::Get(ptr[2], Off8) == 0x50 && _asm::Get(ptr[2], Rn) == bus,
+ ldr::ResultInvalidBusFreqReloc()); /* str Xrail,[Xbus,#0x50] */
+ R_UNLESS(_asm::IsOp(ptr[3], _asm::op::Bl, Imm26), ldr::ResultInvalidBusFreqReloc()); /* bl GetDvfsRailUniqueFreqList */
+
+ constexpr u32 PrologueMargin = 140;
+ u32 *functionPrologue = _asm::FindFnPrologue(ptr, PrologueMargin, nsoStart);
+ R_UNLESS(functionPrologue != nullptr, ldr::ResultInvalidBusFreqReloc());
+
+ busInitCache.site = functionPrologue;
+
+ R_SUCCEED();
+ }
+
+ /* Widen InitDram for a >32-entry EMC DVFS list. Freq array can be dropped to free 264 bytes, relocate the Soc LUT to that space */
+ Result EmcSocLutReloc(u32 *ptr) {
+ constexpr u32 Window = 48;
+
+ using namespace _asm::field;
+
+ R_UNLESS(_asm::Ignoring(ptr[1], EmcSocLutCountStoreAsm, Rt), ldr::ResultInvalidEmcSocLut()); /* str w?,[x0,#0x154] */
+
+ u32 *freqStore = _asm::ScanAssembly(ptr - Window, Window, EmcSocFreqStoreAsm, Rt); /* str x?,[x8,#0x18] */
+ u32 *voltStore = _asm::ScanAssembly(ptr - Window, Window, EmcSocVoltStoreAsm, Rt); /* str w?,[x8,#0x48] */
+ u32 *readLoad = _asm::ScanAssembly(ptr - Window, Window, EmcSocReadLoadAsm, Rt); /* ldr w?,[x9,#0x48] */
+ R_UNLESS(freqStore && voltStore && readLoad, ldr::ResultInvalidEmcSocLut());
+
+ u32 *voltBase = voltStore - 2; /* `add Xb,Xsrc,Xi,LSL#2` (a cmn sits between it and store) */
+ R_UNLESS(_asm::IsOp(*voltBase, _asm::op::AddShifted64, Rd, Rn, Rm, Imm6) && _asm::Get(*voltBase, Rd) == _asm::Get(*voltStore, Rn), ldr::ResultInvalidEmcSocLut());
+
+ /* adrp Xl ; add Xl,Xl,#off ; ... ; str Xl,[rail,#0x120] */
+ const u32 lutReg = _asm::Get(ptr[0], Rt);
+ const u32 railReg = _asm::Get(ptr[0], Rn);
+ R_UNLESS(_asm::IsOp(ptr[-3], _asm::op::Adrp, Rd, ImmAdrpHi, ImmAdrpLo) && _asm::Get(ptr[-3], Rd) == lutReg, ldr::ResultInvalidEmcSocLut());
+ R_UNLESS(_asm::IsOp(ptr[-2], _asm::op::AddImm64, Rd, Rn, Imm12) && _asm::Get(ptr[-2], Rd) == lutReg && _asm::Get(ptr[-2], Rn) == lutReg, ldr::ResultInvalidEmcSocLut());
+
+ const u32 srcBase = _asm::Get(*voltBase, Rn); /* rail ptr at +0x20 */
+ const u32 wBase = _asm::Get(*voltBase, Rd); /* base reg */
+ const u32 wIdx = _asm::Get(*voltBase, Rm); /* loop index */
+
+ PATCH_OFFSET(freqStore, _asm::NopIns); /* Unneeded */
+ PATCH_OFFSET(voltBase, _asm::Encode(_asm::op::LdrImm64, {Rt, wBase}, {Rn, srcBase}, {Off8, 0x20})); /* ldr Xb,[Xsrc,#0x20] (rail) */
+ PATCH_OFFSET(voltStore - 1, _asm::Encode(_asm::op::AddImm64, {Rd, wBase}, {Rn, wBase}, {Imm12, 0x18})); /* add Xb,Xb,#0x18 (was cmn) */
+ PATCH_OFFSET(voltStore, _asm::ToRegOffset(*voltStore, wIdx)); /* str Wv,[Xb,Xi,LSL#2] -> rail+0x18+i*4 */
+ PATCH_OFFSET(voltStore + 1, _asm::NopIns); /* Unneeded */
+
+ /* rail+0x18 as the socMinLut pointer. */
+ PATCH_OFFSET(ptr - 3, _asm::NopIns);
+ PATCH_OFFSET(ptr - 2, _asm::Encode(_asm::op::AddImm64, {Rd, lutReg}, {Rn, railReg}, {Imm12, 0x18})); /* add Xl,rail,#0x18 */
+
+ /* Drop the abort branch in case of a bad read */
+ for (u32 i = 1; i <= 4; ++i) {
+ if (_asm::IsOp(readLoad[i], _asm::op::BCond, Imm19, BCond)) {
+ PATCH_OFFSET(&readLoad[i], _asm::NopIns);
+ break;
+ }
+ }
+ R_SUCCEED();
+ }
+
+ Result EmcDvfsCountLimit(u32 *ptr) {
+ R_UNLESS(_asm::IsOp(*(ptr - 1), _asm::op::Cbz, _asm::field::Imm19, _asm::field::Rt), ldr::ResultInvalidEmcDvfsCount()); /* cbz w?, */
+ R_UNLESS(_asm::Ignoring(*(ptr + 1), _asm::Encode(_asm::op::BCond, {_asm::field::BCond, _asm::cond::Cs}), _asm::field::Imm19),
+ ldr::ResultInvalidEmcDvfsCount()); /* b.cs */
+
+ /* cmp w?,#0x21 -> cmp w?,#EmcDvfsTableEntryCount */
+ PATCH_OFFSET(ptr, _asm::Set(*ptr, _asm::field::Imm12, EmcDvfsTableEntryCount));
+ R_SUCCEED();
+ }
+
+ Result EmcRateListLimit(u32 *ptr) {
+ R_UNLESS(_asm::Ignoring(ptr[1], EmcRateCapCselAsm, _asm::field::Rd, _asm::field::Rn, _asm::field::Rm)
+ && _asm::Get(ptr[0], _asm::field::Rn) == _asm::Get(ptr[1], _asm::field::Rn),
+ ldr::ResultInvalidEmcRateList()); /* csel w?,w?,w?,lt, min(reg, 0x20) */
+ R_UNLESS(_asm::IsOp(ptr[2], _asm::op::Bl, _asm::field::Imm26), ldr::ResultInvalidEmcRateList());
+
+ /* The csel's Rm holds the 32 cap. */
+ const u32 capReg = _asm::Get(ptr[1], _asm::field::Rm);
+ const u32 capMov = _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, capReg}, {_asm::field::Imm16, 0x20}); /* movz w,#0x20 */
+
+ u32 *movPtr = nullptr;
+ for (u32 i = 1; i <= 16; ++i) {
+ if (*(ptr - i) == capMov) {
+ movPtr = ptr - i;
+ break;
+ }
+ }
+ R_UNLESS(movPtr, ldr::ResultInvalidEmcRateList());
+
+ /* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount). */
+ PATCH_OFFSET(ptr, _asm::Set(*ptr, _asm::field::Imm12, EmcDvfsTableEntryCount)); /* cmp w?,#64 */
+ PATCH_OFFSET(movPtr, _asm::Set(*movPtr, _asm::field::Imm16, EmcDvfsTableEntryCount)); /* movz w?,#64 */
+ R_SUCCEED();
+ }
+
+ Result EmcRateSessLimit(u32 *ptr) {
+ u32 movzI = 0;
+ R_UNLESS(EmcRateSessFindClamp(ptr, nullptr, nullptr, &movzI), ldr::ResultInvalidEmcRateList());
+
+ /* Reject cmd11 GetDvfsTable. */
+ for (u32 i = 1; i <= 24; ++i) {
+ const u32 w = ptr[i];
+ if (_asm::IsOp(w, _asm::op::SubImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12)
+ && _asm::Get(w, _asm::field::Rn) == _asm::reg::Fp && _asm::Get(w, _asm::field::Imm12) >= 0x20u) { /* sub x?,x29,#>=0x20 */
+ R_THROW(ldr::ResultInvalidEmcRateList());
+ }
+ }
+
+ /* mov x,x2 */
+ u32 descReg = 0xFFu;
+ for (u32 i = 1; i <= 24; ++i) {
+ if (_asm::Ignoring(ptr[i], _asm::MovReg64(0, 2), _asm::field::Rd)) { descReg = _asm::Get(ptr[i], _asm::field::Rd); break; }
+ }
+ R_UNLESS(descReg != 0xFFu, ldr::ResultInvalidEmcRateList());
+
+ /* Repoint the duplicated-imm pair */
+ u32 *adds[8]; u32 addImm[8]; u32 nAdds = 0;
+ for (u32 i = 1; i <= 24 && nAdds < 8; ++i) {
+ const u32 w = ptr[i];
+ if (_asm::IsOp(w, _asm::op::AddImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12) && _asm::Get(w, _asm::field::Rn) == _asm::reg::Sp) { /* add x?,sp,#imm12 (shift 0) */
+ adds[nAdds] = ptr + i;
+ addImm[nAdds] = _asm::Get(w, _asm::field::Imm12);
+ ++nAdds;
+ }
+ }
+ u32 patched = 0;
+ for (u32 a = 0; a < nAdds; ++a) {
+ bool dup = false;
+ for (u32 b = 0; b < nAdds; ++b) {
+ if (a != b && addImm[a] == addImm[b]) { dup = true; break; }
+ }
+ if (dup) {
+ PATCH_OFFSET(adds[a], _asm::Encode(_asm::op::LdrImm64, {_asm::field::Rt, _asm::Get(*adds[a], _asm::field::Rd)},
+ {_asm::field::Rn, descReg},
+ {_asm::field::Off8, 0})); /* ldr x?,[x] */
+ ++patched;
+ }
+ }
+ R_UNLESS(patched == 2, ldr::ResultInvalidEmcRateList());
+
+ /* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount) */
+ PATCH_OFFSET(ptr, _asm::Set(*ptr, _asm::field::Imm12, EmcDvfsTableEntryCount)); /* cmp w?,#64 */
+ PATCH_OFFSET(ptr + movzI, _asm::Set(*(ptr + movzI), _asm::field::Imm16, EmcDvfsTableEntryCount)); /* movz w?,#64 */
+ R_SUCCEED();
+ }
+
+ Result SharedClkBusInstallHooks(HookPayloadData *data) {
+ R_UNLESS(busInitCache.site != nullptr, ldr::ResultInvalidBusFreqReloc());
+
+ uintptr_t originalFn = 0;
+ R_TRY(INSTALL_IMPL_HOOK_ORIG(busInitCache.site, BusFreqExtendImpl, &originalFn));
+ data->busData.originalFnCallback = originalFn;
+ data->busData.next = reinterpret_cast(Hooks().ToVa(data->busData.table[0]));
+
+ R_SUCCEED();
+ }
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_soc.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_soc.hpp
new file mode 100644
index 00000000..04ea6792
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_soc.hpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) Souldbminer, Lightos and Horizon OC Contributors
+ *
+ * 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 .
+ */
+
+#pragma once
+
+#include "pcv_mariko.hpp"
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ extern u32 *nsoStart;
+
+ /* Movz */
+ /*
+ SF | OPC | HW | Imm16 | RD
+ 31 | 30 29 28 27 26 25 24 23 | 22 21 | 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 | 4 3 2 1 0
+ */
+ constexpr u32 SocVoltCompareSpeedoAsm = 0x7118FAFF; /* subs imm, compares to >=1598 max speedo and then goes down process id 1 route. */
+ constexpr u32 SocVoltWriteProcessIdAsm = 0x2A1F03F4; /* orr, writes id 0. */
+ constexpr u32 SocVoltWriteVoltageAsm = 0x52808358; /* Movz imm, writes 1050mV. */
+ constexpr u32 SocVoltSelectRegisterAsm = 0x1A9A3118; /* Csel, selects the voltage -- we need the register of this. */
+ constexpr u32 SocVoltMultiplyVoltsAsm = 0x1B1A7F0B; /* Mul, converts from mV -> uV */
+ constexpr u32 SocVoltValidateLimitAsm = 0x6B0A017F; /* Subs, checks limits */
+ constexpr u32 SocVoltBranchToAbortAsm = 0x540020AC; /* B.ge Branches to abort if limits are invalid. */
+
+ ALWAYS_INLINE bool SocVoltPatternFn(u32 *ptr) {
+ return _asm::Ignoring(*ptr, SocVoltCompareSpeedoAsm, _asm::field::Rd);
+ }
+
+ constexpr u32 SocVoltLimitOfficial = 1050;
+ constexpr u32 SocVoltLimitMaxDefaultIndex = 17;
+ static const u32 socVoltLimitArray[DvfsTableEntryCount] = { 637, 650, 675, 700, 725, 750, 775, 800, 825, 850, 875, 900, 925, 950, 975, 1000, 1025, 1050, };
+
+ constexpr u32 EmcCountCmpAsm = 0x7100851F; /* cmp w?,#0x21 (subs wzr,w?,#0x21) */
+
+ /*
+ str ,[,#0x120] ; volt-array pointer
+ str w?, [,#0x154] ; num_freqs
+ */
+ constexpr u32 EmcSocLutPtrStoreAsm = 0xF9009009; /* str x?,[x0,#0x120] (anchor) */
+ constexpr u32 EmcSocLutCountStoreAsm = 0xB9015408; /* str w?,[x0,#0x154] (anchor) */
+ constexpr u32 EmcSocFreqStoreAsm = 0xF9000D00; /* str x?,[x8,#0x18] */
+ constexpr u32 EmcSocVoltStoreAsm = 0xB9004900; /* str w?,[x8,#0x48] (socMinLut[i]) */
+ constexpr u32 EmcSocReadLoadAsm = 0xB9404929; /* ldr w?,[x9,#0x48] (socMinLut) */
+
+ inline bool EmcDvfsCountPatternFn(u32 *ptr) {
+ return _asm::Ignoring(*ptr, EmcCountCmpAsm, _asm::field::Rd);
+ }
+
+ inline bool EmcSocLutPatternFn(u32 *ptr) {
+ return _asm::Ignoring(*ptr, EmcSocLutPtrStoreAsm, _asm::field::Rt); /* str x?,[x0,#0x120] */
+ }
+
+ /*
+ mov w?,#0x20 ; the 32 cap
+ cmp w?,#0x20
+ csel w?,w?,w?,lt ; w? = min(maxCount, 32)
+ bl TegraGetEmcDvfsFreqTable
+
+ cmp w?,#0x20 ; (maxCount)
+ csel w?,