refactor assembly patching

This commit is contained in:
Lightos1 committed 2026-08-20 15:48:02 +02:00
1 parent 62a2cfd65a
commit c0096686d8
11 files changed
+391 -502

No files matched your search

@@ -56,13 +56,13 @@ namespace ams::ldr::hoc::pcv::erista {
Result GpuFreqMaxAsm(u32 *ptr32) {
// Check if both two instructions match the pattern
u32 ins1 = *ptr32, ins2 = *(ptr32 + 1);
if (!(asm_compare_no_rd(ins1, GpuAsmPattern[0]) && asm_compare_no_rd(ins2, GpuAsmPattern[1]))) {
if (!(_asm::Ignoring(ins1, GpuAsmPattern[0], _asm::field::Rd) && _asm::Ignoring(ins2, GpuAsmPattern[1], _asm::field::Rd))) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
// Both instructions should operate on the same register
u8 rd = asm_get_rd(ins1);
if (rd != asm_get_rd(ins2)) {
u8 rd = _asm::Get(ins1, _asm::field::Rd);
if (rd != _asm::Get(ins2, _asm::field::Rd)) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
@@ -83,8 +83,8 @@ namespace ams::ldr::hoc::pcv::erista {
}
u32 asm_patch[2] = {
asm_set_rd(asm_set_imm16(GpuAsmPattern[0], max_clock), rd),
asm_set_rd(asm_set_imm16(GpuAsmPattern[1], max_clock >> 16), rd)
_asm::Encode(_asm::op::MovzW, {_asm::field::Rd, rd}, {_asm::field::Imm16, max_clock}),
_asm::Encode(_asm::op::MovkW, {_asm::field::Rd, rd}, {_asm::field::Imm16, max_clock >> 16}, {_asm::field::Hw, 1})
};
PATCH_OFFSET(ptr32, asm_patch[0]);
@@ -51,7 +51,7 @@ namespace ams::ldr::hoc::pcv::erista {
inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 };
inline bool GpuMaxClockPatternFn(u32 *ptr32) {
return asm_compare_no_rd(*ptr32, GpuAsmPattern[0]);
return _asm::Ignoring(*ptr32, GpuAsmPattern[0], _asm::field::Rd);
};
constexpr cvb_entry_t GpuCvbTableDefault[] = {
@@ -447,27 +447,28 @@ namespace ams::ldr::hoc::pcv::erista {
R_UNLESS(ptr - MovOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern());
u32 adrp = *(ptr - AddrpOffset);
R_UNLESS(AsmCompareAdrpNoImm(adrp, MtcAdrpAsm), ldr::ResultInvalidMtcTablePattern());
R_UNLESS(_asm::Ignoring(adrp, MtcAdrpAsm, _asm::field::ImmAdrpHi, _asm::field::ImmAdrpLo), ldr::ResultInvalidMtcTablePattern());
/* Check for the branch instruction above the cbz to ensure we are patching the right location*/
/* Check for the branch instruction above the cbz to ensure we are patching the right location. */
/* Only the opcode is checked; the call target varies by firmware (0x97ffae64 on one build). */
u32 bl = *(ptr - BlOffset);
R_UNLESS(AsmBlCompareOpcodeOnly(bl, MtcBlIns), ldr::ResultInvalidMtcTablePattern());
R_UNLESS(_asm::IsOp(bl, _asm::op::Bl, _asm::field::Imm26), ldr::ResultInvalidMtcTablePattern());
/* Check for the mov that actually sets the mtc table count. */
u32 mov = *(ptr - MovOffset);
bool foundMov = false;
foundMov = asm_compare_no_rd(mov, MtcMovAsm);
foundMov = _asm::Ignoring(mov, MtcMovAsm, _asm::field::Rd);
if (!foundMov) {
mov = *(ptr + MovOffsetOld);
/* Check old firmware offset. */
foundMov = asm_compare_no_rd(mov, MtcMovAsm);
foundMov = _asm::Ignoring(mov, MtcMovAsm, _asm::field::Rd);
}
R_UNLESS(foundMov, ldr::ResultInvalidMtcTablePattern());
constexpr u32 PrologueWindow = 140;
u32 *functionPrologue = FindFnPrologue(ptr, PrologueWindow, nsoStart);
u32 *functionPrologue = _asm::FindFnPrologue(ptr, PrologueWindow, nsoStart);
R_UNLESS(functionPrologue != nullptr, ldr::ResultInvalidMtcTablePattern());
getMtcTableCache.getEristaMtcTableFnSite = functionPrologue;
@@ -73,12 +73,11 @@ namespace ams::ldr::hoc::pcv::erista {
constexpr u32 MtcBrAsm = 0xD61F0140;
constexpr u32 MtcMovAsm = 0x52800148;
constexpr u32 MtcAdrpAsm = 0xD0000081;
constexpr u32 MtcBlIns = 0x97ffae64;
constexpr u32 MtcAddAsm = 0x91131821;
ALWAYS_INLINE bool MemMtcGetGetTablePatternFn(u32 *ptr) {
/* This builds an address that gets returned, so the register must be x0 by convention. */
return AsmCompareAddNoImm12(*ptr, MtcAddAsm);
return _asm::Ignoring(*ptr, MtcAddAsm, _asm::field::Imm12);
}
Result MemFreqMtcTable(u32 *ptr);
@@ -76,19 +76,20 @@ namespace ams::ldr::hoc::pcv::mariko {
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 (!AsmIsAdrp(p[0])) {
if (!_asm::IsOp(p[0], _asm::op::Adrp, _asm::field::Rd, _asm::field::ImmAdrpHi, _asm::field::ImmAdrpLo)) {
continue;
}
const uintptr_t adrpPage = (pc & ~static_cast<uintptr_t>(0xFFFu)) + static_cast<uintptr_t>(AsmAdrpPageOffset(p[0]));
if (adrpPage != (strAddr & ~static_cast<uintptr_t>(0xFFFu))) {
if (_asm::TargetAdrp(p[0], pc) != (strAddr & ~static_cast<uintptr_t>(0xFFFu))) {
continue;
}
const u32 reg = asm_get_rd(p[0]);
if (!(AsmIsAddImm64(p[1]) && asm_get_rd(p[1]) == reg && AsmGetRn(p[1]) == reg && AsmGetImm12(p[1]) == (strAddr & 0xFFFu))) {
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 (AsmIsBl(p[k])) { nvlog_addr = AsmBranchTarget(p[k], pc + k * 4); break; }
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;
@@ -109,77 +110,81 @@ namespace ams::ldr::hoc::pcv::mariko {
size_t n = 0;
auto emit = [&](u32 ins) { t[n] = ins; ++n; };
emit(AsmMakeSubImm64(31, 31, 0x200));
emit(AsmMakeStpImm64(0, 1, 31, 0x100));
emit(AsmMakeStpImm64(2, 3, 31, 0x110));
emit(AsmMakeStpImm64(4, 5, 31, 0x120));
emit(AsmMakeStpImm64(6, 7, 31, 0x130));
emit(AsmMakeStpqImm(0, 1, 31, 0x140));
emit(AsmMakeStpqImm(2, 3, 31, 0x160));
emit(AsmMakeStpqImm(4, 5, 31, 0x180));
emit(AsmMakeStpqImm(6, 7, 31, 0x1A0));
emit(AsmMakeStrImm64(30, 31, 0x1E0));
emit(AsmMakeAddImm64(9, 31, 0x200)); emit(AsmMakeStrImm64(9, 31, 0x1C0)); /* __stack */
emit(AsmMakeAddImm64(9, 31, 0x140)); emit(AsmMakeStrImm64(9, 31, 0x1C8)); /* __gr_top */
emit(AsmMakeAddImm64(9, 31, 0x1C0)); emit(AsmMakeStrImm64(9, 31, 0x1D0)); /* __vr_top */
emit(AsmMakeMovnW(9, 0x37)); emit(AsmMakeStrImm32(9, 31, 0x1D8)); /* __gr_offs = -56 */
emit(AsmMakeMovnW(9, 0x7F)); emit(AsmMakeStrImm32(9, 31, 0x1DC)); /* __vr_offs = -128 */
emit(AsmMakeAddImm64(0, 31, 0x00)); /* mov x0,sp (buf) */
emit(AsmMakeMovzW(1, 0x100)); /* size = 0x100 */
emit(AsmMakeLdrImm64(2, 31, 0x100)); /* fmt (saved x0) */
emit(AsmMakeAddImm64(3, 31, 0x1C0)); /* ap */
emit(AsmMakeBl(helper + n * 4, vsnprintf_addr));
emit(AsmMakeMovReg(1, 0)); /* len = retval */
emit(AsmMakeCmpImm32(1, 0x100));
{ const size_t at = n; emit(AsmMakeBCond(helper + at * 4, helper + (at + 2) * 4, 0x3u)); } /* b.lo +2 */
emit(AsmMakeMovzW(1, 0xFF)); /* clamp len */
emit(AsmMakeAddImm64(0, 31, 0x00)); /* mov x0,sp (str) */
emit(AsmMakeSvc(0x27)); /* svcOutputDebugString */
emit(AsmMakeLdrImm64(30, 31, 0x1E0));
emit(AsmMakeAddImm64(31, 31, 0x200));
emit(RetIns);
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 (!AsmIsBl(*p)) {
if (!_asm::IsOp(*p, _asm::op::Bl, _asm::field::Imm26)) {
continue;
}
const uintptr_t pc = reinterpret_cast<uintptr_t>(p);
if (AsmBranchTarget(*p, pc) != nvlog_addr) {
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 (AsmIsAdrp(w) && asm_get_rd(w) == 0) { /* adrp x0,<page> */
const uintptr_t wpc = pc - j * 4;
const uintptr_t tgtPage = (wpc & ~static_cast<uintptr_t>(0xFFFu)) + static_cast<uintptr_t>(AsmAdrpPageOffset(w));
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, AsmMakeBl(pc, helper));
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);
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
/* Relocate C2/C3Bus to avoid issues*/
Result BusFreqReloc(u32 *ptr) {
const u32 busReg = AsmGetRn(ptr[0]); /* ldr Xbuf,[Xbus,#0x10] : bus struct pointer */
const u32 bufReg = asm_get_rd(ptr[0]); /* : freq-buffer arg */
const u32 bufOff = AsmGetLdStImm64Off(ptr[0]); /* : bus->freqBuf offset */
const u32 cntReg = asm_get_rd(ptr[1]); /* add Xcnt,Xbus,#0x18 : arg2 (&count) */
const u32 railReg = asm_get_rd(ptr[2]); /* str Xrail,[Xbus,#0x50]: arg0 (rail) */
const u32 busReg = _asm::Get(ptr[0], _asm::field::Rn); /* ldr Xbuf,[Xbus,#0x10] : bus struct pointer */
const u32 bufReg = _asm::Get(ptr[0], _asm::field::Rt); /* : freq-buffer arg */
const u32 bufOff = _asm::Get(ptr[0], _asm::field::Off8); /* : bus->freqBuf offset */
const u32 cntReg = _asm::Get(ptr[1], _asm::field::Rd); /* add Xcnt,Xbus,#0x18 : arg2 (&count) */
const u32 railReg = _asm::Get(ptr[2], _asm::field::Rt); /* str Xrail,[Xbus,#0x50]: arg0 (rail) */
u32 *call = ptr + 3; /* the bl to relocate */
const uintptr_t realFn = AsmBranchTarget(*call, reinterpret_cast<uintptr_t>(call));
const uintptr_t realFn = _asm::Target26(*call, reinterpret_cast<uintptr_t>(call));
/* Pick 3 scratch registers */
u32 s[3], sc = 0;
@@ -197,21 +202,21 @@ namespace ams::ldr::hoc::pcv::mariko {
u32 *t = reinterpret_cast<u32 *>(tramp);
size_t n = 0;
auto emit = [&](u32 ins) { t[n] = ins; ++n; };
emit(AsmMakeAdrp(tramp + n * 4, region, s[0])); /* adrp s0,<region> */
emit(AsmMakeAddImm64(s[0], s[0], region & 0xFFFu)); /* add s0,s0,#lo */
emit(AsmMakeLdrImm32(s[1], s[0], 0x00)); /* s1 = counter */
emit(AsmMakeAddImm64(s[2], s[1], 1)); /* s2 = counter+1 */
emit(AsmMakeStrImm32(s[2], s[0], 0x00)); /* counter++ */
emit(AsmMakeAddImm64(s[0], s[0], 0x10)); /* s0 = region+0x10 (buffers) */
emit(AsmMakeAddShiftedReg64(bufReg, s[0], s[1], 10)); /* Xbuf = s0 + counter*0x400 */
emit(AsmMakeStrImm64(bufReg, busReg, bufOff)); /* bus[freqBuf] = Xbuf */
emit(AsmMakeB(tramp + n * 4, realFn)); /* tail-call the real function */
using namespace _asm::field;
emit(_asm::RetargetAdrp(_asm::Encode(_asm::op::Adrp, {Rd, s[0]}), tramp + n * 4, region)); /* adrp s0,<region> */
emit(_asm::Encode(_asm::op::AddImm64, {Rd, s[0]}, {Rn, s[0]}, {Imm12, region & 0xFFFu})); /* add s0,s0,#lo */
emit(_asm::Encode(_asm::op::LdrImm32, {Rt, s[1]}, {Rn, s[0]}, {Off4, 0x00})); /* s1 = counter */
emit(_asm::Encode(_asm::op::AddImm64, {Rd, s[2]}, {Rn, s[1]}, {Imm12, 1})); /* s2 = counter+1 */
emit(_asm::Encode(_asm::op::StrImm32, {Rt, s[2]}, {Rn, s[0]}, {Off4, 0x00})); /* counter++ */
emit(_asm::Encode(_asm::op::AddImm64, {Rd, s[0]}, {Rn, s[0]}, {Imm12, 0x10})); /* s0 = region+0x10 (buffers) */
emit(_asm::Encode(_asm::op::AddShifted64, {Rd, bufReg}, {Rn, s[0]}, {Rm, s[1]}, {Imm6, 10})); /* Xbuf = s0 + counter*0x400 */
emit(_asm::Encode(_asm::op::StrImm64, {Rt, bufReg}, {Rn, busReg}, {Off8, bufOff})); /* bus[freqBuf] = Xbuf */
emit(_asm::Retarget26(_asm::op::B, tramp + n * 4, realFn)); /* tail-call the real function */
PATCH_OFFSET(call, AsmMakeBl(reinterpret_cast<uintptr_t>(call), tramp));
PATCH_OFFSET(call, _asm::Retarget26(_asm::op::Bl, reinterpret_cast<uintptr_t>(call), tramp));
const uintptr_t base = reinterpret_cast<uintptr_t>(nsoStart);
(void) base;
LOGGING("BusFreqReloc: call@+%lx -> tramp@+%lx realfn@+%lx (bus=x%u buf=x%u off=0x%x scratch=x%u,x%u,x%u)",
reinterpret_cast<uintptr_t>(call) - base, tramp - base, realFn - base, busReg, bufReg, bufOff, s[0], s[1], s[2]);
LOGGING("BusFreqReloc: call@+%lx -> tramp@+%lx realfn@+%lx (bus=x%u buf=x%u off=0x%x scratch=x%u,x%u,x%u)", reinterpret_cast<uintptr_t>(call) - base, tramp - base, realFn - base, busReg, bufReg, bufOff, s[0], s[1], s[2]);
R_SUCCEED();
}
@@ -219,9 +224,9 @@ namespace ams::ldr::hoc::pcv::mariko {
#if HOC_UART_LOG
/* Force GetEffectiveVerbosityLevel to return a non-zero level so all NvLog runs. */
Result ForceVerbosity(u32 *ptr) {
if(C.pcvLogVerbosity != 0xff) {
PATCH_OFFSET(&ptr[0], AsmMakeMovzW(0, static_cast<u16>(C.pcvLogVerbosity))); /* movz w0,#level */
PATCH_OFFSET(&ptr[1], RetIns); /* ret */
i f(C.pcvLogVerbosity != 0xff) {
PATCH_OFFSET(&ptr[0], _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, 0}, {_asm::field::Imm16, C.pcvLogVerbosity})); /* movz w0,#level */
PATCH_OFFSET(&ptr[1], _asm::RetIns); /* ret */
}
R_SUCCEED();
}
@@ -231,40 +236,40 @@ namespace ams::ldr::hoc::pcv::mariko {
Result EmcSocLutReloc(u32 *ptr) {
constexpr u32 Window = 48;
u32 *freqStore = ScanAssembly(ptr - Window, Window, EmcSocFreqStoreAsm, asm_compare_no_rd); /* str x?,[x8,#0x18] */
u32 *voltStore = ScanAssembly(ptr - Window, Window, EmcSocVoltStoreAsm, asm_compare_no_rd); /* str w?,[x8,#0x48] */
u32 *readLoad = ScanAssembly(ptr - Window, Window, EmcSocReadLoadAsm, asm_compare_no_rd); /* ldr w?,[x9,#0x48] */
using namespace _asm::field;
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(AsmIsAddShiftedReg64(*voltBase) && asm_get_rd(*voltBase) == AsmGetRn(*voltStore),
ldr::ResultInvalidEmcSocLut());
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_rd(ptr[0]);
const u32 railReg = AsmGetRn(ptr[0]);
R_UNLESS(AsmIsAdrp(ptr[-3]) && asm_get_rd(ptr[-3]) == lutReg, ldr::ResultInvalidEmcSocLut());
R_UNLESS(AsmIsAddImm64(ptr[-2]) && asm_get_rd(ptr[-2]) == lutReg && AsmGetRn(ptr[-2]) == lutReg,
ldr::ResultInvalidEmcSocLut());
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 = AsmGetRn(*voltBase); /* rail ptr at +0x20 */
const u32 wBase = asm_get_rd(*voltBase); /* base reg */
const u32 wIdx = AsmGetRm(*voltBase); /* loop index */
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, NopIns); /* Unneeded */
PATCH_OFFSET(voltBase, AsmMakeLdrImm64(wBase, srcBase, 0x20)); /* ldr Xb,[Xsrc,#0x20] (rail) */
PATCH_OFFSET(voltStore - 1, AsmMakeAddImm64(wBase, wBase, 0x18)); /* add Xb,Xb,#0x18 (was cmn) */
PATCH_OFFSET(voltStore, AsmSetLdStRegOffset(*voltStore, wIdx)); /* str Wv,[Xb,Xi,LSL#2] -> rail+0x18+i*4 */
PATCH_OFFSET(voltStore + 1, NopIns); /* Unneeded */
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, NopIns);
PATCH_OFFSET(ptr - 2, AsmMakeAddImm64(lutReg, railReg, 0x18));/* add Xl,rail,#0x18 */
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 (AsmIsBCond(readLoad[i])) {
PATCH_OFFSET(&readLoad[i], NopIns);
if (_asm::IsOp(readLoad[i], _asm::op::BCond, Imm19, BCond)) {
PATCH_OFFSET(&readLoad[i], _asm::NopIns);
break;
}
}
@@ -275,7 +280,7 @@ namespace ams::ldr::hoc::pcv::mariko {
R_UNLESS(EmcDvfsCountPatternFn(ptr), ldr::ResultInvalidEmcDvfsCount());
/* cmp w?,#0x21 -> cmp w?,#EmcDvfsTableEntryCount */
PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast<u16>(EmcDvfsTableEntryCount)));
PATCH_OFFSET(ptr, _asm::Set(*ptr, _asm::field::Imm12, EmcDvfsTableEntryCount));
R_SUCCEED();
}
@@ -284,8 +289,8 @@ namespace ams::ldr::hoc::pcv::mariko {
R_UNLESS(EmcRateListPatternFn(ptr), ldr::ResultInvalidEmcRateList());
/* The csel's Rm holds the 32 cap. */
const u32 capReg = AsmGetRm(ptr[1]);
const u32 capMov = AsmMakeMovzW(capReg, 0x20); /* movz w<Rm>,#0x20 */
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) {
@@ -297,8 +302,8 @@ namespace ams::ldr::hoc::pcv::mariko {
R_UNLESS(movPtr, ldr::ResultInvalidEmcRateList());
/* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount). */
PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast<u16>(EmcDvfsTableEntryCount))); /* cmp w?,#64 */
PATCH_OFFSET(movPtr, asm_set_imm16(*movPtr, static_cast<u16>(EmcDvfsTableEntryCount))); /* movz w?,#64 */
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();
}
@@ -384,44 +389,47 @@ namespace ams::ldr::hoc::pcv::mariko {
}
Result SocVoltAsm(u32 *compareSpeedos) {
using namespace _asm::field;
constexpr u32 VoltageScanLimit = 10;
/* Might actually be speedo id. */
u32 *writeProcessId = ScanAssembly(compareSpeedos, VoltageScanLimit, SocVoltWriteProcessIdAsm, asm_compare_no_rd);
u32 *writeProcessId = _asm::ScanAssembly(compareSpeedos, VoltageScanLimit, SocVoltWriteProcessIdAsm, Rd);
R_UNLESS(writeProcessId != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 writeProcessIdRd = asm_get_rd(*writeProcessId);
u8 writeProcessIdRd = _asm::Get(*writeProcessId, Rd);
/* This writes 1050mV. */
u32 *writeVoltage = ScanAssembly(writeProcessId, VoltageScanLimit, SocVoltWriteVoltageAsm, asm_compare_no_rd);
u32 *writeVoltage = _asm::ScanAssembly(writeProcessId, VoltageScanLimit, SocVoltWriteVoltageAsm, Rd);
R_UNLESS(writeVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 writeVoltageRd = asm_get_rd(*writeVoltage);
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 = ScanAssembly(writeVoltage, VoltageSelectScanLimit, SocVoltSelectRegisterAsm, AsmCompareCselNoReg);
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_rd(*selectVoltage);
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 = ScanAssembly(selectVoltage, MultiplierScanLimit, SocVoltMultiplyVoltsAsm, AsmCompareMullNoReg);
u32 *multiplier = _asm::ScanAssembly(selectVoltage, MultiplierScanLimit, SocVoltMultiplyVoltsAsm, Rd, Rn, Rm);
R_UNLESS(multiplier != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 multiplierRn = AsmGetMullRn(*multiplier);
u8 multiplierRm = AsmGetMullRm(*multiplier);
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_rd(*multiplier);
u8 multiplierRd = _asm::Get(*multiplier, Rd);
/* Subs instruction is then used to verify against absolute limit. */
u32 limitValidationPattern = AsmSubsSetRn(SocVoltValidateLimitAsm, multiplierRd);
u32 *limitValidation = ScanAssembly(multiplier, VoltageScanLimit, limitValidationPattern, AsmSubsCompareNoReg);
/* 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(AsmCompareBrConNoImm19(*branchToAbort, SocVoltBranchToAbortAsm), ldr::ResultInvalidSocVoltPattern());
R_UNLESS(_asm::Ignoring(*branchToAbort, SocVoltBranchToAbortAsm, Imm19), ldr::ResultInvalidSocVoltPattern());
if (!C.marikoSocVmax || C.marikoSocVmax <= 1000) {
R_SKIP();
@@ -429,7 +437,7 @@ namespace ams::ldr::hoc::pcv::mariko {
/* Adjust 1598 speedo minimum to ensure it always goes down process id 0 branch. */
/* 2200 should be high enough :D */
u32 compareSpeedosPatch = AsmSubsSetImm12(*compareSpeedos, 2200);
u32 compareSpeedosPatch = _asm::Set(*compareSpeedos, Imm12, 2200);
PATCH_OFFSET(compareSpeedos, compareSpeedosPatch);
u32 socSpeedo = 0;
@@ -438,15 +446,15 @@ namespace ams::ldr::hoc::pcv::mariko {
/* 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_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, processId), writeProcessIdRd);
u32 writeProcessIdPatch = _asm::Encode(_asm::op::MovzW, {Rd, writeProcessIdRd}, {Imm16, processId});
PATCH_OFFSET(writeProcessId, writeProcessIdPatch);
/* Adjust voltage limit. */
u32 voltageLimitPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, C.marikoSocVmax), writeVoltageRd);
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, NopIns);
PATCH_OFFSET(branchToAbort, _asm::NopIns);
R_SUCCEED();
}
@@ -479,7 +487,8 @@ namespace ams::ldr::hoc::pcv::mariko {
/* Reject cmd11 GetDvfsTable. */
for (u32 i = 1; i <= 24; ++i) {
const u32 w = ptr[i];
if (AsmIsSubX29Imm(w) && AsmGetImm12(w) >= 0x20u) { /* sub x?,x29,#>=0x20 */
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());
}
}
@@ -487,7 +496,7 @@ namespace ams::ldr::hoc::pcv::mariko {
/* mov x<desc>,x2 */
u32 descReg = 0xFFu;
for (u32 i = 1; i <= 24; ++i) {
if (AsmIsMovReg(ptr[i], 2)) { descReg = asm_get_rd(ptr[i]); break; }
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());
@@ -495,9 +504,9 @@ namespace ams::ldr::hoc::pcv::mariko {
u32 *adds[8]; u32 addImm[8]; u32 nAdds = 0;
for (u32 i = 1; i <= 24 && nAdds < 8; ++i) {
const u32 w = ptr[i];
if (AsmIsAddSpImm(w)) { /* add x?,sp,#imm12 (shift 0) */
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] = AsmGetImm12(w);
addImm[nAdds] = _asm::Get(w, _asm::field::Imm12);
++nAdds;
}
}
@@ -508,15 +517,17 @@ namespace ams::ldr::hoc::pcv::mariko {
if (a != b && addImm[a] == addImm[b]) { dup = true; break; }
}
if (dup) {
PATCH_OFFSET(adds[a], AsmMakeLdrImm64(asm_get_rd(*adds[a]), descReg, 0)); /* ldr x?,[x<desc>] */
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, AsmSubsSetImm12(*ptr, static_cast<u16>(EmcDvfsTableEntryCount))); /* cmp w?,#64 */
PATCH_OFFSET(ptr + movzI, asm_set_imm16(*(ptr + movzI), static_cast<u16>(EmcDvfsTableEntryCount))); /* movz w?,#64 */
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();
}
@@ -56,7 +56,7 @@ namespace ams::ldr::hoc::pcv::mariko {
constexpr u32 SocVoltBranchToAbortAsm = 0x540020AC; /* B.ge Branches to abort if limits are invalid. */
ALWAYS_INLINE bool SocVoltPatternFn(u32 *ptr) {
return asm_compare_no_rd(*ptr, SocVoltCompareSpeedoAsm);
return _asm::Ignoring(*ptr, SocVoltCompareSpeedoAsm, _asm::field::Rd);
}
constexpr u32 SocVoltLimitOfficial = 1050;
@@ -77,13 +77,14 @@ namespace ams::ldr::hoc::pcv::mariko {
inline bool EmcDvfsCountPatternFn(u32 *ptr) {
/* Local context: cbz w?,<skip> ; cmp w?,#0x21 ; b.cs <abort> */
return asm_compare_no_rd(*ptr, EmcCountCmpAsm) && AsmCompareBrConNoImm19(*(ptr + 1), 0x54000002) /* b.cs */
&& AsmCbzCompareOpcodeOnly(*(ptr - 1), 0x34000000); /* cbz */
return _asm::Ignoring(*ptr, EmcCountCmpAsm, _asm::field::Rd)
&& _asm::Ignoring(*(ptr + 1), _asm::Encode(_asm::op::BCond, {_asm::field::BCond, _asm::cond::Cs}), _asm::field::Imm19) /* b.cs */
&& _asm::IsOp(*(ptr - 1), _asm::op::Cbz, _asm::field::Imm19, _asm::field::Rt); /* cbz */
}
inline bool EmcSocLutPatternFn(u32 *ptr) {
return asm_compare_no_rd(*ptr, EmcSocLutPtrStoreAsm) /* str x?,[x0,#0x120] */
&& asm_compare_no_rd(*(ptr + 1), EmcSocLutCountStoreAsm); /* str w?,[x0,#0x154] */
return _asm::Ignoring(*ptr, EmcSocLutPtrStoreAsm, _asm::field::Rt) /* str x?,[x0,#0x120] */
&& _asm::Ignoring(*(ptr + 1), EmcSocLutCountStoreAsm, _asm::field::Rt); /* str w?,[x0,#0x154] */
}
/*
@@ -94,16 +95,16 @@ namespace ams::ldr::hoc::pcv::mariko {
cmp w?,#0x20 ; (maxCount)
csel w?,<same>,<cap>,lt ; min(maxCount, 32)
bl <Get*DvfsFreqTable>
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 AsmSubsCompareNoReg(*ptr, EmcRateCapCmpAsm) /* cmp w?,#0x20 */
&& AsmCompareCselNoReg(*(ptr + 1), EmcRateCapCselAsm) /* csel w?,w?,w?,lt */
&& (AsmGetRn(*ptr) == AsmGetRn(*(ptr + 1))) /* min(reg, 0x20) */
&& AsmBlCompareOpcodeOnly(*(ptr + 2), 0x94000000); /* bl <Get*DvfsFreqTable> */
return _asm::Ignoring(*ptr, EmcRateCapCmpAsm, _asm::field::Rd, _asm::field::Rn) /* cmp w?,#0x20 */
&& _asm::Ignoring(*(ptr + 1), EmcRateCapCselAsm, _asm::field::Rd, _asm::field::Rn, _asm::field::Rm) /* csel w?,w?,w?,lt */
&& (_asm::Get(*ptr, _asm::field::Rn) == _asm::Get(*(ptr + 1), _asm::field::Rn)) /* min(reg, 0x20) */
&& _asm::IsOp(*(ptr + 2), _asm::op::Bl, _asm::field::Imm26); /* bl <_asm::Get*DvfsFreqTable> */
}
constexpr u32 EmcRateSessCmpAsm = 0x710082FF; /* cmp w?,#0x20 */
@@ -111,14 +112,14 @@ namespace ams::ldr::hoc::pcv::mariko {
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) {
if (!AsmSubsCompareNoReg(ptr[0], EmcRateSessCmpAsm)) return false; /* cmp w<c>,#0x20 */
const u32 c = AsmGetRn(ptr[0]);
if (!_asm::Ignoring(ptr[0], EmcRateSessCmpAsm, _asm::field::Rd, _asm::field::Rn)) return false; /* cmp w<c>,#0x20 */
const u32 c = _asm::Get(ptr[0], _asm::field::Rn);
for (u32 i = 1; i <= 14; ++i) {
const u32 w = ptr[i];
if (AsmCompareCselNoReg(w, EmcRateSessCselAsm) && AsmGetRn(w) == c && asm_get_rd(w) == c) {
const u32 cap = AsmGetRm(w);
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 ((ptr[j] & 0xFFFFFFE0u) == EmcRateSessMovAsm && asm_get_rd(ptr[j]) == cap) {
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;
@@ -142,12 +143,12 @@ namespace ams::ldr::hoc::pcv::mariko {
if (reinterpret_cast<uintptr_t>(ptr + 4) > g_pcv_cave) { /* the call site lives in .text */
return false;
}
if (!(AsmIsLdrImm64(ptr[0]) && AsmGetLdStImm64Off(ptr[0]) == 0x10)) return false; /* ldr Xbuf,[Xbus,#0x10] */
if (!(AsmIsAddImm64(ptr[1]) && AsmGetImm12(ptr[1]) == 0x18)) return false; /* add Xcnt,Xbus,#0x18 */
if (!(AsmIsStrImm64(ptr[2]) && AsmGetLdStImm64Off(ptr[2]) == 0x50)) return false; /* str Xrail,[Xbus,#0x50]*/
if (!AsmIsBl(ptr[3])) return false; /* bl GetDvfsRailUnique */
const u32 bus = AsmGetRn(ptr[0]);
return AsmGetRn(ptr[1]) == bus && AsmGetRn(ptr[2]) == bus;
if (!(_asm::IsOp(ptr[0], _asm::op::LdrImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Off8) && _asm::Get(ptr[0], _asm::field::Off8) == 0x10)) return false; /* ldr Xbuf,[Xbus,#0x10] */
if (!(_asm::IsOp(ptr[1], _asm::op::AddImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12) && _asm::Get(ptr[1], _asm::field::Imm12) == 0x18)) return false; /* add Xcnt,Xbus,#0x18 */
if (!(_asm::IsOp(ptr[2], _asm::op::StrImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Off8) && _asm::Get(ptr[2], _asm::field::Off8) == 0x50)) return false; /* str Xrail,[Xbus,#0x50]*/
if (!_asm::IsOp(ptr[3], _asm::op::Bl, _asm::field::Imm26)) return false; /* bl GetDvfsRailUnique */
const u32 bus = _asm::Get(ptr[0], _asm::field::Rn);
return _asm::Get(ptr[1], _asm::field::Rn) == bus && _asm::Get(ptr[2], _asm::field::Rn) == bus;
}
inline bool ForceVerbosityPatternFn(u32 *ptr) {
@@ -158,9 +159,9 @@ namespace ams::ldr::hoc::pcv::mariko {
return false;
}
if (ptr[0] != 0xA9BE7BFDu || ptr[1] != 0xF9000BF3u || ptr[2] != 0x910003FDu) return false; /* stp/str/mov x29,sp */
if (!(AsmIsAddImm64(ptr[3]) && asm_get_rd(ptr[3]) == 0 && AsmGetRn(ptr[3]) == 29)) return false; /* add x0,x29,#imm */
if (!(AsmIsAddImm64(ptr[4]) && asm_get_rd(ptr[4]) == 19 && AsmGetRn(ptr[4]) == 29)) return false; /* add x19,x29,#imm */
if (AsmGetImm12(ptr[3]) != AsmGetImm12(ptr[4]) || !AsmIsBl(ptr[5])) return false;
if (!(_asm::IsOp(ptr[3], _asm::op::AddImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12) && _asm::Get(ptr[3], _asm::field::Rd) == 0 && _asm::Get(ptr[3], _asm::field::Rn) == _asm::reg::Fp)) return false; /* add x0,x29,#imm */
if (!(_asm::IsOp(ptr[4], _asm::op::AddImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12) && _asm::Get(ptr[4], _asm::field::Rd) == 19 && _asm::Get(ptr[4], _asm::field::Rn) == _asm::reg::Fp)) return false; /* add x19,x29,#imm */
if (_asm::Get(ptr[3], _asm::field::Imm12) != _asm::Get(ptr[4], _asm::field::Imm12) || !_asm::IsOp(ptr[5], _asm::op::Bl, _asm::field::Imm26)) return false;
for (u32 j = 6; j <= 10; ++j) {
if (ptr[j] == 0x7100001Fu) { /* cmp w0,#0 */
return true;
@@ -78,13 +78,13 @@ namespace ams::ldr::hoc::pcv::mariko {
Result GpuFreqMaxAsm(u32 *ptr32) {
// Check if both two instructions match the pattern
u32 ins1 = *ptr32, ins2 = *(ptr32 + 1);
if (!(asm_compare_no_rd(ins1, GpuAsmPattern[0]) && asm_compare_no_rd(ins2, GpuAsmPattern[1]))) {
if (!(_asm::Ignoring(ins1, GpuAsmPattern[0], _asm::field::Rd) && _asm::Ignoring(ins2, GpuAsmPattern[1], _asm::field::Rd))) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
// Both instructions should operate on the same register
u8 rd = asm_get_rd(ins1);
if (rd != asm_get_rd(ins2)) {
u8 rd = _asm::Get(ins1, _asm::field::Rd);
if (rd != _asm::Get(ins2, _asm::field::Rd)) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
@@ -111,8 +111,8 @@ namespace ams::ldr::hoc::pcv::mariko {
}
u32 asm_patch[2] = {
asm_set_rd(asm_set_imm16(GpuAsmPattern[0], max_clock), rd),
asm_set_rd(asm_set_imm16(GpuAsmPattern[1], max_clock >> 16), rd)
_asm::Encode(_asm::op::MovzW, {_asm::field::Rd, rd}, {_asm::field::Imm16, max_clock}),
_asm::Encode(_asm::op::MovkW, {_asm::field::Rd, rd}, {_asm::field::Imm16, max_clock >> 16}, {_asm::field::Hw, 1})
};
PATCH_OFFSET(ptr32, asm_patch[0]);
@@ -71,7 +71,7 @@ namespace ams::ldr::hoc::pcv::mariko {
inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 };
inline bool GpuMaxClockPatternFn(u32 *ptr32) {
return asm_compare_no_rd(*ptr32, GpuAsmPattern[0]);
return _asm::Ignoring(*ptr32, GpuAsmPattern[0], _asm::field::Rd);
}
Result GpuVoltDVFS(u32 *ptr);
@@ -652,23 +652,23 @@ namespace ams::ldr::hoc::pcv::mariko {
R_UNLESS(ptr - BrOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern());
u32 adrp = *(ptr - AddpOffset);
R_UNLESS(AsmCompareAdrpNoImm(adrp, MtcAdrpAsm), ldr::ResultInvalidMtcTablePattern());
R_UNLESS(_asm::Ignoring(adrp, MtcAdrpAsm, _asm::field::ImmAdrpHi, _asm::field::ImmAdrpLo), ldr::ResultInvalidMtcTablePattern());
/* We don't check for matching register because both registers must be x0 in order to pass the previous checks. */
/* The correct instructions will always be x0 since the mtcTable pointer is returned. */
/* Pray this does not break. */
u32 br = *(ptr - BrOffset);
R_UNLESS(AsmCompareBrNoRd(br, MtcBrAsm), ldr::ResultInvalidMtcTablePattern());
R_UNLESS(_asm::Ignoring(br, MtcBrAsm, _asm::field::Rn), ldr::ResultInvalidMtcTablePattern());
/* Pray this does not break either. */
u32 mov = *(ptr - MovOffset);
R_UNLESS(asm_compare_no_rd(mov, MtcMovAsm), ldr::ResultInvalidMtcTablePattern());
R_UNLESS(_asm::Ignoring(mov, MtcMovAsm, _asm::field::Rd), ldr::ResultInvalidMtcTablePattern());
u8 movRd = asm_get_rd(mov);
u32 movCountPatch = asm_set_rd(asm_set_imm16(MtcMovAsm, newEmcList.size()), movRd);
u8 movRd = _asm::Get(mov, _asm::field::Rd);
u32 movCountPatch = _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, movRd}, {_asm::field::Imm16, newEmcList.size()});
PATCH_OFFSET(ptr - BrOffset, NopIns);
PATCH_OFFSET(ptr - BrOffset, _asm::NopIns);
PATCH_OFFSET(ptr - MovOffset, movCountPatch);
R_SUCCEED();
@@ -177,7 +177,7 @@ namespace ams::ldr::hoc::pcv::mariko {
ALWAYS_INLINE bool MemMtcGetGetTablePatternFn(u32 *ptr) {
/* This builds an address that gets returned, so the register must be x0 by convention. */
return AsmCompareAddNoImm12(*ptr, MtcAddAsm);
return _asm::Ignoring(*ptr, MtcAddAsm, _asm::field::Imm12);
}
void MtcGenerateFreqTables();
@@ -22,11 +22,215 @@
#include <stratosphere.hpp>
namespace ams::ldr::hoc::pcv {
namespace ams::ldr::hoc::pcv::_asm {
constexpr u32 NopIns = 0xD503201F;
constexpr u32 RetIns = 0xD65F03C0;
template <typename Compare>
constexpr u32 GetField(u32 ins, u8 lsb, u8 width) {
return (ins >> lsb) & ((1u << width) - 1u);
}
constexpr u32 SetField(u32 ins, u8 lsb, u8 width, u32 val) {
const u32 mask = ((1u << width) - 1u) << lsb;
return (ins & ~mask) | ((val << lsb) & mask);
}
constexpr u32 FieldMask(u8 lsb, u8 width) {
return ((1u << width) - 1u) << lsb;
}
constexpr s64 SignExtend(u64 v, u32 bits) {
const u32 pad = 64u - bits;
return static_cast<s64>(v << pad) >> pad;
}
namespace field {
struct Descriptor {
u8 lsb;
u8 width;
u8 shift = 0;
};
constexpr Descriptor Rd { 0, 5}; /* destination register */
constexpr Descriptor Rn { 5, 5}; /* first source register */
constexpr Descriptor Rm {16, 5}; /* second source register */
constexpr Descriptor Ra {10, 5}; /* accumulator register */
constexpr Descriptor Rt { 0, 5}; /* load/store transfer register */
constexpr Descriptor Rt2 {10, 5}; /* second transfer register of a pair */
constexpr Descriptor Cond {12, 4}; /* condition code (CSEL) */
constexpr Descriptor BCond { 0, 4}; /* condition code (B.cond) */
constexpr Descriptor Hw {21, 2}; /* MOVZ/MOVK/MOVN shift */
constexpr Descriptor Sh {22, 1};
constexpr Descriptor Shift {22, 2}; /* ADD/SUB (shifted register) shift type */
constexpr Descriptor Imm6 {10, 6}; /* ADD/SUB (shifted register) shift amount */
constexpr Descriptor Imm16 { 5, 16}; /* MOVZ/MOVK/MOVN immediate */
constexpr Descriptor Imm12 {10, 12}; /* ADD/SUB (immediate) */
constexpr Descriptor Imm19 { 5, 19}; /* B.cond / CBZ / CBNZ immediate */
constexpr Descriptor Imm26 { 0, 26}; /* B / BL immediate */
constexpr Descriptor ImmAdrpHi { 5, 19};
constexpr Descriptor ImmAdrpLo {29, 2};
constexpr Descriptor Off1 {10, 12, 0}; /* LDRB/STRB */
constexpr Descriptor Off4 {10, 12, 2}; /* 32-bit LDR/STR */
constexpr Descriptor Off8 {10, 12, 3}; /* 64-bit LDR/STR */
constexpr Descriptor PairOff8 {15, 7, 3}; /* X pairs */
constexpr Descriptor PairOff16 {15, 7, 4}; /* Q pairs */
}
/* Regs that read as a name in the encoding. */
namespace reg {
constexpr u32 Fp = 29;
constexpr u32 Lr = 30;
constexpr u32 Sp = 31;
constexpr u32 Wzr = 31;
constexpr u32 Xzr = 31;
}
namespace cond {
constexpr u32 Eq = 0x0, Ne = 0x1, Cs = 0x2, Cc = 0x3,
Mi = 0x4, Pl = 0x5, Vs = 0x6, Vc = 0x7,
Hi = 0x8, Ls = 0x9, Ge = 0xA, Lt = 0xB,
Gt = 0xC, Le = 0xD, Al = 0xE;
constexpr u32 Hs = Cs, Lo = Cc;
}
/* Base opcodes. */
/* Either pair with Encode or Ignoring. */
namespace op {
constexpr u32 AddImm32 = 0x11000000;
constexpr u32 AddImm64 = 0x91000000;
constexpr u32 SubImm64 = 0xD1000000;
constexpr u32 SubsImm32 = 0x71000000;
constexpr u32 SubsImm64 = 0xF1000000;
constexpr u32 MovzW = 0x52800000;
constexpr u32 MovkW = 0x72800000;
constexpr u32 MovnW = 0x12800000;
constexpr u32 AddShifted64 = 0x8B000000;
constexpr u32 OrrShifted32 = 0x2A000000;
constexpr u32 OrrShifted64 = 0xAA000000;
constexpr u32 SubsShifted32 = 0x6B000000;
constexpr u32 Csel32 = 0x1A800000;
constexpr u32 Madd32 = 0x1B000000;
constexpr u32 LdrbImm = 0x39400000;
constexpr u32 StrbImm = 0x39000000;
constexpr u32 LdrImm32 = 0xB9400000;
constexpr u32 StrImm32 = 0xB9000000;
constexpr u32 LdrImm64 = 0xF9400000;
constexpr u32 StrImm64 = 0xF9000000;
constexpr u32 StpImm64 = 0xA9000000;
constexpr u32 StpPreImm64 = 0xA9800000;
constexpr u32 StpImmQ = 0xAD000000;
constexpr u32 B = 0x14000000;
constexpr u32 Bl = 0x94000000;
constexpr u32 BCond = 0x54000000;
constexpr u32 Cbz = 0x34000000;
constexpr u32 Br = 0xD61F0000;
constexpr u32 Adrp = 0x90000000;
constexpr u32 Svc = 0xD4000001;
}
constexpr u32 Get(u32 ins, field::Descriptor f) {
return GetField(ins, f.lsb, f.width) << f.shift;
}
constexpr u32 Set(u32 ins, field::Descriptor f, u64 val) {
return SetField(ins, f.lsb, f.width, static_cast<u32>(val >> f.shift));
}
template <typename... Fs>
constexpr bool Ignoring(u32 a, u32 b, Fs... fields) {
const u32 mask = (0u | ... | FieldMask(fields.lsb, fields.width));
return (a & ~mask) == (b & ~mask);
}
inline constexpr auto IgnoringRd = [](u32 a, u32 b) {
return Ignoring(a, b, field::Rd);
};
template <typename... Fs>
constexpr bool IsOp(u32 ins, u32 baseOpcode, Fs... variableFields) {
return Ignoring(ins, baseOpcode, variableFields...);
}
struct FieldValue {
field::Descriptor f;
u64 val;
};
constexpr u32 Encode(u32 base, FieldValue a) {
return Set(base, a.f, a.val);
}
constexpr u32 Encode(u32 base, FieldValue a, FieldValue b) {
return Encode(Encode(base, a), b);
}
constexpr u32 Encode(u32 base, FieldValue a, FieldValue b, FieldValue c) {
return Encode(Encode(base, a, b), c);
}
constexpr u32 Encode(u32 base, FieldValue a, FieldValue b, FieldValue c, FieldValue d) {
return Encode(Encode(base, a, b, c), d);
}
constexpr u32 Encode(u32 base, FieldValue a, FieldValue b, FieldValue c, FieldValue d, FieldValue e) {
return Encode(Encode(base, a, b, c, d), e);
}
constexpr uintptr_t Target26(u32 ins, uintptr_t pc) {
return static_cast<uintptr_t>(static_cast<s64>(pc) + SignExtend(static_cast<u64>(Get(ins, field::Imm26)) << 2, 28));
}
constexpr u32 Retarget26(u32 ins, uintptr_t pc, uintptr_t target) {
return Set(ins, field::Imm26, static_cast<u32>((static_cast<s64>(target) - static_cast<s64>(pc)) >> 2));
}
constexpr uintptr_t Target19(u32 ins, uintptr_t pc) {
return static_cast<uintptr_t>(static_cast<s64>(pc) + SignExtend(static_cast<u64>(Get(ins, field::Imm19)) << 2, 21));
}
constexpr u32 Retarget19(u32 ins, uintptr_t pc, uintptr_t target) {
return Set(ins, field::Imm19, static_cast<u32>((static_cast<s64>(target) - static_cast<s64>(pc)) >> 2));
}
constexpr uintptr_t TargetAdrp(u32 ins, uintptr_t pc) {
const u64 imm21 = (static_cast<u64>(Get(ins, field::ImmAdrpHi)) << 2) | Get(ins, field::ImmAdrpLo);
return static_cast<uintptr_t>(static_cast<s64>(pc & ~static_cast<uintptr_t>(0xFFF)) + SignExtend(imm21 << 12, 33));
}
constexpr u32 RetargetAdrp(u32 ins, uintptr_t pc, uintptr_t target) {
const s64 delta = static_cast<s64>(target & ~static_cast<uintptr_t>(0xFFF)) - static_cast<s64>(pc & ~static_cast<uintptr_t>(0xFFF));
const u32 imm21 = static_cast<u32>((delta >> 12) & 0x1FFFFF);
return Set(Set(ins, field::ImmAdrpHi, imm21 >> 2), field::ImmAdrpLo, imm21 & 0x3u);
}
constexpr u32 MovReg64(u32 rd, u32 rm) {
return Encode(op::OrrShifted64, {field::Rd, rd}, {field::Rn, reg::Xzr}, {field::Rm, rm});
}
constexpr u32 CmpImm32(u32 rn, u32 imm12) {
return Encode(op::SubsImm32, {field::Rd, reg::Wzr}, {field::Rn, rn}, {field::Imm12, imm12});
}
constexpr u32 ToRegOffset(u32 ldstImm, u32 rm) {
constexpr u32 Keep = 0xC0C00000u | FieldMask(field::Rt.lsb, field::Rt.width) | FieldMask(field::Rn.lsb, field::Rn.width);
return ((ldstImm & Keep) | 0x38207800u) | ((rm & 0x1Fu) << field::Rm.lsb);
}
template <typename Compare> requires (!std::is_same_v<std::remove_cvref_t<Compare>, field::Descriptor>)
u32 *ScanAssembly(u32 *ptr, u32 scanLimit, u32 pattern, Compare comp) {
for (u32 i = 0; i < scanLimit; ++i) {
if (comp(pattern, ptr[i])) {
@@ -36,341 +240,14 @@ namespace ams::ldr::hoc::pcv {
return nullptr;
}
inline auto asm_compare_no_rd = [](u32 ins1, u32 ins2) {
return ((ins1 ^ ins2) >> 5) == 0;
};
inline auto asm_get_rd = [](u32 ins) {
return ins & ((1 << 5) - 1);
};
/* Rn (bits 9:5) and Rm (bits 20:16) to get registers. */
inline auto AsmGetRn = [](u32 ins) -> u32 { return (ins >> 5) & 0x1Fu; };
inline auto AsmGetRm = [](u32 ins) -> u32 { return (ins >> 16) & 0x1Fu; };
/* Add (shifted register), 64-bit: sf=1 op=0 S=0 01011 shift(2) 0 Rm imm6 Rn Rd. */
inline auto AsmIsAddShiftedReg64 = [](u32 ins) {
return (ins & 0xFF200000u) == 0x8B000000u;
};
inline auto asm_set_rd = [](u32 ins, u8 rd) {
return (ins & 0xFFFFFFE0) | (rd & 0x1F);
};
inline auto asm_set_imm16 = [](u32 ins, u16 imm) {
return (ins & 0xFFE0001F) | ((imm & 0xFFFF) << 5);
};
inline auto AsmGetImm16 = [](u32 ins) {
return static_cast<u16>((ins >> 5) & 0xFFFF);
};
inline auto AsmCompareBrNoRd = [](u32 ins1, u32 ins2) {
constexpr u32 RegMask = ~(((1 << 5) - 1) << 5);
return ((ins1 & RegMask) ^ (ins2 & RegMask)) == 0;
};
inline auto AsmCompareAddNoImm12 = [](u32 ins1, u32 ins2) {
constexpr u32 Imm12Mask = ~(((1 << 12) - 1) << 10);
return ((ins1 & Imm12Mask) ^ (ins2 & Imm12Mask)) == 0;
};
inline auto AsmCompareAdrpNoImm = [](u32 ins1, u32 ins2) {
constexpr u32 ImmMask = ~((((1 << 2) - 1) << 29) | (((1 << 19) - 1) << 5));
return ((ins1 & ImmMask) ^ (ins2 & ImmMask)) == 0;
};
/* Csel (Conditional Select) */
/*
SF | Op | S | | RM | Cond | 0 | 0 | Rn | Rd
31 | 30 | 29 | 28 27 26 25 24 23 | 20 19 18 17 16 | 15 14 13 12 | 11 | 10 | 9 8 7 6 5 | 4 3 2 1 0
*/
inline auto AsmCbzCompareOpcodeOnly = [](u32 ins1, u32 ins2) {
return ((ins1 ^ ins2) >> 24) == 0;
};
inline auto AsmBlCompareOpcodeOnly = [](u32 ins1, u32 ins2) {
return ((ins1 ^ ins2) >> 26) == 0;
};
inline auto AsmIsAdrX0 = [](u32 ins) {
return (ins & 0x9F00001Fu) == 0x10000000u;
};
inline auto AsmAdrTarget = [](u32 ins, uintptr_t pc) -> uintptr_t {
s64 imm = static_cast<s64>((((ins >> 5) & 0x7FFFFu) << 2) | ((ins >> 29) & 0x3u));
imm = (imm << 43) >> 43;
return static_cast<uintptr_t>(static_cast<s64>(pc) + imm);
};
/* adrp Rd, target */
inline auto AsmMakeAdrp = [](uintptr_t pc, uintptr_t target, u32 rd) -> u32 {
const s64 delta = static_cast<s64>(target & ~static_cast<uintptr_t>(0xFFF))
- static_cast<s64>(pc & ~static_cast<uintptr_t>(0xFFF));
const u32 imm = static_cast<u32>((delta >> 12) & 0x1FFFFF); /* 21-bit page */
const u32 immlo = imm & 0x3;
const u32 immhi = (imm >> 2) & 0x7FFFF;
return 0x90000000u | (immlo << 29) | (immhi << 5) | (rd & 0x1Fu);
};
inline auto AsmSetAdrTarget = [](u32 ins, uintptr_t pc, uintptr_t target) -> u32 {
const s64 delta = static_cast<s64>(target) - static_cast<s64>(pc);
const u32 immlo = static_cast<u32>(delta & 0x3);
const u32 immhi = static_cast<u32>((delta >> 2) & 0x7FFFF);
return (ins & ~((0x3u << 29) | (0x7FFFFu << 5))) | (immlo << 29) | (immhi << 5);
};
/* adrp: bit31=1, bits 28:24 = 10000. */
inline auto AsmIsAdrp = [](u32 ins) -> bool {
return (ins & 0x9F000000u) == 0x90000000u;
};
inline auto AsmAdrpPageOffset = [](u32 ins) -> s64 {
s64 imm = static_cast<s64>((((ins >> 5) & 0x7FFFFu) << 2) | ((ins >> 29) & 0x3u));
imm = (imm << 43) >> 43; /* sign-extend the 21-bit immediate */
return imm << 12;
};
/* add (immediate), 64-bit: sf=1 op=0 S=0 100010 sh imm12 Rn Rd. */
inline auto AsmIsAddImm64 = [](u32 ins) -> bool {
return (ins & 0xFF800000u) == 0x91000000u;
};
inline auto AsmGetImm12 = [](u32 ins) -> u32 {
return (ins >> 10) & 0xFFFu;
};
inline auto AsmMakeAddImm64 = [](u32 rd, u32 rn, u32 imm12) -> u32 {
return 0x91000000u | ((imm12 & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rd & 0x1Fu);
};
/* movz Wd,#imm16 (no shift). */
inline auto AsmMakeMovzW = [](u32 rd, u16 imm16) -> u32 {
return 0x52800000u | (static_cast<u32>(imm16) << 5) | (rd & 0x1Fu);
};
/* mov Xd,Xm == orr Xd,XZR,Xm. */
inline auto AsmMakeMovReg = [](u32 rd, u32 rm) -> u32 {
return 0xAA0003E0u | ((rm & 0x1Fu) << 16) | (rd & 0x1Fu);
};
/* ldr Xt,[Xn,#byteOff] */
inline auto AsmMakeLdrImm64 = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0xF9400000u | (((byteOff / 8u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* b <target> (PC-relative, +-128MB). */
inline auto AsmMakeB = [](uintptr_t pc, uintptr_t target) -> u32 {
const s64 off = (static_cast<s64>(target) - static_cast<s64>(pc)) >> 2;
return 0x14000000u | (static_cast<u32>(off) & 0x03FFFFFFu);
};
/* b.<cond> <target> (cond: LO/CC=0x3, LE=0xD, NE=0x1, ...). */
inline auto AsmMakeBCond = [](uintptr_t pc, uintptr_t target, u32 cond) -> u32 {
const s64 off = (static_cast<s64>(target) - static_cast<s64>(pc)) >> 2;
return 0x54000000u | ((static_cast<u32>(off) & 0x7FFFFu) << 5) | (cond & 0xFu);
};
/* sub Xd,Xn,#imm12 (shift 0). */
inline auto AsmMakeSubImm64 = [](u32 rd, u32 rn, u32 imm12) -> u32 {
return 0xD1000000u | ((imm12 & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rd & 0x1Fu);
};
/* cmp Wn,#imm12 == subs WZR,Wn,#imm12. */
inline auto AsmMakeCmpImm32 = [](u32 rn, u32 imm12) -> u32 {
return 0x7100001Fu | ((imm12 & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5);
};
/* str Wt,[Xn,#byteOff] (32-bit, unsigned scaled by 4). */
inline auto AsmMakeStrImm32 = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0xB9000000u | (((byteOff / 4u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* ldr Wt,[Xn,#byteOff] (32-bit, unsigned scaled by 4). */
inline auto AsmMakeLdrImm32 = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0xB9400000u | (((byteOff / 4u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* add Xd,Xn,Xm,LSL #shift (64-bit shifted register, shift 0-63). */
inline auto AsmMakeAddShiftedReg64 = [](u32 rd, u32 rn, u32 rm, u32 shift) -> u32 {
return 0x8B000000u | ((rm & 0x1Fu) << 16) | ((shift & 0x3Fu) << 10) | ((rn & 0x1Fu) << 5) | (rd & 0x1Fu);
};
/* stp Xt1,Xt2,[Xn,#imm] (signed offset, scaled by 8). */
inline auto AsmMakeStpImm64 = [](u32 rt1, u32 rt2, u32 rn, s32 imm) -> u32 {
return 0xA9000000u | ((static_cast<u32>(imm / 8) & 0x7Fu) << 15) | ((rt2 & 0x1Fu) << 10) | ((rn & 0x1Fu) << 5) | (rt1 & 0x1Fu);
};
/* stp Qt1,Qt2,[Xn,#imm] (128-bit SIMD, signed offset scaled by 16). */
inline auto AsmMakeStpqImm = [](u32 qt1, u32 qt2, u32 rn, s32 imm) -> u32 {
return 0xAD000000u | ((static_cast<u32>(imm / 16) & 0x7Fu) << 15) | ((qt2 & 0x1Fu) << 10) | ((rn & 0x1Fu) << 5) | (qt1 & 0x1Fu);
};
/* str Xt,[Xn,#byteOff] (unsigned scaled by 8). */
inline auto AsmMakeStrImm64 = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0xF9000000u | (((byteOff / 8u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* movn Wd,#imm16 (no shift): loads ~imm16, e.g. movn Wd,#0x37 == -56. */
inline auto AsmMakeMovnW = [](u32 rd, u16 imm16) -> u32 {
return 0x12800000u | (static_cast<u32>(imm16) << 5) | (rd & 0x1Fu);
};
/* bl <target> (PC-relative, +-128MB). */
inline auto AsmMakeBl = [](uintptr_t pc, uintptr_t target) -> u32 {
const s64 off = (static_cast<s64>(target) - static_cast<s64>(pc)) >> 2;
return 0x94000000u | (static_cast<u32>(off) & 0x03FFFFFFu);
};
/* svc #imm16. */
inline auto AsmMakeSvc = [](u16 imm16) -> u32 {
return 0xD4000001u | (static_cast<u32>(imm16) << 5);
};
constexpr u32 RetIns = 0xD65F03C0u; /* ret (x30) */
/* ldrb Wt,[Xn,#imm] (unsigned byte, scale 1). */
inline auto AsmMakeLdrbImm = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0x39400000u | ((byteOff & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* cbz Wt,<target>. */
inline auto AsmMakeCbz = [](uintptr_t pc, uintptr_t target, u32 rt) -> u32 {
const s64 off = (static_cast<s64>(target) - static_cast<s64>(pc)) >> 2;
return 0x34000000u | ((static_cast<u32>(off) & 0x7FFFFu) << 5) | (rt & 0x1Fu);
};
/* strb Wt,[Xn,#imm] (unsigned byte, scale 1). */
inline auto AsmMakeStrbImm = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0x39000000u | ((byteOff & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* mov Xd,X<rm> == orr Xd,XZR,X<rm> (matches any Rd). */
inline auto AsmIsMovReg = [](u32 ins, u32 rm) -> bool {
return (ins & 0xFFFFFFE0u) == (0xAA0003E0u | ((rm & 0x1Fu) << 16));
};
/* add Xd,sp,#imm12 (shift 0). */
inline auto AsmIsAddSpImm = [](u32 ins) -> bool {
return (ins & 0xFFC003E0u) == 0x910003E0u;
};
/* sub Xd,x29,#imm12 (shift 0). */
inline auto AsmIsSubX29Imm = [](u32 ins) -> bool {
return (ins & 0xFFC003E0u) == 0xD10003A0u;
};
inline auto AsmIsB = [](u32 ins) -> bool { return (ins & 0xFC000000u) == 0x14000000u; }; /* b */
inline auto AsmIsBl = [](u32 ins) -> bool { return (ins & 0xFC000000u) == 0x94000000u; }; /* bl */
inline auto AsmIsBCond = [](u32 ins) -> bool { return (ins & 0xFF000010u) == 0x54000000u; }; /* b.c */
/* ldr/str Xt,[Xn,#imm] (64-bit, unsigned scaled offset). */
inline auto AsmIsLdrImm64 = [](u32 ins) -> bool { return (ins & 0xFFC00000u) == 0xF9400000u; };
inline auto AsmIsStrImm64 = [](u32 ins) -> bool { return (ins & 0xFFC00000u) == 0xF9000000u; };
inline auto AsmGetLdStImm64Off = [](u32 ins) -> u32 { return ((ins >> 10) & 0xFFFu) * 8u; };
/* Byte target address of a b/bl at pc. */
inline auto AsmBranchTarget = [](u32 ins, uintptr_t pc) -> uintptr_t {
s64 off = static_cast<s64>((ins & 0x03FFFFFFu) << 2);
off = (off << 36) >> 36; /* sign-extend the 28-bit branch offset */
return static_cast<uintptr_t>(static_cast<s64>(pc) + off);
};
/* Rewrite a scaled immediate-offset load/store (`op Wt,[Xn,#imm]`) into its register-offset form */
inline auto AsmSetLdStRegOffset = [](u32 ldstImm, u32 rm) -> u32 {
return (ldstImm & 0xC0C003FFu) | 0x38207800u | ((rm & 0x1Fu) << 16);
};
inline auto AsmIsLdpX = [](u32 ins) {
return (ins & 0xFE400000u) == 0xA8400000u;
};
inline bool AsmComparePrologue(u32 ins1, u32 ins2, u32 ins3, u32 cmp1, u32 cmp2, u32 cmp3) {
constexpr u32 StpImmMask = ~((((1u << 7) - 1u) << 15));
bool firstMatch = (ins1 & StpImmMask) == (cmp1 & StpImmMask);
constexpr u32 StpRegsImmMask = ~(((1u << 5) - 1u) |(((1u << 5) - 1u) << 10) | (((1u << 7) - 1u) << 15));
bool secondMatch = (ins2 & StpRegsImmMask) == (cmp2 & StpRegsImmMask);
constexpr u32 MovMask = ~((1u << 5) - 1u);
bool thirdMatch = (ins3 & MovMask) == (cmp3 & MovMask);
return firstMatch && secondMatch && thirdMatch;
template <typename... Fs>
u32 *ScanAssembly(u32 *ptr, u32 scanLimit, u32 pattern, Fs... ignoreFields) {
return ScanAssembly(ptr, scanLimit, pattern, [=](u32 a, u32 b) ALWAYS_INLINE_LAMBDA { return Ignoring(a, b, ignoreFields...); });
}
inline auto AsmCompareCselNoReg = [](u32 ins1, u32 ins2) {
constexpr u32 ClearReg = ~(((1 << 10) - 1) | (((1 << 5) - 1) << 16));
return ((ins1 & ClearReg) ^ (ins2 & ClearReg)) == 0;
};
/* Mul */
/*
SF | Op54 | Op31 | RM | o0 | RA | RN | 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
*/
inline auto AsmCompareMullNoReg = [](u32 ins1, u32 ins2) {
constexpr u32 ClearReg = ~(((1 << 10) - 1) | (((1 << 5) - 1) << 16));
return ((ins1 & ClearReg) ^ (ins2 & ClearReg)) == 0;
};
/* Mul */
/* MUL W11, W24, W26 */
/* multiplies by 1000, mV -> uV */
/*
SF | Op54 | Op31 | RM | o0 | RA | RN | 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
*/
inline auto AsmGetMullRn = [](u32 ins) {
constexpr u32 Mask = ((1 << 5) - 1) << 5;
return (ins & Mask) >> 5;
};
inline auto AsmGetMullRm = [](u32 ins) {
constexpr u32 Mask = ((1 << 5) - 1) << 16;
return (ins & Mask) >> 16;
};
/* Subs (Shifted register) */
/*
SF | Op | S | | Shift | 0 | RM | Imm6 | Rn | 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
*/
inline auto AsmSubsSetRn = [](u32 ins, u8 rn) {
constexpr u32 RnMaskClear = ~(((1u << 5) - 1u) << 5);
constexpr u32 RnMaskSet = (1u << 5) - 1u;
return (ins & RnMaskClear) | ((static_cast<u32>(rn) & RnMaskSet) << 5);
};
/* Subs (Immediate) */
/*
SF | Op | S | | Sh | Imm12 | Rn | 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
*/
inline auto AsmSubsSetImm12 = [](u32 ins, u16 imm12) {
constexpr u32 ClearMask = ~(((1u << 12) - 1) << 10);
constexpr u32 SetImm12Mask = ( 1u << 12) - 1;
return (ins & ClearMask) | ((imm12 & SetImm12Mask) << 10);
};
inline auto AsmSubsCompareNoReg = [](u32 ins1, u32 ins2) {
return ((ins1 ^ ins2) >> 10) == 0;
};
inline auto AsmCompareBrConNoImm19 = [](u32 ins1, u32 ins2) {
constexpr u32 ClearImm19 = ~(((1 << 19) - 1) << 5);
return (ins1 & ClearImm19) == (ins2 & ClearImm19);
};
inline bool AsmIsFramePush(u32 ins) {
constexpr u32 FramePushMask = 0xFFC07FFF;
constexpr u32 FramePushValue = 0xA9807BFD;
return (ins & FramePushMask) == FramePushValue;
constexpr bool IsFramePush(u32 ins) {
constexpr u32 Pattern = Encode(op::StpPreImm64, {field::Rt, reg::Fp}, {field::Rt2, reg::Lr}, {field::Rn, reg::Sp});
return Ignoring(ins, Pattern, field::PairOff8);
}
inline u32 *FindFnPrologue(u32 *ptr, u32 margin, u32 *nsoStart) {
@@ -380,7 +257,7 @@ namespace ams::ldr::hoc::pcv {
break;
}
if (AsmIsFramePush(*candidate)) {
if (IsFramePush(*candidate)) {
return candidate;
}
}