Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d8ca16e22 | |||
| 71407da80e | |||
|
|
301c5f675f | ||
| 8a930448eb | |||
|
|
7960dd9be6 | ||
|
|
a71c88942d | ||
|
|
603f9d7ca2 | ||
|
|
b0d111ad17 | ||
|
|
cd7c211cdf | ||
|
|
3714aecf32 | ||
|
|
2a10aa7cc5 | ||
|
|
47bdc26a1a | ||
|
|
192fc41f1a | ||
|
|
73eae055c0 | ||
|
|
044d7b69e0 | ||
|
|
59ea651360 | ||
|
|
12597f8aa3 | ||
|
|
4143857358 | ||
|
|
28e8ca37e1 | ||
|
|
52424a86e3 | ||
|
|
56495e9b64 | ||
|
|
151d8584e4 | ||
|
|
b9bf6dd719 | ||
|
|
0befcd2d75 | ||
|
|
3d5dbf77ea | ||
|
|
ab1fde543d | ||
|
|
66cfbe0d0b | ||
|
|
d92aaff7c1 | ||
|
|
dac56d58b5 | ||
|
|
987e37f4ab | ||
|
|
73dbd98d5b |
10
CHANGELOG.md
Normal file
10
CHANGELOG.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Horizon OC 2.6.0
|
||||
- loader:
|
||||
- Update for Atmosphere 1.12.1
|
||||
- Add 2805MHz CPU and 1235mV VMAX (use with caution!)
|
||||
- Make JEDEC step mode default
|
||||
- hoc-clk:
|
||||
- Fix bugs in GM20B driver
|
||||
- hocate:
|
||||
- update to 6.5.4
|
||||
**Full Changelog**: https://github.com/Horizon-OC/Horizon-OC/compare/3.0.0-test1...2.6.0-rel
|
||||
@@ -143,7 +143,8 @@ Refer to COMPILATION.md
|
||||
* 665
|
||||
|
||||
### 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)
|
||||
|
||||
1
Source/Atmosphere-Patches/diff/README.MD
Normal file
1
Source/Atmosphere-Patches/diff/README.MD
Normal file
@@ -0,0 +1 @@
|
||||
This contains a diff all modifications done to exosphere
|
||||
57
Source/Atmosphere-Patches/diff/secmon_memory_layout.diff
Normal file
57
Source/Atmosphere-Patches/diff/secmon_memory_layout.diff
Normal file
@@ -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__) \
|
||||
29
Source/Atmosphere-Patches/diff/secmon_setup.diff
Normal file
29
Source/Atmosphere-Patches/diff/secmon_setup.diff
Normal file
@@ -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;
|
||||
2
Source/Atmosphere-Patches/diff/secmon_smc_handler.diff
Normal file
2
Source/Atmosphere-Patches/diff/secmon_smc_handler.diff
Normal file
@@ -0,0 +1,2 @@
|
||||
154a155
|
||||
> { 0xF0000005, Restriction_None, SmcShowError },
|
||||
@@ -0,0 +1,9 @@
|
||||
160c160
|
||||
< /* Validate that the bpmp is appropriately halted. */
|
||||
---
|
||||
> /* Horizon-OC: Halt BPMP here as hoc-clk may be running it, and if it's running there will be a panic. */
|
||||
161a162,165
|
||||
> reg::Write(FLOW_CTLR + FLOW_CTLR_HALT_COP_EVENTS, FLOW_REG_BITS_ENUM (HALT_COP_EVENTS_MODE, FLOW_MODE_STOP),
|
||||
> FLOW_REG_BITS_ENUM_SEL(HALT_COP_EVENTS_JTAG, jtag, ENABLED, DISABLED));
|
||||
>
|
||||
> /* Validate that the bpmp is appropriately halted. */
|
||||
@@ -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, },
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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__
|
||||
1040
Source/Atmosphere-Patches/secmon_emc01_access_table_data.inc
Normal file
1040
Source/Atmosphere-Patches/secmon_emc01_access_table_data.inc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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__) \
|
||||
|
||||
1290
Source/Atmosphere-Patches/secmon_setup.cpp
Normal file
1290
Source/Atmosphere-Patches/secmon_setup.cpp
Normal file
File diff suppressed because it is too large
Load Diff
571
Source/Atmosphere-Patches/secmon_smc_power_management.cpp
Normal file
571
Source/Atmosphere-Patches/secmon_smc_power_management.cpp
Normal file
@@ -0,0 +1,571 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <exosphere.hpp>
|
||||
#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)));
|
||||
|
||||
/* Horizon-OC: Halt BPMP here as hoc-clk may be running it, and if it's running there will be a panic. */
|
||||
const bool jtag = IsJtagEnabled();
|
||||
reg::Write(FLOW_CTLR + FLOW_CTLR_HALT_COP_EVENTS, FLOW_REG_BITS_ENUM (HALT_COP_EVENTS_MODE, FLOW_MODE_STOP),
|
||||
FLOW_REG_BITS_ENUM_SEL(HALT_COP_EVENTS_JTAG, jtag, ENABLED, DISABLED));
|
||||
|
||||
/* Validate that the bpmp is appropriately halted. */
|
||||
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<hw::DataCacheLineSize, se::AesBlockSize> key_source;
|
||||
GenerateCryptographicallyRandomBytes(key_source, se::AesBlockSize);
|
||||
|
||||
const u32 * const key_source_32 = reinterpret_cast<const u32 *>(static_cast<u8 *>(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<se::Context>();
|
||||
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<u8>() + MemoryRegionVirtualTzramVolatileData.GetSize() + MemoryRegionVirtualTzramVolatileStack.GetSize();
|
||||
void * const tzram_save_dst = MemoryRegionVirtualIramSc7Work.GetPointer<void>();
|
||||
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<void>();
|
||||
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>();
|
||||
void * const debug_code_dst = MemoryRegionVirtualDramDebugDataStore.GetPointer<void>();
|
||||
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<u32>(MemoryRegionPhysicalIramSc7Firmware.GetAddress() + 0x0);
|
||||
constexpr const u32 Sc7FirmwarePanicVector = static_cast<u32>(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<void>();
|
||||
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<u32>(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<SuspendCpuPowerState::StateType>();
|
||||
const auto state_id = power_state.Get<SuspendCpuPowerState::StateId>();
|
||||
|
||||
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<void *>(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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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. */
|
||||
|
||||
@@ -126,8 +126,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 +180,8 @@ namespace ams::ldr {
|
||||
return (MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Application;
|
||||
}
|
||||
|
||||
Npdm::AddressSpaceType GetAddressSpaceType(const Meta *meta) {
|
||||
return static_cast<Npdm::AddressSpaceType>((meta->npdm->flags & Npdm::MetaFlag_AddressSpaceTypeMask) >> Npdm::MetaFlag_AddressSpaceTypeShift);
|
||||
Npdm::ProcessAddressSpace GetProcessAddressSpace(const Meta *meta) {
|
||||
return static_cast<Npdm::ProcessAddressSpace>((meta->npdm->flags0 & Npdm::MetaFlag0_ProcessAddressSpaceMask) >> Npdm::MetaFlag0_ProcessAddressSpaceShift);
|
||||
}
|
||||
|
||||
Acid::PoolPartition GetPoolPartition(const Meta *meta) {
|
||||
@@ -225,7 +224,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 +263,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 +271,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 +298,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 +393,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 +408,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 +469,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 +489,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 +523,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 +533,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());
|
||||
@@ -567,6 +577,11 @@ namespace ams::ldr {
|
||||
out->nso_size[i] = std::max(out->nso_size[i], rw_end);
|
||||
out->nso_size[i] += static_cast<size_t>(ctx.headers[i].bss_size);
|
||||
|
||||
/* Reserve hook arena memory past pcv's bss. */
|
||||
if (g_is_pcv && i == ctx.main_nso_idx) {
|
||||
out->nso_size[i] = util::AlignUp(out->nso_size[i], os::MemoryPageSize) + hoc::pcv::PcvDataArenaSize;
|
||||
}
|
||||
|
||||
const size_t aligned_up_size = util::AlignUp(out->nso_size[i], os::MemoryPageSize) & (AutoLoadModuleSizeMax - 1);
|
||||
R_UNLESS(out->nso_size[i] <= aligned_up_size, ldr::ResultInvalidNso());
|
||||
R_UNLESS(aligned_up_size > 0, ldr::ResultInvalidNso());
|
||||
@@ -591,25 +606,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 {
|
||||
@@ -622,7 +641,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;
|
||||
}
|
||||
|
||||
@@ -644,10 +663,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<s32>::max(), ldr::ResultInvalidNso());
|
||||
@@ -657,19 +679,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<void *>(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<const void *>(load_address);
|
||||
|
||||
if (is_zstd) {
|
||||
bool decompressed = util::DecompressZstdForLoader(reinterpret_cast<void *>(g_zstd_dctx_workspace), ZstdDctxWorkspaceSize, reinterpret_cast<void *>(map_base), static_cast<size_t>(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<void *>(g_zstd_dctx_workspace), sizeof(g_zstd_dctx_workspace), reinterpret_cast<void *>(map_base), static_cast<size_t>(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<void *>(map_base), segment_size, compressed_data_buf, file_size) == static_cast<int>(segment_size));
|
||||
R_UNLESS(decompressed, ldr::ResultInvalidNso());
|
||||
R_UNLESS_LOG(decompressed, ldr::ResultInvalidNso(), "[ldr] Failed to decompress segment with lz4!\n");
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
@@ -684,12 +706,15 @@ namespace ams::ldr {
|
||||
crypto::GenerateSha256(hash, sizeof(hash),
|
||||
reinterpret_cast<void *>(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<size_t>(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;
|
||||
|
||||
/* Map and read data from file. */
|
||||
{
|
||||
@@ -703,11 +728,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<size_t>(nso_header->text_dst_offset) + static_cast<size_t>(nso_header->text_size);
|
||||
@@ -730,7 +755,15 @@ namespace ams::ldr {
|
||||
|
||||
/* Apply PCV and PTM patches */
|
||||
if (g_is_pcv) {
|
||||
hoc::pcv::Patch(map_address, nso_size);
|
||||
const size_t text_end = static_cast<size_t>(nso_header->text_size);
|
||||
const size_t rx_end = util::AlignUp(text_end, os::MemoryPageSize);
|
||||
const size_t ro_start = static_cast<size_t>(nso_header->ro_dst_offset);
|
||||
const size_t cave_end = (rx_end < ro_start) ? rx_end : ro_start;
|
||||
const uintptr_t cave = map_address + text_end;
|
||||
const size_t cave_size = (cave_end > text_end) ? (cave_end - text_end) : 0;
|
||||
|
||||
/* module_size is used rather than nso_size to exclude the extra data section. */
|
||||
hoc::pcv::Patch(map_address, module_size, cave, cave_size, nso_address, arena_size ? (map_address + module_size) : 0);
|
||||
}
|
||||
|
||||
if (g_is_ptm) {
|
||||
@@ -741,7 +774,7 @@ namespace ams::ldr {
|
||||
/* Set permissions. */
|
||||
const size_t text_size = util::AlignUp(nso_header->text_size, os::MemoryPageSize);
|
||||
const size_t ro_size = util::AlignUp(nso_header->ro_size, os::MemoryPageSize);
|
||||
const size_t rw_size = util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize);
|
||||
const size_t rw_size = util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize) + arena_size;
|
||||
if (text_size) {
|
||||
const bool prevent_code_reads = (nso_header->flags & NsoHeader::Flag_PreventCodeReads);
|
||||
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->text_dst_offset, text_size, prevent_code_reads ? os::MemoryPermission_ExecuteOnly : os::MemoryPermission_ReadExecute));
|
||||
@@ -762,14 +795,15 @@ namespace ams::ldr {
|
||||
|
||||
for (int i = 0; i < ctx.nso_count; i++) {
|
||||
const NsoIndex nso_idx = static_cast<NsoIndex>(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));
|
||||
}
|
||||
@@ -798,11 +832,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 */
|
||||
if (g_is_pcv && ctx.main_nso_idx >= 0) {
|
||||
g_nso_headers[ctx.main_nso_idx].bss_size += static_cast<u32>(hoc::HocPcvScratchSize);
|
||||
}
|
||||
|
||||
/* Get CreateProcessParameter. */
|
||||
svc::CreateProcessParameter param;
|
||||
R_TRY(GetCreateProcessParameter(std::addressof(param), meta, flags, resource_limit));
|
||||
@@ -817,7 +846,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));
|
||||
@@ -875,8 +904,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++) {
|
||||
@@ -887,7 +916,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. */
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -38,11 +38,11 @@ volatile CustomizeTable C = {
|
||||
.commonEmcMemVolt = 1175000, /* LPDDR4(X) JEDEC Specification */
|
||||
.eristaEmcMaxClock = 1600000, /* Maximum HB-MGCH ram rating */
|
||||
|
||||
/* Available: 33MHz step rate, 66MHz step rate, 100MHz step rate, 133MHz step rate and jedec. */
|
||||
/* Available: 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). */
|
||||
.marikoEmcMaxClock = 2133000, /* 1866MHz @ 1866tWRL is guaranteed to work on all Mariko units */
|
||||
.marikoEmcVddqVolt = 600000,
|
||||
|
||||
.emcDvbShift = 0,
|
||||
@@ -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 */
|
||||
@@ -318,6 +319,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 +348,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 +377,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 +406,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 = {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#pragma once
|
||||
|
||||
#define CUST_REV 7
|
||||
#define KIP_VERSION 300
|
||||
#define KIP_VERSION 260
|
||||
|
||||
#include "oc_common.hpp"
|
||||
#include "pcv/pcv_common.hpp"
|
||||
@@ -40,7 +40,6 @@ enum StepMode: u32 {
|
||||
StepMode_100MHz = 1,
|
||||
StepMode_Jedec = 2,
|
||||
StepMode_133MHz = 3,
|
||||
StepMode_33MHz = 4,
|
||||
};
|
||||
|
||||
enum ReadLatency: u32 {
|
||||
|
||||
@@ -31,34 +31,35 @@
|
||||
*(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(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(HookArenaOutOfMemory, 1019);
|
||||
R_DEFINE_ERROR_RESULT(HookPayloadTooLarge, 1020);
|
||||
R_DEFINE_ERROR_RESULT(HookRelocationUnsupported, 1021);
|
||||
R_DEFINE_ERROR_RESULT(HookSiteInvalid, 1022);
|
||||
R_DEFINE_ERROR_RESULT(HookPayloadEscapes, 1023);
|
||||
R_DEFINE_ERROR_RESULT(HookDataOutOfMemory, 1024);
|
||||
R_DEFINE_ERROR_RESULT(HookUnavailable, 1025);
|
||||
}
|
||||
|
||||
namespace ams::ldr::hoc {
|
||||
/* Extra pcv .bss for 64LUT */
|
||||
constexpr size_t HocPcvScratchSize = 0x1000;
|
||||
|
||||
template<typename Pointer>
|
||||
struct PatcherEntry {
|
||||
using patternFn = bool(*)(Pointer *ptr);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
|
||||
#include "pcv.hpp"
|
||||
#include "pcv_hook.hpp"
|
||||
|
||||
namespace ams::ldr::hoc::pcv {
|
||||
|
||||
@@ -27,8 +28,8 @@ namespace ams::ldr::hoc::pcv {
|
||||
R_UNLESS(entry->freq == entry->vco_max, ldr::ResultInvalidMemPllmEntry());
|
||||
|
||||
// Double the max clk simply
|
||||
u32 max_clk = entry->freq * 2;
|
||||
entry->freq = max_clk;
|
||||
u32 max_clk = entry->freq * 2;
|
||||
entry->freq = max_clk;
|
||||
entry->vco_max = max_clk;
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -41,7 +42,7 @@ namespace ams::ldr::hoc::pcv {
|
||||
};
|
||||
|
||||
constexpr u32 uv_step = 12'500;
|
||||
constexpr u32 uv_min = 600'000;
|
||||
constexpr u32 uv_min = 600'000;
|
||||
|
||||
auto validator = [](regulator *entry) {
|
||||
R_UNLESS(entry->id == 1, ldr::ResultInvalidRegulatorEntry());
|
||||
@@ -67,7 +68,7 @@ namespace ams::ldr::hoc::pcv {
|
||||
}
|
||||
|
||||
if (emc_uv % uv_step) {
|
||||
emc_uv = emc_uv / uv_step * uv_step; // rounding
|
||||
emc_uv = emc_uv / uv_step * uv_step; // rounding
|
||||
}
|
||||
|
||||
PATCH_OFFSET(ptr, emc_uv);
|
||||
@@ -102,11 +103,11 @@ namespace ams::ldr::hoc::pcv {
|
||||
|
||||
u32 eristaCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.eristaCpuDvfsTable)->freq);
|
||||
u32 marikoCpuDvfsMaxFreq;
|
||||
if (C.marikoCpuUVHigh) {
|
||||
marikoCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoCpuDvfsTableSLT)->freq);
|
||||
} else {
|
||||
marikoCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq);
|
||||
}
|
||||
if (C.marikoCpuUVHigh) {
|
||||
marikoCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoCpuDvfsTableSLT)->freq);
|
||||
} else {
|
||||
marikoCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq);
|
||||
}
|
||||
u32 eristaGpuDvfsMaxFreq;
|
||||
switch (C.eristaGpuUV) {
|
||||
case 0:
|
||||
@@ -147,11 +148,11 @@ namespace ams::ldr::hoc::pcv {
|
||||
|
||||
Validator validators[] = {
|
||||
{ C.eristaCpuBoostClock, 1020'000, 2397'000, panic::Cpu, true },
|
||||
{ C.marikoCpuBoostClock, 1020'000, 2703'000, panic::Cpu, true },
|
||||
{ C.marikoCpuBoostClock, 1020'000, 2805'000, panic::Cpu, true },
|
||||
{ C.eristaCpuMaxVolt, 1000, 1260, panic::Cpu, },
|
||||
{ C.marikoCpuMaxVolt, 1000, 1200, panic::Cpu, },
|
||||
{ C.marikoCpuMaxVolt, 1000, 1235, panic::Cpu, },
|
||||
{ eristaCpuDvfsMaxFreq, 1785'000, 2397'000, panic::Cpu, },
|
||||
{ marikoCpuDvfsMaxFreq, 1785'000, 2703'000, panic::Cpu, },
|
||||
{ marikoCpuDvfsMaxFreq, 1785'000, 2805'000, panic::Cpu, },
|
||||
{ C.commonEmcMemVolt, 912'500, 1350'000, panic::Emc, }, /* Official vmax for the RAMs is 1400-1500mV */
|
||||
{ C.eristaEmcMaxClock, 1600'000, 2600'000, panic::Emc, },
|
||||
{ C.marikoEmcMaxClock, 1600'000, 3500'000, panic::Emc, },
|
||||
@@ -171,14 +172,16 @@ namespace ams::ldr::hoc::pcv {
|
||||
}
|
||||
|
||||
void WriteKipLoadToIram() {
|
||||
const u32 hocMagic = 0x686F634D;
|
||||
const u32 hocMagic = 0x686F634D;
|
||||
constexpr uintptr_t LoadMagicAddress = 0x4003DC00; /* Should be a pretty safe address. */
|
||||
R_DISCARD(SmcCopyToIram(LoadMagicAddress, &hocMagic, sizeof(hocMagic)));
|
||||
}
|
||||
|
||||
void Patch(uintptr_t mapped_nso, size_t nso_size) {
|
||||
void Patch(uintptr_t mapped_nso, size_t nso_size, uintptr_t cave, size_t cave_size, uintptr_t nso_address, uintptr_t data_arena) {
|
||||
SafetyCheck();
|
||||
|
||||
Hooks().Initialize(mapped_nso, nso_address, cave, cave_size, data_arena);
|
||||
|
||||
bool isMariko = (spl::GetSocType() == spl::SocType_Mariko);
|
||||
if (isMariko) {
|
||||
mariko::Patch(mapped_nso, nso_size);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "pcv_common.hpp"
|
||||
#include "pcv_erista.hpp"
|
||||
#include "pcv_mariko.hpp"
|
||||
#include "pcv_hook.hpp"
|
||||
|
||||
namespace ams::ldr::hoc::pcv {
|
||||
|
||||
@@ -36,7 +37,8 @@ namespace ams::ldr::hoc::pcv {
|
||||
return true;
|
||||
};
|
||||
|
||||
template <bool isMariko> Result CpuFreqCvbTable(u32 *ptr) {
|
||||
template <bool isMariko>
|
||||
Result CpuFreqCvbTable(u32 *ptr) {
|
||||
cvb_entry_t *default_table = isMariko ? (cvb_entry_t *)(&mariko::CpuCvbTableDefault) : (cvb_entry_t *)(&erista::CpuCvbTableDefault);
|
||||
cvb_entry_t *customize_table = nullptr;
|
||||
|
||||
@@ -111,7 +113,8 @@ namespace ams::ldr::hoc::pcv {
|
||||
PATCH_OFFSET(&(entry->cvb_pll_param.c5), 0);
|
||||
}
|
||||
|
||||
template <bool isMariko> Result GpuFreqCvbTable(u32 *ptr) {
|
||||
template <bool isMariko>
|
||||
Result GpuFreqCvbTable(u32 *ptr) {
|
||||
cvb_entry_t *default_table = isMariko ? (cvb_entry_t *)(&mariko::GpuCvbTableDefault) : (cvb_entry_t *)(&erista::GpuCvbTableDefault);
|
||||
cvb_entry_t *customize_table;
|
||||
if (isMariko) {
|
||||
@@ -134,7 +137,7 @@ namespace ams::ldr::hoc::pcv {
|
||||
default:
|
||||
customize_table = const_cast<cvb_entry_t *>(C.marikoGpuDvfsTableHiOPT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (C.eristaGpuUV) {
|
||||
case 0:
|
||||
@@ -149,7 +152,7 @@ namespace ams::ldr::hoc::pcv {
|
||||
default:
|
||||
customize_table = const_cast<cvb_entry_t *>(C.eristaGpuDvfsTable);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t default_entry_count = GetDvfsTableEntryCount(default_table);
|
||||
@@ -200,6 +203,6 @@ namespace ams::ldr::hoc::pcv {
|
||||
Result MemVoltHandler(u32 *ptr); // Used for Erista MEM Vdd2 + EMC Vddq or Mariko MEM Vdd2
|
||||
|
||||
void SafetyCheck();
|
||||
void Patch(uintptr_t mapped_nso, size_t nso_size);
|
||||
void Patch(uintptr_t mapped_nso, size_t nso_size, uintptr_t cave, size_t cave_size, uintptr_t nso_address, uintptr_t data_arena);
|
||||
|
||||
}
|
||||
|
||||
@@ -44,15 +44,6 @@ namespace ams::ldr::hoc::pcv {
|
||||
return ins & ((1 << 5) - 1);
|
||||
};
|
||||
|
||||
/* Rn (bits 9:5) and Rm (bits 20:16) to get registers. */
|
||||
inline auto AsmGetRn = [](u32 ins) -> u32 { return (ins >> 5) & 0x1Fu; };
|
||||
inline auto AsmGetRm = [](u32 ins) -> u32 { return (ins >> 16) & 0x1Fu; };
|
||||
|
||||
/* Add (shifted register), 64-bit: sf=1 op=0 S=0 01011 shift(2) 0 Rm imm6 Rn Rd. */
|
||||
inline auto AsmIsAddShiftedReg64 = [](u32 ins) {
|
||||
return (ins & 0xFF200000u) == 0x8B000000u;
|
||||
};
|
||||
|
||||
inline auto asm_set_rd = [](u32 ins, u8 rd) {
|
||||
return (ins & 0xFFFFFFE0) | (rd & 0x1F);
|
||||
};
|
||||
@@ -103,16 +94,6 @@ namespace ams::ldr::hoc::pcv {
|
||||
return static_cast<uintptr_t>(static_cast<s64>(pc) + imm);
|
||||
};
|
||||
|
||||
/* adrp Rd, target */
|
||||
inline auto AsmMakeAdrp = [](uintptr_t pc, uintptr_t target, u32 rd) -> u32 {
|
||||
const s64 delta = static_cast<s64>(target & ~static_cast<uintptr_t>(0xFFF))
|
||||
- static_cast<s64>(pc & ~static_cast<uintptr_t>(0xFFF));
|
||||
const u32 imm = static_cast<u32>((delta >> 12) & 0x1FFFFF); /* 21-bit page */
|
||||
const u32 immlo = imm & 0x3;
|
||||
const u32 immhi = (imm >> 2) & 0x7FFFF;
|
||||
return 0x90000000u | (immlo << 29) | (immhi << 5) | (rd & 0x1Fu);
|
||||
};
|
||||
|
||||
inline auto AsmSetAdrTarget = [](u32 ins, uintptr_t pc, uintptr_t target) -> u32 {
|
||||
const s64 delta = static_cast<s64>(target) - static_cast<s64>(pc);
|
||||
const u32 immlo = static_cast<u32>(delta & 0x3);
|
||||
@@ -120,76 +101,6 @@ namespace ams::ldr::hoc::pcv {
|
||||
return (ins & ~((0x3u << 29) | (0x7FFFFu << 5))) | (immlo << 29) | (immhi << 5);
|
||||
};
|
||||
|
||||
/* adrp: bit31=1, bits 28:24 = 10000. */
|
||||
inline auto AsmIsAdrp = [](u32 ins) -> bool {
|
||||
return (ins & 0x9F000000u) == 0x90000000u;
|
||||
};
|
||||
|
||||
inline auto AsmAdrpPageOffset = [](u32 ins) -> s64 {
|
||||
s64 imm = static_cast<s64>((((ins >> 5) & 0x7FFFFu) << 2) | ((ins >> 29) & 0x3u));
|
||||
imm = (imm << 43) >> 43; /* sign-extend the 21-bit immediate */
|
||||
return imm << 12;
|
||||
};
|
||||
|
||||
/* add (immediate), 64-bit: sf=1 op=0 S=0 100010 sh imm12 Rn Rd. */
|
||||
inline auto AsmIsAddImm64 = [](u32 ins) -> bool {
|
||||
return (ins & 0xFF800000u) == 0x91000000u;
|
||||
};
|
||||
|
||||
inline auto AsmGetImm12 = [](u32 ins) -> u32 {
|
||||
return (ins >> 10) & 0xFFFu;
|
||||
};
|
||||
|
||||
inline auto AsmMakeAddImm64 = [](u32 rd, u32 rn, u32 imm12) -> u32 {
|
||||
return 0x91000000u | ((imm12 & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rd & 0x1Fu);
|
||||
};
|
||||
|
||||
/* movz Wd,#imm16 (no shift). */
|
||||
inline auto AsmMakeMovzW = [](u32 rd, u16 imm16) -> u32 {
|
||||
return 0x52800000u | (static_cast<u32>(imm16) << 5) | (rd & 0x1Fu);
|
||||
};
|
||||
|
||||
/* mov Xd,Xm == orr Xd,XZR,Xm. */
|
||||
inline auto AsmMakeMovReg = [](u32 rd, u32 rm) -> u32 {
|
||||
return 0xAA0003E0u | ((rm & 0x1Fu) << 16) | (rd & 0x1Fu);
|
||||
};
|
||||
|
||||
/* ldr Xt,[Xn,#byteOff] */
|
||||
inline auto AsmMakeLdrImm64 = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
|
||||
return 0xF9400000u | (((byteOff / 8u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
|
||||
};
|
||||
|
||||
/* mov Xd,X<rm> == orr Xd,XZR,X<rm> (matches any Rd). */
|
||||
inline auto AsmIsMovReg = [](u32 ins, u32 rm) -> bool {
|
||||
return (ins & 0xFFFFFFE0u) == (0xAA0003E0u | ((rm & 0x1Fu) << 16));
|
||||
};
|
||||
|
||||
/* add Xd,sp,#imm12 (shift 0). */
|
||||
inline auto AsmIsAddSpImm = [](u32 ins) -> bool {
|
||||
return (ins & 0xFFC003E0u) == 0x910003E0u;
|
||||
};
|
||||
|
||||
/* sub Xd,x29,#imm12 (shift 0). */
|
||||
inline auto AsmIsSubX29Imm = [](u32 ins) -> bool {
|
||||
return (ins & 0xFFC003E0u) == 0xD10003A0u;
|
||||
};
|
||||
|
||||
inline auto AsmIsB = [](u32 ins) -> bool { return (ins & 0xFC000000u) == 0x14000000u; }; /* b */
|
||||
inline auto AsmIsBl = [](u32 ins) -> bool { return (ins & 0xFC000000u) == 0x94000000u; }; /* bl */
|
||||
inline auto AsmIsBCond = [](u32 ins) -> bool { return (ins & 0xFF000010u) == 0x54000000u; }; /* b.c */
|
||||
|
||||
/* Byte target address of a b/bl at pc. */
|
||||
inline auto AsmBranchTarget = [](u32 ins, uintptr_t pc) -> uintptr_t {
|
||||
s64 off = static_cast<s64>((ins & 0x03FFFFFFu) << 2);
|
||||
off = (off << 36) >> 36; /* sign-extend the 28-bit branch offset */
|
||||
return static_cast<uintptr_t>(static_cast<s64>(pc) + off);
|
||||
};
|
||||
|
||||
/* Rewrite a scaled immediate-offset load/store (`op Wt,[Xn,#imm]`) into its register-offset form */
|
||||
inline auto AsmSetLdStRegOffset = [](u32 ldstImm, u32 rm) -> u32 {
|
||||
return (ldstImm & 0xC0C003FFu) | 0x38207800u | ((rm & 0x1Fu) << 16);
|
||||
};
|
||||
|
||||
inline auto AsmIsLdpX = [](u32 ins) {
|
||||
return (ins & 0xFE400000u) == 0xA8400000u;
|
||||
};
|
||||
@@ -203,7 +114,6 @@ namespace ams::ldr::hoc::pcv {
|
||||
|
||||
bool secondMatch = (ins2 & StpRegsImmMask) == (cmp2 & StpRegsImmMask);
|
||||
|
||||
|
||||
constexpr u32 MovMask = ~((1u << 5) - 1u);
|
||||
|
||||
bool thirdMatch = (ins3 & MovMask) == (cmp3 & MovMask);
|
||||
@@ -277,4 +187,25 @@ namespace ams::ldr::hoc::pcv {
|
||||
return (ins1 & ClearImm19) == (ins2 & ClearImm19);
|
||||
};
|
||||
|
||||
inline bool AsmIsFramePush(u32 ins) {
|
||||
constexpr u32 FramePushMask = 0xFFC07FFF;
|
||||
constexpr u32 FramePushValue = 0xA9807BFD;
|
||||
return (ins & FramePushMask) == FramePushValue;
|
||||
}
|
||||
|
||||
inline u32 *FindFnPrologue(u32 *ptr, u32 margin, u32 *nsoStart) {
|
||||
for (u32 i = 0; i <= margin; ++i) {
|
||||
u32 *candidate = ptr - i;
|
||||
if (candidate < nsoStart) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (AsmIsFramePush(*candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -173,16 +173,6 @@ namespace ams::ldr::hoc::pcv {
|
||||
constexpr size_t DvfsTableEntryCount = 32;
|
||||
constexpr size_t DvfsTableEntryLimit = DvfsTableEntryCount - 1;
|
||||
|
||||
// EMC-only limit.
|
||||
constexpr size_t EmcDvfsTableEntryCount = 64;
|
||||
constexpr size_t EmcDvfsTableEntryLimit = EmcDvfsTableEntryCount - 1;
|
||||
|
||||
// The pcv SoC-voltage DVB table is a fixed 32-entry region. (it doesn't need to be larger)
|
||||
constexpr size_t DvbTableCapacity = 32;
|
||||
|
||||
// extra .bss location, 0 until Patch() runs.
|
||||
inline uintptr_t g_pcv_scratch = 0;
|
||||
|
||||
template<typename T>
|
||||
size_t GetDvfsTableEntryCount(T *table_head) {
|
||||
using NT = std::remove_const_t<std::remove_volatile_t<T>>;
|
||||
|
||||
@@ -27,9 +27,25 @@
|
||||
|
||||
namespace ams::ldr::hoc::pcv::erista {
|
||||
|
||||
std::vector<u32> newEmcList;
|
||||
u32 *nsoStart;
|
||||
u32 *nsoEnd;
|
||||
struct HookPayloadData {
|
||||
struct {
|
||||
EristaMtcTable *mtcTable;
|
||||
u32 mtcCount;
|
||||
} mtcTableAsm;
|
||||
};
|
||||
DEFINE_HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData);
|
||||
|
||||
namespace {
|
||||
std::vector<u32> newEmcList;
|
||||
u32 *nsoStart;
|
||||
u32 *nsoEnd;
|
||||
|
||||
struct {
|
||||
u32 *getEristaMtcTableFnSite = nullptr;
|
||||
EristaMtcTable *mtcTable = nullptr;
|
||||
bool foundMtcTablePattern = false;
|
||||
} getMtcTableCache;
|
||||
}
|
||||
|
||||
Result CpuVoltDvfs(u32 *ptr) {
|
||||
if (std::memcmp(ptr + 5, cpuVoltDvfsPattern, sizeof(cpuVoltDvfsPattern))) {
|
||||
@@ -447,39 +463,6 @@ namespace ams::ldr::hoc::pcv::erista {
|
||||
}
|
||||
}
|
||||
|
||||
/* Relocate the table */
|
||||
/* Rescanning is simpler than trying to extract a bunch of data from the asm patch, performance impact is negligable */
|
||||
/* Also, this is more stable :P */
|
||||
u32 RepointEristaEmcTablePtr(uintptr_t fromSlot, uintptr_t toTable) {
|
||||
constexpr u32 RetIns = 0xD65F03C0; /* ret */
|
||||
u32 patched = 0;
|
||||
|
||||
for (u32 *p = nsoStart; p + 4 < nsoEnd; ++p) {
|
||||
const u32 ins = *p;
|
||||
if (!AsmIsAdrX0(ins)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uintptr_t pc = reinterpret_cast<uintptr_t>(p);
|
||||
if (AsmAdrTarget(ins, pc) != fromSlot) {
|
||||
continue;
|
||||
}
|
||||
if (!(AsmIsLdpX(p[1]) && AsmIsLdpX(p[2]) && p[3] == RetIns)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* adr only reaches +-1MB */
|
||||
const s64 delta = static_cast<s64>(toTable) - static_cast<s64>(pc);
|
||||
if (delta > 0xFFFFF || delta < -0x100000) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PATCH_OFFSET(p, AsmSetAdrTarget(ins, pc, toTable));
|
||||
++patched;
|
||||
}
|
||||
return patched;
|
||||
}
|
||||
|
||||
/* The silicon instructs; the children obey... */
|
||||
void MtcGenerateFreqTables() {
|
||||
newEmcList.clear();
|
||||
@@ -548,24 +531,20 @@ namespace ams::ldr::hoc::pcv::erista {
|
||||
constexpr u32 StartAdjustment = offsetof(EristaMtcTable, rate_khz) + sizeof(EristaMtcTable) * (erista::MtcTableCountDefault - 1);
|
||||
u8 *startPtr = reinterpret_cast<u8 *>(ptr) - StartAdjustment;
|
||||
|
||||
const uintptr_t usedSlot = reinterpret_cast<uintptr_t>(startPtr) + mtcOffset;
|
||||
EristaMtcTable *table = reinterpret_cast<EristaMtcTable *>(usedSlot);
|
||||
EristaMtcTable *table = reinterpret_cast<EristaMtcTable *>(startPtr + mtcOffset);
|
||||
|
||||
R_TRY(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault));
|
||||
|
||||
PrepareMtcMemoryRegion(startPtr, table);
|
||||
table = reinterpret_cast<EristaMtcTable *>(startPtr);
|
||||
|
||||
/* We must do this as the NLE tables don't have enough space past them for our extended ones */
|
||||
if (usedSlot != reinterpret_cast<uintptr_t>(startPtr)) {
|
||||
if (RepointEristaEmcTablePtr(usedSlot, reinterpret_cast<uintptr_t>(startPtr)) == 0) {
|
||||
AbortInvalidMtc("Failed to repoint emc table");
|
||||
}
|
||||
}
|
||||
|
||||
if (R_FAILED(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault))) {
|
||||
AbortInvalidMtc("Failed mtc validation");
|
||||
}
|
||||
|
||||
/* Cache the table for hooks. */
|
||||
getMtcTableCache.mtcTable = table;
|
||||
|
||||
if (C.eristaEmcMaxClock <= EmcClkOSLimit) {
|
||||
R_SKIP();
|
||||
}
|
||||
@@ -620,50 +599,71 @@ namespace ams::ldr::hoc::pcv::erista {
|
||||
// R_SUCCEED();
|
||||
// }
|
||||
|
||||
HOOK_PAYLOAD_FN EristaMtcTable *GetEristaMtcTableImpl(u32 *count) {
|
||||
const HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData);
|
||||
|
||||
*count = data->mtcTableAsm.mtcCount;
|
||||
return data->mtcTableAsm.mtcTable;
|
||||
}
|
||||
|
||||
Result MemMtcTableAsm(u32 *ptr) {
|
||||
/* Return if the pattern was already found. */
|
||||
/* This pattern happens multiple times in this function., we only need to find it once. */
|
||||
R_UNLESS(!getMtcTableCache.foundMtcTablePattern, ldr::ResultInvalidMtcTablePattern());
|
||||
|
||||
/* This is a mess but the compiler made this painful to patch so we must do it this way */
|
||||
constexpr s32 GoodAdrpOffset = -1;
|
||||
constexpr s32 GoodMovOffset = -7;
|
||||
constexpr s32 GoodBlOffset = 1;
|
||||
constexpr u32 MtcGoodBlOpcode = 0x97fe6cfc;
|
||||
|
||||
constexpr u32 MtcBadBlOpcode0 = 0x97ffae64; // bl nn::pcv::GetHardwareType
|
||||
constexpr u32 MtcBadBlOpcode1 = 0x940036d5; // bl strcmp
|
||||
constexpr u32 MtcBadAdrpAsm = 0xd00000a1; // adrp x1, s_ModuleResetStatus_
|
||||
|
||||
constexpr s32 MtcBadBlOffset0 = 2;
|
||||
constexpr s32 MtcBadBlOffset1 = -1;
|
||||
constexpr s32 MtcBadAdrpOffset = 1;
|
||||
constexpr u32 AddrpOffset = 1;
|
||||
constexpr u32 MovOffset = 7;
|
||||
constexpr u32 BlOffset = 5;
|
||||
constexpr u32 MovOffsetOld = 8;
|
||||
|
||||
/* Ensure we don't dereference memory before nso start. */
|
||||
R_UNLESS(ptr + GoodMovOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern());
|
||||
R_UNLESS(ptr - MovOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern());
|
||||
|
||||
/* Check for GetHardwareType asm and skip if it is found */
|
||||
/* The pattern will match on the first time, but the location is bad, so it must be skipped */
|
||||
if(AsmCompareAdrpNoImm(*(ptr + MtcBadAdrpOffset), MtcBadAdrpAsm) && AsmBlCompareOpcodeOnly(*(ptr + MtcBadBlOffset0), MtcBadBlOpcode0) && AsmBlCompareOpcodeOnly(*(ptr + MtcBadBlOffset1), MtcBadBlOpcode1)) {
|
||||
R_SKIP();
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
u32 adrp = *(ptr + GoodAdrpOffset);
|
||||
u32 adrp = *(ptr - AddrpOffset);
|
||||
R_UNLESS(AsmCompareAdrpNoImm(adrp, MtcAdrpAsm), ldr::ResultInvalidMtcTablePattern());
|
||||
|
||||
|
||||
/* Check for the branch instruction above the cbz to ensure we are patching the right location*/
|
||||
u32 bl = *(ptr + GoodBlOffset);
|
||||
R_UNLESS(AsmBlCompareOpcodeOnly(bl, MtcGoodBlOpcode), ldr::ResultInvalidMtcTablePattern());
|
||||
|
||||
u32 bl = *(ptr - BlOffset);
|
||||
R_UNLESS(AsmBlCompareOpcodeOnly(bl, MtcBlIns), ldr::ResultInvalidMtcTablePattern());
|
||||
|
||||
/* Check for the mov that actually sets the mtc table count. */
|
||||
u32 mov = *(ptr + GoodMovOffset);
|
||||
R_UNLESS(asm_compare_no_rd(mov, MtcMovAsm), ldr::ResultInvalidMtcTablePattern());
|
||||
u32 mov = *(ptr - MovOffset);
|
||||
bool foundMov = false;
|
||||
foundMov = asm_compare_no_rd(mov, MtcMovAsm);
|
||||
|
||||
/* Patch out the count of the mov to our custom mtc table amount*/
|
||||
u32 movCountPatch = asm_set_rd(asm_set_imm16(MtcMovAsm, newEmcList.size()), asm_get_rd(mov));
|
||||
if (!foundMov) {
|
||||
mov = *(ptr + MovOffsetOld);
|
||||
/* Check old firmware offset. */
|
||||
foundMov = asm_compare_no_rd(mov, MtcMovAsm);
|
||||
}
|
||||
|
||||
PATCH_OFFSET(ptr + GoodMovOffset, movCountPatch);
|
||||
R_UNLESS(foundMov, ldr::ResultInvalidMtcTablePattern());
|
||||
|
||||
constexpr u32 PrologueWindow = 140;
|
||||
u32 *functionPrologue = FindFnPrologue(ptr, PrologueWindow, nsoStart);
|
||||
R_UNLESS(functionPrologue != nullptr, ldr::ResultInvalidMtcTablePattern());
|
||||
|
||||
getMtcTableCache.getEristaMtcTableFnSite = functionPrologue;
|
||||
getMtcTableCache.foundMtcTablePattern = true;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result InstallHooks() {
|
||||
R_TRY(Hooks().CheckEnabled());
|
||||
|
||||
R_TRY(Hooks().CopyPayload());
|
||||
|
||||
R_UNLESS(getMtcTableCache.getEristaMtcTableFnSite != nullptr && getMtcTableCache.mtcTable != nullptr, ldr::ResultInvalidMtcTablePattern());
|
||||
|
||||
/* Copy the data to the payload. */
|
||||
auto *data = Hooks().BindData(e_HookPayloadData);
|
||||
R_UNLESS(data != nullptr, ldr::ResultHookDataOutOfMemory());
|
||||
|
||||
data->mtcTableAsm.mtcTable = reinterpret_cast<EristaMtcTable *>(Hooks().ToVa(getMtcTableCache.mtcTable));
|
||||
data->mtcTableAsm.mtcCount = newEmcList.size();
|
||||
R_TRY(INSTALL_IMPL_HOOK(getMtcTableCache.getEristaMtcTableFnSite, GetEristaMtcTableImpl));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -685,7 +685,7 @@ namespace ams::ldr::hoc::pcv::erista {
|
||||
{"GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial },
|
||||
{"GPU Freq Table", GpuFreqCvbTable<false>, 1, nullptr, GpuCvbDefaultMaxFreq },
|
||||
{"GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn },
|
||||
{"GPU PLL Max", & GpuFreqPllMax, 1, nullptr, GpuClkPllMax },
|
||||
{"GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax },
|
||||
// {"GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit },
|
||||
{"MEM Table Asm", &MemMtcTableAsm, 4, &MemMtcGetGetTablePatternFn },
|
||||
{"MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit },
|
||||
@@ -706,12 +706,15 @@ namespace ams::ldr::hoc::pcv::erista {
|
||||
for (auto &entry : patches) {
|
||||
LOGGING("%s Count: %zu\n", entry.description, entry.patched_count);
|
||||
if (R_FAILED(entry.CheckResult())) {
|
||||
// ViewLog();
|
||||
panic::SmcError(panic::Patch);
|
||||
|
||||
CRASH(entry.description);
|
||||
}
|
||||
}
|
||||
|
||||
if (R_FAILED(InstallHooks())) {
|
||||
panic::SmcError(panic::Patch);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
364
Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp
Normal file
364
Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp
Normal file
@@ -0,0 +1,364 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../oc_common.hpp"
|
||||
|
||||
#define HOOK_PAYLOAD_FN __attribute__((section("hoc_hookpayload"), used, noinline, visibility("hidden")))
|
||||
|
||||
/* Inline asm because fuck compilers: GCC refuses to put a variable and a function in the same section. */
|
||||
/* It wants alloc+write for one and alloc+exec for the other. */
|
||||
/* You are supposed to be able to override that by writing the flags into the section name yourself, */
|
||||
/* like section("hoc_hookpayload,\"ax\",%progbits"), instead of letting GCC pick them. */
|
||||
/* But GCC takes that whole string as the name and never reads the flags out of it, so it appends the */
|
||||
/* ones it wanted anyway: the .section directive it emits ends up carrying two sets of flags, and the */
|
||||
/* assembler rejects it. The failure lands in the generated assembly, not in anything we wrote. */
|
||||
/* Therefore we must use inline assembly, which reaches the assembler exactly as written. */
|
||||
#define DEFINE_HOOK_PAYLOAD_PTR(type, name) \
|
||||
asm(".section hoc_hookpayload,\"ax\",%progbits\n" \
|
||||
".balign 8\n" \
|
||||
".global " #name "\n" \
|
||||
".hidden " #name "\n" \
|
||||
#name ": .zero 8\n" \
|
||||
".text\n"); \
|
||||
extern "C" __attribute__((visibility("hidden"))) type *name
|
||||
|
||||
#define HOOK_PAYLOAD_PTR(type, name) \
|
||||
([]() -> type * { \
|
||||
type **_hoc_pp; \
|
||||
__asm__("adr %0, " #name : "=r"(_hoc_pp)); \
|
||||
return *_hoc_pp; \
|
||||
}())
|
||||
|
||||
extern "C" const u8 __start_hoc_hookpayload[];
|
||||
extern "C" const u8 __stop_hoc_hookpayload[];
|
||||
|
||||
namespace ams::ldr::hoc::pcv {
|
||||
|
||||
constexpr size_t HookPageSize = 0x1000;
|
||||
constexpr size_t PcvDataArenaSize = 0x1000;
|
||||
|
||||
inline s64 SignExtend(u64 value, int bits) {
|
||||
const int shift = 64 - bits;
|
||||
return static_cast<s64>(value << shift) >> shift;
|
||||
}
|
||||
|
||||
inline u32 EncodeRelBranch(u32 opc, uintptr_t site_va, uintptr_t target_va) {
|
||||
const s64 delta = static_cast<s64>(target_va) - static_cast<s64>(site_va);
|
||||
AMS_ABORT_UNLESS((delta & 0x3) == 0);
|
||||
AMS_ABORT_UNLESS(delta >= -0x08000000 && delta <= 0x07FFFFFC);
|
||||
return opc | (static_cast<u32>(delta >> 2) & 0x03FFFFFFu);
|
||||
}
|
||||
|
||||
inline u32 EncodeB(uintptr_t site_va, uintptr_t target_va) { return EncodeRelBranch(0x14000000u, site_va, target_va); }
|
||||
inline u32 EncodeBL(uintptr_t site_va, uintptr_t target_va) { return EncodeRelBranch(0x94000000u, site_va, target_va); }
|
||||
|
||||
inline u32 EncodePairSp(bool load, u32 rt1, u32 rt2, s32 imm) {
|
||||
const u32 base = load ? 0xA9400000u : 0xA9000000u;
|
||||
const u32 imm7 = static_cast<u32>((imm / 8) & 0x7F);
|
||||
return base | (imm7 << 15) | (rt2 << 10) | (31u << 5) | rt1;
|
||||
}
|
||||
|
||||
inline u32 EncodeSpAdjust(bool sub, u32 imm12) {
|
||||
const u32 base = sub ? 0xD1000000u : 0x91000000u;
|
||||
return base | ((imm12 & 0xFFFu) << 10) | (31u << 5) | 31u;
|
||||
}
|
||||
|
||||
inline Result RelocateInstruction(u32 insn, uintptr_t old_site_va, uintptr_t new_site_va, u32 *out) {
|
||||
const u32 top6 = insn & 0xFC000000u;
|
||||
if (top6 == 0x14000000u || top6 == 0x94000000u) {
|
||||
const s64 old_imm = SignExtend(static_cast<u64>(insn & 0x03FFFFFFu) << 2, 28);
|
||||
const uintptr_t target = old_site_va + old_imm;
|
||||
*out = EncodeRelBranch(top6, new_site_va, target);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
R_UNLESS((insn & 0xFF000010u) != 0x54000000u, ldr::ResultHookRelocationUnsupported()); /* b.cond */
|
||||
R_UNLESS((insn & 0x7E000000u) != 0x34000000u, ldr::ResultHookRelocationUnsupported()); /* cbz/cbnz */
|
||||
R_UNLESS((insn & 0x7E000000u) != 0x36000000u, ldr::ResultHookRelocationUnsupported()); /* tbz/tbnz */
|
||||
R_UNLESS((insn & 0x1F000000u) != 0x10000000u, ldr::ResultHookRelocationUnsupported()); /* adr/adrp */
|
||||
R_UNLESS((insn & 0x3B000000u) != 0x18000000u, ldr::ResultHookRelocationUnsupported()); /* ldr literal */
|
||||
|
||||
*out = insn;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
class HookContext {
|
||||
private:
|
||||
uintptr_t m_map_base = 0; /* loader-side base of pcv's NSO mapping. */
|
||||
uintptr_t m_va_base = 0; /* pcv-side base of the same memory. */
|
||||
uintptr_t m_cave = 0; /* loader-side cave base. (0 if unavailable) */
|
||||
size_t m_cave_size = 0;
|
||||
uintptr_t m_payload = 0; /* loader-side base of the payload copy. */
|
||||
size_t m_payload_sz = 0;
|
||||
size_t m_used = 0;
|
||||
uintptr_t m_data = 0; /* loader-side data arena base, 0 if none. */
|
||||
size_t m_data_used = 0;
|
||||
public:
|
||||
constexpr HookContext() = default;
|
||||
|
||||
void Initialize(uintptr_t map_base, uintptr_t va_base, uintptr_t cave, size_t cave_size, uintptr_t data) {
|
||||
m_map_base = map_base;
|
||||
m_va_base = va_base;
|
||||
m_cave = cave;
|
||||
m_cave_size = cave_size;
|
||||
m_payload = 0;
|
||||
m_payload_sz = 0;
|
||||
m_used = 0;
|
||||
m_data = data;
|
||||
m_data_used = 0;
|
||||
}
|
||||
|
||||
bool IsEnabled() const { return m_cave != 0 && m_cave_size != 0; }
|
||||
|
||||
Result CheckEnabled() const {
|
||||
R_UNLESS(this->IsEnabled(), ldr::ResultHookUnavailable());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
size_t CaveSize() const { return m_cave_size; }
|
||||
size_t CaveUsed() const { return m_used; }
|
||||
size_t CaveFree() const { return m_cave_size > m_used ? m_cave_size - m_used : 0; }
|
||||
|
||||
size_t DataSize() const { return m_data != 0 ? PcvDataArenaSize : 0; }
|
||||
size_t DataUsed() const { return m_data_used; }
|
||||
size_t DataFree() const { return this->DataSize() - m_data_used; }
|
||||
|
||||
/* Convert loader mapped address to pcv-side address. */
|
||||
uintptr_t ToVa(const void *loader_ptr) const {
|
||||
return m_va_base + (reinterpret_cast<uintptr_t>(loader_ptr) - m_map_base);
|
||||
}
|
||||
|
||||
uintptr_t CaveVa() const { return ToVa(reinterpret_cast<const void *>(m_cave)); }
|
||||
|
||||
Result CopyPayload() {
|
||||
R_TRY(this->CheckEnabled());
|
||||
|
||||
const size_t size = static_cast<size_t>(__stop_hoc_hookpayload - __start_hoc_hookpayload);
|
||||
|
||||
/* Zero length: Linker garbage collected the payload .(happens when nothing references it) */
|
||||
/* __start_ and _stop_ don't prevent this. */
|
||||
/* Copying here would succeed at first but fail later by jumping to empty memory. */
|
||||
R_UNLESS(size != 0, ldr::ResultUninitializedPatcher());
|
||||
R_UNLESS(size <= m_cave_size, ldr::ResultHookPayloadTooLarge());
|
||||
|
||||
m_payload = m_cave;
|
||||
m_payload_sz = size;
|
||||
std::memcpy(reinterpret_cast<void *>(m_payload), __start_hoc_hookpayload, size);
|
||||
|
||||
m_used = util::AlignUp(size, sizeof(u32));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
/* pcv-side address of a payload symbol's copy. */
|
||||
uintptr_t PayloadVa(const void *loader_sym) const {
|
||||
const uintptr_t offset = reinterpret_cast<uintptr_t>(loader_sym) - reinterpret_cast<uintptr_t>(__start_hoc_hookpayload);
|
||||
return this->ToVa(reinterpret_cast<void *>(m_payload + offset));
|
||||
}
|
||||
|
||||
/* Loader-side, writable pointer to a payload variable's copy in the cave. */
|
||||
template<typename T>
|
||||
T *PayloadCopyOf(T &loader_sym) const {
|
||||
const uintptr_t offset = reinterpret_cast<uintptr_t>(std::addressof(loader_sym)) - reinterpret_cast<uintptr_t>(__start_hoc_hookpayload);
|
||||
return reinterpret_cast<T *>(m_payload + offset);
|
||||
}
|
||||
|
||||
/* Reserves space in the writable data arena past pcv's .bss. */
|
||||
/* Returns a zeroed, loader-side pointer. */
|
||||
/* The data arena is too far from the cave to be addressed directly by symbol, so we must store a pointer to it in the cave section. */
|
||||
template<typename T>
|
||||
T *DataAlloc() {
|
||||
const size_t size = util::AlignUp(sizeof(T), alignof(T) > 8 ? alignof(T) : 8);
|
||||
if (m_data == 0 || m_data_used + size > PcvDataArenaSize) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
T *p = reinterpret_cast<T *>(m_data + m_data_used);
|
||||
m_data_used += size;
|
||||
|
||||
std::memset(p, 0, sizeof(T));
|
||||
return p;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T *BindData(T *&loader_sym) {
|
||||
T *block = this->DataAlloc<T>();
|
||||
if (block != nullptr) {
|
||||
*this->PayloadCopyOf(loader_sym) = reinterpret_cast<T *>(this->ToVa(block));
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
/* Replaces the function entirely starting at function prologue. */
|
||||
/* Preserves the function arguments. */
|
||||
Result InstallImpl(u32 *site, const void *fn, uintptr_t *out_orig = nullptr) {
|
||||
R_TRY(this->CheckEnabled());
|
||||
R_UNLESS(site != nullptr, ldr::ResultHookSiteInvalid());
|
||||
R_UNLESS(m_payload != 0, ldr::ResultUninitializedPatcher());
|
||||
R_TRY(this->ValidatePayloadFn(fn));
|
||||
|
||||
if (out_orig != nullptr) {
|
||||
u32 *tramp = this->AllocCode(2);
|
||||
R_UNLESS(tramp != nullptr, ldr::ResultHookArenaOutOfMemory());
|
||||
|
||||
u32 relocated;
|
||||
R_TRY(RelocateInstruction(site[0], this->ToVa(site), this->ToVa(&tramp[0]), std::addressof(relocated)));
|
||||
tramp[0] = relocated;
|
||||
tramp[1] = EncodeB(this->ToVa(&tramp[1]), this->ToVa(site) + sizeof(u32));
|
||||
|
||||
*out_orig = this->ToVa(tramp);
|
||||
}
|
||||
|
||||
site[0] = EncodeB(this->ToVa(site), this->PayloadVa(fn));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
/* Takes the same arguments as the hooked function, does not replace. */
|
||||
Result InstallIntercept(u32 *site, const void *fn) {
|
||||
R_TRY(this->CheckEnabled());
|
||||
R_UNLESS(site != nullptr, ldr::ResultHookSiteInvalid());
|
||||
R_UNLESS(m_payload != 0, ldr::ResultUninitializedPatcher());
|
||||
R_TRY(this->ValidatePayloadFn(fn));
|
||||
|
||||
/* x0-x7: arguments */
|
||||
/* x8: result pointer */
|
||||
/* x18: platform register */
|
||||
/* x30: return address */
|
||||
/* x29: keeps pairs clean */
|
||||
/* x9-x17: scratch */
|
||||
constexpr u32 FrameSize = 0x60;
|
||||
constexpr u32 Pairs[][2] = { {0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 18}, {29, 30} };
|
||||
constexpr u32 PairCount = sizeof(Pairs) / sizeof(Pairs[0]);
|
||||
constexpr u32 StubWords = 1 + PairCount + 1 + PairCount + 1 + 1 + 1;
|
||||
|
||||
u32 *stub = this->AllocCode(StubWords);
|
||||
R_UNLESS(stub != nullptr, ldr::ResultHookArenaOutOfMemory());
|
||||
|
||||
u32 i = 0;
|
||||
stub[i++] = EncodeSpAdjust(true, FrameSize);
|
||||
for (u32 p = 0; p < PairCount; ++p) {
|
||||
stub[i++] = EncodePairSp(false, Pairs[p][0], Pairs[p][1], static_cast<s32>(p * 16));
|
||||
}
|
||||
|
||||
stub[i] = EncodeBL(this->ToVa(&stub[i]), this->PayloadVa(fn));
|
||||
++i;
|
||||
|
||||
for (u32 p = 0; p < PairCount; ++p) {
|
||||
stub[i++] = EncodePairSp(true, Pairs[p][0], Pairs[p][1], static_cast<s32>(p * 16));
|
||||
}
|
||||
stub[i++] = EncodeSpAdjust(false, FrameSize);
|
||||
|
||||
u32 relocated;
|
||||
R_TRY(RelocateInstruction(site[0], this->ToVa(site), this->ToVa(&stub[i]), std::addressof(relocated)));
|
||||
stub[i] = relocated;
|
||||
++i;
|
||||
|
||||
stub[i] = EncodeB(this->ToVa(&stub[i]), this->ToVa(site) + sizeof(u32));
|
||||
++i;
|
||||
|
||||
AMS_ABORT_UNLESS(i == StubWords);
|
||||
|
||||
site[0] = EncodeB(this->ToVa(site), this->ToVa(stub));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
/* Checks entry to first ret that everything still points to valid data. */
|
||||
Result ValidatePayloadFn(const void *fn) const {
|
||||
constexpr u32 MaxInstructions = 512;
|
||||
constexpr u32 RetInsn = 0xD65F03C0u;
|
||||
|
||||
const uintptr_t lo = m_payload;
|
||||
const uintptr_t hi = m_payload + m_payload_sz;
|
||||
|
||||
const u32 *insns = reinterpret_cast<const u32 *>(fn);
|
||||
const uintptr_t base = m_payload + (reinterpret_cast<uintptr_t>(fn) - reinterpret_cast<uintptr_t>(__start_hoc_hookpayload));
|
||||
|
||||
auto check = [&](uintptr_t target) -> Result {
|
||||
if (target < lo || target >= hi) {
|
||||
R_THROW(ldr::ResultHookPayloadEscapes());
|
||||
}
|
||||
R_SUCCEED();
|
||||
};
|
||||
|
||||
for (u32 i = 0; i < MaxInstructions; ++i) {
|
||||
const u32 insn = insns[i];
|
||||
const uintptr_t site = base + i * sizeof(u32);
|
||||
|
||||
if (insn == RetInsn) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
const u32 top6 = insn & 0xFC000000u;
|
||||
if (top6 == 0x14000000u || top6 == 0x94000000u) { /* b / bl */
|
||||
const uintptr_t target = site + SignExtend(static_cast<u64>(insn & 0x03FFFFFFu) << 2, 28);
|
||||
R_TRY(check(target));
|
||||
} else if ((insn & 0xFF000010u) == 0x54000000u || /* b.cond */
|
||||
(insn & 0x7E000000u) == 0x34000000u || /* cbz / cbnz */
|
||||
(insn & 0x3B000000u) == 0x18000000u) { /* ldr literal */
|
||||
const uintptr_t target = site + SignExtend(static_cast<u64>((insn >> 5) & 0x7FFFFu) << 2, 21);
|
||||
R_TRY(check(target));
|
||||
} else if ((insn & 0x7E000000u) == 0x36000000u) { /* tbz / tbnz */
|
||||
const uintptr_t target = site + SignExtend(static_cast<u64>((insn >> 5) & 0x3FFFu) << 2, 16);
|
||||
R_TRY(check(target));
|
||||
} else if ((insn & 0x9F000000u) == 0x10000000u) { /* adr */
|
||||
const u64 imm = (static_cast<u64>((insn >> 5) & 0x7FFFFu) << 2) | ((insn >> 29) & 0x3u);
|
||||
const uintptr_t target = site + SignExtend(imm, 21);
|
||||
R_TRY(check(target));
|
||||
} else if ((insn & 0x9F000000u) == 0x90000000u) { /* adrp */
|
||||
/* ADRP is page-relative and the cave is at an arbitrary offset, any adrp would point to potential garbage. */
|
||||
R_THROW(ldr::ResultHookPayloadEscapes());
|
||||
}
|
||||
}
|
||||
|
||||
/* No return found. */
|
||||
R_THROW(ldr::ResultHookPayloadEscapes());
|
||||
}
|
||||
private:
|
||||
u32 *AllocCode(size_t words) {
|
||||
const size_t bytes = words * sizeof(u32);
|
||||
if (!this->IsEnabled() || m_used + bytes > m_cave_size) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u32 *p = reinterpret_cast<u32 *>(m_cave + m_used);
|
||||
m_used += bytes;
|
||||
return p;
|
||||
}
|
||||
};
|
||||
|
||||
inline HookContext &Hooks() {
|
||||
static HookContext s_context;
|
||||
return s_context;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Hook custom impl. Original becomes unreachable. */
|
||||
/* Starts at function entry. Preserves arguments. */
|
||||
#define INSTALL_IMPL_HOOK(site, fn) \
|
||||
(::ams::ldr::hoc::pcv::Hooks().InstallImpl((site), reinterpret_cast<const void *>(&(fn))))
|
||||
|
||||
/* Maintains original function. */
|
||||
/* Starts at function entry. Preserves arguments. */
|
||||
#define INSTALL_IMPL_HOOK_ORIG(site, fn, out_orig) \
|
||||
(::ams::ldr::hoc::pcv::Hooks().InstallImpl((site), reinterpret_cast<const void *>(&(fn)), (out_orig)))
|
||||
|
||||
/* Takes the same arguments as the hooked function, does not replace it and cannot change what it does */
|
||||
/* but it can be placed anywhere, not just at a function entry. */
|
||||
#define INSTALL_INTERC_HOOK(site, fn) \
|
||||
(::ams::ldr::hoc::pcv::Hooks().InstallIntercept((site), reinterpret_cast<const void *>(&(fn))))
|
||||
@@ -622,82 +622,6 @@ namespace ams::ldr::hoc::pcv::mariko {
|
||||
u32 *nsoStart;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
R_UNLESS(EmcSocLutPatternFn(ptr), ldr::ResultInvalidEmcSocLut()); /* str lut,[rail+0x120] ; str n,[rail+0x154] */
|
||||
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<u16>(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<Rm>,#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<u16>(EmcDvfsTableEntryCount))); /* cmp w?,#64 */
|
||||
PATCH_OFFSET(movPtr, asm_set_imm16(*movPtr, static_cast<u16>(EmcDvfsTableEntryCount))); /* movz w?,#64 */
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void MtcGenerateJedecTable() {
|
||||
const u32 jedecFreqs[] = { 1866000, 1996000, 2133000, 2400000, 2666000, 2933000, 3200000 };
|
||||
constexpr u32 JedecFreqCount = std::size(jedecFreqs);
|
||||
@@ -714,7 +638,7 @@ namespace ams::ldr::hoc::pcv::mariko {
|
||||
newEmcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
|
||||
}
|
||||
|
||||
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
|
||||
newEmcList.resize(std::min(newEmcList.size(), DvfsTableEntryLimit));
|
||||
}
|
||||
|
||||
void MtcGenerate133StepTable() {
|
||||
@@ -733,39 +657,12 @@ namespace ams::ldr::hoc::pcv::mariko {
|
||||
newEmcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
|
||||
}
|
||||
|
||||
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
|
||||
}
|
||||
|
||||
void MtcGenerate33StepTable() {
|
||||
/* ~33.33MHz but rounded*/
|
||||
const u32 stepFreqs33[] = {
|
||||
1633000, 1666000, 1700000, 1733000, 1766000, 1800000, 1833000, 1866000, 1900000, 1933000,
|
||||
1966000, 2000000, 2033000, 2066000, 2100000, 2133000, 2166000, 2200000, 2233000, 2266000,
|
||||
2300000, 2333000, 2366000, 2400000, 2433000, 2466000, 2500000, 2533000, 2566000, 2600000,
|
||||
2633000, 2666000, 2700000, 2733000, 2766000, 2800000, 2833000, 2866000, 2900000, 2933000,
|
||||
2966000, 3000000, 3033000, 3066000, 3100000, 3133000, 3166000, 3200000, 3233000, 3266000,
|
||||
3300000, 3333000, 3366000, 3400000, 3433000, 3466000, 3500000,
|
||||
};
|
||||
constexpr u32 StepFreqs33Size = std::size(stepFreqs33);
|
||||
|
||||
for (u32 i = 0; i < StepFreqs33Size; ++i) {
|
||||
if (stepFreqs33[i] <= C.marikoEmcMaxClock) {
|
||||
newEmcList.push_back(stepFreqs33[i]);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newEmcList.back() != C.marikoEmcMaxClock) {
|
||||
newEmcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
|
||||
}
|
||||
|
||||
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
|
||||
newEmcList.resize(std::min(newEmcList.size(), DvfsTableEntryLimit));
|
||||
}
|
||||
|
||||
void MtcGenerateFreqTables() {
|
||||
newEmcList.clear();
|
||||
newEmcList.reserve(EmcDvfsTableEntryCount);
|
||||
newEmcList.reserve(DvfsTableEntryCount);
|
||||
newEmcList.insert(newEmcList.end(), std::begin(EmcListDefault), std::end(EmcListDefault));
|
||||
|
||||
if (C.marikoEmcMaxClock <= EmcClkOSLimit) {
|
||||
@@ -774,9 +671,6 @@ namespace ams::ldr::hoc::pcv::mariko {
|
||||
|
||||
u32 stepRate = 0;
|
||||
switch (C.stepMode) {
|
||||
case StepMode_33MHz:
|
||||
MtcGenerate33StepTable();
|
||||
return;
|
||||
case StepMode_66MHz:
|
||||
stepRate = 66667;
|
||||
break;
|
||||
@@ -807,7 +701,7 @@ namespace ams::ldr::hoc::pcv::mariko {
|
||||
newEmcList.push_back(newFreq);
|
||||
}
|
||||
|
||||
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
|
||||
newEmcList.resize(std::min(newEmcList.size(), DvfsTableEntryLimit));
|
||||
}
|
||||
|
||||
Result VerifyMtcTable(MarikoMtcTable *tableStart, u32 expectedFreq) {
|
||||
@@ -1011,23 +905,24 @@ namespace ams::ldr::hoc::pcv::mariko {
|
||||
#undef DVB
|
||||
#undef DVB_OC
|
||||
|
||||
const size_t dvbCount = std::min(newEmcList.size(), DvbTableCapacity);
|
||||
DvbEntry emcDvbTableOc[DvbTableCapacity] = {};
|
||||
|
||||
DvbEntry emcDvbTableOc[newEmcList.size()];
|
||||
|
||||
u32 bracketIndex = 0;
|
||||
for (size_t i = 0; i < dvbCount; ++i) {
|
||||
const u32 freq = (i == dvbCount - 1) ? static_cast<u32>(newEmcList.back()) : newEmcList[i];
|
||||
while (freq >= emcDvbOcTableBrackets[bracketIndex + 1].freq) {
|
||||
for (u32 i = 0; i < newEmcList.size(); ++i) {
|
||||
while (newEmcList[i] >= emcDvbOcTableBrackets[bracketIndex + 1].freq) {
|
||||
++bracketIndex;
|
||||
}
|
||||
|
||||
emcDvbTableOc[i].freq = freq;
|
||||
emcDvbTableOc[i].freq = newEmcList[i];
|
||||
std::memcpy(emcDvbTableOc[i].volt, emcDvbOcTableBrackets[bracketIndex].volt, sizeof(emcDvbTableOc[i].volt));
|
||||
}
|
||||
|
||||
/* Clear the entire 32-entry region */
|
||||
std::memset(mem_dvb_table_head, 0, DvbTableCapacity * sizeof(DvbEntry));
|
||||
std::memcpy(mem_dvb_table_head, emcDvbTableOc, dvbCount * sizeof(DvbEntry));
|
||||
std::memset(mem_dvb_table_head, 0, sizeof(EmcDvbTableDefault));
|
||||
std::memcpy(mem_dvb_table_head, &emcDvbTableOc, sizeof(emcDvbTableOc));
|
||||
|
||||
/* Max dvfs entry is 32, but HOS doesn't seem to boot if exact freq doesn't exist in dvb table,
|
||||
reason why it's like this
|
||||
*/
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -1241,61 +1136,9 @@ namespace ams::ldr::hoc::pcv::mariko {
|
||||
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<desc>,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<desc>] */
|
||||
++patched;
|
||||
}
|
||||
}
|
||||
R_UNLESS(patched == 2, ldr::ResultInvalidEmcRateList());
|
||||
|
||||
/* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount) */
|
||||
PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast<u16>(EmcDvfsTableEntryCount))); /* cmp w?,#64 */
|
||||
PATCH_OFFSET(ptr + movzI, asm_set_imm16(*(ptr + movzI), static_cast<u16>(EmcDvfsTableEntryCount))); /* movz w?,#64 */
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void Patch(uintptr_t mapped_nso, size_t nso_size) {
|
||||
nsoStart = reinterpret_cast<u32 *>(mapped_nso);
|
||||
|
||||
g_pcv_scratch = mapped_nso + nso_size - HocPcvScratchSize;
|
||||
|
||||
MtcGenerateFreqTables();
|
||||
|
||||
u32 CpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
|
||||
u32 GpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
|
||||
|
||||
@@ -1318,10 +1161,6 @@ namespace ams::ldr::hoc::pcv::mariko {
|
||||
{ "MEM Vddq", &EmcVddqVolt, 2, nullptr, EmcVddqDefault },
|
||||
{ "MEM Vdd2", &MemVoltHandler, 2, nullptr, MemVdd2Default },
|
||||
{ "MEM Table Asm", &MemMtcTableAsm, 1, &MemMtcGetGetTablePatternFn },
|
||||
{ "EMC DVFS Count", &EmcDvfsCountLimit, 1, &EmcDvfsCountPatternFn },
|
||||
{ "EMC SoC LUT", &EmcSocLutReloc, 1, &EmcSocLutPatternFn },
|
||||
{ "EMC Rate List", &EmcRateListLimit, 0, &EmcRateListPatternFn },
|
||||
{ "EMC Rate Sess", &EmcRateSessLimit, 1, &EmcRateSessPatternFn },
|
||||
{ "SOC Volt Asm", &SocVoltAsm, 1, &SocVoltPatternFn },
|
||||
{ "SOC Volt Limit", &SocVoltLimit, 1, nullptr, SocVoltLimitOfficial },
|
||||
};
|
||||
@@ -1336,7 +1175,7 @@ namespace ams::ldr::hoc::pcv::mariko {
|
||||
}
|
||||
|
||||
for (auto &entry : patches) {
|
||||
LOGGING("%s Count: %zu", entry.description, entry.patched_count);
|
||||
LOGGING("%s Count: %zu\n", entry.description, entry.patched_count);
|
||||
if (R_FAILED(entry.CheckResult())) {
|
||||
panic::SmcError(panic::Patch);
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace ams::ldr::hoc::pcv::mariko {
|
||||
|
||||
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, };
|
||||
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 };
|
||||
|
||||
constexpr cvb_entry_t GpuCvbTableDefault[] = {
|
||||
// GPUB01_NA_CVB_TABLE
|
||||
@@ -299,78 +299,6 @@ namespace ams::ldr::hoc::pcv::mariko {
|
||||
return AsmCompareAddNoImm12(*ptr, MtcAddAsm);
|
||||
}
|
||||
|
||||
constexpr u32 EmcCountCmpAsm = 0x7100851F; /* cmp w?,#0x21 (subs wzr,w?,#0x21) */
|
||||
|
||||
/*
|
||||
str <lut>,[<rail>,#0x120] ; volt-array pointer
|
||||
str w?, [<rail>,#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?,<skip> ; cmp w?,#0x21 ; b.cs <abort> */
|
||||
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?,<same>,<cap>,lt ; min(maxCount, 32)
|
||||
bl <Get*DvfsFreqTable>
|
||||
*/
|
||||
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 <Get*DvfsFreqTable> */
|
||||
}
|
||||
|
||||
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<c>,#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);
|
||||
}
|
||||
|
||||
void Patch(uintptr_t mapped_nso, size_t nso_size);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -24,110 +23,178 @@
|
||||
#define ALIGN_PADDING 0x100000
|
||||
#define CACHE_LINE_SIZE 128
|
||||
|
||||
struct f_data {
|
||||
void (*func)(int64_t *, int64_t *, int);
|
||||
int64_t *arg1;
|
||||
int64_t *arg2;
|
||||
int arg3;
|
||||
};
|
||||
#define BW_MAX_THREADS 4
|
||||
|
||||
static pthread_cond_t p_ready, p_start;
|
||||
static pthread_mutex_t p_lock;
|
||||
static pthread_t *p_worker = NULL;
|
||||
static struct f_data *worker_data = NULL;
|
||||
static int p_worker_not_ready, p_workers_ready;
|
||||
typedef void (*bench_kernel_fn)(int64_t *dst, int64_t *src, long size);
|
||||
|
||||
static void *thread_func(void *data) {
|
||||
struct f_data *d = data;
|
||||
pthread_mutex_lock(&p_lock);
|
||||
p_worker_not_ready--;
|
||||
if (!p_worker_not_ready)
|
||||
pthread_cond_signal(&p_ready);
|
||||
while (p_workers_ready != 1)
|
||||
pthread_cond_wait(&p_start, &p_lock);
|
||||
pthread_mutex_unlock(&p_lock);
|
||||
(d->func)(d->arg1, d->arg2, d->arg3);
|
||||
pthread_exit(NULL);
|
||||
struct bw_pool;
|
||||
|
||||
typedef struct {
|
||||
Thread thread;
|
||||
Semaphore start;
|
||||
struct bw_pool *pool;
|
||||
bench_kernel_fn func;
|
||||
int64_t *dst;
|
||||
int64_t *src;
|
||||
long size;
|
||||
int core;
|
||||
bool created;
|
||||
uint64_t ticks;
|
||||
} bw_worker_t;
|
||||
|
||||
typedef struct bw_pool {
|
||||
bw_worker_t workers[BW_MAX_THREADS];
|
||||
Semaphore done;
|
||||
int threads;
|
||||
bool ok;
|
||||
} bw_pool_t;
|
||||
|
||||
static void bw_worker_entry(void *arg) {
|
||||
bw_worker_t *w = (bw_worker_t *)arg;
|
||||
bw_pool_t *p = w->pool;
|
||||
/* Best-effort pin to own core. */
|
||||
svcSetThreadCoreMask(CUR_THREAD_HANDLE, w->core, (u64)1 << w->core);
|
||||
for (;;) {
|
||||
semaphoreWait(&w->start);
|
||||
bench_kernel_fn f = w->func;
|
||||
if (!f)
|
||||
break;
|
||||
uint64_t t0 = armGetSystemTick();
|
||||
f(w->dst, w->src, w->size);
|
||||
w->ticks = armGetSystemTick() - t0;
|
||||
semaphoreSignal(&p->done);
|
||||
}
|
||||
}
|
||||
|
||||
static void parallel_run(void) {
|
||||
pthread_mutex_lock(&p_lock);
|
||||
p_workers_ready = 1;
|
||||
pthread_mutex_unlock(&p_lock);
|
||||
pthread_cond_broadcast(&p_start);
|
||||
static bool bw_pool_init(bw_pool_t *p, int threads) {
|
||||
memset(p, 0, sizeof(*p));
|
||||
if (threads > BW_MAX_THREADS)
|
||||
threads = BW_MAX_THREADS;
|
||||
p->threads = threads;
|
||||
semaphoreInit(&p->done, 0);
|
||||
s32 prio = 0x2C;
|
||||
svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
|
||||
for (int i = 0; i < threads; i++) {
|
||||
bw_worker_t *w = &p->workers[i];
|
||||
w->core = i;
|
||||
w->func = NULL;
|
||||
w->pool = p;
|
||||
semaphoreInit(&w->start, 0);
|
||||
if (R_FAILED(threadCreate(&w->thread, bw_worker_entry, w, NULL, 0x8000, prio, i)))
|
||||
return false;
|
||||
w->created = true;
|
||||
if (R_FAILED(threadStart(&w->thread)))
|
||||
return false;
|
||||
}
|
||||
p->ok = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void parallel_init(int threads) {
|
||||
pthread_attr_t attr;
|
||||
pthread_cond_init(&p_ready, NULL);
|
||||
pthread_cond_init(&p_start, NULL);
|
||||
pthread_mutex_init(&p_lock, NULL);
|
||||
p_worker_not_ready = threads;
|
||||
p_workers_ready = 0;
|
||||
pthread_attr_init(&attr);
|
||||
if (!p_worker || !worker_data) {
|
||||
p_worker = malloc(threads * sizeof(pthread_t));
|
||||
worker_data = malloc(threads * sizeof(struct f_data));
|
||||
static void bw_pool_free(bw_pool_t *p) {
|
||||
for (int i = 0; i < p->threads; i++) {
|
||||
bw_worker_t *w = &p->workers[i];
|
||||
if (w->created) {
|
||||
w->func = NULL; /* shutdown */
|
||||
semaphoreSignal(&w->start);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < p->threads; i++) {
|
||||
bw_worker_t *w = &p->workers[i];
|
||||
if (w->created) {
|
||||
threadWaitForExit(&w->thread);
|
||||
threadClose(&w->thread);
|
||||
w->created = false;
|
||||
}
|
||||
}
|
||||
p->ok = false;
|
||||
}
|
||||
|
||||
static double bw_run_once(bw_pool_t *p, bench_kernel_fn f, int64_t *dstbuf, int64_t *srcbuf, long size) {
|
||||
int threads = p->threads;
|
||||
for (int i = 0; i < threads; i++) {
|
||||
bw_worker_t *w = &p->workers[i];
|
||||
w->func = f;
|
||||
w->dst = dstbuf + (size * i) / (long)sizeof(int64_t);
|
||||
w->src = srcbuf + (size * i) / (long)sizeof(int64_t);
|
||||
w->size = size;
|
||||
}
|
||||
for (int i = 0; i < threads; i++)
|
||||
pthread_create(p_worker + i, &attr, thread_func, worker_data + i);
|
||||
pthread_mutex_lock(&p_lock);
|
||||
while (p_worker_not_ready != 0)
|
||||
pthread_cond_wait(&p_ready, &p_lock);
|
||||
pthread_mutex_unlock(&p_lock);
|
||||
semaphoreSignal(&p->workers[i].start);
|
||||
for (int i = 0; i < threads; i++)
|
||||
semaphoreWait(&p->done);
|
||||
uint64_t sum = 0;
|
||||
for (int i = 0; i < threads; i++)
|
||||
sum += p->workers[i].ticks;
|
||||
return (double)armTicksToNs(sum) / 1000000000.0 / threads;
|
||||
}
|
||||
|
||||
static void aligned_block_copy(int64_t *__restrict dst_, int64_t *__restrict src, int size) {
|
||||
volatile int64_t *dst = dst_;
|
||||
int64_t t1, t2, t3, t4;
|
||||
while ((size -= 64) >= 0) {
|
||||
t1 = *src++;
|
||||
t2 = *src++;
|
||||
t3 = *src++;
|
||||
t4 = *src++;
|
||||
*dst++ = t1;
|
||||
*dst++ = t2;
|
||||
*dst++ = t3;
|
||||
*dst++ = t4;
|
||||
t1 = *src++;
|
||||
t2 = *src++;
|
||||
t3 = *src++;
|
||||
t4 = *src++;
|
||||
*dst++ = t1;
|
||||
*dst++ = t2;
|
||||
*dst++ = t3;
|
||||
*dst++ = t4;
|
||||
}
|
||||
}
|
||||
#define BW_GPR_CLOBBERS \
|
||||
"x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", "x18"
|
||||
|
||||
static void aligned_block_fetch(int64_t *__restrict dst, int64_t *__restrict src_, int size) {
|
||||
volatile int64_t *src = src_;
|
||||
(void)dst;
|
||||
while ((size -= 64) >= 0) {
|
||||
*src++;
|
||||
*src++;
|
||||
*src++;
|
||||
*src++;
|
||||
*src++;
|
||||
*src++;
|
||||
*src++;
|
||||
*src++;
|
||||
}
|
||||
static void aligned_block_copy(int64_t *dst, int64_t *src, long size) {
|
||||
__asm__ __volatile__(
|
||||
"1:\n"
|
||||
"ldp x3, x4, [%[s], #0x00]\n"
|
||||
"ldp x5, x6, [%[s], #0x10]\n"
|
||||
"ldp x7, x8, [%[s], #0x20]\n"
|
||||
"ldp x9, x10, [%[s], #0x30]\n"
|
||||
"ldp x11, x12, [%[s], #0x40]\n"
|
||||
"ldp x13, x14, [%[s], #0x50]\n"
|
||||
"ldp x15, x16, [%[s], #0x60]\n"
|
||||
"ldp x17, x18, [%[s], #0x70]\n"
|
||||
"add %[s], %[s], #0x80\n"
|
||||
"stp x3, x4, [%[d], #0x00]\n"
|
||||
"stp x5, x6, [%[d], #0x10]\n"
|
||||
"stp x7, x8, [%[d], #0x20]\n"
|
||||
"stp x9, x10, [%[d], #0x30]\n"
|
||||
"stp x11, x12, [%[d], #0x40]\n"
|
||||
"stp x13, x14, [%[d], #0x50]\n"
|
||||
"stp x15, x16, [%[d], #0x60]\n"
|
||||
"stp x17, x18, [%[d], #0x70]\n"
|
||||
"add %[d], %[d], #0x80\n"
|
||||
"subs %[n], %[n], #0x80\n"
|
||||
"b.gt 1b\n"
|
||||
: [s] "+r"(src), [d] "+r"(dst), [n] "+r"(size)
|
||||
:
|
||||
: BW_GPR_CLOBBERS, "cc", "memory");
|
||||
}
|
||||
|
||||
static void aligned_block_fill(int64_t *__restrict dst_, int64_t *__restrict src, int size) {
|
||||
volatile int64_t *dst = dst_;
|
||||
int64_t data = *src;
|
||||
while ((size -= 64) >= 0) {
|
||||
*dst++ = data;
|
||||
*dst++ = data;
|
||||
*dst++ = data;
|
||||
*dst++ = data;
|
||||
*dst++ = data;
|
||||
*dst++ = data;
|
||||
*dst++ = data;
|
||||
*dst++ = data;
|
||||
}
|
||||
static void aligned_block_fetch(int64_t *dst, int64_t *src, long size) {
|
||||
(void)src;
|
||||
__asm__ __volatile__(
|
||||
"1:\n"
|
||||
"ldp x3, x4, [%[p], #0x00]\n"
|
||||
"ldp x5, x6, [%[p], #0x10]\n"
|
||||
"ldp x7, x8, [%[p], #0x20]\n"
|
||||
"ldp x9, x10, [%[p], #0x30]\n"
|
||||
"ldp x11, x12, [%[p], #0x40]\n"
|
||||
"ldp x13, x14, [%[p], #0x50]\n"
|
||||
"ldp x15, x16, [%[p], #0x60]\n"
|
||||
"ldp x17, x18, [%[p], #0x70]\n"
|
||||
"add %[p], %[p], #0x80\n"
|
||||
"subs %[n], %[n], #0x80\n"
|
||||
"b.gt 1b\n"
|
||||
: [p] "+r"(dst), [n] "+r"(size)
|
||||
:
|
||||
: BW_GPR_CLOBBERS, "cc", "memory");
|
||||
}
|
||||
static void aligned_block_fill(int64_t *dst, int64_t *src, long size) {
|
||||
(void)src;
|
||||
__asm__ __volatile__(
|
||||
"1:\n"
|
||||
"stp x3, x4, [%[d], #0x00]\n"
|
||||
"stp x5, x6, [%[d], #0x10]\n"
|
||||
"stp x7, x8, [%[d], #0x20]\n"
|
||||
"stp x9, x10, [%[d], #0x30]\n"
|
||||
"stp x11, x12, [%[d], #0x40]\n"
|
||||
"stp x13, x14, [%[d], #0x50]\n"
|
||||
"stp x15, x16, [%[d], #0x60]\n"
|
||||
"stp x17, x18, [%[d], #0x70]\n"
|
||||
"add %[d], %[d], #0x80\n"
|
||||
"subs %[n], %[n], #0x80\n"
|
||||
"b.gt 1b\n"
|
||||
: [d] "+r"(dst), [n] "+r"(size)
|
||||
:
|
||||
: BW_GPR_CLOBBERS, "cc", "memory");
|
||||
}
|
||||
|
||||
static double gettime(void) {
|
||||
@@ -136,47 +203,29 @@ static double gettime(void) {
|
||||
return (double)((int64_t)tv.tv_sec * 1000000 + tv.tv_usec) / 1000000.;
|
||||
}
|
||||
|
||||
static double bandwidth_bench_helper(int threads, int64_t *dstbuf, int64_t *srcbuf, int size, void (*f)(int64_t *, int64_t *, int)) {
|
||||
static double bandwidth_bench_helper(bw_pool_t *pool, int threads, int64_t *dstbuf, int64_t *srcbuf, long size, bench_kernel_fn f) {
|
||||
int i, loopcount, innerloopcount, n;
|
||||
double t, t1, t2, speed, maxspeed, s, s0, s1, s2;
|
||||
double t, speed, maxspeed;
|
||||
|
||||
if (!pool || !pool->ok)
|
||||
return 0.;
|
||||
|
||||
s = s0 = s1 = s2 = 0.;
|
||||
maxspeed = 0.;
|
||||
for (n = 0; n < MAXREPEATS; n++) {
|
||||
bw_run_once(pool, f, dstbuf, srcbuf, size);
|
||||
loopcount = 0;
|
||||
innerloopcount = 1;
|
||||
t = 0.;
|
||||
do {
|
||||
loopcount += innerloopcount;
|
||||
for (i = 0; i < innerloopcount; i++) {
|
||||
parallel_init(threads);
|
||||
for (int pt = 0; pt < threads; pt++) {
|
||||
(worker_data + pt)->func = f;
|
||||
(worker_data + pt)->arg1 = dstbuf + size * pt / sizeof(int64_t);
|
||||
(worker_data + pt)->arg2 = srcbuf + size * pt / sizeof(int64_t);
|
||||
(worker_data + pt)->arg3 = size;
|
||||
}
|
||||
t1 = gettime();
|
||||
parallel_run();
|
||||
for (int pt = 0; pt < threads; pt++)
|
||||
pthread_join(p_worker[pt], NULL);
|
||||
t2 = gettime();
|
||||
t += t2 - t1;
|
||||
}
|
||||
for (i = 0; i < innerloopcount; i++)
|
||||
t += bw_run_once(pool, f, dstbuf, srcbuf, size);
|
||||
innerloopcount *= 2;
|
||||
} while (t < 0.5);
|
||||
} while (t < 0.25);
|
||||
|
||||
speed = (double)size * threads * loopcount / t / 1000000.;
|
||||
s0 += 1.;
|
||||
s1 += speed;
|
||||
s2 += speed * speed;
|
||||
if (speed > maxspeed)
|
||||
maxspeed = speed;
|
||||
if (s0 > 2.) {
|
||||
s = sqrt((s0 * s2 - s1 * s1) / (s0 * (s0 - 1)));
|
||||
if (s < maxspeed / 1000.)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return maxspeed;
|
||||
}
|
||||
@@ -306,13 +355,16 @@ void bench_run_full(bench_results_t *out, bench_progress_fn progress, void *user
|
||||
|
||||
int64_t *srcbuf, *dstbuf;
|
||||
void *poolbuf = alloc_nonaliased_buffers((void **)&srcbuf, size * threads, (void **)&dstbuf, size * threads, NULL, 0);
|
||||
bw_pool_t bw;
|
||||
bw_pool_init(&bw, threads);
|
||||
|
||||
STEP("CPU copy", 0.40f);
|
||||
out->cpu_copy = bandwidth_bench_helper(threads, dstbuf, srcbuf, size, aligned_block_copy);
|
||||
out->cpu_copy = bandwidth_bench_helper(&bw, threads, dstbuf, srcbuf, size, aligned_block_copy);
|
||||
STEP("CPU read", 0.55f);
|
||||
out->cpu_read = bandwidth_bench_helper(threads, dstbuf, srcbuf, size, aligned_block_fetch);
|
||||
out->cpu_read = bandwidth_bench_helper(&bw, threads, dstbuf, srcbuf, size, aligned_block_fetch);
|
||||
STEP("CPU write", 0.70f);
|
||||
out->cpu_write = bandwidth_bench_helper(threads, dstbuf, srcbuf, size, aligned_block_fill);
|
||||
out->cpu_write = bandwidth_bench_helper(&bw, threads, dstbuf, srcbuf, size, aligned_block_fill);
|
||||
bw_pool_free(&bw);
|
||||
free(poolbuf);
|
||||
|
||||
STEP("Latency", 0.85f);
|
||||
@@ -325,9 +377,10 @@ void bench_run_full(bench_results_t *out, bench_progress_fn progress, void *user
|
||||
struct bench_ctx {
|
||||
int phase;
|
||||
bool is_4gb;
|
||||
void *pool;
|
||||
void *buf;
|
||||
int64_t *src;
|
||||
int64_t *dst;
|
||||
bw_pool_t bw;
|
||||
};
|
||||
|
||||
bench_ctx *bench_begin(void) {
|
||||
@@ -335,7 +388,8 @@ bench_ctx *bench_begin(void) {
|
||||
if (!c)
|
||||
return NULL;
|
||||
c->is_4gb = (appletGetAppletType() == AppletType_Application);
|
||||
c->pool = alloc_nonaliased_buffers((void **)&c->src, SIZE * 3, (void **)&c->dst, SIZE * 3, NULL, 0);
|
||||
c->buf = alloc_nonaliased_buffers((void **)&c->src, SIZE * 3, (void **)&c->dst, SIZE * 3, NULL, 0);
|
||||
bw_pool_init(&c->bw, 3);
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -349,24 +403,25 @@ bool bench_step(bench_ctx *c, bench_results_t *out, const char **label, float *f
|
||||
*frac = 0.25f;
|
||||
break;
|
||||
case 1:
|
||||
out->cpu_copy = bandwidth_bench_helper(threads, c->dst, c->src, size, aligned_block_copy);
|
||||
out->cpu_copy = bandwidth_bench_helper(&c->bw, threads, c->dst, c->src, size, aligned_block_copy);
|
||||
*label = "CPU copy";
|
||||
*frac = 0.45f;
|
||||
break;
|
||||
case 2:
|
||||
out->cpu_read = bandwidth_bench_helper(threads, c->dst, c->src, size, aligned_block_fetch);
|
||||
out->cpu_read = bandwidth_bench_helper(&c->bw, threads, c->dst, c->src, size, aligned_block_fetch);
|
||||
*label = "CPU read";
|
||||
*frac = 0.60f;
|
||||
break;
|
||||
case 3:
|
||||
out->cpu_write = bandwidth_bench_helper(threads, c->dst, c->src, size, aligned_block_fill);
|
||||
out->cpu_write = bandwidth_bench_helper(&c->bw, threads, c->dst, c->src, size, aligned_block_fill);
|
||||
*label = "CPU write";
|
||||
*frac = 0.75f;
|
||||
break;
|
||||
case 4:
|
||||
if (c->pool) {
|
||||
free(c->pool);
|
||||
c->pool = NULL;
|
||||
bw_pool_free(&c->bw);
|
||||
if (c->buf) {
|
||||
free(c->buf);
|
||||
c->buf = NULL;
|
||||
}
|
||||
latency_bench(&out->l2_ns, &out->ram_ns);
|
||||
*label = "Latency";
|
||||
@@ -384,7 +439,8 @@ bool bench_step(bench_ctx *c, bench_results_t *out, const char **label, float *f
|
||||
void bench_end(bench_ctx *c) {
|
||||
if (!c)
|
||||
return;
|
||||
if (c->pool)
|
||||
free(c->pool);
|
||||
bw_pool_free(&c->bw);
|
||||
if (c->buf)
|
||||
free(c->buf);
|
||||
free(c);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
960
Source/Old-Atmosphere-Patches/ldr_process_creation_1_12_0.cpp
Normal file
960
Source/Old-Atmosphere-Patches/ldr_process_creation_1_12_0.cpp
Normal file
@@ -0,0 +1,960 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "ldr_capabilities.hpp"
|
||||
#include "ldr_content_management.hpp"
|
||||
#include "ldr_development_manager.hpp"
|
||||
#include "ldr_launch_record.hpp"
|
||||
#include "ldr_meta.hpp"
|
||||
#include "ldr_patcher.hpp"
|
||||
#include "ldr_process_creation.hpp"
|
||||
#include "ldr_ro_manager.hpp"
|
||||
#include "oc/oc_loader.hpp"
|
||||
|
||||
namespace ams::ldr {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Convenience defines. */
|
||||
constexpr size_t SystemResourceSizeMax = 0x1FE00000;
|
||||
constexpr size_t AutoLoadModuleSizeMax = 0x800000000;
|
||||
|
||||
/* Types. */
|
||||
enum NsoIndex {
|
||||
Nso_Rtld = 0,
|
||||
Nso_Main = 1,
|
||||
Nso_Wkc0 = 2,
|
||||
Nso_Wkc1 = 3,
|
||||
Nso_Wkc2 = 4,
|
||||
Nso_Wkc3 = 5,
|
||||
Nso_Wkc4 = 6,
|
||||
Nso_Wkc5 = 7,
|
||||
Nso_Wkc6 = 8,
|
||||
Nso_Wkc7 = 9,
|
||||
Nso_Wkc8 = 10,
|
||||
Nso_Wkc9 = 11,
|
||||
Nso_SubSdk0 = 12,
|
||||
Nso_SubSdk1 = 13,
|
||||
Nso_SubSdk2 = 14,
|
||||
Nso_SubSdk3 = 15,
|
||||
Nso_SubSdk4 = 16,
|
||||
Nso_SubSdk5 = 17,
|
||||
Nso_SubSdk6 = 18,
|
||||
Nso_SubSdk7 = 19,
|
||||
Nso_SubSdk8 = 20,
|
||||
Nso_SubSdk9 = 21,
|
||||
Nso_Sdk = 22,
|
||||
Nso_Count,
|
||||
};
|
||||
|
||||
constexpr inline const char *NsoPaths[Nso_Count] = {
|
||||
ENCODE_ATMOSPHERE_CODE_PATH("/rtld"),
|
||||
ENCODE_ATMOSPHERE_CODE_PATH("/main"),
|
||||
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc0"),
|
||||
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc1"),
|
||||
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc2"),
|
||||
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc3"),
|
||||
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc4"),
|
||||
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc5"),
|
||||
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc6"),
|
||||
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc7"),
|
||||
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc8"),
|
||||
ENCODE_ATMOSPHERE_BDLL_PATH("/wkc9"),
|
||||
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk0"),
|
||||
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk1"),
|
||||
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk2"),
|
||||
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk3"),
|
||||
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk4"),
|
||||
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk5"),
|
||||
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk6"),
|
||||
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk7"),
|
||||
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk8"),
|
||||
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk9"),
|
||||
ENCODE_ATMOSPHERE_CODE_PATH("/sdk"),
|
||||
};
|
||||
|
||||
constexpr const char *GetNsoPath(size_t idx) {
|
||||
AMS_ABORT_UNLESS(idx < Nso_Count);
|
||||
return NsoPaths[idx];
|
||||
}
|
||||
|
||||
struct ProcessInfo {
|
||||
os::NativeHandle process_handle;
|
||||
uintptr_t code_address;
|
||||
size_t total_size;
|
||||
uintptr_t args_address;
|
||||
size_t args_size;
|
||||
uintptr_t nso_address[Nso_Count];
|
||||
size_t nso_size[Nso_Count];
|
||||
};
|
||||
|
||||
struct AutoLoadModuleInfo {
|
||||
bool has_rtld;
|
||||
bool has_main;
|
||||
bool has_sdk;
|
||||
bool has_subsdk;
|
||||
s8 nso_indices[Nso_Count];
|
||||
};
|
||||
|
||||
struct AutoLoadModuleContext {
|
||||
NsoHeader *headers;
|
||||
int nso_count;
|
||||
int rtld_idx;
|
||||
int main_nso_idx;
|
||||
int sdk_nso_idx;
|
||||
AutoLoadModuleInfo ali;
|
||||
};
|
||||
|
||||
/* Global NSO header cache. */
|
||||
NsoHeader g_nso_headers[Nso_Count];
|
||||
|
||||
/* Pcv/Ptm/NvServices check cache */
|
||||
bool g_is_pcv;
|
||||
bool g_is_ptm;
|
||||
bool g_is_nvsrv;
|
||||
|
||||
/* Global Zstd decompression context. */
|
||||
constexpr size_t ZstdDctxWorkspaceSize = 0x176E8;
|
||||
alignas(8) u8 g_zstd_dctx_workspace[ZstdDctxWorkspaceSize];
|
||||
|
||||
Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) {
|
||||
/* No version verification is done before 8.1.0. */
|
||||
R_SUCCEED_IF(hos::GetVersion() < hos::Version_8_1_0);
|
||||
|
||||
/* No verification is done if development. */
|
||||
R_SUCCEED_IF(IsDevelopmentForAntiDowngradeCheck());
|
||||
|
||||
/* TODO: Anti-downgrade checking does not make very much sense for us. Should we do anything? */
|
||||
AMS_UNUSED(program_id, version);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
/* Helpers. */
|
||||
Result GetProgramInfoFromMeta(ProgramInfo *out, const Meta *meta) {
|
||||
/* Copy basic info. */
|
||||
out->main_thread_priority = meta->npdm->main_thread_priority;
|
||||
out->default_cpu_id = meta->npdm->default_cpu_id;
|
||||
out->main_thread_stack_size = meta->npdm->main_thread_stack_size;
|
||||
out->program_id = meta->aci->program_id;
|
||||
|
||||
/* Copy access controls. */
|
||||
size_t offset = 0;
|
||||
#define COPY_ACCESS_CONTROL(source, which) \
|
||||
({ \
|
||||
const size_t size = meta->source->which##_size; \
|
||||
R_UNLESS(offset + size <= sizeof(out->ac_buffer), ldr::ResultInternalError()); \
|
||||
out->source##_##which##_size = size; \
|
||||
std::memcpy(out->ac_buffer + offset, meta->source##_##which, size); \
|
||||
offset += size; \
|
||||
})
|
||||
|
||||
/* Copy all access controls to buffer. */
|
||||
COPY_ACCESS_CONTROL(acid, sac);
|
||||
COPY_ACCESS_CONTROL(aci, sac);
|
||||
COPY_ACCESS_CONTROL(acid, fac);
|
||||
COPY_ACCESS_CONTROL(aci, fah);
|
||||
#undef COPY_ACCESS_CONTROL
|
||||
|
||||
/* Copy flags. */
|
||||
out->flags = MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
bool IsApplet(const Meta *meta) {
|
||||
return (MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Applet;
|
||||
}
|
||||
|
||||
bool IsApplication(const Meta *meta) {
|
||||
return (MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Application;
|
||||
}
|
||||
|
||||
Npdm::AddressSpaceType GetAddressSpaceType(const Meta *meta) {
|
||||
return static_cast<Npdm::AddressSpaceType>((meta->npdm->flags & Npdm::MetaFlag_AddressSpaceTypeMask) >> Npdm::MetaFlag_AddressSpaceTypeShift);
|
||||
}
|
||||
|
||||
Acid::PoolPartition GetPoolPartition(const Meta *meta) {
|
||||
return static_cast<Acid::PoolPartition>((meta->acid->flags & Acid::AcidFlag_PoolPartitionMask) >> Acid::AcidFlag_PoolPartitionShift);
|
||||
}
|
||||
|
||||
Result LoadAutoLoadHeaders(AutoLoadModuleContext &ctx, u32 acid_flags) {
|
||||
/* Clear NSOs. */
|
||||
std::memset(g_nso_headers, 0, sizeof(g_nso_headers));
|
||||
ctx.headers = g_nso_headers;
|
||||
ctx.nso_count = 0;
|
||||
ctx.rtld_idx = -1;
|
||||
ctx.main_nso_idx = -1;
|
||||
ctx.sdk_nso_idx = -1;
|
||||
ctx.ali = {};
|
||||
|
||||
for (size_t i = 0; i < Nso_Count; i++) {
|
||||
/* Only load browser DLLs if acid flags say to do so. */
|
||||
switch (i) {
|
||||
case Nso_Wkc0:
|
||||
case Nso_Wkc1:
|
||||
case Nso_Wkc2:
|
||||
case Nso_Wkc3:
|
||||
case Nso_Wkc4:
|
||||
case Nso_Wkc5:
|
||||
case Nso_Wkc6:
|
||||
case Nso_Wkc7:
|
||||
case Nso_Wkc8:
|
||||
case Nso_Wkc9:
|
||||
if ((acid_flags & Acid::AcidFlag_LoadBrowserCoreDll) == 0) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
fs::FileHandle file;
|
||||
if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), GetNsoPath(i), fs::OpenMode_Read))) {
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* 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());
|
||||
|
||||
/* Note nso is present. */
|
||||
switch (i) {
|
||||
case Nso_Rtld:
|
||||
ctx.rtld_idx = ctx.nso_count;
|
||||
ctx.ali.has_rtld = true;
|
||||
break;
|
||||
case Nso_Main:
|
||||
ctx.main_nso_idx = ctx.nso_count;
|
||||
ctx.ali.has_main = true;
|
||||
break;
|
||||
case Nso_SubSdk0:
|
||||
case Nso_SubSdk1:
|
||||
case Nso_SubSdk2:
|
||||
case Nso_SubSdk3:
|
||||
case Nso_SubSdk4:
|
||||
case Nso_SubSdk5:
|
||||
case Nso_SubSdk6:
|
||||
case Nso_SubSdk7:
|
||||
case Nso_SubSdk8:
|
||||
case Nso_SubSdk9:
|
||||
ctx.ali.has_subsdk = true;
|
||||
break;
|
||||
case Nso_Sdk:
|
||||
ctx.sdk_nso_idx = ctx.nso_count;
|
||||
ctx.ali.has_sdk = true;
|
||||
break;
|
||||
}
|
||||
ctx.ali.nso_indices[ctx.nso_count] = static_cast<s8>(i);
|
||||
ctx.nso_count++;
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheckAutoLoad(const AutoLoadModuleContext &ctx, u32 acid_flags) {
|
||||
/* We must always have a main. */
|
||||
R_UNLESS(ctx.ali.has_main, ldr::ResultInvalidNso());
|
||||
|
||||
/* Validate flags and extents for all present NSOs. */
|
||||
for (int i = 0; i < ctx.nso_count; ++i) {
|
||||
const auto &hdr = ctx.headers[i];
|
||||
|
||||
/* All NSOs must not be --X. */
|
||||
/* This is "probably" not checked on Ounce? */
|
||||
R_UNLESS((hdr.flags & NsoHeader::Flag_PreventCodeReads) == 0, ldr::ResultInvalidNso());
|
||||
|
||||
/* 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());
|
||||
}
|
||||
|
||||
/* NSOs must have page-aligned segments. */
|
||||
R_UNLESS(util::IsAligned(hdr.text_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
|
||||
R_UNLESS(util::IsAligned(hdr.ro_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
|
||||
R_UNLESS(util::IsAligned(hdr.rw_dst_offset, os::MemoryPageSize), ldr::ResultInvalidNso());
|
||||
|
||||
/* NSOs must have zero text offset. */
|
||||
R_UNLESS(hdr.text_dst_offset == 0, ldr::ResultInvalidNso());
|
||||
|
||||
/* NSO .text must precede .rodata. */
|
||||
const size_t text_end = static_cast<size_t>(hdr.text_dst_offset) + static_cast<size_t>(hdr.text_size);
|
||||
R_UNLESS(text_end <= static_cast<size_t>(hdr.ro_dst_offset), ldr::ResultInvalidNso());
|
||||
|
||||
/* NSO .rodata must precede .rwdata. */
|
||||
const size_t ro_end = static_cast<size_t>(hdr.ro_dst_offset) + static_cast<size_t>(hdr.ro_size);
|
||||
R_UNLESS(ro_end <= static_cast<size_t>(hdr.rw_dst_offset), ldr::ResultInvalidNso());
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
/* If we have rtld, we must not have browser core dll. */
|
||||
R_UNLESS(!has_browser_dll, ldr::ResultInvalidNso());
|
||||
} else {
|
||||
/* We must not have both subsdk and browser dll. */
|
||||
R_UNLESS(!(ctx.ali.has_subsdk && has_browser_dll), ldr::ResultInvalidNso());
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
constexpr const ncm::ProgramId UnqualifiedApprovalProgramIds[] = {
|
||||
{ 0x010003F003A34000 }, /* Pokemon: Let's Go, Pikachu! */
|
||||
{ 0x0100152000022000 }, /* Mario Kart 8 Deluxe */
|
||||
{ 0x0100165003504000 }, /* Nintendo Labo Toy-Con 04: VR Kit */
|
||||
{ 0x0100187003A36000 }, /* Pokemon: Let's Go, Eevee! */
|
||||
{ 0x01002E5008C56000 }, /* Pokemon Sword [Live Tournament] */
|
||||
{ 0x01002FF008C24000 }, /* Ring Fit Adventure */
|
||||
{ 0x010049900F546001 }, /* Super Mario 3D All-Stars: Super Mario 64 */
|
||||
{ 0x010057D00ECE4000 }, /* Nintendo Switch Online (Nintendo 64) [for Japan] */
|
||||
{ 0x01006F8002326000 }, /* Animal Crossing: New Horizons */
|
||||
{ 0x01006FB00F50E000 }, /* 宝可梦 走吧!伊布 [Pokemon: Let's Go, Eevee! for China] */
|
||||
{ 0x010070300F50C000 }, /* 宝可梦 走吧!皮卡丘 [Pokemon: Let's Go, Pikachu! for China] */
|
||||
{ 0x010075100E8EC000 }, /* 马力欧卡丁车8 豪华版 [Mario Kart 8 Deluxe for China] */
|
||||
{ 0x01008DB008C2C000 }, /* Pokemon Shield */
|
||||
{ 0x01009AD008C4C000 }, /* Pokemon: Let's Go, Pikachu! [Kiosk] */
|
||||
{ 0x0100A66003384000 }, /* Hulu */
|
||||
{ 0x0100ABF008968000 }, /* Pokemon Sword */
|
||||
{ 0x0100C9A00ECE6000 }, /* Nintendo Switch Online (Nintendo 64) [for America] */
|
||||
{ 0x0100ED100BA3A000 }, /* Mario Kart Live: Home Circuit */
|
||||
{ 0x0100F38011CFE000 }, /* Animal Crossing: New Horizons Island Transfer Tool */
|
||||
{ 0x0100F6B011028000 }, /* 健身环大冒险 [Ring Fit Adventure for China] */
|
||||
};
|
||||
|
||||
/* Check that the unqualified approval programs are sorted. */
|
||||
static_assert([]() -> bool {
|
||||
for (size_t i = 0; i < util::size(UnqualifiedApprovalProgramIds) - 1; ++i) {
|
||||
if (UnqualifiedApprovalProgramIds[i].value >= UnqualifiedApprovalProgramIds[i + 1].value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}());
|
||||
|
||||
bool IsUnqualifiedApprovalProgramId(ncm::ProgramId program_id) {
|
||||
/* Check if the program id is one with unqualified approval. */
|
||||
return std::binary_search(std::begin(UnqualifiedApprovalProgramIds), std::end(UnqualifiedApprovalProgramIds), program_id);
|
||||
}
|
||||
|
||||
bool IsUnqualifiedApproval(const Meta *meta) {
|
||||
/* If the meta has unqualified approval flag, it's unqualified approval. */
|
||||
if (meta->acid->flags & ldr::Acid::AcidFlag_UnqualifiedApproval) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* If the unqualified approval flag is not set, the program must be an application. */
|
||||
if (!IsApplication(meta)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The program id must be a force unqualified approval program id. */
|
||||
return IsUnqualifiedApprovalProgramId(meta->acid->program_id_min) && meta->acid->program_id_min == meta->acid->program_id_max;
|
||||
}
|
||||
|
||||
Result ValidateMeta(const Meta *meta, const ncm::ProgramLocation &loc, const fs::CodeVerificationData &code_verification_data) {
|
||||
/* Validate version. */
|
||||
R_TRY(ValidateProgramVersion(loc.program_id, meta->npdm->version));
|
||||
|
||||
/* Validate program id. */
|
||||
R_UNLESS(meta->aci->program_id >= meta->acid->program_id_min, ldr::ResultInvalidProgramId());
|
||||
R_UNLESS(meta->aci->program_id <= meta->acid->program_id_max, ldr::ResultInvalidProgramId());
|
||||
|
||||
/* Validate the kernel capabilities. */
|
||||
R_TRY(TestCapability(static_cast<const util::BitPack32 *>(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32), static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)));
|
||||
|
||||
/* Check if NCA is PCV or PTM */
|
||||
g_is_pcv = meta->aci->program_id == ncm::SystemProgramId::Pcv;
|
||||
g_is_ptm = meta->aci->program_id == ncm::SystemProgramId::Ptm;
|
||||
g_is_nvsrv = meta->aci->program_id == ncm::SystemProgramId::NvServices;
|
||||
|
||||
/* If we have data to validate, validate it. */
|
||||
if (meta->check_verification_data) {
|
||||
const u8 *sig = code_verification_data.signature;
|
||||
const size_t sig_size = sizeof(code_verification_data.signature);
|
||||
const u8 *mod = static_cast<u8 *>(meta->modulus);
|
||||
const size_t mod_size = crypto::Rsa2048PssSha256Verifier::ModulusSize;
|
||||
const u8 *exp = fssystem::GetAcidSignatureKeyPublicExponent();
|
||||
const size_t exp_size = fssystem::AcidSignatureKeyPublicExponentSize;
|
||||
const u8 *hsh = code_verification_data.target_hash;
|
||||
const size_t hsh_size = sizeof(code_verification_data.target_hash);
|
||||
const bool is_signature_valid = crypto::VerifyRsa2048PssSha256WithHash(sig, sig_size, mod, mod_size, exp, exp_size, hsh, hsh_size);
|
||||
|
||||
/* If the signature check fails, we need to check if this is allowable. */
|
||||
if (!is_signature_valid) {
|
||||
/* 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());
|
||||
|
||||
/* There was no signature to check on dev. Check if this is acceptable. */
|
||||
R_UNLESS(IsUnqualifiedApproval(meta), ldr::ResultInvalidNcaSignature());
|
||||
}
|
||||
}
|
||||
|
||||
/* All good. */
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result GetCreateProcessFlags(u32 *out, const Meta *meta, const u32 ldr_flags) {
|
||||
const u8 meta_flags = meta->npdm->flags;
|
||||
|
||||
u32 flags = 0;
|
||||
|
||||
/* Set Is64Bit. */
|
||||
if (meta_flags & Npdm::MetaFlag_Is64Bit) {
|
||||
flags |= svc::CreateProcessFlag_Is64Bit;
|
||||
}
|
||||
|
||||
/* Set AddressSpaceType. */
|
||||
switch (GetAddressSpaceType(meta)) {
|
||||
case Npdm::AddressSpaceType_32Bit:
|
||||
flags |= svc::CreateProcessFlag_AddressSpace32Bit;
|
||||
break;
|
||||
case Npdm::AddressSpaceType_64BitDeprecated:
|
||||
flags |= svc::CreateProcessFlag_AddressSpace64BitDeprecated;
|
||||
break;
|
||||
case Npdm::AddressSpaceType_32BitWithoutAlias:
|
||||
flags |= svc::CreateProcessFlag_AddressSpace32BitWithoutAlias;
|
||||
break;
|
||||
case Npdm::AddressSpaceType_64Bit:
|
||||
flags |= svc::CreateProcessFlag_AddressSpace64Bit;
|
||||
break;
|
||||
default:
|
||||
R_THROW(ldr::ResultInvalidMeta());
|
||||
}
|
||||
|
||||
/* Set Enable Debug. */
|
||||
if (ldr_flags & CreateProcessFlag_EnableDebug) {
|
||||
flags |= svc::CreateProcessFlag_EnableDebug;
|
||||
}
|
||||
|
||||
/* Set Enable ASLR. */
|
||||
if (!(ldr_flags & CreateProcessFlag_DisableAslr)) {
|
||||
flags |= svc::CreateProcessFlag_EnableAslr;
|
||||
}
|
||||
|
||||
/* Set Is Application. */
|
||||
if (IsApplication(meta)) {
|
||||
flags |= svc::CreateProcessFlag_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 5.0.0+ Set Pool Partition. */
|
||||
if (hos::GetVersion() >= hos::Version_5_0_0) {
|
||||
/* TODO: Nintendo no longer accepts Applet when pool partition == application. Would this break hbl/anything else in the hb ecosystem? */
|
||||
/* TODO: Nintendo uses a helper bool MakeSvcPoolPartitionFlag(u32 *out, Acid::PoolPartition partition); */
|
||||
switch (GetPoolPartition(meta)) {
|
||||
case Acid::PoolPartition_Application:
|
||||
if (IsApplet(meta)) {
|
||||
flags |= svc::CreateProcessFlag_PoolPartitionApplet;
|
||||
} else {
|
||||
flags |= svc::CreateProcessFlag_PoolPartitionApplication;
|
||||
}
|
||||
break;
|
||||
case Acid::PoolPartition_Applet:
|
||||
flags |= svc::CreateProcessFlag_PoolPartitionApplet;
|
||||
break;
|
||||
case Acid::PoolPartition_System:
|
||||
flags |= svc::CreateProcessFlag_PoolPartitionSystem;
|
||||
break;
|
||||
case Acid::PoolPartition_SystemNonSecure:
|
||||
flags |= svc::CreateProcessFlag_PoolPartitionSystemNonSecure;
|
||||
break;
|
||||
default:
|
||||
R_THROW(ldr::ResultInvalidMeta());
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
/* 11.0.0+/meso Set Disable DAS merge. */
|
||||
if (meta_flags & Npdm::MetaFlag_DisableDeviceAddressSpaceMerge) {
|
||||
flags |= svc::CreateProcessFlag_DisableDeviceAddressSpaceMerge;
|
||||
}
|
||||
|
||||
/* 18.0.0+/meso Set Alias region extra size. */
|
||||
if (meta_flags & Npdm::MetaFlag_EnableAliasRegionExtraSize) {
|
||||
flags |= svc::CreateProcessFlag_EnableAliasRegionExtraSize;
|
||||
}
|
||||
|
||||
*out = flags;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result GetCreateProcessParameter(svc::CreateProcessParameter *out, const Meta *meta, u32 flags, os::NativeHandle resource_limit) {
|
||||
/* Clear output. */
|
||||
std::memset(out, 0, sizeof(*out));
|
||||
|
||||
/* Set name, version, program id, resource limit handle. */
|
||||
std::memcpy(out->name, meta->npdm->program_name, sizeof(out->name) - 1);
|
||||
out->version = meta->npdm->version;
|
||||
out->program_id = meta->aci->program_id.value;
|
||||
out->reslimit = resource_limit;
|
||||
|
||||
/* Set flags. */
|
||||
R_TRY(GetCreateProcessFlags(std::addressof(out->flags), meta, flags));
|
||||
|
||||
/* 3.0.0+ System Resource Size. */
|
||||
if (hos::GetVersion() >= hos::Version_3_0_0) {
|
||||
/* Validate size is aligned. */
|
||||
R_UNLESS(util::IsAligned(meta->npdm->system_resource_size, os::MemoryBlockUnitSize), ldr::ResultInvalidSize());
|
||||
|
||||
/* Validate system resource usage. */
|
||||
if (meta->npdm->system_resource_size) {
|
||||
/* Process must be 64-bit. */
|
||||
R_UNLESS((out->flags & svc::CreateProcessFlag_AddressSpace64Bit), ldr::ResultInvalidMeta());
|
||||
|
||||
/* Process must be application or applet. */
|
||||
R_UNLESS(IsApplication(meta) || IsApplet(meta), ldr::ResultInvalidMeta());
|
||||
|
||||
/* Size must be less than or equal to max. */
|
||||
R_UNLESS(meta->npdm->system_resource_size <= SystemResourceSizeMax, ldr::ResultInvalidMeta());
|
||||
}
|
||||
out->system_resource_num_pages = meta->npdm->system_resource_size >> 12;
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
u64 GenerateSecureRandom(u64 max) {
|
||||
/* Generate a cryptographically random number. */
|
||||
u64 rand;
|
||||
crypto::GenerateCryptographicallyRandomBytes(std::addressof(rand), sizeof(rand));
|
||||
|
||||
/* Coerce into range. */
|
||||
return rand % (max + 1);
|
||||
}
|
||||
|
||||
Result DecideAddressSpaceLayout(ProcessInfo *out, svc::CreateProcessParameter *out_param, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument) {
|
||||
/* Clear output. */
|
||||
out->args_address = 0;
|
||||
out->args_size = 0;
|
||||
std::memset(out->nso_address, 0, sizeof(out->nso_address));
|
||||
std::memset(out->nso_size, 0, sizeof(out->nso_size));
|
||||
|
||||
size_t total_size = 0;
|
||||
bool argument_allocated = false;
|
||||
|
||||
/* Calculate base offsets. */
|
||||
for (int i = 0; i < ctx.nso_count; i++) {
|
||||
out->nso_address[i] = total_size;
|
||||
const size_t text_end = static_cast<size_t>(ctx.headers[i].text_dst_offset) + static_cast<size_t>(ctx.headers[i].text_size);
|
||||
const size_t ro_end = static_cast<size_t>(ctx.headers[i].ro_dst_offset) + static_cast<size_t>(ctx.headers[i].ro_size);
|
||||
const size_t rw_end = static_cast<size_t>(ctx.headers[i].rw_dst_offset) + static_cast<size_t>(ctx.headers[i].rw_size);
|
||||
out->nso_size[i] = text_end;
|
||||
out->nso_size[i] = std::max(out->nso_size[i], ro_end);
|
||||
out->nso_size[i] = std::max(out->nso_size[i], rw_end);
|
||||
out->nso_size[i] += static_cast<size_t>(ctx.headers[i].bss_size);
|
||||
|
||||
/* Reserve hook arena memory past pcv's bss. */
|
||||
if (g_is_pcv && i == ctx.main_nso_idx) {
|
||||
out->nso_size[i] = util::AlignUp(out->nso_size[i], os::MemoryPageSize) + hoc::pcv::PcvDataArenaSize;
|
||||
}
|
||||
|
||||
const size_t aligned_up_size = util::AlignUp(out->nso_size[i], os::MemoryPageSize) & (AutoLoadModuleSizeMax - 1);
|
||||
R_UNLESS(out->nso_size[i] <= aligned_up_size, ldr::ResultInvalidNso());
|
||||
R_UNLESS(aligned_up_size > 0, ldr::ResultInvalidNso());
|
||||
|
||||
out->nso_size[i] = aligned_up_size;
|
||||
|
||||
R_UNLESS(util::CanAddWithoutOverflow(total_size, out->nso_size[i]), ldr::ResultInvalidNso());
|
||||
total_size += out->nso_size[i];
|
||||
|
||||
if (!argument_allocated && argument != nullptr) {
|
||||
out->args_address = total_size;
|
||||
out->args_size = util::AlignUp(2 * sizeof(u32) + argument->argument_size * 2 + ArgumentStore::ArgumentBufferSize, os::MemoryPageSize);
|
||||
|
||||
R_UNLESS(util::CanAddWithoutOverflow(total_size, out->args_size), ldr::ResultInvalidNso());
|
||||
total_size += out->args_size;
|
||||
|
||||
argument_allocated = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculate ASLR. */
|
||||
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:
|
||||
aslr_start = svc::AddressSmallMap32Start;
|
||||
aslr_size = svc::AddressSmallMap32Size;
|
||||
break;
|
||||
case svc::CreateProcessFlag_AddressSpace64BitDeprecated:
|
||||
aslr_start = svc::AddressSmallMap36Start;
|
||||
aslr_size = svc::AddressSmallMap36Size;
|
||||
break;
|
||||
case svc::CreateProcessFlag_AddressSpace64Bit:
|
||||
aslr_start = svc::AddressMap39Start;
|
||||
aslr_size = svc::AddressMap39Size;
|
||||
break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
} else {
|
||||
/* On 1.0.0, only 2 address space types existed. */
|
||||
if (out_param->flags & svc::CreateProcessFlag_AddressSpace64BitDeprecated) {
|
||||
aslr_start = svc::AddressSmallMap36Start;
|
||||
aslr_size = svc::AddressSmallMap36Size;
|
||||
} else {
|
||||
aslr_start = svc::AddressSmallMap32Start;
|
||||
aslr_size = svc::AddressSmallMap32Size;
|
||||
}
|
||||
}
|
||||
R_UNLESS(total_size <= aslr_size, svc::ResultOutOfMemory());
|
||||
|
||||
/* Set Create Process output. */
|
||||
uintptr_t aslr_slide = 0;
|
||||
size_t free_size = (aslr_size - total_size);
|
||||
if (out_param->flags & svc::CreateProcessFlag_EnableAslr) {
|
||||
aslr_slide = GenerateSecureRandom(free_size / os::MemoryBlockUnitSize) * os::MemoryBlockUnitSize;
|
||||
}
|
||||
|
||||
/* Set out. */
|
||||
aslr_start += aslr_slide;
|
||||
for (int i = 0; i < ctx.nso_count; i++) {
|
||||
R_UNLESS(util::CanAddWithoutOverflow(out->nso_address[i], aslr_start), ldr::ResultInvalidNso());
|
||||
out->nso_address[i] += aslr_start;
|
||||
}
|
||||
if (out->args_address) {
|
||||
R_UNLESS(util::CanAddWithoutOverflow(out->args_address, aslr_start), ldr::ResultInvalidNso());
|
||||
out->args_address += aslr_start;
|
||||
}
|
||||
|
||||
out_param->code_address = aslr_start;
|
||||
out_param->code_num_pages = total_size >> 12;
|
||||
out->total_size = total_size;
|
||||
|
||||
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) {
|
||||
/* Select read size based on compression. */
|
||||
size_t file_size = is_compressed ? compressed_size : segment_size;
|
||||
|
||||
/* Validate size. */
|
||||
R_UNLESS(file_size <= segment_size, ldr::ResultInvalidNso());
|
||||
R_UNLESS(file_size <= std::numeric_limits<s32>::max(), ldr::ResultInvalidNso());
|
||||
R_UNLESS(segment_size <= std::numeric_limits<s32>::max(), ldr::ResultInvalidNso());
|
||||
|
||||
/* Load data from file. */
|
||||
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<void *>(load_address), file_size));
|
||||
R_UNLESS(read_size == file_size, ldr::ResultInvalidNso());
|
||||
|
||||
/* Uncompress if necessary. */
|
||||
R_SUCCEED_IF(!is_compressed);
|
||||
|
||||
auto compressed_data_buf = reinterpret_cast<const void *>(load_address);
|
||||
|
||||
if (is_zstd) {
|
||||
bool decompressed = util::DecompressZstdForLoader(reinterpret_cast<void *>(g_zstd_dctx_workspace), ZstdDctxWorkspaceSize, reinterpret_cast<void *>(map_base), static_cast<size_t>(map_end - map_base), segment_size, compressed_data_buf, file_size);
|
||||
R_UNLESS(decompressed, ldr::ResultInvalidNso());
|
||||
} else {
|
||||
bool decompressed = (util::DecompressLZ4(reinterpret_cast<void *>(map_base), segment_size, compressed_data_buf, file_size) == static_cast<int>(segment_size));
|
||||
R_UNLESS(decompressed, ldr::ResultInvalidNso());
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheckSegmentHash(const NsoHeader *nso_header, uintptr_t map_address, NsoHeader::Segment segment) {
|
||||
if ((nso_header->flags & (NsoHeader::Flag_CheckHashText << segment)) == 0) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
u8 hash[crypto::Sha256Generator::HashSize];
|
||||
crypto::GenerateSha256(hash, sizeof(hash),
|
||||
reinterpret_cast<void *>(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_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 size_t module_size = static_cast<size_t>(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;
|
||||
|
||||
/* Map and read data from file. */
|
||||
{
|
||||
/* Map the process memory. */
|
||||
void *mapped_memory = nullptr;
|
||||
R_TRY(os::MapProcessMemory(std::addressof(mapped_memory), process_handle, nso_address, map_size, GenerateSecureRandom));
|
||||
ON_SCOPE_EXIT { os::UnmapProcessMemory(mapped_memory, process_handle, nso_address, map_size); };
|
||||
|
||||
const uintptr_t map_address = reinterpret_cast<uintptr_t>(mapped_memory);
|
||||
const uintptr_t map_end = map_address + map_size;
|
||||
|
||||
/* 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));
|
||||
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));
|
||||
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));
|
||||
|
||||
/* Clear unused space to zero. */
|
||||
const size_t text_end = static_cast<size_t>(nso_header->text_dst_offset) + static_cast<size_t>(nso_header->text_size);
|
||||
const size_t ro_end = static_cast<size_t>(nso_header->ro_dst_offset) + static_cast<size_t>(nso_header->ro_size);
|
||||
const size_t rw_end = static_cast<size_t>(nso_header->rw_dst_offset) + static_cast<size_t>(nso_header->rw_size);
|
||||
std::memset(reinterpret_cast<void *>(map_address + text_end), 0, nso_header->ro_dst_offset - text_end);
|
||||
std::memset(reinterpret_cast<void *>(map_address + ro_end), 0, nso_header->rw_dst_offset - ro_end);
|
||||
std::memset(reinterpret_cast<void *>(map_address + rw_end), 0, nso_size - rw_end);
|
||||
|
||||
/* Check segment hashes. */
|
||||
R_TRY(CheckSegmentHash(nso_header, map_address, NsoHeader::Segment_Text));
|
||||
R_TRY(CheckSegmentHash(nso_header, map_address, NsoHeader::Segment_Ro));
|
||||
R_TRY(CheckSegmentHash(nso_header, map_address, NsoHeader::Segment_Rw));
|
||||
|
||||
/* Apply embedded patches. */
|
||||
ApplyEmbeddedPatchesToModule(nso_header->module_id, map_address, nso_size);
|
||||
|
||||
/* Apply IPS patches. */
|
||||
LocateAndApplyIpsPatchesToModule(nso_header->module_id, map_address, nso_size);
|
||||
|
||||
/* Apply PCV and PTM patches */
|
||||
if (g_is_pcv) {
|
||||
const size_t text_end = static_cast<size_t>(nso_header->text_size);
|
||||
const size_t rx_end = util::AlignUp(text_end, os::MemoryPageSize);
|
||||
const size_t ro_start = static_cast<size_t>(nso_header->ro_dst_offset);
|
||||
const size_t cave_end = (rx_end < ro_start) ? rx_end : ro_start;
|
||||
const uintptr_t cave = map_address + text_end;
|
||||
const size_t cave_size = (cave_end > text_end) ? (cave_end - text_end) : 0;
|
||||
|
||||
/* module_size is used rather than nso_size to exclude the extra data section. */
|
||||
hoc::pcv::Patch(map_address, module_size, cave, cave_size, nso_address, arena_size ? (map_address + module_size) : 0);
|
||||
}
|
||||
|
||||
if (g_is_ptm) {
|
||||
hoc::ptm::Patch(map_address, nso_size);
|
||||
}
|
||||
|
||||
// TODO: figure out the cause of crashes with nvsrv patches
|
||||
if (g_is_nvsrv) {
|
||||
// hoc::nvsrv::Patch(map_address, nso_size, nso_address);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set permissions. */
|
||||
const size_t text_size = util::AlignUp(nso_header->text_size, os::MemoryPageSize);
|
||||
const size_t ro_size = util::AlignUp(nso_header->ro_size, os::MemoryPageSize);
|
||||
const size_t rw_size = util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize) + arena_size;
|
||||
if (text_size) {
|
||||
const bool prevent_code_reads = (nso_header->flags & NsoHeader::Flag_PreventCodeReads);
|
||||
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->text_dst_offset, text_size, prevent_code_reads ? os::MemoryPermission_ExecuteOnly : os::MemoryPermission_ReadExecute));
|
||||
}
|
||||
if (ro_size) {
|
||||
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->ro_dst_offset, ro_size, os::MemoryPermission_ReadOnly));
|
||||
}
|
||||
if (rw_size) {
|
||||
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->rw_dst_offset, rw_size, os::MemoryPermission_ReadWrite));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result LoadAutoLoadModules(const ProcessInfo *process_info, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument) {
|
||||
/* Load each NSO. */
|
||||
const uintptr_t total_end = process_info->code_address + process_info->total_size;
|
||||
|
||||
for (int i = 0; i < ctx.nso_count; i++) {
|
||||
const NsoIndex nso_idx = static_cast<NsoIndex>(ctx.ali.nso_indices[i]);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/* Load arguments, if present. */
|
||||
if (argument != nullptr) {
|
||||
/* Write argument data into memory. */
|
||||
{
|
||||
void *map_address = nullptr;
|
||||
R_TRY(os::MapProcessMemory(std::addressof(map_address), process_info->process_handle, process_info->args_address, process_info->args_size, GenerateSecureRandom));
|
||||
ON_SCOPE_EXIT { os::UnmapProcessMemory(map_address, process_info->process_handle, process_info->args_address, process_info->args_size); };
|
||||
|
||||
ProgramArguments *args = static_cast<ProgramArguments *>(map_address);
|
||||
std::memset(args, 0, sizeof(*args));
|
||||
args->allocated_size = process_info->args_size;
|
||||
args->arguments_size = argument->argument_size;
|
||||
std::memcpy(args->arguments, argument->argument, argument->argument_size);
|
||||
}
|
||||
|
||||
/* Set argument region permissions. */
|
||||
/* NOTE: Nintendo uses svc::SetProcessMemoryPermission directly here. */
|
||||
R_TRY(os::SetProcessMemoryPermission(process_info->process_handle, process_info->args_address, process_info->args_size, os::MemoryPermission_ReadWrite));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) {
|
||||
/* Get CreateProcessParameter. */
|
||||
svc::CreateProcessParameter param;
|
||||
R_TRY(GetCreateProcessParameter(std::addressof(param), meta, flags, resource_limit));
|
||||
|
||||
/* Decide on an NSO layout. */
|
||||
R_TRY(DecideAddressSpaceLayout(out, std::addressof(param), ctx, argument));
|
||||
|
||||
/* Actually create process. */
|
||||
svc::Handle process_handle;
|
||||
R_TRY(svc::CreateProcess(std::addressof(process_handle), std::addressof(param), static_cast<const u32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(u32)));
|
||||
|
||||
/* 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); };
|
||||
|
||||
/* Load all auto load modules. */
|
||||
R_RETURN(LoadAutoLoadModules(out, ctx, argument));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Process Creation API. */
|
||||
Result CreateProcess(os::NativeHandle *out, PinId pin_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status, const char *path, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit, const ldr::ProgramAttributes &attrs) {
|
||||
/* Mount code. */
|
||||
AMS_UNUSED(path);
|
||||
ScopedCodeMountForCode mount(loc, override_status, attrs);
|
||||
R_TRY(mount.GetResult());
|
||||
|
||||
/* Load meta, possibly from cache. */
|
||||
Meta meta;
|
||||
R_TRY(LoadMetaFromCache(std::addressof(meta), loc, override_status, attrs.platform));
|
||||
|
||||
/* Validate meta. */
|
||||
R_TRY(ValidateMeta(std::addressof(meta), loc, mount.GetCodeVerificationData()));
|
||||
|
||||
/* If we should, load/validate the browser core dll. */
|
||||
util::optional<ScopedCodeMountForBrowserCoreDll> bdll_mount;
|
||||
if ((meta.acid->flags & Acid::AcidFlag_LoadBrowserCoreDll)) {
|
||||
/* NOTE: I'm unsure whether we should be getting a fresh override status (allowing for different override between main and bdll?) */
|
||||
/* or whether we should be using the main override status. Going to go with main, for sanity's sake. */
|
||||
/* Also noting that Nintendo always passes ProgramAttributes=0 here, but this "should" be different on Ounce? */
|
||||
/* Kind of unclear how to handle this without knowing what exactly is being ifdef'd. */
|
||||
const ncm::ProgramLocation bdll_loc = ncm::ProgramLocation::Make(ncm::SystemProgramId::BrowserCoreDll, ncm::StorageId::BuiltInSystem);
|
||||
const cfg::OverrideStatus bdll_override_status = override_status;
|
||||
const ldr::ProgramAttributes bdll_attrs = attrs;
|
||||
bdll_mount.emplace(bdll_loc, bdll_override_status, bdll_attrs);
|
||||
R_TRY(bdll_mount->GetResult());
|
||||
|
||||
/* Load browser dll meta, possibly from cache. */
|
||||
Meta bdll_meta;
|
||||
R_TRY(LoadMetaFromCacheForBrowserCoreDll(std::addressof(bdll_meta), bdll_loc, bdll_override_status, bdll_attrs.platform));
|
||||
|
||||
/* Validate browser dll meta. */
|
||||
R_TRY(ValidateMeta(std::addressof(bdll_meta), loc, mount.GetCodeVerificationData()));
|
||||
}
|
||||
|
||||
/* Load, validate NSO headers. */
|
||||
AutoLoadModuleContext ctx;
|
||||
R_TRY(LoadAutoLoadHeaders(ctx, meta.acid->flags));
|
||||
R_TRY(CheckAutoLoad(ctx, meta.acid->flags));
|
||||
|
||||
/* Actually create the process and load NSOs into process memory. */
|
||||
ProcessInfo info;
|
||||
R_TRY(CreateProcessAndLoadAutoLoadModules(std::addressof(info), std::addressof(meta), ctx, argument, flags, resource_limit));
|
||||
|
||||
/* Register NSOs with the RoManager. */
|
||||
{
|
||||
/* Nintendo doesn't validate this get, but we do. */
|
||||
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);
|
||||
|
||||
/* Register all NSOs. */
|
||||
for (int i = 0; i < ctx.nso_count; i++) {
|
||||
RoManager::GetInstance().AddNso(pin_id, ctx.headers[i].module_id, info.nso_address[i], info.nso_size[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* If we're overriding for HBL, perform HTML document redirection. */
|
||||
if (override_status.IsHbl()) {
|
||||
/* Don't validate result, failure is okay. */
|
||||
RedirectHtmlDocumentPathForHbl(loc);
|
||||
}
|
||||
|
||||
/* Clear the external code for the program. */
|
||||
fssystem::DestroyExternalCode(loc.program_id);
|
||||
|
||||
/* Note that we've created the program. */
|
||||
SetLaunchedBootProgram(loc.program_id);
|
||||
|
||||
/* Move the process handle to output. */
|
||||
*out = info.process_handle;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result GetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, const char *path, const ldr::ProgramAttributes &attrs) {
|
||||
Meta meta;
|
||||
|
||||
/* Load Meta. */
|
||||
{
|
||||
AMS_UNUSED(path);
|
||||
|
||||
ScopedCodeMountForCode mount(loc, attrs);
|
||||
R_TRY(mount.GetResult());
|
||||
R_TRY(LoadMeta(std::addressof(meta), loc, mount.GetOverrideStatus(), attrs.platform, false));
|
||||
if (out_status != nullptr) {
|
||||
*out_status = mount.GetOverrideStatus();
|
||||
}
|
||||
}
|
||||
|
||||
return GetProgramInfoFromMeta(out, std::addressof(meta));
|
||||
}
|
||||
|
||||
Result PinProgram(PinId *out_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status) {
|
||||
R_UNLESS(RoManager::GetInstance().Allocate(out_id, loc, override_status), ldr::ResultMaxProcess());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result UnpinProgram(PinId id) {
|
||||
R_UNLESS(RoManager::GetInstance().Free(id), ldr::ResultNotPinned());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out, size_t max_out_count, os::ProcessId process_id) {
|
||||
R_UNLESS(RoManager::GetInstance().GetProcessModuleInfo(out_count, out, max_out_count, process_id), ldr::ResultNotPinned());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result GetProgramLocationAndOverrideStatusFromPinId(ncm::ProgramLocation *out, cfg::OverrideStatus *out_status, PinId pin_id) {
|
||||
R_UNLESS(RoManager::GetInstance().GetProgramLocationAndStatus(out, out_status, pin_id), ldr::ResultNotPinned());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
0
Source/hoc-clk/build.sh
Normal file → Executable file
0
Source/hoc-clk/build.sh
Normal file → Executable file
@@ -82,10 +82,9 @@ typedef struct {
|
||||
u8 custRev;
|
||||
u16 kipVersion;
|
||||
bool isKipLoaded;
|
||||
bool rebootRequired;
|
||||
|
||||
// Reserved for future use
|
||||
u8 reserved[0x359];
|
||||
u8 reserved[0x35A];
|
||||
} HocClkContext;
|
||||
|
||||
typedef struct
|
||||
@@ -96,7 +95,7 @@ typedef struct
|
||||
};
|
||||
} HocClkTitleProfileList;
|
||||
|
||||
#define HOCCLK_FREQ_LIST_MAX 64
|
||||
#define HOCCLK_FREQ_LIST_MAX 48
|
||||
|
||||
#define HOCCLK_GLOBAL_PROFILE_TID 0xA111111111111111
|
||||
|
||||
|
||||
@@ -387,7 +387,7 @@ static inline const char* hocclkFormatConfigValue(HocClkConfigValue val, bool pr
|
||||
case KipConfigValue_low_t7_tWTR:
|
||||
return pretty ? "Low T7 - tWTR" : "low_t7_tWTR";
|
||||
case KipConfigValue_low_t8_tREFI:
|
||||
return pretty ? "Low T8 - tREFI" : "low_t7_tREFI";
|
||||
return pretty ? "Low T8 - tREFI" : "low_t8_tREFI";
|
||||
|
||||
case KipConfigValue_read_latency_1333:
|
||||
return pretty ? "1333 Read Latency" : "read_latency_1333";
|
||||
@@ -572,6 +572,7 @@ static inline uint64_t hocclkDefaultConfigValue(HocClkConfigValue val)
|
||||
case HocClkConfigValue_MemoryFrequencyMeasurementMode:
|
||||
case HocClkConfigValue_MarikoMiddleFreqs:
|
||||
case HocClkConfigValue_LiveGpuVoltage:
|
||||
case HocClkConfigValue_AutoRAMCPUOverclock:
|
||||
return 0ULL;
|
||||
case HocClkConfigValue_RamDisplayUnit:
|
||||
return (uint64_t)RamDisplayUnit_MHz;
|
||||
@@ -584,7 +585,6 @@ static inline uint64_t hocclkDefaultConfigValue(HocClkConfigValue val)
|
||||
case HocClkConfigValue_ThermalThrottle:
|
||||
case HocClkConfigValue_IsFirstLoad:
|
||||
case HocClkConfigValue_DVFSMode:
|
||||
case HocClkConfigValue_AutoRAMCPUOverclock:
|
||||
return 1ULL;
|
||||
case HocClkConfigValue_AutoRamCpuCpuOCFreq:
|
||||
return 1683000ULL;
|
||||
|
||||
@@ -39,7 +39,7 @@ include ${TOPDIR}/lib/libultrahand/ultrahand.mk
|
||||
# version control constants
|
||||
#---------------------------------------------------------------------------------
|
||||
#TARGET_VERSION := $(shell git describe --dirty --always --tags)
|
||||
APP_VERSION := 3.0.0 # ensure to set KIP_VERSION and CUST_REV in sysmodule Makefile when updating this
|
||||
APP_VERSION := 2.6.0 # ensure to set KIP_VERSION and CUST_REV in sysmodule Makefile when updating this
|
||||
TARGET_VERSION := $(APP_VERSION)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
@@ -96,15 +96,6 @@ class AppOverlay : public tsl::Overlay {
|
||||
"");
|
||||
}
|
||||
|
||||
HocClkContext context = {};
|
||||
if (R_SUCCEEDED(hocclkIpcGetCurrentContext(&context)) && context.rebootRequired) {
|
||||
return initially<FatalGui>("Horizon OC has been updated.\n\n"
|
||||
"\n"
|
||||
"Please reboot your console\n\n"
|
||||
"to finish applying the update.",
|
||||
"");
|
||||
}
|
||||
|
||||
return initially<MainGui>();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -132,7 +132,7 @@ void BaseGui::preDraw(tsl::gfx::Renderer *renderer) {
|
||||
|
||||
drawDynamicUltraText(renderer, LOGO_TEXT_X, TEXT_Y, LOGO_LABEL_FONT_SIZE, STATIC_TEAL, false);
|
||||
|
||||
static const std::string versionStr = "Version " + getVersionString() + " \"Athena\"";
|
||||
static const std::string versionStr = "Version " + getVersionString() + " \"Gaea\"";
|
||||
static constexpr tsl::Color versionColor(9, 9, 9, 15);
|
||||
static constexpr s32 vx = LOGO_TEXT_X + 15;
|
||||
static constexpr s32 vy = TEXT_Y + 18;
|
||||
|
||||
@@ -228,15 +228,15 @@ std::vector<std::string> ConfigInfoStrings(HocClkConfigValue val, bool isMariko,
|
||||
case KipConfigValue_stepMode:
|
||||
return {
|
||||
"The step that RAM clocks take.",
|
||||
"Options:",
|
||||
" - 33MHz - 3 MHz step (ex. 1600, 1633, 1666, 1700, etc.)",
|
||||
"Options (with examples):",
|
||||
" - 66MHz - 66 MHz step (ex. 1600, 1666, 1733, etc.)",
|
||||
" - 100MHz - 100 MHz step (ex. 1600, 1700, 1800, etc.)",
|
||||
" - 133MHz - 133 MHz step (ex. 1600, 1733, 1866, etc.)",
|
||||
" - 133MHz - 66 MHz step (ex. 1600, 1733, 1866, etc.)",
|
||||
" - JEDEC:",
|
||||
" - 1600, 1866, 1996, 2133, 2400, 2666, 2933 and 3200 MHz are used",
|
||||
"The RAM max clock will always be available regardless of the step mode, but the intermediate frequencies will be limited by the selected step mode.",
|
||||
"This setting does not affect performance and the option you choose mostly is based on your personal taste",
|
||||
"This setting does not affect performance and the option you choose mostly is based on your personal taste",
|
||||
"33 MHz step mode is not possible due to certain limitations of Horizon OS",
|
||||
"Default: 66 MHz",
|
||||
};
|
||||
|
||||
|
||||
@@ -21,12 +21,12 @@
|
||||
|
||||
std::map<uint32_t, std::string> cpu_freq_label_m = {
|
||||
{ 612000000, "Sleep Mode" }, { 1020000000, "Stock" }, { 1224000000, "Dev OC" }, { 1785000000, "Boost Mode" },
|
||||
{ 1963000000, "Safe Max" }, { 2397000000, "Unsafe Max" }, { 2703000000, "Absolute Max" },
|
||||
{ 1963000000, "Safe Max" }, { 2397000000, "Unsafe Max" }, { 2805000000, "Absolute Max" },
|
||||
};
|
||||
|
||||
std::map<uint32_t, std::string> cpu_freq_label_m_uv = {
|
||||
{ 612000000, "Sleep Mode" }, { 1020000000, "Stock" }, { 1224000000, "Dev OC" }, { 1785000000, "Boost Mode" },
|
||||
{ 2397000000, "Safe Max" }, { 2499000000, "Unsafe Max" }, { 2703000000, "Absolute Max" },
|
||||
{ 2397000000, "Safe Max" }, { 2499000000, "Unsafe Max" }, { 2805000000, "Absolute Max" },
|
||||
};
|
||||
|
||||
std::map<uint32_t, std::string> cpu_freq_label_e = {
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
#include "main_gui.h"
|
||||
#include "misc_gui.h"
|
||||
#include "ult_ext.h"
|
||||
#include "update_gui.h"
|
||||
|
||||
tsl::elm::Element *MainGui::baseUI() {
|
||||
auto *list = new BoxClippedList();
|
||||
@@ -114,17 +113,6 @@ void MainGui::listUI() {
|
||||
});
|
||||
this->listElement->addItem(aboutItem);
|
||||
|
||||
tsl::elm::ListItem *updateItem = new tsl::elm::ListItem("Updates");
|
||||
updateItem->setClickListener([this](u64 keys) {
|
||||
if ((keys & HidNpadButton_A) == HidNpadButton_A && this->context) {
|
||||
tsl::swapTo<UpdateGui>();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
this->listElement->addItem(updateItem);
|
||||
|
||||
if (!lastItemName.empty()) {
|
||||
this->listElement->jumpToItem(lastItemName);
|
||||
}
|
||||
|
||||
@@ -925,6 +925,78 @@ class ExperimentalSettingsSubMenuGui : public MiscGui {
|
||||
addConfigButton(HocClkConfigValue_AulaDisplayColorPreset, "Display Color Preset", ValueRange(0, 1, 1, "", 0), "Display Color Preset",
|
||||
&thresholdsDisabled, {}, displayClrPreset, false, false);
|
||||
}
|
||||
std::vector<NamedValue> clkOptionsRamOc = {
|
||||
NamedValue("1122 MHz", 1122000), NamedValue("1224 MHz", 1224000), NamedValue("1326 MHz", 1326000), NamedValue("1428 MHz", 1428000),
|
||||
NamedValue("1581 MHz", 1581000), NamedValue("1683 MHz", 1683000, "Default"), NamedValue("1785 MHz", 1785000), NamedValue("1887 MHz", 1887000),
|
||||
NamedValue("1963 MHz", 1963500), NamedValue("2091 MHz", 2091000), NamedValue("2193 MHz", 2193000), NamedValue("2295 MHz", 2295000),
|
||||
NamedValue("2397 MHz", 2397000), NamedValue("2499 MHz", 2499000), NamedValue("2601 MHz", 2601000), NamedValue("2703 MHz", 2703000),
|
||||
NamedValue("2805 MHz", 2805000),
|
||||
};
|
||||
std::vector<NamedValue> emcMaxClock = {
|
||||
NamedValue("1600 MHz", 1600000),
|
||||
NamedValue("1633 MHz", 1633000),
|
||||
NamedValue("1666 MHz", 1666000),
|
||||
NamedValue("1700 MHz", 1700000),
|
||||
NamedValue("1733 MHz", 1733000),
|
||||
NamedValue("1766 MHz", 1766000),
|
||||
NamedValue("1800 MHz", 1800000),
|
||||
NamedValue("1833 MHz", 1833000),
|
||||
NamedValue("1866 MHz", 1866000, "JEDEC."),
|
||||
NamedValue("1900 MHz", 1900000),
|
||||
NamedValue("1933 MHz", 1933000),
|
||||
NamedValue("1966 MHz", 1966000),
|
||||
NamedValue("1996 MHz", 1996800, "JEDEC."),
|
||||
NamedValue("2000 MHz", 2000000),
|
||||
NamedValue("2033 MHz", 2033000),
|
||||
NamedValue("2066 MHz", 2066000),
|
||||
NamedValue("2100 MHz", 2100000),
|
||||
NamedValue("2133 MHz", 2133000, "JEDEC."),
|
||||
NamedValue("2166 MHz", 2166000),
|
||||
NamedValue("2200 MHz", 2200000),
|
||||
NamedValue("2233 MHz", 2233000),
|
||||
NamedValue("2266 MHz", 2266000),
|
||||
NamedValue("2300 MHz", 2300000),
|
||||
NamedValue("2333 MHz", 2333000),
|
||||
NamedValue("2366 MHz", 2366000),
|
||||
NamedValue("2400 MHz", 2400000, "JEDEC."),
|
||||
NamedValue("2433 MHz", 2433000),
|
||||
NamedValue("2466 MHz", 2466000),
|
||||
NamedValue("2500 MHz", 2500000),
|
||||
NamedValue("2533 MHz", 2533000),
|
||||
NamedValue("2566 MHz", 2566000),
|
||||
NamedValue("2600 MHz", 2600000),
|
||||
NamedValue("2633 MHz", 2633000),
|
||||
NamedValue("2666 MHz", 2666000, "JEDEC."),
|
||||
NamedValue("2700 MHz", 2700000),
|
||||
NamedValue("2733 MHz", 2733000),
|
||||
NamedValue("2766 MHz", 2766000),
|
||||
NamedValue("2800 MHz", 2800000),
|
||||
NamedValue("2833 MHz", 2833000),
|
||||
NamedValue("2866 MHz", 2866000),
|
||||
NamedValue("2900 MHz", 2900000),
|
||||
NamedValue("2933 MHz", 2933000, "JEDEC."),
|
||||
NamedValue("2966 MHz", 2966000),
|
||||
NamedValue("3000 MHz", 3000000),
|
||||
NamedValue("3033 MHz", 3033000),
|
||||
NamedValue("3066 MHz", 3066000),
|
||||
NamedValue("3100 MHz", 3100000),
|
||||
NamedValue("3133 MHz", 3133000),
|
||||
NamedValue("3166 MHz", 3166000),
|
||||
NamedValue("3200 MHz", 3200000, "JEDEC."),
|
||||
NamedValue("3233 MHz", 3233000, "±1720 speedo"),
|
||||
NamedValue("3266 MHz", 3266000, "±1740 speedo"),
|
||||
NamedValue("3300 MHz", 3300000, "±1760 speedo"),
|
||||
NamedValue("3333 MHz", 3333000, "±1780 speedo"),
|
||||
NamedValue("3366 MHz", 3366000, "±1800 speedo"),
|
||||
NamedValue("3400 MHz", 3400000, "±1820 speedo")
|
||||
};
|
||||
|
||||
addConfigToggle(HocClkConfigValue_AutoRAMCPUOverclock, "Auto CPU RAM OC");
|
||||
addConfigButton(HocClkConfigValue_AutoRamCpuCpuOCFreq, "Auto CPU RAM OC CPU clock", ValueRange(0, 0, 1, "", 1), "CPU Clock",
|
||||
&thresholdsDisabled, {}, clkOptionsRamOc, false, false);
|
||||
addConfigButton(HocClkConfigValue_AutoRamCpuRamOCThreshold, "Auto CPU RAM OC Threshold", ValueRange(0, 0, 1, "", 1), "RAM Clock",
|
||||
&thresholdsDisabled, {}, emcMaxClock, false, false);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1132,7 +1204,7 @@ class RamSubmenuGui : public MiscGui {
|
||||
};
|
||||
|
||||
ValueThresholds vdd2Thresholds(IsMariko() ? 1212500 : 1237500, IsMariko() ? 1250000 : 1275000);
|
||||
addConfigButton(KipConfigValue_commonEmcMemVolt, "RAM VDD2 Voltage", ValueRange(912500, 1350000, 12500, "mV", 1000, 1), "Voltage",
|
||||
addConfigButton(KipConfigValue_commonEmcMemVolt, "RAM VDD2 Voltage", ValueRange(912500, IsMariko() ? 1275000 : 1350000, 12500, "mV", 1000, 1), "Voltage",
|
||||
&vdd2Thresholds, emc_voltage_label, noNamedValues, false, true);
|
||||
|
||||
if (IsMariko()) {
|
||||
@@ -1143,7 +1215,6 @@ class RamSubmenuGui : public MiscGui {
|
||||
|
||||
if (IsMariko()) {
|
||||
std::vector<NamedValue> stepMode = {
|
||||
NamedValue("33MHz", 4),
|
||||
NamedValue("66MHz", 0),
|
||||
NamedValue("100MHz", 1),
|
||||
NamedValue("133MHz", 3), // Mantain compatability
|
||||
@@ -2119,25 +2190,19 @@ class CpuSubmenuGui : public MiscGui {
|
||||
{}, false, true);
|
||||
|
||||
ValueThresholds mCpuVoltThresholds(1160, 1180);
|
||||
addConfigButton(KipConfigValue_marikoCpuMaxVolt, "CPU Max Voltage", ValueRange(1000, 1200, 5, "mV", 1), "CPU Max Voltage",
|
||||
addConfigButton(KipConfigValue_marikoCpuMaxVolt, "CPU Max Voltage", ValueRange(1000, 1235, 5, "mV", 1), "CPU Max Voltage",
|
||||
&mCpuVoltThresholds, {}, {}, false, true);
|
||||
|
||||
std::vector<NamedValue> maxClkOptions = {
|
||||
NamedValue("1963 MHz", 1963500, "Rating"), NamedValue("2091 MHz", 2091000), NamedValue("2193 MHz", 2193000), NamedValue("2295 MHz", 2295000),
|
||||
NamedValue("2397 MHz", 2397000, "Safe Max"), NamedValue("2499 MHz", 2499000, "Unsafe Max"), NamedValue("2601 MHz", 2601000), NamedValue("2703 MHz", 2703000, "Absolute Max"),
|
||||
NamedValue("2397 MHz", 2397000, "Safe Max"), NamedValue("2499 MHz", 2499000, "Unsafe Max"), NamedValue("2601 MHz", 2601000), NamedValue("2703 MHz", 2703000),
|
||||
NamedValue("2805 MHz", 2805000, "Absolute Max"),
|
||||
};
|
||||
|
||||
addConfigButton(KipConfigValue_marikoCpuMaxClock, "CPU Max Clock", ValueRange(0, 0, 1, "", 1), "CPU Max Clock",
|
||||
this->configList->values[KipConfigValue_marikoCpuUVHigh] ? &mCpuClockThresholdsUV : &mCpuClockThresholds, {},
|
||||
maxClkOptions, false, true);
|
||||
|
||||
std::vector<NamedValue> clkOptionsRamOc = {
|
||||
NamedValue("1122 MHz", 1122000), NamedValue("1224 MHz", 1224000), NamedValue("1326 MHz", 1326000), NamedValue("1428 MHz", 1428000),
|
||||
NamedValue("1581 MHz", 1581000), NamedValue("1683 MHz", 1683000, "Default"), NamedValue("1785 MHz", 1785000), NamedValue("1887 MHz", 1887000),
|
||||
NamedValue("1963 MHz", 1963500), NamedValue("2091 MHz", 2091000), NamedValue("2193 MHz", 2193000), NamedValue("2295 MHz", 2295000),
|
||||
NamedValue("2397 MHz", 2397000), NamedValue("2499 MHz", 2499000), NamedValue("2601 MHz", 2601000), NamedValue("2703 MHz", 2703000),
|
||||
};
|
||||
|
||||
addConfigButton(KipConfigValue_marikoCpuBoostClock, "CPU Boost Clock", ValueRange(0, 0, 1, "", 1), "CPU Boost Clock",
|
||||
this->configList->values[KipConfigValue_marikoCpuUVHigh] ? &mCpuClockThresholdsUV : &mCpuClockThresholds, {}, maxClkOptions,
|
||||
false, true);
|
||||
@@ -2200,11 +2265,6 @@ class CpuSubmenuGui : public MiscGui {
|
||||
NamedValue("3366 MHz", 3366000, "±1800 speedo"),
|
||||
NamedValue("3400 MHz", 3400000, "±1820 speedo"),
|
||||
};
|
||||
addConfigToggle(HocClkConfigValue_AutoRAMCPUOverclock, "Auto CPU RAM OC");
|
||||
addConfigButton(HocClkConfigValue_AutoRamCpuCpuOCFreq, "Auto CPU RAM OC CPU clock", ValueRange(0, 0, 1, "", 1), "CPU Clock",
|
||||
&thresholdsDisabled, {}, clkOptionsRamOc, false, false);
|
||||
addConfigButton(HocClkConfigValue_AutoRamCpuRamOCThreshold, "Auto CPU RAM OC Threshold", ValueRange(0, 0, 1, "", 1), "RAM Clock",
|
||||
&thresholdsDisabled, {}, emcMaxClock, false, false);
|
||||
} else {
|
||||
addConfigTrackbar(KipConfigValue_eristaCpuUV, "CPU UV", ValueRange(0, 5, 1));
|
||||
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "update_gui.h"
|
||||
#include "ult_ext.h"
|
||||
#include "base_menu_gui.h"
|
||||
#include "../format.h"
|
||||
#include "../style.h"
|
||||
|
||||
#include <tesla.hpp>
|
||||
#include <ultra.hpp>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
|
||||
static constexpr u16 PANEL_H = 80;
|
||||
|
||||
class UpdateStatusPanel : public tsl::elm::Element {
|
||||
public:
|
||||
void setState(int stage, int progress,
|
||||
const std::string &pkg, const std::string &msg) {
|
||||
m_stage = stage;
|
||||
m_progress = progress;
|
||||
m_pkg = pkg;
|
||||
m_msg = msg;
|
||||
}
|
||||
|
||||
void draw(tsl::gfx::Renderer *renderer) override {
|
||||
if (this->getHeight() == 0) return;
|
||||
|
||||
const s32 PAD = 20;
|
||||
const s32 x = this->getX() + PAD;
|
||||
const s32 y = this->getY();
|
||||
const s32 w = this->getWidth() - PAD * 2;
|
||||
|
||||
if (m_stage == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_stage == 1 || m_stage == 2) {
|
||||
const char *opLabel = (m_stage == 1) ? "Downloading" : "Extracting";
|
||||
tsl::Color accent = (m_stage == 1)
|
||||
? tsl::Color{0x4, 0x8, 0xF, 0xF} // blue
|
||||
: tsl::Color{0x3, 0xA, 0x8, 0xF}; // teal
|
||||
|
||||
renderer->drawString(opLabel, false, x, y + 20, 15, accent);
|
||||
renderer->drawString(m_pkg.c_str(), false, x + 104, y + 20, 15,
|
||||
tsl::sectionTextColor);
|
||||
|
||||
const s32 barY = y + 48;
|
||||
renderer->drawRect(x, barY, w, 7, tsl::Color{0x3, 0x3, 0x3, 0xF});
|
||||
|
||||
int pct = (m_progress < 0) ? 0 : (m_progress > 100 ? 100 : m_progress);
|
||||
s32 fillW = (s32)((long)w * pct / 100);
|
||||
if (fillW > 0)
|
||||
renderer->drawRect(x, barY, fillW, 7, accent);
|
||||
|
||||
char buf[8];
|
||||
if (m_progress < 0)
|
||||
std::snprintf(buf, sizeof(buf), "...");
|
||||
else
|
||||
std::snprintf(buf, sizeof(buf), "%d%%", pct);
|
||||
|
||||
u32 tw = renderer->getTextDimensions(buf, false, 13).first;
|
||||
renderer->drawString(buf, false,
|
||||
x + w - (s32)tw, barY - 14, 13, tsl::infoTextColor);
|
||||
|
||||
} else {
|
||||
tsl::Color col;
|
||||
switch (m_stage) {
|
||||
case 3: col = {0x3, 0xB, 0x5, 0xF}; break; // Done → green
|
||||
case 4: col = {0xE, 0x3, 0x3, 0xF}; break; // Failed → red
|
||||
default: col = {0x7, 0x7, 0x7, 0xF}; break; // Cancel → grey
|
||||
}
|
||||
renderer->drawString(m_msg.c_str(), false, x, y + 30, 14, col);
|
||||
}
|
||||
}
|
||||
|
||||
void layout(u16, u16, u16, u16) override {
|
||||
this->setBoundaries(this->getX(), this->getY(),
|
||||
this->getWidth(), PANEL_H);
|
||||
}
|
||||
|
||||
Element *requestFocus(Element *, tsl::FocusDirection) override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
int m_stage = 0;
|
||||
int m_progress = 0;
|
||||
std::string m_pkg;
|
||||
std::string m_msg;
|
||||
};
|
||||
|
||||
UpdateGui::UpdateGui() = default;
|
||||
UpdateGui::~UpdateGui() { reapThread(); }
|
||||
|
||||
void UpdateGui::startJob(int packageIndex, bool extractOnly) {
|
||||
if (isBusy()) return;
|
||||
|
||||
m_activePackage = packageIndex;
|
||||
m_extractOnly = extractOnly;
|
||||
m_resultMessage.clear();
|
||||
|
||||
ult::abortDownload.store(false, std::memory_order_release);
|
||||
ult::abortUnzip.store(false, std::memory_order_release);
|
||||
|
||||
m_stage.store(UpdateStage::Downloading, std::memory_order_release);
|
||||
|
||||
if (R_SUCCEEDED(threadCreate(&m_thread, jobEntry, this,
|
||||
nullptr, 0x8000, 0x20, -2))) {
|
||||
m_threadActive = true;
|
||||
threadStart(&m_thread);
|
||||
} else {
|
||||
m_resultMessage = "Failed to create thread";
|
||||
m_stage.store(UpdateStage::Failed, std::memory_order_release);
|
||||
m_threadActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateGui::requestCancel() {
|
||||
ult::abortDownload.store(true, std::memory_order_release);
|
||||
ult::abortUnzip.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
void UpdateGui::reapThread() {
|
||||
if (m_threadActive) {
|
||||
threadWaitForExit(&m_thread);
|
||||
threadClose(&m_thread);
|
||||
m_threadActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void UpdateGui::jobEntry(void *arg) {
|
||||
static_cast<UpdateGui *>(arg)->jobBody();
|
||||
}
|
||||
|
||||
void UpdateGui::jobBody() {
|
||||
const PackageInfo &pkg = kPackages[m_activePackage];
|
||||
|
||||
if (!m_extractOnly) {
|
||||
m_stage.store(UpdateStage::Downloading, std::memory_order_release);
|
||||
ult::createDirectory("sdmc:/config/horizon-oc/");
|
||||
|
||||
bool ok = ult::downloadFile(pkg.url, pkg.zipPath, false, false);
|
||||
if (!ok || ult::abortDownload.load(std::memory_order_acquire)) {
|
||||
m_resultMessage = ult::abortDownload.load(std::memory_order_acquire)
|
||||
? "Download cancelled"
|
||||
: "Download failed -- check network";
|
||||
m_stage.store(
|
||||
ult::abortDownload.load() ? UpdateStage::Cancelled : UpdateStage::Failed,
|
||||
std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_stage.store(UpdateStage::Extracting, std::memory_order_release);
|
||||
|
||||
bool ok = ult::unzipFile(pkg.zipPath, "sdmc:/");
|
||||
if (!ok || ult::abortUnzip.load(std::memory_order_acquire)) {
|
||||
m_resultMessage = ult::abortUnzip.load(std::memory_order_acquire)
|
||||
? "Extraction cancelled"
|
||||
: "Extraction failed -- archive may be corrupt";
|
||||
m_stage.store(
|
||||
ult::abortUnzip.load() ? UpdateStage::Cancelled : UpdateStage::Failed,
|
||||
std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
|
||||
std::remove(pkg.zipPath);
|
||||
|
||||
m_resultMessage = "Update complete! Restart required.";
|
||||
m_stage.store(UpdateStage::Done, std::memory_order_release);
|
||||
}
|
||||
|
||||
|
||||
void UpdateGui::listUI() {
|
||||
BaseMenuGui::refresh();
|
||||
if (!this->context) return;
|
||||
|
||||
this->listElement->addItem(new tsl::elm::CategoryHeader("Releases"));
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
auto *item = new tsl::elm::ListItem(kPackages[i].displayName);
|
||||
item->setValue("\uE0E0");
|
||||
|
||||
const int idx = i;
|
||||
item->setClickListener([this, idx](u64 keys) -> bool {
|
||||
if (isBusy()) return false;
|
||||
if (keys & HidNpadButton_A) {
|
||||
startJob(idx, false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
m_items[i] = item;
|
||||
this->listElement->addItem(item);
|
||||
}
|
||||
|
||||
this->listElement->addItem(new tsl::elm::CategoryHeader("Status"));
|
||||
|
||||
auto *panel = new UpdateStatusPanel();
|
||||
panel->setState(0, 0, "", "");
|
||||
m_status = panel;
|
||||
this->listElement->addItem(panel);
|
||||
}
|
||||
|
||||
void UpdateGui::pollJob() {
|
||||
auto stage = m_stage.load(std::memory_order_acquire);
|
||||
if (stage == UpdateStage::Idle || !m_status) return;
|
||||
|
||||
const char *pkgName = (m_activePackage >= 0 && m_activePackage < 2)
|
||||
? kPackages[m_activePackage].displayName : "";
|
||||
|
||||
if (stage == UpdateStage::Downloading) {
|
||||
int pct = ult::downloadPercentage.load(std::memory_order_relaxed);
|
||||
m_status->setState(1, pct, pkgName, "");
|
||||
if (m_items[m_activePackage])
|
||||
m_items[m_activePackage]->setValue("...");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stage == UpdateStage::Extracting) {
|
||||
int pct = ult::unzipPercentage.load(std::memory_order_relaxed);
|
||||
m_status->setState(2, pct, pkgName, "");
|
||||
if (m_items[m_activePackage])
|
||||
m_items[m_activePackage]->setValue("...");
|
||||
return;
|
||||
}
|
||||
|
||||
int panelStage = (stage == UpdateStage::Done) ? 3
|
||||
: (stage == UpdateStage::Failed) ? 4 : 5;
|
||||
|
||||
m_status->setState(panelStage, 100, pkgName, m_resultMessage);
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
if (m_items[i]) m_items[i]->setValue("\uE0E0");
|
||||
|
||||
reapThread();
|
||||
m_stage.store(UpdateStage::Idle, std::memory_order_release);
|
||||
}
|
||||
|
||||
void UpdateGui::update() {
|
||||
BaseMenuGui::update();
|
||||
pollJob();
|
||||
}
|
||||
|
||||
bool UpdateGui::handleInput(u64 keysDown, u64 keysHeld,
|
||||
const HidTouchState &touchPos,
|
||||
HidAnalogStickState leftJoy,
|
||||
HidAnalogStickState rightJoy) {
|
||||
if ((keysDown & HidNpadButton_R) && isBusy()) {
|
||||
requestCancel();
|
||||
return true;
|
||||
}
|
||||
|
||||
return BaseMenuGui::handleInput(keysDown, keysHeld, touchPos, leftJoy, rightJoy);
|
||||
}
|
||||
|
||||
std::string UpdateGui::getJumpToItemName() {
|
||||
return "Updates";
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../../ipc.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
#include "base_menu_gui.h"
|
||||
|
||||
class UpdateStatusPanel;
|
||||
|
||||
class UpdateGui : public BaseMenuGui {
|
||||
|
||||
public:
|
||||
UpdateGui();
|
||||
~UpdateGui();
|
||||
|
||||
void listUI() override;
|
||||
void update() override;
|
||||
bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos,
|
||||
HidAnalogStickState leftJoy, HidAnalogStickState rightJoy) override;
|
||||
|
||||
private:
|
||||
enum class UpdateStage {
|
||||
Idle,
|
||||
Downloading,
|
||||
Extracting,
|
||||
Done,
|
||||
Failed,
|
||||
Cancelled,
|
||||
};
|
||||
|
||||
struct PackageInfo {
|
||||
const char *displayName;
|
||||
const char *url;
|
||||
const char *zipPath;
|
||||
};
|
||||
|
||||
static constexpr PackageInfo kPackages[2] = {
|
||||
{"Horizon OC", "https://github.com/Horizon-OC/Horizon-OC/releases/latest/download/dist.zip", "sdmc:/config/horizon-oc/dist.zip"},
|
||||
{"Horizon OC + Extensions", "https://github.com/Horizon-OC/Horizon-OC/releases/latest/download/dist_ext.zip", "sdmc:/config/horizon-oc/dist_ext.zip"},
|
||||
};
|
||||
|
||||
bool isBusy() const {
|
||||
return m_stage.load(std::memory_order_acquire) != UpdateStage::Idle;
|
||||
}
|
||||
|
||||
void startJob(int packageIndex, bool extractOnly);
|
||||
void requestCancel();
|
||||
void reapThread();
|
||||
void pollJob();
|
||||
|
||||
static void jobEntry(void *arg);
|
||||
void jobBody();
|
||||
|
||||
std::atomic<UpdateStage> m_stage{UpdateStage::Idle};
|
||||
int m_activePackage = -1;
|
||||
bool m_extractOnly = false;
|
||||
bool m_threadActive = false;
|
||||
Thread m_thread{};
|
||||
std::string m_resultMessage;
|
||||
|
||||
tsl::elm::ListItem *m_items[2] = {nullptr, nullptr};
|
||||
UpdateStatusPanel *m_status = nullptr;
|
||||
|
||||
protected:
|
||||
std::string getJumpToItemName() override;
|
||||
};
|
||||
@@ -28,8 +28,8 @@ INCLUDES := ../common/include src/hos src/soc src/i2c src/util src/pwr src/ipc
|
||||
EXEFS_SRC := exefs_src
|
||||
LIBNAMES := minIni
|
||||
# major minor patch
|
||||
TARGET_VERSION := 3.0.0
|
||||
KIP_VERSION := 300
|
||||
TARGET_VERSION := 2.6.0
|
||||
KIP_VERSION := 260
|
||||
CUST_REV := 7
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "board_volt.hpp"
|
||||
#include <ipc_server.h>
|
||||
#include <lockable_mutex.h>
|
||||
#include "../soc/dram_mrr.hpp"
|
||||
|
||||
namespace board {
|
||||
|
||||
@@ -238,6 +239,10 @@ namespace board {
|
||||
}
|
||||
|
||||
u8 GetDramID() {
|
||||
return soc::mrr::IdentifyDramId();
|
||||
}
|
||||
|
||||
u8 GetFuseDramId() {
|
||||
return gDramID;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ namespace board {
|
||||
HocClkSocType GetSocType();
|
||||
HocClkConsoleType GetConsoleType();
|
||||
u8 GetDramID();
|
||||
u8 GetFuseDramId();
|
||||
u8 GetGpuSpeedoBracket();
|
||||
bool IsDram8GB();
|
||||
void SetDisplayRefreshDockedState(bool docked);
|
||||
|
||||
@@ -222,8 +222,7 @@ namespace kip {
|
||||
!config::GetConfigValue(HocClkConfigValue_IsFirstLoad)) {
|
||||
MigrateKipData(cust_get_cust_rev(&table), cust_get_kip_version(&table));
|
||||
SetKipData();
|
||||
clockManager::gContext.rebootRequired = true;
|
||||
notification::writeNotification("Horizon OC\nKIP has been updated\nPlease reboot your console to use Horizon OC");
|
||||
notification::writeNotification("Horizon OC\nKIP has been updated\nPlease reboot your console");
|
||||
return;
|
||||
}
|
||||
if (config::GetConfigValue(HocClkConfigValue_IsFirstLoad) == true) {
|
||||
|
||||
@@ -295,47 +295,6 @@ namespace clockManager {
|
||||
hz++;
|
||||
}
|
||||
|
||||
/* Since it is a pain to patch the vtables in ipc we can just hack the freqs in. You can still set them. */
|
||||
constexpr u64 EmcClkOSLimitHz = 1600000ULL * 1000; // 1600 MHz
|
||||
const u64 maxHz = static_cast<u64>(config::GetConfigValue(KipConfigValue_marikoEmcMaxClock)) * 1000;
|
||||
if (module == HocClkModule_MEM && board::GetSocType() == HocClkSocType_Mariko &&
|
||||
kip::kipAvailable && maxHz >= EmcClkOSLimitHz &&
|
||||
config::GetConfigValue(KipConfigValue_stepMode) == 4 /* 33 MHz */) {
|
||||
|
||||
/* Drop the clkrst entries above the OS limit */
|
||||
u32 kept = 0;
|
||||
for (u32 i = 0; i < gFreqTable[module].count; ++i) {
|
||||
if (static_cast<u64>(gFreqTable[module].list[i]) <= EmcClkOSLimitHz) {
|
||||
gFreqTable[module].list[kept++] = gFreqTable[module].list[i];
|
||||
}
|
||||
}
|
||||
gFreqTable[module].count = kept;
|
||||
|
||||
auto push = [&](u64 freqHz) {
|
||||
if (freqHz > maxHz || gFreqTable[module].count >= HOCCLK_FREQ_LIST_MAX) {
|
||||
return;
|
||||
}
|
||||
gFreqTable[module].list[gFreqTable[module].count++] = static_cast<u32>(freqHz);
|
||||
};
|
||||
|
||||
static const u32 stepFreqs33[] = {
|
||||
1633000, 1666000, 1700000, 1733000, 1766000, 1800000, 1833000, 1866000, 1900000, 1933000,
|
||||
1966000, 2000000, 2033000, 2066000, 2100000, 2133000, 2166000, 2200000, 2233000, 2266000,
|
||||
2300000, 2333000, 2366000, 2400000, 2433000, 2466000, 2500000, 2533000, 2566000, 2600000,
|
||||
2633000, 2666000, 2700000, 2733000, 2766000, 2800000, 2833000, 2866000, 2900000, 2933000,
|
||||
2966000, 3000000, 3033000, 3066000, 3100000, 3133000, 3166000, 3200000, 3233000, 3266000,
|
||||
3300000, 3333000, 3366000, 3400000, 3433000, 3466000, 3500000,
|
||||
};
|
||||
for (u32 f : stepFreqs33) {
|
||||
push(static_cast<u64>(f) * 1000);
|
||||
}
|
||||
|
||||
if (gFreqTable[module].count == 0 ||
|
||||
static_cast<u64>(gFreqTable[module].list[gFreqTable[module].count - 1]) != maxHz) {
|
||||
push(maxHz);
|
||||
}
|
||||
}
|
||||
|
||||
fileUtils::LogLine("[mgr] count = %u", gFreqTable[module].count);
|
||||
}
|
||||
|
||||
@@ -501,7 +460,10 @@ namespace clockManager {
|
||||
u32 nearestFreq = GetCurrentNearestFrequency(HocClkModule_MEM);
|
||||
|
||||
if (targetRamHz != nearestFreq) {
|
||||
ApplyGpuDvfs(targetRamHz);
|
||||
if (config::GetConfigValue(HocClkConfigValue_DVFSMode) == DVFSMode_Hijack) {
|
||||
ApplyGpuDvfs(targetRamHz);
|
||||
}
|
||||
|
||||
board::SetHz(HocClkModule_MEM, targetRamHz);
|
||||
}
|
||||
}
|
||||
@@ -600,6 +562,7 @@ namespace clockManager {
|
||||
if (module == HocClkModule_MEM && targetHz > oldHz && config::GetConfigValue(HocClkConfigValue_DVFSMode) == DVFSMode_Hijack) {
|
||||
ApplyGpuDvfs(targetHz);
|
||||
}
|
||||
|
||||
board::SetHz((HocClkModule)module, nearestHz);
|
||||
gContext.freqs[module] = nearestHz;
|
||||
|
||||
@@ -778,11 +741,6 @@ namespace clockManager {
|
||||
gContext = {};
|
||||
gContext.applicationId = 0;
|
||||
gContext.profile = HocClkProfile_Handheld;
|
||||
|
||||
/* Load the KIP customize table before building the freq tables: the MEM freq-list
|
||||
synthesis in RefreshFreqTableRow reads marikoEmcMaxClock / stepMode from it. */
|
||||
kip::GetKipData();
|
||||
|
||||
for (unsigned int module = 0; module < HocClkModule_EnumMax; module++) {
|
||||
gContext.freqs[module] = 0;
|
||||
gContext.realFreqs[module] = 0;
|
||||
@@ -801,6 +759,8 @@ namespace clockManager {
|
||||
gLastTempLogNs = 0;
|
||||
gLastCsvWriteNs = 0;
|
||||
|
||||
kip::GetKipData();
|
||||
|
||||
board::FuseData *fuse = board::GetFuseData();
|
||||
|
||||
gContext.speedos[HocClkSpeedo_CPU] = fuse->cpuSpeedo;
|
||||
|
||||
393
Source/hoc-clk/sysmodule/src/soc/dram_mrr.cpp
Normal file
393
Source/hoc-clk/sysmodule/src/soc/dram_mrr.cpp
Normal file
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
* Copyright (c) CtCaer
|
||||
*
|
||||
* Copyright (c) Souldbminer
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "dram_mrr.hpp"
|
||||
#include "../board/board.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <switch.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace soc::mrr {
|
||||
constexpr u64 EmcPhysBase = 0x7001B000;
|
||||
constexpr u64 Emc0PhysBase = 0x7001E000;
|
||||
constexpr u64 Emc1PhysBase = 0x7001F000;
|
||||
|
||||
constexpr u32 EmcRegMrr = 0xEC;
|
||||
constexpr u32 EmcRegAdrCfg = 0x10;
|
||||
constexpr u32 EmcRegFbioCfg7 = 0x584;
|
||||
constexpr u32 EmcRegEmcStatus = 0x2B4;
|
||||
constexpr u32 EmcStatusMrrDivld = (1u << 20);
|
||||
constexpr int EmcStatusUpdateTimeout = 100000;
|
||||
|
||||
static bool SmcRead(u64 phys_addr, u32 *out) {
|
||||
SecmonArgs args = {};
|
||||
args.X[0] = 0xF0000002;
|
||||
args.X[1] = phys_addr;
|
||||
args.X[2] = 0;
|
||||
args.X[3] = 0;
|
||||
svcCallSecureMonitor(&args);
|
||||
if (args.X[1] == phys_addr) {
|
||||
return false;
|
||||
}
|
||||
*out = static_cast<u32>(args.X[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SmcWrite(u64 phys_addr, u32 value) {
|
||||
SecmonArgs args = {};
|
||||
args.X[0] = 0xF0000002;
|
||||
args.X[1] = phys_addr;
|
||||
args.X[2] = 0xFFFFFFFF;
|
||||
args.X[3] = value;
|
||||
svcCallSecureMonitor(&args);
|
||||
return args.X[1] != phys_addr;
|
||||
}
|
||||
|
||||
static bool EmcRead(u32 offset, u32 *out) { return SmcRead(EmcPhysBase + offset, out); }
|
||||
static bool EmcWrite(u32 offset, u32 value) { return SmcWrite(EmcPhysBase + offset, value); }
|
||||
static bool Emc0Read(u32 offset, u32 *out) { return SmcRead(Emc0PhysBase + offset, out); }
|
||||
static bool Emc1Read(u32 offset, u32 *out) { return SmcRead(Emc1PhysBase + offset, out); }
|
||||
|
||||
/* 0 = ignore */
|
||||
|
||||
const DramRevisionMap rev[DRAM_COUNT] = {
|
||||
/* Erista */
|
||||
{ .soc = HocClkSocType_Erista, .mfg = MFG_Samsung, .major = 0, .minor = 0, .density = 0, .densityCount = 0 }, /* HB-MGCH */
|
||||
{ .soc = HocClkSocType_Erista, .mfg = MFG_SKHynix, .major = 0, .minor = 0, .density = 0, .densityCount = 0 }, /* NLE */
|
||||
{ .soc = HocClkSocType_Erista, .mfg = MFG_Micron, .major = 0, .minor = 0, .density = 0, .densityCount = 0 }, /* WT:C */
|
||||
|
||||
/* Mariko */
|
||||
/* Samsung */
|
||||
{ .soc = HocClkSocType_Mariko, .mfg = MFG_Samsung, .major = 6, .minor = 0x10, .density = 1, .densityCount = 2 }, /* AM-MGCJ (6.10, 4GB) */
|
||||
{ .soc = HocClkSocType_Mariko, .mfg = MFG_Samsung, .major = 7, .minor = 0, .density = 1, .densityCount = 2 }, /* AA-MGCL (7.00, 4GB) */
|
||||
{ .soc = HocClkSocType_Mariko, .mfg = MFG_Samsung, .major = 8, .minor = 0, .density = 1, .densityCount = 2 }, /* AB-MGCL (8.00, 4GB) */
|
||||
|
||||
/* Micron */
|
||||
{ .soc = HocClkSocType_Mariko, .mfg = MFG_Micron, .major = 4, .minor = 0x10, .density = 1, .densityCount = 2 }, /* WT:E (4.10, 4GB) */
|
||||
{ .soc = HocClkSocType_Mariko, .mfg = MFG_Micron, .major = 5, .minor = 0, .density = 1, .densityCount = 2 }, /* WT:F (5.00, 4GB) */
|
||||
{ .soc = HocClkSocType_Mariko, .mfg = MFG_Micron, .major = 7, .minor = 0, .density = 1, .densityCount = 2 }, /* WT:B (7.00, 4GB) */
|
||||
{ .soc = HocClkSocType_Mariko, .mfg = MFG_Micron, .major = 7, .minor = 0, .density = 1, .densityCount = 4 }, /* WT:B (7.00, 8GB) */
|
||||
|
||||
/* SK Hynix */
|
||||
{ .soc = HocClkSocType_Mariko, .mfg = MFG_SKHynix, .major = 4, .minor = 0, .density = 1, .densityCount = 2 }, /* NME (4.00, 4GB) */
|
||||
{ .soc = HocClkSocType_Mariko, .mfg = MFG_SKHynix, .major = 6, .minor = 0, .density = 1, .densityCount = 2 }, /* NEE (6.00, 4GB) */
|
||||
{ .soc = HocClkSocType_Mariko, .mfg = MFG_SKHynix, .major = 6, .minor = 0, .density = 1, .densityCount = 4 }, /* NEE (6.00, 8GB) */
|
||||
{ .soc = HocClkSocType_Mariko, .mfg = MFG_SKHynix, .major = 8, .minor = 0, .density = 1, .densityCount = 2 }, /* x267 (8.00, 4GB) */
|
||||
};
|
||||
|
||||
/* Ported from hekate */
|
||||
|
||||
bool IsMrrAvailable() {
|
||||
u32 val = 0;
|
||||
return EmcRead(EmcRegAdrCfg, &val);
|
||||
}
|
||||
|
||||
static int _sdram_wait_emc_status(u32 reg_offset, u32 bit_mask, bool updated_state, s32 emc_channel)
|
||||
{
|
||||
for (s32 i = 0; i < EmcStatusUpdateTimeout; i++)
|
||||
{
|
||||
u32 val = 0;
|
||||
bool ok;
|
||||
if (emc_channel)
|
||||
{
|
||||
if (emc_channel != 1)
|
||||
return 1;
|
||||
|
||||
ok = Emc1Read(reg_offset, &val);
|
||||
}
|
||||
else
|
||||
{
|
||||
ok = EmcRead(reg_offset, &val);
|
||||
}
|
||||
|
||||
if (ok && (((val & bit_mask) != 0) == updated_state))
|
||||
return 0;
|
||||
|
||||
usleep(1);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool _sdram_req_mrr_data(u32 data, bool dual_channel)
|
||||
{
|
||||
if (!EmcWrite(EmcRegMrr, data))
|
||||
return false;
|
||||
if (_sdram_wait_emc_status(EmcRegEmcStatus, EmcStatusMrrDivld, true, EMC_CHAN0) != 0)
|
||||
return false;
|
||||
if (dual_channel && _sdram_wait_emc_status(EmcRegEmcStatus, EmcStatusMrrDivld, true, EMC_CHAN1) != 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct EmcTopology {
|
||||
u32 ranks;
|
||||
u32 channels;
|
||||
};
|
||||
|
||||
static bool QueryTopology(EmcTopology *topo) {
|
||||
u32 tmp = 0;
|
||||
if (!EmcRead(EmcRegAdrCfg, &tmp))
|
||||
return false;
|
||||
topo->ranks = (tmp & 1) + 1;
|
||||
if (!EmcRead(EmcRegFbioCfg7, &tmp))
|
||||
return false;
|
||||
u32 ch = (tmp >> 1) & 3;
|
||||
topo->channels = (ch & 1) + ((ch & 2) >> 1);
|
||||
return topo->channels != 0;
|
||||
}
|
||||
|
||||
static void ClearMrrLeftovers() {
|
||||
u32 tmp = 0;
|
||||
for (u32 i = 0; i < 16; i++)
|
||||
{
|
||||
(void)EmcRead(EmcRegMrr, &tmp);
|
||||
usleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void StoreMrrBytes(u32 mrr, u8 *lo, u8 *hi) {
|
||||
*lo = mrr & 0xFF;
|
||||
*hi = (mrr >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
static bool ReadMrxRank(emc_mr_t mrx, bool dual_rank, bool dual_channel, emc_mr_data_t *out) {
|
||||
memset(out, 0xFF, sizeof(*out));
|
||||
u32 mrr = 0;
|
||||
|
||||
// Get Device 0 (Rank 0) info from both dram chips (channels).
|
||||
if (!_sdram_req_mrr_data((2u << 30) | (static_cast<u32>(mrx) << 16), dual_channel))
|
||||
return false;
|
||||
|
||||
// Ram module 0 info.
|
||||
if (!Emc0Read(EmcRegMrr, &mrr))
|
||||
return false;
|
||||
StoreMrrBytes(mrr, &out->chip0.rank0_ch0, &out->chip0.rank0_ch1);
|
||||
|
||||
// Ram module 1 info.
|
||||
if (dual_channel)
|
||||
{
|
||||
if (!Emc1Read(EmcRegMrr, &mrr))
|
||||
return false;
|
||||
StoreMrrBytes(mrr, &out->chip1.rank0_ch0, &out->chip1.rank0_ch1);
|
||||
}
|
||||
|
||||
// If Rank 1 exists, get info.
|
||||
if (dual_rank)
|
||||
{
|
||||
// Get Device 1 (Rank 1) info from both dram chips (channels).
|
||||
if (!_sdram_req_mrr_data((1u << 30) | (static_cast<u32>(mrx) << 16), dual_channel))
|
||||
return false;
|
||||
|
||||
// Ram module 0 info.
|
||||
if (!Emc0Read(EmcRegMrr, &mrr))
|
||||
return false;
|
||||
StoreMrrBytes(mrr, &out->chip0.rank1_ch0, &out->chip0.rank1_ch1);
|
||||
|
||||
// Ram module 1 info.
|
||||
if (dual_channel)
|
||||
{
|
||||
if (!Emc1Read(EmcRegMrr, &mrr))
|
||||
return false;
|
||||
StoreMrrBytes(mrr, &out->chip1.rank1_ch0, &out->chip1.rank1_ch1);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ReadMrxSet(emc_mr_data_t *vendor, emc_mr_data_t *rev0, emc_mr_data_t *rev1,
|
||||
emc_mr_data_t *density, EmcTopology *topo) {
|
||||
EmcTopology t = {};
|
||||
if (!QueryTopology(&t))
|
||||
return false;
|
||||
const bool dual_rank = t.ranks > 1;
|
||||
const bool dual_channel = t.channels > 1;
|
||||
|
||||
ClearMrrLeftovers();
|
||||
|
||||
if (!ReadMrxRank(MR5_MAN_ID, dual_rank, dual_channel, vendor))
|
||||
return false;
|
||||
if (!ReadMrxRank(MR6_REV_ID1, dual_rank, dual_channel, rev0))
|
||||
return false;
|
||||
if (!ReadMrxRank(MR7_REV_ID2, dual_rank, dual_channel, rev1))
|
||||
return false;
|
||||
if (!ReadMrxRank(MR8_DENSITY, dual_rank, dual_channel, density))
|
||||
return false;
|
||||
|
||||
*topo = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
emc_mr_data_t sdram_read_mrx(emc_mr_t mrx)
|
||||
{
|
||||
emc_mr_data_t data;
|
||||
memset(&data, 0xFF, sizeof(emc_mr_data_t));
|
||||
|
||||
EmcTopology topo = {};
|
||||
if (!QueryTopology(&topo))
|
||||
return data;
|
||||
|
||||
ClearMrrLeftovers();
|
||||
|
||||
(void)ReadMrxRank(mrx, topo.ranks > 1, topo.channels > 1, &data);
|
||||
return data;
|
||||
}
|
||||
|
||||
bool ReadRamMr4(u8 *mr4) {
|
||||
EmcTopology topo = {};
|
||||
if (!QueryTopology(&topo))
|
||||
return false;
|
||||
|
||||
ClearMrrLeftovers();
|
||||
|
||||
emc_mr_data_t data = {};
|
||||
if (!ReadMrxRank(MR4_TEMP, topo.ranks > 1, topo.channels > 1, &data))
|
||||
return false;
|
||||
|
||||
*mr4 = data.chip0.rank0_ch0;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* MR8 density field (bits 5:2) to MB */
|
||||
static bool DecodeDensityMb(u8 mr8, u32 *mb) {
|
||||
switch ((mr8 & 0x3C) >> 2)
|
||||
{
|
||||
case 2: *mb = 512; break;
|
||||
case 3: *mb = 768; break;
|
||||
case 4: *mb = 1024; break;
|
||||
case 5: *mb = 1536; break;
|
||||
case 6: *mb = 2048; break;
|
||||
default: return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool MatchModule(HocClkSocType soc, DramMfg mfg, u8 major, u8 minor,
|
||||
u32 densityMb, u32 densityCount, RealDramModule *mod) {
|
||||
for (int i = 0; i < DRAM_COUNT; i++)
|
||||
{
|
||||
const DramRevisionMap &e = rev[i];
|
||||
if (e.soc != soc || e.mfg != mfg)
|
||||
continue;
|
||||
if (soc != HocClkSocType_Erista)
|
||||
{
|
||||
if (e.major != major || e.minor != minor)
|
||||
continue;
|
||||
if (e.density * 1024u != densityMb)
|
||||
continue;
|
||||
if (e.densityCount != densityCount)
|
||||
continue;
|
||||
}
|
||||
*mod = static_cast<RealDramModule>(i);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static u8 FuseIdForModule(RealDramModule mod, HocClkConsoleType console, u8 fuseFallback) {
|
||||
switch (mod)
|
||||
{
|
||||
case DRAM_HBMGCH:
|
||||
case DRAM_NLE:
|
||||
case DRAM_WTC:
|
||||
return fuseFallback;
|
||||
case DRAM_AMMGCJ:
|
||||
if (console == HocClkConsoleType_Hoag) return 12;
|
||||
if (console == HocClkConsoleType_Iowa) return 8;
|
||||
return fuseFallback;
|
||||
case DRAM_AAMGCL:
|
||||
if (console == HocClkConsoleType_Iowa) return 17;
|
||||
if (console == HocClkConsoleType_Hoag) return 19;
|
||||
if (console == HocClkConsoleType_Aula) return 24;
|
||||
return fuseFallback;
|
||||
case DRAM_ABMGCL:
|
||||
if (console == HocClkConsoleType_Iowa) return 20;
|
||||
if (console == HocClkConsoleType_Hoag) return 21;
|
||||
if (console == HocClkConsoleType_Aula) return 22;
|
||||
return fuseFallback;
|
||||
case DRAM_WTE:
|
||||
if (console == HocClkConsoleType_Iowa) return 11;
|
||||
if (console == HocClkConsoleType_Hoag) return 15;
|
||||
return fuseFallback;
|
||||
case DRAM_WTF:
|
||||
if (console == HocClkConsoleType_Iowa) return 25;
|
||||
if (console == HocClkConsoleType_Hoag) return 26;
|
||||
if (console == HocClkConsoleType_Aula) return 27;
|
||||
return fuseFallback;
|
||||
case DRAM_WTB:
|
||||
if (console == HocClkConsoleType_Iowa) return 32;
|
||||
if (console == HocClkConsoleType_Hoag) return 33;
|
||||
if (console == HocClkConsoleType_Aula) return 34;
|
||||
return fuseFallback;
|
||||
case DRAM_WTB_8GB:
|
||||
case DRAM_NEE_8GB:
|
||||
/* Used in atmosphere and commonly burnt for 8gb upgrade */
|
||||
return 28;
|
||||
case DRAM_NME:
|
||||
if (console == HocClkConsoleType_Iowa) return 10;
|
||||
if (console == HocClkConsoleType_Hoag) return 14;
|
||||
return fuseFallback;
|
||||
case DRAM_NEE:
|
||||
if (console == HocClkConsoleType_Iowa) return 6;
|
||||
if (console == HocClkConsoleType_Hoag) return 3;
|
||||
if (console == HocClkConsoleType_Aula) return 5;
|
||||
return fuseFallback;
|
||||
case DRAM_X267:
|
||||
if (console == HocClkConsoleType_Iowa) return 29;
|
||||
if (console == HocClkConsoleType_Hoag) return 30;
|
||||
if (console == HocClkConsoleType_Aula) return 31;
|
||||
return fuseFallback;
|
||||
default:
|
||||
return fuseFallback;
|
||||
}
|
||||
}
|
||||
|
||||
u8 IdentifyDramId() {
|
||||
const u8 fuseId = board::GetFuseDramId();
|
||||
const HocClkSocType soc = board::GetSocType();
|
||||
|
||||
if (soc == HocClkSocType_Erista)
|
||||
return fuseId;
|
||||
|
||||
if (!IsMrrAvailable())
|
||||
return fuseId;
|
||||
|
||||
emc_mr_data_t vendor = {}, rev0 = {}, rev1 = {}, density = {};
|
||||
EmcTopology topo = {};
|
||||
if (!ReadMrxSet(&vendor, &rev0, &rev1, &density, &topo))
|
||||
return fuseId;
|
||||
|
||||
DramMfg mfg = static_cast<DramMfg>(vendor.chip0.rank0_ch0);
|
||||
|
||||
u32 densityMb = 0;
|
||||
if (!DecodeDensityMb(density.chip0.rank0_ch0, &densityMb))
|
||||
return fuseId;
|
||||
|
||||
RealDramModule mod = DRAM_COUNT;
|
||||
if (!MatchModule(soc, mfg, rev0.chip0.rank0_ch0, rev1.chip0.rank0_ch0,
|
||||
densityMb, topo.ranks * topo.channels, &mod))
|
||||
return fuseId;
|
||||
|
||||
return FuseIdForModule(mod, board::GetConsoleType(), fuseId);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
105
Source/hoc-clk/sysmodule/src/soc/dram_mrr.hpp
Normal file
105
Source/hoc-clk/sysmodule/src/soc/dram_mrr.hpp
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) CtCaer
|
||||
*
|
||||
* Copyright (c) Souldbminer
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#include <hocclk.h>
|
||||
|
||||
namespace soc::mrr {
|
||||
typedef enum RealDramModule {
|
||||
/* Erista */
|
||||
DRAM_HBMGCH = 0,
|
||||
DRAM_NLE,
|
||||
DRAM_WTC,
|
||||
|
||||
/* Mariko */
|
||||
|
||||
/* Samsung */
|
||||
DRAM_AMMGCJ,
|
||||
DRAM_AAMGCL,
|
||||
DRAM_ABMGCL,
|
||||
|
||||
/* Micron */
|
||||
DRAM_WTE,
|
||||
DRAM_WTF,
|
||||
DRAM_WTB,
|
||||
DRAM_WTB_8GB,
|
||||
|
||||
/* SK Hynix */
|
||||
DRAM_NME,
|
||||
DRAM_NEE,
|
||||
DRAM_NEE_8GB,
|
||||
DRAM_X267,
|
||||
DRAM_COUNT,
|
||||
} RealDramModule;
|
||||
|
||||
typedef enum _emc_mr_t
|
||||
{
|
||||
MR0_FEAT = 0,
|
||||
MR4_TEMP = 4,
|
||||
MR5_MAN_ID = 5,
|
||||
MR6_REV_ID1 = 6,
|
||||
MR7_REV_ID2 = 7,
|
||||
MR8_DENSITY = 8,
|
||||
} emc_mr_t;
|
||||
|
||||
enum
|
||||
{
|
||||
EMC_CHAN0 = 0,
|
||||
EMC_CHAN1 = 1
|
||||
};
|
||||
|
||||
typedef struct _emc_mr_chip_data_t
|
||||
{
|
||||
// Device 0.
|
||||
u8 rank0_ch0;
|
||||
u8 rank0_ch1;
|
||||
|
||||
// Device 1.
|
||||
u8 rank1_ch0;
|
||||
u8 rank1_ch1;
|
||||
} emc_mr_chip_data_t;
|
||||
|
||||
typedef struct _emc_mr_data_t
|
||||
{
|
||||
emc_mr_chip_data_t chip0;
|
||||
emc_mr_chip_data_t chip1;
|
||||
} emc_mr_data_t;
|
||||
|
||||
typedef enum DramMfg {
|
||||
MFG_Samsung = 1,
|
||||
MFG_SKHynix = 6,
|
||||
MFG_Micron = 255,
|
||||
} DramMfg;
|
||||
|
||||
typedef struct DramRevisionMap {
|
||||
HocClkSocType soc;
|
||||
DramMfg mfg;
|
||||
u8 major;
|
||||
u8 minor;
|
||||
u8 density; // GB per die
|
||||
u8 densityCount; // number of dies (2 = 4GB, 4 = 8GB)
|
||||
} DramRevisionMap;
|
||||
|
||||
extern const DramRevisionMap rev[DRAM_COUNT];
|
||||
|
||||
emc_mr_data_t sdram_read_mrx(emc_mr_t mrx);
|
||||
bool IsMrrAvailable();
|
||||
|
||||
bool ReadRamMr4(u8 *mr4);
|
||||
|
||||
u8 IdentifyDramId();
|
||||
}
|
||||
@@ -27,12 +27,12 @@ namespace gm20b {
|
||||
#define GPU_SIZE 0x1000000
|
||||
#define GPU_TRIM_SYS_GPCPLL_CFG 0x00
|
||||
#define GPU_TRIM_SYS_GPCPLL_COEFF 0x04
|
||||
#define GPU_TRIM_SYS_GPCPLL_CFG2 0x08
|
||||
#define GPU_TRIM_SYS_GPCPLL_CFG3 0x0C
|
||||
#define GPU_TRIM_SYS_GPCPLL_CFG2 0x0C
|
||||
#define GPU_TRIM_SYS_GPCPLL_CFG3 0x18
|
||||
#define GPU_TRIM_SYS_GPCPLL_DVFS0 0x10
|
||||
#define GPU_TRIM_SYS_GPCPLL_DVFS1 0x14
|
||||
#define GPU_TRIM_SYS_GPCPLL_NDIV_SLOWDOWN 0x1C
|
||||
#define GPU_TRIM_SYS_SEL_VCO 0x2C
|
||||
#define GPU_TRIM_SYS_SEL_VCO 0x100
|
||||
#define GPU_TRIM_SYS_GPC2CLK_OUT 0x250
|
||||
|
||||
#define GPC_BCAST(x) (*(volatile u32 *)(gpu_base + 0x132800ul + (x)))
|
||||
@@ -47,7 +47,7 @@ namespace gm20b {
|
||||
#define GPCPLL_CFG_LOCK BIT(17)
|
||||
|
||||
#define GPCPLL_CFG2_SDM_DIN_MASK 0x000000FFu
|
||||
#define GPCPLL_CFG2_SDM_DIN_NEW_MASK 0x007FFF00u
|
||||
#define GPCPLL_CFG2_SDM_DIN_NEW_MASK 0x0000FF00u
|
||||
#define GPCPLL_CFG2_STEPA_SHIFT 24
|
||||
#define GPCPLL_CFG2_STEPA_MASK 0xFF000000u
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace gm20b {
|
||||
#define GPCPLL_DVFS0_DFS_COEFF_MASK 0x0000007Fu
|
||||
|
||||
#define NDIV_SLOWDOWN_SLOWDOWN_USING_PLL BIT(22)
|
||||
#define NDIV_SLOWDOWN_EN_DYNRAMP BIT(23)
|
||||
#define NDIV_SLOWDOWN_EN_DYNRAMP BIT(31)
|
||||
|
||||
#define DYNRAMP_DONE_SYNCED BIT(24)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
70
build-exosphere.sh
Executable file
70
build-exosphere.sh
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/bin/sh
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
DIST_DIR="$ROOT_DIR/dist"
|
||||
|
||||
ATMOSPHERE_DIR="$ROOT_DIR/build/atmosphere"
|
||||
ATMOSPHERE_URL="https://git.niklascfw.de/OmniNX/Atmosphere-Pro.git"
|
||||
ATMOSPHERE_BRANCH="boot-storage"
|
||||
|
||||
EXO_SRC="$ROOT_DIR/Source/Atmosphere-Patches"
|
||||
EXO_DEST="$ATMOSPHERE_DIR/exosphere/program/source/smc"
|
||||
LIBEXO_DEST="$ATMOSPHERE_DIR/libraries/libexosphere/include/exosphere/secmon"
|
||||
EXO_OUT="$ATMOSPHERE_DIR/exosphere/out/nintendo_nx_arm64_armv8a/release/exosphere.bin"
|
||||
|
||||
if command -v nproc >/dev/null 2>&1; then
|
||||
CORES="$(nproc --all)"
|
||||
else
|
||||
CORES="$(sysctl -n hw.ncpu)"
|
||||
fi
|
||||
|
||||
echo "CORES: $CORES"
|
||||
|
||||
mkdir -p "$ROOT_DIR/build"
|
||||
|
||||
if [ ! -d "$ATMOSPHERE_DIR" ]; then
|
||||
echo
|
||||
echo "*** Cloning Atmosphere-Pro ***"
|
||||
git clone --depth=1 --single-branch -b "$ATMOSPHERE_BRANCH" "$ATMOSPHERE_URL" "$ATMOSPHERE_DIR"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "*** Patching exosphere ***"
|
||||
cp -v "$EXO_SRC/secmon_define_emc_access_table.inc" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_emc_access_table_data.inc" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_define_emc01_access_table.inc" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_emc01_access_table_data.inc" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_define_rtc_pmc_access_table.inc" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_rtc_pmc_access_table_data.inc" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_smc_register_access.cpp" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_smc_handler.cpp" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_memory_layout.hpp" "$LIBEXO_DEST/"
|
||||
|
||||
echo
|
||||
echo "*** Compiling exosphere ***"
|
||||
cd "$ATMOSPHERE_DIR/exosphere" || exit 1
|
||||
|
||||
# .d files store absolute source paths. If this checkout moved (e.g. renamed
|
||||
# Horizon-OC -> Horizon-OC-pro), those deps point at a dead tree and make fails.
|
||||
if grep -Rql --include='*.d' '/Desktop/Horizon-OC/' "$ATMOSPHERE_DIR" 2>/dev/null; then
|
||||
echo "*** Cleaning stale build deps (old Horizon-OC absolute paths) ***"
|
||||
make clean || true
|
||||
fi
|
||||
|
||||
make -j"$CORES" || {
|
||||
echo "*** Build failed ***" >&2
|
||||
exit 1
|
||||
}
|
||||
cd "$ROOT_DIR" || exit 1
|
||||
|
||||
if [ ! -f "$EXO_OUT" ]; then
|
||||
echo "*** Missing output: $EXO_OUT ***" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$DIST_DIR/atmosphere"
|
||||
cp -v "$EXO_OUT" "$DIST_DIR/atmosphere/exosphere.bin"
|
||||
|
||||
echo
|
||||
echo "*** Done: $EXO_OUT ***"
|
||||
echo "*** Copied to: $DIST_DIR/atmosphere/exosphere.bin ***"
|
||||
49
build.sh
Normal file → Executable file
49
build.sh
Normal file → Executable file
@@ -3,7 +3,6 @@
|
||||
EXT=0
|
||||
LDR_MAKE="nx_release"
|
||||
NO_EXO=0
|
||||
JOBS=""
|
||||
|
||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
DIST_DIR="$ROOT_DIR/dist"
|
||||
@@ -19,13 +18,6 @@ while [ $# -gt 0 ]; do
|
||||
--no-exo)
|
||||
NO_EXO=1
|
||||
;;
|
||||
-j)
|
||||
shift
|
||||
JOBS="$1"
|
||||
;;
|
||||
-j*)
|
||||
JOBS="${1#-j}"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
exit 1
|
||||
@@ -47,28 +39,19 @@ fi
|
||||
|
||||
CORES="$(nproc --all)"
|
||||
echo "CORES: $CORES"
|
||||
JOBS="${JOBS:-$CORES}"
|
||||
echo "JOBS: $JOBS"
|
||||
|
||||
SRC="Source/Atmosphere/stratosphere/loader/"
|
||||
|
||||
mkdir -p "build"
|
||||
|
||||
ATMOSPHERE_DIR="build/atmosphere"
|
||||
ATMOSPHERE_URL="https://github.com/Atmosphere-NX/Atmosphere.git"
|
||||
ATMOSPHERE_URL="https://git.niklascfw.de/OmniNX/Atmosphere-Pro.git"
|
||||
ATMOSPHERE_BRANCH="boot-storage"
|
||||
|
||||
if [ ! -d "$ATMOSPHERE_DIR" ]; then
|
||||
echo
|
||||
echo "*** Cloning atmosphere ***"
|
||||
git clone "$ATMOSPHERE_URL" "$ATMOSPHERE_DIR"
|
||||
|
||||
cd "$ATMOSPHERE_DIR"
|
||||
|
||||
git fetch --tags --quiet
|
||||
LATEST_TAG=$(git tag --sort=-version:refname | head -n1)
|
||||
git checkout -q -b build "$LATEST_TAG"
|
||||
|
||||
cd "$ROOT_DIR"
|
||||
echo "*** Cloning Atmosphere-Pro ***"
|
||||
git clone --depth=1 --single-branch -b "$ATMOSPHERE_BRANCH" "$ATMOSPHERE_URL" "$ATMOSPHERE_DIR"
|
||||
fi
|
||||
|
||||
DEST="build/atmosphere/stratosphere/loader/"
|
||||
@@ -86,19 +69,21 @@ if [ "$NO_EXO" -eq 0 ]; then
|
||||
EXO_DEST="build/atmosphere/exosphere/program/source/smc"
|
||||
LIBEXO_DEST="build/atmosphere/libraries/libexosphere/include/exosphere/secmon"
|
||||
|
||||
cp -v "$EXO_SRC/secmon_emc_access_table_data.inc" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_define_emc_access_table.inc" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_rtc_pmc_access_table_data.inc" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_define_rtc_pmc_access_table.inc" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_smc_register_access.cpp" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_smc_handler.cpp" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_memory_layout.hpp" "$LIBEXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_define_emc_access_table.inc" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_emc_access_table_data.inc" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_define_emc01_access_table.inc" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_emc01_access_table_data.inc" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_define_rtc_pmc_access_table.inc" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_rtc_pmc_access_table_data.inc" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_smc_register_access.cpp" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_smc_handler.cpp" "$EXO_DEST/"
|
||||
cp -v "$EXO_SRC/secmon_memory_layout.hpp" "$LIBEXO_DEST/"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "*** Compiling loader ***"
|
||||
cd build/atmosphere/stratosphere/loader || exit 1
|
||||
make -j$JOBS "$LDR_MAKE"
|
||||
make -j$CORES "$LDR_MAKE"
|
||||
hactool -t kip1 "out/nintendo_nx_arm64_armv8a/$LDR_BUILD_PATH/loader.kip" --uncompress=hoc.kip
|
||||
cd "$ROOT_DIR" # exit
|
||||
cp -v build/atmosphere/stratosphere/loader/hoc.kip dist/atmosphere/kips/hoc.kip
|
||||
@@ -107,7 +92,7 @@ if [ "$NO_EXO" -eq 0 ]; then
|
||||
echo
|
||||
echo "*** Compiling exosphere ***"
|
||||
cd build/atmosphere/exosphere
|
||||
make -j$JOBS
|
||||
make -j$CORES
|
||||
cd "$ROOT_DIR"
|
||||
cp -v build/atmosphere/exosphere/out/nintendo_nx_arm64_armv8a/release/exosphere.bin dist/atmosphere/exosphere.bin
|
||||
fi
|
||||
@@ -141,7 +126,7 @@ if [ "$EXT" -eq 1 ]; then
|
||||
cd hekate/
|
||||
echo
|
||||
echo "*** Compiling custom Hekate ***"
|
||||
make -j$JOBS
|
||||
make -j$CORES
|
||||
echo
|
||||
|
||||
mkdir -p "$DIST_DIR/bootloader/sys/"
|
||||
@@ -152,7 +137,7 @@ if [ "$EXT" -eq 1 ]; then
|
||||
cd "$ROOT_DIR"/Source/Benchmark-Toolbox
|
||||
echo
|
||||
echo "*** Compiling Benchmark-Toolbox ***"
|
||||
make -j$JOBS
|
||||
make -j$CORES
|
||||
cp -v Benchmark-Toolbox.nro "$DIST_DIR"/switch/Benchmark-Toolbox.nro
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user