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).
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/`.

View File

@@ -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<MarikoMtcTable *>(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<MarikoMtcTable *>(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<cpu_freq_cvb_table_t *>(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<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);
if (replace_entire_table) {
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:
| 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 |

View File

@@ -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
}

View File

@@ -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;

View File

@@ -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);

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];
if (freq == 1862400000)
snprintf(clock, sizeof(clock), "Max MHz");
snprintf(clock, sizeof(clock), "Maximum");
else
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)
{
return "Max MHz";
return "Maximum";
}
char buf[10];

View File

@@ -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);

View File

@@ -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:";

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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);
}

View File

@@ -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",

View File

@@ -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
{

View File

@@ -17,6 +17,8 @@
#include "clocks.h"
#include <nxExt/cpp/lockable_mutex.h>
#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();
};

View File

@@ -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");

View File

@@ -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);

View File

@@ -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<int>(SysClkProfile_EnumMax) * static_cast<int>(SysClkModule_EnumMax) + 1];
char* iniValues[static_cast<int>(SysClkProfile_EnumMax) * static_cast<int>(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<int>(SysClkProfile_EnumMax) * static_cast<int>(SysClkModule_EnumMax) * 0x40];
char valuesStr[static_cast<int>(SysClkProfile_EnumMax) * static_cast<int>(SysClkModule_EnumMax) * 0x10];
char section[17] = {0};
// Iteration pointers

View File

@@ -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 " ===");
}

View File

@@ -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();
};

View File

@@ -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;

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
```
- 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.