Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5c1f0faa2 | ||
|
|
3baa3e7b2e | ||
|
|
da7f922782 | ||
|
|
af8912321c | ||
|
|
363d1f0e3c | ||
|
|
7e4b4b5b1d | ||
|
|
b8c006c702 | ||
|
|
b7d0e3c9fa | ||
|
|
1fc9afd6d1 | ||
|
|
88ca2e8688 | ||
|
|
02bbdcdb57 | ||
|
|
139721761a | ||
|
|
88f0dda772 | ||
|
|
38ab15a243 | ||
|
|
6e4a28ee67 | ||
|
|
0cf4927c47 | ||
|
|
bfb2009dab | ||
|
|
8c54df039f | ||
|
|
9eb53f5a0b | ||
|
|
a6f1732e1d | ||
|
|
6f4b58b83e | ||
|
|
9d12aa5cb1 | ||
|
|
ff4fd37cdc | ||
|
|
618fae2532 | ||
|
|
5e9ede09a1 | ||
|
|
40968377c5 |
No files matched your search
@@ -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
|
||||
@@ -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
|
||||
@@ -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 +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)));
|
||||
|
||||
}
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
File renamed without changes.
@@ -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);
|
||||
|
||||
}
|
||||
+16
-257
@@ -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);
|
||||
|
||||
}
|
||||
+204
-204
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+24
-24
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+131
-4
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+17
-8
@@ -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);
|
||||
|
||||
}
|
||||
+49
-560
@@ -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);
|
||||
|
||||
}
|
||||
+47
-47
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+31
-31
@@ -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
|
||||
#---------------------------------------------------------------------------------------
|
||||
@@ -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.
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
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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
@@ -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 ***"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user