From eba557371120d14f30e78e50c7c323eb578acd62 Mon Sep 17 00:00:00 2001 From: Lightos1 <124387232+Lightos1@users.noreply.github.com> Date: Tue, 25 Aug 2026 20:40:32 +0200 Subject: [PATCH] Refactor busReloc to use hooks. Also add sd logging and temporarily remove new Erista timings; they're still slightly too explosive. Todo: Rename stuff and fix uart redirect. --- .../stratosphere/loader/source/oc/oc_log.cpp | 59 ++++++++++ .../stratosphere/loader/source/oc/oc_log.hpp | 2 + .../pcv/erista/calculate_timings_erista.cpp | 2 +- .../source/oc/pcv/erista/pcv_erista_mtc.cpp | 34 +++--- .../source/oc/pcv/mariko/pcv_mariko.cpp | 109 +++++++++--------- .../source/oc/pcv/mariko/pcv_mariko.hpp | 11 ++ .../loader/source/oc/pcv/pcv_asm.hpp | 22 +++- .../loader/source/oc/pcv/pcv_common.hpp | 24 ++++ .../loader/source/oc/pcv/pcv_hook.hpp | 13 ++- 9 files changed, 200 insertions(+), 76 deletions(-) diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.cpp index 6fccc8e4..1335f1a6 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.cpp @@ -155,4 +155,63 @@ namespace ams::ldr::hoc { while(true) { } #endif } + + namespace { + constexpr size_t SdLogBufferSize = 0x1000; + char sdLogBuffer[SdLogBufferSize]; + size_t workingLength = 0; + } + + void SdLog(const char *data, ...) { + if (workingLength >= SdLogBufferSize - 1) { + return; + } + + va_list args; + va_start(args, data); + s32 res = vsnprintf(sdLogBuffer + workingLength, SdLogBufferSize - workingLength, data, args); + va_end(args); + + if (res < 0) { + return; + } + + workingLength = std::min(workingLength + static_cast(res), SdLogBufferSize - 1); + } + + void SaveLog() { + static bool saved = false; + if (workingLength == 0 || saved) { + return; + } + + if (!cfg::IsSdCardInitialized()) { + return; + } + + if (R_FAILED(fs::MountSdCard("sdmc"))) { + return; + } + + ON_SCOPE_EXIT { fs::Unmount("sdmc"); }; + + constexpr const char *Path = "sdmc:/config/horizon-oc/ldr_log.txt"; + + R_DISCARD(fs::DeleteFile(Path)); + if (R_FAILED(fs::CreateFile(Path, workingLength))) { + return; + } + + fs::FileHandle file; + if (R_FAILED(fs::OpenFile(std::addressof(file), Path, fs::OpenMode_Write))) { + return; + } + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + if (R_FAILED(fs::WriteFile(file, 0, sdLogBuffer, workingLength, fs::WriteOption::Flush))) { + return; + } + saved = true; + } + } diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.hpp index 6522ee13..8a1ed243 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_log.hpp @@ -23,6 +23,8 @@ namespace ams::ldr::hoc { Result SmcCopyToIram(uintptr_t dest, const void *src, u32 size); void Log(const char *data, ...); + void SdLog(const char *data, ...); + void SaveLog(); void ViewLog(); /* Emit a formatted line over the kernel debug UART via svcOutputDebugString. diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.cpp index 0d5c8900..04ec6830 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.cpp @@ -95,7 +95,7 @@ namespace ams::ldr::hoc::pcv::erista { u32 wlIndex = 0; for (u32 i = 0; i < std::size(rlMapDBI); ++i) { - if (rlMapDBI[i] == RL) { + if (rlMapDBI[i] == 32) { rlIndex = i; break; } diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.cpp index 362c31a5..7a4a1b4d 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.cpp @@ -119,28 +119,28 @@ namespace ams::ldr::hoc::pcv::erista { /* Accept imperfection or prepare for suffering. */ // #if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING) - WRITE_PARAM_ALL_REG(table, emc_einput, einput); - WRITE_PARAM_ALL_REG(table, emc_einput_duration, einput_duration); - WRITE_PARAM_ALL_REG(table, emc_obdly, obdly); + // WRITE_PARAM_ALL_REG(table, emc_einput, einput); + // WRITE_PARAM_ALL_REG(table, emc_einput_duration, einput_duration); + // WRITE_PARAM_ALL_REG(table, emc_obdly, obdly); WRITE_PARAM_ALL_REG(table, emc_ibdly, ibdly); WRITE_PARAM_ALL_REG(table, emc_wdv_mask, wdv); - WRITE_PARAM_ALL_REG(table, emc_quse_width, quse_width); - WRITE_PARAM_ALL_REG(table, emc_quse, quse); + // WRITE_PARAM_ALL_REG(table, emc_quse_width, quse_width); + // WRITE_PARAM_ALL_REG(table, emc_quse, quse); WRITE_PARAM_ALL_REG(table, emc_wdv, wdv); WRITE_PARAM_ALL_REG(table, emc_wsv, wsv); WRITE_PARAM_ALL_REG(table, emc_wev, wev); - WRITE_PARAM_ALL_REG(table, emc_qrst, qrst); - WRITE_PARAM_ALL_REG(table, emc_tr_qrst, qrst); - WRITE_PARAM_ALL_REG(table, emc_qsafe, qsafe); - WRITE_PARAM_ALL_REG(table, emc_tr_qsafe, qsafe); - WRITE_PARAM_ALL_REG(table, emc_tr_qpop, qpop); - WRITE_PARAM_ALL_REG(table, emc_qpop, qpop); - WRITE_PARAM_ALL_REG(table, emc_rdv, rdv); - WRITE_PARAM_ALL_REG(table, emc_tr_rdv_mask, rdv + 2); - WRITE_PARAM_ALL_REG(table, emc_rdv_early, rdv - 2); - WRITE_PARAM_ALL_REG(table, emc_rdv_early_mask, rdv); - WRITE_PARAM_ALL_REG(table, emc_rdv_mask, rdv + 2); - WRITE_PARAM_ALL_REG(table, emc_tr_rdv, rdv); + // WRITE_PARAM_ALL_REG(table, emc_qrst, qrst); + // WRITE_PARAM_ALL_REG(table, emc_tr_qrst, qrst); + // WRITE_PARAM_ALL_REG(table, emc_qsafe, qsafe); + // WRITE_PARAM_ALL_REG(table, emc_tr_qsafe, qsafe); + // WRITE_PARAM_ALL_REG(table, emc_tr_qpop, qpop); + // WRITE_PARAM_ALL_REG(table, emc_qpop, qpop); + // WRITE_PARAM_ALL_REG(table, emc_rdv, rdv); + // WRITE_PARAM_ALL_REG(table, emc_tr_rdv_mask, rdv + 2); + // WRITE_PARAM_ALL_REG(table, emc_rdv_early, rdv - 2); + // WRITE_PARAM_ALL_REG(table, emc_rdv_early_mask, rdv); + // WRITE_PARAM_ALL_REG(table, emc_rdv_mask, rdv + 2); + // WRITE_PARAM_ALL_REG(table, emc_tr_rdv, rdv); table->emc_mrw2 = (table->emc_mrw2 & ~0xFFu) | static_cast(mrw2); table->dram_timings.rl = RL; // #endif diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.cpp index 9702a7eb..1223493b 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.cpp @@ -34,20 +34,19 @@ namespace ams::ldr::hoc::pcv::mariko { u32 *nsoStart; namespace { - size_t g_nso_size = 0; - uintptr_t g_cave_cursor = 0; + struct { + u32 *site = nullptr; + } busInitCache; } - static uintptr_t CaveReserve(size_t count) { - if (g_pcv_cave == 0 || g_cave_cursor == 0) { - return 0; - } - if (g_cave_cursor + count * sizeof(u32) > g_pcv_cave + g_pcv_cave_size) { - return 0; - } - const uintptr_t entry = g_cave_cursor; - g_cave_cursor += count * sizeof(u32); - return entry; + DEFINE_HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData); + + namespace { + size_t g_nso_size = 0; + } + + [[maybe_unused]] static uintptr_t CaveReserve(size_t count) { + return reinterpret_cast(Hooks().Reserve(count)); } #if HOC_UART_LOG @@ -176,50 +175,27 @@ namespace ams::ldr::hoc::pcv::mariko { } #endif - /* Relocate C2/C3Bus to avoid issues*/ - Result BusFreqReloc(u32 *ptr) { - const u32 busReg = _asm::Get(ptr[0], _asm::field::Rn); /* ldr Xbuf,[Xbus,#0x10] : bus struct pointer */ - const u32 bufReg = _asm::Get(ptr[0], _asm::field::Rt); /* : freq-buffer arg */ - const u32 bufOff = _asm::Get(ptr[0], _asm::field::Off8); /* : bus->freqBuf offset */ - const u32 cntReg = _asm::Get(ptr[1], _asm::field::Rd); /* add Xcnt,Xbus,#0x18 : arg2 (&count) */ - const u32 railReg = _asm::Get(ptr[2], _asm::field::Rt); /* str Xrail,[Xbus,#0x50]: arg0 (rail) */ - u32 *call = ptr + 3; /* the bl to relocate */ - const uintptr_t realFn = _asm::Target26(*call, reinterpret_cast(call)); + HOOK_PAYLOAD_FN u64 BusFreqExtendImpl(SharedClkBus *bus) { + HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData); - /* 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; - } - } - R_UNLESS(sc == 3, ldr::ResultInvalidBusFreqReloc()); + u32 *table = data->busData.next; + bus->allowedFreqTable = table; + data->busData.next = table + EmcDvfsTableEntryCount; - 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; }; - using namespace _asm::field; - emit(_asm::RetargetAdrp(_asm::Encode(_asm::op::Adrp, {Rd, s[0]}), tramp + n * 4, region)); /* adrp s0, */ - emit(_asm::Encode(_asm::op::AddImm64, {Rd, s[0]}, {Rn, s[0]}, {Imm12, region & 0xFFFu})); /* add s0,s0,#lo */ - emit(_asm::Encode(_asm::op::LdrImm32, {Rt, s[1]}, {Rn, s[0]}, {Off4, 0x00})); /* s1 = counter */ - emit(_asm::Encode(_asm::op::AddImm64, {Rd, s[2]}, {Rn, s[1]}, {Imm12, 1})); /* s2 = counter+1 */ - emit(_asm::Encode(_asm::op::StrImm32, {Rt, s[2]}, {Rn, s[0]}, {Off4, 0x00})); /* counter++ */ - emit(_asm::Encode(_asm::op::AddImm64, {Rd, s[0]}, {Rn, s[0]}, {Imm12, 0x10})); /* s0 = region+0x10 (buffers) */ - emit(_asm::Encode(_asm::op::AddShifted64, {Rd, bufReg}, {Rn, s[0]}, {Rm, s[1]}, {Imm6, 10})); /* Xbuf = s0 + counter*0x400 */ - emit(_asm::Encode(_asm::op::StrImm64, {Rt, bufReg}, {Rn, busReg}, {Off8, bufOff})); /* bus[freqBuf] = Xbuf */ - emit(_asm::Retarget26(_asm::op::B, tramp + n * 4, realFn)); /* tail-call the real function */ - - PATCH_OFFSET(call, _asm::Retarget26(_asm::op::Bl, reinterpret_cast(call), tramp)); - const uintptr_t base = reinterpret_cast(nsoStart); - (void) base; - 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(); + /* Call the original function. */ + return reinterpret_cast(data->busData.originalFnCallback)(bus); } + /* Relocate and extend C2/C3Bus to avoid issues (likely buffer overflow) */ + Result BusFreqReloc(u32 *ptr) { + constexpr u32 PrologueMargin = 140; + u32 *functionPrologue = _asm::FindFnPrologue(ptr, PrologueMargin, nsoStart); + R_UNLESS(functionPrologue != nullptr, ldr::ResultInvalidBusFreqReloc()); + + busInitCache.site = functionPrologue; + + R_SUCCEED(); + } #if HOC_UART_LOG /* Force GetEffectiveVerbosityLevel to return a non-zero level so all NvLog runs. */ @@ -531,12 +507,35 @@ namespace ams::ldr::hoc::pcv::mariko { R_SUCCEED(); } + Result SharedClkBusInstallHooks(HookPayloadData *data) { + R_UNLESS(busInitCache.site != nullptr, ldr::ResultInvalidBusFreqReloc()); + + uintptr_t originalFn = 0; + R_TRY(INSTALL_IMPL_HOOK_ORIG(busInitCache.site, BusFreqExtendImpl, &originalFn)); + data->busData.originalFnCallback = originalFn; + data->busData.next = reinterpret_cast(Hooks().ToVa(data->busData.table[0])); + + R_SUCCEED(); + } + + Result InstallHooks() { + R_TRY(Hooks().CheckEnabled()); + + R_TRY(Hooks().CopyPayload()); + + auto *data = Hooks().BindData(m_HookPayloadData); + R_UNLESS(data != nullptr, ldr::ResultHookDataOutOfMemory()); + + R_TRY(SharedClkBusInstallHooks(data)); + + R_SUCCEED(); + } + void Patch(uintptr_t mapped_nso, size_t nso_size) { 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) */ MtcGenerateFreqTables(); @@ -593,6 +592,10 @@ namespace ams::ldr::hoc::pcv::mariko { CRASH(entry.description); } } + + if (R_FAILED(InstallHooks())) { + panic::SmcError(panic::Patch); + } } } diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.hpp index 97f6bcab..cc99ba21 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.hpp @@ -26,6 +26,17 @@ namespace ams::ldr::hoc::pcv::mariko { + constexpr u32 BusFreqTableCount = 2; + + struct HookPayloadData { + struct { + u32 *next; /* Save the bytes. */ + uintptr_t originalFnCallback; + u32 table[BusFreqTableCount][EmcDvfsTableEntryCount]; /* Original bus table size: 32. */ + } busData; + }; + DECLARE_HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData); + extern u32 *nsoStart; struct DvbEntry { 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 f852a2ee..c5d030a8 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_asm.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_asm.hpp @@ -245,11 +245,25 @@ namespace ams::ldr::hoc::pcv::_asm { return ScanAssembly(ptr, scanLimit, pattern, [=](u32 a, u32 b) ALWAYS_INLINE_LAMBDA { return Ignoring(a, b, ignoreFields...); }); } - constexpr bool IsFramePush(u32 ins) { + constexpr bool IsFramePushPre(u32 ins) { constexpr u32 Pattern = Encode(op::StpPreImm64, {field::Rt, reg::Fp}, {field::Rt2, reg::Lr}, {field::Rn, reg::Sp}); return Ignoring(ins, Pattern, field::PairOff8); } + constexpr bool IsFramePushOffset(u32 ins) { + constexpr u32 Pattern = Encode(op::StpImm64, {field::Rt, reg::Fp}, {field::Rt2, reg::Lr}, {field::Rn, reg::Sp}); + return Ignoring(ins, Pattern, field::PairOff8); + } + + constexpr bool IsSpSubImm(u32 ins) { + constexpr u32 Pattern = Encode(op::SubImm64, {field::Rd, reg::Sp}, {field::Rn, reg::Sp}); + return Ignoring(ins, Pattern, field::Imm12); + } + + constexpr bool IsFramePush(u32 ins) { + return IsFramePushPre(ins) || IsFramePushOffset(ins); + } + inline u32 *FindFnPrologue(u32 *ptr, u32 margin, u32 *nsoStart) { for (u32 i = 0; i <= margin; ++i) { u32 *candidate = ptr - i; @@ -257,9 +271,13 @@ namespace ams::ldr::hoc::pcv::_asm { break; } - if (IsFramePush(*candidate)) { + if (IsFramePushPre(*candidate)) { return candidate; } + + if (IsFramePushOffset(*candidate) && candidate - 1 >= nsoStart && IsSpSubImm(candidate[-1])) { + return candidate - 1; + } } return nullptr; diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_common.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_common.hpp index 1d3b9232..c888e77d 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_common.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_common.hpp @@ -163,6 +163,30 @@ namespace ams::ldr::hoc::pcv { }; static_assert(sizeof(CvbMeta) == 0x78); + struct SubClockStub { + u8 _unknown[0x48]; + }; + + struct SharedClkBus { + u64 moduleId; + u32 currentRateHz; + u32 padding0; + u32 *allowedFreqTable; + u32 allowedFreqCount; + u32 padding1; + SubClockStub *subClocksTable; + u32 subClockCount; + u32 padding2; + void *unk30; + void *clockSource; + u32 targetRateHz; + u32 saveDivFlag; + u32 idleRateHz; /* Unsure. */ + u32 alwaysOnFlag; + u64 clockSession; + }; + static_assert(sizeof(SharedClkBus) == 0x58); + constexpr u32 CpuClkOSLimit = 1785'000; constexpr u32 GpuClkOsLimit = 921'600; constexpr u32 EmcClkOSLimit = 1600'000; diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp index fcc6a47a..d4a5eca5 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp @@ -18,7 +18,8 @@ #include "../oc_common.hpp" -#define HOOK_PAYLOAD_FN __attribute__((section("hoc_hookpayload"), used, noinline, visibility("hidden"))) +/* aligned(4) is the ARM64 instruction alignment. This saves some memory over 16 byte padding */ +#define HOOK_PAYLOAD_FN __attribute__((section("hoc_hookpayload"), used, noinline, visibility("hidden"), aligned(4))) /* Inline asm because fuck compilers: GCC refuses to put a variable and a function in the same section. */ /* It wants alloc+write for one and alloc+exec for the other. */ @@ -147,6 +148,10 @@ namespace ams::ldr::hoc::pcv { } uintptr_t CaveVa() const { return ToVa(reinterpret_cast(m_cave)); } + uintptr_t CaveBase() const { return m_cave; } + + /* Todo remove? */ + u32 *Reserve(size_t words) { return this->AllocCode(words); } Result CopyPayload() { R_TRY(this->CheckEnabled()); @@ -284,6 +289,8 @@ namespace ams::ldr::hoc::pcv { Result ValidatePayloadFn(const void *fn) const { constexpr u32 MaxInstructions = 512; constexpr u32 RetInsn = 0xD65F03C0u; + constexpr u32 BrMask = 0xFFFFFC1Fu; + constexpr u32 BrInsn = 0xD61F0000u; const uintptr_t lo = m_payload; const uintptr_t hi = m_payload + m_payload_sz; @@ -302,7 +309,7 @@ namespace ams::ldr::hoc::pcv { const u32 insn = insns[i]; const uintptr_t site = base + i * sizeof(u32); - if (insn == RetInsn) { + if (insn == RetInsn || (insn & BrMask) == BrInsn) { R_SUCCEED(); } @@ -334,7 +341,7 @@ namespace ams::ldr::hoc::pcv { private: u32 *AllocCode(size_t words) { const size_t bytes = words * sizeof(u32); - if (!this->IsEnabled() || m_used + bytes > m_cave_size) { + if (!this->IsEnabled() || m_payload == 0 || m_used + bytes > m_cave_size) { return nullptr; }