Merge upstream Horizon-OC main into Horizon-OC-pro.
Some checks failed
Build Horizon OC Zeus / build (push) Failing after 15s

This commit is contained in:
2026-07-28 23:45:30 +02:00
125 changed files with 3940 additions and 1636 deletions

8
.gitmodules vendored
View File

@@ -1,11 +1,3 @@
[submodule "Source/Horizon-OC-Monitor/lib/Atmosphere-libs"]
path = Source/Horizon-OC-Monitor/lib/Atmosphere-libs
url = https://git.niklascfw.de/OmniNX/Atmosphere-Pro.git
branch = boot-storage
[submodule "Source/Horizon-OC-Monitor/lib/libultrahand"]
path = Source/Horizon-OC-Monitor/lib/libultrahand
url = https://github.com/ppkantorski/libultrahand
branch = main
[submodule "Source/hoc-clk/overlay/lib/libultrahand"]
path = Source/hoc-clk/overlay/lib/libultrahand
url = https://github.com/ppkantorski/libultrahand

View File

@@ -5,3 +5,4 @@ Horizon OC Compilation Instructions
3. Clone the Horizon OC develop branch (``git clone https://github.com/Horizon-OC/Horizon-OC.git --recurse-submodules``)
4. Run ``./build.sh`` in the root directory
- If you want to compile with extensions, append ``--ext``
- Installing ``ccache`` is optional; if it is on your PATH it is used automatically to speed up rebuilds

View File

@@ -567,6 +567,11 @@ namespace ams::ldr {
out->nso_size[i] = std::max(out->nso_size[i], rw_end);
out->nso_size[i] += static_cast<size_t>(ctx.headers[i].bss_size);
/* Reserve hook arena memory past pcv's bss. */
if (g_is_pcv && i == ctx.main_nso_idx) {
out->nso_size[i] = util::AlignUp(out->nso_size[i], os::MemoryPageSize) + hoc::pcv::PcvDataArenaSize;
}
const size_t aligned_up_size = util::AlignUp(out->nso_size[i], os::MemoryPageSize) & (AutoLoadModuleSizeMax - 1);
R_UNLESS(out->nso_size[i] <= aligned_up_size, ldr::ResultInvalidNso());
R_UNLESS(aligned_up_size > 0, ldr::ResultInvalidNso());
@@ -691,6 +696,9 @@ namespace ams::ldr {
Result LoadAutoLoadModule(os::NativeHandle process_handle, fs::FileHandle file, const NsoHeader *nso_header, uintptr_t nso_address, size_t nso_size, size_t map_size) {
const bool is_zstd = (nso_header->flags & NsoHeader::Flag_UseZbicCompression) != 0;
const size_t module_size = static_cast<size_t>(nso_header->rw_dst_offset) + util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize);
const size_t arena_size = (nso_size > module_size) ? (nso_size - module_size) : 0;
/* Map and read data from file. */
{
/* Map the process memory. */
@@ -730,7 +738,15 @@ namespace ams::ldr {
/* Apply PCV and PTM patches */
if (g_is_pcv) {
hoc::pcv::Patch(map_address, nso_size);
const size_t text_end = static_cast<size_t>(nso_header->text_size);
const size_t rx_end = util::AlignUp(text_end, os::MemoryPageSize);
const size_t ro_start = static_cast<size_t>(nso_header->ro_dst_offset);
const size_t cave_end = (rx_end < ro_start) ? rx_end : ro_start;
const uintptr_t cave = map_address + text_end;
const size_t cave_size = (cave_end > text_end) ? (cave_end - text_end) : 0;
/* module_size is used rather than nso_size to exclude the extra data section. */
hoc::pcv::Patch(map_address, module_size, cave, cave_size, nso_address, arena_size ? (map_address + module_size) : 0);
}
if (g_is_ptm) {
@@ -741,7 +757,7 @@ namespace ams::ldr {
/* Set permissions. */
const size_t text_size = util::AlignUp(nso_header->text_size, os::MemoryPageSize);
const size_t ro_size = util::AlignUp(nso_header->ro_size, os::MemoryPageSize);
const size_t rw_size = util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize);
const size_t rw_size = util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize) + arena_size;
if (text_size) {
const bool prevent_code_reads = (nso_header->flags & NsoHeader::Flag_PreventCodeReads);
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->text_dst_offset, text_size, prevent_code_reads ? os::MemoryPermission_ExecuteOnly : os::MemoryPermission_ReadExecute));
@@ -770,8 +786,7 @@ namespace ams::ldr {
const bool is_zstd = (ctx.headers[i].flags & NsoHeader::Flag_UseZbicCompression) != 0;
const size_t map_size = is_zstd ? (total_end - process_info->nso_address[i]) : process_info->nso_size[i];
R_TRY(LoadAutoLoadModule(process_info->process_handle, file, ctx.headers + i,
process_info->nso_address[i], process_info->nso_size[i], map_size));
R_TRY(LoadAutoLoadModule(process_info->process_handle, file, ctx.headers + i, process_info->nso_address[i], process_info->nso_size[i], map_size));
}
/* Load arguments, if present. */
@@ -798,6 +813,12 @@ namespace ams::ldr {
}
Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) {
/* Append extra .bss for 64LUT */
/* TODO: REMOVE THIS. */
if (g_is_pcv && ctx.main_nso_idx >= 0) {
g_nso_headers[ctx.main_nso_idx].bss_size += static_cast<u32>(hoc::pcv::HocPcvScratchSize);
}
/* Get CreateProcessParameter. */
svc::CreateProcessParameter param;
R_TRY(GetCreateProcessParameter(std::addressof(param), meta, flags, resource_limit));

View File

@@ -38,11 +38,11 @@ volatile CustomizeTable C = {
.commonEmcMemVolt = 1175000, /* LPDDR4(X) JEDEC Specification */
.eristaEmcMaxClock = 1600000, /* Maximum HB-MGCH ram rating */
/* Available: 66MHz step rate, 100MHz step rate, 133MHz step rate and jedec. */
/* Available: 33MHz step rate, 66MHz step rate, 100MHz step rate, 133MHz step rate and jedec. */
/* Jedec freqs are 1333MHz, 1600MHz, 1866MHz, 2133MHz, 2400MHz, 2666MHz, 2933MHz, 3200MHz. */
.stepMode = StepMode_66MHz,
.stepMode = StepMode_33MHz,
.marikoEmcMaxClock = 2133000, /* 1866MHz @ 1866tWRL is guaranteed to work on all Mariko units */
.marikoEmcMaxClock = 2133000, /* Requires the EMC DVFS 63-entry patches (EMC DVFS Count / EMC SoC LUT). */
.marikoEmcVddqVolt = 600000,
.emcDvbShift = 0,

View File

@@ -20,8 +20,8 @@
#pragma once
#define CUST_REV 6
#define KIP_VERSION 250
#define CUST_REV 7
#define KIP_VERSION 300
#include "oc_common.hpp"
#include "pcv/pcv_common.hpp"
@@ -40,6 +40,7 @@ enum StepMode: u32 {
StepMode_100MHz = 1,
StepMode_Jedec = 2,
StepMode_133MHz = 3,
StepMode_33MHz = 4,
};
enum ReadLatency: u32 {

View File

@@ -20,36 +20,80 @@
#include <stratosphere.hpp>
#include <vapours/results/results_common.hpp>
#define LOGGING(fmt, ...) ((void)0)
#define CRASH(msg, ...) { ams::diag::AbortImpl(msg, __PRETTY_FUNCTION__, "", 0); __builtin_unreachable(); }
#ifndef HOC_UART_LOG
#define HOC_UART_LOG 0
#endif
#if HOC_UART_LOG && !(defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING))
#undef HOC_UART_LOG
#define HOC_UART_LOG 0
#endif
#define HOC_PCV_NVLOG_PATCH 1
#define HOC_PCV_FORCE_VERBOSITY 1
#include "customize.hpp"
#include "oc_log.hpp"
#define HOC_IRAM_LOG 0
#if (!HOC_UART_LOG) && (defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING))
#undef HOC_IRAM_LOG
#define HOC_IRAM_LOG 1
#endif
#if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
#if HOC_IRAM_LOG
#define LOGGING(...) Log(__VA_ARGS__)
#elif HOC_UART_LOG
#define LOGGING(fmt, ...) AMS_LOG(fmt "\n", ##__VA_ARGS__)
#endif
#else
#define LOGGING(...) ((void)0)
#endif
#define CRASH(msg, ...) { ams::diag::AbortImpl(msg, __PRETTY_FUNCTION__, "", 0); __builtin_unreachable(); }
#define PATCH_OFFSET(offset, value) \
static_assert(sizeof(__typeof__(offset)) <= sizeof(u64)); \
*(offset) = value;
namespace ams::ldr {
R_DEFINE_ERROR_RESULT(OutOfRange, 1000);
R_DEFINE_ERROR_RESULT(InvalidMemPllmEntry, 1001);
R_DEFINE_ERROR_RESULT(InvalidMtcMagic, 1002);
R_DEFINE_ERROR_RESULT(InvalidMtcTable, 1003);
R_DEFINE_ERROR_RESULT(InvalidDvbTable, 1004);
R_DEFINE_ERROR_RESULT(InvalidCpuFreqVddEntry, 1005);
R_DEFINE_ERROR_RESULT(InvalidCpuVoltDfllEntry, 1006);
R_DEFINE_ERROR_RESULT(InvalidCpuDvfs, 1007);
R_DEFINE_ERROR_RESULT(InvalidCpuMinVolt, 1008);
R_DEFINE_ERROR_RESULT(InvalidGpuDvfs, 1009);
R_DEFINE_ERROR_RESULT(InvalidGpuFreqMaxPattern, 1010);
R_DEFINE_ERROR_RESULT(InvalidGpuPllEntry, 1011);
R_DEFINE_ERROR_RESULT(InvalidRegulatorEntry, 1012);
R_DEFINE_ERROR_RESULT(UninitializedPatcher, 1013);
R_DEFINE_ERROR_RESULT(UnsuccessfulPatcher, 1014);
R_DEFINE_ERROR_RESULT(SafetyCheckFailure, 1015);
R_DEFINE_ERROR_RESULT(InvalidMtcTablePattern, 1016);
R_DEFINE_ERROR_RESULT(InvalidSocVoltPattern, 1017);
R_DEFINE_ERROR_RESULT(InvalidSocVoltLimit, 1018);
R_DEFINE_ERROR_RESULT(OutOfRange, 1000);
R_DEFINE_ERROR_RESULT(InvalidMemPllmEntry, 1001);
R_DEFINE_ERROR_RESULT(InvalidMtcMagic, 1002);
R_DEFINE_ERROR_RESULT(InvalidMtcTable, 1003);
R_DEFINE_ERROR_RESULT(InvalidDvbTable, 1004);
R_DEFINE_ERROR_RESULT(InvalidCpuFreqVddEntry, 1005);
R_DEFINE_ERROR_RESULT(InvalidCpuVoltDfllEntry, 1006);
R_DEFINE_ERROR_RESULT(InvalidCpuDvfs, 1007);
R_DEFINE_ERROR_RESULT(InvalidCpuMinVolt, 1008);
R_DEFINE_ERROR_RESULT(InvalidGpuDvfs, 1009);
R_DEFINE_ERROR_RESULT(InvalidGpuFreqMaxPattern, 1010);
R_DEFINE_ERROR_RESULT(InvalidGpuPllEntry, 1011);
R_DEFINE_ERROR_RESULT(InvalidRegulatorEntry, 1012);
R_DEFINE_ERROR_RESULT(UninitializedPatcher, 1013);
R_DEFINE_ERROR_RESULT(UnsuccessfulPatcher, 1014);
R_DEFINE_ERROR_RESULT(SafetyCheckFailure, 1015);
R_DEFINE_ERROR_RESULT(InvalidMtcTablePattern, 1016);
R_DEFINE_ERROR_RESULT(InvalidSocVoltPattern, 1017);
R_DEFINE_ERROR_RESULT(InvalidSocVoltLimit, 1018);
R_DEFINE_ERROR_RESULT(InvalidEmcDvfsCount, 1019);
R_DEFINE_ERROR_RESULT(InvalidEmcSocLut, 1020);
R_DEFINE_ERROR_RESULT(InvalidEmcRateList, 1021);
R_DEFINE_ERROR_RESULT(InvalidNvLogRedirect, 1022);
R_DEFINE_ERROR_RESULT(InvalidBusFreqReloc, 1023);
R_DEFINE_ERROR_RESULT(HookArenaOutOfMemory, 1024);
R_DEFINE_ERROR_RESULT(HookPayloadTooLarge, 1025);
R_DEFINE_ERROR_RESULT(HookRelocationUnsupported, 1026);
R_DEFINE_ERROR_RESULT(HookSiteInvalid, 1027);
R_DEFINE_ERROR_RESULT(HookPayloadEscapes, 1028);
R_DEFINE_ERROR_RESULT(HookDataOutOfMemory, 1029);
R_DEFINE_ERROR_RESULT(HookUnavailable, 1030);
}
namespace ams::ldr::hoc {
@@ -64,6 +108,7 @@ namespace ams::ldr::hoc {
patternFn pattern_search_fn = nullptr;
Pointer value_search;
size_t patched_count = 0;
bool optional = false;
Result Apply(Pointer *ptr) {
Result res = patcher_fn(ptr);
@@ -94,7 +139,7 @@ namespace ams::ldr::hoc {
}
Result CheckResult() {
R_UNLESS(patched_count > 0, ldr::ResultUnsuccessfulPatcher());
R_UNLESS(optional || patched_count > 0, ldr::ResultUnsuccessfulPatcher());
if (maximum_patched_count) {
R_UNLESS(patched_count <= maximum_patched_count, ldr::ResultUnsuccessfulPatcher());

View File

@@ -19,7 +19,7 @@
#include "oc_common.hpp"
#if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
#if HOC_IRAM_LOG
#include "fatal_handler_bin.h"
#endif
@@ -70,6 +70,32 @@ namespace ams::ldr::hoc {
return rc;
}
#if HOC_UART_LOG
/* Remove this? */
void UartLog(const char *fmt, ...) {
char line[256];
constexpr size_t PrefixLen = 13; /* "[HOC] " */
std::memcpy(line, "[Horizon OC] ", PrefixLen);
va_list args;
va_start(args, fmt);
int n = vsnprintf(line + PrefixLen, sizeof(line) - PrefixLen - 1, fmt, args);
va_end(args);
if (n < 0) {
return;
}
size_t body = static_cast<size_t>(n);
if (body > sizeof(line) - PrefixLen - 2) { /* vsnprintf returns the untruncated length */
body = sizeof(line) - PrefixLen - 2;
}
size_t len = PrefixLen + body;
line[len++] = '\n';
svc::OutputDebugString(line, len);
}
#endif
struct log_ctx_t {
u32 magic;
u32 sz;
@@ -81,7 +107,7 @@ namespace ams::ldr::hoc {
#define IRAM_LOG_CTX_ADDR 0x4003C000
#define IRAM_LOG_MAX_SZ 4096
#if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
#if HOC_IRAM_LOG
void Log(const char *data, ...) {
static const u32 max_log_sz = sizeof(working_buf) - sizeof(log_ctx_t);
static bool initDone = false;
@@ -112,8 +138,8 @@ namespace ams::ldr::hoc {
}
#endif
#if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
void ViewLog() {
#if HOC_IRAM_LOG
if (spl::GetSocType() == spl::SocType_Mariko) {
return;
}
@@ -127,6 +153,6 @@ namespace ams::ldr::hoc {
SmcRebootToIramPayload();
while(true) { }
#endif
}
#endif
}

View File

@@ -25,4 +25,8 @@ namespace ams::ldr::hoc {
void Log(const char *data, ...);
void ViewLog();
/* Emit a formatted line over the kernel debug UART via svcOutputDebugString.
No-op unless the running kernel enables debug logging (audit/debug mesosphere). */
void UartLog(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
}

View File

@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../mtc_timing_value.hpp"
#include "../../mtc_timing_value.hpp"
namespace ams::ldr::hoc::pcv::erista {

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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<u32 *>(mapped_nso);
MtcGenerateFreqTables();
u32 CpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
u32 GpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
PatcherEntry<u32> patches[] = {
{"CPU Freq Table", CpuFreqCvbTable<false>, 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<false>, 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<u32 *>(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);
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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<CvbCpuDfllData *>(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();
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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<clk_pll_param *>(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();
// }
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <shift>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);
}

View File

@@ -21,163 +21,20 @@
*/
#include <vector>
#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 {
std::vector<u32> newEmcList;
u32 *nsoStart;
u32 *nsoEnd;
namespace {
std::vector<u32> newEmcList;
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<CvbCpuDfllData *>(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();
struct {
u32 *getEristaMtcTableFnSite = nullptr;
EristaMtcTable *mtcTable = nullptr;
bool foundMtcTablePattern = false;
} getMtcTableCache;
}
/* Note: This does not have proper timings, so base latency adjustment will not work. */
@@ -447,39 +304,6 @@ namespace ams::ldr::hoc::pcv::erista {
}
}
/* Relocate the table */
/* Rescanning is simpler than trying to extract a bunch of data from the asm patch, performance impact is negligable */
/* Also, this is more stable :P */
u32 RepointEristaEmcTablePtr(uintptr_t fromSlot, uintptr_t toTable) {
constexpr u32 RetIns = 0xD65F03C0; /* ret */
u32 patched = 0;
for (u32 *p = nsoStart; p + 4 < nsoEnd; ++p) {
const u32 ins = *p;
if (!AsmIsAdrX0(ins)) {
continue;
}
const uintptr_t pc = reinterpret_cast<uintptr_t>(p);
if (AsmAdrTarget(ins, pc) != fromSlot) {
continue;
}
if (!(AsmIsLdpX(p[1]) && AsmIsLdpX(p[2]) && p[3] == RetIns)) {
continue;
}
/* adr only reaches +-1MB */
const s64 delta = static_cast<s64>(toTable) - static_cast<s64>(pc);
if (delta > 0xFFFFF || delta < -0x100000) {
continue;
}
PATCH_OFFSET(p, AsmSetAdrTarget(ins, pc, toTable));
++patched;
}
return patched;
}
/* The silicon instructs; the children obey... */
void MtcGenerateFreqTables() {
newEmcList.clear();
@@ -548,24 +372,20 @@ namespace ams::ldr::hoc::pcv::erista {
constexpr u32 StartAdjustment = offsetof(EristaMtcTable, rate_khz) + sizeof(EristaMtcTable) * (erista::MtcTableCountDefault - 1);
u8 *startPtr = reinterpret_cast<u8 *>(ptr) - StartAdjustment;
const uintptr_t usedSlot = reinterpret_cast<uintptr_t>(startPtr) + mtcOffset;
EristaMtcTable *table = reinterpret_cast<EristaMtcTable *>(usedSlot);
EristaMtcTable *table = reinterpret_cast<EristaMtcTable *>(startPtr + mtcOffset);
R_TRY(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault));
PrepareMtcMemoryRegion(startPtr, table);
table = reinterpret_cast<EristaMtcTable *>(startPtr);
/* We must do this as the NLE tables don't have enough space past them for our extended ones */
if (usedSlot != reinterpret_cast<uintptr_t>(startPtr)) {
if (RepointEristaEmcTablePtr(usedSlot, reinterpret_cast<uintptr_t>(startPtr)) == 0) {
AbortInvalidMtc("Failed to repoint emc table");
}
}
if (R_FAILED(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault))) {
AbortInvalidMtc("Failed mtc validation");
}
/* Cache the table for hooks. */
getMtcTableCache.mtcTable = table;
if (C.eristaEmcMaxClock <= EmcClkOSLimit) {
R_SKIP();
}
@@ -593,125 +413,67 @@ namespace ams::ldr::hoc::pcv::erista {
R_SUCCEED();
}
Result GpuFreqPllMax(u32 *ptr) {
clk_pll_param *entry = reinterpret_cast<clk_pll_param *>(ptr);
HOOK_PAYLOAD_FN EristaMtcTable *GetEristaMtcTableImpl(u32 *count) {
const HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData);
// 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();
*count = data->mtcTableAsm.mtcCount;
return data->mtcTableAsm.mtcTable;
}
// patch out 1305MHz limit on erista, don't use this!
// Result GpuFreqPllLimit(u32 *ptr) {
// u32 prev_freq = *(ptr - 1);
Result MtcInstallHooks(HookPayloadData *data) {
R_UNLESS(getMtcTableCache.getEristaMtcTableFnSite != nullptr && getMtcTableCache.mtcTable != nullptr, ldr::ResultInvalidMtcTablePattern());
// if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) {
// R_THROW(ldr::ResultInvalidGpuPllEntry());
// }
/* Copy the data to the payload. */
data->mtcTableAsm.mtcTable = reinterpret_cast<EristaMtcTable *>(Hooks().ToVa(getMtcTableCache.mtcTable));
data->mtcTableAsm.mtcCount = newEmcList.size();
// PATCH_OFFSET(ptr, 3600000);
// R_SUCCEED();
// }
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. */
R_UNLESS(!getMtcTableCache.foundMtcTablePattern, ldr::ResultInvalidMtcTablePattern());
/* This is a mess but the compiler made this painful to patch so we must do it this way */
constexpr s32 GoodAdrpOffset = -1;
constexpr s32 GoodMovOffset = -7;
constexpr s32 GoodBlOffset = 1;
constexpr u32 MtcGoodBlOpcode = 0x97fe6cfc;
constexpr u32 MtcBadBlOpcode0 = 0x97ffae64; // bl nn::pcv::GetHardwareType
constexpr u32 MtcBadBlOpcode1 = 0x940036d5; // bl strcmp
constexpr u32 MtcBadAdrpAsm = 0xd00000a1; // adrp x1, s_ModuleResetStatus_
constexpr s32 MtcBadBlOffset0 = 2;
constexpr s32 MtcBadBlOffset1 = -1;
constexpr s32 MtcBadAdrpOffset = 1;
constexpr u32 AddrpOffset = 1;
constexpr u32 MovOffset = 7;
constexpr u32 BlOffset = 5;
constexpr u32 MovOffsetOld = 8;
/* Ensure we don't dereference memory before nso start. */
R_UNLESS(ptr + GoodMovOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern());
R_UNLESS(ptr - MovOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern());
/* Check for GetHardwareType asm and skip if it is found */
/* The pattern will match on the first time, but the location is bad, so it must be skipped */
if(AsmCompareAdrpNoImm(*(ptr + MtcBadAdrpOffset), MtcBadAdrpAsm) && AsmBlCompareOpcodeOnly(*(ptr + MtcBadBlOffset0), MtcBadBlOpcode0) && AsmBlCompareOpcodeOnly(*(ptr + MtcBadBlOffset1), MtcBadBlOpcode1)) {
R_SKIP();
}
/* 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. */
u32 adrp = *(ptr + GoodAdrpOffset);
u32 adrp = *(ptr - AddrpOffset);
R_UNLESS(AsmCompareAdrpNoImm(adrp, MtcAdrpAsm), ldr::ResultInvalidMtcTablePattern());
/* Check for the branch instruction above the cbz to ensure we are patching the right location*/
u32 bl = *(ptr + GoodBlOffset);
R_UNLESS(AsmBlCompareOpcodeOnly(bl, MtcGoodBlOpcode), ldr::ResultInvalidMtcTablePattern());
u32 bl = *(ptr - BlOffset);
R_UNLESS(AsmBlCompareOpcodeOnly(bl, MtcBlIns), ldr::ResultInvalidMtcTablePattern());
/* Check for the mov that actually sets the mtc table count. */
u32 mov = *(ptr + GoodMovOffset);
R_UNLESS(asm_compare_no_rd(mov, MtcMovAsm), ldr::ResultInvalidMtcTablePattern());
u32 mov = *(ptr - MovOffset);
bool foundMov = false;
foundMov = asm_compare_no_rd(mov, MtcMovAsm);
/* Patch out the count of the mov to our custom mtc table amount*/
u32 movCountPatch = asm_set_rd(asm_set_imm16(MtcMovAsm, newEmcList.size()), asm_get_rd(mov));
if (!foundMov) {
mov = *(ptr + MovOffsetOld);
/* Check old firmware offset. */
foundMov = asm_compare_no_rd(mov, MtcMovAsm);
}
PATCH_OFFSET(ptr + GoodMovOffset, movCountPatch);
R_UNLESS(foundMov, ldr::ResultInvalidMtcTablePattern());
constexpr u32 PrologueWindow = 140;
u32 *functionPrologue = FindFnPrologue(ptr, PrologueWindow, nsoStart);
R_UNLESS(functionPrologue != nullptr, ldr::ResultInvalidMtcTablePattern());
getMtcTableCache.getEristaMtcTableFnSite = functionPrologue;
getMtcTableCache.foundMtcTablePattern = true;
R_SUCCEED();
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
nsoStart = reinterpret_cast<u32 *>(mapped_nso);
nsoEnd = reinterpret_cast<u32 *>(mapped_nso + nso_size);
MtcGenerateFreqTables();
u32 CpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
u32 GpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
PatcherEntry<u32> patches[] = {
{"CPU Freq Table", CpuFreqCvbTable<false>, 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<false>, 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<u32 *>(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())) {
// ViewLog();
panic::SmcError(panic::Patch);
CRASH(entry.description);
}
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#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<u8>(((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<u32>(0xFFFF));
}
tRC = tRAS + tRPpb;
tRFCab = tRFCpb * 2;
tXSR = static_cast<double>(tRFCab + 7.5);
tFAW = static_cast<u32>(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<s32>(einput - qrst_duration - 2), static_cast<s32>(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<double>(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<double>(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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#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<u8>(((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<u32>(0xFFFF));
}
tRC = tRAS + tRPpb;
tRFCab = tRFCpb * 2;
tXSR = static_cast<double>(tRFCab + 7.5);
tFAW = static_cast<u32>(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<s32>(einput - qrst_duration - 2), static_cast<s32>(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<double>(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<double>(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();
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <http://www.gnu.org/licenses/>.
*/
#pragma once
namespace ams::ldr::hoc::pcv::mariko {
void CalculateTimings(double tCK_avg, u32 freq);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <vector>
#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<uintptr_t>(nsoStart);
const size_t nso_size = g_nso_size;
const uintptr_t textEnd = g_pcv_cave; /* .text ends where the cave begins */
const uintptr_t vsnprintf_addr = reinterpret_cast<uintptr_t>(ptr);
/* NvLog via the VDD_SOC log */
static const char Fmt[] = "%s(%s): DVFS request VDD_SOC %d mV\n";
constexpr size_t FmtLen = sizeof(Fmt) - 1;
uintptr_t strAddr = 0;
{
const char *hay = reinterpret_cast<const char *>(mapped_nso);
for (size_t i = 0; i + FmtLen <= nso_size; ++i) {
if (std::memcmp(hay + i, Fmt, FmtLen) == 0) { strAddr = mapped_nso + i; break; }
}
}
if (strAddr == 0) {
LOGGING("NvLogRedirect: fmt string not found (vsnprintf@+%lx)", vsnprintf_addr - mapped_nso);
R_THROW(ldr::ResultInvalidNvLogRedirect());
}
uintptr_t nvlog_addr = 0;
for (u32 *p = nsoStart; reinterpret_cast<uintptr_t>(p + 2) <= textEnd; ++p) {
const uintptr_t pc = reinterpret_cast<uintptr_t>(p);
if (!AsmIsAdrp(p[0])) {
continue;
}
const uintptr_t adrpPage = (pc & ~static_cast<uintptr_t>(0xFFFu)) + static_cast<uintptr_t>(AsmAdrpPageOffset(p[0]));
if (adrpPage != (strAddr & ~static_cast<uintptr_t>(0xFFFu))) {
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<unsigned long>(g_pcv_cave), static_cast<unsigned long>(g_pcv_cave_size));
R_THROW(ldr::ResultInvalidNvLogRedirect());
}
u32 *t = reinterpret_cast<u32 *>(helper);
size_t n = 0;
auto emit = [&](u32 ins) { t[n] = ins; ++n; };
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<uintptr_t>(p + 1) <= textEnd; ++p) {
if (!AsmIsBl(*p)) {
continue;
}
const uintptr_t pc = reinterpret_cast<uintptr_t>(p);
if (AsmBranchTarget(*p, pc) != nvlog_addr) {
continue;
}
bool isFmtCall = false;
for (u32 j = 1; j <= 8 && reinterpret_cast<uintptr_t>(p - j) >= reinterpret_cast<uintptr_t>(nsoStart); ++j) {
const u32 w = *(p - j);
if (AsmIsAdrp(w) && asm_get_rd(w) == 0) { /* adrp x0,<page> */
const uintptr_t wpc = pc - j * 4;
const uintptr_t tgtPage = (wpc & ~static_cast<uintptr_t>(0xFFFu)) + static_cast<uintptr_t>(AsmAdrpPageOffset(w));
if (tgtPage >= (roStart & ~static_cast<uintptr_t>(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<uintptr_t>(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<u32 *>(tramp);
size_t n = 0;
auto emit = [&](u32 ins) { t[n] = ins; ++n; };
emit(AsmMakeAdrp(tramp + n * 4, region, s[0])); /* adrp s0,<region> */
emit(AsmMakeAddImm64(s[0], s[0], region & 0xFFFu)); /* add s0,s0,#lo */
emit(AsmMakeLdrImm32(s[1], s[0], 0x00)); /* s1 = counter */
emit(AsmMakeAddImm64(s[2], s[1], 1)); /* s2 = counter+1 */
emit(AsmMakeStrImm32(s[2], s[0], 0x00)); /* counter++ */
emit(AsmMakeAddImm64(s[0], s[0], 0x10)); /* s0 = region+0x10 (buffers) */
emit(AsmMakeAddShiftedReg64(bufReg, s[0], s[1], 10)); /* Xbuf = s0 + counter*0x400 */
emit(AsmMakeStrImm64(bufReg, busReg, bufOff)); /* bus[freqBuf] = Xbuf */
emit(AsmMakeB(tramp + n * 4, realFn)); /* tail-call the real function */
PATCH_OFFSET(call, AsmMakeBl(reinterpret_cast<uintptr_t>(call), tramp));
const uintptr_t base = reinterpret_cast<uintptr_t>(nsoStart);
(void) base;
LOGGING("BusFreqReloc: call@+%lx -> tramp@+%lx realfn@+%lx (bus=x%u buf=x%u off=0x%x scratch=x%u,x%u,x%u)",
reinterpret_cast<uintptr_t>(call) - base, tramp - base, realFn - base, busReg, bufReg, bufOff, s[0], s[1], s[2]);
R_SUCCEED();
}
#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<u16>(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<u16>(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<Rm>,#0x20 */
u32 *movPtr = nullptr;
for (u32 i = 1; i <= 16; ++i) {
if (*(ptr - i) == capMov) {
movPtr = ptr - i;
break;
}
}
R_UNLESS(movPtr, ldr::ResultInvalidEmcRateList());
/* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount). */
PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast<u16>(EmcDvfsTableEntryCount))); /* cmp w?,#64 */
PATCH_OFFSET(movPtr, asm_set_imm16(*movPtr, static_cast<u16>(EmcDvfsTableEntryCount))); /* movz w?,#64 */
R_SUCCEED();
}
Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) {
struct {
u8 reg;
u8 val;
} __attribute__((packed)) cmd;
I2cSession _session;
R_TRY(i2cOpenSession(&_session, dev));
cmd.reg = reg;
cmd.val = val;
Result res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All);
i2csessionClose(&_session);
return res;
}
Result EmcVddqVolt(u32 *ptr) {
regulator *entry = reinterpret_cast<regulator *>(reinterpret_cast<u8 *>(ptr) - offsetof(regulator, type_2_3.default_uv));
constexpr u32 uv_step = 5'000;
constexpr u32 uv_min = 250'000;
auto validator = [entry]() {
R_UNLESS(entry->id == 2, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type == 3, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_2_3.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_2_3.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry());
R_SUCCEED();
};
R_TRY(validator());
u32 emc_uv = C.marikoEmcVddqVolt;
if (!emc_uv) {
R_SKIP();
}
if (emc_uv % uv_step) {
emc_uv = (emc_uv + uv_step - 1) / uv_step * uv_step; // rounding
}
PATCH_OFFSET(ptr, emc_uv);
i2cInitialize();
Result resultI2C = I2cSet_U8(I2cDevice_Max77812_2, 0x25, (emc_uv - uv_min) / uv_step);
i2cExit();
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<u32 *>(virtualAddress + FuseOffset + SocSpeedoOffset);
R_SUCCEED();
}
u32 GetSocProcessId(u32 socSpeedo) {
if (socSpeedo <= 1597) {
return 0;
}
if (socSpeedo <= 1708) {
return 1;
}
/* >= 1709. */
return 2;
}
Result SocVoltAsm(u32 *compareSpeedos) {
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<desc>,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<desc>] */
++patched;
}
}
R_UNLESS(patched == 2, ldr::ResultInvalidEmcRateList());
/* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount) */
PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast<u16>(EmcDvfsTableEntryCount))); /* cmp w?,#64 */
PATCH_OFFSET(ptr + movzI, asm_set_imm16(*(ptr + movzI), static_cast<u16>(EmcDvfsTableEntryCount))); /* movz w?,#64 */
R_SUCCEED();
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
nsoStart = reinterpret_cast<u32 *>(mapped_nso);
g_pcv_scratch = mapped_nso + nso_size - HocPcvScratchSize;
g_nso_size = nso_size;
g_cave_cursor = g_pcv_cave; /* start the .text-cave bump allocator (0 if unavailable) */
MtcGenerateFreqTables();
u32 CpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
u32 GpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
PatcherEntry<u32> patches[] = {
{ "CPU Freq Vdd", &CpuFreqVdd, 1, nullptr, CpuClkOSLimit },
{ "CPU Freq Table", CpuFreqCvbTable<true>, 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<true>, 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<u32 *>(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);
}
}
}
}

View File

@@ -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 }, { } },
@@ -299,6 +301,131 @@ namespace ams::ldr::hoc::pcv::mariko {
return AsmCompareAddNoImm12(*ptr, MtcAddAsm);
}
constexpr u32 EmcCountCmpAsm = 0x7100851F; /* cmp w?,#0x21 (subs wzr,w?,#0x21) */
/*
str <lut>,[<rail>,#0x120] ; volt-array pointer
str w?, [<rail>,#0x154] ; num_freqs
*/
constexpr u32 EmcSocLutPtrStoreAsm = 0xF9009009; /* str x?,[x0,#0x120] (anchor) */
constexpr u32 EmcSocLutCountStoreAsm = 0xB9015408; /* str w?,[x0,#0x154] (anchor) */
constexpr u32 EmcSocFreqStoreAsm = 0xF9000D00; /* str x?,[x8,#0x18] */
constexpr u32 EmcSocVoltStoreAsm = 0xB9004900; /* str w?,[x8,#0x48] (socMinLut[i]) */
constexpr u32 EmcSocReadLoadAsm = 0xB9404929; /* ldr w?,[x9,#0x48] (socMinLut readback) */
inline bool EmcDvfsCountPatternFn(u32 *ptr) {
/* Local context: cbz w?,<skip> ; cmp w?,#0x21 ; b.cs <abort> */
return asm_compare_no_rd(*ptr, EmcCountCmpAsm) && AsmCompareBrConNoImm19(*(ptr + 1), 0x54000002) /* b.cs */
&& AsmCbzCompareOpcodeOnly(*(ptr - 1), 0x34000000); /* cbz */
}
inline bool EmcSocLutPatternFn(u32 *ptr) {
return asm_compare_no_rd(*ptr, EmcSocLutPtrStoreAsm) /* str x?,[x0,#0x120] */
&& asm_compare_no_rd(*(ptr + 1), EmcSocLutCountStoreAsm); /* str w?,[x0,#0x154] */
}
/*
mov w?,#0x20 ; the 32 cap
cmp w?,#0x20
csel w?,w?,w?,lt ; w? = min(maxCount, 32)
bl TegraGetEmcDvfsFreqTable
cmp w?,#0x20 ; (maxCount)
csel w?,<same>,<cap>,lt ; min(maxCount, 32)
bl <Get*DvfsFreqTable>
*/
constexpr u32 EmcRateCapCmpAsm = 0x710082FF; /* cmp w?,#0x20 */
constexpr u32 EmcRateCapCselAsm = 0x1A80B000; /* csel w?,w?,w?,lt */
inline bool EmcRateListPatternFn(u32 *ptr) {
return AsmSubsCompareNoReg(*ptr, EmcRateCapCmpAsm) /* cmp w?,#0x20 */
&& AsmCompareCselNoReg(*(ptr + 1), EmcRateCapCselAsm) /* csel w?,w?,w?,lt */
&& (AsmGetRn(*ptr) == AsmGetRn(*(ptr + 1))) /* min(reg, 0x20) */
&& AsmBlCompareOpcodeOnly(*(ptr + 2), 0x94000000); /* bl <Get*DvfsFreqTable> */
}
constexpr u32 EmcRateSessCmpAsm = 0x710082FF; /* cmp w?,#0x20 */
constexpr u32 EmcRateSessMovAsm = 0x52800400; /* movz w?,#0x20 */
constexpr u32 EmcRateSessCselAsm = 0x1A80B000; /* csel w?,w?,w?,lt (opcode + cond) */
inline bool EmcRateSessFindClamp(u32 *ptr, u32 *out_c, u32 *out_cap, u32 *out_movz_i) {
if (!AsmSubsCompareNoReg(ptr[0], EmcRateSessCmpAsm)) return false; /* cmp w<c>,#0x20 */
const u32 c = AsmGetRn(ptr[0]);
for (u32 i = 1; i <= 14; ++i) {
const u32 w = ptr[i];
if (AsmCompareCselNoReg(w, EmcRateSessCselAsm) && AsmGetRn(w) == c && asm_get_rd(w) == c) {
const u32 cap = AsmGetRm(w);
for (u32 j = 1; j < i; ++j) {
if ((ptr[j] & 0xFFFFFFE0u) == EmcRateSessMovAsm && asm_get_rd(ptr[j]) == cap) {
if (out_c) *out_c = c;
if (out_cap) *out_cap = cap;
if (out_movz_i) *out_movz_i = j;
return true;
}
}
return false;
}
}
return false;
}
inline bool EmcRateSessPatternFn(u32 *ptr) {
return EmcRateSessFindClamp(ptr, nullptr, nullptr, nullptr);
}
inline bool BusFreqRelocPatternFn(u32 *ptr) {
if (g_pcv_scratch == 0 || g_pcv_cave == 0) {
return false;
}
if (reinterpret_cast<uintptr_t>(ptr + 4) > g_pcv_cave) { /* the call site lives in .text */
return false;
}
if (!(AsmIsLdrImm64(ptr[0]) && AsmGetLdStImm64Off(ptr[0]) == 0x10)) return false; /* ldr Xbuf,[Xbus,#0x10] */
if (!(AsmIsAddImm64(ptr[1]) && AsmGetImm12(ptr[1]) == 0x18)) return false; /* add Xcnt,Xbus,#0x18 */
if (!(AsmIsStrImm64(ptr[2]) && AsmGetLdStImm64Off(ptr[2]) == 0x50)) return false; /* str Xrail,[Xbus,#0x50]*/
if (!AsmIsBl(ptr[3])) return false; /* bl GetDvfsRailUnique */
const u32 bus = AsmGetRn(ptr[0]);
return AsmGetRn(ptr[1]) == bus && AsmGetRn(ptr[2]) == bus;
}
inline bool ForceVerbosityPatternFn(u32 *ptr) {
if (HOC_PCV_FORCE_VERBOSITY == 0 || g_pcv_cave == 0) {
return false;
}
if (reinterpret_cast<uintptr_t>(ptr + 11) > g_pcv_cave) { /* .text only */
return false;
}
if (ptr[0] != 0xA9BE7BFDu || ptr[1] != 0xF9000BF3u || ptr[2] != 0x910003FDu) return false; /* stp/str/mov x29,sp */
if (!(AsmIsAddImm64(ptr[3]) && asm_get_rd(ptr[3]) == 0 && AsmGetRn(ptr[3]) == 29)) return false; /* add x0,x29,#imm */
if (!(AsmIsAddImm64(ptr[4]) && asm_get_rd(ptr[4]) == 19 && AsmGetRn(ptr[4]) == 29)) return false; /* add x19,x29,#imm */
if (AsmGetImm12(ptr[3]) != AsmGetImm12(ptr[4]) || !AsmIsBl(ptr[5])) return false;
for (u32 j = 6; j <= 10; ++j) {
if (ptr[j] == 0x7100001Fu) { /* cmp w0,#0 */
return true;
}
}
return false;
}
/* vsnprintf(buf,size,fmt,va_list) prologue */
inline constexpr u32 NvLogVsnSig[] = { 0xD10483FFu, 0xA9107BFDu, 0xF9008BFCu, 0x910403FDu, 0xF100003Fu };
inline bool NvLogVsnprintfPatternFn(u32 *ptr) {
if (HOC_UART_LOG == 0 || g_pcv_cave == 0) {
return false;
}
if (reinterpret_cast<uintptr_t>(ptr + std::size(NvLogVsnSig)) > g_pcv_cave) { /* must sit in .text */
return false;
}
for (size_t k = 0; k < std::size(NvLogVsnSig); ++k) {
if (ptr[k] != NvLogVsnSig[k]) {
return false;
}
}
return true;
}
void Patch(uintptr_t mapped_nso, size_t nso_size);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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<dvfs_rail *>(reinterpret_cast<u8 *>(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<CvbMeta *>(reinterpret_cast<u8 *>(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<CvbCpuDfllData *>(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();
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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<clk_pll_param *>(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();
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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);
}

View File

@@ -20,333 +20,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <vector>
#include "pcv.hpp"
#include "../mtc_timing_value.hpp"
#include "../mariko/calculate_timings.hpp"
#include "../pcv.hpp"
#include "../../mtc_timing_value.hpp"
#include "calculate_timings_mariko.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<dvfs_rail *>(reinterpret_cast<u8 *>(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<CvbMeta *>(reinterpret_cast<u8 *>(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<CvbCpuDfllData *>(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<clk_pll_param *>(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();
namespace {
std::vector<u32> newEmcList;
}
void MemMtcTableAutoAdjust(MarikoMtcTable *table) {
@@ -617,11 +298,6 @@ namespace ams::ldr::hoc::pcv::mariko {
}
}
namespace {
std::vector<u32> newEmcList;
u32 *nsoStart;
}
void MtcGenerateJedecTable() {
const u32 jedecFreqs[] = { 1866000, 1996000, 2133000, 2400000, 2666000, 2933000, 3200000 };
constexpr u32 JedecFreqCount = std::size(jedecFreqs);
@@ -638,7 +314,7 @@ namespace ams::ldr::hoc::pcv::mariko {
newEmcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
}
newEmcList.resize(std::min(newEmcList.size(), DvfsTableEntryLimit));
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
}
void MtcGenerate133StepTable() {
@@ -657,12 +333,39 @@ namespace ams::ldr::hoc::pcv::mariko {
newEmcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
}
newEmcList.resize(std::min(newEmcList.size(), DvfsTableEntryLimit));
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<u32>(C.marikoEmcMaxClock));
}
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
}
void MtcGenerateFreqTables() {
newEmcList.clear();
newEmcList.reserve(DvfsTableEntryCount);
newEmcList.reserve(EmcDvfsTableEntryCount);
newEmcList.insert(newEmcList.end(), std::begin(EmcListDefault), std::end(EmcListDefault));
if (C.marikoEmcMaxClock <= EmcClkOSLimit) {
@@ -671,6 +374,9 @@ namespace ams::ldr::hoc::pcv::mariko {
u32 stepRate = 0;
switch (C.stepMode) {
case StepMode_33MHz:
MtcGenerate33StepTable();
return;
case StepMode_66MHz:
stepRate = 66667;
break;
@@ -701,7 +407,7 @@ namespace ams::ldr::hoc::pcv::mariko {
newEmcList.push_back(newFreq);
}
newEmcList.resize(std::min(newEmcList.size(), DvfsTableEntryLimit));
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
}
Result VerifyMtcTable(MarikoMtcTable *tableStart, u32 expectedFreq) {
@@ -769,6 +475,7 @@ namespace ams::ldr::hoc::pcv::mariko {
Result MemFreqMtcTable(u32 *ptr) {
static const DramId dramId = [] {
DramId id = GetDramId();
id = HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTF;
return id;
}();
@@ -905,24 +612,23 @@ namespace ams::ldr::hoc::pcv::mariko {
#undef DVB
#undef DVB_OC
DvbEntry emcDvbTableOc[newEmcList.size()];
const size_t dvbCount = std::min(newEmcList.size(), DvbTableCapacity);
DvbEntry emcDvbTableOc[DvbTableCapacity] = {};
u32 bracketIndex = 0;
for (u32 i = 0; i < newEmcList.size(); ++i) {
while (newEmcList[i] >= emcDvbOcTableBrackets[bracketIndex + 1].freq) {
for (size_t i = 0; i < dvbCount; ++i) {
const u32 freq = (i == dvbCount - 1) ? static_cast<u32>(newEmcList.back()) : newEmcList[i];
while (freq >= emcDvbOcTableBrackets[bracketIndex + 1].freq) {
++bracketIndex;
}
emcDvbTableOc[i].freq = newEmcList[i];
emcDvbTableOc[i].freq = freq;
std::memcpy(emcDvbTableOc[i].volt, emcDvbOcTableBrackets[bracketIndex].volt, sizeof(emcDvbTableOc[i].volt));
}
std::memset(mem_dvb_table_head, 0, sizeof(EmcDvbTableDefault));
std::memcpy(mem_dvb_table_head, &emcDvbTableOc, sizeof(emcDvbTableOc));
/* Max dvfs entry is 32, but HOS doesn't seem to boot if exact freq doesn't exist in dvb table,
reason why it's like this
*/
/* 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();
}
@@ -936,57 +642,6 @@ namespace ams::ldr::hoc::pcv::mariko {
R_SUCCEED();
}
Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) {
struct {
u8 reg;
u8 val;
} __attribute__((packed)) cmd;
I2cSession _session;
R_TRY(i2cOpenSession(&_session, dev));
cmd.reg = reg;
cmd.val = val;
Result res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All);
i2csessionClose(&_session);
return res;
}
Result EmcVddqVolt(u32 *ptr) {
regulator *entry = reinterpret_cast<regulator *>(reinterpret_cast<u8 *>(ptr) - offsetof(regulator, type_2_3.default_uv));
constexpr u32 uv_step = 5'000;
constexpr u32 uv_min = 250'000;
auto validator = [entry]() {
R_UNLESS(entry->id == 2, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type == 3, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_2_3.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_2_3.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry());
R_SUCCEED();
};
R_TRY(validator());
u32 emc_uv = C.marikoEmcVddqVolt;
if (!emc_uv) {
R_SKIP();
}
if (emc_uv % uv_step) {
emc_uv = (emc_uv + uv_step - 1) / uv_step * uv_step; // rounding
}
PATCH_OFFSET(ptr, emc_uv);
i2cInitialize();
Result resultI2C = I2cSet_U8(I2cDevice_Max77812_2, 0x25, (emc_uv - uv_min) / uv_step);
i2cExit();
return resultI2C;
}
Result MemMtcTableAsm(u32 *ptr) {
constexpr u32 AddpOffset = 1;
constexpr u32 BrOffset = 12;
@@ -1018,170 +673,4 @@ namespace ams::ldr::hoc::pcv::mariko {
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<u32 *>(virtualAddress + FuseOffset + SocSpeedoOffset);
R_SUCCEED();
}
u32 GetSocProcessId(u32 socSpeedo) {
if (socSpeedo <= 1597) {
return 0;
}
if (socSpeedo <= 1708) {
return 1;
}
/* >= 1709. */
return 2;
}
Result SocVoltAsm(u32 *compareSpeedos) {
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();
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
nsoStart = reinterpret_cast<u32 *>(mapped_nso);
MtcGenerateFreqTables();
u32 CpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
u32 GpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
PatcherEntry<u32> patches[] = {
{ "CPU Freq Vdd", &CpuFreqVdd, 1, nullptr, CpuClkOSLimit },
{ "CPU Freq Table", CpuFreqCvbTable<true>, 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<true>, 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 },
{ "SOC Volt Asm", &SocVoltAsm, 1, &SocVoltPatternFn },
{ "SOC Volt Limit", &SocVoltLimit, 1, nullptr, SocVoltLimitOfficial },
};
for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(MarikoMtcTable); ptr += sizeof(u32)) {
u32 *ptr32 = reinterpret_cast<u32 *>(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);
}
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <http://www.gnu.org/licenses/>.
*/
#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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
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();
}

View File

@@ -27,8 +27,8 @@ namespace ams::ldr::hoc::pcv {
R_UNLESS(entry->freq == entry->vco_max, ldr::ResultInvalidMemPllmEntry());
// Double the max clk simply
u32 max_clk = entry->freq * 2;
entry->freq = max_clk;
u32 max_clk = entry->freq * 2;
entry->freq = max_clk;
entry->vco_max = max_clk;
R_SUCCEED();
}
@@ -41,9 +41,9 @@ namespace ams::ldr::hoc::pcv {
};
constexpr u32 uv_step = 12'500;
constexpr u32 uv_min = 600'000;
constexpr u32 uv_min = 600'000;
auto validator = [](regulator* entry) {
auto validator = [](regulator *entry) {
R_UNLESS(entry->id == 1, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type == 1, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_1.volt_reg == 0x17, ldr::ResultInvalidRegulatorEntry());
@@ -67,7 +67,7 @@ namespace ams::ldr::hoc::pcv {
}
if (emc_uv % uv_step) {
emc_uv = emc_uv / uv_step * uv_step; // rounding
emc_uv = emc_uv / uv_step * uv_step; // rounding
}
PATCH_OFFSET(ptr, emc_uv);
@@ -76,14 +76,14 @@ namespace ams::ldr::hoc::pcv {
}
void SafetyCheck() {
struct sValidator {
struct Validator {
volatile u32 value;
u32 min;
u32 max;
u32 panic;
bool value_required = false;
Result check() {
Result Check() {
if (!value_required && !value) {
R_SUCCEED();
}
@@ -102,11 +102,12 @@ namespace ams::ldr::hoc::pcv {
u32 eristaCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.eristaCpuDvfsTable)->freq);
u32 marikoCpuDvfsMaxFreq;
if (C.marikoCpuUVHigh) {
marikoCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoCpuDvfsTableSLT)->freq);
} else {
marikoCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq);
}
if (C.marikoCpuUVHigh) {
marikoCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoCpuDvfsTableSLT)->freq);
} else {
marikoCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq);
}
u32 eristaGpuDvfsMaxFreq;
switch (C.eristaGpuUV) {
case 0:
@@ -145,25 +146,25 @@ namespace ams::ldr::hoc::pcv {
break;
}
sValidator validators[] = {
{ C.eristaCpuBoostClock, 1020'000, 2397'000, panic::Cpu, true },
{ C.marikoCpuBoostClock, 1020'000, 2703'000, panic::Cpu, true },
{ C.eristaCpuMaxVolt, 1000, 1260, panic::Cpu, },
{ C.marikoCpuMaxVolt, 1000, 1200, panic::Cpu, },
{ eristaCpuDvfsMaxFreq, 1785'000, 2397'000, panic::Cpu, },
{ marikoCpuDvfsMaxFreq, 1785'000, 2703'000, panic::Cpu, },
{ C.commonEmcMemVolt, 912'500, 1350'000, panic::Emc, }, /* Official vmax for the RAMs is 1400-1500mV */
{ C.eristaEmcMaxClock, 1600'000, 2600'000, panic::Emc, },
{ C.marikoEmcMaxClock, 1600'000, 3500'000, panic::Emc, },
{ C.marikoEmcVddqVolt, 400'000, 750'000, panic::Emc, },
{ C.marikoSocVmax, 1000, 1200, panic::Emc, },
{ eristaGpuDvfsMaxFreq, 768'000, 1152'000, panic::Gpu, },
{ marikoGpuDvfsMaxFreq, 768'000, 1536'000, panic::Gpu, },
{ C.marikoGpuVmax, 800, 960, panic::Gpu, },
Validator validators[] = {
{ C.eristaCpuBoostClock, 1020'000, 2397'000, panic::Cpu, true },
{ C.marikoCpuBoostClock, 1020'000, 2703'000, panic::Cpu, true },
{ C.eristaCpuMaxVolt, 1000, 1260, panic::Cpu, },
{ C.marikoCpuMaxVolt, 1000, 1200, panic::Cpu, },
{ eristaCpuDvfsMaxFreq, 1785'000, 2397'000, panic::Cpu, },
{ marikoCpuDvfsMaxFreq, 1785'000, 2703'000, panic::Cpu, },
{ C.commonEmcMemVolt, 912'500, 1350'000, panic::Emc, }, /* Official vmax for the RAMs is 1400-1500mV */
{ C.eristaEmcMaxClock, 1600'000, 2600'000, panic::Emc, },
{ C.marikoEmcMaxClock, 1600'000, 3500'000, panic::Emc, },
{ C.marikoEmcVddqVolt, 400'000, 750'000, panic::Emc, },
{ C.marikoSocVmax, 1000, 1200, panic::Emc, },
{ eristaGpuDvfsMaxFreq, 768'000, 1152'000, panic::Gpu, },
{ marikoGpuDvfsMaxFreq, 768'000, 1536'000, panic::Gpu, },
{ C.marikoGpuVmax, 800, 960, panic::Gpu, },
};
for (auto &v : validators) {
if (R_FAILED(v.check())) {
if (R_FAILED(v.Check())) {
panic::SmcError(v.panic);
CRASH("Validation FAIL");
}
@@ -171,14 +172,19 @@ namespace ams::ldr::hoc::pcv {
}
void WriteKipLoadToIram() {
const u32 hocMagic = 0x686F634D;
const u32 hocMagic = 0x686F634D;
constexpr uintptr_t LoadMagicAddress = 0x4003DC00; /* Should be a pretty safe address. */
R_DISCARD(SmcCopyToIram(LoadMagicAddress, &hocMagic, sizeof(hocMagic)));
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
void Patch(uintptr_t mapped_nso, size_t nso_size, uintptr_t cave, size_t cave_size, uintptr_t nso_address, uintptr_t data_arena) {
SafetyCheck();
Hooks().Initialize(mapped_nso, nso_address, cave, cave_size, data_arena);
g_pcv_cave = cave;
g_pcv_cave_size = cave_size;
bool isMariko = (spl::GetSocType() == spl::SocType_Mariko);
if (isMariko) {
mariko::Patch(mapped_nso, nso_size);

View File

@@ -22,8 +22,15 @@
#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 {
@@ -136,7 +143,7 @@ namespace ams::ldr::hoc::pcv {
default:
customize_table = const_cast<cvb_entry_t *>(C.marikoGpuDvfsTableHiOPT);
break;
}
}
} else {
switch (C.eristaGpuUV) {
case 0:
@@ -151,7 +158,7 @@ namespace ams::ldr::hoc::pcv {
default:
customize_table = const_cast<cvb_entry_t *>(C.eristaGpuDvfsTable);
break;
}
}
}
size_t default_entry_count = GetDvfsTableEntryCount(default_table);
@@ -187,6 +194,7 @@ namespace ams::ldr::hoc::pcv {
}
++entry;
}
if (C.commonGpuVoltOffset && !(isMariko ? C.marikoGpuUV : C.eristaGpuUV)) {
cvb_entry_t *entry = static_cast<cvb_entry_t *>(gpu_cvb_table_head);
for (size_t i = 0; i < customize_entry_count; ++i) {
@@ -201,7 +209,11 @@ namespace ams::ldr::hoc::pcv {
Result MemFreqPllmLimit(u32 *ptr);
Result MemVoltHandler(u32 *ptr); // Used for Erista MEM Vdd2 + EMC Vddq or Mariko MEM Vdd2
/* Extra pcv .bss */
constexpr size_t HocPcvScratchSize = 0x2000;
constexpr size_t HocBusFreqBufOffset = 0x1000; /* start of the SOC bus region */
void SafetyCheck();
void Patch(uintptr_t mapped_nso, size_t nso_size);
void Patch(uintptr_t mapped_nso, size_t nso_size, uintptr_t cave, size_t cave_size, uintptr_t nso_address, uintptr_t data_arena);
}

View File

@@ -44,6 +44,15 @@ namespace ams::ldr::hoc::pcv {
return ins & ((1 << 5) - 1);
};
/* Rn (bits 9:5) and Rm (bits 20:16) to get registers. */
inline auto AsmGetRn = [](u32 ins) -> u32 { return (ins >> 5) & 0x1Fu; };
inline auto AsmGetRm = [](u32 ins) -> u32 { return (ins >> 16) & 0x1Fu; };
/* Add (shifted register), 64-bit: sf=1 op=0 S=0 01011 shift(2) 0 Rm imm6 Rn Rd. */
inline auto AsmIsAddShiftedReg64 = [](u32 ins) {
return (ins & 0xFF200000u) == 0x8B000000u;
};
inline auto asm_set_rd = [](u32 ins, u8 rd) {
return (ins & 0xFFFFFFE0) | (rd & 0x1F);
};
@@ -94,6 +103,16 @@ namespace ams::ldr::hoc::pcv {
return static_cast<uintptr_t>(static_cast<s64>(pc) + imm);
};
/* adrp Rd, target */
inline auto AsmMakeAdrp = [](uintptr_t pc, uintptr_t target, u32 rd) -> u32 {
const s64 delta = static_cast<s64>(target & ~static_cast<uintptr_t>(0xFFF))
- static_cast<s64>(pc & ~static_cast<uintptr_t>(0xFFF));
const u32 imm = static_cast<u32>((delta >> 12) & 0x1FFFFF); /* 21-bit page */
const u32 immlo = imm & 0x3;
const u32 immhi = (imm >> 2) & 0x7FFFF;
return 0x90000000u | (immlo << 29) | (immhi << 5) | (rd & 0x1Fu);
};
inline auto AsmSetAdrTarget = [](u32 ins, uintptr_t pc, uintptr_t target) -> u32 {
const s64 delta = static_cast<s64>(target) - static_cast<s64>(pc);
const u32 immlo = static_cast<u32>(delta & 0x3);
@@ -101,6 +120,167 @@ namespace ams::ldr::hoc::pcv {
return (ins & ~((0x3u << 29) | (0x7FFFFu << 5))) | (immlo << 29) | (immhi << 5);
};
/* adrp: bit31=1, bits 28:24 = 10000. */
inline auto AsmIsAdrp = [](u32 ins) -> bool {
return (ins & 0x9F000000u) == 0x90000000u;
};
inline auto AsmAdrpPageOffset = [](u32 ins) -> s64 {
s64 imm = static_cast<s64>((((ins >> 5) & 0x7FFFFu) << 2) | ((ins >> 29) & 0x3u));
imm = (imm << 43) >> 43; /* sign-extend the 21-bit immediate */
return imm << 12;
};
/* add (immediate), 64-bit: sf=1 op=0 S=0 100010 sh imm12 Rn Rd. */
inline auto AsmIsAddImm64 = [](u32 ins) -> bool {
return (ins & 0xFF800000u) == 0x91000000u;
};
inline auto AsmGetImm12 = [](u32 ins) -> u32 {
return (ins >> 10) & 0xFFFu;
};
inline auto AsmMakeAddImm64 = [](u32 rd, u32 rn, u32 imm12) -> u32 {
return 0x91000000u | ((imm12 & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rd & 0x1Fu);
};
/* movz Wd,#imm16 (no shift). */
inline auto AsmMakeMovzW = [](u32 rd, u16 imm16) -> u32 {
return 0x52800000u | (static_cast<u32>(imm16) << 5) | (rd & 0x1Fu);
};
/* mov Xd,Xm == orr Xd,XZR,Xm. */
inline auto AsmMakeMovReg = [](u32 rd, u32 rm) -> u32 {
return 0xAA0003E0u | ((rm & 0x1Fu) << 16) | (rd & 0x1Fu);
};
/* ldr Xt,[Xn,#byteOff] */
inline auto AsmMakeLdrImm64 = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0xF9400000u | (((byteOff / 8u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* b <target> (PC-relative, +-128MB). */
inline auto AsmMakeB = [](uintptr_t pc, uintptr_t target) -> u32 {
const s64 off = (static_cast<s64>(target) - static_cast<s64>(pc)) >> 2;
return 0x14000000u | (static_cast<u32>(off) & 0x03FFFFFFu);
};
/* b.<cond> <target> (cond: LO/CC=0x3, LE=0xD, NE=0x1, ...). */
inline auto AsmMakeBCond = [](uintptr_t pc, uintptr_t target, u32 cond) -> u32 {
const s64 off = (static_cast<s64>(target) - static_cast<s64>(pc)) >> 2;
return 0x54000000u | ((static_cast<u32>(off) & 0x7FFFFu) << 5) | (cond & 0xFu);
};
/* sub Xd,Xn,#imm12 (shift 0). */
inline auto AsmMakeSubImm64 = [](u32 rd, u32 rn, u32 imm12) -> u32 {
return 0xD1000000u | ((imm12 & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rd & 0x1Fu);
};
/* cmp Wn,#imm12 == subs WZR,Wn,#imm12. */
inline auto AsmMakeCmpImm32 = [](u32 rn, u32 imm12) -> u32 {
return 0x7100001Fu | ((imm12 & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5);
};
/* str Wt,[Xn,#byteOff] (32-bit, unsigned scaled by 4). */
inline auto AsmMakeStrImm32 = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0xB9000000u | (((byteOff / 4u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* ldr Wt,[Xn,#byteOff] (32-bit, unsigned scaled by 4). */
inline auto AsmMakeLdrImm32 = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0xB9400000u | (((byteOff / 4u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* add Xd,Xn,Xm,LSL #shift (64-bit shifted register, shift 0-63). */
inline auto AsmMakeAddShiftedReg64 = [](u32 rd, u32 rn, u32 rm, u32 shift) -> u32 {
return 0x8B000000u | ((rm & 0x1Fu) << 16) | ((shift & 0x3Fu) << 10) | ((rn & 0x1Fu) << 5) | (rd & 0x1Fu);
};
/* stp Xt1,Xt2,[Xn,#imm] (signed offset, scaled by 8). */
inline auto AsmMakeStpImm64 = [](u32 rt1, u32 rt2, u32 rn, s32 imm) -> u32 {
return 0xA9000000u | ((static_cast<u32>(imm / 8) & 0x7Fu) << 15) | ((rt2 & 0x1Fu) << 10) | ((rn & 0x1Fu) << 5) | (rt1 & 0x1Fu);
};
/* stp Qt1,Qt2,[Xn,#imm] (128-bit SIMD, signed offset scaled by 16). */
inline auto AsmMakeStpqImm = [](u32 qt1, u32 qt2, u32 rn, s32 imm) -> u32 {
return 0xAD000000u | ((static_cast<u32>(imm / 16) & 0x7Fu) << 15) | ((qt2 & 0x1Fu) << 10) | ((rn & 0x1Fu) << 5) | (qt1 & 0x1Fu);
};
/* str Xt,[Xn,#byteOff] (unsigned scaled by 8). */
inline auto AsmMakeStrImm64 = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0xF9000000u | (((byteOff / 8u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* movn Wd,#imm16 (no shift): loads ~imm16, e.g. movn Wd,#0x37 == -56. */
inline auto AsmMakeMovnW = [](u32 rd, u16 imm16) -> u32 {
return 0x12800000u | (static_cast<u32>(imm16) << 5) | (rd & 0x1Fu);
};
/* bl <target> (PC-relative, +-128MB). */
inline auto AsmMakeBl = [](uintptr_t pc, uintptr_t target) -> u32 {
const s64 off = (static_cast<s64>(target) - static_cast<s64>(pc)) >> 2;
return 0x94000000u | (static_cast<u32>(off) & 0x03FFFFFFu);
};
/* svc #imm16. */
inline auto AsmMakeSvc = [](u16 imm16) -> u32 {
return 0xD4000001u | (static_cast<u32>(imm16) << 5);
};
constexpr u32 RetIns = 0xD65F03C0u; /* ret (x30) */
/* ldrb Wt,[Xn,#imm] (unsigned byte, scale 1). */
inline auto AsmMakeLdrbImm = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0x39400000u | ((byteOff & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* cbz Wt,<target>. */
inline auto AsmMakeCbz = [](uintptr_t pc, uintptr_t target, u32 rt) -> u32 {
const s64 off = (static_cast<s64>(target) - static_cast<s64>(pc)) >> 2;
return 0x34000000u | ((static_cast<u32>(off) & 0x7FFFFu) << 5) | (rt & 0x1Fu);
};
/* strb Wt,[Xn,#imm] (unsigned byte, scale 1). */
inline auto AsmMakeStrbImm = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0x39000000u | ((byteOff & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* mov Xd,X<rm> == orr Xd,XZR,X<rm> (matches any Rd). */
inline auto AsmIsMovReg = [](u32 ins, u32 rm) -> bool {
return (ins & 0xFFFFFFE0u) == (0xAA0003E0u | ((rm & 0x1Fu) << 16));
};
/* add Xd,sp,#imm12 (shift 0). */
inline auto AsmIsAddSpImm = [](u32 ins) -> bool {
return (ins & 0xFFC003E0u) == 0x910003E0u;
};
/* sub Xd,x29,#imm12 (shift 0). */
inline auto AsmIsSubX29Imm = [](u32 ins) -> bool {
return (ins & 0xFFC003E0u) == 0xD10003A0u;
};
inline auto AsmIsB = [](u32 ins) -> bool { return (ins & 0xFC000000u) == 0x14000000u; }; /* b */
inline auto AsmIsBl = [](u32 ins) -> bool { return (ins & 0xFC000000u) == 0x94000000u; }; /* bl */
inline auto AsmIsBCond = [](u32 ins) -> bool { return (ins & 0xFF000010u) == 0x54000000u; }; /* b.c */
/* ldr/str Xt,[Xn,#imm] (64-bit, unsigned scaled offset). */
inline auto AsmIsLdrImm64 = [](u32 ins) -> bool { return (ins & 0xFFC00000u) == 0xF9400000u; };
inline auto AsmIsStrImm64 = [](u32 ins) -> bool { return (ins & 0xFFC00000u) == 0xF9000000u; };
inline auto AsmGetLdStImm64Off = [](u32 ins) -> u32 { return ((ins >> 10) & 0xFFFu) * 8u; };
/* Byte target address of a b/bl at pc. */
inline auto AsmBranchTarget = [](u32 ins, uintptr_t pc) -> uintptr_t {
s64 off = static_cast<s64>((ins & 0x03FFFFFFu) << 2);
off = (off << 36) >> 36; /* sign-extend the 28-bit branch offset */
return static_cast<uintptr_t>(static_cast<s64>(pc) + off);
};
/* Rewrite a scaled immediate-offset load/store (`op Wt,[Xn,#imm]`) into its register-offset form */
inline auto AsmSetLdStRegOffset = [](u32 ldstImm, u32 rm) -> u32 {
return (ldstImm & 0xC0C003FFu) | 0x38207800u | ((rm & 0x1Fu) << 16);
};
inline auto AsmIsLdpX = [](u32 ins) {
return (ins & 0xFE400000u) == 0xA8400000u;
};
@@ -114,7 +294,6 @@ namespace ams::ldr::hoc::pcv {
bool secondMatch = (ins2 & StpRegsImmMask) == (cmp2 & StpRegsImmMask);
constexpr u32 MovMask = ~((1u << 5) - 1u);
bool thirdMatch = (ins3 & MovMask) == (cmp3 & MovMask);
@@ -188,4 +367,25 @@ namespace ams::ldr::hoc::pcv {
return (ins1 & ClearImm19) == (ins2 & ClearImm19);
};
inline bool AsmIsFramePush(u32 ins) {
constexpr u32 FramePushMask = 0xFFC07FFF;
constexpr u32 FramePushValue = 0xA9807BFD;
return (ins & FramePushMask) == FramePushValue;
}
inline u32 *FindFnPrologue(u32 *ptr, u32 margin, u32 *nsoStart) {
for (u32 i = 0; i <= margin; ++i) {
u32 *candidate = ptr - i;
if (candidate < nsoStart) {
break;
}
if (AsmIsFramePush(*candidate)) {
return candidate;
}
}
return nullptr;
}
}

View File

@@ -173,6 +173,19 @@ namespace ams::ldr::hoc::pcv {
constexpr size_t DvfsTableEntryCount = 32;
constexpr size_t DvfsTableEntryLimit = DvfsTableEntryCount - 1;
// EMC-only limit.
constexpr size_t EmcDvfsTableEntryCount = 64;
constexpr size_t EmcDvfsTableEntryLimit = EmcDvfsTableEntryCount - 1;
// The pcv SoC-voltage DVB table is a fixed 32-entry region. (it doesn't need to be larger)
constexpr size_t DvbTableCapacity = 32;
// extra .bss location, 0 until Patch() runs.
inline uintptr_t g_pcv_scratch = 0;
inline uintptr_t g_pcv_cave = 0;
inline size_t g_pcv_cave_size = 0;
template<typename T>
size_t GetDvfsTableEntryCount(T *table_head) {
using NT = std::remove_const_t<std::remove_volatile_t<T>>;

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <shift>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);
}

View File

@@ -0,0 +1,367 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../oc_common.hpp"
#define HOOK_PAYLOAD_FN __attribute__((section("hoc_hookpayload"), used, noinline, visibility("hidden")))
/* Inline asm because fuck compilers: GCC refuses to put a variable and a function in the same section. */
/* It wants alloc+write for one and alloc+exec for the other. */
/* You are supposed to be able to override that by writing the flags into the section name yourself, */
/* like section("hoc_hookpayload,\"ax\",%progbits"), instead of letting GCC pick them. */
/* But GCC takes that whole string as the name and never reads the flags out of it, so it appends the */
/* ones it wanted anyway: the .section directive it emits ends up carrying two sets of flags, and the */
/* assembler rejects it. The failure lands in the generated assembly, not in anything we wrote. */
/* Therefore we must use inline assembly, which reaches the assembler exactly as written. */
#define DEFINE_HOOK_PAYLOAD_PTR(type, name) \
asm(".section hoc_hookpayload,\"ax\",%progbits\n" \
".balign 8\n" \
".global " #name "\n" \
".hidden " #name "\n" \
#name ": .zero 8\n" \
".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; \
__asm__("adr %0, " #name : "=r"(_hoc_pp)); \
return *_hoc_pp; \
}())
extern "C" const u8 __start_hoc_hookpayload[];
extern "C" const u8 __stop_hoc_hookpayload[];
namespace ams::ldr::hoc::pcv {
constexpr size_t HookPageSize = 0x1000;
constexpr size_t PcvDataArenaSize = 0x1000;
inline s64 SignExtend(u64 value, int bits) {
const int shift = 64 - bits;
return static_cast<s64>(value << shift) >> shift;
}
inline u32 EncodeRelBranch(u32 opc, uintptr_t site_va, uintptr_t target_va) {
const s64 delta = static_cast<s64>(target_va) - static_cast<s64>(site_va);
AMS_ABORT_UNLESS((delta & 0x3) == 0);
AMS_ABORT_UNLESS(delta >= -0x08000000 && delta <= 0x07FFFFFC);
return opc | (static_cast<u32>(delta >> 2) & 0x03FFFFFFu);
}
inline u32 EncodeB(uintptr_t site_va, uintptr_t target_va) { return EncodeRelBranch(0x14000000u, site_va, target_va); }
inline u32 EncodeBL(uintptr_t site_va, uintptr_t target_va) { return EncodeRelBranch(0x94000000u, site_va, target_va); }
inline u32 EncodePairSp(bool load, u32 rt1, u32 rt2, s32 imm) {
const u32 base = load ? 0xA9400000u : 0xA9000000u;
const u32 imm7 = static_cast<u32>((imm / 8) & 0x7F);
return base | (imm7 << 15) | (rt2 << 10) | (31u << 5) | rt1;
}
inline u32 EncodeSpAdjust(bool sub, u32 imm12) {
const u32 base = sub ? 0xD1000000u : 0x91000000u;
return base | ((imm12 & 0xFFFu) << 10) | (31u << 5) | 31u;
}
inline Result RelocateInstruction(u32 insn, uintptr_t old_site_va, uintptr_t new_site_va, u32 *out) {
const u32 top6 = insn & 0xFC000000u;
if (top6 == 0x14000000u || top6 == 0x94000000u) {
const s64 old_imm = SignExtend(static_cast<u64>(insn & 0x03FFFFFFu) << 2, 28);
const uintptr_t target = old_site_va + old_imm;
*out = EncodeRelBranch(top6, new_site_va, target);
R_SUCCEED();
}
R_UNLESS((insn & 0xFF000010u) != 0x54000000u, ldr::ResultHookRelocationUnsupported()); /* b.cond */
R_UNLESS((insn & 0x7E000000u) != 0x34000000u, ldr::ResultHookRelocationUnsupported()); /* cbz/cbnz */
R_UNLESS((insn & 0x7E000000u) != 0x36000000u, ldr::ResultHookRelocationUnsupported()); /* tbz/tbnz */
R_UNLESS((insn & 0x1F000000u) != 0x10000000u, ldr::ResultHookRelocationUnsupported()); /* adr/adrp */
R_UNLESS((insn & 0x3B000000u) != 0x18000000u, ldr::ResultHookRelocationUnsupported()); /* ldr literal */
*out = insn;
R_SUCCEED();
}
class HookContext {
private:
uintptr_t m_map_base = 0; /* loader-side base of pcv's NSO mapping. */
uintptr_t m_va_base = 0; /* pcv-side base of the same memory. */
uintptr_t m_cave = 0; /* loader-side cave base. (0 if unavailable) */
size_t m_cave_size = 0;
uintptr_t m_payload = 0; /* loader-side base of the payload copy. */
size_t m_payload_sz = 0;
size_t m_used = 0;
uintptr_t m_data = 0; /* loader-side data arena base, 0 if none. */
size_t m_data_used = 0;
public:
constexpr HookContext() = default;
void Initialize(uintptr_t map_base, uintptr_t va_base, uintptr_t cave, size_t cave_size, uintptr_t data) {
m_map_base = map_base;
m_va_base = va_base;
m_cave = cave;
m_cave_size = cave_size;
m_payload = 0;
m_payload_sz = 0;
m_used = 0;
m_data = data;
m_data_used = 0;
}
bool IsEnabled() const { return m_cave != 0 && m_cave_size != 0; }
Result CheckEnabled() const {
R_UNLESS(this->IsEnabled(), ldr::ResultHookUnavailable());
R_SUCCEED();
}
size_t CaveSize() const { return m_cave_size; }
size_t CaveUsed() const { return m_used; }
size_t CaveFree() const { return m_cave_size > m_used ? m_cave_size - m_used : 0; }
size_t DataSize() const { return m_data != 0 ? PcvDataArenaSize : 0; }
size_t DataUsed() const { return m_data_used; }
size_t DataFree() const { return this->DataSize() - m_data_used; }
/* Convert loader mapped address to pcv-side address. */
uintptr_t ToVa(const void *loader_ptr) const {
return m_va_base + (reinterpret_cast<uintptr_t>(loader_ptr) - m_map_base);
}
uintptr_t CaveVa() const { return ToVa(reinterpret_cast<const void *>(m_cave)); }
Result CopyPayload() {
R_TRY(this->CheckEnabled());
const size_t size = static_cast<size_t>(__stop_hoc_hookpayload - __start_hoc_hookpayload);
/* Zero length: Linker garbage collected the payload .(happens when nothing references it) */
/* __start_ and _stop_ don't prevent this. */
/* Copying here would succeed at first but fail later by jumping to empty memory. */
R_UNLESS(size != 0, ldr::ResultUninitializedPatcher());
R_UNLESS(size <= m_cave_size, ldr::ResultHookPayloadTooLarge());
m_payload = m_cave;
m_payload_sz = size;
std::memcpy(reinterpret_cast<void *>(m_payload), __start_hoc_hookpayload, size);
m_used = util::AlignUp(size, sizeof(u32));
R_SUCCEED();
}
/* pcv-side address of a payload symbol's copy. */
uintptr_t PayloadVa(const void *loader_sym) const {
const uintptr_t offset = reinterpret_cast<uintptr_t>(loader_sym) - reinterpret_cast<uintptr_t>(__start_hoc_hookpayload);
return this->ToVa(reinterpret_cast<void *>(m_payload + offset));
}
/* Loader-side, writable pointer to a payload variable's copy in the cave. */
template<typename T>
T *PayloadCopyOf(T &loader_sym) const {
const uintptr_t offset = reinterpret_cast<uintptr_t>(std::addressof(loader_sym)) - reinterpret_cast<uintptr_t>(__start_hoc_hookpayload);
return reinterpret_cast<T *>(m_payload + offset);
}
/* Reserves space in the writable data arena past pcv's .bss. */
/* Returns a zeroed, loader-side pointer. */
/* The data arena is too far from the cave to be addressed directly by symbol, so we must store a pointer to it in the cave section. */
template<typename T>
T *DataAlloc() {
const size_t size = util::AlignUp(sizeof(T), alignof(T) > 8 ? alignof(T) : 8);
if (m_data == 0 || m_data_used + size > PcvDataArenaSize) {
return nullptr;
}
T *p = reinterpret_cast<T *>(m_data + m_data_used);
m_data_used += size;
std::memset(p, 0, sizeof(T));
return p;
}
template<typename T>
T *BindData(T *&loader_sym) {
T *block = this->DataAlloc<T>();
if (block != nullptr) {
*this->PayloadCopyOf(loader_sym) = reinterpret_cast<T *>(this->ToVa(block));
}
return block;
}
/* Replaces the function entirely starting at function prologue. */
/* Preserves the function arguments. */
Result InstallImpl(u32 *site, const void *fn, uintptr_t *out_orig = nullptr) {
R_TRY(this->CheckEnabled());
R_UNLESS(site != nullptr, ldr::ResultHookSiteInvalid());
R_UNLESS(m_payload != 0, ldr::ResultUninitializedPatcher());
R_TRY(this->ValidatePayloadFn(fn));
if (out_orig != nullptr) {
u32 *tramp = this->AllocCode(2);
R_UNLESS(tramp != nullptr, ldr::ResultHookArenaOutOfMemory());
u32 relocated;
R_TRY(RelocateInstruction(site[0], this->ToVa(site), this->ToVa(&tramp[0]), std::addressof(relocated)));
tramp[0] = relocated;
tramp[1] = EncodeB(this->ToVa(&tramp[1]), this->ToVa(site) + sizeof(u32));
*out_orig = this->ToVa(tramp);
}
site[0] = EncodeB(this->ToVa(site), this->PayloadVa(fn));
R_SUCCEED();
}
/* Takes the same arguments as the hooked function, does not replace. */
Result InstallIntercept(u32 *site, const void *fn) {
R_TRY(this->CheckEnabled());
R_UNLESS(site != nullptr, ldr::ResultHookSiteInvalid());
R_UNLESS(m_payload != 0, ldr::ResultUninitializedPatcher());
R_TRY(this->ValidatePayloadFn(fn));
/* x0-x7: arguments */
/* x8: result pointer */
/* x18: platform register */
/* x30: return address */
/* x29: keeps pairs clean */
/* x9-x17: scratch */
constexpr u32 FrameSize = 0x60;
constexpr u32 Pairs[][2] = { {0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 18}, {29, 30} };
constexpr u32 PairCount = sizeof(Pairs) / sizeof(Pairs[0]);
constexpr u32 StubWords = 1 + PairCount + 1 + PairCount + 1 + 1 + 1;
u32 *stub = this->AllocCode(StubWords);
R_UNLESS(stub != nullptr, ldr::ResultHookArenaOutOfMemory());
u32 i = 0;
stub[i++] = EncodeSpAdjust(true, FrameSize);
for (u32 p = 0; p < PairCount; ++p) {
stub[i++] = EncodePairSp(false, Pairs[p][0], Pairs[p][1], static_cast<s32>(p * 16));
}
stub[i] = EncodeBL(this->ToVa(&stub[i]), this->PayloadVa(fn));
++i;
for (u32 p = 0; p < PairCount; ++p) {
stub[i++] = EncodePairSp(true, Pairs[p][0], Pairs[p][1], static_cast<s32>(p * 16));
}
stub[i++] = EncodeSpAdjust(false, FrameSize);
u32 relocated;
R_TRY(RelocateInstruction(site[0], this->ToVa(site), this->ToVa(&stub[i]), std::addressof(relocated)));
stub[i] = relocated;
++i;
stub[i] = EncodeB(this->ToVa(&stub[i]), this->ToVa(site) + sizeof(u32));
++i;
AMS_ABORT_UNLESS(i == StubWords);
site[0] = EncodeB(this->ToVa(site), this->ToVa(stub));
R_SUCCEED();
}
/* Checks entry to first ret that everything still points to valid data. */
Result ValidatePayloadFn(const void *fn) const {
constexpr u32 MaxInstructions = 512;
constexpr u32 RetInsn = 0xD65F03C0u;
const uintptr_t lo = m_payload;
const uintptr_t hi = m_payload + m_payload_sz;
const u32 *insns = reinterpret_cast<const u32 *>(fn);
const uintptr_t base = m_payload + (reinterpret_cast<uintptr_t>(fn) - reinterpret_cast<uintptr_t>(__start_hoc_hookpayload));
auto check = [&](uintptr_t target) -> Result {
if (target < lo || target >= hi) {
R_THROW(ldr::ResultHookPayloadEscapes());
}
R_SUCCEED();
};
for (u32 i = 0; i < MaxInstructions; ++i) {
const u32 insn = insns[i];
const uintptr_t site = base + i * sizeof(u32);
if (insn == RetInsn) {
R_SUCCEED();
}
const u32 top6 = insn & 0xFC000000u;
if (top6 == 0x14000000u || top6 == 0x94000000u) { /* b / bl */
const uintptr_t target = site + SignExtend(static_cast<u64>(insn & 0x03FFFFFFu) << 2, 28);
R_TRY(check(target));
} else if ((insn & 0xFF000010u) == 0x54000000u || /* b.cond */
(insn & 0x7E000000u) == 0x34000000u || /* cbz / cbnz */
(insn & 0x3B000000u) == 0x18000000u) { /* ldr literal */
const uintptr_t target = site + SignExtend(static_cast<u64>((insn >> 5) & 0x7FFFFu) << 2, 21);
R_TRY(check(target));
} else if ((insn & 0x7E000000u) == 0x36000000u) { /* tbz / tbnz */
const uintptr_t target = site + SignExtend(static_cast<u64>((insn >> 5) & 0x3FFFu) << 2, 16);
R_TRY(check(target));
} else if ((insn & 0x9F000000u) == 0x10000000u) { /* adr */
const u64 imm = (static_cast<u64>((insn >> 5) & 0x7FFFFu) << 2) | ((insn >> 29) & 0x3u);
const uintptr_t target = site + SignExtend(imm, 21);
R_TRY(check(target));
} else if ((insn & 0x9F000000u) == 0x90000000u) { /* adrp */
/* ADRP is page-relative and the cave is at an arbitrary offset, any adrp would point to potential garbage. */
R_THROW(ldr::ResultHookPayloadEscapes());
}
}
/* No return found. */
R_THROW(ldr::ResultHookPayloadEscapes());
}
private:
u32 *AllocCode(size_t words) {
const size_t bytes = words * sizeof(u32);
if (!this->IsEnabled() || m_used + bytes > m_cave_size) {
return nullptr;
}
u32 *p = reinterpret_cast<u32 *>(m_cave + m_used);
m_used += bytes;
return p;
}
};
inline HookContext &Hooks() {
static HookContext s_context;
return s_context;
}
}
/* Hook custom impl. Original becomes unreachable. */
/* Starts at function entry. Preserves arguments. */
#define INSTALL_IMPL_HOOK(site, fn) \
(::ams::ldr::hoc::pcv::Hooks().InstallImpl((site), reinterpret_cast<const void *>(&(fn))))
/* Maintains original function. */
/* Starts at function entry. Preserves arguments. */
#define INSTALL_IMPL_HOOK_ORIG(site, fn, out_orig) \
(::ams::ldr::hoc::pcv::Hooks().InstallImpl((site), reinterpret_cast<const void *>(&(fn)), (out_orig)))
/* Takes the same arguments as the hooked function, does not replace it and cannot change what it does */
/* but it can be placed anywhere, not just at a function entry. */
#define INSTALL_INTERC_HOOK(site, fn) \
(::ams::ldr::hoc::pcv::Hooks().InstallIntercept((site), reinterpret_cast<const void *>(&(fn))))

View File

@@ -0,0 +1,136 @@
#---------------------------------------------------------------------------------
# pull in common stratosphere sysmodule configuration
#---------------------------------------------------------------------------------
THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST)))
CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE)))
include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk
ifneq ($(strip $(HOC_UART_LOG)),)
export CFLAGS += -DHOC_UART_LOG=$(HOC_UART_LOG)
export CXXFLAGS += -DHOC_UART_LOG=$(HOC_UART_LOG)
endif
ATMOSPHERE_SYSTEM_MODULE_TARGETS := kip
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(__RECURSIVE__),1)
#---------------------------------------------------------------------------------
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
CFILES := $(call FIND_SOURCE_FILES,$(SOURCES),c)
CPPFILES := $(call FIND_SOURCE_FILES,$(SOURCES),cpp)
SFILES := $(call FIND_SOURCE_FILES,$(SOURCES),s)
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES := $(addsuffix .o,$(BINFILES)) \
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
$(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR))
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
ifeq ($(strip $(CONFIG_JSON)),)
jsons := $(wildcard *.json)
ifneq (,$(findstring $(TARGET).json,$(jsons)))
export APP_JSON := $(TOPDIR)/$(TARGET).json
else
ifneq (,$(findstring config.json,$(jsons)))
export APP_JSON := $(TOPDIR)/config.json
endif
endif
else
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
endif
.PHONY: clean all check_lib
#---------------------------------------------------------------------------------
all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a
@$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \
DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \
--no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \
-f $(THIS_MAKEFILE)
$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib
@$(SILENTCMD)echo "Checked library."
ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1)
check_lib:
else
check_lib:
@$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk
endif
$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR):
@[ -d $@ ] || mkdir -p $@
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR)
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target))
$(OUTPUT).kip : $(OUTPUT).elf
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
$(OUTPUT).nso : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a
%.npdm : %.npdm.json
@echo built ... $< $@
@npdmtool $< $@
@echo built ... $(notdir $@)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

View File

@@ -0,0 +1,9 @@
Thanks to NaGa for Status Monitor Pro! <br> <br>
**Horizon-OC-Monitor is deprecated.** If you want to compile it anyway, create a `lib` folder and clone Atmosphere-libs and libultrahand into it: <br> <br>
```bash
mkdir lib
git clone https://github.com/Atmosphere-NX/Atmosphere-libs lib/Atmosphere-libs
git clone https://github.com/ppkantorski/libultrahand lib/libultrahand
```

View File

@@ -1,7 +0,0 @@
[submodule "lib/Atmosphere-libs"]
path = lib/Atmosphere-libs
url = https://git.niklascfw.de/OmniNX/Atmosphere-Pro.git
branch = boot-storage
[submodule "lib/libultrahand"]
path = lib/libultrahand
url = https://github.com/ppkantorski/libultrahand

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1 +0,0 @@
Thanks to NaGa for Status Monitor Pro!

View File

@@ -82,9 +82,10 @@ typedef struct {
u8 custRev;
u16 kipVersion;
bool isKipLoaded;
bool rebootRequired;
// Reserved for future use
u8 reserved[0x35A];
u8 reserved[0x359];
} HocClkContext;
typedef struct
@@ -95,7 +96,7 @@ typedef struct
};
} HocClkTitleProfileList;
#define HOCCLK_FREQ_LIST_MAX 48
#define HOCCLK_FREQ_LIST_MAX 64
#define HOCCLK_GLOBAL_PROFILE_TID 0xA111111111111111

View File

@@ -39,7 +39,7 @@ include ${TOPDIR}/lib/libultrahand/ultrahand.mk
# version control constants
#---------------------------------------------------------------------------------
#TARGET_VERSION := $(shell git describe --dirty --always --tags)
APP_VERSION := 2.5.0 # ensure to set KIP_VERSION and CUST_REV in sysmodule Makefile when updating this
APP_VERSION := 3.0.0 # ensure to set KIP_VERSION and CUST_REV in sysmodule Makefile when updating this
TARGET_VERSION := $(APP_VERSION)
#---------------------------------------------------------------------------------

View File

@@ -211,5 +211,19 @@
"X: %u Y: %u": "X: %u Y: %u",
"%u.%u%u mV": "%u.%u%u mV",
"Compiling with minimal features": "Kompilieren mit minimalen Funktionen",
"THE BEER-WARE LICENSE": "DIE BIERWAREN-LIZENZ"
"THE BEER-WARE LICENSE": "DIE BIERWAREN-LIZENZ",
"Horizon OC\nKip opening failed": "Horizon OC\nKIP konnte nicht geöffnet werden",
"Horizon OC\nKip read failed": "Horizon OC\nKIP konnte nicht gelesen werden",
"Horizon OC\nOutdated kip detected!\nPlease update Horizon OC": "Horizon OC\nVeraltetes KIP erkannt!\nBitte Horizon OC aktualisieren",
"Horizon OC\nOutdated sysmodule detected!\nPlease update Horizon OC": "Horizon OC\nVeraltetes Sysmodule erkannt!\nBitte Horizon OC aktualisieren",
"Horizon OC\nKip write failed": "Horizon OC\nKIP-Schreiben fehlgeschlagen",
"Horizon OC\nKip config set failed": "Horizon OC\nKIP-Konfiguration fehlgeschlagen",
"Kip is not loaded!": "KIP ist nicht geladen!",
"Horizon OC\nKIP has been updated\nPlease reboot your console": "Horizon OC\nKIP wurde aktualisiert\nBitte starte die Konsole neu",
"Horizon OC has been installed": "Horizon OC wurde installiert",
"Horizon OC\nConfig Buffer Mismatch": "Horizon OC\nKonfigurationspuffer stimmt nicht überein",
"Horizon OC\nDeactivated frequency.\nReboot to apply.": "Horizon OC\nFrequenz deaktiviert.\nNeustart zum Anwenden.",
"Horizon OC\nSecmon read failed!\n This may be a hardware issue!": "Horizon OC\nSecmon-Lesen fehlgeschlagen!\n Dies kann ein Hardwareproblem sein!",
"Horizon OC\nFailed to write I2C\nwhile setting vddq": "Horizon OC\nI2C-Schreiben fehlgeschlagen\nbeim Setzen von VDDQ",
"Horizon OC\nKip version mismatch\nPlease reinstall Horizon OC": "Horizon OC\nKIP-Versionskonflikt\nBitte Horizon OC neu installieren"
}

View File

@@ -137,5 +137,19 @@
"or 1228MHz on HiOPT can cause ": "or 1228MHz on HiOPT can cause ",
"permanent damage to your Switch!": "permanent damage to your Switch!",
"921MHz without UV and 960MHz on": "921MHz without UV and 960MHz on",
"SLT or HiOPT can cause ": "SLT or HiOPT can cause "
"SLT or HiOPT can cause ": "SLT or HiOPT can cause ",
"Horizon OC\nKip opening failed": "Horizon OC\nKip opening failed",
"Horizon OC\nKip read failed": "Horizon OC\nKip read failed",
"Horizon OC\nOutdated kip detected!\nPlease update Horizon OC": "Horizon OC\nOutdated kip detected!\nPlease update Horizon OC",
"Horizon OC\nOutdated sysmodule detected!\nPlease update Horizon OC": "Horizon OC\nOutdated sysmodule detected!\nPlease update Horizon OC",
"Horizon OC\nKip write failed": "Horizon OC\nKip write failed",
"Horizon OC\nKip config set failed": "Horizon OC\nKip config set failed",
"Kip is not loaded!": "Kip is not loaded!",
"Horizon OC\nKIP has been updated\nPlease reboot your console": "Horizon OC\nKIP has been updated\nPlease reboot your console",
"Horizon OC has been installed": "Horizon OC has been installed",
"Horizon OC\nConfig Buffer Mismatch": "Horizon OC\nConfig Buffer Mismatch",
"Horizon OC\nDeactivated frequency.\nReboot to apply.": "Horizon OC\nDeactivated frequency.\nReboot to apply.",
"Horizon OC\nSecmon read failed!\n This may be a hardware issue!": "Horizon OC\nSecmon read failed!\n This may be a hardware issue!",
"Horizon OC\nFailed to write I2C\nwhile setting vddq": "Horizon OC\nFailed to write I2C\nwhile setting vddq",
"Horizon OC\nKip version mismatch\nPlease reinstall Horizon OC": "Horizon OC\nKip version mismatch\nPlease reinstall Horizon OC"
}

View File

@@ -211,5 +211,19 @@
"X: %u Y: %u": "X: %u Y: %u",
"%u.%u%u mV": "%u.%u%u mV",
"Compiling with minimal features": "Compilado con funciones mínimas",
"THE BEER-WARE LICENSE": "LICENCIA BEER-WARE"
"THE BEER-WARE LICENSE": "LICENCIA BEER-WARE",
"Horizon OC\nKip opening failed": "Horizon OC\nError al abrir el KIP",
"Horizon OC\nKip read failed": "Horizon OC\nError al leer el KIP",
"Horizon OC\nOutdated kip detected!\nPlease update Horizon OC": "Horizon OC\n¡KIP desactualizado detectado!\nActualiza Horizon OC",
"Horizon OC\nOutdated sysmodule detected!\nPlease update Horizon OC": "Horizon OC\n¡Sysmodule desactualizado detectado!\nActualiza Horizon OC",
"Horizon OC\nKip write failed": "Horizon OC\nError al escribir el KIP",
"Horizon OC\nKip config set failed": "Horizon OC\nError al configurar el KIP",
"Kip is not loaded!": "¡El KIP no está cargado!",
"Horizon OC\nKIP has been updated\nPlease reboot your console": "Horizon OC\nEl KIP se ha actualizado\nReinicia la consola",
"Horizon OC has been installed": "Horizon OC se ha instalado",
"Horizon OC\nConfig Buffer Mismatch": "Horizon OC\nDesajuste del búfer de configuración",
"Horizon OC\nDeactivated frequency.\nReboot to apply.": "Horizon OC\nFrecuencia desactivada.\nReinicia para aplicar.",
"Horizon OC\nSecmon read failed!\n This may be a hardware issue!": "Horizon OC\n¡Error al leer Secmon!\n ¡Puede ser un problema de hardware!",
"Horizon OC\nFailed to write I2C\nwhile setting vddq": "Horizon OC\nError al escribir I2C\nal configurar VDDQ",
"Horizon OC\nKip version mismatch\nPlease reinstall Horizon OC": "Horizon OC\nVersión de KIP no coincide\nReinstala Horizon OC"
}

Some files were not shown because too many files have changed in this diff Show More