diff --git a/Source/Atmosphere-Patches/secmon_smc_power_management.cpp b/Source/Atmosphere-Patches/secmon_smc_power_management.cpp
new file mode 100644
index 00000000..4d2214da
--- /dev/null
+++ b/Source/Atmosphere-Patches/secmon_smc_power_management.cpp
@@ -0,0 +1,571 @@
+/*
+ * Copyright (c) Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#include
+#include "../secmon_cache.hpp"
+#include "../secmon_cpu_context.hpp"
+#include "../secmon_error.hpp"
+#include "../secmon_misc.hpp"
+#include "secmon_smc_power_management.hpp"
+#include "secmon_smc_se_lock.hpp"
+
+#include "sc7fw_bin.h"
+
+namespace ams::secmon {
+
+ /* Declare assembly functionality. */
+ void *GetCoreExceptionStackVirtual();
+
+}
+
+namespace ams::secmon::smc {
+
+ /* Declare assembly power-management functionality. */
+ void PivotStackAndInvoke(void *stack, void (*function)());
+ void FinalizePowerOff();
+
+ namespace {
+
+ constexpr inline const uintptr_t PMC = MemoryRegionVirtualDevicePmc.GetAddress();
+ constexpr inline const uintptr_t APB_MISC = MemoryRegionVirtualDeviceApbMisc.GetAddress();
+ constexpr inline const uintptr_t GPIO = MemoryRegionVirtualDeviceGpio.GetAddress();
+ constexpr inline const uintptr_t CLK_RST = MemoryRegionVirtualDeviceClkRst.GetAddress();
+ constexpr inline const uintptr_t EVP = secmon::MemoryRegionVirtualDeviceExceptionVectors.GetAddress();
+ constexpr inline const uintptr_t FLOW_CTLR = MemoryRegionVirtualDeviceFlowController.GetAddress();
+ constexpr inline const uintptr_t AHB_ARBC = MemoryRegionVirtualDeviceSystem.GetAddress();
+
+ constexpr inline uintptr_t CommonSmcStackTop = MemoryRegionVirtualTzramVolatileData.GetEndAddress() - (0x80 * (NumCores - 1));
+
+ enum PowerStateType {
+ PowerStateType_StandBy = 0,
+ PowerStateType_PowerDown = 1,
+ };
+
+ enum PowerStateId {
+ PowerStateId_Sc7 = 27,
+ };
+
+ /* http://infocenter.arm.com/help/topic/com.arm.doc.den0022d/Power_State_Coordination_Interface_PDD_v1_1_DEN0022D.pdf Page 46 */
+ struct SuspendCpuPowerState {
+ using StateId = util::BitPack32::Field< 0, 16, PowerStateId>;
+ using StateType = util::BitPack32::Field<16, 1, PowerStateType>;
+ using PowerLevel = util::BitPack32::Field<24, 2, u32>;
+ };
+
+ constinit bool g_charger_hi_z_mode_enabled = false;
+
+ constinit const reg::BitsMask CpuPowerGateStatusMasks[NumCores] = {
+ PMC_REG_BITS_MASK(PWRGATE_STATUS_CE0),
+ PMC_REG_BITS_MASK(PWRGATE_STATUS_CE1),
+ PMC_REG_BITS_MASK(PWRGATE_STATUS_CE2),
+ PMC_REG_BITS_MASK(PWRGATE_STATUS_CE3),
+ };
+
+ constinit const APBDEV_PMC_PWRGATE_TOGGLE_PARTID CpuPowerGateTogglePartitionIds[NumCores] = {
+ APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE0,
+ APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE1,
+ APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE2,
+ APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE3,
+ };
+
+ bool IsCpuPoweredOn(const reg::BitsMask mask) {
+ return reg::HasValue(PMC + APBDEV_PMC_PWRGATE_STATUS, REG_BITS_VALUE_FROM_MASK(mask, APBDEV_PMC_PWRGATE_STATUS_STATUS_ON));
+ }
+
+ void PowerOnCpu(const reg::BitsMask mask, u32 toggle_partid) {
+ /* If the cpu is already on, we have nothing to do. */
+ if (IsCpuPoweredOn(mask)) {
+ return;
+ }
+
+ /* Wait until nothing is being powergated. */
+ int timeout = 5000;
+ while (true) {
+ if (reg::HasValue(PMC + APBDEV_PMC_PWRGATE_TOGGLE, PMC_REG_BITS_ENUM(PWRGATE_TOGGLE_START, DISABLE))) {
+ break;
+ }
+
+ util::WaitMicroSeconds(1);
+ if ((--timeout) < 0) {
+ /* NOTE: Nintendo doesn't do any error handling here... */
+ return;
+ }
+ }
+
+ /* Toggle on the cpu partition. */
+ reg::Write(PMC + APBDEV_PMC_PWRGATE_TOGGLE, PMC_REG_BITS_ENUM (PWRGATE_TOGGLE_START, ENABLE),
+ PMC_REG_BITS_VALUE(PWRGATE_TOGGLE_PARTID, toggle_partid));
+
+ /* Wait up to 5000 us for the powergate to complete. */
+ timeout = 5000;
+ while (true) {
+ if (IsCpuPoweredOn(mask)) {
+ break;
+ }
+
+ util::WaitMicroSeconds(1);
+ if ((--timeout) < 0) {
+ /* NOTE: Nintendo doesn't do any error handling here... */
+ return;
+ }
+ }
+ }
+
+ void ResetCpu(int which_core) {
+ reg::Write(CLK_RST + CLK_RST_CONTROLLER_RST_CPUG_CMPLX_SET, REG_BITS_VALUE(which_core + 0x00, 1, 1), /* CPURESETn */
+ REG_BITS_VALUE(which_core + 0x10, 1, 1)); /* CORERESETn */
+ }
+
+ void StartCpu(int which_core) {
+ reg::Write(CLK_RST + CLK_RST_CONTROLLER_RST_CPUG_CMPLX_CLR, REG_BITS_VALUE(which_core + 0x00, 1, 1), /* CPURESETn */
+ REG_BITS_VALUE(which_core + 0x10, 1, 1)); /* CORERESETn */
+ }
+
+ void PowerOffCpuImpl() {
+ /* Get the current core id. */
+ const auto core_id = hw::GetCurrentCoreId();
+
+ /* Configure the flow controller to prepare for shutting down the current core. */
+ flow::SetCpuCsr(core_id, FLOW_CTLR_CPUN_CSR_ENABLE_EXT_POWERGATE_CPU_ONLY);
+ flow::SetHaltCpuEvents(core_id, false);
+ flow::SetCc4Ctrl(core_id, 0);
+
+ /* Save the core's context for restoration on next power-on. */
+ SaveDebugRegisters();
+ SetCoreOff();
+
+ /* Ensure there are no pending memory transactions prior to our power-down. */
+ FlushEntireDataCache();
+
+ /* Finalize our powerdown and wait for an interrupt. */
+ FinalizePowerOff();
+ }
+
+ void ValidateSocStateForSuspend() {
+ /* Validate that all other cores are off. */
+ AMS_ABORT_UNLESS(reg::HasValue(PMC + APBDEV_PMC_PWRGATE_STATUS, PMC_REG_BITS_VALUE(PWRGATE_STATUS_CE123, 0)));
+
+ /* 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 key_source;
+ GenerateCryptographicallyRandomBytes(key_source, se::AesBlockSize);
+
+ const u32 * const key_source_32 = reinterpret_cast(static_cast(key_source));
+
+ /* Ensure that the key source registers are not locked. */
+ AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_KeySourceReadWrite) != pmc::LockState::Locked);
+
+ /* Write the key source, lock writes to the key source, and verify that the key source is write-locked. */
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH24, key_source_32[0]);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH25, key_source_32[1]);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH26, key_source_32[2]);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH27, key_source_32[3]);
+ pmc::LockSecureRegister(pmc::SecureRegister_KeySourceWrite);
+ AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_KeySourceWrite) == pmc::LockState::Locked);
+
+ /* Verify the key source is correct in registers, and read-lock the key source registers. */
+ AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH24) == key_source_32[0]);
+ AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH25) == key_source_32[1]);
+ AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH26) == key_source_32[2]);
+ AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH27) == key_source_32[3]);
+ pmc::LockSecureRegister(pmc::SecureRegister_KeySourceRead);
+
+ /* Ensure that the key source registers are locked. */
+ AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_KeySourceReadWrite) == pmc::LockState::Locked);
+
+ /* Generate a random kek into keyslot 2. */
+ se::SetRandomKey(pkg1::AesKeySlot_TzramSaveKek);
+
+ /* Verify that the se is in a validate state, context save, and validate again. */
+ {
+ se::ValidateErrStatus();
+ ON_SCOPE_EXIT { se::ValidateErrStatus(); };
+
+ {
+ /* Transition to non-secure mode for the duration of the context save operation. */
+ se::SetSecure(false);
+ ON_SCOPE_EXIT { se::SetSecure(true); };
+
+ /* Get a pointer to the context storage. */
+ se::Context * const context = MemoryRegionVirtualDramSecureDataStoreSecurityEngineState.GetPointer();
+ static_assert(MemoryRegionVirtualDramSecureDataStoreSecurityEngineState.GetSize() == sizeof(*context));
+
+ /* Save the context. */
+ se::SaveContext(context);
+
+ /* Ensure that the cpu sees consistent data. */
+ hw::FlushDataCache(context, sizeof(*context));
+ hw::DataSynchronizationBarrierInnerShareable();
+
+ /* Write the context pointer to pmc scratch, so that the bootrom will restore it on wake. */
+ reg::Write(PMC + APBDEV_PMC_SCRATCH43, MemoryRegionPhysicalDramSecureDataStoreSecurityEngineState.GetAddress());
+ }
+ }
+
+ /* Clear keyslot 3, and then derive the save key. */
+ se::ClearAesKeySlot(pkg1::AesKeySlot_TzramSaveKey);
+ se::SetEncryptedAesKey256(pkg1::AesKeySlot_TzramSaveKey, pkg1::AesKeySlot_TzramSaveKek, key_source, se::AesBlockSize);
+
+ /* Declare a temporary block to be used as both iv and mac. */
+ u32 temp_block[se::AesBlockSize / sizeof(u32)] = {};
+
+ /* Ensure that the SE sees consistent data for tzram. */
+ const void * const tzram_save_src = MemoryRegionVirtualTzramReadOnlyAlias.GetPointer() + MemoryRegionVirtualTzramVolatileData.GetSize() + MemoryRegionVirtualTzramVolatileStack.GetSize();
+ void * const tzram_save_dst = MemoryRegionVirtualIramSc7Work.GetPointer();
+ constexpr size_t TzramSaveSize = MemoryRegionVirtualDramSecureDataStoreTzram.GetSize();
+
+ hw::FlushDataCache(tzram_save_src, TzramSaveSize);
+ hw::FlushDataCache(tzram_save_dst, TzramSaveSize);
+ hw::DataSynchronizationBarrierInnerShareable();
+
+ /* Encrypt tzram using our random key. */
+ se::EncryptAes256Cbc(tzram_save_dst, TzramSaveSize, pkg1::AesKeySlot_TzramSaveKey, tzram_save_src, TzramSaveSize, temp_block, se::AesBlockSize);
+ hw::FlushDataCache(tzram_save_dst, TzramSaveSize);
+ hw::DataSynchronizationBarrierInnerShareable();
+
+ /* Copy the data from work space to the secure storage destination. */
+ void * const tzram_store_dst = MemoryRegionVirtualDramSecureDataStoreTzram.GetPointer();
+ std::memcpy(tzram_store_dst, tzram_save_dst, TzramSaveSize);
+ hw::FlushDataCache(tzram_store_dst, TzramSaveSize);
+ hw::DataSynchronizationBarrierInnerShareable();
+
+ /* Compute cmac of tzram into our temporary block. */
+ se::ComputeAes256Cmac(temp_block, se::AesBlockSize, pkg1::AesKeySlot_TzramSaveKey, tzram_save_src, TzramSaveSize);
+
+ /* Ensure that the cmac registers are not locked. */
+ AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_CmacReadWrite) != pmc::LockState::Locked);
+
+ /* Write the cmac, lock writes to the cmac, and verify that the cmac is write-locked. */
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH112, temp_block[0]);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH113, temp_block[1]);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH114, temp_block[2]);
+ reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH115, temp_block[3]);
+ pmc::LockSecureRegister(pmc::SecureRegister_CmacWrite);
+ AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_CmacWrite) == pmc::LockState::Locked);
+
+ /* Verify the key source is correct in registers, and read-lock the key source registers. */
+ AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH112) == temp_block[0]);
+ AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH113) == temp_block[1]);
+ AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH114) == temp_block[2]);
+ AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH115) == temp_block[3]);
+ pmc::LockSecureRegister(pmc::SecureRegister_CmacRead);
+
+ /* Ensure that the key source registers are locked. */
+ AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_CmacReadWrite) == pmc::LockState::Locked);
+ }
+
+ void SaveSecureContextForMariko() {
+ /* Save security engine context to TZRAM SE carveout (inaccessible to cpu). */
+ se::SaveContextAutomatic();
+
+ /* Save TZRAM to shadow-TZRAM in always-on power domain. */
+ se::SaveTzramAutomatic();
+ }
+
+ void SaveSecureContext() {
+ /* Save the appropriate secure context. */
+ const auto soc_type = GetSocType();
+ if (soc_type == fuse::SocType_Erista) {
+ SaveSecureContextForErista();
+ } else /* if (soc_type == fuse::SocType_Mariko) */ {
+ SaveSecureContextForMariko();
+ }
+
+ /* Save the debug code. */
+ #if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING)
+ {
+ const void * const debug_code_src = MemoryRegionVirtualDebugCode.GetPointer();
+ void * const debug_code_dst = MemoryRegionVirtualDramDebugDataStore.GetPointer();
+ std::memcpy(debug_code_dst, debug_code_src, MemoryRegionVirtualDebugCode.GetSize());
+ hw::FlushDataCache(debug_code_dst, MemoryRegionVirtualDebugCode.GetSize());
+ hw::DataSynchronizationBarrierInnerShareable();
+ }
+ #endif
+ }
+
+ void LoadAndStartSc7BpmpFirmware() {
+ /* Set BPMP reset. */
+ reg::Write(CLK_RST + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SET_SET_COP_RST, ENABLE));
+
+ /* Set the PMC as insecure, so that the BPMP firmware can access it. */
+ reg::ReadWrite(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0, SLAVE_SECURITY_REG_BITS_ENUM(0, PMC, DISABLE));
+
+ /* Set the exception vectors for the bpmp. RESET should point to RESET, all others should point to generic exception/panic. */
+ constexpr const u32 Sc7FirmwareResetVector = static_cast(MemoryRegionPhysicalIramSc7Firmware.GetAddress() + 0x0);
+ constexpr const u32 Sc7FirmwarePanicVector = static_cast(MemoryRegionPhysicalIramSc7Firmware.GetAddress() + 0x4);
+
+ reg::Write(EVP + EVP_COP_RESET_VECTOR, Sc7FirmwareResetVector);
+ reg::Write(EVP + EVP_COP_UNDEF_VECTOR, Sc7FirmwarePanicVector);
+ reg::Write(EVP + EVP_COP_SWI_VECTOR, Sc7FirmwarePanicVector);
+ reg::Write(EVP + EVP_COP_PREFETCH_ABORT_VECTOR, Sc7FirmwarePanicVector);
+ reg::Write(EVP + EVP_COP_DATA_ABORT_VECTOR, Sc7FirmwarePanicVector);
+ reg::Write(EVP + EVP_COP_RSVD_VECTOR, Sc7FirmwarePanicVector);
+ reg::Write(EVP + EVP_COP_IRQ_VECTOR, Sc7FirmwarePanicVector);
+ reg::Write(EVP + EVP_COP_FIQ_VECTOR, Sc7FirmwarePanicVector);
+
+ /* Disable activity monitor bpmp monitoring, so that we don't panic upon bpmp wake. */
+ actmon::StopMonitoringBpmp();
+
+ /* Load the bpmp firmware. */
+ void * const sc7fw_load_address = MemoryRegionVirtualIramSc7Firmware.GetPointer();
+ std::memcpy(sc7fw_load_address, sc7fw_bin, sc7fw_bin_size);
+ hw::FlushDataCache(sc7fw_load_address, sc7fw_bin_size);
+ hw::DataSynchronizationBarrierInnerShareable();
+
+ /* Ensure that the bpmp firmware was loaded. */
+ AMS_ABORT_UNLESS(crypto::IsSameBytes(sc7fw_load_address, sc7fw_bin, sc7fw_bin_size));
+
+ /* Clear BPMP reset. */
+ reg::Write(CLK_RST + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_CLR_CLR_COP_RST, ENABLE));
+
+ /* Start the bpmp. */
+ reg::Write(FLOW_CTLR + FLOW_CTLR_HALT_COP_EVENTS, FLOW_REG_BITS_ENUM(HALT_COP_EVENTS_MODE, FLOW_MODE_NONE));
+ }
+
+ void SaveSecureContextAndSuspend() {
+ /* Ensure there are no pending memory transactions before we continue */
+ FlushEntireDataCache();
+ hw::DataSynchronizationBarrierInnerShareable();
+
+ /* Save all secure context (security engine state + tzram). */
+ SaveSecureContext();
+
+ /* Load and start the sc7 firmware on the bpmp. */
+ if (GetTargetFirmware() >= TargetFirmware_2_0_0) {
+ LoadAndStartSc7BpmpFirmware();
+ }
+
+ /* Log our suspension. */
+ /* NOTE: Nintendo only does this on dev, but we will always do it. */
+ if (true /* !pkg1::IsProduction() */) {
+ log::Initialize(secmon::GetLogPort(), secmon::GetLogBaudRate(), secmon::GetLogFlags());
+ log::SendText("OYASUMI\n", 8);
+ log::Flush();
+ }
+
+ /* If we're on erista, configure the bootrom to allow our custom warmboot firmware. */
+ if (GetSocType() == fuse::SocType_Erista) {
+ reg::Write(PMC + APBDEV_PMC_SCRATCH31, 0x2202E012);
+ reg::Write(PMC + APBDEV_PMC_SCRATCH32, 0x6001DC28);
+ }
+
+ /* Finalize our powerdown and wait for an interrupt. */
+ FinalizePowerOff();
+ }
+
+ SmcResult SuspendCpuImpl(SmcArguments &args) {
+ /* Decode arguments. */
+ const util::BitPack32 power_state = { static_cast(args.r[1]) };
+ const uintptr_t entry_point = args.r[2];
+ const uintptr_t context_id = args.r[3];
+
+ const auto state_type = power_state.Get();
+ const auto state_id = power_state.Get();
+
+ const auto core_id = hw::GetCurrentCoreId();
+
+ /* Validate arguments. */
+ SMC_R_UNLESS(state_type == PowerStateType_PowerDown, PsciDenied);
+ SMC_R_UNLESS(state_id == PowerStateId_Sc7, PsciDenied);
+
+ /* Orchestrate charger transition to Hi-Z mode if needed. */
+ if (IsChargerHiZModeEnabled()) {
+ /* Ensure we can do comms over i2c-1. */
+ clkrst::EnableI2c1Clock();
+
+ /* If the charger isn't in hi-z mode, perform a transition. */
+ if (!charger::IsHiZMode()) {
+ charger::EnterHiZMode();
+
+ /* Wait up to 50ms for the transition to complete. */
+ const auto start_time = util::GetMicroSeconds();
+ auto current_time = start_time;
+ while ((current_time - start_time) <= 50'000) {
+ if (auto intr_status = reg::Read(GPIO + 0x634); (intr_status & 1) == 0) {
+ /* Wait 256 us to ensure the transition completes. */
+ util::WaitMicroSeconds(256);
+ break;
+ }
+ current_time = util::GetMicroSeconds();
+ }
+ }
+
+ /* Disable i2c-1, since we're done communicating over it. */
+ clkrst::DisableI2c1Clock();
+ }
+
+ /* Enable wake event detection. */
+ pmc::EnableWakeEventDetection();
+
+ /* Ensure that i2c-5 is usable for communicating with the pmic. */
+ clkrst::EnableI2c5Clock();
+ i2c::Initialize(i2c::Port_5);
+
+ /* Orchestrate sleep entry with the pmic. */
+ pmic::EnableSleep();
+
+ /* Ensure that the soc is in a state valid for us to suspend. */
+ if (GetTargetFirmware() >= TargetFirmware_2_0_0) {
+ ValidateSocStateForSuspend();
+ }
+
+ /* Configure the pmc for sc7 entry. */
+ pmc::ConfigureForSc7Entry();
+
+ /* Configure the flow controller for sc7 entry. */
+ flow::SetCc4Ctrl(core_id, 0);
+ flow::SetHaltCpuEvents(core_id, false);
+ flow::ClearL2FlushControl();
+ flow::SetCpuCsr(core_id, FLOW_CTLR_CPUN_CSR_ENABLE_EXT_POWERGATE_CPU_TURNOFF_CPURAIL);
+
+ /* Save the entry context. */
+ SetEntryContext(core_id, entry_point, context_id);
+
+ /* Configure the cpu context for reset. */
+ SaveDebugRegisters();
+ SetCoreOff();
+ SetResetExpected(true);
+
+ /* Switch to use the common smc stack (all other cores are off), and perform suspension. */
+ PivotStackAndInvoke(reinterpret_cast(CommonSmcStackTop), SaveSecureContextAndSuspend);
+
+ /* This code will never be reached. */
+ __builtin_unreachable();
+ }
+
+ }
+
+ void PowerOffCpu() {
+ /* Get the current core id. */
+ const auto core_id = hw::GetCurrentCoreId();
+
+ /* Note that we're expecting a reset for the current core. */
+ SetResetExpected(true);
+
+ /* If we're on the final core, shut down directly. Otherwise, invoke with special stack. */
+ if (core_id == NumCores - 1) {
+ PowerOffCpuImpl();
+ } else {
+ PivotStackAndInvoke(GetCoreExceptionStackVirtual(), PowerOffCpuImpl);
+ }
+
+ /* This code will never be reached. */
+ __builtin_unreachable();
+ }
+
+ SmcResult SmcPowerOffCpu(SmcArguments &args) {
+ AMS_UNUSED(args);
+
+ PowerOffCpu();
+ }
+
+ SmcResult SmcPowerOnCpu(SmcArguments &args) {
+ /* Get and validate the core to power on. */
+ const int which_core = args.r[1];
+ SMC_R_UNLESS(0 <= which_core && which_core < NumCores, PsciInvalidParameters);
+
+ /* Ensure the core isn't already on. */
+ SMC_R_UNLESS(!IsCoreOn(which_core), PsciAlreadyOn);
+
+ /* Save the entry context. */
+ SetEntryContext(which_core, args.r[2], args.r[3]);
+
+ /* Reset the cpu. */
+ ResetCpu(which_core);
+
+ /* Turn on the core. */
+ PowerOnCpu(CpuPowerGateStatusMasks[which_core], CpuPowerGateTogglePartitionIds[which_core]);
+
+ /* Start the core. */
+ StartCpu(which_core);
+
+ return SmcResult::PsciSuccess;
+ }
+
+ SmcResult SmcSuspendCpu(SmcArguments &args) {
+ return LockSecurityEngineAndInvoke(args, SuspendCpuImpl);
+ }
+
+ bool IsChargerHiZModeEnabled() {
+ return g_charger_hi_z_mode_enabled;
+ }
+
+ void SetChargerHiZModeEnabled(bool en) {
+ g_charger_hi_z_mode_enabled = en;
+ }
+
+}
diff --git a/Source/hoc-clk/bpmpfw/out/bpmpfw.bin b/Source/hoc-clk/bpmpfw/out/bpmpfw.bin
index 8f7b8bdf..c6d35308 100644
Binary files a/Source/hoc-clk/bpmpfw/out/bpmpfw.bin and b/Source/hoc-clk/bpmpfw/out/bpmpfw.bin differ
diff --git a/Source/hoc-clk/bpmpfw/source/main.cpp b/Source/hoc-clk/bpmpfw/source/main.cpp
index bcbb764d..e08baa22 100644
--- a/Source/hoc-clk/bpmpfw/source/main.cpp
+++ b/Source/hoc-clk/bpmpfw/source/main.cpp
@@ -3,7 +3,5 @@
extern "C" void main() {
UartPuts("[bpmpfw]: Hello from BPMP-FW\n");
- for (;;) {
- FlowCtlrHaltUsec(200);
- }
+ for (;;);
}
diff --git a/Source/hoc-clk/bpmpfw/source/regs.hpp b/Source/hoc-clk/bpmpfw/source/regs.hpp
index dc7d3761..00efd9eb 100644
--- a/Source/hoc-clk/bpmpfw/source/regs.hpp
+++ b/Source/hoc-clk/bpmpfw/source/regs.hpp
@@ -13,7 +13,7 @@ constexpr u32 FlowCtlrHaltCopEvents = 0x004;
constexpr u32 HaltModeWaitEvent = (2u << 29);
constexpr u32 HaltUsec = (0u << 24);
-inline void FlowCtlrHaltUsec(u32 usec) {
+[[maybe_unused]] inline void FlowCtlrHaltUsec(u32 usec) {
MMIO32(FlowCtlrBase + FlowCtlrHaltCopEvents) = HaltModeWaitEvent | HaltUsec | (usec & 0xFFFFFFu);
}
diff --git a/Source/hoc-clk/sysmodule/src/mapping/mem_map.cpp b/Source/hoc-clk/sysmodule/src/mapping/mem_map.cpp
index 08d3a9d3..f0d52b4d 100644
--- a/Source/hoc-clk/sysmodule/src/mapping/mem_map.cpp
+++ b/Source/hoc-clk/sysmodule/src/mapping/mem_map.cpp
@@ -68,17 +68,25 @@ Result SmcCopyToIram(uintptr_t dest, const void *src, u32 size) {
}
bool IsPatchedExosphere() {
- constexpr u64 EvpPhysBase = 0x6000F000;
- constexpr u64 EvpCopResetVector = 0x200;
+ static bool checked = false;
+ static bool patched = false;
- SecmonArgs args = {};
- args.X[0] = 0xF0000002;
- args.X[1] = EvpPhysBase + EvpCopResetVector;
- args.X[2] = 0;
- args.X[3] = 0;
- svcCallSecureMonitor(&args);
+ if (!checked) {
+ constexpr u64 EvpPhysBase = 0x6000F000;
+ constexpr u64 EvpCopResetVector = 0x200;
- return args.X[0] == 0;
+ SecmonArgs args = {};
+ args.X[0] = 0xF0000002;
+ args.X[1] = EvpPhysBase + EvpCopResetVector;
+ args.X[2] = 0;
+ args.X[3] = 0;
+ svcCallSecureMonitor(&args);
+
+ patched = (args.X[0] == 0);
+ checked = true;
+ }
+
+ return patched;
}
u32 SmcReadWriteRegister(u64 phys_addr, u32 mask, u32 value) {
diff --git a/Source/hoc-clk/sysmodule/src/mgr/clock_manager.cpp b/Source/hoc-clk/sysmodule/src/mgr/clock_manager.cpp
index c035c11e..cfe53c29 100644
--- a/Source/hoc-clk/sysmodule/src/mgr/clock_manager.cpp
+++ b/Source/hoc-clk/sysmodule/src/mgr/clock_manager.cpp
@@ -30,6 +30,7 @@
#include
#include "../board/board.hpp"
+#include "../bpmp/bpmp.hpp"
#include "../display/aula.hpp"
#include "../display/display_refresh_rate.hpp"
#include "../file/config.hpp"
@@ -40,6 +41,7 @@
#include "../hos/process_management.hpp"
#include "../i2c/i2cDrv.h"
#include "../ipc/ipc_service.hpp"
+#include "../mapping/mem_map.hpp"
#include "../soc/gm20b.hpp"
#include "../util/lockable_mutex.h"
#include "clock_manager.hpp"
@@ -355,6 +357,22 @@ namespace clockManager {
if (++tick > 10) {
tick = 0;
+ // Restart BPMP after sleep.
+ if (IsPatchedExosphere()) {
+ constexpr u32 FlowCtlrPhysBase = 0x60007000;
+ constexpr u32 FlowCtlrHaltCopEvents = 0x004;
+ constexpr u32 HaltCopEventsModeShift = 29;
+ constexpr u32 HaltCopEventsModeStop = 2;
+
+ u32 haltReg = SmcReadWriteRegister(FlowCtlrPhysBase + FlowCtlrHaltCopEvents, 0, 0);
+ if ((haltReg >> HaltCopEventsModeShift) == HaltCopEventsModeStop) {
+ Result rc = bpmp::StartBpmfwExecution();
+ if (R_FAILED(rc)) {
+ fileUtils::LogLine("[bpmp] restart after sleep failed: 0x%x", rc);
+ }
+ }
+ }
+
if (config::GetConfigValue(HocClkConfigValue_BatteryChargeCurrent)) {
I2c_Bq24193_SetFastChargeCurrentLimit(config::GetConfigValue(HocClkConfigValue_BatteryChargeCurrent));
}
diff --git a/build.sh b/build.sh
index 31712ab7..edceb3b6 100755
--- a/build.sh
+++ b/build.sh
@@ -120,6 +120,7 @@ if [ "$NO_EXO" -eq 0 ]; then
cp -v "$EXO_SRC/secmon_exception_vectors_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_smc_power_management.cpp" "$EXO_DEST/"
cp -v "$EXO_SRC/secmon_memory_layout.hpp" "$LIBEXO_DEST/"
fi