use hooking for erista mrf

This commit is contained in:
Lightos1
2026-07-27 15:02:53 +02:00
parent 0cf4927c47
commit 6e4a28ee67
2 changed files with 87 additions and 81 deletions

View File

@@ -37,7 +37,8 @@ namespace ams::ldr::hoc::pcv {
return true; 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 *default_table = isMariko ? (cvb_entry_t *)(&mariko::CpuCvbTableDefault) : (cvb_entry_t *)(&erista::CpuCvbTableDefault);
cvb_entry_t *customize_table = nullptr; cvb_entry_t *customize_table = nullptr;
@@ -112,7 +113,8 @@ namespace ams::ldr::hoc::pcv {
PATCH_OFFSET(&(entry->cvb_pll_param.c5), 0); 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 *default_table = isMariko ? (cvb_entry_t *)(&mariko::GpuCvbTableDefault) : (cvb_entry_t *)(&erista::GpuCvbTableDefault);
cvb_entry_t *customize_table; cvb_entry_t *customize_table;
if (isMariko) { if (isMariko) {
@@ -135,7 +137,7 @@ namespace ams::ldr::hoc::pcv {
default: default:
customize_table = const_cast<cvb_entry_t *>(C.marikoGpuDvfsTableHiOPT); customize_table = const_cast<cvb_entry_t *>(C.marikoGpuDvfsTableHiOPT);
break; break;
} }
} else { } else {
switch (C.eristaGpuUV) { switch (C.eristaGpuUV) {
case 0: case 0:
@@ -150,7 +152,7 @@ namespace ams::ldr::hoc::pcv {
default: default:
customize_table = const_cast<cvb_entry_t *>(C.eristaGpuDvfsTable); customize_table = const_cast<cvb_entry_t *>(C.eristaGpuDvfsTable);
break; break;
} }
} }
size_t default_entry_count = GetDvfsTableEntryCount(default_table); size_t default_entry_count = GetDvfsTableEntryCount(default_table);

View File

@@ -27,9 +27,25 @@
namespace ams::ldr::hoc::pcv::erista { namespace ams::ldr::hoc::pcv::erista {
std::vector<u32> newEmcList; struct HookPayloadData {
u32 *nsoStart; struct {
u32 *nsoEnd; EristaMtcTable *mtcTable;
u32 mtcCount;
} mtcTableAsm;
};
DEFINE_HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData);
namespace {
std::vector<u32> newEmcList;
u32 *nsoStart;
u32 *nsoEnd;
struct {
u32 *getEristaMtcTableFnSite = nullptr;
EristaMtcTable *mtcTable = nullptr;
bool foundMtcTablePattern = false;
} getMtcTableCache;
}
Result CpuVoltDvfs(u32 *ptr) { Result CpuVoltDvfs(u32 *ptr) {
if (std::memcmp(ptr + 5, cpuVoltDvfsPattern, sizeof(cpuVoltDvfsPattern))) { if (std::memcmp(ptr + 5, cpuVoltDvfsPattern, sizeof(cpuVoltDvfsPattern))) {
@@ -447,39 +463,6 @@ namespace ams::ldr::hoc::pcv::erista {
} }
} }
/* Relocate the table */
/* Rescanning is simpler than trying to extract a bunch of data from the asm patch, performance impact is negligable */
/* Also, this is more stable :P */
u32 RepointEristaEmcTablePtr(uintptr_t fromSlot, uintptr_t toTable) {
constexpr u32 RetIns = 0xD65F03C0; /* ret */
u32 patched = 0;
for (u32 *p = nsoStart; p + 4 < nsoEnd; ++p) {
const u32 ins = *p;
if (!AsmIsAdrX0(ins)) {
continue;
}
const uintptr_t pc = reinterpret_cast<uintptr_t>(p);
if (AsmAdrTarget(ins, pc) != fromSlot) {
continue;
}
if (!(AsmIsLdpX(p[1]) && AsmIsLdpX(p[2]) && p[3] == RetIns)) {
continue;
}
/* adr only reaches +-1MB */
const s64 delta = static_cast<s64>(toTable) - static_cast<s64>(pc);
if (delta > 0xFFFFF || delta < -0x100000) {
continue;
}
PATCH_OFFSET(p, AsmSetAdrTarget(ins, pc, toTable));
++patched;
}
return patched;
}
/* The silicon instructs; the children obey... */ /* The silicon instructs; the children obey... */
void MtcGenerateFreqTables() { void MtcGenerateFreqTables() {
newEmcList.clear(); newEmcList.clear();
@@ -548,24 +531,20 @@ namespace ams::ldr::hoc::pcv::erista {
constexpr u32 StartAdjustment = offsetof(EristaMtcTable, rate_khz) + sizeof(EristaMtcTable) * (erista::MtcTableCountDefault - 1); constexpr u32 StartAdjustment = offsetof(EristaMtcTable, rate_khz) + sizeof(EristaMtcTable) * (erista::MtcTableCountDefault - 1);
u8 *startPtr = reinterpret_cast<u8 *>(ptr) - StartAdjustment; u8 *startPtr = reinterpret_cast<u8 *>(ptr) - StartAdjustment;
const uintptr_t usedSlot = reinterpret_cast<uintptr_t>(startPtr) + mtcOffset; EristaMtcTable *table = reinterpret_cast<EristaMtcTable *>(startPtr + mtcOffset);
EristaMtcTable *table = reinterpret_cast<EristaMtcTable *>(usedSlot);
R_TRY(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault)); R_TRY(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault));
PrepareMtcMemoryRegion(startPtr, table); PrepareMtcMemoryRegion(startPtr, table);
table = reinterpret_cast<EristaMtcTable *>(startPtr); table = reinterpret_cast<EristaMtcTable *>(startPtr);
/* We must do this as the NLE tables don't have enough space past them for our extended ones */
if (usedSlot != reinterpret_cast<uintptr_t>(startPtr)) {
if (RepointEristaEmcTablePtr(usedSlot, reinterpret_cast<uintptr_t>(startPtr)) == 0) {
AbortInvalidMtc("Failed to repoint emc table");
}
}
if (R_FAILED(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault))) { if (R_FAILED(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault))) {
AbortInvalidMtc("Failed mtc validation"); AbortInvalidMtc("Failed mtc validation");
} }
/* Cache the table for hooks. */
getMtcTableCache.mtcTable = table;
if (C.eristaEmcMaxClock <= EmcClkOSLimit) { if (C.eristaEmcMaxClock <= EmcClkOSLimit) {
R_SKIP(); R_SKIP();
} }
@@ -620,50 +599,71 @@ namespace ams::ldr::hoc::pcv::erista {
// R_SUCCEED(); // R_SUCCEED();
// } // }
HOOK_PAYLOAD_FN EristaMtcTable *GetEristaMtcTableImpl(u32 *count) {
const HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData);
*count = data->mtcTableAsm.mtcCount;
return data->mtcTableAsm.mtcTable;
}
Result MemMtcTableAsm(u32 *ptr) { Result MemMtcTableAsm(u32 *ptr) {
/* Return if the pattern was already found. */
/* This pattern happens multiple times in this function., we only need to find it once. */
R_UNLESS(!getMtcTableCache.foundMtcTablePattern, ldr::ResultInvalidMtcTablePattern());
/* This is a mess but the compiler made this painful to patch so we must do it this way */ /* This is a mess but the compiler made this painful to patch so we must do it this way */
constexpr s32 GoodAdrpOffset = -1; constexpr u32 AddrpOffset = 1;
constexpr s32 GoodMovOffset = -7; constexpr u32 MovOffset = 7;
constexpr s32 GoodBlOffset = 1; constexpr u32 BlOffset = 5;
constexpr u32 MtcGoodBlOpcode = 0x97fe6cfc; constexpr u32 MovOffsetOld = 8;
constexpr u32 MtcBadBlOpcode0 = 0x97ffae64; // bl nn::pcv::GetHardwareType
constexpr u32 MtcBadBlOpcode1 = 0x940036d5; // bl strcmp
constexpr u32 MtcBadAdrpAsm = 0xd00000a1; // adrp x1, s_ModuleResetStatus_
constexpr s32 MtcBadBlOffset0 = 2;
constexpr s32 MtcBadBlOffset1 = -1;
constexpr s32 MtcBadAdrpOffset = 1;
/* Ensure we don't dereference memory before nso start. */ /* Ensure we don't dereference memory before nso start. */
R_UNLESS(ptr + GoodMovOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern()); R_UNLESS(ptr - MovOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern());
/* Check for GetHardwareType asm and skip if it is found */ u32 adrp = *(ptr - AddrpOffset);
/* The pattern will match on the first time, but the location is bad, so it must be skipped */
if(AsmCompareAdrpNoImm(*(ptr + MtcBadAdrpOffset), MtcBadAdrpAsm) && AsmBlCompareOpcodeOnly(*(ptr + MtcBadBlOffset0), MtcBadBlOpcode0) && AsmBlCompareOpcodeOnly(*(ptr + MtcBadBlOffset1), MtcBadBlOpcode1)) {
R_SKIP();
}
/* We don't check for matching register because both registers must be x0 in order to pass the previous checks. */
/* The correct instructions will always be x0 since the mtcTable pointer is returned. */
u32 adrp = *(ptr + GoodAdrpOffset);
R_UNLESS(AsmCompareAdrpNoImm(adrp, MtcAdrpAsm), ldr::ResultInvalidMtcTablePattern()); R_UNLESS(AsmCompareAdrpNoImm(adrp, MtcAdrpAsm), ldr::ResultInvalidMtcTablePattern());
/* Check for the branch instruction above the cbz to ensure we are patching the right location*/ /* Check for the branch instruction above the cbz to ensure we are patching the right location*/
u32 bl = *(ptr + GoodBlOffset); u32 bl = *(ptr - BlOffset);
R_UNLESS(AsmBlCompareOpcodeOnly(bl, MtcGoodBlOpcode), ldr::ResultInvalidMtcTablePattern()); R_UNLESS(AsmBlCompareOpcodeOnly(bl, MtcBlIns), ldr::ResultInvalidMtcTablePattern());
/* Check for the mov that actually sets the mtc table count. */ /* Check for the mov that actually sets the mtc table count. */
u32 mov = *(ptr + GoodMovOffset); u32 mov = *(ptr - MovOffset);
R_UNLESS(asm_compare_no_rd(mov, MtcMovAsm), ldr::ResultInvalidMtcTablePattern()); bool foundMov = false;
foundMov = asm_compare_no_rd(mov, MtcMovAsm);
/* Patch out the count of the mov to our custom mtc table amount*/ if (!foundMov) {
u32 movCountPatch = asm_set_rd(asm_set_imm16(MtcMovAsm, newEmcList.size()), asm_get_rd(mov)); mov = *(ptr + MovOffsetOld);
/* Check old firmware offset. */
foundMov = asm_compare_no_rd(mov, MtcMovAsm);
}
PATCH_OFFSET(ptr + GoodMovOffset, movCountPatch); R_UNLESS(foundMov, ldr::ResultInvalidMtcTablePattern());
constexpr u32 PrologueWindow = 140;
u32 *functionPrologue = FindFnPrologue(ptr, PrologueWindow, nsoStart);
R_UNLESS(functionPrologue != nullptr, ldr::ResultInvalidMtcTablePattern());
getMtcTableCache.getEristaMtcTableFnSite = functionPrologue;
getMtcTableCache.foundMtcTablePattern = true;
R_SUCCEED();
}
Result InstallHooks() {
R_TRY(Hooks().CheckEnabled());
R_TRY(Hooks().CopyPayload());
R_UNLESS(getMtcTableCache.getEristaMtcTableFnSite != nullptr && getMtcTableCache.mtcTable != nullptr, ldr::ResultInvalidMtcTablePattern());
/* Copy the data to the payload. */
auto *data = Hooks().BindData(e_HookPayloadData);
R_UNLESS(data != nullptr, ldr::ResultHookDataOutOfMemory());
data->mtcTableAsm.mtcTable = reinterpret_cast<EristaMtcTable *>(Hooks().ToVa(getMtcTableCache.mtcTable));
data->mtcTableAsm.mtcCount = newEmcList.size();
R_TRY(INSTALL_IMPL_HOOK(getMtcTableCache.getEristaMtcTableFnSite, GetEristaMtcTableImpl));
R_SUCCEED(); R_SUCCEED();
} }
@@ -685,7 +685,7 @@ namespace ams::ldr::hoc::pcv::erista {
{"GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial }, {"GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial },
{"GPU Freq Table", GpuFreqCvbTable<false>, 1, nullptr, GpuCvbDefaultMaxFreq }, {"GPU Freq Table", GpuFreqCvbTable<false>, 1, nullptr, GpuCvbDefaultMaxFreq },
{"GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn }, {"GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn },
{"GPU PLL Max", & GpuFreqPllMax, 1, nullptr, GpuClkPllMax }, {"GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax },
// {"GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit }, // {"GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit },
{"MEM Table Asm", &MemMtcTableAsm, 4, &MemMtcGetGetTablePatternFn }, {"MEM Table Asm", &MemMtcTableAsm, 4, &MemMtcGetGetTablePatternFn },
{"MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit }, {"MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit },
@@ -711,6 +711,10 @@ namespace ams::ldr::hoc::pcv::erista {
CRASH(entry.description); CRASH(entry.description);
} }
} }
if (R_FAILED(InstallHooks())) {
panic::SmcError(panic::Patch);
}
} }
} }