From 6f4b58b83e940407e3cd6da36211e058fb1fd558 Mon Sep 17 00:00:00 2001 From: souldbminersmwc Date: Sat, 25 Jul 2026 14:50:56 -0400 Subject: [PATCH] ldr: code cleanup and fix soc bus table bug --- .../loader/source/oc/oc_common.hpp | 10 +- .../loader/source/oc/pcv/pcv_asm.hpp | 15 +++ .../loader/source/oc/pcv/pcv_mariko.cpp | 120 +++++++++--------- .../loader/source/oc/pcv/pcv_mariko.hpp | 53 ++++++++ 4 files changed, 138 insertions(+), 60 deletions(-) diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp index 8ba07d57..55a98b6d 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp @@ -62,11 +62,14 @@ namespace ams::ldr { R_DEFINE_ERROR_RESULT(InvalidEmcDvfsCount, 1019); R_DEFINE_ERROR_RESULT(InvalidEmcSocLut, 1020); R_DEFINE_ERROR_RESULT(InvalidEmcRateList, 1021); + R_DEFINE_ERROR_RESULT(InvalidNvLogRedirect, 1022); + R_DEFINE_ERROR_RESULT(InvalidBusFreqReloc, 1023); } namespace ams::ldr::hoc { - /* Extra pcv .bss for 64LUT */ - constexpr size_t HocPcvScratchSize = 0x1000; + /* Extra pcv .bss */ + constexpr size_t HocPcvScratchSize = 0x2000; + constexpr size_t HocBusFreqBufOffset = 0x1000; /* start of the SOC bus region */ template struct PatcherEntry { @@ -79,6 +82,7 @@ namespace ams::ldr::hoc { patternFn pattern_search_fn = nullptr; Pointer value_search; size_t patched_count = 0; + bool optional = false; Result Apply(Pointer *ptr) { Result res = patcher_fn(ptr); @@ -109,7 +113,7 @@ namespace ams::ldr::hoc { } Result CheckResult() { - R_UNLESS(patched_count > 0, ldr::ResultUnsuccessfulPatcher()); + R_UNLESS(optional || patched_count > 0, ldr::ResultUnsuccessfulPatcher()); if (maximum_patched_count) { R_UNLESS(patched_count <= maximum_patched_count, ldr::ResultUnsuccessfulPatcher()); diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_asm.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_asm.hpp index 9d9a909e..b2dbceeb 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_asm.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_asm.hpp @@ -186,6 +186,16 @@ namespace ams::ldr::hoc::pcv { return 0xB9000000u | (((byteOff / 4u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu); }; + /* ldr Wt,[Xn,#byteOff] (32-bit, unsigned scaled by 4). */ + inline auto AsmMakeLdrImm32 = [](u32 rt, u32 rn, u32 byteOff) -> u32 { + return 0xB9400000u | (((byteOff / 4u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu); + }; + + /* add Xd,Xn,Xm,LSL #shift (64-bit shifted register, shift 0-63). */ + inline auto AsmMakeAddShiftedReg64 = [](u32 rd, u32 rn, u32 rm, u32 shift) -> u32 { + return 0x8B000000u | ((rm & 0x1Fu) << 16) | ((shift & 0x3Fu) << 10) | ((rn & 0x1Fu) << 5) | (rd & 0x1Fu); + }; + /* stp Xt1,Xt2,[Xn,#imm] (signed offset, scaled by 8). */ inline auto AsmMakeStpImm64 = [](u32 rt1, u32 rt2, u32 rn, s32 imm) -> u32 { return 0xA9000000u | ((static_cast(imm / 8) & 0x7Fu) << 15) | ((rt2 & 0x1Fu) << 10) | ((rn & 0x1Fu) << 5) | (rt1 & 0x1Fu); @@ -254,6 +264,11 @@ namespace ams::ldr::hoc::pcv { inline auto AsmIsBl = [](u32 ins) -> bool { return (ins & 0xFC000000u) == 0x94000000u; }; /* bl */ inline auto AsmIsBCond = [](u32 ins) -> bool { return (ins & 0xFF000010u) == 0x54000000u; }; /* b.c */ + /* ldr/str Xt,[Xn,#imm] (64-bit, unsigned scaled offset). */ + inline auto AsmIsLdrImm64 = [](u32 ins) -> bool { return (ins & 0xFFC00000u) == 0xF9400000u; }; + inline auto AsmIsStrImm64 = [](u32 ins) -> bool { return (ins & 0xFFC00000u) == 0xF9000000u; }; + inline auto AsmGetLdStImm64Off = [](u32 ins) -> u32 { return ((ins >> 10) & 0xFFFu) * 8u; }; + /* Byte target address of a b/bl at pc. */ inline auto AsmBranchTarget = [](u32 ins, uintptr_t pc) -> uintptr_t { s64 off = static_cast((ins & 0x03FFFFFFu) << 2); diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp index 338a540e..e8fce8c0 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp @@ -620,6 +620,7 @@ namespace ams::ldr::hoc::pcv::mariko { namespace { std::vector newEmcList; u32 *nsoStart; + size_t g_nso_size = 0; uintptr_t g_cave_cursor = 0; } @@ -635,26 +636,12 @@ namespace ams::ldr::hoc::pcv::mariko { return entry; } - static void NvLogUartRedirect(uintptr_t mapped_nso, size_t nso_size) { - const uintptr_t textEnd = g_pcv_cave; /* .text ends where the cave begins */ - if (textEnd == 0) { - return; - } - - /* vsnprintf(buf,size,fmt,va_list) */ - static const u32 VsnSig[] = { 0xD10483FFu, 0xA9107BFDu, 0xF9008BFCu, 0x910403FDu, 0xF100003Fu }; - uintptr_t vsnprintf_addr = 0; - for (u32 *p = nsoStart; reinterpret_cast(p + std::size(VsnSig)) <= textEnd; ++p) { - bool ok = true; - for (size_t k = 0; k < std::size(VsnSig); ++k) { - if (p[k] != VsnSig[k]) { ok = false; break; } - } - if (ok) { vsnprintf_addr = reinterpret_cast(p); break; } - } - if (vsnprintf_addr == 0) { - LOGGING("NvLogRedirect: vsnprintf not found"); - return; - } + /* Redirect pcv's NvLog() calls to UART */ + Result NvLogUartRedirect(u32 *ptr) { + const uintptr_t mapped_nso = reinterpret_cast(nsoStart); + const size_t nso_size = g_nso_size; + const uintptr_t textEnd = g_pcv_cave; /* .text ends where the cave begins */ + const uintptr_t vsnprintf_addr = reinterpret_cast(ptr); /* NvLog via the VDD_SOC log */ static const char Fmt[] = "%s(%s): DVFS request VDD_SOC %d mV\n"; @@ -668,7 +655,7 @@ namespace ams::ldr::hoc::pcv::mariko { } if (strAddr == 0) { LOGGING("NvLogRedirect: fmt string not found (vsnprintf@+%lx)", vsnprintf_addr - mapped_nso); - return; + R_THROW(ldr::ResultInvalidNvLogRedirect()); } uintptr_t nvlog_addr = 0; @@ -694,14 +681,14 @@ namespace ams::ldr::hoc::pcv::mariko { } if (nvlog_addr == 0 || nvlog_addr < mapped_nso || nvlog_addr >= textEnd) { LOGGING("NvLogRedirect: NvLog entry not found (fmt@+%lx)", strAddr - mapped_nso); - return; + R_THROW(ldr::ResultInvalidNvLogRedirect()); } const uintptr_t helper = CaveReserve(40); if (helper == 0) { LOGGING("NvLogRedirect: cave unavailable (cave=%lx size=%lx)", static_cast(g_pcv_cave), static_cast(g_pcv_cave_size)); - return; + R_THROW(ldr::ResultInvalidNvLogRedirect()); } u32 *t = reinterpret_cast(helper); size_t n = 0; @@ -767,44 +754,61 @@ namespace ams::ldr::hoc::pcv::mariko { LOGGING("NvLogRedirect: stub@+%lx vsnprintf@+%lx helper@+%lx instr=%zu sites=%zu", nvlog_addr - mapped_nso, vsnprintf_addr - mapped_nso, helper - mapped_nso, n, patchedSites); + R_SUCCEED(); } - /* Force GetEffectiveVerbosityLevel to return a non-zero level so all NvLog runs */ - static void ForceVerbosity() { - const uintptr_t textEnd = g_pcv_cave; - if (textEnd == 0 || HOC_PCV_FORCE_VERBOSITY == 0) { - return; + /* Relocate C2/C3Bus to avoid issues*/ + Result BusFreqReloc(u32 *ptr) { + const u32 busReg = AsmGetRn(ptr[0]); /* ldr Xbuf,[Xbus,#0x10] : bus struct pointer */ + const u32 bufReg = asm_get_rd(ptr[0]); /* : freq-buffer arg */ + const u32 bufOff = AsmGetLdStImm64Off(ptr[0]); /* : bus->freqBuf offset */ + const u32 cntReg = asm_get_rd(ptr[1]); /* add Xcnt,Xbus,#0x18 : arg2 (&count) */ + const u32 railReg = asm_get_rd(ptr[2]); /* str Xrail,[Xbus,#0x50]: arg0 (rail) */ + u32 *call = ptr + 3; /* the bl to relocate */ + const uintptr_t realFn = AsmBranchTarget(*call, reinterpret_cast(call)); + + /* Pick 3 scratch registers */ + u32 s[3], sc = 0; + for (u32 r = 9; r <= 15 && sc < 3; ++r) { + if (r != busReg && r != bufReg && r != cntReg && r != railReg) { + s[sc++] = r; + } } - size_t patched = 0; - for (u32 *p = nsoStart; reinterpret_cast(p + 11) <= textEnd; ++p) { - if (p[0] != 0xA9BE7BFDu || p[1] != 0xF9000BF3u || p[2] != 0x910003FDu) { /* stp/str/mov x29,sp */ - continue; - } - if (!(AsmIsAddImm64(p[3]) && asm_get_rd(p[3]) == 0 && AsmGetRn(p[3]) == 29)) continue; /* add x0,x29,#imm */ - if (!(AsmIsAddImm64(p[4]) && asm_get_rd(p[4]) == 19 && AsmGetRn(p[4]) == 29)) continue; /* add x19,x29,#imm */ - if (AsmGetImm12(p[3]) != AsmGetImm12(p[4]) || !AsmIsBl(p[5])) { - continue; - } - bool hasCmp = false; - for (u32 j = 6; j <= 10; ++j) { - if (p[j] == 0x7100001Fu) { /* cmp w0,#0 */ - hasCmp = true; - break; - } - } - if (!hasCmp) { - continue; - } - PATCH_OFFSET(&p[0], AsmMakeMovzW(0, static_cast(HOC_PCV_FORCE_VERBOSITY))); - PATCH_OFFSET(&p[1], RetIns); - ++patched; - } - LOGGING("ForceVerbosity: patched %zu getter(s) to level %d", patched, HOC_PCV_FORCE_VERBOSITY); + R_UNLESS(sc == 3, ldr::ResultInvalidBusFreqReloc()); + + const uintptr_t tramp = CaveReserve(9); + R_UNLESS(tramp != 0, ldr::ResultInvalidBusFreqReloc()); + + const uintptr_t region = g_pcv_scratch + HocBusFreqBufOffset; /* [0]=counter, +0x10 + i*0x400 = bufs */ + u32 *t = reinterpret_cast(tramp); + size_t n = 0; + auto emit = [&](u32 ins) { t[n] = ins; ++n; }; + emit(AsmMakeAdrp(tramp + n * 4, region, s[0])); /* adrp s0, */ + emit(AsmMakeAddImm64(s[0], s[0], region & 0xFFFu)); /* add s0,s0,#lo */ + emit(AsmMakeLdrImm32(s[1], s[0], 0x00)); /* s1 = counter */ + emit(AsmMakeAddImm64(s[2], s[1], 1)); /* s2 = counter+1 */ + emit(AsmMakeStrImm32(s[2], s[0], 0x00)); /* counter++ */ + emit(AsmMakeAddImm64(s[0], s[0], 0x10)); /* s0 = region+0x10 (buffers) */ + emit(AsmMakeAddShiftedReg64(bufReg, s[0], s[1], 10)); /* Xbuf = s0 + counter*0x400 */ + emit(AsmMakeStrImm64(bufReg, busReg, bufOff)); /* bus[freqBuf] = Xbuf */ + emit(AsmMakeB(tramp + n * 4, realFn)); /* tail-call the real function */ + + PATCH_OFFSET(call, AsmMakeBl(reinterpret_cast(call), tramp)); + const uintptr_t base = reinterpret_cast(nsoStart); + LOGGING("BusFreqReloc: call@+%lx -> tramp@+%lx realfn@+%lx (bus=x%u buf=x%u off=0x%x scratch=x%u,x%u,x%u)", + reinterpret_cast(call) - base, tramp - base, realFn - base, busReg, bufReg, bufOff, s[0], s[1], s[2]); + R_SUCCEED(); + } + + /* Force GetEffectiveVerbosityLevel to return a non-zero level so all NvLog runs. */ + Result ForceVerbosity(u32 *ptr) { + PATCH_OFFSET(&ptr[0], AsmMakeMovzW(0, static_cast(HOC_PCV_FORCE_VERBOSITY))); /* movz w0,#level */ + PATCH_OFFSET(&ptr[1], RetIns); /* ret */ + R_SUCCEED(); } /* 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] */ @@ -1473,11 +1477,9 @@ namespace ams::ldr::hoc::pcv::mariko { nsoStart = reinterpret_cast(mapped_nso); g_pcv_scratch = mapped_nso + nso_size - HocPcvScratchSize; + g_nso_size = nso_size; g_cave_cursor = g_pcv_cave; /* start the .text-cave bump allocator (0 if unavailable) */ - NvLogUartRedirect(mapped_nso, nso_size); - ForceVerbosity(); - MtcGenerateFreqTables(); u32 CpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq); @@ -1506,8 +1508,12 @@ namespace ams::ldr::hoc::pcv::mariko { { "EMC SoC LUT", &EmcSocLutReloc, 1, &EmcSocLutPatternFn }, { "EMC Rate List", &EmcRateListLimit, 0, &EmcRateListPatternFn }, { "EMC Rate Sess", &EmcRateSessLimit, 1, &EmcRateSessPatternFn }, + { "Bus Freq Reloc", &BusFreqReloc, 1, &BusFreqRelocPatternFn }, { "SOC Volt Asm", &SocVoltAsm, 1, &SocVoltPatternFn }, { "SOC Volt Limit", &SocVoltLimit, 1, nullptr, SocVoltLimitOfficial }, + /* Debugging patches */ + { "NvLog Redirect", &NvLogUartRedirect, 1, &NvLogVsnprintfPatternFn, 0, 0, true }, + { "Force Verbosity", &ForceVerbosity, 3, &ForceVerbosityPatternFn, 0, 0, true }, }; for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(MarikoMtcTable); ptr += sizeof(u32)) { diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.hpp index 7bfe1f91..de72f004 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.hpp @@ -371,6 +371,59 @@ namespace ams::ldr::hoc::pcv::mariko { return EmcRateSessFindClamp(ptr, nullptr, nullptr, nullptr); } + inline bool BusFreqRelocPatternFn(u32 *ptr) { + if (g_pcv_scratch == 0 || g_pcv_cave == 0) { + return false; + } + if (reinterpret_cast(ptr + 4) > g_pcv_cave) { /* the call site lives in .text */ + return false; + } + if (!(AsmIsLdrImm64(ptr[0]) && AsmGetLdStImm64Off(ptr[0]) == 0x10)) return false; /* ldr Xbuf,[Xbus,#0x10] */ + if (!(AsmIsAddImm64(ptr[1]) && AsmGetImm12(ptr[1]) == 0x18)) return false; /* add Xcnt,Xbus,#0x18 */ + if (!(AsmIsStrImm64(ptr[2]) && AsmGetLdStImm64Off(ptr[2]) == 0x50)) return false; /* str Xrail,[Xbus,#0x50]*/ + if (!AsmIsBl(ptr[3])) return false; /* bl GetDvfsRailUnique */ + const u32 bus = AsmGetRn(ptr[0]); + return AsmGetRn(ptr[1]) == bus && AsmGetRn(ptr[2]) == bus; + } + + inline bool ForceVerbosityPatternFn(u32 *ptr) { + if (HOC_PCV_FORCE_VERBOSITY == 0 || g_pcv_cave == 0) { + return false; + } + if (reinterpret_cast(ptr + 11) > g_pcv_cave) { /* .text only */ + return false; + } + if (ptr[0] != 0xA9BE7BFDu || ptr[1] != 0xF9000BF3u || ptr[2] != 0x910003FDu) return false; /* stp/str/mov x29,sp */ + if (!(AsmIsAddImm64(ptr[3]) && asm_get_rd(ptr[3]) == 0 && AsmGetRn(ptr[3]) == 29)) return false; /* add x0,x29,#imm */ + if (!(AsmIsAddImm64(ptr[4]) && asm_get_rd(ptr[4]) == 19 && AsmGetRn(ptr[4]) == 29)) return false; /* add x19,x29,#imm */ + if (AsmGetImm12(ptr[3]) != AsmGetImm12(ptr[4]) || !AsmIsBl(ptr[5])) return false; + for (u32 j = 6; j <= 10; ++j) { + if (ptr[j] == 0x7100001Fu) { /* cmp w0,#0 */ + return true; + } + } + return false; + } + + /* vsnprintf(buf,size,fmt,va_list) prologue */ + inline constexpr u32 NvLogVsnSig[] = { 0xD10483FFu, 0xA9107BFDu, 0xF9008BFCu, 0x910403FDu, 0xF100003Fu }; + + inline bool NvLogVsnprintfPatternFn(u32 *ptr) { + if (HOC_UART_LOG == 0 || g_pcv_cave == 0) { + return false; + } + if (reinterpret_cast(ptr + std::size(NvLogVsnSig)) > g_pcv_cave) { /* must sit in .text */ + return false; + } + for (size_t k = 0; k < std::size(NvLogVsnSig); ++k) { + if (ptr[k] != NvLogVsnSig[k]) { + return false; + } + } + return true; + } + + void Patch(uintptr_t mapped_nso, size_t nso_size); }