Merge pull request #108 from Horizon-OC/bpmpfw

hocclk: add BPMP-Lite firmware
This commit is contained in:
Souldbminer authored and GitHub committed 2026-08-25 19:21:30 -04:00
commit 8222fe6b0c
72 files changed
+2712 -58

No files matched your search

+14 -1
View File
@@ -35,6 +35,7 @@
#include <t210.h>
#include <tmp451.h>
#include "../bpmp/bpmp.hpp"
#include "../display/display_refresh_rate.hpp"
#include "../file/file_utils.hpp"
#include "../hos/apm_ext.h"
@@ -52,7 +53,7 @@
namespace board {
u64 clkVirtAddr, dsiVirtAddr, apbVirtAddr, fuseVirtAddr;
u64 clkVirtAddr, dsiVirtAddr, apbVirtAddr, fuseVirtAddr, sysVirtAddr, actmonVirtAddr;
HocClkSocType gSocType;
u8 gDramID;
@@ -153,6 +154,10 @@ namespace board {
rc = QueryMemoryMapping(&fuseVirtAddr, 0x7000F000, 0x1000);
ASSERT_RESULT_OK(rc, "QueryMemoryMapping (fuse)");
rc = QueryMemoryMapping(&sysVirtAddr, 0x6000C000, 0x1000);
ASSERT_RESULT_OK(rc, "QueryMemoryMapping (sys)");
actmonVirtAddr = sysVirtAddr + 0x800;
FetchHardwareInfos();
Result nvCheck = 1;
@@ -173,6 +178,12 @@ namespace board {
tsensor::InitializeSoctherm(); // SOCTHERM must be init before AOTAG
Result bpmpfwRc = bpmp::StartBpmfwExecution();
if (R_FAILED(bpmpfwRc)) {
fileUtils::LogLine("[bpmp] StartBpmfwExecution failed: 0x%x", bpmpfwRc);
}
bpmp::StartSleepMonitorThread();
// PMC exosphere check
SecmonArgs args = {};
args.X[0] = 0xF0000002;
@@ -201,6 +212,8 @@ namespace board {
}
void Exit() {
bpmp::StopSleepMonitorThread();
if (HOSSVC_HAS_CLKRST) {
clkrstExit();
} else {
+1 -1
View File
@@ -42,7 +42,7 @@
#define HOSSVC_HAS_TC (hosversionAtLeast(5, 0, 0))
namespace board {
extern u64 clkVirtAddr, dsiVirtAddr, apbVirtAddr, fuseVirtAddr;
extern u64 clkVirtAddr, dsiVirtAddr, apbVirtAddr, fuseVirtAddr, sysVirtAddr, actmonVirtAddr;
extern HocClkSocType gSocType;
extern u8 gDramID;
extern HocClkConsoleType gConsoleType;
+268
View File
@@ -0,0 +1,268 @@
/*
* 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 <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);
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;
void RestoreStockState() {
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;
}
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));
/* 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
@@ -0,0 +1,33 @@
/*
* 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 <switch.h>
#include <hocclk/bpmp.h>
namespace bpmp {
Result StartBpmfwExecution();
void StartSleepMonitorThread();
void StopSleepMonitorThread();
const HocClkBpmpSharedInfo *GetSharedInfo();
// Always true on stock exosphere
bool IsAwake();
}
@@ -46,7 +46,6 @@ namespace fileUtils {
std::atomic_bool g_has_initialized = false;
bool g_log_enabled = false;
bool g_uart_enabled = false;
std::uint64_t g_last_flag_check = 0;
bool FlagExists(const char *path) {
FILE *file = fopen(path, "r");
@@ -57,16 +56,9 @@ namespace fileUtils {
return false;
}
void RefreshFlags(bool force) {
std::uint64_t now = armTicksToNs(armGetSystemTick());
if (!force && (now - g_last_flag_check) < FILE_FLAG_CHECK_INTERVAL_NS) {
return;
}
void RefreshFlags() {
g_log_enabled = FlagExists(FILE_LOG_FLAG_PATH);
g_uart_enabled = FlagExists(FILE_UART_FLAG_PATH);
g_last_flag_check = now;
}
void InitializeThreadFunc(void *args) {
@@ -94,8 +86,6 @@ namespace fileUtils {
return;
}
RefreshFlags(false);
if (!g_log_enabled && !g_uart_enabled) {
return;
}
@@ -103,7 +93,7 @@ namespace fileUtils {
va_list args;
va_start(args, format);
char buff[0xfff];
char buff[0x200];
int len = vsnprintf(buff, sizeof(buff), format, args);
va_end(args);
@@ -223,7 +213,7 @@ namespace fileUtils {
}
if (R_SUCCEEDED(rc)) {
RefreshFlags(true);
RefreshFlags();
g_has_initialized = true;
LogLine("=== hoc-clk " TARGET_VERSION " ===");
LogLine("by m4xw, natinusala, p-sam, Souldbminer, Lightos_ and Dominatorul");
@@ -35,7 +35,6 @@
#include <vector>
#define FILE_CONFIG_DIR "/config/" CONFIG_DIR
#define FILE_FLAG_CHECK_INTERVAL_NS (10000ULL * 1000000000ULL)
#define FILE_CONTEXT_CSV_PATH FILE_CONFIG_DIR "/context.csv"
#define FILE_LOG_FLAG_PATH FILE_CONFIG_DIR "/log.flag"
#define FILE_UART_FLAG_PATH FILE_CONFIG_DIR "/uart.flag"
+1 -1
View File
@@ -38,7 +38,7 @@
#include "mgr/clock_manager.hpp"
#define INNER_HEAP_SIZE 0x40000
#define INNER_HEAP_SIZE 0x48000
extern "C" {
void virtmemSetup(void);
@@ -65,4 +65,36 @@ Result SmcCopyToIram(uintptr_t dest, const void *src, u32 size) {
rc = (26u | ((u32)args.X[0] << 9u));
}
return rc;
}
bool IsPatchedExosphere() {
static bool checked = false;
static bool patched = false;
if (!checked) {
constexpr u64 EvpPhysBase = 0x6000F000;
constexpr u64 EvpCopResetVector = 0x200;
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) {
SecmonArgs args = {};
args.X[0] = 0xF0000002;
args.X[1] = phys_addr;
args.X[2] = mask;
args.X[3] = value;
svcCallSecureMonitor(&args);
return (u32)args.X[1];
}
@@ -22,3 +22,5 @@ Result QueryMemoryMapping(u64 *virtaddr, u64 physaddr, u64 size);
Result MapAddress(u64 &va, const u64 &physAddr, const char *name);
Result SmcCopyFromIram(void *dest, uintptr_t src, u32 size);
Result SmcCopyToIram(uintptr_t dest, const void *src, u32 size);
bool IsPatchedExosphere();
u32 SmcReadWriteRegister(u64 phys_addr, u32 mask, u32 value);
@@ -30,6 +30,7 @@
#include <i2c.h>
#include "../board/board.hpp"
#include "../bpmp/bpmp.hpp"
#include "../display/aula.hpp"
#include "../display/display_refresh_rate.hpp"
#include "../file/config.hpp"
@@ -629,6 +630,8 @@ namespace clockManager {
bool RefreshContext() {
bool hasChanged = false;
gContext.bpmpInfo = *bpmp::GetSharedInfo();
std::uint32_t mode = 0;
Result rc = apmExtGetCurrentPerformanceConfiguration(&mode);
ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration");
@@ -879,6 +882,11 @@ namespace clockManager {
}
void WaitForNextTick() {
if (!bpmp::IsAwake()) {
svcSleepThread(250 * 1000000ULL); // 250ms
return;
}
if (board::GetHz(HocClkModule_MEM) > 665000000)
svcSleepThread(config::GetConfigValue(HocClkConfigValue_PollingIntervalMs) * 1000000ULL);
else
@@ -24,6 +24,7 @@
#include <switch.h>
#include "../board/board.hpp"
#include "../bpmp/bpmp.hpp"
#include "../file/file_utils.hpp"
#include "../mapping/mem_map.hpp"
#include "soctherm.hpp"
@@ -413,15 +414,6 @@ namespace tsensor {
bool isMariko;
} // namespace
bool IsDisabledThroughSleep() {
return (ReadReg(carVa, CLK_RST_CONTROLLER_RST_DEVICES) & SWR_SOC_THERM_RST) ||
!(ReadReg(carVa, CLK_RST_CONTROLLER_CLK_OUT_ENB) & CLK_ENB_SOC_THERM);
}
bool IsSensorEnabled() {
return ReadReg(socthermVa, TSENSOR_TSENSOR_CLKEN);
}
void EnableSensor(const TSensor *sensor, u32 sensorIdx) {
u32 val = sensor->config->tall << SENSOR_CONFIG0_TALL_SHIFT;
WriteReg(socthermVa, sensor->base + SENSOR_CONFIG0, val);
@@ -434,21 +426,6 @@ namespace tsensor {
WriteReg(socthermVa, sensor->base + SENSOR_CONFIG2, calib[sensorIdx]);
}
s32 TranslateTemp(u16 val) {
s32 t;
t = ((val & READBACK_VALUE_MASK) >> READBACK_VALUE_SHIFT) * 1000;
if (val & READBACK_ADD_HALF) {
t += 500;
}
if (val & READBACK_NEGATE) {
t *= -1;
}
return t;
}
void StartSensors() {
u32 pdiv, hotspot;
@@ -474,23 +451,15 @@ namespace tsensor {
}
void ReadTSensors(TSensorTemps &temps) {
if (IsDisabledThroughSleep()) {
const HocClkBpmpSharedInfo *bpmpInfo = bpmp::GetSharedInfo();
if (bpmpInfo->magic != HOCCLK_BPMP_MAGIC) {
return;
}
if (!IsSensorEnabled()) {
StartSensors();
}
temps.cpu = TranslateTemp(ReadReg(socthermVa, SENSOR_TEMP1) >> 16);
temps.gpu = TranslateTemp(ReadReg(socthermVa, SENSOR_TEMP1) & SENSOR_TEMP1_GPU_TEMP_MASK);
temps.pllx = TranslateTemp(ReadReg(socthermVa, SENSOR_TEMP2) & SENSOR_TEMP2_PLLX_TEMP_MASK);
if (board::GetSocType() == HocClkSocType_Erista) {
temps.mem = TranslateTemp(ReadReg(socthermVa, SENSOR_TEMP2) >> 16);
} else {
temps.mem = -1;
}
temps.cpu = bpmpInfo->tempCpu;
temps.gpu = bpmpInfo->tempGpu;
temps.pllx = bpmpInfo->tempPllx;
temps.mem = (board::GetSocType() == HocClkSocType_Erista) ? bpmpInfo->tempMem : bpmpInfo->tempPllx;
}
void InitializeSoctherm() {