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:
@@ -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/`.
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
79
Source/sys-clk-OC/common/include/sysclk/psm_ext.h
Normal file
79
Source/sys-clk-OC/common/include/sysclk/psm_ext.h
Normal 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);
|
||||
55
Source/sys-clk-OC/common/src/psm_ext.c
Normal file
55
Source/sys-clk-OC/common/src/psm_ext.c
Normal 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 "?";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:";
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 " ===");
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
368
Source/sys-clk-OC/sysmodule/src/oc_extra.cpp
Normal file
368
Source/sys-clk-OC/sysmodule/src/oc_extra.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
215
Source/sys-clk-OC/sysmodule/src/oc_extra.h
Normal file
215
Source/sys-clk-OC/sysmodule/src/oc_extra.h
Normal 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);
|
||||
};
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user