Refactor busReloc to use hooks.

Also add sd logging and temporarily remove new Erista timings; they're still slightly too explosive.
Todo: Rename stuff and fix uart redirect.
This commit is contained in:
Lightos1 committed 2026-08-25 20:40:32 +02:00
1 parent 7fea98a72d
commit eba5573711
9 files changed
+200 -76

No files matched your search

@@ -95,7 +95,7 @@ namespace ams::ldr::hoc::pcv::erista {
u32 wlIndex = 0;
for (u32 i = 0; i < std::size(rlMapDBI); ++i) {
if (rlMapDBI[i] == RL) {
if (rlMapDBI[i] == 32) {
rlIndex = i;
break;
}
@@ -119,28 +119,28 @@ namespace ams::ldr::hoc::pcv::erista {
/* Accept imperfection or prepare for suffering. */
// #if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
WRITE_PARAM_ALL_REG(table, emc_einput, einput);
WRITE_PARAM_ALL_REG(table, emc_einput_duration, einput_duration);
WRITE_PARAM_ALL_REG(table, emc_obdly, obdly);
// WRITE_PARAM_ALL_REG(table, emc_einput, einput);
// WRITE_PARAM_ALL_REG(table, emc_einput_duration, einput_duration);
// WRITE_PARAM_ALL_REG(table, emc_obdly, obdly);
WRITE_PARAM_ALL_REG(table, emc_ibdly, ibdly);
WRITE_PARAM_ALL_REG(table, emc_wdv_mask, wdv);
WRITE_PARAM_ALL_REG(table, emc_quse_width, quse_width);
WRITE_PARAM_ALL_REG(table, emc_quse, quse);
// WRITE_PARAM_ALL_REG(table, emc_quse_width, quse_width);
// WRITE_PARAM_ALL_REG(table, emc_quse, quse);
WRITE_PARAM_ALL_REG(table, emc_wdv, wdv);
WRITE_PARAM_ALL_REG(table, emc_wsv, wsv);
WRITE_PARAM_ALL_REG(table, emc_wev, wev);
WRITE_PARAM_ALL_REG(table, emc_qrst, qrst);
WRITE_PARAM_ALL_REG(table, emc_tr_qrst, qrst);
WRITE_PARAM_ALL_REG(table, emc_qsafe, qsafe);
WRITE_PARAM_ALL_REG(table, emc_tr_qsafe, qsafe);
WRITE_PARAM_ALL_REG(table, emc_tr_qpop, qpop);
WRITE_PARAM_ALL_REG(table, emc_qpop, qpop);
WRITE_PARAM_ALL_REG(table, emc_rdv, rdv);
WRITE_PARAM_ALL_REG(table, emc_tr_rdv_mask, rdv + 2);
WRITE_PARAM_ALL_REG(table, emc_rdv_early, rdv - 2);
WRITE_PARAM_ALL_REG(table, emc_rdv_early_mask, rdv);
WRITE_PARAM_ALL_REG(table, emc_rdv_mask, rdv + 2);
WRITE_PARAM_ALL_REG(table, emc_tr_rdv, rdv);
// WRITE_PARAM_ALL_REG(table, emc_qrst, qrst);
// WRITE_PARAM_ALL_REG(table, emc_tr_qrst, qrst);
// WRITE_PARAM_ALL_REG(table, emc_qsafe, qsafe);
// WRITE_PARAM_ALL_REG(table, emc_tr_qsafe, qsafe);
// WRITE_PARAM_ALL_REG(table, emc_tr_qpop, qpop);
// WRITE_PARAM_ALL_REG(table, emc_qpop, qpop);
// WRITE_PARAM_ALL_REG(table, emc_rdv, rdv);
// WRITE_PARAM_ALL_REG(table, emc_tr_rdv_mask, rdv + 2);
// WRITE_PARAM_ALL_REG(table, emc_rdv_early, rdv - 2);
// WRITE_PARAM_ALL_REG(table, emc_rdv_early_mask, rdv);
// WRITE_PARAM_ALL_REG(table, emc_rdv_mask, rdv + 2);
// WRITE_PARAM_ALL_REG(table, emc_tr_rdv, rdv);
table->emc_mrw2 = (table->emc_mrw2 & ~0xFFu) | static_cast<u32>(mrw2);
table->dram_timings.rl = RL;
// #endif
@@ -34,20 +34,19 @@ namespace ams::ldr::hoc::pcv::mariko {
u32 *nsoStart;
namespace {
size_t g_nso_size = 0;
uintptr_t g_cave_cursor = 0;
struct {
u32 *site = nullptr;
} busInitCache;
}
static uintptr_t CaveReserve(size_t count) {
if (g_pcv_cave == 0 || g_cave_cursor == 0) {
return 0;
}
if (g_cave_cursor + count * sizeof(u32) > g_pcv_cave + g_pcv_cave_size) {
return 0;
}
const uintptr_t entry = g_cave_cursor;
g_cave_cursor += count * sizeof(u32);
return entry;
DEFINE_HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData);
namespace {
size_t g_nso_size = 0;
}
[[maybe_unused]] static uintptr_t CaveReserve(size_t count) {
return reinterpret_cast<uintptr_t>(Hooks().Reserve(count));
}
#if HOC_UART_LOG
@@ -176,50 +175,27 @@ namespace ams::ldr::hoc::pcv::mariko {
}
#endif
/* Relocate C2/C3Bus to avoid issues*/
Result BusFreqReloc(u32 *ptr) {
const u32 busReg = _asm::Get(ptr[0], _asm::field::Rn); /* ldr Xbuf,[Xbus,#0x10] : bus struct pointer */
const u32 bufReg = _asm::Get(ptr[0], _asm::field::Rt); /* : freq-buffer arg */
const u32 bufOff = _asm::Get(ptr[0], _asm::field::Off8); /* : bus->freqBuf offset */
const u32 cntReg = _asm::Get(ptr[1], _asm::field::Rd); /* add Xcnt,Xbus,#0x18 : arg2 (&count) */
const u32 railReg = _asm::Get(ptr[2], _asm::field::Rt); /* str Xrail,[Xbus,#0x50]: arg0 (rail) */
u32 *call = ptr + 3; /* the bl to relocate */
const uintptr_t realFn = _asm::Target26(*call, reinterpret_cast<uintptr_t>(call));
HOOK_PAYLOAD_FN u64 BusFreqExtendImpl(SharedClkBus *bus) {
HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData);
/* Pick 3 scratch registers */
u32 s[3], sc = 0;
for (u32 r = 9; r <= 15 && sc < 3; ++r) {
if (r != busReg && r != bufReg && r != cntReg && r != railReg) {
s[sc++] = r;
}
}
R_UNLESS(sc == 3, ldr::ResultInvalidBusFreqReloc());
u32 *table = data->busData.next;
bus->allowedFreqTable = table;
data->busData.next = table + EmcDvfsTableEntryCount;
const uintptr_t tramp = CaveReserve(9);
R_UNLESS(tramp != 0, ldr::ResultInvalidBusFreqReloc());
const uintptr_t region = g_pcv_scratch + HocBusFreqBufOffset; /* [0]=counter, +0x10 + i*0x400 = bufs */
u32 *t = reinterpret_cast<u32 *>(tramp);
size_t n = 0;
auto emit = [&](u32 ins) { t[n] = ins; ++n; };
using namespace _asm::field;
emit(_asm::RetargetAdrp(_asm::Encode(_asm::op::Adrp, {Rd, s[0]}), tramp + n * 4, region)); /* adrp s0,<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, _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]);
R_SUCCEED();
/* 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) {
constexpr u32 PrologueMargin = 140;
u32 *functionPrologue = _asm::FindFnPrologue(ptr, PrologueMargin, nsoStart);
R_UNLESS(functionPrologue != nullptr, ldr::ResultInvalidBusFreqReloc());
busInitCache.site = functionPrologue;
R_SUCCEED();
}
#if HOC_UART_LOG
/* Force GetEffectiveVerbosityLevel to return a non-zero level so all NvLog runs. */
@@ -531,12 +507,35 @@ namespace ams::ldr::hoc::pcv::mariko {
R_SUCCEED();
}
Result SharedClkBusInstallHooks(HookPayloadData *data) {
R_UNLESS(busInitCache.site != nullptr, ldr::ResultInvalidBusFreqReloc());
uintptr_t originalFn = 0;
R_TRY(INSTALL_IMPL_HOOK_ORIG(busInitCache.site, BusFreqExtendImpl, &originalFn));
data->busData.originalFnCallback = originalFn;
data->busData.next = reinterpret_cast<u32 *>(Hooks().ToVa(data->busData.table[0]));
R_SUCCEED();
}
Result InstallHooks() {
R_TRY(Hooks().CheckEnabled());
R_TRY(Hooks().CopyPayload());
auto *data = Hooks().BindData(m_HookPayloadData);
R_UNLESS(data != nullptr, ldr::ResultHookDataOutOfMemory());
R_TRY(SharedClkBusInstallHooks(data));
R_SUCCEED();
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
nsoStart = reinterpret_cast<u32 *>(mapped_nso);
g_pcv_scratch = mapped_nso + nso_size - HocPcvScratchSize;
g_nso_size = nso_size;
g_cave_cursor = g_pcv_cave; /* start the .text-cave bump allocator (0 if unavailable) */
MtcGenerateFreqTables();
@@ -593,6 +592,10 @@ namespace ams::ldr::hoc::pcv::mariko {
CRASH(entry.description);
}
}
if (R_FAILED(InstallHooks())) {
panic::SmcError(panic::Patch);
}
}
}
@@ -26,6 +26,17 @@
namespace ams::ldr::hoc::pcv::mariko {
constexpr u32 BusFreqTableCount = 2;
struct HookPayloadData {
struct {
u32 *next; /* Save the bytes. */
uintptr_t originalFnCallback;
u32 table[BusFreqTableCount][EmcDvfsTableEntryCount]; /* Original bus table size: 32. */
} busData;
};
DECLARE_HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData);
extern u32 *nsoStart;
struct DvbEntry {
@@ -245,11 +245,25 @@ namespace ams::ldr::hoc::pcv::_asm {
return ScanAssembly(ptr, scanLimit, pattern, [=](u32 a, u32 b) ALWAYS_INLINE_LAMBDA { return Ignoring(a, b, ignoreFields...); });
}
constexpr bool IsFramePush(u32 ins) {
constexpr bool IsFramePushPre(u32 ins) {
constexpr u32 Pattern = Encode(op::StpPreImm64, {field::Rt, reg::Fp}, {field::Rt2, reg::Lr}, {field::Rn, reg::Sp});
return Ignoring(ins, Pattern, field::PairOff8);
}
constexpr bool IsFramePushOffset(u32 ins) {
constexpr u32 Pattern = Encode(op::StpImm64, {field::Rt, reg::Fp}, {field::Rt2, reg::Lr}, {field::Rn, reg::Sp});
return Ignoring(ins, Pattern, field::PairOff8);
}
constexpr bool IsSpSubImm(u32 ins) {
constexpr u32 Pattern = Encode(op::SubImm64, {field::Rd, reg::Sp}, {field::Rn, reg::Sp});
return Ignoring(ins, Pattern, field::Imm12);
}
constexpr bool IsFramePush(u32 ins) {
return IsFramePushPre(ins) || IsFramePushOffset(ins);
}
inline u32 *FindFnPrologue(u32 *ptr, u32 margin, u32 *nsoStart) {
for (u32 i = 0; i <= margin; ++i) {
u32 *candidate = ptr - i;
@@ -257,9 +271,13 @@ namespace ams::ldr::hoc::pcv::_asm {
break;
}
if (IsFramePush(*candidate)) {
if (IsFramePushPre(*candidate)) {
return candidate;
}
if (IsFramePushOffset(*candidate) && candidate - 1 >= nsoStart && IsSpSubImm(candidate[-1])) {
return candidate - 1;
}
}
return nullptr;
@@ -163,6 +163,30 @@ namespace ams::ldr::hoc::pcv {
};
static_assert(sizeof(CvbMeta) == 0x78);
struct SubClockStub {
u8 _unknown[0x48];
};
struct SharedClkBus {
u64 moduleId;
u32 currentRateHz;
u32 padding0;
u32 *allowedFreqTable;
u32 allowedFreqCount;
u32 padding1;
SubClockStub *subClocksTable;
u32 subClockCount;
u32 padding2;
void *unk30;
void *clockSource;
u32 targetRateHz;
u32 saveDivFlag;
u32 idleRateHz; /* Unsure. */
u32 alwaysOnFlag;
u64 clockSession;
};
static_assert(sizeof(SharedClkBus) == 0x58);
constexpr u32 CpuClkOSLimit = 1785'000;
constexpr u32 GpuClkOsLimit = 921'600;
constexpr u32 EmcClkOSLimit = 1600'000;
@@ -18,7 +18,8 @@
#include "../oc_common.hpp"
#define HOOK_PAYLOAD_FN __attribute__((section("hoc_hookpayload"), used, noinline, visibility("hidden")))
/* aligned(4) is the ARM64 instruction alignment. This saves some memory over 16 byte padding */
#define HOOK_PAYLOAD_FN __attribute__((section("hoc_hookpayload"), used, noinline, visibility("hidden"), aligned(4)))
/* Inline asm because fuck compilers: GCC refuses to put a variable and a function in the same section. */
/* It wants alloc+write for one and alloc+exec for the other. */
@@ -147,6 +148,10 @@ namespace ams::ldr::hoc::pcv {
}
uintptr_t CaveVa() const { return ToVa(reinterpret_cast<const void *>(m_cave)); }
uintptr_t CaveBase() const { return m_cave; }
/* Todo remove? */
u32 *Reserve(size_t words) { return this->AllocCode(words); }
Result CopyPayload() {
R_TRY(this->CheckEnabled());
@@ -284,6 +289,8 @@ namespace ams::ldr::hoc::pcv {
Result ValidatePayloadFn(const void *fn) const {
constexpr u32 MaxInstructions = 512;
constexpr u32 RetInsn = 0xD65F03C0u;
constexpr u32 BrMask = 0xFFFFFC1Fu;
constexpr u32 BrInsn = 0xD61F0000u;
const uintptr_t lo = m_payload;
const uintptr_t hi = m_payload + m_payload_sz;
@@ -302,7 +309,7 @@ namespace ams::ldr::hoc::pcv {
const u32 insn = insns[i];
const uintptr_t site = base + i * sizeof(u32);
if (insn == RetInsn) {
if (insn == RetInsn || (insn & BrMask) == BrInsn) {
R_SUCCEED();
}
@@ -334,7 +341,7 @@ namespace ams::ldr::hoc::pcv {
private:
u32 *AllocCode(size_t words) {
const size_t bytes = words * sizeof(u32);
if (!this->IsEnabled() || m_used + bytes > m_cave_size) {
if (!this->IsEnabled() || m_payload == 0 || m_used + bytes > m_cave_size) {
return nullptr;
}