HOC 3.0.0 part 1

- version change to athena (3.0.0)
- cust rev 7
- build script change
- EMC 64LUT
This commit is contained in:
souldbminersmwc
2026-07-22 20:20:57 -04:00
parent d9906a794c
commit 5bb89bfc54
24 changed files with 544 additions and 77 deletions

View File

@@ -798,6 +798,11 @@ namespace ams::ldr {
}
Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) {
/* Append extra .bss for 64LUT */
if (g_is_pcv && ctx.main_nso_idx >= 0) {
g_nso_headers[ctx.main_nso_idx].bss_size += static_cast<u32>(hoc::HocPcvScratchSize);
}
/* Get CreateProcessParameter. */
svc::CreateProcessParameter param;
R_TRY(GetCreateProcessParameter(std::addressof(param), meta, flags, resource_limit));

View File

@@ -38,11 +38,11 @@ volatile CustomizeTable C = {
.commonEmcMemVolt = 1175000, /* LPDDR4(X) JEDEC Specification */
.eristaEmcMaxClock = 1600000, /* Maximum HB-MGCH ram rating */
/* Available: 66MHz step rate, 100MHz step rate, 133MHz step rate and jedec. */
/* Available: 33MHz step rate, 66MHz step rate, 100MHz step rate, 133MHz step rate and jedec. */
/* Jedec freqs are 1333MHz, 1600MHz, 1866MHz, 2133MHz, 2400MHz, 2666MHz, 2933MHz, 3200MHz. */
.stepMode = StepMode_66MHz,
.stepMode = StepMode_33MHz,
.marikoEmcMaxClock = 2133000, /* 1866MHz @ 1866tWRL is guaranteed to work on all Mariko units */
.marikoEmcMaxClock = 2666000, /* Requires the EMC DVFS 63-entry patches (EMC DVFS Count / EMC SoC LUT). */
.marikoEmcVddqVolt = 600000,
.emcDvbShift = 0,

View File

@@ -20,8 +20,8 @@
#pragma once
#define CUST_REV 6
#define KIP_VERSION 250
#define CUST_REV 7
#define KIP_VERSION 300
#include "oc_common.hpp"
#include "pcv/pcv_common.hpp"
@@ -40,6 +40,7 @@ enum StepMode: u32 {
StepMode_100MHz = 1,
StepMode_Jedec = 2,
StepMode_133MHz = 3,
StepMode_33MHz = 4,
};
enum ReadLatency: u32 {

View File

@@ -50,9 +50,15 @@ namespace ams::ldr {
R_DEFINE_ERROR_RESULT(InvalidMtcTablePattern, 1016);
R_DEFINE_ERROR_RESULT(InvalidSocVoltPattern, 1017);
R_DEFINE_ERROR_RESULT(InvalidSocVoltLimit, 1018);
R_DEFINE_ERROR_RESULT(InvalidEmcDvfsCount, 1019);
R_DEFINE_ERROR_RESULT(InvalidEmcSocLut, 1020);
R_DEFINE_ERROR_RESULT(InvalidEmcRateList, 1021);
}
namespace ams::ldr::hoc {
/* Extra pcv .bss for 64LUT */
constexpr size_t HocPcvScratchSize = 0x1000;
template<typename Pointer>
struct PatcherEntry {
using patternFn = bool(*)(Pointer *ptr);

View File

@@ -27,8 +27,8 @@ namespace ams::ldr::hoc::pcv {
R_UNLESS(entry->freq == entry->vco_max, ldr::ResultInvalidMemPllmEntry());
// Double the max clk simply
u32 max_clk = entry->freq * 2;
entry->freq = max_clk;
u32 max_clk = entry->freq * 2;
entry->freq = max_clk;
entry->vco_max = max_clk;
R_SUCCEED();
}
@@ -41,14 +41,14 @@ namespace ams::ldr::hoc::pcv {
};
constexpr u32 uv_step = 12'500;
constexpr u32 uv_min = 600'000;
constexpr u32 uv_min = 600'000;
auto validator = [](regulator* entry) {
R_UNLESS(entry->id == 1, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type == 1, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_1.volt_reg == 0x17, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_1.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_1.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry());
auto validator = [](regulator *entry) {
R_UNLESS(entry->id == 1, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type == 1, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_1.volt_reg == 0x17, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_1.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_1.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry());
R_SUCCEED();
};
@@ -67,7 +67,7 @@ namespace ams::ldr::hoc::pcv {
}
if (emc_uv % uv_step) {
emc_uv = emc_uv / uv_step * uv_step; // rounding
emc_uv = emc_uv / uv_step * uv_step; // rounding
}
PATCH_OFFSET(ptr, emc_uv);
@@ -102,11 +102,11 @@ namespace ams::ldr::hoc::pcv {
u32 eristaCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.eristaCpuDvfsTable)->freq);
u32 marikoCpuDvfsMaxFreq;
if (C.marikoCpuUVHigh) {
marikoCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoCpuDvfsTableSLT)->freq);
} else {
marikoCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq);
}
if (C.marikoCpuUVHigh) {
marikoCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoCpuDvfsTableSLT)->freq);
} else {
marikoCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq);
}
u32 eristaGpuDvfsMaxFreq;
switch (C.eristaGpuUV) {
case 0:
@@ -146,20 +146,80 @@ namespace ams::ldr::hoc::pcv {
}
sValidator validators[] = {
{ C.eristaCpuBoostClock, 1020'000, 2397'000, panic::Cpu, true },
{ C.marikoCpuBoostClock, 1020'000, 2703'000, panic::Cpu, true },
{ C.eristaCpuMaxVolt, 1000, 1260, panic::Cpu, },
{ C.marikoCpuMaxVolt, 1000, 1200, panic::Cpu, },
{ eristaCpuDvfsMaxFreq, 1785'000, 2397'000, panic::Cpu, },
{ marikoCpuDvfsMaxFreq, 1785'000, 2703'000, panic::Cpu, },
{ C.commonEmcMemVolt, 912'500, 1350'000, panic::Emc, }, /* Official vmax for the RAMs is 1400-1500mV */
{ C.eristaEmcMaxClock, 1600'000, 2600'000, panic::Emc, },
{ C.marikoEmcMaxClock, 1600'000, 3500'000, panic::Emc, },
{ C.marikoEmcVddqVolt, 400'000, 750'000, panic::Emc, },
{ C.marikoSocVmax, 1000, 1200, panic::Emc, },
{ eristaGpuDvfsMaxFreq, 768'000, 1152'000, panic::Gpu, },
{ marikoGpuDvfsMaxFreq, 768'000, 1536'000, panic::Gpu, },
{ C.marikoGpuVmax, 800, 960, panic::Gpu, },
{ C.eristaCpuBoostClock, 1020'000, 2397'000, panic::Cpu, true },
{ C.marikoCpuBoostClock, 1020'000, 2703'000, panic::Cpu, true },
{
C.eristaCpuMaxVolt,
1000,
1260,
panic::Cpu,
},
{
C.marikoCpuMaxVolt,
1000,
1200,
panic::Cpu,
},
{
eristaCpuDvfsMaxFreq,
1785'000,
2397'000,
panic::Cpu,
},
{
marikoCpuDvfsMaxFreq,
1785'000,
2703'000,
panic::Cpu,
},
{
C.commonEmcMemVolt,
912'500,
1350'000,
panic::Emc,
}, /* Official vmax for the RAMs is 1400-1500mV */
{
C.eristaEmcMaxClock,
1600'000,
2600'000,
panic::Emc,
},
{
C.marikoEmcMaxClock,
1600'000,
3500'000,
panic::Emc,
},
{
C.marikoEmcVddqVolt,
400'000,
750'000,
panic::Emc,
},
{
C.marikoSocVmax,
1000,
1200,
panic::Emc,
},
{
eristaGpuDvfsMaxFreq,
768'000,
1152'000,
panic::Gpu,
},
{
marikoGpuDvfsMaxFreq,
768'000,
1536'000,
panic::Gpu,
},
{
C.marikoGpuVmax,
800,
960,
panic::Gpu,
},
};
for (auto &v : validators) {
@@ -171,7 +231,7 @@ namespace ams::ldr::hoc::pcv {
}
void WriteKipLoadToIram() {
const u32 hocMagic = 0x686F634D;
const u32 hocMagic = 0x686F634D;
constexpr uintptr_t LoadMagicAddress = 0x4003DC00; /* Should be a pretty safe address. */
R_DISCARD(SmcCopyToIram(LoadMagicAddress, &hocMagic, sizeof(hocMagic)));
}
@@ -189,4 +249,4 @@ namespace ams::ldr::hoc::pcv {
WriteKipLoadToIram();
}
}
} // namespace ams::ldr::hoc::pcv

View File

@@ -36,8 +36,7 @@ namespace ams::ldr::hoc::pcv {
return true;
};
template <bool isMariko>
Result CpuFreqCvbTable(u32 *ptr) {
template <bool isMariko> Result CpuFreqCvbTable(u32 *ptr) {
cvb_entry_t *default_table = isMariko ? (cvb_entry_t *)(&mariko::CpuCvbTableDefault) : (cvb_entry_t *)(&erista::CpuCvbTableDefault);
cvb_entry_t *customize_table = nullptr;
@@ -112,8 +111,7 @@ namespace ams::ldr::hoc::pcv {
PATCH_OFFSET(&(entry->cvb_pll_param.c5), 0);
}
template <bool isMariko>
Result GpuFreqCvbTable(u32 *ptr) {
template <bool isMariko> Result GpuFreqCvbTable(u32 *ptr) {
cvb_entry_t *default_table = isMariko ? (cvb_entry_t *)(&mariko::GpuCvbTableDefault) : (cvb_entry_t *)(&erista::GpuCvbTableDefault);
cvb_entry_t *customize_table;
if (isMariko) {
@@ -136,7 +134,7 @@ namespace ams::ldr::hoc::pcv {
default:
customize_table = const_cast<cvb_entry_t *>(C.marikoGpuDvfsTableHiOPT);
break;
}
}
} else {
switch (C.eristaGpuUV) {
case 0:
@@ -151,7 +149,7 @@ namespace ams::ldr::hoc::pcv {
default:
customize_table = const_cast<cvb_entry_t *>(C.eristaGpuDvfsTable);
break;
}
}
}
size_t default_entry_count = GetDvfsTableEntryCount(default_table);
@@ -204,4 +202,4 @@ namespace ams::ldr::hoc::pcv {
void SafetyCheck();
void Patch(uintptr_t mapped_nso, size_t nso_size);
}
} // namespace ams::ldr::hoc::pcv

View File

@@ -44,6 +44,15 @@ namespace ams::ldr::hoc::pcv {
return ins & ((1 << 5) - 1);
};
/* Rn (bits 9:5) and Rm (bits 20:16) to get registers. */
inline auto AsmGetRn = [](u32 ins) -> u32 { return (ins >> 5) & 0x1Fu; };
inline auto AsmGetRm = [](u32 ins) -> u32 { return (ins >> 16) & 0x1Fu; };
/* Add (shifted register), 64-bit: sf=1 op=0 S=0 01011 shift(2) 0 Rm imm6 Rn Rd. */
inline auto AsmIsAddShiftedReg64 = [](u32 ins) {
return (ins & 0xFF200000u) == 0x8B000000u;
};
inline auto asm_set_rd = [](u32 ins, u8 rd) {
return (ins & 0xFFFFFFE0) | (rd & 0x1F);
};
@@ -94,6 +103,16 @@ namespace ams::ldr::hoc::pcv {
return static_cast<uintptr_t>(static_cast<s64>(pc) + imm);
};
/* adrp Rd, target */
inline auto AsmMakeAdrp = [](uintptr_t pc, uintptr_t target, u32 rd) -> u32 {
const s64 delta = static_cast<s64>(target & ~static_cast<uintptr_t>(0xFFF))
- static_cast<s64>(pc & ~static_cast<uintptr_t>(0xFFF));
const u32 imm = static_cast<u32>((delta >> 12) & 0x1FFFFF); /* 21-bit page */
const u32 immlo = imm & 0x3;
const u32 immhi = (imm >> 2) & 0x7FFFF;
return 0x90000000u | (immlo << 29) | (immhi << 5) | (rd & 0x1Fu);
};
inline auto AsmSetAdrTarget = [](u32 ins, uintptr_t pc, uintptr_t target) -> u32 {
const s64 delta = static_cast<s64>(target) - static_cast<s64>(pc);
const u32 immlo = static_cast<u32>(delta & 0x3);
@@ -101,6 +120,76 @@ namespace ams::ldr::hoc::pcv {
return (ins & ~((0x3u << 29) | (0x7FFFFu << 5))) | (immlo << 29) | (immhi << 5);
};
/* adrp: bit31=1, bits 28:24 = 10000. */
inline auto AsmIsAdrp = [](u32 ins) -> bool {
return (ins & 0x9F000000u) == 0x90000000u;
};
inline auto AsmAdrpPageOffset = [](u32 ins) -> s64 {
s64 imm = static_cast<s64>((((ins >> 5) & 0x7FFFFu) << 2) | ((ins >> 29) & 0x3u));
imm = (imm << 43) >> 43; /* sign-extend the 21-bit immediate */
return imm << 12;
};
/* add (immediate), 64-bit: sf=1 op=0 S=0 100010 sh imm12 Rn Rd. */
inline auto AsmIsAddImm64 = [](u32 ins) -> bool {
return (ins & 0xFF800000u) == 0x91000000u;
};
inline auto AsmGetImm12 = [](u32 ins) -> u32 {
return (ins >> 10) & 0xFFFu;
};
inline auto AsmMakeAddImm64 = [](u32 rd, u32 rn, u32 imm12) -> u32 {
return 0x91000000u | ((imm12 & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rd & 0x1Fu);
};
/* movz Wd,#imm16 (no shift). */
inline auto AsmMakeMovzW = [](u32 rd, u16 imm16) -> u32 {
return 0x52800000u | (static_cast<u32>(imm16) << 5) | (rd & 0x1Fu);
};
/* mov Xd,Xm == orr Xd,XZR,Xm. */
inline auto AsmMakeMovReg = [](u32 rd, u32 rm) -> u32 {
return 0xAA0003E0u | ((rm & 0x1Fu) << 16) | (rd & 0x1Fu);
};
/* ldr Xt,[Xn,#byteOff] */
inline auto AsmMakeLdrImm64 = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0xF9400000u | (((byteOff / 8u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* mov Xd,X<rm> == orr Xd,XZR,X<rm> (matches any Rd). */
inline auto AsmIsMovReg = [](u32 ins, u32 rm) -> bool {
return (ins & 0xFFFFFFE0u) == (0xAA0003E0u | ((rm & 0x1Fu) << 16));
};
/* add Xd,sp,#imm12 (shift 0). */
inline auto AsmIsAddSpImm = [](u32 ins) -> bool {
return (ins & 0xFFC003E0u) == 0x910003E0u;
};
/* sub Xd,x29,#imm12 (shift 0). */
inline auto AsmIsSubX29Imm = [](u32 ins) -> bool {
return (ins & 0xFFC003E0u) == 0xD10003A0u;
};
inline auto AsmIsB = [](u32 ins) -> bool { return (ins & 0xFC000000u) == 0x14000000u; }; /* b */
inline auto AsmIsBl = [](u32 ins) -> bool { return (ins & 0xFC000000u) == 0x94000000u; }; /* bl */
inline auto AsmIsBCond = [](u32 ins) -> bool { return (ins & 0xFF000010u) == 0x54000000u; }; /* b.c */
/* Byte target address of a b/bl at pc. */
inline auto AsmBranchTarget = [](u32 ins, uintptr_t pc) -> uintptr_t {
s64 off = static_cast<s64>((ins & 0x03FFFFFFu) << 2);
off = (off << 36) >> 36; /* sign-extend the 28-bit branch offset */
return static_cast<uintptr_t>(static_cast<s64>(pc) + off);
};
/* Rewrite a scaled immediate-offset load/store (`op Wt,[Xn,#imm]`) into its register-offset form */
inline auto AsmSetLdStRegOffset = [](u32 ldstImm, u32 rm) -> u32 {
return (ldstImm & 0xC0C003FFu) | 0x38207800u | ((rm & 0x1Fu) << 16);
};
inline auto AsmIsLdpX = [](u32 ins) {
return (ins & 0xFE400000u) == 0xA8400000u;
};

View File

@@ -173,6 +173,16 @@ namespace ams::ldr::hoc::pcv {
constexpr size_t DvfsTableEntryCount = 32;
constexpr size_t DvfsTableEntryLimit = DvfsTableEntryCount - 1;
// EMC-only limit.
constexpr size_t EmcDvfsTableEntryCount = 64;
constexpr size_t EmcDvfsTableEntryLimit = EmcDvfsTableEntryCount - 1;
// The pcv SoC-voltage DVB table is a fixed 32-entry region. (it doesn't need to be larger)
constexpr size_t DvbTableCapacity = 32;
// extra .bss location, 0 until Patch() runs.
inline uintptr_t g_pcv_scratch = 0;
template<typename T>
size_t GetDvfsTableEntryCount(T *table_head) {
using NT = std::remove_const_t<std::remove_volatile_t<T>>;

View File

@@ -622,6 +622,82 @@ namespace ams::ldr::hoc::pcv::mariko {
u32 *nsoStart;
}
/* Widen InitDram for a >32-entry EMC DVFS list. Freq array can be dropped to free 264 bytes, relocate the Soc LUT to that space */
Result EmcSocLutReloc(u32 *ptr) {
R_UNLESS(EmcSocLutPatternFn(ptr), ldr::ResultInvalidEmcSocLut()); /* str lut,[rail+0x120] ; str n,[rail+0x154] */
constexpr u32 Window = 48;
u32 *freqStore = ScanAssembly(ptr - Window, Window, EmcSocFreqStoreAsm, asm_compare_no_rd); /* str x?,[x8,#0x18] */
u32 *voltStore = ScanAssembly(ptr - Window, Window, EmcSocVoltStoreAsm, asm_compare_no_rd); /* str w?,[x8,#0x48] */
u32 *readLoad = ScanAssembly(ptr - Window, Window, EmcSocReadLoadAsm, asm_compare_no_rd); /* ldr w?,[x9,#0x48] */
R_UNLESS(freqStore && voltStore && readLoad, ldr::ResultInvalidEmcSocLut());
u32 *voltBase = voltStore - 2; /* `add Xb,Xsrc,Xi,LSL#2` (a cmn sits between it and store) */
R_UNLESS(AsmIsAddShiftedReg64(*voltBase) && asm_get_rd(*voltBase) == AsmGetRn(*voltStore),
ldr::ResultInvalidEmcSocLut());
/* adrp Xl ; add Xl,Xl,#off ; ... ; str Xl,[rail,#0x120] */
const u32 lutReg = asm_get_rd(ptr[0]);
const u32 railReg = AsmGetRn(ptr[0]);
R_UNLESS(AsmIsAdrp(ptr[-3]) && asm_get_rd(ptr[-3]) == lutReg, ldr::ResultInvalidEmcSocLut());
R_UNLESS(AsmIsAddImm64(ptr[-2]) && asm_get_rd(ptr[-2]) == lutReg && AsmGetRn(ptr[-2]) == lutReg,
ldr::ResultInvalidEmcSocLut());
const u32 srcBase = AsmGetRn(*voltBase); /* rail ptr at +0x20 */
const u32 wBase = asm_get_rd(*voltBase); /* base reg */
const u32 wIdx = AsmGetRm(*voltBase); /* loop index */
PATCH_OFFSET(freqStore, NopIns); /* Unneeded */
PATCH_OFFSET(voltBase, AsmMakeLdrImm64(wBase, srcBase, 0x20)); /* ldr Xb,[Xsrc,#0x20] (rail) */
PATCH_OFFSET(voltStore - 1, AsmMakeAddImm64(wBase, wBase, 0x18)); /* add Xb,Xb,#0x18 (was cmn) */
PATCH_OFFSET(voltStore, AsmSetLdStRegOffset(*voltStore, wIdx)); /* str Wv,[Xb,Xi,LSL#2] -> rail+0x18+i*4 */
PATCH_OFFSET(voltStore + 1, NopIns); /* Unneeded */
/* rail+0x18 as the socMinLut pointer. */
PATCH_OFFSET(ptr - 3, NopIns);
PATCH_OFFSET(ptr - 2, AsmMakeAddImm64(lutReg, railReg, 0x18));/* add Xl,rail,#0x18 */
/* Drop the abort branch in case of a bad read */
for (u32 i = 1; i <= 4; ++i) {
if (AsmIsBCond(readLoad[i])) {
PATCH_OFFSET(&readLoad[i], NopIns);
break;
}
}
R_SUCCEED();
}
Result EmcDvfsCountLimit(u32 *ptr) {
R_UNLESS(EmcDvfsCountPatternFn(ptr), ldr::ResultInvalidEmcDvfsCount());
/* cmp w?,#0x21 -> cmp w?,#EmcDvfsTableEntryCount */
PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast<u16>(EmcDvfsTableEntryCount)));
R_SUCCEED();
}
Result EmcRateListLimit(u32 *ptr) {
/* ptr = cmp w?,#0x20 ; ptr[1] = csel w?,w?,w?,lt (w? = min(maxCount, 32)) ; ptr[2] = bl */
R_UNLESS(EmcRateListPatternFn(ptr), ldr::ResultInvalidEmcRateList());
/* The csel's Rm holds the 32 cap. */
const u32 capReg = AsmGetRm(ptr[1]);
const u32 capMov = AsmMakeMovzW(capReg, 0x20); /* movz w<Rm>,#0x20 */
u32 *movPtr = nullptr;
for (u32 i = 1; i <= 16; ++i) {
if (*(ptr - i) == capMov) {
movPtr = ptr - i;
break;
}
}
R_UNLESS(movPtr, ldr::ResultInvalidEmcRateList());
/* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount). */
PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast<u16>(EmcDvfsTableEntryCount))); /* cmp w?,#64 */
PATCH_OFFSET(movPtr, asm_set_imm16(*movPtr, static_cast<u16>(EmcDvfsTableEntryCount))); /* movz w?,#64 */
R_SUCCEED();
}
void MtcGenerateJedecTable() {
const u32 jedecFreqs[] = { 1866000, 1996000, 2133000, 2400000, 2666000, 2933000, 3200000 };
constexpr u32 JedecFreqCount = std::size(jedecFreqs);
@@ -638,7 +714,7 @@ namespace ams::ldr::hoc::pcv::mariko {
newEmcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
}
newEmcList.resize(std::min(newEmcList.size(), DvfsTableEntryLimit));
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
}
void MtcGenerate133StepTable() {
@@ -657,12 +733,39 @@ namespace ams::ldr::hoc::pcv::mariko {
newEmcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
}
newEmcList.resize(std::min(newEmcList.size(), DvfsTableEntryLimit));
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
}
void MtcGenerate33StepTable() {
/* ~33.33MHz but rounded*/
const u32 stepFreqs33[] = {
1633000, 1666000, 1700000, 1733000, 1766000, 1800000, 1833000, 1866000, 1900000, 1933000,
1966000, 2000000, 2033000, 2066000, 2100000, 2133000, 2166000, 2200000, 2233000, 2266000,
2300000, 2333000, 2366000, 2400000, 2433000, 2466000, 2500000, 2533000, 2566000, 2600000,
2633000, 2666000, 2700000, 2733000, 2766000, 2800000, 2833000, 2866000, 2900000, 2933000,
2966000, 3000000, 3033000, 3066000, 3100000, 3133000, 3166000, 3200000, 3233000, 3266000,
3300000, 3333000, 3366000, 3400000, 3433000, 3466000, 3500000,
};
constexpr u32 StepFreqs33Size = std::size(stepFreqs33);
for (u32 i = 0; i < StepFreqs33Size; ++i) {
if (stepFreqs33[i] <= C.marikoEmcMaxClock) {
newEmcList.push_back(stepFreqs33[i]);
} else {
break;
}
}
if (newEmcList.back() != C.marikoEmcMaxClock) {
newEmcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
}
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
}
void MtcGenerateFreqTables() {
newEmcList.clear();
newEmcList.reserve(DvfsTableEntryCount);
newEmcList.reserve(EmcDvfsTableEntryCount);
newEmcList.insert(newEmcList.end(), std::begin(EmcListDefault), std::end(EmcListDefault));
if (C.marikoEmcMaxClock <= EmcClkOSLimit) {
@@ -671,6 +774,9 @@ namespace ams::ldr::hoc::pcv::mariko {
u32 stepRate = 0;
switch (C.stepMode) {
case StepMode_33MHz:
MtcGenerate33StepTable();
return;
case StepMode_66MHz:
stepRate = 66667;
break;
@@ -701,7 +807,7 @@ namespace ams::ldr::hoc::pcv::mariko {
newEmcList.push_back(newFreq);
}
newEmcList.resize(std::min(newEmcList.size(), DvfsTableEntryLimit));
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
}
Result VerifyMtcTable(MarikoMtcTable *tableStart, u32 expectedFreq) {
@@ -905,24 +1011,23 @@ namespace ams::ldr::hoc::pcv::mariko {
#undef DVB
#undef DVB_OC
DvbEntry emcDvbTableOc[newEmcList.size()];
const size_t dvbCount = std::min(newEmcList.size(), DvbTableCapacity);
DvbEntry emcDvbTableOc[DvbTableCapacity] = {};
u32 bracketIndex = 0;
for (u32 i = 0; i < newEmcList.size(); ++i) {
while (newEmcList[i] >= emcDvbOcTableBrackets[bracketIndex + 1].freq) {
for (size_t i = 0; i < dvbCount; ++i) {
const u32 freq = (i == dvbCount - 1) ? static_cast<u32>(newEmcList.back()) : newEmcList[i];
while (freq >= emcDvbOcTableBrackets[bracketIndex + 1].freq) {
++bracketIndex;
}
emcDvbTableOc[i].freq = newEmcList[i];
emcDvbTableOc[i].freq = freq;
std::memcpy(emcDvbTableOc[i].volt, emcDvbOcTableBrackets[bracketIndex].volt, sizeof(emcDvbTableOc[i].volt));
}
std::memset(mem_dvb_table_head, 0, sizeof(EmcDvbTableDefault));
std::memcpy(mem_dvb_table_head, &emcDvbTableOc, sizeof(emcDvbTableOc));
/* Max dvfs entry is 32, but HOS doesn't seem to boot if exact freq doesn't exist in dvb table,
reason why it's like this
*/
/* Clear the entire 32-entry region */
std::memset(mem_dvb_table_head, 0, DvbTableCapacity * sizeof(DvbEntry));
std::memcpy(mem_dvb_table_head, emcDvbTableOc, dvbCount * sizeof(DvbEntry));
R_SUCCEED();
}
@@ -1136,9 +1241,61 @@ namespace ams::ldr::hoc::pcv::mariko {
R_SUCCEED();
}
Result EmcRateSessLimit(u32 *ptr) {
u32 movzI = 0;
R_UNLESS(EmcRateSessFindClamp(ptr, nullptr, nullptr, &movzI), ldr::ResultInvalidEmcRateList());
/* Reject cmd11 GetDvfsTable. */
for (u32 i = 1; i <= 24; ++i) {
const u32 w = ptr[i];
if (AsmIsSubX29Imm(w) && AsmGetImm12(w) >= 0x20u) { /* sub x?,x29,#>=0x20 */
R_THROW(ldr::ResultInvalidEmcRateList());
}
}
/* mov x<desc>,x2 */
u32 descReg = 0xFFu;
for (u32 i = 1; i <= 24; ++i) {
if (AsmIsMovReg(ptr[i], 2)) { descReg = asm_get_rd(ptr[i]); break; }
}
R_UNLESS(descReg != 0xFFu, ldr::ResultInvalidEmcRateList());
/* Repoint the duplicated-imm pair */
u32 *adds[8]; u32 addImm[8]; u32 nAdds = 0;
for (u32 i = 1; i <= 24 && nAdds < 8; ++i) {
const u32 w = ptr[i];
if (AsmIsAddSpImm(w)) { /* add x?,sp,#imm12 (shift 0) */
adds[nAdds] = ptr + i;
addImm[nAdds] = AsmGetImm12(w);
++nAdds;
}
}
u32 patched = 0;
for (u32 a = 0; a < nAdds; ++a) {
bool dup = false;
for (u32 b = 0; b < nAdds; ++b) {
if (a != b && addImm[a] == addImm[b]) { dup = true; break; }
}
if (dup) {
PATCH_OFFSET(adds[a], AsmMakeLdrImm64(asm_get_rd(*adds[a]), descReg, 0)); /* ldr x?,[x<desc>] */
++patched;
}
}
R_UNLESS(patched == 2, ldr::ResultInvalidEmcRateList());
/* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount) */
PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast<u16>(EmcDvfsTableEntryCount))); /* cmp w?,#64 */
PATCH_OFFSET(ptr + movzI, asm_set_imm16(*(ptr + movzI), static_cast<u16>(EmcDvfsTableEntryCount))); /* movz w?,#64 */
R_SUCCEED();
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
nsoStart = reinterpret_cast<u32 *>(mapped_nso);
g_pcv_scratch = mapped_nso + nso_size - HocPcvScratchSize;
MtcGenerateFreqTables();
u32 CpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
u32 GpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
@@ -1161,6 +1318,10 @@ namespace ams::ldr::hoc::pcv::mariko {
{ "MEM Vddq", &EmcVddqVolt, 2, nullptr, EmcVddqDefault },
{ "MEM Vdd2", &MemVoltHandler, 2, nullptr, MemVdd2Default },
{ "MEM Table Asm", &MemMtcTableAsm, 1, &MemMtcGetGetTablePatternFn },
{ "EMC DVFS Count", &EmcDvfsCountLimit, 1, &EmcDvfsCountPatternFn },
{ "EMC SoC LUT", &EmcSocLutReloc, 1, &EmcSocLutPatternFn },
{ "EMC Rate List", &EmcRateListLimit, 0, &EmcRateListPatternFn },
{ "EMC Rate Sess", &EmcRateSessLimit, 1, &EmcRateSessPatternFn },
{ "SOC Volt Asm", &SocVoltAsm, 1, &SocVoltPatternFn },
{ "SOC Volt Limit", &SocVoltLimit, 1, nullptr, SocVoltLimitOfficial },
};

View File

@@ -299,6 +299,78 @@ namespace ams::ldr::hoc::pcv::mariko {
return AsmCompareAddNoImm12(*ptr, MtcAddAsm);
}
constexpr u32 EmcCountCmpAsm = 0x7100851F; /* cmp w?,#0x21 (subs wzr,w?,#0x21) */
/*
str <lut>,[<rail>,#0x120] ; volt-array pointer
str w?, [<rail>,#0x154] ; num_freqs
*/
constexpr u32 EmcSocLutPtrStoreAsm = 0xF9009009; /* str x?,[x0,#0x120] (anchor) */
constexpr u32 EmcSocLutCountStoreAsm = 0xB9015408; /* str w?,[x0,#0x154] (anchor) */
constexpr u32 EmcSocFreqStoreAsm = 0xF9000D00; /* str x?,[x8,#0x18] */
constexpr u32 EmcSocVoltStoreAsm = 0xB9004900; /* str w?,[x8,#0x48] (socMinLut[i]) */
constexpr u32 EmcSocReadLoadAsm = 0xB9404929; /* ldr w?,[x9,#0x48] (socMinLut readback) */
inline bool EmcDvfsCountPatternFn(u32 *ptr) {
/* Local context: cbz w?,<skip> ; cmp w?,#0x21 ; b.cs <abort> */
return asm_compare_no_rd(*ptr, EmcCountCmpAsm) && AsmCompareBrConNoImm19(*(ptr + 1), 0x54000002) /* b.cs */
&& AsmCbzCompareOpcodeOnly(*(ptr - 1), 0x34000000); /* cbz */
}
inline bool EmcSocLutPatternFn(u32 *ptr) {
return asm_compare_no_rd(*ptr, EmcSocLutPtrStoreAsm) /* str x?,[x0,#0x120] */
&& asm_compare_no_rd(*(ptr + 1), EmcSocLutCountStoreAsm); /* str w?,[x0,#0x154] */
}
/*
mov w?,#0x20 ; the 32 cap
cmp w?,#0x20
csel w?,w?,w?,lt ; w? = min(maxCount, 32)
bl TegraGetEmcDvfsFreqTable
cmp w?,#0x20 ; (maxCount)
csel w?,<same>,<cap>,lt ; min(maxCount, 32)
bl <Get*DvfsFreqTable>
*/
constexpr u32 EmcRateCapCmpAsm = 0x710082FF; /* cmp w?,#0x20 */
constexpr u32 EmcRateCapCselAsm = 0x1A80B000; /* csel w?,w?,w?,lt */
inline bool EmcRateListPatternFn(u32 *ptr) {
return AsmSubsCompareNoReg(*ptr, EmcRateCapCmpAsm) /* cmp w?,#0x20 */
&& AsmCompareCselNoReg(*(ptr + 1), EmcRateCapCselAsm) /* csel w?,w?,w?,lt */
&& (AsmGetRn(*ptr) == AsmGetRn(*(ptr + 1))) /* min(reg, 0x20) */
&& AsmBlCompareOpcodeOnly(*(ptr + 2), 0x94000000); /* bl <Get*DvfsFreqTable> */
}
constexpr u32 EmcRateSessCmpAsm = 0x710082FF; /* cmp w?,#0x20 */
constexpr u32 EmcRateSessMovAsm = 0x52800400; /* movz w?,#0x20 */
constexpr u32 EmcRateSessCselAsm = 0x1A80B000; /* csel w?,w?,w?,lt (opcode + cond) */
inline bool EmcRateSessFindClamp(u32 *ptr, u32 *out_c, u32 *out_cap, u32 *out_movz_i) {
if (!AsmSubsCompareNoReg(ptr[0], EmcRateSessCmpAsm)) return false; /* cmp w<c>,#0x20 */
const u32 c = AsmGetRn(ptr[0]);
for (u32 i = 1; i <= 14; ++i) {
const u32 w = ptr[i];
if (AsmCompareCselNoReg(w, EmcRateSessCselAsm) && AsmGetRn(w) == c && asm_get_rd(w) == c) {
const u32 cap = AsmGetRm(w);
for (u32 j = 1; j < i; ++j) {
if ((ptr[j] & 0xFFFFFFE0u) == EmcRateSessMovAsm && asm_get_rd(ptr[j]) == cap) {
if (out_c) *out_c = c;
if (out_cap) *out_cap = cap;
if (out_movz_i) *out_movz_i = j;
return true;
}
}
return false;
}
}
return false;
}
inline bool EmcRateSessPatternFn(u32 *ptr) {
return EmcRateSessFindClamp(ptr, nullptr, nullptr, nullptr);
}
void Patch(uintptr_t mapped_nso, size_t nso_size);
}