Compare commits

..
26 Commits
Author SHA1 Message Date
niklascfw e5c1f0faa2 Merge upstream Horizon-OC main into Horizon-OC-pro.
Build Horizon OC Zeus / build (push) Failing after 15s
2026-07-28 23:45:30 +02:00
niklascfw 3baa3e7b2e Point Atmosphere-Pro clones at the OmniNX git host. 2026-07-28 23:44:08 +02:00
souldbminersmwc da7f922782 fix boot crash 2026-07-28 14:53:32 -04:00
Lightos1 af8912321c ldr: major refactor part 1 2026-07-28 19:54:07 +02:00
souldbminersmwc 363d1f0e3c Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-07-27 15:12:57 -04:00
souldbminersmwc 7e4b4b5b1d Revert "mariko 64lut cleanup"
This reverts commit 88ca2e8688.
2026-07-27 15:12:29 -04:00
Lightos1 b8c006c702 Merge branch 'main' of https://github.com/horizon-OC/horizon-OC 2026-07-27 20:57:58 +02:00
Lightos1 b7d0e3c9fa add ccache 2026-07-27 20:57:48 +02:00
souldbminersmwc 1fc9afd6d1 fix typo 2026-07-27 14:48:16 -04:00
souldbminersmwc 88ca2e8688 mariko 64lut cleanup 2026-07-27 14:38:29 -04:00
Lightos1 02bbdcdb57 improve build system for uart 2026-07-27 19:57:41 +02:00
Lightos1 139721761a fix uart log newline 2026-07-27 19:17:53 +02:00
souldbminersmwc 88f0dda772 add debug flag to build.sh 2026-07-27 10:18:56 -04:00
Lightos1 38ab15a243 fix formating 2026-07-27 15:04:17 +02:00
Lightos1 6e4a28ee67 use hooking for erista mrf 2026-07-27 15:02:53 +02:00
Lightos1 0cf4927c47 add newline for formating 2026-07-26 21:53:06 +02:00
Lightos1 bfb2009dab ldr: fix pre processor hell 2026-07-26 21:52:24 +02:00
Lightos1 8c54df039f ldr: ifdef logging 2026-07-26 21:36:12 +02:00
Lightos1 9eb53f5a0b ldr: add hooking capabilities 2026-07-26 21:04:31 +02:00
Lightos1 a6f1732e1d clkmgr: don't apply gpudvfs during GameStartMemWar if gpu dvfs is disabled 2026-07-26 13:45:25 +02:00
souldbminersmwc 6f4b58b83e ldr: code cleanup and fix soc bus table bug 2026-07-25 14:50:56 -04:00
souldbminersmwc 9d12aa5cb1 add uart logging feature 2026-07-25 12:39:31 -04:00
niklascfw ff4fd37cdc Fix exosphere build after checkout path rename.
Build Horizon OC Zeus / build (push) Failing after 3s
Clean stale absolute dependency paths when the tree moved, and fail the script instead of copying an old binary.
2026-07-15 23:02:43 +02:00
niklascfw 618fae2532 Merge upstream main into Horizon-OC-pro fork.
Build Horizon OC Zeus / build (push) Failing after 6s
Keep Atmosphere-Pro boot-storage submodule; take remaining upstream updates.
2026-07-15 22:59:30 +02:00
niklascfw 5e9ede09a1 Switch Atmosphere clone to Atmosphere-Pro and add exosphere-only build script.
Build Horizon OC Zeus / build (push) Failing after 48s
Use the boot-storage branch required for Horizon OC exosphere patches, and provide build-exosphere.sh for compiling just exosphere.bin without a full project build.
2026-07-14 23:06:55 +02:00
niklascfw 40968377c5 Fix Atmosphere-libs submodule to point at Atmosphere-Pro boot-storage.
The previous Atmosphere-NX commit is not available in the new remote, which broke submodule update.
2026-07-14 23:06:55 +02:00
79 changed files with 3414 additions and 9725 deletions

No files matched your search

-10
View File
@@ -1,10 +0,0 @@
# 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
+1
View File
@@ -5,3 +5,4 @@ Horizon OC Compilation Instructions
3. Clone the Horizon OC develop branch (``git clone https://github.com/Horizon-OC/Horizon-OC.git --recurse-submodules``)
4. Run ``./build.sh`` in the root directory
- If you want to compile with extensions, append ``--ext``
- Installing ``ccache`` is optional; if it is on your PATH it is used automatically to speed up rebuilds
+1 -2
View File
@@ -143,8 +143,7 @@ Refer to COMPILATION.md
* 665
### CPU clocks (mhz)
* 2805 → mariko absolute max, dangerous
* 2703
* 2703 → mariko absolute max, dangerous
* 2601 → unsafe
* 2499
* 2397 → mariko safe max with UV (low speedo)
-1
View File
@@ -1 +0,0 @@
This contains a diff all modifications done to exosphere
@@ -1,57 +0,0 @@
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__) \
@@ -1,29 +0,0 @@
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;
@@ -1,2 +0,0 @@
154a155
> { 0xF0000005, Restriction_None, SmcShowError },
@@ -1,9 +0,0 @@
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. */
@@ -1,22 +0,0 @@
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, },
File diff suppressed because it is too large. Load diff
@@ -151,9 +151,7 @@ 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(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(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__) \
File diff suppressed because it is too large. Load diff
@@ -1,571 +0,0 @@
/*
* 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,19 +99,16 @@ 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, },
{ 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, },
{ 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, },
};
constexpr bool IsAccessAllowed(const AccessTableEntry &entry, uintptr_t address) {
@@ -173,12 +170,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,7 +126,8 @@ namespace ams::ldr {
bool g_is_ptm;
/* Global Zstd decompression context. */
alignas(8) u8 g_zstd_dctx_workspace[util::DecompressZstdWithBicWorkBufferSizeDefault];
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. */
@@ -180,8 +181,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::ProcessAddressSpace GetProcessAddressSpace(const Meta *meta) {
return static_cast<Npdm::ProcessAddressSpace>((meta->npdm->flags0 & Npdm::MetaFlag0_ProcessAddressSpaceMask) >> Npdm::MetaFlag0_ProcessAddressSpaceShift);
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) {
@@ -224,7 +225,7 @@ namespace ams::ldr {
/* Read NSO header. */
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, 0, g_nso_headers + ctx.nso_count, sizeof(NsoHeader)));
R_UNLESS_LOG(read_size == sizeof(NsoHeader), ldr::ResultInvalidNso(), "[ldr] NSO header truncated!\n");
R_UNLESS(read_size == sizeof(NsoHeader), ldr::ResultInvalidNso());
/* Note nso is present. */
switch (i) {
@@ -263,7 +264,7 @@ namespace ams::ldr {
Result CheckAutoLoad(const AutoLoadModuleContext &ctx, u32 acid_flags) {
/* We must always have a main. */
R_UNLESS_LOG(ctx.ali.has_main, ldr::ResultInvalidNso(), "[ldr] Missing main!\n");
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) {
@@ -271,11 +272,11 @@ namespace ams::ldr {
/* All NSOs must not be --X. */
/* This is "probably" not checked on Ounce? */
R_UNLESS_LOG((hdr.flags & NsoHeader::Flag_PreventCodeReads) == 0, ldr::ResultInvalidNso(), "[ldr] NSO[%d] --x not allowed!\n", i);
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_LOG((hdr.flags & NsoHeader::Flag_UseZbicCompression) == 0, ldr::ResultInvalidNso(), "[ldr] NSO[%d] zbic not allowed!\n", i);
R_UNLESS((hdr.flags & NsoHeader::Flag_UseZbicCompression) == 0, ldr::ResultInvalidNso());
}
/* NSOs must have page-aligned segments. */
@@ -298,13 +299,13 @@ namespace ams::ldr {
const bool has_browser_dll = (acid_flags & Acid::AcidFlag_LoadBrowserCoreDll) != 0;
if (ctx.ali.has_rtld || ctx.ali.has_sdk) {
/* If we have sdk we must have rtld. */
R_UNLESS_LOG(ctx.ali.has_rtld, ldr::ResultInvalidNso(), "[ldr] Missing rtld!\n");
R_UNLESS(ctx.ali.has_rtld, ldr::ResultInvalidNso());
/* If we have rtld, we must not have browser core dll. */
R_UNLESS_LOG(!has_browser_dll, ldr::ResultInvalidNso(), "[ldr] BrowserCoreDll must not be present!\n");
R_UNLESS(!has_browser_dll, ldr::ResultInvalidNso());
} else {
/* We must not have both subsdk and browser dll. */
R_UNLESS_LOG(!(ctx.ali.has_subsdk && has_browser_dll), ldr::ResultInvalidNso(), "[ldr] Can't have both subsdk and BrowserCoreDll!\n");
R_UNLESS(!(ctx.ali.has_subsdk && has_browser_dll), ldr::ResultInvalidNso());
}
R_SUCCEED();
@@ -393,8 +394,6 @@ 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());
@@ -408,56 +407,52 @@ namespace ams::ldr {
R_SUCCEED();
}
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;
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_flags0 & Npdm::MetaFlag0_Is64BitInstruction) {
flags |= svc::CreateProcessParameterFlag_64Bit;
if (meta_flags & Npdm::MetaFlag_Is64Bit) {
flags |= svc::CreateProcessFlag_Is64Bit;
}
/* Set ProcessAddressSpace. */
switch (GetProcessAddressSpace(meta)) {
case Npdm::ProcessAddressSpace_32Bit:
flags |= svc::CreateProcessParameterFlag_AddressSpace32Bit;
/* Set AddressSpaceType. */
switch (GetAddressSpaceType(meta)) {
case Npdm::AddressSpaceType_32Bit:
flags |= svc::CreateProcessFlag_AddressSpace32Bit;
break;
case Npdm::ProcessAddressSpace_64Bit36:
flags |= svc::CreateProcessParameterFlag_AddressSpace64Bit36;
case Npdm::AddressSpaceType_64BitDeprecated:
flags |= svc::CreateProcessFlag_AddressSpace64BitDeprecated;
break;
case Npdm::ProcessAddressSpace_32BitNoReserved:
flags |= svc::CreateProcessParameterFlag_AddressSpace32BitNoReserved;
case Npdm::AddressSpaceType_32BitWithoutAlias:
flags |= svc::CreateProcessFlag_AddressSpace32BitWithoutAlias;
break;
case Npdm::ProcessAddressSpace_64Bit39:
flags |= svc::CreateProcessParameterFlag_AddressSpace64Bit39;
break;
case Npdm::ProcessAddressSpace_64Bit42:
flags |= svc::CreateProcessParameterFlag_AddressSpace64Bit42;
case Npdm::AddressSpaceType_64Bit:
flags |= svc::CreateProcessFlag_AddressSpace64Bit;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
}
/* Set Enable Debug. */
if (ldr_flags & CreateProcessParameterFlag_EnableJitDebug) {
flags |= svc::CreateProcessParameterFlag_EnableJitDebug;
if (ldr_flags & CreateProcessFlag_EnableDebug) {
flags |= svc::CreateProcessFlag_EnableDebug;
}
/* Set Enable ASLR. */
if (!(ldr_flags & CreateProcessParameterFlag_DisableAslr)) {
flags |= svc::CreateProcessParameterFlag_EnableAslr;
if (!(ldr_flags & CreateProcessFlag_DisableAslr)) {
flags |= svc::CreateProcessFlag_EnableAslr;
}
/* Set Is Application. */
if (IsApplication(meta)) {
flags |= svc::CreateProcessParameterFlag_IsApplication;
flags |= svc::CreateProcessFlag_IsApplication;
/* 7.0.0+: Set OptimizeMemoryAllocation if relevant. */
if (hos::GetVersion() >= hos::Version_7_0_0) {
if (meta_flags0 & Npdm::MetaFlag0_OptimizeMemoryAllocation) {
flags |= svc::CreateProcessParameterFlag_OptimizeMemoryAllocation;
if (meta_flags & Npdm::MetaFlag_OptimizeMemoryAllocation) {
flags |= svc::CreateProcessFlag_OptimizeMemoryAllocation;
}
}
}
@@ -469,19 +464,19 @@ namespace ams::ldr {
switch (GetPoolPartition(meta)) {
case Acid::PoolPartition_Application:
if (IsApplet(meta)) {
flags |= svc::CreateProcessParameterFlag_PoolPartitionApplet;
flags |= svc::CreateProcessFlag_PoolPartitionApplet;
} else {
flags |= svc::CreateProcessParameterFlag_PoolPartitionApplication;
flags |= svc::CreateProcessFlag_PoolPartitionApplication;
}
break;
case Acid::PoolPartition_Applet:
flags |= svc::CreateProcessParameterFlag_PoolPartitionApplet;
flags |= svc::CreateProcessFlag_PoolPartitionApplet;
break;
case Acid::PoolPartition_System:
flags |= svc::CreateProcessParameterFlag_PoolPartitionSystem;
flags |= svc::CreateProcessFlag_PoolPartitionSystem;
break;
case Acid::PoolPartition_SystemNonSecure:
flags |= svc::CreateProcessParameterFlag_PoolPartitionSystemNonSecure;
flags |= svc::CreateProcessFlag_PoolPartitionSystemNonSecure;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
@@ -489,23 +484,18 @@ 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::CreateProcessParameterFlag_DeprecatedUseSecureMemory;
flags |= svc::CreateProcessFlag_DeprecatedUseSecureMemory;
}
}
/* 11.0.0+/meso Set Disable DAS Merge. */
if (meta_flags0 & Npdm::MetaFlag0_DisableDeviceAddressSpaceMerge) {
flags |= svc::CreateProcessParameterFlag_DisableDeviceAddressSpaceMerge;
/* 11.0.0+/meso Set Disable DAS merge. */
if (meta_flags & Npdm::MetaFlag_DisableDeviceAddressSpaceMerge) {
flags |= svc::CreateProcessFlag_DisableDeviceAddressSpaceMerge;
}
/* 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;
/* 18.0.0+/meso Set Alias region extra size. */
if (meta_flags & Npdm::MetaFlag_EnableAliasRegionExtraSize) {
flags |= svc::CreateProcessFlag_EnableAliasRegionExtraSize;
}
*out = flags;
@@ -523,7 +513,7 @@ namespace ams::ldr {
out->reslimit = resource_limit;
/* Set flags. */
R_TRY(GetCreateProcessParameterFlags(std::addressof(out->flags), meta, flags));
R_TRY(GetCreateProcessFlags(std::addressof(out->flags), meta, flags));
/* 3.0.0+ System Resource Size. */
if (hos::GetVersion() >= hos::Version_3_0_0) {
@@ -533,7 +523,7 @@ namespace ams::ldr {
/* Validate system resource usage. */
if (meta->npdm->system_resource_size) {
/* Process must be 64-bit. */
R_UNLESS((out->flags & svc::CreateProcessParameterFlag_AddressSpace64Bit39), ldr::ResultInvalidMeta());
R_UNLESS((out->flags & svc::CreateProcessFlag_AddressSpace64Bit), ldr::ResultInvalidMeta());
/* Process must be application or applet. */
R_UNLESS(IsApplication(meta) || IsApplet(meta), ldr::ResultInvalidMeta());
@@ -606,29 +596,25 @@ 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::CreateProcessParameterFlag_AddressSpaceMask) {
case svc::CreateProcessParameterFlag_AddressSpace32Bit:
case svc::CreateProcessParameterFlag_AddressSpace32BitNoReserved:
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::CreateProcessParameterFlag_AddressSpace64Bit36:
case svc::CreateProcessFlag_AddressSpace64BitDeprecated:
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
break;
case svc::CreateProcessParameterFlag_AddressSpace64Bit39:
case svc::CreateProcessFlag_AddressSpace64Bit:
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::CreateProcessParameterFlag_AddressSpace64Bit36) {
if (out_param->flags & svc::CreateProcessFlag_AddressSpace64BitDeprecated) {
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
} else {
@@ -641,7 +627,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::CreateProcessParameterFlag_EnableAslr) {
if (out_param->flags & svc::CreateProcessFlag_EnableAslr) {
aslr_slide = GenerateSecureRandom(free_size / os::MemoryBlockUnitSize) * os::MemoryBlockUnitSize;
}
@@ -663,13 +649,10 @@ 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_zbic, 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_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;
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());
@@ -679,19 +662,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_LOG(read_size == file_size, ldr::ResultInvalidNso(), "[ldr] Couldn't read segment from file!\n");
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_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");
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_LOG(decompressed, ldr::ResultInvalidNso(), "[ldr] Failed to decompress segment with lz4!\n");
R_UNLESS(decompressed, ldr::ResultInvalidNso());
}
R_SUCCEED();
@@ -706,12 +689,12 @@ 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_LOG(std::memcmp(hash, nso_header->segment_hashes[segment], sizeof(hash)) == 0, ldr::ResultInvalidNso(), "[ldr] Invalid segment hash!\n");
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_zbic = (nso_header->flags & NsoHeader::Flag_UseZbicCompression) != 0;
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;
@@ -728,11 +711,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_zbic, map_address + nso_header->text_dst_offset, map_end));
(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_zbic, map_address + nso_header->ro_dst_offset, map_end));
(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_zbic, map_address + nso_header->rw_dst_offset, map_end));
(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);
@@ -795,17 +778,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); };
R_TRY(LoadAutoLoadModule(process_info->process_handle, file, ctx.headers + i,
process_info->nso_address[i], process_info->nso_size[i], map_size));
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. */
@@ -832,6 +813,12 @@ namespace ams::ldr {
}
Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) {
/* Append extra .bss for 64LUT */
/* TODO: REMOVE THIS. */
if (g_is_pcv && ctx.main_nso_idx >= 0) {
g_nso_headers[ctx.main_nso_idx].bss_size += static_cast<u32>(hoc::pcv::HocPcvScratchSize);
}
/* Get CreateProcessParameter. */
svc::CreateProcessParameter param;
R_TRY(GetCreateProcessParameter(std::addressof(param), meta, flags, resource_limit));
@@ -846,7 +833,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 { R_DISCARD(svc::CloseHandle(process_handle)); };
ON_RESULT_FAILURE { svc::CloseHandle(process_handle); };
/* Load all auto load modules. */
R_RETURN(LoadAutoLoadModules(out, ctx, argument));
@@ -904,8 +891,8 @@ namespace ams::ldr {
os::ProcessId process_id = os::GetProcessId(info.process_handle);
/* Register new process. */
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);
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++) {
@@ -916,7 +903,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. */
R_DISCARD(RedirectHtmlDocumentPathForHbl(loc));
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 1235000
#define CPU_MAX_MAX_VOLT 1200000
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: 66MHz step rate, 100MHz step rate, 133MHz step rate and jedec. */
/* Available: 33MHz step rate, 66MHz step rate, 100MHz step rate, 133MHz step rate and jedec. */
/* Jedec freqs are 1333MHz, 1600MHz, 1866MHz, 2133MHz, 2400MHz, 2666MHz, 2933MHz, 3200MHz. */
.stepMode = StepMode_Jedec,
.stepMode = StepMode_33MHz,
.marikoEmcMaxClock = 2133000, /* 1866MHz @ 1866tWRL is guaranteed to work on all Mariko units */
.marikoEmcMaxClock = 2133000, /* Requires the EMC DVFS 63-entry patches (EMC DVFS Count / EMC SoC LUT). */
.marikoEmcVddqVolt = 600000,
.emcDvbShift = 0,
@@ -100,14 +100,13 @@ volatile CustomizeTable C = {
/* 1120mV is NVIDIA rating */
.marikoCpuMaxVolt = 1120,
/* 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 */
/* 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. */
.marikoCpuMaxClock = 1963500,
.eristaCpuBoostClock = 1785000, /* Default boost clock */
@@ -319,7 +318,6 @@ 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 {
@@ -348,7 +346,6 @@ 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 {
@@ -377,7 +374,6 @@ 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 {
@@ -406,7 +402,6 @@ 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 260
#define KIP_VERSION 300
#include "oc_common.hpp"
#include "pcv/pcv_common.hpp"
@@ -40,6 +40,7 @@ enum StepMode: u32 {
StepMode_100MHz = 1,
StepMode_Jedec = 2,
StepMode_133MHz = 3,
StepMode_33MHz = 4,
};
enum ReadLatency: u32 {
@@ -20,12 +20,44 @@
#include <stratosphere.hpp>
#include <vapours/results/results_common.hpp>
#define LOGGING(fmt, ...) ((void)0)
#define CRASH(msg, ...) { ams::diag::AbortImpl(msg, __PRETTY_FUNCTION__, "", 0); __builtin_unreachable(); }
#ifndef HOC_UART_LOG
#define HOC_UART_LOG 0
#endif
#if HOC_UART_LOG && !(defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING))
#undef HOC_UART_LOG
#define HOC_UART_LOG 0
#endif
#define HOC_PCV_NVLOG_PATCH 1
#define HOC_PCV_FORCE_VERBOSITY 1
#include "customize.hpp"
#include "oc_log.hpp"
#define HOC_IRAM_LOG 0
#if (!HOC_UART_LOG) && (defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING))
#undef HOC_IRAM_LOG
#define HOC_IRAM_LOG 1
#endif
#if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
#if HOC_IRAM_LOG
#define LOGGING(...) Log(__VA_ARGS__)
#elif HOC_UART_LOG
#define LOGGING(fmt, ...) AMS_LOG(fmt "\n", ##__VA_ARGS__)
#endif
#else
#define LOGGING(...) ((void)0)
#endif
#define CRASH(msg, ...) { ams::diag::AbortImpl(msg, __PRETTY_FUNCTION__, "", 0); __builtin_unreachable(); }
#define PATCH_OFFSET(offset, value) \
static_assert(sizeof(__typeof__(offset)) <= sizeof(u64)); \
*(offset) = value;
@@ -50,13 +82,18 @@ namespace ams::ldr {
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);
R_DEFINE_ERROR_RESULT(InvalidEmcDvfsCount, 1019);
R_DEFINE_ERROR_RESULT(InvalidEmcSocLut, 1020);
R_DEFINE_ERROR_RESULT(InvalidEmcRateList, 1021);
R_DEFINE_ERROR_RESULT(InvalidNvLogRedirect, 1022);
R_DEFINE_ERROR_RESULT(InvalidBusFreqReloc, 1023);
R_DEFINE_ERROR_RESULT(HookArenaOutOfMemory, 1024);
R_DEFINE_ERROR_RESULT(HookPayloadTooLarge, 1025);
R_DEFINE_ERROR_RESULT(HookRelocationUnsupported, 1026);
R_DEFINE_ERROR_RESULT(HookSiteInvalid, 1027);
R_DEFINE_ERROR_RESULT(HookPayloadEscapes, 1028);
R_DEFINE_ERROR_RESULT(HookDataOutOfMemory, 1029);
R_DEFINE_ERROR_RESULT(HookUnavailable, 1030);
}
namespace ams::ldr::hoc {
@@ -71,6 +108,7 @@ namespace ams::ldr::hoc {
patternFn pattern_search_fn = nullptr;
Pointer value_search;
size_t patched_count = 0;
bool optional = false;
Result Apply(Pointer *ptr) {
Result res = patcher_fn(ptr);
@@ -101,7 +139,7 @@ namespace ams::ldr::hoc {
}
Result CheckResult() {
R_UNLESS(patched_count > 0, ldr::ResultUnsuccessfulPatcher());
R_UNLESS(optional || patched_count > 0, ldr::ResultUnsuccessfulPatcher());
if (maximum_patched_count) {
R_UNLESS(patched_count <= maximum_patched_count, ldr::ResultUnsuccessfulPatcher());
@@ -19,7 +19,7 @@
#include "oc_common.hpp"
#if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
#if HOC_IRAM_LOG
#include "fatal_handler_bin.h"
#endif
@@ -70,6 +70,32 @@ namespace ams::ldr::hoc {
return rc;
}
#if HOC_UART_LOG
/* Remove this? */
void UartLog(const char *fmt, ...) {
char line[256];
constexpr size_t PrefixLen = 13; /* "[HOC] " */
std::memcpy(line, "[Horizon OC] ", PrefixLen);
va_list args;
va_start(args, fmt);
int n = vsnprintf(line + PrefixLen, sizeof(line) - PrefixLen - 1, fmt, args);
va_end(args);
if (n < 0) {
return;
}
size_t body = static_cast<size_t>(n);
if (body > sizeof(line) - PrefixLen - 2) { /* vsnprintf returns the untruncated length */
body = sizeof(line) - PrefixLen - 2;
}
size_t len = PrefixLen + body;
line[len++] = '\n';
svc::OutputDebugString(line, len);
}
#endif
struct log_ctx_t {
u32 magic;
u32 sz;
@@ -81,7 +107,7 @@ namespace ams::ldr::hoc {
#define IRAM_LOG_CTX_ADDR 0x4003C000
#define IRAM_LOG_MAX_SZ 4096
#if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
#if HOC_IRAM_LOG
void Log(const char *data, ...) {
static const u32 max_log_sz = sizeof(working_buf) - sizeof(log_ctx_t);
static bool initDone = false;
@@ -112,8 +138,8 @@ namespace ams::ldr::hoc {
}
#endif
#if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
void ViewLog() {
#if HOC_IRAM_LOG
if (spl::GetSocType() == spl::SocType_Mariko) {
return;
}
@@ -127,6 +153,6 @@ namespace ams::ldr::hoc {
SmcRebootToIramPayload();
while(true) { }
#endif
}
#endif
}
@@ -25,4 +25,8 @@ namespace ams::ldr::hoc {
void Log(const char *data, ...);
void ViewLog();
/* Emit a formatted line over the kernel debug UART via svcOutputDebugString.
No-op unless the running kernel enables debug logging (audit/debug mesosphere). */
void UartLog(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
}
@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../mtc_timing_value.hpp"
#include "../../mtc_timing_value.hpp"
namespace ams::ldr::hoc::pcv::erista {
@@ -0,0 +1,97 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 "../pcv.hpp"
#include "../../mtc_timing_value.hpp"
#include "pcv_erista_cpu.hpp"
#include "pcv_erista_gpu.hpp"
#include "pcv_erista_mtc.hpp"
#include "calculate_timings_erista.hpp"
namespace ams::ldr::hoc::pcv::erista {
DEFINE_HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData);
u32 *nsoStart;
Result InstallHooks() {
R_TRY(Hooks().CheckEnabled());
R_TRY(Hooks().CopyPayload());
auto *data = Hooks().BindData(e_HookPayloadData);
R_UNLESS(data != nullptr, ldr::ResultHookDataOutOfMemory());
R_TRY(MtcInstallHooks(data));
R_SUCCEED();
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
nsoStart = reinterpret_cast<u32 *>(mapped_nso);
MtcGenerateFreqTables();
u32 CpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
u32 GpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
PatcherEntry<u32> patches[] = {
{"CPU Freq Table", CpuFreqCvbTable<false>, 1, nullptr, CpuCvbDefaultMaxFreq },
{"CPU Volt DVFS", &CpuVoltDvfs, 1, nullptr, CpuVminOfficial },
{"CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial },
{"CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low },
{"GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial },
{"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 Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit },
{"MEM Table Asm", &MemMtcTableAsm, 4, &MemMtcGetGetTablePatternFn },
{"MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit },
{"MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit },
{"MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit },
{"MEM Volt", &MemVoltHandler, 2, nullptr, MemVoltHOS },
};
for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(EristaMtcTable); ptr += sizeof(u32)) {
u32 *ptr32 = reinterpret_cast<u32 *>(ptr);
for (auto &entry : patches) {
if (R_SUCCEEDED(entry.SearchAndApply(ptr32))) {
break;
}
}
}
for (auto &entry : patches) {
LOGGING("%s Count: %zu", entry.description, entry.patched_count);
if (R_FAILED(entry.CheckResult())) {
panic::SmcError(panic::Patch);
CRASH(entry.description);
}
}
if (R_FAILED(InstallHooks())) {
panic::SmcError(panic::Patch);
}
}
}
@@ -0,0 +1,42 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* 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 "../../oc_common.hpp"
#include "../pcv_common.hpp"
#include "../pcv_asm.hpp"
#include "../pcv_hook.hpp"
namespace ams::ldr::hoc::pcv::erista {
struct HookPayloadData {
struct {
EristaMtcTable *mtcTable;
u32 mtcCount;
} mtcTableAsm;
};
DECLARE_HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData);
extern u32 *nsoStart;
void Patch(uintptr_t mapped_nso, size_t nso_size);
}
@@ -0,0 +1,108 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 "../pcv.hpp"
namespace ams::ldr::hoc::pcv::erista {
Result CpuVoltDvfs(u32 *ptr) {
if (std::memcmp(ptr + 5, cpuVoltDvfsPattern, sizeof(cpuVoltDvfsPattern))) {
R_THROW(ldr::ResultInvalidCpuMinVolt());
}
if (C.eristaCpuVmin) {
PATCH_OFFSET(ptr, C.eristaCpuVmin);
}
if (C.eristaCpuUV) {
PATCH_OFFSET(ptr - 2, C.eristaCpuVmin);
}
if (C.eristaCpuMaxVolt) {
PATCH_OFFSET(ptr + 5, C.eristaCpuMaxVolt);
}
R_SUCCEED();
}
Result CpuVoltThermals(u32 *ptr) {
if (std::memcmp(ptr - 6, cpuVoltageThermalPattern, sizeof(cpuVoltageThermalPattern))) {
R_THROW(ldr::ResultInvalidCpuMinVolt());
}
if (C.eristaCpuVmin) {
PATCH_OFFSET( ptr, C.eristaCpuVmin);
PATCH_OFFSET(ptr + 3, C.eristaCpuVmin);
PATCH_OFFSET(ptr + 6, C.eristaCpuVmin);
}
if (C.eristaCpuMaxVolt) {
PATCH_OFFSET(ptr - 2, C.eristaCpuMaxVolt);
PATCH_OFFSET(ptr + 1, C.eristaCpuMaxVolt);
PATCH_OFFSET(ptr + 4, C.eristaCpuMaxVolt);
PATCH_OFFSET(ptr + 7, C.eristaCpuMaxVolt);
}
R_SUCCEED();
}
Result CpuVoltDfll(u32* ptr) {
CvbCpuDfllData *entry = reinterpret_cast<CvbCpuDfllData *>(ptr);
R_UNLESS(entry->tune0_low == 0xFFEAD0FF, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune0_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_low == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
if (!C.eristaCpuUV) {
R_SKIP();
}
switch (C.eristaCpuUV) {
case 1:
PATCH_OFFSET(&(entry->tune0_high), 0xffff);
PATCH_OFFSET(&(entry->tune1_high), 0x27007ff);
break;
case 2:
PATCH_OFFSET(&(entry->tune0_high), 0xefff);
PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
break;
case 3:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
break;
case 4:
PATCH_OFFSET(&(entry->tune0_high), 0xdfdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27a07ff);
break;
case 5:
PATCH_OFFSET(&(entry->tune0_high), 0xcfdf);
PATCH_OFFSET(&(entry->tune1_high), 0x37007ff);
break;
default:
break;
}
R_SUCCEED();
}
}
@@ -0,0 +1,66 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 "pcv_erista.hpp"
namespace ams::ldr::hoc::pcv::erista {
constexpr cvb_entry_t CpuCvbTableDefault[] = {
// CPU_PLL_CVB_TABLE_ODN
{ 204000, {721094}, { } },
{ 306000, {754040}, { } },
{ 408000, {786986}, { } },
{ 510000, {819932}, { } },
{ 612000, {852878}, { } },
{ 714000, {885824}, { } },
{ 816000, {918770}, { } },
{ 918000, {951716}, { } },
{ 1020000, {984662}, { -2875621, 358099, -8585} },
{ 1122000, {1017608}, { -52225, 104159, -2816} },
{ 1224000, {1050554}, { 1076868, 8356, -727} },
{ 1326000, {1083500}, { 2208191, -84659, 1240} },
{ 1428000, {1116446}, { 2519460, -105063, 1611} },
{ 1581000, {1130000}, { 2889664, -122173, 1834} },
{ 1683000, {1168000}, { 5100873, -279186, 4747} },
{ 1785000, {1227500}, { 5100873, -279186, 4747} },
{ },
};
constexpr u32 CpuVoltOfficial = 1227;
constexpr u32 CpuVminOfficial = 825;
constexpr u32 CpuTune0Low = 0xFFEAD0FF;
constexpr u32 CpuVoltL4T = 1257'000;
static const u32 cpuVoltDvfsPattern[] = { 1227, 1000, 100, 1000, 0 };
static_assert(sizeof(cpuVoltDvfsPattern) == 0x14, "Invalid cpuVoltDvfsPattern size");
static const u32 cpuVoltageThermalPattern[] = { 950, 1132, 0, 950, 1227, 0, 825, 1227, 15000, 825, 1170, 60000, 825, 1132, 80000 };
static_assert(sizeof(cpuVoltageThermalPattern) == 0x3c, "Invalid cpuVoltageThermalPattern size");
Result CpuVoltDvfs(u32 *ptr);
Result CpuVoltThermals(u32 *ptr);
Result CpuVoltDfll(u32* ptr);
}
@@ -0,0 +1,123 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 "../pcv.hpp"
#include "../pcv_asm.hpp"
namespace ams::ldr::hoc::pcv::erista {
Result GpuVoltDVFS(u32 *ptr) {
if (std::memcmp(ptr, gpuVoltDvfsPattern, sizeof(gpuVoltDvfsPattern))) {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
if (C.eristaGpuVmin) {
PATCH_OFFSET(ptr, C.eristaGpuVmin);
}
R_SUCCEED();
}
Result GpuVoltThermals(u32 *ptr) {
if (std::memcmp(ptr - 3, gpuVoltThermalPattern, sizeof(gpuVoltThermalPattern))) {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
if (C.eristaGpuVmin) {
PATCH_OFFSET(ptr, C.eristaGpuVmin);
PATCH_OFFSET(ptr + 3, C.eristaGpuVmin);
PATCH_OFFSET(ptr + 6, C.eristaGpuVmin);
PATCH_OFFSET(ptr + 9, C.eristaGpuVmin);
PATCH_OFFSET(ptr + 12, C.eristaGpuVmin);
}
R_SUCCEED();
}
Result GpuFreqMaxAsm(u32 *ptr32) {
// Check if both two instructions match the pattern
u32 ins1 = *ptr32, ins2 = *(ptr32 + 1);
if (!(asm_compare_no_rd(ins1, GpuAsmPattern[0]) && asm_compare_no_rd(ins2, GpuAsmPattern[1]))) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
// Both instructions should operate on the same register
u8 rd = asm_get_rd(ins1);
if (rd != asm_get_rd(ins2)) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
u32 max_clock;
switch (C.eristaGpuUV) {
case 0:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq;
break;
case 1:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableSLT)->freq;
break;
case 2:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableHiOPT)->freq;
break;
default:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq;
break;
}
u32 asm_patch[2] = {
asm_set_rd(asm_set_imm16(GpuAsmPattern[0], max_clock), rd),
asm_set_rd(asm_set_imm16(GpuAsmPattern[1], max_clock >> 16), rd)
};
PATCH_OFFSET(ptr32, asm_patch[0]);
PATCH_OFFSET(ptr32 + 1, asm_patch[1]);
R_SUCCEED();
}
Result GpuFreqPllMax(u32 *ptr) {
clk_pll_param *entry = reinterpret_cast<clk_pll_param *>(ptr);
// All zero except for freq
for (size_t i = 1; i < sizeof(clk_pll_param) / sizeof(u32); i++) {
R_UNLESS(*(ptr + i) == 0, ldr::ResultInvalidGpuPllEntry());
}
// Double the max clk simply
u32 max_clk = entry->freq * 2;
entry->freq = max_clk;
R_SUCCEED();
}
// patch out 1305MHz limit on erista, don't use this!
// Result GpuFreqPllLimit(u32 *ptr) {
// u32 prev_freq = *(ptr - 1);
// if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) {
// R_THROW(ldr::ResultInvalidGpuPllEntry());
// }
// PATCH_OFFSET(ptr, 3600000);
// R_SUCCEED();
// }
}
@@ -0,0 +1,82 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 "pcv_erista.hpp"
namespace ams::ldr::hoc::pcv::erista {
constexpr u32 GpuClkPllLimit = 2'600'000;
constexpr u32 GpuClkPllMax = 921'600'000;
constexpr u32 GpuVminOfficial = 810;
static const u32 gpuVoltDvfsPattern[] = { 810, 1150, 1000, 100, 1000, 10, };
static_assert(sizeof(gpuVoltDvfsPattern) == (sizeof(u32) * 6), "Invalid gpuVoltDvfsPattern");
static const u32 gpuVoltThermalPattern[] = { 950, 1132, 0, 810, 1132, 15000, 810, 1132, 30000, 810, 1132, 50000, 810, 1132, 70000, 810, 1132, 105000 };
static_assert(sizeof(gpuVoltThermalPattern) == 0x48, "Invalid gpuVoltageThermalPattern size");
/* GPU Max Clock asm Pattern:
*
* MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1
*
* MOVK W11, #0xE, LSL#16 <shift>16 0xE 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1
*/
inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 };
inline bool GpuMaxClockPatternFn(u32 *ptr32) {
return asm_compare_no_rd(*ptr32, GpuAsmPattern[0]);
};
constexpr cvb_entry_t GpuCvbTableDefault[] = {
// NA_FREQ_CVB_TABLE
{ 76800, {}, { 814294, 8144, -940, 808, -21583, 226, } },
{ 153600, {}, { 856185, 8144, -940, 808, -21583, 226, } },
{ 230400, {}, { 898077, 8144, -940, 808, -21583, 226, } },
{ 307200, {}, { 939968, 8144, -940, 808, -21583, 226, } },
{ 384000, {}, { 981860, 8144, -940, 808, -21583, 226, } },
{ 460800, {}, { 1023751, 8144, -940, 808, -21583, 226, } },
{ 537600, {}, { 1065642, 8144, -940, 808, -21583, 226, } },
{ 614400, {}, { 1107534, 8144, -940, 808, -21583, 226, } },
{ 691200, {}, { 1149425, 8144, -940, 808, -21583, 226, } },
{ 768000, {}, { 1191317, 8144, -940, 808, -21583, 226, } },
{ 844800, {}, { 1233208, 8144, -940, 808, -21583, 226, } },
{ 921600, {}, { 1275100, 8144, -940, 808, -21583, 226, } },
{ },
};
Result GpuVoltDVFS(u32 *ptr);
Result GpuVoltThermals(u32 *ptr);
Result GpuFreqMaxAsm(u32 *ptr32);
Result GpuFreqPllMax(u32 *ptr);
// patch out 1305MHz limit on erista, don't use this!
// Result GpuFreqPllLimit(u32 *ptr);
}
@@ -21,181 +21,22 @@
*/
#include <vector>
#include "pcv.hpp"
#include "../mtc_timing_value.hpp"
#include "../erista/calculate_timings_erista.hpp"
#include "../pcv.hpp"
#include "../../mtc_timing_value.hpp"
#include "calculate_timings_erista.hpp"
namespace ams::ldr::hoc::pcv::erista {
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;
bool foundMtcTablePattern = false;
} getMtcTableCache;
}
Result CpuVoltDvfs(u32 *ptr) {
if (std::memcmp(ptr + 5, cpuVoltDvfsPattern, sizeof(cpuVoltDvfsPattern))) {
R_THROW(ldr::ResultInvalidCpuMinVolt());
}
if (C.eristaCpuVmin) {
PATCH_OFFSET(ptr, C.eristaCpuVmin);
}
if (C.eristaCpuUV) {
PATCH_OFFSET(ptr - 2, C.eristaCpuVmin);
}
if (C.eristaCpuMaxVolt) {
PATCH_OFFSET(ptr + 5, C.eristaCpuMaxVolt);
}
R_SUCCEED();
}
Result CpuVoltThermals(u32 *ptr) {
if (std::memcmp(ptr - 6, cpuVoltageThermalPattern, sizeof(cpuVoltageThermalPattern))) {
R_THROW(ldr::ResultInvalidCpuMinVolt());
}
if (C.eristaCpuVmin) {
PATCH_OFFSET( ptr, C.eristaCpuVmin);
PATCH_OFFSET(ptr + 3, C.eristaCpuVmin);
PATCH_OFFSET(ptr + 6, C.eristaCpuVmin);
}
if (C.eristaCpuMaxVolt) {
PATCH_OFFSET(ptr - 2, C.eristaCpuMaxVolt);
PATCH_OFFSET(ptr + 1, C.eristaCpuMaxVolt);
PATCH_OFFSET(ptr + 4, C.eristaCpuMaxVolt);
PATCH_OFFSET(ptr + 7, C.eristaCpuMaxVolt);
}
R_SUCCEED();
}
Result CpuVoltDfll(u32* ptr) {
CvbCpuDfllData *entry = reinterpret_cast<CvbCpuDfllData *>(ptr);
R_UNLESS(entry->tune0_low == 0xFFEAD0FF, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune0_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_low == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
if (!C.eristaCpuUV) {
R_SKIP();
}
switch(C.eristaCpuUV) {
case 1:
PATCH_OFFSET(&(entry->tune0_high), 0xffff);
PATCH_OFFSET(&(entry->tune1_high), 0x27007ff);
break;
case 2:
PATCH_OFFSET(&(entry->tune0_high), 0xefff);
PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
break;
case 3:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
break;
case 4:
PATCH_OFFSET(&(entry->tune0_high), 0xdfdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27a07ff);
break;
case 5:
PATCH_OFFSET(&(entry->tune0_high), 0xcfdf);
PATCH_OFFSET(&(entry->tune1_high), 0x37007ff);
break;
default:
break;
}
R_SUCCEED();
}
Result GpuVoltDVFS(u32 *ptr) {
if (std::memcmp(ptr, gpuVoltDvfsPattern, sizeof(gpuVoltDvfsPattern))) {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
if (C.eristaGpuVmin) {
PATCH_OFFSET(ptr, C.eristaGpuVmin);
}
R_SUCCEED();
}
Result GpuVoltThermals(u32 *ptr) {
if (std::memcmp(ptr - 3, gpuVoltThermalPattern, sizeof(gpuVoltThermalPattern))) {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
if (C.eristaGpuVmin) {
PATCH_OFFSET(ptr, C.eristaGpuVmin);
PATCH_OFFSET(ptr + 3, C.eristaGpuVmin);
PATCH_OFFSET(ptr + 6, C.eristaGpuVmin);
PATCH_OFFSET(ptr + 9, C.eristaGpuVmin);
PATCH_OFFSET(ptr + 12, C.eristaGpuVmin);
}
R_SUCCEED();
}
Result GpuFreqMaxAsm(u32 *ptr32) {
// Check if both two instructions match the pattern
u32 ins1 = *ptr32, ins2 = *(ptr32 + 1);
if (!(asm_compare_no_rd(ins1, GpuAsmPattern[0]) && asm_compare_no_rd(ins2, GpuAsmPattern[1]))) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
// Both instructions should operate on the same register
u8 rd = asm_get_rd(ins1);
if (rd != asm_get_rd(ins2)) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
u32 max_clock;
switch (C.eristaGpuUV) {
case 0:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq;
break;
case 1:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableSLT)->freq;
break;
case 2:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableHiOPT)->freq;
break;
default:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq;
break;
}
u32 asm_patch[2] = {
asm_set_rd(asm_set_imm16(GpuAsmPattern[0], max_clock), rd),
asm_set_rd(asm_set_imm16(GpuAsmPattern[1], max_clock >> 16), rd)
};
PATCH_OFFSET(ptr32, asm_patch[0]);
PATCH_OFFSET(ptr32 + 1, asm_patch[1]);
R_SUCCEED();
}
/* Note: This does not have proper timings, so base latency adjustment will not work. */
/* However, it may still achieve a slightly higher frequency, but not as much as it could be. */
/* I'm certainly not insane enough to attempt this pain again, so this will have to do *for now*. */
@@ -572,33 +413,6 @@ namespace ams::ldr::hoc::pcv::erista {
R_SUCCEED();
}
Result GpuFreqPllMax(u32 *ptr) {
clk_pll_param *entry = reinterpret_cast<clk_pll_param *>(ptr);
// All zero except for freq
for (size_t i = 1; i < sizeof(clk_pll_param) / sizeof(u32); i++) {
R_UNLESS(*(ptr + i) == 0, ldr::ResultInvalidGpuPllEntry());
}
// Double the max clk simply
u32 max_clk = entry->freq * 2;
entry->freq = max_clk;
R_SUCCEED();
}
// patch out 1305MHz limit on erista, don't use this!
// Result GpuFreqPllLimit(u32 *ptr) {
// u32 prev_freq = *(ptr - 1);
// if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) {
// R_THROW(ldr::ResultInvalidGpuPllEntry());
// }
// PATCH_OFFSET(ptr, 3600000);
// R_SUCCEED();
// }
HOOK_PAYLOAD_FN EristaMtcTable *GetEristaMtcTableImpl(u32 *count) {
const HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData);
@@ -606,6 +420,18 @@ namespace ams::ldr::hoc::pcv::erista {
return data->mtcTableAsm.mtcTable;
}
Result MtcInstallHooks(HookPayloadData *data) {
R_UNLESS(getMtcTableCache.getEristaMtcTableFnSite != nullptr && getMtcTableCache.mtcTable != nullptr, ldr::ResultInvalidMtcTablePattern());
/* Copy the data to the payload. */
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();
}
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. */
@@ -650,71 +476,4 @@ namespace ams::ldr::hoc::pcv::erista {
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();
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
nsoStart = reinterpret_cast<u32 *>(mapped_nso);
nsoEnd = reinterpret_cast<u32 *>(mapped_nso + nso_size);
MtcGenerateFreqTables();
u32 CpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
u32 GpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
PatcherEntry<u32> patches[] = {
{"CPU Freq Table", CpuFreqCvbTable<false>, 1, nullptr, CpuCvbDefaultMaxFreq },
{"CPU Volt DVFS", &CpuVoltDvfs, 1, nullptr, CpuVminOfficial },
{"CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial },
{"CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low },
{"GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial },
{"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 Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit },
{"MEM Table Asm", &MemMtcTableAsm, 4, &MemMtcGetGetTablePatternFn },
{"MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit },
{"MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit },
{"MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit },
{"MEM Volt", &MemVoltHandler, 2, nullptr, MemVoltHOS },
};
for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(EristaMtcTable); ptr += sizeof(u32)) {
u32 *ptr32 = reinterpret_cast<u32 *>(ptr);
for (auto &entry : patches) {
if (R_SUCCEEDED(entry.SearchAndApply(ptr32))) {
break;
}
}
}
for (auto &entry : patches) {
LOGGING("%s Count: %zu\n", entry.description, entry.patched_count);
if (R_FAILED(entry.CheckResult())) {
panic::SmcError(panic::Patch);
CRASH(entry.description);
}
}
if (R_FAILED(InstallHooks())) {
panic::SmcError(panic::Patch);
}
}
}
@@ -0,0 +1,92 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 "pcv_erista.hpp"
#include "../../mtc_timing_table.hpp"
namespace ams::ldr::hoc::pcv::erista {
constexpr u32 EmcListDefault[] = { 40800, 68000, 102000, 204000, 408000, 665600, 800000, 1065600, 1331200, 1600000, };
constexpr u32 EmcListSizeDefault = std::size(EmcListDefault);
constexpr u32 EmcListEndDefault = EmcListSizeDefault - 1;
constexpr u32 MemVoltHOS = 1125'000;
constexpr u32 EmcClkPllmLimit = 1866'000'000;
constexpr u32 MTC_TABLE_REV = 7;
constexpr u32 MtcTableCountDefault = 10;
constexpr size_t MtcFullTableSize = sizeof(EristaMtcTable) * MtcTableCountDefault;
constexpr u32 MtcFullTableCount = 3;
/* These dramids were copied from Hekate -- see /bdk/mem/sdram.h */
enum DramId {
ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH = 0,
ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE = 1,
ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC = 2,
ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH = 4,
ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX = 7,
};
enum MtcTableIndex {
T210SdevEmcDvfsTableS4gb01 = 0, /* HB-MGCH, WT:C */
T210SdevEmcDvfsTableS6gb01 = 1, /* HM-MGCH */
T210SdevEmcDvfsTableH4gb01 = 2, /* HR-NLE */
MtcTableIndex_Invalid = 3,
};
struct MtcDramIndex {
DramId dramId;
MtcTableIndex index;
};
/* TODO: Test 6gb and 8gb. */
const inline MtcDramIndex mtcIndexTable[] = {
{ ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH, T210SdevEmcDvfsTableS4gb01, },
{ ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC, T210SdevEmcDvfsTableS4gb01, },
{ ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH, T210SdevEmcDvfsTableS6gb01, },
{ ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX, T210SdevEmcDvfsTableS6gb01, },
{ ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE, T210SdevEmcDvfsTableH4gb01, },
};
constexpr u32 MtcBrAsm = 0xD61F0140;
constexpr u32 MtcMovAsm = 0x52800148;
constexpr u32 MtcAdrpAsm = 0xD0000081;
constexpr u32 MtcBlIns = 0x97ffae64;
constexpr u32 MtcAddAsm = 0x91131821;
ALWAYS_INLINE bool MemMtcGetGetTablePatternFn(u32 *ptr) {
/* This builds an address that gets returned, so the register must be x0 by convention. */
return AsmCompareAddNoImm12(*ptr, MtcAddAsm);
}
Result MemFreqMtcTable(u32 *ptr);
void MtcGenerateFreqTables();
Result MemFreqMax(u32 *ptr);
HOOK_PAYLOAD_FN EristaMtcTable *GetEristaMtcTableImpl(u32 *count);
Result MemMtcTableAsm(u32 *ptr);
Result MtcInstallHooks(HookPayloadData *data);
}
@@ -1,204 +1,204 @@
/*
* 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/>.
*/
#include <stratosphere.hpp>
#include "../mtc_timing_value.hpp"
#include "timing_tables.hpp"
namespace ams::ldr::hoc::pcv::mariko {
void GetRext() {
if (auto r = FindRext()) {
rext = r->rext;
return;
}
/* > 3200 */
rext = 0x1E;
}
void SwitchLatency(volatile u32 &latency, u32 index, u32 latencyStep) {
latency += index * latencyStep;
}
static s32 GetMaxLatencyIndex(volatile u32 *latencyArray, u32 latencySize) {
s32 maxIndex = -1;
for (u32 i = 0; i < latencySize; ++i) {
if (latencyArray[i]) {
maxIndex = i;
}
}
return maxIndex;
}
void AutoLatency(volatile u32 &latency, u32 freq, u32 latencyStep) {
if (freq > 1600'000 && freq <= 1862'400) { /* 1866tRWL */
latency += latencyStep * 2;
} else { /* 2133tRWL */
latency += latencyStep * 3;
}
}
void HandleLatency(u32 freq, volatile u32 &latency, volatile u32 *latencyArray, u32 indexMax, u32 latencyStep) {
for (u32 i = 0; i <= indexMax; ++i) {
if (latencyArray[i] != 0 && freq <= latencyArray[i]) {
SwitchLatency(latency, i, latencyStep);
return;
}
}
SwitchLatency(latency, indexMax, latencyStep);
}
void HandleLatency(u32 freq) {
static s32 rlIndexMax = GetMaxLatencyIndex(C.readLatency, std::size(C.readLatency));
static s32 wlIndexMax = GetMaxLatencyIndex(C.writeLatency, std::size(C.writeLatency));
constexpr u32 ReadLatencyStep = 4;
constexpr u32 WriteLatencyStep = 2;
bool autoLatencyRead = false, autoLatencyWrite = false;
if (rlIndexMax == -1) {
AutoLatency(RL, freq, ReadLatencyStep);
autoLatencyRead = true;
}
if (wlIndexMax == -1) {
AutoLatency(WL, freq, WriteLatencyStep);
autoLatencyWrite = true;
}
if (autoLatencyRead && autoLatencyWrite) {
return;
}
if (!autoLatencyRead) {
HandleLatency(freq, RL, C.readLatency, rlIndexMax, ReadLatencyStep);
}
if (!autoLatencyWrite) {
HandleLatency(freq, WL, C.writeLatency, wlIndexMax, WriteLatencyStep);
}
}
void CalculateMrw2() {
static const u8 rlMapDBI[8] = {
6, 12, 16, 22, 28, 32, 36, 40
};
static const u8 wlMapSetA[8] = {
4, 6, 8, 10, 12, 14, 16, 18
};
u32 rlIndex = 0;
u32 wlIndex = 0;
for (u32 i = 0; i < std::size(rlMapDBI); ++i) {
if (rlMapDBI[i] == RL) {
rlIndex = i;
break;
}
}
for (u32 i = 0; i < std::size(wlMapSetA); ++i) {
if (wlMapSetA[i] == WL) {
wlIndex = i;
break;
}
}
/* DBI is always enabled. */
mrw2 = static_cast<u8>(((rlIndex & 0x7) | ((wlIndex & 0x7) << 3) | ((0 & 0x1) << 6)));
}
void CalculateTimings(double tCK_avg, u32 freq) {
RL = RL_1331;
WL = WL_1331;
HandleLatency(freq);
GetRext();
/* At 1333WL, for some reason (incorrect ram timing config in mtc table?), tRP causes crashes at high reductions - 2 seems to be the most common limit. */
/* This is a lazy workaround until I find the issue... */
const bool lowFreq = freq < C.timingEmcTbreak;
tRCD = tRCD_values[lowFreq ? C.low_t1_tRCD : C.t1_tRCD];
tRPpb = tRP_values[lowFreq ? C.low_t2_tRP : C.t2_tRP];
tRAS = tRAS_values[lowFreq ? C.low_t3_tRAS : C.t3_tRAS];
tRRD = tRRD_values[lowFreq ? C.low_t4_tRRD : C.t4_tRRD];
tRFCpb = tRFC_values[lowFreq ? C.low_t5_tRFC : C.t5_tRFC];
u32 tRTW = lowFreq ? C.low_t6_tRTW : C.t6_tRTW;
u32 tWTR = 10 - tWTR_values[lowFreq ? C.low_t7_tWTR : C.t7_tWTR];
s32 finetRTW = C.fineTune_t6_tRTW;
s32 finetWTR = C.fineTune_t7_tWTR;
u32 tREFI = lowFreq ? C.low_t8_tREFI : C.t8_tREFI;
refresh_raw = 0xFFFF;
if (tREFI != 6) {
refresh_raw = CEIL(tREFpb_values[tREFI] / tCK_avg) - 0x40;
refresh_raw = MIN(refresh_raw, static_cast<u32>(0xFFFF));
}
tRC = tRAS + tRPpb;
tRFCab = tRFCpb * 2;
tXSR = static_cast<double>(tRFCab + 7.5);
tFAW = static_cast<u32>(tRRD * 4.0);
tRPab = tRPpb + 3;
tR2P = CEIL((RL * 0.426) - 2.0);
tR2W = FLOOR(FLOOR((5.0 / tCK_avg) + ((FLOOR(48.0 / WL) - 0.478) * 3.0)) / 1.501) + RL - (tRTW * 3) + finetRTW;
tRTM = FLOOR((10.0 + RL) + (3.502 / tCK_avg)) + FLOOR(7.489 / tCK_avg);
tRATM = CEIL((tRTM - 10.0) + (RL * 0.426));
rdv = RL + FLOOR((5.105 / tCK_avg) + 17.017);
qpop = rdv - 14;
quse_width = CEIL(((4.897 / tCK_avg) - FLOOR(2.538 / tCK_avg)) + 3.782);
quse = FLOOR(RL + ((5.082 / tCK_avg) + FLOOR(2.560 / tCK_avg))) - CEIL(4.820 / tCK_avg);
einput_duration = FLOOR(9.936 / tCK_avg) + 5.0 + quse_width;
einput = quse - CEIL(9.928 / tCK_avg);
u32 qrst_duration = FLOOR(8.399 - tCK_avg);
u32 qrstLow = MAX(static_cast<s32>(einput - qrst_duration - 2), static_cast<s32>(0));
qrst = PACK_U32(qrst_duration, qrstLow);
ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0);
qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput);
tW2P = (CEIL(WL * 1.7303) * 2) - 5;
tWTPDEN = CEIL(((1.803 / tCK_avg) + MAX(RL + (2.694 / tCK_avg), static_cast<double>(tW2P))) + (BL / 2));
tW2R = FLOOR(MAX((5.020 / tCK_avg) + 1.130, WL - MAX(-CEIL(0.258 * (WL - RL)), 1.964)) * 1.964) + WL - CEIL(tWTR / tCK_avg) + finetWTR;
tWTM = CEIL(WL + ((7.570 / tCK_avg) + 8.753));
tWATM = (tWTM + (FLOOR(WL / 0.816) * 2.0)) - 4.0;
wdv = WL;
wsv = WL - 2;
wev = 0xA + (WL - 14);
u32 obdlyHigh = 3 / FLOOR(MIN(static_cast<double>(2), tCK_avg * (WL - 7)));
u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
u32 tMMRI = tRCD + (tCK_avg * 3);
pdex2mrr = tMMRI + 10;
CalculateMrw2();
}
}
/*
* 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/>.
*/
#include <stratosphere.hpp>
#include "../../mtc_timing_value.hpp"
#include "timing_tables.hpp"
namespace ams::ldr::hoc::pcv::mariko {
void GetRext() {
if (auto r = FindRext()) {
rext = r->rext;
return;
}
/* > 3200 */
rext = 0x1E;
}
void SwitchLatency(volatile u32 &latency, u32 index, u32 latencyStep) {
latency += index * latencyStep;
}
static s32 GetMaxLatencyIndex(volatile u32 *latencyArray, u32 latencySize) {
s32 maxIndex = -1;
for (u32 i = 0; i < latencySize; ++i) {
if (latencyArray[i]) {
maxIndex = i;
}
}
return maxIndex;
}
void AutoLatency(volatile u32 &latency, u32 freq, u32 latencyStep) {
if (freq > 1600'000 && freq <= 1862'400) { /* 1866tRWL */
latency += latencyStep * 2;
} else { /* 2133tRWL */
latency += latencyStep * 3;
}
}
void HandleLatency(u32 freq, volatile u32 &latency, volatile u32 *latencyArray, u32 indexMax, u32 latencyStep) {
for (u32 i = 0; i <= indexMax; ++i) {
if (latencyArray[i] != 0 && freq <= latencyArray[i]) {
SwitchLatency(latency, i, latencyStep);
return;
}
}
SwitchLatency(latency, indexMax, latencyStep);
}
void HandleLatency(u32 freq) {
static s32 rlIndexMax = GetMaxLatencyIndex(C.readLatency, std::size(C.readLatency));
static s32 wlIndexMax = GetMaxLatencyIndex(C.writeLatency, std::size(C.writeLatency));
constexpr u32 ReadLatencyStep = 4;
constexpr u32 WriteLatencyStep = 2;
bool autoLatencyRead = false, autoLatencyWrite = false;
if (rlIndexMax == -1) {
AutoLatency(RL, freq, ReadLatencyStep);
autoLatencyRead = true;
}
if (wlIndexMax == -1) {
AutoLatency(WL, freq, WriteLatencyStep);
autoLatencyWrite = true;
}
if (autoLatencyRead && autoLatencyWrite) {
return;
}
if (!autoLatencyRead) {
HandleLatency(freq, RL, C.readLatency, rlIndexMax, ReadLatencyStep);
}
if (!autoLatencyWrite) {
HandleLatency(freq, WL, C.writeLatency, wlIndexMax, WriteLatencyStep);
}
}
void CalculateMrw2() {
static const u8 rlMapDBI[8] = {
6, 12, 16, 22, 28, 32, 36, 40
};
static const u8 wlMapSetA[8] = {
4, 6, 8, 10, 12, 14, 16, 18
};
u32 rlIndex = 0;
u32 wlIndex = 0;
for (u32 i = 0; i < std::size(rlMapDBI); ++i) {
if (rlMapDBI[i] == RL) {
rlIndex = i;
break;
}
}
for (u32 i = 0; i < std::size(wlMapSetA); ++i) {
if (wlMapSetA[i] == WL) {
wlIndex = i;
break;
}
}
/* DBI is always enabled. */
mrw2 = static_cast<u8>(((rlIndex & 0x7) | ((wlIndex & 0x7) << 3) | ((0 & 0x1) << 6)));
}
void CalculateTimings(double tCK_avg, u32 freq) {
RL = RL_1331;
WL = WL_1331;
HandleLatency(freq);
GetRext();
/* At 1333WL, for some reason (incorrect ram timing config in mtc table?), tRP causes crashes at high reductions - 2 seems to be the most common limit. */
/* This is a lazy workaround until I find the issue... */
const bool lowFreq = freq < C.timingEmcTbreak;
tRCD = tRCD_values[lowFreq ? C.low_t1_tRCD : C.t1_tRCD];
tRPpb = tRP_values[lowFreq ? C.low_t2_tRP : C.t2_tRP];
tRAS = tRAS_values[lowFreq ? C.low_t3_tRAS : C.t3_tRAS];
tRRD = tRRD_values[lowFreq ? C.low_t4_tRRD : C.t4_tRRD];
tRFCpb = tRFC_values[lowFreq ? C.low_t5_tRFC : C.t5_tRFC];
u32 tRTW = lowFreq ? C.low_t6_tRTW : C.t6_tRTW;
u32 tWTR = 10 - tWTR_values[lowFreq ? C.low_t7_tWTR : C.t7_tWTR];
s32 finetRTW = C.fineTune_t6_tRTW;
s32 finetWTR = C.fineTune_t7_tWTR;
u32 tREFI = lowFreq ? C.low_t8_tREFI : C.t8_tREFI;
refresh_raw = 0xFFFF;
if (tREFI != 6) {
refresh_raw = CEIL(tREFpb_values[tREFI] / tCK_avg) - 0x40;
refresh_raw = MIN(refresh_raw, static_cast<u32>(0xFFFF));
}
tRC = tRAS + tRPpb;
tRFCab = tRFCpb * 2;
tXSR = static_cast<double>(tRFCab + 7.5);
tFAW = static_cast<u32>(tRRD * 4.0);
tRPab = tRPpb + 3;
tR2P = CEIL((RL * 0.426) - 2.0);
tR2W = FLOOR(FLOOR((5.0 / tCK_avg) + ((FLOOR(48.0 / WL) - 0.478) * 3.0)) / 1.501) + RL - (tRTW * 3) + finetRTW;
tRTM = FLOOR((10.0 + RL) + (3.502 / tCK_avg)) + FLOOR(7.489 / tCK_avg);
tRATM = CEIL((tRTM - 10.0) + (RL * 0.426));
rdv = RL + FLOOR((5.105 / tCK_avg) + 17.017);
qpop = rdv - 14;
quse_width = CEIL(((4.897 / tCK_avg) - FLOOR(2.538 / tCK_avg)) + 3.782);
quse = FLOOR(RL + ((5.082 / tCK_avg) + FLOOR(2.560 / tCK_avg))) - CEIL(4.820 / tCK_avg);
einput_duration = FLOOR(9.936 / tCK_avg) + 5.0 + quse_width;
einput = quse - CEIL(9.928 / tCK_avg);
u32 qrst_duration = FLOOR(8.399 - tCK_avg);
u32 qrstLow = MAX(static_cast<s32>(einput - qrst_duration - 2), static_cast<s32>(0));
qrst = PACK_U32(qrst_duration, qrstLow);
ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0);
qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput);
tW2P = (CEIL(WL * 1.7303) * 2) - 5;
tWTPDEN = CEIL(((1.803 / tCK_avg) + MAX(RL + (2.694 / tCK_avg), static_cast<double>(tW2P))) + (BL / 2));
tW2R = FLOOR(MAX((5.020 / tCK_avg) + 1.130, WL - MAX(-CEIL(0.258 * (WL - RL)), 1.964)) * 1.964) + WL - CEIL(tWTR / tCK_avg) + finetWTR;
tWTM = CEIL(WL + ((7.570 / tCK_avg) + 8.753));
tWATM = (tWTM + (FLOOR(WL / 0.816) * 2.0)) - 4.0;
wdv = WL;
wsv = WL - 2;
wev = 0xA + (WL - 14);
u32 obdlyHigh = 3 / FLOOR(MIN(static_cast<double>(2), tCK_avg * (WL - 7)));
u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
u32 tMMRI = tRCD + (tCK_avg * 3);
pdex2mrr = tMMRI + 10;
CalculateMrw2();
}
}
@@ -1,24 +1,24 @@
/*
* 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
namespace ams::ldr::hoc::pcv::mariko {
void CalculateTimings(double tCK_avg, u32 freq);
}
/*
* 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
namespace ams::ldr::hoc::pcv::mariko {
void CalculateTimings(double tCK_avg, u32 freq);
}
@@ -0,0 +1,588 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* Copyright (c) Souldbminer 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 <vector>
#include "../pcv.hpp"
#include "../../mtc_timing_value.hpp"
#include "pcv_mariko.hpp"
#include "pcv_mariko_cpu.hpp"
#include "pcv_mariko_gpu.hpp"
#include "pcv_mariko_mtc.hpp"
#include "calculate_timings_mariko.hpp"
namespace ams::ldr::hoc::pcv::mariko {
u32 *nsoStart;
namespace {
size_t g_nso_size = 0;
uintptr_t g_cave_cursor = 0;
}
static uintptr_t CaveReserve(size_t count) {
if (g_pcv_cave == 0 || g_cave_cursor == 0) {
return 0;
}
if (g_cave_cursor + count * sizeof(u32) > g_pcv_cave + g_pcv_cave_size) {
return 0;
}
const uintptr_t entry = g_cave_cursor;
g_cave_cursor += count * sizeof(u32);
return entry;
}
#if HOC_UART_LOG
/* Redirect pcv's NvLog() calls to UART */
Result NvLogUartRedirect(u32 *ptr) {
const uintptr_t mapped_nso = reinterpret_cast<uintptr_t>(nsoStart);
const size_t nso_size = g_nso_size;
const uintptr_t textEnd = g_pcv_cave; /* .text ends where the cave begins */
const uintptr_t vsnprintf_addr = reinterpret_cast<uintptr_t>(ptr);
/* NvLog via the VDD_SOC log */
static const char Fmt[] = "%s(%s): DVFS request VDD_SOC %d mV\n";
constexpr size_t FmtLen = sizeof(Fmt) - 1;
uintptr_t strAddr = 0;
{
const char *hay = reinterpret_cast<const char *>(mapped_nso);
for (size_t i = 0; i + FmtLen <= nso_size; ++i) {
if (std::memcmp(hay + i, Fmt, FmtLen) == 0) { strAddr = mapped_nso + i; break; }
}
}
if (strAddr == 0) {
LOGGING("NvLogRedirect: fmt string not found (vsnprintf@+%lx)", vsnprintf_addr - mapped_nso);
R_THROW(ldr::ResultInvalidNvLogRedirect());
}
uintptr_t nvlog_addr = 0;
for (u32 *p = nsoStart; reinterpret_cast<uintptr_t>(p + 2) <= textEnd; ++p) {
const uintptr_t pc = reinterpret_cast<uintptr_t>(p);
if (!AsmIsAdrp(p[0])) {
continue;
}
const uintptr_t adrpPage = (pc & ~static_cast<uintptr_t>(0xFFFu)) + static_cast<uintptr_t>(AsmAdrpPageOffset(p[0]));
if (adrpPage != (strAddr & ~static_cast<uintptr_t>(0xFFFu))) {
continue;
}
const u32 reg = asm_get_rd(p[0]);
if (!(AsmIsAddImm64(p[1]) && asm_get_rd(p[1]) == reg && AsmGetRn(p[1]) == reg && AsmGetImm12(p[1]) == (strAddr & 0xFFFu))) {
continue;
}
for (u32 k = 2; k <= 12 && (pc + (k + 1) * 4) <= textEnd; ++k) {
if (AsmIsBl(p[k])) { nvlog_addr = AsmBranchTarget(p[k], pc + k * 4); break; }
}
if (nvlog_addr != 0) {
break;
}
}
if (nvlog_addr == 0 || nvlog_addr < mapped_nso || nvlog_addr >= textEnd) {
LOGGING("NvLogRedirect: NvLog entry not found (fmt@+%lx)", strAddr - mapped_nso);
R_THROW(ldr::ResultInvalidNvLogRedirect());
}
const uintptr_t helper = CaveReserve(40);
if (helper == 0) {
LOGGING("NvLogRedirect: cave unavailable (cave=%lx size=%lx)",
static_cast<unsigned long>(g_pcv_cave), static_cast<unsigned long>(g_pcv_cave_size));
R_THROW(ldr::ResultInvalidNvLogRedirect());
}
u32 *t = reinterpret_cast<u32 *>(helper);
size_t n = 0;
auto emit = [&](u32 ins) { t[n] = ins; ++n; };
emit(AsmMakeSubImm64(31, 31, 0x200));
emit(AsmMakeStpImm64(0, 1, 31, 0x100));
emit(AsmMakeStpImm64(2, 3, 31, 0x110));
emit(AsmMakeStpImm64(4, 5, 31, 0x120));
emit(AsmMakeStpImm64(6, 7, 31, 0x130));
emit(AsmMakeStpqImm(0, 1, 31, 0x140));
emit(AsmMakeStpqImm(2, 3, 31, 0x160));
emit(AsmMakeStpqImm(4, 5, 31, 0x180));
emit(AsmMakeStpqImm(6, 7, 31, 0x1A0));
emit(AsmMakeStrImm64(30, 31, 0x1E0));
emit(AsmMakeAddImm64(9, 31, 0x200)); emit(AsmMakeStrImm64(9, 31, 0x1C0)); /* __stack */
emit(AsmMakeAddImm64(9, 31, 0x140)); emit(AsmMakeStrImm64(9, 31, 0x1C8)); /* __gr_top */
emit(AsmMakeAddImm64(9, 31, 0x1C0)); emit(AsmMakeStrImm64(9, 31, 0x1D0)); /* __vr_top */
emit(AsmMakeMovnW(9, 0x37)); emit(AsmMakeStrImm32(9, 31, 0x1D8)); /* __gr_offs = -56 */
emit(AsmMakeMovnW(9, 0x7F)); emit(AsmMakeStrImm32(9, 31, 0x1DC)); /* __vr_offs = -128 */
emit(AsmMakeAddImm64(0, 31, 0x00)); /* mov x0,sp (buf) */
emit(AsmMakeMovzW(1, 0x100)); /* size = 0x100 */
emit(AsmMakeLdrImm64(2, 31, 0x100)); /* fmt (saved x0) */
emit(AsmMakeAddImm64(3, 31, 0x1C0)); /* ap */
emit(AsmMakeBl(helper + n * 4, vsnprintf_addr));
emit(AsmMakeMovReg(1, 0)); /* len = retval */
emit(AsmMakeCmpImm32(1, 0x100));
{ const size_t at = n; emit(AsmMakeBCond(helper + at * 4, helper + (at + 2) * 4, 0x3u)); } /* b.lo +2 */
emit(AsmMakeMovzW(1, 0xFF)); /* clamp len */
emit(AsmMakeAddImm64(0, 31, 0x00)); /* mov x0,sp (str) */
emit(AsmMakeSvc(0x27)); /* svcOutputDebugString */
emit(AsmMakeLdrImm64(30, 31, 0x1E0));
emit(AsmMakeAddImm64(31, 31, 0x200));
emit(RetIns);
/* Redirect the call sites as patching the actual function causes crash */
const uintptr_t roStart = g_pcv_cave + g_pcv_cave_size; /* module .rodata start */
size_t patchedSites = 0;
if (HOC_PCV_NVLOG_PATCH) {
for (u32 *p = nsoStart; reinterpret_cast<uintptr_t>(p + 1) <= textEnd; ++p) {
if (!AsmIsBl(*p)) {
continue;
}
const uintptr_t pc = reinterpret_cast<uintptr_t>(p);
if (AsmBranchTarget(*p, pc) != nvlog_addr) {
continue;
}
bool isFmtCall = false;
for (u32 j = 1; j <= 8 && reinterpret_cast<uintptr_t>(p - j) >= reinterpret_cast<uintptr_t>(nsoStart); ++j) {
const u32 w = *(p - j);
if (AsmIsAdrp(w) && asm_get_rd(w) == 0) { /* adrp x0,<page> */
const uintptr_t wpc = pc - j * 4;
const uintptr_t tgtPage = (wpc & ~static_cast<uintptr_t>(0xFFFu)) + static_cast<uintptr_t>(AsmAdrpPageOffset(w));
if (tgtPage >= (roStart & ~static_cast<uintptr_t>(0xFFFu))) { isFmtCall = true; break; }
}
}
if (isFmtCall) {
PATCH_OFFSET(p, AsmMakeBl(pc, helper));
++patchedSites;
}
}
}
LOGGING("NvLogRedirect: stub@+%lx vsnprintf@+%lx helper@+%lx instr=%zu sites=%zu",
nvlog_addr - mapped_nso, vsnprintf_addr - mapped_nso, helper - mapped_nso, n, patchedSites);
R_SUCCEED();
}
#endif
/* Relocate C2/C3Bus to avoid issues*/
Result BusFreqReloc(u32 *ptr) {
const u32 busReg = AsmGetRn(ptr[0]); /* ldr Xbuf,[Xbus,#0x10] : bus struct pointer */
const u32 bufReg = asm_get_rd(ptr[0]); /* : freq-buffer arg */
const u32 bufOff = AsmGetLdStImm64Off(ptr[0]); /* : bus->freqBuf offset */
const u32 cntReg = asm_get_rd(ptr[1]); /* add Xcnt,Xbus,#0x18 : arg2 (&count) */
const u32 railReg = asm_get_rd(ptr[2]); /* str Xrail,[Xbus,#0x50]: arg0 (rail) */
u32 *call = ptr + 3; /* the bl to relocate */
const uintptr_t realFn = AsmBranchTarget(*call, reinterpret_cast<uintptr_t>(call));
/* Pick 3 scratch registers */
u32 s[3], sc = 0;
for (u32 r = 9; r <= 15 && sc < 3; ++r) {
if (r != busReg && r != bufReg && r != cntReg && r != railReg) {
s[sc++] = r;
}
}
R_UNLESS(sc == 3, ldr::ResultInvalidBusFreqReloc());
const uintptr_t tramp = CaveReserve(9);
R_UNLESS(tramp != 0, ldr::ResultInvalidBusFreqReloc());
const uintptr_t region = g_pcv_scratch + HocBusFreqBufOffset; /* [0]=counter, +0x10 + i*0x400 = bufs */
u32 *t = reinterpret_cast<u32 *>(tramp);
size_t n = 0;
auto emit = [&](u32 ins) { t[n] = ins; ++n; };
emit(AsmMakeAdrp(tramp + n * 4, region, s[0])); /* adrp s0,<region> */
emit(AsmMakeAddImm64(s[0], s[0], region & 0xFFFu)); /* add s0,s0,#lo */
emit(AsmMakeLdrImm32(s[1], s[0], 0x00)); /* s1 = counter */
emit(AsmMakeAddImm64(s[2], s[1], 1)); /* s2 = counter+1 */
emit(AsmMakeStrImm32(s[2], s[0], 0x00)); /* counter++ */
emit(AsmMakeAddImm64(s[0], s[0], 0x10)); /* s0 = region+0x10 (buffers) */
emit(AsmMakeAddShiftedReg64(bufReg, s[0], s[1], 10)); /* Xbuf = s0 + counter*0x400 */
emit(AsmMakeStrImm64(bufReg, busReg, bufOff)); /* bus[freqBuf] = Xbuf */
emit(AsmMakeB(tramp + n * 4, realFn)); /* tail-call the real function */
PATCH_OFFSET(call, AsmMakeBl(reinterpret_cast<uintptr_t>(call), tramp));
const uintptr_t base = reinterpret_cast<uintptr_t>(nsoStart);
(void) base;
LOGGING("BusFreqReloc: call@+%lx -> tramp@+%lx realfn@+%lx (bus=x%u buf=x%u off=0x%x scratch=x%u,x%u,x%u)",
reinterpret_cast<uintptr_t>(call) - base, tramp - base, realFn - base, busReg, bufReg, bufOff, s[0], s[1], s[2]);
R_SUCCEED();
}
#if HOC_UART_LOG
/* Force GetEffectiveVerbosityLevel to return a non-zero level so all NvLog runs. */
Result ForceVerbosity(u32 *ptr) {
PATCH_OFFSET(&ptr[0], AsmMakeMovzW(0, static_cast<u16>(HOC_PCV_FORCE_VERBOSITY))); /* movz w0,#level */
PATCH_OFFSET(&ptr[1], RetIns); /* ret */
R_SUCCEED();
}
#endif
/* Widen InitDram for a >32-entry EMC DVFS list. Freq array can be dropped to free 264 bytes, relocate the Soc LUT to that space */
Result EmcSocLutReloc(u32 *ptr) {
constexpr u32 Window = 48;
u32 *freqStore = ScanAssembly(ptr - Window, Window, EmcSocFreqStoreAsm, asm_compare_no_rd); /* str x?,[x8,#0x18] */
u32 *voltStore = ScanAssembly(ptr - Window, Window, EmcSocVoltStoreAsm, asm_compare_no_rd); /* str w?,[x8,#0x48] */
u32 *readLoad = ScanAssembly(ptr - Window, Window, EmcSocReadLoadAsm, asm_compare_no_rd); /* ldr w?,[x9,#0x48] */
R_UNLESS(freqStore && voltStore && readLoad, ldr::ResultInvalidEmcSocLut());
u32 *voltBase = voltStore - 2; /* `add Xb,Xsrc,Xi,LSL#2` (a cmn sits between it and store) */
R_UNLESS(AsmIsAddShiftedReg64(*voltBase) && asm_get_rd(*voltBase) == AsmGetRn(*voltStore),
ldr::ResultInvalidEmcSocLut());
/* adrp Xl ; add Xl,Xl,#off ; ... ; str Xl,[rail,#0x120] */
const u32 lutReg = asm_get_rd(ptr[0]);
const u32 railReg = AsmGetRn(ptr[0]);
R_UNLESS(AsmIsAdrp(ptr[-3]) && asm_get_rd(ptr[-3]) == lutReg, ldr::ResultInvalidEmcSocLut());
R_UNLESS(AsmIsAddImm64(ptr[-2]) && asm_get_rd(ptr[-2]) == lutReg && AsmGetRn(ptr[-2]) == lutReg,
ldr::ResultInvalidEmcSocLut());
const u32 srcBase = AsmGetRn(*voltBase); /* rail ptr at +0x20 */
const u32 wBase = asm_get_rd(*voltBase); /* base reg */
const u32 wIdx = AsmGetRm(*voltBase); /* loop index */
PATCH_OFFSET(freqStore, NopIns); /* Unneeded */
PATCH_OFFSET(voltBase, AsmMakeLdrImm64(wBase, srcBase, 0x20)); /* ldr Xb,[Xsrc,#0x20] (rail) */
PATCH_OFFSET(voltStore - 1, AsmMakeAddImm64(wBase, wBase, 0x18)); /* add Xb,Xb,#0x18 (was cmn) */
PATCH_OFFSET(voltStore, AsmSetLdStRegOffset(*voltStore, wIdx)); /* str Wv,[Xb,Xi,LSL#2] -> rail+0x18+i*4 */
PATCH_OFFSET(voltStore + 1, NopIns); /* Unneeded */
/* rail+0x18 as the socMinLut pointer. */
PATCH_OFFSET(ptr - 3, NopIns);
PATCH_OFFSET(ptr - 2, AsmMakeAddImm64(lutReg, railReg, 0x18));/* add Xl,rail,#0x18 */
/* Drop the abort branch in case of a bad read */
for (u32 i = 1; i <= 4; ++i) {
if (AsmIsBCond(readLoad[i])) {
PATCH_OFFSET(&readLoad[i], NopIns);
break;
}
}
R_SUCCEED();
}
Result EmcDvfsCountLimit(u32 *ptr) {
R_UNLESS(EmcDvfsCountPatternFn(ptr), ldr::ResultInvalidEmcDvfsCount());
/* cmp w?,#0x21 -> cmp w?,#EmcDvfsTableEntryCount */
PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast<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();
}
Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) {
struct {
u8 reg;
u8 val;
} __attribute__((packed)) cmd;
I2cSession _session;
R_TRY(i2cOpenSession(&_session, dev));
cmd.reg = reg;
cmd.val = val;
Result res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All);
i2csessionClose(&_session);
return res;
}
Result EmcVddqVolt(u32 *ptr) {
regulator *entry = reinterpret_cast<regulator *>(reinterpret_cast<u8 *>(ptr) - offsetof(regulator, type_2_3.default_uv));
constexpr u32 uv_step = 5'000;
constexpr u32 uv_min = 250'000;
auto validator = [entry]() {
R_UNLESS(entry->id == 2, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type == 3, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_2_3.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_2_3.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry());
R_SUCCEED();
};
R_TRY(validator());
u32 emc_uv = C.marikoEmcVddqVolt;
if (!emc_uv) {
R_SKIP();
}
if (emc_uv % uv_step) {
emc_uv = (emc_uv + uv_step - 1) / uv_step * uv_step; // rounding
}
PATCH_OFFSET(ptr, emc_uv);
i2cInitialize();
Result resultI2C = I2cSet_U8(I2cDevice_Max77812_2, 0x25, (emc_uv - uv_min) / uv_step);
i2cExit();
R_SUCCEED();
return resultI2C;
}
Result GetSocSpeedo(u32 &socSpeedo) {
constexpr u64 FusePhysicalAddress = 0x7000F000;
u64 virtualAddress = 0;
constexpr u64 Size = 0x1000;
u64 outSize;
/* TODO: use svc::QueryMemoryMapping instead. */
R_TRY(svcQueryMemoryMapping(&virtualAddress, &outSize, FusePhysicalAddress, Size));
constexpr u32 FuseOffset = 2048;
constexpr u32 SocSpeedoOffset = 308;
socSpeedo = *reinterpret_cast<u32 *>(virtualAddress + FuseOffset + SocSpeedoOffset);
R_SUCCEED();
}
u32 GetSocProcessId(u32 socSpeedo) {
if (socSpeedo <= 1597) {
return 0;
}
if (socSpeedo <= 1708) {
return 1;
}
/* >= 1709. */
return 2;
}
Result SocVoltAsm(u32 *compareSpeedos) {
constexpr u32 VoltageScanLimit = 10;
/* Might actually be speedo id. */
u32 *writeProcessId = ScanAssembly(compareSpeedos, VoltageScanLimit, SocVoltWriteProcessIdAsm, asm_compare_no_rd);
R_UNLESS(writeProcessId != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 writeProcessIdRd = asm_get_rd(*writeProcessId);
/* This writes 1050mV. */
u32 *writeVoltage = ScanAssembly(writeProcessId, VoltageScanLimit, SocVoltWriteVoltageAsm, asm_compare_no_rd);
R_UNLESS(writeVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 writeVoltageRd = asm_get_rd(*writeVoltage);
/* A csel instruction is used to select the soc voltage limit register. */
/* We care about its destination register since that is used for verification. */
constexpr u32 VoltageSelectScanLimit = 24;
u32 *selectVoltage = ScanAssembly(writeVoltage, VoltageSelectScanLimit, SocVoltSelectRegisterAsm, AsmCompareCselNoReg);
R_UNLESS(selectVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
/* Todo: check rm and rn? */
u8 selectVoltageRd = asm_get_rd(*selectVoltage);
/* rdCsel is then multiplied by 1000 to convert to uV. */
/* This is pretty far down the function. */
constexpr u32 MultiplierScanLimit = 200;
u32 *multiplier = ScanAssembly(selectVoltage, MultiplierScanLimit, SocVoltMultiplyVoltsAsm, AsmCompareMullNoReg);
R_UNLESS(multiplier != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 multiplierRn = AsmGetMullRn(*multiplier);
u8 multiplierRm = AsmGetMullRm(*multiplier);
/* One of the two registers has to be rdCsel. */
R_UNLESS((multiplierRn == selectVoltageRd) || (multiplierRm == selectVoltageRd), ldr::ResultInvalidSocVoltPattern());
u8 multiplierRd = asm_get_rd(*multiplier);
/* Subs instruction is then used to verify against absolute limit. */
u32 limitValidationPattern = AsmSubsSetRn(SocVoltValidateLimitAsm, multiplierRd);
u32 *limitValidation = ScanAssembly(multiplier, VoltageScanLimit, limitValidationPattern, AsmSubsCompareNoReg);
R_UNLESS(limitValidation != nullptr, ldr::ResultInvalidSocVoltPattern());
/* There is a b.gt instruction right after (checks for socVoltageCap < socVoltageMax). */
u32 *branchToAbort = limitValidation + 1;
R_UNLESS(AsmCompareBrConNoImm19(*branchToAbort, SocVoltBranchToAbortAsm), ldr::ResultInvalidSocVoltPattern());
if (!C.marikoSocVmax || C.marikoSocVmax <= 1000) {
R_SKIP();
}
/* Adjust 1598 speedo minimum to ensure it always goes down process id 0 branch. */
/* 2200 should be high enough :D */
u32 compareSpeedosPatch = AsmSubsSetImm12(*compareSpeedos, 2200);
PATCH_OFFSET(compareSpeedos, compareSpeedosPatch);
u32 socSpeedo = 0;
R_TRY(GetSocSpeedo(socSpeedo));
/* Adjust processId from 0 to [process id of switch booting this]. */
/* We're overwriting the orr instruction entirly. */
u32 processId = GetSocProcessId(socSpeedo);
u32 writeProcessIdPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, processId), writeProcessIdRd);
PATCH_OFFSET(writeProcessId, writeProcessIdPatch);
/* Adjust voltage limit. */
u32 voltageLimitPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, C.marikoSocVmax), writeVoltageRd);
PATCH_OFFSET(writeVoltage, voltageLimitPatch);
/* Branches to an abort if limits are invalid -- we patch the branch instruction with NOP. */
PATCH_OFFSET(branchToAbort, NopIns);
R_SUCCEED();
}
Result SocVoltLimit(u32 *ptr) {
R_UNLESS(!std::memcmp(ptr - SocVoltLimitMaxDefaultIndex, socVoltLimitArray, sizeof(socVoltLimitArray)), ldr::ResultInvalidSocVoltLimit());
if (!C.marikoSocVmax || C.marikoSocVmax <= SocVoltLimitOfficial) {
R_SKIP();
}
constexpr u32 Step = 25;
u32 maxVolt = C.marikoSocVmax;
if (maxVolt % Step) {
maxVolt = maxVolt / Step * Step; /* Round. */
}
u32 volt = SocVoltLimitOfficial;
for (u32 i = 1; i < DvfsTableEntryCount - SocVoltLimitMaxDefaultIndex && volt < maxVolt; ++i) {
volt += Step;
PATCH_OFFSET(ptr + i, volt);
}
R_SUCCEED();
}
Result EmcRateSessLimit(u32 *ptr) {
u32 movzI = 0;
R_UNLESS(EmcRateSessFindClamp(ptr, nullptr, nullptr, &movzI), ldr::ResultInvalidEmcRateList());
/* Reject cmd11 GetDvfsTable. */
for (u32 i = 1; i <= 24; ++i) {
const u32 w = ptr[i];
if (AsmIsSubX29Imm(w) && AsmGetImm12(w) >= 0x20u) { /* sub x?,x29,#>=0x20 */
R_THROW(ldr::ResultInvalidEmcRateList());
}
}
/* mov x<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;
g_nso_size = nso_size;
g_cave_cursor = g_pcv_cave; /* start the .text-cave bump allocator (0 if unavailable) */
MtcGenerateFreqTables();
u32 CpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
u32 GpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
PatcherEntry<u32> patches[] = {
{ "CPU Freq Vdd", &CpuFreqVdd, 1, nullptr, CpuClkOSLimit },
{ "CPU Freq Table", CpuFreqCvbTable<true>, 1, nullptr, CpuCvbDefaultMaxFreq },
{ "CPU Volt DVFS", &CpuVoltDVFS, 1, nullptr, CpuVminOfficial },
{ "CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial },
{ "CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low },
{ "GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial },
{ "GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial },
{ "GPU Freq Table", GpuFreqCvbTable<true>, 1, nullptr, GpuCvbDefaultMaxFreq },
{ "GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn },
{ "GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax },
{ "GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit },
{ "MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit },
{ "MEM Freq Dvb", &MemFreqDvbTable, 1, nullptr, EmcClkOSLimit },
{ "MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit },
{ "MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit },
{ "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 },
{ "Bus Freq Reloc", &BusFreqReloc, 1, &BusFreqRelocPatternFn },
{ "SOC Volt Asm", &SocVoltAsm, 1, &SocVoltPatternFn },
{ "SOC Volt Limit", &SocVoltLimit, 1, nullptr, SocVoltLimitOfficial },
/* Debugging patches */
#if HOC_UART_LOG
{ "NvLog Redirect", &NvLogUartRedirect, 1, &NvLogVsnprintfPatternFn, 0, 0, true },
{ "Force Verbosity", &ForceVerbosity, 3, &ForceVerbosityPatternFn, 0, 0, true },
#endif
};
for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(MarikoMtcTable); ptr += sizeof(u32)) {
u32 *ptr32 = reinterpret_cast<u32 *>(ptr);
for (auto &entry : patches) {
if (R_SUCCEEDED(entry.SearchAndApply(ptr32))) {
break;
}
}
}
for (auto &entry : patches) {
LOGGING("%s Count: %zu", entry.description, entry.patched_count);
if (R_FAILED(entry.CheckResult())) {
panic::SmcError(panic::Patch);
CRASH(entry.description);
}
}
}
}
@@ -20,12 +20,14 @@
#pragma once
#include "../oc_common.hpp"
#include "pcv_common.hpp"
#include "pcv_asm.hpp"
#include "../../oc_common.hpp"
#include "../pcv_common.hpp"
#include "../pcv_asm.hpp"
namespace ams::ldr::hoc::pcv::mariko {
extern u32 *nsoStart;
constexpr cvb_entry_t CpuCvbTableDefault[] = {
{ 204000, { 721589, -12695, 27 }, { } },
{ 306000, { 747134, -14195, 27 }, { } },
@@ -60,7 +62,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, 2'805'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, };
constexpr cvb_entry_t GpuCvbTableDefault[] = {
// GPUB01_NA_CVB_TABLE
@@ -299,6 +301,131 @@ 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);
}
inline bool BusFreqRelocPatternFn(u32 *ptr) {
if (g_pcv_scratch == 0 || g_pcv_cave == 0) {
return false;
}
if (reinterpret_cast<uintptr_t>(ptr + 4) > g_pcv_cave) { /* the call site lives in .text */
return false;
}
if (!(AsmIsLdrImm64(ptr[0]) && AsmGetLdStImm64Off(ptr[0]) == 0x10)) return false; /* ldr Xbuf,[Xbus,#0x10] */
if (!(AsmIsAddImm64(ptr[1]) && AsmGetImm12(ptr[1]) == 0x18)) return false; /* add Xcnt,Xbus,#0x18 */
if (!(AsmIsStrImm64(ptr[2]) && AsmGetLdStImm64Off(ptr[2]) == 0x50)) return false; /* str Xrail,[Xbus,#0x50]*/
if (!AsmIsBl(ptr[3])) return false; /* bl GetDvfsRailUnique */
const u32 bus = AsmGetRn(ptr[0]);
return AsmGetRn(ptr[1]) == bus && AsmGetRn(ptr[2]) == bus;
}
inline bool ForceVerbosityPatternFn(u32 *ptr) {
if (HOC_PCV_FORCE_VERBOSITY == 0 || g_pcv_cave == 0) {
return false;
}
if (reinterpret_cast<uintptr_t>(ptr + 11) > g_pcv_cave) { /* .text only */
return false;
}
if (ptr[0] != 0xA9BE7BFDu || ptr[1] != 0xF9000BF3u || ptr[2] != 0x910003FDu) return false; /* stp/str/mov x29,sp */
if (!(AsmIsAddImm64(ptr[3]) && asm_get_rd(ptr[3]) == 0 && AsmGetRn(ptr[3]) == 29)) return false; /* add x0,x29,#imm */
if (!(AsmIsAddImm64(ptr[4]) && asm_get_rd(ptr[4]) == 19 && AsmGetRn(ptr[4]) == 29)) return false; /* add x19,x29,#imm */
if (AsmGetImm12(ptr[3]) != AsmGetImm12(ptr[4]) || !AsmIsBl(ptr[5])) return false;
for (u32 j = 6; j <= 10; ++j) {
if (ptr[j] == 0x7100001Fu) { /* cmp w0,#0 */
return true;
}
}
return false;
}
/* vsnprintf(buf,size,fmt,va_list) prologue */
inline constexpr u32 NvLogVsnSig[] = { 0xD10483FFu, 0xA9107BFDu, 0xF9008BFCu, 0x910403FDu, 0xF100003Fu };
inline bool NvLogVsnprintfPatternFn(u32 *ptr) {
if (HOC_UART_LOG == 0 || g_pcv_cave == 0) {
return false;
}
if (reinterpret_cast<uintptr_t>(ptr + std::size(NvLogVsnSig)) > g_pcv_cave) { /* must sit in .text */
return false;
}
for (size_t k = 0; k < std::size(NvLogVsnSig); ++k) {
if (ptr[k] != NvLogVsnSig[k]) {
return false;
}
}
return true;
}
void Patch(uintptr_t mapped_nso, size_t nso_size);
}
@@ -0,0 +1,237 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* Copyright (c) Souldbminer 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 "../pcv.hpp"
namespace ams::ldr::hoc::pcv::mariko {
u32 CapCpuClock() {
u32 cpuCap = allowedCpuMaxFrequencies[0];
for (u32 freq : allowedCpuMaxFrequencies) {
if (C.marikoCpuMaxClock >= freq) {
cpuCap = freq;
} else {
break;
}
}
return cpuCap;
}
Result CpuFreqVdd(u32 *ptr) {
dvfs_rail *entry = reinterpret_cast<dvfs_rail *>(reinterpret_cast<u8 *>(ptr) - offsetof(dvfs_rail, freq));
R_UNLESS(entry->id == 1, ldr::ResultInvalidCpuFreqVddEntry());
R_UNLESS(entry->min_mv == 250'000, ldr::ResultInvalidCpuFreqVddEntry());
R_UNLESS(entry->step_mv == 5000, ldr::ResultInvalidCpuFreqVddEntry());
R_UNLESS(entry->max_mv == 1525'000, ldr::ResultInvalidCpuFreqVddEntry());
if (C.marikoCpuUVHigh) {
PATCH_OFFSET(ptr, CapCpuClock());
} else {
PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq);
}
R_SUCCEED();
}
Result CpuVoltDVFS(u32 *ptr) {
CvbMeta *cpuCvbMeta = reinterpret_cast<CvbMeta *>(reinterpret_cast<u8 *>(ptr) - offsetof(CvbMeta, vmin));
R_UNLESS(cpuCvbMeta->highVmin == CpuHighVminOfficial, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->unkStepMaybe == 38, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->vmax == CpuVoltOfficial, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->unkScale2 == 1000, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->speedoScale == 100, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->voltageScale == 1000, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->unkZero5 == 0, ldr::ResultInvalidCpuMinVolt());
if (C.marikoCpuLowVmin) {
PATCH_OFFSET(&(cpuCvbMeta->vmin), C.marikoCpuLowVmin);
}
if (C.marikoCpuHighVmin) {
PATCH_OFFSET(&(cpuCvbMeta->highVmin), C.marikoCpuHighVmin);
}
if (C.marikoCpuMaxVolt) {
PATCH_OFFSET(&(cpuCvbMeta->vmax), C.marikoCpuMaxVolt);
}
R_SUCCEED();
}
Result CpuVoltThermals(u32 *ptr) {
if (std::memcmp(ptr, cpuVoltThermalData, sizeof(cpuVoltThermalData))) {
R_THROW(ldr::ResultInvalidCpuMinVolt());
}
if (C.marikoCpuLowVmin) {
PATCH_OFFSET(ptr, C.marikoCpuLowVmin);
PATCH_OFFSET(ptr + 3, C.marikoCpuLowVmin);
}
if (C.marikoCpuMaxVolt) {
PATCH_OFFSET(ptr - 2, C.marikoCpuMaxVolt);
PATCH_OFFSET(ptr - 5, C.marikoCpuMaxVolt);
PATCH_OFFSET(ptr + 1, C.marikoCpuMaxVolt);
PATCH_OFFSET(ptr + 4, C.marikoCpuMaxVolt);
}
R_SUCCEED();
}
Result CpuVoltDfll(u32 *ptr) {
CvbCpuDfllData *entry = reinterpret_cast<CvbCpuDfllData *>(ptr);
R_UNLESS(entry->tune0_low == 0xFFCF, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune0_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_low == 0x12207FF, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_high == 0x3FFF7FF, ldr::ResultInvalidCpuVoltDfllEntry());
switch (C.marikoCpuUVLow) {
case 1:
PATCH_OFFSET(&(entry->tune0_low), 0xffa0);
PATCH_OFFSET(&(entry->tune0_high), 0xffff);
PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
PATCH_OFFSET(&(entry->tune1_high), 0x0);
break;
case 2:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27207ff);
break;
case 3:
PATCH_OFFSET(&(entry->tune0_low), 0xffdf);
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27307ff);
break;
case 4:
PATCH_OFFSET(&(entry->tune0_low), 0xffff);
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
break;
case 5:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27707ff);
break;
case 6:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
break;
case 7:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
break;
case 8:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
break;
case 9:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27c07ff);
break;
case 10:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27d07ff);
break;
case 11:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27e07ff);
break;
case 12:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff);
break;
default:
break;
}
switch (C.marikoCpuUVHigh) {
case 1:
PATCH_OFFSET(&(entry->tune1_high), 0x0);
PATCH_OFFSET(&(entry->tune0_high), 0xffff);
break;
case 2:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27207ff);
break;
case 3:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27307ff);
break;
case 4:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
break;
case 5:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27707ff);
break;
case 6:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
break;
case 7:
case 8:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
break;
case 9:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27c07ff);
break;
case 10:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27d07ff);
break;
case 11:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27e07ff);
break;
case 12:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff);
break;
default:
break;
}
R_SUCCEED();
}
}
@@ -1,5 +1,11 @@
/*
* Copyright (c) Atmosphère-NX
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* Copyright (c) Souldbminer 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,
@@ -14,12 +20,15 @@
* 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"
#pragma once
#include "secmon_define_access_table.inc"
#include "../pcv.hpp"
#undef __ACCESS_TABLE_INC__
#undef __ACCESS_TABLE_ADDRESS__
#undef __ACCESS_TABLE_NAME__
namespace ams::ldr::hoc::pcv::mariko {
Result CpuFreqVdd(u32 *ptr);
Result CpuVoltDVFS(u32 *ptr);
Result CpuVoltThermals(u32 *ptr);
Result CpuVoltDfll(u32 *ptr);
}
@@ -0,0 +1,140 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* Copyright (c) Souldbminer 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 "../pcv.hpp"
#include "../pcv_asm.hpp"
namespace ams::ldr::hoc::pcv::mariko {
Result GpuVoltDVFS(u32 *ptr) {
/* Check for valid pattern. */
for (size_t i = 0; i < std::size(gpuDVFSPattern); ++i) {
if (*(ptr + i + 1) != gpuDVFSPattern[i]) {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
}
/* Default value is 1050mV. */
if (C.marikoGpuVmax) {
PATCH_OFFSET(ptr + 1, C.marikoGpuVmax);
}
if (C.marikoGpuVmin) {
PATCH_OFFSET(ptr, C.marikoGpuVmin);
}
R_SUCCEED();
}
Result GpuVoltThermals(u32 *ptr) {
if (std::memcmp(ptr - 3, gpuVoltThermalPattern, sizeof(gpuVoltThermalPattern))) {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
// if (C.marikoGpuBootVolt) {
// PATCH_OFFSET(ptr - 3, C.marikoGpuBootVolt);
// }
if (C.marikoGpuVmin) {
PATCH_OFFSET(ptr, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 3, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 6, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 9, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 12, C.marikoGpuVmin);
}
R_SUCCEED();
}
Result GpuFreqMaxAsm(u32 *ptr32) {
// Check if both two instructions match the pattern
u32 ins1 = *ptr32, ins2 = *(ptr32 + 1);
if (!(asm_compare_no_rd(ins1, GpuAsmPattern[0]) && asm_compare_no_rd(ins2, GpuAsmPattern[1]))) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
// Both instructions should operate on the same register
u8 rd = asm_get_rd(ins1);
if (rd != asm_get_rd(ins2)) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
u32 max_clock;
switch (C.marikoGpuUV) {
case 0:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTable)->freq;
break;
case 1:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableSLT)->freq;
break;
case 2:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq;
break;
case 3:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT15)->freq;
break;
case 4:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHighUV)->freq;
break;
default:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq;
break;
}
u32 asm_patch[2] = {
asm_set_rd(asm_set_imm16(GpuAsmPattern[0], max_clock), rd),
asm_set_rd(asm_set_imm16(GpuAsmPattern[1], max_clock >> 16), rd)
};
PATCH_OFFSET(ptr32, asm_patch[0]);
PATCH_OFFSET(ptr32 + 1, asm_patch[1]);
R_SUCCEED();
}
Result GpuFreqPllMax(u32 *ptr) {
clk_pll_param *entry = reinterpret_cast<clk_pll_param *>(ptr);
// All zero except for freq
for (size_t i = 1; i < sizeof(clk_pll_param) / sizeof(u32); i++) {
R_UNLESS(*(ptr + i) == 0, ldr::ResultInvalidGpuPllEntry());
}
// Double the max clk simply
u32 max_clk = entry->freq * 2;
entry->freq = max_clk;
R_SUCCEED();
}
Result GpuFreqPllLimit(u32 *ptr) {
u32 prev_freq = *(ptr - 1);
if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) {
R_THROW(ldr::ResultInvalidGpuPllEntry());
}
PATCH_OFFSET(ptr, 3600000);
R_SUCCEED();
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* Copyright (c) Souldbminer 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 "../pcv.hpp"
namespace ams::ldr::hoc::pcv::mariko {
Result GpuVoltDVFS(u32 *ptr);
Result GpuVoltThermals(u32 *ptr);
Result GpuFreqMaxAsm(u32 *ptr32);
Result GpuFreqPllMax(u32 *ptr);
Result GpuFreqPllLimit(u32 *ptr);
}
@@ -20,333 +20,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <vector>
#include "pcv.hpp"
#include "../mtc_timing_value.hpp"
#include "../mariko/calculate_timings.hpp"
#include "../pcv.hpp"
#include "../../mtc_timing_value.hpp"
#include "calculate_timings_mariko.hpp"
namespace ams::ldr::hoc::pcv::mariko {
Result GpuVoltDVFS(u32 *ptr) {
/* Check for valid pattern. */
for (size_t i = 0; i < std::size(gpuDVFSPattern); ++i) {
if (*(ptr + i + 1) != gpuDVFSPattern[i]) {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
}
/* Default value is 1050mV. */
if (C.marikoGpuVmax) {
PATCH_OFFSET(ptr + 1, C.marikoGpuVmax);
}
if (C.marikoGpuVmin) {
PATCH_OFFSET(ptr, C.marikoGpuVmin);
}
R_SUCCEED();
}
Result GpuVoltThermals(u32 *ptr) {
if (std::memcmp(ptr - 3, gpuVoltThermalPattern, sizeof(gpuVoltThermalPattern))) {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
// if (C.marikoGpuBootVolt) {
// PATCH_OFFSET(ptr - 3, C.marikoGpuBootVolt);
// }
if (C.marikoGpuVmin) {
PATCH_OFFSET(ptr, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 3, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 6, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 9, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 12, C.marikoGpuVmin);
}
R_SUCCEED();
}
u32 CapCpuClock() {
u32 cpuCap = allowedCpuMaxFrequencies[0];
for (u32 freq : allowedCpuMaxFrequencies) {
if (C.marikoCpuMaxClock >= freq) {
cpuCap = freq;
} else {
break;
}
}
return cpuCap;
}
Result CpuFreqVdd(u32 *ptr) {
dvfs_rail *entry = reinterpret_cast<dvfs_rail *>(reinterpret_cast<u8 *>(ptr) - offsetof(dvfs_rail, freq));
R_UNLESS(entry->id == 1, ldr::ResultInvalidCpuFreqVddEntry());
R_UNLESS(entry->min_mv == 250'000, ldr::ResultInvalidCpuFreqVddEntry());
R_UNLESS(entry->step_mv == 5000, ldr::ResultInvalidCpuFreqVddEntry());
R_UNLESS(entry->max_mv == 1525'000, ldr::ResultInvalidCpuFreqVddEntry());
if (C.marikoCpuUVHigh) {
PATCH_OFFSET(ptr, CapCpuClock());
} else {
PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq);
}
R_SUCCEED();
}
Result CpuVoltDVFS(u32 *ptr) {
CvbMeta *cpuCvbMeta = reinterpret_cast<CvbMeta *>(reinterpret_cast<u8 *>(ptr) - offsetof(CvbMeta, vmin));
R_UNLESS(cpuCvbMeta->highVmin == CpuHighVminOfficial, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->unkStepMaybe == 38, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->vmax == CpuVoltOfficial, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->unkScale2 == 1000, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->speedoScale == 100, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->voltageScale == 1000, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->unkZero5 == 0, ldr::ResultInvalidCpuMinVolt());
if (C.marikoCpuLowVmin) {
PATCH_OFFSET(&(cpuCvbMeta->vmin), C.marikoCpuLowVmin);
}
if (C.marikoCpuHighVmin) {
PATCH_OFFSET(&(cpuCvbMeta->highVmin), C.marikoCpuHighVmin);
}
if (C.marikoCpuMaxVolt) {
PATCH_OFFSET(&(cpuCvbMeta->vmax), C.marikoCpuMaxVolt);
}
R_SUCCEED();
}
Result CpuVoltThermals(u32 *ptr) {
if (std::memcmp(ptr, cpuVoltThermalData, sizeof(cpuVoltThermalData))) {
R_THROW(ldr::ResultInvalidCpuMinVolt());
}
if (C.marikoCpuLowVmin) {
PATCH_OFFSET(ptr, C.marikoCpuLowVmin);
PATCH_OFFSET(ptr + 3, C.marikoCpuLowVmin);
}
if (C.marikoCpuMaxVolt) {
PATCH_OFFSET(ptr - 2, C.marikoCpuMaxVolt);
PATCH_OFFSET(ptr - 5, C.marikoCpuMaxVolt);
PATCH_OFFSET(ptr + 1, C.marikoCpuMaxVolt);
PATCH_OFFSET(ptr + 4, C.marikoCpuMaxVolt);
}
R_SUCCEED();
}
Result CpuVoltDfll(u32 *ptr) {
CvbCpuDfllData *entry = reinterpret_cast<CvbCpuDfllData *>(ptr);
R_UNLESS(entry->tune0_low == 0xFFCF, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune0_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_low == 0x12207FF, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_high == 0x3FFF7FF, ldr::ResultInvalidCpuVoltDfllEntry());
switch (C.marikoCpuUVLow) {
case 1:
PATCH_OFFSET(&(entry->tune0_low), 0xffa0);
PATCH_OFFSET(&(entry->tune0_high), 0xffff);
PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
PATCH_OFFSET(&(entry->tune1_high), 0x0);
break;
case 2:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27207ff);
break;
case 3:
PATCH_OFFSET(&(entry->tune0_low), 0xffdf);
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27307ff);
break;
case 4:
PATCH_OFFSET(&(entry->tune0_low), 0xffff);
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
break;
case 5:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27707ff);
break;
case 6:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
break;
case 7:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
break;
case 8:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
break;
case 9:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27c07ff);
break;
case 10:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27d07ff);
break;
case 11:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27e07ff);
break;
case 12:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff);
break;
default:
break;
}
switch (C.marikoCpuUVHigh) {
case 1:
PATCH_OFFSET(&(entry->tune1_high), 0x0);
PATCH_OFFSET(&(entry->tune0_high), 0xffff);
break;
case 2:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27207ff);
break;
case 3:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27307ff);
break;
case 4:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
break;
case 5:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27707ff);
break;
case 6:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
break;
case 7:
case 8:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
break;
case 9:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27c07ff);
break;
case 10:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27d07ff);
break;
case 11:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27e07ff);
break;
case 12:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff);
break;
default:
break;
}
R_SUCCEED();
}
Result GpuFreqMaxAsm(u32 *ptr32) {
// Check if both two instructions match the pattern
u32 ins1 = *ptr32, ins2 = *(ptr32 + 1);
if (!(asm_compare_no_rd(ins1, GpuAsmPattern[0]) && asm_compare_no_rd(ins2, GpuAsmPattern[1]))) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
// Both instructions should operate on the same register
u8 rd = asm_get_rd(ins1);
if (rd != asm_get_rd(ins2)) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
u32 max_clock;
switch (C.marikoGpuUV) {
case 0:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTable)->freq;
break;
case 1:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableSLT)->freq;
break;
case 2:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq;
break;
case 3:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT15)->freq;
break;
case 4:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHighUV)->freq;
break;
default:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq;
break;
}
u32 asm_patch[2] = {
asm_set_rd(asm_set_imm16(GpuAsmPattern[0], max_clock), rd),
asm_set_rd(asm_set_imm16(GpuAsmPattern[1], max_clock >> 16), rd)
};
PATCH_OFFSET(ptr32, asm_patch[0]);
PATCH_OFFSET(ptr32 + 1, asm_patch[1]);
R_SUCCEED();
}
Result GpuFreqPllMax(u32 *ptr) {
clk_pll_param *entry = reinterpret_cast<clk_pll_param *>(ptr);
// All zero except for freq
for (size_t i = 1; i < sizeof(clk_pll_param) / sizeof(u32); i++) {
R_UNLESS(*(ptr + i) == 0, ldr::ResultInvalidGpuPllEntry());
}
// Double the max clk simply
u32 max_clk = entry->freq * 2;
entry->freq = max_clk;
R_SUCCEED();
}
Result GpuFreqPllLimit(u32 *ptr) {
u32 prev_freq = *(ptr - 1);
if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) {
R_THROW(ldr::ResultInvalidGpuPllEntry());
}
PATCH_OFFSET(ptr, 3600000);
R_SUCCEED();
namespace {
std::vector<u32> newEmcList;
}
void MemMtcTableAutoAdjust(MarikoMtcTable *table) {
@@ -617,11 +298,6 @@ namespace ams::ldr::hoc::pcv::mariko {
}
}
namespace {
std::vector<u32> newEmcList;
u32 *nsoStart;
}
void MtcGenerateJedecTable() {
const u32 jedecFreqs[] = { 1866000, 1996000, 2133000, 2400000, 2666000, 2933000, 3200000 };
constexpr u32 JedecFreqCount = std::size(jedecFreqs);
@@ -638,7 +314,7 @@ namespace ams::ldr::hoc::pcv::mariko {
newEmcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
}
newEmcList.resize(std::min(newEmcList.size(), DvfsTableEntryLimit));
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
}
void MtcGenerate133StepTable() {
@@ -657,12 +333,39 @@ namespace ams::ldr::hoc::pcv::mariko {
newEmcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
}
newEmcList.resize(std::min(newEmcList.size(), DvfsTableEntryLimit));
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));
}
void MtcGenerateFreqTables() {
newEmcList.clear();
newEmcList.reserve(DvfsTableEntryCount);
newEmcList.reserve(EmcDvfsTableEntryCount);
newEmcList.insert(newEmcList.end(), std::begin(EmcListDefault), std::end(EmcListDefault));
if (C.marikoEmcMaxClock <= EmcClkOSLimit) {
@@ -671,6 +374,9 @@ namespace ams::ldr::hoc::pcv::mariko {
u32 stepRate = 0;
switch (C.stepMode) {
case StepMode_33MHz:
MtcGenerate33StepTable();
return;
case StepMode_66MHz:
stepRate = 66667;
break;
@@ -701,7 +407,7 @@ namespace ams::ldr::hoc::pcv::mariko {
newEmcList.push_back(newFreq);
}
newEmcList.resize(std::min(newEmcList.size(), DvfsTableEntryLimit));
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
}
Result VerifyMtcTable(MarikoMtcTable *tableStart, u32 expectedFreq) {
@@ -769,6 +475,7 @@ namespace ams::ldr::hoc::pcv::mariko {
Result MemFreqMtcTable(u32 *ptr) {
static const DramId dramId = [] {
DramId id = GetDramId();
id = HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTF;
return id;
}();
@@ -905,24 +612,23 @@ namespace ams::ldr::hoc::pcv::mariko {
#undef DVB
#undef DVB_OC
DvbEntry emcDvbTableOc[newEmcList.size()];
const size_t dvbCount = std::min(newEmcList.size(), DvbTableCapacity);
DvbEntry emcDvbTableOc[DvbTableCapacity] = {};
u32 bracketIndex = 0;
for (u32 i = 0; i < newEmcList.size(); ++i) {
while (newEmcList[i] >= emcDvbOcTableBrackets[bracketIndex + 1].freq) {
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) {
++bracketIndex;
}
emcDvbTableOc[i].freq = newEmcList[i];
emcDvbTableOc[i].freq = freq;
std::memcpy(emcDvbTableOc[i].volt, emcDvbOcTableBrackets[bracketIndex].volt, sizeof(emcDvbTableOc[i].volt));
}
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
*/
/* 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));
R_SUCCEED();
}
@@ -936,57 +642,6 @@ namespace ams::ldr::hoc::pcv::mariko {
R_SUCCEED();
}
Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) {
struct {
u8 reg;
u8 val;
} __attribute__((packed)) cmd;
I2cSession _session;
R_TRY(i2cOpenSession(&_session, dev));
cmd.reg = reg;
cmd.val = val;
Result res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All);
i2csessionClose(&_session);
return res;
}
Result EmcVddqVolt(u32 *ptr) {
regulator *entry = reinterpret_cast<regulator *>(reinterpret_cast<u8 *>(ptr) - offsetof(regulator, type_2_3.default_uv));
constexpr u32 uv_step = 5'000;
constexpr u32 uv_min = 250'000;
auto validator = [entry]() {
R_UNLESS(entry->id == 2, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type == 3, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_2_3.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_2_3.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry());
R_SUCCEED();
};
R_TRY(validator());
u32 emc_uv = C.marikoEmcVddqVolt;
if (!emc_uv) {
R_SKIP();
}
if (emc_uv % uv_step) {
emc_uv = (emc_uv + uv_step - 1) / uv_step * uv_step; // rounding
}
PATCH_OFFSET(ptr, emc_uv);
i2cInitialize();
Result resultI2C = I2cSet_U8(I2cDevice_Max77812_2, 0x25, (emc_uv - uv_min) / uv_step);
i2cExit();
return resultI2C;
}
Result MemMtcTableAsm(u32 *ptr) {
constexpr u32 AddpOffset = 1;
constexpr u32 BrOffset = 12;
@@ -1018,170 +673,4 @@ namespace ams::ldr::hoc::pcv::mariko {
R_SUCCEED();
}
Result GetSocSpeedo(u32 &socSpeedo) {
constexpr u64 FusePhysicalAddress = 0x7000F000;
u64 virtualAddress = 0;
constexpr u64 Size = 0x1000;
u64 outSize;
/* TODO: use svc::QueryMemoryMapping instead. */
R_TRY(svcQueryMemoryMapping(&virtualAddress, &outSize, FusePhysicalAddress, Size));
constexpr u32 FuseOffset = 2048;
constexpr u32 SocSpeedoOffset = 308;
socSpeedo = *reinterpret_cast<u32 *>(virtualAddress + FuseOffset + SocSpeedoOffset);
R_SUCCEED();
}
u32 GetSocProcessId(u32 socSpeedo) {
if (socSpeedo <= 1597) {
return 0;
}
if (socSpeedo <= 1708) {
return 1;
}
/* >= 1709. */
return 2;
}
Result SocVoltAsm(u32 *compareSpeedos) {
constexpr u32 VoltageScanLimit = 10;
/* Might actually be speedo id. */
u32 *writeProcessId = ScanAssembly(compareSpeedos, VoltageScanLimit, SocVoltWriteProcessIdAsm, asm_compare_no_rd);
R_UNLESS(writeProcessId != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 writeProcessIdRd = asm_get_rd(*writeProcessId);
/* This writes 1050mV. */
u32 *writeVoltage = ScanAssembly(writeProcessId, VoltageScanLimit, SocVoltWriteVoltageAsm, asm_compare_no_rd);
R_UNLESS(writeVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 writeVoltageRd = asm_get_rd(*writeVoltage);
/* A csel instruction is used to select the soc voltage limit register. */
/* We care about its destination register since that is used for verification. */
constexpr u32 VoltageSelectScanLimit = 24;
u32 *selectVoltage = ScanAssembly(writeVoltage, VoltageSelectScanLimit, SocVoltSelectRegisterAsm, AsmCompareCselNoReg);
R_UNLESS(selectVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
/* Todo: check rm and rn? */
u8 selectVoltageRd = asm_get_rd(*selectVoltage);
/* rdCsel is then multiplied by 1000 to convert to uV. */
/* This is pretty far down the function. */
constexpr u32 MultiplierScanLimit = 200;
u32 *multiplier = ScanAssembly(selectVoltage, MultiplierScanLimit, SocVoltMultiplyVoltsAsm, AsmCompareMullNoReg);
R_UNLESS(multiplier != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 multiplierRn = AsmGetMullRn(*multiplier);
u8 multiplierRm = AsmGetMullRm(*multiplier);
/* One of the two registers has to be rdCsel. */
R_UNLESS((multiplierRn == selectVoltageRd) || (multiplierRm == selectVoltageRd), ldr::ResultInvalidSocVoltPattern());
u8 multiplierRd = asm_get_rd(*multiplier);
/* Subs instruction is then used to verify against absolute limit. */
u32 limitValidationPattern = AsmSubsSetRn(SocVoltValidateLimitAsm, multiplierRd);
u32 *limitValidation = ScanAssembly(multiplier, VoltageScanLimit, limitValidationPattern, AsmSubsCompareNoReg);
R_UNLESS(limitValidation != nullptr, ldr::ResultInvalidSocVoltPattern());
/* There is a b.gt instruction right after (checks for socVoltageCap < socVoltageMax). */
u32 *branchToAbort = limitValidation + 1;
R_UNLESS(AsmCompareBrConNoImm19(*branchToAbort, SocVoltBranchToAbortAsm), ldr::ResultInvalidSocVoltPattern());
if (!C.marikoSocVmax || C.marikoSocVmax <= 1000) {
R_SKIP();
}
/* Adjust 1598 speedo minimum to ensure it always goes down process id 0 branch. */
/* 2200 should be high enough :D */
u32 compareSpeedosPatch = AsmSubsSetImm12(*compareSpeedos, 2200);
PATCH_OFFSET(compareSpeedos, compareSpeedosPatch);
u32 socSpeedo = 0;
R_TRY(GetSocSpeedo(socSpeedo));
/* Adjust processId from 0 to [process id of switch booting this]. */
/* We're overwriting the orr instruction entirly. */
u32 processId = GetSocProcessId(socSpeedo);
u32 writeProcessIdPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, processId), writeProcessIdRd);
PATCH_OFFSET(writeProcessId, writeProcessIdPatch);
/* Adjust voltage limit. */
u32 voltageLimitPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, C.marikoSocVmax), writeVoltageRd);
PATCH_OFFSET(writeVoltage, voltageLimitPatch);
/* Branches to an abort if limits are invalid -- we patch the branch instruction with NOP. */
PATCH_OFFSET(branchToAbort, NopIns);
R_SUCCEED();
}
Result SocVoltLimit(u32 *ptr) {
R_UNLESS(!std::memcmp(ptr - SocVoltLimitMaxDefaultIndex, socVoltLimitArray, sizeof(socVoltLimitArray)), ldr::ResultInvalidSocVoltLimit());
if (!C.marikoSocVmax || C.marikoSocVmax <= SocVoltLimitOfficial) {
R_SKIP();
}
constexpr u32 Step = 25;
u32 maxVolt = C.marikoSocVmax;
if (maxVolt % Step) {
maxVolt = maxVolt / Step * Step; /* Round. */
}
u32 volt = SocVoltLimitOfficial;
for (u32 i = 1; i < DvfsTableEntryCount - SocVoltLimitMaxDefaultIndex && volt < maxVolt; ++i) {
volt += Step;
PATCH_OFFSET(ptr + i, volt);
}
R_SUCCEED();
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
nsoStart = reinterpret_cast<u32 *>(mapped_nso);
MtcGenerateFreqTables();
u32 CpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
u32 GpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
PatcherEntry<u32> patches[] = {
{ "CPU Freq Vdd", &CpuFreqVdd, 1, nullptr, CpuClkOSLimit },
{ "CPU Freq Table", CpuFreqCvbTable<true>, 1, nullptr, CpuCvbDefaultMaxFreq },
{ "CPU Volt DVFS", &CpuVoltDVFS, 1, nullptr, CpuVminOfficial },
{ "CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial },
{ "CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low },
{ "GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial },
{ "GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial },
{ "GPU Freq Table", GpuFreqCvbTable<true>, 1, nullptr, GpuCvbDefaultMaxFreq },
{ "GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn },
{ "GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax },
{ "GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit },
{ "MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit },
{ "MEM Freq Dvb", &MemFreqDvbTable, 1, nullptr, EmcClkOSLimit },
{ "MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit },
{ "MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit },
{ "MEM Vddq", &EmcVddqVolt, 2, nullptr, EmcVddqDefault },
{ "MEM Vdd2", &MemVoltHandler, 2, nullptr, MemVdd2Default },
{ "MEM Table Asm", &MemMtcTableAsm, 1, &MemMtcGetGetTablePatternFn },
{ "SOC Volt Asm", &SocVoltAsm, 1, &SocVoltPatternFn },
{ "SOC Volt Limit", &SocVoltLimit, 1, nullptr, SocVoltLimitOfficial },
};
for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(MarikoMtcTable); ptr += sizeof(u32)) {
u32 *ptr32 = reinterpret_cast<u32 *>(ptr);
for (auto &entry : patches) {
if (R_SUCCEEDED(entry.SearchAndApply(ptr32))) {
break;
}
}
}
for (auto &entry : patches) {
LOGGING("%s Count: %zu\n", entry.description, entry.patched_count);
if (R_FAILED(entry.CheckResult())) {
panic::SmcError(panic::Patch);
CRASH(entry.description);
}
}
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* Copyright (c) Souldbminer 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 "../pcv.hpp"
namespace ams::ldr::hoc::pcv::mariko {
void MtcGenerateFreqTables();
Result MemFreqMtcTable(u32 *ptr);
Result MemFreqDvbTable(u32 *ptr);
Result MemFreqMax(u32 *ptr);
Result MemMtcTableAsm(u32 *ptr);
}
@@ -1,47 +1,47 @@
/*
* 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/>.
*/
#include "../oc_common.hpp"
#include "timing_tables.hpp"
namespace ams::ldr::hoc::pcv::mariko {
const ReplacePatch g_rext_table[] = {
{2'133'000, 0x1A}, {2'166'000, 0x19}, {2'200'000, 0x19},
{2'233'000, 0x19}, {2'266'000, 0x1A}, {2'300'000, 0x1B},
{2'333'000, 0x1B}, {2'366'000, 0x1B}, {2'400'000, 0x1B},
{2'433'000, 0x1B}, {2'466'000, 0x1B}, {2'500'000, 0x1A},
{2'533'000, 0x1C}, {2'566'000, 0x1B}, {2'600'000, 0x1B},
{2'633'000, 0x1B}, {2'666'000, 0x1B}, {2'700'000, 0x1C},
{2'733'000, 0x1C}, {2'766'000, 0x1D}, {2'800'000, 0x1D},
{2'833'000, 0x1D}, {2'866'000, 0x1D}, {2'900'000, 0x1D},
{2'933'000, 0x1C}, {2'966'000, 0x1D}, {3'000'000, 0x1D},
{3'033'000, 0x1D}, {3'066'000, 0x1D}, {3'100'000, 0x1D},
{3'133'000, 0x1D}, {3'166'000, 0x1C}, {3'200'000, 0x1C},
};
const u32 g_rext_table_size = sizeof(g_rext_table) / sizeof(g_rext_table[0]);
const ReplacePatch *FindRext() {
for (u32 i = 0; i < g_rext_table_size; i++) {
if (g_rext_table[i].freq >= C.marikoEmcMaxClock) {
return &g_rext_table[i];
}
}
return nullptr;
}
}
/*
* 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/>.
*/
#include "../../oc_common.hpp"
#include "timing_tables.hpp"
namespace ams::ldr::hoc::pcv::mariko {
const ReplacePatch g_rext_table[] = {
{2'133'000, 0x1A}, {2'166'000, 0x19}, {2'200'000, 0x19},
{2'233'000, 0x19}, {2'266'000, 0x1A}, {2'300'000, 0x1B},
{2'333'000, 0x1B}, {2'366'000, 0x1B}, {2'400'000, 0x1B},
{2'433'000, 0x1B}, {2'466'000, 0x1B}, {2'500'000, 0x1A},
{2'533'000, 0x1C}, {2'566'000, 0x1B}, {2'600'000, 0x1B},
{2'633'000, 0x1B}, {2'666'000, 0x1B}, {2'700'000, 0x1C},
{2'733'000, 0x1C}, {2'766'000, 0x1D}, {2'800'000, 0x1D},
{2'833'000, 0x1D}, {2'866'000, 0x1D}, {2'900'000, 0x1D},
{2'933'000, 0x1C}, {2'966'000, 0x1D}, {3'000'000, 0x1D},
{3'033'000, 0x1D}, {3'066'000, 0x1D}, {3'100'000, 0x1D},
{3'133'000, 0x1D}, {3'166'000, 0x1C}, {3'200'000, 0x1C},
};
const u32 g_rext_table_size = sizeof(g_rext_table) / sizeof(g_rext_table[0]);
const ReplacePatch *FindRext() {
for (u32 i = 0; i < g_rext_table_size; i++) {
if (g_rext_table[i].freq >= C.marikoEmcMaxClock) {
return &g_rext_table[i];
}
}
return nullptr;
}
}
@@ -1,31 +1,31 @@
/*
* 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 <stratosphere.hpp>
namespace ams::ldr::hoc::pcv::mariko {
struct ReplacePatch {
u32 freq;
u32 rext;
};
extern const ReplacePatch g_rext_table[];
extern const u32 g_rext_table_size;
const ReplacePatch *FindRext();
}
/*
* 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 <stratosphere.hpp>
namespace ams::ldr::hoc::pcv::mariko {
struct ReplacePatch {
u32 freq;
u32 rext;
};
extern const ReplacePatch g_rext_table[];
extern const u32 g_rext_table_size;
const ReplacePatch *FindRext();
}
@@ -19,7 +19,6 @@
*/
#include "pcv.hpp"
#include "pcv_hook.hpp"
namespace ams::ldr::hoc::pcv {
@@ -28,8 +27,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();
}
@@ -42,7 +41,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());
@@ -68,7 +67,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);
@@ -103,11 +102,12 @@ 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:
@@ -148,11 +148,11 @@ namespace ams::ldr::hoc::pcv {
Validator validators[] = {
{ C.eristaCpuBoostClock, 1020'000, 2397'000, panic::Cpu, true },
{ C.marikoCpuBoostClock, 1020'000, 2805'000, panic::Cpu, true },
{ C.marikoCpuBoostClock, 1020'000, 2703'000, panic::Cpu, true },
{ C.eristaCpuMaxVolt, 1000, 1260, panic::Cpu, },
{ C.marikoCpuMaxVolt, 1000, 1235, panic::Cpu, },
{ C.marikoCpuMaxVolt, 1000, 1200, panic::Cpu, },
{ eristaCpuDvfsMaxFreq, 1785'000, 2397'000, panic::Cpu, },
{ marikoCpuDvfsMaxFreq, 1785'000, 2805'000, panic::Cpu, },
{ marikoCpuDvfsMaxFreq, 1785'000, 2703'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, },
@@ -172,7 +172,7 @@ 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)));
}
@@ -182,6 +182,9 @@ namespace ams::ldr::hoc::pcv {
Hooks().Initialize(mapped_nso, nso_address, cave, cave_size, data_arena);
g_pcv_cave = cave;
g_pcv_cave_size = cave_size;
bool isMariko = (spl::GetSocType() == spl::SocType_Mariko);
if (isMariko) {
mariko::Patch(mapped_nso, nso_size);
@@ -22,8 +22,14 @@
#include "../oc_common.hpp"
#include "pcv_common.hpp"
#include "pcv_erista.hpp"
#include "pcv_mariko.hpp"
#include "erista/pcv_erista_cpu.hpp"
#include "erista/pcv_erista_gpu.hpp"
#include "erista/pcv_erista_mtc.hpp"
#include "erista/pcv_erista.hpp"
#include "mariko/pcv_mariko.hpp"
#include "pcv_hook.hpp"
namespace ams::ldr::hoc::pcv {
@@ -137,7 +143,7 @@ namespace ams::ldr::hoc::pcv {
default:
customize_table = const_cast<cvb_entry_t *>(C.marikoGpuDvfsTableHiOPT);
break;
}
}
} else {
switch (C.eristaGpuUV) {
case 0:
@@ -152,7 +158,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);
@@ -188,6 +194,7 @@ namespace ams::ldr::hoc::pcv {
}
++entry;
}
if (C.commonGpuVoltOffset && !(isMariko ? C.marikoGpuUV : C.eristaGpuUV)) {
cvb_entry_t *entry = static_cast<cvb_entry_t *>(gpu_cvb_table_head);
for (size_t i = 0; i < customize_entry_count; ++i) {
@@ -202,6 +209,10 @@ namespace ams::ldr::hoc::pcv {
Result MemFreqPllmLimit(u32 *ptr);
Result MemVoltHandler(u32 *ptr); // Used for Erista MEM Vdd2 + EMC Vddq or Mariko MEM Vdd2
/* Extra pcv .bss */
constexpr size_t HocPcvScratchSize = 0x2000;
constexpr size_t HocBusFreqBufOffset = 0x1000; /* start of the SOC bus region */
void SafetyCheck();
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,6 +44,15 @@ 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);
};
@@ -94,6 +103,16 @@ 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);
@@ -101,6 +120,167 @@ 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);
};
/* b <target> (PC-relative, +-128MB). */
inline auto AsmMakeB = [](uintptr_t pc, uintptr_t target) -> u32 {
const s64 off = (static_cast<s64>(target) - static_cast<s64>(pc)) >> 2;
return 0x14000000u | (static_cast<u32>(off) & 0x03FFFFFFu);
};
/* b.<cond> <target> (cond: LO/CC=0x3, LE=0xD, NE=0x1, ...). */
inline auto AsmMakeBCond = [](uintptr_t pc, uintptr_t target, u32 cond) -> u32 {
const s64 off = (static_cast<s64>(target) - static_cast<s64>(pc)) >> 2;
return 0x54000000u | ((static_cast<u32>(off) & 0x7FFFFu) << 5) | (cond & 0xFu);
};
/* sub Xd,Xn,#imm12 (shift 0). */
inline auto AsmMakeSubImm64 = [](u32 rd, u32 rn, u32 imm12) -> u32 {
return 0xD1000000u | ((imm12 & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rd & 0x1Fu);
};
/* cmp Wn,#imm12 == subs WZR,Wn,#imm12. */
inline auto AsmMakeCmpImm32 = [](u32 rn, u32 imm12) -> u32 {
return 0x7100001Fu | ((imm12 & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5);
};
/* str Wt,[Xn,#byteOff] (32-bit, unsigned scaled by 4). */
inline auto AsmMakeStrImm32 = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0xB9000000u | (((byteOff / 4u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* ldr Wt,[Xn,#byteOff] (32-bit, unsigned scaled by 4). */
inline auto AsmMakeLdrImm32 = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0xB9400000u | (((byteOff / 4u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* add Xd,Xn,Xm,LSL #shift (64-bit shifted register, shift 0-63). */
inline auto AsmMakeAddShiftedReg64 = [](u32 rd, u32 rn, u32 rm, u32 shift) -> u32 {
return 0x8B000000u | ((rm & 0x1Fu) << 16) | ((shift & 0x3Fu) << 10) | ((rn & 0x1Fu) << 5) | (rd & 0x1Fu);
};
/* stp Xt1,Xt2,[Xn,#imm] (signed offset, scaled by 8). */
inline auto AsmMakeStpImm64 = [](u32 rt1, u32 rt2, u32 rn, s32 imm) -> u32 {
return 0xA9000000u | ((static_cast<u32>(imm / 8) & 0x7Fu) << 15) | ((rt2 & 0x1Fu) << 10) | ((rn & 0x1Fu) << 5) | (rt1 & 0x1Fu);
};
/* stp Qt1,Qt2,[Xn,#imm] (128-bit SIMD, signed offset scaled by 16). */
inline auto AsmMakeStpqImm = [](u32 qt1, u32 qt2, u32 rn, s32 imm) -> u32 {
return 0xAD000000u | ((static_cast<u32>(imm / 16) & 0x7Fu) << 15) | ((qt2 & 0x1Fu) << 10) | ((rn & 0x1Fu) << 5) | (qt1 & 0x1Fu);
};
/* str Xt,[Xn,#byteOff] (unsigned scaled by 8). */
inline auto AsmMakeStrImm64 = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0xF9000000u | (((byteOff / 8u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* movn Wd,#imm16 (no shift): loads ~imm16, e.g. movn Wd,#0x37 == -56. */
inline auto AsmMakeMovnW = [](u32 rd, u16 imm16) -> u32 {
return 0x12800000u | (static_cast<u32>(imm16) << 5) | (rd & 0x1Fu);
};
/* bl <target> (PC-relative, +-128MB). */
inline auto AsmMakeBl = [](uintptr_t pc, uintptr_t target) -> u32 {
const s64 off = (static_cast<s64>(target) - static_cast<s64>(pc)) >> 2;
return 0x94000000u | (static_cast<u32>(off) & 0x03FFFFFFu);
};
/* svc #imm16. */
inline auto AsmMakeSvc = [](u16 imm16) -> u32 {
return 0xD4000001u | (static_cast<u32>(imm16) << 5);
};
constexpr u32 RetIns = 0xD65F03C0u; /* ret (x30) */
/* ldrb Wt,[Xn,#imm] (unsigned byte, scale 1). */
inline auto AsmMakeLdrbImm = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0x39400000u | ((byteOff & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* cbz Wt,<target>. */
inline auto AsmMakeCbz = [](uintptr_t pc, uintptr_t target, u32 rt) -> u32 {
const s64 off = (static_cast<s64>(target) - static_cast<s64>(pc)) >> 2;
return 0x34000000u | ((static_cast<u32>(off) & 0x7FFFFu) << 5) | (rt & 0x1Fu);
};
/* strb Wt,[Xn,#imm] (unsigned byte, scale 1). */
inline auto AsmMakeStrbImm = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0x39000000u | ((byteOff & 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 */
/* ldr/str Xt,[Xn,#imm] (64-bit, unsigned scaled offset). */
inline auto AsmIsLdrImm64 = [](u32 ins) -> bool { return (ins & 0xFFC00000u) == 0xF9400000u; };
inline auto AsmIsStrImm64 = [](u32 ins) -> bool { return (ins & 0xFFC00000u) == 0xF9000000u; };
inline auto AsmGetLdStImm64Off = [](u32 ins) -> u32 { return ((ins >> 10) & 0xFFFu) * 8u; };
/* 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;
};
@@ -173,6 +173,19 @@ 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;
inline uintptr_t g_pcv_cave = 0;
inline size_t g_pcv_cave_size = 0;
template<typename T>
size_t GetDvfsTableEntryCount(T *table_head) {
using NT = std::remove_const_t<std::remove_volatile_t<T>>;
@@ -1,165 +0,0 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* 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 "../oc_common.hpp"
#include "pcv_common.hpp"
#include "pcv_asm.hpp"
namespace ams::ldr::hoc::pcv::erista {
constexpr cvb_entry_t CpuCvbTableDefault[] = {
// CPU_PLL_CVB_TABLE_ODN
{ 204000, {721094}, { } },
{ 306000, {754040}, { } },
{ 408000, {786986}, { } },
{ 510000, {819932}, { } },
{ 612000, {852878}, { } },
{ 714000, {885824}, { } },
{ 816000, {918770}, { } },
{ 918000, {951716}, { } },
{ 1020000, {984662}, { -2875621, 358099, -8585} },
{ 1122000, {1017608}, { -52225, 104159, -2816} },
{ 1224000, {1050554}, { 1076868, 8356, -727} },
{ 1326000, {1083500}, { 2208191, -84659, 1240} },
{ 1428000, {1116446}, { 2519460, -105063, 1611} },
{ 1581000, {1130000}, { 2889664, -122173, 1834} },
{ 1683000, {1168000}, { 5100873, -279186, 4747} },
{ 1785000, {1227500}, { 5100873, -279186, 4747} },
{ },
};
constexpr u32 CpuVoltOfficial = 1227;
constexpr u32 CpuVminOfficial = 825;
constexpr u32 CpuTune0Low = 0xFFEAD0FF;
constexpr u32 CpuVoltL4T = 1257'000;
static const u32 cpuVoltDvfsPattern[] = { 1227, 1000, 100, 1000, 0 };
static_assert(sizeof(cpuVoltDvfsPattern) == 0x14, "Invalid cpuVoltDvfsPattern size");
static const u32 cpuVoltageThermalPattern[] = { 950, 1132, 0, 950, 1227, 0, 825, 1227, 15000, 825, 1170, 60000, 825, 1132, 80000 };
static_assert(sizeof(cpuVoltageThermalPattern) == 0x3c, "Invalid cpuVoltageThermalPattern size");
constexpr u32 GpuClkPllLimit = 2'600'000;
constexpr u32 GpuClkPllMax = 921'600'000;
constexpr u32 GpuVminOfficial = 810;
constexpr u16 CpuMinVolts[] = { 950, 850, 825, 810 };
static const u32 gpuVoltDvfsPattern[] = { 810, 1150, 1000, 100, 1000, 10, };
static_assert(sizeof(gpuVoltDvfsPattern) == (sizeof(u32) * 6), "Invalid gpuVoltDvfsPattern");
static const u32 gpuVoltThermalPattern[] = { 950, 1132, 0, 810, 1132, 15000, 810, 1132, 30000, 810, 1132, 50000, 810, 1132, 70000, 810, 1132, 105000 };
static_assert(sizeof(gpuVoltThermalPattern) == 0x48, "Invalid gpuVoltageThermalPattern size");
/* GPU Max Clock asm Pattern:
*
* MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1
*
* MOVK W11, #0xE, LSL#16 <shift>16 0xE 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1
*/
inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 };
inline bool GpuMaxClockPatternFn(u32 *ptr32) {
return asm_compare_no_rd(*ptr32, GpuAsmPattern[0]);
};
constexpr cvb_entry_t GpuCvbTableDefault[] = {
// NA_FREQ_CVB_TABLE
{ 76800, {}, { 814294, 8144, -940, 808, -21583, 226, } },
{ 153600, {}, { 856185, 8144, -940, 808, -21583, 226, } },
{ 230400, {}, { 898077, 8144, -940, 808, -21583, 226, } },
{ 307200, {}, { 939968, 8144, -940, 808, -21583, 226, } },
{ 384000, {}, { 981860, 8144, -940, 808, -21583, 226, } },
{ 460800, {}, { 1023751, 8144, -940, 808, -21583, 226, } },
{ 537600, {}, { 1065642, 8144, -940, 808, -21583, 226, } },
{ 614400, {}, { 1107534, 8144, -940, 808, -21583, 226, } },
{ 691200, {}, { 1149425, 8144, -940, 808, -21583, 226, } },
{ 768000, {}, { 1191317, 8144, -940, 808, -21583, 226, } },
{ 844800, {}, { 1233208, 8144, -940, 808, -21583, 226, } },
{ 921600, {}, { 1275100, 8144, -940, 808, -21583, 226, } },
{ },
};
constexpr u32 EmcListDefault[] = { 40800, 68000, 102000, 204000, 408000, 665600, 800000, 1065600, 1331200, 1600000, };
constexpr u32 EmcListSizeDefault = std::size(EmcListDefault);
constexpr u32 EmcListEndDefault = EmcListSizeDefault - 1;
constexpr u32 MemVoltHOS = 1125'000;
constexpr u32 EmcClkPllmLimit = 1866'000'000;
constexpr u32 MTC_TABLE_REV = 7;
constexpr u32 MtcTableCountDefault = 10;
constexpr size_t MtcFullTableSize = sizeof(EristaMtcTable) * MtcTableCountDefault;
constexpr u32 MtcFullTableCount = 3;
/* These dramids were copied from Hekate -- see /bdk/mem/sdram.h */
enum DramId {
ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH = 0,
ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE = 1,
ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC = 2,
ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH = 4,
ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX = 7,
};
enum MtcTableIndex {
T210SdevEmcDvfsTableS4gb01 = 0, /* HB-MGCH, WT:C */
T210SdevEmcDvfsTableS6gb01 = 1, /* HM-MGCH */
T210SdevEmcDvfsTableH4gb01 = 2, /* HR-NLE */
MtcTableIndex_Invalid = 3,
};
struct MtcDramIndex {
DramId dramId;
MtcTableIndex index;
};
/* TODO: Test 6gb and 8gb. */
const inline MtcDramIndex mtcIndexTable[] = {
{ ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH, T210SdevEmcDvfsTableS4gb01, },
{ ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC, T210SdevEmcDvfsTableS4gb01, },
{ ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH, T210SdevEmcDvfsTableS6gb01, },
{ ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX, T210SdevEmcDvfsTableS6gb01, },
{ ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE, T210SdevEmcDvfsTableH4gb01, },
};
constexpr u32 MtcBrAsm = 0xD61F0140;
constexpr u32 MtcMovAsm = 0x52800148;
constexpr u32 MtcAdrpAsm = 0xD0000081;
constexpr u32 MtcBlIns = 0x97ffae64;
constexpr u32 MtcAddAsm = 0x91131821;
ALWAYS_INLINE bool MemMtcGetGetTablePatternFn(u32 *ptr) {
/* This builds an address that gets returned, so the register must be x0 by convention. */
return AsmCompareAddNoImm12(*ptr, MtcAddAsm);
}
void Patch(uintptr_t mapped_nso, size_t nso_size);
}
@@ -37,6 +37,9 @@
".text\n"); \
extern "C" __attribute__((visibility("hidden"))) type *name
#define DECLARE_HOOK_PAYLOAD_PTR(type, name) \
extern "C" __attribute__((visibility("hidden"))) type *name
#define HOOK_PAYLOAD_PTR(type, name) \
([]() -> type * { \
type **_hoc_pp; \
@@ -0,0 +1,136 @@
#---------------------------------------------------------------------------------
# pull in common stratosphere sysmodule configuration
#---------------------------------------------------------------------------------
THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST)))
CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE)))
include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk
ifneq ($(strip $(HOC_UART_LOG)),)
export CFLAGS += -DHOC_UART_LOG=$(HOC_UART_LOG)
export CXXFLAGS += -DHOC_UART_LOG=$(HOC_UART_LOG)
endif
ATMOSPHERE_SYSTEM_MODULE_TARGETS := kip
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(__RECURSIVE__),1)
#---------------------------------------------------------------------------------
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
CFILES := $(call FIND_SOURCE_FILES,$(SOURCES),c)
CPPFILES := $(call FIND_SOURCE_FILES,$(SOURCES),cpp)
SFILES := $(call FIND_SOURCE_FILES,$(SOURCES),s)
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES := $(addsuffix .o,$(BINFILES)) \
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
$(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR))
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
ifeq ($(strip $(CONFIG_JSON)),)
jsons := $(wildcard *.json)
ifneq (,$(findstring $(TARGET).json,$(jsons)))
export APP_JSON := $(TOPDIR)/$(TARGET).json
else
ifneq (,$(findstring config.json,$(jsons)))
export APP_JSON := $(TOPDIR)/config.json
endif
endif
else
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
endif
.PHONY: clean all check_lib
#---------------------------------------------------------------------------------
all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a
@$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \
DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \
--no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \
-f $(THIS_MAKEFILE)
$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib
@$(SILENTCMD)echo "Checked library."
ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1)
check_lib:
else
check_lib:
@$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk
endif
$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR):
@[ -d $@ ] || mkdir -p $@
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR)
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target))
$(OUTPUT).kip : $(OUTPUT).elf
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
$(OUTPUT).nso : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a
%.npdm : %.npdm.json
@echo built ... $< $@
@npdmtool $< $@
@echo built ... $(notdir $@)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------
+134 -190
View File
@@ -8,6 +8,7 @@
*/
#include <math.h>
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@@ -23,178 +24,110 @@
#define ALIGN_PADDING 0x100000
#define CACHE_LINE_SIZE 128
#define BW_MAX_THREADS 4
struct f_data {
void (*func)(int64_t *, int64_t *, int);
int64_t *arg1;
int64_t *arg2;
int arg3;
};
typedef void (*bench_kernel_fn)(int64_t *dst, int64_t *src, long size);
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;
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 *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);
}
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_run(void) {
pthread_mutex_lock(&p_lock);
p_workers_ready = 1;
pthread_mutex_unlock(&p_lock);
pthread_cond_broadcast(&p_start);
}
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;
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));
}
for (int i = 0; i < threads; i++)
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;
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);
}
#define BW_GPR_CLOBBERS \
"x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", "x18"
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;
}
}
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_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_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 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 double gettime(void) {
@@ -203,29 +136,47 @@ static double gettime(void) {
return (double)((int64_t)tv.tv_sec * 1000000 + tv.tv_usec) / 1000000.;
}
static double bandwidth_bench_helper(bw_pool_t *pool, int threads, int64_t *dstbuf, int64_t *srcbuf, long size, bench_kernel_fn f) {
static double bandwidth_bench_helper(int threads, int64_t *dstbuf, int64_t *srcbuf, int size, void (*f)(int64_t *, int64_t *, int)) {
int i, loopcount, innerloopcount, n;
double t, speed, maxspeed;
if (!pool || !pool->ok)
return 0.;
double t, t1, t2, speed, maxspeed, s, s0, s1, s2;
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++)
t += bw_run_once(pool, f, dstbuf, srcbuf, size);
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;
}
innerloopcount *= 2;
} while (t < 0.25);
} while (t < 0.5);
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;
}
@@ -355,16 +306,13 @@ 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(&bw, threads, dstbuf, srcbuf, size, aligned_block_copy);
out->cpu_copy = bandwidth_bench_helper(threads, dstbuf, srcbuf, size, aligned_block_copy);
STEP("CPU read", 0.55f);
out->cpu_read = bandwidth_bench_helper(&bw, threads, dstbuf, srcbuf, size, aligned_block_fetch);
out->cpu_read = bandwidth_bench_helper(threads, dstbuf, srcbuf, size, aligned_block_fetch);
STEP("CPU write", 0.70f);
out->cpu_write = bandwidth_bench_helper(&bw, threads, dstbuf, srcbuf, size, aligned_block_fill);
bw_pool_free(&bw);
out->cpu_write = bandwidth_bench_helper(threads, dstbuf, srcbuf, size, aligned_block_fill);
free(poolbuf);
STEP("Latency", 0.85f);
@@ -377,10 +325,9 @@ void bench_run_full(bench_results_t *out, bench_progress_fn progress, void *user
struct bench_ctx {
int phase;
bool is_4gb;
void *buf;
void *pool;
int64_t *src;
int64_t *dst;
bw_pool_t bw;
};
bench_ctx *bench_begin(void) {
@@ -388,8 +335,7 @@ bench_ctx *bench_begin(void) {
if (!c)
return NULL;
c->is_4gb = (appletGetAppletType() == AppletType_Application);
c->buf = alloc_nonaliased_buffers((void **)&c->src, SIZE * 3, (void **)&c->dst, SIZE * 3, NULL, 0);
bw_pool_init(&c->bw, 3);
c->pool = alloc_nonaliased_buffers((void **)&c->src, SIZE * 3, (void **)&c->dst, SIZE * 3, NULL, 0);
return c;
}
@@ -403,25 +349,24 @@ 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(&c->bw, threads, c->dst, c->src, size, aligned_block_copy);
out->cpu_copy = bandwidth_bench_helper(threads, c->dst, c->src, size, aligned_block_copy);
*label = "CPU copy";
*frac = 0.45f;
break;
case 2:
out->cpu_read = bandwidth_bench_helper(&c->bw, threads, c->dst, c->src, size, aligned_block_fetch);
out->cpu_read = bandwidth_bench_helper(threads, c->dst, c->src, size, aligned_block_fetch);
*label = "CPU read";
*frac = 0.60f;
break;
case 3:
out->cpu_write = bandwidth_bench_helper(&c->bw, threads, c->dst, c->src, size, aligned_block_fill);
out->cpu_write = bandwidth_bench_helper(threads, c->dst, c->src, size, aligned_block_fill);
*label = "CPU write";
*frac = 0.75f;
break;
case 4:
bw_pool_free(&c->bw);
if (c->buf) {
free(c->buf);
c->buf = NULL;
if (c->pool) {
free(c->pool);
c->pool = NULL;
}
latency_bench(&out->l2_ns, &out->ram_ns);
*label = "Latency";
@@ -439,8 +384,7 @@ bool bench_step(bench_ctx *c, bench_results_t *out, const char **label, float *f
void bench_end(bench_ctx *c) {
if (!c)
return;
bw_pool_free(&c->bw);
if (c->buf)
free(c->buf);
if (c->pool)
free(c->pool);
free(c);
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,960 +0,0 @@
/*
* 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();
}
}
Executable → Regular
View File
File mode changed.
@@ -82,9 +82,10 @@ typedef struct {
u8 custRev;
u16 kipVersion;
bool isKipLoaded;
bool rebootRequired;
// Reserved for future use
u8 reserved[0x35A];
u8 reserved[0x359];
} HocClkContext;
typedef struct
@@ -95,7 +96,7 @@ typedef struct
};
} HocClkTitleProfileList;
#define HOCCLK_FREQ_LIST_MAX 48
#define HOCCLK_FREQ_LIST_MAX 64
#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_t8_tREFI";
return pretty ? "Low T8 - tREFI" : "low_t7_tREFI";
case KipConfigValue_read_latency_1333:
return pretty ? "1333 Read Latency" : "read_latency_1333";
@@ -572,7 +572,6 @@ 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;
@@ -585,6 +584,7 @@ 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;
+1 -1
View File
@@ -39,7 +39,7 @@ include ${TOPDIR}/lib/libultrahand/ultrahand.mk
# version control constants
#---------------------------------------------------------------------------------
#TARGET_VERSION := $(shell git describe --dirty --always --tags)
APP_VERSION := 2.6.0 # ensure to set KIP_VERSION and CUST_REV in sysmodule Makefile when updating this
APP_VERSION := 3.0.0 # ensure to set KIP_VERSION and CUST_REV in sysmodule Makefile when updating this
TARGET_VERSION := $(APP_VERSION)
#---------------------------------------------------------------------------------
+9
View File
@@ -96,6 +96,15 @@ 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() + " \"Gaea\"";
static const std::string versionStr = "Version " + getVersionString() + " \"Athena\"";
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 (with examples):",
"Options:",
" - 33MHz - 33 MHz step (ex. 1600, 1633, 1666, 1700, etc.)",
" - 66MHz - 66 MHz step (ex. 1600, 1666, 1733, etc.)",
" - 100MHz - 100 MHz step (ex. 1600, 1700, 1800, etc.)",
" - 133MHz - 66 MHz step (ex. 1600, 1733, 1866, etc.)",
" - 133MHz - 133 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",
"33 MHz step mode is not possible due to certain limitations of Horizon OS",
"This setting does not affect performance and the option you choose mostly is based on your personal taste",
"Default: 66 MHz",
};
+2 -2
View File
@@ -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" }, { 2805000000, "Absolute Max" },
{ 1963000000, "Safe Max" }, { 2397000000, "Unsafe Max" }, { 2703000000, "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" }, { 2805000000, "Absolute Max" },
{ 2397000000, "Safe Max" }, { 2499000000, "Unsafe Max" }, { 2703000000, "Absolute Max" },
};
std::map<uint32_t, std::string> cpu_freq_label_e = {
@@ -32,6 +32,7 @@
#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();
@@ -113,6 +114,17 @@ 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);
}
+16 -76
View File
@@ -925,78 +925,6 @@ 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);
}
};
@@ -1204,7 +1132,7 @@ class RamSubmenuGui : public MiscGui {
};
ValueThresholds vdd2Thresholds(IsMariko() ? 1212500 : 1237500, IsMariko() ? 1250000 : 1275000);
addConfigButton(KipConfigValue_commonEmcMemVolt, "RAM VDD2 Voltage", ValueRange(912500, IsMariko() ? 1275000 : 1350000, 12500, "mV", 1000, 1), "Voltage",
addConfigButton(KipConfigValue_commonEmcMemVolt, "RAM VDD2 Voltage", ValueRange(912500, 1350000, 12500, "mV", 1000, 1), "Voltage",
&vdd2Thresholds, emc_voltage_label, noNamedValues, false, true);
if (IsMariko()) {
@@ -1215,6 +1143,7 @@ class RamSubmenuGui : public MiscGui {
if (IsMariko()) {
std::vector<NamedValue> stepMode = {
NamedValue("33MHz", 4),
NamedValue("66MHz", 0),
NamedValue("100MHz", 1),
NamedValue("133MHz", 3), // Mantain compatability
@@ -2190,19 +2119,25 @@ class CpuSubmenuGui : public MiscGui {
{}, false, true);
ValueThresholds mCpuVoltThresholds(1160, 1180);
addConfigButton(KipConfigValue_marikoCpuMaxVolt, "CPU Max Voltage", ValueRange(1000, 1235, 5, "mV", 1), "CPU Max Voltage",
addConfigButton(KipConfigValue_marikoCpuMaxVolt, "CPU Max Voltage", ValueRange(1000, 1200, 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),
NamedValue("2805 MHz", 2805000, "Absolute Max"),
NamedValue("2397 MHz", 2397000, "Safe Max"), NamedValue("2499 MHz", 2499000, "Unsafe Max"), NamedValue("2601 MHz", 2601000), NamedValue("2703 MHz", 2703000, "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);
@@ -2265,6 +2200,11 @@ 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));
@@ -0,0 +1,276 @@
/*
* 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";
}
@@ -0,0 +1,83 @@
/*
* 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;
};
+2 -2
View File
@@ -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 := 2.6.0
KIP_VERSION := 260
TARGET_VERSION := 3.0.0
KIP_VERSION := 300
CUST_REV := 7
#---------------------------------------------------------------------------------
@@ -49,7 +49,6 @@
#include "board_volt.hpp"
#include <ipc_server.h>
#include <lockable_mutex.h>
#include "../soc/dram_mrr.hpp"
namespace board {
@@ -239,10 +238,6 @@ namespace board {
}
u8 GetDramID() {
return soc::mrr::IdentifyDramId();
}
u8 GetFuseDramId() {
return gDramID;
}
@@ -54,7 +54,6 @@ namespace board {
HocClkSocType GetSocType();
HocClkConsoleType GetConsoleType();
u8 GetDramID();
u8 GetFuseDramId();
u8 GetGpuSpeedoBracket();
bool IsDram8GB();
void SetDisplayRefreshDockedState(bool docked);
@@ -83,6 +83,24 @@ namespace fileUtils {
va_list args;
va_start(args, format);
char buff[0xfff];
int len = vsnprintf(buff, sizeof(buff), format, args);
va_end(args);
if (len < 0) {
return;
}
// Leave room for the newline + NUL.
if ((size_t)len >= sizeof(buff) - 1) {
len = sizeof(buff) - 2;
}
buff[len++] = '\n';
buff[len] = '\0';
// Debug UART log
svcOutputDebugString(buff, len);
if (g_has_initialized) {
RefreshFlags(false);
@@ -93,14 +111,11 @@ namespace fileUtils {
timespec now = {};
clock_gettime(CLOCK_REALTIME, &now);
fprintf(file, "[%luls] ", now.tv_sec - bootTimeS);
vfprintf(file, format, args);
fprintf(file, "\n");
fprintf(file, "[%luls] %s", now.tv_sec - bootTimeS, buff);
fclose(file);
}
}
}
va_end(args);
}
void WriteContextToCsv(const HocClkContext *context) {
+2 -1
View File
@@ -222,7 +222,8 @@ namespace kip {
!config::GetConfigValue(HocClkConfigValue_IsFirstLoad)) {
MigrateKipData(cust_get_cust_rev(&table), cust_get_kip_version(&table));
SetKipData();
notification::writeNotification("Horizon OC\nKIP has been updated\nPlease reboot your console");
clockManager::gContext.rebootRequired = true;
notification::writeNotification("Horizon OC\nKIP has been updated\nPlease reboot your console to use Horizon OC");
return;
}
if (config::GetConfigValue(HocClkConfigValue_IsFirstLoad) == true) {
@@ -295,6 +295,47 @@ 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);
}
@@ -562,7 +603,6 @@ 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;
@@ -741,6 +781,11 @@ 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;
@@ -759,8 +804,6 @@ namespace clockManager {
gLastTempLogNs = 0;
gLastCsvWriteNs = 0;
kip::GetKipData();
board::FuseData *fuse = board::GetFuseData();
gContext.speedos[HocClkSpeedo_CPU] = fuse->cpuSpeedo;
@@ -1,393 +0,0 @@
/*
* 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);
}
}
@@ -1,105 +0,0 @@
/*
* 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();
}
+5 -5
View File
@@ -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 0x0C
#define GPU_TRIM_SYS_GPCPLL_CFG3 0x18
#define GPU_TRIM_SYS_GPCPLL_CFG2 0x08
#define GPU_TRIM_SYS_GPCPLL_CFG3 0x0C
#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 0x100
#define GPU_TRIM_SYS_SEL_VCO 0x2C
#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 0x0000FF00u
#define GPCPLL_CFG2_SDM_DIN_NEW_MASK 0x007FFF00u
#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(31)
#define NDIV_SLOWDOWN_EN_DYNRAMP BIT(23)
#define DYNRAMP_DONE_SYNCED BIT(24)
File diff suppressed because it is too large. Load diff
+7 -9
View File
@@ -30,15 +30,13 @@ 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/"
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/"
echo
echo "*** Compiling exosphere ***"
+74 -14
View File
@@ -2,7 +2,10 @@
EXT=0
LDR_MAKE="nx_release"
LDR_SET=0
NO_EXO=0
JOBS=""
UART_LOGGING=0
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
DIST_DIR="$ROOT_DIR/dist"
@@ -14,10 +17,21 @@ while [ $# -gt 0 ]; do
;;
--ldr=*)
LDR_MAKE="${1#*=}"
LDR_SET=1
;;
-u|--uart)
UART_LOGGING=1
;;
--no-exo)
NO_EXO=1
;;
-j)
shift
JOBS="$1"
;;
-j*)
JOBS="${1#-j}"
;;
*)
echo "Unknown option: $1"
exit 1
@@ -26,6 +40,10 @@ while [ $# -gt 0 ]; do
shift
done
if [ "$UART_LOGGING" -eq 1 ] && [ "$LDR_SET" -eq 0 ]; then
LDR_MAKE="nx_audit"
fi
LDR_BUILD_PATH="${LDR_MAKE#nx_}"
echo
@@ -37,8 +55,24 @@ if [ "$NO_EXO" -eq 1 ]; then
echo "NO_EXO = 1"
fi
if [ "$UART_LOGGING" -eq 1 ]; then
echo "UART_LOGGING = 1 (loader build: $LDR_MAKE, UART logging enabled)"
fi
CORES="$(nproc --all)"
echo "CORES: $CORES"
JOBS="${JOBS:-$CORES}"
echo "JOBS: $JOBS"
if command -v ccache >/dev/null 2>&1; then
export ATMOSPHERE_CCACHE="ccache"
export CCACHE_BASEDIR="$ROOT_DIR"
export CCACHE_SLOPPINESS="time_macros,include_file_mtime,include_file_ctime,pch_defines,locale"
export CCACHE_MAXSIZE="10G"
echo "CCACHE: enabled ($(ccache --version | head -n1))"
else
echo "CCACHE: not found, building without it"
fi
SRC="Source/Atmosphere/stratosphere/loader/"
@@ -61,29 +95,49 @@ mkdir -p "$DEST"
echo
echo "*** Patching loader ***"
cp -vr "$SRC"/. "$DEST"/
echo
if [ "$NO_EXO" -eq 0 ]; then
echo
echo "*** Patching exosphere ***"
EXO_SRC="Source/Atmosphere-Patches"
EXO_DEST="build/atmosphere/exosphere/program/source/smc"
LIBEXO_DEST="build/atmosphere/libraries/libexosphere/include/exosphere/secmon"
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/"
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/"
fi
CCACHE_MKS="build/atmosphere/libraries/config/common.mk
build/atmosphere/libraries/config/templates/stratosphere.mk
build/atmosphere/libraries/libstratosphere/libstratosphere.mk"
for CCACHE_MK in $CCACHE_MKS; do
if ! grep -q "ATMOSPHERE_CCACHE" "$CCACHE_MK"; then
echo
echo "*** Patching $CCACHE_MK for ccache ***"
cat >> "$CCACHE_MK" <<'EOF'
ifneq ($(strip $(ATMOSPHERE_CCACHE)),)
ifneq ($(firstword $(CC)),$(ATMOSPHERE_CCACHE))
export CC := $(ATMOSPHERE_CCACHE) $(CC)
endif
ifneq ($(firstword $(CXX)),$(ATMOSPHERE_CCACHE))
export CXX := $(ATMOSPHERE_CCACHE) $(CXX)
endif
endif
EOF
fi
done
echo
echo "*** Compiling loader ***"
cd build/atmosphere/stratosphere/loader || exit 1
make -j$CORES "$LDR_MAKE"
make -j$JOBS HOC_UART_LOG=$UART_LOGGING "$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
@@ -92,11 +146,17 @@ if [ "$NO_EXO" -eq 0 ]; then
echo
echo "*** Compiling exosphere ***"
cd build/atmosphere/exosphere
make -j$CORES
make -j$JOBS
cd "$ROOT_DIR"
cp -v build/atmosphere/exosphere/out/nintendo_nx_arm64_armv8a/release/exosphere.bin dist/atmosphere/exosphere.bin
fi
if [ -n "$ATMOSPHERE_CCACHE" ]; then
echo
echo "*** ccache stats ***"
ccache --show-stats
fi
cd Source/hoc-clk/
./build.sh
cp -r dist/ ../../
@@ -126,7 +186,7 @@ if [ "$EXT" -eq 1 ]; then
cd hekate/
echo
echo "*** Compiling custom Hekate ***"
make -j$CORES
make -j$JOBS
echo
mkdir -p "$DIST_DIR/bootloader/sys/"
@@ -137,7 +197,7 @@ if [ "$EXT" -eq 1 ]; then
cd "$ROOT_DIR"/Source/Benchmark-Toolbox
echo
echo "*** Compiling Benchmark-Toolbox ***"
make -j$CORES
make -j$JOBS
cp -v Benchmark-Toolbox.nro "$DIST_DIR"/switch/Benchmark-Toolbox.nro
fi