ldr: code cleanup
This commit is contained in:
1 parent
8fc18ee3e8
commit
5dacd30dd3
8 files changed
+831
-715
No files matched your search
@@ -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<uintptr_t>(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<uintptr_t>(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<uintptr_t>(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<const char *>(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<uintptr_t>(p + 2) <= textEnd; ++p) {
|
||||
const uintptr_t pc = reinterpret_cast<uintptr_t>(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<uintptr_t>(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<uintptr_t>(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<unsigned long>(g_pcv_cave), static_cast<unsigned long>(g_pcv_cave_size));
|
||||
R_THROW(ldr::ResultInvalidNvLogRedirect());
|
||||
}
|
||||
u32 *t = reinterpret_cast<u32 *>(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<uintptr_t>(p + 1) <= textEnd; ++p) {
|
||||
if (!_asm::IsOp(*p, _asm::op::Bl, _asm::field::Imm26)) {
|
||||
continue;
|
||||
}
|
||||
const uintptr_t pc = reinterpret_cast<uintptr_t>(p);
|
||||
if (_asm::Target26(*p, pc) != nvlog_addr) {
|
||||
continue;
|
||||
}
|
||||
bool isFmtCall = false;
|
||||
for (u32 j = 1; j <= 8 && reinterpret_cast<uintptr_t>(p - j) >= reinterpret_cast<uintptr_t>(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,<page> */
|
||||
const uintptr_t tgtPage = _asm::TargetAdrp(w, pc - j * 4);
|
||||
if (tgtPage >= (roStart & ~static_cast<uintptr_t>(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<u64 (*)(SharedClkBus *)>(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?,<skip> */
|
||||
R_UNLESS(_asm::Ignoring(*(ptr + 1), _asm::Encode(_asm::op::BCond, {_asm::field::BCond, _asm::cond::Cs}), _asm::field::Imm19),
|
||||
ldr::ResultInvalidEmcDvfsCount()); /* b.cs <abort> */
|
||||
|
||||
/* 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<Rm>,#0x20 */
|
||||
|
||||
u32 *movPtr = nullptr;
|
||||
for (u32 i = 1; i <= 16; ++i) {
|
||||
if (*(ptr - i) == capMov) {
|
||||
movPtr = ptr - i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
R_UNLESS(movPtr, ldr::ResultInvalidEmcRateList());
|
||||
|
||||
/* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount). */
|
||||
PATCH_OFFSET(ptr, _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<regulator *>(reinterpret_cast<u8 *>(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<u32 *>(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<desc>,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<desc>] */
|
||||
++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<u32 *>(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();
|
||||
|
||||
@@ -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 <lut>,[<rail>,#0x120] ; volt-array pointer
|
||||
str w?, [<rail>,#0x154] ; num_freqs
|
||||
*/
|
||||
constexpr u32 EmcSocLutPtrStoreAsm = 0xF9009009; /* str x?,[x0,#0x120] (anchor) */
|
||||
constexpr u32 EmcSocLutCountStoreAsm = 0xB9015408; /* str w?,[x0,#0x154] (anchor) */
|
||||
constexpr u32 EmcSocFreqStoreAsm = 0xF9000D00; /* str x?,[x8,#0x18] */
|
||||
constexpr u32 EmcSocVoltStoreAsm = 0xB9004900; /* str w?,[x8,#0x48] (socMinLut[i]) */
|
||||
constexpr u32 EmcSocReadLoadAsm = 0xB9404929; /* ldr w?,[x9,#0x48] (socMinLut) */
|
||||
|
||||
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?,<same>,<cap>,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<uintptr_t>(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<uintptr_t>(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<uintptr_t>(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);
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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<uintptr_t>(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<uintptr_t>(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<uintptr_t>(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<const char *>(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<uintptr_t>(p + 2) <= textEnd; ++p) {
|
||||
const uintptr_t pc = reinterpret_cast<uintptr_t>(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<uintptr_t>(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<uintptr_t>(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<unsigned long>(g_pcv_cave), static_cast<unsigned long>(g_pcv_cave_size));
|
||||
R_THROW(ldr::ResultInvalidNvLogRedirect());
|
||||
}
|
||||
u32 *t = reinterpret_cast<u32 *>(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<uintptr_t>(p + 1) <= textEnd; ++p) {
|
||||
if (!_asm::IsOp(*p, _asm::op::Bl, _asm::field::Imm26)) {
|
||||
continue;
|
||||
}
|
||||
const uintptr_t pc = reinterpret_cast<uintptr_t>(p);
|
||||
if (_asm::Target26(*p, pc) != nvlog_addr) {
|
||||
continue;
|
||||
}
|
||||
bool isFmtCall = false;
|
||||
for (u32 j = 1; j <= 8 && reinterpret_cast<uintptr_t>(p - j) >= reinterpret_cast<uintptr_t>(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,<page> */
|
||||
const uintptr_t tgtPage = _asm::TargetAdrp(w, pc - j * 4);
|
||||
if (tgtPage >= (roStart & ~static_cast<uintptr_t>(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
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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<uintptr_t>(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<uintptr_t>(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);
|
||||
|
||||
}
|
||||
@@ -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<regulator *>(reinterpret_cast<u8 *>(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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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<u32 *>(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<u64 (*)(SharedClkBus *)>(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?,<skip> */
|
||||
R_UNLESS(_asm::Ignoring(*(ptr + 1), _asm::Encode(_asm::op::BCond, {_asm::field::BCond, _asm::cond::Cs}), _asm::field::Imm19),
|
||||
ldr::ResultInvalidEmcDvfsCount()); /* b.cs <abort> */
|
||||
|
||||
/* 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<Rm>,#0x20 */
|
||||
|
||||
u32 *movPtr = nullptr;
|
||||
for (u32 i = 1; i <= 16; ++i) {
|
||||
if (*(ptr - i) == capMov) {
|
||||
movPtr = ptr - i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
R_UNLESS(movPtr, ldr::ResultInvalidEmcRateList());
|
||||
|
||||
/* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount). */
|
||||
PATCH_OFFSET(ptr, _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<desc>,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<desc>] */
|
||||
++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<u32 *>(Hooks().ToVa(data->busData.table[0]));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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 <lut>,[<rail>,#0x120] ; volt-array pointer
|
||||
str w?, [<rail>,#0x154] ; num_freqs
|
||||
*/
|
||||
constexpr u32 EmcSocLutPtrStoreAsm = 0xF9009009; /* str x?,[x0,#0x120] (anchor) */
|
||||
constexpr u32 EmcSocLutCountStoreAsm = 0xB9015408; /* str w?,[x0,#0x154] (anchor) */
|
||||
constexpr u32 EmcSocFreqStoreAsm = 0xF9000D00; /* str x?,[x8,#0x18] */
|
||||
constexpr u32 EmcSocVoltStoreAsm = 0xB9004900; /* str w?,[x8,#0x48] (socMinLut[i]) */
|
||||
constexpr u32 EmcSocReadLoadAsm = 0xB9404929; /* ldr w?,[x9,#0x48] (socMinLut) */
|
||||
|
||||
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?,<same>,<cap>,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<uintptr_t>(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);
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user