From af8912321c297ed21f8114761f274a5d040fa9d5 Mon Sep 17 00:00:00 2001 From: Lightos1 <124387232+Lightos1@users.noreply.github.com> Date: Tue, 28 Jul 2026 19:54:07 +0200 Subject: [PATCH] ldr: major refactor part 1 --- .../erista/calculate_timings_erista.cpp | 2 +- .../erista/calculate_timings_erista.hpp | 0 .../source/oc/pcv/erista/pcv_erista.cpp | 97 ++ .../source/oc/pcv/erista/pcv_erista.hpp | 42 + .../source/oc/pcv/erista/pcv_erista_cpu.cpp | 108 ++ .../source/oc/pcv/erista/pcv_erista_cpu.hpp | 66 + .../source/oc/pcv/erista/pcv_erista_gpu.cpp | 123 ++ .../source/oc/pcv/erista/pcv_erista_gpu.hpp | 82 + .../pcv_erista_mtc.cpp} | 273 +-- .../source/oc/pcv/erista/pcv_erista_mtc.hpp | 92 + .../mariko/calculate_timings_mariko.cpp} | 408 ++--- .../mariko/calculate_timings_mariko.hpp} | 48 +- .../source/oc/pcv/mariko/pcv_mariko.cpp | 588 +++++++ .../source/oc/pcv/{ => mariko}/pcv_mariko.hpp | 8 +- .../source/oc/pcv/mariko/pcv_mariko_cpu.cpp | 237 +++ .../source/oc/pcv/mariko/pcv_mariko_cpu.hpp | 34 + .../source/oc/pcv/mariko/pcv_mariko_gpu.cpp | 140 ++ .../source/oc/pcv/mariko/pcv_mariko_gpu.hpp | 35 + .../source/oc/pcv/mariko/pcv_mariko_mtc.cpp | 676 ++++++++ .../source/oc/pcv/mariko/pcv_mariko_mtc.hpp | 35 + .../oc/{ => pcv}/mariko/timing_tables.cpp | 94 +- .../oc/{ => pcv}/mariko/timing_tables.hpp | 62 +- .../stratosphere/loader/source/oc/pcv/pcv.hpp | 11 +- .../loader/source/oc/pcv/pcv_erista.hpp | 165 -- .../loader/source/oc/pcv/pcv_hook.hpp | 3 + .../loader/source/oc/pcv/pcv_mariko.cpp | 1545 ----------------- 26 files changed, 2695 insertions(+), 2279 deletions(-) rename Source/Atmosphere/stratosphere/loader/source/oc/{ => pcv}/erista/calculate_timings_erista.cpp (99%) rename Source/Atmosphere/stratosphere/loader/source/oc/{ => pcv}/erista/calculate_timings_erista.hpp (100%) create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista.cpp create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista.hpp create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_cpu.cpp create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_cpu.hpp create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.cpp create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.hpp rename Source/Atmosphere/stratosphere/loader/source/oc/pcv/{pcv_erista.cpp => erista/pcv_erista_mtc.cpp} (72%) create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.hpp rename Source/Atmosphere/stratosphere/loader/source/oc/{mariko/calculate_timings.cpp => pcv/mariko/calculate_timings_mariko.cpp} (96%) rename Source/Atmosphere/stratosphere/loader/source/oc/{mariko/calculate_timings.hpp => pcv/mariko/calculate_timings_mariko.hpp} (96%) create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.cpp rename Source/Atmosphere/stratosphere/loader/source/oc/pcv/{ => mariko}/pcv_mariko.hpp (99%) create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.cpp create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.hpp create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.cpp create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.hpp create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.cpp create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.hpp rename Source/Atmosphere/stratosphere/loader/source/oc/{ => pcv}/mariko/timing_tables.cpp (96%) rename Source/Atmosphere/stratosphere/loader/source/oc/{ => pcv}/mariko/timing_tables.hpp (96%) delete mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.hpp delete mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/erista/calculate_timings_erista.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.cpp similarity index 99% rename from Source/Atmosphere/stratosphere/loader/source/oc/erista/calculate_timings_erista.cpp rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.cpp index a8106726..fc6ae8f8 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/erista/calculate_timings_erista.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.cpp @@ -14,7 +14,7 @@ * along with this program. If not, see . */ -#include "../mtc_timing_value.hpp" +#include "../../mtc_timing_value.hpp" namespace ams::ldr::hoc::pcv::erista { diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/erista/calculate_timings_erista.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.hpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/erista/calculate_timings_erista.hpp rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.hpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista.cpp new file mode 100644 index 00000000..742776cb --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * + * Copyright (c) B3711 + * + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../pcv.hpp" +#include "../../mtc_timing_value.hpp" +#include "pcv_erista_cpu.hpp" +#include "pcv_erista_gpu.hpp" +#include "pcv_erista_mtc.hpp" +#include "calculate_timings_erista.hpp" + +namespace ams::ldr::hoc::pcv::erista { + + DEFINE_HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData); + + u32 *nsoStart; + + Result InstallHooks() { + R_TRY(Hooks().CheckEnabled()); + + R_TRY(Hooks().CopyPayload()); + + auto *data = Hooks().BindData(e_HookPayloadData); + R_UNLESS(data != nullptr, ldr::ResultHookDataOutOfMemory()); + + R_TRY(MtcInstallHooks(data)); + + R_SUCCEED(); + } + + void Patch(uintptr_t mapped_nso, size_t nso_size) { + nsoStart = reinterpret_cast(mapped_nso); + MtcGenerateFreqTables(); + + u32 CpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq); + u32 GpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq); + + PatcherEntry patches[] = { + {"CPU Freq Table", CpuFreqCvbTable, 1, nullptr, CpuCvbDefaultMaxFreq }, + {"CPU Volt DVFS", &CpuVoltDvfs, 1, nullptr, CpuVminOfficial }, + {"CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial }, + {"CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low }, + {"GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial }, + {"GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial }, + {"GPU Freq Table", GpuFreqCvbTable, 1, nullptr, GpuCvbDefaultMaxFreq }, + {"GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn }, + {"GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax }, + // {"GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit }, + {"MEM Table Asm", &MemMtcTableAsm, 4, &MemMtcGetGetTablePatternFn }, + {"MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit }, + {"MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit }, + {"MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit }, + {"MEM Volt", &MemVoltHandler, 2, nullptr, MemVoltHOS }, + }; + + for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(EristaMtcTable); ptr += sizeof(u32)) { + u32 *ptr32 = reinterpret_cast(ptr); + for (auto &entry : patches) { + if (R_SUCCEEDED(entry.SearchAndApply(ptr32))) { + break; + } + } + } + + for (auto &entry : patches) { + LOGGING("%s Count: %zu", entry.description, entry.patched_count); + if (R_FAILED(entry.CheckResult())) { + panic::SmcError(panic::Patch); + + CRASH(entry.description); + } + } + + if (R_FAILED(InstallHooks())) { + panic::SmcError(panic::Patch); + } + } + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista.hpp new file mode 100644 index 00000000..8a530486 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "../../oc_common.hpp" +#include "../pcv_common.hpp" +#include "../pcv_asm.hpp" +#include "../pcv_hook.hpp" + +namespace ams::ldr::hoc::pcv::erista { + + struct HookPayloadData { + struct { + EristaMtcTable *mtcTable; + u32 mtcCount; + } mtcTableAsm; + }; + DECLARE_HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData); + + extern u32 *nsoStart; + + void Patch(uintptr_t mapped_nso, size_t nso_size); + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_cpu.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_cpu.cpp new file mode 100644 index 00000000..8fddf0af --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_cpu.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * + * Copyright (c) B3711 + * + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../pcv.hpp" + +namespace ams::ldr::hoc::pcv::erista { + + Result CpuVoltDvfs(u32 *ptr) { + if (std::memcmp(ptr + 5, cpuVoltDvfsPattern, sizeof(cpuVoltDvfsPattern))) { + R_THROW(ldr::ResultInvalidCpuMinVolt()); + } + + if (C.eristaCpuVmin) { + PATCH_OFFSET(ptr, C.eristaCpuVmin); + } + + if (C.eristaCpuUV) { + PATCH_OFFSET(ptr - 2, C.eristaCpuVmin); + } + + if (C.eristaCpuMaxVolt) { + PATCH_OFFSET(ptr + 5, C.eristaCpuMaxVolt); + } + + R_SUCCEED(); + } + + Result CpuVoltThermals(u32 *ptr) { + if (std::memcmp(ptr - 6, cpuVoltageThermalPattern, sizeof(cpuVoltageThermalPattern))) { + R_THROW(ldr::ResultInvalidCpuMinVolt()); + } + + if (C.eristaCpuVmin) { + PATCH_OFFSET( ptr, C.eristaCpuVmin); + PATCH_OFFSET(ptr + 3, C.eristaCpuVmin); + PATCH_OFFSET(ptr + 6, C.eristaCpuVmin); + } + + if (C.eristaCpuMaxVolt) { + PATCH_OFFSET(ptr - 2, C.eristaCpuMaxVolt); + PATCH_OFFSET(ptr + 1, C.eristaCpuMaxVolt); + PATCH_OFFSET(ptr + 4, C.eristaCpuMaxVolt); + PATCH_OFFSET(ptr + 7, C.eristaCpuMaxVolt); + } + + R_SUCCEED(); + } + + Result CpuVoltDfll(u32* ptr) { + CvbCpuDfllData *entry = reinterpret_cast(ptr); + + R_UNLESS(entry->tune0_low == 0xFFEAD0FF, ldr::ResultInvalidCpuVoltDfllEntry()); + R_UNLESS(entry->tune0_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry()); + R_UNLESS(entry->tune1_low == 0x0, ldr::ResultInvalidCpuVoltDfllEntry()); + R_UNLESS(entry->tune1_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry()); + + if (!C.eristaCpuUV) { + R_SKIP(); + } + + switch (C.eristaCpuUV) { + case 1: + PATCH_OFFSET(&(entry->tune0_high), 0xffff); + PATCH_OFFSET(&(entry->tune1_high), 0x27007ff); + break; + case 2: + PATCH_OFFSET(&(entry->tune0_high), 0xefff); + PATCH_OFFSET(&(entry->tune1_high), 0x27407ff); + break; + case 3: + PATCH_OFFSET(&(entry->tune0_high), 0xdfff); + PATCH_OFFSET(&(entry->tune1_high), 0x27807ff); + break; + case 4: + PATCH_OFFSET(&(entry->tune0_high), 0xdfdf); + PATCH_OFFSET(&(entry->tune1_high), 0x27a07ff); + break; + case 5: + PATCH_OFFSET(&(entry->tune0_high), 0xcfdf); + PATCH_OFFSET(&(entry->tune1_high), 0x37007ff); + break; + default: + break; + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_cpu.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_cpu.hpp new file mode 100644 index 00000000..e028ff54 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_cpu.hpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * + * Copyright (c) B3711 + * + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include "pcv_erista.hpp" + +namespace ams::ldr::hoc::pcv::erista { + + constexpr cvb_entry_t CpuCvbTableDefault[] = { + // CPU_PLL_CVB_TABLE_ODN + { 204000, {721094}, { } }, + { 306000, {754040}, { } }, + { 408000, {786986}, { } }, + { 510000, {819932}, { } }, + { 612000, {852878}, { } }, + { 714000, {885824}, { } }, + { 816000, {918770}, { } }, + { 918000, {951716}, { } }, + { 1020000, {984662}, { -2875621, 358099, -8585} }, + { 1122000, {1017608}, { -52225, 104159, -2816} }, + { 1224000, {1050554}, { 1076868, 8356, -727} }, + { 1326000, {1083500}, { 2208191, -84659, 1240} }, + { 1428000, {1116446}, { 2519460, -105063, 1611} }, + { 1581000, {1130000}, { 2889664, -122173, 1834} }, + { 1683000, {1168000}, { 5100873, -279186, 4747} }, + { 1785000, {1227500}, { 5100873, -279186, 4747} }, + { }, + }; + + constexpr u32 CpuVoltOfficial = 1227; + constexpr u32 CpuVminOfficial = 825; + constexpr u32 CpuTune0Low = 0xFFEAD0FF; + + constexpr u32 CpuVoltL4T = 1257'000; + + static const u32 cpuVoltDvfsPattern[] = { 1227, 1000, 100, 1000, 0 }; + static_assert(sizeof(cpuVoltDvfsPattern) == 0x14, "Invalid cpuVoltDvfsPattern size"); + + static const u32 cpuVoltageThermalPattern[] = { 950, 1132, 0, 950, 1227, 0, 825, 1227, 15000, 825, 1170, 60000, 825, 1132, 80000 }; + static_assert(sizeof(cpuVoltageThermalPattern) == 0x3c, "Invalid cpuVoltageThermalPattern size"); + + + Result CpuVoltDvfs(u32 *ptr); + Result CpuVoltThermals(u32 *ptr); + Result CpuVoltDfll(u32* ptr); + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.cpp new file mode 100644 index 00000000..937af7d7 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * + * Copyright (c) B3711 + * + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../pcv.hpp" +#include "../pcv_asm.hpp" + +namespace ams::ldr::hoc::pcv::erista { + + Result GpuVoltDVFS(u32 *ptr) { + if (std::memcmp(ptr, gpuVoltDvfsPattern, sizeof(gpuVoltDvfsPattern))) { + R_THROW(ldr::ResultInvalidGpuDvfs()); + } + + if (C.eristaGpuVmin) { + PATCH_OFFSET(ptr, C.eristaGpuVmin); + } + + R_SUCCEED(); + } + + Result GpuVoltThermals(u32 *ptr) { + if (std::memcmp(ptr - 3, gpuVoltThermalPattern, sizeof(gpuVoltThermalPattern))) { + R_THROW(ldr::ResultInvalidGpuDvfs()); + } + + if (C.eristaGpuVmin) { + PATCH_OFFSET(ptr, C.eristaGpuVmin); + PATCH_OFFSET(ptr + 3, C.eristaGpuVmin); + PATCH_OFFSET(ptr + 6, C.eristaGpuVmin); + PATCH_OFFSET(ptr + 9, C.eristaGpuVmin); + PATCH_OFFSET(ptr + 12, C.eristaGpuVmin); + } + + R_SUCCEED(); + } + + 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]))) { + R_THROW(ldr::ResultInvalidGpuFreqMaxPattern()); + } + + // Both instructions should operate on the same register + u8 rd = asm_get_rd(ins1); + if (rd != asm_get_rd(ins2)) { + R_THROW(ldr::ResultInvalidGpuFreqMaxPattern()); + } + + u32 max_clock; + switch (C.eristaGpuUV) { + case 0: + max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq; + break; + case 1: + max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableSLT)->freq; + break; + case 2: + max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableHiOPT)->freq; + break; + default: + max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq; + break; + } + + 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) + }; + + PATCH_OFFSET(ptr32, asm_patch[0]); + PATCH_OFFSET(ptr32 + 1, asm_patch[1]); + + R_SUCCEED(); + } + + Result GpuFreqPllMax(u32 *ptr) { + clk_pll_param *entry = reinterpret_cast(ptr); + + // All zero except for freq + for (size_t i = 1; i < sizeof(clk_pll_param) / sizeof(u32); i++) { + R_UNLESS(*(ptr + i) == 0, ldr::ResultInvalidGpuPllEntry()); + } + + // Double the max clk simply + u32 max_clk = entry->freq * 2; + entry->freq = max_clk; + R_SUCCEED(); + } + + // patch out 1305MHz limit on erista, don't use this! + // Result GpuFreqPllLimit(u32 *ptr) { + // u32 prev_freq = *(ptr - 1); + + // if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) { + // R_THROW(ldr::ResultInvalidGpuPllEntry()); + // } + + // PATCH_OFFSET(ptr, 3600000); + + // R_SUCCEED(); + // } + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.hpp new file mode 100644 index 00000000..cc964628 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.hpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * + * Copyright (c) B3711 + * + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "pcv_erista.hpp" + +namespace ams::ldr::hoc::pcv::erista { + + constexpr u32 GpuClkPllLimit = 2'600'000; + constexpr u32 GpuClkPllMax = 921'600'000; + constexpr u32 GpuVminOfficial = 810; + + static const u32 gpuVoltDvfsPattern[] = { 810, 1150, 1000, 100, 1000, 10, }; + static_assert(sizeof(gpuVoltDvfsPattern) == (sizeof(u32) * 6), "Invalid gpuVoltDvfsPattern"); + + static const u32 gpuVoltThermalPattern[] = { 950, 1132, 0, 810, 1132, 15000, 810, 1132, 30000, 810, 1132, 50000, 810, 1132, 70000, 810, 1132, 105000 }; + static_assert(sizeof(gpuVoltThermalPattern) == 0x48, "Invalid gpuVoltageThermalPattern size"); + + /* GPU Max Clock asm Pattern: + * + * MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11) + * 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 + * 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1 + * + * MOVK W11, #0xE, LSL#16 16 0xE 0xB (11) + * 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 + * 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1 + */ + inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 }; + + inline bool GpuMaxClockPatternFn(u32 *ptr32) { + return asm_compare_no_rd(*ptr32, GpuAsmPattern[0]); + }; + + constexpr cvb_entry_t GpuCvbTableDefault[] = { + // NA_FREQ_CVB_TABLE + { 76800, {}, { 814294, 8144, -940, 808, -21583, 226, } }, + { 153600, {}, { 856185, 8144, -940, 808, -21583, 226, } }, + { 230400, {}, { 898077, 8144, -940, 808, -21583, 226, } }, + { 307200, {}, { 939968, 8144, -940, 808, -21583, 226, } }, + { 384000, {}, { 981860, 8144, -940, 808, -21583, 226, } }, + { 460800, {}, { 1023751, 8144, -940, 808, -21583, 226, } }, + { 537600, {}, { 1065642, 8144, -940, 808, -21583, 226, } }, + { 614400, {}, { 1107534, 8144, -940, 808, -21583, 226, } }, + { 691200, {}, { 1149425, 8144, -940, 808, -21583, 226, } }, + { 768000, {}, { 1191317, 8144, -940, 808, -21583, 226, } }, + { 844800, {}, { 1233208, 8144, -940, 808, -21583, 226, } }, + { 921600, {}, { 1275100, 8144, -940, 808, -21583, 226, } }, + { }, + }; + + Result GpuVoltDVFS(u32 *ptr); + Result GpuVoltThermals(u32 *ptr); + Result GpuFreqMaxAsm(u32 *ptr32); + Result GpuFreqPllMax(u32 *ptr); + + // patch out 1305MHz limit on erista, don't use this! + // Result GpuFreqPllLimit(u32 *ptr); + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.cpp similarity index 72% rename from Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.cpp index c4d51f97..6b2d4321 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.cpp @@ -21,181 +21,22 @@ */ #include -#include "pcv.hpp" -#include "../mtc_timing_value.hpp" -#include "../erista/calculate_timings_erista.hpp" +#include "../pcv.hpp" +#include "../../mtc_timing_value.hpp" +#include "calculate_timings_erista.hpp" namespace ams::ldr::hoc::pcv::erista { - struct HookPayloadData { - struct { - EristaMtcTable *mtcTable; - u32 mtcCount; - } mtcTableAsm; - }; - DEFINE_HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData); - namespace { std::vector newEmcList; - u32 *nsoStart; - u32 *nsoEnd; struct { u32 *getEristaMtcTableFnSite = nullptr; EristaMtcTable *mtcTable = nullptr; - bool foundMtcTablePattern = false; + bool foundMtcTablePattern = false; } getMtcTableCache; } - Result CpuVoltDvfs(u32 *ptr) { - if (std::memcmp(ptr + 5, cpuVoltDvfsPattern, sizeof(cpuVoltDvfsPattern))) { - R_THROW(ldr::ResultInvalidCpuMinVolt()); - } - - if (C.eristaCpuVmin) { - PATCH_OFFSET(ptr, C.eristaCpuVmin); - } - - if (C.eristaCpuUV) { - PATCH_OFFSET(ptr - 2, C.eristaCpuVmin); - } - - if (C.eristaCpuMaxVolt) { - PATCH_OFFSET(ptr + 5, C.eristaCpuMaxVolt); - } - - R_SUCCEED(); - } - - Result CpuVoltThermals(u32 *ptr) { - if (std::memcmp(ptr - 6, cpuVoltageThermalPattern, sizeof(cpuVoltageThermalPattern))) { - R_THROW(ldr::ResultInvalidCpuMinVolt()); - } - - if (C.eristaCpuVmin) { - PATCH_OFFSET( ptr, C.eristaCpuVmin); - PATCH_OFFSET(ptr + 3, C.eristaCpuVmin); - PATCH_OFFSET(ptr + 6, C.eristaCpuVmin); - } - - if (C.eristaCpuMaxVolt) { - PATCH_OFFSET(ptr - 2, C.eristaCpuMaxVolt); - PATCH_OFFSET(ptr + 1, C.eristaCpuMaxVolt); - PATCH_OFFSET(ptr + 4, C.eristaCpuMaxVolt); - PATCH_OFFSET(ptr + 7, C.eristaCpuMaxVolt); - } - - R_SUCCEED(); - } - - Result CpuVoltDfll(u32* ptr) { - CvbCpuDfllData *entry = reinterpret_cast(ptr); - - R_UNLESS(entry->tune0_low == 0xFFEAD0FF, ldr::ResultInvalidCpuVoltDfllEntry()); - R_UNLESS(entry->tune0_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry()); - R_UNLESS(entry->tune1_low == 0x0, ldr::ResultInvalidCpuVoltDfllEntry()); - R_UNLESS(entry->tune1_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry()); - - if (!C.eristaCpuUV) { - R_SKIP(); - } - - switch(C.eristaCpuUV) { - case 1: - PATCH_OFFSET(&(entry->tune0_high), 0xffff); - PATCH_OFFSET(&(entry->tune1_high), 0x27007ff); - break; - case 2: - PATCH_OFFSET(&(entry->tune0_high), 0xefff); - PATCH_OFFSET(&(entry->tune1_high), 0x27407ff); - break; - case 3: - PATCH_OFFSET(&(entry->tune0_high), 0xdfff); - PATCH_OFFSET(&(entry->tune1_high), 0x27807ff); - break; - case 4: - PATCH_OFFSET(&(entry->tune0_high), 0xdfdf); - PATCH_OFFSET(&(entry->tune1_high), 0x27a07ff); - break; - case 5: - PATCH_OFFSET(&(entry->tune0_high), 0xcfdf); - PATCH_OFFSET(&(entry->tune1_high), 0x37007ff); - break; - default: - break; - } - - R_SUCCEED(); - } - - Result GpuVoltDVFS(u32 *ptr) { - if (std::memcmp(ptr, gpuVoltDvfsPattern, sizeof(gpuVoltDvfsPattern))) { - R_THROW(ldr::ResultInvalidGpuDvfs()); - } - - if (C.eristaGpuVmin) { - PATCH_OFFSET(ptr, C.eristaGpuVmin); - } - - R_SUCCEED(); - } - - Result GpuVoltThermals(u32 *ptr) { - if (std::memcmp(ptr - 3, gpuVoltThermalPattern, sizeof(gpuVoltThermalPattern))) { - R_THROW(ldr::ResultInvalidGpuDvfs()); - } - - if (C.eristaGpuVmin) { - PATCH_OFFSET(ptr, C.eristaGpuVmin); - PATCH_OFFSET(ptr + 3, C.eristaGpuVmin); - PATCH_OFFSET(ptr + 6, C.eristaGpuVmin); - PATCH_OFFSET(ptr + 9, C.eristaGpuVmin); - PATCH_OFFSET(ptr + 12, C.eristaGpuVmin); - } - - R_SUCCEED(); - } - - 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]))) { - R_THROW(ldr::ResultInvalidGpuFreqMaxPattern()); - } - - // Both instructions should operate on the same register - u8 rd = asm_get_rd(ins1); - if (rd != asm_get_rd(ins2)) { - R_THROW(ldr::ResultInvalidGpuFreqMaxPattern()); - } - - u32 max_clock; - switch (C.eristaGpuUV) { - case 0: - max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq; - break; - case 1: - max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableSLT)->freq; - break; - case 2: - max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableHiOPT)->freq; - break; - default: - max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq; - break; - } - - 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) - }; - - PATCH_OFFSET(ptr32, asm_patch[0]); - PATCH_OFFSET(ptr32 + 1, asm_patch[1]); - - R_SUCCEED(); - } - /* Note: This does not have proper timings, so base latency adjustment will not work. */ /* However, it may still achieve a slightly higher frequency, but not as much as it could be. */ /* I'm certainly not insane enough to attempt this pain again, so this will have to do *for now*. */ @@ -572,33 +413,6 @@ namespace ams::ldr::hoc::pcv::erista { R_SUCCEED(); } - Result GpuFreqPllMax(u32 *ptr) { - clk_pll_param *entry = reinterpret_cast(ptr); - - // All zero except for freq - for (size_t i = 1; i < sizeof(clk_pll_param) / sizeof(u32); i++) { - R_UNLESS(*(ptr + i) == 0, ldr::ResultInvalidGpuPllEntry()); - } - - // Double the max clk simply - u32 max_clk = entry->freq * 2; - entry->freq = max_clk; - R_SUCCEED(); - } - - // patch out 1305MHz limit on erista, don't use this! - // Result GpuFreqPllLimit(u32 *ptr) { - // u32 prev_freq = *(ptr - 1); - - // if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) { - // R_THROW(ldr::ResultInvalidGpuPllEntry()); - // } - - // PATCH_OFFSET(ptr, 3600000); - - // R_SUCCEED(); - // } - HOOK_PAYLOAD_FN EristaMtcTable *GetEristaMtcTableImpl(u32 *count) { const HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData); @@ -606,6 +420,18 @@ namespace ams::ldr::hoc::pcv::erista { return data->mtcTableAsm.mtcTable; } + Result MtcInstallHooks(HookPayloadData *data) { + R_UNLESS(getMtcTableCache.getEristaMtcTableFnSite != nullptr && getMtcTableCache.mtcTable != nullptr, ldr::ResultInvalidMtcTablePattern()); + + /* Copy the data to the payload. */ + data->mtcTableAsm.mtcTable = reinterpret_cast(Hooks().ToVa(getMtcTableCache.mtcTable)); + data->mtcTableAsm.mtcCount = newEmcList.size(); + + R_TRY(INSTALL_IMPL_HOOK(getMtcTableCache.getEristaMtcTableFnSite, GetEristaMtcTableImpl)); + + R_SUCCEED(); + } + Result MemMtcTableAsm(u32 *ptr) { /* Return if the pattern was already found. */ /* This pattern happens multiple times in this function., we only need to find it once. */ @@ -650,71 +476,4 @@ namespace ams::ldr::hoc::pcv::erista { R_SUCCEED(); } - Result InstallHooks() { - R_TRY(Hooks().CheckEnabled()); - - R_TRY(Hooks().CopyPayload()); - - R_UNLESS(getMtcTableCache.getEristaMtcTableFnSite != nullptr && getMtcTableCache.mtcTable != nullptr, ldr::ResultInvalidMtcTablePattern()); - - /* Copy the data to the payload. */ - auto *data = Hooks().BindData(e_HookPayloadData); - R_UNLESS(data != nullptr, ldr::ResultHookDataOutOfMemory()); - - data->mtcTableAsm.mtcTable = reinterpret_cast(Hooks().ToVa(getMtcTableCache.mtcTable)); - data->mtcTableAsm.mtcCount = newEmcList.size(); - R_TRY(INSTALL_IMPL_HOOK(getMtcTableCache.getEristaMtcTableFnSite, GetEristaMtcTableImpl)); - - R_SUCCEED(); - } - - void Patch(uintptr_t mapped_nso, size_t nso_size) { - nsoStart = reinterpret_cast(mapped_nso); - nsoEnd = reinterpret_cast(mapped_nso + nso_size); - MtcGenerateFreqTables(); - - u32 CpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq); - u32 GpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq); - - PatcherEntry patches[] = { - {"CPU Freq Table", CpuFreqCvbTable, 1, nullptr, CpuCvbDefaultMaxFreq }, - {"CPU Volt DVFS", &CpuVoltDvfs, 1, nullptr, CpuVminOfficial }, - {"CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial }, - {"CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low }, - {"GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial }, - {"GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial }, - {"GPU Freq Table", GpuFreqCvbTable, 1, nullptr, GpuCvbDefaultMaxFreq }, - {"GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn }, - {"GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax }, - // {"GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit }, - {"MEM Table Asm", &MemMtcTableAsm, 4, &MemMtcGetGetTablePatternFn }, - {"MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit }, - {"MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit }, - {"MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit }, - {"MEM Volt", &MemVoltHandler, 2, nullptr, MemVoltHOS }, - }; - - for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(EristaMtcTable); ptr += sizeof(u32)) { - u32 *ptr32 = reinterpret_cast(ptr); - for (auto &entry : patches) { - if (R_SUCCEEDED(entry.SearchAndApply(ptr32))) { - break; - } - } - } - - for (auto &entry : patches) { - LOGGING("%s Count: %zu\n", entry.description, entry.patched_count); - if (R_FAILED(entry.CheckResult())) { - panic::SmcError(panic::Patch); - - CRASH(entry.description); - } - } - - if (R_FAILED(InstallHooks())) { - panic::SmcError(panic::Patch); - } - } - } diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.hpp new file mode 100644 index 00000000..5c1c5100 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.hpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * + * Copyright (c) B3711 + * + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "pcv_erista.hpp" +#include "../../mtc_timing_table.hpp" + +namespace ams::ldr::hoc::pcv::erista { + + constexpr u32 EmcListDefault[] = { 40800, 68000, 102000, 204000, 408000, 665600, 800000, 1065600, 1331200, 1600000, }; + constexpr u32 EmcListSizeDefault = std::size(EmcListDefault); + constexpr u32 EmcListEndDefault = EmcListSizeDefault - 1; + + constexpr u32 MemVoltHOS = 1125'000; + constexpr u32 EmcClkPllmLimit = 1866'000'000; + + constexpr u32 MTC_TABLE_REV = 7; + constexpr u32 MtcTableCountDefault = 10; + + constexpr size_t MtcFullTableSize = sizeof(EristaMtcTable) * MtcTableCountDefault; + constexpr u32 MtcFullTableCount = 3; + + /* These dramids were copied from Hekate -- see /bdk/mem/sdram.h */ + enum DramId { + ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH = 0, + ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE = 1, + ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC = 2, + ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH = 4, + ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX = 7, + }; + + enum MtcTableIndex { + T210SdevEmcDvfsTableS4gb01 = 0, /* HB-MGCH, WT:C */ + T210SdevEmcDvfsTableS6gb01 = 1, /* HM-MGCH */ + T210SdevEmcDvfsTableH4gb01 = 2, /* HR-NLE */ + MtcTableIndex_Invalid = 3, + }; + + struct MtcDramIndex { + DramId dramId; + MtcTableIndex index; + }; + + /* TODO: Test 6gb and 8gb. */ + const inline MtcDramIndex mtcIndexTable[] = { + { ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH, T210SdevEmcDvfsTableS4gb01, }, + { ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC, T210SdevEmcDvfsTableS4gb01, }, + { ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH, T210SdevEmcDvfsTableS6gb01, }, + { ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX, T210SdevEmcDvfsTableS6gb01, }, + { ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE, T210SdevEmcDvfsTableH4gb01, }, + }; + + 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); + } + + Result MemFreqMtcTable(u32 *ptr); + void MtcGenerateFreqTables(); + Result MemFreqMax(u32 *ptr); + HOOK_PAYLOAD_FN EristaMtcTable *GetEristaMtcTableImpl(u32 *count); + Result MemMtcTableAsm(u32 *ptr); + + Result MtcInstallHooks(HookPayloadData *data); + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/mariko/calculate_timings.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.cpp similarity index 96% rename from Source/Atmosphere/stratosphere/loader/source/oc/mariko/calculate_timings.cpp rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.cpp index f83353fb..fab24dbf 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/mariko/calculate_timings.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.cpp @@ -1,204 +1,204 @@ -/* - * Copyright (c) Lightos_ - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include "../mtc_timing_value.hpp" -#include "timing_tables.hpp" - -namespace ams::ldr::hoc::pcv::mariko { - - void GetRext() { - if (auto r = FindRext()) { - rext = r->rext; - return; - } - - /* > 3200 */ - rext = 0x1E; - } - - void SwitchLatency(volatile u32 &latency, u32 index, u32 latencyStep) { - latency += index * latencyStep; - } - - static s32 GetMaxLatencyIndex(volatile u32 *latencyArray, u32 latencySize) { - s32 maxIndex = -1; - for (u32 i = 0; i < latencySize; ++i) { - if (latencyArray[i]) { - maxIndex = i; - } - } - - return maxIndex; - } - - void AutoLatency(volatile u32 &latency, u32 freq, u32 latencyStep) { - if (freq > 1600'000 && freq <= 1862'400) { /* 1866tRWL */ - latency += latencyStep * 2; - } else { /* 2133tRWL */ - latency += latencyStep * 3; - } - } - - void HandleLatency(u32 freq, volatile u32 &latency, volatile u32 *latencyArray, u32 indexMax, u32 latencyStep) { - for (u32 i = 0; i <= indexMax; ++i) { - if (latencyArray[i] != 0 && freq <= latencyArray[i]) { - SwitchLatency(latency, i, latencyStep); - return; - } - } - - SwitchLatency(latency, indexMax, latencyStep); - } - - void HandleLatency(u32 freq) { - static s32 rlIndexMax = GetMaxLatencyIndex(C.readLatency, std::size(C.readLatency)); - static s32 wlIndexMax = GetMaxLatencyIndex(C.writeLatency, std::size(C.writeLatency)); - constexpr u32 ReadLatencyStep = 4; - constexpr u32 WriteLatencyStep = 2; - bool autoLatencyRead = false, autoLatencyWrite = false; - - if (rlIndexMax == -1) { - AutoLatency(RL, freq, ReadLatencyStep); - autoLatencyRead = true; - } - - if (wlIndexMax == -1) { - AutoLatency(WL, freq, WriteLatencyStep); - autoLatencyWrite = true; - } - - if (autoLatencyRead && autoLatencyWrite) { - return; - } - - if (!autoLatencyRead) { - HandleLatency(freq, RL, C.readLatency, rlIndexMax, ReadLatencyStep); - } - - if (!autoLatencyWrite) { - HandleLatency(freq, WL, C.writeLatency, wlIndexMax, WriteLatencyStep); - } - } - - void CalculateMrw2() { - static const u8 rlMapDBI[8] = { - 6, 12, 16, 22, 28, 32, 36, 40 - }; - - static const u8 wlMapSetA[8] = { - 4, 6, 8, 10, 12, 14, 16, 18 - }; - - u32 rlIndex = 0; - u32 wlIndex = 0; - - for (u32 i = 0; i < std::size(rlMapDBI); ++i) { - if (rlMapDBI[i] == RL) { - rlIndex = i; - break; - } - } - - for (u32 i = 0; i < std::size(wlMapSetA); ++i) { - if (wlMapSetA[i] == WL) { - wlIndex = i; - break; - } - } - - /* DBI is always enabled. */ - mrw2 = static_cast(((rlIndex & 0x7) | ((wlIndex & 0x7) << 3) | ((0 & 0x1) << 6))); - } - - void CalculateTimings(double tCK_avg, u32 freq) { - RL = RL_1331; - WL = WL_1331; - - HandleLatency(freq); - - GetRext(); - - /* At 1333WL, for some reason (incorrect ram timing config in mtc table?), tRP causes crashes at high reductions - 2 seems to be the most common limit. */ - /* This is a lazy workaround until I find the issue... */ - const bool lowFreq = freq < C.timingEmcTbreak; - - tRCD = tRCD_values[lowFreq ? C.low_t1_tRCD : C.t1_tRCD]; - tRPpb = tRP_values[lowFreq ? C.low_t2_tRP : C.t2_tRP]; - tRAS = tRAS_values[lowFreq ? C.low_t3_tRAS : C.t3_tRAS]; - tRRD = tRRD_values[lowFreq ? C.low_t4_tRRD : C.t4_tRRD]; - tRFCpb = tRFC_values[lowFreq ? C.low_t5_tRFC : C.t5_tRFC]; - - u32 tRTW = lowFreq ? C.low_t6_tRTW : C.t6_tRTW; - u32 tWTR = 10 - tWTR_values[lowFreq ? C.low_t7_tWTR : C.t7_tWTR]; - - s32 finetRTW = C.fineTune_t6_tRTW; - s32 finetWTR = C.fineTune_t7_tWTR; - - u32 tREFI = lowFreq ? C.low_t8_tREFI : C.t8_tREFI; - refresh_raw = 0xFFFF; - if (tREFI != 6) { - refresh_raw = CEIL(tREFpb_values[tREFI] / tCK_avg) - 0x40; - refresh_raw = MIN(refresh_raw, static_cast(0xFFFF)); - } - - tRC = tRAS + tRPpb; - tRFCab = tRFCpb * 2; - tXSR = static_cast(tRFCab + 7.5); - tFAW = static_cast(tRRD * 4.0); - tRPab = tRPpb + 3; - - tR2P = CEIL((RL * 0.426) - 2.0); - tR2W = FLOOR(FLOOR((5.0 / tCK_avg) + ((FLOOR(48.0 / WL) - 0.478) * 3.0)) / 1.501) + RL - (tRTW * 3) + finetRTW; - tRTM = FLOOR((10.0 + RL) + (3.502 / tCK_avg)) + FLOOR(7.489 / tCK_avg); - tRATM = CEIL((tRTM - 10.0) + (RL * 0.426)); - - rdv = RL + FLOOR((5.105 / tCK_avg) + 17.017); - qpop = rdv - 14; - quse_width = CEIL(((4.897 / tCK_avg) - FLOOR(2.538 / tCK_avg)) + 3.782); - quse = FLOOR(RL + ((5.082 / tCK_avg) + FLOOR(2.560 / tCK_avg))) - CEIL(4.820 / tCK_avg); - einput_duration = FLOOR(9.936 / tCK_avg) + 5.0 + quse_width; - einput = quse - CEIL(9.928 / tCK_avg); - u32 qrst_duration = FLOOR(8.399 - tCK_avg); - u32 qrstLow = MAX(static_cast(einput - qrst_duration - 2), static_cast(0)); - qrst = PACK_U32(qrst_duration, qrstLow); - ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0); - qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput); - tW2P = (CEIL(WL * 1.7303) * 2) - 5; - tWTPDEN = CEIL(((1.803 / tCK_avg) + MAX(RL + (2.694 / tCK_avg), static_cast(tW2P))) + (BL / 2)); - tW2R = FLOOR(MAX((5.020 / tCK_avg) + 1.130, WL - MAX(-CEIL(0.258 * (WL - RL)), 1.964)) * 1.964) + WL - CEIL(tWTR / tCK_avg) + finetWTR; - tWTM = CEIL(WL + ((7.570 / tCK_avg) + 8.753)); - tWATM = (tWTM + (FLOOR(WL / 0.816) * 2.0)) - 4.0; - - wdv = WL; - wsv = WL - 2; - wev = 0xA + (WL - 14); - - u32 obdlyHigh = 3 / FLOOR(MIN(static_cast(2), tCK_avg * (WL - 7))); - u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0); - obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow); - - pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361))); - - tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0; - - u32 tMMRI = tRCD + (tCK_avg * 3); - pdex2mrr = tMMRI + 10; - - CalculateMrw2(); - } - -} +/* + * Copyright (c) Lightos_ + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "../../mtc_timing_value.hpp" +#include "timing_tables.hpp" + +namespace ams::ldr::hoc::pcv::mariko { + + void GetRext() { + if (auto r = FindRext()) { + rext = r->rext; + return; + } + + /* > 3200 */ + rext = 0x1E; + } + + void SwitchLatency(volatile u32 &latency, u32 index, u32 latencyStep) { + latency += index * latencyStep; + } + + static s32 GetMaxLatencyIndex(volatile u32 *latencyArray, u32 latencySize) { + s32 maxIndex = -1; + for (u32 i = 0; i < latencySize; ++i) { + if (latencyArray[i]) { + maxIndex = i; + } + } + + return maxIndex; + } + + void AutoLatency(volatile u32 &latency, u32 freq, u32 latencyStep) { + if (freq > 1600'000 && freq <= 1862'400) { /* 1866tRWL */ + latency += latencyStep * 2; + } else { /* 2133tRWL */ + latency += latencyStep * 3; + } + } + + void HandleLatency(u32 freq, volatile u32 &latency, volatile u32 *latencyArray, u32 indexMax, u32 latencyStep) { + for (u32 i = 0; i <= indexMax; ++i) { + if (latencyArray[i] != 0 && freq <= latencyArray[i]) { + SwitchLatency(latency, i, latencyStep); + return; + } + } + + SwitchLatency(latency, indexMax, latencyStep); + } + + void HandleLatency(u32 freq) { + static s32 rlIndexMax = GetMaxLatencyIndex(C.readLatency, std::size(C.readLatency)); + static s32 wlIndexMax = GetMaxLatencyIndex(C.writeLatency, std::size(C.writeLatency)); + constexpr u32 ReadLatencyStep = 4; + constexpr u32 WriteLatencyStep = 2; + bool autoLatencyRead = false, autoLatencyWrite = false; + + if (rlIndexMax == -1) { + AutoLatency(RL, freq, ReadLatencyStep); + autoLatencyRead = true; + } + + if (wlIndexMax == -1) { + AutoLatency(WL, freq, WriteLatencyStep); + autoLatencyWrite = true; + } + + if (autoLatencyRead && autoLatencyWrite) { + return; + } + + if (!autoLatencyRead) { + HandleLatency(freq, RL, C.readLatency, rlIndexMax, ReadLatencyStep); + } + + if (!autoLatencyWrite) { + HandleLatency(freq, WL, C.writeLatency, wlIndexMax, WriteLatencyStep); + } + } + + void CalculateMrw2() { + static const u8 rlMapDBI[8] = { + 6, 12, 16, 22, 28, 32, 36, 40 + }; + + static const u8 wlMapSetA[8] = { + 4, 6, 8, 10, 12, 14, 16, 18 + }; + + u32 rlIndex = 0; + u32 wlIndex = 0; + + for (u32 i = 0; i < std::size(rlMapDBI); ++i) { + if (rlMapDBI[i] == RL) { + rlIndex = i; + break; + } + } + + for (u32 i = 0; i < std::size(wlMapSetA); ++i) { + if (wlMapSetA[i] == WL) { + wlIndex = i; + break; + } + } + + /* DBI is always enabled. */ + mrw2 = static_cast(((rlIndex & 0x7) | ((wlIndex & 0x7) << 3) | ((0 & 0x1) << 6))); + } + + void CalculateTimings(double tCK_avg, u32 freq) { + RL = RL_1331; + WL = WL_1331; + + HandleLatency(freq); + + GetRext(); + + /* At 1333WL, for some reason (incorrect ram timing config in mtc table?), tRP causes crashes at high reductions - 2 seems to be the most common limit. */ + /* This is a lazy workaround until I find the issue... */ + const bool lowFreq = freq < C.timingEmcTbreak; + + tRCD = tRCD_values[lowFreq ? C.low_t1_tRCD : C.t1_tRCD]; + tRPpb = tRP_values[lowFreq ? C.low_t2_tRP : C.t2_tRP]; + tRAS = tRAS_values[lowFreq ? C.low_t3_tRAS : C.t3_tRAS]; + tRRD = tRRD_values[lowFreq ? C.low_t4_tRRD : C.t4_tRRD]; + tRFCpb = tRFC_values[lowFreq ? C.low_t5_tRFC : C.t5_tRFC]; + + u32 tRTW = lowFreq ? C.low_t6_tRTW : C.t6_tRTW; + u32 tWTR = 10 - tWTR_values[lowFreq ? C.low_t7_tWTR : C.t7_tWTR]; + + s32 finetRTW = C.fineTune_t6_tRTW; + s32 finetWTR = C.fineTune_t7_tWTR; + + u32 tREFI = lowFreq ? C.low_t8_tREFI : C.t8_tREFI; + refresh_raw = 0xFFFF; + if (tREFI != 6) { + refresh_raw = CEIL(tREFpb_values[tREFI] / tCK_avg) - 0x40; + refresh_raw = MIN(refresh_raw, static_cast(0xFFFF)); + } + + tRC = tRAS + tRPpb; + tRFCab = tRFCpb * 2; + tXSR = static_cast(tRFCab + 7.5); + tFAW = static_cast(tRRD * 4.0); + tRPab = tRPpb + 3; + + tR2P = CEIL((RL * 0.426) - 2.0); + tR2W = FLOOR(FLOOR((5.0 / tCK_avg) + ((FLOOR(48.0 / WL) - 0.478) * 3.0)) / 1.501) + RL - (tRTW * 3) + finetRTW; + tRTM = FLOOR((10.0 + RL) + (3.502 / tCK_avg)) + FLOOR(7.489 / tCK_avg); + tRATM = CEIL((tRTM - 10.0) + (RL * 0.426)); + + rdv = RL + FLOOR((5.105 / tCK_avg) + 17.017); + qpop = rdv - 14; + quse_width = CEIL(((4.897 / tCK_avg) - FLOOR(2.538 / tCK_avg)) + 3.782); + quse = FLOOR(RL + ((5.082 / tCK_avg) + FLOOR(2.560 / tCK_avg))) - CEIL(4.820 / tCK_avg); + einput_duration = FLOOR(9.936 / tCK_avg) + 5.0 + quse_width; + einput = quse - CEIL(9.928 / tCK_avg); + u32 qrst_duration = FLOOR(8.399 - tCK_avg); + u32 qrstLow = MAX(static_cast(einput - qrst_duration - 2), static_cast(0)); + qrst = PACK_U32(qrst_duration, qrstLow); + ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0); + qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput); + tW2P = (CEIL(WL * 1.7303) * 2) - 5; + tWTPDEN = CEIL(((1.803 / tCK_avg) + MAX(RL + (2.694 / tCK_avg), static_cast(tW2P))) + (BL / 2)); + tW2R = FLOOR(MAX((5.020 / tCK_avg) + 1.130, WL - MAX(-CEIL(0.258 * (WL - RL)), 1.964)) * 1.964) + WL - CEIL(tWTR / tCK_avg) + finetWTR; + tWTM = CEIL(WL + ((7.570 / tCK_avg) + 8.753)); + tWATM = (tWTM + (FLOOR(WL / 0.816) * 2.0)) - 4.0; + + wdv = WL; + wsv = WL - 2; + wev = 0xA + (WL - 14); + + u32 obdlyHigh = 3 / FLOOR(MIN(static_cast(2), tCK_avg * (WL - 7))); + u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0); + obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow); + + pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361))); + + tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0; + + u32 tMMRI = tRCD + (tCK_avg * 3); + pdex2mrr = tMMRI + 10; + + CalculateMrw2(); + } + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/mariko/calculate_timings.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.hpp similarity index 96% rename from Source/Atmosphere/stratosphere/loader/source/oc/mariko/calculate_timings.hpp rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.hpp index 84165e07..8659d3f2 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/mariko/calculate_timings.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.hpp @@ -1,24 +1,24 @@ -/* - * Copyright (c) Lightos_ - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -namespace ams::ldr::hoc::pcv::mariko { - - void CalculateTimings(double tCK_avg, u32 freq); - -} - +/* + * Copyright (c) Lightos_ + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +namespace ams::ldr::hoc::pcv::mariko { + + void CalculateTimings(double tCK_avg, u32 freq); + +} + diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.cpp new file mode 100644 index 00000000..3525a5a3 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.cpp @@ -0,0 +1,588 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * + * Copyright (c) B3711 + * + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "../pcv.hpp" +#include "../../mtc_timing_value.hpp" +#include "pcv_mariko.hpp" +#include "pcv_mariko_cpu.hpp" +#include "pcv_mariko_gpu.hpp" +#include "pcv_mariko_mtc.hpp" +#include "calculate_timings_mariko.hpp" + +namespace ams::ldr::hoc::pcv::mariko { + + u32 *nsoStart; + + namespace { + size_t g_nso_size = 0; + uintptr_t g_cave_cursor = 0; + } + + 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; + } + + #if HOC_UART_LOG + /* Redirect pcv's NvLog() calls to UART */ + Result NvLogUartRedirect(u32 *ptr) { + const uintptr_t mapped_nso = reinterpret_cast(nsoStart); + const size_t nso_size = g_nso_size; + const uintptr_t textEnd = g_pcv_cave; /* .text ends where the cave begins */ + const uintptr_t vsnprintf_addr = reinterpret_cast(ptr); + + /* NvLog via the VDD_SOC log */ + static const char Fmt[] = "%s(%s): DVFS request VDD_SOC %d mV\n"; + constexpr size_t FmtLen = sizeof(Fmt) - 1; + uintptr_t strAddr = 0; + { + const char *hay = reinterpret_cast(mapped_nso); + for (size_t i = 0; i + FmtLen <= nso_size; ++i) { + if (std::memcmp(hay + i, Fmt, FmtLen) == 0) { strAddr = mapped_nso + i; break; } + } + } + if (strAddr == 0) { + LOGGING("NvLogRedirect: fmt string not found (vsnprintf@+%lx)", vsnprintf_addr - mapped_nso); + R_THROW(ldr::ResultInvalidNvLogRedirect()); + } + + uintptr_t nvlog_addr = 0; + for (u32 *p = nsoStart; reinterpret_cast(p + 2) <= textEnd; ++p) { + const uintptr_t pc = reinterpret_cast(p); + if (!AsmIsAdrp(p[0])) { + continue; + } + const uintptr_t adrpPage = (pc & ~static_cast(0xFFFu)) + static_cast(AsmAdrpPageOffset(p[0])); + if (adrpPage != (strAddr & ~static_cast(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))) { + 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 (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()); + } + + const uintptr_t helper = CaveReserve(40); + if (helper == 0) { + LOGGING("NvLogRedirect: cave unavailable (cave=%lx size=%lx)", + static_cast(g_pcv_cave), static_cast(g_pcv_cave_size)); + R_THROW(ldr::ResultInvalidNvLogRedirect()); + } + u32 *t = reinterpret_cast(helper); + size_t n = 0; + auto emit = [&](u32 ins) { t[n] = ins; ++n; }; + + 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); + + /* 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; + if (HOC_PCV_NVLOG_PATCH) { + for (u32 *p = nsoStart; reinterpret_cast(p + 1) <= textEnd; ++p) { + if (!AsmIsBl(*p)) { + continue; + } + const uintptr_t pc = reinterpret_cast(p); + if (AsmBranchTarget(*p, pc) != nvlog_addr) { + continue; + } + bool isFmtCall = false; + for (u32 j = 1; j <= 8 && reinterpret_cast(p - j) >= reinterpret_cast(nsoStart); ++j) { + const u32 w = *(p - j); + if (AsmIsAdrp(w) && asm_get_rd(w) == 0) { /* adrp x0, */ + const uintptr_t wpc = pc - j * 4; + const uintptr_t tgtPage = (wpc & ~static_cast(0xFFFu)) + static_cast(AsmAdrpPageOffset(w)); + if (tgtPage >= (roStart & ~static_cast(0xFFFu))) { isFmtCall = true; break; } + } + } + if (isFmtCall) { + PATCH_OFFSET(p, AsmMakeBl(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 + + /* 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) */ + u32 *call = ptr + 3; /* the bl to relocate */ + const uintptr_t realFn = AsmBranchTarget(*call, reinterpret_cast(call)); + + /* 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()); + + const uintptr_t tramp = CaveReserve(9); + R_UNLESS(tramp != 0, ldr::ResultInvalidBusFreqReloc()); + + const uintptr_t region = g_pcv_scratch + HocBusFreqBufOffset; /* [0]=counter, +0x10 + i*0x400 = bufs */ + u32 *t = reinterpret_cast(tramp); + size_t n = 0; + auto emit = [&](u32 ins) { t[n] = ins; ++n; }; + emit(AsmMakeAdrp(tramp + n * 4, region, s[0])); /* adrp s0, */ + 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 */ + + PATCH_OFFSET(call, AsmMakeBl(reinterpret_cast(call), tramp)); + const uintptr_t base = reinterpret_cast(nsoStart); + (void) base; + LOGGING("BusFreqReloc: call@+%lx -> tramp@+%lx realfn@+%lx (bus=x%u buf=x%u off=0x%x scratch=x%u,x%u,x%u)", + reinterpret_cast(call) - base, tramp - base, realFn - base, busReg, bufReg, bufOff, s[0], s[1], s[2]); + R_SUCCEED(); + } + + + #if HOC_UART_LOG + /* Force GetEffectiveVerbosityLevel to return a non-zero level so all NvLog runs. */ + Result ForceVerbosity(u32 *ptr) { + PATCH_OFFSET(&ptr[0], AsmMakeMovzW(0, static_cast(HOC_PCV_FORCE_VERBOSITY))); /* movz w0,#level */ + PATCH_OFFSET(&ptr[1], RetIns); /* ret */ + 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; + + 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] */ + 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()); + + /* 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 srcBase = AsmGetRn(*voltBase); /* rail ptr at +0x20 */ + const u32 wBase = asm_get_rd(*voltBase); /* base reg */ + const u32 wIdx = AsmGetRm(*voltBase); /* 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 */ + + /* rail+0x18 as the socMinLut pointer. */ + PATCH_OFFSET(ptr - 3, NopIns); + PATCH_OFFSET(ptr - 2, AsmMakeAddImm64(lutReg, railReg, 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); + break; + } + } + R_SUCCEED(); + } + + Result EmcDvfsCountLimit(u32 *ptr) { + R_UNLESS(EmcDvfsCountPatternFn(ptr), ldr::ResultInvalidEmcDvfsCount()); + + /* cmp w?,#0x21 -> cmp w?,#EmcDvfsTableEntryCount */ + PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast(EmcDvfsTableEntryCount))); + R_SUCCEED(); + } + + Result EmcRateListLimit(u32 *ptr) { + /* ptr = cmp w?,#0x20 ; ptr[1] = csel w?,w?,w?,lt (w? = min(maxCount, 32)) ; ptr[2] = bl */ + 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,#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, AsmSubsSetImm12(*ptr, static_cast(EmcDvfsTableEntryCount))); /* cmp w?,#64 */ + PATCH_OFFSET(movPtr, asm_set_imm16(*movPtr, static_cast(EmcDvfsTableEntryCount))); /* movz w?,#64 */ + R_SUCCEED(); + } + + Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) { + struct { + u8 reg; + u8 val; + } __attribute__((packed)) cmd; + + I2cSession _session; + R_TRY(i2cOpenSession(&_session, dev)); + + cmd.reg = reg; + cmd.val = val; + Result res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All); + i2csessionClose(&_session); + return res; + } + + Result EmcVddqVolt(u32 *ptr) { + regulator *entry = reinterpret_cast(reinterpret_cast(ptr) - offsetof(regulator, type_2_3.default_uv)); + + constexpr u32 uv_step = 5'000; + constexpr u32 uv_min = 250'000; + + auto validator = [entry]() { + R_UNLESS(entry->id == 2, ldr::ResultInvalidRegulatorEntry()); + R_UNLESS(entry->type == 3, ldr::ResultInvalidRegulatorEntry()); + R_UNLESS(entry->type_2_3.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry()); + R_UNLESS(entry->type_2_3.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry()); + R_SUCCEED(); + }; + + R_TRY(validator()); + + u32 emc_uv = C.marikoEmcVddqVolt; + + if (!emc_uv) { + R_SKIP(); + } + + if (emc_uv % uv_step) { + emc_uv = (emc_uv + uv_step - 1) / uv_step * uv_step; // rounding + } + + PATCH_OFFSET(ptr, emc_uv); + + i2cInitialize(); + Result resultI2C = I2cSet_U8(I2cDevice_Max77812_2, 0x25, (emc_uv - uv_min) / uv_step); + i2cExit(); + + R_SUCCEED(); + + return resultI2C; + } + + Result GetSocSpeedo(u32 &socSpeedo) { + constexpr u64 FusePhysicalAddress = 0x7000F000; + u64 virtualAddress = 0; + constexpr u64 Size = 0x1000; + + u64 outSize; + /* TODO: use svc::QueryMemoryMapping instead. */ + R_TRY(svcQueryMemoryMapping(&virtualAddress, &outSize, FusePhysicalAddress, Size)); + + constexpr u32 FuseOffset = 2048; + constexpr u32 SocSpeedoOffset = 308; + socSpeedo = *reinterpret_cast(virtualAddress + FuseOffset + SocSpeedoOffset); + + R_SUCCEED(); + } + + u32 GetSocProcessId(u32 socSpeedo) { + if (socSpeedo <= 1597) { + return 0; + } + + if (socSpeedo <= 1708) { + return 1; + } + + /* >= 1709. */ + return 2; + } + + Result SocVoltAsm(u32 *compareSpeedos) { + constexpr u32 VoltageScanLimit = 10; + /* Might actually be speedo id. */ + u32 *writeProcessId = ScanAssembly(compareSpeedos, VoltageScanLimit, SocVoltWriteProcessIdAsm, asm_compare_no_rd); + R_UNLESS(writeProcessId != nullptr, ldr::ResultInvalidSocVoltPattern()); + u8 writeProcessIdRd = asm_get_rd(*writeProcessId); + + /* This writes 1050mV. */ + u32 *writeVoltage = ScanAssembly(writeProcessId, VoltageScanLimit, SocVoltWriteVoltageAsm, asm_compare_no_rd); + R_UNLESS(writeVoltage != nullptr, ldr::ResultInvalidSocVoltPattern()); + u8 writeVoltageRd = asm_get_rd(*writeVoltage); + + /* 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); + R_UNLESS(selectVoltage != nullptr, ldr::ResultInvalidSocVoltPattern()); + /* Todo: check rm and rn? */ + u8 selectVoltageRd = asm_get_rd(*selectVoltage); + + /* 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); + R_UNLESS(multiplier != nullptr, ldr::ResultInvalidSocVoltPattern()); + u8 multiplierRn = AsmGetMullRn(*multiplier); + u8 multiplierRm = AsmGetMullRm(*multiplier); + /* One of the two registers has to be rdCsel. */ + R_UNLESS((multiplierRn == selectVoltageRd) || (multiplierRm == selectVoltageRd), ldr::ResultInvalidSocVoltPattern()); + u8 multiplierRd = asm_get_rd(*multiplier); + + /* Subs instruction is then used to verify against absolute limit. */ + u32 limitValidationPattern = AsmSubsSetRn(SocVoltValidateLimitAsm, multiplierRd); + u32 *limitValidation = ScanAssembly(multiplier, VoltageScanLimit, limitValidationPattern, AsmSubsCompareNoReg); + 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()); + + 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 = AsmSubsSetImm12(*compareSpeedos, 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_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, processId), writeProcessIdRd); + PATCH_OFFSET(writeProcessId, writeProcessIdPatch); + + /* Adjust voltage limit. */ + u32 voltageLimitPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, C.marikoSocVmax), writeVoltageRd); + PATCH_OFFSET(writeVoltage, voltageLimitPatch); + + /* Branches to an abort if limits are invalid -- we patch the branch instruction with NOP. */ + PATCH_OFFSET(branchToAbort, 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 (AsmIsSubX29Imm(w) && AsmGetImm12(w) >= 0x20u) { /* sub x?,x29,#>=0x20 */ + R_THROW(ldr::ResultInvalidEmcRateList()); + } + } + + /* mov x,x2 */ + u32 descReg = 0xFFu; + for (u32 i = 1; i <= 24; ++i) { + if (AsmIsMovReg(ptr[i], 2)) { descReg = asm_get_rd(ptr[i]); 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 (AsmIsAddSpImm(w)) { /* add x?,sp,#imm12 (shift 0) */ + adds[nAdds] = ptr + i; + addImm[nAdds] = AsmGetImm12(w); + ++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], AsmMakeLdrImm64(asm_get_rd(*adds[a]), descReg, 0)); /* ldr x?,[x] */ + ++patched; + } + } + R_UNLESS(patched == 2, ldr::ResultInvalidEmcRateList()); + + /* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount) */ + PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast(EmcDvfsTableEntryCount))); /* cmp w?,#64 */ + PATCH_OFFSET(ptr + movzI, asm_set_imm16(*(ptr + movzI), static_cast(EmcDvfsTableEntryCount))); /* movz w?,#64 */ + R_SUCCEED(); + } + + void Patch(uintptr_t mapped_nso, size_t nso_size) { + nsoStart = reinterpret_cast(mapped_nso); + + g_pcv_scratch = mapped_nso + nso_size - HocPcvScratchSize; + g_nso_size = nso_size; + g_cave_cursor = g_pcv_cave; /* start the .text-cave bump allocator (0 if unavailable) */ + + MtcGenerateFreqTables(); + + u32 CpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq); + u32 GpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq); + + PatcherEntry patches[] = { + { "CPU Freq Vdd", &CpuFreqVdd, 1, nullptr, CpuClkOSLimit }, + { "CPU Freq Table", CpuFreqCvbTable, 1, nullptr, CpuCvbDefaultMaxFreq }, + { "CPU Volt DVFS", &CpuVoltDVFS, 1, nullptr, CpuVminOfficial }, + { "CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial }, + { "CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low }, + { "GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial }, + { "GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial }, + { "GPU Freq Table", GpuFreqCvbTable, 1, nullptr, GpuCvbDefaultMaxFreq }, + { "GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn }, + { "GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax }, + { "GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit }, + { "MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit }, + { "MEM Freq Dvb", &MemFreqDvbTable, 1, nullptr, EmcClkOSLimit }, + { "MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit }, + { "MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit }, + { "MEM Vddq", &EmcVddqVolt, 2, nullptr, EmcVddqDefault }, + { "MEM Vdd2", &MemVoltHandler, 2, nullptr, MemVdd2Default }, + { "MEM Table Asm", &MemMtcTableAsm, 1, &MemMtcGetGetTablePatternFn }, + // { "EMC DVFS Count", &EmcDvfsCountLimit, 1, &EmcDvfsCountPatternFn }, + //{ "EMC SoC LUT", &EmcSocLutReloc, 1, &EmcSocLutPatternFn }, + // { "EMC Rate List", &EmcRateListLimit, 0, &EmcRateListPatternFn }, + // { "EMC Rate Sess", &EmcRateSessLimit, 1, &EmcRateSessPatternFn }, + // { "Bus Freq Reloc", &BusFreqReloc, 1, &BusFreqRelocPatternFn }, + { "SOC Volt Asm", &SocVoltAsm, 1, &SocVoltPatternFn }, + { "SOC Volt Limit", &SocVoltLimit, 1, nullptr, SocVoltLimitOfficial }, + /* Debugging patches */ + #if HOC_UART_LOG + { "NvLog Redirect", &NvLogUartRedirect, 1, &NvLogVsnprintfPatternFn, 0, 0, true }, + { "Force Verbosity", &ForceVerbosity, 3, &ForceVerbosityPatternFn, 0, 0, true }, + #endif + }; + + for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(MarikoMtcTable); ptr += sizeof(u32)) { + u32 *ptr32 = reinterpret_cast(ptr); + for (auto &entry : patches) { + if (R_SUCCEEDED(entry.SearchAndApply(ptr32))) { + break; + } + } + } + + for (auto &entry : patches) { + LOGGING("%s Count: %zu", entry.description, entry.patched_count); + if (R_FAILED(entry.CheckResult())) { + panic::SmcError(panic::Patch); + + CRASH(entry.description); + } + } + } + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.hpp similarity index 99% rename from Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.hpp rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.hpp index de72f004..33b7ed09 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.hpp @@ -20,12 +20,14 @@ #pragma once -#include "../oc_common.hpp" -#include "pcv_common.hpp" -#include "pcv_asm.hpp" +#include "../../oc_common.hpp" +#include "../pcv_common.hpp" +#include "../pcv_asm.hpp" namespace ams::ldr::hoc::pcv::mariko { + extern u32 *nsoStart; + constexpr cvb_entry_t CpuCvbTableDefault[] = { { 204000, { 721589, -12695, 27 }, { } }, { 306000, { 747134, -14195, 27 }, { } }, diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.cpp new file mode 100644 index 00000000..01400c9d --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * + * Copyright (c) B3711 + * + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../pcv.hpp" + +namespace ams::ldr::hoc::pcv::mariko { + + u32 CapCpuClock() { + u32 cpuCap = allowedCpuMaxFrequencies[0]; + + for (u32 freq : allowedCpuMaxFrequencies) { + if (C.marikoCpuMaxClock >= freq) { + cpuCap = freq; + } else { + break; + } + } + return cpuCap; + } + + Result CpuFreqVdd(u32 *ptr) { + dvfs_rail *entry = reinterpret_cast(reinterpret_cast(ptr) - offsetof(dvfs_rail, freq)); + + R_UNLESS(entry->id == 1, ldr::ResultInvalidCpuFreqVddEntry()); + R_UNLESS(entry->min_mv == 250'000, ldr::ResultInvalidCpuFreqVddEntry()); + R_UNLESS(entry->step_mv == 5000, ldr::ResultInvalidCpuFreqVddEntry()); + R_UNLESS(entry->max_mv == 1525'000, ldr::ResultInvalidCpuFreqVddEntry()); + + if (C.marikoCpuUVHigh) { + PATCH_OFFSET(ptr, CapCpuClock()); + } else { + PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq); + } + + R_SUCCEED(); + } + + Result CpuVoltDVFS(u32 *ptr) { + CvbMeta *cpuCvbMeta = reinterpret_cast(reinterpret_cast(ptr) - offsetof(CvbMeta, vmin)); + + R_UNLESS(cpuCvbMeta->highVmin == CpuHighVminOfficial, ldr::ResultInvalidCpuMinVolt()); + R_UNLESS(cpuCvbMeta->unkStepMaybe == 38, ldr::ResultInvalidCpuMinVolt()); + R_UNLESS(cpuCvbMeta->vmax == CpuVoltOfficial, ldr::ResultInvalidCpuMinVolt()); + R_UNLESS(cpuCvbMeta->unkScale2 == 1000, ldr::ResultInvalidCpuMinVolt()); + R_UNLESS(cpuCvbMeta->speedoScale == 100, ldr::ResultInvalidCpuMinVolt()); + R_UNLESS(cpuCvbMeta->voltageScale == 1000, ldr::ResultInvalidCpuMinVolt()); + R_UNLESS(cpuCvbMeta->unkZero5 == 0, ldr::ResultInvalidCpuMinVolt()); + + if (C.marikoCpuLowVmin) { + PATCH_OFFSET(&(cpuCvbMeta->vmin), C.marikoCpuLowVmin); + } + + if (C.marikoCpuHighVmin) { + PATCH_OFFSET(&(cpuCvbMeta->highVmin), C.marikoCpuHighVmin); + } + + if (C.marikoCpuMaxVolt) { + PATCH_OFFSET(&(cpuCvbMeta->vmax), C.marikoCpuMaxVolt); + } + + R_SUCCEED(); + } + + Result CpuVoltThermals(u32 *ptr) { + if (std::memcmp(ptr, cpuVoltThermalData, sizeof(cpuVoltThermalData))) { + R_THROW(ldr::ResultInvalidCpuMinVolt()); + } + + if (C.marikoCpuLowVmin) { + PATCH_OFFSET(ptr, C.marikoCpuLowVmin); + PATCH_OFFSET(ptr + 3, C.marikoCpuLowVmin); + } + + if (C.marikoCpuMaxVolt) { + PATCH_OFFSET(ptr - 2, C.marikoCpuMaxVolt); + PATCH_OFFSET(ptr - 5, C.marikoCpuMaxVolt); + PATCH_OFFSET(ptr + 1, C.marikoCpuMaxVolt); + PATCH_OFFSET(ptr + 4, C.marikoCpuMaxVolt); + } + + R_SUCCEED(); + } + + Result CpuVoltDfll(u32 *ptr) { + CvbCpuDfllData *entry = reinterpret_cast(ptr); + + R_UNLESS(entry->tune0_low == 0xFFCF, ldr::ResultInvalidCpuVoltDfllEntry()); + R_UNLESS(entry->tune0_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry()); + R_UNLESS(entry->tune1_low == 0x12207FF, ldr::ResultInvalidCpuVoltDfllEntry()); + R_UNLESS(entry->tune1_high == 0x3FFF7FF, ldr::ResultInvalidCpuVoltDfllEntry()); + + switch (C.marikoCpuUVLow) { + case 1: + PATCH_OFFSET(&(entry->tune0_low), 0xffa0); + PATCH_OFFSET(&(entry->tune0_high), 0xffff); + PATCH_OFFSET(&(entry->tune1_low), 0x21107ff); + PATCH_OFFSET(&(entry->tune1_high), 0x0); + break; + case 2: + PATCH_OFFSET(&(entry->tune0_high), 0xffdf); + PATCH_OFFSET(&(entry->tune1_low), 0x21107ff); + PATCH_OFFSET(&(entry->tune1_high), 0x27207ff); + break; + case 3: + PATCH_OFFSET(&(entry->tune0_low), 0xffdf); + PATCH_OFFSET(&(entry->tune0_high), 0xffdf); + PATCH_OFFSET(&(entry->tune1_low), 0x21107ff); + PATCH_OFFSET(&(entry->tune1_high), 0x27307ff); + break; + case 4: + PATCH_OFFSET(&(entry->tune0_low), 0xffff); + PATCH_OFFSET(&(entry->tune0_high), 0xffdf); + PATCH_OFFSET(&(entry->tune1_low), 0x21107ff); + PATCH_OFFSET(&(entry->tune1_high), 0x27407ff); + break; + case 5: + PATCH_OFFSET(&(entry->tune0_high), 0xffdf); + PATCH_OFFSET(&(entry->tune1_low), 0x21607ff); + PATCH_OFFSET(&(entry->tune1_high), 0x27707ff); + break; + case 6: + PATCH_OFFSET(&(entry->tune0_high), 0xffdf); + PATCH_OFFSET(&(entry->tune1_low), 0x21607ff); + PATCH_OFFSET(&(entry->tune1_high), 0x27807ff); + break; + case 7: + PATCH_OFFSET(&(entry->tune0_high), 0xdfff); + PATCH_OFFSET(&(entry->tune1_low), 0x21607ff); + PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff); + break; + case 8: + PATCH_OFFSET(&(entry->tune0_low), 0xdfff); + PATCH_OFFSET(&(entry->tune0_high), 0xdfff); + PATCH_OFFSET(&(entry->tune1_low), 0x21707ff); + PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff); + break; + case 9: + PATCH_OFFSET(&(entry->tune0_low), 0xdfff); + PATCH_OFFSET(&(entry->tune0_high), 0xdfff); + PATCH_OFFSET(&(entry->tune1_low), 0x21707ff); + PATCH_OFFSET(&(entry->tune1_high), 0x27c07ff); + break; + case 10: + PATCH_OFFSET(&(entry->tune0_low), 0xdfff); + PATCH_OFFSET(&(entry->tune0_high), 0xdfff); + PATCH_OFFSET(&(entry->tune1_low), 0x21707ff); + PATCH_OFFSET(&(entry->tune1_high), 0x27d07ff); + break; + case 11: + PATCH_OFFSET(&(entry->tune0_low), 0xdfff); + PATCH_OFFSET(&(entry->tune0_high), 0xdfff); + PATCH_OFFSET(&(entry->tune1_low), 0x21707ff); + PATCH_OFFSET(&(entry->tune1_high), 0x27e07ff); + break; + case 12: + PATCH_OFFSET(&(entry->tune0_low), 0xdfff); + PATCH_OFFSET(&(entry->tune0_high), 0xdfff); + PATCH_OFFSET(&(entry->tune1_low), 0x21707ff); + PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff); + break; + default: + break; + } + + switch (C.marikoCpuUVHigh) { + case 1: + PATCH_OFFSET(&(entry->tune1_high), 0x0); + PATCH_OFFSET(&(entry->tune0_high), 0xffff); + break; + case 2: + PATCH_OFFSET(&(entry->tune0_high), 0xffdf); + PATCH_OFFSET(&(entry->tune1_high), 0x27207ff); + break; + case 3: + PATCH_OFFSET(&(entry->tune0_high), 0xffdf); + PATCH_OFFSET(&(entry->tune1_high), 0x27307ff); + break; + case 4: + PATCH_OFFSET(&(entry->tune0_high), 0xffdf); + PATCH_OFFSET(&(entry->tune1_high), 0x27407ff); + break; + case 5: + PATCH_OFFSET(&(entry->tune0_high), 0xffdf); + PATCH_OFFSET(&(entry->tune1_high), 0x27707ff); + break; + case 6: + PATCH_OFFSET(&(entry->tune0_high), 0xffdf); + PATCH_OFFSET(&(entry->tune1_high), 0x27807ff); + break; + case 7: + case 8: + PATCH_OFFSET(&(entry->tune0_high), 0xdfff); + PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff); + break; + case 9: + PATCH_OFFSET(&(entry->tune0_high), 0xdfff); + PATCH_OFFSET(&(entry->tune1_high), 0x27c07ff); + break; + case 10: + PATCH_OFFSET(&(entry->tune0_high), 0xdfff); + PATCH_OFFSET(&(entry->tune1_high), 0x27d07ff); + break; + case 11: + PATCH_OFFSET(&(entry->tune0_high), 0xdfff); + PATCH_OFFSET(&(entry->tune1_high), 0x27e07ff); + break; + case 12: + PATCH_OFFSET(&(entry->tune0_high), 0xdfff); + PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff); + break; + default: + break; + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.hpp new file mode 100644 index 00000000..f6a570d4 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.hpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * + * Copyright (c) B3711 + * + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "../pcv.hpp" + +namespace ams::ldr::hoc::pcv::mariko { + + Result CpuFreqVdd(u32 *ptr); + Result CpuVoltDVFS(u32 *ptr); + Result CpuVoltThermals(u32 *ptr); + Result CpuVoltDfll(u32 *ptr); + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.cpp new file mode 100644 index 00000000..5b6c1286 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * + * Copyright (c) B3711 + * + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../pcv.hpp" +#include "../pcv_asm.hpp" + +namespace ams::ldr::hoc::pcv::mariko { + + Result GpuVoltDVFS(u32 *ptr) { + /* Check for valid pattern. */ + for (size_t i = 0; i < std::size(gpuDVFSPattern); ++i) { + if (*(ptr + i + 1) != gpuDVFSPattern[i]) { + R_THROW(ldr::ResultInvalidGpuDvfs()); + } + } + + /* Default value is 1050mV. */ + if (C.marikoGpuVmax) { + PATCH_OFFSET(ptr + 1, C.marikoGpuVmax); + } + + if (C.marikoGpuVmin) { + PATCH_OFFSET(ptr, C.marikoGpuVmin); + } + + R_SUCCEED(); + } + + Result GpuVoltThermals(u32 *ptr) { + if (std::memcmp(ptr - 3, gpuVoltThermalPattern, sizeof(gpuVoltThermalPattern))) { + R_THROW(ldr::ResultInvalidGpuDvfs()); + } + + // if (C.marikoGpuBootVolt) { + // PATCH_OFFSET(ptr - 3, C.marikoGpuBootVolt); + // } + + if (C.marikoGpuVmin) { + PATCH_OFFSET(ptr, C.marikoGpuVmin); + PATCH_OFFSET(ptr + 3, C.marikoGpuVmin); + PATCH_OFFSET(ptr + 6, C.marikoGpuVmin); + PATCH_OFFSET(ptr + 9, C.marikoGpuVmin); + PATCH_OFFSET(ptr + 12, C.marikoGpuVmin); + } + + R_SUCCEED(); + } + + 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]))) { + R_THROW(ldr::ResultInvalidGpuFreqMaxPattern()); + } + + // Both instructions should operate on the same register + u8 rd = asm_get_rd(ins1); + if (rd != asm_get_rd(ins2)) { + R_THROW(ldr::ResultInvalidGpuFreqMaxPattern()); + } + + u32 max_clock; + switch (C.marikoGpuUV) { + case 0: + max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTable)->freq; + break; + case 1: + max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableSLT)->freq; + break; + case 2: + max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq; + break; + case 3: + max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT15)->freq; + break; + case 4: + max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHighUV)->freq; + break; + default: + max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq; + break; + } + + 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) + }; + + PATCH_OFFSET(ptr32, asm_patch[0]); + PATCH_OFFSET(ptr32 + 1, asm_patch[1]); + + R_SUCCEED(); + } + + Result GpuFreqPllMax(u32 *ptr) { + clk_pll_param *entry = reinterpret_cast(ptr); + + // All zero except for freq + for (size_t i = 1; i < sizeof(clk_pll_param) / sizeof(u32); i++) { + R_UNLESS(*(ptr + i) == 0, ldr::ResultInvalidGpuPllEntry()); + } + + // Double the max clk simply + u32 max_clk = entry->freq * 2; + entry->freq = max_clk; + R_SUCCEED(); + } + + Result GpuFreqPllLimit(u32 *ptr) { + u32 prev_freq = *(ptr - 1); + + if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) { + R_THROW(ldr::ResultInvalidGpuPllEntry()); + } + + PATCH_OFFSET(ptr, 3600000); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.hpp new file mode 100644 index 00000000..f5e4c33f --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * + * Copyright (c) B3711 + * + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "../pcv.hpp" + +namespace ams::ldr::hoc::pcv::mariko { + + Result GpuVoltDVFS(u32 *ptr); + Result GpuVoltThermals(u32 *ptr); + Result GpuFreqMaxAsm(u32 *ptr32); + Result GpuFreqPllMax(u32 *ptr); + Result GpuFreqPllLimit(u32 *ptr); + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.cpp new file mode 100644 index 00000000..7dacbdbe --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.cpp @@ -0,0 +1,676 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * + * Copyright (c) B3711 + * + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../pcv.hpp" +#include "../../mtc_timing_value.hpp" +#include "calculate_timings_mariko.hpp" + +namespace ams::ldr::hoc::pcv::mariko { + + namespace { + std::vector newEmcList; + } + + void MemMtcTableAutoAdjust(MarikoMtcTable *table) { + /* Official Tegra X1 TRM, sign up for nvidia developer program (free) to download: + * https://developer.nvidia.com/embedded/dlc/tegra-x1-technical-reference-manual + * Section 18.11.1: MC Registers + * Section 18.11.2: EMC Registers + */ + + #define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE) \ + TABLE->burst_regs.PARAM = VALUE; \ + TABLE->shadow_regs_ca_train.PARAM = VALUE; \ + TABLE->shadow_regs_rdwr_train.PARAM = VALUE; + + const double tCK_avg = 1000'000.0 / table->rate_khz; + + #define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg)) + + /* Ram power down */ + /* B31: DRAM_CLKSTOP_PD */ + /* B30: DRAM_CLKSTOP_SR */ + /* B29: DRAM_ACPD */ + if (C.hpMode) { + WRITE_PARAM_ALL_REG(table, emc_cfg, 0x13200000); + } else { + WRITE_PARAM_ALL_REG(table, emc_cfg, 0xF3200000); + } + + u32 trefbw = refresh_raw + 0x40; + trefbw = MIN(trefbw, static_cast(0x3FFF)); + + const u32 dyn_self_ref_control = (static_cast(7605.0 / tCK_avg) + 260) | (table->burst_regs.emc_dyn_self_ref_control & 0xffff0000); + + CalculateTimings(tCK_avg, table->rate_khz); + + WRITE_PARAM_ALL_REG(table, emc_rd_rcd, GET_CYCLE_CEIL(tRCD)); + WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(tRCD)); + WRITE_PARAM_ALL_REG(table, emc_rc, MIN(GET_CYCLE_CEIL(tRC), static_cast(0xB9))); + WRITE_PARAM_ALL_REG(table, emc_ras, MIN(GET_CYCLE_CEIL(tRAS), static_cast(0x7F))); + WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(tRRD)); + WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(tRFCpb)); + WRITE_PARAM_ALL_REG(table, emc_rfc, GET_CYCLE_CEIL(tRFCab)); + WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(tRPpb)); + WRITE_PARAM_ALL_REG(table, emc_txsr, MIN(GET_CYCLE_CEIL(tXSR), static_cast(0x3fe))); + WRITE_PARAM_ALL_REG(table, emc_txsrdll, MIN(GET_CYCLE_CEIL(tXSR), static_cast(0x3fe))); + WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(tFAW)); + WRITE_PARAM_ALL_REG(table, emc_trpab, MIN(GET_CYCLE_CEIL(tRPab), static_cast(0x3F))); + WRITE_PARAM_ALL_REG(table, emc_tckesr, GET_CYCLE_CEIL(tSR)); + WRITE_PARAM_ALL_REG(table, emc_tcke, GET_CYCLE_CEIL(7.425) + 2); + WRITE_PARAM_ALL_REG(table, emc_tpd, GET_CYCLE_CEIL(tXP)); + WRITE_PARAM_ALL_REG(table, emc_tclkstop, tCLKSTOP); + WRITE_PARAM_ALL_REG(table, emc_r2p, tR2P); + WRITE_PARAM_ALL_REG(table, emc_r2w, tR2W); + WRITE_PARAM_ALL_REG(table, emc_trtm, tRTM); + WRITE_PARAM_ALL_REG(table, emc_tratm, tRATM); + WRITE_PARAM_ALL_REG(table, emc_w2p, tW2P); + WRITE_PARAM_ALL_REG(table, emc_w2r, tW2R); + WRITE_PARAM_ALL_REG(table, emc_twtm, tWTM); + WRITE_PARAM_ALL_REG(table, emc_twatm, tWATM); + WRITE_PARAM_ALL_REG(table, emc_rext, rext); + WRITE_PARAM_ALL_REG(table, emc_wext, (table->rate_khz >= 2533000) ? 0x19 : 0x16); + WRITE_PARAM_ALL_REG(table, emc_refresh, refresh_raw); + WRITE_PARAM_ALL_REG(table, emc_pre_refresh_req_cnt, refresh_raw / 4); + WRITE_PARAM_ALL_REG(table, emc_trefbw, trefbw); + WRITE_PARAM_ALL_REG(table, emc_dyn_self_ref_control, dyn_self_ref_control); + WRITE_PARAM_ALL_REG(table, emc_pdex2wr, pdex2rw); + WRITE_PARAM_ALL_REG(table, emc_pdex2rd, pdex2rw); + WRITE_PARAM_ALL_REG(table, emc_pchg2pden, GET_CYCLE_CEIL(1.763)); + WRITE_PARAM_ALL_REG(table, emc_ar2pden, GET_CYCLE_CEIL(1.75)); + WRITE_PARAM_ALL_REG(table, emc_pdex2cke, GET_CYCLE_CEIL(1.05)); + WRITE_PARAM_ALL_REG(table, emc_act2pden, GET_CYCLE_CEIL(14.0)); + WRITE_PARAM_ALL_REG(table, emc_cke2pden, GET_CYCLE_CEIL(8.499)); + WRITE_PARAM_ALL_REG(table, emc_pdex2mrr, GET_CYCLE_CEIL(pdex2mrr)); + WRITE_PARAM_ALL_REG(table, emc_rw2pden, tWTPDEN); + 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_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_cmd_brlshft_2, 0x24); + WRITE_PARAM_ALL_REG(table, emc_cmd_brlshft_3, 0x24); + + /* This needs some clean up. */ + constexpr double MC_ARB_DIV = 4.0; + constexpr u32 MC_ARB_SFA = 2; + + table->burst_mc_regs.mc_emem_arb_cfg = table->rate_khz / (33.3 * 1000) / MC_ARB_DIV; + table->burst_mc_regs.mc_emem_arb_timing_rcd = CEIL(GET_CYCLE_CEIL(tRCD) / MC_ARB_DIV) - 2; + table->burst_mc_regs.mc_emem_arb_timing_rp = CEIL(GET_CYCLE_CEIL(tRPpb) / MC_ARB_DIV) - 1; + table->burst_mc_regs.mc_emem_arb_timing_rc = CEIL(GET_CYCLE_CEIL(tRC) / MC_ARB_DIV) - 1; + table->burst_mc_regs.mc_emem_arb_timing_ras = CEIL(GET_CYCLE_CEIL(tRAS) / MC_ARB_DIV) - 2; + table->burst_mc_regs.mc_emem_arb_timing_faw = CEIL(GET_CYCLE_CEIL(tFAW) / MC_ARB_DIV) - 1; + table->burst_mc_regs.mc_emem_arb_timing_rrd = CEIL(GET_CYCLE_CEIL(tRRD) / MC_ARB_DIV) - 1; + table->burst_mc_regs.mc_emem_arb_timing_rfcpb = CEIL(GET_CYCLE_CEIL(tRFCpb) / MC_ARB_DIV) - 1; + table->burst_mc_regs.mc_emem_arb_timing_rap2pre = CEIL(tR2P / MC_ARB_DIV); + table->burst_mc_regs.mc_emem_arb_timing_wap2pre = CEIL(tW2P / MC_ARB_DIV) + MC_ARB_SFA; + + /* Two consecutive reads between two different dram modules. */ + /* Only above 1 for 8gb ram. */ + if (table->burst_mc_regs.mc_emem_arb_timing_r2r > 1) { + table->burst_mc_regs.mc_emem_arb_timing_r2r = CEIL(table->burst_regs.emc_rext / 4) - 1 + MC_ARB_SFA; + } + + /* Same as r2r but for write. */ + if (table->burst_mc_regs.mc_emem_arb_timing_w2w > 1) { + table->burst_mc_regs.mc_emem_arb_timing_w2w = CEIL(table->burst_regs.emc_wext / MC_ARB_DIV) - 1 + MC_ARB_SFA; + } + + table->burst_mc_regs.mc_emem_arb_timing_r2w = CEIL(tR2W / MC_ARB_DIV) - 1 + MC_ARB_SFA; + table->burst_mc_regs.mc_emem_arb_timing_w2r = CEIL(tW2R / MC_ARB_DIV) - 1 + MC_ARB_SFA; + + u32 da_turns = 0; + da_turns |= u8(table->burst_mc_regs.mc_emem_arb_timing_r2w / 2) << 16; + da_turns |= u8(table->burst_mc_regs.mc_emem_arb_timing_w2r / 2) << 24; + table->burst_mc_regs.mc_emem_arb_da_turns = da_turns; + + u32 da_covers = 0; + u8 r_cover = (table->burst_mc_regs.mc_emem_arb_timing_rap2pre + table->burst_mc_regs.mc_emem_arb_timing_rp + table->burst_mc_regs.mc_emem_arb_timing_rcd) / 2; + u8 w_cover = (table->burst_mc_regs.mc_emem_arb_timing_wap2pre + table->burst_mc_regs.mc_emem_arb_timing_rp + table->burst_mc_regs.mc_emem_arb_timing_rcd) / 2; + da_covers |= (table->burst_mc_regs.mc_emem_arb_timing_rc / 2); + da_covers |= (r_cover << 8); + da_covers |= (w_cover << 16); + table->burst_mc_regs.mc_emem_arb_da_covers = da_covers; + + constexpr u32 AtomsPerDvfsPulse = 0x7; + constexpr u32 McEmcSameFreq = 0x0; + const u32 expiringSoonSlackThreshold = [&] { + switch (table->rate_khz) { + case 2966000: + case 3100000: + case 3133000: + case 3200000: + return 0x12u; + default: + return 0x13u; + } + }(); + + const u32 priorityInversionIsoThreshold = GET_CYCLE_CEIL(7.5); + constexpr u32 EmcReqB2bXfer = 0x0; + const u32 priorityInversionThreshold = GET_CYCLE_CEIL(22.5); + const u32 bc2aaHoldoffThreshold = table->burst_mc_regs.mc_emem_arb_timing_rc + 1; + + const u32 mc_emem_arb_misc0 = (AtomsPerDvfsPulse << 28) | (McEmcSameFreq << 27) | (expiringSoonSlackThreshold << 21) | (priorityInversionIsoThreshold << 16) | (EmcReqB2bXfer << 15) | (priorityInversionThreshold << 8) | (bc2aaHoldoffThreshold << 0); + table->burst_mc_regs.mc_emem_arb_misc0 = mc_emem_arb_misc0; + + table->la_scale_regs.mc_mll_mpcorer_ptsa_rate = 0x115; + + if (table->rate_khz >= 2133000) { + table->la_scale_regs.mc_ftop_ptsa_rate = 0x1F; + } else { + table->la_scale_regs.mc_ftop_ptsa_rate = 0x1B; + } + + table->la_scale_regs.mc_ptsa_grant_decrement = 0x17ff; + + constexpr u32 MaskHigh = 0xFF00FFFF; + constexpr u32 Mask2 = 0xFFFFFF00; + constexpr u32 Mask3 = 0xFF00FF00; + + const u32 allowance1 = static_cast(0x32000 / (table->rate_khz / 1000)) & 0xFF; + const u32 allowance2 = static_cast(0x9C40 / (table->rate_khz / 1000)) & 0xFF; + const u32 allowance3 = static_cast(0xB540 / (table->rate_khz / 1000)) & 0xFF; + const u32 allowance4 = static_cast(0x9600 / (table->rate_khz / 1000)) & 0xFF; + const u32 allowance5 = static_cast(0x8980 / (table->rate_khz / 1000)) & 0xFF; + + table->la_scale_regs.mc_latency_allowance_xusb_0 = (table->la_scale_regs.mc_latency_allowance_xusb_0 & MaskHigh) | (allowance1 << 16); + table->la_scale_regs.mc_latency_allowance_xusb_1 = (table->la_scale_regs.mc_latency_allowance_xusb_1 & MaskHigh) | (allowance1 << 16); + table->la_scale_regs.mc_latency_allowance_tsec_0 = (table->la_scale_regs.mc_latency_allowance_tsec_0 & MaskHigh) | (allowance1 << 16); + table->la_scale_regs.mc_latency_allowance_sdmmcaa_0 = (table->la_scale_regs.mc_latency_allowance_sdmmcaa_0 & MaskHigh) | (allowance1 << 16); + table->la_scale_regs.mc_latency_allowance_sdmmcab_0 = (table->la_scale_regs.mc_latency_allowance_sdmmcab_0 & MaskHigh) | (allowance1 << 16); + table->la_scale_regs.mc_latency_allowance_sdmmc_0 = (table->la_scale_regs.mc_latency_allowance_sdmmc_0 & MaskHigh) | (allowance1 << 16); + table->la_scale_regs.mc_latency_allowance_sdmmca_0 = (table->la_scale_regs.mc_latency_allowance_sdmmca_0 & MaskHigh) | (allowance1 << 16); + table->la_scale_regs.mc_latency_allowance_ppcs_1 = (table->la_scale_regs.mc_latency_allowance_ppcs_1 & MaskHigh) | (allowance1 << 16); + table->la_scale_regs.mc_latency_allowance_nvdec_0 = (table->la_scale_regs.mc_latency_allowance_nvdec_0 & MaskHigh) | (allowance1 << 16); + table->la_scale_regs.mc_latency_allowance_mpcore_0 = (table->la_scale_regs.mc_latency_allowance_mpcore_0 & MaskHigh) | (allowance1 << 16); + table->la_scale_regs.mc_latency_allowance_avpc_0 = (table->la_scale_regs.mc_latency_allowance_avpc_0 & MaskHigh) | (allowance1 << 16); + table->la_scale_regs.mc_latency_allowance_isp2_1 = allowance1 | (table->la_scale_regs.mc_latency_allowance_isp2_1 & Mask3) | (allowance1 << 16); + table->la_scale_regs.mc_latency_allowance_gpu_0 = allowance2 | (table->la_scale_regs.mc_latency_allowance_gpu_0 & Mask3) | (allowance1 << 16); + table->la_scale_regs.mc_latency_allowance_gpu2_0 = allowance2 | (table->la_scale_regs.mc_latency_allowance_gpu2_0 & Mask3) | (allowance1 << 16); + table->la_scale_regs.mc_latency_allowance_vic_0 = allowance3 | (table->la_scale_regs.mc_latency_allowance_vic_0 & Mask3) | (allowance1 << 16); + table->la_scale_regs.mc_latency_allowance_nvenc_0 = allowance4 | (table->la_scale_regs.mc_latency_allowance_nvenc_0 & Mask3) | (allowance1 << 16); + table->la_scale_regs.mc_latency_allowance_hc_0 = (table->la_scale_regs.mc_latency_allowance_hc_0 & Mask2) | allowance5; + table->la_scale_regs.mc_latency_allowance_hc_1 = (table->la_scale_regs.mc_latency_allowance_hc_1 & Mask2) | allowance1; + table->la_scale_regs.mc_latency_allowance_vi2_0 = (table->la_scale_regs.mc_latency_allowance_vi2_0 & Mask2) | allowance1; + + table->dram_timings.t_rp = tRP_values[0]; + const u32 tRFCabStock = tRFC_values[0] * 2; + table->dram_timings.t_rfc = tRFCabStock; + + table->dram_timings.rl = RL; + table->emc_mrw2 = (table->emc_mrw2 & ~0xFFu) | static_cast(mrw2); + table->emc_mrw = (table->emc_mrw & ~0x70u) | 0x40; /* nWR */ + table->emc_cfg_2 = 0x11083D; + } + + void MemMtcPllmbDivisor(MarikoMtcTable *table) { + constexpr u32 PllOscInKHz = 38400; + constexpr u32 PllOscHalfKHz = 19200; + + double target_freq_d = static_cast(table->rate_khz); + + s32 divm_candidate_half = static_cast(table->rate_khz / PllOscHalfKHz); + + bool remainder_check = (table->rate_khz - PllOscInKHz * (table->rate_khz / PllOscInKHz)) > (table->rate_khz - PllOscHalfKHz * divm_candidate_half) && static_cast(((target_freq_d / PllOscHalfKHz - divm_candidate_half - 0.5) * 8192.0)) != 0; + + u32 divm_final = remainder_check + 1; + table->pllmb_divm = divm_final; + + double div_step_d = static_cast(PllOscInKHz) / divm_final; + s32 divn_integer = static_cast(table->rate_khz / div_step_d); + table->pllmb_divn = divn_integer; + + u32 divn_fraction = static_cast((target_freq_d / div_step_d - divn_integer - 0.5) * 8192.0); + + u32 actual_freq_khz = static_cast((divn_integer + 0.5 + divn_fraction * 0.000122070312) * div_step_d); + + if (table->rate_khz - 2366001 < 133999) { + s32 divn_fraction_ssc = static_cast((actual_freq_khz * 0.997 / div_step_d - divn_integer - 0.5) * 8192.0); + + double delta_scaled = (0.3 / div_step_d + 0.3 / div_step_d) * (divn_fraction - divn_fraction_ssc); + s32 delta_int = static_cast(delta_scaled); + double delta_frac = delta_scaled - delta_int; + + u32 setup_value = 0; + if (delta_frac <= 0.5) { + double round_val = (delta_int + ROUND(delta_frac + delta_frac)) ? 0.5 : 0.0; + setup_value = ROUND(delta_frac + delta_frac) ? static_cast(round_val + round_val) | 0x1000 : static_cast(round_val); + } else { + s32 frac_doubled = ROUND(delta_frac - 0.5 + delta_frac - 0.5); + double round_val = 1.0; + setup_value = frac_doubled ? static_cast(round_val) : static_cast(round_val + round_val) | 0x1000; + } + + u32 ctrl1 = static_cast(divn_fraction_ssc) | (static_cast(divn_fraction) << 16); + u32 ctrl2 = static_cast(divn_fraction) | (static_cast(setup_value) << 16); + + table->pllm_ss_ctrl1 = ctrl1; + table->pllm_ss_ctrl2 = ctrl2; + table->pllmb_ss_ctrl1 = ctrl1; + table->pllmb_ss_ctrl2 = ctrl2; + } else { + table->pllm_ss_cfg &= 0xBFFFFFFF; + table->pllmb_ss_cfg &= 0xBFFFFFFF; + + u64 pair = (static_cast(divn_fraction) << 32) | static_cast(table->rate_khz); + u32 pll_misc = (table->pllm_ss_ctrl2 & 0xFFFF0000) | static_cast((pair - actual_freq_khz) >> 32); + + table->pllm_ss_ctrl2 = pll_misc; + table->pllmb_ss_ctrl2 = pll_misc; + } + } + + void MtcGenerateJedecTable() { + const u32 jedecFreqs[] = { 1866000, 1996000, 2133000, 2400000, 2666000, 2933000, 3200000 }; + constexpr u32 JedecFreqCount = std::size(jedecFreqs); + + for (u32 i = 0; i < JedecFreqCount; ++i) { + if (jedecFreqs[i] <= C.marikoEmcMaxClock) { + newEmcList.push_back(jedecFreqs[i]); + } else { + break; + } + } + + if (newEmcList.back() != C.marikoEmcMaxClock) { + newEmcList.push_back(static_cast(C.marikoEmcMaxClock)); + } + + newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit)); + } + + void MtcGenerate133StepTable() { + const u32 stepFreqs133[] = { 1733000, 1866000, 2000000, 2133000, 2266000, 2400000, 2533000, 2666000, 2800000, 2933000, 3066000, 3200000, 3333000, 3466000, }; // Avoid rounding issues + constexpr u32 StepFreqs133Size = std::size(stepFreqs133); + + for (u32 i = 0; i < StepFreqs133Size; ++i) { + if (stepFreqs133[i] <= C.marikoEmcMaxClock) { + newEmcList.push_back(stepFreqs133[i]); + } else { + break; + } + } + + if (newEmcList.back() != C.marikoEmcMaxClock) { + newEmcList.push_back(static_cast(C.marikoEmcMaxClock)); + } + + newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit)); + } + + void MtcGenerate33StepTable() { + /* ~33.33MHz but rounded*/ + const u32 stepFreqs33[] = { + 1633000, 1666000, 1700000, 1733000, 1766000, 1800000, 1833000, 1866000, 1900000, 1933000, + 1966000, 2000000, 2033000, 2066000, 2100000, 2133000, 2166000, 2200000, 2233000, 2266000, + 2300000, 2333000, 2366000, 2400000, 2433000, 2466000, 2500000, 2533000, 2566000, 2600000, + 2633000, 2666000, 2700000, 2733000, 2766000, 2800000, 2833000, 2866000, 2900000, 2933000, + 2966000, 3000000, 3033000, 3066000, 3100000, 3133000, 3166000, 3200000, 3233000, 3266000, + 3300000, 3333000, 3366000, 3400000, 3433000, 3466000, 3500000, + }; + constexpr u32 StepFreqs33Size = std::size(stepFreqs33); + + for (u32 i = 0; i < StepFreqs33Size; ++i) { + if (stepFreqs33[i] <= C.marikoEmcMaxClock) { + newEmcList.push_back(stepFreqs33[i]); + } else { + break; + } + } + + if (newEmcList.back() != C.marikoEmcMaxClock) { + newEmcList.push_back(static_cast(C.marikoEmcMaxClock)); + } + + newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit)); + } + + void MtcGenerateFreqTables() { + newEmcList.clear(); + newEmcList.reserve(EmcDvfsTableEntryCount); + newEmcList.insert(newEmcList.end(), std::begin(EmcListDefault), std::end(EmcListDefault)); + + if (C.marikoEmcMaxClock <= EmcClkOSLimit) { + return; + } + + u32 stepRate = 0; + switch (C.stepMode) { + case StepMode_33MHz: + MtcGenerate33StepTable(); + return; + case StepMode_66MHz: + stepRate = 66667; + break; + case StepMode_100MHz: + stepRate = 100000; + break; + case StepMode_Jedec: + MtcGenerateJedecTable(); + return; + case StepMode_133MHz: + MtcGenerate133StepTable(); + return; + default: + stepRate = 66667; + break; + } + + constexpr u32 RoundHz = 1000; + for (u32 stepIndex = 1;; ++stepIndex) { + u32 newFreq = EmcClkOSLimit + stepIndex * stepRate; + newFreq = (newFreq / RoundHz) * RoundHz; + if (newFreq > C.marikoEmcMaxClock) { + if (newEmcList.back() != C.marikoEmcMaxClock) { + newEmcList.push_back(static_cast(C.marikoEmcMaxClock)); + } + break; + } + newEmcList.push_back(newFreq); + } + + newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit)); + } + + Result VerifyMtcTable(MarikoMtcTable *tableStart, u32 expectedFreq) { + R_UNLESS(tableStart->rate_khz == expectedFreq, ldr::ResultInvalidMtcTable()); + R_UNLESS(tableStart->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable()); + + R_SUCCEED(); + } + + Result MtcValidateAllTables(MarikoMtcTable *tableStart, const u32 *validationList, u32 tableCount) { + for (u32 i = 0; i < tableCount; ++i) { + R_TRY(VerifyMtcTable(&tableStart[i], validationList[i])); + } + + R_SUCCEED(); + } + + DramId GetDramId() { + u64 id64; + splGetConfig(SplConfigItem_DramId, &id64); + return static_cast(id64); + } + + MtcTableIndex GetMtcDramIndex(DramId dramId) { + for (u32 i = 0; i < std::size(mtcIndexTable); ++i) { + if (mtcIndexTable[i].dramId == dramId) { + return mtcIndexTable[i].index; + } + } + + return MtcTableIndex_Invalid; + } + + NORETURN void AbortInvalidMtc(const char *crashMsg) { + panic::SmcError(panic::Emc); + CRASH(crashMsg); + } + + u32 GetMtcOffset(MtcTableIndex index) { + if (index < T210b0SdevEmcDvfsTableS4gb03) { + return index * mariko::MtcFullTableSize; + } + + /* There are 2 erista mtc tables between T210b0SdevEmcDvfsTableS4gb01 and T210b0SdevEmcDvfsTableS4gb03, so we have to do this adjustment. */ + return mariko::MtcFullTableSize * index + (2 * erista::MtcFullTableSize); + } + + void PrepareMtcMemoryRegion(u8 *firstTable, MarikoMtcTable *usedTable) { + memmove(firstTable, usedTable, mariko::MtcFullTableSize); + + /* Clear all other tables. */ + /* 1 erista table is excluded because it's always before firstTable. */ + /* We also exclude the used table obviously. */ + constexpr size_t RemainingRegionSize = (mariko::MtcFullTableSize) * (mariko::MtcFullTableCount - 1) + (erista::MtcFullTableSize * (erista::MtcFullTableCount - 1)); + memset(firstTable + mariko::MtcFullTableSize, 0, RemainingRegionSize); + } + + void MtcExtendTables(MarikoMtcTable *table) { + for (u32 i = mariko::MtcTableCountDefault; i < newEmcList.size(); ++i) { + std::memcpy(&table[i], &table[i - 1], sizeof(MarikoMtcTable)); + table[i].rate_khz = newEmcList[i]; + } + } + + Result MemFreqMtcTable(u32 *ptr) { + static const DramId dramId = [] { + DramId id = GetDramId(); + id = HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTF; + return id; + }(); + + static const MtcTableIndex mtcIndex = [] { + MtcTableIndex idx = GetMtcDramIndex(dramId); + /* If for some reason this happens, there is no chance of recovering this. */ + if (idx == MtcTableIndex_Invalid) { + AbortInvalidMtc("Invalid dramId"); + } + return idx; + }(); + + /* Offset to dram id specific mtc table. */ + static const u32 mtcOffset = GetMtcOffset(mtcIndex); + + /* Offset from 1600MHz pointer to 204Mhz table start. */ + constexpr u32 StartAdjustment = offsetof(MarikoMtcTable, rate_khz) + sizeof(MarikoMtcTable) * (mariko::MtcTableCountDefault - 1); + u8 *startPtr = reinterpret_cast(ptr) - StartAdjustment; + MarikoMtcTable *table = reinterpret_cast(startPtr + mtcOffset); + R_TRY(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault)); + + PrepareMtcMemoryRegion(startPtr, table); + table = reinterpret_cast(startPtr); + + if (R_FAILED(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault))) { + AbortInvalidMtc("Failed mtc validation"); + } + + if (C.marikoEmcMaxClock <= EmcClkOSLimit) { + R_SKIP(); + } + + MtcExtendTables(table); + + if (R_FAILED(MtcValidateAllTables(table, newEmcList.data(), newEmcList.size()))) { + AbortInvalidMtc("Failed mtc validation"); + } + + for (u32 i = mariko::MtcTableCountDefault; i < newEmcList.size(); ++i) { + MemMtcTableAutoAdjust(&table[i]); + MemMtcPllmbDivisor(&table[i]); + } + + R_SUCCEED(); + } + + Result MemFreqDvbTable(u32 *ptr) { + DvbEntry *default_end = reinterpret_cast(ptr); + DvbEntry *new_start = default_end + 1; + + // Validate existing table + void *mem_dvb_table_head = reinterpret_cast(new_start) - sizeof(EmcDvbTableDefault); + bool validated = std::memcmp(mem_dvb_table_head, EmcDvbTableDefault, sizeof(EmcDvbTableDefault)) == 0; + R_UNLESS(validated, ldr::ResultInvalidDvbTable()); + + if (C.marikoEmcMaxClock <= EmcClkOSLimit) { + R_SKIP(); + } + + s32 max0 = 1050; + s32 max1 = 1025; + s32 max2 = 1000; + + if (C.marikoSocVmax && C.marikoSocVmax > 1000) { + max0 = C.marikoSocVmax; + max1 = C.marikoSocVmax; + max2 = C.marikoSocVmax; + } + + constexpr s32 MinVolt = 637; + + auto ClampVolt = [&](s32 value, s32 max, s32 voltAdd) { + return std::clamp(value + voltAdd, MinVolt, max); + }; + + auto DvbVolt = [&](s32 zero, s32 one, s32 two, u32 index) { + const s32 overrideVoltage = C.marikoSocVoltArray[index]; + s32 voltAdd = 25 * C.emcDvbShift; + + if (overrideVoltage) { + zero = one = two = overrideVoltage; + voltAdd = 0; + } + + return std::array{ + ClampVolt(zero, max0, voltAdd), + ClampVolt(one, max1, voltAdd), + ClampVolt(two, max2, voltAdd) + }; + }; + + #define DVB(v) \ + static_cast((v)[0]), \ + static_cast((v)[1]), \ + static_cast((v)[2]) + + #define DVB_OC(one, zero, two, idx) \ + DVB(DvbVolt(one, zero, two, idx)) + + DvbEntry emcDvbOcTableBrackets[] = { + { 204000, { 637, 637, 637, }, }, + { 1331200, { 650, 637, 637, }, }, + { 1600000, { 675, 650, 637, }, }, + { 1866000, { DVB_OC( 700, 675, 650, 0) }, }, + { 2000000, { DVB_OC( 712, 687, 662, 1) }, }, + { 2133000, { DVB_OC( 725, 700, 675, 2) }, }, + { 2200000, { DVB_OC( 737, 712, 687, 3) }, }, + { 2266000, { DVB_OC( 750, 725, 700, 4) }, }, + { 2333000, { DVB_OC( 762, 737, 712, 5) }, }, + { 2400000, { DVB_OC( 775, 750, 725, 6) }, }, + { 2433000, { DVB_OC( 787, 762, 737, 7) }, }, + { 2466000, { DVB_OC( 800, 775, 750, 8) }, }, + { 2533000, { DVB_OC( 812, 787, 762, 9) }, }, + { 2566000, { DVB_OC( 825, 800, 775, 10) }, }, + { 2600000, { DVB_OC( 837, 812, 787, 11) }, }, + { 2666000, { DVB_OC( 850, 825, 800, 12) }, }, + { 2700000, { DVB_OC( 875, 850, 825, 13) }, }, + { 2733000, { DVB_OC( 887, 862, 837, 14) }, }, + { 2766000, { DVB_OC( 912, 887, 862, 15) }, }, + { 2800000, { DVB_OC( 925, 900, 875, 16) }, }, + { 2833000, { DVB_OC( 937, 912, 887, 17) }, }, + { 2900000, { DVB_OC( 950, 925, 900, 18) }, }, + { 2933000, { DVB_OC( 962, 937, 912, 19) }, }, + { 3000000, { DVB_OC( 975, 950, 925, 20) }, }, + { 3033000, { DVB_OC( 987, 962, 937, 21) }, }, + { 3100000, { DVB_OC(1000, 975, 950, 22) }, }, + { 3133000, { DVB_OC(1025, 1000, 975, 23) }, }, + { 3166000, { DVB_OC(1037, 1012, 987, 24) }, }, + { 3200000, { DVB_OC(1050, 1025, 1000, 25) }, }, + { 3266000, { DVB_OC(1075, 1050, 1025, 26) }, }, + { 3333000, { DVB_OC(1100, 1075, 1050, 27) }, }, + { ~0u, { }, }, + }; + #undef DVB + #undef DVB_OC + + const size_t dvbCount = std::min(newEmcList.size(), DvbTableCapacity); + DvbEntry emcDvbTableOc[DvbTableCapacity] = {}; + + u32 bracketIndex = 0; + for (size_t i = 0; i < dvbCount; ++i) { + const u32 freq = (i == dvbCount - 1) ? static_cast(newEmcList.back()) : newEmcList[i]; + while (freq >= emcDvbOcTableBrackets[bracketIndex + 1].freq) { + ++bracketIndex; + } + + emcDvbTableOc[i].freq = freq; + std::memcpy(emcDvbTableOc[i].volt, emcDvbOcTableBrackets[bracketIndex].volt, sizeof(emcDvbTableOc[i].volt)); + } + + /* Clear the entire 32-entry region */ + std::memset(mem_dvb_table_head, 0, DvbTableCapacity * sizeof(DvbEntry)); + std::memcpy(mem_dvb_table_head, emcDvbTableOc, dvbCount * sizeof(DvbEntry)); + + R_SUCCEED(); + } + + Result MemFreqMax(u32 *ptr) { + if (C.marikoEmcMaxClock <= EmcClkOSLimit) { + R_SKIP(); + } + + PATCH_OFFSET(ptr, C.marikoEmcMaxClock); + R_SUCCEED(); + } + + Result MemMtcTableAsm(u32 *ptr) { + constexpr u32 AddpOffset = 1; + constexpr u32 BrOffset = 12; + constexpr u32 MovOffset = 10; + + /* Ensure we don't dereference memory before nso start. */ + R_UNLESS(ptr - BrOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern()); + + u32 adrp = *(ptr - AddpOffset); + R_UNLESS(AsmCompareAdrpNoImm(adrp, MtcAdrpAsm), 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()); + + /* Pray this does not break either. */ + u32 mov = *(ptr - MovOffset); + R_UNLESS(asm_compare_no_rd(mov, MtcMovAsm), ldr::ResultInvalidMtcTablePattern()); + + u8 movRd = asm_get_rd(mov); + u32 movCountPatch = asm_set_rd(asm_set_imm16(MtcMovAsm, newEmcList.size()), movRd); + + PATCH_OFFSET(ptr - BrOffset, NopIns); + PATCH_OFFSET(ptr - MovOffset, movCountPatch); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.hpp new file mode 100644 index 00000000..99531813 --- /dev/null +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * + * Copyright (c) B3711 + * + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "../pcv.hpp" + +namespace ams::ldr::hoc::pcv::mariko { + + void MtcGenerateFreqTables(); + Result MemFreqMtcTable(u32 *ptr); + Result MemFreqDvbTable(u32 *ptr); + Result MemFreqMax(u32 *ptr); + Result MemMtcTableAsm(u32 *ptr); + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/mariko/timing_tables.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/timing_tables.cpp similarity index 96% rename from Source/Atmosphere/stratosphere/loader/source/oc/mariko/timing_tables.cpp rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/timing_tables.cpp index 61842b3d..ecb4d47f 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/mariko/timing_tables.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/timing_tables.cpp @@ -1,47 +1,47 @@ -/* - * Copyright (c) Lightos_ - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "../oc_common.hpp" -#include "timing_tables.hpp" - -namespace ams::ldr::hoc::pcv::mariko { - - const ReplacePatch g_rext_table[] = { - {2'133'000, 0x1A}, {2'166'000, 0x19}, {2'200'000, 0x19}, - {2'233'000, 0x19}, {2'266'000, 0x1A}, {2'300'000, 0x1B}, - {2'333'000, 0x1B}, {2'366'000, 0x1B}, {2'400'000, 0x1B}, - {2'433'000, 0x1B}, {2'466'000, 0x1B}, {2'500'000, 0x1A}, - {2'533'000, 0x1C}, {2'566'000, 0x1B}, {2'600'000, 0x1B}, - {2'633'000, 0x1B}, {2'666'000, 0x1B}, {2'700'000, 0x1C}, - {2'733'000, 0x1C}, {2'766'000, 0x1D}, {2'800'000, 0x1D}, - {2'833'000, 0x1D}, {2'866'000, 0x1D}, {2'900'000, 0x1D}, - {2'933'000, 0x1C}, {2'966'000, 0x1D}, {3'000'000, 0x1D}, - {3'033'000, 0x1D}, {3'066'000, 0x1D}, {3'100'000, 0x1D}, - {3'133'000, 0x1D}, {3'166'000, 0x1C}, {3'200'000, 0x1C}, - }; - - const u32 g_rext_table_size = sizeof(g_rext_table) / sizeof(g_rext_table[0]); - - const ReplacePatch *FindRext() { - for (u32 i = 0; i < g_rext_table_size; i++) { - if (g_rext_table[i].freq >= C.marikoEmcMaxClock) { - return &g_rext_table[i]; - } - } - return nullptr; - } - -} +/* + * Copyright (c) Lightos_ + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../../oc_common.hpp" +#include "timing_tables.hpp" + +namespace ams::ldr::hoc::pcv::mariko { + + const ReplacePatch g_rext_table[] = { + {2'133'000, 0x1A}, {2'166'000, 0x19}, {2'200'000, 0x19}, + {2'233'000, 0x19}, {2'266'000, 0x1A}, {2'300'000, 0x1B}, + {2'333'000, 0x1B}, {2'366'000, 0x1B}, {2'400'000, 0x1B}, + {2'433'000, 0x1B}, {2'466'000, 0x1B}, {2'500'000, 0x1A}, + {2'533'000, 0x1C}, {2'566'000, 0x1B}, {2'600'000, 0x1B}, + {2'633'000, 0x1B}, {2'666'000, 0x1B}, {2'700'000, 0x1C}, + {2'733'000, 0x1C}, {2'766'000, 0x1D}, {2'800'000, 0x1D}, + {2'833'000, 0x1D}, {2'866'000, 0x1D}, {2'900'000, 0x1D}, + {2'933'000, 0x1C}, {2'966'000, 0x1D}, {3'000'000, 0x1D}, + {3'033'000, 0x1D}, {3'066'000, 0x1D}, {3'100'000, 0x1D}, + {3'133'000, 0x1D}, {3'166'000, 0x1C}, {3'200'000, 0x1C}, + }; + + const u32 g_rext_table_size = sizeof(g_rext_table) / sizeof(g_rext_table[0]); + + const ReplacePatch *FindRext() { + for (u32 i = 0; i < g_rext_table_size; i++) { + if (g_rext_table[i].freq >= C.marikoEmcMaxClock) { + return &g_rext_table[i]; + } + } + return nullptr; + } + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/mariko/timing_tables.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/timing_tables.hpp similarity index 96% rename from Source/Atmosphere/stratosphere/loader/source/oc/mariko/timing_tables.hpp rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/timing_tables.hpp index c23b812d..d682000e 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/mariko/timing_tables.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/timing_tables.hpp @@ -1,31 +1,31 @@ -/* - * Copyright (c) Lightos_ - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once -#include - -namespace ams::ldr::hoc::pcv::mariko { - - struct ReplacePatch { - u32 freq; - u32 rext; - }; - - extern const ReplacePatch g_rext_table[]; - extern const u32 g_rext_table_size; - const ReplacePatch *FindRext(); - -} +/* + * Copyright (c) Lightos_ + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +namespace ams::ldr::hoc::pcv::mariko { + + struct ReplacePatch { + u32 freq; + u32 rext; + }; + + extern const ReplacePatch g_rext_table[]; + extern const u32 g_rext_table_size; + const ReplacePatch *FindRext(); + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp index 74e540eb..c50f55a8 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp @@ -22,8 +22,14 @@ #include "../oc_common.hpp" #include "pcv_common.hpp" -#include "pcv_erista.hpp" -#include "pcv_mariko.hpp" + +#include "erista/pcv_erista_cpu.hpp" +#include "erista/pcv_erista_gpu.hpp" +#include "erista/pcv_erista_mtc.hpp" +#include "erista/pcv_erista.hpp" + +#include "mariko/pcv_mariko.hpp" + #include "pcv_hook.hpp" namespace ams::ldr::hoc::pcv { @@ -188,6 +194,7 @@ namespace ams::ldr::hoc::pcv { } ++entry; } + if (C.commonGpuVoltOffset && !(isMariko ? C.marikoGpuUV : C.eristaGpuUV)) { cvb_entry_t *entry = static_cast(gpu_cvb_table_head); for (size_t i = 0; i < customize_entry_count; ++i) { diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.hpp deleted file mode 100644 index 6e46f8fa..00000000 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.hpp +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) Switch-OC-Suite - * - * Copyright (c) 2023 hanai3Bi - * - * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -#include "../oc_common.hpp" -#include "pcv_common.hpp" -#include "pcv_asm.hpp" - -namespace ams::ldr::hoc::pcv::erista { - - constexpr cvb_entry_t CpuCvbTableDefault[] = { - // CPU_PLL_CVB_TABLE_ODN - { 204000, {721094}, { } }, - { 306000, {754040}, { } }, - { 408000, {786986}, { } }, - { 510000, {819932}, { } }, - { 612000, {852878}, { } }, - { 714000, {885824}, { } }, - { 816000, {918770}, { } }, - { 918000, {951716}, { } }, - { 1020000, {984662}, { -2875621, 358099, -8585} }, - { 1122000, {1017608}, { -52225, 104159, -2816} }, - { 1224000, {1050554}, { 1076868, 8356, -727} }, - { 1326000, {1083500}, { 2208191, -84659, 1240} }, - { 1428000, {1116446}, { 2519460, -105063, 1611} }, - { 1581000, {1130000}, { 2889664, -122173, 1834} }, - { 1683000, {1168000}, { 5100873, -279186, 4747} }, - { 1785000, {1227500}, { 5100873, -279186, 4747} }, - { }, - }; - - constexpr u32 CpuVoltOfficial = 1227; - constexpr u32 CpuVminOfficial = 825; - constexpr u32 CpuTune0Low = 0xFFEAD0FF; - - constexpr u32 CpuVoltL4T = 1257'000; - - static const u32 cpuVoltDvfsPattern[] = { 1227, 1000, 100, 1000, 0 }; - static_assert(sizeof(cpuVoltDvfsPattern) == 0x14, "Invalid cpuVoltDvfsPattern size"); - - static const u32 cpuVoltageThermalPattern[] = { 950, 1132, 0, 950, 1227, 0, 825, 1227, 15000, 825, 1170, 60000, 825, 1132, 80000 }; - static_assert(sizeof(cpuVoltageThermalPattern) == 0x3c, "Invalid cpuVoltageThermalPattern size"); - - constexpr u32 GpuClkPllLimit = 2'600'000; - constexpr u32 GpuClkPllMax = 921'600'000; - constexpr u32 GpuVminOfficial = 810; - - constexpr u16 CpuMinVolts[] = { 950, 850, 825, 810 }; - - static const u32 gpuVoltDvfsPattern[] = { 810, 1150, 1000, 100, 1000, 10, }; - static_assert(sizeof(gpuVoltDvfsPattern) == (sizeof(u32) * 6), "Invalid gpuVoltDvfsPattern"); - - static const u32 gpuVoltThermalPattern[] = { 950, 1132, 0, 810, 1132, 15000, 810, 1132, 30000, 810, 1132, 50000, 810, 1132, 70000, 810, 1132, 105000 }; - static_assert(sizeof(gpuVoltThermalPattern) == 0x48, "Invalid gpuVoltageThermalPattern size"); - - /* GPU Max Clock asm Pattern: - * - * MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11) - * 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 - * 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1 - * - * MOVK W11, #0xE, LSL#16 16 0xE 0xB (11) - * 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 - * 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1 - */ - inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 }; - - inline bool GpuMaxClockPatternFn(u32 *ptr32) { - return asm_compare_no_rd(*ptr32, GpuAsmPattern[0]); - }; - - constexpr cvb_entry_t GpuCvbTableDefault[] = { - // NA_FREQ_CVB_TABLE - { 76800, {}, { 814294, 8144, -940, 808, -21583, 226, } }, - { 153600, {}, { 856185, 8144, -940, 808, -21583, 226, } }, - { 230400, {}, { 898077, 8144, -940, 808, -21583, 226, } }, - { 307200, {}, { 939968, 8144, -940, 808, -21583, 226, } }, - { 384000, {}, { 981860, 8144, -940, 808, -21583, 226, } }, - { 460800, {}, { 1023751, 8144, -940, 808, -21583, 226, } }, - { 537600, {}, { 1065642, 8144, -940, 808, -21583, 226, } }, - { 614400, {}, { 1107534, 8144, -940, 808, -21583, 226, } }, - { 691200, {}, { 1149425, 8144, -940, 808, -21583, 226, } }, - { 768000, {}, { 1191317, 8144, -940, 808, -21583, 226, } }, - { 844800, {}, { 1233208, 8144, -940, 808, -21583, 226, } }, - { 921600, {}, { 1275100, 8144, -940, 808, -21583, 226, } }, - { }, - }; - - constexpr u32 EmcListDefault[] = { 40800, 68000, 102000, 204000, 408000, 665600, 800000, 1065600, 1331200, 1600000, }; - constexpr u32 EmcListSizeDefault = std::size(EmcListDefault); - constexpr u32 EmcListEndDefault = EmcListSizeDefault - 1; - - constexpr u32 MemVoltHOS = 1125'000; - constexpr u32 EmcClkPllmLimit = 1866'000'000; - - constexpr u32 MTC_TABLE_REV = 7; - constexpr u32 MtcTableCountDefault = 10; - - constexpr size_t MtcFullTableSize = sizeof(EristaMtcTable) * MtcTableCountDefault; - constexpr u32 MtcFullTableCount = 3; - - /* These dramids were copied from Hekate -- see /bdk/mem/sdram.h */ - enum DramId { - ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH = 0, - ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE = 1, - ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC = 2, - ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH = 4, - ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX = 7, - }; - - enum MtcTableIndex { - T210SdevEmcDvfsTableS4gb01 = 0, /* HB-MGCH, WT:C */ - T210SdevEmcDvfsTableS6gb01 = 1, /* HM-MGCH */ - T210SdevEmcDvfsTableH4gb01 = 2, /* HR-NLE */ - MtcTableIndex_Invalid = 3, - }; - - struct MtcDramIndex { - DramId dramId; - MtcTableIndex index; - }; - - /* TODO: Test 6gb and 8gb. */ - const inline MtcDramIndex mtcIndexTable[] = { - { ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH, T210SdevEmcDvfsTableS4gb01, }, - { ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC, T210SdevEmcDvfsTableS4gb01, }, - { ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH, T210SdevEmcDvfsTableS6gb01, }, - { ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX, T210SdevEmcDvfsTableS6gb01, }, - { ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE, T210SdevEmcDvfsTableH4gb01, }, - }; - - 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); - } - - void Patch(uintptr_t mapped_nso, size_t nso_size); - -} diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp index 06acacda..fcc6a47a 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp @@ -37,6 +37,9 @@ ".text\n"); \ extern "C" __attribute__((visibility("hidden"))) type *name +#define DECLARE_HOOK_PAYLOAD_PTR(type, name) \ + extern "C" __attribute__((visibility("hidden"))) type *name + #define HOOK_PAYLOAD_PTR(type, name) \ ([]() -> type * { \ type **_hoc_pp; \ diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp deleted file mode 100644 index 111e8a55..00000000 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp +++ /dev/null @@ -1,1545 +0,0 @@ -/* - * Copyright (C) Switch-OC-Suite - * - * Copyright (c) 2023 hanai3Bi - * - * Copyright (c) B3711 - * - * Copyright (c) Souldbminer and Horizon OC Contributors - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include "pcv.hpp" -#include "../mtc_timing_value.hpp" -#include "../mariko/calculate_timings.hpp" - -namespace ams::ldr::hoc::pcv::mariko { - - Result GpuVoltDVFS(u32 *ptr) { - /* Check for valid pattern. */ - for (size_t i = 0; i < std::size(gpuDVFSPattern); ++i) { - if (*(ptr + i + 1) != gpuDVFSPattern[i]) { - R_THROW(ldr::ResultInvalidGpuDvfs()); - } - } - - /* Default value is 1050mV. */ - if (C.marikoGpuVmax) { - PATCH_OFFSET(ptr + 1, C.marikoGpuVmax); - } - - if (C.marikoGpuVmin) { - PATCH_OFFSET(ptr, C.marikoGpuVmin); - } - - R_SUCCEED(); - } - - Result GpuVoltThermals(u32 *ptr) { - if (std::memcmp(ptr - 3, gpuVoltThermalPattern, sizeof(gpuVoltThermalPattern))) { - R_THROW(ldr::ResultInvalidGpuDvfs()); - } - - // if (C.marikoGpuBootVolt) { - // PATCH_OFFSET(ptr - 3, C.marikoGpuBootVolt); - // } - - if (C.marikoGpuVmin) { - PATCH_OFFSET(ptr, C.marikoGpuVmin); - PATCH_OFFSET(ptr + 3, C.marikoGpuVmin); - PATCH_OFFSET(ptr + 6, C.marikoGpuVmin); - PATCH_OFFSET(ptr + 9, C.marikoGpuVmin); - PATCH_OFFSET(ptr + 12, C.marikoGpuVmin); - } - - R_SUCCEED(); - } - - u32 CapCpuClock() { - u32 cpuCap = allowedCpuMaxFrequencies[0]; - - for (u32 freq : allowedCpuMaxFrequencies) { - if (C.marikoCpuMaxClock >= freq) { - cpuCap = freq; - } else { - break; - } - } - return cpuCap; - } - - Result CpuFreqVdd(u32 *ptr) { - dvfs_rail *entry = reinterpret_cast(reinterpret_cast(ptr) - offsetof(dvfs_rail, freq)); - - R_UNLESS(entry->id == 1, ldr::ResultInvalidCpuFreqVddEntry()); - R_UNLESS(entry->min_mv == 250'000, ldr::ResultInvalidCpuFreqVddEntry()); - R_UNLESS(entry->step_mv == 5000, ldr::ResultInvalidCpuFreqVddEntry()); - R_UNLESS(entry->max_mv == 1525'000, ldr::ResultInvalidCpuFreqVddEntry()); - - if (C.marikoCpuUVHigh) { - PATCH_OFFSET(ptr, CapCpuClock()); - } else { - PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq); - } - - R_SUCCEED(); - } - - Result CpuVoltDVFS(u32 *ptr) { - CvbMeta *cpuCvbMeta = reinterpret_cast(reinterpret_cast(ptr) - offsetof(CvbMeta, vmin)); - - R_UNLESS(cpuCvbMeta->highVmin == CpuHighVminOfficial, ldr::ResultInvalidCpuMinVolt()); - R_UNLESS(cpuCvbMeta->unkStepMaybe == 38, ldr::ResultInvalidCpuMinVolt()); - R_UNLESS(cpuCvbMeta->vmax == CpuVoltOfficial, ldr::ResultInvalidCpuMinVolt()); - R_UNLESS(cpuCvbMeta->unkScale2 == 1000, ldr::ResultInvalidCpuMinVolt()); - R_UNLESS(cpuCvbMeta->speedoScale == 100, ldr::ResultInvalidCpuMinVolt()); - R_UNLESS(cpuCvbMeta->voltageScale == 1000, ldr::ResultInvalidCpuMinVolt()); - R_UNLESS(cpuCvbMeta->unkZero5 == 0, ldr::ResultInvalidCpuMinVolt()); - - if (C.marikoCpuLowVmin) { - PATCH_OFFSET(&(cpuCvbMeta->vmin), C.marikoCpuLowVmin); - } - - if (C.marikoCpuHighVmin) { - PATCH_OFFSET(&(cpuCvbMeta->highVmin), C.marikoCpuHighVmin); - } - - if (C.marikoCpuMaxVolt) { - PATCH_OFFSET(&(cpuCvbMeta->vmax), C.marikoCpuMaxVolt); - } - - R_SUCCEED(); - } - - Result CpuVoltThermals(u32 *ptr) { - if (std::memcmp(ptr, cpuVoltThermalData, sizeof(cpuVoltThermalData))) { - R_THROW(ldr::ResultInvalidCpuMinVolt()); - } - - if (C.marikoCpuLowVmin) { - PATCH_OFFSET(ptr, C.marikoCpuLowVmin); - PATCH_OFFSET(ptr + 3, C.marikoCpuLowVmin); - } - - if (C.marikoCpuMaxVolt) { - PATCH_OFFSET(ptr - 2, C.marikoCpuMaxVolt); - PATCH_OFFSET(ptr - 5, C.marikoCpuMaxVolt); - PATCH_OFFSET(ptr + 1, C.marikoCpuMaxVolt); - PATCH_OFFSET(ptr + 4, C.marikoCpuMaxVolt); - } - - R_SUCCEED(); - } - - Result CpuVoltDfll(u32 *ptr) { - CvbCpuDfllData *entry = reinterpret_cast(ptr); - - R_UNLESS(entry->tune0_low == 0xFFCF, ldr::ResultInvalidCpuVoltDfllEntry()); - R_UNLESS(entry->tune0_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry()); - R_UNLESS(entry->tune1_low == 0x12207FF, ldr::ResultInvalidCpuVoltDfllEntry()); - R_UNLESS(entry->tune1_high == 0x3FFF7FF, ldr::ResultInvalidCpuVoltDfllEntry()); - - switch (C.marikoCpuUVLow) { - case 1: - PATCH_OFFSET(&(entry->tune0_low), 0xffa0); - PATCH_OFFSET(&(entry->tune0_high), 0xffff); - PATCH_OFFSET(&(entry->tune1_low), 0x21107ff); - PATCH_OFFSET(&(entry->tune1_high), 0x0); - break; - case 2: - PATCH_OFFSET(&(entry->tune0_high), 0xffdf); - PATCH_OFFSET(&(entry->tune1_low), 0x21107ff); - PATCH_OFFSET(&(entry->tune1_high), 0x27207ff); - break; - case 3: - PATCH_OFFSET(&(entry->tune0_low), 0xffdf); - PATCH_OFFSET(&(entry->tune0_high), 0xffdf); - PATCH_OFFSET(&(entry->tune1_low), 0x21107ff); - PATCH_OFFSET(&(entry->tune1_high), 0x27307ff); - break; - case 4: - PATCH_OFFSET(&(entry->tune0_low), 0xffff); - PATCH_OFFSET(&(entry->tune0_high), 0xffdf); - PATCH_OFFSET(&(entry->tune1_low), 0x21107ff); - PATCH_OFFSET(&(entry->tune1_high), 0x27407ff); - break; - case 5: - PATCH_OFFSET(&(entry->tune0_high), 0xffdf); - PATCH_OFFSET(&(entry->tune1_low), 0x21607ff); - PATCH_OFFSET(&(entry->tune1_high), 0x27707ff); - break; - case 6: - PATCH_OFFSET(&(entry->tune0_high), 0xffdf); - PATCH_OFFSET(&(entry->tune1_low), 0x21607ff); - PATCH_OFFSET(&(entry->tune1_high), 0x27807ff); - break; - case 7: - PATCH_OFFSET(&(entry->tune0_high), 0xdfff); - PATCH_OFFSET(&(entry->tune1_low), 0x21607ff); - PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff); - break; - case 8: - PATCH_OFFSET(&(entry->tune0_low), 0xdfff); - PATCH_OFFSET(&(entry->tune0_high), 0xdfff); - PATCH_OFFSET(&(entry->tune1_low), 0x21707ff); - PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff); - break; - case 9: - PATCH_OFFSET(&(entry->tune0_low), 0xdfff); - PATCH_OFFSET(&(entry->tune0_high), 0xdfff); - PATCH_OFFSET(&(entry->tune1_low), 0x21707ff); - PATCH_OFFSET(&(entry->tune1_high), 0x27c07ff); - break; - case 10: - PATCH_OFFSET(&(entry->tune0_low), 0xdfff); - PATCH_OFFSET(&(entry->tune0_high), 0xdfff); - PATCH_OFFSET(&(entry->tune1_low), 0x21707ff); - PATCH_OFFSET(&(entry->tune1_high), 0x27d07ff); - break; - case 11: - PATCH_OFFSET(&(entry->tune0_low), 0xdfff); - PATCH_OFFSET(&(entry->tune0_high), 0xdfff); - PATCH_OFFSET(&(entry->tune1_low), 0x21707ff); - PATCH_OFFSET(&(entry->tune1_high), 0x27e07ff); - break; - case 12: - PATCH_OFFSET(&(entry->tune0_low), 0xdfff); - PATCH_OFFSET(&(entry->tune0_high), 0xdfff); - PATCH_OFFSET(&(entry->tune1_low), 0x21707ff); - PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff); - break; - default: - break; - } - - switch (C.marikoCpuUVHigh) { - case 1: - PATCH_OFFSET(&(entry->tune1_high), 0x0); - PATCH_OFFSET(&(entry->tune0_high), 0xffff); - break; - case 2: - PATCH_OFFSET(&(entry->tune0_high), 0xffdf); - PATCH_OFFSET(&(entry->tune1_high), 0x27207ff); - break; - case 3: - PATCH_OFFSET(&(entry->tune0_high), 0xffdf); - PATCH_OFFSET(&(entry->tune1_high), 0x27307ff); - break; - case 4: - PATCH_OFFSET(&(entry->tune0_high), 0xffdf); - PATCH_OFFSET(&(entry->tune1_high), 0x27407ff); - break; - case 5: - PATCH_OFFSET(&(entry->tune0_high), 0xffdf); - PATCH_OFFSET(&(entry->tune1_high), 0x27707ff); - break; - case 6: - PATCH_OFFSET(&(entry->tune0_high), 0xffdf); - PATCH_OFFSET(&(entry->tune1_high), 0x27807ff); - break; - case 7: - case 8: - PATCH_OFFSET(&(entry->tune0_high), 0xdfff); - PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff); - break; - case 9: - PATCH_OFFSET(&(entry->tune0_high), 0xdfff); - PATCH_OFFSET(&(entry->tune1_high), 0x27c07ff); - break; - case 10: - PATCH_OFFSET(&(entry->tune0_high), 0xdfff); - PATCH_OFFSET(&(entry->tune1_high), 0x27d07ff); - break; - case 11: - PATCH_OFFSET(&(entry->tune0_high), 0xdfff); - PATCH_OFFSET(&(entry->tune1_high), 0x27e07ff); - break; - case 12: - PATCH_OFFSET(&(entry->tune0_high), 0xdfff); - PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff); - break; - default: - break; - } - - R_SUCCEED(); - } - - 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]))) { - R_THROW(ldr::ResultInvalidGpuFreqMaxPattern()); - } - - // Both instructions should operate on the same register - u8 rd = asm_get_rd(ins1); - if (rd != asm_get_rd(ins2)) { - R_THROW(ldr::ResultInvalidGpuFreqMaxPattern()); - } - - u32 max_clock; - switch (C.marikoGpuUV) { - case 0: - max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTable)->freq; - break; - case 1: - max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableSLT)->freq; - break; - case 2: - max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq; - break; - case 3: - max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT15)->freq; - break; - case 4: - max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHighUV)->freq; - break; - default: - max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq; - break; - } - - 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) - }; - - PATCH_OFFSET(ptr32, asm_patch[0]); - PATCH_OFFSET(ptr32 + 1, asm_patch[1]); - - R_SUCCEED(); - } - - Result GpuFreqPllMax(u32 *ptr) { - clk_pll_param *entry = reinterpret_cast(ptr); - - // All zero except for freq - for (size_t i = 1; i < sizeof(clk_pll_param) / sizeof(u32); i++) { - R_UNLESS(*(ptr + i) == 0, ldr::ResultInvalidGpuPllEntry()); - } - - // Double the max clk simply - u32 max_clk = entry->freq * 2; - entry->freq = max_clk; - R_SUCCEED(); - } - - Result GpuFreqPllLimit(u32 *ptr) { - u32 prev_freq = *(ptr - 1); - - if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) { - R_THROW(ldr::ResultInvalidGpuPllEntry()); - } - - PATCH_OFFSET(ptr, 3600000); - - R_SUCCEED(); - } - - void MemMtcTableAutoAdjust(MarikoMtcTable *table) { - /* Official Tegra X1 TRM, sign up for nvidia developer program (free) to download: - * https://developer.nvidia.com/embedded/dlc/tegra-x1-technical-reference-manual - * Section 18.11.1: MC Registers - * Section 18.11.2: EMC Registers - */ - - #define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE) \ - TABLE->burst_regs.PARAM = VALUE; \ - TABLE->shadow_regs_ca_train.PARAM = VALUE; \ - TABLE->shadow_regs_rdwr_train.PARAM = VALUE; - - const double tCK_avg = 1000'000.0 / table->rate_khz; - - #define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg)) - - /* Ram power down */ - /* B31: DRAM_CLKSTOP_PD */ - /* B30: DRAM_CLKSTOP_SR */ - /* B29: DRAM_ACPD */ - if (C.hpMode) { - WRITE_PARAM_ALL_REG(table, emc_cfg, 0x13200000); - } else { - WRITE_PARAM_ALL_REG(table, emc_cfg, 0xF3200000); - } - - u32 trefbw = refresh_raw + 0x40; - trefbw = MIN(trefbw, static_cast(0x3FFF)); - - const u32 dyn_self_ref_control = (static_cast(7605.0 / tCK_avg) + 260) | (table->burst_regs.emc_dyn_self_ref_control & 0xffff0000); - - CalculateTimings(tCK_avg, table->rate_khz); - - WRITE_PARAM_ALL_REG(table, emc_rd_rcd, GET_CYCLE_CEIL(tRCD)); - WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(tRCD)); - WRITE_PARAM_ALL_REG(table, emc_rc, MIN(GET_CYCLE_CEIL(tRC), static_cast(0xB9))); - WRITE_PARAM_ALL_REG(table, emc_ras, MIN(GET_CYCLE_CEIL(tRAS), static_cast(0x7F))); - WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(tRRD)); - WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(tRFCpb)); - WRITE_PARAM_ALL_REG(table, emc_rfc, GET_CYCLE_CEIL(tRFCab)); - WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(tRPpb)); - WRITE_PARAM_ALL_REG(table, emc_txsr, MIN(GET_CYCLE_CEIL(tXSR), static_cast(0x3fe))); - WRITE_PARAM_ALL_REG(table, emc_txsrdll, MIN(GET_CYCLE_CEIL(tXSR), static_cast(0x3fe))); - WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(tFAW)); - WRITE_PARAM_ALL_REG(table, emc_trpab, MIN(GET_CYCLE_CEIL(tRPab), static_cast(0x3F))); - WRITE_PARAM_ALL_REG(table, emc_tckesr, GET_CYCLE_CEIL(tSR)); - WRITE_PARAM_ALL_REG(table, emc_tcke, GET_CYCLE_CEIL(7.425) + 2); - WRITE_PARAM_ALL_REG(table, emc_tpd, GET_CYCLE_CEIL(tXP)); - WRITE_PARAM_ALL_REG(table, emc_tclkstop, tCLKSTOP); - WRITE_PARAM_ALL_REG(table, emc_r2p, tR2P); - WRITE_PARAM_ALL_REG(table, emc_r2w, tR2W); - WRITE_PARAM_ALL_REG(table, emc_trtm, tRTM); - WRITE_PARAM_ALL_REG(table, emc_tratm, tRATM); - WRITE_PARAM_ALL_REG(table, emc_w2p, tW2P); - WRITE_PARAM_ALL_REG(table, emc_w2r, tW2R); - WRITE_PARAM_ALL_REG(table, emc_twtm, tWTM); - WRITE_PARAM_ALL_REG(table, emc_twatm, tWATM); - WRITE_PARAM_ALL_REG(table, emc_rext, rext); - WRITE_PARAM_ALL_REG(table, emc_wext, (table->rate_khz >= 2533000) ? 0x19 : 0x16); - WRITE_PARAM_ALL_REG(table, emc_refresh, refresh_raw); - WRITE_PARAM_ALL_REG(table, emc_pre_refresh_req_cnt, refresh_raw / 4); - WRITE_PARAM_ALL_REG(table, emc_trefbw, trefbw); - WRITE_PARAM_ALL_REG(table, emc_dyn_self_ref_control, dyn_self_ref_control); - WRITE_PARAM_ALL_REG(table, emc_pdex2wr, pdex2rw); - WRITE_PARAM_ALL_REG(table, emc_pdex2rd, pdex2rw); - WRITE_PARAM_ALL_REG(table, emc_pchg2pden, GET_CYCLE_CEIL(1.763)); - WRITE_PARAM_ALL_REG(table, emc_ar2pden, GET_CYCLE_CEIL(1.75)); - WRITE_PARAM_ALL_REG(table, emc_pdex2cke, GET_CYCLE_CEIL(1.05)); - WRITE_PARAM_ALL_REG(table, emc_act2pden, GET_CYCLE_CEIL(14.0)); - WRITE_PARAM_ALL_REG(table, emc_cke2pden, GET_CYCLE_CEIL(8.499)); - WRITE_PARAM_ALL_REG(table, emc_pdex2mrr, GET_CYCLE_CEIL(pdex2mrr)); - WRITE_PARAM_ALL_REG(table, emc_rw2pden, tWTPDEN); - 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_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_cmd_brlshft_2, 0x24); - WRITE_PARAM_ALL_REG(table, emc_cmd_brlshft_3, 0x24); - - /* This needs some clean up. */ - constexpr double MC_ARB_DIV = 4.0; - constexpr u32 MC_ARB_SFA = 2; - - table->burst_mc_regs.mc_emem_arb_cfg = table->rate_khz / (33.3 * 1000) / MC_ARB_DIV; - table->burst_mc_regs.mc_emem_arb_timing_rcd = CEIL(GET_CYCLE_CEIL(tRCD) / MC_ARB_DIV) - 2; - table->burst_mc_regs.mc_emem_arb_timing_rp = CEIL(GET_CYCLE_CEIL(tRPpb) / MC_ARB_DIV) - 1; - table->burst_mc_regs.mc_emem_arb_timing_rc = CEIL(GET_CYCLE_CEIL(tRC) / MC_ARB_DIV) - 1; - table->burst_mc_regs.mc_emem_arb_timing_ras = CEIL(GET_CYCLE_CEIL(tRAS) / MC_ARB_DIV) - 2; - table->burst_mc_regs.mc_emem_arb_timing_faw = CEIL(GET_CYCLE_CEIL(tFAW) / MC_ARB_DIV) - 1; - table->burst_mc_regs.mc_emem_arb_timing_rrd = CEIL(GET_CYCLE_CEIL(tRRD) / MC_ARB_DIV) - 1; - table->burst_mc_regs.mc_emem_arb_timing_rfcpb = CEIL(GET_CYCLE_CEIL(tRFCpb) / MC_ARB_DIV) - 1; - table->burst_mc_regs.mc_emem_arb_timing_rap2pre = CEIL(tR2P / MC_ARB_DIV); - table->burst_mc_regs.mc_emem_arb_timing_wap2pre = CEIL(tW2P / MC_ARB_DIV) + MC_ARB_SFA; - - /* Two consecutive reads between two different dram modules. */ - /* Only above 1 for 8gb ram. */ - if (table->burst_mc_regs.mc_emem_arb_timing_r2r > 1) { - table->burst_mc_regs.mc_emem_arb_timing_r2r = CEIL(table->burst_regs.emc_rext / 4) - 1 + MC_ARB_SFA; - } - - /* Same as r2r but for write. */ - if (table->burst_mc_regs.mc_emem_arb_timing_w2w > 1) { - table->burst_mc_regs.mc_emem_arb_timing_w2w = CEIL(table->burst_regs.emc_wext / MC_ARB_DIV) - 1 + MC_ARB_SFA; - } - - table->burst_mc_regs.mc_emem_arb_timing_r2w = CEIL(tR2W / MC_ARB_DIV) - 1 + MC_ARB_SFA; - table->burst_mc_regs.mc_emem_arb_timing_w2r = CEIL(tW2R / MC_ARB_DIV) - 1 + MC_ARB_SFA; - - u32 da_turns = 0; - da_turns |= u8(table->burst_mc_regs.mc_emem_arb_timing_r2w / 2) << 16; - da_turns |= u8(table->burst_mc_regs.mc_emem_arb_timing_w2r / 2) << 24; - table->burst_mc_regs.mc_emem_arb_da_turns = da_turns; - - u32 da_covers = 0; - u8 r_cover = (table->burst_mc_regs.mc_emem_arb_timing_rap2pre + table->burst_mc_regs.mc_emem_arb_timing_rp + table->burst_mc_regs.mc_emem_arb_timing_rcd) / 2; - u8 w_cover = (table->burst_mc_regs.mc_emem_arb_timing_wap2pre + table->burst_mc_regs.mc_emem_arb_timing_rp + table->burst_mc_regs.mc_emem_arb_timing_rcd) / 2; - da_covers |= (table->burst_mc_regs.mc_emem_arb_timing_rc / 2); - da_covers |= (r_cover << 8); - da_covers |= (w_cover << 16); - table->burst_mc_regs.mc_emem_arb_da_covers = da_covers; - - constexpr u32 AtomsPerDvfsPulse = 0x7; - constexpr u32 McEmcSameFreq = 0x0; - const u32 expiringSoonSlackThreshold = [&] { - switch (table->rate_khz) { - case 2966000: - case 3100000: - case 3133000: - case 3200000: - return 0x12u; - default: - return 0x13u; - } - }(); - - const u32 priorityInversionIsoThreshold = GET_CYCLE_CEIL(7.5); - constexpr u32 EmcReqB2bXfer = 0x0; - const u32 priorityInversionThreshold = GET_CYCLE_CEIL(22.5); - const u32 bc2aaHoldoffThreshold = table->burst_mc_regs.mc_emem_arb_timing_rc + 1; - - const u32 mc_emem_arb_misc0 = (AtomsPerDvfsPulse << 28) | (McEmcSameFreq << 27) | (expiringSoonSlackThreshold << 21) | (priorityInversionIsoThreshold << 16) | (EmcReqB2bXfer << 15) | (priorityInversionThreshold << 8) | (bc2aaHoldoffThreshold << 0); - table->burst_mc_regs.mc_emem_arb_misc0 = mc_emem_arb_misc0; - - table->la_scale_regs.mc_mll_mpcorer_ptsa_rate = 0x115; - - if (table->rate_khz >= 2133000) { - table->la_scale_regs.mc_ftop_ptsa_rate = 0x1F; - } else { - table->la_scale_regs.mc_ftop_ptsa_rate = 0x1B; - } - - table->la_scale_regs.mc_ptsa_grant_decrement = 0x17ff; - - constexpr u32 MaskHigh = 0xFF00FFFF; - constexpr u32 Mask2 = 0xFFFFFF00; - constexpr u32 Mask3 = 0xFF00FF00; - - const u32 allowance1 = static_cast(0x32000 / (table->rate_khz / 1000)) & 0xFF; - const u32 allowance2 = static_cast(0x9C40 / (table->rate_khz / 1000)) & 0xFF; - const u32 allowance3 = static_cast(0xB540 / (table->rate_khz / 1000)) & 0xFF; - const u32 allowance4 = static_cast(0x9600 / (table->rate_khz / 1000)) & 0xFF; - const u32 allowance5 = static_cast(0x8980 / (table->rate_khz / 1000)) & 0xFF; - - table->la_scale_regs.mc_latency_allowance_xusb_0 = (table->la_scale_regs.mc_latency_allowance_xusb_0 & MaskHigh) | (allowance1 << 16); - table->la_scale_regs.mc_latency_allowance_xusb_1 = (table->la_scale_regs.mc_latency_allowance_xusb_1 & MaskHigh) | (allowance1 << 16); - table->la_scale_regs.mc_latency_allowance_tsec_0 = (table->la_scale_regs.mc_latency_allowance_tsec_0 & MaskHigh) | (allowance1 << 16); - table->la_scale_regs.mc_latency_allowance_sdmmcaa_0 = (table->la_scale_regs.mc_latency_allowance_sdmmcaa_0 & MaskHigh) | (allowance1 << 16); - table->la_scale_regs.mc_latency_allowance_sdmmcab_0 = (table->la_scale_regs.mc_latency_allowance_sdmmcab_0 & MaskHigh) | (allowance1 << 16); - table->la_scale_regs.mc_latency_allowance_sdmmc_0 = (table->la_scale_regs.mc_latency_allowance_sdmmc_0 & MaskHigh) | (allowance1 << 16); - table->la_scale_regs.mc_latency_allowance_sdmmca_0 = (table->la_scale_regs.mc_latency_allowance_sdmmca_0 & MaskHigh) | (allowance1 << 16); - table->la_scale_regs.mc_latency_allowance_ppcs_1 = (table->la_scale_regs.mc_latency_allowance_ppcs_1 & MaskHigh) | (allowance1 << 16); - table->la_scale_regs.mc_latency_allowance_nvdec_0 = (table->la_scale_regs.mc_latency_allowance_nvdec_0 & MaskHigh) | (allowance1 << 16); - table->la_scale_regs.mc_latency_allowance_mpcore_0 = (table->la_scale_regs.mc_latency_allowance_mpcore_0 & MaskHigh) | (allowance1 << 16); - table->la_scale_regs.mc_latency_allowance_avpc_0 = (table->la_scale_regs.mc_latency_allowance_avpc_0 & MaskHigh) | (allowance1 << 16); - table->la_scale_regs.mc_latency_allowance_isp2_1 = allowance1 | (table->la_scale_regs.mc_latency_allowance_isp2_1 & Mask3) | (allowance1 << 16); - table->la_scale_regs.mc_latency_allowance_gpu_0 = allowance2 | (table->la_scale_regs.mc_latency_allowance_gpu_0 & Mask3) | (allowance1 << 16); - table->la_scale_regs.mc_latency_allowance_gpu2_0 = allowance2 | (table->la_scale_regs.mc_latency_allowance_gpu2_0 & Mask3) | (allowance1 << 16); - table->la_scale_regs.mc_latency_allowance_vic_0 = allowance3 | (table->la_scale_regs.mc_latency_allowance_vic_0 & Mask3) | (allowance1 << 16); - table->la_scale_regs.mc_latency_allowance_nvenc_0 = allowance4 | (table->la_scale_regs.mc_latency_allowance_nvenc_0 & Mask3) | (allowance1 << 16); - table->la_scale_regs.mc_latency_allowance_hc_0 = (table->la_scale_regs.mc_latency_allowance_hc_0 & Mask2) | allowance5; - table->la_scale_regs.mc_latency_allowance_hc_1 = (table->la_scale_regs.mc_latency_allowance_hc_1 & Mask2) | allowance1; - table->la_scale_regs.mc_latency_allowance_vi2_0 = (table->la_scale_regs.mc_latency_allowance_vi2_0 & Mask2) | allowance1; - - table->dram_timings.t_rp = tRP_values[0]; - const u32 tRFCabStock = tRFC_values[0] * 2; - table->dram_timings.t_rfc = tRFCabStock; - - table->dram_timings.rl = RL; - table->emc_mrw2 = (table->emc_mrw2 & ~0xFFu) | static_cast(mrw2); - table->emc_mrw = (table->emc_mrw & ~0x70u) | 0x40; /* nWR */ - table->emc_cfg_2 = 0x11083D; - } - - void MemMtcPllmbDivisor(MarikoMtcTable *table) { - constexpr u32 PllOscInKHz = 38400; - constexpr u32 PllOscHalfKHz = 19200; - - double target_freq_d = static_cast(table->rate_khz); - - s32 divm_candidate_half = static_cast(table->rate_khz / PllOscHalfKHz); - - bool remainder_check = (table->rate_khz - PllOscInKHz * (table->rate_khz / PllOscInKHz)) > (table->rate_khz - PllOscHalfKHz * divm_candidate_half) && static_cast(((target_freq_d / PllOscHalfKHz - divm_candidate_half - 0.5) * 8192.0)) != 0; - - u32 divm_final = remainder_check + 1; - table->pllmb_divm = divm_final; - - double div_step_d = static_cast(PllOscInKHz) / divm_final; - s32 divn_integer = static_cast(table->rate_khz / div_step_d); - table->pllmb_divn = divn_integer; - - u32 divn_fraction = static_cast((target_freq_d / div_step_d - divn_integer - 0.5) * 8192.0); - - u32 actual_freq_khz = static_cast((divn_integer + 0.5 + divn_fraction * 0.000122070312) * div_step_d); - - if (table->rate_khz - 2366001 < 133999) { - s32 divn_fraction_ssc = static_cast((actual_freq_khz * 0.997 / div_step_d - divn_integer - 0.5) * 8192.0); - - double delta_scaled = (0.3 / div_step_d + 0.3 / div_step_d) * (divn_fraction - divn_fraction_ssc); - s32 delta_int = static_cast(delta_scaled); - double delta_frac = delta_scaled - delta_int; - - u32 setup_value = 0; - if (delta_frac <= 0.5) { - double round_val = (delta_int + ROUND(delta_frac + delta_frac)) ? 0.5 : 0.0; - setup_value = ROUND(delta_frac + delta_frac) ? static_cast(round_val + round_val) | 0x1000 : static_cast(round_val); - } else { - s32 frac_doubled = ROUND(delta_frac - 0.5 + delta_frac - 0.5); - double round_val = 1.0; - setup_value = frac_doubled ? static_cast(round_val) : static_cast(round_val + round_val) | 0x1000; - } - - u32 ctrl1 = static_cast(divn_fraction_ssc) | (static_cast(divn_fraction) << 16); - u32 ctrl2 = static_cast(divn_fraction) | (static_cast(setup_value) << 16); - - table->pllm_ss_ctrl1 = ctrl1; - table->pllm_ss_ctrl2 = ctrl2; - table->pllmb_ss_ctrl1 = ctrl1; - table->pllmb_ss_ctrl2 = ctrl2; - } else { - table->pllm_ss_cfg &= 0xBFFFFFFF; - table->pllmb_ss_cfg &= 0xBFFFFFFF; - - u64 pair = (static_cast(divn_fraction) << 32) | static_cast(table->rate_khz); - u32 pll_misc = (table->pllm_ss_ctrl2 & 0xFFFF0000) | static_cast((pair - actual_freq_khz) >> 32); - - table->pllm_ss_ctrl2 = pll_misc; - table->pllmb_ss_ctrl2 = pll_misc; - } - } - - namespace { - std::vector newEmcList; - u32 *nsoStart; - size_t g_nso_size = 0; - uintptr_t g_cave_cursor = 0; - } - - 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; - } - - #if HOC_UART_LOG - /* Redirect pcv's NvLog() calls to UART */ - Result NvLogUartRedirect(u32 *ptr) { - const uintptr_t mapped_nso = reinterpret_cast(nsoStart); - const size_t nso_size = g_nso_size; - const uintptr_t textEnd = g_pcv_cave; /* .text ends where the cave begins */ - const uintptr_t vsnprintf_addr = reinterpret_cast(ptr); - - /* NvLog via the VDD_SOC log */ - static const char Fmt[] = "%s(%s): DVFS request VDD_SOC %d mV\n"; - constexpr size_t FmtLen = sizeof(Fmt) - 1; - uintptr_t strAddr = 0; - { - const char *hay = reinterpret_cast(mapped_nso); - for (size_t i = 0; i + FmtLen <= nso_size; ++i) { - if (std::memcmp(hay + i, Fmt, FmtLen) == 0) { strAddr = mapped_nso + i; break; } - } - } - if (strAddr == 0) { - LOGGING("NvLogRedirect: fmt string not found (vsnprintf@+%lx)", vsnprintf_addr - mapped_nso); - R_THROW(ldr::ResultInvalidNvLogRedirect()); - } - - uintptr_t nvlog_addr = 0; - for (u32 *p = nsoStart; reinterpret_cast(p + 2) <= textEnd; ++p) { - const uintptr_t pc = reinterpret_cast(p); - if (!AsmIsAdrp(p[0])) { - continue; - } - const uintptr_t adrpPage = (pc & ~static_cast(0xFFFu)) + static_cast(AsmAdrpPageOffset(p[0])); - if (adrpPage != (strAddr & ~static_cast(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))) { - 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 (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()); - } - - const uintptr_t helper = CaveReserve(40); - if (helper == 0) { - LOGGING("NvLogRedirect: cave unavailable (cave=%lx size=%lx)", - static_cast(g_pcv_cave), static_cast(g_pcv_cave_size)); - R_THROW(ldr::ResultInvalidNvLogRedirect()); - } - u32 *t = reinterpret_cast(helper); - size_t n = 0; - auto emit = [&](u32 ins) { t[n] = ins; ++n; }; - - 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); - - /* 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; - if (HOC_PCV_NVLOG_PATCH) { - for (u32 *p = nsoStart; reinterpret_cast(p + 1) <= textEnd; ++p) { - if (!AsmIsBl(*p)) { - continue; - } - const uintptr_t pc = reinterpret_cast(p); - if (AsmBranchTarget(*p, pc) != nvlog_addr) { - continue; - } - bool isFmtCall = false; - for (u32 j = 1; j <= 8 && reinterpret_cast(p - j) >= reinterpret_cast(nsoStart); ++j) { - const u32 w = *(p - j); - if (AsmIsAdrp(w) && asm_get_rd(w) == 0) { /* adrp x0, */ - const uintptr_t wpc = pc - j * 4; - const uintptr_t tgtPage = (wpc & ~static_cast(0xFFFu)) + static_cast(AsmAdrpPageOffset(w)); - if (tgtPage >= (roStart & ~static_cast(0xFFFu))) { isFmtCall = true; break; } - } - } - if (isFmtCall) { - PATCH_OFFSET(p, AsmMakeBl(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 - - /* 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) */ - u32 *call = ptr + 3; /* the bl to relocate */ - const uintptr_t realFn = AsmBranchTarget(*call, reinterpret_cast(call)); - - /* 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()); - - const uintptr_t tramp = CaveReserve(9); - R_UNLESS(tramp != 0, ldr::ResultInvalidBusFreqReloc()); - - const uintptr_t region = g_pcv_scratch + HocBusFreqBufOffset; /* [0]=counter, +0x10 + i*0x400 = bufs */ - u32 *t = reinterpret_cast(tramp); - size_t n = 0; - auto emit = [&](u32 ins) { t[n] = ins; ++n; }; - emit(AsmMakeAdrp(tramp + n * 4, region, s[0])); /* adrp s0, */ - 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 */ - - PATCH_OFFSET(call, AsmMakeBl(reinterpret_cast(call), tramp)); - const uintptr_t base = reinterpret_cast(nsoStart); - (void) base; - LOGGING("BusFreqReloc: call@+%lx -> tramp@+%lx realfn@+%lx (bus=x%u buf=x%u off=0x%x scratch=x%u,x%u,x%u)", - reinterpret_cast(call) - base, tramp - base, realFn - base, busReg, bufReg, bufOff, s[0], s[1], s[2]); - R_SUCCEED(); - } - - #if HOC_UART_LOG - /* Force GetEffectiveVerbosityLevel to return a non-zero level so all NvLog runs. */ - Result ForceVerbosity(u32 *ptr) { - PATCH_OFFSET(&ptr[0], AsmMakeMovzW(0, static_cast(HOC_PCV_FORCE_VERBOSITY))); /* movz w0,#level */ - PATCH_OFFSET(&ptr[1], RetIns); /* ret */ - 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; - - 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] */ - 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()); - - /* 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 srcBase = AsmGetRn(*voltBase); /* rail ptr at +0x20 */ - const u32 wBase = asm_get_rd(*voltBase); /* base reg */ - const u32 wIdx = AsmGetRm(*voltBase); /* 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 */ - - /* rail+0x18 as the socMinLut pointer. */ - PATCH_OFFSET(ptr - 3, NopIns); - PATCH_OFFSET(ptr - 2, AsmMakeAddImm64(lutReg, railReg, 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); - break; - } - } - R_SUCCEED(); - } - - Result EmcDvfsCountLimit(u32 *ptr) { - R_UNLESS(EmcDvfsCountPatternFn(ptr), ldr::ResultInvalidEmcDvfsCount()); - - /* cmp w?,#0x21 -> cmp w?,#EmcDvfsTableEntryCount */ - PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast(EmcDvfsTableEntryCount))); - R_SUCCEED(); - } - - Result EmcRateListLimit(u32 *ptr) { - /* ptr = cmp w?,#0x20 ; ptr[1] = csel w?,w?,w?,lt (w? = min(maxCount, 32)) ; ptr[2] = bl */ - 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,#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, AsmSubsSetImm12(*ptr, static_cast(EmcDvfsTableEntryCount))); /* cmp w?,#64 */ - PATCH_OFFSET(movPtr, asm_set_imm16(*movPtr, static_cast(EmcDvfsTableEntryCount))); /* movz w?,#64 */ - R_SUCCEED(); - } - - void MtcGenerateJedecTable() { - const u32 jedecFreqs[] = { 1866000, 1996000, 2133000, 2400000, 2666000, 2933000, 3200000 }; - constexpr u32 JedecFreqCount = std::size(jedecFreqs); - - for (u32 i = 0; i < JedecFreqCount; ++i) { - if (jedecFreqs[i] <= C.marikoEmcMaxClock) { - newEmcList.push_back(jedecFreqs[i]); - } else { - break; - } - } - - if (newEmcList.back() != C.marikoEmcMaxClock) { - newEmcList.push_back(static_cast(C.marikoEmcMaxClock)); - } - - newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit)); - } - - void MtcGenerate133StepTable() { - const u32 stepFreqs133[] = { 1733000, 1866000, 2000000, 2133000, 2266000, 2400000, 2533000, 2666000, 2800000, 2933000, 3066000, 3200000, 3333000, 3466000, }; // Avoid rounding issues - constexpr u32 StepFreqs133Size = std::size(stepFreqs133); - - for (u32 i = 0; i < StepFreqs133Size; ++i) { - if (stepFreqs133[i] <= C.marikoEmcMaxClock) { - newEmcList.push_back(stepFreqs133[i]); - } else { - break; - } - } - - if (newEmcList.back() != C.marikoEmcMaxClock) { - newEmcList.push_back(static_cast(C.marikoEmcMaxClock)); - } - - newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit)); - } - - void MtcGenerate33StepTable() { - /* ~33.33MHz but rounded*/ - const u32 stepFreqs33[] = { - 1633000, 1666000, 1700000, 1733000, 1766000, 1800000, 1833000, 1866000, 1900000, 1933000, - 1966000, 2000000, 2033000, 2066000, 2100000, 2133000, 2166000, 2200000, 2233000, 2266000, - 2300000, 2333000, 2366000, 2400000, 2433000, 2466000, 2500000, 2533000, 2566000, 2600000, - 2633000, 2666000, 2700000, 2733000, 2766000, 2800000, 2833000, 2866000, 2900000, 2933000, - 2966000, 3000000, 3033000, 3066000, 3100000, 3133000, 3166000, 3200000, 3233000, 3266000, - 3300000, 3333000, 3366000, 3400000, 3433000, 3466000, 3500000, - }; - constexpr u32 StepFreqs33Size = std::size(stepFreqs33); - - for (u32 i = 0; i < StepFreqs33Size; ++i) { - if (stepFreqs33[i] <= C.marikoEmcMaxClock) { - newEmcList.push_back(stepFreqs33[i]); - } else { - break; - } - } - - if (newEmcList.back() != C.marikoEmcMaxClock) { - newEmcList.push_back(static_cast(C.marikoEmcMaxClock)); - } - - newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit)); - } - - void MtcGenerateFreqTables() { - newEmcList.clear(); - newEmcList.reserve(EmcDvfsTableEntryCount); - newEmcList.insert(newEmcList.end(), std::begin(EmcListDefault), std::end(EmcListDefault)); - - if (C.marikoEmcMaxClock <= EmcClkOSLimit) { - return; - } - - u32 stepRate = 0; - switch (C.stepMode) { - case StepMode_33MHz: - MtcGenerate33StepTable(); - return; - case StepMode_66MHz: - stepRate = 66667; - break; - case StepMode_100MHz: - stepRate = 100000; - break; - case StepMode_Jedec: - MtcGenerateJedecTable(); - return; - case StepMode_133MHz: - MtcGenerate133StepTable(); - return; - default: - stepRate = 66667; - break; - } - - constexpr u32 RoundHz = 1000; - for (u32 stepIndex = 1;; ++stepIndex) { - u32 newFreq = EmcClkOSLimit + stepIndex * stepRate; - newFreq = (newFreq / RoundHz) * RoundHz; - if (newFreq > C.marikoEmcMaxClock) { - if (newEmcList.back() != C.marikoEmcMaxClock) { - newEmcList.push_back(static_cast(C.marikoEmcMaxClock)); - } - break; - } - newEmcList.push_back(newFreq); - } - - newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit)); - } - - Result VerifyMtcTable(MarikoMtcTable *tableStart, u32 expectedFreq) { - R_UNLESS(tableStart->rate_khz == expectedFreq, ldr::ResultInvalidMtcTable()); - R_UNLESS(tableStart->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable()); - - R_SUCCEED(); - } - - Result MtcValidateAllTables(MarikoMtcTable *tableStart, const u32 *validationList, u32 tableCount) { - for (u32 i = 0; i < tableCount; ++i) { - R_TRY(VerifyMtcTable(&tableStart[i], validationList[i])); - } - - R_SUCCEED(); - } - - DramId GetDramId() { - u64 id64; - splGetConfig(SplConfigItem_DramId, &id64); - return static_cast(id64); - } - - MtcTableIndex GetMtcDramIndex(DramId dramId) { - for (u32 i = 0; i < std::size(mtcIndexTable); ++i) { - if (mtcIndexTable[i].dramId == dramId) { - return mtcIndexTable[i].index; - } - } - - return MtcTableIndex_Invalid; - } - - NORETURN void AbortInvalidMtc(const char *crashMsg) { - panic::SmcError(panic::Emc); - CRASH(crashMsg); - } - - u32 GetMtcOffset(MtcTableIndex index) { - if (index < T210b0SdevEmcDvfsTableS4gb03) { - return index * mariko::MtcFullTableSize; - } - - /* There are 2 erista mtc tables between T210b0SdevEmcDvfsTableS4gb01 and T210b0SdevEmcDvfsTableS4gb03, so we have to do this adjustment. */ - return mariko::MtcFullTableSize * index + (2 * erista::MtcFullTableSize); - } - - void PrepareMtcMemoryRegion(u8 *firstTable, MarikoMtcTable *usedTable) { - memmove(firstTable, usedTable, mariko::MtcFullTableSize); - - /* Clear all other tables. */ - /* 1 erista table is excluded because it's always before firstTable. */ - /* We also exclude the used table obviously. */ - constexpr size_t RemainingRegionSize = (mariko::MtcFullTableSize) * (mariko::MtcFullTableCount - 1) + (erista::MtcFullTableSize * (erista::MtcFullTableCount - 1)); - memset(firstTable + mariko::MtcFullTableSize, 0, RemainingRegionSize); - } - - void MtcExtendTables(MarikoMtcTable *table) { - for (u32 i = mariko::MtcTableCountDefault; i < newEmcList.size(); ++i) { - std::memcpy(&table[i], &table[i - 1], sizeof(MarikoMtcTable)); - table[i].rate_khz = newEmcList[i]; - } - } - - Result MemFreqMtcTable(u32 *ptr) { - static const DramId dramId = [] { - DramId id = GetDramId(); - return id; - }(); - - static const MtcTableIndex mtcIndex = [] { - MtcTableIndex idx = GetMtcDramIndex(dramId); - /* If for some reason this happens, there is no chance of recovering this. */ - if (idx == MtcTableIndex_Invalid) { - AbortInvalidMtc("Invalid dramId"); - } - return idx; - }(); - - /* Offset to dram id specific mtc table. */ - static const u32 mtcOffset = GetMtcOffset(mtcIndex); - - /* Offset from 1600MHz pointer to 204Mhz table start. */ - constexpr u32 StartAdjustment = offsetof(MarikoMtcTable, rate_khz) + sizeof(MarikoMtcTable) * (mariko::MtcTableCountDefault - 1); - u8 *startPtr = reinterpret_cast(ptr) - StartAdjustment; - MarikoMtcTable *table = reinterpret_cast(startPtr + mtcOffset); - R_TRY(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault)); - - PrepareMtcMemoryRegion(startPtr, table); - table = reinterpret_cast(startPtr); - - if (R_FAILED(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault))) { - AbortInvalidMtc("Failed mtc validation"); - } - - if (C.marikoEmcMaxClock <= EmcClkOSLimit) { - R_SKIP(); - } - - MtcExtendTables(table); - - if (R_FAILED(MtcValidateAllTables(table, newEmcList.data(), newEmcList.size()))) { - AbortInvalidMtc("Failed mtc validation"); - } - - for (u32 i = mariko::MtcTableCountDefault; i < newEmcList.size(); ++i) { - MemMtcTableAutoAdjust(&table[i]); - MemMtcPllmbDivisor(&table[i]); - } - - R_SUCCEED(); - } - - Result MemFreqDvbTable(u32 *ptr) { - DvbEntry *default_end = reinterpret_cast(ptr); - DvbEntry *new_start = default_end + 1; - - // Validate existing table - void *mem_dvb_table_head = reinterpret_cast(new_start) - sizeof(EmcDvbTableDefault); - bool validated = std::memcmp(mem_dvb_table_head, EmcDvbTableDefault, sizeof(EmcDvbTableDefault)) == 0; - R_UNLESS(validated, ldr::ResultInvalidDvbTable()); - - if (C.marikoEmcMaxClock <= EmcClkOSLimit) { - R_SKIP(); - } - - s32 max0 = 1050; - s32 max1 = 1025; - s32 max2 = 1000; - - if (C.marikoSocVmax && C.marikoSocVmax > 1000) { - max0 = C.marikoSocVmax; - max1 = C.marikoSocVmax; - max2 = C.marikoSocVmax; - } - - constexpr s32 MinVolt = 637; - - auto ClampVolt = [&](s32 value, s32 max, s32 voltAdd) { - return std::clamp(value + voltAdd, MinVolt, max); - }; - - auto DvbVolt = [&](s32 zero, s32 one, s32 two, u32 index) { - const s32 overrideVoltage = C.marikoSocVoltArray[index]; - s32 voltAdd = 25 * C.emcDvbShift; - - if (overrideVoltage) { - zero = one = two = overrideVoltage; - voltAdd = 0; - } - - return std::array{ - ClampVolt(zero, max0, voltAdd), - ClampVolt(one, max1, voltAdd), - ClampVolt(two, max2, voltAdd) - }; - }; - - #define DVB(v) \ - static_cast((v)[0]), \ - static_cast((v)[1]), \ - static_cast((v)[2]) - - #define DVB_OC(one, zero, two, idx) \ - DVB(DvbVolt(one, zero, two, idx)) - - DvbEntry emcDvbOcTableBrackets[] = { - { 204000, { 637, 637, 637, }, }, - { 1331200, { 650, 637, 637, }, }, - { 1600000, { 675, 650, 637, }, }, - { 1866000, { DVB_OC( 700, 675, 650, 0) }, }, - { 2000000, { DVB_OC( 712, 687, 662, 1) }, }, - { 2133000, { DVB_OC( 725, 700, 675, 2) }, }, - { 2200000, { DVB_OC( 737, 712, 687, 3) }, }, - { 2266000, { DVB_OC( 750, 725, 700, 4) }, }, - { 2333000, { DVB_OC( 762, 737, 712, 5) }, }, - { 2400000, { DVB_OC( 775, 750, 725, 6) }, }, - { 2433000, { DVB_OC( 787, 762, 737, 7) }, }, - { 2466000, { DVB_OC( 800, 775, 750, 8) }, }, - { 2533000, { DVB_OC( 812, 787, 762, 9) }, }, - { 2566000, { DVB_OC( 825, 800, 775, 10) }, }, - { 2600000, { DVB_OC( 837, 812, 787, 11) }, }, - { 2666000, { DVB_OC( 850, 825, 800, 12) }, }, - { 2700000, { DVB_OC( 875, 850, 825, 13) }, }, - { 2733000, { DVB_OC( 887, 862, 837, 14) }, }, - { 2766000, { DVB_OC( 912, 887, 862, 15) }, }, - { 2800000, { DVB_OC( 925, 900, 875, 16) }, }, - { 2833000, { DVB_OC( 937, 912, 887, 17) }, }, - { 2900000, { DVB_OC( 950, 925, 900, 18) }, }, - { 2933000, { DVB_OC( 962, 937, 912, 19) }, }, - { 3000000, { DVB_OC( 975, 950, 925, 20) }, }, - { 3033000, { DVB_OC( 987, 962, 937, 21) }, }, - { 3100000, { DVB_OC(1000, 975, 950, 22) }, }, - { 3133000, { DVB_OC(1025, 1000, 975, 23) }, }, - { 3166000, { DVB_OC(1037, 1012, 987, 24) }, }, - { 3200000, { DVB_OC(1050, 1025, 1000, 25) }, }, - { 3266000, { DVB_OC(1075, 1050, 1025, 26) }, }, - { 3333000, { DVB_OC(1100, 1075, 1050, 27) }, }, - { ~0u, { }, }, - }; - #undef DVB - #undef DVB_OC - - const size_t dvbCount = std::min(newEmcList.size(), DvbTableCapacity); - DvbEntry emcDvbTableOc[DvbTableCapacity] = {}; - - u32 bracketIndex = 0; - for (size_t i = 0; i < dvbCount; ++i) { - const u32 freq = (i == dvbCount - 1) ? static_cast(newEmcList.back()) : newEmcList[i]; - while (freq >= emcDvbOcTableBrackets[bracketIndex + 1].freq) { - ++bracketIndex; - } - - emcDvbTableOc[i].freq = freq; - std::memcpy(emcDvbTableOc[i].volt, emcDvbOcTableBrackets[bracketIndex].volt, sizeof(emcDvbTableOc[i].volt)); - } - - /* Clear the entire 32-entry region */ - std::memset(mem_dvb_table_head, 0, DvbTableCapacity * sizeof(DvbEntry)); - std::memcpy(mem_dvb_table_head, emcDvbTableOc, dvbCount * sizeof(DvbEntry)); - - R_SUCCEED(); - } - - Result MemFreqMax(u32 *ptr) { - if (C.marikoEmcMaxClock <= EmcClkOSLimit) { - R_SKIP(); - } - - PATCH_OFFSET(ptr, C.marikoEmcMaxClock); - R_SUCCEED(); - } - - Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) { - struct { - u8 reg; - u8 val; - } __attribute__((packed)) cmd; - - I2cSession _session; - R_TRY(i2cOpenSession(&_session, dev)); - - cmd.reg = reg; - cmd.val = val; - Result res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All); - i2csessionClose(&_session); - return res; - } - - Result EmcVddqVolt(u32 *ptr) { - regulator *entry = reinterpret_cast(reinterpret_cast(ptr) - offsetof(regulator, type_2_3.default_uv)); - - constexpr u32 uv_step = 5'000; - constexpr u32 uv_min = 250'000; - - auto validator = [entry]() { - R_UNLESS(entry->id == 2, ldr::ResultInvalidRegulatorEntry()); - R_UNLESS(entry->type == 3, ldr::ResultInvalidRegulatorEntry()); - R_UNLESS(entry->type_2_3.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry()); - R_UNLESS(entry->type_2_3.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry()); - R_SUCCEED(); - }; - - R_TRY(validator()); - - u32 emc_uv = C.marikoEmcVddqVolt; - - if (!emc_uv) { - R_SKIP(); - } - - if (emc_uv % uv_step) { - emc_uv = (emc_uv + uv_step - 1) / uv_step * uv_step; // rounding - } - - PATCH_OFFSET(ptr, emc_uv); - - i2cInitialize(); - Result resultI2C = I2cSet_U8(I2cDevice_Max77812_2, 0x25, (emc_uv - uv_min) / uv_step); - i2cExit(); - - return resultI2C; - } - - Result MemMtcTableAsm(u32 *ptr) { - constexpr u32 AddpOffset = 1; - constexpr u32 BrOffset = 12; - constexpr u32 MovOffset = 10; - - /* Ensure we don't dereference memory before nso start. */ - R_UNLESS(ptr - BrOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern()); - - u32 adrp = *(ptr - AddpOffset); - R_UNLESS(AsmCompareAdrpNoImm(adrp, MtcAdrpAsm), 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()); - - /* Pray this does not break either. */ - u32 mov = *(ptr - MovOffset); - R_UNLESS(asm_compare_no_rd(mov, MtcMovAsm), ldr::ResultInvalidMtcTablePattern()); - - u8 movRd = asm_get_rd(mov); - u32 movCountPatch = asm_set_rd(asm_set_imm16(MtcMovAsm, newEmcList.size()), movRd); - - PATCH_OFFSET(ptr - BrOffset, NopIns); - PATCH_OFFSET(ptr - MovOffset, movCountPatch); - - R_SUCCEED(); - } - - Result GetSocSpeedo(u32 &socSpeedo) { - constexpr u64 FusePhysicalAddress = 0x7000F000; - u64 virtualAddress = 0; - constexpr u64 Size = 0x1000; - - u64 outSize; - /* TODO: use svc::QueryMemoryMapping instead. */ - R_TRY(svcQueryMemoryMapping(&virtualAddress, &outSize, FusePhysicalAddress, Size)); - - constexpr u32 FuseOffset = 2048; - constexpr u32 SocSpeedoOffset = 308; - socSpeedo = *reinterpret_cast(virtualAddress + FuseOffset + SocSpeedoOffset); - - R_SUCCEED(); - } - - u32 GetSocProcessId(u32 socSpeedo) { - if (socSpeedo <= 1597) { - return 0; - } - - if (socSpeedo <= 1708) { - return 1; - } - - /* >= 1709. */ - return 2; - } - - Result SocVoltAsm(u32 *compareSpeedos) { - constexpr u32 VoltageScanLimit = 10; - /* Might actually be speedo id. */ - u32 *writeProcessId = ScanAssembly(compareSpeedos, VoltageScanLimit, SocVoltWriteProcessIdAsm, asm_compare_no_rd); - R_UNLESS(writeProcessId != nullptr, ldr::ResultInvalidSocVoltPattern()); - u8 writeProcessIdRd = asm_get_rd(*writeProcessId); - - /* This writes 1050mV. */ - u32 *writeVoltage = ScanAssembly(writeProcessId, VoltageScanLimit, SocVoltWriteVoltageAsm, asm_compare_no_rd); - R_UNLESS(writeVoltage != nullptr, ldr::ResultInvalidSocVoltPattern()); - u8 writeVoltageRd = asm_get_rd(*writeVoltage); - - /* 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); - R_UNLESS(selectVoltage != nullptr, ldr::ResultInvalidSocVoltPattern()); - /* Todo: check rm and rn? */ - u8 selectVoltageRd = asm_get_rd(*selectVoltage); - - /* 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); - R_UNLESS(multiplier != nullptr, ldr::ResultInvalidSocVoltPattern()); - u8 multiplierRn = AsmGetMullRn(*multiplier); - u8 multiplierRm = AsmGetMullRm(*multiplier); - /* One of the two registers has to be rdCsel. */ - R_UNLESS((multiplierRn == selectVoltageRd) || (multiplierRm == selectVoltageRd), ldr::ResultInvalidSocVoltPattern()); - u8 multiplierRd = asm_get_rd(*multiplier); - - /* Subs instruction is then used to verify against absolute limit. */ - u32 limitValidationPattern = AsmSubsSetRn(SocVoltValidateLimitAsm, multiplierRd); - u32 *limitValidation = ScanAssembly(multiplier, VoltageScanLimit, limitValidationPattern, AsmSubsCompareNoReg); - 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()); - - 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 = AsmSubsSetImm12(*compareSpeedos, 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_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, processId), writeProcessIdRd); - PATCH_OFFSET(writeProcessId, writeProcessIdPatch); - - /* Adjust voltage limit. */ - u32 voltageLimitPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, C.marikoSocVmax), writeVoltageRd); - PATCH_OFFSET(writeVoltage, voltageLimitPatch); - - /* Branches to an abort if limits are invalid -- we patch the branch instruction with NOP. */ - PATCH_OFFSET(branchToAbort, 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 (AsmIsSubX29Imm(w) && AsmGetImm12(w) >= 0x20u) { /* sub x?,x29,#>=0x20 */ - R_THROW(ldr::ResultInvalidEmcRateList()); - } - } - - /* mov x,x2 */ - u32 descReg = 0xFFu; - for (u32 i = 1; i <= 24; ++i) { - if (AsmIsMovReg(ptr[i], 2)) { descReg = asm_get_rd(ptr[i]); 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 (AsmIsAddSpImm(w)) { /* add x?,sp,#imm12 (shift 0) */ - adds[nAdds] = ptr + i; - addImm[nAdds] = AsmGetImm12(w); - ++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], AsmMakeLdrImm64(asm_get_rd(*adds[a]), descReg, 0)); /* ldr x?,[x] */ - ++patched; - } - } - R_UNLESS(patched == 2, ldr::ResultInvalidEmcRateList()); - - /* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount) */ - PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast(EmcDvfsTableEntryCount))); /* cmp w?,#64 */ - PATCH_OFFSET(ptr + movzI, asm_set_imm16(*(ptr + movzI), static_cast(EmcDvfsTableEntryCount))); /* movz w?,#64 */ - R_SUCCEED(); - } - - void Patch(uintptr_t mapped_nso, size_t nso_size) { - nsoStart = reinterpret_cast(mapped_nso); - - g_pcv_scratch = mapped_nso + nso_size - HocPcvScratchSize; - g_nso_size = nso_size; - g_cave_cursor = g_pcv_cave; /* start the .text-cave bump allocator (0 if unavailable) */ - - MtcGenerateFreqTables(); - - u32 CpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq); - u32 GpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq); - - PatcherEntry patches[] = { - { "CPU Freq Vdd", &CpuFreqVdd, 1, nullptr, CpuClkOSLimit }, - { "CPU Freq Table", CpuFreqCvbTable, 1, nullptr, CpuCvbDefaultMaxFreq }, - { "CPU Volt DVFS", &CpuVoltDVFS, 1, nullptr, CpuVminOfficial }, - { "CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial }, - { "CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low }, - { "GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial }, - { "GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial }, - { "GPU Freq Table", GpuFreqCvbTable, 1, nullptr, GpuCvbDefaultMaxFreq }, - { "GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn }, - { "GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax }, - { "GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit }, - { "MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit }, - { "MEM Freq Dvb", &MemFreqDvbTable, 1, nullptr, EmcClkOSLimit }, - { "MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit }, - { "MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit }, - { "MEM Vddq", &EmcVddqVolt, 2, nullptr, EmcVddqDefault }, - { "MEM Vdd2", &MemVoltHandler, 2, nullptr, MemVdd2Default }, - { "MEM Table Asm", &MemMtcTableAsm, 1, &MemMtcGetGetTablePatternFn }, - { "EMC DVFS Count", &EmcDvfsCountLimit, 1, &EmcDvfsCountPatternFn }, - { "EMC SoC LUT", &EmcSocLutReloc, 1, &EmcSocLutPatternFn }, - { "EMC Rate List", &EmcRateListLimit, 0, &EmcRateListPatternFn }, - { "EMC Rate Sess", &EmcRateSessLimit, 1, &EmcRateSessPatternFn }, - { "Bus Freq Reloc", &BusFreqReloc, 1, &BusFreqRelocPatternFn }, - { "SOC Volt Asm", &SocVoltAsm, 1, &SocVoltPatternFn }, - { "SOC Volt Limit", &SocVoltLimit, 1, nullptr, SocVoltLimitOfficial }, - /* Debugging patches */ - #if HOC_UART_LOG - { "NvLog Redirect", &NvLogUartRedirect, 1, &NvLogVsnprintfPatternFn, 0, 0, true }, - { "Force Verbosity", &ForceVerbosity, 3, &ForceVerbosityPatternFn, 0, 0, true }, - #endif - }; - - for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(MarikoMtcTable); ptr += sizeof(u32)) { - u32 *ptr32 = reinterpret_cast(ptr); - for (auto &entry : patches) { - if (R_SUCCEEDED(entry.SearchAndApply(ptr32))) { - break; - } - } - } - - for (auto &entry : patches) { - LOGGING("%s Count: %zu", entry.description, entry.patched_count); - if (R_FAILED(entry.CheckResult())) { - panic::SmcError(panic::Patch); - - CRASH(entry.description); - } - } - } - -}