/* * 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 . * */ /* -------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * , , * 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.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "../hos/apm_ext.h" #include #include #include #include #include #include #include "../board/board.hpp" #include "errors.hpp" #include "file_utils.hpp" namespace config { uint64_t configValues[HocClkConfigValue_EnumMax]; namespace { bool gLoaded = false; std::string gPath; time_t gMtime = 0; std::atomic_bool gEnabled{false}; std::uint32_t gOverrideFreqs[HocClkModule_EnumMax]; std::map, std::uint32_t> gProfileMHzMap; std::map gProfileCountMap; LockableMutex gConfigMutex; LockableMutex gOverrideMutex; time_t CheckModificationTime() { time_t mtime = 0; struct stat st; if (stat(gPath.c_str(), &st) == 0) { mtime = st.st_mtime; } return mtime; } std::uint32_t FindClockMHz(std::uint64_t tid, HocClkModule module, HocClkProfile profile) { if (gLoaded) { auto it = gProfileMHzMap.find(std::make_tuple(tid, profile, module)); if (it != gProfileMHzMap.end()) { return it->second; } } return 0; } std::uint32_t FindClockHzFromProfiles(std::uint64_t tid, HocClkModule module, std::initializer_list profiles, u32 mhzMultiplier = 1000000) { std::uint32_t mhz = 0; if (gLoaded) { for (auto profile: profiles) { mhz = FindClockMHz(tid, module, profile); if (mhz) { break; } } } return std::max((std::uint32_t)0, mhz * mhzMultiplier); } int BrowseIniFunc(const char* section, const char* key, const char* value, void* userdata) { (void)userdata; std::uint64_t input; if (!strcmp(section, CONFIG_VAL_SECTION)) { for (unsigned int kval = 0; kval < HocClkConfigValue_EnumMax; kval++) { if (!strcmp(key, hocclkFormatConfigValue((HocClkConfigValue)kval, false))) { input = strtoul(value, NULL, 0); if (!hocclkValidConfigValue((HocClkConfigValue)kval, input)) { input = hocclkDefaultConfigValue((HocClkConfigValue)kval); fileUtils::LogLine("[cfg] Invalid value for key '%s' in section '%s': using default %d", key, section, input); } 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; } HocClkProfile parsedProfile = HocClkProfile_EnumMax; HocClkModule parsedModule = HocClkModule_EnumMax; for (unsigned int profile = 0; profile < HocClkProfile_EnumMax; profile++) { const char* profileCode = board::GetProfileName((HocClkProfile)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 < HocClkModule_EnumMax; module++) { const char* moduleCode = board::GetModuleName((HocClkModule)module, false); size_t moduleCodeLen = strlen(moduleCode); if (!strncmp(subkey, moduleCode, moduleCodeLen) && subkey[moduleCodeLen] == '\0') { parsedProfile = (HocClkProfile)profile; parsedModule = (HocClkModule)module; } } } } if (parsedModule == HocClkModule_EnumMax || parsedProfile == HocClkProfile_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; } gProfileMHzMap[std::make_tuple(tid, parsedProfile, parsedModule)] = mhz; auto it = gProfileCountMap.find(tid); if (it == gProfileCountMap.end()) { gProfileCountMap[tid] = 1; } else { it->second++; } return 1; } void Close() { gLoaded = false; gProfileMHzMap.clear(); gProfileCountMap.clear(); for (unsigned int i = 0; i < HocClkConfigValue_EnumMax; i++) { configValues[i] = hocclkDefaultConfigValue((HocClkConfigValue)i); } } void Load() { fileUtils::LogLine("[cfg] Reading %s", gPath.c_str()); Close(); gMtime = CheckModificationTime(); if (!gMtime) { fileUtils::LogLine("[cfg] Error finding file"); } else if (!ini_browse(&BrowseIniFunc, nullptr, gPath.c_str())) { fileUtils::LogLine("[cfg] Error loading file"); } gLoaded = true; } } void Initialize() { gPath = FILE_CONFIG_DIR "/config.ini"; gLoaded = false; gProfileMHzMap.clear(); gProfileCountMap.clear(); gMtime = 0; gEnabled = false; for (unsigned int i = 0; i < HocClkModule_EnumMax; i++) { gOverrideFreqs[i] = 0; } for (unsigned int i = 0; i < HocClkConfigValue_EnumMax; i++) { configValues[i] = hocclkDefaultConfigValue((HocClkConfigValue)i); } } void Exit() { std::scoped_lock lock{gConfigMutex}; Close(); } bool Refresh() { std::scoped_lock lock{gConfigMutex}; if (!gLoaded || gMtime != CheckModificationTime()) { Load(); return true; } return false; } bool HasProfilesLoaded() { std::scoped_lock lock{gConfigMutex}; return gLoaded; } std::uint32_t GetAutoClockHz(std::uint64_t tid, HocClkModule module, HocClkProfile profile, bool returnRaw) { std::scoped_lock lock{gConfigMutex}; switch (profile) { case HocClkProfile_Handheld: return FindClockHzFromProfiles(tid, module, {HocClkProfile_Handheld}, returnRaw ? 1 : 1000000); case HocClkProfile_HandheldCharging: case HocClkProfile_HandheldChargingUSB: return FindClockHzFromProfiles(tid, module, {HocClkProfile_HandheldChargingUSB, HocClkProfile_HandheldCharging, HocClkProfile_Handheld}, returnRaw ? 1 : 1000000); case HocClkProfile_HandheldChargingOfficial: return FindClockHzFromProfiles(tid, module, {HocClkProfile_HandheldChargingOfficial, HocClkProfile_HandheldCharging, HocClkProfile_Handheld}, returnRaw ? 1 : 1000000); case HocClkProfile_Docked: return FindClockHzFromProfiles(tid, module, {HocClkProfile_Docked}, returnRaw ? 1 : 1000000); default: ERROR_THROW("Unhandled HocClkProfile: %u", profile); } return 0; } void GetProfiles(std::uint64_t tid, HocClkTitleProfileList* out_profiles) { std::scoped_lock lock{gConfigMutex}; for (unsigned int profile = 0; profile < HocClkProfile_EnumMax; profile++) { for (unsigned int module = 0; module < HocClkModule_EnumMax; module++) { out_profiles->mhzMap[profile][module] = FindClockMHz(tid, (HocClkModule)module, (HocClkProfile)profile); } } } bool SetProfiles(std::uint64_t tid, HocClkTitleProfileList* profiles, bool immediate) { std::scoped_lock lock{gConfigMutex}; uint8_t numProfiles = 0; char section[17] = {0}; snprintf(section, sizeof(section), "%016lX", tid); std::vector keys; std::vector values; keys.reserve(+HocClkProfile_EnumMax * +HocClkModule_EnumMax); values.reserve(+HocClkProfile_EnumMax * +HocClkModule_EnumMax); std::uint32_t* mhz = &profiles->mhz[0]; for (unsigned int profile = 0; profile < HocClkProfile_EnumMax; profile++) { for (unsigned int module = 0; module < HocClkModule_EnumMax; module++) { if (*mhz) { numProfiles++; std::string key = std::string(board::GetProfileName((HocClkProfile)profile, false)) + "_" + board::GetModuleName((HocClkModule)module, false); std::string value = std::to_string(*mhz); keys.push_back(key); values.push_back(value); } mhz++; } } std::vector keyPointers; std::vector valuePointers; keyPointers.reserve(keys.size() + 1); valuePointers.reserve(values.size() + 1); for (size_t i = 0; i < keys.size(); i++) { keyPointers.push_back(keys[i].c_str()); valuePointers.push_back(values[i].c_str()); } keyPointers.push_back(NULL); valuePointers.push_back(NULL); if (!ini_putsection(section, keyPointers.data(), valuePointers.data(), gPath.c_str())) { return false; } if (immediate) { mhz = &profiles->mhz[0]; gProfileCountMap[tid] = numProfiles; for (unsigned int profile = 0; profile < HocClkProfile_EnumMax; profile++) { for (unsigned int module = 0; module < HocClkModule_EnumMax; module++) { if (*mhz) { gProfileMHzMap[std::make_tuple(tid, (HocClkProfile)profile, (HocClkModule)module)] = *mhz; } else { gProfileMHzMap.erase(std::make_tuple(tid, (HocClkProfile)profile, (HocClkModule)module)); } mhz++; } } } return true; } std::uint8_t GetProfileCount(std::uint64_t tid) { auto it = gProfileCountMap.find(tid); if (it == gProfileCountMap.end()) { return 0; } return it->second; } void SetEnabled(bool enabled) { gEnabled = enabled; } bool Enabled() { return gEnabled; } void SetOverrideHz(HocClkModule module, std::uint32_t hz) { ASSERT_ENUM_VALID(HocClkModule, module); std::scoped_lock lock{gOverrideMutex}; gOverrideFreqs[module] = hz; } std::uint32_t GetOverrideHz(HocClkModule module) { ASSERT_ENUM_VALID(HocClkModule, module); std::scoped_lock lock{gOverrideMutex}; return gOverrideFreqs[module]; } std::uint64_t GetConfigValue(HocClkConfigValue kval) { ASSERT_ENUM_VALID(HocClkConfigValue, kval); std::scoped_lock lock{gConfigMutex}; return configValues[kval]; } const char* GetConfigValueName(HocClkConfigValue kval, bool pretty) { ASSERT_ENUM_VALID(HocClkConfigValue, kval); return hocclkFormatConfigValue(kval, pretty); } void GetConfigValues(HocClkConfigValueList* out_configValues) { std::scoped_lock lock{gConfigMutex}; for (unsigned int kval = 0; kval < HocClkConfigValue_EnumMax; kval++) { out_configValues->values[kval] = configValues[kval]; } } bool SetConfigValues(HocClkConfigValueList* configValues, bool immediate) { std::scoped_lock lock{gConfigMutex}; std::vector iniKeys; std::vector iniValues; iniKeys.reserve(HocClkConfigValue_EnumMax + 1); iniValues.reserve(HocClkConfigValue_EnumMax); for (unsigned int kval = 0; kval < HocClkConfigValue_EnumMax; kval++) { if (!hocclkValidConfigValue((HocClkConfigValue)kval, configValues->values[kval]) || configValues->values[kval] == hocclkDefaultConfigValue((HocClkConfigValue)kval)) { continue; } iniValues.push_back(std::to_string(configValues->values[kval])); iniKeys.push_back(hocclkFormatConfigValue((HocClkConfigValue)kval, false)); } iniKeys.push_back(NULL); std::vector valuePointers; valuePointers.reserve(iniValues.size() + 1); for (const auto& val : iniValues) { valuePointers.push_back(val.c_str()); } valuePointers.push_back(NULL); if (!ini_putsection(CONFIG_VAL_SECTION, iniKeys.data(), valuePointers.data(), gPath.c_str())) { return false; } if (immediate) { for (unsigned int kval = 0; kval < HocClkConfigValue_EnumMax; kval++) { if (hocclkValidConfigValue((HocClkConfigValue)kval, configValues->values[kval])) { config::configValues[kval] = configValues->values[kval]; } else { config::configValues[kval] = hocclkDefaultConfigValue((HocClkConfigValue)kval); } } } return true; } bool ResetConfigValue(HocClkConfigValue kval) { if (!HOCCLK_ENUM_VALID(HocClkConfigValue, kval)) { fileUtils::LogLine("[cfg] Invalid HocClkConfigValue: %u", kval); return false; } std::scoped_lock lock{gConfigMutex}; std::uint64_t defaultValue = hocclkDefaultConfigValue(kval); std::vector iniKeys; std::vector iniValues; iniKeys.reserve(2); iniValues.reserve(1); iniKeys.push_back(hocclkFormatConfigValue(kval, false)); iniValues.push_back(""); iniKeys.push_back(NULL); std::vector valuePointers; valuePointers.reserve(iniValues.size() + 1); for (const auto& val : iniValues) { valuePointers.push_back(val.c_str()); } valuePointers.push_back(NULL); if (!ini_putsection(CONFIG_VAL_SECTION, iniKeys.data(), valuePointers.data(), gPath.c_str())) { fileUtils::LogLine("[cfg] Failed to reset config value %u in INI", kval); return false; } configValues[kval] = defaultValue; fileUtils::LogLine("[cfg] Reset config value %u to default: %llu", kval, defaultValue); return true; } bool SetConfigValue(HocClkConfigValue kval, std::uint64_t value, bool immediate) { if (!HOCCLK_ENUM_VALID(HocClkConfigValue, kval)) { return false; } if (!hocclkValidConfigValue(kval, value)) { return false; } std::scoped_lock lock{gConfigMutex}; std::vector iniKeys; std::vector iniValues; iniKeys.reserve(2); iniValues.reserve(1); iniKeys.push_back(hocclkFormatConfigValue(kval, false)); iniValues.push_back(std::to_string(value)); iniKeys.push_back(NULL); std::vector valuePointers; valuePointers.reserve(2); valuePointers.push_back(iniValues[0].c_str()); valuePointers.push_back(NULL); if (!ini_putsection(CONFIG_VAL_SECTION, iniKeys.data(), valuePointers.data(), gPath.c_str())) { return false; } if (immediate) { configValues[kval] = value; } return true; } }