Files
Horizon-OC-pro/Source/hoc-clk/sysmodule/src/bpmp/bpmp.cpp
T

370 lines
15 KiB
C++

/*
* 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 <atomic>
#include <cstddef>
#include <cstring>
#include <switch.h>
#include "../board/board.hpp"
#include "../file/config.hpp"
#include "../file/file_utils.hpp"
#include "../mapping/mem_map.hpp"
#include "../tsensor/soctherm.hpp"
#include "bpmp.hpp"
#include "bpmpfw_bin.h"
namespace bpmp {
namespace {
constexpr u64 FlowCtlrPhysBase = 0x60007000;
constexpr u64 EvpPhysBase = 0x6000F000;
constexpr u32 ClkRstRstDevLSet = 0x300;
constexpr u32 ClkRstRstDevLClr = 0x304;
constexpr u32 RstDevLCopRstBit = (1u << 1); /* SWR_COP_RST */
constexpr u32 ApbMiscSlaveSecurityReg0 = 0xC00;
constexpr u32 ApbMiscSlaveSecurityReg1 = 0xC04;
constexpr u32 ApbMiscSlaveSecurityReg2 = 0xC08;
/* Relative to actmonVirtAddr */
constexpr u32 ActmonGlbPeriodCtrl = 0x004;
constexpr u32 ActmonCopCtrl = 0xC0;
constexpr u32 ActmonCopUpperWmark = 0xC4;
constexpr u32 ActmonCopIntrStatus = 0xE4;
constexpr u32 ActmonGlbPeriodCtrlUsec = (1u << 8);
constexpr u32 ActmonCopCtrlStockArmed = (1u << 31) | (1u << 30) | (1u << 18);
/* MC SMMU handling for the BPMP. */
constexpr u64 McPhysBase = 0x70019000;
constexpr u32 McSmmuTlbConfig = 0x014;
constexpr u32 McSmmuPtbAsid = 0x01C;
constexpr u32 McSmmuPtbData = 0x020;
constexpr u32 McSmmuTlbFlush = 0x030;
constexpr u32 McSmmuPtcFlush = 0x034;
constexpr u32 McSmmuAvpcAsid = 0x23C;
constexpr u32 McSmmuPpcs1Asid = 0x298;
/* Page table is backed by the security engine's MMIO aperture. */
constexpr u64 BpmpSmmuPdeBase = 0x70012000;
constexpr u32 BpmpSmmuAsid = 1;
/* MC_SMMU_PTB_ASID */
constexpr u32 SmmuPtbAsidCurrentAsid = BpmpSmmuAsid; /* [0:7] */
/* MC_SMMU_PTB_DATA */
constexpr u32 SmmuPtbDataPdeMask = 0x7FFFFF; /* [0:22] */
/* MC_SMMU_AVPC_ASID / MC_SMMU_PPCS1_ASID */
constexpr u32 SmmuClientSmmuEnableBit = (1u << 31);
constexpr u32 SmmuAvpcAsidValue = (SmmuClientSmmuEnableBit | BpmpSmmuAsid);
constexpr u32 SmmuPpcs1AsidValue = (SmmuClientSmmuEnableBit | BpmpSmmuAsid);
constexpr u32 EvpCopResetVector = 0x200;
constexpr u32 EvpCopUndefVector = 0x204;
constexpr u32 EvpCopSwiVector = 0x208;
constexpr u32 EvpCopPrefetchAbortVector = 0x20C;
constexpr u32 EvpCopDataAbortVector = 0x210;
constexpr u32 EvpCopRsvdVector = 0x214;
constexpr u32 EvpCopIrqVector = 0x218;
constexpr u32 EvpCopFiqVector = 0x21C;
constexpr u32 StockBpmpExceptionVector = 0x7D000000;
constexpr u32 FlowCtlrHaltCopEvents = 0x004;
constexpr u32 FlowModeNone = 0x00000000;
constexpr u32 FlowModeStop = (2u << 29);
constexpr u64 FwIramPhysBase = 0x40004000;
constexpr u32 FwStagingSize = 0x8000;
constexpr u64 WorkRamPhysBase = FwIramPhysBase + FwStagingSize; // matches bpmpfw's WorkRamStart
/* secmon will return error unless we apply thiss workaround */
alignas(4096) u8 s_fwStageBuf[FwStagingSize];
HocClkBpmpSharedInfo s_sharedInfo = {};
inline volatile u32 &Mmio(u64 base_va, u32 offset) {
return *reinterpret_cast<volatile u32 *>(base_va + offset);
}
// Late in the boot chain, otherwise a fatal will occur from invalid boot state
constexpr u32 pscDependencies[] = { PscPmModuleId_Olsc };
constexpr PscPmModuleId PscModuleId = (PscPmModuleId)705;
PscPmModule s_pscModule;
bool s_pscPrepared = false;
Thread s_pscThread;
bool s_pscExit = false;
std::atomic_bool s_isAwake = true;
/* Must be called to prevent secmon/erpt panic on wake */
void RequestBpmpExitAndWait() {
if (!IsPatchedExosphere()) {
return;
}
const u32 one = 1;
Result rc = SmcCopyToIram(WorkRamPhysBase + offsetof(HocClkBpmpSharedInfo, exitRequested), &one, sizeof(one));
if (R_FAILED(rc)) {
fileUtils::LogLine("[bpmp] RequestBpmpExitAndWait: SmcCopyToIram failed: 0x%x", rc);
return;
}
constexpr int MaxAttempts = 20;
constexpr u64 PollIntervalNs = 20'000'000; // 20ms
HocClkBpmpSharedInfo info = {};
for (int i = 0; i < MaxAttempts; i++) {
svcSleepThread(PollIntervalNs);
rc = SmcCopyFromIram(&info, WorkRamPhysBase, sizeof(info));
if (R_SUCCEEDED(rc) && info.magic != HOCCLK_BPMP_MAGIC) {
return;
}
}
fileUtils::LogLine("[bpmp] RequestBpmpExitAndWait: BPMP timeout (cannot sleep)");
}
void EnableBpmpSmmu() {
/* Point the ASID page table at the security engine aperture. */
const u32 pde_base = static_cast<u32>(BpmpSmmuPdeBase / 0x1000);
/* Select the ASID and configure its page table entry. */
SmcReadWriteRegister(McPhysBase + McSmmuPtbAsid, 0xFFFFFFFF, SmmuPtbAsidCurrentAsid);
SmcReadWriteRegister(McPhysBase + McSmmuPtbData, 0xFFFFFFFF, pde_base & SmmuPtbDataPdeMask);
/* Route BPMP (AVPC) and PPCS1 through the SMMU. */
SmcReadWriteRegister(McPhysBase + McSmmuAvpcAsid, 0xFFFFFFFF, SmmuAvpcAsidValue);
SmcReadWriteRegister(McPhysBase + McSmmuPpcs1Asid, 0xFFFFFFFF, SmmuPpcs1AsidValue);
/* Flush the page table cache. */
SmcReadWriteRegister(McPhysBase + McSmmuPtcFlush, 0xFFFFFFFF, 0);
SmcReadWriteRegister(McPhysBase + McSmmuTlbConfig, 0, 0);
/* Flush the translation lookaside buffer. */
SmcReadWriteRegister(McPhysBase + McSmmuTlbFlush, 0xFFFFFFFF, 0);
SmcReadWriteRegister(McPhysBase + McSmmuTlbConfig, 0, 0);
}
void DisableBpmpSmmu() {
/* Clear the ASID page table. */
SmcReadWriteRegister(McPhysBase + McSmmuPtbAsid, 0xFFFFFFFF, SmmuPtbAsidCurrentAsid);
SmcReadWriteRegister(McPhysBase + McSmmuPtbData, 0xFFFFFFFF, 0);
SmcReadWriteRegister(McPhysBase + McSmmuAvpcAsid, 0xFFFFFFFF, 0);
SmcReadWriteRegister(McPhysBase + McSmmuPpcs1Asid, 0xFFFFFFFF, 0);
/* Flush the page table cache. */
SmcReadWriteRegister(McPhysBase + McSmmuPtcFlush, 0xFFFFFFFF, 0);
SmcReadWriteRegister(McPhysBase + McSmmuTlbConfig, 0, 0);
/* Flush the translation lookaside buffer. */
SmcReadWriteRegister(McPhysBase + McSmmuTlbFlush, 0xFFFFFFFF, 0);
SmcReadWriteRegister(McPhysBase + McSmmuTlbConfig, 0, 0);
}
void RestoreStockState() {
RequestBpmpExitAndWait();
SmcReadWriteRegister(EvpPhysBase + EvpCopResetVector, 0xFFFFFFFF, StockBpmpExceptionVector);
SmcReadWriteRegister(EvpPhysBase + EvpCopUndefVector, 0xFFFFFFFF, StockBpmpExceptionVector);
SmcReadWriteRegister(EvpPhysBase + EvpCopSwiVector, 0xFFFFFFFF, StockBpmpExceptionVector);
SmcReadWriteRegister(EvpPhysBase + EvpCopPrefetchAbortVector, 0xFFFFFFFF, StockBpmpExceptionVector);
SmcReadWriteRegister(EvpPhysBase + EvpCopDataAbortVector, 0xFFFFFFFF, StockBpmpExceptionVector);
SmcReadWriteRegister(EvpPhysBase + EvpCopRsvdVector, 0xFFFFFFFF, StockBpmpExceptionVector);
SmcReadWriteRegister(EvpPhysBase + EvpCopIrqVector, 0xFFFFFFFF, StockBpmpExceptionVector);
SmcReadWriteRegister(EvpPhysBase + EvpCopFiqVector, 0xFFFFFFFF, StockBpmpExceptionVector);
SmcReadWriteRegister(FlowCtlrPhysBase + FlowCtlrHaltCopEvents, 0xFFFFFFFF, FlowModeStop);
Mmio(board::actmonVirtAddr, ActmonGlbPeriodCtrl) = ActmonGlbPeriodCtrlUsec;
Mmio(board::actmonVirtAddr, ActmonCopUpperWmark) = 0;
Mmio(board::actmonVirtAddr, ActmonCopIntrStatus) = Mmio(board::actmonVirtAddr, ActmonCopIntrStatus);
Mmio(board::actmonVirtAddr, ActmonCopCtrl) = ActmonCopCtrlStockArmed;
EnableBpmpSmmu();
}
void PscThreadFunc(void *) {
while (!s_pscExit) {
Result rc = eventWait(&s_pscModule.event, 1'000'000'000ULL);
if (R_FAILED(rc)) {
continue; // Timeout
}
PscPmState state;
u32 flags;
rc = pscPmModuleGetRequest(&s_pscModule, &state, &flags);
if (R_SUCCEEDED(rc)) {
if (state == PscPmState_Awake) {
svcSleepThread(500'000'000); // 500ms
/* I have no clue why this is needed as it wasn't needed before ¯\(ツ)/¯ */
tsensor::InitializeSoctherm();
Result wrc = StartBpmfwExecution();
if (R_FAILED(wrc)) {
fileUtils::LogLine("[bpmp] restart after wake failed: 0x%x", wrc);
}
s_isAwake = true;
} else if (state == PscPmState_ReadySleep || state == PscPmState_ReadyShutdown) {
RestoreStockState();
s_isAwake = false;
}
pscPmModuleAcknowledge(&s_pscModule, state);
}
}
}
} // namespace
Result StartBpmfwExecution() {
if (!IsPatchedExosphere()) {
fileUtils::LogLine("[bpmp] Cannot start without patched exosphere");
return (Result)1;
}
const size_t fw_size = bpmpfw_bin_size;
if (fw_size == 0 || fw_size > FwStagingSize) {
fileUtils::LogLine("[bpmp] StartBpmfwExecution: bpmpfw of %zu bytes is invalid (should not happen)!", fw_size);
return (Result)1;
}
std::memcpy(s_fwStageBuf, bpmpfw_bin, fw_size);
fileUtils::LogLine("[bpmp] StartBpmfwExecution: loading %zu bytes at 0x%llx",
fw_size, static_cast<unsigned long long>(FwIramPhysBase));
/* Disable the SMMU so DRAM can be accessed */
DisableBpmpSmmu();
/* Set BPMP reset */
Mmio(board::clkVirtAddr, ClkRstRstDevLSet) = RstDevLCopRstBit;
/* Grant BPMP access to all a bunch of registers that are useful */
Mmio(board::apbVirtAddr, ApbMiscSlaveSecurityReg0) = 0;
Mmio(board::apbVirtAddr, ApbMiscSlaveSecurityReg1) = 0;
Mmio(board::apbVirtAddr, ApbMiscSlaveSecurityReg2) = 0;
/* Set up exeption vectors for bpmp */
const u32 fw_reset_vector = static_cast<u32>(FwIramPhysBase + 0x0);
const u32 fw_panic_vector = static_cast<u32>(FwIramPhysBase + 0x4);
SmcReadWriteRegister(EvpPhysBase + EvpCopResetVector, 0xFFFFFFFF, fw_reset_vector);
SmcReadWriteRegister(EvpPhysBase + EvpCopUndefVector, 0xFFFFFFFF, fw_panic_vector);
SmcReadWriteRegister(EvpPhysBase + EvpCopSwiVector, 0xFFFFFFFF, fw_panic_vector);
SmcReadWriteRegister(EvpPhysBase + EvpCopPrefetchAbortVector, 0xFFFFFFFF, fw_panic_vector);
SmcReadWriteRegister(EvpPhysBase + EvpCopDataAbortVector, 0xFFFFFFFF, fw_panic_vector);
SmcReadWriteRegister(EvpPhysBase + EvpCopRsvdVector, 0xFFFFFFFF, fw_panic_vector);
SmcReadWriteRegister(EvpPhysBase + EvpCopIrqVector, 0xFFFFFFFF, fw_panic_vector);
SmcReadWriteRegister(EvpPhysBase + EvpCopFiqVector, 0xFFFFFFFF, fw_panic_vector);
/* Disable actmon monitoring to avoid hang on FW >4.0.0 */
Mmio(board::actmonVirtAddr, ActmonCopCtrl) = 0;
Mmio(board::actmonVirtAddr, ActmonCopIntrStatus) = Mmio(board::actmonVirtAddr, ActmonCopIntrStatus);
/* Load the BPMPFW into iram per page. TODO: determine if this is nessesary */
constexpr size_t PageSize = 4096;
for (size_t ofs = 0; ofs < fw_size; ofs += PageSize) {
const size_t chunk = (fw_size - ofs) < PageSize ? (fw_size - ofs) : PageSize;
Result copy_rc = SmcCopyToIram(FwIramPhysBase + ofs, s_fwStageBuf + ofs, static_cast<u32>(chunk));
if (R_FAILED(copy_rc)) {
fileUtils::LogLine("[bpmp] SmcCopyToIram failed at +0x%zx: 0x%x", ofs, copy_rc);
return copy_rc;
}
}
/* Clear BPMP reset. */
Mmio(board::clkVirtAddr, ClkRstRstDevLClr) = RstDevLCopRstBit;
/* Clear the halt written by bootloader. */
SmcReadWriteRegister(FlowCtlrPhysBase + FlowCtlrHaltCopEvents, 0xFFFFFFFF, FlowModeNone);
return 0;
}
void StartSleepMonitorThread() {
if (!IsPatchedExosphere()) {
fileUtils::LogLine("[bpmp] Cant start BPMP without exosphere patch");
return;
}
Result rc = pscmInitialize();
if (R_FAILED(rc)) {
fileUtils::LogLine("[bpmp] pscmInitialize failed: 0x%x", rc);
return;
}
rc = pscmGetPmModule(&s_pscModule, PscModuleId, pscDependencies, sizeof(pscDependencies) / sizeof(u32), true);
if (R_FAILED(rc)) {
fileUtils::LogLine("[bpmp] pscmGetPmModule failed: 0x%x", rc);
pscmExit();
return;
}
s_pscPrepared = true;
rc = threadCreate(&s_pscThread, PscThreadFunc, nullptr, NULL, 0x1000, 0x10, 3);
if (R_FAILED(rc)) {
fileUtils::LogLine("[bpmp] failed to create psc thread: 0x%x", rc);
return;
}
threadStart(&s_pscThread);
}
void StopSleepMonitorThread() {
if (!s_pscPrepared) {
return;
}
s_pscExit = true;
eventFire(&s_pscModule.event);
threadWaitForExit(&s_pscThread);
threadClose(&s_pscThread);
pscPmModuleFinalize(&s_pscModule);
pscPmModuleClose(&s_pscModule);
pscmExit();
s_pscPrepared = false;
}
const HocClkBpmpSharedInfo *GetSharedInfo() {
if (!IsPatchedExosphere()) {
s_sharedInfo = {};
return &s_sharedInfo;
}
SmcCopyFromIram(&s_sharedInfo, WorkRamPhysBase, sizeof(s_sharedInfo));
return &s_sharedInfo;
}
bool IsAwake() {
return s_isAwake;
}
} // namespace bpmp