diff --git a/README.md b/README.md index 303e1772..a1b8ba58 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,8 @@ This project will not be actively maintained or regularly updated along with Atm 1. Download latest [release](https://github.com/KazushiMe/Switch-OC-Suite/releases/latest). 2. Mariko Only: Copy all files in `SdOut` to the root of SD card. - - Erista: Use official sys-clk instead. Only `loader.kip` and some benchmark homebrew are available. + + Erista user: Use other modified sys-clk instead. (Add your RAM OC frequency to sys-clk and recompiling). Only `loader.kip` and some benchmark homebrew are available in this repo for now. 3. Grab `x.x.x_loader.kip` for your Atmosphere version, rename it to `loader.kip` and place it in `/atmosphere/kips/`. diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite.cpp index 4f723ee5..f4a3cb31 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite.cpp @@ -148,6 +148,7 @@ namespace ams::ldr::oc { #define COMPARE_HIGH(val1, val2, bit_div) (((val1 ^ val2) >> bit_div) == 0) /* EMC */ + constexpr u32 MTC_TABLE_REV = 3; // DvbTable is all about frequency scaling along with CPU core voltage, no need to care about this for now. // constexpr emc_dvb_dvfs_table_t EmcDvbTable[6] = @@ -771,9 +772,8 @@ namespace ams::ldr::oc { Result MtcTableHandler(u32* ptr) { MarikoMtcTable* const mtc_table_max = reinterpret_cast(ptr - offsetof(MarikoMtcTable, rate_khz) / sizeof(u32)); MarikoMtcTable* const mtc_table_alt = mtc_table_max - 1; - constexpr u32 mtc_mariko_rev = 3; - R_UNLESS(mtc_table_max->rev == mtc_mariko_rev, ldr::ResultInvalidMtcTable()); - R_UNLESS(mtc_table_alt->rev == mtc_mariko_rev, ldr::ResultInvalidMtcTable()); + R_UNLESS(mtc_table_max->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable()); + R_UNLESS(mtc_table_alt->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable()); R_UNLESS(mtc_table_alt->rate_khz == MemClkOSAlt, ldr::ResultInvalidMtcTable()); MarikoMtcTable* const table = const_cast(C.marikoMtc); @@ -964,6 +964,8 @@ namespace ams::ldr::oc { { 2091000, {}, {} }, }; + constexpr u32 MTC_TABLE_REV = 7; + Result CpuDvfsHandler(u32* ptr, uintptr_t nso_end_offset) { cpu_freq_cvb_table_t* entry_1785 = reinterpret_cast(ptr); cpu_freq_cvb_table_t* entry_free = entry_1785 + 1; @@ -998,6 +1000,21 @@ namespace ams::ldr::oc { } Result MtcTableHandler(u32* ptr) { + u32 khz_list[] = { 1600000, 1331200, 1065600, 800000, 665600, 408000, 204000, 102000, 68000, 40800 }; + u32 khz_list_size = sizeof(khz_list) / sizeof(u32); + EristaMtcTable* table_list[khz_list_size]; + table_list[0] = reinterpret_cast(ptr - offsetof(EristaMtcTable, rate_khz) / sizeof(u32)); + + for (u32 i = 1; i < khz_list_size; i++ ) { + table_list[i] = table_list[i-1] - 1; + R_UNLESS(table_list[i]->rate_khz == khz_list[i], ldr::ResultInvalidMtcTable()); + R_UNLESS(table_list[i]->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable()); + } + + // Make room for new mtc table, discarding useless 40.8 MHz table + for (u32 i = khz_list_size - 1; i > 0; i--) + std::memcpy(static_cast(table_list[i]), static_cast(table_list[i - 1]), sizeof(EristaMtcTable)); + bool replace_entire_table = (C.mtcConf == ENTIRE_TABLE_ERISTA); if (replace_entire_table) { EristaMtcTable* const mtc_table_max = reinterpret_cast(ptr - offsetof(EristaMtcTable, rate_khz) / sizeof(u32)); diff --git a/Source/sys-clk-OC/README.md b/Source/sys-clk-OC/README.md index b38048b2..aad2d583 100644 --- a/Source/sys-clk-OC/README.md +++ b/Source/sys-clk-OC/README.md @@ -178,13 +178,14 @@ handheld_gpu=153 The `[values]` section allows you to alter timings in sys-clk, you should not need to edit any of these unless you know what you are doing. Possible values are: -| Key | Desc | Default | -|:-----------------------:|-------------------------------------------------------------------------------|:---------:| -|**allow_unsafe_freq** | Allow unsafe frequencies (CPU > 1963.5 MHz, GPU > 921.6 MHz) | OFF | -|**auto_cpu_boost** | Auto-boost CPU when system Core #3 utilization ≥ 95% | ON | -|**sync_reversenx_mode** | Sync nominal profile (mode) with ReverseNX (-Tool and -RT) | ON | -|**disable_fast_charging**| Disable Fast Charging (2000mA -> 500 mA) | OFF | -|**charging_limit_perc** | Charging Limit (20% - 100%) | 100%(OFF) | -|**temp_log_interval_ms** | Defines how often sys-clk log temperatures, in milliseconds (`0` to disable) | 0 ms | -|**csv_write_interval_ms**| Defines how often sys-clk writes to the CSV, in milliseconds (`0` to disable) | 0 ms | -|**poll_interval_ms** | Defines how fast sys-clk checks and applies profiles, in milliseconds | 500 ms | +| Key | Desc | Default | +|:------------------------:|-------------------------------------------------------------------------------|:---------:| +|**allow_unsafe_freq** | Allow unsafe frequencies (CPU > 1963.5 MHz, GPU > 921.6 MHz) | OFF | +|**auto_cpu_boost** | Auto-boost CPU when system Core #3 utilization ≥ 95% | ON | +|**sync_reversenx_mode** | Sync nominal profile (mode) with ReverseNX (-Tool and -RT) | ON | +|**disable_fast_charging** | Disable fast charging (2000mA -> 500 mA) | OFF | +|**charging_limit_perc** | Charging limit (20% - 100%) | 100%(OFF) | +|**governor_experimental** | CPU & GPU frequency governor (Experimental) | OFF | +|**temp_log_interval_ms** | Defines how often sys-clk log temperatures, in milliseconds (`0` to disable) | 0 ms | +|**csv_write_interval_ms** | Defines how often sys-clk writes to the CSV, in milliseconds (`0` to disable) | 0 ms | +|**poll_interval_ms** | Defines how fast sys-clk checks and applies profiles, in milliseconds | 500 ms | diff --git a/Source/sys-clk-OC/common/include/sysclk.h b/Source/sys-clk-OC/common/include/sysclk.h index 6ec3ead3..ad52c5b4 100644 --- a/Source/sys-clk-OC/common/include/sysclk.h +++ b/Source/sys-clk-OC/common/include/sysclk.h @@ -19,6 +19,7 @@ extern "C" { #include "sysclk/apm.h" #include "sysclk/config.h" #include "sysclk/errors.h" +#include "sysclk/psm_ext.h" #ifdef __cplusplus } diff --git a/Source/sys-clk-OC/common/include/sysclk/clocks.h b/Source/sys-clk-OC/common/include/sysclk/clocks.h index ba767986..90849b31 100644 --- a/Source/sys-clk-OC/common/include/sysclk/clocks.h +++ b/Source/sys-clk-OC/common/include/sysclk/clocks.h @@ -63,10 +63,8 @@ typedef struct { bool systemCoreBoostCPU; bool allowUnsafeFreq; - bool syncReverseNXMode; + bool governor; SysClkProfile realProfile; - ReverseNXMode reverseNXToolMode; - ReverseNXMode reverseNXRTMode; uint32_t maxMEMFreq; uint32_t boostCPUFreq; } SysClkOcExtra; diff --git a/Source/sys-clk-OC/common/include/sysclk/config.h b/Source/sys-clk-OC/common/include/sysclk/config.h index 3552ebd6..bec14dfe 100644 --- a/Source/sys-clk-OC/common/include/sysclk/config.h +++ b/Source/sys-clk-OC/common/include/sysclk/config.h @@ -22,6 +22,7 @@ typedef enum { SysClkConfigValue_AllowUnsafeFrequencies, SysClkConfigValue_DisableFastCharging, SysClkConfigValue_ChargingLimitPercentage, + SysClkConfigValue_GovernorExperimental, SysClkConfigValue_EnumMax, } SysClkConfigValue; @@ -49,6 +50,8 @@ static inline const char* sysclkFormatConfigValue(SysClkConfigValue val, bool pr return pretty ? "Disable Fast Charging" : "disable_fast_charging"; case SysClkConfigValue_ChargingLimitPercentage: return pretty ? "Charging Limit (%%)" : "charging_limit_perc"; + case SysClkConfigValue_GovernorExperimental: + return pretty ? "Governor (Experimental)" : "governor_experimental"; default: return NULL; } @@ -64,6 +67,7 @@ static inline uint64_t sysclkDefaultConfigValue(SysClkConfigValue val) case SysClkConfigValue_CsvWriteIntervalMs: case SysClkConfigValue_AllowUnsafeFrequencies: case SysClkConfigValue_DisableFastCharging: + case SysClkConfigValue_GovernorExperimental: return 0ULL; case SysClkConfigValue_AutoCPUBoost: case SysClkConfigValue_SyncReverseNXMode: @@ -88,6 +92,7 @@ static inline uint64_t sysclkValidConfigValue(SysClkConfigValue val, uint64_t in case SysClkConfigValue_SyncReverseNXMode: case SysClkConfigValue_AllowUnsafeFrequencies: case SysClkConfigValue_DisableFastCharging: + case SysClkConfigValue_GovernorExperimental: return (input & 0x1) == input; case SysClkConfigValue_ChargingLimitPercentage: return (input <= 100 && input >= 20); diff --git a/Source/sys-clk-OC/common/include/sysclk/psm_ext.h b/Source/sys-clk-OC/common/include/sysclk/psm_ext.h new file mode 100644 index 00000000..6f6420ce --- /dev/null +++ b/Source/sys-clk-OC/common/include/sysclk/psm_ext.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +typedef enum { + PsmPDC_NewPDO = 1, //Received new Power Data Object + PsmPDC_NoPD = 2, //No Power Delivery source is detected + PsmPDC_AcceptedRDO = 3 //Received and accepted Request Data Object +} PsmChargeInfoPDC; //BM92T series + +typedef enum { + PsmPowerRole_Sink = 1, + PsmPowerRole_Source = 2 +} PsmPowerRole; + +const char* PsmPowerRoleToStr(PsmPowerRole role); + +typedef enum { + PsmInfoChargerType_None = 0, + PsmInfoChargerType_PD = 1, + PsmInfoChargerType_TypeC_1500mA = 2, + PsmInfoChargerType_TypeC_3000mA = 3, + PsmInfoChargerType_DCP = 4, + PsmInfoChargerType_CDP = 5, + PsmInfoChargerType_SDP = 6, + PsmInfoChargerType_Apple_500mA = 7, + PsmInfoChargerType_Apple_1000mA = 8, + PsmInfoChargerType_Apple_2000mA = 9 +} PsmInfoChargerType; + +const char* PsmInfoChargerTypeToStr(PsmInfoChargerType type); + +typedef enum { + PsmFlags_NoHub = BIT(0), //If hub is disconnected + PsmFlags_Rail = BIT(8), //At least one Joy-con is charging from rail + PsmFlags_SPDSRC = BIT(12), //OTG + PsmFlags_ACC = BIT(16) //Accessory +} PsmChargeInfoFlags; + +typedef struct { + int32_t InputCurrentLimit; //Input (Sink) current limit in mA + int32_t VBUSCurrentLimit; //Output (Source/VBUS/OTG) current limit in mA + int32_t ChargeCurrentLimit; //Battery charging current limit in mA (512mA when Docked, 768mA when BatteryTemperature < 17.0 C) + int32_t ChargeVoltageLimit; //Battery charging voltage limit in mV (3952mV when BatteryTemperature >= 51.0 C) + int32_t unk_x10; //Possibly an emum, getting the same value as PowerRole in all tested cases + int32_t unk_x14; //Possibly flags + PsmChargeInfoPDC PDCState; //Power Delivery Controller State + int32_t BatteryTemperature; //Battery temperature in milli C + int32_t RawBatteryCharge; //Raw battery charged capacity per cent-mille (i.e. 100% = 100000 pcm) + int32_t VoltageAvg; //Voltage avg in mV (more in Notes) + int32_t BatteryAge; //Battery age (capacity full / capacity design) per cent-mille (i.e. 100% = 100000 pcm) + PsmPowerRole PowerRole; + PsmInfoChargerType ChargerType; + int32_t ChargerVoltageLimit; //Charger and external device voltage limit in mV + int32_t ChargerCurrentLimit; //Charger and external device current limit in mA + PsmChargeInfoFlags Flags; //Unknown flags +} PsmChargeInfo; + +typedef enum { + Psm_EnableBatteryCharging = 2, + Psm_DisableBatteryCharging = 3, + Psm_EnableFastBatteryCharging = 10, + Psm_DisableFastBatteryCharging = 11, + Psm_GetBatteryChargeInfoFields = 17, +} IPsmServerCmd; + +bool PsmIsChargerConnected(const PsmChargeInfo* info); +bool PsmIsCharging(const PsmChargeInfo* info); +bool PsmIsFastChargingEnabled(const PsmChargeInfo* info); + +typedef enum { + PsmBatteryState_Discharging, + PsmBatteryState_ChargingPaused, + PsmBatteryState_SlowCharging, + PsmBatteryState_FastCharging +} PsmBatteryState; + +PsmBatteryState PsmGetBatteryState(const PsmChargeInfo* info); +const char* PsmGetBatteryStateIcon(const PsmChargeInfo* info); \ No newline at end of file diff --git a/Source/sys-clk-OC/common/src/psm_ext.c b/Source/sys-clk-OC/common/src/psm_ext.c new file mode 100644 index 00000000..1792ebc1 --- /dev/null +++ b/Source/sys-clk-OC/common/src/psm_ext.c @@ -0,0 +1,55 @@ +#include + +const char* PsmPowerRoleToStr(PsmPowerRole role) { + switch (role) { + case PsmPowerRole_Sink: return "Sink"; + case PsmPowerRole_Source: return "Source"; + default: return "Unknown"; + } +} + +const char* PsmInfoChargerTypeToStr(PsmInfoChargerType type) { + switch (type) { + case PsmInfoChargerType_None: return "None"; + case PsmInfoChargerType_PD: return "USB-C PD"; + case PsmInfoChargerType_TypeC_1500mA: + case PsmInfoChargerType_TypeC_3000mA: return "USB-C"; + case PsmInfoChargerType_DCP: return "USB DCP"; + case PsmInfoChargerType_CDP: return "USB CDP"; + case PsmInfoChargerType_SDP: return "USB SDP"; + case PsmInfoChargerType_Apple_500mA: + case PsmInfoChargerType_Apple_1000mA: + case PsmInfoChargerType_Apple_2000mA: return "Apple"; + default: return "Unknown"; + } +} + +bool PsmIsChargerConnected(const PsmChargeInfo* info) { + return info->ChargerType != PsmInfoChargerType_None; +} + +bool PsmIsCharging(const PsmChargeInfo* info) { + return PsmIsChargerConnected(info) && ((info->unk_x14 >> 8) & 1); +} + +bool PsmIsFastChargingEnabled(const PsmChargeInfo* info) { + return info->ChargeCurrentLimit > 768; +} + +PsmBatteryState PsmGetBatteryState(const PsmChargeInfo* info) { + if (!PsmIsChargerConnected(info)) + return PsmBatteryState_Discharging; + if (!PsmIsCharging(info)) + return PsmBatteryState_ChargingPaused; + return PsmIsFastChargingEnabled(info) ? PsmBatteryState_FastCharging : PsmBatteryState_SlowCharging; +} + +const char* PsmGetBatteryStateIcon(const PsmChargeInfo* info) { + switch (PsmGetBatteryState(info)) { + case PsmBatteryState_Discharging: return "\u25c0"; // ◀ + case PsmBatteryState_ChargingPaused:return "| |"; + case PsmBatteryState_SlowCharging: return "\u25b6"; // ▶ + case PsmBatteryState_FastCharging: return "\u25b6\u25b6"; // ▶▶ + default: return "?"; + } +} diff --git a/Source/sys-clk-OC/manager/src/utils.cpp b/Source/sys-clk-OC/manager/src/utils.cpp index 6104c67d..ccd9e433 100644 --- a/Source/sys-clk-OC/manager/src/utils.cpp +++ b/Source/sys-clk-OC/manager/src/utils.cpp @@ -111,7 +111,7 @@ brls::SelectListItem* createFreqListItem(SysClkModule module, uint32_t selectedF char clock[16]; if (freq == 1862400000) - snprintf(clock, sizeof(clock), "Max MHz"); + snprintf(clock, sizeof(clock), "Maximum"); else snprintf(clock, sizeof(clock), "%d MHz", freq / 1000000); diff --git a/Source/sys-clk-OC/overlay/src/ui/format.h b/Source/sys-clk-OC/overlay/src/ui/format.h index 989e1efd..383b69b2 100644 --- a/Source/sys-clk-OC/overlay/src/ui/format.h +++ b/Source/sys-clk-OC/overlay/src/ui/format.h @@ -22,7 +22,7 @@ static inline std::string formatListFreqMhz(std::uint32_t mhz) } else if (mhz == 1862) { - return "Max MHz"; + return "Maximum"; } char buf[10]; diff --git a/Source/sys-clk-OC/overlay/src/ui/gui/misc_gui.cpp b/Source/sys-clk-OC/overlay/src/ui/gui/misc_gui.cpp index c91b2981..e0a44222 100644 --- a/Source/sys-clk-OC/overlay/src/ui/gui/misc_gui.cpp +++ b/Source/sys-clk-OC/overlay/src/ui/gui/misc_gui.cpp @@ -15,7 +15,7 @@ MiscGui::MiscGui() { this->configList = new SysClkConfigValueList {}; - this->chargeInfo = new ChargeInfo {}; + this->chargeInfo = new PsmChargeInfo {}; this->i2cInfo = new I2cInfo {}; } @@ -48,6 +48,14 @@ void MiscGui::updateConfigToggle(tsl::elm::ToggleListItem *toggle, SysClkConfigV void MiscGui::listUI() { + this->listElement->addItem(new tsl::elm::CategoryHeader("Temporary toggles")); + + this->backlightToggle = new tsl::elm::ToggleListItem("Screen Backlight", false); + backlightToggle->setStateChangedListener([this](bool state) { + LblUpdate(true); + }); + this->listElement->addItem(this->backlightToggle); + sysclkIpcGetConfigValues(this->configList); this->listElement->addItem(new tsl::elm::CategoryHeader("Config")); @@ -55,6 +63,7 @@ void MiscGui::listUI() this->cpuBoostToggle = addConfigToggle(SysClkConfigValue_AutoCPUBoost, "Auto CPU Boost"); this->syncModeToggle = addConfigToggle(SysClkConfigValue_SyncReverseNXMode, "Sync ReverseNX Mode"); this->fastChargingToggle = addConfigToggle(SysClkConfigValue_DisableFastCharging, "Disable Fast Charging"); + this->governorToggle = addConfigToggle(SysClkConfigValue_GovernorExperimental, "Governor (Experimental)"); this->chargingLimitHeader = new tsl::elm::CategoryHeader(""); this->listElement->addItem(this->chargingLimitHeader); @@ -69,7 +78,7 @@ void MiscGui::listUI() snprintf(chargingLimitBarDesc, 30, "Battery Charging Limit: %lu%%", this->configList->values[SysClkConfigValue_ChargingLimitPercentage]); this->chargingLimitHeader->setText(chargingLimitBarDesc); - this->chargingLimitBar->setIcon(getBatteryStateIcon()); + this->chargingLimitBar->setIcon(PsmGetBatteryStateIcon(this->chargeInfo)); Result rc = sysclkIpcSetConfigValues(this->configList); if (R_FAILED(rc)) @@ -82,14 +91,6 @@ void MiscGui::listUI() renderer->drawString("\uE016 Long-term use may render the battery gauge \ninaccurate!", false, x, y + 20, SMALL_TEXT_SIZE, DESC_COLOR); }), SMALL_TEXT_SIZE * 2 + 20); - this->listElement->addItem(new tsl::elm::CategoryHeader("Temporary toggles")); - - this->backlightToggle = new tsl::elm::ToggleListItem("Screen Backlight", false); - backlightToggle->setStateChangedListener([this](bool state) { - LblUpdate(true); - }); - this->listElement->addItem(this->backlightToggle); - this->listElement->addItem(new tsl::elm::CategoryHeader("Info")); this->listElement->addItem(new tsl::elm::CustomDrawer([this](tsl::gfx::Renderer *renderer, s32 x, s32 y, s32 w, s32 h) { renderer->drawString(this->infoNames, false, x, y + 20, SMALL_TEXT_SIZE, DESC_COLOR); @@ -114,7 +115,7 @@ void MiscGui::refresh() { this->chargingLimitHeader->setText(chargingLimitBarDesc); PsmUpdate(); - this->chargingLimitBar->setIcon(getBatteryStateIcon()); + this->chargingLimitBar->setIcon(PsmGetBatteryStateIcon(this->chargeInfo)); LblUpdate(); this->backlightToggle->setState(lblstatus); diff --git a/Source/sys-clk-OC/overlay/src/ui/gui/misc_gui.h b/Source/sys-clk-OC/overlay/src/ui/gui/misc_gui.h index f1a888f8..ea709ca3 100644 --- a/Source/sys-clk-OC/overlay/src/ui/gui/misc_gui.h +++ b/Source/sys-clk-OC/overlay/src/ui/gui/misc_gui.h @@ -30,84 +30,6 @@ class MiscGui : public BaseMenuGui void refresh() override; protected: - typedef enum { - PDCtrler_NewPDO = 1, //Received new Power Data Object - PDCtrler_NoPD = 2, //No Power Delivery source is detected - PDCtrler_AcceptedRDO = 3 //Received and accepted Request Data Object - } ChargeInfoPDCtrler; //BM92T series - - typedef enum { - PowerRole_Sink = 1, - PowerRole_Source = 2 - } ChargeInfoPowerRole; - - constexpr const char* ChargeInfoPowerRoleToStr(ChargeInfoPowerRole in) - { - switch (in) - { - case PowerRole_Sink: return "Sink"; - case PowerRole_Source: return "Source"; - default: return "Unknown"; - } - }; - - typedef enum { - ChargerType_None = 0, - ChargerType_PD = 1, - ChargerType_TypeC_1500mA = 2, - ChargerType_TypeC_3000mA = 3, - ChargerType_DCP = 4, - ChargerType_CDP = 5, - ChargerType_SDP = 6, - ChargerType_Apple_500mA = 7, - ChargerType_Apple_1000mA = 8, - ChargerType_Apple_2000mA = 9 - } ChargeInfoChargerType; - - constexpr const char* ChargeInfoChargerTypeToStr(ChargeInfoChargerType in) noexcept - { - switch (in) - { - case ChargerType_None: return "None"; - case ChargerType_PD: return "USB-C PD"; - case ChargerType_TypeC_1500mA: - case ChargerType_TypeC_3000mA: return "USB-C"; - case ChargerType_DCP: return "USB DCP"; - case ChargerType_CDP: return "USB CDP"; - case ChargerType_SDP: return "USB SDP"; - case ChargerType_Apple_500mA: - case ChargerType_Apple_1000mA: - case ChargerType_Apple_2000mA: return "Apple"; - default: return "Unknown"; - } - }; - - typedef enum { - Flags_NoHub = BIT(0), //If hub is disconnected - Flags_Rail = BIT(8), //At least one Joy-con is charging from rail - Flags_SPDSRC = BIT(12), //OTG - Flags_ACC = BIT(16) //Accessory - } ChargeInfoFlags; - - typedef struct { - int32_t InputCurrentLimit; //Input (Sink) current limit in mA - int32_t VBUSCurrentLimit; //Output (Source/VBUS/OTG) current limit in mA - int32_t ChargeCurrentLimit; //Battery charging current limit in mA (512mA when Docked, 768mA when BatteryTemperature < 17.0 C) - int32_t ChargeVoltageLimit; //Battery charging voltage limit in mV (3952mV when BatteryTemperature >= 51.0 C) - int32_t unk_x10; //Possibly an emum, getting the same value as PowerRole in all tested cases - int32_t unk_x14; //Possibly flags - ChargeInfoPDCtrler PDCtrlerState; //Power Delivery Controller State - int32_t BatteryTemperature; //Battery temperature in milli C - int32_t RawBatteryCharge; //Raw battery charged capacity per cent-mille (i.e. 100% = 100000 pcm) - int32_t VoltageAvg; //Voltage avg in mV (more in Notes) - int32_t BatteryAge; //Battery age (capacity full / capacity design) per cent-mille (i.e. 100% = 100000 pcm) - ChargeInfoPowerRole PowerRole; - ChargeInfoChargerType ChargerType; - int32_t ChargerVoltageLimit; //Charger and external device voltage limit in mV - int32_t ChargerCurrentLimit; //Charger and external device current limit in mA - ChargeInfoFlags Flags; //Unknown flags - } ChargeInfo; - typedef struct { float batCurrent; u32 cpuVolt = 620; @@ -132,16 +54,6 @@ class MiscGui : public BaseMenuGui smExit(); } - bool PsmIsChargerConnected() - { - return this->chargeInfo->ChargerType != ChargerType_None; - } - - bool PsmIsCharging() - { - return PsmIsChargerConnected() && ((this->chargeInfo->unk_x14 >> 8) & 1); - } - Result I2cRead_OutU16(u8 reg, I2cDevice dev, u16 *out) { // ams::fatal::srv::StopSoundTask::StopSound() @@ -285,14 +197,14 @@ class MiscGui : public BaseMenuGui "%dmV\n" "%dmV\n" , - ChargeInfoChargerTypeToStr(chargeInfo->ChargerType), chargWattsInfo, + PsmInfoChargerTypeToStr(chargeInfo->ChargerType), chargWattsInfo, (float)chargeInfo->VoltageAvg / 1000, (float)chargeInfo->BatteryTemperature / 1000, chargeInfo->InputCurrentLimit, chargeInfo->VBUSCurrentLimit, chargeInfo->ChargeCurrentLimit, chargeVoltLimit, (float)chargeInfo->RawBatteryCharge / 1000, (float)chargeInfo->BatteryAge / 1000, - ChargeInfoPowerRoleToStr(chargeInfo->PowerRole), + PsmPowerRoleToStr(chargeInfo->PowerRole), batCurInfo, i2cInfo->cpuVolt, i2cInfo->gpuVolt, @@ -302,7 +214,7 @@ class MiscGui : public BaseMenuGui void PsmChargingToggler(bool* enable) { - if (!PsmIsChargerConnected()) + if (!PsmIsChargerConnected(this->chargeInfo)) { *enable = false; return; @@ -310,7 +222,7 @@ class MiscGui : public BaseMenuGui PsmUpdate(*enable ? 2 : 3); - *enable = (PsmIsCharging() == *enable); + *enable = (PsmIsCharging(this->chargeInfo) == *enable); } void LblUpdate(bool shouldSwitch = false) @@ -324,44 +236,16 @@ class MiscGui : public BaseMenuGui smExit(); } - typedef enum { - Discharging, - ChargingPaused, - SlowCharging, - FastCharging - } BatteryState; - - BatteryState getBatteryState() { - if (!PsmIsChargerConnected()) - return Discharging; - - if (!PsmIsCharging()) - return ChargingPaused; - - return chargeInfo->ChargeCurrentLimit > 768 ? FastCharging : SlowCharging; - } - - const char* getBatteryStateIcon() { - switch (getBatteryState()) { - case Discharging: return "\u25c0"; // ◀ - case ChargingPaused:return "| |"; - case SlowCharging: return "\u25b6"; // ▶ - case FastCharging: return "\u25b6\u25b6"; // ▶▶ - default: return "?"; - } - } - tsl::elm::ToggleListItem* addConfigToggle(SysClkConfigValue, std::string); void updateConfigToggle(tsl::elm::ToggleListItem*, SysClkConfigValue); - void updateLiftChargingLimitToggle(); - tsl::elm::ToggleListItem *unsafeFreqToggle, *cpuBoostToggle, *syncModeToggle, *chargingToggle, *fastChargingToggle, *backlightToggle; + tsl::elm::ToggleListItem *backlightToggle, *unsafeFreqToggle, *cpuBoostToggle, *syncModeToggle, *fastChargingToggle, *governorToggle; tsl::elm::CategoryHeader *chargingLimitHeader; StepTrackBarIcon *chargingLimitBar; SysClkConfigValueList* configList; - ChargeInfo* chargeInfo; - I2cInfo* i2cInfo; + PsmChargeInfo* chargeInfo; + I2cInfo* i2cInfo; LblBacklightSwitchStatus lblstatus = LblBacklightSwitchStatus_Disabled; const char* infoNames = "Charger:\nBattery:\nCurrent Limit:\nCharging Limit:\nRaw Charge:\nBattery Age:\nPower Role:\nCurrent Flow:\n\nCPU Volt:\nGPU Volt:\nDRAM Volt:"; diff --git a/Source/sys-clk-OC/sysmodule/Makefile b/Source/sys-clk-OC/sysmodule/Makefile index af418d5f..6a29934a 100644 --- a/Source/sys-clk-OC/sysmodule/Makefile +++ b/Source/sys-clk-OC/sysmodule/Makefile @@ -44,7 +44,7 @@ CFLAGS := -g -Wall -O2 -ffunction-sections \ CFLAGS += $(INCLUDE) -D__SWITCH__ -CXXFLAGS := $(CFLAGS) -fno-rtti -std=gnu++17 +CXXFLAGS := $(CFLAGS) -fno-rtti -std=gnu++20 ASFLAGS := -g $(ARCH) LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) diff --git a/Source/sys-clk-OC/sysmodule/lib/nxExt/include/nxExt/apm_ext.h b/Source/sys-clk-OC/sysmodule/lib/nxExt/include/nxExt/apm_ext.h index b4dd14d0..b8fd2284 100644 --- a/Source/sys-clk-OC/sysmodule/lib/nxExt/include/nxExt/apm_ext.h +++ b/Source/sys-clk-OC/sysmodule/lib/nxExt/include/nxExt/apm_ext.h @@ -23,6 +23,7 @@ void apmExtExit(void); Result apmExtGetPerformanceMode(u32 *out_mode); Result apmExtSysRequestPerformanceMode(u32 mode); Result apmExtGetCurrentPerformanceConfiguration(u32 *out_conf); +bool apmExtIsBoostMode(u32 conf_id, bool allow_cpu_limited); #ifdef __cplusplus } diff --git a/Source/sys-clk-OC/sysmodule/lib/nxExt/src/apm_ext.c b/Source/sys-clk-OC/sysmodule/lib/nxExt/src/apm_ext.c index ba30d4ff..736e015f 100644 --- a/Source/sys-clk-OC/sysmodule/lib/nxExt/src/apm_ext.c +++ b/Source/sys-clk-OC/sysmodule/lib/nxExt/src/apm_ext.c @@ -64,3 +64,9 @@ Result apmExtGetCurrentPerformanceConfiguration(u32 *out_conf) { return serviceDispatchOut(&g_apmSysSrv, 7, *out_conf); } + +bool apmExtIsBoostMode(u32 conf_id, bool allow_cpu_limited) { + if (allow_cpu_limited) + return (conf_id >= 0x92220009 && conf_id <= 0x922200C); + return (conf_id == 0x92220009 || conf_id == 0x922200A); +} \ No newline at end of file diff --git a/Source/sys-clk-OC/sysmodule/perms.json b/Source/sys-clk-OC/sysmodule/perms.json index a2210b63..b37c0408 100644 --- a/Source/sys-clk-OC/sysmodule/perms.json +++ b/Source/sys-clk-OC/sysmodule/perms.json @@ -26,7 +26,7 @@ "value": { "highest_thread_priority": 63, "lowest_thread_priority": 24, - "lowest_cpu_id": 3, + "lowest_cpu_id": 0, "highest_cpu_id": 3 } }, @@ -74,6 +74,7 @@ "svcOutputDebugString": "0x27", "svcReturnFromException": "0x28", "svcGetInfo": "0x29", + "svcSetThreadActivity": "0x32", "svcWaitForAddress": "0x34", "svcSignalToAddress": "0x35", "svcCreateSession": "0x40", diff --git a/Source/sys-clk-OC/sysmodule/src/clock_manager.cpp b/Source/sys-clk-OC/sysmodule/src/clock_manager.cpp index c44eefd1..fd02201e 100644 --- a/Source/sys-clk-OC/sysmodule/src/clock_manager.cpp +++ b/Source/sys-clk-OC/sysmodule/src/clock_manager.cpp @@ -59,27 +59,32 @@ ClockManager::ClockManager() this->oc = new SysClkOcExtra; this->oc->systemCoreBoostCPU = false; this->oc->allowUnsafeFreq = false; - this->oc->syncReverseNXMode = false; + this->oc->governor = false; this->oc->realProfile = SysClkProfile_Handheld; - this->oc->reverseNXToolMode = ReverseNX_NotFound; - this->oc->reverseNXRTMode = ReverseNX_NotFound; this->oc->maxMEMFreq = 0; this->oc->boostCPUFreq = 0; + + this->rnxSync = new ReverseNXSync; + this->governor = new Governor; } ClockManager::~ClockManager() { - delete this->config; - delete this->context; + delete this->governor; + delete this->rnxSync; delete this->oc; + delete this->context; + delete this->config; } bool ClockManager::IsCpuBoostMode() { std::uint32_t confId = this->context->perfConfId; - bool isCpuBoostMode = (confId == 0x92220009 || confId == 0x9222000A); - if (isCpuBoostMode && !this->oc->boostCPUFreq) + bool isCpuBoostMode = apmExtIsBoostMode(confId, false); + if (isCpuBoostMode && !this->oc->boostCPUFreq) { this->oc->boostCPUFreq = std::max(this->context->freqs[SysClkModule_CPU], 1785'000'000U); + this->governor->SetCPUBoostHz(this->oc->boostCPUFreq); + } return isCpuBoostMode; } @@ -107,7 +112,8 @@ uint32_t ClockManager::GetHz(SysClkModule module) hz = this->config->GetAutoClockHz(SYSCLK_GLOBAL_PROFILE_TID, module, this->context->profile); /* Return pre-set hz */ - if (!hz && this->oc->syncReverseNXMode && GetReverseNXMode()) + ReverseNXMode mode; + if (!hz && (mode = this->rnxSync->GetMode())) { switch (module) { @@ -115,12 +121,12 @@ uint32_t ClockManager::GetHz(SysClkModule module) hz = 1020'000'000; break; case SysClkModule_GPU: - hz = (GetReverseNXMode() == ReverseNX_Docked || + hz = (mode == ReverseNX_Docked || this->oc->realProfile == SysClkProfile_Docked) ? 768'000'000 : 460'800'000; break; case SysClkModule_MEM: - hz = (GetReverseNXMode() == ReverseNX_Docked || + hz = (mode == ReverseNX_Docked || this->oc->realProfile == SysClkProfile_Docked) ? MAX_MEM_CLOCK : 1600'000'000; break; @@ -172,17 +178,23 @@ void ClockManager::Tick() { uint32_t hz = GetHz((SysClkModule)module); + if (this->oc->governor) { + this->governor->SetMaxHz(hz, (SysClkModule)module); + continue; + } + if (hz && hz != this->context->freqs[module]) { // Skip setting CPU or GPU clocks in CpuBoostMode if CPU <= boostCPUFreq or GPU >= 76.8MHz - if (IsCpuBoostMode() && ((module == SysClkModule_CPU && hz <= this->oc->boostCPUFreq) || module == SysClkModule_GPU)) - { - continue; + bool skipBoost = IsCpuBoostMode() && ((module == SysClkModule_CPU && hz <= this->oc->boostCPUFreq) || module == SysClkModule_GPU); + if (!skipBoost) { + FileUtils::LogLine("[mgr] %s clock set : %u.%u MHz", Clocks::GetModuleName((SysClkModule)module, true), hz/1000000, hz/100000 - hz/1000000*10); + Clocks::SetHz((SysClkModule)module, hz); + uint32_t hz_now = Clocks::GetCurrentHz((SysClkModule)module); + if (hz != hz_now) + FileUtils::LogLine("[mgr] Cannot set %s clock to %u.%u MHz", Clocks::GetModuleName((SysClkModule)module, true), hz/1000000, hz/100000 - hz/1000000*10); + this->context->freqs[module] = hz_now; } - - FileUtils::LogLine("[mgr] %s clock set : %u.%u Mhz", Clocks::GetModuleName((SysClkModule)module, true), hz/1000000, hz/100000 - hz/1000000*10); - Clocks::SetHz((SysClkModule)module, hz); - this->context->freqs[module] = hz; } } } @@ -192,162 +204,48 @@ void ClockManager::WaitForNextTick() { /* Self-check system core (#3) usage via idleticks at intervals (Not enabled at higher CPU freq or without charger) */ uint64_t tickWaitTimeMs = this->GetConfig()->GetConfigValue(SysClkConfigValue_PollingIntervalMs); - uint64_t tickWaitTimeNs = tickWaitTimeMs * 1000000ULL; - bool isAutoBoostEnabled = this->GetConfig()->GetConfigValue(SysClkConfigValue_AutoCPUBoost); - if ( isAutoBoostEnabled - && this->oc->realProfile != SysClkProfile_Handheld - && this->context->enabled - && this->context->freqs[SysClkModule_CPU] <= this->oc->boostCPUFreq) - { - uint64_t systemCoreIdleTickPrev = 0, systemCoreIdleTickNext = 0; - svcGetInfo(&systemCoreIdleTickPrev, InfoType_IdleTickCount, INVALID_HANDLE, 3); - svcSleepThread(tickWaitTimeNs); - svcGetInfo(&systemCoreIdleTickNext, InfoType_IdleTickCount, INVALID_HANDLE, 3); + if (this->oc->governor) { + svcSleepThread(tickWaitTimeMs * 1000'000ULL); + return; + } - /* Convert idletick to free% */ - /* If CPU core usage is 0%, then idletick = 19'200'000 per sec */ - uint64_t systemCoreIdleTick = systemCoreIdleTickNext - systemCoreIdleTickPrev; - uint64_t freeIdleTick = 19'200 * tickWaitTimeMs; - uint8_t freePerc = systemCoreIdleTick / (freeIdleTick / 100); + bool boostOK = + this->GetConfig()->GetConfigValue(SysClkConfigValue_AutoCPUBoost) && + this->context->enabled && + this->oc->realProfile != SysClkProfile_Handheld && + this->context->freqs[SysClkModule_CPU] <= this->oc->boostCPUFreq; - constexpr uint8_t systemCoreBoostFreeThreshold = 5; + if (boostOK) { + uint64_t core3Util = CpuCoreUtil(3, tickWaitTimeMs).Get(); + bool lastBoost = this->oc->systemCoreBoostCPU; + constexpr uint8_t BOOST_THRESHOLD = 95; + this->oc->systemCoreBoostCPU = (core3Util >= BOOST_THRESHOLD); - bool systemCoreBoostCPUPrevState = this->oc->systemCoreBoostCPU; - this->oc->systemCoreBoostCPU = (freePerc <= systemCoreBoostFreeThreshold); - - if (systemCoreBoostCPUPrevState && !this->oc->systemCoreBoostCPU) - { + if (lastBoost && !this->oc->systemCoreBoostCPU) Clocks::SetHz(SysClkModule_CPU, GetHz(SysClkModule_CPU)); - } - else if (!systemCoreBoostCPUPrevState && this->oc->systemCoreBoostCPU) - { + + if (!lastBoost && this->oc->systemCoreBoostCPU) Clocks::SetHz(SysClkModule_CPU, this->oc->boostCPUFreq); - } - } - else - { - if (this->oc->systemCoreBoostCPU) { - this->oc->systemCoreBoostCPU = false; - Clocks::SetHz(SysClkModule_CPU, GetHz(SysClkModule_CPU)); - } - svcSleepThread(tickWaitTimeNs); - } -} -SysClkProfile ClockManager::ReverseNXProfileHandler() -{ - switch (GetReverseNXMode()) - { - case ReverseNX_Docked: - return SysClkProfile_Docked; - case ReverseNX_Handheld: - return (this->oc->realProfile == SysClkProfile_Docked) ? - SysClkProfile_HandheldChargingOfficial : this->oc->realProfile; - default: - return this->oc->realProfile; - } -} - -ReverseNXMode ClockManager::ReverseNXFileHandler(const char* filePath) -{ - FILE *readFile; - readFile = fopen(filePath, "rb"); - - if (!readFile) - return ReverseNX_NotFound; - - uint64_t magicDocked = 0xD65F03C0320003E0; - uint64_t magicHandheld = 0xD65F03C052A00000; - - uint64_t readBuffer = 0; - fread(&readBuffer, 1, sizeof(readBuffer), readFile); - fclose(readFile); - - if (R_SUCCEEDED(memcmp(&readBuffer, &magicDocked, sizeof(readBuffer)))) - return ReverseNX_Docked; - - if (R_SUCCEEDED(memcmp(&readBuffer, &magicHandheld, sizeof(readBuffer)))) - return ReverseNX_Handheld; - - return ReverseNX_NotFound; -} - -ReverseNXMode ClockManager::GetReverseNXToolMode() -{ - bool shouldCheckReverseNXTool = FileUtils::ExistReverseNXTool(); - if (!shouldCheckReverseNXTool) - return ReverseNX_NotFound; - - ReverseNXMode getMode = ReverseNX_NotFound; - if (this->context->applicationId != PROCESS_MANAGEMENT_QLAUNCH_TID) - { - const char asmFileName[] = "_ZN2nn2oe18GetPerformanceModeEv.asm64"; // Checking one asm64 file is enough - char asmFilePath[128]; - - /* Check per-game patch */ - snprintf(asmFilePath, sizeof(asmFilePath), "/SaltySD/patches/%016lX/%s", this->context->applicationId, asmFileName); - getMode = ReverseNXFileHandler(asmFilePath); - - if (!getMode) - { - /* Check global patch */ - snprintf(asmFilePath, sizeof(asmFilePath), "/SaltySD/patches/%s", asmFileName); - getMode = ReverseNXFileHandler(asmFilePath); - } + return; } - return getMode; -} - -ReverseNXMode ClockManager::GetReverseNXMode() -{ - if (this->oc->reverseNXRTMode) - return this->oc->reverseNXRTMode; - return this->oc->reverseNXToolMode; -} - -void ClockManager::ChargingHandler() -{ - smInitialize(); - psmInitialize(); - ChargeInfo* chargeInfoField = new ChargeInfo; - Service* session = psmGetServiceSession(); - serviceDispatchOut(session, GetBatteryChargeInfoFields, *(chargeInfoField)); - - bool fastChargingState = chargeInfoField->ChargeCurrentLimit > 768; - bool fastChargingConfig = !(this->GetConfig()->GetConfigValue(SysClkConfigValue_DisableFastCharging)); - if (fastChargingState != fastChargingConfig) - serviceDispatch(session, fastChargingConfig ? EnableFastBatteryCharging : DisableFastBatteryCharging); - - bool isChargerConnected = (chargeInfoField->ChargerType != ChargerType_None); - if (isChargerConnected) - { - u32 chargeNow = 0; - if (R_SUCCEEDED(psmGetBatteryChargePercentage(&chargeNow))) - { - bool isCharging = ((chargeInfoField->unk_x14 >> 8) & 1); - u32 chargeLimit = this->GetConfig()->GetConfigValue(SysClkConfigValue_ChargingLimitPercentage); - if (isCharging && chargeLimit < chargeNow) { - serviceDispatch(session, DisableBatteryCharging); - } - if (!isCharging && chargeLimit > chargeNow) { - serviceDispatch(session, EnableBatteryCharging); - } - } + if (this->oc->systemCoreBoostCPU) { + this->oc->systemCoreBoostCPU = false; + Clocks::SetHz(SysClkModule_CPU, GetHz(SysClkModule_CPU)); } - - delete chargeInfoField; - psmExit(); - smExit(); + svcSleepThread(tickWaitTimeMs * 1000'000ULL); } bool ClockManager::RefreshContext() { - ChargingHandler(); + bool fastChargingEnabled = !(this->GetConfig()->GetConfigValue(SysClkConfigValue_DisableFastCharging)); + uint32_t chargingLimit = this->GetConfig()->GetConfigValue(SysClkConfigValue_ChargingLimitPercentage); + PsmExt::ChargingHandler(fastChargingEnabled, chargingLimit); bool hasChanged = this->config->Refresh(); - this->oc->syncReverseNXMode = this->GetConfig()->GetConfigValue(SysClkConfigValue_SyncReverseNXMode); + this->rnxSync->ToggleSync(this->GetConfig()->GetConfigValue(SysClkConfigValue_SyncReverseNXMode)); this->oc->allowUnsafeFreq = this->GetConfig()->GetConfigValue(SysClkConfigValue_AllowUnsafeFrequencies); bool enabled = this->GetConfig()->Enabled(); @@ -358,6 +256,18 @@ bool ClockManager::RefreshContext() hasChanged = true; } + bool governor = this->GetConfig()->GetConfigValue(SysClkConfigValue_GovernorExperimental); + if (governor != this->oc->governor) + { + this->oc->governor = governor; + FileUtils::LogLine("[mgr] Governor status: %s", governor ? "enabled" : "disabled"); + if (governor) + this->governor->Start(); + else + this->governor->Stop(); + hasChanged = true; + } + std::uint64_t applicationId = ProcessManagement::GetCurrentApplicationId(); if (applicationId != this->context->applicationId) { @@ -367,8 +277,7 @@ bool ClockManager::RefreshContext() /* Clear ReverseNX state */ this->GetConfig()->SetReverseNXRTMode(ReverseNX_NotFound); - this->oc->reverseNXRTMode = ReverseNX_NotFound; - this->oc->reverseNXToolMode = GetReverseNXToolMode(); + this->rnxSync->Reset(applicationId); } SysClkProfile profile = Clocks::GetCurrentProfile(); @@ -388,17 +297,17 @@ bool ClockManager::RefreshContext() if (this->context->perfConfId != confId) { this->context->perfConfId = confId; + this->governor->SetPerfConf(confId); hasChanged = true; } } { - this->oc->reverseNXRTMode = this->GetConfig()->GetReverseNXRTMode(); - SysClkProfile currentProfile = this->context->profile; - SysClkProfile expectedProfile = this->oc->syncReverseNXMode ? - ReverseNXProfileHandler() : this->oc->realProfile; - this->context->profile = expectedProfile; - if (currentProfile != expectedProfile) + this->rnxSync->SetRTMode(this->GetConfig()->GetReverseNXRTMode()); + SysClkProfile current = this->context->profile; + SysClkProfile expected = this->rnxSync->GetProfile(this->oc->realProfile); + this->context->profile = expected; + if (current != expected) hasChanged = true; } @@ -414,9 +323,11 @@ bool ClockManager::RefreshContext() if (hz != 0 && hz != this->context->freqs[module]) { - FileUtils::LogLine("[mgr] %s clock change: %u.%u Mhz", Clocks::GetModuleName((SysClkModule)module, true), hz/1000000, hz/100000 - hz/1000000*10); this->context->freqs[module] = hz; - hasChanged = true; + if (!this->oc->governor) { + FileUtils::LogLine("[mgr] %s clock change: %u.%u MHz", Clocks::GetModuleName((SysClkModule)module, true), hz/1000000, hz/100000 - hz/1000000*10); + hasChanged = true; + } } hz = this->GetConfig()->GetOverrideHz((SysClkModule)module); @@ -424,7 +335,7 @@ bool ClockManager::RefreshContext() { if(hz) { - FileUtils::LogLine("[mgr] %s override change: %u.%u Mhz", Clocks::GetModuleName((SysClkModule)module, true), hz/1000000, hz/100000 - hz/1000000*10); + FileUtils::LogLine("[mgr] %s override change: %u.%u MHz", Clocks::GetModuleName((SysClkModule)module, true), hz/1000000, hz/100000 - hz/1000000*10); } else { diff --git a/Source/sys-clk-OC/sysmodule/src/clock_manager.h b/Source/sys-clk-OC/sysmodule/src/clock_manager.h index f1246210..3dd8ff71 100644 --- a/Source/sys-clk-OC/sysmodule/src/clock_manager.h +++ b/Source/sys-clk-OC/sysmodule/src/clock_manager.h @@ -17,6 +17,8 @@ #include "clocks.h" #include +#include "oc_extra.h" + class ClockManager { public: @@ -32,64 +34,6 @@ class ClockManager SysClkContext GetCurrentContext(); Config* GetConfig(); - typedef enum { - PDCtrler_NewPDO = 1, //Received new Power Data Object - PDCtrler_NoPD = 2, //No Power Delivery source is detected - PDCtrler_AcceptedRDO = 3 //Received and accepted Request Data Object - } ChargeInfoPDCtrler; //BM92T series - - typedef enum { - PowerRole_Sink = 1, - PowerRole_Source = 2 - } ChargeInfoPowerRole; - - typedef enum { - ChargerType_None = 0, - ChargerType_PD = 1, - ChargerType_TypeC_1500mA = 2, - ChargerType_TypeC_3000mA = 3, - ChargerType_DCP = 4, - ChargerType_CDP = 5, - ChargerType_SDP = 6, - ChargerType_Apple_500mA = 7, - ChargerType_Apple_1000mA = 8, - ChargerType_Apple_2000mA = 9 - } ChargeInfoChargerType; - - typedef enum { - Flags_NoHub = BIT(0), //If hub is disconnected - Flags_Rail = BIT(8), //At least one Joy-con is charging from rail - Flags_SPDSRC = BIT(12), //OTG - Flags_ACC = BIT(16) //Accessory - } ChargeInfoFlags; - - typedef struct { - int32_t InputCurrentLimit; //Input (Sink) current limit in mA - int32_t VBUSCurrentLimit; //Output (Source/VBUS/OTG) current limit in mA - int32_t ChargeCurrentLimit; //Battery charging current limit in mA (512mA when Docked, 768mA when BatteryTemperature < 17.0 C) - int32_t ChargeVoltageLimit; //Battery charging voltage limit in mV (3952mV when BatteryTemperature >= 51.0 C) - int32_t unk_x10; //Possibly an emum, getting the same value as PowerRole in all tested cases - int32_t unk_x14; //Possibly flags - ChargeInfoPDCtrler PDCtrlerState; //Power Delivery Controller State - int32_t BatteryTemperature; //Battery temperature in milli C - int32_t RawBatteryCharge; //Raw battery charged capacity per cent-mille (i.e. 100% = 100000 pcm) - int32_t VoltageAvg; //Voltage avg in mV (more in Notes) - int32_t BatteryAge; //Battery age (capacity full / capacity design) per cent-mille (i.e. 100% = 100000 pcm) - ChargeInfoPowerRole PowerRole; - ChargeInfoChargerType ChargerType; - int32_t ChargerVoltageLimit; //Charger and external device voltage limit in mV - int32_t ChargerCurrentLimit; //Charger and external device current limit in mA - ChargeInfoFlags Flags; //Unknown flags - } ChargeInfo; - - typedef enum { - EnableBatteryCharging = 2, - DisableBatteryCharging = 3, - EnableFastBatteryCharging = 10, - DisableFastBatteryCharging = 11, - GetBatteryChargeInfoFields = 17, - } IPsmServerCmd; - protected: ClockManager(); virtual ~ClockManager(); @@ -105,16 +49,10 @@ class ClockManager std::uint64_t lastCsvWriteNs; SysClkOcExtra *oc; + ReverseNXSync *rnxSync; + Governor *governor; bool IsCpuBoostMode(); uint32_t GetHz(SysClkModule); - - SysClkProfile ReverseNXProfileHandler(); - ReverseNXMode ReverseNXFileHandler(const char*); - ReverseNXMode GetReverseNXToolMode(); - ReverseNXMode GetReverseNXMode(); - - void ChargingHandler(); - }; diff --git a/Source/sys-clk-OC/sysmodule/src/clocks.cpp b/Source/sys-clk-OC/sysmodule/src/clocks.cpp index e66cdba9..45807fab 100644 --- a/Source/sys-clk-OC/sysmodule/src/clocks.cpp +++ b/Source/sys-clk-OC/sysmodule/src/clocks.cpp @@ -158,29 +158,49 @@ PcvModuleId Clocks::GetPcvModuleId(SysClkModule sysclkModule) return pcvModuleId; } +SysClkApmConfiguration* Clocks::GetEmbeddedApmConfig(uint32_t confId) +{ + 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); + } + return apmConfiguration; +} + +uint32_t Clocks::GetStockClock(SysClkApmConfiguration* apm, SysClkModule module) +{ + switch (module) { + case SysClkModule_CPU: + return apm->cpu_hz; + case SysClkModule_GPU: + return apm->gpu_hz; + case SysClkModule_MEM: + return apm->mem_hz; + default: + ERROR_THROW("Unknown SysClkModule: %x", module); + return 0; + } +} + void Clocks::ResetToStock(unsigned int module) { - Result rc = 0; if(hosversionAtLeast(9,0,0)) { std::uint32_t confId = 0; - rc = apmExtGetCurrentPerformanceConfiguration(&confId); + Result 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); - } + SysClkApmConfiguration* apmConfiguration = GetEmbeddedApmConfig(confId); if (module == SysClkModule_EnumMax || module == SysClkModule_CPU) { @@ -197,6 +217,7 @@ void Clocks::ResetToStock(unsigned int module) } else { + Result rc = 0; std::uint32_t mode = 0; rc = apmExtGetPerformanceMode(&mode); ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); diff --git a/Source/sys-clk-OC/sysmodule/src/clocks.h b/Source/sys-clk-OC/sysmodule/src/clocks.h index 6470f69f..dcc04114 100644 --- a/Source/sys-clk-OC/sysmodule/src/clocks.h +++ b/Source/sys-clk-OC/sysmodule/src/clocks.h @@ -20,6 +20,8 @@ class Clocks public: static void Exit(); static void Initialize(); + static SysClkApmConfiguration* GetEmbeddedApmConfig(uint32_t confId); + static uint32_t GetStockClock(SysClkApmConfiguration* apm, SysClkModule module); static void ResetToStock(unsigned int module = SysClkModule_EnumMax); static SysClkProfile GetCurrentProfile(); static std::uint32_t GetCurrentHz(SysClkModule module); diff --git a/Source/sys-clk-OC/sysmodule/src/config.cpp b/Source/sys-clk-OC/sysmodule/src/config.cpp index 2b176dd6..6fa09505 100644 --- a/Source/sys-clk-OC/sysmodule/src/config.cpp +++ b/Source/sys-clk-OC/sysmodule/src/config.cpp @@ -183,12 +183,12 @@ bool Config::SetProfiles(std::uint64_t tid, SysClkTitleProfileList* profiles, bo 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* iniKeys[static_cast(SysClkProfile_EnumMax) * static_cast(SysClkModule_EnumMax) + 1]; + char* iniValues[static_cast(SysClkProfile_EnumMax) * static_cast(SysClkModule_EnumMax) + 1]; // Char arrays to build strings - char keysStr[SysClkProfile_EnumMax * SysClkModule_EnumMax * 0x40]; - char valuesStr[SysClkProfile_EnumMax * SysClkModule_EnumMax * 0x10]; + char keysStr[static_cast(SysClkProfile_EnumMax) * static_cast(SysClkModule_EnumMax) * 0x40]; + char valuesStr[static_cast(SysClkProfile_EnumMax) * static_cast(SysClkModule_EnumMax) * 0x10]; char section[17] = {0}; // Iteration pointers diff --git a/Source/sys-clk-OC/sysmodule/src/file_utils.cpp b/Source/sys-clk-OC/sysmodule/src/file_utils.cpp index fd8edce2..551d91c6 100644 --- a/Source/sys-clk-OC/sysmodule/src/file_utils.cpp +++ b/Source/sys-clk-OC/sysmodule/src/file_utils.cpp @@ -18,7 +18,6 @@ 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 bool g_reversenx_tool_exist = false; static std::uint64_t g_last_flag_check = 0; extern "C" void __libnx_init_time(void); @@ -131,22 +130,6 @@ void FileUtils::RefreshFlags(bool force) g_last_flag_check = now; } -void FileUtils::InitCheckFlags() -{ - FILE *file; - file = fopen(FILE_SALTYNX_PATH, "r"); - if (file) - { - g_reversenx_tool_exist = true; - fclose(file); - } -} - -bool FileUtils::ExistReverseNXTool() -{ - return g_reversenx_tool_exist; -} - void FileUtils::InitializeAsync() { Thread initThread = {0}; @@ -179,7 +162,6 @@ Result FileUtils::Initialize() if (R_SUCCEEDED(rc)) { FileUtils::RefreshFlags(true); - FileUtils::InitCheckFlags(); g_has_initialized = true; FileUtils::LogLine("=== " TARGET " " TARGET_VERSION " ==="); } diff --git a/Source/sys-clk-OC/sysmodule/src/file_utils.h b/Source/sys-clk-OC/sysmodule/src/file_utils.h index eead6a34..1956342f 100644 --- a/Source/sys-clk-OC/sysmodule/src/file_utils.h +++ b/Source/sys-clk-OC/sysmodule/src/file_utils.h @@ -22,7 +22,6 @@ #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" -#define FILE_SALTYNX_PATH "/atmosphere/contents/0000000000534C56/flags/boot2.flag" // Just check for SaltyNX boot flag class FileUtils { @@ -37,5 +36,4 @@ class FileUtils static void WriteContextToCsv(const SysClkContext* context); protected: static void RefreshFlags(bool force); - static void InitCheckFlags(); }; diff --git a/Source/sys-clk-OC/sysmodule/src/main.cpp b/Source/sys-clk-OC/sysmodule/src/main.cpp index b7981c2f..98dc755c 100644 --- a/Source/sys-clk-OC/sysmodule/src/main.cpp +++ b/Source/sys-clk-OC/sysmodule/src/main.cpp @@ -20,6 +20,7 @@ #include "process_management.h" #include "clock_manager.h" #include "ipc_service.h" +#include "oc_extra.h" #define INNER_HEAP_SIZE 0x30000 @@ -27,13 +28,18 @@ extern "C" { extern std::uint32_t __start__; - std::uint32_t __nx_applet_type = AppletType_None; + //set applet type to use nvdrv* service + std::uint32_t __nx_applet_type = AppletType_SystemApplication; 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]; + // set transfermem size to 32kib as [Fizeau](https://github.com/averne/Fizeau/) + // or LibnxError_OutOfMemory + u32 __nx_nv_transfermem_size = 0x8000; + void __libnx_initheap(void) { void *addr = nx_inner_heap; diff --git a/Source/sys-clk-OC/sysmodule/src/oc_extra.cpp b/Source/sys-clk-OC/sysmodule/src/oc_extra.cpp new file mode 100644 index 00000000..383ccbfd --- /dev/null +++ b/Source/sys-clk-OC/sysmodule/src/oc_extra.cpp @@ -0,0 +1,368 @@ +#include "oc_extra.h" + +SysClkProfile ReverseNXSync::GetProfile(SysClkProfile real) { + switch (this->GetMode()) { + case ReverseNX_Docked: + return SysClkProfile_Docked; + case ReverseNX_Handheld: + if (real == SysClkProfile_Docked) + return SysClkProfile_HandheldChargingOfficial; + default: + return real; + } +} + +ReverseNXMode ReverseNXSync::GetMode() { + if (!this->m_sync_enabled) + return ReverseNX_NotFound; + if (this->m_rt_mode) + return this->m_rt_mode; + return this->m_tool_mode; +} + +bool ReverseNXSync::CheckToolEnabled() { + FILE *fp = fopen("/atmosphere/contents/0000000000534C56/flags/boot2.flag", "r"); + if (fp) { + this->m_tool_enabled = true; + fclose(fp); + } else { + this->m_tool_enabled = false; + } + return this->m_tool_enabled; +} + +ReverseNXMode ReverseNXSync::GetToolModeFromPatch(const char* patch_path) { + constexpr uint32_t DOCKED_MAGIC = 0x320003E0; + constexpr uint32_t HANDHELD_MAGIC = 0x52A00000; + FILE *fp = fopen(patch_path, "rb"); + if (fp) { + uint32_t buf = 0; + fread(&buf, sizeof(buf), 1, fp); + fclose(fp); + + if (buf == DOCKED_MAGIC) + return ReverseNX_Docked; + if (buf == HANDHELD_MAGIC) + return ReverseNX_Handheld; + } + + return ReverseNX_NotFound; +} + +ReverseNXMode ReverseNXSync::RecheckToolMode() { + ReverseNXMode mode = ReverseNX_NotFound; + if (this->m_tool_enabled) { + const char* fileName = "_ZN2nn2oe16GetOperationModeEv.asm64"; // or _ZN2nn2oe18GetPerformanceModeEv.asm64 + const char* filePath = new char[72]; + /* Check per-game patch */ + snprintf((char*)filePath, 72, "/SaltySD/patches/%016lX/%s", this->m_app_id, fileName); + mode = this->GetToolModeFromPatch(filePath); + if (!mode) { + /* Check global patch */ + snprintf((char*)filePath, 72, "/SaltySD/patches/%s", fileName); + mode = this->GetToolModeFromPatch(filePath); + } + delete[] filePath; + } + + return mode; +} + +void PsmExt::ChargingHandler(bool fastChargingEnabled, uint32_t chargingLimit) { + PsmChargeInfo* info = new PsmChargeInfo; + Service* session = psmGetServiceSession(); + serviceDispatchOut(session, Psm_GetBatteryChargeInfoFields, *info); + + if (PsmIsFastChargingEnabled(info) != fastChargingEnabled) + serviceDispatch(session, fastChargingEnabled ? Psm_EnableFastBatteryCharging : Psm_DisableFastBatteryCharging); + + if (PsmIsChargerConnected(info)) { + u32 chargeNow = 0; + if (R_SUCCEEDED(psmGetBatteryChargePercentage(&chargeNow))) { + bool isCharging = PsmIsCharging(info); + if (isCharging && chargingLimit < chargeNow) + serviceDispatch(session, Psm_DisableBatteryCharging); + if (!isCharging && chargingLimit > chargeNow) + serviceDispatch(session, Psm_EnableBatteryCharging); + } + } + + delete info; +} + +void Governor::Start() { + m_stop_threads = false; + svcSleepThread(8 * TICK_TIME_MAIN_NS); + Result rc = 0; + + for (int core = 0; core < CORE_NUMS; core++) { + if (m_t_cpuworker[core].handle) + continue; + s_CoreContext* s = InitCoreContext(&m_cpu_core_ctx[core], this, core); + rc = threadCreate(&m_t_cpuworker[core], &CheckCpuUtilWorker, (void*)s, NULL, 0x1000, 0x20, core); + if (rc) { + ERROR_THROW("Cannot create thread m_t_cpuworker[%d]: %u", core, rc); + return; + } + rc = threadStart(&m_t_cpuworker[core]); + if (rc) { + ERROR_THROW("Cannot start thread m_t_cpuworker[%d]: %u", core, rc); + return; + } + } + rc = threadCreate(&m_t_main, &Main, (void*)this, NULL, 0x1000, 0x3F, 3); + if (rc) { + ERROR_THROW("Cannot create thread m_t_main: %u", rc); + return; + } + rc = threadStart(&m_t_main); + if (rc) { + ERROR_THROW("Cannot start thread m_t_main: %u", rc); + return; + } +} + +void Governor::Stop() { + m_stop_threads = true; + svcSleepThread(8 * TICK_TIME_MAIN_NS); + + threadWaitForExit(&m_t_main); + threadClose(&m_t_main); + + for (int core = 0; core < CORE_NUMS; core++) { + threadWaitForExit(&m_t_cpuworker[core]); + threadClose(&m_t_cpuworker[core]); + } +} + +void Governor::SetMaxHz(uint32_t max_hz, SysClkModule module) { + if (!max_hz) // Fallback to apm configuration + max_hz = Clocks::GetStockClock(m_apm_conf, (SysClkModule)module); + + switch (module) { + case SysClkModule_CPU: + m_cpu_freq.idx_max_hz = FindIndex(&m_cpu_freq, max_hz); + break; + case SysClkModule_GPU: + m_gpu_freq.idx_boost_hz = m_gpu_freq.idx_max_hz = FindIndex(&m_gpu_freq, max_hz); + break; + case SysClkModule_MEM: + m_mem_freq = max_hz; + Clocks::SetHz(SysClkModule_MEM, max_hz); + break; + default: + break; + } +} + +void Governor::SetPerfConf(uint32_t id) { + m_perf_conf_id = id; + m_apm_conf = Clocks::GetEmbeddedApmConfig(id); +} + +uint32_t Governor::FindIndex(s_Freq* f, uint32_t hz) { + uint32_t idx = 0, hz_in_list; + while ((hz_in_list = f->hz_list[idx]) != 0) { + if (hz == hz_in_list) + return idx; + idx++; + } + ERROR_THROW("[mgr] Cannot find hz: %lu", hz); + return 0; +} + +bool Governor::TargetRamp(s_Freq* f, FREQ_RAMP_DIRECTION dir) { + uint8_t idx_old = f->idx_target_hz; + + switch (dir) { + case RAMP_UP: + f->idx_target_hz++; + if (f->idx_target_hz > f->idx_max_hz) + f->idx_target_hz = f->idx_max_hz; + break; + case RAMP_DOWN: + if (f->idx_target_hz > 0) + f->idx_target_hz--; + if (f->idx_target_hz < f->idx_min_hz) + f->idx_target_hz = f->idx_min_hz; + break; + case RAMP_MAX: + f->idx_target_hz = f->idx_max_hz; + break; + case RAMP_MIN: + f->idx_target_hz = f->idx_min_hz; + break; + case RAMP_BOOST: + f->idx_target_hz = f->idx_boost_hz; + break; + } + + uint8_t idx_new = f->idx_target_hz; + bool changed = idx_old != idx_new; + return changed; +} + +void Governor::SetHz(s_Freq* f) { + uint32_t hz = f->hz_list[f->idx_target_hz]; + if (hz) + Clocks::SetHz(f->module, hz); +} + +void Governor::SetBoostHz(s_Freq* f) { + f->idx_target_hz = f->idx_boost_hz; + if (f->module == SysClkModule_CPU && f->idx_max_hz > f->idx_boost_hz) + f->idx_target_hz = f->idx_max_hz; + SetHz(f); +} + +Governor::s_CoreContext* Governor::InitCoreContext( + s_CoreContext* context, Governor* self, int64_t id +) { + memset(reinterpret_cast(context), 0, sizeof(s_CoreContext)); + context->self = self; + context->id = id; + return context; +} + +void Governor::CheckCpuUtilWorker(void* args) { + s_CoreContext* s = static_cast(args); + int64_t coreid = s->id; + Governor* self = s->self; + + bool isSystemCore = (coreid == CORE_NUMS - 1); + if (isSystemCore) + self->CheckCpuUtilWorkerSysCore(); + else + self->CheckCpuUtilWorkerAppCore(coreid); +} + +void Governor::CheckCpuUtilWorkerAppCore(int64_t coreid) { + constexpr uint64_t STUCK_TICKS = 2; + s_Queue q; + while (!m_stop_threads) { + bool isBusy = m_core3_stuck_cnt > STUCK_TICKS * (CORE_NUMS - 1); + if (isBusy) { + m_core3_stuck_cnt = 0; + SetBoostHz(&m_cpu_freq); + svcSleepThread(STUCK_TICKS * TICK_TIME_CPU_NS); + } else { + m_core3_stuck_cnt++; + } + + uint64_t load = CpuCoreUtil(coreid, TICK_TIME_CPU_MS).Get(); + q.PopAndPush(load); + m_cpu_core_ctx[coreid].util = q.GetAvg(); + } +} + +void Governor::CheckCpuUtilWorkerSysCore() { + s_Queue q; + int64_t coreid = CORE_NUMS - 1; + while (!m_stop_threads) { + uint64_t load = CpuCoreUtil(coreid, TICK_TIME_CPU_MS).Get(); + q.PopAndPush(load); + m_cpu_core_ctx[coreid].util = q.GetAvg() * 7 / 8; // Adjusted, Multipler: 0.875 + } +} + +void Governor::Main(void* args) { + Governor* self = static_cast(args); + uint32_t nvgpu_field = self->m_nvgpu_field; + + auto GetCpuUtil = [self]() { + uint64_t cpu_util = self->m_cpu_core_ctx[0].util; + for (size_t i = 1; i < CORE_NUMS; i++) { + if (cpu_util < self->m_cpu_core_ctx[i].util) + cpu_util = self->m_cpu_core_ctx[i].util; + } + return cpu_util; + }; + + struct s_MaxQueue { + uint32_t queue[QUEUE_SIZE] = { 0 }; + size_t pos = 0; + } q; + + auto GetGpuUtil = [nvgpu_field, q]() mutable { + uint32_t load = GpuCoreUtil(nvgpu_field, TICK_TIME_GPU_MS).Get(); + if (load > 20) { // Ignore load <= 2.0% + q.queue[q.pos % QUEUE_SIZE] = load; + q.pos++; + } else { + load = q.queue[(q.pos - 1) % QUEUE_SIZE]; + } + // Get max of the queue + for (size_t i = 1; i < QUEUE_SIZE; i++) { + size_t p = (q.pos + i - 1) % QUEUE_SIZE; + if (load < q.queue[p]) + load = q.queue[p]; + } + + return load; + }; + + uint64_t update_ticks = SAMPLE_RATE_MAIN; + bool CPUBoosted = false; + bool GPUBoosted = false; // Limited to 76.8 MHz, literally + + while (!self->m_stop_threads) { + self->m_core3_stuck_cnt = 0; + + bool shouldUpdateContext = update_ticks++ >= SAMPLE_RATE_MAIN; + if (shouldUpdateContext) { + update_ticks = 0; + uint32_t hz = Clocks::GetCurrentHz(SysClkModule_GPU); + // Sleep mode detected, wait 1 tick + while (!hz) { + self->m_core3_stuck_cnt = 0; + svcSleepThread(TICK_TIME_MAIN_NS); + hz = Clocks::GetCurrentHz(SysClkModule_GPU); + } + + GPUBoosted = apmExtIsBoostMode(self->m_perf_conf_id, true); + CPUBoosted = apmExtIsBoostMode(self->m_perf_conf_id, false); + + self->m_gpu_freq.idx_target_hz = FindIndex(&self->m_gpu_freq, hz); + if (GPUBoosted) + SetBoostHz(&self->m_gpu_freq); + + hz = Clocks::GetCurrentHz(SysClkModule_CPU); + self->m_cpu_freq.idx_target_hz = FindIndex(&self->m_cpu_freq, hz); + if (CPUBoosted) + SetBoostHz(&self->m_cpu_freq); + + hz = Clocks::GetCurrentHz(SysClkModule_MEM); + if (!self->m_mem_freq) + self->m_mem_freq = hz; + if (hz != self->m_mem_freq) + Clocks::SetHz(SysClkModule_MEM, self->m_mem_freq); + } else { + if (!GPUBoosted) { + uint32_t gpu_util = GetGpuUtil(); + if (gpu_util > GPU_THR_RAMP_MAX) { + if (TargetRamp(&self->m_gpu_freq, RAMP_MAX)) + SetHz(&self->m_gpu_freq); + } else if (gpu_util > GPU_THR_RAMP_UP) { + if (TargetRamp(&self->m_gpu_freq, RAMP_UP)) + SetHz(&self->m_gpu_freq); + } else if (gpu_util < GPU_THR_RAMP_DOWN) { + if (TargetRamp(&self->m_gpu_freq, RAMP_DOWN)) + SetHz(&self->m_gpu_freq); + } + } + if (!CPUBoosted) { + uint64_t cpu_util = GetCpuUtil(); + if (cpu_util > CPU_THR_RAMP_UP) { + if (TargetRamp(&self->m_cpu_freq, RAMP_UP)) + SetHz(&self->m_cpu_freq); + } else if (cpu_util < CPU_THR_RAMP_DOWN) { + if (TargetRamp(&self->m_cpu_freq, RAMP_DOWN)) + SetHz(&self->m_cpu_freq); + } + } + } + + svcSleepThread(TICK_TIME_MAIN_NS); + } +} + diff --git a/Source/sys-clk-OC/sysmodule/src/oc_extra.h b/Source/sys-clk-OC/sysmodule/src/oc_extra.h new file mode 100644 index 00000000..576e2c08 --- /dev/null +++ b/Source/sys-clk-OC/sysmodule/src/oc_extra.h @@ -0,0 +1,215 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "file_utils.h" +#include "clocks.h" + +class CpuCoreUtil { +public: + CpuCoreUtil (int coreid = -2, uint64_t ms = 1): + m_core_id(coreid), m_wait_time_ms(ms), m_wait_time_ns(ms * 1000'000ULL) {}; + + inline uint64_t Get() { Start(); WaitForStop(); Stop(); return Calculate(); }; + inline void Start() { m_idletick = GetIdleTickCount(); }; + inline void WaitForStop() { svcSleepThread(m_wait_time_ns); }; + inline void Stop() { m_idletick = GetIdleTickCount() - m_idletick; }; + + static constexpr uint64_t TICKS_PER_MS = 192; + inline uint64_t Calculate() { return 100'0 - m_idletick * 10 / (TICKS_PER_MS * m_wait_time_ms); }; + +protected: + const int m_core_id; + const uint64_t m_wait_time_ms, m_wait_time_ns; + uint64_t m_idletick; + + inline uint64_t GetIdleTickCount() { + uint64_t idletick = 0; + svcGetInfo(&idletick, InfoType_IdleTickCount, INVALID_HANDLE, m_core_id); + return idletick; + }; +}; + +class GpuCoreUtil { +public: + GpuCoreUtil (uint32_t nvgpu_field, uint64_t ms = 1): + m_nvgpu_field(nvgpu_field), m_wait_time_ns(ms * 1000'000ULL) {}; + + inline uint64_t Get() { Wait(); return GetLoad(); }; + inline void Wait() { svcSleepThread(m_wait_time_ns); }; + inline uint32_t GetLoad() { + uint32_t load; + nvIoctl(m_nvgpu_field, NVGPU_GPU_IOCTL_PMU_GET_GPU_LOAD, &load); + // if (R_FAILED(rc)) { + // ERROR_THROW("[mgr] nvIoctl() failed: 0x%lX", rc); + // } + return load; + }; + +protected: + uint32_t m_nvgpu_field; + const uint64_t m_wait_time_ns; + static constexpr uint64_t NVGPU_GPU_IOCTL_PMU_GET_GPU_LOAD = 0x80044715; +}; + +class ReverseNXSync { +public: + ReverseNXSync () + : m_rt_mode(ReverseNX_NotFound), m_tool_mode(ReverseNX_NotFound) { + CheckToolEnabled(); + }; + + void ToggleSync(bool enable) { m_sync_enabled = enable; }; + void Reset(uint64_t app_id) { m_app_id = app_id; SetRTMode(ReverseNX_NotFound); GetToolMode(); } + ReverseNXMode GetRTMode() { return m_rt_mode; }; + void SetRTMode(ReverseNXMode mode) { m_rt_mode = mode; }; + ReverseNXMode GetToolMode() { return m_tool_mode = RecheckToolMode(); }; + SysClkProfile GetProfile(SysClkProfile real); + ReverseNXMode GetMode(); + +protected: + ReverseNXMode m_rt_mode, m_tool_mode; + uint64_t m_app_id = 0; + bool m_tool_enabled; + bool m_sync_enabled; + + bool CheckToolEnabled(); + ReverseNXMode GetToolModeFromPatch(const char* patch_path); + ReverseNXMode RecheckToolMode(); +}; + +namespace PsmExt { + void ChargingHandler(bool fastChargingEnabled, uint32_t chargingLimit); +}; + +class Governor { +public: + Governor() { + memset(reinterpret_cast(&m_cpu_freq), 0, sizeof(m_cpu_freq)); + memset(reinterpret_cast(&m_gpu_freq), 0, sizeof(m_gpu_freq)); + + m_cpu_freq.module = SysClkModule_CPU; + m_gpu_freq.module = SysClkModule_GPU; + + m_cpu_freq.hz_list = &sysclk_g_freq_table_cpu_hz[0]; + m_gpu_freq.hz_list = &sysclk_g_freq_table_gpu_hz[0]; + + m_cpu_freq.idx_boost_hz = FindIndex(&m_cpu_freq, 1785'000'000); + + m_gpu_freq.idx_boost_hz = FindIndex(&m_gpu_freq, 76'800'000); + m_gpu_freq.idx_min_hz = FindIndex(&m_gpu_freq, 153'600'000); + + nvInitialize(); + Result rc = nvOpen(&m_nvgpu_field, "/dev/nvhost-ctrl-gpu"); + if (R_FAILED(rc)) { + ERROR_THROW("[mgr] nvOpen() failed: 0x%lX", rc); + nvExit(); + } + }; + + ~Governor() { + Stop(); + nvClose(m_nvgpu_field); + nvExit(); + }; + + void Start(); + void Stop(); + void SetMaxHz(uint32_t max_hz, SysClkModule module); + void SetCPUBoostHz(uint32_t hz) { m_cpu_freq.idx_boost_hz = FindIndex(&m_cpu_freq, hz); }; + void SetPerfConf(uint32_t id); + +protected: + // Parameters for sampling + static constexpr uint64_t SAMPLE_RATE_MAIN = 60, SAMPLE_RATE_GPU = 60; + static constexpr uint64_t SAMPLE_RATE_CPU = SAMPLE_RATE_GPU / 2; + static constexpr uint64_t UPDATE_CONTEXT_RATE = 60; + static constexpr uint64_t TICK_TIME_CPU_MS = 1000 / SAMPLE_RATE_CPU; + static constexpr uint64_t TICK_TIME_CPU_NS = 1E9 / SAMPLE_RATE_CPU; + static constexpr uint64_t TICK_TIME_GPU_MS = 1000 / SAMPLE_RATE_GPU; + static constexpr uint64_t TICK_TIME_MAIN_MS = 1000 / SAMPLE_RATE_MAIN; + static constexpr uint64_t TICK_TIME_MAIN_NS = 1E9 / SAMPLE_RATE_MAIN; + + // Parameters for frequency ramp threshold + static constexpr uint64_t CPU_THR_RAMP_DOWN = 70'0; + static constexpr uint64_t CPU_THR_RAMP_UP = 90'0; + static constexpr uint64_t GPU_THR_RAMP_DOWN = 60'0; + static constexpr uint64_t GPU_THR_RAMP_UP = 80'0; + static constexpr uint64_t GPU_THR_RAMP_MAX = 90'0; + + static constexpr int CORE_NUMS = 4; + + bool m_stop_threads = false; + Thread m_t_cpuworker[CORE_NUMS], m_t_main; + std::atomic m_core3_stuck_cnt = 0; + + uint32_t m_nvgpu_field; + uint32_t m_mem_freq; + uint32_t m_perf_conf_id; + SysClkApmConfiguration *m_apm_conf; + + typedef enum { + RAMP_UP, + RAMP_DOWN, + RAMP_MAX, + RAMP_MIN, + RAMP_BOOST, + } FREQ_RAMP_DIRECTION; + + typedef struct { + SysClkModule module; + uint32_t* hz_list; + uint8_t idx_target_hz; + uint8_t idx_min_hz; + uint8_t idx_max_hz; + uint8_t idx_boost_hz; + } s_Freq; + s_Freq m_cpu_freq, m_gpu_freq; + + static uint32_t FindIndex(s_Freq* f, uint32_t hz); + static bool TargetRamp(s_Freq* f, FREQ_RAMP_DIRECTION dir); + static void SetHz(s_Freq* f); + static void SetBoostHz(s_Freq* f); + + typedef struct { + Governor* self; + int64_t id; + uint64_t util; + } s_CoreContext; + s_CoreContext m_cpu_core_ctx[CORE_NUMS]; + + s_CoreContext* InitCoreContext(s_CoreContext* context, Governor* self, int64_t id = 0); + + static void CheckCpuUtilWorker(void* args); + static void Main(void* args); + +private: + static constexpr size_t QUEUE_SIZE = 8; + template + struct s_Queue { + // Much faster than from stl + T queue[QUEUE_SIZE] = { 0 }; + T sum = 0; + T pos = 0; + + T GetAvg() { return sum / QUEUE_SIZE; }; + T GetFirst() { return queue[pos % QUEUE_SIZE]; }; + T GetLast() { return queue[(pos - 1) % QUEUE_SIZE]; }; + T PopAndPush(T val_to_push) { + T val_to_pop; + sum -= (val_to_pop = GetFirst()); // Pop and subtract from sum + sum += (queue[pos % QUEUE_SIZE] = val_to_push); // Push and add to sum + pos++; + return val_to_pop; + } + }; + + void CheckCpuUtilWorkerSysCore(); + void CheckCpuUtilWorkerAppCore(int64_t coreid); +}; diff --git a/system_settings.md b/system_settings.md index 94e6d103..7774782a 100644 --- a/system_settings.md +++ b/system_settings.md @@ -107,11 +107,12 @@ polling_interval_sec_min = u32!0x7FFFFFFF ``` - - FPS and bitrate of game recording and streaming + - Parameters of game recording and streaming - ```ini [am.debug] - continuous_recording_fps = u32!60 ; 30 or 60 FPS - continuous_recording_video_bit_rate = u32!0x780000 ; ~7.5Mbps(0x780000 = 7,864,320), default is ~5Mbps, VBR(Variable Bitrate) + continuous_recording_fps = u32!60 ; 30 or 60 FPS, default: 30 + continuous_recording_video_bit_rate = u32!0x780000 ; 7.5Mbps(0x780000 = 7,864,320), default: ~5Mbps(0x4C4B40), VBR(Variable Bitrate) + continuous_recording_key_frame_count = u32!15 ; One I-frame in 15 frames (with other 14 P-frames), default: 15 ``` - Recommended: [dvr-patches](https://github.com/exelix11/dvr-patches): Allow screenshot/recording in any games and remove overlay image (copyright notice or logo). - For optimal streaming experience, SysDVR via USB interface is recommended.