From 5dacd30dd30a0f0da43d41129aa0ba9ca1631e52 Mon Sep 17 00:00:00 2001 From: Lightos1 <124387232+Lightos1@users.noreply.github.com> Date: Fri, 11 Sep 2026 14:16:29 +0200 Subject: [PATCH] ldr: code cleanup --- .../source/oc/pcv/mariko/pcv_mariko.cpp | 573 +----------------- .../source/oc/pcv/mariko/pcv_mariko.hpp | 146 ----- .../source/oc/pcv/mariko/pcv_mariko_log.cpp | 237 ++++++++ .../source/oc/pcv/mariko/pcv_mariko_log.hpp | 64 ++ .../source/oc/pcv/mariko/pcv_mariko_mtc.cpp | 52 ++ .../source/oc/pcv/mariko/pcv_mariko_mtc.hpp | 17 + .../source/oc/pcv/mariko/pcv_mariko_soc.cpp | 325 ++++++++++ .../source/oc/pcv/mariko/pcv_mariko_soc.hpp | 132 ++++ 8 files changed, 831 insertions(+), 715 deletions(-) create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_log.cpp create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_log.hpp create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_soc.cpp create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_soc.hpp 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 b41f2061..4db531f9 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 @@ -27,573 +27,17 @@ #include "pcv_mariko_cpu.hpp" #include "pcv_mariko_gpu.hpp" #include "pcv_mariko_mtc.hpp" +#include "pcv_mariko_soc.hpp" +#include "pcv_mariko_log.hpp" #include "calculate_timings_mariko.hpp" namespace ams::ldr::hoc::pcv::mariko { u32 *nsoStart; - - namespace { - struct { - u32 *site = nullptr; - } busInitCache; - } + size_t g_nso_size = 0; DEFINE_HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData); - namespace { - size_t g_nso_size = 0; - } - - /* Todo: Remove this bs. */ - [[maybe_unused]] static uintptr_t CaveReserve(size_t count) { - return reinterpret_cast(Hooks().Reserve(count)); - } - - #if HOC_UART_LOG - namespace { - struct { - uintptr_t vsnprintf_addr = 0; - uintptr_t nvlog_addr = 0; - } nvLogCache; - } - - 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); - - { - using namespace _asm::field; - R_UNLESS(_asm::Ignoring(ptr[1], NvLogStpFpLrAsm, PairOff8), ldr::ResultInvalidNvLogRedirect()); /* stp x29,x30,[sp,#imm] */ - R_UNLESS(_asm::Ignoring(ptr[2], NvLogStrSpillAsm, Rt, Off8), ldr::ResultInvalidNvLogRedirect()); /* str x?,[sp,#imm] */ - R_UNLESS(_asm::Ignoring(ptr[3], NvLogMovFpAsm, Imm12), ldr::ResultInvalidNvLogRedirect()); /* add x29,sp,#imm */ - R_UNLESS(ptr[4] == NvLogCmpSizeAsm, ldr::ResultInvalidNvLogRedirect()); /* cmp x1,#0 */ - } - - /* NvLog via the VDD_SOC log */ - static const char Fmt[] = "%s(%s): DVFS request VDD_SOC %d mV\n"; - constexpr size_t FmtLen = sizeof(Fmt) - 1; - uintptr_t strAddr = 0; - { - const char *hay = reinterpret_cast(mapped_nso); - for (size_t i = 0; i + FmtLen <= nso_size; ++i) { - if (std::memcmp(hay + i, Fmt, FmtLen) == 0) { strAddr = mapped_nso + i; break; } - } - } - if (strAddr == 0) { - LOGGING("NvLogRedirect: fmt string not found (vsnprintf@+%lx)", vsnprintf_addr - mapped_nso); - R_THROW(ldr::ResultInvalidNvLogRedirect()); - } - - uintptr_t nvlog_addr = 0; - for (u32 *p = nsoStart; reinterpret_cast(p + 2) <= textEnd; ++p) { - const uintptr_t pc = reinterpret_cast(p); - if (!_asm::IsOp(p[0], _asm::op::Adrp, _asm::field::Rd, _asm::field::ImmAdrpHi, _asm::field::ImmAdrpLo)) { - continue; - } - if (_asm::TargetAdrp(p[0], pc) != (strAddr & ~static_cast(0xFFFu))) { - continue; - } - const u32 adrpReg = _asm::Get(p[0], _asm::field::Rd); - if (!(_asm::IsOp(p[1], _asm::op::AddImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12) - && _asm::Get(p[1], _asm::field::Rd) == adrpReg && _asm::Get(p[1], _asm::field::Rn) == adrpReg - && _asm::Get(p[1], _asm::field::Imm12) == (strAddr & 0xFFFu))) { - continue; - } - for (u32 k = 2; k <= 12 && (pc + (k + 1) * 4) <= textEnd; ++k) { - if (_asm::IsOp(p[k], _asm::op::Bl, _asm::field::Imm26)) { nvlog_addr = _asm::Target26(p[k], pc + k * 4); break; } - } - if (nvlog_addr != 0) { - break; - } - } - if (nvlog_addr == 0 || nvlog_addr < mapped_nso || nvlog_addr >= textEnd) { - LOGGING("NvLogRedirect: NvLog entry not found (fmt@+%lx)", strAddr - mapped_nso); - R_THROW(ldr::ResultInvalidNvLogRedirect()); - } - - nvLogCache.vsnprintf_addr = vsnprintf_addr; - nvLogCache.nvlog_addr = nvlog_addr; - R_SUCCEED(); - } - - Result InstallNvLogRedirect() { - const uintptr_t mapped_nso = reinterpret_cast(nsoStart); - const uintptr_t textEnd = g_pcv_cave; - const uintptr_t vsnprintf_addr = nvLogCache.vsnprintf_addr; - const uintptr_t nvlog_addr = nvLogCache.nvlog_addr; - - 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)); - R_THROW(ldr::ResultInvalidNvLogRedirect()); - } - u32 *t = reinterpret_cast(helper); - size_t n = 0; - auto emit = [&](u32 ins) { t[n] = ins; ++n; }; - - using namespace _asm::field; - constexpr u32 SP = _asm::reg::Sp; - - emit(_asm::Encode(_asm::op::SubImm64, {Rd, SP}, {Rn, SP}, {Imm12, 0x200})); - emit(_asm::Encode(_asm::op::StpImm64, {Rt, 0}, {Rt2, 1}, {Rn, SP}, {PairOff8, 0x100})); - emit(_asm::Encode(_asm::op::StpImm64, {Rt, 2}, {Rt2, 3}, {Rn, SP}, {PairOff8, 0x110})); - emit(_asm::Encode(_asm::op::StpImm64, {Rt, 4}, {Rt2, 5}, {Rn, SP}, {PairOff8, 0x120})); - emit(_asm::Encode(_asm::op::StpImm64, {Rt, 6}, {Rt2, 7}, {Rn, SP}, {PairOff8, 0x130})); - emit(_asm::Encode(_asm::op::StpImmQ, {Rt, 0}, {Rt2, 1}, {Rn, SP}, {PairOff16, 0x140})); - emit(_asm::Encode(_asm::op::StpImmQ, {Rt, 2}, {Rt2, 3}, {Rn, SP}, {PairOff16, 0x160})); - emit(_asm::Encode(_asm::op::StpImmQ, {Rt, 4}, {Rt2, 5}, {Rn, SP}, {PairOff16, 0x180})); - emit(_asm::Encode(_asm::op::StpImmQ, {Rt, 6}, {Rt2, 7}, {Rn, SP}, {PairOff16, 0x1A0})); - emit(_asm::Encode(_asm::op::StrImm64, {Rt, _asm::reg::Lr}, {Rn, SP}, {Off8, 0x1E0})); - emit(_asm::Encode(_asm::op::AddImm64, {Rd, 9}, {Rn, SP}, {Imm12, 0x200})); emit(_asm::Encode(_asm::op::StrImm64, {Rt, 9}, {Rn, SP}, {Off8, 0x1C0})); /* __stack */ - emit(_asm::Encode(_asm::op::AddImm64, {Rd, 9}, {Rn, SP}, {Imm12, 0x140})); emit(_asm::Encode(_asm::op::StrImm64, {Rt, 9}, {Rn, SP}, {Off8, 0x1C8})); /* __gr_top */ - emit(_asm::Encode(_asm::op::AddImm64, {Rd, 9}, {Rn, SP}, {Imm12, 0x1C0})); emit(_asm::Encode(_asm::op::StrImm64, {Rt, 9}, {Rn, SP}, {Off8, 0x1D0})); /* __vr_top */ - emit(_asm::Encode(_asm::op::MovnW, {Rd, 9}, {Imm16, 0x37})); emit(_asm::Encode(_asm::op::StrImm32, {Rt, 9}, {Rn, SP}, {Off4, 0x1D8})); /* __gr_offs = -56 */ - emit(_asm::Encode(_asm::op::MovnW, {Rd, 9}, {Imm16, 0x7F})); emit(_asm::Encode(_asm::op::StrImm32, {Rt, 9}, {Rn, SP}, {Off4, 0x1DC})); /* __vr_offs = -128 */ - emit(_asm::Encode(_asm::op::AddImm64, {Rd, 0}, {Rn, SP}, {Imm12, 0x00})); /* mov x0,sp (buf) */ - emit(_asm::Encode(_asm::op::MovzW, {Rd, 1}, {Imm16, 0x100})); /* size = 0x100 */ - emit(_asm::Encode(_asm::op::LdrImm64, {Rt, 2}, {Rn, SP}, {Off8, 0x100})); /* fmt (saved x0) */ - emit(_asm::Encode(_asm::op::AddImm64, {Rd, 3}, {Rn, SP}, {Imm12, 0x1C0})); /* ap */ - emit(_asm::Retarget26(_asm::op::Bl, helper + n * 4, vsnprintf_addr)); - emit(_asm::MovReg64(1, 0)); /* len = retval */ - emit(_asm::CmpImm32(1, 0x100)); - { /* b.lo +2 */ - const size_t at = n; - emit(_asm::Retarget19(_asm::Encode(_asm::op::BCond, {BCond, _asm::cond::Lo}), helper + at * 4, helper + (at + 2) * 4)); - } - emit(_asm::Encode(_asm::op::MovzW, {Rd, 1}, {Imm16, 0xFF})); /* clamp len */ - emit(_asm::Encode(_asm::op::AddImm64, {Rd, 0}, {Rn, SP}, {Imm12, 0x00})); /* mov x0,sp (str) */ - emit(_asm::Encode(_asm::op::Svc, {Imm16, 0x27})); /* svcOutputDebugString */ - emit(_asm::Encode(_asm::op::LdrImm64, {Rt, _asm::reg::Lr}, {Rn, SP}, {Off8, 0x1E0})); - emit(_asm::Encode(_asm::op::AddImm64, {Rd, SP}, {Rn, SP}, {Imm12, 0x200})); - emit(_asm::RetIns); - - /* Redirect the call sites as patching the actual function causes crash */ - const uintptr_t roStart = g_pcv_cave + g_pcv_cave_size; /* module .rodata start */ - size_t patchedSites = 0; - for (u32 *p = nsoStart; reinterpret_cast(p + 1) <= textEnd; ++p) { - if (!_asm::IsOp(*p, _asm::op::Bl, _asm::field::Imm26)) { - continue; - } - const uintptr_t pc = reinterpret_cast(p); - if (_asm::Target26(*p, pc) != nvlog_addr) { - continue; - } - bool isFmtCall = false; - for (u32 j = 1; j <= 8 && reinterpret_cast(p - j) >= reinterpret_cast(nsoStart); ++j) { - const u32 w = *(p - j); - if (_asm::IsOp(w, _asm::op::Adrp, _asm::field::Rd, _asm::field::ImmAdrpHi, _asm::field::ImmAdrpLo) && _asm::Get(w, _asm::field::Rd) == 0) { /* adrp x0, */ - const uintptr_t tgtPage = _asm::TargetAdrp(w, pc - j * 4); - if (tgtPage >= (roStart & ~static_cast(0xFFFu))) { isFmtCall = true; break; } - } - } - if (isFmtCall) { - PATCH_OFFSET(p, _asm::Retarget26(_asm::op::Bl, pc, helper)); - ++patchedSites; - } - } - - 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(); - } - #endif - - HOOK_PAYLOAD_FN u64 BusFreqExtendImpl(SharedClkBus *bus) { - HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData); - - u32 *table = data->busData.next; - bus->allowedFreqTable = table; - data->busData.next = table + EmcDvfsTableEntryCount; - - /* 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) { - using namespace _asm::field; - - const u32 bus = _asm::Get(ptr[0], Rn); - R_UNLESS(_asm::IsOp(ptr[1], _asm::op::AddImm64, Rd, Rn, Imm12) && _asm::Get(ptr[1], Imm12) == 0x18 && _asm::Get(ptr[1], Rn) == bus, - ldr::ResultInvalidBusFreqReloc()); /* add Xcnt,Xbus,#0x18 */ - R_UNLESS(_asm::IsOp(ptr[2], _asm::op::StrImm64, Rt, Rn, Off8) && _asm::Get(ptr[2], Off8) == 0x50 && _asm::Get(ptr[2], Rn) == bus, - ldr::ResultInvalidBusFreqReloc()); /* str Xrail,[Xbus,#0x50] */ - R_UNLESS(_asm::IsOp(ptr[3], _asm::op::Bl, Imm26), ldr::ResultInvalidBusFreqReloc()); /* bl GetDvfsRailUniqueFreqList */ - - 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 - namespace { - struct { - u32 *sites[3] = {}; - u32 count = 0; - } forceVerbosityCache; - } - - HOOK_PAYLOAD_FN u32 ForceVerbosityImpl() { - HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData); - return data->verbosityLevel; - } - - /* Force GetEffectiveVerbosityLevel to return a non-zero level so all NvLog runs. */ - Result ForceVerbosity(u32 *ptr) { - using namespace _asm::field; - - R_UNLESS(_asm::IsOp(ptr[1], _asm::op::StrImm64, Rt, Rn, Off8) && _asm::Get(ptr[1], Rn) == _asm::reg::Sp, - ldr::ResultInvalidForceVerbosityPattern()); /* str x?,[sp,#imm] */ - R_UNLESS(_asm::IsOp(ptr[2], _asm::op::AddImm64, Rd, Rn, Imm12) - && _asm::Get(ptr[2], Rd) == _asm::reg::Fp && _asm::Get(ptr[2], Rn) == _asm::reg::Sp, - ldr::ResultInvalidForceVerbosityPattern()); /* add x29,sp,#imm */ - R_UNLESS(_asm::IsOp(ptr[3], _asm::op::AddImm64, Rn, Imm12) - && _asm::Get(ptr[3], Rd) == 0 && _asm::Get(ptr[3], Rn) == _asm::reg::Fp, - ldr::ResultInvalidForceVerbosityPattern()); /* add x0,x29,#imm */ - R_UNLESS(_asm::IsOp(ptr[4], _asm::op::AddImm64, Rd, Rn, Imm12) && _asm::Get(ptr[4], Rn) == _asm::reg::Fp, - ldr::ResultInvalidForceVerbosityPattern()); /* add x?,x29,#imm */ - R_UNLESS(_asm::Get(ptr[3], Imm12) == _asm::Get(ptr[4], Imm12) - && _asm::IsOp(ptr[5], _asm::op::Bl, Imm26), - ldr::ResultInvalidForceVerbosityPattern()); - - bool foundCmp = false; - for (u32 j = 6; j <= 10; ++j) { - if (ptr[j] == 0x7100001Fu) { /* cmp w0,#0 */ - foundCmp = true; - break; - } - } - R_UNLESS(foundCmp, ldr::ResultInvalidForceVerbosityPattern()); - - R_UNLESS(forceVerbosityCache.count < std::size(forceVerbosityCache.sites), ldr::ResultInvalidForceVerbosityPattern()); - forceVerbosityCache.sites[forceVerbosityCache.count++] = ptr; - R_SUCCEED(); - } - #endif - - /* 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) { - constexpr u32 Window = 48; - - using namespace _asm::field; - - R_UNLESS(_asm::Ignoring(ptr[1], EmcSocLutCountStoreAsm, Rt), ldr::ResultInvalidEmcSocLut()); /* str w?,[x0,#0x154] */ - - u32 *freqStore = _asm::ScanAssembly(ptr - Window, Window, EmcSocFreqStoreAsm, Rt); /* str x?,[x8,#0x18] */ - u32 *voltStore = _asm::ScanAssembly(ptr - Window, Window, EmcSocVoltStoreAsm, Rt); /* str w?,[x8,#0x48] */ - u32 *readLoad = _asm::ScanAssembly(ptr - Window, Window, EmcSocReadLoadAsm, Rt); /* 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(_asm::IsOp(*voltBase, _asm::op::AddShifted64, Rd, Rn, Rm, Imm6) && _asm::Get(*voltBase, Rd) == _asm::Get(*voltStore, Rn), ldr::ResultInvalidEmcSocLut()); - - /* adrp Xl ; add Xl,Xl,#off ; ... ; str Xl,[rail,#0x120] */ - const u32 lutReg = _asm::Get(ptr[0], Rt); - const u32 railReg = _asm::Get(ptr[0], Rn); - R_UNLESS(_asm::IsOp(ptr[-3], _asm::op::Adrp, Rd, ImmAdrpHi, ImmAdrpLo) && _asm::Get(ptr[-3], Rd) == lutReg, ldr::ResultInvalidEmcSocLut()); - R_UNLESS(_asm::IsOp(ptr[-2], _asm::op::AddImm64, Rd, Rn, Imm12) && _asm::Get(ptr[-2], Rd) == lutReg && _asm::Get(ptr[-2], Rn) == lutReg, ldr::ResultInvalidEmcSocLut()); - - const u32 srcBase = _asm::Get(*voltBase, Rn); /* rail ptr at +0x20 */ - const u32 wBase = _asm::Get(*voltBase, Rd); /* base reg */ - const u32 wIdx = _asm::Get(*voltBase, Rm); /* loop index */ - - PATCH_OFFSET(freqStore, _asm::NopIns); /* Unneeded */ - PATCH_OFFSET(voltBase, _asm::Encode(_asm::op::LdrImm64, {Rt, wBase}, {Rn, srcBase}, {Off8, 0x20})); /* ldr Xb,[Xsrc,#0x20] (rail) */ - PATCH_OFFSET(voltStore - 1, _asm::Encode(_asm::op::AddImm64, {Rd, wBase}, {Rn, wBase}, {Imm12, 0x18})); /* add Xb,Xb,#0x18 (was cmn) */ - PATCH_OFFSET(voltStore, _asm::ToRegOffset(*voltStore, wIdx)); /* str Wv,[Xb,Xi,LSL#2] -> rail+0x18+i*4 */ - PATCH_OFFSET(voltStore + 1, _asm::NopIns); /* Unneeded */ - - /* rail+0x18 as the socMinLut pointer. */ - PATCH_OFFSET(ptr - 3, _asm::NopIns); - PATCH_OFFSET(ptr - 2, _asm::Encode(_asm::op::AddImm64, {Rd, lutReg}, {Rn, railReg}, {Imm12, 0x18})); /* add Xl,rail,#0x18 */ - - /* Drop the abort branch in case of a bad read */ - for (u32 i = 1; i <= 4; ++i) { - if (_asm::IsOp(readLoad[i], _asm::op::BCond, Imm19, BCond)) { - PATCH_OFFSET(&readLoad[i], _asm::NopIns); - break; - } - } - R_SUCCEED(); - } - - Result EmcDvfsCountLimit(u32 *ptr) { - R_UNLESS(_asm::IsOp(*(ptr - 1), _asm::op::Cbz, _asm::field::Imm19, _asm::field::Rt), ldr::ResultInvalidEmcDvfsCount()); /* cbz w?, */ - R_UNLESS(_asm::Ignoring(*(ptr + 1), _asm::Encode(_asm::op::BCond, {_asm::field::BCond, _asm::cond::Cs}), _asm::field::Imm19), - ldr::ResultInvalidEmcDvfsCount()); /* b.cs */ - - /* cmp w?,#0x21 -> cmp w?,#EmcDvfsTableEntryCount */ - PATCH_OFFSET(ptr, _asm::Set(*ptr, _asm::field::Imm12, EmcDvfsTableEntryCount)); - R_SUCCEED(); - } - - Result EmcRateListLimit(u32 *ptr) { - R_UNLESS(_asm::Ignoring(ptr[1], EmcRateCapCselAsm, _asm::field::Rd, _asm::field::Rn, _asm::field::Rm) - && _asm::Get(ptr[0], _asm::field::Rn) == _asm::Get(ptr[1], _asm::field::Rn), - ldr::ResultInvalidEmcRateList()); /* csel w?,w?,w?,lt, min(reg, 0x20) */ - R_UNLESS(_asm::IsOp(ptr[2], _asm::op::Bl, _asm::field::Imm26), ldr::ResultInvalidEmcRateList()); - - /* The csel's Rm holds the 32 cap. */ - const u32 capReg = _asm::Get(ptr[1], _asm::field::Rm); - const u32 capMov = _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, capReg}, {_asm::field::Imm16, 0x20}); /* movz w,#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, _asm::Set(*ptr, _asm::field::Imm12, EmcDvfsTableEntryCount)); /* cmp w?,#64 */ - PATCH_OFFSET(movPtr, _asm::Set(*movPtr, _asm::field::Imm16, EmcDvfsTableEntryCount)); /* movz w?,#64 */ - R_SUCCEED(); - } - - Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) { - struct { - u8 reg; - u8 val; - } __attribute__((packed)) cmd; - - I2cSession _session; - R_TRY(i2cOpenSession(&_session, dev)); - - cmd.reg = reg; - cmd.val = val; - Result res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All); - i2csessionClose(&_session); - - return res; - } - - Result EmcVddqVolt(u32 *ptr) { - regulator *entry = reinterpret_cast(reinterpret_cast(ptr) - offsetof(regulator, type_2_3.default_uv)); - - constexpr u32 uv_step = 5'000; - constexpr u32 uv_min = 250'000; - - auto validator = [entry]() { - R_UNLESS(entry->id == 2, ldr::ResultInvalidRegulatorEntry()); - R_UNLESS(entry->type == 3, ldr::ResultInvalidRegulatorEntry()); - R_UNLESS(entry->type_2_3.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry()); - R_UNLESS(entry->type_2_3.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry()); - R_SUCCEED(); - }; - - R_TRY(validator()); - - u32 emc_uv = C.marikoEmcVddqVolt; - - if (!emc_uv) { - R_SKIP(); - } - - if (emc_uv % uv_step) { - emc_uv = (emc_uv + uv_step - 1) / uv_step * uv_step; // rounding - } - - PATCH_OFFSET(ptr, emc_uv); - - i2cInitialize(); - Result resultI2C = I2cSet_U8(I2cDevice_Max77812_2, 0x25, (emc_uv - uv_min) / uv_step); - i2cExit(); - - return resultI2C; - } - - 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(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) { - using namespace _asm::field; - - constexpr u32 VoltageScanLimit = 10; - /* Might actually be speedo id. */ - u32 *writeProcessId = _asm::ScanAssembly(compareSpeedos, VoltageScanLimit, SocVoltWriteProcessIdAsm, Rd); - R_UNLESS(writeProcessId != nullptr, ldr::ResultInvalidSocVoltPattern()); - u8 writeProcessIdRd = _asm::Get(*writeProcessId, Rd); - - /* This writes 1050mV. */ - u32 *writeVoltage = _asm::ScanAssembly(writeProcessId, VoltageScanLimit, SocVoltWriteVoltageAsm, Rd); - R_UNLESS(writeVoltage != nullptr, ldr::ResultInvalidSocVoltPattern()); - u8 writeVoltageRd = _asm::Get(*writeVoltage, Rd); - - /* 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 = _asm::ScanAssembly(writeVoltage, VoltageSelectScanLimit, SocVoltSelectRegisterAsm, Rd, Rn, Rm); - R_UNLESS(selectVoltage != nullptr, ldr::ResultInvalidSocVoltPattern()); - /* Todo: check rm and rn? */ - u8 selectVoltageRd = _asm::Get(*selectVoltage, Rd); - - /* rdCsel is then multiplied by 1000 to convert to uV. */ - /* This is pretty far down the function. */ - constexpr u32 MultiplierScanLimit = 200; - u32 *multiplier = _asm::ScanAssembly(selectVoltage, MultiplierScanLimit, SocVoltMultiplyVoltsAsm, Rd, Rn, Rm); - R_UNLESS(multiplier != nullptr, ldr::ResultInvalidSocVoltPattern()); - u8 multiplierRn = _asm::Get(*multiplier, Rn); - u8 multiplierRm = _asm::Get(*multiplier, Rm); - /* One of the two registers has to be rdCsel. */ - R_UNLESS((multiplierRn == selectVoltageRd) || (multiplierRm == selectVoltageRd), ldr::ResultInvalidSocVoltPattern()); - u8 multiplierRd = _asm::Get(*multiplier, Rd); - - /* Subs instruction is then used to verify against absolute limit. */ - /* NB: Rn is written into the pattern and then ignored by the comparison; only Rm is matched. */ - u32 limitValidationPattern = _asm::Set(SocVoltValidateLimitAsm, Rn, multiplierRd); - u32 *limitValidation = _asm::ScanAssembly(multiplier, VoltageScanLimit, limitValidationPattern, Rd, Rn); - R_UNLESS(limitValidation != nullptr, ldr::ResultInvalidSocVoltPattern()); - - /* There is a b.gt instruction right after (checks for socVoltageCap < socVoltageMax). */ - u32 *branchToAbort = limitValidation + 1; - R_UNLESS(_asm::Ignoring(*branchToAbort, SocVoltBranchToAbortAsm, Imm19), 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 = _asm::Set(*compareSpeedos, Imm12, 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::Encode(_asm::op::MovzW, {Rd, writeProcessIdRd}, {Imm16, processId}); - PATCH_OFFSET(writeProcessId, writeProcessIdPatch); - - /* Adjust voltage limit. */ - u32 voltageLimitPatch = _asm::Encode(_asm::op::MovzW, {Rd, writeVoltageRd}, {Imm16, C.marikoSocVmax}); - PATCH_OFFSET(writeVoltage, voltageLimitPatch); - - /* Branches to an abort if limits are invalid -- we patch the branch instruction with NOP. */ - PATCH_OFFSET(branchToAbort, _asm::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(); - } - - 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 (_asm::IsOp(w, _asm::op::SubImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12) - && _asm::Get(w, _asm::field::Rn) == _asm::reg::Fp && _asm::Get(w, _asm::field::Imm12) >= 0x20u) { /* sub x?,x29,#>=0x20 */ - R_THROW(ldr::ResultInvalidEmcRateList()); - } - } - - /* mov x,x2 */ - u32 descReg = 0xFFu; - for (u32 i = 1; i <= 24; ++i) { - if (_asm::Ignoring(ptr[i], _asm::MovReg64(0, 2), _asm::field::Rd)) { descReg = _asm::Get(ptr[i], _asm::field::Rd); 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 (_asm::IsOp(w, _asm::op::AddImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12) && _asm::Get(w, _asm::field::Rn) == _asm::reg::Sp) { /* add x?,sp,#imm12 (shift 0) */ - adds[nAdds] = ptr + i; - addImm[nAdds] = _asm::Get(w, _asm::field::Imm12); - ++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], _asm::Encode(_asm::op::LdrImm64, {_asm::field::Rt, _asm::Get(*adds[a], _asm::field::Rd)}, - {_asm::field::Rn, descReg}, - {_asm::field::Off8, 0})); /* ldr x?,[x] */ - ++patched; - } - } - R_UNLESS(patched == 2, ldr::ResultInvalidEmcRateList()); - - /* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount) */ - PATCH_OFFSET(ptr, _asm::Set(*ptr, _asm::field::Imm12, EmcDvfsTableEntryCount)); /* cmp w?,#64 */ - PATCH_OFFSET(ptr + movzI, _asm::Set(*(ptr + movzI), _asm::field::Imm16, EmcDvfsTableEntryCount)); /* movz w?,#64 */ - 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()); @@ -605,16 +49,7 @@ namespace ams::ldr::hoc::pcv::mariko { R_TRY(SharedClkBusInstallHooks(data)); #if HOC_UART_LOG - if (nvLogCache.nvlog_addr != 0) { - R_DISCARD(InstallNvLogRedirect()); - } - - if (forceVerbosityCache.count != 0 && C.pcvLogVerbosity != 0xff) { - data->verbosityLevel = C.pcvLogVerbosity; - for (u32 i = 0; i < forceVerbosityCache.count; ++i) { - R_DISCARD(INSTALL_IMPL_HOOK(forceVerbosityCache.sites[i], ForceVerbosityImpl)); - } - } + R_TRY(ForceVebosityInstallHooks(data)); #endif R_SUCCEED(); 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 a3381515..0bc99c18 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 @@ -40,152 +40,6 @@ namespace ams::ldr::hoc::pcv::mariko { }; DECLARE_HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData); - extern u32 *nsoStart; - - struct DvbEntry { - u64 freq; - u32 volt[4] = {}; - }; - - constexpr DvbEntry EmcDvbTableDefault[] = { - { 204000, { 637, 637, 637, } }, - { 408000, { 637, 637, 637, } }, - { 800000, { 637, 637, 637, } }, - { 1065600, { 637, 637, 637, } }, - { 1331200, { 650, 637, 637, } }, - { 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::Ignoring(*ptr, SocVoltCompareSpeedoAsm, _asm::field::Rd); - } - - 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 EmcCountCmpAsm = 0x7100851F; /* cmp w?,#0x21 (subs wzr,w?,#0x21) */ - - /* - str ,[,#0x120] ; volt-array pointer - str w?, [,#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) */ - - inline bool EmcDvfsCountPatternFn(u32 *ptr) { - return _asm::Ignoring(*ptr, EmcCountCmpAsm, _asm::field::Rd); - } - - inline bool EmcSocLutPatternFn(u32 *ptr) { - return _asm::Ignoring(*ptr, EmcSocLutPtrStoreAsm, _asm::field::Rt); /* str x?,[x0,#0x120] */ - } - - /* - 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?,,,lt ; min(maxCount, 32) - bl <_asm::Get*DvfsFreqTable> - */ - constexpr u32 EmcRateCapCmpAsm = 0x710082FF; /* cmp w?,#0x20 */ - constexpr u32 EmcRateCapCselAsm = 0x1A80B000; /* csel w?,w?,w?,lt */ - - inline bool EmcRateListPatternFn(u32 *ptr) { - return _asm::Ignoring(*ptr, EmcRateCapCmpAsm, _asm::field::Rd, _asm::field::Rn); - } - - 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) { - const u32 c = _asm::Get(ptr[0], _asm::field::Rn); - for (u32 i = 1; i <= 14; ++i) { - const u32 w = ptr[i]; - if (_asm::Ignoring(w, EmcRateSessCselAsm, _asm::field::Rd, _asm::field::Rn, _asm::field::Rm) && _asm::Get(w, _asm::field::Rn) == c && _asm::Get(w, _asm::field::Rd) == c) { - const u32 cap = _asm::Get(w, _asm::field::Rm); - for (u32 j = 1; j < i; ++j) { - if (_asm::Ignoring(ptr[j], EmcRateSessMovAsm, _asm::field::Rd) && _asm::Get(ptr[j], _asm::field::Rd) == 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 _asm::Ignoring(*ptr, EmcRateSessCmpAsm, _asm::field::Rd, _asm::field::Rn); - } - - inline bool BusFreqRelocPatternFn(u32 *ptr) { - if (g_pcv_cave == 0) { - return false; - } - if (reinterpret_cast(ptr + 4) > g_pcv_cave) { /* the call site lives in .text */ - return false; - } - return _asm::IsOp(*ptr, _asm::op::LdrImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Off8) - && _asm::Get(*ptr, _asm::field::Off8) == 0x10; /* ldr Xbuf,[Xbus,#0x10] */ - } - - inline bool ForceVerbosityPatternFn(u32 *ptr) { - if (g_pcv_cave == 0) { - return false; - } - if (reinterpret_cast(ptr + 11) > g_pcv_cave) { /* .text only */ - return false; - } - return _asm::IsFramePushPre(*ptr); - } - - /* - sub sp, sp, #imm - stp x29,x30,[sp,#imm] - str x?, [sp,#imm] - add x29,sp, #imm - subs xzr, x1, #0 - */ - constexpr u32 NvLogSubSpAsm = 0xD10003FFu; /* sub sp,sp,#0 */ - constexpr u32 NvLogStpFpLrAsm = 0xA9007BFDu; /* stp x29,x30,[sp,#0] */ - constexpr u32 NvLogStrSpillAsm = 0xF90003E0u; /* str x?,[sp,#0] */ - constexpr u32 NvLogMovFpAsm = 0x910003FDu; /* add x29,sp,#0 */ - constexpr u32 NvLogCmpSizeAsm = 0xF100003Fu; /* subs xzr,x1,#0 */ - - inline bool NvLogVsnprintfPatternFn(u32 *ptr) { - if (HOC_UART_LOG == 0 || g_pcv_cave == 0) { - return false; - } - if (reinterpret_cast(ptr + 5) > g_pcv_cave) { /* must sit in .text */ - return false; - } - return _asm::Ignoring(*ptr, NvLogSubSpAsm, _asm::field::Imm12); - } - void Patch(uintptr_t mapped_nso, size_t nso_size); } diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_log.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_log.cpp new file mode 100644 index 00000000..5f24e677 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_log.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (c) Souldbminer, Lightos and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../pcv.hpp" +#include "pcv_mariko_log.hpp" + +namespace ams::ldr::hoc::pcv::mariko { + + /* Todo: Remove this bs. */ + [[maybe_unused]] static uintptr_t CaveReserve(size_t count) { + return reinterpret_cast(Hooks().Reserve(count)); + } + + #if HOC_UART_LOG + namespace { + struct { + uintptr_t vsnprintf_addr = 0; + uintptr_t nvlog_addr = 0; + } nvLogCache; + } + + 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); + + { + using namespace _asm::field; + R_UNLESS(_asm::Ignoring(ptr[1], NvLogStpFpLrAsm, PairOff8), ldr::ResultInvalidNvLogRedirect()); /* stp x29,x30,[sp,#imm] */ + R_UNLESS(_asm::Ignoring(ptr[2], NvLogStrSpillAsm, Rt, Off8), ldr::ResultInvalidNvLogRedirect()); /* str x?,[sp,#imm] */ + R_UNLESS(_asm::Ignoring(ptr[3], NvLogMovFpAsm, Imm12), ldr::ResultInvalidNvLogRedirect()); /* add x29,sp,#imm */ + R_UNLESS(ptr[4] == NvLogCmpSizeAsm, ldr::ResultInvalidNvLogRedirect()); /* cmp x1,#0 */ + } + + /* NvLog via the VDD_SOC log */ + static const char Fmt[] = "%s(%s): DVFS request VDD_SOC %d mV\n"; + constexpr size_t FmtLen = sizeof(Fmt) - 1; + uintptr_t strAddr = 0; + { + const char *hay = reinterpret_cast(mapped_nso); + for (size_t i = 0; i + FmtLen <= nso_size; ++i) { + if (std::memcmp(hay + i, Fmt, FmtLen) == 0) { strAddr = mapped_nso + i; break; } + } + } + if (strAddr == 0) { + LOGGING("NvLogRedirect: fmt string not found (vsnprintf@+%lx)", vsnprintf_addr - mapped_nso); + R_THROW(ldr::ResultInvalidNvLogRedirect()); + } + + uintptr_t nvlog_addr = 0; + for (u32 *p = nsoStart; reinterpret_cast(p + 2) <= textEnd; ++p) { + const uintptr_t pc = reinterpret_cast(p); + if (!_asm::IsOp(p[0], _asm::op::Adrp, _asm::field::Rd, _asm::field::ImmAdrpHi, _asm::field::ImmAdrpLo)) { + continue; + } + if (_asm::TargetAdrp(p[0], pc) != (strAddr & ~static_cast(0xFFFu))) { + continue; + } + const u32 adrpReg = _asm::Get(p[0], _asm::field::Rd); + if (!(_asm::IsOp(p[1], _asm::op::AddImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12) + && _asm::Get(p[1], _asm::field::Rd) == adrpReg && _asm::Get(p[1], _asm::field::Rn) == adrpReg + && _asm::Get(p[1], _asm::field::Imm12) == (strAddr & 0xFFFu))) { + continue; + } + for (u32 k = 2; k <= 12 && (pc + (k + 1) * 4) <= textEnd; ++k) { + if (_asm::IsOp(p[k], _asm::op::Bl, _asm::field::Imm26)) { nvlog_addr = _asm::Target26(p[k], pc + k * 4); break; } + } + if (nvlog_addr != 0) { + break; + } + } + if (nvlog_addr == 0 || nvlog_addr < mapped_nso || nvlog_addr >= textEnd) { + LOGGING("NvLogRedirect: NvLog entry not found (fmt@+%lx)", strAddr - mapped_nso); + R_THROW(ldr::ResultInvalidNvLogRedirect()); + } + + nvLogCache.vsnprintf_addr = vsnprintf_addr; + nvLogCache.nvlog_addr = nvlog_addr; + R_SUCCEED(); + } + + namespace { + Result InstallNvLogRedirect() { + const uintptr_t mapped_nso = reinterpret_cast(nsoStart); + const uintptr_t textEnd = g_pcv_cave; + const uintptr_t vsnprintf_addr = nvLogCache.vsnprintf_addr; + const uintptr_t nvlog_addr = nvLogCache.nvlog_addr; + + 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)); + R_THROW(ldr::ResultInvalidNvLogRedirect()); + } + u32 *t = reinterpret_cast(helper); + size_t n = 0; + auto emit = [&](u32 ins) { t[n] = ins; ++n; }; + + using namespace _asm::field; + constexpr u32 SP = _asm::reg::Sp; + + emit(_asm::Encode(_asm::op::SubImm64, {Rd, SP}, {Rn, SP}, {Imm12, 0x200})); + emit(_asm::Encode(_asm::op::StpImm64, {Rt, 0}, {Rt2, 1}, {Rn, SP}, {PairOff8, 0x100})); + emit(_asm::Encode(_asm::op::StpImm64, {Rt, 2}, {Rt2, 3}, {Rn, SP}, {PairOff8, 0x110})); + emit(_asm::Encode(_asm::op::StpImm64, {Rt, 4}, {Rt2, 5}, {Rn, SP}, {PairOff8, 0x120})); + emit(_asm::Encode(_asm::op::StpImm64, {Rt, 6}, {Rt2, 7}, {Rn, SP}, {PairOff8, 0x130})); + emit(_asm::Encode(_asm::op::StpImmQ, {Rt, 0}, {Rt2, 1}, {Rn, SP}, {PairOff16, 0x140})); + emit(_asm::Encode(_asm::op::StpImmQ, {Rt, 2}, {Rt2, 3}, {Rn, SP}, {PairOff16, 0x160})); + emit(_asm::Encode(_asm::op::StpImmQ, {Rt, 4}, {Rt2, 5}, {Rn, SP}, {PairOff16, 0x180})); + emit(_asm::Encode(_asm::op::StpImmQ, {Rt, 6}, {Rt2, 7}, {Rn, SP}, {PairOff16, 0x1A0})); + emit(_asm::Encode(_asm::op::StrImm64, {Rt, _asm::reg::Lr}, {Rn, SP}, {Off8, 0x1E0})); + emit(_asm::Encode(_asm::op::AddImm64, {Rd, 9}, {Rn, SP}, {Imm12, 0x200})); emit(_asm::Encode(_asm::op::StrImm64, {Rt, 9}, {Rn, SP}, {Off8, 0x1C0})); /* __stack */ + emit(_asm::Encode(_asm::op::AddImm64, {Rd, 9}, {Rn, SP}, {Imm12, 0x140})); emit(_asm::Encode(_asm::op::StrImm64, {Rt, 9}, {Rn, SP}, {Off8, 0x1C8})); /* __gr_top */ + emit(_asm::Encode(_asm::op::AddImm64, {Rd, 9}, {Rn, SP}, {Imm12, 0x1C0})); emit(_asm::Encode(_asm::op::StrImm64, {Rt, 9}, {Rn, SP}, {Off8, 0x1D0})); /* __vr_top */ + emit(_asm::Encode(_asm::op::MovnW, {Rd, 9}, {Imm16, 0x37})); emit(_asm::Encode(_asm::op::StrImm32, {Rt, 9}, {Rn, SP}, {Off4, 0x1D8})); /* __gr_offs = -56 */ + emit(_asm::Encode(_asm::op::MovnW, {Rd, 9}, {Imm16, 0x7F})); emit(_asm::Encode(_asm::op::StrImm32, {Rt, 9}, {Rn, SP}, {Off4, 0x1DC})); /* __vr_offs = -128 */ + emit(_asm::Encode(_asm::op::AddImm64, {Rd, 0}, {Rn, SP}, {Imm12, 0x00})); /* mov x0,sp (buf) */ + emit(_asm::Encode(_asm::op::MovzW, {Rd, 1}, {Imm16, 0x100})); /* size = 0x100 */ + emit(_asm::Encode(_asm::op::LdrImm64, {Rt, 2}, {Rn, SP}, {Off8, 0x100})); /* fmt (saved x0) */ + emit(_asm::Encode(_asm::op::AddImm64, {Rd, 3}, {Rn, SP}, {Imm12, 0x1C0})); /* ap */ + emit(_asm::Retarget26(_asm::op::Bl, helper + n * 4, vsnprintf_addr)); + emit(_asm::MovReg64(1, 0)); /* len = retval */ + emit(_asm::CmpImm32(1, 0x100)); + { /* b.lo +2 */ + const size_t at = n; + emit(_asm::Retarget19(_asm::Encode(_asm::op::BCond, {BCond, _asm::cond::Lo}), helper + at * 4, helper + (at + 2) * 4)); + } + emit(_asm::Encode(_asm::op::MovzW, {Rd, 1}, {Imm16, 0xFF})); /* clamp len */ + emit(_asm::Encode(_asm::op::AddImm64, {Rd, 0}, {Rn, SP}, {Imm12, 0x00})); /* mov x0,sp (str) */ + emit(_asm::Encode(_asm::op::Svc, {Imm16, 0x27})); /* svcOutputDebugString */ + emit(_asm::Encode(_asm::op::LdrImm64, {Rt, _asm::reg::Lr}, {Rn, SP}, {Off8, 0x1E0})); + emit(_asm::Encode(_asm::op::AddImm64, {Rd, SP}, {Rn, SP}, {Imm12, 0x200})); + emit(_asm::RetIns); + + /* Redirect the call sites as patching the actual function causes crash */ + const uintptr_t roStart = g_pcv_cave + g_pcv_cave_size; /* module .rodata start */ + size_t patchedSites = 0; + for (u32 *p = nsoStart; reinterpret_cast(p + 1) <= textEnd; ++p) { + if (!_asm::IsOp(*p, _asm::op::Bl, _asm::field::Imm26)) { + continue; + } + const uintptr_t pc = reinterpret_cast(p); + if (_asm::Target26(*p, pc) != nvlog_addr) { + continue; + } + bool isFmtCall = false; + for (u32 j = 1; j <= 8 && reinterpret_cast(p - j) >= reinterpret_cast(nsoStart); ++j) { + const u32 w = *(p - j); + if (_asm::IsOp(w, _asm::op::Adrp, _asm::field::Rd, _asm::field::ImmAdrpHi, _asm::field::ImmAdrpLo) && _asm::Get(w, _asm::field::Rd) == 0) { /* adrp x0, */ + const uintptr_t tgtPage = _asm::TargetAdrp(w, pc - j * 4); + if (tgtPage >= (roStart & ~static_cast(0xFFFu))) { isFmtCall = true; break; } + } + } + if (isFmtCall) { + PATCH_OFFSET(p, _asm::Retarget26(_asm::op::Bl, pc, helper)); + ++patchedSites; + } + } + + 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(); + } + } + + namespace { + struct { + u32 *sites[3] = {}; + u32 count = 0; + } forceVerbosityCache; + } + + HOOK_PAYLOAD_FN u32 ForceVerbosityImpl() { + HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData); + return data->verbosityLevel; + } + + /* Force GetEffectiveVerbosityLevel to return a non-zero level so all NvLog runs. */ + Result ForceVerbosity(u32 *ptr) { + using namespace _asm::field; + + R_UNLESS(_asm::IsOp(ptr[1], _asm::op::StrImm64, Rt, Rn, Off8) && _asm::Get(ptr[1], Rn) == _asm::reg::Sp, + ldr::ResultInvalidForceVerbosityPattern()); /* str x?,[sp,#imm] */ + R_UNLESS(_asm::IsOp(ptr[2], _asm::op::AddImm64, Rd, Rn, Imm12) + && _asm::Get(ptr[2], Rd) == _asm::reg::Fp && _asm::Get(ptr[2], Rn) == _asm::reg::Sp, + ldr::ResultInvalidForceVerbosityPattern()); /* add x29,sp,#imm */ + R_UNLESS(_asm::IsOp(ptr[3], _asm::op::AddImm64, Rn, Imm12) + && _asm::Get(ptr[3], Rd) == 0 && _asm::Get(ptr[3], Rn) == _asm::reg::Fp, + ldr::ResultInvalidForceVerbosityPattern()); /* add x0,x29,#imm */ + R_UNLESS(_asm::IsOp(ptr[4], _asm::op::AddImm64, Rd, Rn, Imm12) && _asm::Get(ptr[4], Rn) == _asm::reg::Fp, + ldr::ResultInvalidForceVerbosityPattern()); /* add x?,x29,#imm */ + R_UNLESS(_asm::Get(ptr[3], Imm12) == _asm::Get(ptr[4], Imm12) + && _asm::IsOp(ptr[5], _asm::op::Bl, Imm26), + ldr::ResultInvalidForceVerbosityPattern()); + + bool foundCmp = false; + for (u32 j = 6; j <= 10; ++j) { + if (ptr[j] == 0x7100001Fu) { /* cmp w0,#0 */ + foundCmp = true; + break; + } + } + R_UNLESS(foundCmp, ldr::ResultInvalidForceVerbosityPattern()); + + R_UNLESS(forceVerbosityCache.count < std::size(forceVerbosityCache.sites), ldr::ResultInvalidForceVerbosityPattern()); + forceVerbosityCache.sites[forceVerbosityCache.count++] = ptr; + R_SUCCEED(); + } + + Result ForceVebosityInstallHooks(HookPayloadData *data) { + if (nvLogCache.nvlog_addr != 0) { + R_TRY(InstallNvLogRedirect()); + } + + if (forceVerbosityCache.count != 0 && C.pcvLogVerbosity != 0xff) { + data->verbosityLevel = C.pcvLogVerbosity; + for (u32 i = 0; i < forceVerbosityCache.count; ++i) { + R_DISCARD(INSTALL_IMPL_HOOK(forceVerbosityCache.sites[i], ForceVerbosityImpl)); + } + } + } +#endif + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_log.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_log.hpp new file mode 100644 index 00000000..f9855e19 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_log.hpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) Souldbminer, Lightos and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "../pcv.hpp" + +namespace ams::ldr::hoc::pcv::mariko { + + extern u32 *nsoStart; + extern size_t g_nso_size; + + inline bool ForceVerbosityPatternFn(u32 *ptr) { + if (g_pcv_cave == 0) { + return false; + } + if (reinterpret_cast(ptr + 11) > g_pcv_cave) { /* .text only */ + return false; + } + return _asm::IsFramePushPre(*ptr); + } + + /* + sub sp, sp, #imm + stp x29,x30,[sp,#imm] + str x?, [sp,#imm] + add x29,sp, #imm + subs xzr, x1, #0 + */ + constexpr u32 NvLogSubSpAsm = 0xD10003FFu; /* sub sp,sp,#0 */ + constexpr u32 NvLogStpFpLrAsm = 0xA9007BFDu; /* stp x29,x30,[sp,#0] */ + constexpr u32 NvLogStrSpillAsm = 0xF90003E0u; /* str x?,[sp,#0] */ + constexpr u32 NvLogMovFpAsm = 0x910003FDu; /* add x29,sp,#0 */ + constexpr u32 NvLogCmpSizeAsm = 0xF100003Fu; /* subs xzr,x1,#0 */ + + inline bool NvLogVsnprintfPatternFn(u32 *ptr) { + if (HOC_UART_LOG == 0 || g_pcv_cave == 0) { + return false; + } + if (reinterpret_cast(ptr + 5) > g_pcv_cave) { /* must sit in .text */ + return false; + } + return _asm::Ignoring(*ptr, NvLogSubSpAsm, _asm::field::Imm12); + } + + Result NvLogUartRedirect(u32 *ptr); + HOOK_PAYLOAD_FN u32 ForceVerbosityImpl(); + Result ForceVerbosity(u32 *ptr); + Result ForceVebosityInstallHooks(HookPayloadData *data); + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.cpp index 3f41ecf4..21c6a2a7 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.cpp @@ -673,4 +673,56 @@ namespace ams::ldr::hoc::pcv::mariko { R_SUCCEED(); } + Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) { + struct { + u8 reg; + u8 val; + } __attribute__((packed)) cmd; + + I2cSession _session; + R_TRY(i2cOpenSession(&_session, dev)); + + cmd.reg = reg; + cmd.val = val; + Result res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All); + i2csessionClose(&_session); + + return res; + } + + Result EmcVddqVolt(u32 *ptr) { + regulator *entry = reinterpret_cast(reinterpret_cast(ptr) - offsetof(regulator, type_2_3.default_uv)); + + constexpr u32 uv_step = 5'000; + constexpr u32 uv_min = 250'000; + + auto validator = [entry]() { + R_UNLESS(entry->id == 2, ldr::ResultInvalidRegulatorEntry()); + R_UNLESS(entry->type == 3, ldr::ResultInvalidRegulatorEntry()); + R_UNLESS(entry->type_2_3.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry()); + R_UNLESS(entry->type_2_3.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry()); + R_SUCCEED(); + }; + + R_TRY(validator()); + + u32 emc_uv = C.marikoEmcVddqVolt; + + if (!emc_uv) { + R_SKIP(); + } + + if (emc_uv % uv_step) { + emc_uv = (emc_uv + uv_step - 1) / uv_step * uv_step; // rounding + } + + PATCH_OFFSET(ptr, emc_uv); + + i2cInitialize(); + Result resultI2C = I2cSet_U8(I2cDevice_Max77812_2, 0x25, (emc_uv - uv_min) / uv_step); + i2cExit(); + + return resultI2C; + } + } diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.hpp index ff05dbf5..f9c2826c 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.hpp @@ -26,6 +26,22 @@ namespace ams::ldr::hoc::pcv::mariko { + extern u32 *nsoStart; + + struct DvbEntry { + u64 freq; + u32 volt[4] = {}; + }; + + constexpr DvbEntry EmcDvbTableDefault[] = { + { 204000, { 637, 637, 637, } }, + { 408000, { 637, 637, 637, } }, + { 800000, { 637, 637, 637, } }, + { 1065600, { 637, 637, 637, } }, + { 1331200, { 650, 637, 637, } }, + { 1600000, { 675, 650, 637, } }, + }; + constexpr u32 EmcListDefault[] = { 204000, 1331200, 1600000, }; constexpr u32 EmcListSizeDefault = std::size(EmcListDefault); constexpr u32 EmcListEndDefault = EmcListSizeDefault - 1; @@ -185,5 +201,6 @@ namespace ams::ldr::hoc::pcv::mariko { Result MemFreqDvbTable(u32 *ptr); Result MemFreqMax(u32 *ptr); Result MemMtcTableAsm(u32 *ptr); + Result EmcVddqVolt(u32 *ptr); } diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_soc.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_soc.cpp new file mode 100644 index 00000000..432a7fab --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_soc.cpp @@ -0,0 +1,325 @@ +/* + * Copyright (c) Souldbminer, Lightos and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../pcv.hpp" +#include "pcv_mariko_soc.hpp" + +namespace ams::ldr::hoc::pcv::mariko { + + namespace { + + struct { + u32 *site = nullptr; + } busInitCache; + + 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(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) { + using namespace _asm::field; + + constexpr u32 VoltageScanLimit = 10; + /* Might actually be speedo id. */ + u32 *writeProcessId = _asm::ScanAssembly(compareSpeedos, VoltageScanLimit, SocVoltWriteProcessIdAsm, Rd); + R_UNLESS(writeProcessId != nullptr, ldr::ResultInvalidSocVoltPattern()); + u8 writeProcessIdRd = _asm::Get(*writeProcessId, Rd); + + /* This writes 1050mV. */ + u32 *writeVoltage = _asm::ScanAssembly(writeProcessId, VoltageScanLimit, SocVoltWriteVoltageAsm, Rd); + R_UNLESS(writeVoltage != nullptr, ldr::ResultInvalidSocVoltPattern()); + u8 writeVoltageRd = _asm::Get(*writeVoltage, Rd); + + /* 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 = _asm::ScanAssembly(writeVoltage, VoltageSelectScanLimit, SocVoltSelectRegisterAsm, Rd, Rn, Rm); + R_UNLESS(selectVoltage != nullptr, ldr::ResultInvalidSocVoltPattern()); + /* Todo: check rm and rn? */ + u8 selectVoltageRd = _asm::Get(*selectVoltage, Rd); + + /* rdCsel is then multiplied by 1000 to convert to uV. */ + /* This is pretty far down the function. */ + constexpr u32 MultiplierScanLimit = 200; + u32 *multiplier = _asm::ScanAssembly(selectVoltage, MultiplierScanLimit, SocVoltMultiplyVoltsAsm, Rd, Rn, Rm); + R_UNLESS(multiplier != nullptr, ldr::ResultInvalidSocVoltPattern()); + u8 multiplierRn = _asm::Get(*multiplier, Rn); + u8 multiplierRm = _asm::Get(*multiplier, Rm); + /* One of the two registers has to be rdCsel. */ + R_UNLESS((multiplierRn == selectVoltageRd) || (multiplierRm == selectVoltageRd), ldr::ResultInvalidSocVoltPattern()); + u8 multiplierRd = _asm::Get(*multiplier, Rd); + + /* Subs instruction is then used to verify against absolute limit. */ + /* NB: Rn is written into the pattern and then ignored by the comparison; only Rm is matched. */ + u32 limitValidationPattern = _asm::Set(SocVoltValidateLimitAsm, Rn, multiplierRd); + u32 *limitValidation = _asm::ScanAssembly(multiplier, VoltageScanLimit, limitValidationPattern, Rd, Rn); + R_UNLESS(limitValidation != nullptr, ldr::ResultInvalidSocVoltPattern()); + + /* There is a b.gt instruction right after (checks for socVoltageCap < socVoltageMax). */ + u32 *branchToAbort = limitValidation + 1; + R_UNLESS(_asm::Ignoring(*branchToAbort, SocVoltBranchToAbortAsm, Imm19), 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 = _asm::Set(*compareSpeedos, Imm12, 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::Encode(_asm::op::MovzW, {Rd, writeProcessIdRd}, {Imm16, processId}); + PATCH_OFFSET(writeProcessId, writeProcessIdPatch); + + /* Adjust voltage limit. */ + u32 voltageLimitPatch = _asm::Encode(_asm::op::MovzW, {Rd, writeVoltageRd}, {Imm16, C.marikoSocVmax}); + PATCH_OFFSET(writeVoltage, voltageLimitPatch); + + /* Branches to an abort if limits are invalid -- we patch the branch instruction with NOP. */ + PATCH_OFFSET(branchToAbort, _asm::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(); + } + + HOOK_PAYLOAD_FN u64 BusFreqExtendImpl(SharedClkBus *bus) { + HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData); + + u32 *table = data->busData.next; + bus->allowedFreqTable = table; + data->busData.next = table + EmcDvfsTableEntryCount; + + /* 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) { + using namespace _asm::field; + + const u32 bus = _asm::Get(ptr[0], Rn); + R_UNLESS(_asm::IsOp(ptr[1], _asm::op::AddImm64, Rd, Rn, Imm12) && _asm::Get(ptr[1], Imm12) == 0x18 && _asm::Get(ptr[1], Rn) == bus, + ldr::ResultInvalidBusFreqReloc()); /* add Xcnt,Xbus,#0x18 */ + R_UNLESS(_asm::IsOp(ptr[2], _asm::op::StrImm64, Rt, Rn, Off8) && _asm::Get(ptr[2], Off8) == 0x50 && _asm::Get(ptr[2], Rn) == bus, + ldr::ResultInvalidBusFreqReloc()); /* str Xrail,[Xbus,#0x50] */ + R_UNLESS(_asm::IsOp(ptr[3], _asm::op::Bl, Imm26), ldr::ResultInvalidBusFreqReloc()); /* bl GetDvfsRailUniqueFreqList */ + + constexpr u32 PrologueMargin = 140; + u32 *functionPrologue = _asm::FindFnPrologue(ptr, PrologueMargin, nsoStart); + R_UNLESS(functionPrologue != nullptr, ldr::ResultInvalidBusFreqReloc()); + + busInitCache.site = functionPrologue; + + 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) { + constexpr u32 Window = 48; + + using namespace _asm::field; + + R_UNLESS(_asm::Ignoring(ptr[1], EmcSocLutCountStoreAsm, Rt), ldr::ResultInvalidEmcSocLut()); /* str w?,[x0,#0x154] */ + + u32 *freqStore = _asm::ScanAssembly(ptr - Window, Window, EmcSocFreqStoreAsm, Rt); /* str x?,[x8,#0x18] */ + u32 *voltStore = _asm::ScanAssembly(ptr - Window, Window, EmcSocVoltStoreAsm, Rt); /* str w?,[x8,#0x48] */ + u32 *readLoad = _asm::ScanAssembly(ptr - Window, Window, EmcSocReadLoadAsm, Rt); /* 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(_asm::IsOp(*voltBase, _asm::op::AddShifted64, Rd, Rn, Rm, Imm6) && _asm::Get(*voltBase, Rd) == _asm::Get(*voltStore, Rn), ldr::ResultInvalidEmcSocLut()); + + /* adrp Xl ; add Xl,Xl,#off ; ... ; str Xl,[rail,#0x120] */ + const u32 lutReg = _asm::Get(ptr[0], Rt); + const u32 railReg = _asm::Get(ptr[0], Rn); + R_UNLESS(_asm::IsOp(ptr[-3], _asm::op::Adrp, Rd, ImmAdrpHi, ImmAdrpLo) && _asm::Get(ptr[-3], Rd) == lutReg, ldr::ResultInvalidEmcSocLut()); + R_UNLESS(_asm::IsOp(ptr[-2], _asm::op::AddImm64, Rd, Rn, Imm12) && _asm::Get(ptr[-2], Rd) == lutReg && _asm::Get(ptr[-2], Rn) == lutReg, ldr::ResultInvalidEmcSocLut()); + + const u32 srcBase = _asm::Get(*voltBase, Rn); /* rail ptr at +0x20 */ + const u32 wBase = _asm::Get(*voltBase, Rd); /* base reg */ + const u32 wIdx = _asm::Get(*voltBase, Rm); /* loop index */ + + PATCH_OFFSET(freqStore, _asm::NopIns); /* Unneeded */ + PATCH_OFFSET(voltBase, _asm::Encode(_asm::op::LdrImm64, {Rt, wBase}, {Rn, srcBase}, {Off8, 0x20})); /* ldr Xb,[Xsrc,#0x20] (rail) */ + PATCH_OFFSET(voltStore - 1, _asm::Encode(_asm::op::AddImm64, {Rd, wBase}, {Rn, wBase}, {Imm12, 0x18})); /* add Xb,Xb,#0x18 (was cmn) */ + PATCH_OFFSET(voltStore, _asm::ToRegOffset(*voltStore, wIdx)); /* str Wv,[Xb,Xi,LSL#2] -> rail+0x18+i*4 */ + PATCH_OFFSET(voltStore + 1, _asm::NopIns); /* Unneeded */ + + /* rail+0x18 as the socMinLut pointer. */ + PATCH_OFFSET(ptr - 3, _asm::NopIns); + PATCH_OFFSET(ptr - 2, _asm::Encode(_asm::op::AddImm64, {Rd, lutReg}, {Rn, railReg}, {Imm12, 0x18})); /* add Xl,rail,#0x18 */ + + /* Drop the abort branch in case of a bad read */ + for (u32 i = 1; i <= 4; ++i) { + if (_asm::IsOp(readLoad[i], _asm::op::BCond, Imm19, BCond)) { + PATCH_OFFSET(&readLoad[i], _asm::NopIns); + break; + } + } + R_SUCCEED(); + } + + Result EmcDvfsCountLimit(u32 *ptr) { + R_UNLESS(_asm::IsOp(*(ptr - 1), _asm::op::Cbz, _asm::field::Imm19, _asm::field::Rt), ldr::ResultInvalidEmcDvfsCount()); /* cbz w?, */ + R_UNLESS(_asm::Ignoring(*(ptr + 1), _asm::Encode(_asm::op::BCond, {_asm::field::BCond, _asm::cond::Cs}), _asm::field::Imm19), + ldr::ResultInvalidEmcDvfsCount()); /* b.cs */ + + /* cmp w?,#0x21 -> cmp w?,#EmcDvfsTableEntryCount */ + PATCH_OFFSET(ptr, _asm::Set(*ptr, _asm::field::Imm12, EmcDvfsTableEntryCount)); + R_SUCCEED(); + } + + Result EmcRateListLimit(u32 *ptr) { + R_UNLESS(_asm::Ignoring(ptr[1], EmcRateCapCselAsm, _asm::field::Rd, _asm::field::Rn, _asm::field::Rm) + && _asm::Get(ptr[0], _asm::field::Rn) == _asm::Get(ptr[1], _asm::field::Rn), + ldr::ResultInvalidEmcRateList()); /* csel w?,w?,w?,lt, min(reg, 0x20) */ + R_UNLESS(_asm::IsOp(ptr[2], _asm::op::Bl, _asm::field::Imm26), ldr::ResultInvalidEmcRateList()); + + /* The csel's Rm holds the 32 cap. */ + const u32 capReg = _asm::Get(ptr[1], _asm::field::Rm); + const u32 capMov = _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, capReg}, {_asm::field::Imm16, 0x20}); /* movz w,#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, _asm::Set(*ptr, _asm::field::Imm12, EmcDvfsTableEntryCount)); /* cmp w?,#64 */ + PATCH_OFFSET(movPtr, _asm::Set(*movPtr, _asm::field::Imm16, EmcDvfsTableEntryCount)); /* movz w?,#64 */ + 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 (_asm::IsOp(w, _asm::op::SubImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12) + && _asm::Get(w, _asm::field::Rn) == _asm::reg::Fp && _asm::Get(w, _asm::field::Imm12) >= 0x20u) { /* sub x?,x29,#>=0x20 */ + R_THROW(ldr::ResultInvalidEmcRateList()); + } + } + + /* mov x,x2 */ + u32 descReg = 0xFFu; + for (u32 i = 1; i <= 24; ++i) { + if (_asm::Ignoring(ptr[i], _asm::MovReg64(0, 2), _asm::field::Rd)) { descReg = _asm::Get(ptr[i], _asm::field::Rd); 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 (_asm::IsOp(w, _asm::op::AddImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12) && _asm::Get(w, _asm::field::Rn) == _asm::reg::Sp) { /* add x?,sp,#imm12 (shift 0) */ + adds[nAdds] = ptr + i; + addImm[nAdds] = _asm::Get(w, _asm::field::Imm12); + ++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], _asm::Encode(_asm::op::LdrImm64, {_asm::field::Rt, _asm::Get(*adds[a], _asm::field::Rd)}, + {_asm::field::Rn, descReg}, + {_asm::field::Off8, 0})); /* ldr x?,[x] */ + ++patched; + } + } + R_UNLESS(patched == 2, ldr::ResultInvalidEmcRateList()); + + /* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount) */ + PATCH_OFFSET(ptr, _asm::Set(*ptr, _asm::field::Imm12, EmcDvfsTableEntryCount)); /* cmp w?,#64 */ + PATCH_OFFSET(ptr + movzI, _asm::Set(*(ptr + movzI), _asm::field::Imm16, EmcDvfsTableEntryCount)); /* movz w?,#64 */ + 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(); + } + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_soc.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_soc.hpp new file mode 100644 index 00000000..04ea6792 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_soc.hpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) Souldbminer, Lightos and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "pcv_mariko.hpp" + +namespace ams::ldr::hoc::pcv::mariko { + + extern u32 *nsoStart; + + /* 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::Ignoring(*ptr, SocVoltCompareSpeedoAsm, _asm::field::Rd); + } + + 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 EmcCountCmpAsm = 0x7100851F; /* cmp w?,#0x21 (subs wzr,w?,#0x21) */ + + /* + str ,[,#0x120] ; volt-array pointer + str w?, [,#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) */ + + inline bool EmcDvfsCountPatternFn(u32 *ptr) { + return _asm::Ignoring(*ptr, EmcCountCmpAsm, _asm::field::Rd); + } + + inline bool EmcSocLutPatternFn(u32 *ptr) { + return _asm::Ignoring(*ptr, EmcSocLutPtrStoreAsm, _asm::field::Rt); /* str x?,[x0,#0x120] */ + } + + /* + 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?,,,lt ; min(maxCount, 32) + bl <_asm::Get*DvfsFreqTable> + */ + constexpr u32 EmcRateCapCmpAsm = 0x710082FF; /* cmp w?,#0x20 */ + constexpr u32 EmcRateCapCselAsm = 0x1A80B000; /* csel w?,w?,w?,lt */ + + inline bool EmcRateListPatternFn(u32 *ptr) { + return _asm::Ignoring(*ptr, EmcRateCapCmpAsm, _asm::field::Rd, _asm::field::Rn); + } + + 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) { + const u32 c = _asm::Get(ptr[0], _asm::field::Rn); + for (u32 i = 1; i <= 14; ++i) { + const u32 w = ptr[i]; + if (_asm::Ignoring(w, EmcRateSessCselAsm, _asm::field::Rd, _asm::field::Rn, _asm::field::Rm) && _asm::Get(w, _asm::field::Rn) == c && _asm::Get(w, _asm::field::Rd) == c) { + const u32 cap = _asm::Get(w, _asm::field::Rm); + for (u32 j = 1; j < i; ++j) { + if (_asm::Ignoring(ptr[j], EmcRateSessMovAsm, _asm::field::Rd) && _asm::Get(ptr[j], _asm::field::Rd) == 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 _asm::Ignoring(*ptr, EmcRateSessCmpAsm, _asm::field::Rd, _asm::field::Rn); + } + + inline bool BusFreqRelocPatternFn(u32 *ptr) { + if (g_pcv_cave == 0) { + return false; + } + if (reinterpret_cast(ptr + 4) > g_pcv_cave) { /* the call site lives in .text */ + return false; + } + return _asm::IsOp(*ptr, _asm::op::LdrImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Off8) + && _asm::Get(*ptr, _asm::field::Off8) == 0x10; /* ldr Xbuf,[Xbus,#0x10] */ + } + + Result SocVoltAsm(u32 *compareSpeedos); + Result SocVoltLimit(u32 *ptr); + HOOK_PAYLOAD_FN u64 BusFreqExtendImpl(SharedClkBus *bus); + Result BusFreqReloc(u32 *ptr); + Result EmcSocLutReloc(u32 *ptr); + Result EmcDvfsCountLimit(u32 *ptr); + Result EmcRateListLimit(u32 *ptr); + Result EmcRateSessLimit(u32 *ptr); + Result SharedClkBusInstallHooks(HookPayloadData *data); + +}