Add soc uncap

This commit is contained in:
Lightos1
2026-04-25 20:22:23 +02:00
parent 9d149f0939
commit d7178ddd2c
8 changed files with 365 additions and 33 deletions

View File

@@ -0,0 +1,88 @@
{
"name": "Loader",
"title_id": "0x0100000000000001",
"main_thread_stack_size": "0x4000",
"main_thread_priority": 49,
"default_cpu_id": 3,
"process_category": 1,
"use_secure_memory": true,
"immortal": true,
"kernel_capabilities": [
{
"type": "handle_table_size",
"value": 128
},
{
"type": "syscalls",
"value": {
"svcSetHeapSize" : "0x01",
"svcSetMemoryPermission" : "0x02",
"svcSetMemoryAttribute" : "0x03",
"svcMapMemory" : "0x04",
"svcUnmapMemory" : "0x05",
"svcQueryMemory" : "0x06",
"svcExitProcess" : "0x07",
"svcCreateThread" : "0x08",
"svcStartThread" : "0x09",
"svcExitThread" : "0x0A",
"svcSleepThread" : "0x0B",
"svcGetThreadPriority" : "0x0C",
"svcSetThreadPriority" : "0x0D",
"svcGetThreadCoreMask" : "0x0E",
"svcSetThreadCoreMask" : "0x0F",
"svcGetCurrentProcessorNumber" : "0x10",
"svcSignalEvent" : "0x11",
"svcClearEvent" : "0x12",
"svcMapSharedMemory" : "0x13",
"svcUnmapSharedMemory" : "0x14",
"svcCreateTransferMemory" : "0x15",
"svcCloseHandle" : "0x16",
"svcResetSignal" : "0x17",
"svcWaitSynchronization" : "0x18",
"svcCancelSynchronization" : "0x19",
"svcArbitrateLock" : "0x1A",
"svcArbitrateUnlock" : "0x1B",
"svcWaitProcessWideKeyAtomic" : "0x1C",
"svcSignalProcessWideKey" : "0x1D",
"svcGetSystemTick" : "0x1E",
"svcConnectToNamedPort" : "0x1F",
"svcSendSyncRequestLight" : "0x20",
"svcSendSyncRequest" : "0x21",
"svcSendSyncRequestWithUserBuffer" : "0x22",
"svcSendAsyncRequestWithUserBuffer" : "0x23",
"svcGetProcessId" : "0x24",
"svcGetThreadId" : "0x25",
"svcBreak" : "0x26",
"svcOutputDebugString" : "0x27",
"svcReturnFromException" : "0x28",
"svcGetInfo" : "0x29",
"svcWaitForAddress" : "0x34",
"svcSignalToAddress" : "0x35",
"svcSynchronizePreemptionState" : "0x36",
"svcCreateSession" : "0x40",
"svcAcceptSession" : "0x41",
"svcReplyAndReceiveLight" : "0x42",
"svcReplyAndReceive" : "0x43",
"svcReplyAndReceiveWithUserBuffer" : "0x44",
"svcCreateEvent" : "0x45",
"svcReadWriteRegister" : "0x4E",
"svcQueryIoMapping" : "0x55",
"svcSetProcessMemoryPermission" : "0x73",
"svcMapProcessMemory" : "0x74",
"svcUnmapProcessMemory" : "0x75",
"svcMapProcessCodeMemory" : "0x77",
"svcUnmapProcessCodeMemory" : "0x78",
"svcCreateProcess" : "0x79",
"svcCallSecureMonitor" : "0x7F"
}
}, {
"type": "map",
"value": {
"address": "0x7000F000",
"is_ro": false,
"size": "0x00001000",
"is_io": true
}
}
]
}

View File

@@ -46,7 +46,9 @@ volatile CustomizeTable C = {
.marikoEmcMaxClock = 2133000, /* 1866MHz @ 1866tWRL is guaranteed to work on all Mariko units */
.marikoEmcVddqVolt = 600000,
.emcDvbShift = 0,
.marikoSocVmax = 0, /* 0 = stock limits. */
// Primary
.t1_tRCD = 0,
@@ -65,21 +67,21 @@ volatile CustomizeTable C = {
/* Frequency where non low timings gets used. */
.timingEmcTbreak = DISABLED,
.low_t6_tRTW = DISABLED,
.low_t7_tWTR = DISABLED,
.low_t6_tRTW = 0,
.low_t7_tWTR = 0,
.readLatency = {
DISABLED,
DISABLED,
DISABLED,
DISABLED,
/* 1333 */ 0,
/* 1600 */ 0,
/* 1866 */ 0,
/* 2133 */ 0,
},
.writeLatency = {
DISABLED,
DISABLED,
DISABLED,
DISABLED,
/* 1333 */ 0,
/* 1600 */ 0,
/* 1866 */ 0,
/* 2133 */ 0,
},
/* You can mix and match different latencies if needed */

View File

@@ -93,6 +93,7 @@ typedef struct CustomizeTable {
u32 marikoEmcMaxClock;
u32 marikoEmcVddqVolt;
u32 emcDvbShift;
u32 marikoSocVmax;
// advanced config
u32 t1_tRCD;
u32 t2_tRP;

View File

@@ -52,6 +52,8 @@ namespace ams::ldr {
R_DEFINE_ERROR_RESULT(UnsuccessfulPatcher, 1014);
R_DEFINE_ERROR_RESULT(SafetyCheckFailure, 1015);
R_DEFINE_ERROR_RESULT(InvalidMtcTablePattern, 1016);
R_DEFINE_ERROR_RESULT(InvalidSocVoltPattern, 1017);
R_DEFINE_ERROR_RESULT(InvalidSocVoltLimit, 1018);
}
namespace ams::ldr::hoc {

View File

@@ -154,6 +154,7 @@ namespace ams::ldr::hoc::pcv {
{ GET_MAX_OF_ARR(erista::maxEmcClocks), 1600'000, 2600'000, false, panic::Emc },
{ C.marikoEmcMaxClock, 1600'000, 3500'000, false, panic::Emc },
{ C.marikoEmcVddqVolt, 250'000, 700'000, false, panic::Emc },
{ C.marikoSocVmax, 1000, 1200, false, panic::Emc },
{ eristaGpuDvfsMaxFreq, 768'000, 1152'000, false, panic::Gpu },
{ marikoGpuDvfsMaxFreq, 768'000, 1536'000, false, panic::Gpu },
{ C.marikoGpuVmax, 800, 960, false, panic::Gpu },

View File

@@ -26,6 +26,16 @@ namespace ams::ldr::hoc::pcv {
constexpr u32 NopIns = 0x1f2003d5;
template <typename Compare>
u32 *ScanAssembly(u32 *ptr, u32 scanLimit, u32 pattern, Compare comp) {
for (u32 i = 0; i < scanLimit; ++i) {
if (comp(pattern, ptr[i])) {
return ptr + i;
}
}
return nullptr;
}
inline auto asm_compare_no_rd = [](u32 ins1, u32 ins2) {
return ((ins1 ^ ins2) >> 5) == 0;
};
@@ -61,4 +71,75 @@ namespace ams::ldr::hoc::pcv {
return ((ins1 & ImmMask) ^ (ins2 & ImmMask)) == 0;
};
/* Csel (Conditional Select) */
/*
SF | Op | S | | RM | Cond | 0 | 0 | Rn | Rd
31 | 30 | 29 | 28 27 26 25 24 23 | 20 19 18 17 16 | 15 14 13 12 | 11 | 10 | 9 8 7 6 5 | 4 3 2 1 0
*/
inline auto AsmCompareCselNoReg = [](u32 ins1, u32 ins2) {
constexpr u32 ClearReg = ~(((1 << 10) - 1) | (((1 << 5) - 1) << 16));
return ((ins1 & ClearReg) ^ (ins2 & ClearReg)) == 0;
};
/* Mul */
/*
SF | Op54 | Op31 | RM | o0 | RA | RN | RD
31 | 30 29 28 27 26 25 24 | 23 22 21 | 20 19 18 17 16 | 15 | 14 13 12 11 10 | 9 8 7 6 5 | 4 3 2 1 0
*/
inline auto AsmCompareMullNoReg = [](u32 ins1, u32 ins2) {
constexpr u32 ClearReg = ~(((1 << 10) - 1) | (((1 << 5) - 1) << 16));
return ((ins1 & ClearReg) ^ (ins2 & ClearReg)) == 0;
};
/* Mul */
/* MUL W11, W24, W26 */
/* multiplies by 1000, mV -> uV */
/*
SF | Op54 | Op31 | RM | o0 | RA | RN | RD
31 | 30 29 28 27 26 25 24 | 23 22 21 | 20 19 18 17 16 | 15 | 14 13 12 11 10 | 9 8 7 6 5 | 4 3 2 1 0
*/
inline auto AsmGetMullRn = [](u32 ins) {
constexpr u32 Mask = ((1 << 5) - 1) << 5;
return (ins & Mask) >> 5;
};
inline auto AsmGetMullRm = [](u32 ins) {
constexpr u32 Mask = ((1 << 5) - 1) << 16;
return (ins & Mask) >> 16;
};
/* Subs (Shifted register) */
/*
SF | Op | S | | Shift | 0 | RM | Imm6 | Rn | Rd
31 | 30 | 29 | 28 27 26 25 24 | 23 22 | 21 | 20 19 18 17 16 | 15 14 13 12 11 10 | 9 8 7 6 5 | 4 3 2 1 0
*/
inline auto AsmSubsSetRn = [](u32 ins, u8 rn) {
constexpr u32 RnMaskClear = ~(((1u << 5) - 1u) << 5);
constexpr u32 RnMaskSet = (1u << 5) - 1u;
return (ins & RnMaskClear) | ((static_cast<u32>(rn) & RnMaskSet) << 5);
};
/* Subs (Immediate) */
/*
SF | Op | S | | Sh | Imm12 | Rn | Rd
31 | 30 | 29 | 28 27 26 25 24 23 | 22 | 21 20 19 18 17 16 15 14 13 12 11 10 | 9 8 7 6 5 | 4 3 2 1 0
*/
inline auto AsmSubsSetImm12 = [](u32 ins, u16 imm12) {
constexpr u32 ClearMask = ~(((1u << 12) - 1) << 10);
constexpr u32 SetImm12Mask = ( 1u << 12) - 1;
return (ins & ClearMask) | ((imm12 & SetImm12Mask) << 10);
};
inline auto AsmSubsCompareNoReg = [](u32 ins1, u32 ins2) {
return ((ins1 ^ ins2) >> 10) == 0;
};
inline auto AsmCompareBrConNoImm19 = [](u32 ins1, u32 ins2) {
constexpr u32 ClearImm19 = ~(((1 << 19) - 1) << 5);
return (ins1 & ClearImm19) == (ins2 & ClearImm19);
};
}

View File

@@ -279,12 +279,6 @@ namespace ams::ldr::hoc::pcv::mariko {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
/* Verify the limit. */
/* TODO: Make this a little bit cleaner at some point. */
if (AsmGetImm16(ins1) != (GpuClkOsLimit & 0xFFFF) || AsmGetImm16(ins2) != (GpuClkOsLimit >> 16)) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
u32 max_clock;
switch (C.marikoGpuUV) {
case 0:
@@ -782,21 +776,43 @@ namespace ams::ldr::hoc::pcv::mariko {
R_SKIP();
}
u32 max0 = 1050;
u32 max1 = 1025;
u32 max2 = 1000;
s32 voltAdd = 25 * C.emcDvbShift;
#define DVB_VOLT(zero, one, two) std::min(zero + voltAdd, 1050), std::min(one + voltAdd, 1025), std::min(two + voltAdd, 1000),
DvbEntry emcDvbTableNew[] = {
{ 204000, { 637, 637, 637, } },
{ 1331200, { 650, 637, 637, } },
{ 1600000, { 675, 650, 637, } },
{ 1866000, { DVB_VOLT(700, 675, 650) } },
{ 2133000, { DVB_VOLT(725, 700, 675) } },
{ 2400000, { DVB_VOLT(750, 725, 700) } },
{ 2666000, { DVB_VOLT(775, 750, 725) } },
{ 2933000, { DVB_VOLT(800, 775, 750) } },
{ 3200000, { DVB_VOLT(800, 800, 775) } },
{ 0xFFFFFFFF, { } },
if (C.marikoSocVmax && C.marikoSocVmax > 1000) {
max0 = C.marikoSocVmax;
max1 = C.marikoSocVmax;
max2 = C.marikoSocVmax;
}
auto DvbVolt = [&](u32 zero, u32 one, u32 two) {
return std::array<u32, 3>{
std::min(zero + voltAdd, max0),
std::min(one + voltAdd, max1),
std::min(two + voltAdd, max2)
};
};
#define DVB(v) \
static_cast<u32>((v)[0]), \
static_cast<u32>((v)[1]), \
static_cast<u32>((v)[2])
DvbEntry emcDvbTableNew[] = {
{ 204000, { 637, 637, 637, } },
{ 1331200, { 650, 637, 637, } },
{ 1600000, { 675, 650, 637, } },
{ 1866000, { DVB(DvbVolt(700, 675, 650)) } },
{ 2133000, { DVB(DvbVolt(725, 700, 675)) } },
{ 2400000, { DVB(DvbVolt(750, 725, 700)) } },
{ 2666000, { DVB(DvbVolt(775, 750, 725)) } },
{ 2933000, { DVB(DvbVolt(800, 775, 750)) } },
{ 3200000, { DVB(DvbVolt(800, 800, 775)) } },
{ 0xFFFFFFFF, { } },
};
#undef DVB
u32 j = MtcTableCountDefault;
for (u32 i = MtcTableCountDefault; i < newEmcList.size(); ++i) {
if (newEmcList[i] >= emcDvbTableNew[j].freq && newEmcList[i] < emcDvbTableNew[j + 1].freq) {
@@ -914,6 +930,124 @@ namespace ams::ldr::hoc::pcv::mariko {
R_SUCCEED();
}
Result GetSocSpeedo(u32 &socSpeedo) {
constexpr u64 FusePhysicalAddress = 0x7000F000;
u64 virtualAddress = 0;
constexpr u64 Size = 0x1000;
u64 outSize;
/* TODO: use svc::QueryMemoryMapping instead. */
R_TRY(svcQueryMemoryMapping(&virtualAddress, &outSize, FusePhysicalAddress, Size));
constexpr u32 FuseOffset = 2048;
constexpr u32 SocSpeedoOffset = 308;
socSpeedo = *reinterpret_cast<u32 *>(virtualAddress + FuseOffset + SocSpeedoOffset);
R_SUCCEED();
}
u32 GetSocProcessId(u32 socSpeedo) {
if (socSpeedo <= 1597) {
return 0;
}
if (socSpeedo <= 1708) {
return 1;
}
/* >= 1709. */
return 2;
}
Result SocVoltAsm(u32 *compareSpeedos) {
constexpr u32 VoltageScanLimit = 10;
/* Might actually be speedo id. */
u32 *writeProcessId = ScanAssembly(compareSpeedos, VoltageScanLimit, SocVoltWriteProcessIdAsm, asm_compare_no_rd);
R_UNLESS(writeProcessId != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 writeProcessIdRd = asm_get_rd(*writeProcessId);
/* This writes 1050mV. */
u32 *writeVoltage = ScanAssembly(writeProcessId, VoltageScanLimit, SocVoltWriteVoltageAsm, asm_compare_no_rd);
R_UNLESS(writeVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 writeVoltageRd = asm_get_rd(*writeVoltage);
/* A csel instruction is used to select the soc voltage limit register. */
/* We care about its destination register since that is used for verification. */
constexpr u32 VoltageSelectScanLimit = 24;
u32 *selectVoltage = ScanAssembly(writeVoltage, VoltageSelectScanLimit, SocVoltSelectRegisterAsm, AsmCompareCselNoReg);
R_UNLESS(selectVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
/* Todo: check rm and rn? */
u8 selectVoltageRd = asm_get_rd(*selectVoltage);
/* rdCsel is then multiplied by 1000 to convert to uV. */
/* This is pretty far down the function. */
constexpr u32 MultiplierScanLimit = 200;
u32 *multiplier = ScanAssembly(selectVoltage, MultiplierScanLimit, SocVoltMultiplyVoltsAsm, AsmCompareMullNoReg);
R_UNLESS(multiplier != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 multiplierRn = AsmGetMullRn(*multiplier);
u8 multiplierRm = AsmGetMullRm(*multiplier);
/* One of the two registers has to be rdCsel. */
R_UNLESS((multiplierRn == selectVoltageRd) || (multiplierRm == selectVoltageRd), ldr::ResultInvalidSocVoltPattern());
u8 multiplierRd = asm_get_rd(*multiplier);
/* Subs instruction is then used to verify against absolute limit. */
u32 limitValidationPattern = AsmSubsSetRn(SocVoltValidateLimitAsm, multiplierRd);
u32 *limitValidation = ScanAssembly(multiplier, VoltageScanLimit, limitValidationPattern, AsmSubsCompareNoReg);
R_UNLESS(limitValidation != nullptr, ldr::ResultInvalidSocVoltPattern());
/* There is a b.gt instruction right after (checks for socVoltageCap < socVoltageMax). */
u32 *branchToAbort = limitValidation + 1;
R_UNLESS(AsmCompareBrConNoImm19(*branchToAbort, SocVoltBranchToAbortAsm), ldr::ResultInvalidSocVoltPattern());
if (!C.marikoSocVmax || C.marikoSocVmax <= 1000) {
R_SKIP();
}
/* Adjust 1598 speedo minimum to ensure it always goes down process id 0 branch. */
/* 2200 should be high enough :D */
u32 compareSpeedosPatch = AsmSubsSetImm12(*compareSpeedos, 2200);
PATCH_OFFSET(compareSpeedos, compareSpeedosPatch);
u32 socSpeedo = 0;
R_TRY(GetSocSpeedo(socSpeedo));
/* Adjust processId from 0 to [process id of switch booting this]. */
/* We're overwriting the orr instruction entirly. */
u32 processId = GetSocProcessId(socSpeedo);
u32 writeProcessIdPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, processId), writeProcessIdRd);
PATCH_OFFSET(writeProcessId, writeProcessIdPatch);
/* Adjust voltage limit. */
u32 voltageLimitPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, C.marikoSocVmax), writeVoltageRd);
PATCH_OFFSET(writeVoltage, voltageLimitPatch);
/* Branches to an abort if limits are invalid -- we patch the branch instruction with NOP. */
PATCH_OFFSET(branchToAbort, NopIns);
R_SUCCEED();
}
Result SocVoltLimit(u32 *ptr) {
R_UNLESS(!std::memcmp(ptr - SocVoltLimitMaxDefaultIndex, socVoltLimitArray, sizeof(socVoltLimitArray)), ldr::ResultInvalidSocVoltLimit());
if (!C.marikoSocVmax || C.marikoSocVmax <= SocVoltLimitOfficial) {
R_SKIP();
}
constexpr u32 Step = 25;
u32 maxVolt = C.marikoSocVmax;
if (maxVolt % Step) {
maxVolt = maxVolt / Step * Step; /* Round. */
}
u32 volt = SocVoltLimitOfficial;
for (u32 i = 1; i < DvfsTableEntryCount - SocVoltLimitMaxDefaultIndex && volt < maxVolt; ++i) {
volt += Step;
PATCH_OFFSET(ptr + i, volt);
}
R_SUCCEED();
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
nsoStart = reinterpret_cast<u32 *>(mapped_nso);
MtcGenerateFreqTables();
@@ -932,13 +1066,15 @@ namespace ams::ldr::hoc::pcv::mariko {
{ "GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn },
{ "GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax },
{ "GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit },
{ "MEM Freq Mtc", &MemFreqMtcTable, 0, nullptr, EmcClkOSLimit },
{ "MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit },
{ "MEM Freq Dvb", &MemFreqDvbTable, 1, nullptr, EmcClkOSLimit },
{ "MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit },
{ "MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit },
{ "MEM Vddq", &EmcVddqVolt, 2, nullptr, EmcVddqDefault },
{ "MEM Vdd2", &MemVoltHandler, 2, nullptr, MemVdd2Default },
{ "Mem Table Asm", &MemMtcTableAsm, 0, &MemMtcGetGetTablePatternFn },
{ "MEM Table Asm", &MemMtcTableAsm, 1, &MemMtcGetGetTablePatternFn },
{ "SOC Volt Asm", &SocVoltAsm, 1, &SocVoltPatternFn },
{ "SOC Volt Limit", &SocVoltLimit, 1, nullptr, SocVoltLimitOfficial },
};
for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(MarikoMtcTable); ptr += sizeof(u32)) {

View File

@@ -112,7 +112,7 @@ namespace ams::ldr::hoc::pcv::mariko {
struct DvbEntry {
u64 freq;
s32 volt[4] = {};
u32 volt[4] = {};
};
constexpr DvbEntry EmcDvbTableDefault[] = {
@@ -124,6 +124,27 @@ namespace ams::ldr::hoc::pcv::mariko {
{ 1600000, { 675, 650, 637, } },
};
/* Movz */
/*
SF | OPC | HW | Imm16 | RD
31 | 30 29 28 27 26 25 24 23 | 22 21 | 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 | 4 3 2 1 0
*/
constexpr u32 SocVoltCompareSpeedoAsm = 0x7118FAFF; /* subs imm, compares to >=1598 max speedo and then goes down process id 1 route. */
constexpr u32 SocVoltWriteProcessIdAsm = 0x2A1F03F4; /* orr, writes id 0. */
constexpr u32 SocVoltWriteVoltageAsm = 0x52808358; /* Movz imm, writes 1050mV. */
constexpr u32 SocVoltSelectRegisterAsm = 0x1A9A3118; /* Csel, selects the voltage -- we need the register of this. */
constexpr u32 SocVoltMultiplyVoltsAsm = 0x1B1A7F0B; /* Mul, converts from mV -> uV */
constexpr u32 SocVoltValidateLimitAsm = 0x6B0A017F; /* Subs, checks limits */
constexpr u32 SocVoltBranchToAbortAsm = 0x540020AC; /* B.ge Branches to abort if limits are invalid. */
ALWAYS_INLINE bool SocVoltPatternFn(u32 *ptr) {
return asm_compare_no_rd(*ptr, SocVoltCompareSpeedoAsm);
}
constexpr u32 SocVoltLimitOfficial = 1050;
constexpr u32 SocVoltLimitMaxDefaultIndex = 17;
static const u32 socVoltLimitArray[DvfsTableEntryCount] = { 637, 650, 675, 700, 725, 750, 775, 800, 825, 850, 875, 900, 925, 950, 975, 1000, 1025, 1050, };
constexpr u32 EmcListDefault[] = { 204000, 1331200, 1600000, };
constexpr u32 EmcListSizeDefault = std::size(EmcListDefault);
constexpr u32 EmcListEndDefault = EmcListSizeDefault - 1;
@@ -260,8 +281,8 @@ namespace ams::ldr::hoc::pcv::mariko {
/* Adrp */
/*
OP | ImmLow | ImmHigh | RD
31 | 30 29 28 27 26 25 24 | 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 | 4 3 2 1 0
OP | ImmLow | | ImmHigh | RD
31 | 30 29 | 28 27 26 25 24 | 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 | 4 3 2 1 0
*/
/* ADD (immediate) */