Updated: Sys-clk-OC, Loader, System_settings

- Sys-clk-OC

  - Major cleanup in clock_manager, preparing to add basic Erista support.

  - Added an experimental CPU & GPU frequency governor.

    - Known issue:

      - Occasional stuttering is expected: GPU load% metric PMU_GET_GPU_LOAD does not reflect real utilization precisely. Use another metric, some interpolation algo or add min frequency option for improvement.

- Loader

  - Addressed an issue for Erista variants: Boot with unmodified Fusee or Hekate, nn::pcv::EmcDvfsPeriodicCompHandler will fail with rc 0x8C5. Fixed by removing 40.8 MHz while retaining 1600.0 MHz MEM table -- however, this means user has to use modified sys-clk.
This commit is contained in:
KazushiM
2022-10-23 23:01:50 +08:00
parent d596016c84
commit b52bef3c31
27 changed files with 925 additions and 433 deletions

View File

@@ -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). 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. 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/`. 3. Grab `x.x.x_loader.kip` for your Atmosphere version, rename it to `loader.kip` and place it in `/atmosphere/kips/`.

View File

@@ -148,6 +148,7 @@ namespace ams::ldr::oc {
#define COMPARE_HIGH(val1, val2, bit_div) (((val1 ^ val2) >> bit_div) == 0) #define COMPARE_HIGH(val1, val2, bit_div) (((val1 ^ val2) >> bit_div) == 0)
/* EMC */ /* 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. // 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] = // constexpr emc_dvb_dvfs_table_t EmcDvbTable[6] =
@@ -771,9 +772,8 @@ namespace ams::ldr::oc {
Result MtcTableHandler(u32* ptr) { Result MtcTableHandler(u32* ptr) {
MarikoMtcTable* const mtc_table_max = reinterpret_cast<MarikoMtcTable *>(ptr - offsetof(MarikoMtcTable, rate_khz) / sizeof(u32)); MarikoMtcTable* const mtc_table_max = reinterpret_cast<MarikoMtcTable *>(ptr - offsetof(MarikoMtcTable, rate_khz) / sizeof(u32));
MarikoMtcTable* const mtc_table_alt = mtc_table_max - 1; MarikoMtcTable* const mtc_table_alt = mtc_table_max - 1;
constexpr u32 mtc_mariko_rev = 3; R_UNLESS(mtc_table_max->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable());
R_UNLESS(mtc_table_max->rev == mtc_mariko_rev, ldr::ResultInvalidMtcTable()); R_UNLESS(mtc_table_alt->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable());
R_UNLESS(mtc_table_alt->rev == mtc_mariko_rev, ldr::ResultInvalidMtcTable());
R_UNLESS(mtc_table_alt->rate_khz == MemClkOSAlt, ldr::ResultInvalidMtcTable()); R_UNLESS(mtc_table_alt->rate_khz == MemClkOSAlt, ldr::ResultInvalidMtcTable());
MarikoMtcTable* const table = const_cast<MarikoMtcTable *>(C.marikoMtc); MarikoMtcTable* const table = const_cast<MarikoMtcTable *>(C.marikoMtc);
@@ -964,6 +964,8 @@ namespace ams::ldr::oc {
{ 2091000, {}, {} }, { 2091000, {}, {} },
}; };
constexpr u32 MTC_TABLE_REV = 7;
Result CpuDvfsHandler(u32* ptr, uintptr_t nso_end_offset) { Result CpuDvfsHandler(u32* ptr, uintptr_t nso_end_offset) {
cpu_freq_cvb_table_t* entry_1785 = reinterpret_cast<cpu_freq_cvb_table_t *>(ptr); cpu_freq_cvb_table_t* entry_1785 = reinterpret_cast<cpu_freq_cvb_table_t *>(ptr);
cpu_freq_cvb_table_t* entry_free = entry_1785 + 1; cpu_freq_cvb_table_t* entry_free = entry_1785 + 1;
@@ -998,6 +1000,21 @@ namespace ams::ldr::oc {
} }
Result MtcTableHandler(u32* ptr) { 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<EristaMtcTable *>(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<void *>(table_list[i]), static_cast<void *>(table_list[i - 1]), sizeof(EristaMtcTable));
bool replace_entire_table = (C.mtcConf == ENTIRE_TABLE_ERISTA); bool replace_entire_table = (C.mtcConf == ENTIRE_TABLE_ERISTA);
if (replace_entire_table) { if (replace_entire_table) {
EristaMtcTable* const mtc_table_max = reinterpret_cast<EristaMtcTable *>(ptr - offsetof(EristaMtcTable, rate_khz) / sizeof(u32)); EristaMtcTable* const mtc_table_max = reinterpret_cast<EristaMtcTable *>(ptr - offsetof(EristaMtcTable, rate_khz) / sizeof(u32));

View File

@@ -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: 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 | | Key | Desc | Default |
|:-----------------------:|-------------------------------------------------------------------------------|:---------:| |:------------------------:|-------------------------------------------------------------------------------|:---------:|
|**allow_unsafe_freq** | Allow unsafe frequencies (CPU > 1963.5 MHz, GPU > 921.6 MHz) | OFF | |**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 | |**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 | |**sync_reversenx_mode** | Sync nominal profile (mode) with ReverseNX (-Tool and -RT) | ON |
|**disable_fast_charging**| Disable Fast Charging (2000mA -> 500 mA) | OFF | |**disable_fast_charging** | Disable fast charging (2000mA -> 500 mA) | OFF |
|**charging_limit_perc** | Charging Limit (20% - 100%) | 100%(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 | |**governor_experimental** | CPU & GPU frequency governor (Experimental) | OFF |
|**csv_write_interval_ms**| Defines how often sys-clk writes to the CSV, in milliseconds (`0` to disable) | 0 ms | |**temp_log_interval_ms** | Defines how often sys-clk log temperatures, in milliseconds (`0` to disable) | 0 ms |
|**poll_interval_ms** | Defines how fast sys-clk checks and applies profiles, in milliseconds | 500 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 |

View File

@@ -19,6 +19,7 @@ extern "C" {
#include "sysclk/apm.h" #include "sysclk/apm.h"
#include "sysclk/config.h" #include "sysclk/config.h"
#include "sysclk/errors.h" #include "sysclk/errors.h"
#include "sysclk/psm_ext.h"
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -63,10 +63,8 @@ typedef struct
{ {
bool systemCoreBoostCPU; bool systemCoreBoostCPU;
bool allowUnsafeFreq; bool allowUnsafeFreq;
bool syncReverseNXMode; bool governor;
SysClkProfile realProfile; SysClkProfile realProfile;
ReverseNXMode reverseNXToolMode;
ReverseNXMode reverseNXRTMode;
uint32_t maxMEMFreq; uint32_t maxMEMFreq;
uint32_t boostCPUFreq; uint32_t boostCPUFreq;
} SysClkOcExtra; } SysClkOcExtra;

View File

@@ -22,6 +22,7 @@ typedef enum {
SysClkConfigValue_AllowUnsafeFrequencies, SysClkConfigValue_AllowUnsafeFrequencies,
SysClkConfigValue_DisableFastCharging, SysClkConfigValue_DisableFastCharging,
SysClkConfigValue_ChargingLimitPercentage, SysClkConfigValue_ChargingLimitPercentage,
SysClkConfigValue_GovernorExperimental,
SysClkConfigValue_EnumMax, SysClkConfigValue_EnumMax,
} SysClkConfigValue; } SysClkConfigValue;
@@ -49,6 +50,8 @@ static inline const char* sysclkFormatConfigValue(SysClkConfigValue val, bool pr
return pretty ? "Disable Fast Charging" : "disable_fast_charging"; return pretty ? "Disable Fast Charging" : "disable_fast_charging";
case SysClkConfigValue_ChargingLimitPercentage: case SysClkConfigValue_ChargingLimitPercentage:
return pretty ? "Charging Limit (%%)" : "charging_limit_perc"; return pretty ? "Charging Limit (%%)" : "charging_limit_perc";
case SysClkConfigValue_GovernorExperimental:
return pretty ? "Governor (Experimental)" : "governor_experimental";
default: default:
return NULL; return NULL;
} }
@@ -64,6 +67,7 @@ static inline uint64_t sysclkDefaultConfigValue(SysClkConfigValue val)
case SysClkConfigValue_CsvWriteIntervalMs: case SysClkConfigValue_CsvWriteIntervalMs:
case SysClkConfigValue_AllowUnsafeFrequencies: case SysClkConfigValue_AllowUnsafeFrequencies:
case SysClkConfigValue_DisableFastCharging: case SysClkConfigValue_DisableFastCharging:
case SysClkConfigValue_GovernorExperimental:
return 0ULL; return 0ULL;
case SysClkConfigValue_AutoCPUBoost: case SysClkConfigValue_AutoCPUBoost:
case SysClkConfigValue_SyncReverseNXMode: case SysClkConfigValue_SyncReverseNXMode:
@@ -88,6 +92,7 @@ static inline uint64_t sysclkValidConfigValue(SysClkConfigValue val, uint64_t in
case SysClkConfigValue_SyncReverseNXMode: case SysClkConfigValue_SyncReverseNXMode:
case SysClkConfigValue_AllowUnsafeFrequencies: case SysClkConfigValue_AllowUnsafeFrequencies:
case SysClkConfigValue_DisableFastCharging: case SysClkConfigValue_DisableFastCharging:
case SysClkConfigValue_GovernorExperimental:
return (input & 0x1) == input; return (input & 0x1) == input;
case SysClkConfigValue_ChargingLimitPercentage: case SysClkConfigValue_ChargingLimitPercentage:
return (input <= 100 && input >= 20); return (input <= 100 && input >= 20);

View File

@@ -0,0 +1,79 @@
#pragma once
#include <switch.h>
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);

View File

@@ -0,0 +1,55 @@
#include <sysclk/psm_ext.h>
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 "?";
}
}

View File

@@ -111,7 +111,7 @@ brls::SelectListItem* createFreqListItem(SysClkModule module, uint32_t selectedF
char clock[16]; char clock[16];
if (freq == 1862400000) if (freq == 1862400000)
snprintf(clock, sizeof(clock), "Max MHz"); snprintf(clock, sizeof(clock), "Maximum");
else else
snprintf(clock, sizeof(clock), "%d MHz", freq / 1000000); snprintf(clock, sizeof(clock), "%d MHz", freq / 1000000);

View File

@@ -22,7 +22,7 @@ static inline std::string formatListFreqMhz(std::uint32_t mhz)
} }
else if (mhz == 1862) else if (mhz == 1862)
{ {
return "Max MHz"; return "Maximum";
} }
char buf[10]; char buf[10];

View File

@@ -15,7 +15,7 @@
MiscGui::MiscGui() MiscGui::MiscGui()
{ {
this->configList = new SysClkConfigValueList {}; this->configList = new SysClkConfigValueList {};
this->chargeInfo = new ChargeInfo {}; this->chargeInfo = new PsmChargeInfo {};
this->i2cInfo = new I2cInfo {}; this->i2cInfo = new I2cInfo {};
} }
@@ -48,6 +48,14 @@ void MiscGui::updateConfigToggle(tsl::elm::ToggleListItem *toggle, SysClkConfigV
void MiscGui::listUI() 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); sysclkIpcGetConfigValues(this->configList);
this->listElement->addItem(new tsl::elm::CategoryHeader("Config")); this->listElement->addItem(new tsl::elm::CategoryHeader("Config"));
@@ -55,6 +63,7 @@ void MiscGui::listUI()
this->cpuBoostToggle = addConfigToggle(SysClkConfigValue_AutoCPUBoost, "Auto CPU Boost"); this->cpuBoostToggle = addConfigToggle(SysClkConfigValue_AutoCPUBoost, "Auto CPU Boost");
this->syncModeToggle = addConfigToggle(SysClkConfigValue_SyncReverseNXMode, "Sync ReverseNX Mode"); this->syncModeToggle = addConfigToggle(SysClkConfigValue_SyncReverseNXMode, "Sync ReverseNX Mode");
this->fastChargingToggle = addConfigToggle(SysClkConfigValue_DisableFastCharging, "Disable Fast Charging"); this->fastChargingToggle = addConfigToggle(SysClkConfigValue_DisableFastCharging, "Disable Fast Charging");
this->governorToggle = addConfigToggle(SysClkConfigValue_GovernorExperimental, "Governor (Experimental)");
this->chargingLimitHeader = new tsl::elm::CategoryHeader(""); this->chargingLimitHeader = new tsl::elm::CategoryHeader("");
this->listElement->addItem(this->chargingLimitHeader); this->listElement->addItem(this->chargingLimitHeader);
@@ -69,7 +78,7 @@ void MiscGui::listUI()
snprintf(chargingLimitBarDesc, 30, "Battery Charging Limit: %lu%%", this->configList->values[SysClkConfigValue_ChargingLimitPercentage]); snprintf(chargingLimitBarDesc, 30, "Battery Charging Limit: %lu%%", this->configList->values[SysClkConfigValue_ChargingLimitPercentage]);
this->chargingLimitHeader->setText(chargingLimitBarDesc); this->chargingLimitHeader->setText(chargingLimitBarDesc);
this->chargingLimitBar->setIcon(getBatteryStateIcon()); this->chargingLimitBar->setIcon(PsmGetBatteryStateIcon(this->chargeInfo));
Result rc = sysclkIpcSetConfigValues(this->configList); Result rc = sysclkIpcSetConfigValues(this->configList);
if (R_FAILED(rc)) 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); 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); }), 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::CategoryHeader("Info"));
this->listElement->addItem(new tsl::elm::CustomDrawer([this](tsl::gfx::Renderer *renderer, s32 x, s32 y, s32 w, s32 h) { 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); renderer->drawString(this->infoNames, false, x, y + 20, SMALL_TEXT_SIZE, DESC_COLOR);
@@ -114,7 +115,7 @@ void MiscGui::refresh() {
this->chargingLimitHeader->setText(chargingLimitBarDesc); this->chargingLimitHeader->setText(chargingLimitBarDesc);
PsmUpdate(); PsmUpdate();
this->chargingLimitBar->setIcon(getBatteryStateIcon()); this->chargingLimitBar->setIcon(PsmGetBatteryStateIcon(this->chargeInfo));
LblUpdate(); LblUpdate();
this->backlightToggle->setState(lblstatus); this->backlightToggle->setState(lblstatus);

View File

@@ -30,84 +30,6 @@ class MiscGui : public BaseMenuGui
void refresh() override; void refresh() override;
protected: 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 { typedef struct {
float batCurrent; float batCurrent;
u32 cpuVolt = 620; u32 cpuVolt = 620;
@@ -132,16 +54,6 @@ class MiscGui : public BaseMenuGui
smExit(); 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) Result I2cRead_OutU16(u8 reg, I2cDevice dev, u16 *out)
{ {
// ams::fatal::srv::StopSoundTask::StopSound() // ams::fatal::srv::StopSoundTask::StopSound()
@@ -285,14 +197,14 @@ class MiscGui : public BaseMenuGui
"%dmV\n" "%dmV\n"
"%dmV\n" "%dmV\n"
, ,
ChargeInfoChargerTypeToStr(chargeInfo->ChargerType), chargWattsInfo, PsmInfoChargerTypeToStr(chargeInfo->ChargerType), chargWattsInfo,
(float)chargeInfo->VoltageAvg / 1000, (float)chargeInfo->VoltageAvg / 1000,
(float)chargeInfo->BatteryTemperature / 1000, (float)chargeInfo->BatteryTemperature / 1000,
chargeInfo->InputCurrentLimit, chargeInfo->VBUSCurrentLimit, chargeInfo->InputCurrentLimit, chargeInfo->VBUSCurrentLimit,
chargeInfo->ChargeCurrentLimit, chargeVoltLimit, chargeInfo->ChargeCurrentLimit, chargeVoltLimit,
(float)chargeInfo->RawBatteryCharge / 1000, (float)chargeInfo->RawBatteryCharge / 1000,
(float)chargeInfo->BatteryAge / 1000, (float)chargeInfo->BatteryAge / 1000,
ChargeInfoPowerRoleToStr(chargeInfo->PowerRole), PsmPowerRoleToStr(chargeInfo->PowerRole),
batCurInfo, batCurInfo,
i2cInfo->cpuVolt, i2cInfo->cpuVolt,
i2cInfo->gpuVolt, i2cInfo->gpuVolt,
@@ -302,7 +214,7 @@ class MiscGui : public BaseMenuGui
void PsmChargingToggler(bool* enable) void PsmChargingToggler(bool* enable)
{ {
if (!PsmIsChargerConnected()) if (!PsmIsChargerConnected(this->chargeInfo))
{ {
*enable = false; *enable = false;
return; return;
@@ -310,7 +222,7 @@ class MiscGui : public BaseMenuGui
PsmUpdate(*enable ? 2 : 3); PsmUpdate(*enable ? 2 : 3);
*enable = (PsmIsCharging() == *enable); *enable = (PsmIsCharging(this->chargeInfo) == *enable);
} }
void LblUpdate(bool shouldSwitch = false) void LblUpdate(bool shouldSwitch = false)
@@ -324,44 +236,16 @@ class MiscGui : public BaseMenuGui
smExit(); 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); tsl::elm::ToggleListItem* addConfigToggle(SysClkConfigValue, std::string);
void updateConfigToggle(tsl::elm::ToggleListItem*, SysClkConfigValue); 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; tsl::elm::CategoryHeader *chargingLimitHeader;
StepTrackBarIcon *chargingLimitBar; StepTrackBarIcon *chargingLimitBar;
SysClkConfigValueList* configList; SysClkConfigValueList* configList;
ChargeInfo* chargeInfo; PsmChargeInfo* chargeInfo;
I2cInfo* i2cInfo; I2cInfo* i2cInfo;
LblBacklightSwitchStatus lblstatus = LblBacklightSwitchStatus_Disabled; 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:"; const char* infoNames = "Charger:\nBattery:\nCurrent Limit:\nCharging Limit:\nRaw Charge:\nBattery Age:\nPower Role:\nCurrent Flow:\n\nCPU Volt:\nGPU Volt:\nDRAM Volt:";

View File

@@ -44,7 +44,7 @@ CFLAGS := -g -Wall -O2 -ffunction-sections \
CFLAGS += $(INCLUDE) -D__SWITCH__ CFLAGS += $(INCLUDE) -D__SWITCH__
CXXFLAGS := $(CFLAGS) -fno-rtti -std=gnu++17 CXXFLAGS := $(CFLAGS) -fno-rtti -std=gnu++20
ASFLAGS := -g $(ARCH) ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)

View File

@@ -23,6 +23,7 @@ void apmExtExit(void);
Result apmExtGetPerformanceMode(u32 *out_mode); Result apmExtGetPerformanceMode(u32 *out_mode);
Result apmExtSysRequestPerformanceMode(u32 mode); Result apmExtSysRequestPerformanceMode(u32 mode);
Result apmExtGetCurrentPerformanceConfiguration(u32 *out_conf); Result apmExtGetCurrentPerformanceConfiguration(u32 *out_conf);
bool apmExtIsBoostMode(u32 conf_id, bool allow_cpu_limited);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -64,3 +64,9 @@ Result apmExtGetCurrentPerformanceConfiguration(u32 *out_conf)
{ {
return serviceDispatchOut(&g_apmSysSrv, 7, *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);
}

View File

@@ -26,7 +26,7 @@
"value": { "value": {
"highest_thread_priority": 63, "highest_thread_priority": 63,
"lowest_thread_priority": 24, "lowest_thread_priority": 24,
"lowest_cpu_id": 3, "lowest_cpu_id": 0,
"highest_cpu_id": 3 "highest_cpu_id": 3
} }
}, },
@@ -74,6 +74,7 @@
"svcOutputDebugString": "0x27", "svcOutputDebugString": "0x27",
"svcReturnFromException": "0x28", "svcReturnFromException": "0x28",
"svcGetInfo": "0x29", "svcGetInfo": "0x29",
"svcSetThreadActivity": "0x32",
"svcWaitForAddress": "0x34", "svcWaitForAddress": "0x34",
"svcSignalToAddress": "0x35", "svcSignalToAddress": "0x35",
"svcCreateSession": "0x40", "svcCreateSession": "0x40",

View File

@@ -59,27 +59,32 @@ ClockManager::ClockManager()
this->oc = new SysClkOcExtra; this->oc = new SysClkOcExtra;
this->oc->systemCoreBoostCPU = false; this->oc->systemCoreBoostCPU = false;
this->oc->allowUnsafeFreq = false; this->oc->allowUnsafeFreq = false;
this->oc->syncReverseNXMode = false; this->oc->governor = false;
this->oc->realProfile = SysClkProfile_Handheld; this->oc->realProfile = SysClkProfile_Handheld;
this->oc->reverseNXToolMode = ReverseNX_NotFound;
this->oc->reverseNXRTMode = ReverseNX_NotFound;
this->oc->maxMEMFreq = 0; this->oc->maxMEMFreq = 0;
this->oc->boostCPUFreq = 0; this->oc->boostCPUFreq = 0;
this->rnxSync = new ReverseNXSync;
this->governor = new Governor;
} }
ClockManager::~ClockManager() ClockManager::~ClockManager()
{ {
delete this->config; delete this->governor;
delete this->context; delete this->rnxSync;
delete this->oc; delete this->oc;
delete this->context;
delete this->config;
} }
bool ClockManager::IsCpuBoostMode() bool ClockManager::IsCpuBoostMode()
{ {
std::uint32_t confId = this->context->perfConfId; std::uint32_t confId = this->context->perfConfId;
bool isCpuBoostMode = (confId == 0x92220009 || confId == 0x9222000A); bool isCpuBoostMode = apmExtIsBoostMode(confId, false);
if (isCpuBoostMode && !this->oc->boostCPUFreq) if (isCpuBoostMode && !this->oc->boostCPUFreq) {
this->oc->boostCPUFreq = std::max(this->context->freqs[SysClkModule_CPU], 1785'000'000U); this->oc->boostCPUFreq = std::max(this->context->freqs[SysClkModule_CPU], 1785'000'000U);
this->governor->SetCPUBoostHz(this->oc->boostCPUFreq);
}
return isCpuBoostMode; return isCpuBoostMode;
} }
@@ -107,7 +112,8 @@ uint32_t ClockManager::GetHz(SysClkModule module)
hz = this->config->GetAutoClockHz(SYSCLK_GLOBAL_PROFILE_TID, module, this->context->profile); hz = this->config->GetAutoClockHz(SYSCLK_GLOBAL_PROFILE_TID, module, this->context->profile);
/* Return pre-set hz */ /* Return pre-set hz */
if (!hz && this->oc->syncReverseNXMode && GetReverseNXMode()) ReverseNXMode mode;
if (!hz && (mode = this->rnxSync->GetMode()))
{ {
switch (module) switch (module)
{ {
@@ -115,12 +121,12 @@ uint32_t ClockManager::GetHz(SysClkModule module)
hz = 1020'000'000; hz = 1020'000'000;
break; break;
case SysClkModule_GPU: case SysClkModule_GPU:
hz = (GetReverseNXMode() == ReverseNX_Docked || hz = (mode == ReverseNX_Docked ||
this->oc->realProfile == SysClkProfile_Docked) ? this->oc->realProfile == SysClkProfile_Docked) ?
768'000'000 : 460'800'000; 768'000'000 : 460'800'000;
break; break;
case SysClkModule_MEM: case SysClkModule_MEM:
hz = (GetReverseNXMode() == ReverseNX_Docked || hz = (mode == ReverseNX_Docked ||
this->oc->realProfile == SysClkProfile_Docked) ? this->oc->realProfile == SysClkProfile_Docked) ?
MAX_MEM_CLOCK : 1600'000'000; MAX_MEM_CLOCK : 1600'000'000;
break; break;
@@ -172,17 +178,23 @@ void ClockManager::Tick()
{ {
uint32_t hz = GetHz((SysClkModule)module); uint32_t hz = GetHz((SysClkModule)module);
if (this->oc->governor) {
this->governor->SetMaxHz(hz, (SysClkModule)module);
continue;
}
if (hz && hz != this->context->freqs[module]) if (hz && hz != this->context->freqs[module])
{ {
// Skip setting CPU or GPU clocks in CpuBoostMode if CPU <= boostCPUFreq or GPU >= 76.8MHz // 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)) bool skipBoost = IsCpuBoostMode() && ((module == SysClkModule_CPU && hz <= this->oc->boostCPUFreq) || module == SysClkModule_GPU);
{ if (!skipBoost) {
continue; 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) */ /* 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 tickWaitTimeMs = this->GetConfig()->GetConfigValue(SysClkConfigValue_PollingIntervalMs);
uint64_t tickWaitTimeNs = tickWaitTimeMs * 1000000ULL;
bool isAutoBoostEnabled = this->GetConfig()->GetConfigValue(SysClkConfigValue_AutoCPUBoost); if (this->oc->governor) {
if ( isAutoBoostEnabled svcSleepThread(tickWaitTimeMs * 1000'000ULL);
&& this->oc->realProfile != SysClkProfile_Handheld return;
&& 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);
/* Convert idletick to free% */ bool boostOK =
/* If CPU core usage is 0%, then idletick = 19'200'000 per sec */ this->GetConfig()->GetConfigValue(SysClkConfigValue_AutoCPUBoost) &&
uint64_t systemCoreIdleTick = systemCoreIdleTickNext - systemCoreIdleTickPrev; this->context->enabled &&
uint64_t freeIdleTick = 19'200 * tickWaitTimeMs; this->oc->realProfile != SysClkProfile_Handheld &&
uint8_t freePerc = systemCoreIdleTick / (freeIdleTick / 100); 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; if (lastBoost && !this->oc->systemCoreBoostCPU)
this->oc->systemCoreBoostCPU = (freePerc <= systemCoreBoostFreeThreshold);
if (systemCoreBoostCPUPrevState && !this->oc->systemCoreBoostCPU)
{
Clocks::SetHz(SysClkModule_CPU, GetHz(SysClkModule_CPU)); 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); 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() return;
{
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 getMode; if (this->oc->systemCoreBoostCPU) {
} this->oc->systemCoreBoostCPU = false;
Clocks::SetHz(SysClkModule_CPU, GetHz(SysClkModule_CPU));
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);
}
}
} }
svcSleepThread(tickWaitTimeMs * 1000'000ULL);
delete chargeInfoField;
psmExit();
smExit();
} }
bool ClockManager::RefreshContext() 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(); 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); this->oc->allowUnsafeFreq = this->GetConfig()->GetConfigValue(SysClkConfigValue_AllowUnsafeFrequencies);
bool enabled = this->GetConfig()->Enabled(); bool enabled = this->GetConfig()->Enabled();
@@ -358,6 +256,18 @@ bool ClockManager::RefreshContext()
hasChanged = true; 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(); std::uint64_t applicationId = ProcessManagement::GetCurrentApplicationId();
if (applicationId != this->context->applicationId) if (applicationId != this->context->applicationId)
{ {
@@ -367,8 +277,7 @@ bool ClockManager::RefreshContext()
/* Clear ReverseNX state */ /* Clear ReverseNX state */
this->GetConfig()->SetReverseNXRTMode(ReverseNX_NotFound); this->GetConfig()->SetReverseNXRTMode(ReverseNX_NotFound);
this->oc->reverseNXRTMode = ReverseNX_NotFound; this->rnxSync->Reset(applicationId);
this->oc->reverseNXToolMode = GetReverseNXToolMode();
} }
SysClkProfile profile = Clocks::GetCurrentProfile(); SysClkProfile profile = Clocks::GetCurrentProfile();
@@ -388,17 +297,17 @@ bool ClockManager::RefreshContext()
if (this->context->perfConfId != confId) if (this->context->perfConfId != confId)
{ {
this->context->perfConfId = confId; this->context->perfConfId = confId;
this->governor->SetPerfConf(confId);
hasChanged = true; hasChanged = true;
} }
} }
{ {
this->oc->reverseNXRTMode = this->GetConfig()->GetReverseNXRTMode(); this->rnxSync->SetRTMode(this->GetConfig()->GetReverseNXRTMode());
SysClkProfile currentProfile = this->context->profile; SysClkProfile current = this->context->profile;
SysClkProfile expectedProfile = this->oc->syncReverseNXMode ? SysClkProfile expected = this->rnxSync->GetProfile(this->oc->realProfile);
ReverseNXProfileHandler() : this->oc->realProfile; this->context->profile = expected;
this->context->profile = expectedProfile; if (current != expected)
if (currentProfile != expectedProfile)
hasChanged = true; hasChanged = true;
} }
@@ -414,9 +323,11 @@ bool ClockManager::RefreshContext()
if (hz != 0 && hz != this->context->freqs[module]) 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; 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); hz = this->GetConfig()->GetOverrideHz((SysClkModule)module);
@@ -424,7 +335,7 @@ bool ClockManager::RefreshContext()
{ {
if(hz) 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 else
{ {

View File

@@ -17,6 +17,8 @@
#include "clocks.h" #include "clocks.h"
#include <nxExt/cpp/lockable_mutex.h> #include <nxExt/cpp/lockable_mutex.h>
#include "oc_extra.h"
class ClockManager class ClockManager
{ {
public: public:
@@ -32,64 +34,6 @@ class ClockManager
SysClkContext GetCurrentContext(); SysClkContext GetCurrentContext();
Config* GetConfig(); 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: protected:
ClockManager(); ClockManager();
virtual ~ClockManager(); virtual ~ClockManager();
@@ -105,16 +49,10 @@ class ClockManager
std::uint64_t lastCsvWriteNs; std::uint64_t lastCsvWriteNs;
SysClkOcExtra *oc; SysClkOcExtra *oc;
ReverseNXSync *rnxSync;
Governor *governor;
bool IsCpuBoostMode(); bool IsCpuBoostMode();
uint32_t GetHz(SysClkModule); uint32_t GetHz(SysClkModule);
SysClkProfile ReverseNXProfileHandler();
ReverseNXMode ReverseNXFileHandler(const char*);
ReverseNXMode GetReverseNXToolMode();
ReverseNXMode GetReverseNXMode();
void ChargingHandler();
}; };

View File

@@ -158,29 +158,49 @@ PcvModuleId Clocks::GetPcvModuleId(SysClkModule sysclkModule)
return pcvModuleId; 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) void Clocks::ResetToStock(unsigned int module)
{ {
Result rc = 0;
if(hosversionAtLeast(9,0,0)) if(hosversionAtLeast(9,0,0))
{ {
std::uint32_t confId = 0; std::uint32_t confId = 0;
rc = apmExtGetCurrentPerformanceConfiguration(&confId); Result rc = apmExtGetCurrentPerformanceConfiguration(&confId);
ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration");
SysClkApmConfiguration* apmConfiguration = NULL; SysClkApmConfiguration* apmConfiguration = GetEmbeddedApmConfig(confId);
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);
}
if (module == SysClkModule_EnumMax || module == SysClkModule_CPU) if (module == SysClkModule_EnumMax || module == SysClkModule_CPU)
{ {
@@ -197,6 +217,7 @@ void Clocks::ResetToStock(unsigned int module)
} }
else else
{ {
Result rc = 0;
std::uint32_t mode = 0; std::uint32_t mode = 0;
rc = apmExtGetPerformanceMode(&mode); rc = apmExtGetPerformanceMode(&mode);
ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode");

View File

@@ -20,6 +20,8 @@ class Clocks
public: public:
static void Exit(); static void Exit();
static void Initialize(); 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 void ResetToStock(unsigned int module = SysClkModule_EnumMax);
static SysClkProfile GetCurrentProfile(); static SysClkProfile GetCurrentProfile();
static std::uint32_t GetCurrentHz(SysClkModule module); static std::uint32_t GetCurrentHz(SysClkModule module);

View File

@@ -183,12 +183,12 @@ bool Config::SetProfiles(std::uint64_t tid, SysClkTitleProfileList* profiles, bo
uint8_t numProfiles = 0; uint8_t numProfiles = 0;
// String pointer array passed to ini // String pointer array passed to ini
char* iniKeys[SysClkProfile_EnumMax * SysClkModule_EnumMax + 1]; char* iniKeys[static_cast<int>(SysClkProfile_EnumMax) * static_cast<int>(SysClkModule_EnumMax) + 1];
char* iniValues[SysClkProfile_EnumMax * SysClkModule_EnumMax + 1]; char* iniValues[static_cast<int>(SysClkProfile_EnumMax) * static_cast<int>(SysClkModule_EnumMax) + 1];
// Char arrays to build strings // Char arrays to build strings
char keysStr[SysClkProfile_EnumMax * SysClkModule_EnumMax * 0x40]; char keysStr[static_cast<int>(SysClkProfile_EnumMax) * static_cast<int>(SysClkModule_EnumMax) * 0x40];
char valuesStr[SysClkProfile_EnumMax * SysClkModule_EnumMax * 0x10]; char valuesStr[static_cast<int>(SysClkProfile_EnumMax) * static_cast<int>(SysClkModule_EnumMax) * 0x10];
char section[17] = {0}; char section[17] = {0};
// Iteration pointers // Iteration pointers

View File

@@ -18,7 +18,6 @@ static LockableMutex g_log_mutex;
static LockableMutex g_csv_mutex; static LockableMutex g_csv_mutex;
static std::atomic_bool g_has_initialized = false; static std::atomic_bool g_has_initialized = false;
static bool g_log_enabled = false; static bool g_log_enabled = false;
static bool g_reversenx_tool_exist = false;
static std::uint64_t g_last_flag_check = 0; static std::uint64_t g_last_flag_check = 0;
extern "C" void __libnx_init_time(void); extern "C" void __libnx_init_time(void);
@@ -131,22 +130,6 @@ void FileUtils::RefreshFlags(bool force)
g_last_flag_check = now; 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() void FileUtils::InitializeAsync()
{ {
Thread initThread = {0}; Thread initThread = {0};
@@ -179,7 +162,6 @@ Result FileUtils::Initialize()
if (R_SUCCEEDED(rc)) if (R_SUCCEEDED(rc))
{ {
FileUtils::RefreshFlags(true); FileUtils::RefreshFlags(true);
FileUtils::InitCheckFlags();
g_has_initialized = true; g_has_initialized = true;
FileUtils::LogLine("=== " TARGET " " TARGET_VERSION " ==="); FileUtils::LogLine("=== " TARGET " " TARGET_VERSION " ===");
} }

View File

@@ -22,7 +22,6 @@
#define FILE_CONTEXT_CSV_PATH FILE_CONFIG_DIR "/context.csv" #define FILE_CONTEXT_CSV_PATH FILE_CONFIG_DIR "/context.csv"
#define FILE_LOG_FLAG_PATH FILE_CONFIG_DIR "/log.flag" #define FILE_LOG_FLAG_PATH FILE_CONFIG_DIR "/log.flag"
#define FILE_LOG_FILE_PATH FILE_CONFIG_DIR "/log.txt" #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 class FileUtils
{ {
@@ -37,5 +36,4 @@ class FileUtils
static void WriteContextToCsv(const SysClkContext* context); static void WriteContextToCsv(const SysClkContext* context);
protected: protected:
static void RefreshFlags(bool force); static void RefreshFlags(bool force);
static void InitCheckFlags();
}; };

View File

@@ -20,6 +20,7 @@
#include "process_management.h" #include "process_management.h"
#include "clock_manager.h" #include "clock_manager.h"
#include "ipc_service.h" #include "ipc_service.h"
#include "oc_extra.h"
#define INNER_HEAP_SIZE 0x30000 #define INNER_HEAP_SIZE 0x30000
@@ -27,13 +28,18 @@ extern "C"
{ {
extern std::uint32_t __start__; 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; TimeServiceType __nx_time_service_type = TimeServiceType_System;
std::uint32_t __nx_fs_num_sessions = 1; std::uint32_t __nx_fs_num_sessions = 1;
size_t nx_inner_heap_size = INNER_HEAP_SIZE; size_t nx_inner_heap_size = INNER_HEAP_SIZE;
char nx_inner_heap[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 __libnx_initheap(void)
{ {
void *addr = nx_inner_heap; void *addr = nx_inner_heap;

View File

@@ -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<void*>(context), 0, sizeof(s_CoreContext));
context->self = self;
context->id = id;
return context;
}
void Governor::CheckCpuUtilWorker(void* args) {
s_CoreContext* s = static_cast<s_CoreContext*>(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<uint64_t> 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<uint64_t> 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<Governor*>(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);
}
}

View File

@@ -0,0 +1,215 @@
#pragma once
#include <atomic>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stack>
#include <nxExt.h>
#include <sysclk.h>
#include <switch.h>
#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<void*>(&m_cpu_freq), 0, sizeof(m_cpu_freq));
memset(reinterpret_cast<void*>(&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<uint64_t> 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 <typename T>
struct s_Queue {
// Much faster than <queue> 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);
};

View File

@@ -107,11 +107,12 @@
polling_interval_sec_min = u32!0x7FFFFFFF polling_interval_sec_min = u32!0x7FFFFFFF
``` ```
- FPS and bitrate of game recording and streaming - Parameters of game recording and streaming
- ```ini - ```ini
[am.debug] [am.debug]
continuous_recording_fps = u32!60 ; 30 or 60 FPS 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 is ~5Mbps, VBR(Variable Bitrate) 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). - 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. - For optimal streaming experience, SysDVR via USB interface is recommended.