rewrite everything

This commit is contained in:
souldbminersmwc
2025-09-17 19:56:06 -04:00
parent a1bfcebba8
commit f3eae72b47
177 changed files with 49152 additions and 1258 deletions

View File

@@ -0,0 +1,377 @@
/*
* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If you meet any of us some day, and you think this
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
* --------------------------------------------------------------------------
*/
#include <nxExt.h>
#include "board.h"
#include "errors.h"
#define HOSSVC_HAS_CLKRST (hosversionAtLeast(8,0,0))
#define HOSSVC_HAS_TC (hosversionAtLeast(5,0,0))
static SysClkSocType g_socType = SysClkSocType_Erista;
const char* Board::GetModuleName(SysClkModule module, bool pretty)
{
ASSERT_ENUM_VALID(SysClkModule, module);
return sysclkFormatModule(module, pretty);
}
const char* Board::GetProfileName(SysClkProfile profile, bool pretty)
{
ASSERT_ENUM_VALID(SysClkProfile, profile);
return sysclkFormatProfile(profile, pretty);
}
const char* Board::GetThermalSensorName(SysClkThermalSensor sensor, bool pretty)
{
ASSERT_ENUM_VALID(SysClkThermalSensor, sensor);
return sysclkFormatThermalSensor(sensor, pretty);
}
const char* Board::GetPowerSensorName(SysClkPowerSensor sensor, bool pretty)
{
ASSERT_ENUM_VALID(SysClkPowerSensor, sensor);
return sysclkFormatPowerSensor(sensor, pretty);
}
PcvModule Board::GetPcvModule(SysClkModule sysclkModule)
{
switch(sysclkModule)
{
case SysClkModule_CPU:
return PcvModule_CpuBus;
case SysClkModule_GPU:
return PcvModule_GPU;
case SysClkModule_MEM:
return PcvModule_EMC;
default:
ASSERT_ENUM_VALID(SysClkModule, sysclkModule);
}
return (PcvModule)0;
}
PcvModuleId Board::GetPcvModuleId(SysClkModule sysclkModule)
{
PcvModuleId pcvModuleId;
Result rc = pcvGetModuleId(&pcvModuleId, GetPcvModule(sysclkModule));
ASSERT_RESULT_OK(rc, "pcvGetModuleId");
return pcvModuleId;
}
void Board::Initialize()
{
Result rc = 0;
if(HOSSVC_HAS_CLKRST)
{
rc = clkrstInitialize();
ASSERT_RESULT_OK(rc, "clkrstInitialize");
}
else
{
rc = pcvInitialize();
ASSERT_RESULT_OK(rc, "pcvInitialize");
}
rc = apmExtInitialize();
ASSERT_RESULT_OK(rc, "apmExtInitialize");
rc = psmInitialize();
ASSERT_RESULT_OK(rc, "psmInitialize");
if(HOSSVC_HAS_TC)
{
rc = tcInitialize();
ASSERT_RESULT_OK(rc, "tcInitialize");
}
rc = max17050Initialize();
ASSERT_RESULT_OK(rc, "max17050Initialize");
rc = tmp451Initialize();
ASSERT_RESULT_OK(rc, "tmp451Initialize");
FetchHardwareInfos();
}
void Board::Exit()
{
if(HOSSVC_HAS_CLKRST)
{
clkrstExit();
}
else
{
pcvExit();
}
apmExtExit();
psmExit();
if(HOSSVC_HAS_TC)
{
tcExit();
}
max17050Exit();
tmp451Exit();
}
SysClkProfile Board::GetProfile()
{
std::uint32_t mode = 0;
Result rc = apmExtGetPerformanceMode(&mode);
ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode");
if(mode)
{
return SysClkProfile_Docked;
}
PsmChargerType chargerType;
rc = psmGetChargerType(&chargerType);
ASSERT_RESULT_OK(rc, "psmGetChargerType");
if(chargerType == PsmChargerType_EnoughPower)
{
return SysClkProfile_HandheldChargingOfficial;
}
else if(chargerType == PsmChargerType_LowPower)
{
return SysClkProfile_HandheldChargingUSB;
}
return SysClkProfile_Handheld;
}
void Board::SetHz(SysClkModule module, std::uint32_t hz)
{
Result rc = 0;
if(HOSSVC_HAS_CLKRST)
{
ClkrstSession session = {0};
rc = clkrstOpenSession(&session, Board::GetPcvModuleId(module), 3);
ASSERT_RESULT_OK(rc, "clkrstOpenSession");
rc = clkrstSetClockRate(&session, hz);
ASSERT_RESULT_OK(rc, "clkrstSetClockRate");
clkrstCloseSession(&session);
}
else
{
rc = pcvSetClockRate(Board::GetPcvModule(module), hz);
ASSERT_RESULT_OK(rc, "pcvSetClockRate");
}
}
std::uint32_t Board::GetHz(SysClkModule module)
{
Result rc = 0;
std::uint32_t hz = 0;
if(HOSSVC_HAS_CLKRST)
{
ClkrstSession session = {0};
rc = clkrstOpenSession(&session, Board::GetPcvModuleId(module), 3);
ASSERT_RESULT_OK(rc, "clkrstOpenSession");
rc = clkrstGetClockRate(&session, &hz);
ASSERT_RESULT_OK(rc, "clkrstSetClockRate");
clkrstCloseSession(&session);
}
else
{
rc = pcvGetClockRate(Board::GetPcvModule(module), &hz);
ASSERT_RESULT_OK(rc, "pcvGetClockRate");
}
return hz;
}
std::uint32_t Board::GetRealHz(SysClkModule module)
{
switch(module)
{
case SysClkModule_CPU:
return t210ClkCpuFreq();
case SysClkModule_GPU:
return t210ClkGpuFreq();
case SysClkModule_MEM:
return t210ClkMemFreq();
default:
ASSERT_ENUM_VALID(SysClkModule, module);
}
return 0;
}
void Board::GetFreqList(SysClkModule module, std::uint32_t* outList, std::uint32_t maxCount, std::uint32_t* outCount)
{
Result rc = 0;
PcvClockRatesListType type;
s32 tmpInMaxCount = maxCount;
s32 tmpOutCount = 0;
if(HOSSVC_HAS_CLKRST)
{
ClkrstSession session = {0};
rc = clkrstOpenSession(&session, Board::GetPcvModuleId(module), 3);
ASSERT_RESULT_OK(rc, "clkrstOpenSession");
rc = clkrstGetPossibleClockRates(&session, outList, tmpInMaxCount, &type, &tmpOutCount);
ASSERT_RESULT_OK(rc, "clkrstGetPossibleClockRates");
clkrstCloseSession(&session);
}
else
{
rc = pcvGetPossibleClockRates(Board::GetPcvModule(module), outList, tmpInMaxCount, &type, &tmpOutCount);
ASSERT_RESULT_OK(rc, "pcvGetPossibleClockRates");
}
if(type != PcvClockRatesListType_Discrete)
{
ERROR_THROW("Unexpected PcvClockRatesListType: %u (module = %s)", type, Board::GetModuleName(module, false));
}
*outCount = tmpOutCount;
}
void Board::ResetToStock()
{
Result rc = 0;
if(hosversionAtLeast(9,0,0))
{
std::uint32_t confId = 0;
rc = apmExtGetCurrentPerformanceConfiguration(&confId);
ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration");
SysClkApmConfiguration* apmConfiguration = NULL;
for(size_t i = 0; sysclk_g_apm_configurations[i].id; i++)
{
if(sysclk_g_apm_configurations[i].id == confId)
{
apmConfiguration = &sysclk_g_apm_configurations[i];
break;
}
}
if(!apmConfiguration)
{
ERROR_THROW("Unknown apm configuration: %x", confId);
}
Board::SetHz(SysClkModule_CPU, apmConfiguration->cpu_hz);
Board::SetHz(SysClkModule_GPU, apmConfiguration->gpu_hz);
Board::SetHz(SysClkModule_MEM, apmConfiguration->mem_hz);
}
else
{
std::uint32_t mode = 0;
rc = apmExtGetPerformanceMode(&mode);
ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode");
rc = apmExtSysRequestPerformanceMode(mode);
ASSERT_RESULT_OK(rc, "apmExtSysRequestPerformanceMode");
}
}
std::uint32_t Board::GetTemperatureMilli(SysClkThermalSensor sensor)
{
std::int32_t millis = 0;
if(sensor == SysClkThermalSensor_SOC)
{
millis = tmp451TempSoc();
}
else if(sensor == SysClkThermalSensor_PCB)
{
millis = tmp451TempPcb();
}
else if(sensor == SysClkThermalSensor_Skin)
{
if(HOSSVC_HAS_TC)
{
Result rc;
rc = tcGetSkinTemperatureMilliC(&millis);
ASSERT_RESULT_OK(rc, "tcGetSkinTemperatureMilliC");
}
}
else
{
ASSERT_ENUM_VALID(SysClkThermalSensor, sensor);
}
return std::max(0, millis);
}
std::int32_t Board::GetPowerMw(SysClkPowerSensor sensor)
{
switch(sensor)
{
case SysClkPowerSensor_Now:
return max17050PowerNow();
case SysClkPowerSensor_Avg:
return max17050PowerAvg();
default:
ASSERT_ENUM_VALID(SysClkPowerSensor, sensor);
}
return 0;
}
std::uint32_t Board::GetRamLoad(SysClkRamLoad loadSource)
{
switch(loadSource)
{
case SysClkRamLoad_All:
return t210EmcLoadAll();
case SysClkRamLoad_Cpu:
return t210EmcLoadCpu();
default:
ASSERT_ENUM_VALID(SysClkRamLoad, loadSource);
}
return 0;
}
SysClkSocType Board::GetSocType() {
return g_socType;
}
void Board::FetchHardwareInfos()
{
u64 sku = 0;
Result rc = splInitialize();
ASSERT_RESULT_OK(rc, "splInitialize");
rc = splGetConfig(SplConfigItem_HardwareType, &sku);
ASSERT_RESULT_OK(rc, "splGetConfig");
splExit();
switch(sku)
{
case 2 ... 5:
g_socType = SysClkSocType_Mariko;
break;
default:
g_socType = SysClkSocType_Erista;
}
}

View File

@@ -0,0 +1,40 @@
/*
* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If you meet any of us some day, and you think this
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
* --------------------------------------------------------------------------
*/
#pragma once
#include <cstdint>
#include <switch.h>
#include <sysclk.h>
class Board
{
public:
static const char* GetProfileName(SysClkProfile profile, bool pretty);
static const char* GetModuleName(SysClkModule module, bool pretty);
static const char* GetThermalSensorName(SysClkThermalSensor sensor, bool pretty);
static const char* GetPowerSensorName(SysClkPowerSensor sensor, bool pretty);
static void Initialize();
static void Exit();
static void ResetToStock();
static SysClkProfile GetProfile();
static void SetHz(SysClkModule module, std::uint32_t hz);
static std::uint32_t GetHz(SysClkModule module);
static std::uint32_t GetRealHz(SysClkModule module);
static void GetFreqList(SysClkModule module, std::uint32_t* outList, std::uint32_t maxCount, std::uint32_t* outCount);
static std::uint32_t GetTemperatureMilli(SysClkThermalSensor sensor);
static std::int32_t GetPowerMw(SysClkPowerSensor sensor);
static std::uint32_t GetRamLoad(SysClkRamLoad load);
static SysClkSocType GetSocType();
protected:
static void FetchHardwareInfos();
static PcvModule GetPcvModule(SysClkModule sysclkModule);
static PcvModuleId GetPcvModuleId(SysClkModule sysclkModule);
};

View File

@@ -0,0 +1,316 @@
/*
* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If you meet any of us some day, and you think this
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
* --------------------------------------------------------------------------
*/
#include "clock_manager.h"
#include <cstring>
#include "file_utils.h"
#include "board.h"
#include "process_management.h"
#include "errors.h"
#include "ipc_service.h"
ClockManager::ClockManager()
{
this->config = Config::CreateDefault();
this->context = new SysClkContext;
this->context->applicationId = 0;
this->context->profile = SysClkProfile_Handheld;
this->context->enabled = false;
for (unsigned int module = 0; module < SysClkModule_EnumMax; module++)
{
this->context->freqs[module] = 0;
this->context->realFreqs[module] = 0;
this->context->overrideFreqs[module] = 0;
this->RefreshFreqTableRow((SysClkModule)module);
}
this->running = false;
this->lastTempLogNs = 0;
this->lastCsvWriteNs = 0;
}
ClockManager::~ClockManager()
{
delete this->config;
delete this->context;
}
SysClkContext ClockManager::GetCurrentContext()
{
std::scoped_lock lock{this->contextMutex};
return *this->context;
}
Config *ClockManager::GetConfig()
{
return this->config;
}
void ClockManager::SetRunning(bool running)
{
this->running = running;
}
bool ClockManager::Running()
{
return this->running;
}
void ClockManager::GetFreqList(SysClkModule module, std::uint32_t *list, std::uint32_t maxCount, std::uint32_t *outCount)
{
ASSERT_ENUM_VALID(SysClkModule, module);
*outCount = std::min(maxCount, this->freqTable[module].count);
memcpy(list, &this->freqTable[module].list[0], *outCount * sizeof(this->freqTable[0].list[0]));
}
bool ClockManager::IsAssignableHz(SysClkModule module, std::uint32_t hz)
{
switch (module)
{
case SysClkModule_CPU:
return hz >= 400000000;
case SysClkModule_MEM:
return hz == 204000000 || hz >= 665600000;
default:
return true;
}
}
std::uint32_t ClockManager::GetMaxAllowedHz(SysClkModule module, SysClkProfile profile)
{
return 4294967294; // Integer limit, uncapped clocks ON
}
std::uint32_t ClockManager::GetNearestHz(SysClkModule module, std::uint32_t inHz, std::uint32_t maxHz)
{
std::uint32_t *freqs = &this->freqTable[module].list[0];
size_t count = this->freqTable[module].count - 1;
size_t i = 0;
while (i < count)
{
if (maxHz > 0 && freqs[i] >= maxHz)
{
break;
}
if (inHz <= ((std::uint64_t)freqs[i] + freqs[i + 1]) / 2)
{
break;
}
i++;
}
return freqs[i];
}
bool ClockManager::ConfigIntervalTimeout(SysClkConfigValue intervalMsConfigValue, std::uint64_t ns, std::uint64_t *lastLogNs)
{
std::uint64_t logInterval = this->GetConfig()->GetConfigValue(intervalMsConfigValue) * 1000000ULL;
bool shouldLog = logInterval && ((ns - *lastLogNs) > logInterval);
if (shouldLog)
{
*lastLogNs = ns;
}
return shouldLog;
}
void ClockManager::RefreshFreqTableRow(SysClkModule module)
{
std::scoped_lock lock{this->contextMutex};
std::uint32_t freqs[SYSCLK_FREQ_LIST_MAX];
std::uint32_t count;
FileUtils::LogLine("[mgr] %s freq list refresh", Board::GetModuleName(module, true));
Board::GetFreqList(module, &freqs[0], SYSCLK_FREQ_LIST_MAX, &count);
std::uint32_t *hz = &this->freqTable[module].list[0];
this->freqTable[module].count = 0;
for (std::uint32_t i = 0; i < count; i++)
{
if (!this->IsAssignableHz(module, freqs[i]))
{
continue;
}
*hz = freqs[i];
FileUtils::LogLine("[mgr] %02u - %u - %u.%u MHz", this->freqTable[module].count, *hz, *hz / 1000000, *hz / 100000 - *hz / 1000000 * 10);
this->freqTable[module].count++;
hz++;
}
FileUtils::LogLine("[mgr] count = %u", this->freqTable[module].count);
}
void ClockManager::Tick()
{
std::scoped_lock lock{this->contextMutex};
if (this->RefreshContext() || this->config->Refresh())
{
std::uint32_t targetHz = 0;
std::uint32_t maxHz = 0;
std::uint32_t nearestHz = 0;
for (unsigned int module = 0; module < SysClkModule_EnumMax; module++)
{
targetHz = this->context->overrideFreqs[module];
if (!targetHz)
{
targetHz = this->config->GetAutoClockHz(this->context->applicationId, (SysClkModule)module, this->context->profile);
}
if (targetHz)
{
maxHz = this->GetMaxAllowedHz((SysClkModule)module, this->context->profile);
nearestHz = this->GetNearestHz((SysClkModule)module, targetHz, maxHz);
if (nearestHz != this->context->freqs[module] && this->context->enabled)
{
FileUtils::LogLine(
"[mgr] %s clock set : %u.%u MHz (target = %u.%u MHz)",
Board::GetModuleName((SysClkModule)module, true),
nearestHz / 1000000, nearestHz / 100000 - nearestHz / 1000000 * 10,
targetHz / 1000000, targetHz / 100000 - targetHz / 1000000 * 10);
Board::SetHz((SysClkModule)module, nearestHz);
this->context->freqs[module] = nearestHz;
}
}
}
}
}
void ClockManager::WaitForNextTick()
{
svcSleepThread(this->GetConfig()->GetConfigValue(SysClkConfigValue_PollingIntervalMs) * 1000000ULL);
}
bool ClockManager::RefreshContext()
{
bool hasChanged = false;
bool enabled = this->GetConfig()->Enabled();
if (enabled != this->context->enabled)
{
this->context->enabled = enabled;
FileUtils::LogLine("[mgr] " TARGET " status: %s", enabled ? "enabled" : "disabled");
hasChanged = true;
}
std::uint64_t applicationId = ProcessManagement::GetCurrentApplicationId();
if (applicationId != this->context->applicationId)
{
FileUtils::LogLine("[mgr] TitleID change: %016lX", applicationId);
this->context->applicationId = applicationId;
hasChanged = true;
}
SysClkProfile profile = Board::GetProfile();
if (profile != this->context->profile)
{
FileUtils::LogLine("[mgr] Profile change: %s", Board::GetProfileName(profile, true));
this->context->profile = profile;
hasChanged = true;
}
// restore clocks to stock values on app or profile change
if (hasChanged)
{
Board::ResetToStock();
this->WaitForNextTick();
}
std::uint32_t hz = 0;
for (unsigned int module = 0; module < SysClkModule_EnumMax; module++)
{
hz = Board::GetHz((SysClkModule)module);
if (hz != 0 && hz != this->context->freqs[module])
{
FileUtils::LogLine("[mgr] %s clock change: %u.%u MHz", Board::GetModuleName((SysClkModule)module, true), hz / 1000000, hz / 100000 - hz / 1000000 * 10);
this->context->freqs[module] = hz;
hasChanged = true;
}
hz = this->GetConfig()->GetOverrideHz((SysClkModule)module);
if (hz != this->context->overrideFreqs[module])
{
if (hz)
{
FileUtils::LogLine("[mgr] %s override change: %u.%u MHz", Board::GetModuleName((SysClkModule)module, true), hz / 1000000, hz / 100000 - hz / 1000000 * 10);
}
else
{
FileUtils::LogLine("[mgr] %s override disabled", Board::GetModuleName((SysClkModule)module, true));
}
this->context->overrideFreqs[module] = hz;
hasChanged = true;
}
}
std::uint64_t ns = armTicksToNs(armGetSystemTick());
// temperatures do not and should not force a refresh, hasChanged untouched
std::uint32_t millis = 0;
bool shouldLogTemp = this->ConfigIntervalTimeout(SysClkConfigValue_TempLogIntervalMs, ns, &this->lastTempLogNs);
for (unsigned int sensor = 0; sensor < SysClkThermalSensor_EnumMax; sensor++)
{
millis = Board::GetTemperatureMilli((SysClkThermalSensor)sensor);
if (shouldLogTemp)
{
FileUtils::LogLine("[mgr] %s temp: %u.%u °C", Board::GetThermalSensorName((SysClkThermalSensor)sensor, true), millis / 1000, (millis - millis / 1000 * 1000) / 100);
}
this->context->temps[sensor] = millis;
}
// power stats do not and should not force a refresh, hasChanged untouched
std::int32_t mw = 0;
bool shouldLogPower = this->ConfigIntervalTimeout(SysClkConfigValue_PowerLogIntervalMs, ns, &this->lastPowerLogNs);
for (unsigned int sensor = 0; sensor < SysClkPowerSensor_EnumMax; sensor++)
{
mw = Board::GetPowerMw((SysClkPowerSensor)sensor);
if (shouldLogPower)
{
FileUtils::LogLine("[mgr] Power %s: %d mW", Board::GetPowerSensorName((SysClkPowerSensor)sensor, false), mw);
}
this->context->power[sensor] = mw;
}
// real freqs do not and should not force a refresh, hasChanged untouched
std::uint32_t realHz = 0;
bool shouldLogFreq = this->ConfigIntervalTimeout(SysClkConfigValue_FreqLogIntervalMs, ns, &this->lastFreqLogNs);
for (unsigned int module = 0; module < SysClkModule_EnumMax; module++)
{
realHz = Board::GetRealHz((SysClkModule)module);
if (shouldLogFreq)
{
FileUtils::LogLine("[mgr] %s real freq: %u.%u MHz", Board::GetModuleName((SysClkModule)module, true), realHz / 1000000, realHz / 100000 - realHz / 1000000 * 10);
}
this->context->realFreqs[module] = realHz;
}
// ram load do not and should not force a refresh, hasChanged untouched
for (unsigned int loadSource = 0; loadSource < SysClkRamLoad_EnumMax; loadSource++)
{
this->context->ramLoad[loadSource] = Board::GetRamLoad((SysClkRamLoad)loadSource);
}
if (this->ConfigIntervalTimeout(SysClkConfigValue_CsvWriteIntervalMs, ns, &this->lastCsvWriteNs))
{
FileUtils::WriteContextToCsv(this->context);
}
return hasChanged;
}

View File

@@ -0,0 +1,54 @@
/*
* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If you meet any of us some day, and you think this
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
* --------------------------------------------------------------------------
*/
#pragma once
#include <atomic>
#include <sysclk.h>
#include "config.h"
#include "board.h"
#include <nxExt/cpp/lockable_mutex.h>
class ClockManager
{
public:
ClockManager();
virtual ~ClockManager();
SysClkContext GetCurrentContext();
Config* GetConfig();
void SetRunning(bool running);
bool Running();
void GetFreqList(SysClkModule module, std::uint32_t* list, std::uint32_t maxCount, std::uint32_t* outCount);
void Tick();
void WaitForNextTick();
protected:
bool IsAssignableHz(SysClkModule module, std::uint32_t hz);
std::uint32_t GetMaxAllowedHz(SysClkModule module, SysClkProfile profile);
std::uint32_t GetNearestHz(SysClkModule module, std::uint32_t inHz, std::uint32_t maxHz);
bool ConfigIntervalTimeout(SysClkConfigValue intervalMsConfigValue, std::uint64_t ns, std::uint64_t* lastLogNs);
void RefreshFreqTableRow(SysClkModule module);
bool RefreshContext();
std::atomic_bool running;
LockableMutex contextMutex;
struct {
std::uint32_t count;
std::uint32_t list[SYSCLK_FREQ_LIST_MAX];
} freqTable[SysClkModule_EnumMax];
Config* config;
SysClkContext* context;
std::uint64_t lastTempLogNs;
std::uint64_t lastFreqLogNs;
std::uint64_t lastPowerLogNs;
std::uint64_t lastCsvWriteNs;
};

View File

@@ -0,0 +1,473 @@
/*
* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If you meet any of us some day, and you think this
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
* --------------------------------------------------------------------------
*/
#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sstream>
#include <algorithm>
#include <cstring>
#include "errors.h"
#include "file_utils.h"
Config::Config(std::string path)
{
this->path = path;
this->loaded = false;
this->profileMHzMap = std::map<std::tuple<std::uint64_t, SysClkProfile, SysClkModule>, std::uint32_t>();
this->profileCountMap = std::map<std::uint64_t, std::uint8_t>();
this->mtime = 0;
this->enabled = false;
for(unsigned int i = 0; i < SysClkModule_EnumMax; i++)
{
this->overrideFreqs[i] = 0;
}
for(unsigned int i = 0; i < SysClkConfigValue_EnumMax; i++)
{
this->configValues[i] = sysclkDefaultConfigValue((SysClkConfigValue)i);
}
}
Config::~Config()
{
std::scoped_lock lock{this->configMutex};
this->Close();
}
Config* Config::CreateDefault()
{
return new Config(FILE_CONFIG_DIR "/config.ini");
}
void Config::Load()
{
FileUtils::LogLine("[cfg] Reading %s", this->path.c_str());
this->Close();
this->mtime = this->CheckModificationTime();
if(!this->mtime)
{
FileUtils::LogLine("[cfg] Error finding file");
}
else if (!ini_browse(&BrowseIniFunc, this, this->path.c_str()))
{
FileUtils::LogLine("[cfg] Error loading file");
}
this->loaded = true;
}
void Config::Close()
{
this->loaded = false;
this->profileMHzMap.clear();
this->profileCountMap.clear();
for(unsigned int i = 0; i < SysClkConfigValue_EnumMax; i++)
{
this->configValues[i] = sysclkDefaultConfigValue((SysClkConfigValue)i);
}
}
bool Config::Refresh()
{
std::scoped_lock lock{this->configMutex};
if (!this->loaded || this->mtime != this->CheckModificationTime())
{
this->Load();
return true;
}
return false;
}
bool Config::HasProfilesLoaded()
{
std::scoped_lock lock{this->configMutex};
return this->loaded;
}
time_t Config::CheckModificationTime()
{
time_t mtime = 0;
struct stat st;
if (stat(this->path.c_str(), &st) == 0)
{
mtime = st.st_mtime;
}
return mtime;
}
std::uint32_t Config::FindClockMHz(std::uint64_t tid, SysClkModule module, SysClkProfile profile)
{
if (this->loaded)
{
std::map<std::tuple<std::uint64_t, SysClkProfile, SysClkModule>, std::uint32_t>::const_iterator it = this->profileMHzMap.find(std::make_tuple(tid, profile, module));
if (it != this->profileMHzMap.end())
{
return it->second;
}
}
return 0;
}
std::uint32_t Config::FindClockHzFromProfiles(std::uint64_t tid, SysClkModule module, std::initializer_list<SysClkProfile> profiles)
{
std::uint32_t mhz = 0;
if (this->loaded)
{
for(auto profile: profiles)
{
mhz = FindClockMHz(tid, module, profile);
if(mhz)
{
break;
}
}
}
return std::max((std::uint32_t)0, mhz * 1000000);
}
std::uint32_t Config::GetAutoClockHz(std::uint64_t tid, SysClkModule module, SysClkProfile profile)
{
std::scoped_lock lock{this->configMutex};
switch(profile)
{
case SysClkProfile_Handheld:
return FindClockHzFromProfiles(tid, module, {SysClkProfile_Handheld});
case SysClkProfile_HandheldCharging:
case SysClkProfile_HandheldChargingUSB:
return FindClockHzFromProfiles(tid, module, {SysClkProfile_HandheldChargingUSB, SysClkProfile_HandheldCharging, SysClkProfile_Handheld});
case SysClkProfile_HandheldChargingOfficial:
return FindClockHzFromProfiles(tid, module, {SysClkProfile_HandheldChargingOfficial, SysClkProfile_HandheldCharging, SysClkProfile_Handheld});
case SysClkProfile_Docked:
return FindClockHzFromProfiles(tid, module, {SysClkProfile_Docked});
default:
ERROR_THROW("Unhandled SysClkProfile: %u", profile);
}
return 0;
}
void Config::GetProfiles(std::uint64_t tid, SysClkTitleProfileList* out_profiles)
{
std::scoped_lock lock{this->configMutex};
for(unsigned int profile = 0; profile < SysClkProfile_EnumMax; profile++)
{
for(unsigned int module = 0; module < SysClkModule_EnumMax; module++)
{
out_profiles->mhzMap[profile][module] = FindClockMHz(tid, (SysClkModule)module, (SysClkProfile)profile);
}
}
}
bool Config::SetProfiles(std::uint64_t tid, SysClkTitleProfileList* profiles, bool immediate)
{
std::scoped_lock lock{this->configMutex};
uint8_t numProfiles = 0;
// String pointer array passed to ini
char* iniKeys[SysClkProfile_EnumMax * SysClkModule_EnumMax + 1];
char* iniValues[SysClkProfile_EnumMax * SysClkModule_EnumMax + 1];
// Char arrays to build strings
char keysStr[SysClkProfile_EnumMax * SysClkModule_EnumMax * 0x40];
char valuesStr[SysClkProfile_EnumMax * SysClkModule_EnumMax * 0x10];
char section[17] = {0};
// Iteration pointers
char** ik = &iniKeys[0];
char** iv = &iniValues[0];
char* sk = &keysStr[0];
char* sv = &valuesStr[0];
std::uint32_t* mhz = &profiles->mhz[0];
snprintf(section, sizeof(section), "%016lX", tid);
for(unsigned int profile = 0; profile < SysClkProfile_EnumMax; profile++)
{
for(unsigned int module = 0; module < SysClkModule_EnumMax; module++)
{
if(*mhz)
{
numProfiles++;
// Put key and value as string
snprintf(sk, 0x40, "%s_%s", Board::GetProfileName((SysClkProfile)profile, false), Board::GetModuleName((SysClkModule)module, false));
snprintf(sv, 0x10, "%d", *mhz);
// Add them to the ini key/value str arrays
*ik = sk;
*iv = sv;
ik++;
iv++;
// We used those chars, get to the next ones
sk += 0x40;
sv += 0x10;
}
mhz++;
}
}
*ik = NULL;
*iv = NULL;
if(!ini_putsection(section, (const char**)iniKeys, (const char**)iniValues, this->path.c_str()))
{
return false;
}
// Only actually apply changes in memory after a succesful save
if(immediate)
{
mhz = &profiles->mhz[0];
this->profileCountMap[tid] = numProfiles;
for(unsigned int profile = 0; profile < SysClkProfile_EnumMax; profile++)
{
for(unsigned int module = 0; module < SysClkModule_EnumMax; module++)
{
if(*mhz)
{
this->profileMHzMap[std::make_tuple(tid, (SysClkProfile)profile, (SysClkModule)module)] = *mhz;
}
else
{
this->profileMHzMap.erase(std::make_tuple(tid, (SysClkProfile)profile, (SysClkModule)module));
}
mhz++;
}
}
}
return true;
}
std::uint8_t Config::GetProfileCount(std::uint64_t tid)
{
std::map<std::uint64_t, std::uint8_t>::iterator it = this->profileCountMap.find(tid);
if (it == this->profileCountMap.end())
{
return 0;
}
return it->second;
}
int Config::BrowseIniFunc(const char* section, const char* key, const char* value, void* userdata)
{
Config* config = (Config*)userdata;
std::uint64_t input;
if(!strcmp(section, CONFIG_VAL_SECTION))
{
for(unsigned int kval = 0; kval < SysClkConfigValue_EnumMax; kval++)
{
if(!strcmp(key, sysclkFormatConfigValue((SysClkConfigValue)kval, false)))
{
input = strtoul(value, NULL, 0);
if(!sysclkValidConfigValue((SysClkConfigValue)kval, input))
{
input = sysclkDefaultConfigValue((SysClkConfigValue)kval);
FileUtils::LogLine("[cfg] Invalid value for key '%s' in section '%s': using default %d", key, section, input);
}
config->configValues[kval] = input;
return 1;
}
}
FileUtils::LogLine("[cfg] Skipping key '%s' in section '%s': Unrecognized config value", key, section);
return 1;
}
std::uint64_t tid = strtoul(section, NULL, 16);
if(!tid || strlen(section) != 16)
{
FileUtils::LogLine("[cfg] Skipping key '%s' in section '%s': Invalid TitleID", key, section);
return 1;
}
SysClkProfile parsedProfile = SysClkProfile_EnumMax;
SysClkModule parsedModule = SysClkModule_EnumMax;
for(unsigned int profile = 0; profile < SysClkProfile_EnumMax; profile++)
{
const char* profileCode = Board::GetProfileName((SysClkProfile)profile, false);
size_t profileCodeLen = strlen(profileCode);
if(!strncmp(key, profileCode, profileCodeLen) && key[profileCodeLen] == '_')
{
const char* subkey = key + profileCodeLen + 1;
for(unsigned int module = 0; module < SysClkModule_EnumMax; module++)
{
const char* moduleCode = Board::GetModuleName((SysClkModule)module, false);
size_t moduleCodeLen = strlen(moduleCode);
if(!strncmp(subkey, moduleCode, moduleCodeLen) && subkey[moduleCodeLen] == '\0')
{
parsedProfile = (SysClkProfile)profile;
parsedModule = (SysClkModule)module;
}
}
}
}
if(parsedModule == SysClkModule_EnumMax || parsedProfile == SysClkProfile_EnumMax)
{
FileUtils::LogLine("[cfg] Skipping key '%s' in section '%s': Unrecognized key", key, section);
return 1;
}
std::uint32_t mhz = strtoul(value, NULL, 10);
if(!mhz)
{
FileUtils::LogLine("[cfg] Skipping key '%s' in section '%s': Invalid value", key, section);
return 1;
}
config->profileMHzMap[std::make_tuple(tid, parsedProfile, parsedModule)] = mhz;
std::map<std::uint64_t, std::uint8_t>::iterator it = config->profileCountMap.find(tid);
if (it == config->profileCountMap.end())
{
config->profileCountMap[tid] = 1;
}
else
{
it->second++;
}
return 1;
}
void Config::SetEnabled(bool enabled)
{
this->enabled = enabled;
}
bool Config::Enabled()
{
return this->enabled;
}
void Config::SetOverrideHz(SysClkModule module, std::uint32_t hz)
{
ASSERT_ENUM_VALID(SysClkModule, module);
std::scoped_lock lock{this->overrideMutex};
this->overrideFreqs[module] = hz;
}
std::uint32_t Config::GetOverrideHz(SysClkModule module)
{
ASSERT_ENUM_VALID(SysClkModule, module);
std::scoped_lock lock{this->overrideMutex};
return this->overrideFreqs[module];
}
std::uint64_t Config::GetConfigValue(SysClkConfigValue kval)
{
ASSERT_ENUM_VALID(SysClkConfigValue, kval);
std::scoped_lock lock{this->configMutex};
return this->configValues[kval];
}
const char* Config::GetConfigValueName(SysClkConfigValue kval, bool pretty)
{
ASSERT_ENUM_VALID(SysClkConfigValue, kval);
const char* result = sysclkFormatConfigValue(kval, pretty);
return result;
}
void Config::GetConfigValues(SysClkConfigValueList* out_configValues)
{
std::scoped_lock lock{this->configMutex};
for(unsigned int kval = 0; kval < SysClkConfigValue_EnumMax; kval++)
{
out_configValues->values[kval] = this->configValues[kval];
}
}
bool Config::SetConfigValues(SysClkConfigValueList* configValues, bool immediate)
{
std::scoped_lock lock{this->configMutex};
// String pointer array passed to ini
const char* iniKeys[SysClkConfigValue_EnumMax + 1];
char* iniValues[SysClkConfigValue_EnumMax + 1];
// char arrays to build strings
char valuesStr[SysClkConfigValue_EnumMax * 0x20];
// Iteration pointers
char* sv = &valuesStr[0];
const char** ik = &iniKeys[0];
char** iv = &iniValues[0];
for(unsigned int kval = 0; kval < SysClkConfigValue_EnumMax; kval++)
{
if(!sysclkValidConfigValue((SysClkConfigValue)kval, configValues->values[kval]) || configValues->values[kval] == sysclkDefaultConfigValue((SysClkConfigValue)kval))
{
continue;
}
// Put key and value as string
// And add them to the ini key/value str arrays
snprintf(sv, 0x20, "%ld", configValues->values[kval]);
*ik = sysclkFormatConfigValue((SysClkConfigValue)kval, false);
*iv = sv;
// We used those chars, get to the next ones
sv += 0x20;
ik++;
iv++;
}
*ik = NULL;
*iv = NULL;
if(!ini_putsection(CONFIG_VAL_SECTION, (const char**)iniKeys, (const char**)iniValues, this->path.c_str()))
{
return false;
}
// Only actually apply changes in memory after a succesful save
if(immediate)
{
for(unsigned int kval = 0; kval < SysClkConfigValue_EnumMax; kval++)
{
if(sysclkValidConfigValue((SysClkConfigValue)kval, configValues->values[kval]))
{
this->configValues[kval] = configValues->values[kval];
}
else
{
this->configValues[kval] = sysclkDefaultConfigValue((SysClkConfigValue)kval);
}
}
}
return true;
}

View File

@@ -0,0 +1,70 @@
/*
* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If you meet any of us some day, and you think this
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
* --------------------------------------------------------------------------
*/
#pragma once
#include <atomic>
#include <ctime>
#include <map>
#include <mutex>
#include <initializer_list>
#include <string>
#include <switch.h>
#include <minIni.h>
#include <nxExt.h>
#include "board.h"
#define CONFIG_VAL_SECTION "values"
class Config
{
public:
Config(std::string path);
virtual ~Config();
static Config* CreateDefault();
bool Refresh();
bool HasProfilesLoaded();
std::uint8_t GetProfileCount(std::uint64_t tid);
void GetProfiles(std::uint64_t tid, SysClkTitleProfileList* out_profiles);
bool SetProfiles(std::uint64_t tid, SysClkTitleProfileList* profiles, bool immediate);
std::uint32_t GetAutoClockHz(std::uint64_t tid, SysClkModule module, SysClkProfile profile);
void SetEnabled(bool enabled);
bool Enabled();
void SetOverrideHz(SysClkModule module, std::uint32_t hz);
std::uint32_t GetOverrideHz(SysClkModule module);
std::uint64_t GetConfigValue(SysClkConfigValue val);
const char* GetConfigValueName(SysClkConfigValue val, bool pretty);
void GetConfigValues(SysClkConfigValueList* out_configValues);
bool SetConfigValues(SysClkConfigValueList* configValues, bool immediate);
protected:
void Load();
void Close();
time_t CheckModificationTime();
std::uint32_t FindClockMHz(std::uint64_t tid, SysClkModule module, SysClkProfile profile);
std::uint32_t FindClockHzFromProfiles(std::uint64_t tid, SysClkModule module, std::initializer_list<SysClkProfile> profiles);
static int BrowseIniFunc(const char* section, const char* key, const char* value, void* userdata);
std::map<std::tuple<std::uint64_t, SysClkProfile, SysClkModule>, std::uint32_t> profileMHzMap;
std::map<std::uint64_t, std::uint8_t> profileCountMap;
bool loaded;
std::string path;
time_t mtime;
LockableMutex configMutex;
LockableMutex overrideMutex;
std::atomic_bool enabled;
std::uint32_t overrideFreqs[SysClkModule_EnumMax];
std::uint64_t configValues[SysClkConfigValue_EnumMax];
};

View File

@@ -0,0 +1,37 @@
/*
* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If you meet any of us some day, and you think this
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
* --------------------------------------------------------------------------
*/
#include "errors.h"
#include <cstdarg>
#include <cstring>
void Errors::ThrowException(const char* format, ...)
{
va_list args;
va_start(args, format);
const char* msg = Errors::FormatMessage(format, args);
va_end(args);
throw std::runtime_error(msg);
}
const char* Errors::FormatMessage(const char* format, va_list args)
{
size_t len = vsnprintf(NULL, 0, format, args) * sizeof(char);
char* buf = (char*)malloc(len + 1);
if (buf == NULL)
{
return format;
}
vsnprintf(buf, len + 1, format, args);
return buf;
}

View File

@@ -0,0 +1,35 @@
/*
* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If you meet any of us some day, and you think this
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
* --------------------------------------------------------------------------
*/
#pragma once
#include <switch.h>
#include <stdexcept>
#define ERROR_THROW(format, ...) Errors::ThrowException(format "\n in %s:%u", ##__VA_ARGS__, __FILE__, __LINE__)
#define ERROR_RESULT_THROW(rc, format, ...) ERROR_THROW(format "\n RC: [0x%x] %04d-%04d", ##__VA_ARGS__, rc, R_MODULE(rc), R_DESCRIPTION(rc))
#define ASSERT_RESULT_OK(rc, format, ...) \
if (R_FAILED(rc)) \
{ \
ERROR_RESULT_THROW(rc, "ASSERT_RESULT_OK: " format, ##__VA_ARGS__); \
}
#define ASSERT_ENUM_VALID(n, v) \
if(!SYSCLK_ENUM_VALID(n, v)) { \
ERROR_THROW("No such %s: %u", #n, v); \
}
class Errors
{
public:
static void ThrowException(const char* format, ...);
protected:
static const char* FormatMessage(const char* format, va_list args);
};

View File

@@ -0,0 +1,199 @@
/*
* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If you meet any of us some day, and you think this
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
* --------------------------------------------------------------------------
*/
#include "file_utils.h"
#include <nxExt.h>
static LockableMutex g_log_mutex;
static LockableMutex g_csv_mutex;
static std::atomic_bool g_has_initialized = false;
static bool g_log_enabled = false;
static std::uint64_t g_last_flag_check = 0;
extern "C" void __libnx_init_time(void);
static void _FileUtils_InitializeThreadFunc(void* args)
{
FileUtils::Initialize();
}
bool FileUtils::IsInitialized()
{
return g_has_initialized;
}
void FileUtils::LogLine(const char* format, ...)
{
std::scoped_lock lock{g_log_mutex};
va_list args;
va_start(args, format);
if (g_has_initialized)
{
FileUtils::RefreshFlags(false);
if(g_log_enabled)
{
FILE* file = fopen(FILE_LOG_FILE_PATH, "a");
if (file)
{
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
struct tm* nowTm = localtime(&now.tv_sec);
fprintf(file, "[%04d-%02d-%02d %02d:%02d:%02d.%03ld] ", nowTm->tm_year+1900, nowTm->tm_mon+1, nowTm->tm_mday, nowTm->tm_hour, nowTm->tm_min, nowTm->tm_sec, now.tv_nsec / 1000000UL);
vfprintf(file, format, args);
fprintf(file, "\n");
fclose(file);
}
}
}
va_end(args);
}
void FileUtils::WriteContextToCsv(const SysClkContext* context)
{
std::scoped_lock lock{g_csv_mutex};
FILE* file = fopen(FILE_CONTEXT_CSV_PATH, "a");
if (file)
{
// Print header
if(!ftell(file))
{
fprintf(file, "timestamp,profile,app_tid");
for (unsigned int module = 0; module < SysClkModule_EnumMax; module++)
{
fprintf(file, ",%s_hz", sysclkFormatModule((SysClkModule)module, false));
}
for (unsigned int sensor = 0; sensor < SysClkThermalSensor_EnumMax; sensor++)
{
fprintf(file, ",%s_milliC", sysclkFormatThermalSensor((SysClkThermalSensor)sensor, false));
}
for (unsigned int module = 0; module < SysClkModule_EnumMax; module++)
{
fprintf(file, ",%s_real_hz", sysclkFormatModule((SysClkModule)module, false));
}
for (unsigned int sensor = 0; sensor < SysClkPowerSensor_EnumMax; sensor++)
{
fprintf(file, ",%s_mw", sysclkFormatPowerSensor((SysClkPowerSensor)sensor, false));
}
fprintf(file, "\n");
}
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
fprintf(file, "%ld%03ld,%s,%016lx", now.tv_sec, now.tv_nsec / 1000000UL, sysclkFormatProfile(context->profile, false), context->applicationId);
for (unsigned int module = 0; module < SysClkModule_EnumMax; module++)
{
fprintf(file, ",%d", context->freqs[module]);
}
for (unsigned int sensor = 0; sensor < SysClkThermalSensor_EnumMax; sensor++)
{
fprintf(file, ",%d", context->temps[sensor]);
}
for (unsigned int module = 0; module < SysClkModule_EnumMax; module++)
{
fprintf(file, ",%d", context->realFreqs[module]);
}
for (unsigned int sensor = 0; sensor < SysClkPowerSensor_EnumMax; sensor++)
{
fprintf(file, ",%d", context->power[sensor]);
}
fprintf(file, "\n");
fclose(file);
}
}
void FileUtils::RefreshFlags(bool force)
{
std::uint64_t now = armTicksToNs(armGetSystemTick());
if(!force && (now - g_last_flag_check) < FILE_FLAG_CHECK_INTERVAL_NS)
{
return;
}
FILE* file = fopen(FILE_LOG_FLAG_PATH, "r");
if (file)
{
g_log_enabled = true;
fclose(file);
} else {
g_log_enabled = false;
}
g_last_flag_check = now;
}
void FileUtils::InitializeAsync()
{
Thread initThread = {0};
threadCreate(&initThread, _FileUtils_InitializeThreadFunc, NULL, NULL, 0x4000, 0x15, 0);
threadStart(&initThread);
}
Result FileUtils::Initialize()
{
Result rc = 0;
if (R_SUCCEEDED(rc))
{
rc = timeInitialize();
}
__libnx_init_time();
timeExit();
if (R_SUCCEEDED(rc))
{
rc = fsInitialize();
}
if (R_SUCCEEDED(rc))
{
rc = fsdevMountSdmc();
}
if (R_SUCCEEDED(rc))
{
FileUtils::RefreshFlags(true);
g_has_initialized = true;
FileUtils::LogLine("=== " TARGET " " TARGET_VERSION " ===");
}
return rc;
}
void FileUtils::Exit()
{
if (!g_has_initialized)
{
return;
}
g_has_initialized = false;
g_log_enabled = false;
fsdevUnmountAll();
fsExit();
}

View File

@@ -0,0 +1,38 @@
/*
* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If you meet any of us some day, and you think this
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
* --------------------------------------------------------------------------
*/
#pragma once
#include <switch.h>
#include <time.h>
#include <vector>
#include <string>
#include <atomic>
#include <cstdarg>
#include <sysclk.h>
#define FILE_CONFIG_DIR "/config/" TARGET
#define FILE_FLAG_CHECK_INTERVAL_NS 5000000000ULL
#define FILE_CONTEXT_CSV_PATH FILE_CONFIG_DIR "/context.csv"
#define FILE_LOG_FLAG_PATH FILE_CONFIG_DIR "/log.flag"
#define FILE_LOG_FILE_PATH FILE_CONFIG_DIR "/log.txt"
class FileUtils
{
public:
static void Exit();
static Result Initialize();
static bool IsInitialized();
static bool IsLogEnabled();
static void InitializeAsync();
static void LogLine(const char* format, ...);
static void WriteContextToCsv(const SysClkContext* context);
protected:
static void RefreshFlags(bool force);
};

View File

@@ -0,0 +1,326 @@
/*
* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If you meet any of us some day, and you think this
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
* --------------------------------------------------------------------------
*/
#include "ipc_service.h"
#include <cstring>
#include <switch.h>
#include "file_utils.h"
#include "errors.h"
IpcService::IpcService(ClockManager* clockMgr)
{
std::int32_t priority;
Result rc = svcGetThreadPriority(&priority, CUR_THREAD_HANDLE);
ASSERT_RESULT_OK(rc, "svcGetThreadPriority");
rc = ipcServerInit(&this->server, SYSCLK_IPC_SERVICE_NAME, 42);
ASSERT_RESULT_OK(rc, "ipcServerInit");
rc = threadCreate(&this->thread, &IpcService::ProcessThreadFunc, this, NULL, 0x2000, priority, -2);
ASSERT_RESULT_OK(rc, "threadCreate");
this->running = false;
this->clockMgr = clockMgr;
}
void IpcService::SetRunning(bool running)
{
std::scoped_lock lock{this->threadMutex};
if(this->running == running)
{
return;
}
this->running = running;
if(running)
{
Result rc = threadStart(&this->thread);
ASSERT_RESULT_OK(rc, "threadStart");
}
else
{
svcCancelSynchronization(this->thread.handle);
threadWaitForExit(&this->thread);
}
}
IpcService::~IpcService()
{
this->SetRunning(false);
Result rc = threadClose(&this->thread);
ASSERT_RESULT_OK(rc, "threadClose");
rc = ipcServerExit(&this->server);
ASSERT_RESULT_OK(rc, "ipcServerExit");
}
void IpcService::ProcessThreadFunc(void* arg)
{
Result rc;
IpcService* ipcSrv = (IpcService*)arg;
while(true)
{
rc = ipcServerProcess(&ipcSrv->server, &IpcService::ServiceHandlerFunc, arg);
if(R_FAILED(rc))
{
if(rc == KERNELRESULT(Cancelled))
{
return;
}
if(rc != KERNELRESULT(ConnectionClosed))
{
FileUtils::LogLine("[ipc] ipcServerProcess: [0x%x] %04d-%04d", rc, R_MODULE(rc), R_DESCRIPTION(rc));
}
}
}
}
Result IpcService::ServiceHandlerFunc(void* arg, const IpcServerRequest* r, u8* out_data, size_t* out_dataSize)
{
IpcService* ipcSrv = (IpcService*)arg;
switch(r->data.cmdId)
{
case SysClkIpcCmd_GetApiVersion:
*out_dataSize = sizeof(u32);
return ipcSrv->GetApiVersion((u32*)out_data);
case SysClkIpcCmd_GetVersionString:
if(r->hipc.meta.num_recv_buffers >= 1)
{
return ipcSrv->GetVersionString(
(char*)hipcGetBufferAddress(r->hipc.data.recv_buffers),
hipcGetBufferSize(r->hipc.data.recv_buffers)
);
}
break;
case SysClkIpcCmd_GetCurrentContext:
*out_dataSize = sizeof(SysClkContext);
return ipcSrv->GetCurrentContext((SysClkContext*)out_data);
case SysClkIpcCmd_Exit:
return ipcSrv->Exit();
case SysClkIpcCmd_GetProfileCount:
if(r->data.size >= sizeof(std::uint64_t))
{
*out_dataSize = sizeof(std::uint8_t);
return ipcSrv->GetProfileCount((std::uint64_t*)r->data.ptr, (std::uint8_t*)out_data);
}
break;
case SysClkIpcCmd_GetProfiles:
if(r->data.size >= sizeof(std::uint64_t))
{
*out_dataSize = sizeof(SysClkTitleProfileList);
return ipcSrv->GetProfiles((std::uint64_t*)r->data.ptr, (SysClkTitleProfileList*)out_data);
}
break;
case SysClkIpcCmd_SetProfiles:
if(r->data.size >= sizeof(SysClkIpc_SetProfiles_Args))
{
return ipcSrv->SetProfiles((SysClkIpc_SetProfiles_Args*)r->data.ptr);
}
break;
case SysClkIpcCmd_SetEnabled:
if(r->data.size >= sizeof(std::uint8_t))
{
return ipcSrv->SetEnabled((std::uint8_t*)r->data.ptr);
}
break;
case SysClkIpcCmd_SetOverride:
if(r->data.size >= sizeof(SysClkIpc_SetOverride_Args))
{
return ipcSrv->SetOverride((SysClkIpc_SetOverride_Args*)r->data.ptr);
}
break;
case SysClkIpcCmd_GetConfigValues:
*out_dataSize = sizeof(SysClkTitleProfileList);
return ipcSrv->GetConfigValues((SysClkConfigValueList*)out_data);
case SysClkIpcCmd_SetConfigValues:
if(r->data.size >= sizeof(SysClkConfigValueList))
{
return ipcSrv->SetConfigValues((SysClkConfigValueList*)r->data.ptr);
}
break;
case SysClkIpcCmd_ToggleUncappedClocks:
if(r->data.size >= sizeof(SysClkConfigValueList))
{
return ipcSrv->SetConfigValues((SysClkConfigValueList*)r->data.ptr);
}
break;
case SysClkIpcCmd_GetFreqList:
if(r->data.size >= sizeof(SysClkIpc_GetFreqList_Args) && r->hipc.meta.num_recv_buffers >= 1)
{
*out_dataSize = sizeof(std::uint32_t);
return ipcSrv->GetFreqList(
(SysClkIpc_GetFreqList_Args*)r->data.ptr,
(std::uint32_t*)hipcGetBufferAddress(r->hipc.data.recv_buffers),
hipcGetBufferSize(r->hipc.data.recv_buffers),
(std::uint32_t*)out_data
);
}
break;
}
return SYSCLK_ERROR(Generic);
}
Result IpcService::GetApiVersion(u32* out_version)
{
*out_version = SYSCLK_IPC_API_VERSION;
return 0;
}
Result IpcService::GetVersionString(char* out_buf, size_t bufSize)
{
if(bufSize)
{
strncpy(out_buf, TARGET_VERSION, bufSize-1);
}
return 0;
}
Result IpcService::GetCurrentContext(SysClkContext* out_ctx)
{
*out_ctx = this->clockMgr->GetCurrentContext();
return 0;
}
Result IpcService::Exit()
{
this->clockMgr->SetRunning(false);
return 0;
}
Result IpcService::GetProfileCount(std::uint64_t* tid, std::uint8_t* out_count)
{
Config* config = this->clockMgr->GetConfig();
if(!config->HasProfilesLoaded())
{
return SYSCLK_ERROR(ConfigNotLoaded);
}
*out_count = config->GetProfileCount(*tid);
return 0;
}
Result IpcService::GetProfiles(std::uint64_t* tid, SysClkTitleProfileList* out_profiles)
{
Config* config = this->clockMgr->GetConfig();
if(!config->HasProfilesLoaded())
{
return SYSCLK_ERROR(ConfigNotLoaded);
}
config->GetProfiles(*tid, out_profiles);
return 0;
}
Result IpcService::SetProfiles(SysClkIpc_SetProfiles_Args* args)
{
Config* config = this->clockMgr->GetConfig();
if(!config->HasProfilesLoaded())
{
return SYSCLK_ERROR(ConfigNotLoaded);
}
SysClkTitleProfileList profiles = args->profiles;
if(!config->SetProfiles(args->tid, &profiles, true))
{
return SYSCLK_ERROR(ConfigSaveFailed);
}
return 0;
}
Result IpcService::SetEnabled(std::uint8_t* enabled)
{
Config* config = this->clockMgr->GetConfig();
config->SetEnabled(*enabled);
return 0;
}
Result IpcService::SetOverride(SysClkIpc_SetOverride_Args* args)
{
SysClkModule module = args->module;
std::uint32_t hz = args->hz;
if(!SYSCLK_ENUM_VALID(SysClkModule, args->module))
{
return SYSCLK_ERROR(Generic);
}
Config* config = this->clockMgr->GetConfig();
config->SetOverrideHz(module, hz);
return 0;
}
Result IpcService::GetConfigValues(SysClkConfigValueList* out_configValues)
{
Config* config = this->clockMgr->GetConfig();
if(!config->HasProfilesLoaded())
{
return SYSCLK_ERROR(ConfigNotLoaded);
}
config->GetConfigValues(out_configValues);
return 0;
}
Result IpcService::SetConfigValues(SysClkConfigValueList* configValues)
{
Config* config = this->clockMgr->GetConfig();
if(!config->HasProfilesLoaded())
{
return SYSCLK_ERROR(ConfigNotLoaded);
}
SysClkConfigValueList configValuesCopy = *configValues;
if(!config->SetConfigValues(&configValuesCopy, true))
{
return SYSCLK_ERROR(ConfigSaveFailed);
}
return 0;
}
Result IpcService::GetFreqList(SysClkIpc_GetFreqList_Args* args, std::uint32_t* out_list, std::size_t size, std::uint32_t* out_count)
{
if(!SYSCLK_ENUM_VALID(SysClkModule, args->module))
{
return SYSCLK_ERROR(Generic);
}
if(args->maxCount != size/sizeof(*out_list))
{
return SYSCLK_ERROR(Generic);
}
this->clockMgr->GetFreqList(args->module, out_list, args->maxCount, out_count);
return 0;
}

View File

@@ -0,0 +1,47 @@
/*
* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If you meet any of us some day, and you think this
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
* --------------------------------------------------------------------------
*/
#pragma once
#include <atomic>
#include <nxExt.h>
#include <sysclk.h>
#include "clock_manager.h"
class IpcService
{
public:
IpcService(ClockManager* clockMgr);
virtual ~IpcService();
void SetRunning(bool running);
static void ProcessThreadFunc(void* arg);
static Result ServiceHandlerFunc(void* arg, const IpcServerRequest* r, std::uint8_t* out_data, size_t* out_dataSize);
Result GetApiVersion(u32* out_version);
Result GetVersionString(char* out_buf, size_t bufSize);
Result GetCurrentContext(SysClkContext* out_ctx);
Result Exit();
Result GetProfileCount(std::uint64_t* tid, std::uint8_t* out_count);
Result GetProfiles(std::uint64_t* tid, SysClkTitleProfileList* out_profiles);
Result SetProfiles(SysClkIpc_SetProfiles_Args* args);
Result SetEnabled(std::uint8_t* enabled);
Result SetOverride(SysClkIpc_SetOverride_Args* args);
Result GetConfigValues(SysClkConfigValueList* out_configValues);
Result SetConfigValues(SysClkConfigValueList* configValues);
Result GetFreqList(SysClkIpc_GetFreqList_Args* args, std::uint32_t* out_list, std::size_t size, std::uint32_t* out_count);
bool running;
Thread thread;
LockableMutex threadMutex;
IpcServer server;
ClockManager* clockMgr;
bool uncappedClocks = 0;
protected:
};

View File

@@ -0,0 +1,125 @@
/*
* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If you meet any of us some day, and you think this
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
* --------------------------------------------------------------------------
*/
#include <cstdlib>
#include <cstring>
#include <malloc.h>
#include <switch.h>
#include "errors.h"
#include "file_utils.h"
#include "board.h"
#include "process_management.h"
#include "clock_manager.h"
#include "ipc_service.h"
#define INNER_HEAP_SIZE 0x30000
extern "C"
{
extern std::uint32_t __start__;
std::uint32_t __nx_applet_type = AppletType_None;
TimeServiceType __nx_time_service_type = TimeServiceType_System;
std::uint32_t __nx_fs_num_sessions = 1;
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
char nx_inner_heap[INNER_HEAP_SIZE];
void __libnx_initheap(void)
{
void* addr = nx_inner_heap;
size_t size = nx_inner_heap_size;
/* Newlib Heap Management */
extern char* fake_heap_start;
extern char* fake_heap_end;
fake_heap_start = (char*)addr;
fake_heap_end = (char*)addr + size;
}
void __appInit(void)
{
if (R_FAILED(smInitialize()))
{
fatalThrow(MAKERESULT(Module_Libnx, LibnxError_InitFail_SM));
}
Result rc = setsysInitialize();
if (R_SUCCEEDED(rc))
{
SetSysFirmwareVersion fw;
rc = setsysGetFirmwareVersion(&fw);
if (R_SUCCEEDED(rc))
hosversionSet(MAKEHOSVERSION(fw.major, fw.minor, fw.micro));
setsysExit();
}
}
void __appExit(void)
{
smExit();
}
}
int main(int argc, char** argv)
{
Result rc = FileUtils::Initialize();
if (R_FAILED(rc))
{
fatalThrow(rc);
return 1;
}
try
{
Board::Initialize();
ProcessManagement::Initialize();
ProcessManagement::WaitForQLaunch();
ClockManager* clockMgr = new ClockManager();
IpcService* ipcSrv = new IpcService(clockMgr);
FileUtils::LogLine("Ready");
clockMgr->SetRunning(true);
clockMgr->GetConfig()->SetEnabled(true);
ipcSrv->SetRunning(true);
while (clockMgr->Running())
{
clockMgr->Tick();
clockMgr->WaitForNextTick();
}
ipcSrv->SetRunning(false);
delete ipcSrv;
delete clockMgr;
ProcessManagement::Exit();
Board::Exit();
}
catch (const std::exception &ex)
{
FileUtils::LogLine("[!] %s", ex.what());
}
catch (...)
{
std::exception_ptr p = std::current_exception();
FileUtils::LogLine("[!?] %s", p ? p.__cxa_exception_type()->name() : "...");
}
FileUtils::LogLine("Exit");
svcSleepThread(1000000ULL);
FileUtils::Exit();
return 0;
}

View File

@@ -0,0 +1,67 @@
/*
* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If you meet any of us some day, and you think this
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
* --------------------------------------------------------------------------
*/
#include "process_management.h"
#include "file_utils.h"
#include "errors.h"
void ProcessManagement::Initialize()
{
Result rc = 0;
rc = pmdmntInitialize();
ASSERT_RESULT_OK(rc, "pmdmntInitialize");
rc = pminfoInitialize();
ASSERT_RESULT_OK(rc, "pminfoInitialize");
}
void ProcessManagement::WaitForQLaunch()
{
Result rc = 0;
std::uint64_t pid = 0;
do
{
rc = pmdmntGetProcessId(&pid, PROCESS_MANAGEMENT_QLAUNCH_TID);
svcSleepThread(500000000ULL);
} while (R_FAILED(rc));
}
std::uint64_t ProcessManagement::GetCurrentApplicationId()
{
Result rc = 0;
std::uint64_t pid = 0;
std::uint64_t tid = 0;
rc = pmdmntGetApplicationProcessId(&pid);
if (rc == 0x20f)
{
return PROCESS_MANAGEMENT_QLAUNCH_TID;
}
ASSERT_RESULT_OK(rc, "pmdmntGetApplicationProcessId");
rc = pminfoGetProgramId(&tid, pid);
if (rc == 0x20f)
{
return PROCESS_MANAGEMENT_QLAUNCH_TID;
}
ASSERT_RESULT_OK(rc, "pminfoGetProgramId");
return tid;
}
void ProcessManagement::Exit()
{
pmdmntExit();
pminfoExit();
}

View File

@@ -0,0 +1,24 @@
/*
* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If you meet any of us some day, and you think this
* stuff is worth it, you can buy us a beer in return. - The sys-clk authors
* --------------------------------------------------------------------------
*/
#pragma once
#include <switch.h>
#include <cstdint>
#define PROCESS_MANAGEMENT_QLAUNCH_TID 0x0100000000001000ULL
class ProcessManagement
{
public:
static void Initialize();
static void WaitForQLaunch();
static std::uint64_t GetCurrentApplicationId();
static void Exit();
};