mirror of
https://github.com/Atmosphere-NX/Atmosphere-libs.git
synced 2025-11-17 18:11:17 +01:00
NOTE: This work is not yet fully complete; kernel is done, but it was taking an exceedingly long time to get through libstratosphere. Thus, I've temporarily added -Wno-error=unused-result for libstratosphere/stratosphere. All warnings should be fixed to do the same thing Nintendo does as relevant, but this is taking a phenomenally long time and is not actually the most important work to do, so it can be put off for some time to prioritize other tasks for 21.0.0 support.
640 lines
25 KiB
C++
640 lines
25 KiB
C++
/*
|
|
* 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 <mesosphere.hpp>
|
|
#include "kern_k_sleep_manager.hpp"
|
|
#include "kern_secure_monitor.hpp"
|
|
#include "kern_lps_driver.hpp"
|
|
|
|
namespace ams::kern::init {
|
|
|
|
void StartOtherCore(const ams::kern::init::KInitArguments *init_args);
|
|
|
|
}
|
|
|
|
namespace ams::kern::board::nintendo::nx {
|
|
|
|
namespace {
|
|
|
|
/* Struct representing registers saved on wake/sleep. */
|
|
class SavedSystemRegisters {
|
|
private:
|
|
u64 elr_el1;
|
|
u64 sp_el0;
|
|
u64 spsr_el1;
|
|
u64 daif;
|
|
u64 cpacr_el1;
|
|
u64 vbar_el1;
|
|
u64 csselr_el1;
|
|
u64 cntp_ctl_el0;
|
|
u64 cntp_cval_el0;
|
|
u64 cntkctl_el1;
|
|
u64 tpidr_el0;
|
|
u64 tpidrro_el0;
|
|
u64 mdscr_el1;
|
|
u64 contextidr_el1;
|
|
u64 dbgwcrN_el1[16];
|
|
u64 dbgwvrN_el1[16];
|
|
u64 dbgbcrN_el1[16];
|
|
u64 dbgbvrN_el1[16];
|
|
u64 pmccfiltr_el0;
|
|
u64 pmccntr_el0;
|
|
u64 pmcntenset_el0;
|
|
u64 pmcr_el0;
|
|
u64 pmevcntrN_el0[31];
|
|
u64 pmevtyperN_el0[31];
|
|
u64 pmintenset_el1;
|
|
u64 pmovsset_el0;
|
|
u64 pmselr_el0;
|
|
u64 pmuserenr_el0;
|
|
public:
|
|
void Save();
|
|
void Restore() const;
|
|
};
|
|
|
|
constexpr s32 SleepManagerThreadPriority = 2;
|
|
|
|
/* Globals for sleep/wake. */
|
|
constinit u64 g_sleep_target_cores;
|
|
constinit KLightLock g_request_lock;
|
|
constinit KLightLock g_cv_lock;
|
|
constinit KLightConditionVariable g_cv{util::ConstantInitialize};
|
|
alignas(1_KB) constinit u64 g_sleep_buffers[cpu::NumCores][1_KB / sizeof(u64)];
|
|
constinit ams::kern::init::KInitArguments g_sleep_init_arguments[cpu::NumCores];
|
|
constinit SavedSystemRegisters g_sleep_system_registers[cpu::NumCores] = {};
|
|
|
|
void WaitOtherCpuPowerOff() {
|
|
constexpr u64 PmcPhysicalAddress = 0x7000E400;
|
|
constexpr u32 PWRGATE_STATUS_CE123_MASK = ((1u << 3) - 1) << 9;
|
|
|
|
u32 value;
|
|
do {
|
|
bool res = smc::ReadWriteRegister(std::addressof(value), PmcPhysicalAddress + APBDEV_PMC_PWRGATE_STATUS, 0, 0);
|
|
MESOSPHERE_ASSERT(res);
|
|
MESOSPHERE_UNUSED(res);
|
|
} while ((value & PWRGATE_STATUS_CE123_MASK) != 0);
|
|
}
|
|
|
|
void SavedSystemRegisters::Save() {
|
|
/* Save system registers. */
|
|
this->tpidr_el0 = cpu::GetTpidrEl0();
|
|
this->elr_el1 = cpu::GetElrEl1();
|
|
this->sp_el0 = cpu::GetSpEl0();
|
|
this->spsr_el1 = cpu::GetSpsrEl1();
|
|
this->daif = cpu::GetDaif();
|
|
this->cpacr_el1 = cpu::GetCpacrEl1();
|
|
this->vbar_el1 = cpu::GetVbarEl1();
|
|
this->csselr_el1 = cpu::GetCsselrEl1();
|
|
this->cntp_ctl_el0 = cpu::GetCntpCtlEl0();
|
|
this->cntp_cval_el0 = cpu::GetCntpCvalEl0();
|
|
this->cntkctl_el1 = cpu::GetCntkCtlEl1();
|
|
this->tpidrro_el0 = cpu::GetTpidrRoEl0();
|
|
|
|
/* Save pmu registers. */
|
|
{
|
|
/* Get and clear pmcr_el0 */
|
|
this->pmcr_el0 = cpu::GetPmcrEl0();
|
|
cpu::SetPmcrEl0(0);
|
|
cpu::EnsureInstructionConsistency();
|
|
|
|
/* Save other pmu registers. */
|
|
this->pmuserenr_el0 = cpu::GetPmUserEnrEl0();
|
|
this->pmselr_el0 = cpu::GetPmSelrEl0();
|
|
this->pmccfiltr_el0 = cpu::GetPmcCfiltrEl0();
|
|
this->pmcntenset_el0 = cpu::GetPmCntEnSetEl0();
|
|
this->pmintenset_el1 = cpu::GetPmIntEnSetEl1();
|
|
this->pmovsset_el0 = cpu::GetPmOvsSetEl0();
|
|
this->pmccntr_el0 = cpu::GetPmcCntrEl0();
|
|
|
|
switch (cpu::PerformanceMonitorsControlRegisterAccessor(this->pmcr_el0).GetN()) {
|
|
#define HANDLE_PMU_CASE(N) \
|
|
case (N+1): \
|
|
this->pmevcntrN_el0 [ N ] = cpu::GetPmevCntr##N##El0(); \
|
|
this->pmevtyperN_el0[ N ] = cpu::GetPmevTyper##N##El0(); \
|
|
[[fallthrough]]
|
|
|
|
HANDLE_PMU_CASE(30);
|
|
HANDLE_PMU_CASE(29);
|
|
HANDLE_PMU_CASE(28);
|
|
HANDLE_PMU_CASE(27);
|
|
HANDLE_PMU_CASE(26);
|
|
HANDLE_PMU_CASE(25);
|
|
HANDLE_PMU_CASE(24);
|
|
HANDLE_PMU_CASE(23);
|
|
HANDLE_PMU_CASE(22);
|
|
HANDLE_PMU_CASE(21);
|
|
HANDLE_PMU_CASE(20);
|
|
HANDLE_PMU_CASE(19);
|
|
HANDLE_PMU_CASE(18);
|
|
HANDLE_PMU_CASE(17);
|
|
HANDLE_PMU_CASE(16);
|
|
HANDLE_PMU_CASE(15);
|
|
HANDLE_PMU_CASE(14);
|
|
HANDLE_PMU_CASE(13);
|
|
HANDLE_PMU_CASE(12);
|
|
HANDLE_PMU_CASE(11);
|
|
HANDLE_PMU_CASE(10);
|
|
HANDLE_PMU_CASE( 9);
|
|
HANDLE_PMU_CASE( 8);
|
|
HANDLE_PMU_CASE( 7);
|
|
HANDLE_PMU_CASE( 6);
|
|
HANDLE_PMU_CASE( 5);
|
|
HANDLE_PMU_CASE( 4);
|
|
HANDLE_PMU_CASE( 3);
|
|
HANDLE_PMU_CASE( 2);
|
|
HANDLE_PMU_CASE( 1);
|
|
HANDLE_PMU_CASE( 0);
|
|
|
|
#undef HANDLE_PMU_CASE
|
|
case 0:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Save debug registers. */
|
|
const u64 dfr0 = cpu::GetIdAa64Dfr0El1();
|
|
|
|
this->mdscr_el1 = cpu::GetMdscrEl1();
|
|
this->contextidr_el1 = cpu::GetContextidrEl1();
|
|
|
|
/* Save watchpoints. */
|
|
switch (cpu::DebugFeatureRegisterAccessor(dfr0).GetNumWatchpoints()) {
|
|
#define HANDLE_DBG_CASE(N) \
|
|
case N: \
|
|
this->dbgwcrN_el1[ N ] = cpu::GetDbgWcr##N##El1(); \
|
|
this->dbgwvrN_el1[ N ] = cpu::GetDbgWvr##N##El1(); \
|
|
[[fallthrough]]
|
|
|
|
HANDLE_DBG_CASE(15);
|
|
HANDLE_DBG_CASE(14);
|
|
HANDLE_DBG_CASE(13);
|
|
HANDLE_DBG_CASE(12);
|
|
HANDLE_DBG_CASE(11);
|
|
HANDLE_DBG_CASE(10);
|
|
HANDLE_DBG_CASE( 9);
|
|
HANDLE_DBG_CASE( 8);
|
|
HANDLE_DBG_CASE( 7);
|
|
HANDLE_DBG_CASE( 6);
|
|
HANDLE_DBG_CASE( 5);
|
|
HANDLE_DBG_CASE( 4);
|
|
HANDLE_DBG_CASE( 3);
|
|
HANDLE_DBG_CASE( 2);
|
|
|
|
#undef HANDLE_DBG_CASE
|
|
|
|
case 1:
|
|
this->dbgwcrN_el1[1] = cpu::GetDbgWcr1El1();
|
|
this->dbgwvrN_el1[1] = cpu::GetDbgWvr1El1();
|
|
this->dbgwcrN_el1[0] = cpu::GetDbgWcr0El1();
|
|
this->dbgwvrN_el1[0] = cpu::GetDbgWvr0El1();
|
|
[[fallthrough]];
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Save breakpoints. */
|
|
switch (cpu::DebugFeatureRegisterAccessor(dfr0).GetNumBreakpoints()) {
|
|
#define HANDLE_DBG_CASE(N) \
|
|
case N: \
|
|
this->dbgbcrN_el1[ N ] = cpu::GetDbgBcr##N##El1(); \
|
|
this->dbgbvrN_el1[ N ] = cpu::GetDbgBvr##N##El1(); \
|
|
[[fallthrough]]
|
|
|
|
HANDLE_DBG_CASE(15);
|
|
HANDLE_DBG_CASE(14);
|
|
HANDLE_DBG_CASE(13);
|
|
HANDLE_DBG_CASE(12);
|
|
HANDLE_DBG_CASE(11);
|
|
HANDLE_DBG_CASE(10);
|
|
HANDLE_DBG_CASE( 9);
|
|
HANDLE_DBG_CASE( 8);
|
|
HANDLE_DBG_CASE( 7);
|
|
HANDLE_DBG_CASE( 6);
|
|
HANDLE_DBG_CASE( 5);
|
|
HANDLE_DBG_CASE( 4);
|
|
HANDLE_DBG_CASE( 3);
|
|
HANDLE_DBG_CASE( 2);
|
|
|
|
#undef HANDLE_DBG_CASE
|
|
|
|
case 1:
|
|
this->dbgbcrN_el1[1] = cpu::GetDbgBcr1El1();
|
|
this->dbgbvrN_el1[1] = cpu::GetDbgBvr1El1();
|
|
[[fallthrough]];
|
|
default:
|
|
break;
|
|
}
|
|
|
|
this->dbgbcrN_el1[0] = cpu::GetDbgBcr0El1();
|
|
this->dbgbvrN_el1[0] = cpu::GetDbgBvr0El1();
|
|
cpu::EnsureInstructionConsistency();
|
|
|
|
/* Clear mdscr_el1. */
|
|
cpu::SetMdscrEl1(0);
|
|
cpu::EnsureInstructionConsistency();
|
|
}
|
|
|
|
void SavedSystemRegisters::Restore() const {
|
|
/* Restore debug registers. */
|
|
const u64 dfr0 = cpu::GetIdAa64Dfr0El1();
|
|
cpu::EnsureInstructionConsistency();
|
|
|
|
cpu::SetMdscrEl1(0);
|
|
cpu::EnsureInstructionConsistency();
|
|
|
|
cpu::SetOslarEl1(0);
|
|
cpu::EnsureInstructionConsistency();
|
|
|
|
/* Restore watchpoints. */
|
|
switch (cpu::DebugFeatureRegisterAccessor(dfr0).GetNumWatchpoints()) {
|
|
#define HANDLE_DBG_CASE(N) \
|
|
case N: \
|
|
cpu::SetDbgWcr##N##El1(this->dbgwcrN_el1[ N ]); \
|
|
cpu::SetDbgWvr##N##El1(this->dbgwvrN_el1[ N ]); \
|
|
[[fallthrough]]
|
|
|
|
HANDLE_DBG_CASE(15);
|
|
HANDLE_DBG_CASE(14);
|
|
HANDLE_DBG_CASE(13);
|
|
HANDLE_DBG_CASE(12);
|
|
HANDLE_DBG_CASE(11);
|
|
HANDLE_DBG_CASE(10);
|
|
HANDLE_DBG_CASE( 9);
|
|
HANDLE_DBG_CASE( 8);
|
|
HANDLE_DBG_CASE( 7);
|
|
HANDLE_DBG_CASE( 6);
|
|
HANDLE_DBG_CASE( 5);
|
|
HANDLE_DBG_CASE( 4);
|
|
HANDLE_DBG_CASE( 3);
|
|
HANDLE_DBG_CASE( 2);
|
|
|
|
#undef HANDLE_DBG_CASE
|
|
|
|
case 1:
|
|
cpu::SetDbgWcr1El1(this->dbgwcrN_el1[1]);
|
|
cpu::SetDbgWvr1El1(this->dbgwvrN_el1[1]);
|
|
cpu::SetDbgWcr0El1(this->dbgwcrN_el1[0]);
|
|
cpu::SetDbgWvr0El1(this->dbgwvrN_el1[0]);
|
|
[[fallthrough]];
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Restore breakpoints. */
|
|
switch (cpu::DebugFeatureRegisterAccessor(dfr0).GetNumBreakpoints()) {
|
|
#define HANDLE_DBG_CASE(N) \
|
|
case N: \
|
|
cpu::SetDbgBcr##N##El1(this->dbgbcrN_el1[ N ]); \
|
|
cpu::SetDbgBvr##N##El1(this->dbgbvrN_el1[ N ]); \
|
|
[[fallthrough]]
|
|
|
|
HANDLE_DBG_CASE(15);
|
|
HANDLE_DBG_CASE(14);
|
|
HANDLE_DBG_CASE(13);
|
|
HANDLE_DBG_CASE(12);
|
|
HANDLE_DBG_CASE(11);
|
|
HANDLE_DBG_CASE(10);
|
|
HANDLE_DBG_CASE( 9);
|
|
HANDLE_DBG_CASE( 8);
|
|
HANDLE_DBG_CASE( 7);
|
|
HANDLE_DBG_CASE( 6);
|
|
HANDLE_DBG_CASE( 5);
|
|
HANDLE_DBG_CASE( 4);
|
|
HANDLE_DBG_CASE( 3);
|
|
HANDLE_DBG_CASE( 2);
|
|
|
|
#undef HANDLE_DBG_CASE
|
|
|
|
case 1:
|
|
cpu::SetDbgBcr1El1(this->dbgbcrN_el1[1]);
|
|
cpu::SetDbgBvr1El1(this->dbgbvrN_el1[1]);
|
|
[[fallthrough]];
|
|
default:
|
|
break;
|
|
}
|
|
|
|
cpu::SetDbgBcr0El1(this->dbgbcrN_el1[0]);
|
|
cpu::SetDbgBvr0El1(this->dbgbvrN_el1[0]);
|
|
cpu::EnsureInstructionConsistency();
|
|
|
|
cpu::SetContextidrEl1(this->contextidr_el1);
|
|
cpu::EnsureInstructionConsistency();
|
|
|
|
cpu::SetMdscrEl1(this->mdscr_el1);
|
|
cpu::EnsureInstructionConsistency();
|
|
|
|
/* Restore pmu registers. */
|
|
cpu::SetPmUserEnrEl0(0);
|
|
cpu::PerformanceMonitorsControlRegisterAccessor(0).SetEventCounterReset(true).SetCycleCounterReset(true).Store();
|
|
cpu::EnsureInstructionConsistency();
|
|
|
|
cpu::SetPmOvsClrEl0(static_cast<u64>(static_cast<u32>(~u32())));
|
|
cpu::SetPmIntEnClrEl1(static_cast<u64>(static_cast<u32>(~u32())));
|
|
cpu::SetPmCntEnClrEl0(static_cast<u64>(static_cast<u32>(~u32())));
|
|
|
|
switch (cpu::PerformanceMonitorsControlRegisterAccessor(this->pmcr_el0).GetN()) {
|
|
#define HANDLE_PMU_CASE(N) \
|
|
case (N+1): \
|
|
cpu::SetPmevCntr##N##El0 (this->pmevcntrN_el0 [ N ]); \
|
|
cpu::SetPmevTyper##N##El0(this->pmevtyperN_el0[ N ]); \
|
|
[[fallthrough]]
|
|
|
|
HANDLE_PMU_CASE(30);
|
|
HANDLE_PMU_CASE(29);
|
|
HANDLE_PMU_CASE(28);
|
|
HANDLE_PMU_CASE(27);
|
|
HANDLE_PMU_CASE(26);
|
|
HANDLE_PMU_CASE(25);
|
|
HANDLE_PMU_CASE(24);
|
|
HANDLE_PMU_CASE(23);
|
|
HANDLE_PMU_CASE(22);
|
|
HANDLE_PMU_CASE(21);
|
|
HANDLE_PMU_CASE(20);
|
|
HANDLE_PMU_CASE(19);
|
|
HANDLE_PMU_CASE(18);
|
|
HANDLE_PMU_CASE(17);
|
|
HANDLE_PMU_CASE(16);
|
|
HANDLE_PMU_CASE(15);
|
|
HANDLE_PMU_CASE(14);
|
|
HANDLE_PMU_CASE(13);
|
|
HANDLE_PMU_CASE(12);
|
|
HANDLE_PMU_CASE(11);
|
|
HANDLE_PMU_CASE(10);
|
|
HANDLE_PMU_CASE( 9);
|
|
HANDLE_PMU_CASE( 8);
|
|
HANDLE_PMU_CASE( 7);
|
|
HANDLE_PMU_CASE( 6);
|
|
HANDLE_PMU_CASE( 5);
|
|
HANDLE_PMU_CASE( 4);
|
|
HANDLE_PMU_CASE( 3);
|
|
HANDLE_PMU_CASE( 2);
|
|
HANDLE_PMU_CASE( 1);
|
|
HANDLE_PMU_CASE( 0);
|
|
|
|
#undef HANDLE_PMU_CASE
|
|
case 0:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
cpu::SetPmUserEnrEl0 (this->pmuserenr_el0);
|
|
cpu::SetPmSelrEl0 (this->pmselr_el0);
|
|
cpu::SetPmcCfiltrEl0 (this->pmccfiltr_el0);
|
|
cpu::SetPmCntEnSetEl0(this->pmcntenset_el0);
|
|
cpu::SetPmIntEnSetEl1(this->pmintenset_el1);
|
|
cpu::SetPmOvsSetEl0 (this->pmovsset_el0);
|
|
cpu::SetPmcCntrEl0 (this->pmccntr_el0);
|
|
cpu::EnsureInstructionConsistency();
|
|
|
|
cpu::SetPmcrEl0(this->pmcr_el0);
|
|
cpu::EnsureInstructionConsistency();
|
|
|
|
/* Restore system registers. */
|
|
cpu::SetTtbr0El1 (KPageTable::GetKernelTtbr0());
|
|
cpu::SetTpidrEl0 (this->tpidr_el0);
|
|
cpu::SetElrEl1 (this->elr_el1);
|
|
cpu::SetSpEl0 (this->sp_el0);
|
|
cpu::SetSpsrEl1 (this->spsr_el1);
|
|
cpu::SetDaif (this->daif);
|
|
cpu::SetCpacrEl1 (this->cpacr_el1);
|
|
cpu::SetVbarEl1 (this->vbar_el1);
|
|
cpu::SetCsselrEl1 (this->csselr_el1);
|
|
cpu::SetCntpCtlEl0 (this->cntp_ctl_el0);
|
|
cpu::SetCntpCvalEl0(this->cntp_cval_el0);
|
|
cpu::SetCntkCtlEl1 (this->cntkctl_el1);
|
|
cpu::SetTpidrRoEl0 (this->tpidrro_el0);
|
|
cpu::EnsureInstructionConsistency();
|
|
|
|
/* Invalidate the entire tlb. */
|
|
cpu::InvalidateEntireTlb();
|
|
}
|
|
|
|
}
|
|
|
|
void KSleepManager::Initialize() {
|
|
/* Create a sleep manager thread for each core. */
|
|
for (size_t core_id = 0; core_id < cpu::NumCores; core_id++) {
|
|
/* Reserve a thread from the system limit. */
|
|
MESOSPHERE_ABORT_UNLESS(Kernel::GetSystemResourceLimit().Reserve(ams::svc::LimitableResource_ThreadCountMax, 1));
|
|
|
|
/* Create a new thread. */
|
|
KThread *new_thread = KThread::Create();
|
|
MESOSPHERE_ABORT_UNLESS(new_thread != nullptr);
|
|
|
|
/* Launch the new thread. */
|
|
MESOSPHERE_R_ABORT_UNLESS(KThread::InitializeKernelThread(new_thread, KSleepManager::ProcessRequests, reinterpret_cast<uintptr_t>(g_sleep_buffers[core_id]), SleepManagerThreadPriority, static_cast<s32>(core_id)));
|
|
|
|
/* Register the new thread. */
|
|
KThread::Register(new_thread);
|
|
|
|
/* Run the thread. */
|
|
MESOSPHERE_R_ABORT_UNLESS(new_thread->Run());
|
|
}
|
|
}
|
|
|
|
void KSleepManager::SleepSystem() {
|
|
/* Ensure device mappings are not modified during sleep. */
|
|
KDevicePageTable::Lock();
|
|
ON_SCOPE_EXIT { KDevicePageTable::Unlock(); };
|
|
|
|
/* Request that the system sleep. */
|
|
{
|
|
KScopedLightLock lk(g_request_lock);
|
|
|
|
/* Signal the manager to sleep on all cores. */
|
|
{
|
|
KScopedLightLock lk(g_cv_lock);
|
|
MESOSPHERE_ABORT_UNLESS(g_sleep_target_cores == 0);
|
|
|
|
g_sleep_target_cores = (1ul << cpu::NumCores) - 1;
|
|
g_cv.Broadcast();
|
|
|
|
while (g_sleep_target_cores != 0) {
|
|
g_cv.Wait(std::addressof(g_cv_lock));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void KSleepManager::ProcessRequests(uintptr_t sleep_buffer) {
|
|
const auto target_fw = GetTargetFirmware();
|
|
const s32 core_id = GetCurrentCoreId();
|
|
|
|
ams::kern::init::KInitArguments * const init_args = g_sleep_init_arguments + core_id;
|
|
KPhysicalAddress start_core_phys_addr = Null<KPhysicalAddress>;
|
|
KPhysicalAddress init_args_phys_addr = Null<KPhysicalAddress>;
|
|
|
|
/* Get the physical addresses we'll need. */
|
|
{
|
|
MESOSPHERE_ABORT_UNLESS(Kernel::GetKernelPageTable().GetPhysicalAddress(std::addressof(start_core_phys_addr), KProcessAddress(&::ams::kern::init::StartOtherCore)));
|
|
MESOSPHERE_ABORT_UNLESS(Kernel::GetKernelPageTable().GetPhysicalAddress(std::addressof(init_args_phys_addr), KProcessAddress(init_args)));
|
|
}
|
|
|
|
const u64 target_core_mask = (1ul << core_id);
|
|
|
|
const bool use_legacy_lps_driver = target_fw < TargetFirmware_2_0_0;
|
|
|
|
/* Loop, processing sleep when requested. */
|
|
while (true) {
|
|
/* Wait for a request. */
|
|
{
|
|
KScopedLightLock lk(g_cv_lock);
|
|
while ((g_sleep_target_cores & target_core_mask) == 0) {
|
|
g_cv.Wait(std::addressof(g_cv_lock));
|
|
}
|
|
}
|
|
|
|
/* If on core 0, ensure the legacy lps driver is initialized. */
|
|
if (use_legacy_lps_driver && core_id == 0) {
|
|
lps::Initialize();
|
|
}
|
|
|
|
/* Perform Sleep/Wake sequence. */
|
|
{
|
|
/* Disable interrupts. */
|
|
KScopedInterruptDisable di;
|
|
|
|
/* Save the system registers for the current core. */
|
|
g_sleep_system_registers[core_id].Save();
|
|
|
|
/* Invalidate the entire tlb. */
|
|
cpu::InvalidateEntireTlb();
|
|
|
|
/* Ensure that all cores get to this point before continuing. */
|
|
cpu::SynchronizeAllCores();
|
|
|
|
/* If on core 0, put the device page tables to sleep. */
|
|
if (core_id == 0) {
|
|
KDevicePageTable::Sleep();
|
|
}
|
|
|
|
/* Ensure that all cores get to this point before continuing. */
|
|
cpu::SynchronizeAllCores();
|
|
|
|
/* Wait 100us before continuing. */
|
|
{
|
|
const s64 timeout = KHardwareTimer::GetTick() + ams::svc::Tick(TimeSpan::FromMicroSeconds(100));
|
|
while (KHardwareTimer::GetTick() < timeout) {
|
|
__asm__ __volatile__("" ::: "memory");
|
|
}
|
|
}
|
|
|
|
/* Save the interrupt manager's state. */
|
|
Kernel::GetInterruptManager().Save(core_id);
|
|
|
|
/* Setup the initial arguments. */
|
|
{
|
|
/* Determine whether we're running on a cortex-a53 or a-57. */
|
|
cpu::MainIdRegisterAccessor midr_el1;
|
|
const auto implementer = midr_el1.GetImplementer();
|
|
const auto primary_part = midr_el1.GetPrimaryPartNumber();
|
|
const bool needs_cpu_ctlr = (implementer == cpu::MainIdRegisterAccessor::Implementer::ArmLimited) && (primary_part == cpu::MainIdRegisterAccessor::PrimaryPartNumber::CortexA57 || primary_part == cpu::MainIdRegisterAccessor::PrimaryPartNumber::CortexA53);
|
|
|
|
init_args->cpuactlr = needs_cpu_ctlr ? cpu::GetCpuActlrEl1() : 0;
|
|
init_args->cpuectlr = needs_cpu_ctlr ? cpu::GetCpuEctlrEl1() : 0;
|
|
init_args->sp = 0;
|
|
init_args->entrypoint = reinterpret_cast<uintptr_t>(::ams::kern::board::nintendo::nx::KSleepManager::ResumeEntry);
|
|
init_args->argument = sleep_buffer;
|
|
}
|
|
|
|
/* Ensure that all cores get to this point before continuing. */
|
|
cpu::SynchronizeAllCores();
|
|
|
|
/* Log that the core is going to sleep. */
|
|
MESOSPHERE_LOG("Core[%d]: Going to sleep, buffer = %010lx\n", core_id, sleep_buffer);
|
|
|
|
/* If we're on a core other than zero, we can just invoke the sleep handler. */
|
|
if (core_id != 0) {
|
|
CpuSleepHandler(sleep_buffer, GetInteger(start_core_phys_addr), GetInteger(init_args_phys_addr));
|
|
} else {
|
|
/* Wait for all other cores to be powered off. */
|
|
WaitOtherCpuPowerOff();
|
|
|
|
/* If we're using the legacy lps driver, enable suspend. */
|
|
if (use_legacy_lps_driver) {
|
|
MESOSPHERE_R_ABORT_UNLESS(lps::EnableSuspend(true));
|
|
}
|
|
|
|
/* Log that we're about to enter SC7. */
|
|
MESOSPHERE_LOG("Entering SC7\n");
|
|
|
|
/* Save the debug log state. */
|
|
KDebugLog::Save();
|
|
|
|
/* Invoke the sleep handler. */
|
|
if (!use_legacy_lps_driver) {
|
|
/* When not using the legacy driver, invoke directly. */
|
|
CpuSleepHandler(sleep_buffer, GetInteger(start_core_phys_addr), GetInteger(init_args_phys_addr));
|
|
} else {
|
|
lps::InvokeCpuSleepHandler(sleep_buffer, GetInteger(start_core_phys_addr), GetInteger(init_args_phys_addr));
|
|
}
|
|
|
|
/* Restore the debug log state. */
|
|
KDebugLog::Restore();
|
|
|
|
/* Log that we're about to exit SC7. */
|
|
MESOSPHERE_LOG("Exiting SC7\n");
|
|
|
|
/* Wake up the other cores. */
|
|
cpu::MultiprocessorAffinityRegisterAccessor mpidr;
|
|
const auto arg = mpidr.GetCpuOnArgument();
|
|
for (s32 i = 1; i < static_cast<s32>(cpu::NumCores); ++i) {
|
|
KSystemControl::Init::TurnOnCpu(arg | i, g_sleep_init_arguments + i);
|
|
}
|
|
}
|
|
|
|
/* Log that the core is waking from sleep. */
|
|
MESOSPHERE_LOG("Core[%d]: Woke from sleep.\n", core_id);
|
|
|
|
/* Ensure that all cores get to this point before continuing. */
|
|
cpu::SynchronizeAllCores();
|
|
|
|
/* Restore the interrupt manager's state. */
|
|
Kernel::GetInterruptManager().Restore(core_id);
|
|
|
|
/* Ensure that all cores get to this point before continuing. */
|
|
cpu::SynchronizeAllCores();
|
|
|
|
/* If on core 0, wake up the device page tables. */
|
|
if (core_id == 0) {
|
|
KDevicePageTable::Wakeup();
|
|
|
|
/* If we're using the legacy driver, resume the bpmp firmware. */
|
|
if (use_legacy_lps_driver) {
|
|
lps::ResumeBpmpFirmware();
|
|
}
|
|
}
|
|
|
|
/* Ensure that all cores get to this point before continuing. */
|
|
cpu::SynchronizeAllCores();
|
|
|
|
/* Restore the system registers for the current core. */
|
|
g_sleep_system_registers[core_id].Restore();
|
|
}
|
|
|
|
/* Signal request completed. */
|
|
{
|
|
KScopedLightLock lk(g_cv_lock);
|
|
g_sleep_target_cores &= ~target_core_mask;
|
|
if (g_sleep_target_cores == 0) {
|
|
g_cv.Broadcast();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|